diff --git a/QUALITY_AND_STYLE.md b/QUALITY_AND_STYLE.md index 2937ec7..92fabe0 100644 --- a/QUALITY_AND_STYLE.md +++ b/QUALITY_AND_STYLE.md @@ -57,6 +57,17 @@ yourself whether this function would take 6-months-from-now-you more than 10 min there comments you could add that would help future you get back up to speed faster about what this code is doing and which parts were done for a very specific reason and should not be changed on a whim. +## Naming Conventions + +All normal rust naming convensions from clippy apply. In addition, some library-specific naming conventions: + +* In constants, "LEN" is the length of a value in bytes (typically used for sizing arrays), whereas "SIZE" is a value in + bits (typically used as a security parameter). For example SHA256 could have constants `HASH_SIZE = 256` and + `HASH_LEN = 32`. +* Functions that are part of a stateful streaming api should be named `do_*()`. +* We use "pk" for public key and "sk" for secret key / private key. (some other libraries use "pub" and "priv", but " + pub" is a keyword in rust, and "pubkey / privkey" is verbose :P ) + ## APIs Where possible, primitives should expose "one-shot APIs" that simply take data and return a result as a static member diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index afb4af1..2449f9a 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -6,6 +6,8 @@ * Check the crate release checklist and run claude against the style guide (maybe Francis could cross-check me) * Run Crucible testing * Add factories for ML-DSA and ML-KEM (if we are keeping factories, see below) + * After merging the Signer/Verifier, Encrypter/Decrypter split, check if the keygen_from_rng() is still on the right + trait. * Split the Signature trait into a Signer and a Verifier so that, for example, we can implement the verifier for MTC in a different struct from the signer; or so that you can get FIPS compliance on old algorithms that are currently only FIPS-allowed for verification of existing signatures but not for creation of new ones. diff --git a/crypto/core-test-framework/src/fixed_seed_rng.rs b/crypto/core-test-framework/src/fixed_seed_rng.rs new file mode 100644 index 0000000..244efed --- /dev/null +++ b/crypto/core-test-framework/src/fixed_seed_rng.rs @@ -0,0 +1,92 @@ +//! A deterministic fake [RNG] for reproducible tests. + +use bouncycastle_core::errors::RNGError; +use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; +use bouncycastle_core::traits::{RNG, SecurityStrength}; + +/// A test-only fake [RNG] that produces a fixed, fully deterministic byte stream. +/// +/// The stream is the `SEED_LEN`-byte seed repeated indefinitely. A single internal counter is +/// shared across every [RNG] method, so each byte handed out — whether through +/// [RNG::next_bytes_out], [RNG::next_bytes], [RNG::next_int], or [RNG::fill_keymaterial_out] — +/// advances the same stream. Two instances built from the same seed therefore emit identical +/// streams, which is what makes RNG-driven operations reproducible (and comparable against their +/// seed/`m`-driven internal counterparts) in tests. +/// +/// This is a deterministic stub for tests only; it is in no way a secure RNG. +pub struct FixedSeedRNG { + seed: [u8; SEED_LEN], + counter: usize, +} + +impl FixedSeedRNG { + /// Create an instance that emits `seed` repeated indefinitely, starting from its first byte. + pub fn new(seed: [u8; SEED_LEN]) -> Self { + Self { seed, counter: 0 } + } + + /// Pull the next byte from the deterministic stream and advance the counter. + fn next_byte(&mut self) -> u8 { + let b = self.seed[self.counter % SEED_LEN]; + self.counter += 1; + b + } +} + +impl RNG for FixedSeedRNG { + /// No-op: this fake RNG ignores reseeding, since its stream is fixed by construction. + fn add_seed_keymaterial( + &mut self, + _additional_seed: &dyn KeyMaterialTrait, + ) -> Result<(), RNGError> { + Ok(()) + } + + fn next_int(&mut self) -> Result { + let mut buf = [0u8; 4]; + for slot in buf.iter_mut() { + *slot = self.next_byte(); + } + Ok(u32::from_le_bytes(buf)) + } + + fn next_bytes(&mut self, len: usize) -> Result, RNGError> { + let mut out = vec![0u8; len]; + for slot in out.iter_mut() { + *slot = self.next_byte(); + } + Ok(out) + } + + fn next_bytes_out(&mut self, out: &mut [u8]) -> Result { + for slot in out.iter_mut() { + *slot = self.next_byte(); + } + Ok(out.len()) + } + + /// Fill `out` to capacity from the stream and mark it as a full-entropy 256-bit seed, + /// mirroring what a real DRBG's `generate_keymaterial_out` produces. A 256-bit security + /// strength is enough for every ML-KEM / ML-DSA parameter set. + fn fill_keymaterial_out(&mut self, out: &mut dyn KeyMaterialTrait) -> Result { + out.allow_hazardous_operations(); + let len = { + // mut_ref_to_bytes is infallible here because we just allowed hazardous operations. + let buf = out.mut_ref_to_bytes().unwrap(); + for slot in buf.iter_mut() { + *slot = self.seed[self.counter % SEED_LEN]; + self.counter += 1; + } + buf.len() + }; + out.set_key_len(len)?; + out.set_key_type(KeyType::Seed)?; + out.set_security_strength(SecurityStrength::_256bit)?; + out.drop_hazardous_operations(); + Ok(len) + } + + fn security_strength(&self) -> SecurityStrength { + SecurityStrength::_256bit + } +} diff --git a/crypto/core-test-framework/src/kem.rs b/crypto/core-test-framework/src/kem.rs index cc16593..ef82fb0 100644 --- a/crypto/core-test-framework/src/kem.rs +++ b/crypto/core-test-framework/src/kem.rs @@ -1,3 +1,4 @@ +use crate::FixedSeedRNG; use bouncycastle_core::errors::KEMError; use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey}; @@ -36,6 +37,24 @@ impl TestFrameworkKEM { let ss1 = KEMAlg::decaps(&sk, &ct).unwrap(); assert_eq!(ss, ss1); + // Test that encaps_rng is deterministic in its RNG input: two encapsulations against the + // same public key, each fed an RNG that emits identical bytes, must produce the same + // shared secret and ciphertext. + { + let mut rng_a = FixedSeedRNG::new([0x5A; 64]); + let mut rng_b = FixedSeedRNG::new([0x5A; 64]); + let (ss_a, ct_a) = KEMAlg::encaps_rng(&pk, &mut rng_a).unwrap(); + let (ss_b, ct_b) = KEMAlg::encaps_rng(&pk, &mut rng_b).unwrap(); + assert_eq!( + ss_a, ss_b, + "encaps_rng shared secret must be deterministic given fixed RNG output" + ); + assert_eq!( + ct_a, ct_b, + "encaps_rng ciphertext must be deterministic given fixed RNG output" + ); + } + // Test non-determinism if !self.alg_is_deterministic { let (ss1, ct1) = KEMAlg::encaps(&pk).unwrap(); diff --git a/crypto/core-test-framework/src/lib.rs b/crypto/core-test-framework/src/lib.rs index 92f7215..517e80d 100644 --- a/crypto/core-test-framework/src/lib.rs +++ b/crypto/core-test-framework/src/lib.rs @@ -14,6 +14,10 @@ pub mod kdf; pub mod kem; pub mod mac; pub mod signature; +pub mod symmetric_ciphers; + +mod fixed_seed_rng; +pub use fixed_seed_rng::FixedSeedRNG; pub const DUMMY_SEED_512: &[u8; 512] = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"; diff --git a/crypto/core-test-framework/src/symmetric_ciphers.rs b/crypto/core-test-framework/src/symmetric_ciphers.rs new file mode 100644 index 0000000..0023f8e --- /dev/null +++ b/crypto/core-test-framework/src/symmetric_ciphers.rs @@ -0,0 +1,361 @@ +use crate::{DUMMY_SEED_512, DUMMY_SEED_1024}; +use bouncycastle_core::errors::SymmetricCipherError; +use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; +use bouncycastle_core::traits::{ + AEADCipher, BlockCipher, SecurityStrength, StreamCipher, SymmetricCipher, +}; + +pub struct TestFrameworkSymmetricCipher { + // Put any config options here +} + +impl TestFrameworkSymmetricCipher { + pub fn new() -> Self { + Self {} + } + + /// Test all the members of trait Hash against the given input-output pair. + /// This gives good baseline test coverage, but is not exhaustive. + pub fn test< + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + C: SymmetricCipher, + >( + &self, + ) { + let msg = b"The quick brown fox jumps over the lazy dog"; + + let key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED_512[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + + // one-shot API + let mut ct = [0u8; 1024]; + let (iv, ct_bytes_written) = C::encrypt_out(&key, msg, &mut ct).unwrap(); + assert_ne!(ct_bytes_written, 0); + + let mut pt = [0u8; 1024]; + let pt_bytes_written = C::decrypt_out(&key, iv, &ct[..ct_bytes_written], &mut pt).unwrap(); + assert_ne!(pt_bytes_written, 0); + assert_eq!(msg, &pt[..pt_bytes_written]); + + // todo -- add tests for encrypt() / decrypt() wrapped in a #[cfg(std)] + + // messing with the ciphertext does not give back the same plaintext (or failing to decrypt is also ok) + ct[17] ^= 0xFF; + match C::decrypt_out(&key, iv, &ct[..ct_bytes_written], &mut pt) { + Ok(bytes_written) => { + // so it decrypted something, but it had better not match the original plaintext + assert_eq!(bytes_written, pt_bytes_written); + assert_ne!(&pt[..bytes_written], msg); + } + Err(SymmetricCipherError::DecryptionFailed) => { /* also ok */ } + _ => panic!("Unexpected error"), + }; + + // error case: KeyMaterial of wrong type + let mac_key = + KeyMaterial::::from_bytes_as_type(&DUMMY_SEED_512[..KEY_LEN], KeyType::MACKey) + .unwrap(); + match C::encrypt_out(&mac_key, msg, &mut ct) { + Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } + _ => panic!("Unexpected error"), + }; + + // error case: security strengths too weak and too strong + let mut key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED_512[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + key.allow_hazardous_operations(); + + let security_strengths = [ + SecurityStrength::None, + SecurityStrength::_112bit, + SecurityStrength::_128bit, + SecurityStrength::_192bit, + SecurityStrength::_256bit, + ]; + for ss in security_strengths.iter() { + key.set_security_strength(ss.clone()).unwrap(); + + match C::encrypt_out(&key, msg, &mut ct) { + Ok(_) => { + if ss >= &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should have been a strong enough key"); + } + } + Err(SymmetricCipherError::KeyMaterialError(_)) => { + if ss < &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should not have accepted a key weaker than algorithm"); + } + } + _ => panic!("Unexpected error"), + }; + } + } +} + +pub struct TestFrameworkBlockCipher { + // Put any config options here +} + +impl TestFrameworkBlockCipher { + pub fn new() -> Self { + Self {} + } + + pub fn test< + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + const BLOCK_LEN: usize, + C: BlockCipher, + >( + &self, + ) { + let key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED_512[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + + // to test blocks, we'll chunk our dummy seed + let (mut encryptor, iv) = C::do_encrypt_init(&key).unwrap(); + let mut decryptor = C::do_decrypt_init(&key, &iv).unwrap(); + + for msg_chunk in DUMMY_SEED_512.as_chunks::().0.iter() { + let ct = encryptor.do_encrypt_block(msg_chunk).unwrap(); + let pt = decryptor.do_decrypt_block(&ct).unwrap(); + assert_eq!(msg_chunk, &pt); + } + + // do it again using the _out versions + + let (mut encryptor, iv) = C::do_encrypt_init(&key).unwrap(); + let mut decryptor = C::do_decrypt_init(&key, &iv).unwrap(); + + let mut ct = [0u8; BLOCK_LEN]; + let mut pt = [0u8; BLOCK_LEN]; + for msg_chunk in DUMMY_SEED_1024.as_chunks::().0.iter() { + let ct_bytes_written = encryptor.do_encrypt_block_out(msg_chunk, &mut ct).unwrap(); + assert_eq!(ct_bytes_written, BLOCK_LEN); + + let pt_bytes_written = decryptor.do_decrypt_block_out(&ct, &mut pt).unwrap(); + assert_eq!(pt_bytes_written, BLOCK_LEN); + + assert_eq!(msg_chunk, &pt); + } + + // test that the iv is random (ie not the same on two runs) + let (_encryptor, iv1) = C::do_encrypt_init(&key).unwrap(); + let (_encryptor, iv2) = C::do_encrypt_init(&key).unwrap(); + assert_ne!(iv1, iv2); + + // error case: KeyMaterial of wrong type + let mac_key = + KeyMaterial::::from_bytes_as_type(&DUMMY_SEED_512[..KEY_LEN], KeyType::MACKey) + .unwrap(); + match C::do_encrypt_init(&mac_key) { + Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } + _ => panic!("Unexpected error"), + }; + + // error case: security strengths too weak and too strong + let mut key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED_512[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + key.allow_hazardous_operations(); + + let security_strengths = [ + SecurityStrength::None, + SecurityStrength::_112bit, + SecurityStrength::_128bit, + SecurityStrength::_192bit, + SecurityStrength::_256bit, + ]; + for ss in security_strengths.iter() { + key.set_security_strength(ss.clone()).unwrap(); + + match C::do_encrypt_init(&key) { + Ok(_) => { + if ss >= &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should have been a strong enough key"); + } + } + Err(SymmetricCipherError::KeyMaterialError(_)) => { + if ss < &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should not have accepted a key weaker than algorithm"); + } + } + _ => panic!("Unexpected error"), + }; + } + } +} + +pub struct TestFrameworkAEADCipher { + // Put any config options here +} + +impl TestFrameworkAEADCipher { + pub fn new() -> Self { + Self {} + } + + /// Test all the members of trait Hash against the given input-output pair. + /// This gives good baseline test coverage, but is not exhaustive. + pub fn test< + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + C: AEADCipher, + >( + &self, + ) { + let msg = b"The quick brown fox jumps over the lazy dog"; + let aad = b"some associated data"; + + let key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED_512[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + + // one-shot API + let mut ct = [0u8; 1024]; + let (nonce, ct_bytes_written, tag) = C::aead_encrypt_out(&key, aad, msg, &mut ct).unwrap(); + if nonce.len() != 0 { + assert_ne!(nonce, [0u8; NONCE_LEN]); + } + assert_ne!(ct_bytes_written, 0); + assert_ne!(tag, [0u8; TAG_LEN]); + + let mut pt = [0u8; 1024]; + let pt_bytes_written = + C::aead_decrypt_out(&key, &nonce, aad, &ct[..ct_bytes_written], &tag, &mut pt).unwrap(); + assert_ne!(pt_bytes_written, 0); + assert_eq!(msg, &pt[..pt_bytes_written]); + + // todo -- add tests for aead_encrypt() / aead_decrypt() wrapped in a #[cfg(std)] + + // messing with the ciphertext does not give back the same plaintext (or failing to decrypt is also ok) + ct[17] ^= 0xFF; + match C::aead_decrypt_out(&key, &nonce, aad, &ct[..ct_bytes_written], &tag, &mut pt) { + Ok(bytes_written) => { + // so it decrypted something, but it had better not match the original plaintext + assert_eq!(bytes_written, pt_bytes_written); + assert_ne!(&pt[..bytes_written], msg); + } + Err(SymmetricCipherError::DecryptionFailed) => { /* also ok */ } + _ => panic!("Unexpected error"), + }; + + // messing with the aad causes the aead_decrypt to fail + match C::aead_decrypt_out( + &key, + &nonce, + b"not the right associated data", + &ct[..ct_bytes_written], + &tag, + &mut pt, + ) { + Err(SymmetricCipherError::AEADTagCheckFailed) => { /* good */ } + _ => panic!("Expected TagCheckFailed error"), + }; + + // messing with the tag causes the aead_decrypt to fail + match C::aead_decrypt_out( + &key, + &nonce, + aad, + &ct[..ct_bytes_written], + &[3u8; TAG_LEN], + &mut pt, + ) { + Err(SymmetricCipherError::AEADTagCheckFailed) => { /* good */ } + _ => panic!("Expected TagCheckFailed error"), + }; + + // multiple invocations give different nonces + let (nonce1, _ct_bytes_written, _tag) = + C::aead_encrypt_out(&key, aad, msg, &mut ct).unwrap(); + let (nonce2, _ct_bytes_written, _tag) = + C::aead_encrypt_out(&key, aad, msg, &mut ct).unwrap(); + assert_ne!(nonce1, nonce2); + + // error case: KeyMaterial of wrong type + let mac_key = + KeyMaterial::::from_bytes_as_type(&DUMMY_SEED_512[..KEY_LEN], KeyType::MACKey) + .unwrap(); + match C::encrypt_out(&mac_key, msg, &mut ct) { + Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } + _ => panic!("Unexpected error"), + }; + + // error case: security strengths too weak and too strong + let mut key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED_512[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + key.allow_hazardous_operations(); + + let security_strengths = [ + SecurityStrength::None, + SecurityStrength::_112bit, + SecurityStrength::_128bit, + SecurityStrength::_192bit, + SecurityStrength::_256bit, + ]; + for ss in security_strengths.iter() { + key.set_security_strength(ss.clone()).unwrap(); + + match C::encrypt_out(&mac_key, msg, &mut ct) { + Ok(_) => { + if ss >= &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should have been a strong enough key"); + } + } + Err(SymmetricCipherError::KeyMaterialError(_)) => { + if ss < &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should not have accepted a key weaker than algorithm"); + } + } + _ => panic!("Unexpected error"), + }; + } + } +} + +pub struct TestFrameworkStreamCipher { + // Put any config options here +} + +impl TestFrameworkStreamCipher { + pub fn new() -> Self { + Self {} + } + + /// Test all the members of trait Hash against the given input-output pair. + /// This gives good baseline test coverage, but is not exhaustive. + pub fn test< + const KEY_LEN: usize, + const INIT_DATA_LEN: usize, + C: StreamCipher, + >( + &self, + ) { + todo!() + } +} diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index b2eaf0c..f57b5e5 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -80,7 +80,32 @@ pub enum SignatureError { RNGError(RNGError), } +#[derive(Debug)] +pub enum SymmetricCipherError { + GenericError(&'static str), + AEADTagCheckFailed, + DecryptionFailed, + /// Indicates that the output buffer is not large enough to hold the requested output. + /// The usize represents the required buffer length. + IncorrectOutputBufferLength(&'static str, usize), + KeyMaterialError(KeyMaterialError), + RNGError(RNGError), + StateError(&'static str), +} + /*** Promotion functions ***/ +impl From for SymmetricCipherError { + fn from(e: KeyMaterialError) -> SymmetricCipherError { + Self::KeyMaterialError(e) + } +} + +impl From for SymmetricCipherError { + fn from(e: RNGError) -> SymmetricCipherError { + Self::RNGError(e) + } +} + impl From for HashError { fn from(e: KeyMaterialError) -> HashError { Self::KeyMaterialError(e) diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 089e282..b60b928 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -1,6 +1,8 @@ //! Provides simplified abstracted APIs over classes of cryptigraphic primitives, such as Hash, KDF, etc. -use crate::errors::{HashError, KDFError, KEMError, MACError, RNGError, SignatureError}; +use crate::errors::{ + HashError, KDFError, KEMError, MACError, RNGError, SignatureError, SymmetricCipherError, +}; use crate::key_material::KeyMaterialTrait; use core::fmt::{Debug, Display}; use core::marker::Sized; @@ -17,6 +19,230 @@ pub trait Algorithm { const MAX_SECURITY_STRENGTH: SecurityStrength; } +// todo -- split all the SymmetricCipher traits into Encryptor and Decryptor +/// The basic one-shot encrypt and decrypt that all types of symmetric ciphers must implement. +/// These are meant to be simple, easy to use, secure, and fool-proof APIs, but they may result in +/// ciphertexts that are incompatible with other implementations as ciphers in more complex modes, such +/// as AEADs or stream ciphers may need to stick extra data either at the beginning or end of the ciphertext. +/// See the documentation of the underlying implementation for more details. +pub trait SymmetricCipher: + Algorithm + Secret +{ + #[cfg(std)] + /// A one-shot API to encrypt some plaintext with the given key. + /// This function returns the ciphertext as a Vec, and therefore is only available when compiling with std. + /// Returns a tuple containing the initialization data and the ciphertext. + /// This is not available if building for no_std. + fn encrypt( + key: &KeyMaterial, + plaintext: &[u8], + ) -> Result<([u8; INIT_DATA_LEN], Vec), SymmetricCipherError>; + /// A one-shot API to encrypt some plaintext with the given key. + /// This function takes a reference to the output buffer for the ciphertext, and is therefore available in no_std. + /// See the documentation for the underlying implementation for details on providing a ciphertext buffer of sufficient size; + /// typically the ciphertext is the same length as the plaintext, but some ciphers may have an expansion factor or require + /// extra space for a nonce or tag. + /// Returns a tuple containing the initialization data and the number of bytes written to the ciphertext buffer. + fn encrypt_out( + key: &KeyMaterial, + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result<([u8; INIT_DATA_LEN], usize), SymmetricCipherError>; + #[cfg(std)] + /// A one-shot API to decrypt some ciphertext with the given key. + /// This function returns the ciphertext as a Vec, and therefore is only available when compiling with std. + /// This is not available if building for no_std. + fn decrypt( + key: &KeyMaterial, + init_data: [u8; INIT_DATA_LEN], + ciphertext: &[u8], + ) -> Result, SymmetricCipherError>; + /// A one-shot API to decrypt some ciphertext with the given key. + /// This function takes a reference to the output buffer for the plaintext, and is therefore available in no_std. + /// See the documentation for the underlying implementation for details on providing a plaintext buffer of sufficient size; + /// typically the ciphertext is the same length as the plaintext, but some ciphers may have an expansion factor or require + /// extra space for a nonce or tag. + /// Returns a tuple containing the initialization data and the number of bytes written to the plaintext buffer. + fn decrypt_out( + key: &KeyMaterial, + init_data: [u8; INIT_DATA_LEN], + ciphertext: &[u8], + plaintext: &mut [u8], + ) -> Result; +} + +/// The basic functions of a block cipher. +/// This trait allows for a block cipher to generate initialization data, such as an Initialization Vector (IV) or Counter (CTR) +/// which is not technically part of the ciphertext, but must be transmitted along with the ciphertext in order for the +/// recipient to perform successful decryption. The length of the initialization data is specified by the implementing struct +/// via the `INIT_DATA_SIZE` constant. +/// In order for these one-shot APIs to be usable securely in all contexts, the init data will be generated +/// securely by the block cipher implementation and returned along with the ciphertext, and there is no API for the +/// user to provide the init data. If you require this functionality, see the documentation for the underlying implementation. +pub trait BlockCipher: + SymmetricCipher + Sized +{ + /// Constructor that begins a flow of the streaming API for encrypting one block at a time. + /// Allows for the implementation to return init data such as an IV which is generated prior to encrypting the first block. + fn do_encrypt_init( + key: &KeyMaterial, + ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError>; + /// Encrypts a single block of plaintext. + fn do_encrypt_block( + &mut self, + plaintext: &[u8; BLOCK_LEN], + ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>; + /// Encrypts a single block of plaintext and writes the ciphertext to the provided buffer. + fn do_encrypt_block_out( + &mut self, + plaintext: &[u8; BLOCK_LEN], + ciphertext: &mut [u8; BLOCK_LEN], + ) -> Result; + /// Encrypts the final block of plaintext. + fn do_encrypt_final( + &mut self, + plaintext: &[u8; BLOCK_LEN], + ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>; + /// Decrypts the final block of plaintext and writes the ciphertext to the provided buffer. + fn do_encrypt_final_out( + &mut self, + plaintext: &[u8; BLOCK_LEN], + ciphertext: &mut [u8; BLOCK_LEN], + ) -> Result; + /// Constructor that begins a flow of the streaming API for decryption one block at a time. + fn do_decrypt_init( + key: &KeyMaterial, + init_data: &[u8; INIT_DATA_LEN], + ) -> Result; + /// Decrypts a single block of ciphertext. + fn do_decrypt_block( + &mut self, + ciphertext: &[u8; BLOCK_LEN], + ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>; + /// Decrypts a single block of ciphertext and writes the plaintext to the provided buffer. + fn do_decrypt_block_out( + &mut self, + ciphertext: &[u8; BLOCK_LEN], + plaintext: &mut [u8; BLOCK_LEN], + ) -> Result; +} + +/// The basic functions of an Authenticated Encryption with Addititional Data cipher. +pub trait AEADCipher: + SymmetricCipher + Sized +{ + #[cfg(std)] + /// A one-shot API to encrypt some plaintext with the given key. + /// A distinguishing feature of AEAD ciphers is the ability to provide additional authenticated data (AAD) + /// that is not encrypted but is protected by the authentication tag; ie it can be sent along with the ciphertext + /// and any tampering with it will result in the decryption operation failing the tag check. + /// This function returns the ciphertext as a Vec, and therefore is only available when compiling with std. + /// Returns a tuple containing a generated nonce, the ciphertext and the tag. + fn aead_encrypt( + key: &KeyMaterial, + aad: &[u8], + plaintext: &[u8], + ) -> Result<([u8; NONCE_LEN], Vec, [u8; TAG_LEN]), SymmetricCipherError>; + /// A one-shot API to encrypt some plaintext with the given key. + /// A distinguishing feature of AEAD ciphers is the ability to provide additional authenticated data (AAD) + /// that is not encrypted but is protected by the authentication tag; ie it can be sent along with the ciphertext + /// and any tampering with it will result in the decryption operation failing the tag check. + /// Returns a tuple containing the randomly-generated nonce, number of bytes written to the ciphertext buffer, and the tag. + /// If you need a deterministic mode where you feed in the nonce, use the streaming API of [BlockCipher] + /// or [StreamCipher] as appropriate and feed the nonce into the IV field. + fn aead_encrypt_out( + key: &KeyMaterial, + aad: &[u8], + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result<([u8; NONCE_LEN], usize, [u8; TAG_LEN]), SymmetricCipherError>; + /// All AEAD ciphers will also be either a [BlockCipher] or a [StreamCipher], and so will already + /// have a streaming API. + /// This allows you to finish either style of streaming API flow with AEAD specific do_final() + /// that computes and returns the authentication tag. + fn do_aead_encrypt_final(self) -> Result<[u8; TAG_LEN], SymmetricCipherError>; + #[cfg(std)] + /// A one-shot API to decrypt some ciphertext with the given key. + /// This function returns the ciphertext as a Vec, and therefore is only available when compiling with std. + fn aead_decrypt( + key: &KeyMaterial, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + ciphertext: &[u8], + tag: &[u8; TAG_LEN], + ) -> Result, SymmetricCipherError>; + /// A one-shot API to decrypt some ciphertext with the given key. + /// This function takes a reference to the output buffer for the plaintext, and is therefore available in no_std. + /// See the documentation for the underlying implementation for details on providing a plaintext buffer of sufficient size; + /// typically the ciphertext is the same length as the plaintext, but some ciphers may have an expansion factor or require + /// extra space for a nonce or tag. + /// Returns a tuple containing the initialization data and the number of bytes written to the plaintext buffer. + fn aead_decrypt_out( + key: &KeyMaterial, + nonce: &[u8; NONCE_LEN], + aad: &[u8], + ciphertext: &[u8], + tag: &[u8], + plaintext: &mut [u8], + ) -> Result; + /// All AEAD ciphers will also be either a [BlockCipher] or a [StreamCipher], and so will already + /// have a streaming API. + /// This allows you to finish either style of streaming API flow with AEAD specific do_final() + /// that computes and returns the authentication tag. + fn do_aead_decrypt_final(self, tag: &[u8]) -> Result<(), SymmetricCipherError>; +} + +/// The basic functions of a stream cipher, which differ from those of a block cipher only in that +/// a stream cipher is assumed to have no underlying block size tied to the implementation, and so the caller gets to specify +/// the block size for the streaming APIs. +pub trait StreamCipher: + SymmetricCipher + Sized +{ + /// Constructor that begins a flow of the streaming API for encrypting one block at a time. + /// Allows for the implementation to return init data such as an IV which is generated prior to encrypting the first block. + fn do_stream_encrypt_init( + key: &KeyMaterial, + ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError>; + /// Encrypts a single block of plaintext. + fn do_stream_encrypt_block( + &mut self, + plaintext: &[u8; BLOCK_LEN], + ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>; + /// Encrypts a single block of plaintext and writes the ciphertext to the provided buffer. + fn do_stream_encrypt_block_out( + &mut self, + plaintext: &[u8; BLOCK_LEN], + ciphertext: &mut [u8; BLOCK_LEN], + ) -> Result<(), SymmetricCipherError>; + /// Encrypts the final block of plaintext. + fn do_stream_encrypt_final( + &mut self, + plaintext: &[u8; BLOCK_LEN], + ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>; + /// Encrypts the final block of plaintext and writes the ciphertext to the provided buffer. + fn do_stream_encrypt_final_out( + &mut self, + plaintext: &[u8; BLOCK_LEN], + ciphertext: &mut [u8; BLOCK_LEN], + ) -> Result<(), SymmetricCipherError>; + /// Constructor that begins a flow of the streaming API for decryption one block at a time. + fn do_stream_decrypt_init( + key: &KeyMaterial, + init_data: &[u8; INIT_DATA_LEN], + ) -> Result; + /// Decrypts a single block of ciphertext. + fn do_stream_decrypt_block( + &mut self, + ciphertext: &[u8; BLOCK_LEN], + ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>; + /// Decrypts a single block of ciphertext and writes the plaintext to the provided buffer. + fn do_stream_decrypt_block_out( + &mut self, + ciphertext: &[u8; BLOCK_LEN], + plaintext: &mut [u8; BLOCK_LEN], + ) -> Result<(), SymmetricCipherError>; +} + pub trait Hash: Default { /// The size of the internal block in bits -- needed by functions such as HMAC to compute security parameters. fn block_bitlen(&self) -> usize; @@ -37,19 +263,17 @@ pub trait Hash: Default { /// Provide a chunk of data to be absorbed into the hashes. /// `data` can be of any length, including zero bytes. /// do_update() is intended to be used as part of a streaming interface, and so may by called multiple times. - // fn do_update(&mut self, data: &[u8]) -> Result<(), HashError>; fn do_update(&mut self, data: &[u8]); /// Finish absorbing input and produce the hashes output. /// Consumes self, so this must be the final call to this object. - // fn do_final(self) -> Result, HashError>; fn do_final(self) -> Vec; /// Finish absorbing input and produce the hashes output. /// Consumes self, so this must be the final call to this object. /// /// If the provided buffer is smaller than the hash's output length, the output will be truncated. - /// If the provided buffor is larger than the hash's output length, the output will be placed in + /// If the provided buffer is larger than the hash's output length, the output will be placed in /// the first [Hash::output_len] bytes. /// The entire output buffer is zeroized before the hash output is written, so any bytes past /// [Hash::output_len] will be 0. @@ -192,6 +416,7 @@ pub trait KEM< const SS_LEN: usize, >: Sized { + // todo -- if keygen stays in this trait, then we also need a keygen_from_rng /// Generate a keypair. /// Error condition: Basically only on RNG failures fn keygen() -> Result<(PK, SK), KEMError>; @@ -200,6 +425,14 @@ pub trait KEM< /// Returns the ciphertext and derived shared secret. fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; + /// Performs an encapsulation against the given public key. + /// Returns the ciphertext and derived shared secret. + /// All entropy required for the encapsulation is sourced from the given RNG. + fn encaps_rng( + pk: &PK, + rng: &mut dyn RNG, + ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; + /// Performs a decapsulation of the given ciphertext. /// Returns the derived shared secret. fn decaps(sk: &SK, ct: &[u8]) -> Result, KEMError>; @@ -393,13 +626,18 @@ impl SecurityStrength { /// be used by applications that intend to submit to FIPS certification as it more closely aligns with the /// requirements of SP 800-90A. /// Note: this interface produces bytes. If you want a [KeyMaterialTrait], then use [KeyMaterial::from_rng]. -pub trait RNG: Default { +/// +/// Implementors are expected to also implement [Default] (default-construction should produce a +/// securely OS-seeded instance), but this is intentionally *not* a supertrait bound: requiring +/// `Default` would make `RNG` not dyn-compatible, and `&mut dyn RNG` is needed so RNG instances +/// can be handed around as trait objects. +pub trait RNG { // TODO: add back once we figure out streaming interaction with entropy sources. // fn add_seed_bytes(&mut self, additional_seed: &[u8]) -> Result<(), RNGError>; fn add_seed_keymaterial( &mut self, - additional_seed: impl KeyMaterialTrait, + additional_seed: &dyn KeyMaterialTrait, ) -> Result<(), RNGError>; fn next_int(&mut self) -> Result; @@ -410,7 +648,7 @@ pub trait RNG: Default { /// The entire output buffer is zeroized before the random bytes are written. fn next_bytes_out(&mut self, out: &mut [u8]) -> Result; - fn fill_keymaterial_out(&mut self, out: &mut impl KeyMaterialTrait) -> Result; + fn fill_keymaterial_out(&mut self, out: &mut dyn KeyMaterialTrait) -> Result; /// Returns the Security Strength of this RNG. fn security_strength(&self) -> SecurityStrength; @@ -504,6 +742,7 @@ pub trait Signature< const SIG_LEN: usize, >: Sized { + // todo -- if keygen stays in this trait, then we also need a keygen_from_rng /// Generate a keypair. /// Error condition: Basically only on RNG failures fn keygen() -> Result<(PK, SK), SignatureError>; diff --git a/crypto/factory/src/rng_factory.rs b/crypto/factory/src/rng_factory.rs index 9f1b8e0..aa4bcb6 100644 --- a/crypto/factory/src/rng_factory.rs +++ b/crypto/factory/src/rng_factory.rs @@ -95,7 +95,7 @@ impl AlgorithmFactory for RNGFactory { impl RNG for RNGFactory { fn add_seed_keymaterial( &mut self, - additional_seed: impl KeyMaterialTrait, + additional_seed: &dyn KeyMaterialTrait, ) -> Result<(), RNGError> { match self { Self::HashDRBG_SHA256(rng) => rng.add_seed_keymaterial(additional_seed), @@ -126,7 +126,7 @@ impl RNG for RNGFactory { } } - fn fill_keymaterial_out(&mut self, out: &mut impl KeyMaterialTrait) -> Result { + fn fill_keymaterial_out(&mut self, out: &mut dyn KeyMaterialTrait) -> Result { match self { Self::HashDRBG_SHA256(rng) => rng.fill_keymaterial_out(out), Self::HashDRBG_SHA512(rng) => rng.fill_keymaterial_out(out), diff --git a/crypto/mldsa-lowmemory/src/mldsa.rs b/crypto/mldsa-lowmemory/src/mldsa.rs index dc6ae2e..bd49d9c 100644 --- a/crypto/mldsa-lowmemory/src/mldsa.rs +++ b/crypto/mldsa-lowmemory/src/mldsa.rs @@ -718,12 +718,6 @@ impl< GAMMA1_MASK_LEN, > { - /// Should still be ok in FIPS mode - pub fn keygen_from_os_rng() -> Result<(PK, SK), SignatureError> { - let mut seed = KeyMaterial::<32>::new(); - HashDRBG_SHA512::new_from_os().fill_keymaterial_out(&mut seed)?; - Self::keygen_internal(&seed) - } /// Performs the first step of key generation to transform the single provided seed into a set of internal intermediate seeds. /// /// Unlike other interfaces across the library that take an &impl KeyMaterial, this one @@ -1326,6 +1320,13 @@ pub trait MLDSATrait< const ETA: usize, >: Sized { + /// Run a keygen using the provided RNG implementation. + // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG. + fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), SignatureError> { + let mut seed = KeyMaterial::<32>::new(); + rng.fill_keymaterial_out(&mut seed)?; + Self::keygen_from_seed(&seed) + } /// Imports a secret key from a seed. fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError>; /// Imports a secret key from both a seed and an encoded_sk. @@ -1573,8 +1574,13 @@ impl< GAMMA1_MASK_LEN, > { + /// Runs a key generation using the library's default RNG, seeded from the OS. + /// In environments where the default OS based RNG is not available, use instead [MLDSA::keygen_from_rng] + /// and explicitly provide a [RNG] implementation, or use [MLDSATrait::keygen_from_seed] and provide the + /// private key seed directly. fn keygen() -> Result<(PK, SK), SignatureError> { - Self::keygen_from_os_rng() + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::keygen_from_rng(&mut os_rng) } fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> { diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index b3eb8c3..6aa91a5 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -8,6 +8,7 @@ mod mldsa_tests { RNG, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; + use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::{ @@ -18,6 +19,41 @@ mod mldsa_tests { use bouncycastle_mldsa_lowmemory::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; use bouncycastle_mldsa_lowmemory::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; use bouncycastle_mldsa_lowmemory::{MLDSAPrivateKeyTrait, MLDSAPublicKeyTrait, MLDSATrait}; + + #[test] + fn keygen_from_rng_matches_keygen_from_seed() { + // Same arbitrary fixed seed as rfc9881_keygen. + let seed_bytes: [u8; 32] = + hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + .try_into() + .unwrap(); + + // The seed as fed directly to keygen_from_seed. + let seed = KeyMaterial256::from_bytes_as_type(&seed_bytes, KeyType::Seed).unwrap(); + + // ML-DSA-44 + let (pk_seed, sk_seed) = MLDSA44::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLDSA44::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-DSA-44 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-DSA-44 sk from RNG must match sk from seed"); + + // ML-DSA-65 + let (pk_seed, sk_seed) = MLDSA65::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLDSA65::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-DSA-65 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-DSA-65 sk from RNG must match sk from seed"); + + // ML-DSA-87 + let (pk_seed, sk_seed) = MLDSA87::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLDSA87::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-DSA-87 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-DSA-87 sk from RNG must match sk from seed"); + } + #[test] fn test_framework_signature() { let tf = TestFrameworkSignature::new(false, true); diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index dd8eb0f..caafa82 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -728,12 +728,6 @@ impl< GAMMA1_MASK_LEN, > { - /// Should still be ok in FIPS mode - pub fn keygen_from_os_rng() -> Result<(PK, SK), SignatureError> { - let mut seed = KeyMaterial256::new(); - HashDRBG_SHA512::new_from_os().fill_keymaterial_out(&mut seed)?; - Self::keygen_internal(&seed) - } /// Implements Algorithm 6 of FIPS 204 /// Note: NIST has made a special exception in the FIPS 204 FAQ that this _internal function /// may in fact be exposed outside the crypto module. @@ -1651,6 +1645,13 @@ pub trait MLDSATrait< const ETA: usize, >: Sized { + /// Run a keygen using the provided RNG implementation. + // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG. + fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), SignatureError> { + let mut seed = KeyMaterial256::new(); + rng.fill_keymaterial_out(&mut seed)?; + Self::keygen_from_seed(&seed) + } /// Imports a secret key from a seed. fn keygen_from_seed(seed: &KeyMaterial<32>) -> Result<(PK, SK), SignatureError>; /// Imports a secret key from both a seed and an encoded_sk. @@ -1923,8 +1924,13 @@ impl< GAMMA1_MASK_LEN, > { + /// Runs a key generation using the library's default RNG, seeded from the OS. + /// In environments where the default OS based RNG is not available, use instead [MLDSA::keygen_from_rng] + /// and explicitly provide a [RNG] implementation, or use [MLDSATrait::keygen_from_seed] and provide the + /// private key seed directly. fn keygen() -> Result<(PK, SK), SignatureError> { - Self::keygen_from_os_rng() + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::keygen_from_rng(&mut os_rng) } fn sign(sk: &SK, msg: &[u8], ctx: Option<&[u8]>) -> Result<[u8; SIG_LEN], SignatureError> { diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index b5a1b5f..b25d23a 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -8,6 +8,7 @@ mod mldsa_tests { RNG, SecurityStrength, Signature, SignaturePrivateKey, SignaturePublicKey, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; + use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ @@ -198,6 +199,40 @@ mod mldsa_tests { } } + #[test] + fn keygen_from_rng_matches_keygen_from_seed() { + // Same arbitrary fixed seed as rfc9881_keygen. + let seed_bytes: [u8; 32] = + hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + .try_into() + .unwrap(); + + // The seed as fed directly to keygen_from_seed. + let seed = KeyMaterial256::from_bytes_as_type(&seed_bytes, KeyType::Seed).unwrap(); + + // ML-DSA-44 + let (pk_seed, sk_seed) = MLDSA44::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLDSA44::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-DSA-44 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-DSA-44 sk from RNG must match sk from seed"); + + // ML-DSA-65 + let (pk_seed, sk_seed) = MLDSA65::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLDSA65::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-DSA-65 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-DSA-65 sk from RNG must match sk from seed"); + + // ML-DSA-87 + let (pk_seed, sk_seed) = MLDSA87::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLDSA87::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-DSA-87 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-DSA-87 sk from RNG must match sk from seed"); + } + #[test] fn keygen_error_cases() { /* diff --git a/crypto/mlkem-lowmemory/src/mlkem.rs b/crypto/mlkem-lowmemory/src/mlkem.rs index 3d75cb3..299efbd 100644 --- a/crypto/mlkem-lowmemory/src/mlkem.rs +++ b/crypto/mlkem-lowmemory/src/mlkem.rs @@ -233,13 +233,6 @@ impl< T_PACKED_LEN, > { - /// Should still be ok in FIPS mode - pub fn keygen_from_os_rng() -> Result<(PK, SK), KEMError> { - let mut seed = KeyMaterial::<64>::new(); - HashDRBG_SHA512::new_from_os().fill_keymaterial_out(&mut seed)?; - // Self::keygen_internal(&seed) - Self::keygen_internal(&seed) - } /// Performs the first step of key generation to transform the single provided seed into a set of internal intermediate seeds. /// /// Unlike other interfaces across the library that take an &impl KeyMaterial, this one @@ -618,6 +611,13 @@ pub trait MLKEMTrait< const T_PACKED_LEN: usize, >: Sized { + /// Run a keygen using the provided RNG implementation. + // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG. + fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), KEMError> { + let mut seed = KeyMaterial::<64>::new(); + rng.fill_keymaterial_out(&mut seed)?; + Self::keygen_from_seed(&seed) + } /// Imports a secret key from a seed. fn keygen_from_seed(seed: &KeyMaterial<64>) -> Result<(PK, SK), KEMError>; /// Imports a secret key from both a seed and an encoded_sk. @@ -676,12 +676,21 @@ impl< { /// Generates a fresh key pair. fn keygen() -> Result<(PK, SK), KEMError> { - Self::keygen_from_os_rng() + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::keygen_from_rng(&mut os_rng) } fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::encaps_rng(pk, &mut os_rng) + } + + fn encaps_rng( + pk: &PK, + rng: &mut dyn RNG, + ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { let mut m = [0u8; 32]; - HashDRBG_SHA512::new_from_os().next_bytes_out(&mut m)?; + rng.next_bytes_out(&mut m)?; let (ss_bytes, ct) = Self::encaps_internal(pk, m); diff --git a/crypto/mlkem-lowmemory/tests/mlkem_tests.rs b/crypto/mlkem-lowmemory/tests/mlkem_tests.rs index ff8cc5c..53c4395 100644 --- a/crypto/mlkem-lowmemory/tests/mlkem_tests.rs +++ b/crypto/mlkem-lowmemory/tests/mlkem_tests.rs @@ -4,6 +4,7 @@ mod mlkem_tests { use bouncycastle_core::errors::KEMError; use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength, XOF}; + use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_hex as hex; use bouncycastle_mlkem_lowmemory::mlkem::{ MLKEM512_FULL_SK_LEN, MLKEM768_FULL_SK_LEN, MLKEM1024_FULL_SK_LEN, @@ -20,6 +21,42 @@ mod mlkem_tests { MLKEM1024PrivateKey, MLKEM1024PublicKey, }; use bouncycastle_mlkem_lowmemory::{MLKEMPrivateKeyTrait, MLKEMTrait}; + + #[test] + fn keygen_from_rng_matches_keygen_from_seed() { + // An arbitrary fixed 64-byte seed (bytes 0x00..=0x3f). + let seed_bytes: [u8; 64] = hex::decode( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\ + 202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + ) + .unwrap() + .try_into() + .unwrap(); + + // The seed as fed directly to keygen_from_seed. + let seed = KeyMaterial512::from_bytes_as_type(&seed_bytes, KeyType::Seed).unwrap(); + + // ML-KEM-512 + let (pk_seed, sk_seed) = MLKEM512::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLKEM512::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-KEM-512 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-KEM-512 sk from RNG must match sk from seed"); + + // ML-KEM-768 + let (pk_seed, sk_seed) = MLKEM768::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLKEM768::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-KEM-768 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-KEM-768 sk from RNG must match sk from seed"); + + // ML-KEM-1024 + let (pk_seed, sk_seed) = MLKEM1024::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLKEM1024::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-KEM-1024 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-KEM-1024 sk from RNG must match sk from seed"); + } use bouncycastle_sha3::SHAKE256; // #[test] @@ -550,6 +587,62 @@ mod mlkem_tests { _ => panic!("Expected error for different key"), }; } + + /// Proves that `encaps_rng` is just `encaps_internal` with the message `m` sourced from the + /// RNG: when the RNG hands back exactly the bytes that `m` would be, the two must produce the + /// same shared secret and ciphertext. + /// + /// Determinism of `encaps_rng` itself is covered by the shared KEM test + /// framework via `core_framework_tests`.) + #[test] + fn encaps_rng_matches_encaps_internal() { + // An arbitrary fixed 64-byte seed; FixedSeedRNG::next_bytes_out hands back its leading + // 32 bytes as the encapsulation message `m`. + let seed_bytes: [u8; 64] = hex::decode( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\ + 202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + ) + .unwrap() + .try_into() + .unwrap(); + let m: [u8; MLKEM_RND_LEN] = seed_bytes[..MLKEM_RND_LEN].try_into().unwrap(); + + // ML-KEM-512 + let (pk, _sk) = MLKEM512::keygen().unwrap(); + let (ss_ref, ct_ref) = MLKEM512::encaps_internal(&pk, m); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (ss, ct) = MLKEM512::encaps_rng(&pk, &mut rng).unwrap(); + assert_eq!(ct, ct_ref, "ML-KEM-512 ciphertext must match encaps_internal"); + assert_eq!( + ss_ref, + ss.ref_to_bytes(), + "ML-KEM-512 shared secret must match encaps_internal" + ); + + // ML-KEM-768 + let (pk, _sk) = MLKEM768::keygen().unwrap(); + let (ss_ref, ct_ref) = MLKEM768::encaps_internal(&pk, m); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (ss, ct) = MLKEM768::encaps_rng(&pk, &mut rng).unwrap(); + assert_eq!(ct, ct_ref, "ML-KEM-768 ciphertext must match encaps_internal"); + assert_eq!( + ss_ref, + ss.ref_to_bytes(), + "ML-KEM-768 shared secret must match encaps_internal" + ); + + // ML-KEM-1024 + let (pk, _sk) = MLKEM1024::keygen().unwrap(); + let (ss_ref, ct_ref) = MLKEM1024::encaps_internal(&pk, m); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (ss, ct) = MLKEM1024::encaps_rng(&pk, &mut rng).unwrap(); + assert_eq!(ct, ct_ref, "ML-KEM-1024 ciphertext must match encaps_internal"); + assert_eq!( + ss_ref, + ss.ref_to_bytes(), + "ML-KEM-1024 shared secret must match encaps_internal" + ); + } } // struct Kat { diff --git a/crypto/mlkem/src/mlkem.rs b/crypto/mlkem/src/mlkem.rs index bd31e43..55dae1e 100644 --- a/crypto/mlkem/src/mlkem.rs +++ b/crypto/mlkem/src/mlkem.rs @@ -319,12 +319,6 @@ impl< const LAMBDA: i16, > MLKEM { - /// Should still be ok in FIPS mode - pub fn keygen_from_os_rng() -> Result<(PK, SK), KEMError> { - let mut seed = KeyMaterial::<64>::new(); - HashDRBG_SHA512::new_from_os().fill_keymaterial_out(&mut seed)?; - Self::keygen_internal(&seed) - } /// Algorithm 16 ML-KEM.KeyGen_internal(𝑑, 𝑧) /// Uses randomness to generate an encapsulation key and a corresponding decapsulation key. /// Input: randomness 𝑑 ∈ 𝔹32 . @@ -759,9 +753,17 @@ impl< fn encaps_for_expanded_key( pk: &MLKEMPublicKeyExpanded, + ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::encaps_for_expanded_key_rng(pk, &mut os_rng) + } + + fn encaps_for_expanded_key_rng( + pk: &MLKEMPublicKeyExpanded, + rng: &mut dyn RNG, ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { let mut m = [0u8; 32]; - HashDRBG_SHA512::new_from_os().next_bytes_out(&mut m)?; + rng.next_bytes_out(&mut m)?; let (ss, ct) = Self::encaps_internal(&pk.ek, Some(&pk.A_hat), m); @@ -821,6 +823,13 @@ pub trait MLKEMTrait< const LAMBDA: i16, >: Sized { + /// Run a keygen using the provided RNG implementation. + // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG. + fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), KEMError> { + let mut seed = KeyMaterial::<64>::new(); + rng.fill_keymaterial_out(&mut seed)?; + Self::keygen_from_seed(&seed) + } /// Imports a secret key from a seed. fn keygen_from_seed(seed: &KeyMaterial<64>) -> Result<(PK, SK), KEMError>; /// Imports a secret key from both a seed and an encoded_sk. @@ -848,6 +857,12 @@ pub trait MLKEMTrait< pk: &MLKEMPublicKeyExpanded, ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; + /// Same as [KEM::encaps], but acts on an [MLKEMPublicKeyExpanded] and uses a provided RNG. + fn encaps_for_expanded_key_rng( + pk: &MLKEMPublicKeyExpanded, + rng: &mut dyn RNG, + ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>; + /// Same as [KEM::decaps], but acts on an [MLKEMPrivateKeyExpanded]. fn decaps_with_expanded_key( sk: &MLKEMPrivateKeyExpanded, @@ -873,7 +888,8 @@ impl< { /// Generates a fresh key pair. fn keygen() -> Result<(PK, SK), KEMError> { - Self::keygen_from_os_rng() + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::keygen_from_rng(&mut os_rng) } /// Performs an encapsulation against the given public key, using the library's default internal RNG. @@ -887,7 +903,15 @@ impl< /// Output: shared secret key 𝐾 ∈ 𝔹32 . /// Output: ciphertext 𝑐 ∈ 𝔹32(𝑑𝑢𝑘+𝑑𝑣). fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { - Self::encaps_for_expanded_key(&MLKEMPublicKeyExpanded::::from(pk)) + let mut os_rng = HashDRBG_SHA512::new_from_os(); + Self::encaps_rng(pk, &mut os_rng) + } + + fn encaps_rng( + pk: &PK, + rng: &mut dyn RNG, + ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError> { + Self::encaps_for_expanded_key_rng(&MLKEMPublicKeyExpanded::::from(pk), rng) } /// Performs a decapsulation of the given ciphertext. diff --git a/crypto/mlkem/tests/mlkem_tests.rs b/crypto/mlkem/tests/mlkem_tests.rs index c24abd2..e8cd160 100644 --- a/crypto/mlkem/tests/mlkem_tests.rs +++ b/crypto/mlkem/tests/mlkem_tests.rs @@ -4,6 +4,7 @@ mod mlkem_tests { use bouncycastle_core::errors::KEMError; use bouncycastle_core::key_material::{KeyMaterial512, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{KEM, KEMPrivateKey, KEMPublicKey, SecurityStrength, XOF}; + use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_hex as hex; use bouncycastle_mlkem::{MLKEM_RND_LEN, MLKEM512, MLKEM768, MLKEM1024, Polynomial}; use bouncycastle_mlkem::{ @@ -17,6 +18,42 @@ mod mlkem_tests { use bouncycastle_mlkem::{MLKEMPrivateKeyTrait, MLKEMTrait}; use bouncycastle_sha3::SHAKE256; + #[test] + fn keygen_from_rng_matches_keygen_from_seed() { + // An arbitrary fixed 64-byte seed (bytes 0x00..=0x3f). + let seed_bytes: [u8; 64] = hex::decode( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\ + 202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + ) + .unwrap() + .try_into() + .unwrap(); + + // The seed as fed directly to keygen_from_seed. + let seed = KeyMaterial512::from_bytes_as_type(&seed_bytes, KeyType::Seed).unwrap(); + + // ML-KEM-512 + let (pk_seed, sk_seed) = MLKEM512::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLKEM512::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-KEM-512 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-KEM-512 sk from RNG must match sk from seed"); + + // ML-KEM-768 + let (pk_seed, sk_seed) = MLKEM768::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLKEM768::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-KEM-768 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-KEM-768 sk from RNG must match sk from seed"); + + // ML-KEM-1024 + let (pk_seed, sk_seed) = MLKEM1024::keygen_from_seed(&seed).unwrap(); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (pk_rng, sk_rng) = MLKEM1024::keygen_from_rng(&mut rng).unwrap(); + assert_eq!(pk_rng, pk_seed, "ML-KEM-1024 pk from RNG must match pk from seed"); + assert_eq!(sk_rng, sk_seed, "ML-KEM-1024 sk from RNG must match sk from seed"); + } + // #[test] // fn generate_kats_for_low_mem() { // let seed = KeyMaterial256::from_bytes_as_type( @@ -633,6 +670,66 @@ mod mlkem_tests { }; assert_eq!(ss, ss1); } + + /// Proves that `encaps_for_expanded_key_rng` is just `encaps_internal` with the message `m` + /// sourced from the RNG: when the RNG hands back exactly the bytes that `m` would be, the two + /// must produce the same shared secret and ciphertext. + #[test] + fn encaps_for_expanded_key_rng_matches_encaps_internal() { + use bouncycastle_mlkem::{ + MLKEM512PublicKeyExpanded, MLKEM768PublicKeyExpanded, MLKEM1024PublicKeyExpanded, + }; + + // An arbitrary fixed 64-byte seed; FixedSeedRNG::next_bytes_out hands back its leading + // 32 bytes as the encapsulation message `m`. + let seed_bytes: [u8; 64] = hex::decode( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\ + 202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", + ) + .unwrap() + .try_into() + .unwrap(); + let m: [u8; MLKEM_RND_LEN] = seed_bytes[..MLKEM_RND_LEN].try_into().unwrap(); + + // ML-KEM-512 + let (pk, _sk) = MLKEM512::keygen().unwrap(); + let (ss_ref, ct_ref) = MLKEM512::encaps_internal(&pk, None, m); + let pk_expanded = MLKEM512PublicKeyExpanded::from(&pk); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (ss, ct) = MLKEM512::encaps_for_expanded_key_rng(&pk_expanded, &mut rng).unwrap(); + assert_eq!(ct, ct_ref, "ML-KEM-512 ciphertext must match encaps_internal"); + assert_eq!( + ss_ref, + ss.ref_to_bytes(), + "ML-KEM-512 shared secret must match encaps_internal" + ); + + // ML-KEM-768 + let (pk, _sk) = MLKEM768::keygen().unwrap(); + let (ss_ref, ct_ref) = MLKEM768::encaps_internal(&pk, None, m); + let pk_expanded = MLKEM768PublicKeyExpanded::from(&pk); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (ss, ct) = MLKEM768::encaps_for_expanded_key_rng(&pk_expanded, &mut rng).unwrap(); + assert_eq!(ct, ct_ref, "ML-KEM-768 ciphertext must match encaps_internal"); + assert_eq!( + ss_ref, + ss.ref_to_bytes(), + "ML-KEM-768 shared secret must match encaps_internal" + ); + + // ML-KEM-1024 + let (pk, _sk) = MLKEM1024::keygen().unwrap(); + let (ss_ref, ct_ref) = MLKEM1024::encaps_internal(&pk, None, m); + let pk_expanded = MLKEM1024PublicKeyExpanded::from(&pk); + let mut rng = FixedSeedRNG::new(seed_bytes); + let (ss, ct) = MLKEM1024::encaps_for_expanded_key_rng(&pk_expanded, &mut rng).unwrap(); + assert_eq!(ct, ct_ref, "ML-KEM-1024 ciphertext must match encaps_internal"); + assert_eq!( + ss_ref, + ss.ref_to_bytes(), + "ML-KEM-1024 shared secret must match encaps_internal" + ); + } } // struct Kat { diff --git a/crypto/rng/src/hash_drbg80090a.rs b/crypto/rng/src/hash_drbg80090a.rs index 431d036..642d9c5 100644 --- a/crypto/rng/src/hash_drbg80090a.rs +++ b/crypto/rng/src/hash_drbg80090a.rs @@ -266,9 +266,9 @@ impl Sp80090ADrbg for HashDRBG80090A { Ok(()) } - fn reseed( + fn reseed( &mut self, - seed: &impl KeyMaterialTrait, + seed: &K, additional_input: &[u8], ) -> Result<(), RNGError> { // Hash_DRBG Reseed Process: @@ -455,10 +455,10 @@ impl Sp80090ADrbg for HashDRBG80090A { Ok(out.len()) } - fn generate_keymaterial_out( + fn generate_keymaterial_out( &mut self, additional_input: &[u8], - out: &mut impl KeyMaterialTrait, + out: &mut K, ) -> Result { out.allow_hazardous_operations(); let bytes_written = self.generate_out(additional_input, out.mut_ref_to_bytes().unwrap())?; @@ -483,9 +483,9 @@ impl RNG for HashDRBG80090A { fn add_seed_keymaterial( &mut self, - additional_seed: impl KeyMaterialTrait, + additional_seed: &dyn KeyMaterialTrait, ) -> Result<(), RNGError> { - self.reseed(&additional_seed, "add_seed_keymaterial".as_bytes()) + self.reseed(additional_seed, "add_seed_keymaterial".as_bytes()) } fn next_int(&mut self) -> Result { @@ -504,7 +504,7 @@ impl RNG for HashDRBG80090A { self.generate_out("next_bytes_out".as_bytes(), out) } - fn fill_keymaterial_out(&mut self, out: &mut impl KeyMaterialTrait) -> Result { + fn fill_keymaterial_out(&mut self, out: &mut dyn KeyMaterialTrait) -> Result { self.generate_keymaterial_out("fill_keymaterial".as_bytes(), out) } diff --git a/crypto/rng/src/lib.rs b/crypto/rng/src/lib.rs index dbe62fb..44824d9 100644 --- a/crypto/rng/src/lib.rs +++ b/crypto/rng/src/lib.rs @@ -94,9 +94,9 @@ pub trait Sp80090ADrbg { /// Reseeds the DRBG with the provided seed. /// TODO: this needs to be thought out to take some sort of EntropySource object that'll work well with DRBGs that require frequent reseeding. - fn reseed( + fn reseed( &mut self, - seed: &impl KeyMaterialTrait, + seed: &K, additional_input: &[u8], ) -> Result<(), RNGError>; @@ -127,10 +127,10 @@ pub trait Sp80090ADrbg { /// The output [KeyMaterialTrait] is filled to capacity. /// Throws a [RNGError::InsufficientSeedEntropy] if the capacity of the output KeyMaterial exceeds [SecurityStrength]. /// Retruns the number of bits output. - fn generate_keymaterial_out( + fn generate_keymaterial_out( &mut self, additional_input: &[u8], - out: &mut impl KeyMaterialTrait, + out: &mut K, ) -> Result; // TODO -- implement FIPS health tests diff --git a/crypto/rng/tests/hash_drbg80090a_tests.rs b/crypto/rng/tests/hash_drbg80090a_tests.rs index e4fd534..c6c2cef 100644 --- a/crypto/rng/tests/hash_drbg80090a_tests.rs +++ b/crypto/rng/tests/hash_drbg80090a_tests.rs @@ -301,7 +301,7 @@ mod tests { KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Seed).unwrap(); /* test add_seed_keymaterial */ - rng.add_seed_keymaterial(seed).unwrap(); + rng.add_seed_keymaterial(&seed).unwrap(); /* test next_int */ let out1: u32 = rng.next_int().unwrap();