From 7710d126eb99e8990039fccf088b1170f3830573 Mon Sep 17 00:00:00 2001 From: Wiktor Kwapisiewicz Date: Mon, 3 Oct 2022 11:51:41 +0200 Subject: openpgp: Add OpenSSL cryptographic backend. - Adds the backend behind `crypto-openssl` feature. - The backend is considered experimental for now. - Add CI configuration to run tests with the new backend. - See #333. --- .Makefile | 2 +- .gitlab-ci.yml | 14 + Cargo.lock | 25 +- openpgp/Cargo.toml | 8 + openpgp/README.md | 4 + openpgp/build.rs | 19 + openpgp/src/crypto/backend.rs | 5 + openpgp/src/crypto/backend/openssl.rs | 80 +++ openpgp/src/crypto/backend/openssl/aead.rs | 153 ++++++ openpgp/src/crypto/backend/openssl/asymmetric.rs | 642 +++++++++++++++++++++++ openpgp/src/crypto/backend/openssl/ecdh.rs | 123 +++++ openpgp/src/crypto/backend/openssl/hash.rs | 99 ++++ openpgp/src/crypto/backend/openssl/symmetric.rs | 166 ++++++ 13 files changed, 1334 insertions(+), 6 deletions(-) create mode 100644 openpgp/src/crypto/backend/openssl.rs create mode 100644 openpgp/src/crypto/backend/openssl/aead.rs create mode 100644 openpgp/src/crypto/backend/openssl/asymmetric.rs create mode 100644 openpgp/src/crypto/backend/openssl/ecdh.rs create mode 100644 openpgp/src/crypto/backend/openssl/hash.rs create mode 100644 openpgp/src/crypto/backend/openssl/symmetric.rs diff --git a/.Makefile b/.Makefile index 0377017b..d35f90b0 100644 --- a/.Makefile +++ b/.Makefile @@ -149,5 +149,5 @@ clean: .PHONY: codespell codespell: $(CODESPELL) $(CODESPELL_FLAGS) \ - -L "crate,ede,iff,mut,nd,te,uint,KeyServer,keyserver,Keyserver,keyservers,Keyservers,keypair,keypairs,KeyPair,fpr,dedup" \ + -L "crate,ede,iff,mut,nd,te,uint,KeyServer,keyserver,Keyserver,keyservers,Keyservers,keypair,keypairs,KeyPair,fpr,dedup,deriver" \ -S "*.bin,*.gpg,*.pgp,./.git,data,highlight.js,*/target,Makefile" diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b478e2ca..0387a36a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -120,6 +120,20 @@ bookworm-crypto-rust: CARGO_TARGET_DIR: /target CARGO_HOME: /cargo +bookworm-crypto-openssl: + tags: + - linux + stage: build + image: registry.gitlab.com/sequoia-pgp/build-docker-image/bookworm-prebuild:latest + dependencies: + - codespell + script: + - cargo run --manifest-path openpgp/Cargo.toml --no-default-features --features crypto-openssl,compression --example supported-algorithms + - cargo test --release --manifest-path openpgp/Cargo.toml --no-default-features --features crypto-openssl,compression + variables: + CARGO_TARGET_DIR: /target + CARGO_HOME: /cargo + benchmarks: stage: test image: registry.gitlab.com/sequoia-pgp/build-docker-image/bookworm:latest diff --git a/Cargo.lock b/Cargo.lock index 2bcf9c47..2cfb5bc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1971,18 +1971,30 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.38" +version = "0.10.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +checksum = "29d971fd5722fec23977260f6e81aa67d2f22cadbdc2aa049f1022d9a3be1566" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", + "openssl-macros", "openssl-sys", ] +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -1991,9 +2003,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.72" +version = "0.9.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +checksum = "5454462c0eced1e97f2ec09036abc8da362e66802f66fd20f86854d9d8cbcbc4" dependencies = [ "autocfg 1.1.0", "cc", @@ -2781,6 +2793,7 @@ dependencies = [ "ecdsa", "ed25519-dalek", "flate2", + "foreign-types-shared", "generic-array 0.14.5", "getrandom 0.2.6", "idea", @@ -2793,6 +2806,8 @@ dependencies = [ "memsec", "nettle", "num-bigint-dig", + "openssl", + "openssl-sys", "p256", "quickcheck", "rand 0.7.3", @@ -2938,7 +2953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31bf4e9fe5cd8cea8e0887e2e4eb1b4d736ff11b776c8537bf0912a4b381285" dependencies = [ "digest 0.9.0", - "generic-array 0.12.4", + "generic-array 0.14.5", ] [[package]] diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml index ba65f97a..22a77157 100644 --- a/openpgp/Cargo.toml +++ b/openpgp/Cargo.toml @@ -44,6 +44,13 @@ regex-syntax = "0.6" sha1collisiondetection = { version = "0.2.3", default-features = false, features = ["std"] } thiserror = "1.0.2" xxhash-rust = { version = "0.8", features = ["xxh3"] } +# At least 0.10.44 is needed due to many changes we contributed upstream +openssl = { version = ">= 0.10.44", optional = true } +# We need to directly depend on the sys crate so that the metadata produced +# in its build script is passed to sequoia-openpgp's build script +# see: https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key +openssl-sys = { version = ">= 0.9.79", optional = true } +foreign-types-shared = { version = "0.1.1", optional = true } # RustCrypto crates. aes = { version = "0.6.0", optional = true } @@ -101,6 +108,7 @@ crypto-rust = [ "rand_core", "rand_core/getrandom", "ecdsa" ] crypto-cng = ["eax", "winapi", "win-crypto-ng", "ed25519-dalek", "num-bigint-dig"] +crypto-openssl = ["openssl", "openssl-sys", "foreign-types-shared"] # Experimental and variable-time cryptographic backends opt-ins allow-experimental-crypto = [] diff --git a/openpgp/README.md b/openpgp/README.md index 00512b7a..2001a8bc 100644 --- a/openpgp/README.md +++ b/openpgp/README.md @@ -88,6 +88,10 @@ at compile time. Currently, these libraries are available: cannot offer the same security guarantees as more mature cryptographic libraries. + - The OpenSSL backend. To select this backend, use + `default-features = false`, and explicitly include the + `crypto-openssl` feature to enable it. + ### Experimental and variable-time cryptographic backends Some cryptographic backends are not yet considered mature enough for diff --git a/openpgp/build.rs b/openpgp/build.rs index aabf157d..9de8fb76 100644 --- a/openpgp/build.rs +++ b/openpgp/build.rs @@ -6,10 +6,23 @@ use std::process::exit; fn main() { crypto_backends_sanity_check(); + include_openssl_conf(); lalrpop::process_root().unwrap(); include_test_data().unwrap(); } +/// Optionally include configuration passed from openssl-sys build +/// script. This configuration is then exposed as a set of `osslconf` +/// parameters and is used by OpenSSL backend to enable or disable +/// algorithms available by the current environment. +fn include_openssl_conf() { + if let Ok(vars) = env::var("DEP_OPENSSL_CONF") { + for var in vars.split(',') { + println!("cargo:rustc-cfg=osslconf=\"{}\"", var); + } + } +} + /// Builds the index of the test data for use with the `::tests` /// module. fn include_test_data() -> io::Result<()> { @@ -68,6 +81,12 @@ fn crypto_backends_sanity_check() { production_ready: false, constant_time: false, }), + (cfg!(feature = "crypto-openssl"), + Backend { + name: "OpenSSL", + production_ready: true, + constant_time: true, + }), ].into_iter().filter_map(|(selected, backend)| { if selected { Some(backend) } else { None } }).collect::>(); diff --git a/openpgp/src/crypto/backend.rs b/openpgp/src/crypto/backend.rs index c0fd883f..780b0bb1 100644 --- a/openpgp/src/crypto/backend.rs +++ b/openpgp/src/crypto/backend.rs @@ -17,3 +17,8 @@ pub use self::rust::*; mod cng; #[cfg(feature = "crypto-cng")] pub use self::cng::*; + +#[cfg(feature = "crypto-openssl")] +mod openssl; +#[cfg(feature = "crypto-openssl")] +pub use self::openssl::*; diff --git a/openpgp/src/crypto/backend/openssl.rs b/openpgp/src/crypto/backend/openssl.rs new file mode 100644 index 00000000..4e610bfe --- /dev/null +++ b/openpgp/src/crypto/backend/openssl.rs @@ -0,0 +1,80 @@ +//! Implementation of Sequoia crypto API using the OpenSSL cryptographic library. +use std::convert::TryFrom; + +use crate::types::*; + +pub mod aead; +pub mod asymmetric; +pub mod ecdh; +pub mod hash; +pub mod symmetric; + +/// Returns a short, human-readable description of the backend. +pub fn backend() -> String { + "OpenSSL".to_string() +} + +/// Fills the given buffer with random data. +pub fn random(buf: &mut [u8]) { + // random is expected to always work or panic on wrong data. + // This is similar to what other backends do like CNG or Rust + // see: https://docs.rs/rand/latest/rand/trait.RngCore.html#tymethod.fill_bytes + openssl::rand::rand_bytes(buf).expect("rand_bytes to work"); +} + +impl PublicKeyAlgorithm { + pub(crate) fn is_supported_by_backend(&self) -> bool { + use PublicKeyAlgorithm::*; + #[allow(deprecated)] + match self { + RSAEncryptSign | RSAEncrypt | RSASign => true, + DSA => true, + ECDH | ECDSA | EdDSA => true, + _ => false, + } + } +} + +impl Curve { + pub(crate) fn is_supported_by_backend(&self) -> bool { + if matches!(self, Curve::Ed25519 | Curve::Cv25519) { + // 25519-based algorithms are special-cased and supported + true + } else { + // the rest of EC algorithms are supported via the same + // codepath + openssl::nid::Nid::try_from(self).is_ok() + } + } +} + +impl AEADAlgorithm { + /// Returns the best AEAD mode supported by the backend. + /// + /// This SHOULD return OCB, which is the mandatory-to-implement + /// algorithm and the most performing one, but fall back to any + /// supported algorithm. + pub(crate) const fn const_default() -> AEADAlgorithm { + AEADAlgorithm::OCB + } + + pub(crate) fn is_supported_by_backend(&self) -> bool { + *self == AEADAlgorithm::OCB + } + + #[cfg(test)] + pub(crate) fn supports_symmetric_algo(&self, algo: &SymmetricAlgorithm) -> bool { + match &self { + AEADAlgorithm::OCB => + match algo { + // OpenSSL supports OCB only with AES + // see: https://wiki.openssl.org/index.php/OCB + SymmetricAlgorithm::AES128 | + SymmetricAlgorithm::AES192 | + SymmetricAlgorithm::AES256 => true, + _ => false, + }, + _ => false + } + } +} diff --git a/openpgp/src/crypto/backend/openssl/aead.rs b/openpgp/src/crypto/backend/openssl/aead.rs new file mode 100644 index 00000000..9139aa5a --- /dev/null +++ b/openpgp/src/crypto/backend/openssl/aead.rs @@ -0,0 +1,153 @@ +//! Implementation of AEAD using OpenSSL cryptographic library. + +use crate::{Error, Result}; + +use crate::crypto::aead::{Aead, CipherOp}; +use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; + +use openssl::cipher::Cipher; +use openssl::cipher_ctx::{CipherCtx, CipherCtxRef}; +use openssl::error::ErrorStack; +use openssl_sys::c_int; + +struct OpenSslContextEncrypt { + ctx: CipherCtx, + // The last chunk to be processed does not call `encrypt` thus + // leaves the crypter in non-finalized state. This makes the + // `get_tag` function of the crypter panic when calling `digest`. + // If this flag is set to `false` it means the crypter needs to be + // finalized. + finalized: bool, +} + +fn cvt(r: c_int) -> std::result::Result { + if r <= 0 { + Err(ErrorStack::get()) + } else { + Ok(r) + } +} + +// Uses the same logic as `CipherCtxRef::cipher_update` to calculate the +// required size of the output buffer. This fix will be upstreamed. +fn safe_cipher_final(ctx: &CipherCtxRef, output: &mut [u8]) -> Result { + let min_output_size = ctx.minimal_output_size(0); + assert!( + output.len() >= min_output_size, + "Output buffer size should be at least {} bytes.", + min_output_size + ); + + let mut outl = 0; + unsafe { + use foreign_types_shared::ForeignTypeRef; + cvt(openssl_sys::EVP_CipherFinal( + ctx.as_ptr(), + output.as_mut_ptr(), + &mut outl, + ))?; + } + + Ok(outl as usize) +} + +impl Aead for OpenSslContextEncrypt { + fn update(&mut self, ad: &[u8]) -> Result<()> { + self.ctx.cipher_update(ad, None)?; + Ok(()) + } + + fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { + let size = self.ctx.cipher_update(src, Some(dst))?; + safe_cipher_final(&self.ctx, &mut dst[size..])?; + self.finalized = true; + Ok(()) + } + + fn decrypt_verify(&mut self, _dst: &mut [u8], _src: &[u8], _valid_digest: &[u8]) -> Result<()> { + panic!("Decrypt called in encrypt context"); + } + + fn digest(&mut self, digest: &mut [u8]) -> Result<()> { + if !self.finalized { + safe_cipher_final(&self.ctx, &mut [])?; + } + self.ctx.tag(digest)?; + Ok(()) + } + + fn digest_size(&self) -> usize { + panic!("Unsupported op"); + } +} + +impl crate::seal::Sealed for OpenSslContextEncrypt {} + +struct OpenSslContextDecrypt { + ctx: CipherCtx, +} + +impl Aead for OpenSslContextDecrypt { + fn update(&mut self, ad: &[u8]) -> Result<()> { + self.ctx.cipher_update(ad, None)?; + Ok(()) + } + + fn encrypt(&mut self, _dst: &mut [u8], _src: &[u8]) -> Result<()> { + panic!("Encrypt called in decrypt context"); + } + + fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8], valid_digest: &[u8]) -> Result<()> { + let size = self.ctx.cipher_update(src, Some(dst))?; + self.ctx.set_tag(valid_digest)?; + safe_cipher_final(&self.ctx, &mut dst[size..])?; + Ok(()) + } + + fn digest(&mut self, _digest: &mut [u8]) -> Result<()> { + panic!("Unsupported op, use decrypt_verify"); + } + + fn digest_size(&self) -> usize { + panic!("Unsupported operation"); + } +} + +impl crate::seal::Sealed for OpenSslContextDecrypt {} + +impl AEADAlgorithm { + pub(crate) fn context( + &self, + sym_algo: SymmetricAlgorithm, + key: &[u8], + nonce: &[u8], + op: CipherOp, + ) -> Result> { + match self { + AEADAlgorithm::OCB => { + let cipher = match sym_algo { + SymmetricAlgorithm::AES128 => Cipher::aes_128_ocb(), + SymmetricAlgorithm::AES192 => Cipher::aes_192_ocb(), + SymmetricAlgorithm::AES256 => Cipher::aes_256_ocb(), + _ => return Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), + }; + let mut ctx = CipherCtx::new()?; + ctx.set_padding(false); + match op { + CipherOp::Encrypt => { + ctx.encrypt_init(Some(cipher), Some(key), Some(nonce))?; + Ok(Box::new(OpenSslContextEncrypt { + ctx, + finalized: false, + })) + } + CipherOp::Decrypt => { + ctx.decrypt_init(Some(cipher), Some(key), Some(nonce))?; + Ok(Box::new(OpenSslContextDecrypt { ctx })) + } + } + } + _ => Err(Error::UnsupportedAEADAlgorithm(*self).into()), + } + } +} diff --git a/openpgp/src/crypto/backend/openssl/asymmetric.rs b/openpgp/src/crypto/backend/openssl/asymmetric.rs new file mode 100644 index 00000000..aef777bf --- /dev/null +++ b/openpgp/src/crypto/backend/openssl/asymmetric.rs @@ -0,0 +1,642 @@ +use crate::Result; + +use crate::crypto::asymmetric::{Decryptor, KeyPair, Signer}; +use crate::crypto::mpi; +use crate::crypto::mpi::{ProtectedMPI, MPI}; +use crate::crypto::mem::Protected; +use crate::crypto::SessionKey; +use crate::packet::key::{Key4, SecretParts}; +use crate::packet::{key, Key}; +use crate::types::SymmetricAlgorithm; +use crate::types::{Curve, HashAlgorithm, PublicKeyAlgorithm}; +use std::convert::{TryFrom, TryInto}; +use std::time::SystemTime; + +use openssl::bn::{BigNum, BigNumRef, BigNumContext}; +use openssl::ec::{EcGroup, EcKey, EcPoint, PointConversionForm}; +use openssl::ecdsa::EcdsaSig; +use openssl::nid::Nid; +use openssl::pkey::PKey; +use openssl::pkey_ctx::PkeyCtx; +use openssl::rsa::{Padding, Rsa, RsaPrivateKeyBuilder}; +use openssl::sign::Signer as OpenSslSigner; +use openssl::sign::Verifier; + +impl TryFrom<&ProtectedMPI> for BigNum { + type Error = anyhow::Error; + fn try_from(mpi: &ProtectedMPI) -> std::result::Result { + let mut bn = BigNum::new_secure()?; + bn.copy_from_slice(mpi.value())?; + Ok(bn) + } +} + +impl From<&BigNumRef> for ProtectedMPI { + fn from(bn: &BigNumRef) -> Self { + bn.to_vec().into() + } +} + +impl From for ProtectedMPI { + fn from(bn: BigNum) -> Self { + bn.to_vec().into() + } +} + +impl From for MPI { + fn from(bn: BigNum) -> Self { + bn.to_vec().into() + } +} + +impl TryFrom<&MPI> for BigNum { + type Error = anyhow::Error; + fn try_from(mpi: &MPI) -> std::result::Result { + Ok(BigNum::from_slice(mpi.value())?) + } +} + +impl From<&BigNumRef> for MPI { + fn from(bn: &BigNumRef) -> Self { + bn.to_vec().into() + } +} + +impl TryFrom<&Curve> for Nid { + type Error = crate::Error; + fn try_from(curve: &Curve) -> std::result::Result { + Ok(match curve { + Curve::NistP256 => Nid::X9_62_PRIME256V1, + Curve::NistP384 => Nid::SECP384R1, + Curve::NistP521 => Nid::SECP521R1, + Curve::BrainpoolP256 => Nid::BRAINPOOL_P256R1, + Curve::BrainpoolP512 => Nid::BRAINPOOL_P512R1, + _ => return Err(crate::Error::UnsupportedEllipticCurve(curve.clone()).into()), + }) + } +} + +impl Signer for KeyPair { + fn public(&self) -> &Key { + KeyPair::public(self) + } + + fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) -> Result { + use crate::PublicKeyAlgorithm::*; + #[allow(deprecated)] + self.secret().map( + |secret| match (self.public().pk_algo(), self.public().mpis(), secret) { + ( + RSAEncryptSign, + mpi::PublicKey::RSA { e, n }, + mpi::SecretKeyMaterial::RSA { p, q, d, .. }, + ) + | ( + RSASign, + mpi::PublicKey::RSA { e, n }, + mpi::SecretKeyMaterial::RSA { p, q, d, .. }, + ) => { + let key = + RsaPrivateKeyBuilder::new(n.try_into()?, e.try_into()?, d.try_into()?)? + .set_factors(p.try_into()?, q.try_into()?)? + .build(); + + let key = PKey::from_rsa(key)?; + + let mut signature: Vec = vec![]; + + const MAX_OID_SIZE: usize = 20; + let mut v = Vec::with_capacity(MAX_OID_SIZE + digest.len()); + v.extend(hash_algo.oid()?); + v.extend(digest); + + let mut ctx = PkeyCtx::new(&key)?; + ctx.sign_init()?; + ctx.sign_to_vec(&v, &mut signature)?; + + Ok(mpi::Signature::RSA { + s: signature.into(), + }) + } + ( + PublicKeyAlgorithm::DSA, + mpi::PublicKey::DSA { p, q, g, y }, + mpi::SecretKeyMaterial::DSA { x }, + ) => { + use openssl::dsa::{Dsa, DsaSig}; + let dsa = Dsa::from_private_components( + p.try_into()?, + q.try_into()?, + g.try_into()?, + x.try_into()?, + y.try_into()?, + )?; + let key: PKey<_> = dsa.try_into()?; + let mut ctx = PkeyCtx::new(&key)?; + ctx.sign_init()?; + let mut signature = vec![]; + ctx.sign_to_vec(&digest, &mut signature)?; + let signature = DsaSig::from_der(&signature)?; + + Ok(mpi::Signature::DSA { + r: signature.r().to_vec().into(), + s: signature.s().to_vec().into(), + }) + } + ( + PublicKeyAlgorithm::ECDSA, + mpi::PublicKey::ECDSA { curve, q }, + mpi::SecretKeyMaterial::ECDSA { scalar }, + ) => { + let nid = curve.try_into()?; + let group = EcGroup::from_curve_name(nid)?; + let mut ctx = BigNumContext::new()?; + let point = EcPoint::from_bytes(&group, q.value(), &mut ctx)?; + let mut private = BigNum::new_secure()?; + private.copy_from_slice(scalar.value())?; + let key = EcKey::from_private_components(&group, &private, &point)?; + let sig = EcdsaSig::sign(digest, &key)?; + Ok(mpi::Signature::ECDSA { + r: sig.r().into(), + s: sig.s().into(), + }) + } + + ( + EdDSA, + mpi::PublicKey::EdDSA { curve, .. }, + mpi::SecretKeyMaterial::EdDSA { scalar }, + ) => match curve { + Curve::Ed25519 => { + let scalar = scalar.value_padded(32); + + let key = + PKey::private_key_from_raw_bytes(&scalar, openssl::pkey::Id::ED25519)?; + + let mut signer = OpenSslSigner::new_without_digest(&key)?; + let signature = signer.sign_oneshot_to_vec(digest)?; + + // https://tools.ietf.org/html/rfc8032#section-5.1.6 + let (r, s) = signature.split_at(signature.len() / 2); + Ok(mpi::Signature::EdDSA { + r: r.to_vec().into(), + s: s.to_vec().into(), + }) + } + _ => Err(crate::Error::UnsupportedEllipticCurve(curve.clone()).into()), + }, + + (pk_algo, _, _) => Err(crate::Error::InvalidOperation(format!( + "unsupported combination of algorithm {:?}, key {:?}, \ + and secret key {:?} by OpenSSL backend", + pk_algo, + self.public(), + self.secret() + )) + .into()), + }, + ) + } +} + +impl Decryptor for KeyPair { + fn public(&self) -> &Key { + KeyPair::public(self) + } + + fn decrypt( + &mut self, + ciphertext: &mpi::Ciphertext, + _plaintext_len: Option, + ) -> Result { + use crate::crypto::mpi::PublicKey; + + self.secret().map(|secret| { + Ok(match (self.public().mpis(), secret, ciphertext) { + ( + PublicKey::RSA { ref e, ref n }, + mpi::SecretKeyMaterial::RSA { + ref p, + ref q, + ref d, + .. + }, + mpi::Ciphertext::RSA { ref c }, + ) => { + let key = + RsaPrivateKeyBuilder::new(n.try_into()?, e.try_into()?, d.try_into()?)? + .set_factors(p.try_into()?, q.try_into()?)? + .build(); + + let mut buf: Protected = vec![0; key.size().try_into()?].into(); + let encrypted_len = key.private_decrypt(c.value(), &mut buf, Padding::PKCS1)?; + buf[..encrypted_len].into() + } + + ( + PublicKey::ECDH { .. }, + mpi::SecretKeyMaterial::ECDH { .. }, + mpi::Ciphertext::ECDH { .. }, + ) => crate::crypto::ecdh::decrypt(self.public(), secret, ciphertext)?, + + (public, secret, ciphertext) => { + return Err(crate::Error::InvalidOperation(format!( + "unsupported combination of key pair {:?}/{:?} \ + and ciphertext {:?}", + public, secret, ciphertext + )) + .into()) + } + }) + }) + } +} + +impl Key { + /// Encrypts the given data with this key. + pub fn encrypt(&self, data: &SessionKey) -> Result { + use PublicKeyAlgorithm::*; + #[allow(deprecated)] + match self.pk_algo() { + RSAEncryptSign | RSAEncrypt => match self.mpis() { + mpi::PublicKey::RSA { e, n } => { + // The ciphertext has the length of the modulus. + let ciphertext_len = n.value().len(); + if data.len() + 11 > ciphertext_len { + return Err(crate::Error::InvalidArgument( + "Plaintext data too large".into(), + ) + .into()); + } + + let e = BigNum::from_slice(e.value())?; + let n = BigNum::from_slice(n.value())?; + let rsa = Rsa::::from_public_components(n, e)?; + + // The ciphertext has the length of the modulus. + let mut buf = vec![0; rsa.size().try_into()?]; + rsa.public_encrypt(data, &mut buf, Padding::PKCS1)?; + Ok(mpi::Ciphertext::RSA { + c: buf.into(), + }) + } + pk => Err(crate::Error::MalformedPacket(format!( + "Key: Expected RSA public key, got {:?}", + pk + )) + .into()), + }, + ECDH => crate::crypto::ecdh::encrypt(self.parts_as_public(), data), + algo => Err(crate::Error::UnsupportedPublicKeyAlgorithm(algo).into()), + } + } + + /// Verifies the given signature. + pub fn verify( + &self, + sig: &mpi::Signature, + hash_algo: HashAlgorithm, + digest: &[u8], + ) -> Result<()> { + let ok = match (self.mpis(), sig) { + (mpi::PublicKey::RSA { e, n }, mpi::Signature::RSA { s }) => { + let e = BigNum::from_slice(e.value())?; + let n = BigNum::from_slice(n.value())?; + let keypair = Rsa::::from_public_components(n, e)?; + let keypair = PKey::from_rsa(keypair)?; + + let signature = s.value(); + let mut v = vec![]; + v.extend(hash_algo.oid()?); + v.extend(digest); + + let mut ctx = PkeyCtx::new(&keypair)?; + ctx.verify_init()?; + ctx.verify(&v, signature)? + } + (mpi::PublicKey::DSA { p, q, g, y }, mpi::Signature::DSA { r, s }) => { + use openssl::dsa::{Dsa, DsaSig}; + let dsa = Dsa::from_public_components( + p.try_into()?, + q.try_into()?, + g.try_into()?, + y.try_into()?, + )?; + let key: PKey<_> = dsa.try_into()?; + let r = r.try_into()?; + let s = s.try_into()?; + let signature = DsaSig::from_private_components(r, s)?; + let mut ctx = PkeyCtx::new(&key)?; + ctx.verify_init()?; + ctx.verify(&digest, &signature.to_der()?)? + } + (mpi::PublicKey::EdDSA { curve, q }, mpi::Signature::EdDSA { r, s }) => match curve { + Curve::Ed25519 => { + let public = q.decode_point(&Curve::Ed25519)?.0; + + let key = PKey::public_key_from_raw_bytes(public, openssl::pkey::Id::ED25519)?; + + const SIGNATURE_LENGTH: usize = 64; + + // ed25519 expects full-sized signatures but OpenPGP allows + // for stripped leading zeroes, pad each part with zeroes. + let mut sig_bytes = [0u8; SIGNATURE_LENGTH]; + + // We need to zero-pad them at the front, because + // the MPI encoding drops leading zero bytes. + let half = SIGNATURE_LENGTH / 2; + sig_bytes[..half].copy_from_slice(&r.value_padded(half)?); + sig_bytes[half..].copy_from_slice(&s.value_padded(half)?); + + let mut verifier = Verifier::new_without_digest(&key)?; + verifier.verify_oneshot(&sig_bytes, digest)? + } + _ => return Err(crate::Error::UnsupportedEllipticCurve(curve.clone()).into()), + }, + (mpi::PublicKey::ECDSA { curve, q }, mpi::Signature::ECDSA { s, r }) => { + let nid = curve.try_into()?; + let group = EcGroup::from_curve_name(nid)?; + let mut ctx = BigNumContext::new()?; + let point = EcPoint::from_bytes(&group, q.value(), &mut ctx)?; + let key = EcKey::from_public_key(&group, &point)?; + let sig = EcdsaSig::from_private_components( + r.try_into()?, + s.try_into()?, + )?; + sig.verify(digest, &key)? + } + _ => { + return Err(crate::Error::MalformedPacket(format!( + "unsupported combination of key {} and signature {:?}.", + self.pk_algo(), + sig + )) + .into()) + } + }; + + if ok { + Ok(()) + } else { + Err(crate::Error::ManipulatedMessage.into()) + } + } +} + +impl Key4 +where + R: key::KeyRole, +{ + /// Creates a new OpenPGP secret key packet for an existing X25519 key. + /// + /// The ECDH key will use hash algorithm `hash` and symmetric + /// algorithm `sym`. If one or both are `None` secure defaults + /// will be used. The key will have it's creation date set to + /// `ctime` or the current time if `None` is given. + pub fn import_secret_cv25519( + private_key: &[u8], + hash: H, + sym: S, + ctime: T, + ) -> Result + where + H: Into>, + S: Into>, + T: Into>, + { + let key = PKey::private_key_from_raw_bytes(private_key, openssl::pkey::Id::X25519)?; + let public_key = key.raw_public_key()?; + + let mut private_key: Protected = key.raw_private_key().map(|key| key.into())?; + private_key.reverse(); + + use crate::crypto::ecdh; + Self::with_secret( + ctime.into().unwrap_or_else(crate::now), + PublicKeyAlgorithm::ECDH, + mpi::PublicKey::ECDH { + curve: Curve::Cv25519, + hash: hash + .into() + .unwrap_or_else(|| ecdh::default_ecdh_kdf_hash(&Curve::Cv25519)), + sym: sym + .into() + .unwrap_or_else(|| ecdh::default_ecdh_kek_cipher(&Curve::Cv25519)), + q: MPI::new_compressed_point(&public_key), + }, + mpi::SecretKeyMaterial::ECDH { + scalar: private_key.into(), + } + .into(), + ) + } + + /// Creates a new OpenPGP secret key packet for an existing Ed25519 key. + /// + /// The ECDH key will use hash algorithm `hash` and symmetric + /// algorithm `sym`. If one or both are `None` secure defaults + /// will be used. The key will have it's creation date set to + /// `ctime` or the current time if `None` is given. + pub fn import_secret_ed25519(private_key: &[u8], ctime: T) -> Result + where + T: Into>, + { + let key = PKey::private_key_from_raw_bytes(private_key, openssl::pkey::Id::ED25519)?; + let public_key = key.raw_public_key()?; + + Self::with_secret( + ctime.into().unwrap_or_else(crate::now), + PublicKeyAlgorithm::EdDSA, + mpi::PublicKey::EdDSA { + curve: Curve::Ed25519, + q: public_key.into(), + }, + mpi::SecretKeyMaterial::EdDSA { + scalar: mpi::MPI::new(&private_key).into(), + } + .into(), + ) + } + + /// Creates a new OpenPGP public key packet for an existing RSA key. + /// + /// The RSA key will use public exponent `e` and modulo `n`. The key will + /// have it's creation date set to `ctime` or the current time if `None` + /// is given. + #[allow(clippy::many_single_char_names)] + pub fn import_secret_rsa(d: &[u8], p: &[u8], q: &[u8], ctime: T) -> Result + where + T: Into>, + { + // RFC 4880: `p < q` + let (p, q) = if p < q { (p, q) } else { (q, p) }; + + let mut big_p = BigNum::new_secure()?; + big_p.copy_from_slice(p)?; + let mut big_q = BigNum::new_secure()?; + big_q.copy_from_slice(q)?; + let n = &big_p * &big_q; + + let mut one = BigNum::new_secure()?; + one.copy_from_slice(&[1])?; + let big_phi = &(&big_p - &one) * &(&big_q - &one); + + let mut ctx = BigNumContext::new_secure()?; + + let mut e = BigNum::new_secure()?; + let mut d_bn = BigNum::new_secure()?; + d_bn.copy_from_slice(d)?; + e.mod_inverse(&d_bn, &big_phi, &mut ctx)?; // e ≡ d⁻¹ (mod 𝜙) + + let mut u = BigNum::new_secure()?; + u.mod_inverse(&big_p, &big_q, &mut ctx)?; // RFC 4880: u ≡ p⁻¹ (mod q) + + Self::with_secret( + ctime.into().unwrap_or_else(crate::now), + PublicKeyAlgorithm::RSAEncryptSign, + mpi::PublicKey::RSA { + e: e.into(), + n: n.into(), + }, + mpi::SecretKeyMaterial::RSA { + d: d_bn.into(), + p: mpi::MPI::new(p).into(), + q: mpi::MPI::new(q).into(), + u: u.into(), + } + .into(), + ) + } + + /// Generates a new RSA key with a public modulos of size `bits`. + #[allow(clippy::many_single_char_names)] + pub fn generate_rsa(bits: usize) -> Result { + let key = Rsa::generate(bits.try_into()?)?; + let e = key.e(); + let n = key.n(); + let d = key.d(); + let p = key + .p() + .ok_or_else(|| crate::Error::InvalidOperation("p".into()))?; + let q = key + .q() + .ok_or_else(|| crate::Error::InvalidOperation("q".into()))?; + + let mut ctx = BigNumContext::new_secure()?; + let mut u = BigNum::new_secure()?; + u.mod_inverse(p, q, &mut ctx)?; + + Self::with_secret( + crate::now(), + PublicKeyAlgorithm::RSAEncryptSign, + mpi::PublicKey::RSA { + e: e.into(), + n: n.into(), + }, + mpi::SecretKeyMaterial::RSA { + d: d.into(), + p: p.into(), + q: q.into(), + u: u.into(), + } + .into(), + ) + } + + /// Generates a new ECC key over `curve`. + /// + /// If `for_signing` is false a ECDH key, if it's true either a + /// EdDSA or ECDSA key is generated. Giving `for_signing == true` and + /// `curve == Cv25519` will produce an error. Likewise + /// `for_signing == false` and `curve == Ed25519` will produce an error. + pub fn generate_ecc(for_signing: bool, curve: Curve) -> Result { + if for_signing && curve == Curve::Cv25519 { + return Err(crate::Error::UnsupportedEllipticCurve(curve.clone()).into()); + } + + if !for_signing && curve == Curve::Ed25519 { + return Err(crate::Error::UnsupportedEllipticCurve(curve.clone()).into()); + } + + if curve == Curve::Cv25519 || curve == Curve::Ed25519 { + let key = if curve == Curve::Cv25519 { + openssl::pkey::PKey::generate_x25519()? + } else { + openssl::pkey::PKey::generate_ed25519()? + }; + + let hash = crate::crypto::ecdh::default_ecdh_kdf_hash(&curve); + let sym = crate::crypto::ecdh::default_ecdh_kek_cipher(&curve); + + let q = MPI::new_compressed_point(&key.raw_public_key()?); + let mut scalar: Protected = key.raw_private_key().map(|key| key.into())?; + + if curve == Curve::Cv25519 { + scalar.reverse(); + } + let scalar = scalar.into(); + + let (algo, public, private) = if for_signing { + ( + PublicKeyAlgorithm::EdDSA, + mpi::PublicKey::EdDSA { curve, q }, + mpi::SecretKeyMaterial::EdDSA { scalar }, + ) + } else { + ( + PublicKeyAlgorithm::ECDH, + mpi::PublicKey::ECDH { + curve, + q, + hash, + sym, + }, + mpi::SecretKeyMaterial::ECDH { scalar }, + ) + }; + return Self::with_secret(crate::now(), algo, public, private.into()); + } + + let nid = match curve { + Curve::NistP256 => Nid::X9_62_PRIME256V1, + Curve::NistP384 => Nid::SECP384R1, + Curve::NistP521 => Nid::SECP521R1, + _ => return Err(crate::Error::UnsupportedEllipticCurve(curve.clone()).into()), + }; + + let group = EcGroup::from_curve_name(nid)?; + let key = EcKey::generate(&group)?; + + let hash = crate::crypto::ecdh::default_ecdh_kdf_hash(&curve); + let sym = crate::crypto::ecdh::default_ecdh_kek_cipher(&curve); + let mut ctx = BigNumContext::new()?; + + let q = MPI::new(&key.public_key().to_bytes( + &group, + PointConversionForm::COMPRESSED, + &mut ctx, + )?); + let scalar = key.private_key().to_vec().into(); + + let (algo, public, private) = if for_signing { + ( + PublicKeyAlgorithm::ECDSA, + mpi::PublicKey::ECDSA { curve, q }, + mpi::SecretKeyMaterial::ECDSA { scalar }, + ) + } else { + ( + PublicKeyAlgorithm::ECDH, + mpi::PublicKey::ECDH { + curve, + q, + hash, + sym, + }, + mpi::SecretKeyMaterial::ECDH { scalar }, + ) + }; + + Self::with_secret(crate::now(), algo, public, private.into()) + } +} diff --git a/openpgp/src/crypto/backend/openssl/ecdh.rs b/openpgp/src/crypto/backend/openssl/ecdh.rs new file mode 100644 index 00000000..662cc596 --- /dev/null +++ b/openpgp/src/crypto/backend/openssl/ecdh.rs @@ -0,0 +1,123 @@ +//! Elliptic Curve Diffie-Hellman. +use std::convert::{TryFrom, TryInto}; + +use crate::crypto::ecdh::{decrypt_unwrap, encrypt_wrap}; +use crate::crypto::mpi; +use crate::crypto::mpi::{Ciphertext, SecretKeyMaterial}; +use crate::crypto::SessionKey; +use crate::packet::{key, Key}; +use crate::types::Curve; +use crate::{Error, Result}; + +use openssl::bn::{BigNum, BigNumContext}; +use openssl::derive::Deriver; +use openssl::ec::{EcGroup, EcKey, EcPoint, PointConversionForm}; +use openssl::pkey::PKey; + +/// Wraps a session key using Elliptic Curve Diffie-Hellman. +pub fn encrypt( + recipient: &Key, + session_key: &SessionKey, +) -> Result +where + R: key::KeyRole, +{ + let (curve, q) = match recipient.mpis() { + mpi::PublicKey::ECDH { curve, q, .. } => (curve, q), + _ => return Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), + }; + if curve == &Curve::Cv25519 { + let public = q.decode_point(curve)?.0; + + let public_key = PKey::public_key_from_raw_bytes(public, openssl::pkey::Id::X25519)?; + + let key = PKey::generate_x25519()?; + let mut deriver = Deriver::new(&key)?; + deriver.set_peer(&public_key)?; + + let secret = deriver.derive_to_vec()?.into(); + + let q = mpi::MPI::new_compressed_point(&key.raw_public_key()?); + + return encrypt_wrap(recipient, session_key, q, &secret); + } + + let nid = curve.try_into()?; + let group = EcGroup::from_curve_name(nid)?; + let mut ctx = BigNumContext::new()?; + let point = EcPoint::from_bytes(&group, q.value(), &mut ctx)?; + let recipient_key = EcKey::from_public_key(&group, &point)?; + let recipient_key = PKey::<_>::try_from(recipient_key)?; + + let key = EcKey::generate(&group)?; + + let q = mpi::MPI::new(&key.public_key().to_bytes( + &group, + PointConversionForm::COMPRESSED, + &mut ctx, + )?); + + let key = PKey::<_>::try_from(key)?; + let mut deriver = Deriver::new(&key)?; + deriver.set_peer(&recipient_key)?; + + let secret = deriver.derive_to_vec()?.into(); + + encrypt_wrap(recipient, session_key, q, &secret) +} + +/// Unwraps a session key using Elliptic Curve Diffie-Hellman. +pub fn decrypt( + recipient: &Key, + recipient_sec: &SecretKeyMaterial, + ciphertext: &Ciphertext, +) -> Result +where + R: key::KeyRole, +{ + let (curve, scalar, e, q) = match (recipient.mpis(), recipient_sec, ciphertext) { + ( + mpi::PublicKey::ECDH { + ref curve, ref q, .. + }, + SecretKeyMaterial::ECDH { ref scalar }, + Ciphertext::ECDH { ref e, .. }, + ) => (curve, scalar, e, q), + _ => return Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), + }; + + if curve == &Curve::Cv25519 { + let mut scalar = scalar.value_padded(32); + scalar.reverse(); + + let key = PKey::private_key_from_raw_bytes(&scalar[..], openssl::pkey::Id::X25519)?; + + let public = e.decode_point(curve)?.0; + let public_key = PKey::public_key_from_raw_bytes(public, openssl::pkey::Id::X25519)?; + + let mut deriver = Deriver::new(&key)?; + deriver.set_peer(&public_key)?; + let secret = deriver.derive_to_vec()?.into(); + + return decrypt_unwrap(recipient, &secret, ciphertext); + } + + let nid = curve.try_into()?; + let group = EcGroup::from_curve_name(nid)?; + let mut ctx = BigNumContext::new()?; + let point = EcPoint::from_bytes(&group, e.value(), &mut ctx)?; + + let public_point = EcPoint::from_bytes(&group, q.value(), &mut ctx)?; + let scalar = BigNum::from_slice(scalar.value())?; + let key = EcKey::from_private_components(&group, &scalar, &public_point)?; + + let recipient_key = EcKey::from_public_key(&group, &point)?; + let recipient_key = PKey::<_>::try_from(recipient_key)?; + + let key = PKey::<_>::try_from(key)?; + let mut deriver = Deriver::new(&key)?; + deriver.set_peer(&recipient_key)?; + let secret = deriver.derive_to_vec()?.into(); + + decrypt_unwrap(recipient, &secret, ciphertext) +} diff --git a/openpgp/src/crypto/backend/openssl/hash.rs b/openpgp/src/crypto/backend/openssl/hash.rs new file mode 100644 index 00000000..c379ac2e --- /dev/null +++ b/openpgp/src/crypto/backend/openssl/hash.rs @@ -0,0 +1,99 @@ +use crate::crypto::hash::Digest; +use crate::types::HashAlgorithm; +use crate::Result; + +use openssl::error::ErrorStack; +use openssl::hash::{Hasher, MessageDigest}; +use openssl::nid::Nid; + +#[derive(Clone)] +struct OpenSslDigest { + digest_size: usize, + algo: HashAlgorithm, + hasher: Hasher, + update_result: std::result::Result<(), ErrorStack>, +} + +impl OpenSslDigest { + fn new(algo: HashAlgorithm) -> Result { + if let Some(md) = get_md(algo) { + Ok(Self { + digest_size: md.size(), + algo, + update_result: Ok(()), + hasher: Hasher::new(md)?, + }) + } else { + Err(crate::Error::UnsupportedHashAlgorithm(algo).into()) + } + } +} + +impl Digest for OpenSslDigest { + fn algo(&self) -> HashAlgorithm { + self.algo + } + + fn digest_size(&self) -> usize { + self.digest_size + } + + fn update(&mut self, data: &[u8]) { + if self.update_result.is_ok() { + self.update_result = self.hasher.update(data); + } + } + + fn digest(&mut self, digest: &mut [u8]) -> Result<()> { + self.update_result.clone()?; + let result = self.hasher.finish()?; + digest.copy_from_slice(&result[..digest.len()]); + Ok(()) + } +} + +impl std::io::Write for OpenSslDigest { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.update(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> std::io::Result<()> { + // Do nothing. + Ok(()) + } +} + +fn get_md(algo: HashAlgorithm) -> Option { + use HashAlgorithm::*; + let nid = match algo { + MD5 => Nid::MD5, + RipeMD => Nid::RIPEMD160, + SHA1 => Nid::SHA1, + SHA256 => Nid::SHA256, + SHA384 => Nid::SHA384, + SHA512 => Nid::SHA512, + SHA224 => Nid::SHA224, + _ => return None, + }; + MessageDigest::from_nid(nid) +} + +impl HashAlgorithm { + /// Whether Sequoia supports this algorithm. + pub fn is_supported(self) -> bool { + get_md(self).is_some() + } + + /// Creates a new hash context for this algorithm. + /// + /// # Errors + /// + /// Fails with `Error::UnsupportedHashAlgorithm` if Sequoia does + /// not support this algorithm. See + /// [`HashAlgorithm::is_supported`]. + /// + /// [`HashAlgorithm::is_supported`]: HashAlgorithm::is_supported() + pub(crate) fn new_hasher(self) -> Result> { + Ok(Box::new(OpenSslDigest::new(self)?)) + } +} diff --git a/openpgp/src/crypto/backend/openssl/symmetric.rs b/openpgp/src/crypto/backend/openssl/symmetric.rs new file mode 100644 index 00000000..922b4f07 --- /dev/null +++ b/openpgp/src/crypto/backend/openssl/symmetric.rs @@ -0,0 +1,166 @@ +use crate::crypto::symmetric::Mode; + +use crate::types::SymmetricAlgorithm; +use crate::{Error, Result}; + +use openssl::cipher::{Cipher, CipherRef}; +use openssl::cipher_ctx::CipherCtx; + +struct OpenSslMode { + ctx: CipherCtx, +} + +impl OpenSslMode { + fn new(ctx: CipherCtx) -> Self { + Self { ctx } + } +} + +impl Mode for OpenSslMode { + fn block_size(&self) -> usize { + self.ctx.block_size() + } + + fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { + self.ctx.cipher_update(src, Some(dst))?; + Ok(()) + } + + fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { + self.encrypt(dst, src) + } +} + +impl SymmetricAlgorithm { + /// Returns whether this algorithm is supported by the crypto backend. + /// + /// All backends support all the AES variants. + /// + /// # Examples + /// + /// ```rust + /// use sequoia_openpgp as openpgp; + /// use openpgp::types::SymmetricAlgorithm; + /// + /// assert!(SymmetricAlgorithm::AES256.is_supported()); + /// assert!(SymmetricAlgorithm::TripleDES.is_supported()); + /// + /// assert!(!SymmetricAlgorithm::Twofish.is_supported()); + /// assert!(!SymmetricAlgorithm::Unencrypted.is_supported()); + /// assert!(!SymmetricAlgorithm::Private(101).is_supported()); + /// ``` + pub fn is_supported(&self) -> bool { + let cipher: &CipherRef = if let Ok(cipher) = (*self).make_cfb_cipher() { + cipher + } else { + return false; + }; + + let mut ctx = if let Ok(ctx) = CipherCtx::new() { + ctx + } else { + return false; + }; + ctx.encrypt_init(Some(cipher), None, None).is_ok() + } + + /// Length of a key for this algorithm in bytes. + /// + /// Fails if Sequoia does not support this algorithm. + pub fn key_size(self) -> Result { + Ok(self.make_cfb_cipher()?.key_length()) + } + + /// Length of a block for this algorithm in bytes. + /// + /// Fails if Sequoia does not support this algorithm. + pub fn block_size(self) -> Result { + Ok(self.make_ecb_cipher()?.block_size()) + } + + /// Creates a OpenSSL context for encrypting in CFB mode. + pub(crate) fn make_encrypt_cfb(self, key: &[u8], iv: Vec) -> Result> { + let cipher = self.make_cfb_cipher()?; + let mut ctx = CipherCtx::new()?; + ctx.encrypt_init(Some(cipher), Some(key), Some(&iv))?; + Ok(Box::new(OpenSslMode::new(ctx))) + } + + /// Creates a OpenSSL context for decrypting in CFB mode. + pub(crate) fn make_decrypt_cfb(self, key: &[u8], iv: Vec) -> Result> { + let cipher = self.make_cfb_cipher()?; + let mut ctx = CipherCtx::new()?; + ctx.decrypt_init(Some(cipher), Some(key), Some(&iv))?; + Ok(Box::new(OpenSslMode::new(ctx))) + } + + /// Creates a OpenSSL context for encrypting in ECB mode. + pub(crate) fn make_encrypt_ecb(self, key: &[u8]) -> Result> { + let cipher = self.make_ecb_cipher()?; + let mut ctx = CipherCtx::new()?; + ctx.encrypt_init(Some(cipher), Some(key), None)?; + ctx.set_padding(false); + Ok(Box::new(OpenSslMode::new(ctx))) + } + + /// Creates a OpenSSL context for decrypting in ECB mode. + pub(crate) fn make_decrypt_ecb(self, key: &[u8]) -> Result> { + let cipher = self.make_ecb_cipher()?; + let mut ctx = CipherCtx::new()?; + ctx.decrypt_init(Some(cipher), Some(key), None)?; + ctx.set_padding(false); + Ok(Box::new(OpenSslMode::new(ctx))) + } + + fn make_cfb_cipher(self) -> Result<&'static CipherRef> { + Ok(match self { + #[cfg(not(osslconf = "OPENSSL_NO_IDEA"))] + SymmetricAlgorithm::IDEA => Cipher::idea_cfb64(), + + SymmetricAlgorithm::AES128 => Cipher::aes_128_cfb128(), + SymmetricAlgorithm::AES192 => Cipher::aes_192_cfb128(), + SymmetricAlgorithm::AES256 => Cipher::aes_256_cfb128(), + + SymmetricAlgorithm::TripleDES => Cipher::des_ede3_cfb64(), + + #[cfg(not(osslconf = "OPENSSL_NO_CAMELLIA"))] + SymmetricAlgorithm::Camellia128 => Cipher::camellia128_cfb128(), + #[cfg(not(osslconf = "OPENSSL_NO_CAMELLIA"))] + SymmetricAlgorithm::Camellia192 => Cipher::camellia192_cfb128(), + #[cfg(not(osslconf = "OPENSSL_NO_CAMELLIA"))] + SymmetricAlgorithm::Camellia256 => Cipher::camellia256_cfb128(), + + #[cfg(not(osslconf = "OPENSSL_NO_BF"))] + SymmetricAlgorithm::Blowfish => Cipher::bf_cfb64(), + + SymmetricAlgorithm::CAST5 => Cipher::cast5_cfb64(), + _ => return Err(Error::UnsupportedSymmetricAlgorithm(self))?, + }) + } + + fn make_ecb_cipher(self) -> Result<&'static CipherRef> { + Ok(match self { + #[cfg(not(osslconf = "OPENSSL_NO_IDEA"))] + SymmetricAlgorithm::IDEA => Cipher::idea_ecb(), + + SymmetricAlgorithm::AES128 => Cipher::aes_128_ecb(), + SymmetricAlgorithm::AES192 => Cipher::aes_192_ecb(), + SymmetricAlgorithm::AES256 => Cipher::aes_256_ecb(), + + SymmetricAlgorithm::TripleDES => Cipher::des_ecb(), + + #[cfg(not(osslconf = "OPENSSL_NO_CAMELLIA"))] + SymmetricAlgorithm::Camellia128 => Cipher::camellia128_ecb(), + #[cfg(not(osslconf = "OPENSSL_NO_CAMELLIA"))] + SymmetricAlgorithm::Camellia192 => Cipher::camellia192_ecb(), + #[cfg(not(osslconf = "OPENSSL_NO_CAMELLIA"))] + SymmetricAlgorithm::Camellia256 => Cipher::camellia256_ecb(), + + #[cfg(not(osslconf = "OPENSSL_NO_BF"))] + SymmetricAlgorithm::Blowfish => Cipher::bf_ecb(), + + SymmetricAlgorithm::CAST5 => Cipher::cast5_ecb(), + _ => Err(Error::UnsupportedSymmetricAlgorithm(self))?, + }) + } +} -- cgit v1.2.3