diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2023-01-18 15:47:04 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2023-10-10 11:16:07 +0200 |
commit | a4c2b9f50a9f6e6539154eb3bcef8a0a062b331f (patch) | |
tree | b6d1724949eebd467d82d8467e7d7eaa4ff67142 /openpgp | |
parent | 3c76d468060a02b0aa129308bc413b91d166167b (diff) |
openpgp: Add a null crypto backend for fuzzing.
- Fixes #962.
Diffstat (limited to 'openpgp')
-rw-r--r-- | openpgp/Cargo.toml | 1 | ||||
-rw-r--r-- | openpgp/build.rs | 8 | ||||
-rw-r--r-- | openpgp/src/crypto/backend.rs | 12 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/fuzzing.rs | 49 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/fuzzing/aead.rs | 41 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/fuzzing/asymmetric.rs | 169 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/fuzzing/ecdh.rs | 33 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/fuzzing/hash.rs | 65 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/fuzzing/symmetric.rs | 75 | ||||
-rw-r--r-- | openpgp/src/crypto/ecdh.rs | 4 | ||||
-rw-r--r-- | openpgp/src/crypto/hash.rs | 2 |
11 files changed, 457 insertions, 2 deletions
diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml index 08719619..bdb42247 100644 --- a/openpgp/Cargo.toml +++ b/openpgp/Cargo.toml @@ -126,6 +126,7 @@ crypto-cng = [ crypto-openssl = ["openssl", "openssl-sys"] crypto-botan = ["botan/botan3"] crypto-botan2 = ["botan"] +crypto-fuzzing = [] __implicit-crypto-backend-for-tests = [] # Experimental and variable-time cryptographic backends opt-ins diff --git a/openpgp/build.rs b/openpgp/build.rs index 57969fd3..c8302fdf 100644 --- a/openpgp/build.rs +++ b/openpgp/build.rs @@ -68,6 +68,7 @@ fn crypto_backends_sanity_check() { any(feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", + feature = "crypto-fuzzing", feature = "crypto-rust"))))), Backend { name: "Nettle", @@ -80,6 +81,7 @@ fn crypto_backends_sanity_check() { feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", + feature = "crypto-fuzzing", feature = "crypto-rust"))))), Backend { name: "Windows CNG", @@ -110,6 +112,12 @@ fn crypto_backends_sanity_check() { production_ready: true, constant_time: true, }), + (cfg!(feature = "crypto-fuzzing"), + Backend { + name: "Fuzzing", + production_ready: false, + constant_time: false, + }), ].into_iter().filter_map(|(selected, backend)| { if selected { Some(backend) } else { None } }).collect::<Vec<_>>(); diff --git a/openpgp/src/crypto/backend.rs b/openpgp/src/crypto/backend.rs index f69d0a10..d6fb9487 100644 --- a/openpgp/src/crypto/backend.rs +++ b/openpgp/src/crypto/backend.rs @@ -17,6 +17,7 @@ pub(crate) mod sha1cd; any(feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", + feature = "crypto-fuzzing", feature = "crypto-rust")))))] mod nettle; #[cfg(all(feature = "crypto-nettle", @@ -24,6 +25,7 @@ mod nettle; any(feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", + feature = "crypto-fuzzing", feature = "crypto-rust")))))] pub use self::nettle::*; #[cfg(all(feature = "crypto-nettle", @@ -48,6 +50,7 @@ pub use self::nettle::Backend; feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", + feature = "crypto-fuzzing", feature = "crypto-rust")))))] mod cng; #[cfg(all(feature = "crypto-cng", @@ -56,6 +59,7 @@ mod cng; feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", + feature = "crypto-fuzzing", feature = "crypto-rust")))))] pub use self::cng::*; #[cfg(all(feature = "crypto-cng", @@ -64,6 +68,7 @@ pub use self::cng::*; feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", + feature = "crypto-fuzzing", feature = "crypto-rust")))))] pub use self::cng::Backend; @@ -87,3 +92,10 @@ mod botan; pub use self::botan::*; #[cfg(feature = "crypto-botan")] pub use self::botan::Backend; + +#[cfg(feature = "crypto-fuzzing")] +mod fuzzing; +#[cfg(feature = "crypto-fuzzing")] +pub use self::fuzzing::*; +#[cfg(feature = "crypto-fuzzing")] +pub use self::fuzzing::Backend; diff --git a/openpgp/src/crypto/backend/fuzzing.rs b/openpgp/src/crypto/backend/fuzzing.rs new file mode 100644 index 00000000..e36ee34e --- /dev/null +++ b/openpgp/src/crypto/backend/fuzzing.rs @@ -0,0 +1,49 @@ +//! Implementation of Sequoia crypto API using a fuzzing-friendly null +//! backend. + +use crate::types::*; + +#[allow(unused_variables)] +pub mod aead; +#[allow(unused_variables)] +pub mod asymmetric; +#[allow(unused_variables)] +pub mod ecdh; +#[allow(unused_variables)] +pub mod hash; +#[allow(unused_variables)] +pub mod symmetric; + +pub struct Backend(()); + +impl super::interface::Backend for Backend { + fn backend() -> String { + "Fuzzing".to_string() + } + + fn random(buf: &mut [u8]) -> crate::Result<()> { + buf.iter_mut().for_each(|b| *b = 4); + 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 { + true + } + + #[cfg(test)] + pub(crate) fn supports_symmetric_algo(&self, _: &SymmetricAlgorithm) + -> bool { + true + } +} diff --git a/openpgp/src/crypto/backend/fuzzing/aead.rs b/openpgp/src/crypto/backend/fuzzing/aead.rs new file mode 100644 index 00000000..0d995c56 --- /dev/null +++ b/openpgp/src/crypto/backend/fuzzing/aead.rs @@ -0,0 +1,41 @@ +//! Implementation of AEAD using Nettle cryptographic library. + +use crate::Result; + +use crate::crypto::aead::{Aead, CipherOp}; +use crate::seal; +use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; + +struct NullAEADMode {} + +const DIGEST_SIZE: usize = 16; + +impl seal::Sealed for NullAEADMode {} +impl Aead for NullAEADMode { + fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { + let l = dst.len() - DIGEST_SIZE; + dst[..l].copy_from_slice(src); + dst[l..].iter_mut().for_each(|p| *p = 0x04); + Ok(()) + } + fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { + dst.copy_from_slice(&src[..src.len() - DIGEST_SIZE]); + Ok(()) + } + fn digest_size(&self) -> usize { + DIGEST_SIZE + } +} + +impl AEADAlgorithm { + pub(crate) fn context( + &self, + sym_algo: SymmetricAlgorithm, + key: &[u8], + aad: &[u8], + nonce: &[u8], + _op: CipherOp, + ) -> Result<Box<dyn Aead>> { + Ok(Box::new(NullAEADMode {})) + } +} diff --git a/openpgp/src/crypto/backend/fuzzing/asymmetric.rs b/openpgp/src/crypto/backend/fuzzing/asymmetric.rs new file mode 100644 index 00000000..797795e9 --- /dev/null +++ b/openpgp/src/crypto/backend/fuzzing/asymmetric.rs @@ -0,0 +1,169 @@ +use crate::{Error, Result}; + +use crate::packet::{key, Key}; +use crate::crypto::asymmetric::KeyPair; +use crate::crypto::backend::interface::Asymmetric; +use crate::crypto::mem::Protected; +use crate::crypto::mpi::{self, MPI, ProtectedMPI}; +use crate::crypto::SessionKey; +use crate::types::{Curve, HashAlgorithm, PublicKeyAlgorithm}; + +impl Asymmetric for super::Backend { + fn supports_algo(_: PublicKeyAlgorithm) -> bool { + true + } + + fn supports_curve(_: &Curve) -> bool { + true + } + + fn x25519_generate_key() -> Result<(Protected, [u8; 32])> { + Ok((vec![4; 32].into(), [4; 32])) + } + + fn x25519_derive_public(_: &Protected) -> Result<[u8; 32]> { + Ok([4; 32]) + } + + fn x25519_shared_point(_: &Protected, _: &[u8; 32]) + -> Result<Protected> { + Ok(vec![4; 32].into()) + } + + fn ed25519_generate_key() -> Result<(Protected, [u8; 32])> { + Ok((vec![4; 32].into(), [4; 32])) + } + + fn ed25519_derive_public(_: &Protected) -> Result<[u8; 32]> { + Ok([4; 32]) + } + + fn ed25519_sign(_: &Protected, _: &[u8; 32], _: &[u8]) -> Result<[u8; 64]> { + Ok([4; 64]) + } + + fn ed25519_verify(_: &[u8; 32], _: &[u8], _: &[u8; 64]) -> Result<bool> { + Ok(true) + } + + fn dsa_generate_key(p_bits: usize) + -> Result<(MPI, MPI, MPI, MPI, ProtectedMPI)> + { + let four = MPI::new(&[4]); + Ok((four.clone(), + four.clone(), + four.clone(), + four.clone(), + vec![4].into())) + } + + fn elgamal_generate_key(p_bits: usize) + -> Result<(MPI, MPI, MPI, ProtectedMPI)> + { + let four = MPI::new(&[4]); + Ok((four.clone(), + four.clone(), + four.clone(), + vec![4].into())) + } +} + +impl KeyPair { + pub(crate) fn sign_backend(&self, + _: &mpi::SecretKeyMaterial, + _: HashAlgorithm, + _: &[u8]) + -> Result<mpi::Signature> + { + Err(Error::InvalidOperation("not implemented".into()).into()) + } + + pub(crate) fn decrypt_backend(&self, + _: &mpi::SecretKeyMaterial, + ciphertext: &mpi::Ciphertext, + _: Option<usize>) + -> Result<SessionKey> + { + match ciphertext { + mpi::Ciphertext::RSA { c } + | mpi::Ciphertext::ElGamal { c, .. } => + Ok(Vec::from(c.value()).into()), + mpi::Ciphertext::ECDH { key, .. } => + Ok(Vec::from(&key[..]).into()), + _ => Err(Error::InvalidOperation("not implemented".into()).into()), + } + } +} + + +impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { + /// Encrypts the given data with this key. + pub(crate) fn encrypt_backend(&self, data: &SessionKey) -> Result<mpi::Ciphertext> { + use crate::PublicKeyAlgorithm::*; + + #[allow(deprecated)] + match self.pk_algo() { + RSAEncryptSign | RSAEncrypt => + Ok(mpi::Ciphertext::RSA { + c: MPI::new(&data), + }), + ElGamalEncrypt | ElGamalEncryptSign => + Ok(mpi::Ciphertext::ElGamal { + e: MPI::new(&data), + c: MPI::new(&data), + }), + ECDH => + Ok(mpi::Ciphertext::ECDH { + e: MPI::new(&data), + key: Vec::from(&data[..]).into_boxed_slice(), + }), + _ => Err(Error::InvalidOperation("not implemented".into()).into()), + } + } + + /// Verifies the given signature. + pub(crate) fn verify_backend(&self, _: &mpi::Signature, _: HashAlgorithm, + _: &[u8]) -> Result<()> + { + let ok = true; // XXX maybe we also want to have bad signatures? + if ok { + Ok(()) + } else { + Err(Error::ManipulatedMessage.into()) + } + } +} + +use std::time::SystemTime; +use crate::packet::key::{Key4, SecretParts}; + +impl<R> Key4<SecretParts, R> + where R: key::KeyRole, +{ + /// 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<T>(d: &[u8], p: &[u8], q: &[u8], ctime: T) + -> Result<Self> where T: Into<Option<SystemTime>> + { + Err(Error::InvalidOperation("not implemented".into()).into()) + } + + /// Generates a new RSA key with a public modulos of size `bits`. + pub fn generate_rsa(bits: usize) -> Result<Self> { + Err(Error::InvalidOperation("not implemented".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<Self> { + Err(Error::InvalidOperation("not implemented".into()).into()) + } +} diff --git a/openpgp/src/crypto/backend/fuzzing/ecdh.rs b/openpgp/src/crypto/backend/fuzzing/ecdh.rs new file mode 100644 index 00000000..a01eccbb --- /dev/null +++ b/openpgp/src/crypto/backend/fuzzing/ecdh.rs @@ -0,0 +1,33 @@ +//! Elliptic Curve Diffie-Hellman. + +use crate::{Error, Result}; +use crate::crypto::SessionKey; +use crate::crypto::mpi::{MPI, Ciphertext, SecretKeyMaterial}; +use crate::packet::{key, Key}; + +/// Wraps a session key using Elliptic Curve Diffie-Hellman. +#[allow(dead_code)] +pub fn encrypt<R>(recipient: &Key<key::PublicParts, R>, + session_key: &SessionKey) + -> Result<Ciphertext> + where R: key::KeyRole +{ + Ok(Ciphertext::ECDH { + e: MPI::new(&session_key), + key: Vec::from(&session_key[..]).into_boxed_slice(), + }) +} + +/// Unwraps a session key using Elliptic Curve Diffie-Hellman. +#[allow(dead_code)] +pub fn decrypt<R>(recipient: &Key<key::PublicParts, R>, + recipient_sec: &SecretKeyMaterial, + ciphertext: &Ciphertext) + -> Result<SessionKey> + where R: key::KeyRole +{ + match ciphertext { + Ciphertext::ECDH { key, .. } => Ok(Vec::from(&key[..]).into()), + _ => Err(Error::InvalidArgument("not a ecdh ciphertext".into()).into()), + } +} diff --git a/openpgp/src/crypto/backend/fuzzing/hash.rs b/openpgp/src/crypto/backend/fuzzing/hash.rs new file mode 100644 index 00000000..b854ec89 --- /dev/null +++ b/openpgp/src/crypto/backend/fuzzing/hash.rs @@ -0,0 +1,65 @@ +use std::io; + +use crate::crypto::hash::Digest; +use crate::Result; +use crate::types::{HashAlgorithm}; + +#[derive(Clone)] +struct NullHasher(HashAlgorithm); + +impl io::Write for NullHasher { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Digest for NullHasher { + fn algo(&self) -> HashAlgorithm { + self.0 + } + + fn digest_size(&self) -> usize { + match self.0 { + HashAlgorithm::SHA1 => 20, + HashAlgorithm::SHA224 => 28, + HashAlgorithm::SHA256 => 32, + HashAlgorithm::SHA384 => 48, + HashAlgorithm::SHA512 => 64, + HashAlgorithm::RipeMD => 20, + HashAlgorithm::MD5 => 16, + _ => 32, // Made up. + } + } + + fn update(&mut self, data: &[u8]) { + // Nop. + } + + fn digest(&mut self, digest: &mut [u8]) -> Result<()> { + digest.iter_mut().enumerate().for_each(|(i, b)| *b = i as u8); + Ok(()) + } +} + +impl HashAlgorithm { + /// Whether Sequoia supports this algorithm. + pub fn is_supported(self) -> bool { + true + } + + /// 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<Box<dyn Digest>> { + Ok(Box::new(NullHasher(self))) + } +} diff --git a/openpgp/src/crypto/backend/fuzzing/symmetric.rs b/openpgp/src/crypto/backend/fuzzing/symmetric.rs new file mode 100644 index 00000000..e92055e7 --- /dev/null +++ b/openpgp/src/crypto/backend/fuzzing/symmetric.rs @@ -0,0 +1,75 @@ +use crate::crypto::symmetric::Mode; + +use crate::Result; +use crate::types::SymmetricAlgorithm; + +struct NullCipher(usize); + +impl Mode for NullCipher { + fn block_size(&self) -> usize { + self.0 + } + + fn encrypt( + &mut self, + dst: &mut [u8], + src: &[u8], + ) -> Result<()> { + dst.copy_from_slice(src); + Ok(()) + } + + fn decrypt( + &mut self, + dst: &mut [u8], + src: &[u8], + ) -> Result<()> { + dst.copy_from_slice(src); + Ok(()) + } +} + +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::IDEA.is_supported()); + /// assert!(!SymmetricAlgorithm::Unencrypted.is_supported()); + /// assert!(!SymmetricAlgorithm::Private(101).is_supported()); + /// ``` + pub(crate) fn is_supported_by_backend(&self) -> bool { + true + } + + /// Creates a Nettle context for encrypting in CFB mode. + pub(crate) fn make_encrypt_cfb(self, key: &[u8], iv: Vec<u8>) + -> Result<Box<dyn Mode>> { + Ok(Box::new(NullCipher(self.block_size().unwrap_or(16)))) + } + + /// Creates a Nettle context for decrypting in CFB mode. + pub(crate) fn make_decrypt_cfb(self, key: &[u8], iv: Vec<u8>) + -> Result<Box<dyn Mode>> { + Ok(Box::new(NullCipher(self.block_size().unwrap_or(16)))) + } + + /// Creates a Nettle context for encrypting in ECB mode. + pub(crate) fn make_encrypt_ecb(self, key: &[u8]) -> Result<Box<dyn Mode>> { + Ok(Box::new(NullCipher(self.block_size().unwrap_or(16)))) + } + + /// Creates a Nettle context for decrypting in ECB mode. + pub(crate) fn make_decrypt_ecb(self, key: &[u8]) -> Result<Box<dyn Mode>> { + Ok(Box::new(NullCipher(self.block_size().unwrap_or(16)))) + } +} diff --git a/openpgp/src/crypto/ecdh.rs b/openpgp/src/crypto/ecdh.rs index 14e1ac1b..3dcf0ed2 100644 --- a/openpgp/src/crypto/ecdh.rs +++ b/openpgp/src/crypto/ecdh.rs @@ -20,6 +20,7 @@ use crate::packet::Key; use crate::types::{Curve, HashAlgorithm, PublicKeyAlgorithm, SymmetricAlgorithm}; use crate::utils::{read_be_u64, write_be_u64}; +#[allow(unused_imports)] pub(crate) use crate::crypto::backend::ecdh::{encrypt, decrypt}; /// Returns the default ECDH KDF hash function. @@ -66,7 +67,7 @@ pub(crate) fn default_ecdh_kek_cipher(curve: &Curve) -> SymmetricAlgorithm { /// `VB` is the ephemeral public key encoded appropriately as MPI /// (i.e. with the 0x40 prefix for X25519, or 0x04 for the NIST /// curves), `S` is the shared Diffie-Hellman secret. -#[allow(non_snake_case)] +#[allow(non_snake_case, dead_code)] pub(crate) fn encrypt_wrap<R>(recipient: &Key<key::PublicParts, R>, session_key: &SessionKey, VB: MPI, S: &Protected) @@ -194,6 +195,7 @@ fn kdf(x: &Protected, obits: usize, hash: HashAlgorithm, param: &[u8]) /// See [Section 8 of RFC 6637]. /// /// [Section 8 of RFC 6637]: https://tools.ietf.org/html/rfc6637#section-8 +#[allow(dead_code)] fn pkcs5_pad(sk: Protected, target_len: usize) -> Result<Protected> { if sk.len() > target_len { return Err(Error::InvalidArgument( diff --git a/openpgp/src/crypto/hash.rs b/openpgp/src/crypto/hash.rs index d22b0377..79a31f8a 100644 --- a/openpgp/src/crypto/hash.rs +++ b/openpgp/src/crypto/hash.rs @@ -194,7 +194,7 @@ impl HashAlgorithm { /// [`HashAlgorithm::is_supported`]: HashAlgorithm::is_supported() pub fn context(self) -> Result<Box<dyn Digest>> { let hasher: Box<dyn Digest> = match self { - HashAlgorithm::SHA1 => + HashAlgorithm::SHA1 if ! cfg!(feature = "crypto-fuzzing") => Box::new(crate::crypto::backend::sha1cd::build()), _ => self.new_hasher()?, }; |