From 19ea35f074a1bbff9611306cff070da406c504af Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 30 Jun 2026 21:39:23 -0500 Subject: [PATCH 01/26] initial trait defn for SerializableState --- crypto/core-test-framework/src/lib.rs | 1 + .../src/serializable_state.rs | 43 +++++++++ crypto/core/src/errors.rs | 5 ++ crypto/core/src/lib.rs | 1 + crypto/core/src/serializable_state.rs | 90 +++++++++++++++++++ crypto/core/src/traits.rs | 28 +++++- 6 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 crypto/core-test-framework/src/serializable_state.rs create mode 100644 crypto/core/src/serializable_state.rs diff --git a/crypto/core-test-framework/src/lib.rs b/crypto/core-test-framework/src/lib.rs index be769ae..b169dfa 100644 --- a/crypto/core-test-framework/src/lib.rs +++ b/crypto/core-test-framework/src/lib.rs @@ -13,6 +13,7 @@ pub mod hash; pub mod kdf; pub mod kem; pub mod mac; +pub mod serializable_state; pub mod signature; mod fixed_seed_rng; diff --git a/crypto/core-test-framework/src/serializable_state.rs b/crypto/core-test-framework/src/serializable_state.rs new file mode 100644 index 0000000..4968905 --- /dev/null +++ b/crypto/core-test-framework/src/serializable_state.rs @@ -0,0 +1,43 @@ +use bouncycastle_core::errors::CoreError; +use bouncycastle_core::serializable_state::{LIB_VERSION}; +use bouncycastle_core::traits::{SerializableState}; + +pub struct TestFrameworkSerializableState { } + +impl TestFrameworkSerializableState { + pub fn new() -> Self { + Self { } + } + + /// Test all the members of trait SerializableState. + /// + /// Expects ta be handed an instance of the object that has some in-progress state to be serialized. + pub fn test>( + &self, + instance: &S, + ) { + // There's not a lot we can test here in the abstract, but we can test a few things to + // ensure that the SerializableState trait has been impl'd correctly. + + // You can serialize and then deserialize the state. + let serialized_state = instance.serialize_state(); + assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN); + + let _deserialized_state = S::from_serialized_state(serialized_state).unwrap(); + + + // The serialized state MUST include a prefix indicating the current version of the library. + assert_eq!(serialized_state[..3], LIB_VERSION); + + + // All implementations MUST reject a serialized state from lib ver 0.0.0 + // This doesn't really serve any purpose except testing that all impl's have properly + // used the helper functions. + let mut busted_serialized_state = serialized_state.clone(); + busted_serialized_state[..3].copy_from_slice(&[0, 0, 0]); + match S::from_serialized_state(busted_serialized_state) { + Err(CoreError::IncompatibleVersion) => { /* good */ }, + _ => { panic!("Expected IncompatibleVersion error") } + } + } +} diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index 1334cdb..e20afb2 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -1,3 +1,8 @@ +#[derive(Debug)] +pub enum CoreError { + IncompatibleVersion, +} + #[derive(Debug)] pub enum HashError { GenericError(&'static str), diff --git a/crypto/core/src/lib.rs b/crypto/core/src/lib.rs index 379e54c..9516907 100644 --- a/crypto/core/src/lib.rs +++ b/crypto/core/src/lib.rs @@ -7,4 +7,5 @@ pub mod errors; pub mod key_material; +pub mod serializable_state; pub mod traits; diff --git a/crypto/core/src/serializable_state.rs b/crypto/core/src/serializable_state.rs new file mode 100644 index 0000000..c9ddb87 --- /dev/null +++ b/crypto/core/src/serializable_state.rs @@ -0,0 +1,90 @@ +//! Helper functions for standardizing serialization and deserialization of stateful objects. + +use crate::errors::CoreError; + +/// The current library version. +// There is almost certainly a more elegant way to do this. +pub const LIB_VERSION: [u8; 3] = [0, 1, 2]; + +/// Compare two library semantic versions in the standard C format: +/// * if a < b => -1 +///* if a == b => 0 +///* if a > b => 1 +pub fn cmp_lib_ver(a: &[u8; 3], b: &[u8; 3]) -> i8 { + if a[0] < b[0] { + -1 + } else if a[0] > b[0] { + 1 + } + // first component is equal + else if a[1] < b[1] { + -1 + } else if a[1] > b[1] { + 1 + } + // first two components are equal + else if a[2] < b[2] { + -1 + } else if a[2] > b[2] { + 1 + } + // all three components are equal + else { + 0 + } +} + +#[test] +fn test_cmp_lib_ver() { + assert_eq!(cmp_lib_ver(&[0, 2, 1], &[1, 1, 1]), -1); + assert_eq!(cmp_lib_ver(&[2, 1, 1], &[1, 1, 1]), 1); + assert_eq!(cmp_lib_ver(&[1, 0, 2], &[1, 1, 1]), -1); + assert_eq!(cmp_lib_ver(&[1, 2, 0], &[1, 1, 1]), 1); + assert_eq!(cmp_lib_ver(&[1, 1, 0], &[1, 1, 1]), -1); + assert_eq!(cmp_lib_ver(&[1, 1, 2], &[1, 1, 1]), 1); + assert_eq!(cmp_lib_ver(&[1, 1, 1], &[1, 1, 1]), 0); +} + +/// A helper for serializing an object's state +/// +/// The state array must have length SERIALIZED_LEN - 3 to account for adding the 3-byte symver tag. +pub fn add_lib_ver(state: &[u8]) -> [u8; SERIALIZED_LEN] { + assert_eq!(state.len(), SERIALIZED_LEN - 3); + + let mut out = [0u8; SERIALIZED_LEN]; + out[..3].copy_from_slice(&LIB_VERSION); + out[3..].copy_from_slice(state); + + out +} + +/// A helper for deserializing an object's state +/// +/// The state_out array must have length at least SERIALIZED_LEN - 3. +/// +/// Returns the number of bytes written to state_out, or a [CoreError::IncompatibleVersion] if the +/// serialized state contains a version header earlier than the specified `not_before` version. +/// +/// Note that for testability, this will always reject if the serialized state contains a version tag +/// of `[0,0,0]`. +pub fn remove_lib_ver( + state_in: &[u8; SERIALIZED_LEN], + state_out: &mut [u8], + not_before: Option<[u8; 3]>, +) -> Result { + assert!(state_out.len() >= SERIALIZED_LEN - 3); + + let ver: [u8; 3] = state_in[..3].try_into().unwrap(); + + let not_before = not_before.unwrap_or([0, 0, 0]); + + if cmp_lib_ver(&ver, ¬_before) < 0 { + return Err(CoreError::IncompatibleVersion); + }; + if ver == [0, 0, 0] { + return Err(CoreError::IncompatibleVersion); + }; + + state_out.copy_from_slice(&state_in[3..]); + Ok(SERIALIZED_LEN - 3) +} diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 81fa854..ddfd62e 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -1,6 +1,6 @@ //! 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::{CoreError, HashError, KDFError, KEMError, MACError, RNGError, SignatureError}; use crate::key_material::KeyMaterialTrait; use core::fmt::{Debug, Display}; use core::marker::Sized; @@ -37,7 +37,6 @@ 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. @@ -470,6 +469,31 @@ pub trait RNG { #[allow(drop_bounds)] pub trait Secret: Drop + Debug + Display {} +/// Allows a stateful object to serialize its state so that it can be paused and resumed later, +/// potentially from a different host. +/// +/// This is intended for situations where an object is being used through its streaming API +/// (do_update, do_final) and the operation wants to be paused to a cache, for example while waiting +/// for network IO. +/// +/// This is not intended as a mechanism to clone the state of an object since in most cases `.clone()` +/// will be more straightforward. +pub trait SerializableState: Sized { + /// Serialize the state of the object. + /// + /// The serialized state MUST include a prefix indicating the version of the library that serialized it. + fn serialize_state(&self) -> [u8; SERIALIZED_STATE_LEN]; + + /// Create a new object from a serialized state. + /// + /// Deserializers SHOULD check the version and reject serialized states from incompatible versions. + /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its + /// deserializer should reject serialized states from that version or older. + fn from_serialized_state( + serialized_state: [u8; SERIALIZED_STATE_LEN], + ) -> Result; +} + /// Pre-Hashed Signer is an extension to [Signer] that adds functionality specific to signature /// primatives that can operate on a pre-hashed message instead of the full message. pub trait PHSigner< From 64365de735ce3c72fdb0fd92d5726fbba4f57ccd Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 30 Jun 2026 22:42:22 -0500 Subject: [PATCH 02/26] sha2 impls SerializableState --- crypto/core/src/errors.rs | 1 + crypto/core/src/serializable_state.rs | 31 ++++------ crypto/sha2/Cargo.toml | 1 + crypto/sha2/src/lib.rs | 6 +- crypto/sha2/src/sha256.rs | 81 +++++++++++++++++++++++---- crypto/sha2/src/sha512.rs | 81 ++++++++++++++++++++++++--- crypto/sha2/tests/sha2_tests.rs | 63 +++++++++++++++++++++ 7 files changed, 224 insertions(+), 40 deletions(-) diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index e20afb2..5051443 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -1,6 +1,7 @@ #[derive(Debug)] pub enum CoreError { IncompatibleVersion, + InvalidData, } #[derive(Debug)] diff --git a/crypto/core/src/serializable_state.rs b/crypto/core/src/serializable_state.rs index c9ddb87..3015e24 100644 --- a/crypto/core/src/serializable_state.rs +++ b/crypto/core/src/serializable_state.rs @@ -45,17 +45,12 @@ fn test_cmp_lib_ver() { assert_eq!(cmp_lib_ver(&[1, 1, 1], &[1, 1, 1]), 0); } -/// A helper for serializing an object's state +/// Puts the library version into the first three bytes of the state array. /// -/// The state array must have length SERIALIZED_LEN - 3 to account for adding the 3-byte symver tag. -pub fn add_lib_ver(state: &[u8]) -> [u8; SERIALIZED_LEN] { - assert_eq!(state.len(), SERIALIZED_LEN - 3); - - let mut out = [0u8; SERIALIZED_LEN]; - out[..3].copy_from_slice(&LIB_VERSION); - out[3..].copy_from_slice(state); - - out +/// Hands back a slice to the same array, starting after the version tag. +pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN]) -> &mut [u8] { + state[..3].copy_from_slice(&LIB_VERSION); + &mut state[3..] } /// A helper for deserializing an object's state @@ -67,14 +62,13 @@ pub fn add_lib_ver(state: &[u8]) -> [u8; SERIALIZED /// /// Note that for testability, this will always reject if the serialized state contains a version tag /// of `[0,0,0]`. -pub fn remove_lib_ver( - state_in: &[u8; SERIALIZED_LEN], - state_out: &mut [u8], +/// +/// Hands back a slice to the same array, starting after the version tag. +pub fn check_lib_ver( + state: &[u8; SERIALIZED_LEN], not_before: Option<[u8; 3]>, -) -> Result { - assert!(state_out.len() >= SERIALIZED_LEN - 3); - - let ver: [u8; 3] = state_in[..3].try_into().unwrap(); +) -> Result<&[u8], CoreError> { + let ver: [u8; 3] = state[..3].try_into().unwrap(); let not_before = not_before.unwrap_or([0, 0, 0]); @@ -85,6 +79,5 @@ pub fn remove_lib_ver( return Err(CoreError::IncompatibleVersion); }; - state_out.copy_from_slice(&state_in[3..]); - Ok(SERIALIZED_LEN - 3) + Ok(&state[3..]) } diff --git a/crypto/sha2/Cargo.toml b/crypto/sha2/Cargo.toml index a620a6a..ae8a52d 100644 --- a/crypto/sha2/Cargo.toml +++ b/crypto/sha2/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true [dependencies] bouncycastle-core.workspace = true bouncycastle-utils.workspace = true +serde = { version = "1.0.228", features = ["derive"] } [dev-dependencies] criterion.workspace = true diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index b3582fc..d8a4544 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -41,7 +41,7 @@ mod sha256; mod sha512; pub use self::sha256::SHA256Internal; -pub use self::sha512::Sha512Internal; +pub use self::sha512::SHA512Internal; use bouncycastle_core::traits::{Algorithm, HashAlgParams, SecurityStrength}; /*** String constants ***/ @@ -53,8 +53,8 @@ pub const SHA512_NAME: &str = "SHA512"; /*** pub types ***/ pub type SHA224 = SHA256Internal; pub type SHA256 = SHA256Internal; -pub type SHA384 = Sha512Internal; -pub type SHA512 = Sha512Internal; +pub type SHA384 = SHA512Internal; +pub type SHA512 = SHA512Internal; /*** Param traits ***/ diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index 7be7f47..6995642 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -1,6 +1,7 @@ use crate::SHA2Params; -use bouncycastle_core::errors::HashError; -use bouncycastle_core::traits::{Hash, SecurityStrength}; +use bouncycastle_core::errors::{CoreError, HashError}; +use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; use bouncycastle_utils::min; use core::slice; @@ -45,11 +46,9 @@ fn theta1(x: u32) -> u32 { x.rotate_right(17) ^ x.rotate_right(19) ^ (x >> 10) } -// todo -- cleanup -// #[derive(Clone, Copy)] #[derive(Clone)] pub(crate) struct Sha256State { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, h: [u32; 8], } @@ -63,7 +62,7 @@ impl Sha256State { pub(crate) fn new() -> Self { match PARAMS::OUTPUT_LEN * 8 { 224 => Self { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, h: [ 0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939, 0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4, @@ -145,11 +144,9 @@ impl Sha256State { } } -// todo -- cleanup -// #[derive(Clone, Copy)] #[derive(Clone)] pub struct SHA256Internal { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, state: Sha256State, byte_count: u64, x_buf: [u8; 64], @@ -166,7 +163,7 @@ impl Drop for SHA256Internal { impl SHA256Internal { pub fn new() -> Self { Self { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, state: Sha256State::::new(), byte_count: 0, x_buf: [0; 64], @@ -306,3 +303,67 @@ impl Hash for SHA256Internal { SecurityStrength::from_bytes(PARAMS::OUTPUT_LEN / 2) } } + +impl SerializableState<108> for SHA256Internal { + fn serialize_state(&self) -> [u8; 108] { + let mut out_to_return = [0u8; 108]; + + // insert the version tag + let out: &mut [u8; 105] = add_lib_ver(&mut out_to_return).try_into().unwrap(); + + // state.h: [u32; 8] + // 4 * 8 = 32 + for i in 0..8 { + out[i * 4..(i * 4) + 4].copy_from_slice(&self.state.h[i].to_le_bytes()); + } + + // byte_count: u64 + out[32..40].copy_from_slice(&self.byte_count.to_le_bytes()); + + // x_buf: [u8; 64] + out[40..104].copy_from_slice(&self.x_buf); + + // x_buf_off: usize + // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 64 + debug_assert!(self.x_buf_off < 64); + out[104] = self.x_buf_off as u8; + + out_to_return + } + + fn from_serialized_state(serialized_state: [u8; 108]) -> Result { + // check the version tag + // At the moment, we have no not_before version to specify. + let input: &[u8; 105] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + // state.h: [u32; 8] + // 4 * 8 = 32 + let mut h = [0u32; 8]; + for i in 0..8 { + h[i] = u32::from_le_bytes(input[i * 4..(i * 4) + 4].try_into().unwrap()); + } + + // byte_count: u64 + let byte_count: u64 = u64::from_le_bytes(input[32..40].try_into().unwrap()); + + // x_buf: [u8; 64] + let x_buf: [u8; 64] = input[40..104].try_into().unwrap(); + + // x_buf_off: usize + // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 64 + let x_buf_off: usize = input[104] as usize; + if x_buf_off >= 64 { + return Err(CoreError::InvalidData); + } + + // Construct the object + let state = Sha256State { _params: core::marker::PhantomData, h }; + Ok(SHA256Internal { + _params: core::marker::PhantomData, + state, + byte_count, + x_buf, + x_buf_off, + }) + } +} diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index b4ae990..49aed8b 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -1,6 +1,7 @@ -use crate::SHA2Params; -use bouncycastle_core::errors::HashError; -use bouncycastle_core::traits::{Hash, SecurityStrength}; +use crate::{SHA2Params}; +use bouncycastle_core::errors::{CoreError, HashError}; +use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; use bouncycastle_utils::min; use core::slice; @@ -160,7 +161,7 @@ impl Sha512State { // todo -- cleanup // #[derive(Clone, Copy)] #[derive(Clone)] -pub struct Sha512Internal { +pub struct SHA512Internal { _params: std::marker::PhantomData, state: Sha512State, byte_count: u64, // NOTE We only support 2^67 bits, not the full 2^128 @@ -168,13 +169,13 @@ pub struct Sha512Internal { x_buf_off: usize, } -impl Drop for Sha512Internal { +impl Drop for SHA512Internal { fn drop(&mut self) { self.x_buf.fill(0); } } -impl Sha512Internal { +impl SHA512Internal { pub fn new() -> Self { Self { _params: std::marker::PhantomData, @@ -186,13 +187,13 @@ impl Sha512Internal { } } -impl Default for Sha512Internal { +impl Default for SHA512Internal { fn default() -> Self { Self::new() } } -impl Hash for Sha512Internal { +impl Hash for SHA512Internal { /// As per FIPS 180-4 Figure 1 fn block_bitlen(&self) -> usize { 1024 @@ -317,3 +318,67 @@ impl Hash for Sha512Internal { SecurityStrength::from_bytes(PARAMS::OUTPUT_LEN / 2) } } + +impl SerializableState<204> for SHA512Internal { + fn serialize_state(&self) -> [u8; 204] { + let mut out_to_return = [0u8; 204]; + + // insert the version tag + let out: &mut [u8; 201] = add_lib_ver(&mut out_to_return).try_into().unwrap(); + + // state.h: [u64; 8] + // 8 * 8 = 64 + for i in 0..8 { + out[i * 8..(i * 8) + 8].copy_from_slice(&self.state.h[i].to_le_bytes()); + } + + // byte_count: u64 + out[64..72].copy_from_slice(&self.byte_count.to_le_bytes()); + + // x_buf: [u8; 128] + out[72..200].copy_from_slice(&self.x_buf); + + // x_buf_off: usize + // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 128 + debug_assert!(self.x_buf_off < 128); + out[200] = self.x_buf_off as u8; + + out_to_return + } + + fn from_serialized_state(serialized_state: [u8; 204]) -> Result { + // check the version tag + // At the moment, we have no not_before version to specify. + let input: &[u8; 201] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + // state.h: [u64; 8] + // 8 * 8 = 64 + let mut h = [0u64; 8]; + for i in 0..8 { + h[i] = u64::from_le_bytes(input[i * 8..(i * 8) + 8].try_into().unwrap()); + } + + // byte_count: u64 + let byte_count: u64 = u64::from_le_bytes(input[64..72].try_into().unwrap()); + + // x_buf: [u8; 128] + let x_buf: [u8; 128] = input[72..200].try_into().unwrap(); + + // x_buf_off: usize + // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 128 + let x_buf_off: usize = input[200] as usize; + if x_buf_off >= 128 { + return Err(CoreError::InvalidData); + } + + // Construct the object + let state = Sha512State { _params: core::marker::PhantomData, h }; + Ok(SHA512Internal { + _params: core::marker::PhantomData, + state, + byte_count, + x_buf, + x_buf_off, + }) + } +} diff --git a/crypto/sha2/tests/sha2_tests.rs b/crypto/sha2/tests/sha2_tests.rs index 7d3e239..442b94b 100644 --- a/crypto/sha2/tests/sha2_tests.rs +++ b/crypto/sha2/tests/sha2_tests.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod sha2_tests { + use bouncycastle_core::errors::CoreError; use bouncycastle_core::traits::{Algorithm, Hash, HashAlgParams, SecurityStrength}; use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::hash::TestFrameworkHash; @@ -89,4 +90,66 @@ mod sha2_tests { assert_eq!(SHA384::default().max_security_strength(), SecurityStrength::_192bit); assert_eq!(SHA512::default().max_security_strength(), SecurityStrength::_256bit); } + + #[test] + fn test_serializable_state() { + use bouncycastle_core::traits::SerializableState; + use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; + + let str = "Colorless green ideas sleep furiously"; + + // SHA256 + let mut sha256 = SHA256::new(); + sha256.do_update(str.as_bytes()); + + // do the default tests + let test_framework = TestFrameworkSerializableState::new(); + test_framework.test(&sha256); + + // now let's serialize the in-progress state + let serialized_state = sha256.serialize_state(); + + // finish the hash + let output = sha256.do_final(); + + // then load from state and finish the hash and make sure we get the same thing + let sha2_from_state = SHA256::from_serialized_state(serialized_state).unwrap(); + let output2 = sha2_from_state.do_final(); + assert_eq!(output, output2); + + // also, give it a busted x_buf_off, just to satisfy mutants that that's been tested + let mut busted_state = serialized_state.clone(); + busted_state[3 + 104] = 65; + match SHA256::from_serialized_state(busted_state) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error"), + } + + // SHA512 + let mut sha512 = SHA512::new(); + sha512.do_update(str.as_bytes()); + + // do the default tests + let test_framework = TestFrameworkSerializableState::new(); + test_framework.test(&sha512); + + // now let's serialize the in-progress state + let serialized_state = sha512.serialize_state(); + + // finish the hash + let output = sha512.do_final(); + + // then load from state and finish the hash and make sure we get the same thing + let sha2_from_state = SHA512::from_serialized_state(serialized_state).unwrap(); + let output2 = sha2_from_state.do_final(); + assert_eq!(output, output2); + + // also, give it a busted x_buf_off, just to satisfy mutants that that's been tested + let mut busted_state = serialized_state.clone(); + busted_state[3 + 200] = 129; + match SHA512::from_serialized_state(busted_state) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error"), + } + } } From f75bc5d65e2417803cbc3a691ef30c1b2d632258 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 30 Jun 2026 22:43:54 -0500 Subject: [PATCH 03/26] rustfmt --- .../src/serializable_state.rs | 26 +++++++++---------- crypto/sha2/src/sha512.rs | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crypto/core-test-framework/src/serializable_state.rs b/crypto/core-test-framework/src/serializable_state.rs index 4968905..f79c496 100644 --- a/crypto/core-test-framework/src/serializable_state.rs +++ b/crypto/core-test-framework/src/serializable_state.rs @@ -1,16 +1,16 @@ use bouncycastle_core::errors::CoreError; -use bouncycastle_core::serializable_state::{LIB_VERSION}; -use bouncycastle_core::traits::{SerializableState}; +use bouncycastle_core::serializable_state::LIB_VERSION; +use bouncycastle_core::traits::SerializableState; -pub struct TestFrameworkSerializableState { } +pub struct TestFrameworkSerializableState {} impl TestFrameworkSerializableState { pub fn new() -> Self { - Self { } + Self {} } /// Test all the members of trait SerializableState. - /// + /// /// Expects ta be handed an instance of the object that has some in-progress state to be serialized. pub fn test>( &self, @@ -18,26 +18,26 @@ impl TestFrameworkSerializableState { ) { // There's not a lot we can test here in the abstract, but we can test a few things to // ensure that the SerializableState trait has been impl'd correctly. - + // You can serialize and then deserialize the state. let serialized_state = instance.serialize_state(); assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN); - + let _deserialized_state = S::from_serialized_state(serialized_state).unwrap(); - - + // The serialized state MUST include a prefix indicating the current version of the library. assert_eq!(serialized_state[..3], LIB_VERSION); - - + // All implementations MUST reject a serialized state from lib ver 0.0.0 // This doesn't really serve any purpose except testing that all impl's have properly // used the helper functions. let mut busted_serialized_state = serialized_state.clone(); busted_serialized_state[..3].copy_from_slice(&[0, 0, 0]); match S::from_serialized_state(busted_serialized_state) { - Err(CoreError::IncompatibleVersion) => { /* good */ }, - _ => { panic!("Expected IncompatibleVersion error") } + Err(CoreError::IncompatibleVersion) => { /* good */ } + _ => { + panic!("Expected IncompatibleVersion error") + } } } } diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 49aed8b..0c5ed13 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -1,4 +1,4 @@ -use crate::{SHA2Params}; +use crate::SHA2Params; use bouncycastle_core::errors::{CoreError, HashError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; From 54f93f65b03b233a2d230c44a0fc4c8d1934a1bc Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sun, 5 Jul 2026 22:49:25 -0500 Subject: [PATCH 04/26] Incorporating Nikola and David's feedback --- .../src/serializable_state.rs | 24 +++-- crypto/core/src/serializable_state.rs | 87 ++++++++++--------- crypto/core/src/traits.rs | 3 +- crypto/sha2/Cargo.toml | 1 - 4 files changed, 69 insertions(+), 46 deletions(-) diff --git a/crypto/core-test-framework/src/serializable_state.rs b/crypto/core-test-framework/src/serializable_state.rs index f79c496..b8b7f63 100644 --- a/crypto/core-test-framework/src/serializable_state.rs +++ b/crypto/core-test-framework/src/serializable_state.rs @@ -1,5 +1,5 @@ use bouncycastle_core::errors::CoreError; -use bouncycastle_core::serializable_state::LIB_VERSION; +use bouncycastle_core::serializable_state::{LIB_VERSION, SemVer}; use bouncycastle_core::traits::SerializableState; pub struct TestFrameworkSerializableState {} @@ -26,14 +26,28 @@ impl TestFrameworkSerializableState { let _deserialized_state = S::from_serialized_state(serialized_state).unwrap(); // The serialized state MUST include a prefix indicating the current version of the library. - assert_eq!(serialized_state[..3], LIB_VERSION); + let state_sized: [u8; 3] = serialized_state[..3].try_into().unwrap(); + assert_eq!(SemVer::from(state_sized), LIB_VERSION); // All implementations MUST reject a serialized state from lib ver 0.0.0 // This doesn't really serve any purpose except testing that all impl's have properly // used the helper functions. - let mut busted_serialized_state = serialized_state.clone(); - busted_serialized_state[..3].copy_from_slice(&[0, 0, 0]); - match S::from_serialized_state(busted_serialized_state) { + let mut ver0_serialized_state = serialized_state.clone(); + ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); + match S::from_serialized_state(ver0_serialized_state) { + Err(CoreError::IncompatibleVersion) => { /* good */ } + _ => { + panic!("Expected IncompatibleVersion error") + } + } + + // All implementations MUST reject a serialized state from a future version. + let mut future_ver = LIB_VERSION; + future_ver.patch += 1; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + match S::from_serialized_state(futurever_serialized_state) { Err(CoreError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") diff --git a/crypto/core/src/serializable_state.rs b/crypto/core/src/serializable_state.rs index 3015e24..819a4f3 100644 --- a/crypto/core/src/serializable_state.rs +++ b/crypto/core/src/serializable_state.rs @@ -2,54 +2,56 @@ use crate::errors::CoreError; -/// The current library version. -// There is almost certainly a more elegant way to do this. -pub const LIB_VERSION: [u8; 3] = [0, 1, 2]; +/// A semantic library version, ordered by `major`, then `minor`, then `patch`. +/// +/// The field declaration order matters: the derived [`Ord`]/[`PartialOrd`] compare fields +/// lexicographically in declaration order, which is exactly semantic-version precedence. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct SemVer { + pub major: u8, + pub minor: u8, + pub patch: u8, + // A semantic version can often also take a suffix, e.g. "alpha", "beta", "rc1", etc. + // We're not going to model that here because it's not useful for versioning serialized states. +} -/// Compare two library semantic versions in the standard C format: -/// * if a < b => -1 -///* if a == b => 0 -///* if a > b => 1 -pub fn cmp_lib_ver(a: &[u8; 3], b: &[u8; 3]) -> i8 { - if a[0] < b[0] { - -1 - } else if a[0] > b[0] { - 1 +impl From<[u8; 3]> for SemVer { + fn from(v: [u8; 3]) -> Self { + SemVer { major: v[0], minor: v[1], patch: v[2] } } - // first component is equal - else if a[1] < b[1] { - -1 - } else if a[1] > b[1] { - 1 - } - // first two components are equal - else if a[2] < b[2] { - -1 - } else if a[2] > b[2] { - 1 - } - // all three components are equal - else { - 0 +} + +impl From for [u8; 3] { + fn from(v: SemVer) -> Self { + [v.major, v.minor, v.patch] } } +/// The current library version. +// There is almost certainly a more elegant way to do this. +pub const LIB_VERSION: SemVer = SemVer { major: 0, minor: 1, patch: 2 }; + #[test] fn test_cmp_lib_ver() { - assert_eq!(cmp_lib_ver(&[0, 2, 1], &[1, 1, 1]), -1); - assert_eq!(cmp_lib_ver(&[2, 1, 1], &[1, 1, 1]), 1); - assert_eq!(cmp_lib_ver(&[1, 0, 2], &[1, 1, 1]), -1); - assert_eq!(cmp_lib_ver(&[1, 2, 0], &[1, 1, 1]), 1); - assert_eq!(cmp_lib_ver(&[1, 1, 0], &[1, 1, 1]), -1); - assert_eq!(cmp_lib_ver(&[1, 1, 2], &[1, 1, 1]), 1); - assert_eq!(cmp_lib_ver(&[1, 1, 1], &[1, 1, 1]), 0); + use core::cmp::Ordering; + + assert!([0, 0, 0] < [0, 0, 1]); + + let cmp = |a: [u8; 3], b: [u8; 3]| SemVer::from(a).cmp(&SemVer::from(b)); + assert_eq!(cmp([0, 2, 1], [1, 1, 1]), Ordering::Less); + assert_eq!(cmp([2, 1, 1], [1, 1, 1]), Ordering::Greater); + assert_eq!(cmp([1, 0, 2], [1, 1, 1]), Ordering::Less); + assert_eq!(cmp([1, 2, 0], [1, 1, 1]), Ordering::Greater); + assert_eq!(cmp([1, 1, 0], [1, 1, 1]), Ordering::Less); + assert_eq!(cmp([1, 1, 2], [1, 1, 1]), Ordering::Greater); + assert_eq!(cmp([1, 1, 1], [1, 1, 1]), Ordering::Equal); } /// Puts the library version into the first three bytes of the state array. /// /// Hands back a slice to the same array, starting after the version tag. pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN]) -> &mut [u8] { - state[..3].copy_from_slice(&LIB_VERSION); + state[..3].copy_from_slice(&<[u8; 3]>::from(LIB_VERSION)); &mut state[3..] } @@ -68,16 +70,23 @@ pub fn check_lib_ver( state: &[u8; SERIALIZED_LEN], not_before: Option<[u8; 3]>, ) -> Result<&[u8], CoreError> { - let ver: [u8; 3] = state[..3].try_into().unwrap(); + let ver_bytes: [u8; 3] = state[..3].try_into().unwrap(); + let ver = SemVer::from(ver_bytes); - let not_before = not_before.unwrap_or([0, 0, 0]); + let not_before = SemVer::from(not_before.unwrap_or([0, 0, 0])); - if cmp_lib_ver(&ver, ¬_before) < 0 { + if ver < not_before { return Err(CoreError::IncompatibleVersion); }; - if ver == [0, 0, 0] { + // Nothing is ever compatible with [0,0,0] + if ver == SemVer::from([0, 0, 0]) { return Err(CoreError::IncompatibleVersion); }; + // Also not compatible with future versions. + if ver > LIB_VERSION { + return Err(CoreError::IncompatibleVersion); + } + Ok(&state[3..]) } diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index ddfd62e..9a06df2 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -486,7 +486,8 @@ pub trait SerializableState: Sized { /// Create a new object from a serialized state. /// - /// Deserializers SHOULD check the version and reject serialized states from incompatible versions. + /// Deserializers SHOULD check the version and reject serialized states from incompatible versions + /// (including rejecting serializations from a future version of the library). /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its /// deserializer should reject serialized states from that version or older. fn from_serialized_state( diff --git a/crypto/sha2/Cargo.toml b/crypto/sha2/Cargo.toml index ae8a52d..a620a6a 100644 --- a/crypto/sha2/Cargo.toml +++ b/crypto/sha2/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true [dependencies] bouncycastle-core.workspace = true bouncycastle-utils.workspace = true -serde = { version = "1.0.228", features = ["derive"] } [dev-dependencies] criterion.workspace = true From 46100d3c1a07faa9486632a5bfb5b3704273c816 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Mon, 6 Jul 2026 00:18:05 -0500 Subject: [PATCH 05/26] An in-progress SHA3 object can now be serialized --- crypto/core/src/key_material.rs | 36 +++++-- crypto/core/src/traits.rs | 31 +++++-- crypto/sha3/src/keccak.rs | 155 +++++++++++++++++++++++++++++-- crypto/sha3/src/lib.rs | 12 +++ crypto/sha3/src/sha3.rs | 53 ++++++++++- crypto/sha3/src/shake.rs | 53 ++++++++++- crypto/sha3/tests/sha3_tests.rs | 57 +++++++++++- crypto/sha3/tests/shake_tests.rs | 59 +++++++++++- 8 files changed, 429 insertions(+), 27 deletions(-) diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index 2d1db76..da8697e 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -50,7 +50,7 @@ //! //! See [do_hazardous_operations] for documentation and sample code. -use crate::errors::KeyMaterialError; +use crate::errors::{CoreError, KeyMaterialError}; use crate::traits::{RNG, Secret, SecurityStrength}; use bouncycastle_utils::{ct, min}; @@ -221,10 +221,15 @@ pub struct KeyMaterial { impl Secret for KeyMaterial {} +// The explicit `#[repr(u8)]` discriminants are the stable on-the-wire encoding used by +// `SerializableState` implementations (see the `TryFrom` impl below). Pin each value to its +// variant name: reordering variants is fine, but never reuse or renumber an existing discriminant, +// or previously-serialized states will be misread. #[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] pub enum KeyType { /// The KeyMaterial is zeroized and MUST NOT be used for any cryptographic operation in this state. - Zeroized, + Zeroized = 0, /// The KeyMaterial contains non-zero data of unknown key type. /// A KeyMaterial of key type Unknown will always have a [SecurityStrength] of [SecurityStrength::None]. @@ -234,19 +239,36 @@ pub enum KeyType { /// and must be done within a [do_hazardous_operations] closure. /// If you want to import key material directly into a known key type, use [KeyMaterial::from_bytes_as_type], /// which does not require a hazardous operations closure. - Unknown, + Unknown = 1, /// The KeyMaterial contains data of full entropy and can be safely converted to any other key type. - CryptographicRandom, + CryptographicRandom = 2, /// A seed for asymmetric private keys, RNGs, and other seed-based cryptographic objects. - Seed, + Seed = 3, /// A MAC key. - MACKey, + MACKey = 4, /// A key for a symmetric block or stream cipher. - SymmetricCipherKey, + SymmetricCipherKey = 5, +} + +impl TryFrom for KeyType { + type Error = CoreError; + + /// Inverse of `self as u8`; rejects unrecognized discriminants with [CoreError::InvalidData]. + fn try_from(value: u8) -> Result { + Ok(match value { + 0 => Self::Zeroized, + 1 => Self::Unknown, + 2 => Self::CryptographicRandom, + 3 => Self::Seed, + 4 => Self::MACKey, + 5 => Self::SymmetricCipherKey, + _ => return Err(CoreError::InvalidData), + }) + } } impl Default for KeyMaterial { diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 9a06df2..6ddc0d5 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -386,13 +386,32 @@ pub trait MAC: Sized { fn max_security_strength(&self) -> SecurityStrength; } -#[derive(Eq, PartialEq, PartialOrd, Clone, Debug)] +// The explicit `#[repr(u8)]` discriminants are the stable on-the-wire encoding used by +// `SerializableState` implementations (see the `TryFrom` impl below). +#[derive(Eq, PartialEq, PartialOrd, Clone, Copy, Debug)] +#[repr(u8)] pub enum SecurityStrength { - None, - _112bit, - _128bit, - _192bit, - _256bit, + None = 0, + _112bit = 1, + _128bit = 2, + _192bit = 3, + _256bit = 4, +} + +impl TryFrom for SecurityStrength { + type Error = CoreError; + + /// Inverse of `self as u8`; rejects unrecognized discriminants with [CoreError::InvalidData]. + fn try_from(value: u8) -> Result { + Ok(match value { + 0 => Self::None, + 1 => Self::_112bit, + 2 => Self::_128bit, + 3 => Self::_192bit, + 4 => Self::_256bit, + _ => return Err(CoreError::InvalidData), + }) + } } impl SecurityStrength { diff --git a/crypto/sha3/src/keccak.rs b/crypto/sha3/src/keccak.rs index 5e6f6a8..27fc702 100644 --- a/crypto/sha3/src/keccak.rs +++ b/crypto/sha3/src/keccak.rs @@ -1,4 +1,7 @@ -use bouncycastle_core::errors::HashError; +use bouncycastle_core::errors::{CoreError, HashError}; +use bouncycastle_core::key_material::KeyType; +use bouncycastle_core::traits::SecurityStrength; +use crate::{SHA3_224Params, SHA3_256Params, SHA3_384Params, SHA3_512Params, SHAKE128Params, SHAKE256Params, SHA3, SHAKE}; const KECCAK_ROUND_CONSTANTS: [u64; 24] = [ 0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000, @@ -210,11 +213,6 @@ impl KeccakDigest { pub(super) fn new(size: KeccakSize) -> Self { let rate = 1600 - ((size as usize) << 1); - // todo I think this check is not needed since the fixed set of allowed sizes can't yield an invalid rate, but I'll leave this here for now. - // if rate == 0 || rate >= 1600 || (rate & 63) != 0 { - // return Err(HashError::InvalidLength("invalid rate value")); - // } - Self { state: KeccakState::new(rate), data_queue: [0u8; 192], @@ -343,6 +341,151 @@ impl KeccakDigest { } } +/*** State serialization ***/ +// +// The SHA3 and SHAKE public objects have identical state: a [KeccakDigest] plus three pieces of +// KDF metadata. The helpers below serialize that shared state so the `SerializableState` impls in +// `sha3.rs` and `shake.rs` are just thin wrappers that add/check the library version header. + +/// Number of bytes needed to serialize a [KeccakDigest]'s mutable state. +/// +/// The `rate` is intentionally NOT serialized: it is fully determined by the SHA3/SHAKE variant and +/// is re-supplied at deserialization time (see [KeccakDigest::from_serialized_state]). +/// +/// Layout (all integers little-endian): +/// [0 .. 200) state.buf [u64; 25] +/// [200 .. 392) data_queue [u8; 192] +/// [392 .. 400) bits_in_queue usize serialized as u64 +/// [400 .. 401) squeezing bool (0 or 1) +const KECCAK_SERIALIZED_LEN: usize = 200 + 192 + 8 + 1; + +/// Number of bytes needed to serialize the shared SHA3-family state (a variant tag, a [KeccakDigest], +/// plus the three KDF metadata fields), excluding the library version header. +/// +/// The leading variant tag distinguishes every SHA3/SHAKE variant — crucially including same-rate +/// pairs such as SHA3-256 and SHAKE256 — so a serialized state can never be deserialized into a +/// different algorithm (which would silently apply the wrong domain separation). +/// +/// Layout (all integers little-endian): +/// [0 .. 1) variant tag (see `STATE_TAG` on the param traits) +/// [1 .. 1 + KECCAK_SERIALIZED_LEN) keccak digest state +/// [.. + 1) kdf_key_type (1 byte enum tag) +/// [.. + 1) kdf_security_strength (1 byte enum tag) +/// [.. + 8) kdf_entropy usize serialized as u64 +pub(super) const SHA3_FAMILY_STATE_LEN: usize = 1 + KECCAK_SERIALIZED_LEN + 10; + +/// Total number of bytes in a serialized SHA3-family state, including the 3-byte library version +/// header prepended by [add_lib_ver]. This is the const generic used by the `SerializableState` +/// impls for SHA3 and SHAKE. +pub(super) const SHA3_FAMILY_SERIALIZED_STATE_LEN: usize = 3 + SHA3_FAMILY_STATE_LEN; + +impl KeccakDigest { + /// Serializes this digest's mutable state into `out`. The `rate` is deliberately omitted; see + /// [KECCAK_SERIALIZED_LEN]. + fn serialize_state(&self, out: &mut [u8; KECCAK_SERIALIZED_LEN]) { + // state.buf: [u64; 25] + for i in 0..25 { + out[i * 8..(i * 8) + 8].copy_from_slice(&self.state.buf[i].to_le_bytes()); + } + + // data_queue: [u8; 192] + out[200..392].copy_from_slice(&self.data_queue); + + // bits_in_queue: usize + out[392..400].copy_from_slice(&(self.bits_in_queue as u64).to_le_bytes()); + + // squeezing: bool + out[400] = self.squeezing as u8; + } + + /// Reconstructs a [KeccakDigest] from a state produced by [KeccakDigest::serialize_state]. + /// + /// `rate` is supplied by the caller (derived from its algorithm parameters) rather than read + /// from the serialized bytes, since the rate is fully determined by the SHA3/SHAKE variant. The + /// caller is responsible for having already verified the variant tag so that this `rate` is the + /// correct one for the serialized state. + fn from_serialized_state( + input: &[u8; KECCAK_SERIALIZED_LEN], + rate: usize, + ) -> Result { + // state.buf: [u64; 25] + let mut buf = [0u64; 25]; + for i in 0..25 { + buf[i] = u64::from_le_bytes(input[i * 8..(i * 8) + 8].try_into().unwrap()); + } + + // data_queue: [u8; 192] + let data_queue: [u8; 192] = input[200..392].try_into().unwrap(); + + // bits_in_queue: usize. It can never legitimately exceed the rate (which is at most 168 + // bytes, well within data_queue's 192-byte capacity). + let bits_in_queue = u64::from_le_bytes(input[392..400].try_into().unwrap()) as usize; + if bits_in_queue > rate { + return Err(CoreError::InvalidData); + } + + // squeezing: bool + let squeezing = match input[400] { + 0 => false, + 1 => true, + _ => return Err(CoreError::InvalidData), + }; + + Ok(Self { state: KeccakState { buf, rate }, data_queue, rate, bits_in_queue, squeezing }) + } +} + +/// Serializes the state shared by all SHA3-family objects (the `variant_tag`, a [KeccakDigest], plus +/// the three KDF metadata fields) into `out`. See [SHA3_FAMILY_STATE_LEN] for the layout. +pub(super) fn serialize_sha3_family_state( + out: &mut [u8; SHA3_FAMILY_STATE_LEN], + variant_tag: u8, + keccak: &KeccakDigest, + kdf_key_type: KeyType, + kdf_security_strength: SecurityStrength, + kdf_entropy: usize, +) { + out[0] = variant_tag; + + let keccak_out: &mut [u8; KECCAK_SERIALIZED_LEN] = + (&mut out[1..1 + KECCAK_SERIALIZED_LEN]).try_into().unwrap(); + keccak.serialize_state(keccak_out); + + out[1 + KECCAK_SERIALIZED_LEN] = kdf_key_type as u8; + out[1 + KECCAK_SERIALIZED_LEN + 1] = kdf_security_strength as u8; + out[1 + KECCAK_SERIALIZED_LEN + 2..1 + KECCAK_SERIALIZED_LEN + 10] + .copy_from_slice(&(kdf_entropy as u64).to_le_bytes()); +} + +/// Reconstructs the shared SHA3-family state from a buffer produced by [serialize_sha3_family_state]. +/// +/// `expected_variant_tag` and `rate` are both derived from the caller's algorithm parameters. The +/// tag is checked against the serialized one first: this is what prevents a state from one variant +/// being loaded into another (e.g. SHA3-256 vs SHAKE256, which share a rate but differ in domain +/// separation). Only once the tag matches is `rate` guaranteed to be the correct one to rebuild with. +pub(super) fn deserialize_sha3_family_state( + input: &[u8; SHA3_FAMILY_STATE_LEN], + expected_variant_tag: u8, + rate: usize, +) -> Result<(KeccakDigest, KeyType, SecurityStrength, usize), CoreError> { + if input[0] != expected_variant_tag { + return Err(CoreError::InvalidData); + } + + let keccak_in: &[u8; KECCAK_SERIALIZED_LEN] = + input[1..1 + KECCAK_SERIALIZED_LEN].try_into().unwrap(); + let keccak = KeccakDigest::from_serialized_state(keccak_in, rate)?; + + // KeyType and SecurityStrength each own their canonical 1-byte encoding (`as u8` / `TryFrom`). + let kdf_key_type = KeyType::try_from(input[1 + KECCAK_SERIALIZED_LEN])?; + let kdf_security_strength = SecurityStrength::try_from(input[1 + KECCAK_SERIALIZED_LEN + 1])?; + let kdf_entropy = u64::from_le_bytes( + input[1 + KECCAK_SERIALIZED_LEN + 2..1 + KECCAK_SERIALIZED_LEN + 10].try_into().unwrap(), + ) as usize; + + Ok((keccak, kdf_key_type, kdf_security_strength, kdf_entropy)) +} + #[cfg(test)] mod keccak_tests { use super::*; diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 7ef4a44..3f5a7ca 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -144,6 +144,10 @@ pub type SHAKE256 = SHAKE; /// Private trait on purpose so that only the NIST-approved params can be used. trait SHA3Params: HashAlgParams { const SIZE: KeccakSize; + /// A tag, unique across all SHA3 *and* SHAKE variants, identifying which variant produced a + /// serialized state. Distinguishing same-rate variants (e.g. SHA3-256 vs SHAKE256) requires + /// this to be distinct from every value used by [SHAKEParams::STATE_TAG]. Never reuse a value. + const STATE_TAG: u8; } // TODO: more elegant to macro this? @@ -168,6 +172,7 @@ impl HashAlgParams for SHA3_224Params { } impl SHA3Params for SHA3_224Params { const SIZE: KeccakSize = KeccakSize::_224; + const STATE_TAG: u8 = 1; } impl Algorithm for SHA3_256 { @@ -191,6 +196,7 @@ impl HashAlgParams for SHA3_256Params { } impl SHA3Params for SHA3_256Params { const SIZE: KeccakSize = KeccakSize::_256; + const STATE_TAG: u8 = 2; } pub struct SHA3_384Params; @@ -214,6 +220,7 @@ impl HashAlgParams for SHA3_384Params { } impl SHA3Params for SHA3_384Params { const SIZE: KeccakSize = KeccakSize::_384; + const STATE_TAG: u8 = 3; } pub struct SHA3_512Params; @@ -237,10 +244,13 @@ impl HashAlgParams for SHA3_512Params { } impl SHA3Params for SHA3_512Params { const SIZE: KeccakSize = KeccakSize::_512; + const STATE_TAG: u8 = 4; } trait SHAKEParams: Algorithm { const SIZE: KeccakSize; + /// See [SHA3Params::STATE_TAG]. Must be distinct from every SHA3 *and* SHAKE variant's tag. + const STATE_TAG: u8; } pub struct SHAKE128Params; impl Algorithm for SHAKE128Params { @@ -249,6 +259,7 @@ impl Algorithm for SHAKE128Params { } impl SHAKEParams for SHAKE128Params { const SIZE: KeccakSize = KeccakSize::_128; + const STATE_TAG: u8 = 5; } pub struct SHAKE256Params; @@ -258,4 +269,5 @@ impl Algorithm for SHAKE256Params { } impl SHAKEParams for SHAKE256Params { const SIZE: KeccakSize = KeccakSize::_256; + const STATE_TAG: u8 = 6; } diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index fff2439..2e4ca72 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -1,9 +1,13 @@ use crate::SHA3Params; -use crate::keccak::KeccakDigest; -use bouncycastle_core::errors::{HashError, KDFError}; +use crate::keccak::{ + KeccakDigest, SHA3_FAMILY_SERIALIZED_STATE_LEN, SHA3_FAMILY_STATE_LEN, + deserialize_sha3_family_state, serialize_sha3_family_state, +}; +use bouncycastle_core::errors::{CoreError, HashError, KDFError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Hash, KDF, SecurityStrength}; +use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::traits::{Hash, KDF, SecurityStrength, SerializableState}; use bouncycastle_utils::{max, min}; #[derive(Clone)] @@ -217,6 +221,49 @@ impl Hash for SHA3 { } } +impl SerializableState for SHA3 { + fn serialize_state(&self) -> [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN] { + let mut out_to_return = [0u8; SHA3_FAMILY_SERIALIZED_STATE_LEN]; + + // insert the version tag + let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = + add_lib_ver(&mut out_to_return).try_into().unwrap(); + + serialize_sha3_family_state( + out, + PARAMS::STATE_TAG, + &self.keccak, + self.kdf_key_type, + self.kdf_security_strength, + self.kdf_entropy, + ); + + out_to_return + } + + fn from_serialized_state( + serialized_state: [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN], + ) -> Result { + // check the version tag. At the moment, we have no not_before version to specify. + let input: &[u8; SHA3_FAMILY_STATE_LEN] = + check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + // The variant tag rejects states from any other SHA3/SHAKE variant; the rate is then the + // correct one to rebuild with (both are fully determined by the algorithm parameters). + let rate = 1600 - ((PARAMS::SIZE as usize) << 1); + let (keccak, kdf_key_type, kdf_security_strength, kdf_entropy) = + deserialize_sha3_family_state(input, PARAMS::STATE_TAG, rate)?; + + Ok(SHA3 { + _params: std::marker::PhantomData, + keccak, + kdf_key_type, + kdf_security_strength, + kdf_entropy, + }) + } +} + /// SHA3 is allowed to be used as a KDF in the form HASH(X) as per NIST SP 800-56C. impl KDF for SHA3 { /// Returns a [KeyMaterial]. diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index fecff5a..b2252b1 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -1,9 +1,13 @@ use crate::SHAKEParams; -use crate::keccak::{KeccakDigest, KeccakSize}; -use bouncycastle_core::errors::{HashError, KDFError}; +use crate::keccak::{ + KeccakDigest, KeccakSize, SHA3_FAMILY_SERIALIZED_STATE_LEN, SHA3_FAMILY_STATE_LEN, + deserialize_sha3_family_state, serialize_sha3_family_state, +}; +use bouncycastle_core::errors::{CoreError, HashError, KDFError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Algorithm, KDF, SecurityStrength, XOF}; +use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::traits::{Algorithm, KDF, SecurityStrength, SerializableState, XOF}; use bouncycastle_utils::{max, min}; /// Note: FIPS 202 section 7 states: @@ -138,6 +142,49 @@ impl SHAKE { } } +impl SerializableState for SHAKE { + fn serialize_state(&self) -> [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN] { + let mut out_to_return = [0u8; SHA3_FAMILY_SERIALIZED_STATE_LEN]; + + // insert the version tag + let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = + add_lib_ver(&mut out_to_return).try_into().unwrap(); + + serialize_sha3_family_state( + out, + PARAMS::STATE_TAG, + &self.keccak, + self.kdf_key_type, + self.kdf_security_strength, + self.kdf_entropy, + ); + + out_to_return + } + + fn from_serialized_state( + serialized_state: [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN], + ) -> Result { + // check the version tag. At the moment, we have no not_before version to specify. + let input: &[u8; SHA3_FAMILY_STATE_LEN] = + check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + // The variant tag rejects states from any other SHA3/SHAKE variant; the rate is then the + // correct one to rebuild with (both are fully determined by the algorithm parameters). + let rate = 1600 - ((PARAMS::SIZE as usize) << 1); + let (keccak, kdf_key_type, kdf_security_strength, kdf_entropy) = + deserialize_sha3_family_state(input, PARAMS::STATE_TAG, rate)?; + + Ok(SHAKE { + _phantomdata: std::marker::PhantomData, + keccak, + kdf_key_type, + kdf_security_strength, + kdf_entropy, + }) + } +} + impl KDF for SHAKE { /// Returns a [KeyMaterial]. /// For the KDF to be considered "fully-seeded" and be capable of outputting full-entropy KeyMaterials, diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index 1add04b..0cdb21a 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -9,7 +9,7 @@ mod sha3_tests { use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::hash::TestFrameworkHash; use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; - use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; + use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE256}; #[test] fn test_constants() { @@ -394,6 +394,61 @@ mod sha3_tests { run_test_vectors(read_test_vectors("tests/data/SHA3TestVectors.txt")); } + #[test] + fn test_serializable_state() { + use bouncycastle_core::errors::CoreError; + use bouncycastle_core::traits::SerializableState; + use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; + + let str = "Colorless green ideas sleep furiously"; + + // A helper that exercises the full round-trip for one SHA3 variant. + fn round_trip>(mut hash: H, input: &[u8]) { + hash.do_update(input); + + // do the default trait-conformance tests + TestFrameworkSerializableState::new().test(&hash); + + // serialize the in-progress state, then finish the original + let serialized_state = hash.serialize_state(); + let expected = hash.do_final(); + + // rebuild from the serialized state and confirm it produces the same digest + let from_state = H::from_serialized_state(serialized_state).unwrap(); + assert_eq!(expected, from_state.do_final()); + + // a corrupt `squeezing` byte (last byte of the keccak state) must be rejected. + // Layout: 3 version bytes + variant tag(1) + [u64;25](200) + data_queue(192) + // + bits_in_queue(8) + squeezing(1) + let mut busted = serialized_state; + busted[3 + 1 + 400] = 42; + match H::from_serialized_state(busted) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error for a corrupt squeezing byte"), + } + } + + round_trip(SHA3_224::new(), str.as_bytes()); + round_trip(SHA3_256::new(), str.as_bytes()); + round_trip(SHA3_384::new(), str.as_bytes()); + round_trip(SHA3_512::new(), str.as_bytes()); + + // A state serialized by one variant must be rejected by a different variant (mismatched + // variant tag). SHA3-256 and SHAKE256 share the same rate (1088), so this cross-family case + // is only caught by the tag, not the rate -- it is the exact bug the tag exists to prevent. + let mut sha3_256 = SHA3_256::new(); + sha3_256.do_update(str.as_bytes()); + let serialized_256 = sha3_256.serialize_state(); + match SHA3_512::from_serialized_state(serialized_256) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error when loading a SHA3-256 state into SHA3-512"), + } + match SHAKE256::from_serialized_state(serialized_256) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error when loading a SHA3-256 state into SHAKE256"), + } + } + fn run_test_vectors(test_vectors: Vec) { for tc in test_vectors { match tc.algorithm { diff --git a/crypto/sha3/tests/shake_tests.rs b/crypto/sha3/tests/shake_tests.rs index edbca2d..d0611c7 100644 --- a/crypto/sha3/tests/shake_tests.rs +++ b/crypto/sha3/tests/shake_tests.rs @@ -9,7 +9,7 @@ mod shake_tests { use bouncycastle_core::traits::{KDF, SecurityStrength, XOF}; use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; - use bouncycastle_sha3::{SHAKE128, SHAKE256}; + use bouncycastle_sha3::{SHA3_256, SHAKE128, SHAKE256}; #[test] fn test_xof_partial_bit_output() { @@ -238,6 +238,63 @@ mod shake_tests { run_test_vectors(read_test_vectors("tests/data/SHAKETestVectors.txt")); } + #[test] + fn test_serializable_state() { + use bouncycastle_core::errors::CoreError; + use bouncycastle_core::traits::SerializableState; + use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; + + let str = "Colorless green ideas sleep furiously"; + + // A helper that exercises the full round-trip for one SHAKE variant. + fn round_trip>(mut shake: X, input: &[u8]) { + shake.absorb(input); + + // do the default trait-conformance tests + TestFrameworkSerializableState::new().test(&shake); + + // serialize the in-progress (absorbing) state, then squeeze from the original + let serialized_state = shake.serialize_state(); + let expected = shake.squeeze(64); + + // rebuild from the serialized state and confirm it produces the same output + let mut from_state = X::from_serialized_state(serialized_state).unwrap(); + assert_eq!(expected, from_state.squeeze(64)); + + // a corrupt `squeezing` byte (last byte of the keccak state) must be rejected. + // Layout: 3 version bytes + variant tag(1) + [u64;25](200) + data_queue(192) + // + bits_in_queue(8) + squeezing(1) + let mut busted = serialized_state; + busted[3 + 1 + 400] = 42; + match X::from_serialized_state(busted) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error for a corrupt squeezing byte"), + } + } + + round_trip(SHAKE128::new(), str.as_bytes()); + round_trip(SHAKE256::new(), str.as_bytes()); + + // A state serialized by one variant must be rejected by a different variant (mismatched + // variant tag). The SHAKE256 -> SHA3-256 case is the important one: they share the same rate + // (1088), so only the variant tag distinguishes them. + let mut shake128 = SHAKE128::new(); + shake128.absorb(str.as_bytes()); + let serialized_128 = shake128.serialize_state(); + match SHAKE256::from_serialized_state(serialized_128) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error when loading a SHAKE128 state into SHAKE256"), + } + + let mut shake256 = SHAKE256::new(); + shake256.absorb(str.as_bytes()); + let serialized_256 = shake256.serialize_state(); + match SHA3_256::from_serialized_state(serialized_256) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error when loading a SHAKE256 state into SHA3-256"), + } + } + fn run_test_vectors(test_vectors: Vec) { for tc in test_vectors { //println!("SHA3-{} {}-bits", &tc.algorithm, &tc.bits); From 6b3ecec87005678a6e6cf40932e86a146a883680 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Mon, 6 Jul 2026 17:36:07 -0500 Subject: [PATCH 06/26] Impl'd SerializableState for MLDSA, and some quality-of-life fixes for the impl for SHA2 and SHA3. --- QUALITY_AND_STYLE.md | 4 + alpha_0.1.2_release_notes.md | 9 + .../src/serializable_state.rs | 17 +- crypto/core/src/errors.rs | 17 +- crypto/core/src/key_material.rs | 8 +- crypto/core/src/serializable_state.rs | 37 +- crypto/core/src/traits.rs | 46 ++- crypto/mldsa/src/hash_mldsa.rs | 106 +++--- crypto/mldsa/src/lib.rs | 2 + crypto/mldsa/src/mldsa.rs | 326 ++++++++++++------ crypto/mldsa/tests/mldsa_tests.rs | 90 ++++- crypto/mldsa/tests/wycheproof.rs | 48 +-- crypto/sha2/src/lib.rs | 39 ++- crypto/sha2/src/sha256.rs | 22 +- crypto/sha2/src/sha512.rs | 20 +- crypto/sha2/tests/sha2_tests.rs | 10 +- crypto/sha3/src/keccak.rs | 30 +- crypto/sha3/src/lib.rs | 40 +++ crypto/sha3/src/sha3.rs | 16 +- crypto/sha3/src/shake.rs | 14 +- crypto/sha3/tests/sha3_tests.rs | 12 +- crypto/sha3/tests/shake_tests.rs | 12 +- 22 files changed, 636 insertions(+), 289 deletions(-) diff --git a/QUALITY_AND_STYLE.md b/QUALITY_AND_STYLE.md index 2937ec7..77e9c08 100644 --- a/QUALITY_AND_STYLE.md +++ b/QUALITY_AND_STYLE.md @@ -12,6 +12,10 @@ lib.rs for all crates needs to contain: `#![forbid(missing_docs)]`, `#![no_std]` All primitives must be accompanied by a CLI in `/cli`. +All algorithms with a state that is exercised across multiple API calls -- typically through a `do_update()`, +`do_final()` pattern -- should implement SerializableState so that the user can pause the execution of this algorithm to +a cache and resume later. + # Quality ## Tests diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index ebe62c9..a298549 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -38,11 +38,20 @@ # 0.1.2 Features / Changelog +## Major features + * New algorithms added to crypto/ : * mldsa (FIPS 204) * mldsa-lowmemory -- runs in about 1/10th of the usual memory (~ 30 kb of stack) with comparable performance impact. * mlkem (FIPS 203) * mlkem-lowmemory -- runs in about 1/4th of the usual memory (~ 12 kb of stack) with comparable performance impact. +* New traits SerializeState and SerializeKeyedState allow algorithms with a streaming API (`do_update()` -> + `do_final()`) to be suspended to a small byte array and then resumed later, potentially from a different host. The + intended use case is if you are processing a large input that depends on one or more network round-trips and you wish + to suspect and potentially transfer to a new host while waiting for network IO. + +## Minor features / bug fixes + * All public `*_out(.., out: &mut [u8])` functions now begin by zeroizing the entire output buffer with `.fill(0)`, preventing exposure of stale data in oversized output buffers or on early error returns. * Reworked the way KeyMaterial hazardous operations work; instead of a stateful .allow_hazardous_operations() / diff --git a/crypto/core-test-framework/src/serializable_state.rs b/crypto/core-test-framework/src/serializable_state.rs index b8b7f63..2daea7d 100644 --- a/crypto/core-test-framework/src/serializable_state.rs +++ b/crypto/core-test-framework/src/serializable_state.rs @@ -1,4 +1,4 @@ -use bouncycastle_core::errors::CoreError; +use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::serializable_state::{LIB_VERSION, SemVer}; use bouncycastle_core::traits::SerializableState; @@ -12,15 +12,22 @@ impl TestFrameworkSerializableState { /// Test all the members of trait SerializableState. /// /// Expects ta be handed an instance of the object that has some in-progress state to be serialized. - pub fn test>( + pub fn test< + const SERIALIZED_STATE_LEN: usize, + S: SerializableState + Clone, + >( &self, instance: &S, ) { // There's not a lot we can test here in the abstract, but we can test a few things to // ensure that the SerializableState trait has been impl'd correctly. + // we need to work on a clone because .serialize_state() moves self, which you can't do on a + // borrowed instance. + let instance_clone = instance.clone(); + // You can serialize and then deserialize the state. - let serialized_state = instance.serialize_state(); + let serialized_state = instance_clone.serialize_state(); assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN); let _deserialized_state = S::from_serialized_state(serialized_state).unwrap(); @@ -35,7 +42,7 @@ impl TestFrameworkSerializableState { let mut ver0_serialized_state = serialized_state.clone(); ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); match S::from_serialized_state(ver0_serialized_state) { - Err(CoreError::IncompatibleVersion) => { /* good */ } + Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") } @@ -48,7 +55,7 @@ impl TestFrameworkSerializableState { futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); match S::from_serialized_state(futurever_serialized_state) { - Err(CoreError::IncompatibleVersion) => { /* good */ } + Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") } diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index 5051443..43424f7 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -1,9 +1,3 @@ -#[derive(Debug)] -pub enum CoreError { - IncompatibleVersion, - InvalidData, -} - #[derive(Debug)] pub enum HashError { GenericError(&'static str), @@ -78,6 +72,17 @@ pub enum RNGError { KeyMaterialError(KeyMaterialError), } +#[derive(Debug)] +pub enum SerializedStateError { + /// The serialized state was produced by a library version incompatible with this one. + IncompatibleVersion, + /// The serialized state is malformed or corrupt. + InvalidData, + /// The key supplied to [crate::traits::SerializableKeyedState::from_serialized_state] does not + /// match the key the state was created with (it is bound to a different public-key hash `tr`). + IncorrectKey, +} + #[derive(Debug)] pub enum SignatureError { GenericError(&'static str), diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index da8697e..c730c26 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -50,7 +50,7 @@ //! //! See [do_hazardous_operations] for documentation and sample code. -use crate::errors::{CoreError, KeyMaterialError}; +use crate::errors::{KeyMaterialError, SerializedStateError}; use crate::traits::{RNG, Secret, SecurityStrength}; use bouncycastle_utils::{ct, min}; @@ -255,9 +255,9 @@ pub enum KeyType { } impl TryFrom for KeyType { - type Error = CoreError; + type Error = SerializedStateError; - /// Inverse of `self as u8`; rejects unrecognized discriminants with [CoreError::InvalidData]. + /// Inverse of `self as u8`; rejects unrecognized discriminants with [SerializedStateError::InvalidData]. fn try_from(value: u8) -> Result { Ok(match value { 0 => Self::Zeroized, @@ -266,7 +266,7 @@ impl TryFrom for KeyType { 3 => Self::Seed, 4 => Self::MACKey, 5 => Self::SymmetricCipherKey, - _ => return Err(CoreError::InvalidData), + _ => return Err(SerializedStateError::InvalidData), }) } } diff --git a/crypto/core/src/serializable_state.rs b/crypto/core/src/serializable_state.rs index 819a4f3..420484a 100644 --- a/crypto/core/src/serializable_state.rs +++ b/crypto/core/src/serializable_state.rs @@ -1,6 +1,6 @@ //! Helper functions for standardizing serialization and deserialization of stateful objects. -use crate::errors::CoreError; +use crate::errors::SerializedStateError; /// A semantic library version, ordered by `major`, then `minor`, then `patch`. /// @@ -27,9 +27,28 @@ impl From for [u8; 3] { } } -/// The current library version. -// There is almost certainly a more elegant way to do this. -pub const LIB_VERSION: SemVer = SemVer { major: 0, minor: 1, patch: 2 }; +/// Parse a decimal ASCII string (a Cargo version component) into a u8 at compile time. +const fn parse_version_component(s: &str) -> u8 { + let bytes = s.as_bytes(); + let mut result: u8 = 0; + let mut i = 0; + while i < bytes.len() { + let d = bytes[i]; + assert!(d >= b'0' && d <= b'9', "version component must be numeric"); + // A component > 255 overflows u8 and fails the build (SemVer fields are u8 by design). + result = result * 10 + (d - b'0'); + i += 1; + } + result +} + +/// The current library version, taken from this crate's `Cargo.toml` at compile time (via Cargo's +/// `CARGO_PKG_VERSION_*` env vars) so it can never drift from the published version. +pub const LIB_VERSION: SemVer = SemVer { + major: parse_version_component(env!("CARGO_PKG_VERSION_MAJOR")), + minor: parse_version_component(env!("CARGO_PKG_VERSION_MINOR")), + patch: parse_version_component(env!("CARGO_PKG_VERSION_PATCH")), +}; #[test] fn test_cmp_lib_ver() { @@ -59,7 +78,7 @@ pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN] /// /// The state_out array must have length at least SERIALIZED_LEN - 3. /// -/// Returns the number of bytes written to state_out, or a [CoreError::IncompatibleVersion] if the +/// Returns the number of bytes written to state_out, or a [SerializedStateError::IncompatibleVersion] if the /// serialized state contains a version header earlier than the specified `not_before` version. /// /// Note that for testability, this will always reject if the serialized state contains a version tag @@ -69,23 +88,23 @@ pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN] pub fn check_lib_ver( state: &[u8; SERIALIZED_LEN], not_before: Option<[u8; 3]>, -) -> Result<&[u8], CoreError> { +) -> Result<&[u8], SerializedStateError> { let ver_bytes: [u8; 3] = state[..3].try_into().unwrap(); let ver = SemVer::from(ver_bytes); let not_before = SemVer::from(not_before.unwrap_or([0, 0, 0])); if ver < not_before { - return Err(CoreError::IncompatibleVersion); + return Err(SerializedStateError::IncompatibleVersion); }; // Nothing is ever compatible with [0,0,0] if ver == SemVer::from([0, 0, 0]) { - return Err(CoreError::IncompatibleVersion); + return Err(SerializedStateError::IncompatibleVersion); }; // Also not compatible with future versions. if ver > LIB_VERSION { - return Err(CoreError::IncompatibleVersion); + return Err(SerializedStateError::IncompatibleVersion); } Ok(&state[3..]) diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 6ddc0d5..8c774d9 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -1,6 +1,6 @@ //! Provides simplified abstracted APIs over classes of cryptigraphic primitives, such as Hash, KDF, etc. -use crate::errors::{CoreError, HashError, KDFError, KEMError, MACError, RNGError, SignatureError}; +use crate::errors::*; use crate::key_material::KeyMaterialTrait; use core::fmt::{Debug, Display}; use core::marker::Sized; @@ -399,9 +399,9 @@ pub enum SecurityStrength { } impl TryFrom for SecurityStrength { - type Error = CoreError; + type Error = SerializedStateError; - /// Inverse of `self as u8`; rejects unrecognized discriminants with [CoreError::InvalidData]. + /// Inverse of `self as u8`; rejects unrecognized discriminants with [SerializedStateError::InvalidData]. fn try_from(value: u8) -> Result { Ok(match value { 0 => Self::None, @@ -409,7 +409,7 @@ impl TryFrom for SecurityStrength { 2 => Self::_128bit, 3 => Self::_192bit, 4 => Self::_256bit, - _ => return Err(CoreError::InvalidData), + _ => return Err(SerializedStateError::InvalidData), }) } } @@ -497,11 +497,44 @@ pub trait Secret: Drop + Debug + Display {} /// /// This is not intended as a mechanism to clone the state of an object since in most cases `.clone()` /// will be more straightforward. +/// +/// The serialized state MAY contain short-term sensitive values such as nonces or IVs, +/// but it MUST NOT include a serialized private key. Keyed algorithms MUST instead impl +/// [SerializableKeyedState] which requires the key to be supplied independently at the time of deserialization. pub trait SerializableState: Sized { /// Serialize the state of the object. /// + /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization. + /// If you want to do this intentionally, then you will need to clone the object before serializing it. + /// + /// The serialized state MUST include a prefix indicating the version of the library that serialized it. + fn serialize_state(self) -> [u8; SERIALIZED_STATE_LEN]; + + /// Create a new object from a serialized state. + /// + /// Deserializers SHOULD check the version and reject serialized states from incompatible versions + /// (including rejecting serializations from a future version of the library). + /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its + /// deserializer should reject serialized states from that version or older. + fn from_serialized_state( + serialized_state: [u8; SERIALIZED_STATE_LEN], + ) -> Result; +} + +/// Similar to [SerializableState] in that it allows a stateful object to serialize its state so that +/// it can be paused and resumed later, potentially from a different host. +/// +/// The difference is that this trait is for keyed algorithms -- MACs, symmetric ciphers, signatures, etc -- +/// which require a private key. For security reasons, the private key is not included in the serialized state +/// and must be provided separately as part of the deserialization process. +pub trait SerializableKeyedState: Sized { + /// Serialize the state of the object. + /// + /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization. + /// If you want to do this intentionally, then you will need to clone the object before serializing it. + /// /// The serialized state MUST include a prefix indicating the version of the library that serialized it. - fn serialize_state(&self) -> [u8; SERIALIZED_STATE_LEN]; + fn serialize_state(self) -> [u8; SERIALIZED_STATE_LEN]; /// Create a new object from a serialized state. /// @@ -511,7 +544,8 @@ pub trait SerializableState: Sized { /// deserializer should reject serialized states from that version or older. fn from_serialized_state( serialized_state: [u8; SERIALIZED_STATE_LEN], - ) -> Result; + key: K, + ) -> Result; } /// Pre-Hashed Signer is an extension to [Signer] that adds functionality specific to signature diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 4b69c41..0cfd620 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -756,66 +756,52 @@ impl< }; match A_hat { - Some(A_hat) => { - if MLDSA::< - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::verify_mu_internal(pk, A_hat, &mu, sig_sized) - { - Ok(()) - } else { - Err(SignatureError::SignatureVerificationFailed) - } - } - None => { - if MLDSA::< - PK_LEN, - SK_LEN, - SIG_LEN, - PK, - SK, - TAU, - LAMBDA, - GAMMA1, - GAMMA2, - k, - l, - ETA, - BETA, - OMEGA, - C_TILDE, - POLY_Z_PACKED_LEN, - POLY_W1_PACKED_LEN, - LAMBDA_over_4, - GAMMA1_MINUS_BETA, - GAMMA2_MINUS_BETA, - GAMMA1_MASK_LEN, - >::verify_mu_internal(pk, &pk.A_hat(), &mu, sig_sized) - { - Ok(()) - } else { - Err(SignatureError::SignatureVerificationFailed) - } - } + Some(A_hat) => MLDSA::< + PK_LEN, + SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + >::verify_mu(pk, Some(A_hat), &mu, sig_sized), + None => MLDSA::< + PK_LEN, + SK_LEN, + SIG_LEN, + PK, + SK, + TAU, + LAMBDA, + GAMMA1, + GAMMA2, + k, + l, + ETA, + BETA, + OMEGA, + C_TILDE, + POLY_Z_PACKED_LEN, + POLY_W1_PACKED_LEN, + LAMBDA_over_4, + GAMMA1_MINUS_BETA, + GAMMA2_MINUS_BETA, + GAMMA1_MASK_LEN, + >::verify_mu(pk, Some(&pk.A_hat()), &mu, sig_sized), } } } diff --git a/crypto/mldsa/src/lib.rs b/crypto/mldsa/src/lib.rs index 7480c3c..e0f2b4f 100644 --- a/crypto/mldsa/src/lib.rs +++ b/crypto/mldsa/src/lib.rs @@ -176,6 +176,8 @@ pub use mldsa::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; pub use mldsa::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; pub use mldsa::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; +pub use mldsa::MU_BUILDER_SERIALIZED_STATE_LEN; + pub use matrix::Matrix; // re-export just so it's visible to unit tests diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index 102293a..434777b 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -398,6 +398,81 @@ //! Err(e) => panic!("Something else went wrong: {:?}", e), //! } //! ``` +//! +//! # Suspending and resuming execution via SerializableState +//! +//! When signing or verifying a large message, it can be advantageous to be able to suspend the operation +//! to a cache and resume it later; for example if waiting for the message to stream over a slow network +//! connection. +//! +//! This can bo accomplished for both the ML-DSA signer and verifier through the [MuBuilder] object. +//! +//! Suspending an in-progress sign operation: +//! +//! ```rust +//! use bouncycastle_mldsa::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; +//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let (pk, sk) = MLDSA65::keygen().unwrap(); +//! +//! let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); +//! mb.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = mb.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! mb_resumed.do_update(msg_part2); +//! let mu: [u8; 64] = mb_resumed.do_final(); +//! +//! // Now we'll do the actual sign_mu operation +//! let sig = MLDSA65::sign_mu(&sk, None, &mu).unwrap(); +//! ``` +//! +//! Suspending an in-progress verify operation behaves exactly the same way: +//! +//! ```rust +//! use bouncycastle_mldsa::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; +//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! use bouncycastle_core::errors::SignatureError; +//! +//! let (pk, sk) = MLDSA65::keygen().unwrap(); +//! +//! // first, let's generate a signature to verify +//! let sig = MLDSA65::sign(&sk, b"The quick brown fox jumped over the lazy dog", None).unwrap(); +//! +//! // Now we'll verify it with a suspension in the middle +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); +//! mb.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = mb.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! mb_resumed.do_update(msg_part2); +//! let mu: [u8; 64] = mb_resumed.do_final(); +//! +//! // Now we'll do the actual verify_mu operation +//! match MLDSA65::verify_mu(&pk, Some(&pk.A_hat()), &mu, &sig) { +//! Ok(()) => println!("Signature is valid!"), +//! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), +//! Err(e) => panic!("Something else went wrong: {:?}", e), +//! } +//! ``` use crate::aux_functions::{ expand_mask, expandA, expandS, make_hint_vecs, power_2_round_vec, sample_in_ball, sig_decode, @@ -410,11 +485,13 @@ use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyExpanded, MLDSAPublicKeyExpanded, }; -use bouncycastle_core::errors::{RNGError, SignatureError}; +use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, XOF}; +use bouncycastle_core::traits::{ + Algorithm, RNG, SecurityStrength, SerializableState, SignatureVerifier, Signer, XOF, +}; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha3::{SHAKE128, SHAKE256}; +use bouncycastle_sha3::{SHA3_SERIALIZED_STATE_LEN, SHAKE128, SHAKE256}; use core::marker::PhantomData; // imports needed just for docs @@ -1017,6 +1094,99 @@ impl< Ok(bytes_written) } + + /// Algorithm 8 ML-DSA.Verify_internal(𝑝𝑘, 𝑀′, 𝜎) + /// Internal function to verify a signature 𝜎 for a formatted message 𝑀′ . + /// Input: Public key 𝑝𝑘 ∈ 𝔹32+32𝑘(bitlen (𝑞−1)−𝑑) and message 𝑀′ ∈ {0, 1}∗ . + /// Input: Signature 𝜎 ∈ 𝔹𝜆/4+ℓ⋅32⋅(1+bitlen (𝛾1−1))+𝜔+𝑘. + fn verify_internal( + pk: &PK, + A_hat: &Matrix, + mu: &[u8; 64], + sig: &[u8; SIG_LEN], + ) -> Result<(), SignatureError> { + // 1: (𝜌, 𝐭1) ← pkDecode(𝑝𝑘) + // Already done -- the pk struct is already decoded + + // 2: (𝑐_tilde, 𝐳, 𝐡) ← sigDecode(𝜎) + // ▷ signer’s commitment hash c_tilde, response 𝐳, and hint 𝐡 + // 3: if 𝐡 = ⊥ then return false + let (c_tilde, z, h) = + sig_decode::(&sig) + .map_err(|_| SignatureError::SignatureVerificationFailed)?; + + // 13 (first half) return [[ ||𝐳||∞ < 𝛾1 − 𝛽]] + if z.check_norm::() { + return Err(SignatureError::SignatureVerificationFailed); + } + + // 5: 𝐀 ← ExpandA(𝜌) + // ▷ 𝐀 is generated and stored in NTT representation as 𝐀 + // We're doing an optimization where the user can pre-expand A_hat within the + // public key object for faster repeated encapsulations against this public key. + + // 6: 𝑡𝑟 ← H(𝑝𝑘, 64) + // 7: 𝜇 ← (H(BytesToBits(𝑡𝑟)||𝑀 ′, 64)) + // ▷ message representative that may optionally be + // computed in a different cryptographic module + // skip because this function is being handed mu + + // 8: 𝑐 ∈ 𝑅𝑞 ← SampleInBall(c_tilde) + let c_hat = { + let mut c = sample_in_ball::(&c_tilde); + c.ntt(); + + c + }; + + // 9: 𝐰′_approx ← NTT−1(𝐀_hat ∘ NTT(𝐳) − NTT(𝑐) ∘ NTT(𝐭1 ⋅ 2^𝑑)) + // broken out for clarity: + // NTT−1( + // 𝐀_hat ∘ NTT(𝐳) − + // NTT(𝑐) ∘ NTT(𝐭1 ⋅ 2^𝑑) + // ) + // ▷ 𝐰'_approx = 𝐀𝐳 − 𝑐𝐭1 ⋅ 2^𝑑 + // weird nested scoping is to reduce peak stack memory usage + let w1p = { + let Az = { + let mut z_hat = z.clone(); + z_hat.ntt(); + A_hat.matrix_vector_ntt(&z_hat) + }; + let ct1 = { + // potential optimization -- pre-compute this on key load? + let mut t1_shift_hat = pk.t1().shift_left::(); + t1_shift_hat.ntt(); + t1_shift_hat.scalar_vector_ntt(&c_hat) + }; + let mut wp_approx = Az.sub_vector(&ct1); + wp_approx.inv_ntt(); + wp_approx.conditional_add_q(); + + // 10: 𝐰1′ ← UseHint(𝐡, 𝐰'_approx) + // ▷ reconstruction of signer’s commitment + use_hint_vecs::(&h, &wp_approx) + }; + // 12: 𝑐_tilde_p ← H(𝜇||w1Encode(𝐰1'), 𝜆/4) + // ▷ hash it; this should match 𝑐_tilde + let c_tilde_p = { + let mut c_tilde_p = [0u8; LAMBDA_over_4]; + let mut hash = H::new(); + hash.absorb(mu); + w1p.w1_encode_and_hash::(&mut hash); + hash.squeeze_out(&mut c_tilde_p); + + c_tilde_p + }; + + // verification probably doesn't technically need to be constant-time, but why not? + // 13 (second half): return [[ ||𝐳||∞ < 𝛾1 − 𝛽]] and [[𝑐 ̃ = 𝑐′ ]] + if bouncycastle_utils::ct::ct_eq_bytes(&c_tilde, &c_tilde_p) { + Ok(()) + } else { + Err(SignatureError::SignatureVerificationFailed) + } + } } impl< @@ -1524,108 +1694,19 @@ impl< let sig: &[u8; SIG_LEN] = sig.try_into().map_err(|_| { SignatureError::LengthError("Signature value is not the correct length.") })?; - Self::verify_mu_internal(&pk.pk, &pk.A_hat(), &mu, sig) - .then_some(()) - .ok_or(SignatureError::SignatureVerificationFailed) + Self::verify_mu(&pk.pk, Some(&pk.A_hat()), &mu, sig) } - /// Algorithm 8 ML-DSA.Verify_internal(𝑝𝑘, 𝑀′, 𝜎) - /// Internal function to verify a signature 𝜎 for a formatted message 𝑀′ . - /// Input: Public key 𝑝𝑘 ∈ 𝔹32+32𝑘(bitlen (𝑞−1)−𝑑) and message 𝑀′ ∈ {0, 1}∗ . - /// Input: Signature 𝜎 ∈ 𝔹𝜆/4+ℓ⋅32⋅(1+bitlen (𝛾1−1))+𝜔+𝑘. - fn verify_mu_internal( + fn verify_mu( pk: &PK, - A_hat: &Matrix, + A_hat: Option<&Matrix>, mu: &[u8; 64], sig: &[u8; SIG_LEN], - ) -> bool { - // 1: (𝜌, 𝐭1) ← pkDecode(𝑝𝑘) - // Already done -- the pk struct is already decoded - - // 2: (𝑐_tilde, 𝐳, 𝐡) ← sigDecode(𝜎) - // ▷ signer’s commitment hash c_tilde, response 𝐳, and hint 𝐡 - // 3: if 𝐡 = ⊥ then return false - let (c_tilde, z, h) = match sig_decode::< - GAMMA1, - k, - l, - LAMBDA_over_4, - OMEGA, - POLY_Z_PACKED_LEN, - SIG_LEN, - >(&sig) - { - Ok((c_tilde, z, h)) => (c_tilde, z, h), - Err(_) => return false, - }; - - // 13 (first half) return [[ ||𝐳||∞ < 𝛾1 − 𝛽]] - if z.check_norm::() { - return false; + ) -> Result<(), SignatureError> { + match A_hat { + Some(A_hat) => Self::verify_internal(pk, A_hat, mu, sig), + None => Self::verify_internal(pk, &pk.A_hat(), mu, sig), } - - // 5: 𝐀 ← ExpandA(𝜌) - // ▷ 𝐀 is generated and stored in NTT representation as 𝐀 - // We're doing an optimization where the user can pre-expand A_hat within the - // public key object for faster repeated encapsulations against this public key. - - // 6: 𝑡𝑟 ← H(𝑝𝑘, 64) - // 7: 𝜇 ← (H(BytesToBits(𝑡𝑟)||𝑀 ′, 64)) - // ▷ message representative that may optionally be - // computed in a different cryptographic module - // skip because this function is being handed mu - - // 8: 𝑐 ∈ 𝑅𝑞 ← SampleInBall(c_tilde) - let c_hat = { - let mut c = sample_in_ball::(&c_tilde); - c.ntt(); - - c - }; - - // 9: 𝐰′_approx ← NTT−1(𝐀_hat ∘ NTT(𝐳) − NTT(𝑐) ∘ NTT(𝐭1 ⋅ 2^𝑑)) - // broken out for clarity: - // NTT−1( - // 𝐀_hat ∘ NTT(𝐳) − - // NTT(𝑐) ∘ NTT(𝐭1 ⋅ 2^𝑑) - // ) - // ▷ 𝐰'_approx = 𝐀𝐳 − 𝑐𝐭1 ⋅ 2^𝑑 - // weird nested scoping is to reduce peak stack memory usage - let w1p = { - let Az = { - let mut z_hat = z.clone(); - z_hat.ntt(); - A_hat.matrix_vector_ntt(&z_hat) - }; - let ct1 = { - // potential optimization -- pre-compute this on key load? - let mut t1_shift_hat = pk.t1().shift_left::(); - t1_shift_hat.ntt(); - t1_shift_hat.scalar_vector_ntt(&c_hat) - }; - let mut wp_approx = Az.sub_vector(&ct1); - wp_approx.inv_ntt(); - wp_approx.conditional_add_q(); - - // 10: 𝐰1′ ← UseHint(𝐡, 𝐰'_approx) - // ▷ reconstruction of signer’s commitment - use_hint_vecs::(&h, &wp_approx) - }; - // 12: 𝑐_tilde_p ← H(𝜇||w1Encode(𝐰1'), 𝜆/4) - // ▷ hash it; this should match 𝑐_tilde - let c_tilde_p = { - let mut c_tilde_p = [0u8; LAMBDA_over_4]; - let mut hash = H::new(); - hash.absorb(mu); - w1p.w1_encode_and_hash::(&mut hash); - hash.squeeze_out(&mut c_tilde_p); - - c_tilde_p - }; - - // verification probably doesn't technically need to be constant-time, but why not? - // 13 (second half): return [[ ||𝐳||∞ < 𝛾1 − 𝛽]] and [[𝑐 ̃ = 𝑐′ ]] - bouncycastle_utils::ct::ct_eq_bytes(&c_tilde, &c_tilde_p) } } @@ -1750,6 +1831,8 @@ pub trait MLDSATrait< /// This implements FIPS 204 Algorithm 7 with line 6 removed; a modification that is allowed by both /// FIPS 204 itself, as well as subsequent FAQ documents. /// This mode uses randomized signing (called "hedged mode" in FIPS 204) using an internal RNG. + /// + /// Optionally, takes a pre-expanded public matrix `A_hat`. fn sign_mu( sk: &SK, A_hat: Option<&Matrix>, @@ -1874,16 +1957,16 @@ pub trait MLDSATrait< ctx: Option<&[u8]>, sig: &[u8], ) -> Result<(), SignatureError>; - /// Algorithm 8 ML-DSA.Verify_internal(𝑝𝑘, 𝑀′, 𝜎) - /// Internal function to verify a signature 𝜎 for a formatted message 𝑀′ . - /// Input: Public key 𝑝𝑘 ∈ 𝔹32+32𝑘(bitlen (𝑞−1)−𝑑) and message 𝑀′ ∈ {0, 1}∗ . - /// Input: Signature 𝜎 ∈ 𝔹𝜆/4+ℓ⋅32⋅(1+bitlen (𝛾1−1))+𝜔+𝑘. - fn verify_mu_internal( + /// Performs an ML-DSA signature verification using the provided external message representative `mu`. + /// This implements FIPS 204 Algorithm 8 with line 7 removed; a modification that is allowed by both + /// FIPS 204 itself, as well as subsequent FAQ documents. + /// Optionally, takes a pre-expanded public matrix `A_hat`. + fn verify_mu( pk: &PK, - A_hat: &Matrix, + A_hat: Option<&Matrix>, mu: &[u8; 64], sig: &[u8; SIG_LEN], - ) -> bool; + ) -> Result<(), SignatureError>; } impl< @@ -2067,9 +2150,7 @@ impl< let sig: &[u8; SIG_LEN] = sig.try_into().map_err(|_| { SignatureError::LengthError("Signature value is not the correct length.") })?; - Self::verify_mu_internal(pk, &pk.A_hat(), &mu, sig) - .then_some(()) - .ok_or(SignatureError::SignatureVerificationFailed) + Self::verify_mu(pk, Some(&pk.A_hat()), &mu, sig) } fn verify_init(pk: &PK, ctx: Option<&[u8]>) -> Result { @@ -2097,9 +2178,7 @@ impl< let sig: &[u8; SIG_LEN] = sig.try_into().map_err(|_| { SignatureError::LengthError("Signature value is not the correct length.") })?; - Self::verify_mu_internal(pk, &pk.A_hat(), &mu, sig) - .then_some(()) - .ok_or(SignatureError::SignatureVerificationFailed) + Self::verify_mu(pk, Some(&pk.A_hat()), &mu, sig) } } @@ -2112,6 +2191,7 @@ impl< /// does not benefit from allowing external construction of the message representative mu. /// You can get the same behaviour by computing the pre-hash `ph` with the appropriate hash function /// and providing that to HashMLDSA via [PHSigner::sign_ph]. +#[derive(Clone)] pub struct MuBuilder { h: H, } @@ -2177,3 +2257,25 @@ impl MuBuilder { mu } } + +/// The length, in bytes, of a serialized state of a [MuBuilder] object. +pub const MU_BUILDER_SERIALIZED_STATE_LEN: usize = SHA3_SERIALIZED_STATE_LEN; + +/// If you are processing a large input message into ML-DSA and want to pause the operation +/// -- maybe while waiting for slow network IO), you'll need to use [SerializableState]. +/// Serialization of the state of an in-progress ML-DSA instance is really just serialization +/// of the construction of the message representative mu, since no other part of the ML-DSA algorithm +/// has a pausable state. +// A [MuBuilder]'s (and by virtue, an ML-DSA instance's) entire mutable state is its inner SHAKE256 sponge, +// so serialization delegates directly to [SHAKE256]'s [SerializableState] impl. +impl SerializableState for MuBuilder { + fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { + self.h.serialize_state() + } + + fn from_serialized_state( + serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + ) -> Result { + Ok(MuBuilder { h: H::from_serialized_state(serialized_state)? }) + } +} diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index 73e1f16..8207a96 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -2,15 +2,17 @@ #[cfg(test)] mod mldsa_tests { use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; - use bouncycastle_core::errors::{RNGError, SignatureError}; + use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; use bouncycastle_core::key_material::{ KeyMaterial256, KeyMaterialTrait, KeyType, do_hazardous_operations, }; use bouncycastle_core::traits::{ - RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, + RNG, SecurityStrength, SerializableState, SignaturePrivateKey, SignaturePublicKey, + SignatureVerifier, Signer, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::FixedSeedRNG; + use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ @@ -974,6 +976,30 @@ mod mldsa_tests { mb.do_update(b"jumped over the lazy dog"); let mu6 = mb.do_final(); assert_eq!(mu1, mu6); + + // test SerializedState for MuBuilder + + // serializing and resuming after init + let mb = MuBuilder::do_init(&pk_expanded.compute_tr(), None).unwrap(); + let serialized_state = mb.serialize_state(); + + let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + mb_resumed.do_update(msg); + let mu_resumed = mb_resumed.do_final(); + assert_eq!(mu_resumed, mu1); + + // serializing and resuming partway through message consumption + let msg1 = b"The quick brown fox"; + let msg2 = b" jumped over the lazy dog"; + + let mut mb = MuBuilder::do_init(&pk_expanded.compute_tr(), None).unwrap(); + mb.do_update(msg1); + + let serialized_state = mb.serialize_state(); + let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + mb_resumed.do_update(msg2); + let mu_resumed2 = mb_resumed.do_final(); + assert_eq!(mu_resumed2, mu1); } #[test] @@ -997,6 +1023,66 @@ mod mldsa_tests { MLDSA44::sign_mu_deterministic_out(&sk, None, &mu, [1u8; 32], &mut sig_buf).unwrap(); MLDSA44::verify(&pk, msg, None, &sig_buf).unwrap(); } + + #[test] + fn mubuilder_serializable_state() { + // `tr` is the 64-byte public-key hash that seeds mu computation; its exact value doesn't + // matter for exercising serialization, only that it is fixed across the comparison. + let tr = [0x42u8; 64]; + let msg = b"Colorless green ideas sleep furiously"; + + // Put the builder into a mid-stream state (some, but not all, of the message absorbed). + let mut mb = MuBuilder::do_init(&tr, None).unwrap(); + mb.do_update(&msg[..10]); + + // Generic trait-conformance tests (version header present, [0,0,0] and future versions + // rejected). + TestFrameworkSerializableState::new().test(&mb); + + // Serialize the in-progress state, then finish the original. + let serialized_state = mb.clone().serialize_state(); + mb.do_update(&msg[10..]); + let expected_mu = mb.do_final(); + + // Rebuild from the serialized state, feed it the identical remaining input, and confirm it + // produces the same mu. + let mut from_state = MuBuilder::from_serialized_state(serialized_state).unwrap(); + from_state.do_update(&msg[10..]); + assert_eq!(expected_mu, from_state.do_final()); + + // Anchor correctness to the existing one-shot path: streaming must match compute_mu. + let one_shot = MuBuilder::compute_mu(&tr, msg, None).unwrap(); + assert_eq!(expected_mu, one_shot); + + // A corrupt SHAKE256 `squeezing` byte must be rejected. Layout of the delegated SHAKE256 + // state: 3 version bytes + variant tag(1) + [u64;25](200) + data_queue(192) + // + bits_in_queue(8) + squeezing(1) -> the squeezing byte is at offset 3 + 1 + 400. + let mut busted = serialized_state; + busted[3 + 1 + 400] = 42; + match MuBuilder::from_serialized_state(busted) { + Err(SerializedStateError::InvalidData) => { /* good */ } + _ => panic!("Expected an error for a corrupt squeezing byte"), + } + } + + #[test] + fn serializable_state_mubuilder_rejects_wrong_variant() { + use bouncycastle_core::traits::XOF; + use bouncycastle_sha3::SHAKE128; + + // A MuBuilder is always backed by SHAKE256. A serialized SHAKE128 state has the same length + // (both are SHA3-family states), so this would deserialize into the wrong sponge if the + // variant tag weren't checked -- SHAKE128 (tag 5) must be rejected by MuBuilder (SHAKE256, + // tag 6). + let mut shake128 = SHAKE128::new(); + shake128.absorb(b"Colorless green ideas sleep furiously"); + let serialized_128 = shake128.serialize_state(); + + match MuBuilder::from_serialized_state(serialized_128) { + Err(SerializedStateError::InvalidData) => { /* good */ } + _ => panic!("Expected an error when loading a SHAKE128 state into a MuBuilder"), + } + } } struct Kat { diff --git a/crypto/mldsa/tests/wycheproof.rs b/crypto/mldsa/tests/wycheproof.rs index 523be36..93c9855 100644 --- a/crypto/mldsa/tests/wycheproof.rs +++ b/crypto/mldsa/tests/wycheproof.rs @@ -353,17 +353,17 @@ impl MLDSASignNoSeedTestCase { let sig = MLDSA44::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA44::verify_mu_internal( + let res = MLDSA44::verify_mu( &pk, - &pk.A_hat(), + Some(&pk.A_hat()), &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap(), ); if self.result == "valid" { - assert!(res); + res.unwrap() } else { - assert!(!res); + assert!(res.is_err()); }; } @@ -421,17 +421,17 @@ impl MLDSASignNoSeedTestCase { let sig = MLDSA65::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA65::verify_mu_internal( + let res = MLDSA65::verify_mu( &pk, - &pk.A_hat(), + Some(&pk.A_hat()), &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap(), ); if self.result == "valid" { - assert!(res); + res.unwrap() } else { - assert!(!res); + assert!(res.is_err()); }; } @@ -489,17 +489,17 @@ impl MLDSASignNoSeedTestCase { let sig = MLDSA87::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA87::verify_mu_internal( + let res = MLDSA87::verify_mu( &pk, - &pk.A_hat(), + Some(&pk.A_hat()), &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap(), ); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } } @@ -650,17 +650,17 @@ impl MLDSASignSeedTestCase { let sig = MLDSA44::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA44::verify_mu_internal( + let res = MLDSA44::verify_mu( &pk, - &pk.A_hat(), + Some(&pk.A_hat()), &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap(), ); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } @@ -742,17 +742,17 @@ impl MLDSASignSeedTestCase { let sig = MLDSA65::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA65::verify_mu_internal( + let res = MLDSA65::verify_mu( &pk, - &pk.A_hat(), + Some(&pk.A_hat()), &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap(), ); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } @@ -834,17 +834,17 @@ impl MLDSASignSeedTestCase { let sig = MLDSA87::sign_mu_deterministic(&sk, None, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA87::verify_mu_internal( + let res = MLDSA87::verify_mu( &pk, - &pk.A_hat(), + Some(&pk.A_hat()), &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap(), ); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } } diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index d8a4544..d0bac1e 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -18,8 +18,8 @@ //! for example if input is received in chunks and not all available at the same time: //! //! ``` -//! use bouncycastle_core::traits::Hash; //! use bouncycastle_sha2 as sha2; +//! use bouncycastle_core::traits::Hash; //! //! let data: &[u8] = 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 @@ -33,6 +33,36 @@ //! //! let output: Vec = sha2.do_final(); //! ``` +//! +//! # Suspending and resuming execution via SerializableState +//! +//! When hashing a large message, it can be advantageous to be able to suspend the operation +//! to a cache and resume it later; for example if waiting for the message to stream over a slow network +//! connection. +//! +//! For this reason, all SHA2 algorithms impl [SerializableState]. +//! +//! ```rust +//! use bouncycastle_sha2 as sha2; +//! use bouncycastle_core::traits::{Hash, SerializableState}; +//! +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let mut sha2 = sha2::SHA256::new(); +//! sha2.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = sha2.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! let mut sha2_resumed = sha2::SHA256::from_serialized_state(serialized_state).unwrap(); +//! sha2_resumed.do_update(msg_part2); +//! let h: Vec = sha2_resumed.do_final(); +//! ``` #![forbid(unsafe_code)] #![allow(private_bounds)] @@ -68,6 +98,7 @@ impl HashAlgParams for SHA224 { const OUTPUT_LEN: usize = 28; const BLOCK_LEN: usize = 64; } +#[derive(Clone)] pub struct SHA224Params; impl Algorithm for SHA224Params { const ALG_NAME: &'static str = SHA224_NAME; @@ -87,6 +118,7 @@ impl HashAlgParams for SHA256 { const OUTPUT_LEN: usize = 32; const BLOCK_LEN: usize = 64; } +#[derive(Clone)] pub struct SHA256Params; impl Algorithm for SHA256Params { const ALG_NAME: &'static str = SHA256_NAME; @@ -106,6 +138,7 @@ impl HashAlgParams for SHA384 { const OUTPUT_LEN: usize = 48; const BLOCK_LEN: usize = 128; } +#[derive(Clone)] pub struct SHA384Params; impl Algorithm for SHA384Params { const ALG_NAME: &'static str = SHA384_NAME; @@ -117,6 +150,7 @@ impl HashAlgParams for SHA384Params { } impl SHA2Params for SHA384Params {} +#[derive(Clone)] pub struct SHA512Params; impl Algorithm for SHA512 { const ALG_NAME: &'static str = SHA512_NAME; @@ -135,3 +169,6 @@ impl HashAlgParams for SHA512Params { const BLOCK_LEN: usize = 128; } impl SHA2Params for SHA512Params {} + +pub use sha256::SHA256_SERIALIZED_STATE_LEN; +pub use sha512::SHA512_SERIALIZED_STATE_LEN; diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index 6995642..bbd56e5 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -1,5 +1,5 @@ use crate::SHA2Params; -use bouncycastle_core::errors::{CoreError, HashError}; +use bouncycastle_core::errors::{HashError, SerializedStateError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; use bouncycastle_utils::min; @@ -304,9 +304,15 @@ impl Hash for SHA256Internal { } } -impl SerializableState<108> for SHA256Internal { - fn serialize_state(&self) -> [u8; 108] { - let mut out_to_return = [0u8; 108]; +/// The number of bytes produced by [SerializableState::serialize_state] for any SHA224 or SHA256 +/// object. +pub const SHA256_SERIALIZED_STATE_LEN: usize = 108; + +impl SerializableState for SHA256Internal { + fn serialize_state(self) -> [u8; SHA256_SERIALIZED_STATE_LEN] { + debug_assert_eq!(SHA256_SERIALIZED_STATE_LEN, 108); + + let mut out_to_return = [0u8; SHA256_SERIALIZED_STATE_LEN]; // insert the version tag let out: &mut [u8; 105] = add_lib_ver(&mut out_to_return).try_into().unwrap(); @@ -331,7 +337,11 @@ impl SerializableState<108> for SHA256Internal { out_to_return } - fn from_serialized_state(serialized_state: [u8; 108]) -> Result { + fn from_serialized_state( + serialized_state: [u8; SHA256_SERIALIZED_STATE_LEN], + ) -> Result { + debug_assert_eq!(SHA256_SERIALIZED_STATE_LEN, 108); + // check the version tag // At the moment, we have no not_before version to specify. let input: &[u8; 105] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); @@ -353,7 +363,7 @@ impl SerializableState<108> for SHA256Internal { // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 64 let x_buf_off: usize = input[104] as usize; if x_buf_off >= 64 { - return Err(CoreError::InvalidData); + return Err(SerializedStateError::InvalidData); } // Construct the object diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 0c5ed13..7b98b0e 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -1,5 +1,5 @@ use crate::SHA2Params; -use bouncycastle_core::errors::{CoreError, HashError}; +use bouncycastle_core::errors::{HashError, SerializedStateError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; use bouncycastle_utils::min; @@ -319,9 +319,15 @@ impl Hash for SHA512Internal { } } -impl SerializableState<204> for SHA512Internal { - fn serialize_state(&self) -> [u8; 204] { - let mut out_to_return = [0u8; 204]; +/// The number of bytes produced by [SerializableState::serialize_state] for any SHA384 or SHA512 +/// object. +pub const SHA512_SERIALIZED_STATE_LEN: usize = 204; + +impl SerializableState for SHA512Internal { + fn serialize_state(self) -> [u8; SHA512_SERIALIZED_STATE_LEN] { + debug_assert_eq!(SHA512_SERIALIZED_STATE_LEN, 204); + + let mut out_to_return = [0u8; SHA512_SERIALIZED_STATE_LEN]; // insert the version tag let out: &mut [u8; 201] = add_lib_ver(&mut out_to_return).try_into().unwrap(); @@ -346,7 +352,9 @@ impl SerializableState<204> for SHA512Internal { out_to_return } - fn from_serialized_state(serialized_state: [u8; 204]) -> Result { + fn from_serialized_state( + serialized_state: [u8; SHA512_SERIALIZED_STATE_LEN], + ) -> Result { // check the version tag // At the moment, we have no not_before version to specify. let input: &[u8; 201] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); @@ -368,7 +376,7 @@ impl SerializableState<204> for SHA512Internal { // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 128 let x_buf_off: usize = input[200] as usize; if x_buf_off >= 128 { - return Err(CoreError::InvalidData); + return Err(SerializedStateError::InvalidData); } // Construct the object diff --git a/crypto/sha2/tests/sha2_tests.rs b/crypto/sha2/tests/sha2_tests.rs index 442b94b..ad49cdd 100644 --- a/crypto/sha2/tests/sha2_tests.rs +++ b/crypto/sha2/tests/sha2_tests.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod sha2_tests { - use bouncycastle_core::errors::CoreError; + use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::traits::{Algorithm, Hash, HashAlgParams, SecurityStrength}; use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::hash::TestFrameworkHash; @@ -107,7 +107,7 @@ mod sha2_tests { test_framework.test(&sha256); // now let's serialize the in-progress state - let serialized_state = sha256.serialize_state(); + let serialized_state = sha256.clone().serialize_state(); // finish the hash let output = sha256.do_final(); @@ -121,7 +121,7 @@ mod sha2_tests { let mut busted_state = serialized_state.clone(); busted_state[3 + 104] = 65; match SHA256::from_serialized_state(busted_state) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error"), } @@ -134,7 +134,7 @@ mod sha2_tests { test_framework.test(&sha512); // now let's serialize the in-progress state - let serialized_state = sha512.serialize_state(); + let serialized_state = sha512.clone().serialize_state(); // finish the hash let output = sha512.do_final(); @@ -148,7 +148,7 @@ mod sha2_tests { let mut busted_state = serialized_state.clone(); busted_state[3 + 200] = 129; match SHA512::from_serialized_state(busted_state) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error"), } } diff --git a/crypto/sha3/src/keccak.rs b/crypto/sha3/src/keccak.rs index 27fc702..3609b88 100644 --- a/crypto/sha3/src/keccak.rs +++ b/crypto/sha3/src/keccak.rs @@ -1,7 +1,6 @@ -use bouncycastle_core::errors::{CoreError, HashError}; +use bouncycastle_core::errors::{HashError, SerializedStateError}; use bouncycastle_core::key_material::KeyType; use bouncycastle_core::traits::SecurityStrength; -use crate::{SHA3_224Params, SHA3_256Params, SHA3_384Params, SHA3_512Params, SHAKE128Params, SHAKE256Params, SHA3, SHAKE}; const KECCAK_ROUND_CONSTANTS: [u64; 24] = [ 0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000, @@ -190,13 +189,14 @@ impl Drop for KeccakState { } } +// This is pub(crate) so that the SerializableState handlers can unpack it. #[derive(Clone)] -pub(super) struct KeccakDigest { +pub(crate) struct KeccakDigest { state: KeccakState, pub data_queue: [u8; 192], rate: usize, pub bits_in_queue: usize, - pub(super) squeezing: bool, + pub squeezing: bool, } #[derive(Clone)] @@ -372,12 +372,10 @@ const KECCAK_SERIALIZED_LEN: usize = 200 + 192 + 8 + 1; /// [.. + 1) kdf_key_type (1 byte enum tag) /// [.. + 1) kdf_security_strength (1 byte enum tag) /// [.. + 8) kdf_entropy usize serialized as u64 -pub(super) const SHA3_FAMILY_STATE_LEN: usize = 1 + KECCAK_SERIALIZED_LEN + 10; +pub(crate) const SHA3_FAMILY_STATE_LEN: usize = 1 + KECCAK_SERIALIZED_LEN + 10; -/// Total number of bytes in a serialized SHA3-family state, including the 3-byte library version -/// header prepended by [add_lib_ver]. This is the const generic used by the `SerializableState` -/// impls for SHA3 and SHAKE. -pub(super) const SHA3_FAMILY_SERIALIZED_STATE_LEN: usize = 3 + SHA3_FAMILY_STATE_LEN; +/// Total number of bytes in a serialized state of a SHA3 or SHAKE instance. +pub const SHA3_SERIALIZED_STATE_LEN: usize = 3 + SHA3_FAMILY_STATE_LEN; impl KeccakDigest { /// Serializes this digest's mutable state into `out`. The `rate` is deliberately omitted; see @@ -407,7 +405,7 @@ impl KeccakDigest { fn from_serialized_state( input: &[u8; KECCAK_SERIALIZED_LEN], rate: usize, - ) -> Result { + ) -> Result { // state.buf: [u64; 25] let mut buf = [0u64; 25]; for i in 0..25 { @@ -421,14 +419,14 @@ impl KeccakDigest { // bytes, well within data_queue's 192-byte capacity). let bits_in_queue = u64::from_le_bytes(input[392..400].try_into().unwrap()) as usize; if bits_in_queue > rate { - return Err(CoreError::InvalidData); + return Err(SerializedStateError::InvalidData); } // squeezing: bool let squeezing = match input[400] { 0 => false, 1 => true, - _ => return Err(CoreError::InvalidData), + _ => return Err(SerializedStateError::InvalidData), }; Ok(Self { state: KeccakState { buf, rate }, data_queue, rate, bits_in_queue, squeezing }) @@ -437,7 +435,7 @@ impl KeccakDigest { /// Serializes the state shared by all SHA3-family objects (the `variant_tag`, a [KeccakDigest], plus /// the three KDF metadata fields) into `out`. See [SHA3_FAMILY_STATE_LEN] for the layout. -pub(super) fn serialize_sha3_family_state( +pub(crate) fn serialize_sha3_family_state( out: &mut [u8; SHA3_FAMILY_STATE_LEN], variant_tag: u8, keccak: &KeccakDigest, @@ -463,13 +461,13 @@ pub(super) fn serialize_sha3_family_state( /// tag is checked against the serialized one first: this is what prevents a state from one variant /// being loaded into another (e.g. SHA3-256 vs SHAKE256, which share a rate but differ in domain /// separation). Only once the tag matches is `rate` guaranteed to be the correct one to rebuild with. -pub(super) fn deserialize_sha3_family_state( +pub(crate) fn deserialize_sha3_family_state( input: &[u8; SHA3_FAMILY_STATE_LEN], expected_variant_tag: u8, rate: usize, -) -> Result<(KeccakDigest, KeyType, SecurityStrength, usize), CoreError> { +) -> Result<(KeccakDigest, KeyType, SecurityStrength, usize), SerializedStateError> { if input[0] != expected_variant_tag { - return Err(CoreError::InvalidData); + return Err(SerializedStateError::InvalidData); } let keccak_in: &[u8; KECCAK_SERIALIZED_LEN] = diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 3f5a7ca..8a80639 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -103,6 +103,36 @@ //! This would also be the case even if the input had type //! [KeyType::CryptographicRandom] since the input [KeyMaterial] is 16 bytes but [SHA3_256] needs at least 32 bytes of //! full-entropy input key material in order to be able to produce full entropy output key material. +//! +//! # Suspending and resuming execution via SerializableState +//! +//! When hashing a large message, it can be advantageous to be able to suspend the operation +//! to a cache and resume it later; for example if waiting for the message to stream over a slow network +//! connection. +//! +//! For this reason, all SHA3 algorithms impl [SerializableState]. +//! +//!```rust +//! use bouncycastle_sha3 as sha3; +//! use bouncycastle_core::traits::{Hash, SerializableState}; +//! +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let mut sha3 = sha3::SHA3_256::new(); +//! sha3.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = sha3.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! let mut sha3_resumed = sha3::SHA3_256::from_serialized_state(serialized_state).unwrap(); +//! sha3_resumed.do_update(msg_part2); +//! let h: Vec = sha3_resumed.do_final(); +//! ``` #![forbid(unsafe_code)] #![allow(private_bounds)] @@ -132,6 +162,10 @@ pub const SHAKE256_NAME: &str = "SHAKE256"; /*** pub types ***/ pub use sha3::SHA3; pub use shake::SHAKE; + +/// The number of bytes produced by [SerializableState::serialize_state] for any SHA3 or SHAKE +/// object. +pub use keccak::SHA3_SERIALIZED_STATE_LEN; pub type SHA3_224 = SHA3; pub type SHA3_256 = SHA3; pub type SHA3_384 = SHA3; @@ -160,6 +194,7 @@ impl HashAlgParams for SHA3_224 { // const BLOCK_LEN: usize = 64; const BLOCK_LEN: usize = 144; // FIPS 202 Table 3 } +#[derive(Clone)] pub struct SHA3_224Params; impl Algorithm for SHA3_224Params { const ALG_NAME: &'static str = SHA3_224_NAME; @@ -184,6 +219,7 @@ impl HashAlgParams for SHA3_256 { // const BLOCK_LEN: usize = 64; const BLOCK_LEN: usize = 136; // FIPS 202 Table 3 } +#[derive(Clone)] pub struct SHA3_256Params; impl Algorithm for SHA3_256Params { const ALG_NAME: &'static str = SHA3_256_NAME; @@ -199,6 +235,7 @@ impl SHA3Params for SHA3_256Params { const STATE_TAG: u8 = 2; } +#[derive(Clone)] pub struct SHA3_384Params; impl Algorithm for SHA3_384 { const ALG_NAME: &'static str = SHA3_384_NAME; @@ -223,6 +260,7 @@ impl SHA3Params for SHA3_384Params { const STATE_TAG: u8 = 3; } +#[derive(Clone)] pub struct SHA3_512Params; impl Algorithm for SHA3_512 { const ALG_NAME: &'static str = SHA3_512_NAME; @@ -252,6 +290,7 @@ trait SHAKEParams: Algorithm { /// See [SHA3Params::STATE_TAG]. Must be distinct from every SHA3 *and* SHAKE variant's tag. const STATE_TAG: u8; } +#[derive(Clone)] pub struct SHAKE128Params; impl Algorithm for SHAKE128Params { const ALG_NAME: &'static str = SHAKE128_NAME; @@ -262,6 +301,7 @@ impl SHAKEParams for SHAKE128Params { const STATE_TAG: u8 = 5; } +#[derive(Clone)] pub struct SHAKE256Params; impl Algorithm for SHAKE256Params { const ALG_NAME: &'static str = SHAKE256_NAME; diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 2e4ca72..4d78f37 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -1,9 +1,9 @@ use crate::SHA3Params; use crate::keccak::{ - KeccakDigest, SHA3_FAMILY_SERIALIZED_STATE_LEN, SHA3_FAMILY_STATE_LEN, - deserialize_sha3_family_state, serialize_sha3_family_state, + KeccakDigest, SHA3_FAMILY_STATE_LEN, SHA3_SERIALIZED_STATE_LEN, deserialize_sha3_family_state, + serialize_sha3_family_state, }; -use bouncycastle_core::errors::{CoreError, HashError, KDFError}; +use bouncycastle_core::errors::{HashError, KDFError, SerializedStateError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; @@ -221,9 +221,9 @@ impl Hash for SHA3 { } } -impl SerializableState for SHA3 { - fn serialize_state(&self) -> [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN] { - let mut out_to_return = [0u8; SHA3_FAMILY_SERIALIZED_STATE_LEN]; +impl SerializableState for SHA3 { + fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { + let mut out_to_return = [0u8; SHA3_SERIALIZED_STATE_LEN]; // insert the version tag let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = @@ -242,8 +242,8 @@ impl SerializableState for } fn from_serialized_state( - serialized_state: [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN], - ) -> Result { + serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + ) -> Result { // check the version tag. At the moment, we have no not_before version to specify. let input: &[u8; SHA3_FAMILY_STATE_LEN] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index b2252b1..dd05d52 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -1,9 +1,9 @@ use crate::SHAKEParams; use crate::keccak::{ - KeccakDigest, KeccakSize, SHA3_FAMILY_SERIALIZED_STATE_LEN, SHA3_FAMILY_STATE_LEN, + KeccakDigest, KeccakSize, SHA3_FAMILY_STATE_LEN, SHA3_SERIALIZED_STATE_LEN, deserialize_sha3_family_state, serialize_sha3_family_state, }; -use bouncycastle_core::errors::{CoreError, HashError, KDFError}; +use bouncycastle_core::errors::{HashError, KDFError, SerializedStateError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; @@ -142,9 +142,9 @@ impl SHAKE { } } -impl SerializableState for SHAKE { - fn serialize_state(&self) -> [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN] { - let mut out_to_return = [0u8; SHA3_FAMILY_SERIALIZED_STATE_LEN]; +impl SerializableState for SHAKE { + fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { + let mut out_to_return = [0u8; SHA3_SERIALIZED_STATE_LEN]; // insert the version tag let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = @@ -163,8 +163,8 @@ impl SerializableState fo } fn from_serialized_state( - serialized_state: [u8; SHA3_FAMILY_SERIALIZED_STATE_LEN], - ) -> Result { + serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + ) -> Result { // check the version tag. At the moment, we have no not_before version to specify. let input: &[u8; SHA3_FAMILY_STATE_LEN] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index 0cdb21a..a93704e 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -396,21 +396,21 @@ mod sha3_tests { #[test] fn test_serializable_state() { - use bouncycastle_core::errors::CoreError; + use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::traits::SerializableState; use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; let str = "Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one SHA3 variant. - fn round_trip>(mut hash: H, input: &[u8]) { + fn round_trip + Clone>(mut hash: H, input: &[u8]) { hash.do_update(input); // do the default trait-conformance tests TestFrameworkSerializableState::new().test(&hash); // serialize the in-progress state, then finish the original - let serialized_state = hash.serialize_state(); + let serialized_state = hash.clone().serialize_state(); let expected = hash.do_final(); // rebuild from the serialized state and confirm it produces the same digest @@ -423,7 +423,7 @@ mod sha3_tests { let mut busted = serialized_state; busted[3 + 1 + 400] = 42; match H::from_serialized_state(busted) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } } @@ -440,11 +440,11 @@ mod sha3_tests { sha3_256.do_update(str.as_bytes()); let serialized_256 = sha3_256.serialize_state(); match SHA3_512::from_serialized_state(serialized_256) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHA3-256 state into SHA3-512"), } match SHAKE256::from_serialized_state(serialized_256) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHA3-256 state into SHAKE256"), } } diff --git a/crypto/sha3/tests/shake_tests.rs b/crypto/sha3/tests/shake_tests.rs index d0611c7..10584f7 100644 --- a/crypto/sha3/tests/shake_tests.rs +++ b/crypto/sha3/tests/shake_tests.rs @@ -240,21 +240,21 @@ mod shake_tests { #[test] fn test_serializable_state() { - use bouncycastle_core::errors::CoreError; + use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::traits::SerializableState; use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; let str = "Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one SHAKE variant. - fn round_trip>(mut shake: X, input: &[u8]) { + fn round_trip + Clone>(mut shake: X, input: &[u8]) { shake.absorb(input); // do the default trait-conformance tests TestFrameworkSerializableState::new().test(&shake); // serialize the in-progress (absorbing) state, then squeeze from the original - let serialized_state = shake.serialize_state(); + let serialized_state = shake.clone().serialize_state(); let expected = shake.squeeze(64); // rebuild from the serialized state and confirm it produces the same output @@ -267,7 +267,7 @@ mod shake_tests { let mut busted = serialized_state; busted[3 + 1 + 400] = 42; match X::from_serialized_state(busted) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } } @@ -282,7 +282,7 @@ mod shake_tests { shake128.absorb(str.as_bytes()); let serialized_128 = shake128.serialize_state(); match SHAKE256::from_serialized_state(serialized_128) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into SHAKE256"), } @@ -290,7 +290,7 @@ mod shake_tests { shake256.absorb(str.as_bytes()); let serialized_256 = shake256.serialize_state(); match SHA3_256::from_serialized_state(serialized_256) { - Err(CoreError::InvalidData) => { /* good */ } + Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE256 state into SHA3-256"), } } From d7192dd701a5cd7beb031f0e35885b6f18c79700 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Mon, 6 Jul 2026 17:36:54 -0500 Subject: [PATCH 07/26] rustfmt --- crypto/sha3/tests/sha3_tests.rs | 5 ++++- crypto/sha3/tests/shake_tests.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index a93704e..ffff758 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -403,7 +403,10 @@ mod sha3_tests { let str = "Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one SHA3 variant. - fn round_trip + Clone>(mut hash: H, input: &[u8]) { + fn round_trip + Clone>( + mut hash: H, + input: &[u8], + ) { hash.do_update(input); // do the default trait-conformance tests diff --git a/crypto/sha3/tests/shake_tests.rs b/crypto/sha3/tests/shake_tests.rs index 10584f7..1e43b39 100644 --- a/crypto/sha3/tests/shake_tests.rs +++ b/crypto/sha3/tests/shake_tests.rs @@ -247,7 +247,10 @@ mod shake_tests { let str = "Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one SHAKE variant. - fn round_trip + Clone>(mut shake: X, input: &[u8]) { + fn round_trip + Clone>( + mut shake: X, + input: &[u8], + ) { shake.absorb(input); // do the default trait-conformance tests From 45ba74244a82dfbedebcd9146d3a2c5fea5819eb Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Mon, 6 Jul 2026 18:21:33 -0500 Subject: [PATCH 08/26] Impl'd SerializableState for MLDSA-lowmemory. --- crypto/mldsa-lowmemory/src/hash_mldsa.rs | 9 +- crypto/mldsa-lowmemory/src/lib.rs | 2 + crypto/mldsa-lowmemory/src/mldsa.rs | 139 ++++++++++++++++---- crypto/mldsa-lowmemory/tests/mldsa_tests.rs | 90 ++++++++++++- crypto/mldsa-lowmemory/tests/wycheproof.rs | 33 ++--- 5 files changed, 221 insertions(+), 52 deletions(-) diff --git a/crypto/mldsa-lowmemory/src/hash_mldsa.rs b/crypto/mldsa-lowmemory/src/hash_mldsa.rs index 1c9f9e8..179301c 100644 --- a/crypto/mldsa-lowmemory/src/hash_mldsa.rs +++ b/crypto/mldsa-lowmemory/src/hash_mldsa.rs @@ -1236,7 +1236,7 @@ impl< let mut mu = [0u8; MLDSA_MU_LEN]; _ = h.squeeze_out(&mut mu); - if MLDSA::< + MLDSA::< PK_LEN, SK_LEN, FULL_SK_LEN, @@ -1262,11 +1262,6 @@ impl< GAMMA1_MINUS_BETA, GAMMA2_MINUS_BETA, GAMMA1_MASK_LEN, - >::verify_mu_internal(pk, &mu, sig_sized) - { - Ok(()) - } else { - Err(SignatureError::SignatureVerificationFailed) - } + >::verify_mu(pk, &mu, sig_sized) } } diff --git a/crypto/mldsa-lowmemory/src/lib.rs b/crypto/mldsa-lowmemory/src/lib.rs index 64a91f1..5d2ed88 100644 --- a/crypto/mldsa-lowmemory/src/lib.rs +++ b/crypto/mldsa-lowmemory/src/lib.rs @@ -258,5 +258,7 @@ pub use mldsa::{MLDSA44_PK_LEN, MLDSA44_SIG_LEN, MLDSA44_SK_LEN}; pub use mldsa::{MLDSA65_PK_LEN, MLDSA65_SIG_LEN, MLDSA65_SK_LEN}; pub use mldsa::{MLDSA87_PK_LEN, MLDSA87_SIG_LEN, MLDSA87_SK_LEN}; +pub use mldsa::MU_BUILDER_SERIALIZED_STATE_LEN; + // re-export just so it's visible to unit tests pub use polynomial::Polynomial; diff --git a/crypto/mldsa-lowmemory/src/mldsa.rs b/crypto/mldsa-lowmemory/src/mldsa.rs index f7124e1..befebd7 100644 --- a/crypto/mldsa-lowmemory/src/mldsa.rs +++ b/crypto/mldsa-lowmemory/src/mldsa.rs @@ -308,6 +308,81 @@ //! private key encoding (which is often called the "semi-expanded format" since the in-memory representation //! is still larger). //! Contact us if you need such a thing implemented. +//! +//! # Suspending and resuming execution via SerializableState +//! +//! When signing or verifying a large message, it can be advantageous to be able to suspend the operation +//! to a cache and resume it later; for example if waiting for the message to stream over a slow network +//! connection. +//! +//! This can bo accomplished for both the ML-DSA signer and verifier through the [MuBuilder] object. +//! +//! Suspending an in-progress sign operation: +//! +//! ```rust +//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; +//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let (pk, sk) = MLDSA65::keygen().unwrap(); +//! +//! let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); +//! mb.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = mb.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! mb_resumed.do_update(msg_part2); +//! let mu: [u8; 64] = mb_resumed.do_final(); +//! +//! // Now we'll do the actual sign_mu operation +//! let sig = MLDSA65::sign_mu(&sk, &mu).unwrap(); +//! ``` +//! +//! Suspending an in-progress verify operation behaves exactly the same way: +//! +//! ```rust +//! use bouncycastle_mldsa_lowmemory::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; +//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! use bouncycastle_core::errors::SignatureError; +//! +//! let (pk, sk) = MLDSA65::keygen().unwrap(); +//! +//! // first, let's generate a signature to verify +//! let sig = MLDSA65::sign(&sk, b"The quick brown fox jumped over the lazy dog", None).unwrap(); +//! +//! // Now we'll verify it with a suspension in the middle +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); +//! mb.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = mb.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! mb_resumed.do_update(msg_part2); +//! let mu: [u8; 64] = mb_resumed.do_final(); +//! +//! // Now we'll do the actual verify_mu operation +//! match MLDSA65::verify_mu(&pk, &mu, &sig) { +//! Ok(()) => println!("Signature is valid!"), +//! Err(SignatureError::SignatureVerificationFailed) => println!("Signature is invalid!"), +//! Err(e) => panic!("Something else went wrong: {:?}", e), +//! } +//! ``` use crate::aux_functions::{ bitlen_eta, bitpack_gamma1, sample_in_ball, unpack_c_tilde, unpack_h_row, @@ -322,11 +397,13 @@ use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, }; -use bouncycastle_core::errors::{RNGError, SignatureError}; +use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::traits::{Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, XOF}; +use bouncycastle_core::traits::{ + Algorithm, RNG, SecurityStrength, SerializableState, SignatureVerifier, Signer, XOF, +}; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha3::{SHAKE128, SHAKE256}; +use bouncycastle_sha3::{SHA3_SERIALIZED_STATE_LEN, SHAKE128, SHAKE256}; use core::marker::PhantomData; // imports needed just for docs @@ -1210,7 +1287,7 @@ impl< /// Internal function to verify a signature 𝜎 for a formatted message 𝑀′ . /// Input: Public key 𝑝𝑘 ∈ 𝔹32+32𝑘(bitlen (𝑞−1)−𝑑) and message 𝑀′ ∈ {0, 1}∗ . /// Input: Signature 𝜎 ∈ 𝔹𝜆/4+ℓ⋅32⋅(1+bitlen (𝛾1−1))+𝜔+𝑘. - fn verify_mu_internal(pk: &PK, mu: &[u8; 64], sig: &[u8; SIG_LEN]) -> bool { + fn verify_mu(pk: &PK, mu: &[u8; 64], sig: &[u8; SIG_LEN]) -> Result<(), SignatureError> { // 1: (𝜌, 𝐭1) ← pkDecode(𝑝𝑘) // Already done -- the pk struct is already decoded @@ -1247,7 +1324,7 @@ impl< } { Ok(wp_approx) => wp_approx, // means the norm check on z failed - Err(_) => return false, + Err(_) => return Err(SignatureError::SignatureVerificationFailed), }; let h_i = match unpack_h_row::< @@ -1262,7 +1339,7 @@ impl< { Some(h_i) => h_i, // means there were more than OMEGA bits set in the hint - None => return false, + None => return Err(SignatureError::SignatureVerificationFailed), }; // 10: 𝐰1′ ← UseHint(𝐡, 𝐰'_approx) @@ -1278,7 +1355,11 @@ impl< // 13 (second half): return [[ ||𝐳||∞ < 𝛾1 − 𝛽]] and [[𝑐 ̃ = 𝑐′ ]] // note: the first half of this check (the norm check) is buried in unpack_z_row(), // which is called from compute_wp_approx_row() - bouncycastle_utils::ct::ct_eq_bytes(unpack_c_tilde::(sig), &c_tilde_p) + if bouncycastle_utils::ct::ct_eq_bytes(unpack_c_tilde::(sig), &c_tilde_p) { + Ok(()) + } else { + Err(SignatureError::SignatureVerificationFailed) + } } } @@ -1504,11 +1585,10 @@ pub trait MLDSATrait< seed: &KeyMaterial<32>, ctx: Option<&[u8]>, ) -> Result; - /// Algorithm 8 ML-DSA.Verify_internal(𝑝𝑘, 𝑀′, 𝜎) - /// Internal function to verify a signature 𝜎 for a formatted message 𝑀′ . - /// Input: Public key 𝑝𝑘 ∈ 𝔹32+32𝑘(bitlen (𝑞−1)−𝑑) and message 𝑀′ ∈ {0, 1}∗ . - /// Input: Signature 𝜎 ∈ 𝔹𝜆/4+ℓ⋅32⋅(1+bitlen (𝛾1−1))+𝜔+𝑘. - fn verify_mu_internal(pk: &PK, mu: &[u8; 64], sig: &[u8; SIG_LEN]) -> bool; + /// Performs an ML-DSA signature verification using the provided external message representative `mu`. + /// This implements FIPS 204 Algorithm 8 with line 7 removed; a modification that is allowed by both + /// FIPS 204 itself, as well as subsequent FAQ documents. + fn verify_mu(pk: &PK, mu: &[u8; 64], sig: &[u8; SIG_LEN]) -> Result<(), SignatureError>; } impl< @@ -1746,11 +1826,7 @@ impl< if sig.len() != SIG_LEN { return Err(SignatureError::LengthError("Signature value is not the correct length.")); } - if Self::verify_mu_internal(pk, &mu, &sig.try_into().unwrap()) { - Ok(()) - } else { - Err(SignatureError::SignatureVerificationFailed) - } + Self::verify_mu(pk, &mu, &sig.try_into().unwrap()) } fn verify_init(pk: &PK, ctx: Option<&[u8]>) -> Result { @@ -1780,11 +1856,7 @@ impl< return Err(SignatureError::LengthError("Signature value is not the correct length.")); } - if Self::verify_mu_internal(&self.pk.unwrap(), &mu, &sig.try_into().unwrap()) { - Ok(()) - } else { - Err(SignatureError::SignatureVerificationFailed) - } + Self::verify_mu(&self.pk.unwrap(), &mu, &sig.try_into().unwrap()) } } @@ -1797,6 +1869,7 @@ impl< /// does not benefit from allowing external construction of the message representative mu. /// You can get the same behaviour by computing the pre-hash `ph` with the appropriate hash function /// and providing that to HashMLDSA via [PHSigner::sign_ph]. +#[derive(Clone)] pub struct MuBuilder { h: H, } @@ -1862,3 +1935,25 @@ impl MuBuilder { mu } } + +/// The length, in bytes, of a serialized state of a [MuBuilder] object. +pub const MU_BUILDER_SERIALIZED_STATE_LEN: usize = SHA3_SERIALIZED_STATE_LEN; + +/// If you are processing a large input message into ML-DSA and want to pause the operation +/// -- maybe while waiting for slow network IO), you'll need to use [SerializableState]. +/// Serialization of the state of an in-progress ML-DSA instance is really just serialization +/// of the construction of the message representative mu, since no other part of the ML-DSA algorithm +/// has a pausable state. +// A [MuBuilder]'s (and by virtue, an ML-DSA instance's) entire mutable state is its inner SHAKE256 sponge, +// so serialization delegates directly to [SHAKE256]'s [SerializableState] impl. +impl SerializableState for MuBuilder { + fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { + self.h.serialize_state() + } + + fn from_serialized_state( + serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + ) -> Result { + Ok(MuBuilder { h: H::from_serialized_state(serialized_state)? }) + } +} diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index f1cd7ac..b69cb67 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -2,14 +2,16 @@ #[cfg(test)] mod mldsa_tests { use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; - use bouncycastle_core::errors::{RNGError, SignatureError}; + use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, + RNG, SecurityStrength, SerializableState, SignaturePrivateKey, SignaturePublicKey, + SignatureVerifier, Signer, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::FixedSeedRNG; + use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::{ @@ -810,6 +812,90 @@ mod mldsa_tests { mb.do_update(b"jumped over the lazy dog"); let mu6 = mb.do_final(); assert_eq!(mu1, mu6); + + // test SerializedState for MuBuilder + + // serializing and resuming after init + let mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); + let serialized_state = mb.serialize_state(); + + let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + mb_resumed.do_update(msg); + let mu_resumed = mb_resumed.do_final(); + assert_eq!(mu_resumed, mu1); + + // serializing and resuming partway through message consumption + let msg1 = b"The quick brown fox"; + let msg2 = b" jumped over the lazy dog"; + + let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); + mb.do_update(msg1); + + let serialized_state = mb.serialize_state(); + let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + mb_resumed.do_update(msg2); + let mu_resumed2 = mb_resumed.do_final(); + assert_eq!(mu_resumed2, mu1); + } + + #[test] + fn mubuilder_serializable_state() { + // `tr` is the 64-byte public-key hash that seeds mu computation; its exact value doesn't + // matter for exercising serialization, only that it is fixed across the comparison. + let tr = [0x42u8; 64]; + let msg = b"Colorless green ideas sleep furiously"; + + // Put the builder into a mid-stream state (some, but not all, of the message absorbed). + let mut mb = MuBuilder::do_init(&tr, None).unwrap(); + mb.do_update(&msg[..10]); + + // Generic trait-conformance tests (version header present, [0,0,0] and future versions + // rejected). + TestFrameworkSerializableState::new().test(&mb); + + // Serialize the in-progress state, then finish the original. + let serialized_state = mb.clone().serialize_state(); + mb.do_update(&msg[10..]); + let expected_mu = mb.do_final(); + + // Rebuild from the serialized state, feed it the identical remaining input, and confirm it + // produces the same mu. + let mut from_state = MuBuilder::from_serialized_state(serialized_state).unwrap(); + from_state.do_update(&msg[10..]); + assert_eq!(expected_mu, from_state.do_final()); + + // Anchor correctness to the existing one-shot path: streaming must match compute_mu. + let one_shot = MuBuilder::compute_mu(&tr, msg, None).unwrap(); + assert_eq!(expected_mu, one_shot); + + // A corrupt SHAKE256 `squeezing` byte must be rejected. Layout of the delegated SHAKE256 + // state: 3 version bytes + variant tag(1) + [u64;25](200) + data_queue(192) + // + bits_in_queue(8) + squeezing(1) -> the squeezing byte is at offset 3 + 1 + 400. + let mut busted = serialized_state; + busted[3 + 1 + 400] = 42; + match MuBuilder::from_serialized_state(busted) { + Err(SerializedStateError::InvalidData) => { /* good */ } + _ => panic!("Expected an error for a corrupt squeezing byte"), + } + } + + #[test] + fn serializable_state_mubuilder_rejects_wrong_variant() { + use bouncycastle_core::traits::XOF; + use bouncycastle_sha3::SHAKE128; + + // A MuBuilder is always backed by SHAKE256. A serialized SHAKE128 state has the same length + // (both are SHA3-family states), so this would deserialize into the wrong sponge if the + // variant tag weren't checked -- SHAKE128 (tag 5) must be rejected by MuBuilder (SHAKE256, + // tag 6). + let mut shake128 = SHAKE128::new(); + shake128.absorb(b"Colorless green ideas sleep furiously"); + let serialized_128 = shake128.serialize_state(); + + match MuBuilder::from_serialized_state(serialized_128) { + Err(SerializedStateError::InvalidData) => { /* good */ } + _ => panic!("Expected an error when loading a SHAKE128 state into a MuBuilder"), + } } #[test] diff --git a/crypto/mldsa-lowmemory/tests/wycheproof.rs b/crypto/mldsa-lowmemory/tests/wycheproof.rs index 3dcdedc..9a95f45 100644 --- a/crypto/mldsa-lowmemory/tests/wycheproof.rs +++ b/crypto/mldsa-lowmemory/tests/wycheproof.rs @@ -333,16 +333,13 @@ impl MLDSASignSeedTestCase { let sig = MLDSA44::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA44::verify_mu_internal( - &pk, - &mu, - &hex::decode(&self.sig).unwrap().try_into().unwrap(), - ); + let res = + MLDSA44::verify_mu(&pk, &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap()); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } @@ -430,16 +427,13 @@ impl MLDSASignSeedTestCase { let sig = MLDSA65::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA65::verify_mu_internal( - &pk, - &mu, - &hex::decode(&self.sig).unwrap().try_into().unwrap(), - ); + let res = + MLDSA65::verify_mu(&pk, &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap()); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } @@ -527,16 +521,13 @@ impl MLDSASignSeedTestCase { let sig = MLDSA87::sign_mu_deterministic(&sk, &mu, [0u8; 32]).unwrap(); assert_eq!(sig, hex::decode(&self.sig).unwrap().as_slice()); - let res = MLDSA87::verify_mu_internal( - &pk, - &mu, - &hex::decode(&self.sig).unwrap().try_into().unwrap(), - ); + let res = + MLDSA87::verify_mu(&pk, &mu, &hex::decode(&self.sig).unwrap().try_into().unwrap()); if self.result == "valid" { - assert!(res); + res.unwrap(); } else { - assert!(!res); + assert!(res.is_err()); }; } } From d9b06bdce2b3cd86032c7b1c2cc13540aa5a71bf Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Mon, 6 Jul 2026 21:54:22 -0500 Subject: [PATCH 09/26] Impl'd SerializableKeyedState for HMAC. --- .../src/serializable_state.rs | 64 ++++++++- crypto/core/src/traits.rs | 7 +- crypto/hmac/src/lib.rs | 124 ++++++++++++++---- crypto/hmac/tests/hmac_tests.rs | 59 +++++++++ 4 files changed, 229 insertions(+), 25 deletions(-) diff --git a/crypto/core-test-framework/src/serializable_state.rs b/crypto/core-test-framework/src/serializable_state.rs index 2daea7d..694019b 100644 --- a/crypto/core-test-framework/src/serializable_state.rs +++ b/crypto/core-test-framework/src/serializable_state.rs @@ -1,6 +1,6 @@ use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::serializable_state::{LIB_VERSION, SemVer}; -use bouncycastle_core::traits::SerializableState; +use bouncycastle_core::traits::{SerializableKeyedState, SerializableState}; pub struct TestFrameworkSerializableState {} @@ -62,3 +62,65 @@ impl TestFrameworkSerializableState { } } } + +pub struct TestFrameworkSerializableKeyedState {} + +impl TestFrameworkSerializableKeyedState { + pub fn new() -> Self { + Self {} + } + + /// Test all the members of trait SerializableState. + /// + /// Expects ta be handed an instance of the object that has some in-progress state to be serialized. + pub fn test< + const SERIALIZED_STATE_LEN: usize, + S: SerializableKeyedState + Clone, + >( + &self, + instance: &S, + key: &S::Key, + ) { + // There's not a lot we can test here in the abstract, but we can test a few things to + // ensure that the SerializableState trait has been impl'd correctly. + + // we need to work on a clone because .serialize_state() moves self, which you can't do on a + // borrowed instance. + let instance_clone = instance.clone(); + + // You can serialize and then deserialize the state. + let serialized_state = instance_clone.serialize_state(); + assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN); + + let _deserialized_state = S::from_serialized_state(serialized_state, key).unwrap(); + + // The serialized state MUST include a prefix indicating the current version of the library. + let state_sized: [u8; 3] = serialized_state[..3].try_into().unwrap(); + assert_eq!(SemVer::from(state_sized), LIB_VERSION); + + // All implementations MUST reject a serialized state from lib ver 0.0.0 + // This doesn't really serve any purpose except testing that all impl's have properly + // used the helper functions. + let mut ver0_serialized_state = serialized_state.clone(); + ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); + match S::from_serialized_state(ver0_serialized_state, key) { + Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + _ => { + panic!("Expected IncompatibleVersion error") + } + } + + // All implementations MUST reject a serialized state from a future version. + let mut future_ver = LIB_VERSION; + future_ver.patch += 1; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + match S::from_serialized_state(futurever_serialized_state, key) { + Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + _ => { + panic!("Expected IncompatibleVersion error") + } + } + } +} diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 8c774d9..e2f9efc 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -527,7 +527,10 @@ pub trait SerializableState: Sized { /// The difference is that this trait is for keyed algorithms -- MACs, symmetric ciphers, signatures, etc -- /// which require a private key. For security reasons, the private key is not included in the serialized state /// and must be provided separately as part of the deserialization process. -pub trait SerializableKeyedState: Sized { +pub trait SerializableKeyedState: Sized { + /// The type of key that must be re-supplied to resume this object. + type Key: ?Sized; + /// Serialize the state of the object. /// /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization. @@ -544,7 +547,7 @@ pub trait SerializableKeyedState: Sized { /// deserializer should reject serialized states from that version or older. fn from_serialized_state( serialized_state: [u8; SERIALIZED_STATE_LEN], - key: K, + key: &Self::Key, ) -> Result; } diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index c63c249..70e5816 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -137,29 +137,58 @@ //! } //! ``` //! -//! # Request for feedback on fallability of this API -//! We have made an effort to reduce the number of fallibly APIs in the [MAC] trait; in particular -//! the APIs for the update and most of the finalize phases are infallible -- they just work. -//! However, we were not able to design it to completely avoid runtime error conditions, such as -//! providing [MAC::do_final_out] with a buffer that is too small to hold what NIST considered the smallest -//! valid MAC value for the given underlying hash function. +//! # Suspending and resuming execution via SerializableState //! -//! Also, the key strength and type checking in the initialization phase means that both [MAC::new] and -//! [MAC::new_allow_weak_key] have several runtime error conditions that they check for. +//! When MAC'ing a large message, it can be advantageous to be able to suspend the operation +//! to a cache and resume it later; for example if waiting for the message to stream over a slow network +//! connection. For this reason, all HMAC algorithms impl [SerializableKeyedState]. //! -//! All of this leads to application code that requires a lot more .unwrap(), .expect(), or match than we would -//! really like. +//! Note that since HMAC is a keyed +//! algorithm and we do not want to serialize the private key into the state, the trait structure forces you to +//! re-provide the same key when you resume the operation. Securely storing this key in the interim +//! is the responsibility of the caller. Note also that if you resume the HMAC with the wrong key, +//! `from_serialized_state` has no way to detect this, so the end result will be a broken MAC value +//! computed with different keys in the inner and outer pad. So make sure you resume with the same key! //! -//! We would love feedback on an alternate design for this API than carries less runtime error -//! conditions, without sacrificing the key strength checking that the metadata in the [KeyMaterialTrait] object allows. +//!```rust +//! use bouncycastle_hmac::HMAC_SHA256; +//! use bouncycastle_core::key_material::KeyMaterial256; +//! use bouncycastle_core::traits::{MAC, SerializableKeyedState}; +//! use bouncycastle_core::key_material::KeyType; +//! +//! let msg_part1 = b"The quick brown fox"; +//! let msg_part2 = b" jumped over the lazy dog"; +//! +//! let key = KeyMaterial256::from_bytes_as_type( +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! KeyType::MACKey).unwrap(); +//! +//! let mut hmac = HMAC_SHA256::new(&key).unwrap(); +//! hmac.do_update(msg_part1); +//! +//! // here, we'll suspend while "waiting" for the second part of the message +//! let serialized_state = hmac.serialize_state(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! // Since the key is not serialized into the state, you have to store it somewhere and provide +//! // it to the deserializer. Make sure you store it securely! +//! let mut hmac_resumed = HMAC_SHA256::from_serialized_state(serialized_state, &key).unwrap(); +//! hmac_resumed.do_update(msg_part2); +//! let h: Vec = hmac_resumed.do_final(); +//! ``` #![forbid(unsafe_code)] #![allow(incomplete_features)] // because at time of writing, generic_const_exprs is not a stable feature #![feature(generic_const_exprs)] -use bouncycastle_core::errors::{KeyMaterialError, MACError}; +use bouncycastle_core::errors::{KeyMaterialError, MACError, SerializedStateError}; use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Algorithm, Hash, MAC, SecurityStrength}; +use bouncycastle_core::traits::{ + Algorithm, Hash, MAC, SecurityStrength, SerializableKeyedState, SerializableState, +}; use bouncycastle_sha2::{SHA224, SHA256, SHA384, SHA512}; use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; use bouncycastle_utils::ct; @@ -283,6 +312,20 @@ impl HMAC { self.hasher.do_update(&padded) } + /// Loads the raw key bytes into `self.key` / `self.key_len`, pre-hashing them first if they + /// exceed the underlying hash's block length (per RFC 2104 Section 2). This does NOT absorb the + /// key into the hasher; that is done separately via [HMAC::pad_key_into_hasher]. + fn load_key_material(&mut self, key_bytes: &[u8]) { + if key_bytes.len() > self.hasher.block_bitlen() / 8 { + // then we have to pre-hash it -- use a new instance of the hasher rather than the internal one + HASH::default().hash_out(key_bytes, &mut self.key[..self.hasher.output_len()]); + self.key_len = self.hasher.output_len(); + } else { + self.key[..key_bytes.len()].copy_from_slice(key_bytes); + self.key_len = key_bytes.len(); + } + } + /// Private init so that users are forced to go through one of the public new methods and thus we /// don't need to track state errors. fn init(&mut self, key: &impl KeyMaterialTrait, allow_weak_keys: bool) -> Result<(), MACError> { @@ -300,14 +343,7 @@ impl HMAC { // length of the underlying hashes algorithm, we apply a hash invocation // over the key first. - if key.key_len() > self.hasher.block_bitlen() / 8 { - // then we have to pre-hash it -- use a new instance of the hasher rather than the internal one - HASH::default().hash_out(key.ref_to_bytes(), &mut self.key[..self.hasher.output_len()]); - self.key_len = self.hasher.output_len(); - } else { - self.key[..key.key_len()].copy_from_slice(key.ref_to_bytes()); - self.key_len = key.key_len(); - } + self.load_key_material(key.ref_to_bytes()); self.pad_key_into_hasher(IPAD_BYTE); @@ -420,3 +456,47 @@ impl MAC for HMAC { HASH::default().max_security_strength() } } + +/// HMAC is a keyed algorithm, so it implements [SerializableKeyedState] (rather than +/// [SerializableState]) for suspending and resuming in-progress operations. +/// The key is deliberately NOT written into the serialized +/// bytes and must be re-supplied at deserialization. +/// +/// The serialized state is exactly the inner hasher's state (which has already absorbed `K ⊕ ipad` +/// and any message chunks provided so far) — so this is a straight passthrough to the underlying hash's +/// [SerializableState] impl. The re-supplied key is needed to reconstruct the material for the outer +/// (`K ⊕ opad`) step at finalization. +/// +/// There is no way to detect a mismatched key on +/// resume: the caller MUST supply the same key the HMAC was created with, otherwise the resumed +/// operation will silently produce an incorrect MAC. +impl> + SerializableKeyedState for HMAC +{ + // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait` + // rather than a single concrete key type. The key is only used (by reference) to reload the key + // bytes at from_serialized_state, so dynamic dispatch here is negligible. + type Key = dyn KeyMaterialTrait; + + fn serialize_state(self) -> [u8; HASH_STATE_LEN] { + // The key is intentionally excluded; the resumable state is just the inner hasher, which + // already carries the library version header from the hash's own SerializableState impl. + self.hasher.serialize_state() + } + + fn from_serialized_state( + serialized_state: [u8; HASH_STATE_LEN], + key: &dyn KeyMaterialTrait, + ) -> Result { + // Rebuild the inner hasher (version-compatibility is validated by the hash's impl). + let hasher = HASH::from_serialized_state(serialized_state)?; + + // Re-load the key material exactly as `new()` did (pre-hashing an over-length key), but do + // NOT re-absorb `K ⊕ ipad` — the deserialized hasher already contains it. The key is only + // needed for the outer `K ⊕ opad` step at finalization. + let mut hmac = HMAC { hasher, key: [0u8; LARGEST_HASHER_OUTPUT_LEN], key_len: 0 }; + hmac.load_key_material(key.ref_to_bytes()); + + Ok(hmac) + } +} diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/hmac/tests/hmac_tests.rs index 862bfa1..944da5b 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/hmac/tests/hmac_tests.rs @@ -580,4 +580,63 @@ mod hmac_tests { ); } } + + #[test] + fn serializable_keyed_state() { + use bouncycastle_core::errors::SerializedStateError; + use bouncycastle_core::serializable_state::LIB_VERSION; + use bouncycastle_core::traits::SerializableKeyedState; + use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableKeyedState; + + let key = + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::MACKey).unwrap(); + let msg = b"Colorless green ideas sleep furiously"; + + // A helper that exercises the full round-trip for one HMAC variant. HMAC is keyed, so the + // key is NOT in the serialized state -- it is re-supplied (by reference) to + // from_serialized_state. + // The `+ 'static` on the trait object matches the associated type `type Key = dyn + // KeyMaterialTrait` (a bare `dyn` in an associated type defaults to `'static`). The concrete + // key types are owned, so they satisfy it. + fn round_trip( + mut hmac: H, + key: &(dyn KeyMaterialTrait + 'static), + input: &[u8], + ) where + H: MAC + Clone + SerializableKeyedState, + { + hmac.do_update(&input[..10]); + + // do the default trait-conformance tests + TestFrameworkSerializableKeyedState::new().test(&hmac, key); + + // serialize the in-progress state (on a clone), then finish the original + let serialized_state = hmac.clone().serialize_state(); + + // the serialized state carries the library version header (from the inner hash) + let header: [u8; 3] = serialized_state[..3].try_into().unwrap(); + assert_eq!(header, <[u8; 3]>::from(LIB_VERSION)); + + hmac.do_update(&input[10..]); + let expected = hmac.do_final(); + + // rebuild from the serialized state (re-supplying the key), feed the identical remaining + // input, and confirm the MAC matches + let mut from_state = H::from_serialized_state(serialized_state, key).unwrap(); + from_state.do_update(&input[10..]); + assert_eq!(expected, from_state.do_final()); + + // a state whose version header is zeroed must be rejected (delegated to the hash's impl) + let mut busted = serialized_state; + busted[..3].copy_from_slice(&[0, 0, 0]); + match H::from_serialized_state(busted, key) { + Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + _ => panic!("Expected IncompatibleVersion for a zeroed version header"), + } + } + + round_trip(HMAC_SHA256::new(&key).unwrap(), &key, msg); + round_trip(HMAC_SHA512::new(&key).unwrap(), &key, msg); + round_trip(HMAC_SHA3_256::new(&key).unwrap(), &key, msg); + } } From e464f684503309d536221f7b7d1ecfee8ca72159 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 7 Jul 2026 00:33:57 -0500 Subject: [PATCH 10/26] Fixed a bug in HMAC that could cause panics. --- crypto/hkdf/src/lib.rs | 8 ++-- crypto/hmac/src/lib.rs | 73 ++++++++++++++++++--------------- crypto/hmac/tests/hmac_tests.rs | 21 ++++++++++ 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index f6920ef..569c7f6 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -179,9 +179,10 @@ pub type HKDF_SHA256 = HKDF; pub type HKDF_SHA512 = HKDF; pub struct HKDF { - hmac: Option>, // Optional because we can't construct an HMAC until they give us a key + // Optional because we can't construct an HMAC until they give us a key // to initialize it with. // None should correspond to a state of Uninitialized. + hmac: Option>, entropy: HkdfEntropyTracker, state: HkdfStates, } @@ -410,7 +411,6 @@ impl HKDF { key_material::do_hazardous_operations(okm, |okm| { let out = okm.ref_to_bytes_mut()?; while i < N { - // todo: might need this to be new_allow_weak_key() let mut hmac = HMAC::::new(&prk_as_mac_key) .map_err(|_| KeyMaterialError::GenericError("HMAC initialization failed"))?; hmac.do_update(&T[..t_len]); @@ -430,7 +430,6 @@ impl HKDF { // On the last iteration, we don't take all of the output. let remaining = L - bytes_written; - // todo: might need this to be new_allow_weak_key() let mut hmac = HMAC::::new(&prk_as_mac_key)?; hmac.do_update(&T[..t_len]); hmac.do_update(info); @@ -489,8 +488,7 @@ impl HKDF { /// The output KeyMaterial will be of fixed size, with a capacity large enough to cover any /// underlying hash function, but the actual key length will be appropriate to the underlying hash function. /// - /// Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero, - /// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt. + /// Salt is optional; to omit it, provide a KeyMaterial0, which will cause HKDF to use the default all-zero salt. /// /// Returns the number of bits of entropy credited to this input key material. pub fn do_extract_init(&mut self, salt: &impl KeyMaterialTrait) -> Result { diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 70e5816..f1460f6 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -205,69 +205,76 @@ pub const HMAC_SHA3_512_NAME: &str = "HMAC-SHA3-512"; /*** Type aliases ***/ #[allow(non_camel_case_types)] -pub type HMAC_SHA224 = HMAC; +pub type HMAC_SHA224 = HMAC; impl Algorithm for HMAC_SHA224 { const ALG_NAME: &'static str = HMAC_SHA224_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA256 = HMAC; +pub type HMAC_SHA256 = HMAC; impl Algorithm for HMAC_SHA256 { const ALG_NAME: &'static str = HMAC_SHA256_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA384 = HMAC; +pub type HMAC_SHA384 = HMAC; impl Algorithm for HMAC_SHA384 { const ALG_NAME: &'static str = HMAC_SHA384_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA512 = HMAC; +pub type HMAC_SHA512 = HMAC; impl Algorithm for HMAC_SHA512 { const ALG_NAME: &'static str = HMAC_SHA512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA3_224 = HMAC; +pub type HMAC_SHA3_224 = HMAC; impl Algorithm for HMAC_SHA3_224 { const ALG_NAME: &'static str = HMAC_SHA3_224_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA3_256 = HMAC; +pub type HMAC_SHA3_256 = HMAC; impl Algorithm for HMAC_SHA3_256 { const ALG_NAME: &'static str = HMAC_SHA3_256_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA3_384 = HMAC; +pub type HMAC_SHA3_384 = HMAC; impl Algorithm for HMAC_SHA3_384 { const ALG_NAME: &'static str = HMAC_SHA3_384_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } #[allow(non_camel_case_types)] -pub type HMAC_SHA3_512 = HMAC; +pub type HMAC_SHA3_512 = HMAC; impl Algorithm for HMAC_SHA3_512 { const ALG_NAME: &'static str = HMAC_SHA3_512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } -// TODO: is there a rustacious way to extract this from HASH? -const LARGEST_HASHER_OUTPUT_LEN: usize = 64; +// The internal key buffer must be able to hold a key up to the *block length* of the underlying hash: +// per RFC 2104, a key no longer than the block is used verbatim (only longer keys are pre-hashed down +// to the output length). So the buffer size is a const parameter of the struct, set per hash to its +// block length by the type aliases below. Block lengths (bytes): SHA-224/256 = 64, SHA-384/512 = 128, +// SHA3-224 = 144, SHA3-256 = 136, SHA3-384 = 104, SHA3-512 = 72. +// +// The default is used only when `HMAC` is written without an explicit buffer size; it is the +// largest block length across all supported hashes, so it is always large enough. +const LARGEST_HASHER_BLOCK_LEN: usize = 144; // HMAC implements RFC 2104. #[derive(Clone)] -pub struct HMAC { +pub struct HMAC { hasher: HASH, - key: [u8; LARGEST_HASHER_OUTPUT_LEN], + key: [u8; KEY_BUF_LEN], key_len: usize, // Doing it this way to avoid needing a vec, so that this can be made no_std friendly. } @@ -286,21 +293,13 @@ const OPAD_BYTE: u8 = 0x5C; // be too strict about it, pub const MIN_FIPS_DIGEST_LEN: usize = 4; // 32 / 8; -impl HMAC { +impl HMAC { fn pad_key_into_hasher(&mut self, padding: u8) { // TODO: it would be nice to be able to statically extract the length of HASH and not need a Vec or over-sized array here. // TODO: make this no_std-friendly let mut padded = vec![0u8; self.hasher.block_bitlen() / 8]; - - // Per RFC 2104 Section 2, if the application key exceeds the block - // length of the underlying hashes algorithm, we apply a hash invocation - // over the key first. - // if self.key_len > self.hasher.block_bitlen() / 8 { - // HASH::default().hash_out(&self.key[..self.key_len], &mut padded[..self.hasher.output_len()])?; - // } else { - // TODO: does this need a guard for a key_len longer than the block length? + padded[..self.key_len].copy_from_slice(&self.key[..self.key_len]); - // } // XXX: easier way to xor over Vec? for entry in &mut padded { @@ -312,9 +311,10 @@ impl HMAC { self.hasher.do_update(&padded) } - /// Loads the raw key bytes into `self.key` / `self.key_len`, pre-hashing them first if they - /// exceed the underlying hash's block length (per RFC 2104 Section 2). This does NOT absorb the - /// key into the hasher; that is done separately via [HMAC::pad_key_into_hasher]. + /// Per RFC 2104 Section 2, if the application key exceeds the block + /// length of the underlying hashes algorithm, we apply a hash invocation + /// over the key first. + /// This does NOT absorb the key into the hasher; that is done separately via [HMAC::pad_key_into_hasher]. fn load_key_material(&mut self, key_bytes: &[u8]) { if key_bytes.len() > self.hasher.block_bitlen() / 8 { // then we have to pre-hash it -- use a new instance of the hasher rather than the internal one @@ -324,6 +324,12 @@ impl HMAC { self.key[..key_bytes.len()].copy_from_slice(key_bytes); self.key_len = key_bytes.len(); } + + // Just as a sanity-check. + assert!( + self.key_len <= KEY_BUF_LEN, + "Fatal error: Key length exceeds HMAC internal buffer length" + ); } /// Private init so that users are forced to go through one of the public new methods and thus we @@ -390,17 +396,15 @@ impl HMAC { // TODO: This is essentially a "batch mode" where you want to perform many MACs or Verifications with the same key // TODO: against different data. -impl MAC for HMAC { +impl MAC for HMAC { fn new(key: &impl KeyMaterialTrait) -> Result { - let mut hmac = - Self { hasher: HASH::default(), key: [0u8; LARGEST_HASHER_OUTPUT_LEN], key_len: 0 }; + let mut hmac = Self { hasher: HASH::default(), key: [0u8; KEY_BUF_LEN], key_len: 0 }; hmac.init(key, false)?; Ok(hmac) } fn new_allow_weak_key(key: &impl KeyMaterialTrait) -> Result { - let mut hmac = - Self { hasher: HASH::default(), key: [0u8; LARGEST_HASHER_OUTPUT_LEN], key_len: 0 }; + let mut hmac = Self { hasher: HASH::default(), key: [0u8; KEY_BUF_LEN], key_len: 0 }; hmac.init(key, true)?; Ok(hmac) } @@ -470,8 +474,11 @@ impl MAC for HMAC { /// There is no way to detect a mismatched key on /// resume: the caller MUST supply the same key the HMAC was created with, otherwise the resumed /// operation will silently produce an incorrect MAC. -impl> - SerializableKeyedState for HMAC +impl< + const HASH_STATE_LEN: usize, + const KEY_BUF_LEN: usize, + HASH: Hash + Default + SerializableState, +> SerializableKeyedState for HMAC { // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait` // rather than a single concrete key type. The key is only used (by reference) to reload the key @@ -494,7 +501,7 @@ impl::from_bytes_as_type(&[0x0B; 127], KeyType::MACKey).unwrap(); + let mut mac = HMAC_SHA512::new(&key).unwrap(); + mac.do_update(b"Hi There"); + let tag = mac.do_final(); + assert!(HMAC_SHA512::new(&key).unwrap().verify(b"Hi There", &tag)); + + // SHA3-224 has the largest block (144 bytes); a 143-byte key exercises the top of the range. + let key = KeyMaterial::<200>::from_bytes_as_type(&[0x0B; 143], KeyType::MACKey).unwrap(); + let mut mac = HMAC_SHA3_224::new(&key).unwrap(); + mac.do_update(b"Hi There"); + let tag = mac.do_final(); + assert!(HMAC_SHA3_224::new(&key).unwrap().verify(b"Hi There", &tag)); + } + #[test] fn security_strength_tests() { // test: provided key has the correct length, but insufficient tagged security strength From 937d859a539a1f35ed8233d9c0103cab50dfa2ee Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 7 Jul 2026 14:01:13 -0500 Subject: [PATCH 11/26] Implemented Suspendable for HKDF and a bunch of other tidy-up. --- alpha_0.1.2_release_notes.md | 9 +- crypto/core-test-framework/src/lib.rs | 2 +- ...alizable_state.rs => suspendable_state.rs} | 33 ++-- crypto/core/src/errors.rs | 2 +- crypto/core/src/traits.rs | 40 ++-- crypto/hkdf/src/lib.rs | 180 +++++++++++++++++- crypto/hkdf/tests/hkdf_tests.rs | 53 +++++- crypto/hmac/src/lib.rs | 78 +++++--- crypto/hmac/tests/hmac_tests.rs | 18 +- crypto/mldsa-lowmemory/src/mldsa.rs | 32 ++-- crypto/mldsa-lowmemory/tests/mldsa_tests.rs | 29 +-- crypto/mldsa/src/mldsa.rs | 32 ++-- crypto/mldsa/tests/mldsa_tests.rs | 29 +-- crypto/sha2/src/lib.rs | 21 +- crypto/sha2/src/sha256.rs | 21 +- crypto/sha2/src/sha512.rs | 19 +- crypto/sha2/tests/sha2_tests.rs | 24 +-- crypto/sha3/src/keccak.rs | 4 +- crypto/sha3/src/lib.rs | 19 +- crypto/sha3/src/sha3.rs | 90 ++++----- crypto/sha3/src/shake.rs | 14 +- crypto/sha3/tests/sha3_tests.rs | 31 +-- crypto/sha3/tests/shake_tests.rs | 29 ++- 23 files changed, 530 insertions(+), 279 deletions(-) rename crypto/core-test-framework/src/{serializable_state.rs => suspendable_state.rs} (80%) diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index a298549..327f19c 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -45,10 +45,11 @@ * mldsa-lowmemory -- runs in about 1/10th of the usual memory (~ 30 kb of stack) with comparable performance impact. * mlkem (FIPS 203) * mlkem-lowmemory -- runs in about 1/4th of the usual memory (~ 12 kb of stack) with comparable performance impact. -* New traits SerializeState and SerializeKeyedState allow algorithms with a streaming API (`do_update()` -> - `do_final()`) to be suspended to a small byte array and then resumed later, potentially from a different host. The - intended use case is if you are processing a large input that depends on one or more network round-trips and you wish - to suspect and potentially transfer to a new host while waiting for network IO. +* New traits [Suspendable] and [SuspendableKeyed] allow algorithms with a streaming API (`do_update()` -> + `do_final()`) to be suspended to a small byte array and then resumed later, potentially from a different host and + potentially across versions of the library. The intended use case is if you are processing a large input that depends + on one or more network round-trips and you wish to suspend to a cache and potentially transfer to a new host while + waiting for network IO. ## Minor features / bug fixes diff --git a/crypto/core-test-framework/src/lib.rs b/crypto/core-test-framework/src/lib.rs index b169dfa..52ccc87 100644 --- a/crypto/core-test-framework/src/lib.rs +++ b/crypto/core-test-framework/src/lib.rs @@ -13,8 +13,8 @@ pub mod hash; pub mod kdf; pub mod kem; pub mod mac; -pub mod serializable_state; pub mod signature; +pub mod suspendable_state; mod fixed_seed_rng; pub use fixed_seed_rng::FixedSeedRNG; diff --git a/crypto/core-test-framework/src/serializable_state.rs b/crypto/core-test-framework/src/suspendable_state.rs similarity index 80% rename from crypto/core-test-framework/src/serializable_state.rs rename to crypto/core-test-framework/src/suspendable_state.rs index 694019b..7be7c21 100644 --- a/crypto/core-test-framework/src/serializable_state.rs +++ b/crypto/core-test-framework/src/suspendable_state.rs @@ -1,10 +1,10 @@ use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::serializable_state::{LIB_VERSION, SemVer}; -use bouncycastle_core::traits::{SerializableKeyedState, SerializableState}; +use bouncycastle_core::traits::{Suspendable, SuspendableKeyed}; -pub struct TestFrameworkSerializableState {} +pub struct TestFrameworkSuspendableState {} -impl TestFrameworkSerializableState { +impl TestFrameworkSuspendableState { pub fn new() -> Self { Self {} } @@ -12,10 +12,7 @@ impl TestFrameworkSerializableState { /// Test all the members of trait SerializableState. /// /// Expects ta be handed an instance of the object that has some in-progress state to be serialized. - pub fn test< - const SERIALIZED_STATE_LEN: usize, - S: SerializableState + Clone, - >( + pub fn test + Clone>( &self, instance: &S, ) { @@ -27,10 +24,10 @@ impl TestFrameworkSerializableState { let instance_clone = instance.clone(); // You can serialize and then deserialize the state. - let serialized_state = instance_clone.serialize_state(); + let serialized_state = instance_clone.suspend(); assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN); - let _deserialized_state = S::from_serialized_state(serialized_state).unwrap(); + let _deserialized_state = S::from_suspended(serialized_state).unwrap(); // The serialized state MUST include a prefix indicating the current version of the library. let state_sized: [u8; 3] = serialized_state[..3].try_into().unwrap(); @@ -41,7 +38,7 @@ impl TestFrameworkSerializableState { // used the helper functions. let mut ver0_serialized_state = serialized_state.clone(); ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); - match S::from_serialized_state(ver0_serialized_state) { + match S::from_suspended(ver0_serialized_state) { Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") @@ -54,7 +51,7 @@ impl TestFrameworkSerializableState { let mut futurever_serialized_state = serialized_state.clone(); futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); - match S::from_serialized_state(futurever_serialized_state) { + match S::from_suspended(futurever_serialized_state) { Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") @@ -63,9 +60,9 @@ impl TestFrameworkSerializableState { } } -pub struct TestFrameworkSerializableKeyedState {} +pub struct TestFrameworkSuspendableKeyedState {} -impl TestFrameworkSerializableKeyedState { +impl TestFrameworkSuspendableKeyedState { pub fn new() -> Self { Self {} } @@ -75,7 +72,7 @@ impl TestFrameworkSerializableKeyedState { /// Expects ta be handed an instance of the object that has some in-progress state to be serialized. pub fn test< const SERIALIZED_STATE_LEN: usize, - S: SerializableKeyedState + Clone, + S: SuspendableKeyed + Clone, >( &self, instance: &S, @@ -89,10 +86,10 @@ impl TestFrameworkSerializableKeyedState { let instance_clone = instance.clone(); // You can serialize and then deserialize the state. - let serialized_state = instance_clone.serialize_state(); + let serialized_state = instance_clone.suspend(); assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN); - let _deserialized_state = S::from_serialized_state(serialized_state, key).unwrap(); + let _deserialized_state = S::from_suspended(serialized_state, key).unwrap(); // The serialized state MUST include a prefix indicating the current version of the library. let state_sized: [u8; 3] = serialized_state[..3].try_into().unwrap(); @@ -103,7 +100,7 @@ impl TestFrameworkSerializableKeyedState { // used the helper functions. let mut ver0_serialized_state = serialized_state.clone(); ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); - match S::from_serialized_state(ver0_serialized_state, key) { + match S::from_suspended(ver0_serialized_state, key) { Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") @@ -116,7 +113,7 @@ impl TestFrameworkSerializableKeyedState { let mut futurever_serialized_state = serialized_state.clone(); futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); - match S::from_serialized_state(futurever_serialized_state, key) { + match S::from_suspended(futurever_serialized_state, key) { Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index 43424f7..0241ae7 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -78,7 +78,7 @@ pub enum SerializedStateError { IncompatibleVersion, /// The serialized state is malformed or corrupt. InvalidData, - /// The key supplied to [crate::traits::SerializableKeyedState::from_serialized_state] does not + /// The key supplied to [crate::traits::SuspendableKeyed::from_suspended] does not /// match the key the state was created with (it is bound to a different public-key hash `tr`). IncorrectKey, } diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index e2f9efc..f05cb43 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -488,8 +488,8 @@ pub trait RNG { #[allow(drop_bounds)] pub trait Secret: Drop + Debug + Display {} -/// Allows a stateful object to serialize its state so that it can be paused and resumed later, -/// potentially from a different host. +/// Allows a stateful object to suspend its operation by serializing its state into a byte array +///so that it can be resumed later, potentially from a different host. /// /// This is intended for situations where an object is being used through its streaming API /// (do_update, do_final) and the operation wants to be paused to a cache, for example while waiting @@ -499,54 +499,56 @@ pub trait Secret: Drop + Debug + Display {} /// will be more straightforward. /// /// The serialized state MAY contain short-term sensitive values such as nonces or IVs, -/// but it MUST NOT include a serialized private key. Keyed algorithms MUST instead impl -/// [SerializableKeyedState] which requires the key to be supplied independently at the time of deserialization. -pub trait SerializableState: Sized { - /// Serialize the state of the object. +/// but it MUST NOT include a serialized private key. +/// Keyed algorithms MUST instead impl +/// [SuspendableKeyed] which requires the key to be supplied independently at the time of deserialization. +pub trait Suspendable: Sized { + /// Suspend operation by serializing out the state of the object. /// /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization. /// If you want to do this intentionally, then you will need to clone the object before serializing it. /// /// The serialized state MUST include a prefix indicating the version of the library that serialized it. - fn serialize_state(self) -> [u8; SERIALIZED_STATE_LEN]; + fn suspend(self) -> [u8; SERIALIZED_STATE_LEN]; - /// Create a new object from a serialized state. + /// Resume operation from a serialized state. /// /// Deserializers SHOULD check the version and reject serialized states from incompatible versions /// (including rejecting serializations from a future version of the library). /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its /// deserializer should reject serialized states from that version or older. - fn from_serialized_state( - serialized_state: [u8; SERIALIZED_STATE_LEN], + fn from_suspended( + state: [u8; SERIALIZED_STATE_LEN], ) -> Result; } -/// Similar to [SerializableState] in that it allows a stateful object to serialize its state so that -/// it can be paused and resumed later, potentially from a different host. +/// Similar to [Suspendable] in that it allows a stateful object to suspend its operation by +/// serializing its state into a byte array so that it can be resumed later, potentially from a different host. /// /// The difference is that this trait is for keyed algorithms -- MACs, symmetric ciphers, signatures, etc -- -/// which require a private key. For security reasons, the private key is not included in the serialized state +/// which require a private key in order to resume successfully. +/// For security reasons, the private key is not included in the serialized state /// and must be provided separately as part of the deserialization process. -pub trait SerializableKeyedState: Sized { +pub trait SuspendableKeyed: Sized { /// The type of key that must be re-supplied to resume this object. type Key: ?Sized; - /// Serialize the state of the object. + /// Suspend operation by serializing out the state of the object. /// /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization. /// If you want to do this intentionally, then you will need to clone the object before serializing it. /// /// The serialized state MUST include a prefix indicating the version of the library that serialized it. - fn serialize_state(self) -> [u8; SERIALIZED_STATE_LEN]; + fn suspend(self) -> [u8; SERIALIZED_STATE_LEN]; - /// Create a new object from a serialized state. + /// Resume operation from a serialized state and the key. /// /// Deserializers SHOULD check the version and reject serialized states from incompatible versions /// (including rejecting serializations from a future version of the library). /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its /// deserializer should reject serialized states from that version or older. - fn from_serialized_state( - serialized_state: [u8; SERIALIZED_STATE_LEN], + fn from_suspended( + state: [u8; SERIALIZED_STATE_LEN], key: &Self::Key, ) -> Result; } diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 569c7f6..20773b9 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -144,16 +144,63 @@ //! let mut okm = KeyMaterial::<200>::new(); //! let _bytes_written = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, info, 200, &mut okm).unwrap(); //! ``` +//! +//! # Suspending and resuming execution +//! +//! The *HKDF-Extract* phase supports a streaming API whereby any amount of additional input keying +//! material can be provided either via [HKDF::do_extract_update_key] -- which will +//! credit the entropy of the provided [KeyMaterial] -- or as raw uncredited bytes via +//! [HKDF::do_extract_update_bytes]. +//! +//! As such, The *HKDF-Extract* phase can be suspended to a cache and resumed later via the +//! [SuspendableKeyed] trait. +//! +//! The HKDF algorithm is keyed by a `salt`, which is required twice: once at initialization and again +//! during finalization. Suspension and resumption are supported via the [SuspendableKeyed] trait +//! which requires the caller to store the salt securely and provide it again during resumption. +//! Note that providing a different salt during resumption cannot be detected by the library and +//! would silently produce a different PRK. +//! +//! ```rust +//! use bouncycastle_hkdf::HKDF_SHA256; +//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; +//! use bouncycastle_core::traits::SuspendableKeyed; +//! +//! let salt = KeyMaterial256::from_bytes_as_type( +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! KeyType::MACKey).unwrap(); +//! let ikm_part1 = b"input keying material part 1"; +//! let ikm_part2 = b" ...and part 2"; +//! +//! let mut hkdf = HKDF_SHA256::new(); +//! hkdf.do_extract_init(&salt).unwrap(); +//! hkdf.do_extract_update_bytes(ikm_part1).unwrap(); +//! +//! // suspend the in-progress extract (the salt is NOT included in the serialized state) +//! let serialized_state = hkdf.suspend(); +//! +//! // ... +//! // do other things in the meantime +//! // ... +//! +//! // ... later, possibly on another host: resume from the serialized state by re-supplying +//! // the same salt (make sure you store it securely!). +//! let mut hkdf = HKDF_SHA256::from_suspended(serialized_state, &salt).unwrap(); +//! hkdf.do_extract_update_bytes(ikm_part2).unwrap(); +//! let _prk = hkdf.do_extract_final().unwrap(); +//! ``` #![forbid(unsafe_code)] -use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError}; +use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError, SerializedStateError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial0, KeyMaterial512, KeyMaterialTrait, KeyType, }; -use bouncycastle_core::traits::{Hash, HashAlgParams, KDF, MAC, SecurityStrength}; -use bouncycastle_hmac::HMAC; +use bouncycastle_core::traits::{ + Hash, HashAlgParams, KDF, MAC, SecurityStrength, SuspendableKeyed, +}; +use bouncycastle_hmac::{HMAC, SUSPENDED_HMAC_SHA256_STATE_LEN, SUSPENDED_HMAC_SHA512_STATE_LEN}; use bouncycastle_sha2::{SHA256, SHA512}; use bouncycastle_utils::{max, min}; use std::marker::PhantomData; @@ -163,7 +210,7 @@ use bouncycastle_core::traits::XOF; // end doc-only imports /*** Constants ***/ -// Slightly hacky, but set this to accomodate the underlying hash primitive with the largest output size. +// Slightly hacky, but set this to accommodate the underlying hash primitive with the largest output size. // Would be better to somehow pull that at compile time from H, but I'm not sure how to do that. const HMAC_BLOCK_LEN: usize = 64; @@ -178,6 +225,7 @@ pub type HKDF_SHA256 = HKDF; #[allow(non_camel_case_types)] pub type HKDF_SHA512 = HKDF; +#[derive(Clone)] pub struct HKDF { // Optional because we can't construct an HMAC until they give us a key // to initialize it with. @@ -190,18 +238,34 @@ pub struct HKDF { // Note: does not need to impl Drop because HKDF itself does not hold any sensitive state data. #[derive(Clone, Debug, PartialOrd, PartialEq)] +#[repr(u8)] enum HkdfStates { /// waiting for salt - Uninitialized, + Uninitialized = 0, /// Salt set, waiting for IKMs or do_final - Initialized, + Initialized = 1, /// [HKDF::do_extract_update_key] has been called, after which no more credited IKMs can be given. /// This is in conformance with NIST SP 800-133 which requires all keys to come before other inputs. - TakingAdditionalInfo, + TakingAdditionalInfo = 2, +} + +impl TryFrom for HkdfStates { + type Error = SerializedStateError; + + /// Inverse of `self as u8`; rejects unrecognized discriminants with [SerializedStateError::InvalidData]. + fn try_from(value: u8) -> Result { + Ok(match value { + 0 => Self::Uninitialized, + 1 => Self::Initialized, + 2 => Self::TakingAdditionalInfo, + _ => return Err(SerializedStateError::InvalidData), + }) + } } +#[derive(Clone)] struct HkdfEntropyTracker { _phantomhash: PhantomData, entropy: usize, @@ -711,3 +775,105 @@ impl KDF for HKDF { H::default().max_security_strength() } } + +/// Length in bytes of the serialized state of [HKDF_SHA256]. +pub const SUSPENDED_HKDF_SHA256_STATE_LEN: usize = SUSPENDED_HMAC_SHA256_STATE_LEN + 11; +/// Length in bytes of the serialized state of [HKDF_SHA512]. +pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_LEN + 11; + +/// HKDF is *keyed by its salt* -- the salt keys the extract-phase HMAC -- so it implements +/// [SuspendableKeyed] (not [SerializableState]). An in-progress +/// extract operation can be suspended and resumed, but the salt is NOT written into the serialized +/// state and must be re-supplied to [SuspendableKeyed::from_serialized_state]. +/// +/// Only the extract phase carries resumable state (expand is a one-shot static operation). As with +/// HMAC, resuming with the wrong salt cannot be detected and will silently produce a wrong PRK. +/// +/// Serialized layout: the 3-byte library version header comes first and is checked before anything +/// else is parsed; then, using `B` = the inner HMAC blob length: +/// [0 .. B) the inner HMAC's SerializableKeyedState blob (salt excluded); zeroed when absent +/// [B] inner-HMAC present flag (0 = extract not yet initialized) +/// [B + 1] state-machine tag (see `HkdfStates`) +/// [B + 2 .. B + 10) entropy counter (usize serialized as u64, little-endian) +/// [B + 10] accumulated security strength (1-byte tag) +/// The number of bytes produced by `SerializableKeyedState::serialize_state` for each HKDF variant: +/// the 3-byte library version header + 11 bytes of HKDF bookkeeping (HMAC-present flag, state tag, +/// entropy counter, security strength) + the inner HMAC's serialized-state blob. +macro_rules! impl_suspendable_keyed_state_for_hkdf { + // $hash: the concrete hash; $hmac_blob: the inner HMAC's serialized-state length for that hash; + // $total: the full HKDF serialized-state length (= 3 + 11 + $hmac_blob). + ($hash:ty, $serialized_hmac_len:expr, $serialized_hkdf_len:expr) => { + impl SuspendableKeyed<{ $serialized_hkdf_len }> for HKDF<$hash> { + // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait` + // rather than a single concrete key type. The key is only used (by reference) to reload the key + // bytes at from_serialized_state, so dynamic dispatch here is negligible. + type Key = dyn KeyMaterialTrait; + + fn suspend(self) -> [u8; $serialized_hkdf_len] { + debug_assert_eq!($serialized_hkdf_len, $serialized_hmac_len + 11); + let mut state = [0u8; $serialized_hkdf_len]; + + // The inner HMAC blob goes first, which carries a lib version header. + if let Some(hmac) = self.hmac { + state[..$serialized_hmac_len].copy_from_slice(&hmac.suspend()); + state[$serialized_hmac_len] = 1; // present flag + } + + state[$serialized_hmac_len + 1] = self.state as u8; + state[$serialized_hmac_len + 2..$serialized_hmac_len + 10] + .copy_from_slice(&(self.entropy.entropy as u64).to_le_bytes()); + state[$serialized_hmac_len + 10] = self.entropy.security_strength as u8; + + state + } + + fn from_suspended( + state: [u8; $serialized_hkdf_len], + salt: &Self::Key, + ) -> Result { + // Rebuild the salt-keyed HMAC (first in the payload) by re-supplying the salt. + + // This double-dips on HMAC checking the version tag before going any further. + // If ever we need to version-reject HKDF separately from HMAC, then we'll need to add + // an explicit version check here, and change "None" to the oldest accepted version. + // _ = check_lib_ver(&serialized_state, None)?; + let hmac = match state[$serialized_hmac_len] { + 0 => None, + 1 => Some(HMAC::<$hash>::from_suspended( + state[..$serialized_hmac_len].try_into().unwrap(), + salt, + )?), + _ => return Err(SerializedStateError::InvalidData), + }; + + let hkdf_state = HkdfStates::try_from(state[$serialized_hmac_len + 1])?; + let entropy = u64::from_le_bytes( + state[$serialized_hmac_len + 2..$serialized_hmac_len + 10].try_into().unwrap(), + ) as usize; + let security_strength = + SecurityStrength::try_from(state[$serialized_hmac_len + 10])?; + + Ok(HKDF { + hmac, + entropy: HkdfEntropyTracker { + _phantomhash: PhantomData, + entropy, + security_strength, + }, + state: hkdf_state, + }) + } + } + }; +} + +impl_suspendable_keyed_state_for_hkdf!( + SHA256, + SUSPENDED_HMAC_SHA256_STATE_LEN, + SUSPENDED_HKDF_SHA256_STATE_LEN +); +impl_suspendable_keyed_state_for_hkdf!( + SHA512, + SUSPENDED_HMAC_SHA512_STATE_LEN, + SUSPENDED_HKDF_SHA512_STATE_LEN +); diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index 84037ea..7131943 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -11,7 +11,7 @@ mod hkdf_tests { use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; use bouncycastle_hex as hex; use bouncycastle_hkdf::{HKDF, HKDF_SHA256, HKDF_SHA512}; - use bouncycastle_sha2::SHA256; + use bouncycastle_sha2::{SHA256, SHA512}; use bouncycastle_utils::ct; #[test] @@ -739,4 +739,55 @@ mod hkdf_tests { &mut expected_key, ); } + #[test] + fn serializable_keyed_state() { + use bouncycastle_core::traits::{Hash, SuspendableKeyed}; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; + use bouncycastle_hkdf::{SUSPENDED_HKDF_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA512_STATE_LEN}; + + // HKDF is keyed by its salt: the salt is NOT serialized and is re-supplied on resume. + let salt = + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::MACKey).unwrap(); + let ikm = &DUMMY_SEED_512[16..64]; + let (part1, part2) = ikm.split_at(20); + + // A helper that exercises the full round-trip for one HKDF variant. A concrete `&KeyMaterial128` + // works for `do_extract_init` (which wants a `Sized` `&impl KeyMaterialTrait`) and coerces to + // `&dyn KeyMaterialTrait` for the serialization APIs. + fn round_trip(salt: &KeyMaterial128, part1: &[u8], part2: &[u8]) + where + H: Hash + HashAlgParams + Default, + HKDF: Clone + SuspendableKeyed, + { + let hkdf = HKDF::::new(); + + // it can be serialized pre-init, which is kinda a no-op, but at least it works. + let serialized_state = hkdf.suspend(); + assert_eq!(serialized_state.len(), LEN); + let mut hkdf = HKDF::::from_suspended(serialized_state, salt).unwrap(); + + hkdf.do_extract_init(salt).unwrap(); + hkdf.do_extract_update_bytes(part1).unwrap(); + + // generic trait-conformance tests (version header present, [0,0,0]/future rejected) + TestFrameworkSuspendableKeyedState::new().test(&hkdf, salt); + + // serialize the in-progress extract state (on a clone), then finish the original + let serialized_state = hkdf.clone().suspend(); + assert_eq!(serialized_state.len(), LEN); + + hkdf.do_extract_update_bytes(part2).unwrap(); + let prk = hkdf.do_extract_final().unwrap(); + + // resume (re-supplying the salt), feed the identical remaining IKM, and compare PRKs + let mut resumed = HKDF::::from_suspended(serialized_state, salt).unwrap(); + resumed.do_extract_update_bytes(part2).unwrap(); + let prk_resumed = resumed.do_extract_final().unwrap(); + + assert_eq!(prk.ref_to_bytes(), prk_resumed.ref_to_bytes()); + } + + round_trip::(&salt, part1, part2); + round_trip::(&salt, part1, part2); + } } diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index f1460f6..043e3f0 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -137,11 +137,11 @@ //! } //! ``` //! -//! # Suspending and resuming execution via SerializableState +//! # Suspending and resuming execution //! //! When MAC'ing a large message, it can be advantageous to be able to suspend the operation //! to a cache and resume it later; for example if waiting for the message to stream over a slow network -//! connection. For this reason, all HMAC algorithms impl [SerializableKeyedState]. +//! connection. For this reason, all HMAC algorithms impl [SuspendableKeyed]. //! //! Note that since HMAC is a keyed //! algorithm and we do not want to serialize the private key into the state, the trait structure forces you to @@ -153,7 +153,7 @@ //!```rust //! use bouncycastle_hmac::HMAC_SHA256; //! use bouncycastle_core::key_material::KeyMaterial256; -//! use bouncycastle_core::traits::{MAC, SerializableKeyedState}; +//! use bouncycastle_core::traits::{MAC, SuspendableKeyed}; //! use bouncycastle_core::key_material::KeyType; //! //! let msg_part1 = b"The quick brown fox"; @@ -166,16 +166,16 @@ //! let mut hmac = HMAC_SHA256::new(&key).unwrap(); //! hmac.do_update(msg_part1); //! -//! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = hmac.serialize_state(); +//! // suspend the in-progress mac (the key is NOT included in the serialized state) +//! let serialized_state = hmac.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! // Since the key is not serialized into the state, you have to store it somewhere and provide -//! // it to the deserializer. Make sure you store it securely! -//! let mut hmac_resumed = HMAC_SHA256::from_serialized_state(serialized_state, &key).unwrap(); +//! // ... later, possibly on another host: resume from the serialized state by re-supplying +//! // the same salt (make sure you store it securely!). +//! let mut hmac_resumed = HMAC_SHA256::from_suspended(serialized_state, &key).unwrap(); //! hmac_resumed.do_update(msg_part2); //! let h: Vec = hmac_resumed.do_final(); //! ``` @@ -187,20 +187,30 @@ use bouncycastle_core::errors::{KeyMaterialError, MACError, SerializedStateError}; use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - Algorithm, Hash, MAC, SecurityStrength, SerializableKeyedState, SerializableState, + Algorithm, Hash, MAC, SecurityStrength, Suspendable, SuspendableKeyed, }; -use bouncycastle_sha2::{SHA224, SHA256, SHA384, SHA512}; -use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; +use bouncycastle_sha2::{ + SHA224, SHA256, SHA384, SHA512, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN, +}; +use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512, SUSPENDED_SHA3_STATE_LEN}; use bouncycastle_utils::ct; /*** String constants ***/ +/// pub const HMAC_SHA224_NAME: &str = "HMAC-SHA224"; +/// pub const HMAC_SHA256_NAME: &str = "HMAC-SHA256"; +/// pub const HMAC_SHA384_NAME: &str = "HMAC-SHA384"; +/// pub const HMAC_SHA512_NAME: &str = "HMAC-SHA512"; +/// pub const HMAC_SHA3_224_NAME: &str = "HMAC-SHA3-224"; +/// pub const HMAC_SHA3_256_NAME: &str = "HMAC-SHA3-256"; +/// pub const HMAC_SHA3_384_NAME: &str = "HMAC-SHA3-384"; +/// pub const HMAC_SHA3_512_NAME: &str = "HMAC-SHA3-512"; /*** Type aliases ***/ @@ -298,7 +308,7 @@ impl HMAC { // TODO: it would be nice to be able to statically extract the length of HASH and not need a Vec or over-sized array here. // TODO: make this no_std-friendly let mut padded = vec![0u8; self.hasher.block_bitlen() / 8]; - + padded[..self.key_len].copy_from_slice(&self.key[..self.key_len]); // XXX: easier way to xor over Vec? @@ -324,7 +334,7 @@ impl HMAC { self.key[..key_bytes.len()].copy_from_slice(key_bytes); self.key_len = key_bytes.len(); } - + // Just as a sanity-check. assert!( self.key_len <= KEY_BUF_LEN, @@ -461,14 +471,34 @@ impl MAC for HMAC MAC for HMAC, -> SerializableKeyedState for HMAC + HASH: Hash + Default + Suspendable, +> SuspendableKeyed for HMAC { // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait` // rather than a single concrete key type. The key is only used (by reference) to reload the key // bytes at from_serialized_state, so dynamic dispatch here is negligible. type Key = dyn KeyMaterialTrait; - fn serialize_state(self) -> [u8; HASH_STATE_LEN] { + fn suspend(self) -> [u8; HASH_STATE_LEN] { // The key is intentionally excluded; the resumable state is just the inner hasher, which // already carries the library version header from the hash's own SerializableState impl. - self.hasher.serialize_state() + self.hasher.suspend() } - fn from_serialized_state( - serialized_state: [u8; HASH_STATE_LEN], - key: &dyn KeyMaterialTrait, + fn from_suspended( + state: [u8; HASH_STATE_LEN], + key: &Self::Key, ) -> Result { // Rebuild the inner hasher (version-compatibility is validated by the hash's impl). - let hasher = HASH::from_serialized_state(serialized_state)?; + let hasher = HASH::from_suspended(state)?; // Re-load the key material exactly as `new()` did (pre-hashing an over-length key), but do // NOT re-absorb `K ⊕ ipad` — the deserialized hasher already contains it. The key is only diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/hmac/tests/hmac_tests.rs index 4e2f9fc..0c5daa4 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/hmac/tests/hmac_tests.rs @@ -148,7 +148,7 @@ mod hmac_tests { fn long_key() { // Regression test: a key just under the maximum length before HMAC will hash it down. // (RFC 2104 only pre-hashes keys *longer* than the block). - // This test wil detect an overflow-write and panic on HMAC's internal key buffer. + // This test is designed to detect an overflow-write and panic on HMAC's internal key buffer. // SHA-512 has a 128-byte block, so use a 127-byte key let key = KeyMaterial::<200>::from_bytes_as_type(&[0x0B; 127], KeyType::MACKey).unwrap(); @@ -603,11 +603,11 @@ mod hmac_tests { } #[test] - fn serializable_keyed_state() { + fn suspendable_keyed_state() { use bouncycastle_core::errors::SerializedStateError; use bouncycastle_core::serializable_state::LIB_VERSION; - use bouncycastle_core::traits::SerializableKeyedState; - use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableKeyedState; + use bouncycastle_core::traits::SuspendableKeyed; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; let key = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::MACKey).unwrap(); @@ -624,15 +624,15 @@ mod hmac_tests { key: &(dyn KeyMaterialTrait + 'static), input: &[u8], ) where - H: MAC + Clone + SerializableKeyedState, + H: MAC + Clone + SuspendableKeyed, { hmac.do_update(&input[..10]); // do the default trait-conformance tests - TestFrameworkSerializableKeyedState::new().test(&hmac, key); + TestFrameworkSuspendableKeyedState::new().test(&hmac, key); // serialize the in-progress state (on a clone), then finish the original - let serialized_state = hmac.clone().serialize_state(); + let serialized_state = hmac.clone().suspend(); // the serialized state carries the library version header (from the inner hash) let header: [u8; 3] = serialized_state[..3].try_into().unwrap(); @@ -643,14 +643,14 @@ mod hmac_tests { // rebuild from the serialized state (re-supplying the key), feed the identical remaining // input, and confirm the MAC matches - let mut from_state = H::from_serialized_state(serialized_state, key).unwrap(); + let mut from_state = H::from_suspended(serialized_state, key).unwrap(); from_state.do_update(&input[10..]); assert_eq!(expected, from_state.do_final()); // a state whose version header is zeroed must be rejected (delegated to the hash's impl) let mut busted = serialized_state; busted[..3].copy_from_slice(&[0, 0, 0]); - match H::from_serialized_state(busted, key) { + match H::from_suspended(busted, key) { Err(SerializedStateError::IncompatibleVersion) => { /* good */ } _ => panic!("Expected IncompatibleVersion for a zeroed version header"), } diff --git a/crypto/mldsa-lowmemory/src/mldsa.rs b/crypto/mldsa-lowmemory/src/mldsa.rs index befebd7..77254a3 100644 --- a/crypto/mldsa-lowmemory/src/mldsa.rs +++ b/crypto/mldsa-lowmemory/src/mldsa.rs @@ -321,7 +321,7 @@ //! //! ```rust //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; -//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! use bouncycastle_core::traits::{Signer, Suspendable}; //! //! let msg_part1 = b"The quick brown fox"; //! let msg_part2 = b" jumped over the lazy dog"; @@ -332,13 +332,13 @@ //! mb.do_update(msg_part1); //! //! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = mb.serialize_state(); +//! let serialized_state = mb.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); //! mb_resumed.do_update(msg_part2); //! let mu: [u8; 64] = mb_resumed.do_final(); //! @@ -350,7 +350,7 @@ //! //! ```rust //! use bouncycastle_mldsa_lowmemory::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; -//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! use bouncycastle_core::traits::{Signer, Suspendable}; //! use bouncycastle_core::errors::SignatureError; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -366,13 +366,13 @@ //! mb.do_update(msg_part1); //! //! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = mb.serialize_state(); +//! let serialized_state = mb.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); //! mb_resumed.do_update(msg_part2); //! let mu: [u8; 64] = mb_resumed.do_final(); //! @@ -400,10 +400,10 @@ use crate::{ use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{ - Algorithm, RNG, SecurityStrength, SerializableState, SignatureVerifier, Signer, XOF, + Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, Suspendable, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha3::{SHA3_SERIALIZED_STATE_LEN, SHAKE128, SHAKE256}; +use bouncycastle_sha3::{SHAKE128, SHAKE256, SUSPENDED_SHA3_STATE_LEN}; use core::marker::PhantomData; // imports needed just for docs @@ -1937,23 +1937,23 @@ impl MuBuilder { } /// The length, in bytes, of a serialized state of a [MuBuilder] object. -pub const MU_BUILDER_SERIALIZED_STATE_LEN: usize = SHA3_SERIALIZED_STATE_LEN; +pub const MU_BUILDER_SERIALIZED_STATE_LEN: usize = SUSPENDED_SHA3_STATE_LEN; /// If you are processing a large input message into ML-DSA and want to pause the operation -/// -- maybe while waiting for slow network IO), you'll need to use [SerializableState]. +/// -- maybe while waiting for slow network IO), you'll need to use [Suspendable]. /// Serialization of the state of an in-progress ML-DSA instance is really just serialization /// of the construction of the message representative mu, since no other part of the ML-DSA algorithm /// has a pausable state. // A [MuBuilder]'s (and by virtue, an ML-DSA instance's) entire mutable state is its inner SHAKE256 sponge, // so serialization delegates directly to [SHAKE256]'s [SerializableState] impl. -impl SerializableState for MuBuilder { - fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { - self.h.serialize_state() +impl Suspendable for MuBuilder { + fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] { + self.h.suspend() } - fn from_serialized_state( - serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + fn from_suspended( + serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], ) -> Result { - Ok(MuBuilder { h: H::from_serialized_state(serialized_state)? }) + Ok(MuBuilder { h: H::from_suspended(serialized_state)? }) } } diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index b69cb67..27e21c6 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -6,12 +6,11 @@ mod mldsa_tests { use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - RNG, SecurityStrength, SerializableState, SignaturePrivateKey, SignaturePublicKey, - SignatureVerifier, Signer, + RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, + Suspendable, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::FixedSeedRNG; - use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; use bouncycastle_mldsa_lowmemory::{ @@ -817,9 +816,9 @@ mod mldsa_tests { // serializing and resuming after init let mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); - let serialized_state = mb.serialize_state(); + let serialized_state = mb.suspend(); - let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); mb_resumed.do_update(msg); let mu_resumed = mb_resumed.do_final(); assert_eq!(mu_resumed, mu1); @@ -831,15 +830,17 @@ mod mldsa_tests { let mut mb = MuBuilder::do_init(&pk.compute_tr(), None).unwrap(); mb.do_update(msg1); - let serialized_state = mb.serialize_state(); - let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + let serialized_state = mb.suspend(); + let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); mb_resumed.do_update(msg2); let mu_resumed2 = mb_resumed.do_final(); assert_eq!(mu_resumed2, mu1); } #[test] - fn mubuilder_serializable_state() { + fn mubuilder_suspendable_state() { + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; + // `tr` is the 64-byte public-key hash that seeds mu computation; its exact value doesn't // matter for exercising serialization, only that it is fixed across the comparison. let tr = [0x42u8; 64]; @@ -851,16 +852,16 @@ mod mldsa_tests { // Generic trait-conformance tests (version header present, [0,0,0] and future versions // rejected). - TestFrameworkSerializableState::new().test(&mb); + TestFrameworkSuspendableState::new().test(&mb); // Serialize the in-progress state, then finish the original. - let serialized_state = mb.clone().serialize_state(); + let serialized_state = mb.clone().suspend(); mb.do_update(&msg[10..]); let expected_mu = mb.do_final(); // Rebuild from the serialized state, feed it the identical remaining input, and confirm it // produces the same mu. - let mut from_state = MuBuilder::from_serialized_state(serialized_state).unwrap(); + let mut from_state = MuBuilder::from_suspended(serialized_state).unwrap(); from_state.do_update(&msg[10..]); assert_eq!(expected_mu, from_state.do_final()); @@ -873,7 +874,7 @@ mod mldsa_tests { // + bits_in_queue(8) + squeezing(1) -> the squeezing byte is at offset 3 + 1 + 400. let mut busted = serialized_state; busted[3 + 1 + 400] = 42; - match MuBuilder::from_serialized_state(busted) { + match MuBuilder::from_suspended(busted) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } @@ -890,9 +891,9 @@ mod mldsa_tests { // tag 6). let mut shake128 = SHAKE128::new(); shake128.absorb(b"Colorless green ideas sleep furiously"); - let serialized_128 = shake128.serialize_state(); + let serialized_128 = shake128.suspend(); - match MuBuilder::from_serialized_state(serialized_128) { + match MuBuilder::from_suspended(serialized_128) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into a MuBuilder"), } diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index 434777b..71a76eb 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -411,7 +411,7 @@ //! //! ```rust //! use bouncycastle_mldsa::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; -//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! use bouncycastle_core::traits::{Signer, Suspendable}; //! //! let msg_part1 = b"The quick brown fox"; //! let msg_part2 = b" jumped over the lazy dog"; @@ -422,13 +422,13 @@ //! mb.do_update(msg_part1); //! //! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = mb.serialize_state(); +//! let serialized_state = mb.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); //! mb_resumed.do_update(msg_part2); //! let mu: [u8; 64] = mb_resumed.do_final(); //! @@ -440,7 +440,7 @@ //! //! ```rust //! use bouncycastle_mldsa::{MLDSA65, MuBuilder, MLDSATrait, MLDSAPublicKeyTrait}; -//! use bouncycastle_core::traits::{Signer, SerializableState}; +//! use bouncycastle_core::traits::{Signer, Suspendable}; //! use bouncycastle_core::errors::SignatureError; //! //! let (pk, sk) = MLDSA65::keygen().unwrap(); @@ -456,13 +456,13 @@ //! mb.do_update(msg_part1); //! //! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = mb.serialize_state(); +//! let serialized_state = mb.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); +//! let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); //! mb_resumed.do_update(msg_part2); //! let mu: [u8; 64] = mb_resumed.do_final(); //! @@ -488,10 +488,10 @@ use crate::{ use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - Algorithm, RNG, SecurityStrength, SerializableState, SignatureVerifier, Signer, XOF, + Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, Suspendable, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha3::{SHA3_SERIALIZED_STATE_LEN, SHAKE128, SHAKE256}; +use bouncycastle_sha3::{SHAKE128, SHAKE256, SUSPENDED_SHA3_STATE_LEN}; use core::marker::PhantomData; // imports needed just for docs @@ -2259,23 +2259,23 @@ impl MuBuilder { } /// The length, in bytes, of a serialized state of a [MuBuilder] object. -pub const MU_BUILDER_SERIALIZED_STATE_LEN: usize = SHA3_SERIALIZED_STATE_LEN; +pub const MU_BUILDER_SERIALIZED_STATE_LEN: usize = SUSPENDED_SHA3_STATE_LEN; /// If you are processing a large input message into ML-DSA and want to pause the operation -/// -- maybe while waiting for slow network IO), you'll need to use [SerializableState]. +/// -- maybe while waiting for slow network IO), you'll need to use [Suspendable]. /// Serialization of the state of an in-progress ML-DSA instance is really just serialization /// of the construction of the message representative mu, since no other part of the ML-DSA algorithm /// has a pausable state. // A [MuBuilder]'s (and by virtue, an ML-DSA instance's) entire mutable state is its inner SHAKE256 sponge, // so serialization delegates directly to [SHAKE256]'s [SerializableState] impl. -impl SerializableState for MuBuilder { - fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { - self.h.serialize_state() +impl Suspendable for MuBuilder { + fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] { + self.h.suspend() } - fn from_serialized_state( - serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + fn from_suspended( + serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], ) -> Result { - Ok(MuBuilder { h: H::from_serialized_state(serialized_state)? }) + Ok(MuBuilder { h: H::from_suspended(serialized_state)? }) } } diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index 8207a96..adc5c18 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -7,12 +7,11 @@ mod mldsa_tests { KeyMaterial256, KeyMaterialTrait, KeyType, do_hazardous_operations, }; use bouncycastle_core::traits::{ - RNG, SecurityStrength, SerializableState, SignaturePrivateKey, SignaturePublicKey, - SignatureVerifier, Signer, + RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, + Suspendable, }; use bouncycastle_core_test_framework::DUMMY_SEED_1024; use bouncycastle_core_test_framework::FixedSeedRNG; - use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; use bouncycastle_mldsa::{ @@ -981,9 +980,9 @@ mod mldsa_tests { // serializing and resuming after init let mb = MuBuilder::do_init(&pk_expanded.compute_tr(), None).unwrap(); - let serialized_state = mb.serialize_state(); + let serialized_state = mb.suspend(); - let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); mb_resumed.do_update(msg); let mu_resumed = mb_resumed.do_final(); assert_eq!(mu_resumed, mu1); @@ -995,8 +994,8 @@ mod mldsa_tests { let mut mb = MuBuilder::do_init(&pk_expanded.compute_tr(), None).unwrap(); mb.do_update(msg1); - let serialized_state = mb.serialize_state(); - let mut mb_resumed = MuBuilder::from_serialized_state(serialized_state).unwrap(); + let serialized_state = mb.suspend(); + let mut mb_resumed = MuBuilder::from_suspended(serialized_state).unwrap(); mb_resumed.do_update(msg2); let mu_resumed2 = mb_resumed.do_final(); assert_eq!(mu_resumed2, mu1); @@ -1025,7 +1024,9 @@ mod mldsa_tests { } #[test] - fn mubuilder_serializable_state() { + fn mubuilder_suspendable_state() { + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; + // `tr` is the 64-byte public-key hash that seeds mu computation; its exact value doesn't // matter for exercising serialization, only that it is fixed across the comparison. let tr = [0x42u8; 64]; @@ -1037,16 +1038,16 @@ mod mldsa_tests { // Generic trait-conformance tests (version header present, [0,0,0] and future versions // rejected). - TestFrameworkSerializableState::new().test(&mb); + TestFrameworkSuspendableState::new().test(&mb); // Serialize the in-progress state, then finish the original. - let serialized_state = mb.clone().serialize_state(); + let serialized_state = mb.clone().suspend(); mb.do_update(&msg[10..]); let expected_mu = mb.do_final(); // Rebuild from the serialized state, feed it the identical remaining input, and confirm it // produces the same mu. - let mut from_state = MuBuilder::from_serialized_state(serialized_state).unwrap(); + let mut from_state = MuBuilder::from_suspended(serialized_state).unwrap(); from_state.do_update(&msg[10..]); assert_eq!(expected_mu, from_state.do_final()); @@ -1059,7 +1060,7 @@ mod mldsa_tests { // + bits_in_queue(8) + squeezing(1) -> the squeezing byte is at offset 3 + 1 + 400. let mut busted = serialized_state; busted[3 + 1 + 400] = 42; - match MuBuilder::from_serialized_state(busted) { + match MuBuilder::from_suspended(busted) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } @@ -1076,9 +1077,9 @@ mod mldsa_tests { // tag 6). let mut shake128 = SHAKE128::new(); shake128.absorb(b"Colorless green ideas sleep furiously"); - let serialized_128 = shake128.serialize_state(); + let serialized_128 = shake128.suspend(); - match MuBuilder::from_serialized_state(serialized_128) { + match MuBuilder::from_suspended(serialized_128) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into a MuBuilder"), } diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index d0bac1e..687817e 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -34,17 +34,17 @@ //! let output: Vec = sha2.do_final(); //! ``` //! -//! # Suspending and resuming execution via SerializableState +//! # Suspending and resuming execution //! //! When hashing a large message, it can be advantageous to be able to suspend the operation //! to a cache and resume it later; for example if waiting for the message to stream over a slow network //! connection. //! -//! For this reason, all SHA2 algorithms impl [SerializableState]. +//! For this reason, all SHA2 algorithms impl [Suspendable]. //! //! ```rust //! use bouncycastle_sha2 as sha2; -//! use bouncycastle_core::traits::{Hash, SerializableState}; +//! use bouncycastle_core::traits::{Hash, Suspendable}; //! //! let msg_part1 = b"The quick brown fox"; //! let msg_part2 = b" jumped over the lazy dog"; @@ -52,14 +52,15 @@ //! let mut sha2 = sha2::SHA256::new(); //! sha2.do_update(msg_part1); //! -//! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = sha2.serialize_state(); +//! // suspend the in-progress extract while "waiting" for the second part of the message. +//! let serialized_state = sha2.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! let mut sha2_resumed = sha2::SHA256::from_serialized_state(serialized_state).unwrap(); +//! // ... later, possibly on another host: resume from the serialized state. +//! let mut sha2_resumed = sha2::SHA256::from_suspended(serialized_state).unwrap(); //! sha2_resumed.do_update(msg_part2); //! let h: Vec = sha2_resumed.do_final(); //! ``` @@ -74,6 +75,10 @@ pub use self::sha256::SHA256Internal; pub use self::sha512::SHA512Internal; use bouncycastle_core::traits::{Algorithm, HashAlgParams, SecurityStrength}; +/*** Imports needed for docs ***/ +#[allow(unused_imports)] +use bouncycastle_core::traits::Suspendable; + /*** String constants ***/ pub const SHA224_NAME: &str = "SHA224"; pub const SHA256_NAME: &str = "SHA256"; @@ -170,5 +175,5 @@ impl HashAlgParams for SHA512Params { } impl SHA2Params for SHA512Params {} -pub use sha256::SHA256_SERIALIZED_STATE_LEN; -pub use sha512::SHA512_SERIALIZED_STATE_LEN; +pub use sha256::SUSPENDED_SHA256_STATE_LEN; +pub use sha512::SUSPENDED_SHA512_STATE_LEN; diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index bbd56e5..12bbdcd 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -1,7 +1,7 @@ use crate::SHA2Params; use bouncycastle_core::errors::{HashError, SerializedStateError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; +use bouncycastle_core::traits::{Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; @@ -304,15 +304,14 @@ impl Hash for SHA256Internal { } } -/// The number of bytes produced by [SerializableState::serialize_state] for any SHA224 or SHA256 -/// object. -pub const SHA256_SERIALIZED_STATE_LEN: usize = 108; +/// Length in bytes of the serialized state of SHA224 and SHA256. +pub const SUSPENDED_SHA256_STATE_LEN: usize = 108; -impl SerializableState for SHA256Internal { - fn serialize_state(self) -> [u8; SHA256_SERIALIZED_STATE_LEN] { - debug_assert_eq!(SHA256_SERIALIZED_STATE_LEN, 108); +impl Suspendable for SHA256Internal { + fn suspend(self) -> [u8; SUSPENDED_SHA256_STATE_LEN] { + debug_assert_eq!(SUSPENDED_SHA256_STATE_LEN, 108); - let mut out_to_return = [0u8; SHA256_SERIALIZED_STATE_LEN]; + let mut out_to_return = [0u8; SUSPENDED_SHA256_STATE_LEN]; // insert the version tag let out: &mut [u8; 105] = add_lib_ver(&mut out_to_return).try_into().unwrap(); @@ -337,10 +336,10 @@ impl SerializableState for SHA2 out_to_return } - fn from_serialized_state( - serialized_state: [u8; SHA256_SERIALIZED_STATE_LEN], + fn from_suspended( + serialized_state: [u8; SUSPENDED_SHA256_STATE_LEN], ) -> Result { - debug_assert_eq!(SHA256_SERIALIZED_STATE_LEN, 108); + debug_assert_eq!(SUSPENDED_SHA256_STATE_LEN, 108); // check the version tag // At the moment, we have no not_before version to specify. diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 7b98b0e..89c4af2 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -1,7 +1,7 @@ use crate::SHA2Params; use bouncycastle_core::errors::{HashError, SerializedStateError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState}; +use bouncycastle_core::traits::{Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; @@ -319,15 +319,14 @@ impl Hash for SHA512Internal { } } -/// The number of bytes produced by [SerializableState::serialize_state] for any SHA384 or SHA512 -/// object. -pub const SHA512_SERIALIZED_STATE_LEN: usize = 204; +/// Length in bytes of the serialized state of SHA384 and SHA512. +pub const SUSPENDED_SHA512_STATE_LEN: usize = 204; -impl SerializableState for SHA512Internal { - fn serialize_state(self) -> [u8; SHA512_SERIALIZED_STATE_LEN] { - debug_assert_eq!(SHA512_SERIALIZED_STATE_LEN, 204); +impl Suspendable for SHA512Internal { + fn suspend(self) -> [u8; SUSPENDED_SHA512_STATE_LEN] { + debug_assert_eq!(SUSPENDED_SHA512_STATE_LEN, 204); - let mut out_to_return = [0u8; SHA512_SERIALIZED_STATE_LEN]; + let mut out_to_return = [0u8; SUSPENDED_SHA512_STATE_LEN]; // insert the version tag let out: &mut [u8; 201] = add_lib_ver(&mut out_to_return).try_into().unwrap(); @@ -352,8 +351,8 @@ impl SerializableState for SHA5 out_to_return } - fn from_serialized_state( - serialized_state: [u8; SHA512_SERIALIZED_STATE_LEN], + fn from_suspended( + serialized_state: [u8; SUSPENDED_SHA512_STATE_LEN], ) -> Result { // check the version tag // At the moment, we have no not_before version to specify. diff --git a/crypto/sha2/tests/sha2_tests.rs b/crypto/sha2/tests/sha2_tests.rs index ad49cdd..d6f2103 100644 --- a/crypto/sha2/tests/sha2_tests.rs +++ b/crypto/sha2/tests/sha2_tests.rs @@ -92,9 +92,9 @@ mod sha2_tests { } #[test] - fn test_serializable_state() { - use bouncycastle_core::traits::SerializableState; - use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; + fn suspendable_state() { + use bouncycastle_core::traits::Suspendable; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; let str = "Colorless green ideas sleep furiously"; @@ -103,24 +103,25 @@ mod sha2_tests { sha256.do_update(str.as_bytes()); // do the default tests - let test_framework = TestFrameworkSerializableState::new(); + let test_framework = TestFrameworkSuspendableState::new(); test_framework.test(&sha256); // now let's serialize the in-progress state - let serialized_state = sha256.clone().serialize_state(); + let serialized_state = sha256.clone().suspend(); + assert_eq!(serialized_state.len(), SUSPENDED_SHA256_STATE_LEN); // finish the hash let output = sha256.do_final(); // then load from state and finish the hash and make sure we get the same thing - let sha2_from_state = SHA256::from_serialized_state(serialized_state).unwrap(); + let sha2_from_state = SHA256::from_suspended(serialized_state).unwrap(); let output2 = sha2_from_state.do_final(); assert_eq!(output, output2); // also, give it a busted x_buf_off, just to satisfy mutants that that's been tested let mut busted_state = serialized_state.clone(); busted_state[3 + 104] = 65; - match SHA256::from_serialized_state(busted_state) { + match SHA256::from_suspended(busted_state) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error"), } @@ -130,24 +131,25 @@ mod sha2_tests { sha512.do_update(str.as_bytes()); // do the default tests - let test_framework = TestFrameworkSerializableState::new(); + let test_framework = TestFrameworkSuspendableState::new(); test_framework.test(&sha512); // now let's serialize the in-progress state - let serialized_state = sha512.clone().serialize_state(); + let serialized_state = sha512.clone().suspend(); + assert_eq!(serialized_state.len(), SUSPENDED_SHA512_STATE_LEN); // finish the hash let output = sha512.do_final(); // then load from state and finish the hash and make sure we get the same thing - let sha2_from_state = SHA512::from_serialized_state(serialized_state).unwrap(); + let sha2_from_state = SHA512::from_suspended(serialized_state).unwrap(); let output2 = sha2_from_state.do_final(); assert_eq!(output, output2); // also, give it a busted x_buf_off, just to satisfy mutants that that's been tested let mut busted_state = serialized_state.clone(); busted_state[3 + 200] = 129; - match SHA512::from_serialized_state(busted_state) { + match SHA512::from_suspended(busted_state) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error"), } diff --git a/crypto/sha3/src/keccak.rs b/crypto/sha3/src/keccak.rs index 3609b88..c887e16 100644 --- a/crypto/sha3/src/keccak.rs +++ b/crypto/sha3/src/keccak.rs @@ -374,8 +374,8 @@ const KECCAK_SERIALIZED_LEN: usize = 200 + 192 + 8 + 1; /// [.. + 8) kdf_entropy usize serialized as u64 pub(crate) const SHA3_FAMILY_STATE_LEN: usize = 1 + KECCAK_SERIALIZED_LEN + 10; -/// Total number of bytes in a serialized state of a SHA3 or SHAKE instance. -pub const SHA3_SERIALIZED_STATE_LEN: usize = 3 + SHA3_FAMILY_STATE_LEN; +/// Length in bytes of the serialized state of a SHA3 or SHAKE instance. +pub const SUSPENDED_SHA3_STATE_LEN: usize = 3 + SHA3_FAMILY_STATE_LEN; impl KeccakDigest { /// Serializes this digest's mutable state into `out`. The `rate` is deliberately omitted; see diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 8a80639..902c8ed 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -104,17 +104,17 @@ //! [KeyType::CryptographicRandom] since the input [KeyMaterial] is 16 bytes but [SHA3_256] needs at least 32 bytes of //! full-entropy input key material in order to be able to produce full entropy output key material. //! -//! # Suspending and resuming execution via SerializableState +//! # Suspending and resuming execution //! //! When hashing a large message, it can be advantageous to be able to suspend the operation //! to a cache and resume it later; for example if waiting for the message to stream over a slow network //! connection. //! -//! For this reason, all SHA3 algorithms impl [SerializableState]. +//! For this reason, all SHA3 algorithms impl [Suspendable]. //! //!```rust //! use bouncycastle_sha3 as sha3; -//! use bouncycastle_core::traits::{Hash, SerializableState}; +//! use bouncycastle_core::traits::{Hash, Suspendable}; //! //! let msg_part1 = b"The quick brown fox"; //! let msg_part2 = b" jumped over the lazy dog"; @@ -122,14 +122,15 @@ //! let mut sha3 = sha3::SHA3_256::new(); //! sha3.do_update(msg_part1); //! -//! // here, we'll suspend while "waiting" for the second part of the message -//! let serialized_state = sha3.serialize_state(); +//! // suspend the in-progress extract while "waiting" for the second part of the message. +//! let serialized_state = sha3.suspend(); //! //! // ... //! // do other things in the meantime //! // ... //! -//! let mut sha3_resumed = sha3::SHA3_256::from_serialized_state(serialized_state).unwrap(); +//! // ... later, possibly on another host: resume from the serialized state. +//! let mut sha3_resumed = sha3::SHA3_256::from_suspended(serialized_state).unwrap(); //! sha3_resumed.do_update(msg_part2); //! let h: Vec = sha3_resumed.do_final(); //! ``` @@ -144,7 +145,7 @@ use bouncycastle_core::traits::{Algorithm, HashAlgParams, SecurityStrength}; #[allow(unused_imports)] use bouncycastle_core::key_material::{KeyMaterial, KeyType}; #[allow(unused_imports)] -use bouncycastle_core::traits::{Hash, KDF, XOF}; +use bouncycastle_core::traits::{Hash, KDF, Suspendable, XOF}; // end of doc-only imports mod keccak; @@ -163,9 +164,7 @@ pub const SHAKE256_NAME: &str = "SHAKE256"; pub use sha3::SHA3; pub use shake::SHAKE; -/// The number of bytes produced by [SerializableState::serialize_state] for any SHA3 or SHAKE -/// object. -pub use keccak::SHA3_SERIALIZED_STATE_LEN; +pub use keccak::SUSPENDED_SHA3_STATE_LEN; pub type SHA3_224 = SHA3; pub type SHA3_256 = SHA3; pub type SHA3_384 = SHA3; diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 4d78f37..1bfde8a 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -1,13 +1,13 @@ use crate::SHA3Params; use crate::keccak::{ - KeccakDigest, SHA3_FAMILY_STATE_LEN, SHA3_SERIALIZED_STATE_LEN, deserialize_sha3_family_state, + KeccakDigest, SHA3_FAMILY_STATE_LEN, SUSPENDED_SHA3_STATE_LEN, deserialize_sha3_family_state, serialize_sha3_family_state, }; use bouncycastle_core::errors::{HashError, KDFError, SerializedStateError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Hash, KDF, SecurityStrength, SerializableState}; +use bouncycastle_core::traits::{Hash, KDF, SecurityStrength, Suspendable}; use bouncycastle_utils::{max, min}; #[derive(Clone)] @@ -221,49 +221,6 @@ impl Hash for SHA3 { } } -impl SerializableState for SHA3 { - fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { - let mut out_to_return = [0u8; SHA3_SERIALIZED_STATE_LEN]; - - // insert the version tag - let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = - add_lib_ver(&mut out_to_return).try_into().unwrap(); - - serialize_sha3_family_state( - out, - PARAMS::STATE_TAG, - &self.keccak, - self.kdf_key_type, - self.kdf_security_strength, - self.kdf_entropy, - ); - - out_to_return - } - - fn from_serialized_state( - serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], - ) -> Result { - // check the version tag. At the moment, we have no not_before version to specify. - let input: &[u8; SHA3_FAMILY_STATE_LEN] = - check_lib_ver(&serialized_state, None)?.try_into().unwrap(); - - // The variant tag rejects states from any other SHA3/SHAKE variant; the rate is then the - // correct one to rebuild with (both are fully determined by the algorithm parameters). - let rate = 1600 - ((PARAMS::SIZE as usize) << 1); - let (keccak, kdf_key_type, kdf_security_strength, kdf_entropy) = - deserialize_sha3_family_state(input, PARAMS::STATE_TAG, rate)?; - - Ok(SHA3 { - _params: std::marker::PhantomData, - keccak, - kdf_key_type, - kdf_security_strength, - kdf_entropy, - }) - } -} - /// SHA3 is allowed to be used as a KDF in the form HASH(X) as per NIST SP 800-56C. impl KDF for SHA3 { /// Returns a [KeyMaterial]. @@ -317,3 +274,46 @@ impl KDF for SHA3 { SecurityStrength::from_bytes(PARAMS::OUTPUT_LEN / 2) } } + +impl Suspendable for SHA3 { + fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_SHA3_STATE_LEN]; + + // insert the version tag + let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = + add_lib_ver(&mut out_to_return).try_into().unwrap(); + + serialize_sha3_family_state( + out, + PARAMS::STATE_TAG, + &self.keccak, + self.kdf_key_type, + self.kdf_security_strength, + self.kdf_entropy, + ); + + out_to_return + } + + fn from_suspended( + serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], + ) -> Result { + // check the version tag. At the moment, we have no not_before version to specify. + let input: &[u8; SHA3_FAMILY_STATE_LEN] = + check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + // The variant tag rejects states from any other SHA3/SHAKE variant; the rate is then the + // correct one to rebuild with (both are fully determined by the algorithm parameters). + let rate = 1600 - ((PARAMS::SIZE as usize) << 1); + let (keccak, kdf_key_type, kdf_security_strength, kdf_entropy) = + deserialize_sha3_family_state(input, PARAMS::STATE_TAG, rate)?; + + Ok(SHA3 { + _params: std::marker::PhantomData, + keccak, + kdf_key_type, + kdf_security_strength, + kdf_entropy, + }) + } +} diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index dd05d52..31868ec 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -1,13 +1,13 @@ use crate::SHAKEParams; use crate::keccak::{ - KeccakDigest, KeccakSize, SHA3_FAMILY_STATE_LEN, SHA3_SERIALIZED_STATE_LEN, + KeccakDigest, KeccakSize, SHA3_FAMILY_STATE_LEN, SUSPENDED_SHA3_STATE_LEN, deserialize_sha3_family_state, serialize_sha3_family_state, }; use bouncycastle_core::errors::{HashError, KDFError, SerializedStateError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Algorithm, KDF, SecurityStrength, SerializableState, XOF}; +use bouncycastle_core::traits::{Algorithm, KDF, SecurityStrength, Suspendable, XOF}; use bouncycastle_utils::{max, min}; /// Note: FIPS 202 section 7 states: @@ -142,9 +142,9 @@ impl SHAKE { } } -impl SerializableState for SHAKE { - fn serialize_state(self) -> [u8; SHA3_SERIALIZED_STATE_LEN] { - let mut out_to_return = [0u8; SHA3_SERIALIZED_STATE_LEN]; +impl Suspendable for SHAKE { + fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] { + let mut out_to_return = [0u8; SUSPENDED_SHA3_STATE_LEN]; // insert the version tag let out: &mut [u8; SHA3_FAMILY_STATE_LEN] = @@ -162,8 +162,8 @@ impl SerializableState for SHAKE out_to_return } - fn from_serialized_state( - serialized_state: [u8; SHA3_SERIALIZED_STATE_LEN], + fn from_suspended( + serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], ) -> Result { // check the version tag. At the moment, we have no not_before version to specify. let input: &[u8; SHA3_FAMILY_STATE_LEN] = diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index ffff758..3bbe67b 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -9,7 +9,9 @@ mod sha3_tests { use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::hash::TestFrameworkHash; use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; - use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE256}; + use bouncycastle_sha3::{ + SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE256, SUSPENDED_SHA3_STATE_LEN, + }; #[test] fn test_constants() { @@ -395,29 +397,28 @@ mod sha3_tests { } #[test] - fn test_serializable_state() { + fn serializable_state() { use bouncycastle_core::errors::SerializedStateError; - use bouncycastle_core::traits::SerializableState; - use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; + use bouncycastle_core::traits::Suspendable; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; let str = "Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one SHA3 variant. - fn round_trip + Clone>( - mut hash: H, - input: &[u8], - ) { + fn round_trip + Clone>(mut hash: H, input: &[u8]) { hash.do_update(input); // do the default trait-conformance tests - TestFrameworkSerializableState::new().test(&hash); + TestFrameworkSuspendableState::new().test(&hash); // serialize the in-progress state, then finish the original - let serialized_state = hash.clone().serialize_state(); + let serialized_state = hash.clone().suspend(); + assert_eq!(serialized_state.len(), SUSPENDED_SHA3_STATE_LEN); + let expected = hash.do_final(); // rebuild from the serialized state and confirm it produces the same digest - let from_state = H::from_serialized_state(serialized_state).unwrap(); + let from_state = H::from_suspended(serialized_state).unwrap(); assert_eq!(expected, from_state.do_final()); // a corrupt `squeezing` byte (last byte of the keccak state) must be rejected. @@ -425,7 +426,7 @@ mod sha3_tests { // + bits_in_queue(8) + squeezing(1) let mut busted = serialized_state; busted[3 + 1 + 400] = 42; - match H::from_serialized_state(busted) { + match H::from_suspended(busted) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } @@ -441,12 +442,12 @@ mod sha3_tests { // is only caught by the tag, not the rate -- it is the exact bug the tag exists to prevent. let mut sha3_256 = SHA3_256::new(); sha3_256.do_update(str.as_bytes()); - let serialized_256 = sha3_256.serialize_state(); - match SHA3_512::from_serialized_state(serialized_256) { + let serialized_256 = sha3_256.suspend(); + match SHA3_512::from_suspended(serialized_256) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHA3-256 state into SHA3-512"), } - match SHAKE256::from_serialized_state(serialized_256) { + match SHAKE256::from_suspended(serialized_256) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHA3-256 state into SHAKE256"), } diff --git a/crypto/sha3/tests/shake_tests.rs b/crypto/sha3/tests/shake_tests.rs index 1e43b39..6fb8612 100644 --- a/crypto/sha3/tests/shake_tests.rs +++ b/crypto/sha3/tests/shake_tests.rs @@ -226,7 +226,7 @@ mod shake_tests { } #[test] - fn test_security_strength() { + fn security_strength() { assert_eq!(KDF::max_security_strength(&SHAKE128::default()), SecurityStrength::_128bit); assert_eq!(XOF::max_security_strength(&SHAKE128::default()), SecurityStrength::_128bit); assert_eq!(KDF::max_security_strength(&SHAKE256::default()), SecurityStrength::_256bit); @@ -239,29 +239,26 @@ mod shake_tests { } #[test] - fn test_serializable_state() { + fn suspendable_state() { use bouncycastle_core::errors::SerializedStateError; - use bouncycastle_core::traits::SerializableState; - use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; + use bouncycastle_core::traits::Suspendable; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; let str = "Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one SHAKE variant. - fn round_trip + Clone>( - mut shake: X, - input: &[u8], - ) { + fn round_trip + Clone>(mut shake: X, input: &[u8]) { shake.absorb(input); // do the default trait-conformance tests - TestFrameworkSerializableState::new().test(&shake); + TestFrameworkSuspendableState::new().test(&shake); // serialize the in-progress (absorbing) state, then squeeze from the original - let serialized_state = shake.clone().serialize_state(); + let serialized_state = shake.clone().suspend(); let expected = shake.squeeze(64); // rebuild from the serialized state and confirm it produces the same output - let mut from_state = X::from_serialized_state(serialized_state).unwrap(); + let mut from_state = X::from_suspended(serialized_state).unwrap(); assert_eq!(expected, from_state.squeeze(64)); // a corrupt `squeezing` byte (last byte of the keccak state) must be rejected. @@ -269,7 +266,7 @@ mod shake_tests { // + bits_in_queue(8) + squeezing(1) let mut busted = serialized_state; busted[3 + 1 + 400] = 42; - match X::from_serialized_state(busted) { + match X::from_suspended(busted) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } @@ -283,16 +280,16 @@ mod shake_tests { // (1088), so only the variant tag distinguishes them. let mut shake128 = SHAKE128::new(); shake128.absorb(str.as_bytes()); - let serialized_128 = shake128.serialize_state(); - match SHAKE256::from_serialized_state(serialized_128) { + let serialized_128 = shake128.suspend(); + match SHAKE256::from_suspended(serialized_128) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into SHAKE256"), } let mut shake256 = SHAKE256::new(); shake256.absorb(str.as_bytes()); - let serialized_256 = shake256.serialize_state(); - match SHA3_256::from_serialized_state(serialized_256) { + let serialized_256 = shake256.suspend(); + match SHA3_256::from_suspended(serialized_256) { Err(SerializedStateError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE256 state into SHA3-256"), } From 1dacaa27a5801512f2cf1a28ee08515fde38cef3 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 7 Jul 2026 21:33:27 -0500 Subject: [PATCH 12/26] rustfmt --- crypto/core/src/traits.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index f05cb43..6b4cddd 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -517,9 +517,7 @@ pub trait Suspendable: Sized { /// (including rejecting serializations from a future version of the library). /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its /// deserializer should reject serialized states from that version or older. - fn from_suspended( - state: [u8; SERIALIZED_STATE_LEN], - ) -> Result; + fn from_suspended(state: [u8; SERIALIZED_STATE_LEN]) -> Result; } /// Similar to [Suspendable] in that it allows a stateful object to suspend its operation by From 07889fcc9cc260bebc1c468929752b3c78ba822f Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 8 Jul 2026 18:33:18 -0500 Subject: [PATCH 13/26] HMAC now impl's Drop like it should. --- .../src/suspendable_state.rs | 10 +-- crypto/core/src/errors.rs | 2 +- crypto/core/src/key_material.rs | 8 +-- crypto/core/src/serializable_state.rs | 12 ++-- crypto/core/src/traits.rs | 12 ++-- crypto/factory/src/hash_factory.rs | 8 ++- crypto/hkdf/src/lib.rs | 23 +++++-- crypto/hkdf/tests/hkdf_tests.rs | 44 ++++++++++++- crypto/hmac/src/lib.rs | 41 +++++++++--- crypto/hmac/tests/hmac_tests.rs | 62 +++++++++++++------ crypto/mldsa-lowmemory/src/mldsa.rs | 4 +- crypto/mldsa-lowmemory/tests/mldsa_tests.rs | 6 +- crypto/mldsa/src/mldsa.rs | 4 +- crypto/mldsa/tests/mldsa_tests.rs | 6 +- crypto/sha2/src/lib.rs | 16 ----- crypto/sha2/src/sha256.rs | 13 ++-- crypto/sha2/src/sha512.rs | 13 ++-- crypto/sha2/tests/sha2_tests.rs | 6 +- crypto/sha3/src/keccak.rs | 12 ++-- crypto/sha3/src/lib.rs | 17 ----- crypto/sha3/src/sha3.rs | 11 +++- crypto/sha3/src/shake.rs | 4 +- crypto/sha3/tests/sha3_tests.rs | 8 +-- crypto/sha3/tests/shake_tests.rs | 20 ++++-- 24 files changed, 230 insertions(+), 132 deletions(-) diff --git a/crypto/core-test-framework/src/suspendable_state.rs b/crypto/core-test-framework/src/suspendable_state.rs index 7be7c21..c432aa8 100644 --- a/crypto/core-test-framework/src/suspendable_state.rs +++ b/crypto/core-test-framework/src/suspendable_state.rs @@ -1,4 +1,4 @@ -use bouncycastle_core::errors::SerializedStateError; +use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::serializable_state::{LIB_VERSION, SemVer}; use bouncycastle_core::traits::{Suspendable, SuspendableKeyed}; @@ -39,7 +39,7 @@ impl TestFrameworkSuspendableState { let mut ver0_serialized_state = serialized_state.clone(); ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); match S::from_suspended(ver0_serialized_state) { - Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + Err(SuspendableError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") } @@ -52,7 +52,7 @@ impl TestFrameworkSuspendableState { futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); match S::from_suspended(futurever_serialized_state) { - Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + Err(SuspendableError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") } @@ -101,7 +101,7 @@ impl TestFrameworkSuspendableKeyedState { let mut ver0_serialized_state = serialized_state.clone(); ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]); match S::from_suspended(ver0_serialized_state, key) { - Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + Err(SuspendableError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") } @@ -114,7 +114,7 @@ impl TestFrameworkSuspendableKeyedState { futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); match S::from_suspended(futurever_serialized_state, key) { - Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + Err(SuspendableError::IncompatibleVersion) => { /* good */ } _ => { panic!("Expected IncompatibleVersion error") } diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index 0241ae7..806380e 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -73,7 +73,7 @@ pub enum RNGError { } #[derive(Debug)] -pub enum SerializedStateError { +pub enum SuspendableError { /// The serialized state was produced by a library version incompatible with this one. IncompatibleVersion, /// The serialized state is malformed or corrupt. diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index c730c26..ece984c 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -50,7 +50,7 @@ //! //! See [do_hazardous_operations] for documentation and sample code. -use crate::errors::{KeyMaterialError, SerializedStateError}; +use crate::errors::{KeyMaterialError, SuspendableError}; use crate::traits::{RNG, Secret, SecurityStrength}; use bouncycastle_utils::{ct, min}; @@ -255,9 +255,9 @@ pub enum KeyType { } impl TryFrom for KeyType { - type Error = SerializedStateError; + type Error = SuspendableError; - /// Inverse of `self as u8`; rejects unrecognized discriminants with [SerializedStateError::InvalidData]. + /// Inverse of `self as u8`; rejects unrecognized discriminants with [SuspendableError::InvalidData]. fn try_from(value: u8) -> Result { Ok(match value { 0 => Self::Zeroized, @@ -266,7 +266,7 @@ impl TryFrom for KeyType { 3 => Self::Seed, 4 => Self::MACKey, 5 => Self::SymmetricCipherKey, - _ => return Err(SerializedStateError::InvalidData), + _ => return Err(SuspendableError::InvalidData), }) } } diff --git a/crypto/core/src/serializable_state.rs b/crypto/core/src/serializable_state.rs index 420484a..615cfc3 100644 --- a/crypto/core/src/serializable_state.rs +++ b/crypto/core/src/serializable_state.rs @@ -1,6 +1,6 @@ //! Helper functions for standardizing serialization and deserialization of stateful objects. -use crate::errors::SerializedStateError; +use crate::errors::SuspendableError; /// A semantic library version, ordered by `major`, then `minor`, then `patch`. /// @@ -78,7 +78,7 @@ pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN] /// /// The state_out array must have length at least SERIALIZED_LEN - 3. /// -/// Returns the number of bytes written to state_out, or a [SerializedStateError::IncompatibleVersion] if the +/// Returns the number of bytes written to state_out, or a [SuspendableError::IncompatibleVersion] if the /// serialized state contains a version header earlier than the specified `not_before` version. /// /// Note that for testability, this will always reject if the serialized state contains a version tag @@ -88,23 +88,23 @@ pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN] pub fn check_lib_ver( state: &[u8; SERIALIZED_LEN], not_before: Option<[u8; 3]>, -) -> Result<&[u8], SerializedStateError> { +) -> Result<&[u8], SuspendableError> { let ver_bytes: [u8; 3] = state[..3].try_into().unwrap(); let ver = SemVer::from(ver_bytes); let not_before = SemVer::from(not_before.unwrap_or([0, 0, 0])); if ver < not_before { - return Err(SerializedStateError::IncompatibleVersion); + return Err(SuspendableError::IncompatibleVersion); }; // Nothing is ever compatible with [0,0,0] if ver == SemVer::from([0, 0, 0]) { - return Err(SerializedStateError::IncompatibleVersion); + return Err(SuspendableError::IncompatibleVersion); }; // Also not compatible with future versions. if ver > LIB_VERSION { - return Err(SerializedStateError::IncompatibleVersion); + return Err(SuspendableError::IncompatibleVersion); } Ok(&state[3..]) diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 6b4cddd..3086630 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -17,7 +17,7 @@ pub trait Algorithm { const MAX_SECURITY_STRENGTH: SecurityStrength; } -pub trait Hash: Default { +pub trait Hash: Algorithm + Default { /// The size of the internal block in bits -- needed by functions such as HMAC to compute security parameters. fn block_bitlen(&self) -> usize; @@ -399,9 +399,9 @@ pub enum SecurityStrength { } impl TryFrom for SecurityStrength { - type Error = SerializedStateError; + type Error = SuspendableError; - /// Inverse of `self as u8`; rejects unrecognized discriminants with [SerializedStateError::InvalidData]. + /// Inverse of `self as u8`; rejects unrecognized discriminants with [SuspendableError::InvalidData]. fn try_from(value: u8) -> Result { Ok(match value { 0 => Self::None, @@ -409,7 +409,7 @@ impl TryFrom for SecurityStrength { 2 => Self::_128bit, 3 => Self::_192bit, 4 => Self::_256bit, - _ => return Err(SerializedStateError::InvalidData), + _ => return Err(SuspendableError::InvalidData), }) } } @@ -517,7 +517,7 @@ pub trait Suspendable: Sized { /// (including rejecting serializations from a future version of the library). /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its /// deserializer should reject serialized states from that version or older. - fn from_suspended(state: [u8; SERIALIZED_STATE_LEN]) -> Result; + fn from_suspended(state: [u8; SERIALIZED_STATE_LEN]) -> Result; } /// Similar to [Suspendable] in that it allows a stateful object to suspend its operation by @@ -548,7 +548,7 @@ pub trait SuspendableKeyed: Sized { fn from_suspended( state: [u8; SERIALIZED_STATE_LEN], key: &Self::Key, - ) -> Result; + ) -> Result; } /// Pre-Hashed Signer is an extension to [Signer] that adds functionality specific to signature diff --git a/crypto/factory/src/hash_factory.rs b/crypto/factory/src/hash_factory.rs index 052f4b5..34793de 100644 --- a/crypto/factory/src/hash_factory.rs +++ b/crypto/factory/src/hash_factory.rs @@ -29,7 +29,7 @@ use crate::{AlgorithmFactory, FactoryError}; use crate::{DEFAULT, DEFAULT_128_BIT, DEFAULT_256_BIT}; use bouncycastle_core::errors::HashError; -use bouncycastle_core::traits::{Hash, SecurityStrength}; +use bouncycastle_core::traits::{Algorithm, Hash, SecurityStrength}; use bouncycastle_sha2 as sha2; use bouncycastle_sha2::{SHA224_NAME, SHA256_NAME, SHA384_NAME, SHA512_NAME}; use bouncycastle_sha3 as sha3; @@ -83,6 +83,12 @@ impl AlgorithmFactory for HashFactory { } } +// TODO -- this does't work. Perhaps Algorithm needs to be re-worked so that these are functions instead? +impl Algorithm for HashFactory { + const ALG_NAME: &'static str = "TODO"; + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::None; +} + impl Hash for HashFactory { fn block_bitlen(&self) -> usize { match self { diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 20773b9..5c436d0 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -192,7 +192,7 @@ #![forbid(unsafe_code)] -use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError, SerializedStateError}; +use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial0, KeyMaterial512, KeyMaterialTrait, KeyType, @@ -252,15 +252,15 @@ enum HkdfStates { } impl TryFrom for HkdfStates { - type Error = SerializedStateError; + type Error = SuspendableError; - /// Inverse of `self as u8`; rejects unrecognized discriminants with [SerializedStateError::InvalidData]. + /// Inverse of `self as u8`; rejects unrecognized discriminants with [SuspendableError::InvalidData]. fn try_from(value: u8) -> Result { Ok(match value { 0 => Self::Uninitialized, 1 => Self::Initialized, 2 => Self::TakingAdditionalInfo, - _ => return Err(SerializedStateError::InvalidData), + _ => return Err(SuspendableError::InvalidData), }) } } @@ -830,7 +830,7 @@ macro_rules! impl_suspendable_keyed_state_for_hkdf { fn from_suspended( state: [u8; $serialized_hkdf_len], salt: &Self::Key, - ) -> Result { + ) -> Result { // Rebuild the salt-keyed HMAC (first in the payload) by re-supplying the salt. // This double-dips on HMAC checking the version tag before going any further. @@ -843,10 +843,21 @@ macro_rules! impl_suspendable_keyed_state_for_hkdf { state[..$serialized_hmac_len].try_into().unwrap(), salt, )?), - _ => return Err(SerializedStateError::InvalidData), + _ => return Err(SuspendableError::InvalidData), }; let hkdf_state = HkdfStates::try_from(state[$serialized_hmac_len + 1])?; + + // check that the hkdf_state aligns with the presence of an hmac + if + // an hmac object should not be present in the init state. + (hmac.is_some() && hkdf_state == HkdfStates::Uninitialized) || + // any other state must have an hmac object. + (hmac.is_none() && hkdf_state != HkdfStates::Uninitialized) + { + return Err(SuspendableError::InvalidData); + } + let entropy = u64::from_le_bytes( state[$serialized_hmac_len + 2..$serialized_hmac_len + 10].try_into().unwrap(), ) as usize; diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index 7131943..448c222 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod hkdf_tests { - use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError}; + use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial0, KeyMaterial128, KeyMaterial256, KeyMaterial512, @@ -571,7 +571,8 @@ mod hkdf_tests { assert_eq!(okm_key.ref_to_bytes().len(), L); assert_eq!(okm_key.ref_to_bytes(), hex::decode(okm).unwrap()); } - Err(KDFError::KeyMaterialError(_)) => { /* some of the rfc5896 test vectors are in fact low entropy, so just skip */ + Err(KDFError::KeyMaterialError(_)) => { + /* some of the rfc5896 test vectors are in fact low entropy, so just skip */ } Err(_) => panic!("Should have returned a MACError::KeyMaterialError."), } @@ -689,7 +690,7 @@ mod hkdf_tests { // SP800-56Cr2 tcId 1 let mut salt = KeyMaterial::<128>::new(); // have to do it this way for it to accept a zeroized key - key_material::do_hazardous_operations(&mut salt, |salt|{ + key_material::do_hazardous_operations(&mut salt, |salt| { salt.set_bytes_as_type(&hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), KeyType::MACKey) }).unwrap(); @@ -789,5 +790,42 @@ mod hkdf_tests { round_trip::(&salt, part1, part2); round_trip::(&salt, part1, part2); + + // Test the guard for invalid states + // testing just on HKDF_SHA256 + + const UNINITIALIZED: u8 = 0; // HkdfStates::Uninitialized + const INITIALIZED: u8 = 1; // HkdfStates::Initialized + let present_idx = SUSPENDED_HKDF_SHA256_STATE_LEN - 11; + let state_idx = SUSPENDED_HKDF_SHA256_STATE_LEN - 10; + + // Case 1: no HMAC present but state claims Initialized -> reject. + // construct a valid, pre-init state: no HMAC (flag = 0), state = Uninitialized. + let valid_uninitialized = HKDF_SHA256::new().suspend(); + assert_eq!(valid_uninitialized[present_idx], 0); + assert_eq!(valid_uninitialized[state_idx], UNINITIALIZED); + + let mut corrupt = valid_uninitialized; + corrupt[state_idx] = INITIALIZED; + assert!(matches!( + HKDF_SHA256::from_suspended(corrupt, &salt), + Err(SuspendableError::InvalidData) + )); + + // Case 2: HMAC present but state claims Uninitialized -> reject. + // construct a valid, mid-extract state: HMAC present (flag = 1), state = Initialized. + let mut hkdf = HKDF_SHA256::new(); + hkdf.do_extract_init(&salt).unwrap(); + hkdf.do_extract_update_bytes(ikm).unwrap(); + let valid_initialized = hkdf.suspend(); + assert_eq!(valid_initialized[present_idx], 1); + assert_ne!(valid_initialized[state_idx], UNINITIALIZED); + + let mut corrupt = valid_initialized; + corrupt[state_idx] = UNINITIALIZED; + assert!(matches!( + HKDF_SHA256::from_suspended(corrupt, &salt), + Err(SuspendableError::InvalidData) + )); } } diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 043e3f0..cd59def 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -184,16 +184,17 @@ #![allow(incomplete_features)] // because at time of writing, generic_const_exprs is not a stable feature #![feature(generic_const_exprs)] -use bouncycastle_core::errors::{KeyMaterialError, MACError, SerializedStateError}; +use bouncycastle_core::errors::{KeyMaterialError, MACError, SuspendableError}; use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - Algorithm, Hash, MAC, SecurityStrength, Suspendable, SuspendableKeyed, + Algorithm, Hash, MAC, Secret, SecurityStrength, Suspendable, SuspendableKeyed, }; use bouncycastle_sha2::{ SHA224, SHA256, SHA384, SHA512, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN, }; use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512, SUSPENDED_SHA3_STATE_LEN}; use bouncycastle_utils::ct; +use core::fmt::{Debug, Display, Formatter}; /*** String constants ***/ /// @@ -288,6 +289,28 @@ pub struct HMAC Secret for HMAC {} + +impl Drop for HMAC { + fn drop(&mut self) { + self.key.fill(0); + self.key_len = 0; + } +} + +impl Debug for HMAC { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "HMAC-{} instance", HASH::ALG_NAME,) + } +} + +impl Display for HMAC { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "HMAC-{} instance", HASH::ALG_NAME,) + } +} + // See definitions in RFC 2104 Section 2. const IPAD_BYTE: u8 = 0x36; const OPAD_BYTE: u8 = 0x5C; @@ -391,13 +414,15 @@ impl HMAC { // invalid outer hashes. // TODO: rework this to be no_std friendly (ie no vec!) let mut ihash = vec![0u8; self.hasher.output_len()]; - self.hasher.do_final_out(&mut ihash); + // `HMAC` implements `Drop` (required by `Secret`), so we cannot move `self.hasher` out + // directly. Swap in a fresh default and consume the taken-out hasher instead. + core::mem::take(&mut self.hasher).do_final_out(&mut ihash); // ohash self.hasher = HASH::default(); self.pad_key_into_hasher(OPAD_BYTE); self.hasher.do_update(&ihash); - Ok(self.hasher.do_final_out(out)) + Ok(core::mem::take(&mut self.hasher).do_final_out(out)) } } @@ -515,16 +540,18 @@ impl< // bytes at from_serialized_state, so dynamic dispatch here is negligible. type Key = dyn KeyMaterialTrait; - fn suspend(self) -> [u8; HASH_STATE_LEN] { + fn suspend(mut self) -> [u8; HASH_STATE_LEN] { // The key is intentionally excluded; the resumable state is just the inner hasher, which // already carries the library version header from the hash's own SerializableState impl. - self.hasher.suspend() + // `HMAC` implements `Drop` (required by `Secret`), so move the hasher out via `mem::take` + // rather than a direct partial move. + core::mem::take(&mut self.hasher).suspend() } fn from_suspended( state: [u8; HASH_STATE_LEN], key: &Self::Key, - ) -> Result { + ) -> Result { // Rebuild the inner hasher (version-compatibility is validated by the hash's impl). let hasher = HASH::from_suspended(state)?; diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/hmac/tests/hmac_tests.rs index 0c5daa4..4921fef 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/hmac/tests/hmac_tests.rs @@ -59,8 +59,8 @@ mod hmac_tests { #[test] fn test_type_aliases() { let key = KeyMaterial512::from_bytes_as_type( - b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", - KeyType::MACKey).unwrap(); + b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + KeyType::MACKey).unwrap(); _ = HMAC::::new(&key).unwrap(); _ = HMAC_SHA224::new(&key).unwrap(); @@ -330,14 +330,14 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), - &hex::decode("7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea").unwrap() + &hex::decode("7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), - &hex::decode("6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a").unwrap() + &hex::decode("6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a").unwrap(), ); // RFC4231 Test Case 5 -- Test with a truncation of output to 128 bits. @@ -363,7 +363,7 @@ mod hmac_tests { // of SHA-384 and SHA-512) test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", - &hex::decode("3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1").unwrap() + &hex::decode("3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1").unwrap(), ); } @@ -411,14 +411,14 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), - &hex::decode("773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe").unwrap() + &hex::decode("773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), - &hex::decode("82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b").unwrap() + &hex::decode("82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b").unwrap(), ); // RFC4231 Test Case 5 -- Test with a truncation of output to 128 bits. @@ -435,14 +435,14 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"Test Using Larger Than Block-Size Key - Hash Key First", - &hex::decode("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54").unwrap() + &hex::decode("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54").unwrap(), ); // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", - &hex::decode("9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2").unwrap() + &hex::decode("9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2").unwrap(), ); } @@ -486,14 +486,14 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), - &hex::decode("88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27").unwrap() + &hex::decode("88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), - &hex::decode("3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb").unwrap() + &hex::decode("3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb").unwrap(), ); // RFC4231 Test Case 5 -- Test with a truncation of output to 128 bits. @@ -512,14 +512,14 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"Test Using Larger Than Block-Size Key - Hash Key First", - &hex::decode("4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952").unwrap() + &hex::decode("4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952").unwrap(), ); // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", - &hex::decode("6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e").unwrap() + &hex::decode("6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e").unwrap(), ); } @@ -564,14 +564,14 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), - &hex::decode("fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb").unwrap() + &hex::decode("fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), - &hex::decode("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd").unwrap() + &hex::decode("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd").unwrap(), ); // RFC4231 Test Case 5 -- Test with a truncation of output to 128 bits. @@ -590,21 +590,21 @@ mod hmac_tests { // bytes (= block-size of SHA-224 and SHA-256). test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"Test Using Larger Than Block-Size Key - Hash Key First", - &hex::decode("80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598").unwrap() + &hex::decode("80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598").unwrap(), ); // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", - &hex::decode("e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58").unwrap() + &hex::decode("e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58").unwrap(), ); } } #[test] fn suspendable_keyed_state() { - use bouncycastle_core::errors::SerializedStateError; + use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::serializable_state::LIB_VERSION; use bouncycastle_core::traits::SuspendableKeyed; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; @@ -651,7 +651,7 @@ mod hmac_tests { let mut busted = serialized_state; busted[..3].copy_from_slice(&[0, 0, 0]); match H::from_suspended(busted, key) { - Err(SerializedStateError::IncompatibleVersion) => { /* good */ } + Err(SuspendableError::IncompatibleVersion) => { /* good */ } _ => panic!("Expected IncompatibleVersion for a zeroed version header"), } } @@ -659,5 +659,29 @@ mod hmac_tests { round_trip(HMAC_SHA256::new(&key).unwrap(), &key, msg); round_trip(HMAC_SHA512::new(&key).unwrap(), &key, msg); round_trip(HMAC_SHA3_256::new(&key).unwrap(), &key, msg); + + // test suspend / resume with a key larger than block size + let long_key = + KeyMaterial::<200>::from_bytes_as_type(&DUMMY_SEED_512[..200], KeyType::MACKey) + .unwrap(); + round_trip(HMAC_SHA256::new(&long_key).unwrap(), &long_key, msg); + } + + /// Tests that no private data is displayed + #[test] + fn test_display() { + let key = KeyMaterial256::from_bytes_as_type( + &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), + KeyType::MACKey, + ) + .unwrap(); + let hmac = HMAC_SHA256::new(&key).unwrap(); + + // test fmt + let fmt_str = format!("{}", &hmac); + assert_eq!(fmt_str, "HMAC-SHA256 instance"); + + // test debug + assert_eq!(format!("{:?}", &hmac), "HMAC-SHA256 instance"); } } diff --git a/crypto/mldsa-lowmemory/src/mldsa.rs b/crypto/mldsa-lowmemory/src/mldsa.rs index 77254a3..843a858 100644 --- a/crypto/mldsa-lowmemory/src/mldsa.rs +++ b/crypto/mldsa-lowmemory/src/mldsa.rs @@ -397,7 +397,7 @@ use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, }; -use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; +use bouncycastle_core::errors::{RNGError, SignatureError, SuspendableError}; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{ Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, Suspendable, XOF, @@ -1953,7 +1953,7 @@ impl Suspendable for MuBuilder { fn from_suspended( serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], - ) -> Result { + ) -> Result { Ok(MuBuilder { h: H::from_suspended(serialized_state)? }) } } diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index 27e21c6..c195e63 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -2,7 +2,7 @@ #[cfg(test)] mod mldsa_tests { use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; - use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; + use bouncycastle_core::errors::{RNGError, SignatureError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ @@ -875,7 +875,7 @@ mod mldsa_tests { let mut busted = serialized_state; busted[3 + 1 + 400] = 42; match MuBuilder::from_suspended(busted) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } } @@ -894,7 +894,7 @@ mod mldsa_tests { let serialized_128 = shake128.suspend(); match MuBuilder::from_suspended(serialized_128) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into a MuBuilder"), } } diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index 71a76eb..0869c44 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -485,7 +485,7 @@ use crate::{ MLDSA44PrivateKey, MLDSA44PublicKey, MLDSA65PrivateKey, MLDSA65PublicKey, MLDSA87PrivateKey, MLDSA87PublicKey, MLDSAPrivateKeyExpanded, MLDSAPublicKeyExpanded, }; -use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; +use bouncycastle_core::errors::{RNGError, SignatureError, SuspendableError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, Suspendable, XOF, @@ -2275,7 +2275,7 @@ impl Suspendable for MuBuilder { fn from_suspended( serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], - ) -> Result { + ) -> Result { Ok(MuBuilder { h: H::from_suspended(serialized_state)? }) } } diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index adc5c18..d46bf98 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -2,7 +2,7 @@ #[cfg(test)] mod mldsa_tests { use crate::{MLDSA44_KAT1, MLDSA65_KAT1, MLDSA87_KAT1}; - use bouncycastle_core::errors::{RNGError, SerializedStateError, SignatureError}; + use bouncycastle_core::errors::{RNGError, SignatureError, SuspendableError}; use bouncycastle_core::key_material::{ KeyMaterial256, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -1061,7 +1061,7 @@ mod mldsa_tests { let mut busted = serialized_state; busted[3 + 1 + 400] = 42; match MuBuilder::from_suspended(busted) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } } @@ -1080,7 +1080,7 @@ mod mldsa_tests { let serialized_128 = shake128.suspend(); match MuBuilder::from_suspended(serialized_128) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into a MuBuilder"), } } diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index 687817e..fb8d933 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -95,10 +95,6 @@ pub type SHA512 = SHA512Internal; trait SHA2Params: HashAlgParams {} -impl Algorithm for SHA224 { - const ALG_NAME: &'static str = SHA224_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; -} impl HashAlgParams for SHA224 { const OUTPUT_LEN: usize = 28; const BLOCK_LEN: usize = 64; @@ -115,10 +111,6 @@ impl HashAlgParams for SHA224Params { } impl SHA2Params for SHA224Params {} -impl Algorithm for SHA256 { - const ALG_NAME: &'static str = SHA256_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} impl HashAlgParams for SHA256 { const OUTPUT_LEN: usize = 32; const BLOCK_LEN: usize = 64; @@ -135,10 +127,6 @@ impl HashAlgParams for SHA256Params { } impl SHA2Params for SHA256Params {} -impl Algorithm for SHA384 { - const ALG_NAME: &'static str = SHA384_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; -} impl HashAlgParams for SHA384 { const OUTPUT_LEN: usize = 48; const BLOCK_LEN: usize = 128; @@ -157,10 +145,6 @@ impl SHA2Params for SHA384Params {} #[derive(Clone)] pub struct SHA512Params; -impl Algorithm for SHA512 { - const ALG_NAME: &'static str = SHA512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; -} impl HashAlgParams for SHA512 { const OUTPUT_LEN: usize = 64; const BLOCK_LEN: usize = 128; diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index 12bbdcd..26fd556 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -1,7 +1,7 @@ use crate::SHA2Params; -use bouncycastle_core::errors::{HashError, SerializedStateError}; +use bouncycastle_core::errors::{HashError, SuspendableError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Hash, SecurityStrength, Suspendable}; +use bouncycastle_core::traits::{Algorithm, Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; @@ -178,6 +178,11 @@ impl Default for SHA256Internal { } } +impl Algorithm for SHA256Internal { + const ALG_NAME: &'static str = PARAMS::ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = PARAMS::MAX_SECURITY_STRENGTH; +} + impl Hash for SHA256Internal { /// As per FIPS 180-4 Figure 1 fn block_bitlen(&self) -> usize { @@ -338,7 +343,7 @@ impl Suspendable for SHA256Inter fn from_suspended( serialized_state: [u8; SUSPENDED_SHA256_STATE_LEN], - ) -> Result { + ) -> Result { debug_assert_eq!(SUSPENDED_SHA256_STATE_LEN, 108); // check the version tag @@ -362,7 +367,7 @@ impl Suspendable for SHA256Inter // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 64 let x_buf_off: usize = input[104] as usize; if x_buf_off >= 64 { - return Err(SerializedStateError::InvalidData); + return Err(SuspendableError::InvalidData); } // Construct the object diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 89c4af2..4d2a7f4 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -1,7 +1,7 @@ use crate::SHA2Params; -use bouncycastle_core::errors::{HashError, SerializedStateError}; +use bouncycastle_core::errors::{HashError, SuspendableError}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Hash, SecurityStrength, Suspendable}; +use bouncycastle_core::traits::{Algorithm, Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; @@ -193,6 +193,11 @@ impl Default for SHA512Internal { } } +impl Algorithm for SHA512Internal { + const ALG_NAME: &'static str = PARAMS::ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = PARAMS::MAX_SECURITY_STRENGTH; +} + impl Hash for SHA512Internal { /// As per FIPS 180-4 Figure 1 fn block_bitlen(&self) -> usize { @@ -353,7 +358,7 @@ impl Suspendable for SHA512Inter fn from_suspended( serialized_state: [u8; SUSPENDED_SHA512_STATE_LEN], - ) -> Result { + ) -> Result { // check the version tag // At the moment, we have no not_before version to specify. let input: &[u8; 201] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); @@ -375,7 +380,7 @@ impl Suspendable for SHA512Inter // in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 128 let x_buf_off: usize = input[200] as usize; if x_buf_off >= 128 { - return Err(SerializedStateError::InvalidData); + return Err(SuspendableError::InvalidData); } // Construct the object diff --git a/crypto/sha2/tests/sha2_tests.rs b/crypto/sha2/tests/sha2_tests.rs index d6f2103..38b4e2f 100644 --- a/crypto/sha2/tests/sha2_tests.rs +++ b/crypto/sha2/tests/sha2_tests.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod sha2_tests { - use bouncycastle_core::errors::SerializedStateError; + use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::traits::{Algorithm, Hash, HashAlgParams, SecurityStrength}; use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::hash::TestFrameworkHash; @@ -122,7 +122,7 @@ mod sha2_tests { let mut busted_state = serialized_state.clone(); busted_state[3 + 104] = 65; match SHA256::from_suspended(busted_state) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error"), } @@ -150,7 +150,7 @@ mod sha2_tests { let mut busted_state = serialized_state.clone(); busted_state[3 + 200] = 129; match SHA512::from_suspended(busted_state) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error"), } } diff --git a/crypto/sha3/src/keccak.rs b/crypto/sha3/src/keccak.rs index c887e16..fd63544 100644 --- a/crypto/sha3/src/keccak.rs +++ b/crypto/sha3/src/keccak.rs @@ -1,4 +1,4 @@ -use bouncycastle_core::errors::{HashError, SerializedStateError}; +use bouncycastle_core::errors::{HashError, SuspendableError}; use bouncycastle_core::key_material::KeyType; use bouncycastle_core::traits::SecurityStrength; @@ -405,7 +405,7 @@ impl KeccakDigest { fn from_serialized_state( input: &[u8; KECCAK_SERIALIZED_LEN], rate: usize, - ) -> Result { + ) -> Result { // state.buf: [u64; 25] let mut buf = [0u64; 25]; for i in 0..25 { @@ -419,14 +419,14 @@ impl KeccakDigest { // bytes, well within data_queue's 192-byte capacity). let bits_in_queue = u64::from_le_bytes(input[392..400].try_into().unwrap()) as usize; if bits_in_queue > rate { - return Err(SerializedStateError::InvalidData); + return Err(SuspendableError::InvalidData); } // squeezing: bool let squeezing = match input[400] { 0 => false, 1 => true, - _ => return Err(SerializedStateError::InvalidData), + _ => return Err(SuspendableError::InvalidData), }; Ok(Self { state: KeccakState { buf, rate }, data_queue, rate, bits_in_queue, squeezing }) @@ -465,9 +465,9 @@ pub(crate) fn deserialize_sha3_family_state( input: &[u8; SHA3_FAMILY_STATE_LEN], expected_variant_tag: u8, rate: usize, -) -> Result<(KeccakDigest, KeyType, SecurityStrength, usize), SerializedStateError> { +) -> Result<(KeccakDigest, KeyType, SecurityStrength, usize), SuspendableError> { if input[0] != expected_variant_tag { - return Err(SerializedStateError::InvalidData); + return Err(SuspendableError::InvalidData); } let keccak_in: &[u8; KECCAK_SERIALIZED_LEN] = diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 902c8ed..16262dd 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -183,11 +183,6 @@ trait SHA3Params: HashAlgParams { const STATE_TAG: u8; } -// TODO: more elegant to macro this? -impl Algorithm for SHA3_224 { - const ALG_NAME: &'static str = SHA3_224_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; -} impl HashAlgParams for SHA3_224 { const OUTPUT_LEN: usize = 28; // const BLOCK_LEN: usize = 64; @@ -209,10 +204,6 @@ impl SHA3Params for SHA3_224Params { const STATE_TAG: u8 = 1; } -impl Algorithm for SHA3_256 { - const ALG_NAME: &'static str = SHA3_256_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; -} impl HashAlgParams for SHA3_256 { const OUTPUT_LEN: usize = 32; // const BLOCK_LEN: usize = 64; @@ -236,10 +227,6 @@ impl SHA3Params for SHA3_256Params { #[derive(Clone)] pub struct SHA3_384Params; -impl Algorithm for SHA3_384 { - const ALG_NAME: &'static str = SHA3_384_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; -} impl HashAlgParams for SHA3_384 { const OUTPUT_LEN: usize = 48; // const BLOCK_LEN: usize = 128; @@ -261,10 +248,6 @@ impl SHA3Params for SHA3_384Params { #[derive(Clone)] pub struct SHA3_512Params; -impl Algorithm for SHA3_512 { - const ALG_NAME: &'static str = SHA3_512_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; -} impl HashAlgParams for SHA3_512 { const OUTPUT_LEN: usize = 64; // const BLOCK_LEN: usize = 128; diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 1bfde8a..42933e6 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -3,11 +3,11 @@ use crate::keccak::{ KeccakDigest, SHA3_FAMILY_STATE_LEN, SUSPENDED_SHA3_STATE_LEN, deserialize_sha3_family_state, serialize_sha3_family_state, }; -use bouncycastle_core::errors::{HashError, KDFError, SerializedStateError}; +use bouncycastle_core::errors::{HashError, KDFError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; -use bouncycastle_core::traits::{Hash, KDF, SecurityStrength, Suspendable}; +use bouncycastle_core::traits::{Algorithm, Hash, KDF, SecurityStrength, Suspendable}; use bouncycastle_utils::{max, min}; #[derive(Clone)] @@ -124,6 +124,11 @@ impl Default for SHA3 { } } +impl Algorithm for SHA3 { + const ALG_NAME: &'static str = PARAMS::ALG_NAME; + const MAX_SECURITY_STRENGTH: SecurityStrength = PARAMS::MAX_SECURITY_STRENGTH; +} + impl Hash for SHA3 { /// As per FIPS 202 Table 3. /// Required, for example, to compute the pad lengths in HMAC. @@ -297,7 +302,7 @@ impl Suspendable for SHA3 fn from_suspended( serialized_state: [u8; SUSPENDED_SHA3_STATE_LEN], - ) -> Result { + ) -> Result { // check the version tag. At the moment, we have no not_before version to specify. let input: &[u8; SHA3_FAMILY_STATE_LEN] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index 31868ec..e1b3aef 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -3,7 +3,7 @@ use crate::keccak::{ KeccakDigest, KeccakSize, SHA3_FAMILY_STATE_LEN, SUSPENDED_SHA3_STATE_LEN, deserialize_sha3_family_state, serialize_sha3_family_state, }; -use bouncycastle_core::errors::{HashError, KDFError, SerializedStateError}; +use bouncycastle_core::errors::{HashError, KDFError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; @@ -164,7 +164,7 @@ impl Suspendable for SHAKE Result { + ) -> Result { // check the version tag. At the moment, we have no not_before version to specify. let input: &[u8; SHA3_FAMILY_STATE_LEN] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index 3bbe67b..535e2dd 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -398,7 +398,7 @@ mod sha3_tests { #[test] fn serializable_state() { - use bouncycastle_core::errors::SerializedStateError; + use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::traits::Suspendable; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; @@ -427,7 +427,7 @@ mod sha3_tests { let mut busted = serialized_state; busted[3 + 1 + 400] = 42; match H::from_suspended(busted) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } } @@ -444,11 +444,11 @@ mod sha3_tests { sha3_256.do_update(str.as_bytes()); let serialized_256 = sha3_256.suspend(); match SHA3_512::from_suspended(serialized_256) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHA3-256 state into SHA3-512"), } match SHAKE256::from_suspended(serialized_256) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHA3-256 state into SHAKE256"), } } diff --git a/crypto/sha3/tests/shake_tests.rs b/crypto/sha3/tests/shake_tests.rs index 6fb8612..0140874 100644 --- a/crypto/sha3/tests/shake_tests.rs +++ b/crypto/sha3/tests/shake_tests.rs @@ -240,7 +240,7 @@ mod shake_tests { #[test] fn suspendable_state() { - use bouncycastle_core::errors::SerializedStateError; + use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::traits::Suspendable; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableState; @@ -253,7 +253,17 @@ mod shake_tests { // do the default trait-conformance tests TestFrameworkSuspendableState::new().test(&shake); - // serialize the in-progress (absorbing) state, then squeeze from the original + // Test #1 + // serialize the in-progress (absorbing) state, then squeeze from the original and compare + let serialized_state = shake.clone().suspend(); + let expected = shake.squeeze(64); + + // rebuild from the serialized state and confirm it produces the same output + let mut from_state = X::from_suspended(serialized_state).unwrap(); + assert_eq!(expected, from_state.squeeze(64)); + + // Test #2 + // serialize the in-progress (squeezing) state, then squeeze more from the original and compare let serialized_state = shake.clone().suspend(); let expected = shake.squeeze(64); @@ -267,7 +277,7 @@ mod shake_tests { let mut busted = serialized_state; busted[3 + 1 + 400] = 42; match X::from_suspended(busted) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error for a corrupt squeezing byte"), } } @@ -282,7 +292,7 @@ mod shake_tests { shake128.absorb(str.as_bytes()); let serialized_128 = shake128.suspend(); match SHAKE256::from_suspended(serialized_128) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE128 state into SHAKE256"), } @@ -290,7 +300,7 @@ mod shake_tests { shake256.absorb(str.as_bytes()); let serialized_256 = shake256.suspend(); match SHA3_256::from_suspended(serialized_256) { - Err(SerializedStateError::InvalidData) => { /* good */ } + Err(SuspendableError::InvalidData) => { /* good */ } _ => panic!("Expected an error when loading a SHAKE256 state into SHA3-256"), } } From 3c8d64b0730e97f4d4448f75ac8c3a964e0a2806 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 8 Jul 2026 19:06:46 -0500 Subject: [PATCH 14/26] Adjusted the Suspendable version checker to allow future versions from the same patch stream --- .../src/suspendable_state.rs | 66 +++++++++++++++++-- crypto/core/src/errors.rs | 3 - crypto/core/src/lib.rs | 2 +- ...alizable_state.rs => suspendable_state.rs} | 19 ++++-- crypto/hmac/tests/hmac_tests.rs | 2 +- crypto/sha2/src/sha256.rs | 2 +- crypto/sha2/src/sha512.rs | 2 +- crypto/sha3/src/sha3.rs | 2 +- crypto/sha3/src/shake.rs | 2 +- 9 files changed, 80 insertions(+), 20 deletions(-) rename crypto/core/src/{serializable_state.rs => suspendable_state.rs} (82%) diff --git a/crypto/core-test-framework/src/suspendable_state.rs b/crypto/core-test-framework/src/suspendable_state.rs index c432aa8..677ed4e 100644 --- a/crypto/core-test-framework/src/suspendable_state.rs +++ b/crypto/core-test-framework/src/suspendable_state.rs @@ -1,5 +1,5 @@ use bouncycastle_core::errors::SuspendableError; -use bouncycastle_core::serializable_state::{LIB_VERSION, SemVer}; +use bouncycastle_core::suspendable_state::{LIB_VERSION, SemVer}; use bouncycastle_core::traits::{Suspendable, SuspendableKeyed}; pub struct TestFrameworkSuspendableState {} @@ -45,9 +45,21 @@ impl TestFrameworkSuspendableState { } } - // All implementations MUST reject a serialized state from a future version. + // All implementations MUST reject a serialized state from a future MAJOR or MINOR version. let mut future_ver = LIB_VERSION; - future_ver.patch += 1; + future_ver.major += 1; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + match S::from_suspended(futurever_serialized_state) { + Err(SuspendableError::IncompatibleVersion) => { /* good */ } + _ => { + panic!("Expected IncompatibleVersion error") + } + } + + let mut future_ver = LIB_VERSION; + future_ver.minor += 1; let mut futurever_serialized_state = serialized_state.clone(); futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); @@ -57,6 +69,22 @@ impl TestFrameworkSuspendableState { panic!("Expected IncompatibleVersion error") } } + + // but should accept anything on the same patch stream. + let mut future_ver = LIB_VERSION; + future_ver.patch += 1; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + let _deserialized_state = S::from_suspended(futurever_serialized_state).unwrap(); + + // ... even up to patch 255 + let mut future_ver = LIB_VERSION; + future_ver.patch = 255; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + let _deserialized_state = S::from_suspended(futurever_serialized_state).unwrap(); } } @@ -107,9 +135,21 @@ impl TestFrameworkSuspendableKeyedState { } } - // All implementations MUST reject a serialized state from a future version. + // All implementations MUST reject a serialized state from a future MAJOR or MINOR version. let mut future_ver = LIB_VERSION; - future_ver.patch += 1; + future_ver.major += 1; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + match S::from_suspended(futurever_serialized_state, key) { + Err(SuspendableError::IncompatibleVersion) => { /* good */ } + _ => { + panic!("Expected IncompatibleVersion error") + } + } + + let mut future_ver = LIB_VERSION; + future_ver.minor += 1; let mut futurever_serialized_state = serialized_state.clone(); futurever_serialized_state[..3] .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); @@ -119,5 +159,21 @@ impl TestFrameworkSuspendableKeyedState { panic!("Expected IncompatibleVersion error") } } + + // but should accept anything on the same patch stream. + let mut future_ver = LIB_VERSION; + future_ver.patch += 1; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + let _deserialized_state = S::from_suspended(futurever_serialized_state, key).unwrap(); + + // ... even up to patch 255 + let mut future_ver = LIB_VERSION; + future_ver.patch = 255; + let mut futurever_serialized_state = serialized_state.clone(); + futurever_serialized_state[..3] + .copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]); + let _deserialized_state = S::from_suspended(futurever_serialized_state, key).unwrap(); } } diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index 806380e..a079aad 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -78,9 +78,6 @@ pub enum SuspendableError { IncompatibleVersion, /// The serialized state is malformed or corrupt. InvalidData, - /// The key supplied to [crate::traits::SuspendableKeyed::from_suspended] does not - /// match the key the state was created with (it is bound to a different public-key hash `tr`). - IncorrectKey, } #[derive(Debug)] diff --git a/crypto/core/src/lib.rs b/crypto/core/src/lib.rs index 9516907..66bff60 100644 --- a/crypto/core/src/lib.rs +++ b/crypto/core/src/lib.rs @@ -7,5 +7,5 @@ pub mod errors; pub mod key_material; -pub mod serializable_state; +pub mod suspendable_state; pub mod traits; diff --git a/crypto/core/src/serializable_state.rs b/crypto/core/src/suspendable_state.rs similarity index 82% rename from crypto/core/src/serializable_state.rs rename to crypto/core/src/suspendable_state.rs index 615cfc3..a668676 100644 --- a/crypto/core/src/serializable_state.rs +++ b/crypto/core/src/suspendable_state.rs @@ -42,8 +42,8 @@ const fn parse_version_component(s: &str) -> u8 { result } -/// The current library version, taken from this crate's `Cargo.toml` at compile time (via Cargo's -/// `CARGO_PKG_VERSION_*` env vars) so it can never drift from the published version. +/// The current library version -- ie the version of the *bouncycastle-core* crate -- at compile time (via Cargo's +/// `CARGO_PKG_VERSION_*` env vars). pub const LIB_VERSION: SemVer = SemVer { major: parse_version_component(env!("CARGO_PKG_VERSION_MAJOR")), minor: parse_version_component(env!("CARGO_PKG_VERSION_MINOR")), @@ -78,8 +78,9 @@ pub fn add_lib_ver(state: &mut [u8; SERIALIZED_LEN] /// /// The state_out array must have length at least SERIALIZED_LEN - 3. /// -/// Returns the number of bytes written to state_out, or a [SuspendableError::IncompatibleVersion] if the -/// serialized state contains a version header earlier than the specified `not_before` version. +/// Returns the number of bytes written to state_out, or a [SuspendableError::IncompatibleVersion] if +/// the version of the serialized state is earlier than the specified `not_before` version, or +/// is a future MAJOR or MINOR version (but future PATCH versions are ok). /// /// Note that for testability, this will always reject if the serialized state contains a version tag /// of `[0,0,0]`. @@ -89,6 +90,10 @@ pub fn check_lib_ver( state: &[u8; SERIALIZED_LEN], not_before: Option<[u8; 3]>, ) -> Result<&[u8], SuspendableError> { + // the .unwrap is infallible after the guard check + if state.len() < 3 { + return Err(SuspendableError::InvalidData); + } let ver_bytes: [u8; 3] = state[..3].try_into().unwrap(); let ver = SemVer::from(ver_bytes); @@ -102,8 +107,10 @@ pub fn check_lib_ver( return Err(SuspendableError::IncompatibleVersion); }; - // Also not compatible with future versions. - if ver > LIB_VERSION { + // Check if state was produced by a later MAJOR or MINOR version; + // a future version on the same patch stream is ok (if not, then we've broken the rules of semantic versioning); + let patch_stream = SemVer::from([LIB_VERSION.major, LIB_VERSION.minor, 255]); + if ver > patch_stream { return Err(SuspendableError::IncompatibleVersion); } diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/hmac/tests/hmac_tests.rs index 4921fef..817a89d 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/hmac/tests/hmac_tests.rs @@ -605,7 +605,7 @@ mod hmac_tests { #[test] fn suspendable_keyed_state() { use bouncycastle_core::errors::SuspendableError; - use bouncycastle_core::serializable_state::LIB_VERSION; + use bouncycastle_core::suspendable_state::LIB_VERSION; use bouncycastle_core::traits::SuspendableKeyed; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index 26fd556..cd92454 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -1,6 +1,6 @@ use crate::SHA2Params; use bouncycastle_core::errors::{HashError, SuspendableError}; -use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Algorithm, Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 4d2a7f4..3a031bc 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -1,6 +1,6 @@ use crate::SHA2Params; use bouncycastle_core::errors::{HashError, SuspendableError}; -use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Algorithm, Hash, SecurityStrength, Suspendable}; use bouncycastle_utils::min; use core::slice; diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 42933e6..705e741 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -6,7 +6,7 @@ use crate::keccak::{ use bouncycastle_core::errors::{HashError, KDFError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Algorithm, Hash, KDF, SecurityStrength, Suspendable}; use bouncycastle_utils::{max, min}; diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index e1b3aef..551e6fc 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -6,7 +6,7 @@ use crate::keccak::{ use bouncycastle_core::errors::{HashError, KDFError, SuspendableError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver}; +use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Algorithm, KDF, SecurityStrength, Suspendable, XOF}; use bouncycastle_utils::{max, min}; From fd17f91a2e3c91d2cf7ac51d5e6596b6ffea04a1 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 8 Jul 2026 19:41:12 -0500 Subject: [PATCH 15/26] HKDF now explicitly carries a lib_ver tag --- crypto/hkdf/src/lib.rs | 71 +++++++++++++++++++-------------- crypto/hkdf/tests/hkdf_tests.rs | 3 +- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 5c436d0..13ec778 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -197,6 +197,7 @@ use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial0, KeyMaterial512, KeyMaterialTrait, KeyType, }; +use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{ Hash, HashAlgParams, KDF, MAC, SecurityStrength, SuspendableKeyed, }; @@ -777,9 +778,9 @@ impl KDF for HKDF { } /// Length in bytes of the serialized state of [HKDF_SHA256]. -pub const SUSPENDED_HKDF_SHA256_STATE_LEN: usize = SUSPENDED_HMAC_SHA256_STATE_LEN + 11; +pub const SUSPENDED_HKDF_SHA256_STATE_LEN: usize = SUSPENDED_HMAC_SHA256_STATE_LEN + 14; /// Length in bytes of the serialized state of [HKDF_SHA512]. -pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_LEN + 11; +pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_LEN + 14; /// HKDF is *keyed by its salt* -- the salt keys the extract-phase HMAC -- so it implements /// [SuspendableKeyed] (not [SerializableState]). An in-progress @@ -789,19 +790,21 @@ pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_L /// Only the extract phase carries resumable state (expand is a one-shot static operation). As with /// HMAC, resuming with the wrong salt cannot be detected and will silently produce a wrong PRK. /// -/// Serialized layout: the 3-byte library version header comes first and is checked before anything -/// else is parsed; then, using `B` = the inner HMAC blob length: -/// [0 .. B) the inner HMAC's SerializableKeyedState blob (salt excluded); zeroed when absent -/// [B] inner-HMAC present flag (0 = extract not yet initialized) -/// [B + 1] state-machine tag (see `HkdfStates`) -/// [B + 2 .. B + 10) entropy counter (usize serialized as u64, little-endian) -/// [B + 10] accumulated security strength (1-byte tag) -/// The number of bytes produced by `SerializableKeyedState::serialize_state` for each HKDF variant: -/// the 3-byte library version header + 11 bytes of HKDF bookkeeping (HMAC-present flag, state tag, -/// entropy counter, security strength) + the inner HMAC's serialized-state blob. +/// Serialized layout: HKDF writes its own 3-byte library version header first and checks it before +/// parsing anything else. This matters because the inner HMAC blob (which carries its own header) is +/// absent before extract is initialized -- without HKDF's own header, a pre-init state would have no +/// version tag at all. Using `B` = the inner HMAC blob length: +/// [0 .. 3) HKDF library version header (checked on resume) +/// [3] inner-HMAC present flag (0 = extract not yet initialized) +/// [4 .. 4 + B) the inner HMAC's SuspendableKeyed blob (salt excluded); zeroed when absent +/// [4 + B] state-machine tag (see `HkdfStates`) +/// [5 + B .. 13 + B) entropy counter (usize serialized as u64, little-endian) +/// [13 + B] accumulated security strength (1-byte tag) +/// So the total per HKDF variant is the 3-byte version header + 11 bytes of HKDF bookkeeping +/// (present flag, state tag, entropy counter, security strength) + the inner HMAC's blob = `B + 14`. macro_rules! impl_suspendable_keyed_state_for_hkdf { - // $hash: the concrete hash; $hmac_blob: the inner HMAC's serialized-state length for that hash; - // $total: the full HKDF serialized-state length (= 3 + 11 + $hmac_blob). + // $hash: the concrete hash; $serialized_hmac_len: the inner HMAC's serialized-state length for that + // hash; $serialized_hkdf_len: the full HKDF serialized-state length (= 3 + 11 + $serialized_hmac_len). ($hash:ty, $serialized_hmac_len:expr, $serialized_hkdf_len:expr) => { impl SuspendableKeyed<{ $serialized_hkdf_len }> for HKDF<$hash> { // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait` @@ -810,19 +813,27 @@ macro_rules! impl_suspendable_keyed_state_for_hkdf { type Key = dyn KeyMaterialTrait; fn suspend(self) -> [u8; $serialized_hkdf_len] { - debug_assert_eq!($serialized_hkdf_len, $serialized_hmac_len + 11); + debug_assert_eq!($serialized_hkdf_len, $serialized_hmac_len + 14); let mut state = [0u8; $serialized_hkdf_len]; - // The inner HMAC blob goes first, which carries a lib version header. + // HKDF's own library version header comes first: the inner HMAC blob is absent before + // extract is initialized, so we can't rely on its header being present. + add_lib_ver(&mut state); + + // The present flag, then (when present) the inner salt-keyed HMAC blob right after it. if let Some(hmac) = self.hmac { - state[..$serialized_hmac_len].copy_from_slice(&hmac.suspend()); - state[$serialized_hmac_len] = 1; // present flag + state[3] = 1; // present flag + state[4..4 + $serialized_hmac_len].copy_from_slice(&hmac.suspend()); } + // else None: + // the presence flag = 0 + // the content = [u8; 0] + // which is how it already is, so nothing to do. - state[$serialized_hmac_len + 1] = self.state as u8; - state[$serialized_hmac_len + 2..$serialized_hmac_len + 10] + state[4 + $serialized_hmac_len] = self.state as u8; + state[5 + $serialized_hmac_len..13 + $serialized_hmac_len] .copy_from_slice(&(self.entropy.entropy as u64).to_le_bytes()); - state[$serialized_hmac_len + 10] = self.entropy.security_strength as u8; + state[13 + $serialized_hmac_len] = self.entropy.security_strength as u8; state } @@ -831,22 +842,20 @@ macro_rules! impl_suspendable_keyed_state_for_hkdf { state: [u8; $serialized_hkdf_len], salt: &Self::Key, ) -> Result { - // Rebuild the salt-keyed HMAC (first in the payload) by re-supplying the salt. + // Check HKDF's own version header before parsing anything else. + check_lib_ver(&state, None)?; - // This double-dips on HMAC checking the version tag before going any further. - // If ever we need to version-reject HKDF separately from HMAC, then we'll need to add - // an explicit version check here, and change "None" to the oldest accepted version. - // _ = check_lib_ver(&serialized_state, None)?; - let hmac = match state[$serialized_hmac_len] { + // Rebuild the salt-keyed HMAC (when present) by re-supplying the salt. + let hmac = match state[3] { 0 => None, 1 => Some(HMAC::<$hash>::from_suspended( - state[..$serialized_hmac_len].try_into().unwrap(), + state[4..4 + $serialized_hmac_len].try_into().unwrap(), salt, )?), _ => return Err(SuspendableError::InvalidData), }; - let hkdf_state = HkdfStates::try_from(state[$serialized_hmac_len + 1])?; + let hkdf_state = HkdfStates::try_from(state[4 + $serialized_hmac_len])?; // check that the hkdf_state aligns with the presence of an hmac if @@ -859,10 +868,10 @@ macro_rules! impl_suspendable_keyed_state_for_hkdf { } let entropy = u64::from_le_bytes( - state[$serialized_hmac_len + 2..$serialized_hmac_len + 10].try_into().unwrap(), + state[5 + $serialized_hmac_len..13 + $serialized_hmac_len].try_into().unwrap(), ) as usize; let security_strength = - SecurityStrength::try_from(state[$serialized_hmac_len + 10])?; + SecurityStrength::try_from(state[13 + $serialized_hmac_len])?; Ok(HKDF { hmac, diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index 448c222..fee0444 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -796,7 +796,8 @@ mod hkdf_tests { const UNINITIALIZED: u8 = 0; // HkdfStates::Uninitialized const INITIALIZED: u8 = 1; // HkdfStates::Initialized - let present_idx = SUSPENDED_HKDF_SHA256_STATE_LEN - 11; + // Layout: [version(3) | present flag | hmac blob | state | entropy(8) | strength]. + let present_idx = 3; let state_idx = SUSPENDED_HKDF_SHA256_STATE_LEN - 10; // Case 1: no HMAC present but state claims Initialized -> reject. From d46f2881fffb28757891b1a43aad8e5da3a0d1ff Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 8 Jul 2026 20:55:30 -0500 Subject: [PATCH 16/26] Towards removing nightly, I had to refactor how HashMLDSA gets its hash function OIDs. Honestly, this is MUCH cleaner anyway. --- crypto/core/src/traits.rs | 9 +++++ crypto/hmac/src/lib.rs | 2 - crypto/mldsa-lowmemory/src/hash_mldsa.rs | 40 +++++-------------- crypto/mldsa-lowmemory/src/lib.rs | 5 --- crypto/mldsa/src/hash_mldsa.rs | 49 ++++++------------------ crypto/mlkem-lowmemory/src/lib.rs | 4 +- crypto/mlkem/src/lib.rs | 4 +- crypto/sha2/src/lib.rs | 30 ++++++++++++++- rust-toolchain.toml | 2 - 9 files changed, 61 insertions(+), 84 deletions(-) delete mode 100644 rust-toolchain.toml diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 3086630..ee3bcef 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -12,11 +12,20 @@ use crate::key_material::KeyMaterial; use crate::key_material::KeyType; // end of imports needed for docs +/// All algorithms carry a name and a max security strength tha they can support. pub trait Algorithm { const ALG_NAME: &'static str; const MAX_SECURITY_STRENGTH: SecurityStrength; } +/// Some algorithms have an assigned OID. +pub trait AlgorithmOID { + /// The OID in component form -- each u128 is one OID component. + const OID: &'static [u128]; + /// The OID in its DER-encoded form. + const OID_DER: &'static [u8]; +} + pub trait Hash: Algorithm + Default { /// The size of the internal block in bits -- needed by functions such as HMAC to compute security parameters. fn block_bitlen(&self) -> usize; diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index cd59def..ad1c810 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -181,8 +181,6 @@ //! ``` #![forbid(unsafe_code)] -#![allow(incomplete_features)] // because at time of writing, generic_const_exprs is not a stable feature -#![feature(generic_const_exprs)] use bouncycastle_core::errors::{KeyMaterialError, MACError, SuspendableError}; use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; diff --git a/crypto/mldsa-lowmemory/src/hash_mldsa.rs b/crypto/mldsa-lowmemory/src/hash_mldsa.rs index 179301c..989cc72 100644 --- a/crypto/mldsa-lowmemory/src/hash_mldsa.rs +++ b/crypto/mldsa-lowmemory/src/hash_mldsa.rs @@ -96,8 +96,8 @@ use crate::{ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{ - Algorithm, Hash, PHSignatureVerifier, PHSigner, RNG, SecurityStrength, SignatureVerifier, - Signer, XOF, + Algorithm, AlgorithmOID, Hash, PHSignatureVerifier, PHSigner, RNG, SecurityStrength, + SignatureVerifier, Signer, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha2::{SHA256, SHA512}; @@ -107,9 +107,6 @@ use core::marker::PhantomData; #[allow(unused_imports)] use crate::mldsa::MuBuilder; -const SHA256_OID: &[u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]; -const SHA512_OID: &[u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]; - /*** Constants ***/ /// @@ -132,7 +129,6 @@ pub const HASH_ML_DSA_87_WITH_SHA512_NAME: &str = "HashML-DSA-87_with_SHA512"; pub type HashMLDSA44_with_SHA256 = HashMLDSA< SHA256, 32, - SHA256_OID, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_FULL_SK_LEN, @@ -170,7 +166,6 @@ impl Algorithm for HashMLDSA44_with_SHA256 { pub type HashMLDSA65_with_SHA256 = HashMLDSA< SHA256, 32, - SHA256_OID, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_FULL_SK_LEN, @@ -208,7 +203,6 @@ impl Algorithm for HashMLDSA65_with_SHA256 { pub type HashMLDSA87_with_SHA256 = HashMLDSA< SHA256, 32, - SHA256_OID, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_FULL_SK_LEN, @@ -246,7 +240,6 @@ impl Algorithm for HashMLDSA87_with_SHA256 { pub type HashMLDSA44_with_SHA512 = HashMLDSA< SHA512, 64, - SHA512_OID, MLDSA44_PK_LEN, MLDSA44_SK_LEN, MLDSA44_FULL_SK_LEN, @@ -284,7 +277,6 @@ impl Algorithm for HashMLDSA44_with_SHA512 { pub type HashMLDSA65_with_SHA512 = HashMLDSA< SHA512, 64, - SHA512_OID, MLDSA65_PK_LEN, MLDSA65_SK_LEN, MLDSA65_FULL_SK_LEN, @@ -322,7 +314,6 @@ impl Algorithm for HashMLDSA65_with_SHA512 { pub type HashMLDSA87_with_SHA512 = HashMLDSA< SHA512, 64, - SHA512_OID, MLDSA87_PK_LEN, MLDSA87_SK_LEN, MLDSA87_FULL_SK_LEN, @@ -362,9 +353,8 @@ impl Algorithm for HashMLDSA87_with_SHA512 { /// by specifying the hash function to use (in the verifier), and specifying the bytes of the OID to /// to use as its domain separator in constructing the message representative M'. pub struct HashMLDSA< - HASH: Hash + Default, + HASH: Hash + AlgorithmOID + Default, const HASH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, @@ -434,9 +424,8 @@ pub struct HashMLDSA< } impl< - HASH: Hash + Default, + HASH: Hash + AlgorithmOID + Default, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, @@ -486,7 +475,6 @@ impl< HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, FULL_SK_LEN, @@ -645,7 +633,7 @@ impl< h.absorb(&[1u8]); h.absorb(&[ctx.len() as u8]); h.absorb(ctx); - h.absorb(oid); + h.absorb(HASH::OID_DER); h.absorb(ph); let mut mu = [0u8; MLDSA_MU_LEN]; let bytes_written = h.squeeze_out(&mut mu); @@ -727,7 +715,7 @@ impl< } impl< - HASH: Hash + Default, + HASH: Hash + AlgorithmOID + Default, PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, SK: MLDSAPrivateKeyTrait< @@ -751,7 +739,6 @@ impl< SK_LEN, >, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, @@ -779,7 +766,6 @@ impl< for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, FULL_SK_LEN, @@ -895,7 +881,7 @@ impl< } impl< - HASH: Hash + Default, + HASH: Hash + AlgorithmOID + Default, PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, SK: MLDSAPrivateKeyTrait< @@ -919,7 +905,6 @@ impl< SK_LEN, >, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, @@ -947,7 +932,6 @@ impl< for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, FULL_SK_LEN, @@ -1011,9 +995,8 @@ impl< } impl< - HASH: Hash + Default, + HASH: Hash + AlgorithmOID + Default, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, @@ -1063,7 +1046,6 @@ impl< for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, FULL_SK_LEN, @@ -1121,9 +1103,8 @@ impl< } impl< - HASH: Hash + Default, + HASH: Hash + AlgorithmOID + Default, const PH_LEN: usize, - const oid: &'static [u8], const PK_LEN: usize, const SK_LEN: usize, const FULL_SK_LEN: usize, @@ -1173,7 +1154,6 @@ impl< for HashMLDSA< HASH, PH_LEN, - oid, PK_LEN, SK_LEN, FULL_SK_LEN, @@ -1231,7 +1211,7 @@ impl< h.absorb(&[1u8]); h.absorb(&[ctx.len() as u8]); h.absorb(ctx); - h.absorb(oid); + h.absorb(HASH::OID_DER); h.absorb(ph); let mut mu = [0u8; MLDSA_MU_LEN]; _ = h.squeeze_out(&mut mu); diff --git a/crypto/mldsa-lowmemory/src/lib.rs b/crypto/mldsa-lowmemory/src/lib.rs index 5d2ed88..d0b0192 100644 --- a/crypto/mldsa-lowmemory/src/lib.rs +++ b/crypto/mldsa-lowmemory/src/lib.rs @@ -202,9 +202,6 @@ #![no_std] #![forbid(missing_docs)] #![forbid(unsafe_code)] -#![allow(incomplete_features)] // needed because currently generic_const_exprs is experimental -#![feature(generic_const_exprs)] -#![feature(adt_const_params)] // These are because I'm matching variable names exactly against FIPS 204, for example both 'K' and 'k', // or 'A' and 'a' are used and have specific meanings. // But need to tell the rust linter to not care. @@ -213,8 +210,6 @@ // so I can use private traits to hide internal stuff that needs to be generic within the // MLDSA implementation, but I don't want accessed from outside, such as FIPS-internal functions. #![allow(private_bounds)] -// Used in HashMLDSA -#![feature(unsized_const_params)] // imports needed just for docs #[allow(unused_imports)] diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 0cfd620..7142310 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -93,20 +93,17 @@ use crate::{ use bouncycastle_core::errors::SignatureError; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{ - Algorithm, Hash, PHSignatureVerifier, PHSigner, RNG, SecurityStrength, SignatureVerifier, - Signer, XOF, + Algorithm, AlgorithmOID, Hash, PHSignatureVerifier, PHSigner, RNG, SecurityStrength, + SignatureVerifier, Signer, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; -use bouncycastle_sha2::{SHA256, SHA256_NAME, SHA512, SHA512_NAME}; +use bouncycastle_sha2::{SHA256, SHA512}; use core::marker::PhantomData; // Imports needed only for docs #[allow(unused_imports)] use crate::mldsa::MuBuilder; -const SHA256_OID: &[u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]; -const SHA512_OID: &[u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]; - /*** Constants ***/ /// @@ -329,7 +326,7 @@ impl Algorithm for HashMLDSA87_with_SHA512 { /// by specifying the hash function to use (in the verifier), and specifying the bytes of the OID to /// to use as its domain separator in constructing the message representative M'. pub struct HashMLDSA< - HASH: Hash + Algorithm + Default, + HASH: Hash + AlgorithmOID + Default, const HASH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, @@ -377,7 +374,7 @@ pub struct HashMLDSA< } impl< - HASH: Hash + Algorithm + Default, + HASH: Hash + AlgorithmOID + Default, const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, @@ -600,20 +597,7 @@ impl< h.absorb(&[1u8]); h.absorb(&[ctx.len() as u8]); h.absorb(ctx); - - // this is all statics, so the branch should compile out. - // Really, this should be a generic param of HashMLDSA, but unsized_const_params is currently - // a nightly-only feature. - match HASH::ALG_NAME { - SHA256_NAME => h.absorb(SHA256_OID), - SHA512_NAME => h.absorb(SHA512_OID), - _ => { - return Err(SignatureError::GenericError( - "Unsupported hash algorithm; you need to add it to the switch", - )); - } - }; - + h.absorb(HASH::OID_DER); h.absorb(ph); let mut mu = [0u8; MLDSA_MU_LEN]; let bytes_written = h.squeeze_out(&mut mu); @@ -736,18 +720,7 @@ impl< h.absorb(&[1u8]); h.absorb(&[ctx.len() as u8]); h.absorb(ctx); - // this is all statics, so the branch should compile out. - // Really, this should be a generic param of HashMLDSA, but unsized_const_params is currently - // a nightly-only feature. - match HASH::ALG_NAME { - SHA256_NAME => h.absorb(SHA256_OID), - SHA512_NAME => h.absorb(SHA512_OID), - _ => { - return Err(SignatureError::GenericError( - "Unsupported hash algorithm; you need to add it to the switch", - )); - } - }; + h.absorb(HASH::OID_DER); h.absorb(ph); let mut mu = [0u8; MLDSA_MU_LEN]; _ = h.squeeze_out(&mut mu); @@ -807,7 +780,7 @@ impl< } impl< - HASH: Hash + Algorithm + Default, + HASH: Hash + AlgorithmOID + Default, PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, @@ -954,7 +927,7 @@ impl< } impl< - HASH: Hash + Algorithm + Default, + HASH: Hash + AlgorithmOID + Default, PK: MLDSAPublicKeyTrait + MLDSAPublicKeyInternalTrait, SK: MLDSAPrivateKeyTrait + MLDSAPrivateKeyInternalTrait, @@ -1041,7 +1014,7 @@ impl< } impl< - HASH: Hash + Algorithm + Default, + HASH: Hash + AlgorithmOID + Default, const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, @@ -1122,7 +1095,7 @@ impl< } impl< - HASH: Hash + Algorithm + Default, + HASH: Hash + AlgorithmOID + Default, const PH_LEN: usize, const PK_LEN: usize, const SK_LEN: usize, diff --git a/crypto/mlkem-lowmemory/src/lib.rs b/crypto/mlkem-lowmemory/src/lib.rs index d371326..a39a100 100644 --- a/crypto/mlkem-lowmemory/src/lib.rs +++ b/crypto/mlkem-lowmemory/src/lib.rs @@ -225,9 +225,7 @@ #![no_std] #![forbid(missing_docs)] #![forbid(unsafe_code)] -#![allow(incomplete_features)] // needed because currently generic_const_exprs is experimental -#![feature(generic_const_exprs)] -#![feature(adt_const_params)] + // These are because I'm matching variable names exactly against FIPS 204, for example both 'K' and 'k', // or 'A' and 'a' are used and have specific meanings. // But need to tell the rust linter to not care. diff --git a/crypto/mlkem/src/lib.rs b/crypto/mlkem/src/lib.rs index c3dd524..69686c2 100644 --- a/crypto/mlkem/src/lib.rs +++ b/crypto/mlkem/src/lib.rs @@ -139,9 +139,7 @@ #![no_std] #![forbid(missing_docs)] #![forbid(unsafe_code)] -#![allow(incomplete_features)] // needed because currently generic_const_exprs is experimental -#![feature(generic_const_exprs)] -#![feature(adt_const_params)] + // These are because I'm matching variable names exactly against FIPS 204, for example both 'K' and 'k', // or 'A' and 'a' are used and have specific meanings. // But need to tell the rust linter to not care. diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index fb8d933..564af40 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -73,7 +73,7 @@ mod sha512; pub use self::sha256::SHA256Internal; pub use self::sha512::SHA512Internal; -use bouncycastle_core::traits::{Algorithm, HashAlgParams, SecurityStrength}; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams, SecurityStrength}; /*** Imports needed for docs ***/ #[allow(unused_imports)] @@ -95,10 +95,12 @@ pub type SHA512 = SHA512Internal; trait SHA2Params: HashAlgParams {} +/*** SHA224 ***/ impl HashAlgParams for SHA224 { const OUTPUT_LEN: usize = 28; const BLOCK_LEN: usize = 64; } +/// Assigned by NIST in the Computer Security Objects Register #[derive(Clone)] pub struct SHA224Params; impl Algorithm for SHA224Params { @@ -109,8 +111,14 @@ impl HashAlgParams for SHA224Params { const OUTPUT_LEN: usize = 28; const BLOCK_LEN: usize = 64; } +impl AlgorithmOID for SHA224 { + const OID: &'static [u128] = &[2, 16, 840, 1, 101, 3, 4, 2, 4]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04]; +} impl SHA2Params for SHA224Params {} +/*** SHA256 ***/ impl HashAlgParams for SHA256 { const OUTPUT_LEN: usize = 32; const BLOCK_LEN: usize = 64; @@ -121,12 +129,19 @@ impl Algorithm for SHA256Params { const ALG_NAME: &'static str = SHA256_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } +/// Assigned by NIST in the Computer Security Objects Register +impl AlgorithmOID for SHA256 { + const OID: &'static [u128] = &[2, 16, 840, 1, 101, 3, 4, 2, 1]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]; +} impl HashAlgParams for SHA256Params { const OUTPUT_LEN: usize = 32; const BLOCK_LEN: usize = 64; } impl SHA2Params for SHA256Params {} +/*** SHA384 ***/ impl HashAlgParams for SHA384 { const OUTPUT_LEN: usize = 48; const BLOCK_LEN: usize = 128; @@ -137,12 +152,19 @@ impl Algorithm for SHA384Params { const ALG_NAME: &'static str = SHA384_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } +/// Assigned by NIST in the Computer Security Objects Register +impl AlgorithmOID for SHA384 { + const OID: &'static [u128] = &[2, 16, 840, 1, 101, 3, 4, 2, 2]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02]; +} impl HashAlgParams for SHA384Params { const OUTPUT_LEN: usize = 48; const BLOCK_LEN: usize = 128; } impl SHA2Params for SHA384Params {} +/*** SHA512 ***/ #[derive(Clone)] pub struct SHA512Params; impl HashAlgParams for SHA512 { @@ -157,6 +179,12 @@ impl HashAlgParams for SHA512Params { const OUTPUT_LEN: usize = 64; const BLOCK_LEN: usize = 128; } +/// Assigned by NIST in the Computer Security Objects Register +impl AlgorithmOID for SHA512 { + const OID: &'static [u128] = &[2, 16, 840, 1, 101, 3, 4, 2, 3]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]; +} impl SHA2Params for SHA512Params {} pub use sha256::SUSPENDED_SHA256_STATE_LEN; diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 5d56faf..0000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "nightly" From f088ef968a9aeb3e445a3d2483870a8bf108c3d3 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 8 Jul 2026 20:59:01 -0500 Subject: [PATCH 17/26] rustfmt --- crypto/mlkem-lowmemory/src/lib.rs | 1 - crypto/mlkem/src/lib.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/crypto/mlkem-lowmemory/src/lib.rs b/crypto/mlkem-lowmemory/src/lib.rs index a39a100..0aaf81a 100644 --- a/crypto/mlkem-lowmemory/src/lib.rs +++ b/crypto/mlkem-lowmemory/src/lib.rs @@ -225,7 +225,6 @@ #![no_std] #![forbid(missing_docs)] #![forbid(unsafe_code)] - // These are because I'm matching variable names exactly against FIPS 204, for example both 'K' and 'k', // or 'A' and 'a' are used and have specific meanings. // But need to tell the rust linter to not care. diff --git a/crypto/mlkem/src/lib.rs b/crypto/mlkem/src/lib.rs index 69686c2..c7ffde8 100644 --- a/crypto/mlkem/src/lib.rs +++ b/crypto/mlkem/src/lib.rs @@ -139,7 +139,6 @@ #![no_std] #![forbid(missing_docs)] #![forbid(unsafe_code)] - // These are because I'm matching variable names exactly against FIPS 204, for example both 'K' and 'k', // or 'A' and 'a' are used and have specific meanings. // But need to tell the rust linter to not care. From 1dc415b93d730f321fc6ea1a76e34762cc7beba3 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Wed, 8 Jul 2026 22:26:04 -0500 Subject: [PATCH 18/26] Added OIDs for all algorithms. --- crypto/core/src/traits.rs | 4 +-- crypto/hmac/src/lib.rs | 46 +++++++++++++++++++++++- crypto/mldsa-lowmemory/src/hash_mldsa.rs | 18 ++++++++++ crypto/mldsa-lowmemory/src/mldsa.rs | 20 ++++++++++- crypto/mldsa/src/hash_mldsa.rs | 18 ++++++++++ crypto/mldsa/src/mldsa.rs | 20 ++++++++++- crypto/mlkem-lowmemory/src/mlkem.rs | 20 ++++++++++- crypto/mlkem/src/mlkem.rs | 20 ++++++++++- crypto/sha2/src/lib.rs | 15 ++++---- crypto/sha3/src/lib.rs | 38 +++++++++++++++++++- rustfmt.toml | 1 - 11 files changed, 204 insertions(+), 16 deletions(-) diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index ee3bcef..5cc7087 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -20,8 +20,8 @@ pub trait Algorithm { /// Some algorithms have an assigned OID. pub trait AlgorithmOID { - /// The OID in component form -- each u128 is one OID component. - const OID: &'static [u128]; + /// The OID in component form -- each u32 is one OID component. + const OID: &'static [u32]; /// The OID in its DER-encoded form. const OID_DER: &'static [u8]; } diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index ad1c810..41e9f7e 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -185,7 +185,7 @@ use bouncycastle_core::errors::{KeyMaterialError, MACError, SuspendableError}; use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - Algorithm, Hash, MAC, Secret, SecurityStrength, Suspendable, SuspendableKeyed, + Algorithm, AlgorithmOID, Hash, MAC, Secret, SecurityStrength, Suspendable, SuspendableKeyed, }; use bouncycastle_sha2::{ SHA224, SHA256, SHA384, SHA512, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN, @@ -219,6 +219,11 @@ impl Algorithm for HMAC_SHA224 { const ALG_NAME: &'static str = HMAC_SHA224_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; } +/// Defined in RFC 4231: id-hmacWithSHA224 { digestAlgorithm 8 } +impl AlgorithmOID for HMAC_SHA224 { + const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 8]; + const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x08]; +} #[allow(non_camel_case_types)] pub type HMAC_SHA256 = HMAC; @@ -226,6 +231,11 @@ impl Algorithm for HMAC_SHA256 { const ALG_NAME: &'static str = HMAC_SHA256_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } +/// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } +impl AlgorithmOID for HMAC_SHA256 { + const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 9]; + const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09]; +} #[allow(non_camel_case_types)] pub type HMAC_SHA384 = HMAC; @@ -233,6 +243,11 @@ impl Algorithm for HMAC_SHA384 { const ALG_NAME: &'static str = HMAC_SHA384_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } +/// Defined in RFC 4231: id-hmacWithSHA384 { digestAlgorithm 10 } +impl AlgorithmOID for HMAC_SHA384 { + const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 10]; + const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0a]; +} #[allow(non_camel_case_types)] pub type HMAC_SHA512 = HMAC; @@ -240,6 +255,11 @@ impl Algorithm for HMAC_SHA512 { const ALG_NAME: &'static str = HMAC_SHA512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } +/// Defined in RFC 4231: id-hmacWithSHA512 { digestAlgorithm 11 } +impl AlgorithmOID for HMAC_SHA512 { + const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 11]; + const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0b]; +} #[allow(non_camel_case_types)] pub type HMAC_SHA3_224 = HMAC; @@ -247,6 +267,12 @@ impl Algorithm for HMAC_SHA3_224 { const ALG_NAME: &'static str = HMAC_SHA3_224_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-224 { hashAlgs 13 } +impl AlgorithmOID for HMAC_SHA3_224 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 13]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0d]; +} #[allow(non_camel_case_types)] pub type HMAC_SHA3_256 = HMAC; @@ -254,6 +280,12 @@ impl Algorithm for HMAC_SHA3_256 { const ALG_NAME: &'static str = HMAC_SHA3_256_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-256 { hashAlgs 14 } +impl AlgorithmOID for HMAC_SHA3_256 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 14]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0e]; +} #[allow(non_camel_case_types)] pub type HMAC_SHA3_384 = HMAC; @@ -261,6 +293,12 @@ impl Algorithm for HMAC_SHA3_384 { const ALG_NAME: &'static str = HMAC_SHA3_384_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-384 { hashAlgs 15 } +impl AlgorithmOID for HMAC_SHA3_384 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 15]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0f]; +} #[allow(non_camel_case_types)] pub type HMAC_SHA3_512 = HMAC; @@ -268,6 +306,12 @@ impl Algorithm for HMAC_SHA3_512 { const ALG_NAME: &'static str = HMAC_SHA3_512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-512 { hashAlgs 16 } +impl AlgorithmOID for HMAC_SHA3_512 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 16]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x10]; +} // The internal key buffer must be able to hold a key up to the *block length* of the underlying hash: // per RFC 2104, a key no longer than the block is used verbatim (only longer keys are pre-hashed down diff --git a/crypto/mldsa-lowmemory/src/hash_mldsa.rs b/crypto/mldsa-lowmemory/src/hash_mldsa.rs index 989cc72..150e7b5 100644 --- a/crypto/mldsa-lowmemory/src/hash_mldsa.rs +++ b/crypto/mldsa-lowmemory/src/hash_mldsa.rs @@ -271,6 +271,12 @@ impl Algorithm for HashMLDSA44_with_SHA512 { const ALG_NAME: &'static str = HASH_ML_DSA_44_with_SHA512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-hash-ml-dsa-44-with-sha512 { sigAlgs 32 } +impl AlgorithmOID for HashMLDSA44_with_SHA512 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 32]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x20]; +} /// The HashML-DSA-65_with_SHA512 signature algorithm. #[allow(non_camel_case_types)] @@ -308,6 +314,12 @@ impl Algorithm for HashMLDSA65_with_SHA512 { const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-hash-ml-dsa-65-with-sha512 { sigAlgs 33 } +impl AlgorithmOID for HashMLDSA65_with_SHA512 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 33]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x21]; +} /// The HashML-DSA-87_with_SHA512 signature algorithm. #[allow(non_camel_case_types)] @@ -345,6 +357,12 @@ impl Algorithm for HashMLDSA87_with_SHA512 { const ALG_NAME: &'static str = HASH_ML_DSA_87_WITH_SHA512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-hash-ml-dsa-87-with-sha512 { sigAlgs 34 } +impl AlgorithmOID for HashMLDSA87_with_SHA512 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 34]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x22]; +} /// An instance of the HashML-DSA algorithm. /// diff --git a/crypto/mldsa-lowmemory/src/mldsa.rs b/crypto/mldsa-lowmemory/src/mldsa.rs index 843a858..ed29341 100644 --- a/crypto/mldsa-lowmemory/src/mldsa.rs +++ b/crypto/mldsa-lowmemory/src/mldsa.rs @@ -400,7 +400,7 @@ use crate::{ use bouncycastle_core::errors::{RNGError, SignatureError, SuspendableError}; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{ - Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, Suspendable, XOF, + Algorithm, AlgorithmOID, RNG, SecurityStrength, SignatureVerifier, Signer, Suspendable, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::{SHAKE128, SHAKE256, SUSPENDED_SHA3_STATE_LEN}; @@ -584,6 +584,12 @@ impl Algorithm for MLDSA44 { const ALG_NAME: &'static str = ML_DSA_44_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 } +impl AlgorithmOID for MLDSA44 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 17]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11]; +} /// The ML-DSA-65 algorithm. pub type MLDSA65 = MLDSA< @@ -618,6 +624,12 @@ impl Algorithm for MLDSA65 { const ALG_NAME: &'static str = ML_DSA_65_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-65 { sigAlgs 18 } +impl AlgorithmOID for MLDSA65 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 18]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12]; +} /// The ML-DSA-87 algorithm. pub type MLDSA87 = MLDSA< @@ -652,6 +664,12 @@ impl Algorithm for MLDSA87 { const ALG_NAME: &'static str = ML_DSA_87_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-87 { sigAlgs 19 } +impl AlgorithmOID for MLDSA87 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 19]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13]; +} /// The core internal implementation of the ML-DSA algorithm. /// This needs to be public for the compiler to be able to find it, but you shouldn't ever diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index 7142310..8de8a6f 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -252,6 +252,12 @@ impl Algorithm for HashMLDSA44_with_SHA512 { const ALG_NAME: &'static str = HASH_ML_DSA_44_with_SHA512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-hash-ml-dsa-44-with-sha512 { sigAlgs 32 } +impl AlgorithmOID for HashMLDSA44_with_SHA512 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 32]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x20]; +} /// The HashML-DSA-65_with_SHA512 signature algorithm. #[allow(non_camel_case_types)] @@ -285,6 +291,12 @@ impl Algorithm for HashMLDSA65_with_SHA512 { const ALG_NAME: &'static str = HASH_ML_DSA_65_WITH_SHA512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-hash-ml-dsa-65-with-sha512 { sigAlgs 33 } +impl AlgorithmOID for HashMLDSA65_with_SHA512 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 33]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x21]; +} /// The HashML-DSA-87_with_SHA512 signature algorithm. #[allow(non_camel_case_types)] @@ -318,6 +330,12 @@ impl Algorithm for HashMLDSA87_with_SHA512 { const ALG_NAME: &'static str = HASH_ML_DSA_87_WITH_SHA512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-hash-ml-dsa-87-with-sha512 { sigAlgs 34 } +impl AlgorithmOID for HashMLDSA87_with_SHA512 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 34]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x22]; +} /// An instance of the HashML-DSA algorithm. /// diff --git a/crypto/mldsa/src/mldsa.rs b/crypto/mldsa/src/mldsa.rs index 0869c44..6f75529 100644 --- a/crypto/mldsa/src/mldsa.rs +++ b/crypto/mldsa/src/mldsa.rs @@ -488,7 +488,7 @@ use crate::{ use bouncycastle_core::errors::{RNGError, SignatureError, SuspendableError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ - Algorithm, RNG, SecurityStrength, SignatureVerifier, Signer, Suspendable, XOF, + Algorithm, AlgorithmOID, RNG, SecurityStrength, SignatureVerifier, Signer, Suspendable, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::{SHAKE128, SHAKE256, SUSPENDED_SHA3_STATE_LEN}; @@ -652,6 +652,12 @@ impl Algorithm for MLDSA44 { const ALG_NAME: &'static str = ML_DSA_44_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-44 { sigAlgs 17 } +impl AlgorithmOID for MLDSA44 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 17]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11]; +} /// The ML-DSA-65 algorithm. pub type MLDSA65 = MLDSA< @@ -682,6 +688,12 @@ impl Algorithm for MLDSA65 { const ALG_NAME: &'static str = ML_DSA_65_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-65 { sigAlgs 18 } +impl AlgorithmOID for MLDSA65 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 18]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12]; +} /// The ML-DSA-87 algorithm. pub type MLDSA87 = MLDSA< @@ -712,6 +724,12 @@ impl Algorithm for MLDSA87 { const ALG_NAME: &'static str = ML_DSA_87_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-ml-dsa-87 { sigAlgs 19 } +impl AlgorithmOID for MLDSA87 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 3, 19]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13]; +} /// The core internal implementation of the ML-DSA algorithm. /// This needs to be public for the compiler to be able to find it, but you shouldn't ever diff --git a/crypto/mlkem-lowmemory/src/mlkem.rs b/crypto/mlkem-lowmemory/src/mlkem.rs index 8c41bac..9f5636c 100644 --- a/crypto/mlkem-lowmemory/src/mlkem.rs +++ b/crypto/mlkem-lowmemory/src/mlkem.rs @@ -17,7 +17,7 @@ use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; use bouncycastle_core::traits::{ - Algorithm, Hash, KEMDecapsulator, KEMEncapsulator, RNG, SecurityStrength, XOF, + Algorithm, AlgorithmOID, Hash, KEMDecapsulator, KEMEncapsulator, RNG, SecurityStrength, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::{SHA3_256, SHA3_512, SHAKE256}; @@ -136,6 +136,12 @@ impl Algorithm for MLKEM512 { const ALG_NAME: &'static str = ML_KEM_512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-512 { kems 1 } +impl AlgorithmOID for MLKEM512 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 1]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x01]; +} /// The ML-KEM-768 algorithm. pub type MLKEM768 = MLKEM< @@ -158,6 +164,12 @@ impl Algorithm for MLKEM768 { const ALG_NAME: &'static str = ML_KEM_768_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-768 { kems 2 } +impl AlgorithmOID for MLKEM768 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 2]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02]; +} /// The ML-KEM-1024 algorithm. pub type MLKEM1024 = MLKEM< @@ -180,6 +192,12 @@ impl Algorithm for MLKEM1024 { const ALG_NAME: &'static str = ML_KEM_1024_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-1024 { kems 3 } +impl AlgorithmOID for MLKEM1024 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 3]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x03]; +} /// The core internal implementation of the ML-KEM algorithm. /// This needs to be public for the compiler to be able to find it, but you shouldn't ever diff --git a/crypto/mlkem/src/mlkem.rs b/crypto/mlkem/src/mlkem.rs index adf42da..13c03c0 100644 --- a/crypto/mlkem/src/mlkem.rs +++ b/crypto/mlkem/src/mlkem.rs @@ -145,7 +145,7 @@ use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; use bouncycastle_core::traits::{ - Algorithm, Hash, KEMDecapsulator, KEMEncapsulator, RNG, SecurityStrength, XOF, + Algorithm, AlgorithmOID, Hash, KEMDecapsulator, KEMEncapsulator, RNG, SecurityStrength, XOF, }; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::{SHA3_256, SHA3_512, SHAKE256}; @@ -246,6 +246,12 @@ impl Algorithm for MLKEM512 { const ALG_NAME: &'static str = ML_KEM_512_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-512 { kems 1 } +impl AlgorithmOID for MLKEM512 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 1]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x01]; +} /// The ML-KEM-768 algorithm. pub type MLKEM768 = MLKEM< @@ -266,6 +272,12 @@ impl Algorithm for MLKEM768 { const ALG_NAME: &'static str = ML_KEM_768_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-768 { kems 2 } +impl AlgorithmOID for MLKEM768 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 2]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02]; +} /// The ML-KEM-1024 algorithm. pub type MLKEM1024 = MLKEM< @@ -286,6 +298,12 @@ impl Algorithm for MLKEM1024 { const ALG_NAME: &'static str = ML_KEM_1024_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } +/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-1024 { kems 3 } +impl AlgorithmOID for MLKEM1024 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 3]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x03]; +} /// The core internal implementation of the ML-KEM algorithm. /// This needs to be public for the compiler to be able to find it, but you shouldn't ever diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index 564af40..77060a5 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -111,8 +111,9 @@ impl HashAlgParams for SHA224Params { const OUTPUT_LEN: usize = 28; const BLOCK_LEN: usize = 64; } +/// Assigned by NIST in the Computer Security Objects Register: id-sha224 { hashAlgs 4 } impl AlgorithmOID for SHA224 { - const OID: &'static [u128] = &[2, 16, 840, 1, 101, 3, 4, 2, 4]; + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 4]; const OID_DER: &'static [u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04]; } @@ -129,9 +130,9 @@ impl Algorithm for SHA256Params { const ALG_NAME: &'static str = SHA256_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } -/// Assigned by NIST in the Computer Security Objects Register +/// Assigned by NIST in the Computer Security Objects Register: id-sha256 { hashAlgs 1 } impl AlgorithmOID for SHA256 { - const OID: &'static [u128] = &[2, 16, 840, 1, 101, 3, 4, 2, 1]; + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 1]; const OID_DER: &'static [u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]; } @@ -152,9 +153,9 @@ impl Algorithm for SHA384Params { const ALG_NAME: &'static str = SHA384_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } -/// Assigned by NIST in the Computer Security Objects Register +/// Assigned by NIST in the Computer Security Objects Register: id-sha384 { hashAlgs 2 } impl AlgorithmOID for SHA384 { - const OID: &'static [u128] = &[2, 16, 840, 1, 101, 3, 4, 2, 2]; + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 2]; const OID_DER: &'static [u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02]; } @@ -179,9 +180,9 @@ impl HashAlgParams for SHA512Params { const OUTPUT_LEN: usize = 64; const BLOCK_LEN: usize = 128; } -/// Assigned by NIST in the Computer Security Objects Register +/// Assigned by NIST in the Computer Security Objects Register: id-sha512 { hashAlgs 3 } impl AlgorithmOID for SHA512 { - const OID: &'static [u128] = &[2, 16, 840, 1, 101, 3, 4, 2, 3]; + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 3]; const OID_DER: &'static [u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]; } diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 16262dd..810b5a8 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -139,7 +139,7 @@ #![allow(private_bounds)] use crate::keccak::KeccakSize; -use bouncycastle_core::traits::{Algorithm, HashAlgParams, SecurityStrength}; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams, SecurityStrength}; // imports needed for docs #[allow(unused_imports)] @@ -203,6 +203,12 @@ impl SHA3Params for SHA3_224Params { const SIZE: KeccakSize = KeccakSize::_224; const STATE_TAG: u8 = 1; } +/// Assigned by NIST in the Computer Security Objects Register: id-sha3-224 { hashAlgs 7 } +impl AlgorithmOID for SHA3_224 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 7]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x07]; +} impl HashAlgParams for SHA3_256 { const OUTPUT_LEN: usize = 32; @@ -224,6 +230,12 @@ impl SHA3Params for SHA3_256Params { const SIZE: KeccakSize = KeccakSize::_256; const STATE_TAG: u8 = 2; } +/// Assigned by NIST in the Computer Security Objects Register: id-sha3-256 { hashAlgs 8 } +impl AlgorithmOID for SHA3_256 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 8]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08]; +} #[derive(Clone)] pub struct SHA3_384Params; @@ -245,6 +257,12 @@ impl SHA3Params for SHA3_384Params { const SIZE: KeccakSize = KeccakSize::_384; const STATE_TAG: u8 = 3; } +/// Assigned by NIST in the Computer Security Objects Register: id-sha3-384 { hashAlgs 9 } +impl AlgorithmOID for SHA3_384 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 9]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x09]; +} #[derive(Clone)] pub struct SHA3_512Params; @@ -266,6 +284,12 @@ impl SHA3Params for SHA3_512Params { const SIZE: KeccakSize = KeccakSize::_512; const STATE_TAG: u8 = 4; } +/// Assigned by NIST in the Computer Security Objects Register: id-sha3-512 { hashAlgs 10 } +impl AlgorithmOID for SHA3_512 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 10]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0a]; +} trait SHAKEParams: Algorithm { const SIZE: KeccakSize; @@ -282,6 +306,12 @@ impl SHAKEParams for SHAKE128Params { const SIZE: KeccakSize = KeccakSize::_128; const STATE_TAG: u8 = 5; } +/// Assigned by NIST in the Computer Security Objects Register: id-shake128 { hashAlgs 11 } +impl AlgorithmOID for SHAKE128 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 11]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0b]; +} #[derive(Clone)] pub struct SHAKE256Params; @@ -293,3 +323,9 @@ impl SHAKEParams for SHAKE256Params { const SIZE: KeccakSize = KeccakSize::_256; const STATE_TAG: u8 = 6; } +/// Assigned by NIST in the Computer Security Objects Register: id-shake256 { hashAlgs 12 } +impl AlgorithmOID for SHAKE256 { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 12]; + const OID_DER: &'static [u8] = + &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0c]; +} diff --git a/rustfmt.toml b/rustfmt.toml index f464555..7402b5d 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,4 @@ hex_literal_case = "Upper" max_width = 100 short_array_element_width_threshold = 24 -unstable_features = true use_small_heuristics = "Max" From b1e54836143491b36ba60112c21530286fc74ce4 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Thu, 9 Jul 2026 16:55:51 -0500 Subject: [PATCH 19/26] progress at applying #to all crates --- alpha_0.1.2_release_notes.md | 6 +-- crypto/base64/src/lib.rs | 8 ++++ crypto/core/src/errors.rs | 55 ++++++++++++++++++++++++++++ crypto/core/src/key_material.rs | 10 ++++- crypto/core/src/lib.rs | 1 + crypto/core/src/suspendable_state.rs | 7 +++- crypto/core/src/traits.rs | 54 +++++++++++++++++++++++++-- 7 files changed, 129 insertions(+), 12 deletions(-) diff --git a/alpha_0.1.2_release_notes.md b/alpha_0.1.2_release_notes.md index 327f19c..f60dbb3 100644 --- a/alpha_0.1.2_release_notes.md +++ b/alpha_0.1.2_release_notes.md @@ -22,8 +22,6 @@ appropriate. * Probably it makes sense to leave Hex and Base64 as requiring std; ... or maybe add a no_std version that uses fixed-sized blocks? -* Make this build on the stable compiler. IE Remove the rust-toolchain.toml file that builds with nightly. Will require - some refactoring. * Create a cargo feature #[cfg(feature='rng')] and put it around things like keygen that takes an rng so that the build dependency on bouncycastle_rng is optional. * Factories ... Are they worth it? Michael Richardson says Very Yes. If we are keeping them, then we need a serious @@ -31,8 +29,7 @@ static one-shot APIs. * Deal with as many of the inline TODOs as possible * Close all open github issues and document them in this file. -* After everything is merged, circle back to crucible, and make sure that the harness still works (and maybe remove the - nightly build toolchain) +* After everything is merged, circle back to crucible, and make sure that the harness still works * Search for all the uses of .unwrap() in non-test code and replace each with either a comment or an expect with a meaningful error string. @@ -59,6 +56,7 @@ .drop_hazardous_operations(), it now uses a closure-based do_hazardous_operations(). Github issue #39. * Renamed KeyMaterial::KeyType's and deleted KeyMaterial::concatenate in order to give a better intuition and FIPS-alignment. +* Removed the dependence on nightly / experimental compiler features; the library now buildds on stable. * Github issues resolved: * #6: https://github.com/bcgit/bc-rust/issues/6, thanks to Q. T. Felix (github: @Quant-TheodoreFelix) * #10: https://github.com/bcgit/bc-rust/issues/10, thanks to Nicola Tuveri (github: @romen) \ No newline at end of file diff --git a/crypto/base64/src/lib.rs b/crypto/base64/src/lib.rs index b3e57d8..057e99f 100644 --- a/crypto/base64/src/lib.rs +++ b/crypto/base64/src/lib.rs @@ -81,6 +81,9 @@ // /// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=" // URLSafe, +#![forbid(unsafe_code)] +#![forbid(missing_docs)] + use bouncycastle_utils::ct::Condition; /// One-shot encode from bytes to a base64-encoded string using a constant-time implementation. @@ -93,6 +96,7 @@ pub fn decode>(input: T) -> Result, Base64Error> { Base64Decoder::new(true).do_final(input) } +/// Return type for errors relating to Base64 encoding and decoding. #[derive(Debug)] pub enum Base64Error { /// the do_update() method must not be called on a block that contains padding. @@ -141,6 +145,8 @@ impl Base64Encoder { ret as u8 } + /// Streaming API that performs Base64 encoding of the provided input, but does not apply + /// the final padding and will hold an incomplete block while waiting for more input. pub fn do_update>(&mut self, input: T) -> String { let inref = input.as_ref(); let mut out: Vec = Vec::with_capacity(inref.len() * 4 / 3 + 4); @@ -245,6 +251,8 @@ impl Base64Decoder { ret as u8 } + /// Streaming API that performs Base64 encoding of the provided input, but does not apply + /// the final padding and will hold an incomplete block while waiting for more input. pub fn do_update>(&mut self, input: T) -> Result, Base64Error> { self.decode_internal(input, true) } diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index a079aad..1f614dc 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -1,56 +1,99 @@ +//! Core error types to be used in Result return types. +//! Errors defined in this module are typically one-to-one with traits defined in [crate::traits]. +//! +//! Most errors are self-explanatory, but additional description is available on some. + +/// #[derive(Debug)] pub enum HashError { + /// GenericError(&'static str), + /// InvalidLength(&'static str), + /// InvalidState(&'static str), + /// InvalidInput(&'static str), + /// KeyMaterialError(KeyMaterialError), } +/// #[derive(Debug)] pub enum KeyMaterialError { + /// ActingOnZeroizedKey, + /// GenericError(&'static str), + /// HazardousOperationNotPermitted, + /// InputDataLongerThanKeyCapacity, + /// InvalidKeyType(&'static str), + /// InvalidLength, + /// SecurityStrength(&'static str), } +/// #[derive(Debug)] pub enum KDFError { + /// GenericError(&'static str), + /// HashError(HashError), + /// InvalidLength(&'static str), + /// KeyMaterialError(KeyMaterialError), + /// MACError(MACError), } +/// #[derive(Debug)] pub enum KEMError { + /// GenericError(&'static str), + /// ConsistencyCheckFailed(&'static str), + /// EncodingError(&'static str), + /// DecapsulationFailed, + /// DecodingError(&'static str), + /// KeyGenError(&'static str), + /// KeyMaterialError(KeyMaterialError), + /// LengthError(&'static str), + /// RNGError(RNGError), } +/// #[derive(Debug)] pub enum MACError { + /// GenericError(&'static str), + /// HashError(HashError), + /// InvalidLength(&'static str), + /// InvalidState(&'static str), + /// KeyMaterialError(KeyMaterialError), } +/// #[derive(Debug)] pub enum RNGError { + /// GenericError(&'static str), /// Attempting to extract output before the RNG has been seeded. @@ -69,9 +112,11 @@ pub enum RNGError { /// than the algorithm requires. SecurityStrengthInsufficientForAlgorithm, + /// KeyMaterialError(KeyMaterialError), } +/// #[derive(Debug)] pub enum SuspendableError { /// The serialized state was produced by a library version incompatible with this one. @@ -80,16 +125,26 @@ pub enum SuspendableError { InvalidData, } +/// #[derive(Debug)] pub enum SignatureError { + /// GenericError(&'static str), + /// ConsistencyCheckFailed(), + /// EncodingError(&'static str), + /// DecodingError(&'static str), + /// KeyGenError(&'static str), + /// KeyMaterialError(KeyMaterialError), + /// LengthError(&'static str), + /// SignatureVerificationFailed, + /// RNGError(RNGError), } diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index ece984c..4ea99c3 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -59,9 +59,11 @@ use core::fmt; /// Sometimes you just need a zero-length dummy key. pub type KeyMaterial0 = KeyMaterial<0>; - +/// Named type for a 128-bit (16-byte) key, for convenience. pub type KeyMaterial128 = KeyMaterial<16>; +/// Named type for a 256-bit (32-byte) key, for convenience. pub type KeyMaterial256 = KeyMaterial<32>; +/// Named type for a 512-bit (64-byte) key, for convenience. pub type KeyMaterial512 = KeyMaterial<64>; /// A helper class used across the bc-rust.test library to hold bytes-like key material. @@ -153,6 +155,7 @@ pub trait KeyMaterialTrait: KeyMaterialInternalTrait { /// [do_hazardous_operations] closure. fn set_key_len(&mut self, key_len: usize) -> Result<(), KeyMaterialError>; + /// Returns the [KeyType] of this KeyMaterial object. fn key_type(&self) -> KeyType; /// Sets (or safely converts) the [KeyType] of this KeyMaterial object. @@ -225,6 +228,7 @@ impl Secret for KeyMaterial {} // `SerializableState` implementations (see the `TryFrom` impl below). Pin each value to its // variant name: reordering variants is fine, but never reuse or renumber an existing discriminant, // or previously-serialized states will be misread. +/// #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] pub enum KeyType { @@ -279,6 +283,8 @@ impl Default for KeyMaterial { } impl KeyMaterial { + /// Creates a new empty instance (key_len = 0, key_type = Zeroized). + /// If you want a properly populated instance, use [KeyMaterial::from_rng]. pub fn new() -> Self { Self { buf: [0u8; KEY_LEN], @@ -289,7 +295,7 @@ impl KeyMaterial { } } - /// Create a new instance of KeyMaterial containing random bytes from the provided random number generator. + /// Creates a new instance of KeyMaterial containing random bytes from the provided random number generator. pub fn from_rng(rng: &mut impl RNG) -> Result { let mut key = Self::new(); diff --git a/crypto/core/src/lib.rs b/crypto/core/src/lib.rs index 66bff60..a75792d 100644 --- a/crypto/core/src/lib.rs +++ b/crypto/core/src/lib.rs @@ -4,6 +4,7 @@ // #![no_std] #![forbid(unsafe_code)] +#![forbid(missing_docs)] pub mod errors; pub mod key_material; diff --git a/crypto/core/src/suspendable_state.rs b/crypto/core/src/suspendable_state.rs index a668676..e646326 100644 --- a/crypto/core/src/suspendable_state.rs +++ b/crypto/core/src/suspendable_state.rs @@ -6,13 +6,16 @@ use crate::errors::SuspendableError; /// /// The field declaration order matters: the derived [`Ord`]/[`PartialOrd`] compare fields /// lexicographically in declaration order, which is exactly semantic-version precedence. +/// A semantic version can often also take a suffix, e.g. "alpha", "beta", "rc1", etc. +/// We're not going to model that here because it's not useful for versioning serialized states. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct SemVer { + /// pub major: u8, + /// pub minor: u8, + /// pub patch: u8, - // A semantic version can often also take a suffix, e.g. "alpha", "beta", "rc1", etc. - // We're not going to model that here because it's not useful for versioning serialized states. } impl From<[u8; 3]> for SemVer { diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 5cc7087..49e672f 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -12,9 +12,13 @@ use crate::key_material::KeyMaterial; use crate::key_material::KeyType; // end of imports needed for docs -/// All algorithms carry a name and a max security strength tha they can support. +/// Metadata about a cryptographic algorithm. pub trait Algorithm { + /// String name for the algorithm, used consistently across the library. const ALG_NAME: &'static str; + /// Maximum security strength supported by the algorithm. + /// In other words, this algorithm can produce outputs up to this security strength, + /// but may produce outputs with lower security strength, for example, if asked to truncate. const MAX_SECURITY_STRENGTH: SecurityStrength; } @@ -26,6 +30,12 @@ pub trait AlgorithmOID { const OID_DER: &'static [u8]; } +/// A hash function is a cryptographic primitive that takes an input of any length and produces a fixed-size output. +/// Formally: `H: {0,1}^* -> {0,1}^n`. +/// A cryptographic hash function will typically satisfy several security properties, including: +/// * Collision resistance: finding two inputs that yield the same output is computationally difficult. +/// * Preimage resistance: from a given output, finding an input that generates it is computationally difficult. +/// * Second preimage resistance: given an input, finding another input that yields the same output is computationally difficult. pub trait Hash: Algorithm + Default { /// The size of the internal block in bits -- needed by functions such as HMAC to compute security parameters. fn block_bitlen(&self) -> usize; @@ -89,8 +99,13 @@ pub trait Hash: Algorithm + Default { fn max_security_strength(&self) -> SecurityStrength; } +/// Standard parameters for a hash function. pub trait HashAlgParams: Algorithm { + /// The fixed output length of the hash function. const OUTPUT_LEN: usize; + /// The internal block length of the hash function, which is often used as a meta-parameter for + /// determining the security strength of the hash function since this limits the internal + /// collision resistance of the hash function. const BLOCK_LEN: usize; } @@ -372,6 +387,7 @@ pub trait MAC: Sized { /// 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]); + /// Finish absorbing input and produce the MAC value. fn do_final(self) -> Vec; /// Depending on the underlying MAC implementation, NIST may require that the library enforce @@ -395,15 +411,32 @@ pub trait MAC: Sized { fn max_security_strength(&self) -> SecurityStrength; } -// The explicit `#[repr(u8)]` discriminants are the stable on-the-wire encoding used by -// `SerializableState` implementations (see the `TryFrom` impl below). +/// A general indicator used across the library for marking the security level of a cryptographic primitive, +/// and for tracking the security level of the algorithms that interacted with a given piece of data. +/// For example, if a KDF at the 128-bit security strength is used to produce a 512-bit key, that key +/// will also be tagged as having a 128-bit security strength. +/// +/// Some functions across the library may reject or behave differently based on the security strength +/// of the inputs they are given. For example a `keygen_from_seed()` may reject a seed taged at a lower +/// security strength than the one required by the algorithm, or it may proceed, but lower its own +/// advertised security strength accordingly -- each cryptographic primitive may have additional detail. +// Dev note: The explicit `#[repr(u8)]` discriminants are the stable on-the-wire encoding used by +// `SerializableState` implementations (see the corresponding `TryFrom` impl below). +// If additional strength levels are added in the future, they can be placed into the enum in +// any order, but should use currently unassigned values (unless you're doing this on a MAJOR or MINOR +// release as a breaking change). #[derive(Eq, PartialEq, PartialOrd, Clone, Copy, Debug)] #[repr(u8)] pub enum SecurityStrength { + /// None = 0, + /// _112bit = 1, + /// _128bit = 2, + /// _192bit = 3, + /// _256bit = 4, } @@ -440,10 +473,13 @@ impl SecurityStrength { } } + /// Rounds down to the closest supported security strength. + /// For example, 15 bytes (120-bits) is rounded down to 112-bit. pub fn from_bytes(bytes: usize) -> Self { Self::from_bits(bytes * 8) } + /// Outputs the security strength in bits for easier computation. pub fn as_int(&self) -> u32 { match self { Self::None => 0, @@ -470,10 +506,14 @@ 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>; + /// Provide additional key material to be mixed in to the existing RNG instance. + /// The exact behaviour will be implementation-specific, but this is intended for injecting + /// additional entropy, not as the primary method of seeding the RNG. fn add_seed_keymaterial( &mut self, additional_seed: &dyn KeyMaterialTrait, ) -> Result<(), RNGError>; + /// Returns the next random 32-bit integer. fn next_int(&mut self) -> Result; /// Returns the number of requested bytes. @@ -483,9 +523,12 @@ pub trait RNG { /// The entire output buffer is zeroized before the random bytes are written. fn next_bytes_out(&mut self, out: &mut [u8]) -> Result; + /// Fill the provided [KeyMaterial] with random bytes. fn fill_keymaterial_out(&mut self, out: &mut dyn KeyMaterialTrait) -> Result; /// Returns the Security Strength of this RNG. + // todo: we should do a refactor to make [Algorithm] be a `security_strength()` function instead of constant, + // then have `RNG: Algorithm`, then delete this function. fn security_strength(&self) -> SecurityStrength; } @@ -758,7 +801,7 @@ pub trait SignatureVerifier< /// On failure, returns Err([SignatureError::SignatureVerificationFailed]); may also return other types of [SignatureError] as appropriate (such as for invalid-length inputs). fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError>; - /* streaming verification API */ + /// streaming verification API fn verify_init(pk: &PK, ctx: Option<&[u8]>) -> Result; // todo: make this a AsRef<[u8]> ? @@ -798,6 +841,7 @@ pub trait XOF: Default { /// The entire output buffer is zeroized before the output is written. fn hash_xof_out(self, data: &[u8], output: &mut [u8]) -> usize; + /// Absorb some amount of input. fn absorb(&mut self, data: &[u8]); /// Switches to squeezing. @@ -829,5 +873,7 @@ pub trait XOF: Default { ) -> Result<(), HashError>; /// Returns the maximum security strength that this KDF is capable of supporting, based on the underlying primitives. + // todo: we should do a refactor to make [Algorithm] be a `security_strength()` function instead of constant, + // then have `RNG: Algorithm`, then delete this function. fn max_security_strength(&self) -> SecurityStrength; } From ed125466c77ca19be6552d6f576522bc8a330531 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Thu, 9 Jul 2026 18:56:15 -0500 Subject: [PATCH 20/26] missing_docs for factory --- crypto/factory/src/hash_factory.rs | 10 +++++++++- crypto/factory/src/kdf_factory.rs | 10 +++++++++- crypto/factory/src/lib.rs | 18 +++++++++++++----- crypto/factory/src/mac_factory.rs | 20 ++++++++++++++++---- crypto/factory/src/rng_factory.rs | 4 +++- crypto/factory/src/xof_factory.rs | 7 ++++++- 6 files changed, 56 insertions(+), 13 deletions(-) diff --git a/crypto/factory/src/hash_factory.rs b/crypto/factory/src/hash_factory.rs index 34793de..558a800 100644 --- a/crypto/factory/src/hash_factory.rs +++ b/crypto/factory/src/hash_factory.rs @@ -35,16 +35,24 @@ use bouncycastle_sha2::{SHA224_NAME, SHA256_NAME, SHA384_NAME, SHA512_NAME}; use bouncycastle_sha3 as sha3; use bouncycastle_sha3::{SHA3_224_NAME, SHA3_256_NAME, SHA3_384_NAME, SHA3_512_NAME}; -/// All members must impl Hash. +/// Wrapper object for all algorithms that impl [Hash]. /// Note: no SHAKE because SHAKE is not NIST approved as a hash function. See FIPS 202 section A.2. pub enum HashFactory { + /// SHA224(sha2::SHA224), + /// SHA256(sha2::SHA256), + /// SHA384(sha2::SHA384), + /// SHA512(sha2::SHA512), + /// SHA3_224(sha3::SHA3_224), + /// SHA3_256(sha3::SHA3_256), + /// SHA3_384(sha3::SHA3_384), + /// SHA3_512(sha3::SHA3_512), } diff --git a/crypto/factory/src/kdf_factory.rs b/crypto/factory/src/kdf_factory.rs index 50aa274..0edac01 100644 --- a/crypto/factory/src/kdf_factory.rs +++ b/crypto/factory/src/kdf_factory.rs @@ -58,17 +58,25 @@ use bouncycastle_sha3::{ SHA3_224_NAME, SHA3_256_NAME, SHA3_384_NAME, SHA3_512_NAME, SHAKE128_NAME, SHAKE256_NAME, }; -// All members must impl KDF. +/// Wrapper object for all algorithms that impl [KDF]. pub enum KDFFactory { + /// #[allow(non_camel_case_types)] HKDF_SHA256(hkdf::HKDF_SHA256), + /// #[allow(non_camel_case_types)] HKDF_SHA512(hkdf::HKDF_SHA512), + /// SHA3_224(sha3::SHA3_224), + /// SHA3_256(sha3::SHA3_256), + /// SHA3_384(sha3::SHA3_384), + /// SHA3_512(sha3::SHA3_512), + /// SHAKE128(sha3::SHAKE128), + /// SHAKE256(sha3::SHAKE256), } diff --git a/crypto/factory/src/lib.rs b/crypto/factory/src/lib.rs index 7c0a12b..9a55ddc 100644 --- a/crypto/factory/src/lib.rs +++ b/crypto/factory/src/lib.rs @@ -24,6 +24,11 @@ //! get the either the default algorithm or the default algorithm at the 128-bit or 256-bit security level. //! It also exposes [AlgorithmFactory::new] which can be used to create an instance of the algorithm //! by string name according to the string constants associated with the respective factory type. +//! +//! This crate compiles with STD; ie it is explicitly not tagged as `no_std` and it makes use of `Vec` and other +//! dynamically-sized nice things. + +#![forbid(missing_docs)] use bouncycastle_core::errors::MACError; @@ -34,13 +39,19 @@ pub mod rng_factory; pub mod xof_factory; /*** String constants ***/ +/// pub const DEFAULT: &str = "Default"; +/// pub const DEFAULT_128_BIT: &str = "Default128Bit"; +/// pub const DEFAULT_256_BIT: &str = "Default256Bit"; +/// Top-level error type for Factories. #[derive(Debug)] pub enum FactoryError { + /// MACError(MACError), + /// UnsupportedAlgorithm(String), } @@ -50,17 +61,14 @@ impl From for FactoryError { } } +/// pub trait AlgorithmFactory: Sized + Default { - // Get the default configured algorithm. - // Not implemented because all factories MUST impl Default. - // fn default() -> Self; - /// Get the default configured algorithm at the 128-bit security level. fn default_128_bit() -> Self; /// Get the default configured algorithm at the 256-bit security level. fn default_256_bit() -> Self; - /// Create an instance of the algorithm by name. + /// Get an instance of the algorithm by name. fn new(alg_name: &str) -> Result; } diff --git a/crypto/factory/src/mac_factory.rs b/crypto/factory/src/mac_factory.rs index 141c59f..614f181 100644 --- a/crypto/factory/src/mac_factory.rs +++ b/crypto/factory/src/mac_factory.rs @@ -83,39 +83,51 @@ use bouncycastle_sha2 as sha2; use bouncycastle_sha3 as sha3; /*** Defaults ***/ +/// pub const DEFAULT_MAC_NAME: &str = HMAC_SHA256_NAME; +/// pub const DEFAULT_128BIT_MAC_NAME: &str = HMAC_SHA256_NAME; +/// pub const DEFAULT_256BIT_MAC_NAME: &str = HMAC_SHA256_NAME; #[allow(non_camel_case_types)] +/// Wrapper object for all algorithms that impl [MAC]. /// MACFactory deviates from the usual AlgorithmFactory trait because MAC objects do not have a no-arg constructor; /// instead they have a constructor that takes a [KeyMaterialTrait] and can return an error. pub enum MACFactory { - // All members must impl MAC. + /// HMAC_SHA224(hmac::HMAC), + /// HMAC_SHA256(hmac::HMAC), + /// HMAC_SHA384(hmac::HMAC), + /// HMAC_SHA512(hmac::HMAC), + /// HMAC_SHA3_224(hmac::HMAC), + /// HMAC_SHA3_256(hmac::HMAC), + /// HMAC_SHA3_384(hmac::HMAC), + /// HMAC_SHA3_512(hmac::HMAC), } impl MACFactory { + /// Get the default MAC algorithm. pub fn default(key: &impl KeyMaterialTrait) -> Result { Self::new(DEFAULT_MAC_NAME, key) } - + /// Get the default 128-bit MAC algorithm. pub fn default_128_bit(key: &impl KeyMaterialTrait) -> Result { Self::new(DEFAULT_128BIT_MAC_NAME, key) } - + /// Get the default 256-bit MAC algorithm. pub fn default_256_bit(key: &impl KeyMaterialTrait) -> Result { Self::new(DEFAULT_256BIT_MAC_NAME, key) } - + /// Get an instance of the algorithm by name. pub fn new(alg_name: &str, key: &impl KeyMaterialTrait) -> Result { match alg_name { DEFAULT => Self::default(key), diff --git a/crypto/factory/src/rng_factory.rs b/crypto/factory/src/rng_factory.rs index 4e542e4..7ac0636 100644 --- a/crypto/factory/src/rng_factory.rs +++ b/crypto/factory/src/rng_factory.rs @@ -50,10 +50,12 @@ use bouncycastle_core::traits::{RNG, SecurityStrength}; use bouncycastle_rng as rng; use bouncycastle_rng::{HASH_DRBG_SHA256_NAME, HASH_DRBG_SHA512_NAME}; -/// All members must impl RNG. +/// Wrapper object for all algorithms that impl [RNG]. pub enum RNGFactory { + /// #[allow(non_camel_case_types)] HashDRBG_SHA256(rng::HashDRBG_SHA256), + /// #[allow(non_camel_case_types)] HashDRBG_SHA512(rng::HashDRBG_SHA512), } diff --git a/crypto/factory/src/xof_factory.rs b/crypto/factory/src/xof_factory.rs index e35e86e..47c68b4 100644 --- a/crypto/factory/src/xof_factory.rs +++ b/crypto/factory/src/xof_factory.rs @@ -41,13 +41,18 @@ use bouncycastle_sha3 as sha3; use bouncycastle_sha3::{SHAKE128_NAME, SHAKE256_NAME}; /*** Defaults ***/ +/// pub const DEFAULT_XOF_NAME: &str = SHAKE128_NAME; +/// pub const DEFAULT_128BIT_XOF_NAME: &str = SHAKE128_NAME; +/// pub const DEFAULT_256BIT_XOF_NAME: &str = SHAKE256_NAME; -// All members must impl XOF. +/// Wrapper object for all algorithms that impl [XOF]. pub enum XOFFactory { + /// SHAKE128(sha3::SHAKE128), + /// SHAKE256(sha3::SHAKE256), } From 09bf94cf68c873d271f1380f90f3bec306472de4 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Fri, 10 Jul 2026 07:45:21 -0500 Subject: [PATCH 21/26] progress on missing_docs. DOES NOT COMPILE --- crypto/base64/src/lib.rs | 8 ++++---- crypto/hex/src/lib.rs | 5 +++++ crypto/hkdf/src/lib.rs | 24 ++++++++++++++++++++---- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/crypto/base64/src/lib.rs b/crypto/base64/src/lib.rs index 057e99f..41a97ee 100644 --- a/crypto/base64/src/lib.rs +++ b/crypto/base64/src/lib.rs @@ -99,11 +99,11 @@ pub fn decode>(input: T) -> Result, Base64Error> { /// Return type for errors relating to Base64 encoding and decoding. #[derive(Debug)] pub enum Base64Error { - /// the do_update() method must not be called on a block that contains padding. + /// The [Base64Decoder::do_update] method must not be called on a block that contains padding. /// If this error is returned, then the provided input has not been processed and the caller must instead - /// pass the same input to do_final(). Note that do_final() is tolerant of incomplete padding blocks, - /// so even if an additional padding character is contained in the next chunk of input, do_final() will still produce - /// the correct output -- ie any additional chunks held by the caller can be discarded. + /// pass the same input to [Base64Decoder::do_final]. Note that do_final() is tolerant of incomplete padding blocks, + /// so even if an additional padding character is contained in the next chunk of input, do_final() + /// will still produce the correct output -- ie any additional chunks held by the caller can be discarded. PaddingEnconteredDuringDoUpdate, /// Input contained a character that was not in the base64 alphabet. The index of the illegal character is included in the output. diff --git a/crypto/hex/src/lib.rs b/crypto/hex/src/lib.rs index 6fcf787..4e89caa 100644 --- a/crypto/hex/src/lib.rs +++ b/crypto/hex/src/lib.rs @@ -21,13 +21,18 @@ //! The decoder ignores whitespace and "\x". #![forbid(unsafe_code)] +#![forbid(missing_docs)] use bouncycastle_utils::ct::Condition; +/// Return type for errors relating to Hex encoding and decoding. #[derive(Debug)] pub enum HexError { + /// Invalid hex character encountered at the given index. InvalidHexCharacter(usize), + /// Since hex encodes each byte as two characters, the input must have an even length. OddLengthInput, + /// InsufficientOutputBufferSize, } diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 13ec778..d0a675d 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -191,6 +191,7 @@ //! ``` #![forbid(unsafe_code)] +#![forbid(missing_docs)] use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError, SuspendableError}; use bouncycastle_core::key_material; @@ -213,19 +214,26 @@ use bouncycastle_core::traits::XOF; /*** Constants ***/ // Slightly hacky, but set this to accommodate the underlying hash primitive with the largest output size. // Would be better to somehow pull that at compile time from H, but I'm not sure how to do that. +// todo can I delete this? const HMAC_BLOCK_LEN: usize = 64; /*** String constants ***/ +/// pub const HKDF_SHA256_NAME: &str = "HKDF-SHA256"; +/// pub const HKDF_SHA512_NAME: &str = "HKDF-SHA512"; /*** Types ***/ +/// Public type for HKDF using SHA256. #[allow(non_camel_case_types)] pub type HKDF_SHA256 = HKDF; +/// Public type for HKDF using SHA512. #[allow(non_camel_case_types)] pub type HKDF_SHA512 = HKDF; +/// Internal struct for HKDF. +/// Can, in theory, be instantiated with hash functions other than the ones provided by this crate (even custom ones). #[derive(Clone)] pub struct HKDF { // Optional because we can't construct an HMAC until they give us a key @@ -336,6 +344,7 @@ impl Default for HKDF { } impl HKDF { + /// Creates a new, uninstantiated HKDF object. pub fn new() -> Self { Self { hmac: None, entropy: HkdfEntropyTracker::new(), state: HkdfStates::Uninitialized } } @@ -379,10 +388,11 @@ impl HKDF { } /// Same as [HKDF::extract], but writes the output to a provided KeyMaterial buffer. + /// Note that the provided KeyMaterial must be correctly sized to the hash function output length. pub fn extract_out( salt: &impl KeyMaterialTrait, ikm: &impl KeyMaterialTrait, - prk: &mut impl KeyMaterialTrait, + prk: &mut KeyMaterial, ) -> Result { // PRK = HMAC-Hash(salt, IKM) @@ -625,18 +635,24 @@ impl HKDF { Ok(0) } + /// Finish the HKDF-Extract phase and produce the output `prk`. #[allow(non_snake_case)] - pub fn do_extract_final(self) -> Result { + pub fn do_extract_final(self) -> Result, MACError> { let mut okm = KeyMaterial::::new(); self.do_extract_final_out(&mut okm)?; Ok(okm) } + /// Finish the HKDF-Extract phase and fill the provided `prk`. + /// Note that the provided KeyMaterial must be correctly sized to the HMAC block length. #[allow(non_snake_case)] - pub fn do_extract_final_out(self, okm: &mut impl KeyMaterialTrait) -> Result { + pub fn do_extract_final_out( + self, + okm: &mut KeyMaterial, + ) -> Result { if self.state == HkdfStates::Uninitialized { return Err(MACError::InvalidState( - "Must call do_extract_init() before calling do_extract_complete().", + "Must call do_extract_init() before calling do_extract_final().", )); }; debug_assert!(self.hmac.is_some()); From 4624785c7d66c70c36b1b2a27b1b9961db61cd12 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Fri, 10 Jul 2026 09:07:32 -0500 Subject: [PATCH 22/26] hkdf crate now has \#![forbit(missing_docs)] --- crypto/hkdf/benches/hkdf_benches.rs | 2 +- crypto/hkdf/src/lib.rs | 46 ++++++++++++++++++----------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/crypto/hkdf/benches/hkdf_benches.rs b/crypto/hkdf/benches/hkdf_benches.rs index 72e3d77..f9f3cb8 100644 --- a/crypto/hkdf/benches/hkdf_benches.rs +++ b/crypto/hkdf/benches/hkdf_benches.rs @@ -9,7 +9,7 @@ use std::hint::black_box; fn bench_hkdf_sha256(c: &mut Criterion) { let mut data_block = [0_u8; 1024]; - let mut output = KeyMaterial256::new(); + let mut output = KeyMaterial512::new(); rng::DefaultRNG::default().next_bytes_out(&mut data_block).unwrap(); let key = KeyMaterial256::from_bytes_as_type(&data_block[..32], KeyType::MACKey).unwrap(); diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index d0a675d..cbe332b 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -212,10 +212,16 @@ use bouncycastle_core::traits::XOF; // end doc-only imports /*** Constants ***/ -// Slightly hacky, but set this to accommodate the underlying hash primitive with the largest output size. -// Would be better to somehow pull that at compile time from H, but I'm not sure how to do that. -// todo can I delete this? -const HMAC_BLOCK_LEN: usize = 64; +/// The size of the output key material from the HKDF-Extract phase `prk`, in bytes. +/// This has been sized so that the output KeyMaterial has enough capacity to accommodate the +/// underlying hash primitive with the largest output size. +/// If the given hash function has a smaller output size, then the output KeyMaterial will be +/// under-full (ie have a key_len that does not use its full capacity). +/// TODO: This is a dirty dirty hack because correctly sizing the output key +/// really requires the generic_const_exprs feature, which is currently only available on +/// nightly Rust, and not on stable. Once they merge that feature, we will be able to get rid of this +/// and declare `prk: &mut KeyMaterial` instead of this hack. +pub const MAX_HMAC_OUTPUT_LEN: usize = 64; /*** String constants ***/ @@ -382,7 +388,7 @@ impl HKDF { salt: &impl KeyMaterialTrait, ikm: &impl KeyMaterialTrait, ) -> Result { - let mut prk = KeyMaterial::::new(); + let mut prk = KeyMaterial::::new(); Self::extract_out(salt, ikm, &mut prk)?; Ok(prk) } @@ -392,7 +398,7 @@ impl HKDF { pub fn extract_out( salt: &impl KeyMaterialTrait, ikm: &impl KeyMaterialTrait, - prk: &mut KeyMaterial, + prk: &mut KeyMaterial, ) -> Result { // PRK = HMAC-Hash(salt, IKM) @@ -474,12 +480,15 @@ impl HKDF { // Could potentially speed this up by unrolling T(0) and T(1) - // We're gonna have to kludge the prk key type to MACKey to make HMAC happy, but we'll set it back to the original value afterwards. - let prk_as_mac_key = - KeyMaterial::::from_bytes_as_type(prk.ref_to_bytes(), KeyType::MACKey)?; + // We're gonna have to kludge the prk key type to MACKey to make HMAC happy, + // but we'll set it back to the original value afterwards. + let prk_as_mac_key = KeyMaterial::::from_bytes_as_type( + prk.ref_to_bytes(), + KeyType::MACKey, + )?; #[allow(non_snake_case)] - let mut T = [0u8; HMAC_BLOCK_LEN]; + let mut T = [0u8; MAX_HMAC_OUTPUT_LEN]; let mut t_len: usize = 0; let mut i = 1u8; @@ -637,10 +646,10 @@ impl HKDF { /// Finish the HKDF-Extract phase and produce the output `prk`. #[allow(non_snake_case)] - pub fn do_extract_final(self) -> Result, MACError> { - let mut okm = KeyMaterial::::new(); - self.do_extract_final_out(&mut okm)?; - Ok(okm) + pub fn do_extract_final(self) -> Result, MACError> { + let mut prk = KeyMaterial::::new(); + self.do_extract_final_out(&mut prk)?; + Ok(prk) } /// Finish the HKDF-Extract phase and fill the provided `prk`. @@ -648,7 +657,7 @@ impl HKDF { #[allow(non_snake_case)] pub fn do_extract_final_out( self, - okm: &mut KeyMaterial, + prk: &mut KeyMaterial, ) -> Result { if self.state == HkdfStates::Uninitialized { return Err(MACError::InvalidState( @@ -660,7 +669,7 @@ impl HKDF { let output_key_type = self.entropy.get_output_key_type(); // need to do this above self.hmac.do_final_out, which will consume self. let mut bytes_written = 0; - key_material::do_hazardous_operations(okm, |okm| { + key_material::do_hazardous_operations(prk, |okm| { bytes_written = self .hmac .unwrap() @@ -680,6 +689,9 @@ impl HKDF { ) } })?; + // By RFC5869, the output size of prk is HashLen denotes the length of the + // hash function output in octets + debug_assert_eq!(prk.key_len(), H::OUTPUT_LEN); Ok(bytes_written) } } @@ -769,7 +781,7 @@ impl KDF for HKDF { entropy.credit_entropy(*key); } } - let mut prk = KeyMaterial::::new(); + let mut prk = KeyMaterial::::new(); _ = hkdf.do_extract_final_out(&mut prk)?; let bytes_written = HKDF::::expand_out(&prk, additional_input, output_key.capacity(), output_key)?; From e265aae0f720d71c33020ea98275c2c5a3462cd8 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Fri, 10 Jul 2026 10:13:17 -0500 Subject: [PATCH 23/26] hmac crate now tagged with \#![forbid(missing_docs)] --- crypto/hmac/src/lib.rs | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 41e9f7e..99fbc12 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -181,6 +181,7 @@ //! ``` #![forbid(unsafe_code)] +#![forbid(missing_docs)] use bouncycastle_core::errors::{KeyMaterialError, MACError, SuspendableError}; use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; @@ -213,6 +214,7 @@ pub const HMAC_SHA3_384_NAME: &str = "HMAC-SHA3-384"; pub const HMAC_SHA3_512_NAME: &str = "HMAC-SHA3-512"; /*** Type aliases ***/ +/// Public type for HMAC using SHA224. #[allow(non_camel_case_types)] pub type HMAC_SHA224 = HMAC; impl Algorithm for HMAC_SHA224 { @@ -225,6 +227,7 @@ impl AlgorithmOID for HMAC_SHA224 { const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x08]; } +/// Public type for HKDF using SHA256. #[allow(non_camel_case_types)] pub type HMAC_SHA256 = HMAC; impl Algorithm for HMAC_SHA256 { @@ -237,6 +240,7 @@ impl AlgorithmOID for HMAC_SHA256 { const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09]; } +/// Public type for HKDF using SHA384. #[allow(non_camel_case_types)] pub type HMAC_SHA384 = HMAC; impl Algorithm for HMAC_SHA384 { @@ -249,6 +253,7 @@ impl AlgorithmOID for HMAC_SHA384 { const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0a]; } +/// Public type for HKDF using SHA512. #[allow(non_camel_case_types)] pub type HMAC_SHA512 = HMAC; impl Algorithm for HMAC_SHA512 { @@ -261,6 +266,7 @@ impl AlgorithmOID for HMAC_SHA512 { const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0b]; } +/// Public type for HKDF using SHA3_224. #[allow(non_camel_case_types)] pub type HMAC_SHA3_224 = HMAC; impl Algorithm for HMAC_SHA3_224 { @@ -274,6 +280,7 @@ impl AlgorithmOID for HMAC_SHA3_224 { &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0d]; } +/// Public type for HKDF using SHA3_256. #[allow(non_camel_case_types)] pub type HMAC_SHA3_256 = HMAC; impl Algorithm for HMAC_SHA3_256 { @@ -287,6 +294,7 @@ impl AlgorithmOID for HMAC_SHA3_256 { &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0e]; } +/// Public type for HKDF using SHA3_384. #[allow(non_camel_case_types)] pub type HMAC_SHA3_384 = HMAC; impl Algorithm for HMAC_SHA3_384 { @@ -300,6 +308,7 @@ impl AlgorithmOID for HMAC_SHA3_384 { &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0f]; } +/// Public type for HKDF using SHA3_512. #[allow(non_camel_case_types)] pub type HMAC_SHA3_512 = HMAC; impl Algorithm for HMAC_SHA3_512 { @@ -323,7 +332,9 @@ impl AlgorithmOID for HMAC_SHA3_512 { // largest block length across all supported hashes, so it is always large enough. const LARGEST_HASHER_BLOCK_LEN: usize = 144; -// HMAC implements RFC 2104. +/// Internal struct for HKDF. +/// HMAC implements RFC 2104. +/// Can, in theory, be instantiated with hash functions other than the ones provided by this crate (even custom ones). #[derive(Clone)] pub struct HMAC { hasher: HASH, @@ -357,16 +368,17 @@ impl Display for HMAC HMAC { fn pad_key_into_hasher(&mut self, padding: u8) { From 26a369eb9009318e97ce0ebf8179df389ad300ba Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Fri, 10 Jul 2026 12:08:46 -0500 Subject: [PATCH 24/26] All crates now build with \#![forbid(unsafe_code)] and \#![forbid[missing_docs]). Also a small refactor to remove the redundant DUMMY_SEED_512 from core-test-framework. --- cli/src/hkdf_cmd.rs | 2 +- crypto/core-test-framework/src/hash.rs | 6 ++ crypto/core-test-framework/src/kdf.rs | 12 ++- crypto/core-test-framework/src/kem.rs | 6 ++ crypto/core-test-framework/src/lib.rs | 10 +- crypto/core-test-framework/src/mac.rs | 10 +- crypto/core-test-framework/src/signature.rs | 42 +++++---- .../src/suspendable_state.rs | 10 +- crypto/factory/src/lib.rs | 1 + crypto/factory/tests/hash_factory_tests.rs | 58 ++++++------ crypto/factory/tests/kdf_factory_tests.rs | 10 +- crypto/hkdf/src/lib.rs | 2 +- crypto/hkdf/tests/hkdf_tests.rs | 92 ++++++++----------- crypto/hmac/tests/hmac_tests.rs | 10 +- crypto/mldsa-lowmemory/src/lib.rs | 2 +- crypto/mldsa-lowmemory/tests/mldsa_tests.rs | 6 +- crypto/mldsa/src/lib.rs | 2 +- crypto/mldsa/tests/mldsa_tests.rs | 6 +- crypto/mlkem/src/lib.rs | 2 +- crypto/rng/src/hash_drbg80090a.rs | 10 +- crypto/rng/src/lib.rs | 8 ++ crypto/rng/tests/hash_drbg80090a_tests.rs | 43 ++++----- crypto/sha2/src/lib.rs | 16 +++- crypto/sha2/src/sha256.rs | 4 + crypto/sha2/src/sha512.rs | 6 +- crypto/sha2/tests/sha2_tests.rs | 13 ++- crypto/sha3/src/lib.rs | 40 +++++--- crypto/sha3/src/sha3.rs | 20 ++-- crypto/sha3/src/shake.rs | 36 +++++--- crypto/sha3/tests/sha3_tests.rs | 74 +++++++-------- crypto/sha3/tests/shake_tests.rs | 26 +++--- crypto/utils/src/ct.rs | 52 +++++------ crypto/utils/src/lib.rs | 11 +++ 33 files changed, 359 insertions(+), 289 deletions(-) diff --git a/cli/src/hkdf_cmd.rs b/cli/src/hkdf_cmd.rs index b53e57e..b49d167 100644 --- a/cli/src/hkdf_cmd.rs +++ b/cli/src/hkdf_cmd.rs @@ -22,7 +22,7 @@ pub(crate) fn hkdf_cmd( let salt_bytes: Vec; let ikm_bytes: Vec; let additional_input_bytes: Vec; - let mut out_key = KeyMaterial::<1024>::new(); + let mut out_key = KeyMaterial::<{ hkdf::MAX_HMAC_OUTPUT_LEN }>::new(); if len > 1024 { eprintln!("Error: The CLI only supports output lengths up to 128 bytes (1024 bits)."); diff --git a/crypto/core-test-framework/src/hash.rs b/crypto/core-test-framework/src/hash.rs index 8bcc000..9793232 100644 --- a/crypto/core-test-framework/src/hash.rs +++ b/crypto/core-test-framework/src/hash.rs @@ -1,10 +1,16 @@ +//! Generic behaviour tests for anything that implements [Hash]. + use bouncycastle_core::traits::{Hash, HashAlgParams}; +/// Instance of the test framework. pub struct TestFrameworkHash { + // Put any config options here + /// Can be disabled for hash functions that don't implement [Hash::do_final_partial_bits]. pub enable_partial_final_input_tests: bool, } impl TestFrameworkHash { + /// pub fn new() -> Self { Self { enable_partial_final_input_tests: true } } diff --git a/crypto/core-test-framework/src/kdf.rs b/crypto/core-test-framework/src/kdf.rs index cac4081..3bc0b07 100644 --- a/crypto/core-test-framework/src/kdf.rs +++ b/crypto/core-test-framework/src/kdf.rs @@ -1,15 +1,21 @@ +//! Generic behaviour tests for anything that implements [KDF]. + use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; use bouncycastle_core::traits::{KDF, SecurityStrength}; -pub struct TestFrameworkKDF {} +/// Instance of the test framework. +pub struct TestFrameworkKDF { + // Put any config options here +} impl TestFrameworkKDF { + /// pub fn new() -> Self { Self {} } - + /// pub fn test_kdf_single_key( &self, key: &impl KeyMaterialTrait, @@ -86,7 +92,7 @@ impl TestFrameworkKDF { assert_eq!(out_key.key_type(), KeyType::CryptographicRandom); assert!(out_key.security_strength() > SecurityStrength::None); } - + /// pub fn test_kdf_multiple_key( &self, keys: &[&impl KeyMaterialTrait], diff --git a/crypto/core-test-framework/src/kem.rs b/crypto/core-test-framework/src/kem.rs index 170c688..b57640d 100644 --- a/crypto/core-test-framework/src/kem.rs +++ b/crypto/core-test-framework/src/kem.rs @@ -1,9 +1,12 @@ +//! Generic behaviour tests for anything that implements [KEMEncapsulator] and [KEMDecapsulator]. + use crate::FixedSeedRNG; use bouncycastle_core::errors::KEMError; use bouncycastle_core::traits::{ KEMDecapsulator, KEMEncapsulator, KEMPrivateKey, KEMPublicKey, RNG, SecurityStrength, }; +/// Instance of the test framework. pub struct TestFrameworkKEM { // Put any config options here /// Should the test framework expect that repeated calls to encaps() will produce the same CT? @@ -15,6 +18,7 @@ pub struct TestFrameworkKEM { } impl TestFrameworkKEM { + /// pub fn new(alg_is_deterministic: bool, is_implicitly_rejecting: bool) -> Self { Self { alg_is_deterministic, is_implicitly_rejecting } } @@ -140,9 +144,11 @@ impl TestFrameworkKEM { } } +/// Instance of the test framework. pub struct TestFrameworkKEMKeys {} impl TestFrameworkKEMKeys { + /// pub fn new() -> Self { Self {} } diff --git a/crypto/core-test-framework/src/lib.rs b/crypto/core-test-framework/src/lib.rs index 52ccc87..64213eb 100644 --- a/crypto/core-test-framework/src/lib.rs +++ b/crypto/core-test-framework/src/lib.rs @@ -9,6 +9,11 @@ //! //! Should only ever be a dev-dependency. +#![forbid(unsafe_code)] +// Let's include this for completeness, but since this in an internal test crate, no reason to fully +// properly document everything. +#![forbid(missing_docs)] + pub mod hash; pub mod kdf; pub mod kem; @@ -19,6 +24,5 @@ pub mod suspendable_state; 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"; - -pub const DUMMY_SEED_1024: &[u8; 1024] = 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\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"; +/// A dummy seed for use in tests which is \x00..\xFF repeated for 1024 bytes +pub const DUMMY_SEED: &[u8; 1024] = 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\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/mac.rs b/crypto/core-test-framework/src/mac.rs index a87d7f6..5a171a5 100644 --- a/crypto/core-test-framework/src/mac.rs +++ b/crypto/core-test-framework/src/mac.rs @@ -1,4 +1,6 @@ -use crate::DUMMY_SEED_512; +//! Generic behaviour tests for anything that implements [MAC]. + +use crate::DUMMY_SEED; use bouncycastle_core::errors::{KeyMaterialError, MACError}; use bouncycastle_core::key_material::{ KeyMaterial512, KeyMaterialTrait, KeyType, do_hazardous_operations, @@ -6,11 +8,13 @@ use bouncycastle_core::key_material::{ use bouncycastle_core::traits::MAC; use bouncycastle_core::traits::SecurityStrength; +/// Instance of the test framework. pub struct TestFrameworkMAC { // Put any config options here } impl TestFrameworkMAC { + /// pub fn new() -> Self { Self {} } @@ -78,7 +82,7 @@ impl TestFrameworkMAC { // MACs of all security strengths should throw an error on a no-security (and non-zero) key. let mut key_none = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[0..64], KeyType::MACKey).unwrap(); + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[0..64], KeyType::MACKey).unwrap(); key_none.set_security_strength(SecurityStrength::None).unwrap(); match M::new(&key_none) { @@ -89,7 +93,7 @@ impl TestFrameworkMAC { } let mut low_security_key = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::MACKey).unwrap(); + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::MACKey).unwrap(); do_hazardous_operations(&mut low_security_key, |low_security_key| { match M::new_allow_weak_key(key).unwrap().max_security_strength() { SecurityStrength::None => { diff --git a/crypto/core-test-framework/src/signature.rs b/crypto/core-test-framework/src/signature.rs index 914ae44..f8514a9 100644 --- a/crypto/core-test-framework/src/signature.rs +++ b/crypto/core-test-framework/src/signature.rs @@ -1,10 +1,13 @@ -use crate::DUMMY_SEED_1024; +//! Generic behaviour tests for anything that implements [Signer] and [SignatureVerifier]. + +use crate::DUMMY_SEED; use bouncycastle_core::errors::SignatureError; use bouncycastle_core::traits::{ Hash, PHSignatureVerifier, PHSigner, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, }; +/// Instance of the test framework. pub struct TestFrameworkSignature { // Put any config options here /// Should the test framework expect that repeated calls to sign() will produce the same signature? @@ -15,6 +18,7 @@ pub struct TestFrameworkSignature { } impl TestFrameworkSignature { + /// pub fn new(alg_is_deterministic: bool, alg_accepts_ctx: bool) -> Self { Self { alg_is_deterministic, alg_accepts_ctx } } @@ -104,8 +108,8 @@ impl TestFrameworkSignature { VERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); // test with a large message - let sig = SIGNER::sign(&sk, DUMMY_SEED_1024, None).unwrap(); - VERIFIER::verify(&pk, DUMMY_SEED_1024, None, &sig).unwrap(); + let sig = SIGNER::sign(&sk, DUMMY_SEED, None).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED, None, &sig).unwrap(); // Test the streaming signing API // fn sign_init(&mut self, sk: &SK) -> Result<(), SignatureError>; @@ -115,35 +119,35 @@ impl TestFrameworkSignature { // First, test the streaming API with one call to .sign_update let mut s = SIGNER::sign_init(&sk, Some(b"streaming API")).unwrap(); - s.sign_update(DUMMY_SEED_1024); + s.sign_update(DUMMY_SEED); let sig_val = s.sign_final().unwrap(); - VERIFIER::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED, Some(b"streaming API"), &sig_val).unwrap(); // Then with the message broken into chunks let mut s = SIGNER::sign_init(&sk, Some(b"streaming API chunked")).unwrap(); - for msg_chunk in DUMMY_SEED_1024.chunks(100) { + for msg_chunk in DUMMY_SEED.chunks(100) { s.sign_update(msg_chunk); } let sig_val = s.sign_final().unwrap(); - VERIFIER::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API chunked"), &sig_val).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED, Some(b"streaming API chunked"), &sig_val).unwrap(); // Test the streaming verification API // one-shot - let sig = SIGNER::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); + let sig = SIGNER::sign(&sk, DUMMY_SEED, Some(b"streaming API")).unwrap(); let mut v = VERIFIER::verify_init(&pk, Some(b"streaming API")).unwrap(); - v.verify_update(DUMMY_SEED_1024); + v.verify_update(DUMMY_SEED); v.verify_final(&sig).unwrap(); // chunked - let sig = SIGNER::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); + let sig = SIGNER::sign(&sk, DUMMY_SEED, Some(b"streaming API")).unwrap(); let mut v = VERIFIER::verify_init(&pk, Some(b"streaming API")).unwrap(); - for msg_chunk in DUMMY_SEED_1024.chunks(100) { + for msg_chunk in DUMMY_SEED.chunks(100) { v.verify_update(msg_chunk); } v.verify_final(&sig).unwrap(); // failure case for streaming verify - let sig = SIGNER::sign(&sk, DUMMY_SEED_1024, Some(b"streaming API")).unwrap(); + let sig = SIGNER::sign(&sk, DUMMY_SEED, Some(b"streaming API")).unwrap(); let mut v = VERIFIER::verify_init(&pk, Some(b"streaming API")).unwrap(); v.verify_update(b"this is the wrong message"); match v.verify_final(&sig) { @@ -153,16 +157,16 @@ impl TestFrameworkSignature { // test sign_out version of streaming API let mut s = SIGNER::sign_init(&sk, Some(b"streaming API")).unwrap(); - s.sign_update(DUMMY_SEED_1024); + s.sign_update(DUMMY_SEED); let mut sig_val = [0u8; SIG_LEN]; let bytes_written = s.sign_final_out(&mut sig_val).unwrap(); assert_eq!(bytes_written, SIG_LEN); - VERIFIER::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED, Some(b"streaming API"), &sig_val).unwrap(); // the ::verify API should accept a sig value that's too long and just ignore the extra bytes let mut sig_val_too_long = vec![1u8; SIG_LEN + 2]; sig_val_too_long[..SIG_LEN].copy_from_slice(&sig_val); - VERIFIER::verify(&pk, DUMMY_SEED_1024, Some(b"streaming API"), &sig_val).unwrap(); + VERIFIER::verify(&pk, DUMMY_SEED, Some(b"streaming API"), &sig_val).unwrap(); } /// Test all the members of traits [PHSigner] and [PHSignatureVerifier] against the given input-output pair. @@ -253,13 +257,13 @@ impl TestFrameworkSignature { PHVERIFIER::verify(&pk, msg, None, &sig_val).unwrap(); // test with a large message - let sig = PHSIGNER::sign(&sk, DUMMY_SEED_1024, None).unwrap(); - PHVERIFIER::verify(&pk, DUMMY_SEED_1024, None, &sig).unwrap(); + let sig = PHSIGNER::sign(&sk, DUMMY_SEED, None).unwrap(); + PHVERIFIER::verify(&pk, DUMMY_SEED, None, &sig).unwrap(); // the ::verify API should not accept a sig value that's too let mut sig_val_too_long = vec![1u8; SIG_LEN + 2]; sig_val_too_long[..SIG_LEN].copy_from_slice(&sig); - match PHVERIFIER::verify(&pk, DUMMY_SEED_1024, None, &sig_val_too_long) { + match PHVERIFIER::verify(&pk, DUMMY_SEED, None, &sig_val_too_long) { Err(SignatureError::LengthError(_)) => (), _ => panic!("Unexpected error"), } @@ -282,9 +286,11 @@ impl TestFrameworkSignature { } } +/// Instance of the test framework. pub struct TestFrameworkSignatureKeys {} impl TestFrameworkSignatureKeys { + /// pub fn new() -> Self { Self {} } diff --git a/crypto/core-test-framework/src/suspendable_state.rs b/crypto/core-test-framework/src/suspendable_state.rs index 677ed4e..a08c85c 100644 --- a/crypto/core-test-framework/src/suspendable_state.rs +++ b/crypto/core-test-framework/src/suspendable_state.rs @@ -1,10 +1,16 @@ +//! Generic behaviour tests for anything that implements [Suspendable] and [SuspendableKeyed]. + use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::suspendable_state::{LIB_VERSION, SemVer}; use bouncycastle_core::traits::{Suspendable, SuspendableKeyed}; -pub struct TestFrameworkSuspendableState {} +/// Instance of the test framework. +pub struct TestFrameworkSuspendableState { + // Put any config options here +} impl TestFrameworkSuspendableState { + /// pub fn new() -> Self { Self {} } @@ -88,9 +94,11 @@ impl TestFrameworkSuspendableState { } } +/// Instance of the test framework. pub struct TestFrameworkSuspendableKeyedState {} impl TestFrameworkSuspendableKeyedState { + /// pub fn new() -> Self { Self {} } diff --git a/crypto/factory/src/lib.rs b/crypto/factory/src/lib.rs index 9a55ddc..cc30f6b 100644 --- a/crypto/factory/src/lib.rs +++ b/crypto/factory/src/lib.rs @@ -28,6 +28,7 @@ //! This crate compiles with STD; ie it is explicitly not tagged as `no_std` and it makes use of `Vec` and other //! dynamically-sized nice things. +#![forbid(unsafe_code)] #![forbid(missing_docs)] use bouncycastle_core::errors::MACError; diff --git a/crypto/factory/tests/hash_factory_tests.rs b/crypto/factory/tests/hash_factory_tests.rs index 6be8756..31d216b 100644 --- a/crypto/factory/tests/hash_factory_tests.rs +++ b/crypto/factory/tests/hash_factory_tests.rs @@ -1,7 +1,7 @@ #[cfg(test)] mod hash_factory_tests { use bouncycastle_core::traits::{Hash, XOF}; - use bouncycastle_core_test_framework::DUMMY_SEED_512; + use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_factory::AlgorithmFactory; use bouncycastle_factory::hash_factory::HashFactory; use bouncycastle_factory::xof_factory::XOFFactory; @@ -18,42 +18,42 @@ mod hash_factory_tests { // SHA224 let h = SHA224::new(); - h.hash(&DUMMY_SEED_512[..24]); + h.hash(&DUMMY_SEED[..24]); let sha2 = HashFactory::new("SHA224").unwrap(); assert_eq!(sha2.output_len(), 28); - assert_eq!(sha2.hash(DUMMY_SEED_512), b"\xb8\x06\x0c\xcc\x82\xd4\x0c\x57\x61\x56\xf7\xca\x03\x33\xe4\x38\x9e\x41\x0d\xf0\x27\xd2\xfb\x8f\x76\x4f\xa6\x03"); + assert_eq!(sha2.hash(&DUMMY_SEED[..512]), b"\xb8\x06\x0c\xcc\x82\xd4\x0c\x57\x61\x56\xf7\xca\x03\x33\xe4\x38\x9e\x41\x0d\xf0\x27\xd2\xfb\x8f\x76\x4f\xa6\x03"); let sha2 = HashFactory::new(sha2::SHA224_NAME).unwrap(); assert_eq!(sha2.output_len(), 28); - assert_eq!(sha2.hash(DUMMY_SEED_512), b"\xb8\x06\x0c\xcc\x82\xd4\x0c\x57\x61\x56\xf7\xca\x03\x33\xe4\x38\x9e\x41\x0d\xf0\x27\xd2\xfb\x8f\x76\x4f\xa6\x03"); + assert_eq!(sha2.hash(&DUMMY_SEED[..512]), b"\xb8\x06\x0c\xcc\x82\xd4\x0c\x57\x61\x56\xf7\xca\x03\x33\xe4\x38\x9e\x41\x0d\xf0\x27\xd2\xfb\x8f\x76\x4f\xa6\x03"); // SHA256 let sha2 = HashFactory::new("SHA256").unwrap(); assert_eq!(sha2.output_len(), 32); - assert_eq!(sha2.hash(DUMMY_SEED_512), b"\x11\x00\x09\xdc\xee\x21\x62\x0b\x16\x6f\x3a\xbf\xec\xb5\xef\xf7\xa8\x73\xbe\x72\x9d\x1c\x2d\x53\x82\x2e\x7a\xcc\x5f\x34\xeb\x9b"); + assert_eq!(sha2.hash(&DUMMY_SEED[..512]), b"\x11\x00\x09\xdc\xee\x21\x62\x0b\x16\x6f\x3a\xbf\xec\xb5\xef\xf7\xa8\x73\xbe\x72\x9d\x1c\x2d\x53\x82\x2e\x7a\xcc\x5f\x34\xeb\x9b"); let sha2 = HashFactory::new(sha2::SHA256_NAME).unwrap(); assert_eq!(sha2.output_len(), 32); - assert_eq!(sha2.hash(DUMMY_SEED_512), b"\x11\x00\x09\xdc\xee\x21\x62\x0b\x16\x6f\x3a\xbf\xec\xb5\xef\xf7\xa8\x73\xbe\x72\x9d\x1c\x2d\x53\x82\x2e\x7a\xcc\x5f\x34\xeb\x9b"); + assert_eq!(sha2.hash(&DUMMY_SEED[..512]), b"\x11\x00\x09\xdc\xee\x21\x62\x0b\x16\x6f\x3a\xbf\xec\xb5\xef\xf7\xa8\x73\xbe\x72\x9d\x1c\x2d\x53\x82\x2e\x7a\xcc\x5f\x34\xeb\x9b"); // SHA384 let sha2 = HashFactory::new("SHA384").unwrap(); assert_eq!(sha2.output_len(), 48); - assert_eq!(sha2.hash(DUMMY_SEED_512), b"\x45\x82\xfc\x82\x43\x0e\x52\x68\x86\xa1\x85\x34\x11\xe6\x06\x45\xfe\xf7\xe8\xea\x0c\x85\x46\xb7\xc9\xba\x0c\x84\x16\xd9\xa9\x8f\xb5\x2e\xbd\x0c\x60\x5f\xbb\x70\x74\x9c\x4e\x3e\x5d\xa3\xdb\xac"); + assert_eq!(sha2.hash(&DUMMY_SEED[..512]), b"\x45\x82\xfc\x82\x43\x0e\x52\x68\x86\xa1\x85\x34\x11\xe6\x06\x45\xfe\xf7\xe8\xea\x0c\x85\x46\xb7\xc9\xba\x0c\x84\x16\xd9\xa9\x8f\xb5\x2e\xbd\x0c\x60\x5f\xbb\x70\x74\x9c\x4e\x3e\x5d\xa3\xdb\xac"); let sha2 = HashFactory::new(sha2::SHA384_NAME).unwrap(); assert_eq!(sha2.output_len(), 48); - assert_eq!(sha2.hash(DUMMY_SEED_512), b"\x45\x82\xfc\x82\x43\x0e\x52\x68\x86\xa1\x85\x34\x11\xe6\x06\x45\xfe\xf7\xe8\xea\x0c\x85\x46\xb7\xc9\xba\x0c\x84\x16\xd9\xa9\x8f\xb5\x2e\xbd\x0c\x60\x5f\xbb\x70\x74\x9c\x4e\x3e\x5d\xa3\xdb\xac"); + assert_eq!(sha2.hash(&DUMMY_SEED[..512]), b"\x45\x82\xfc\x82\x43\x0e\x52\x68\x86\xa1\x85\x34\x11\xe6\x06\x45\xfe\xf7\xe8\xea\x0c\x85\x46\xb7\xc9\xba\x0c\x84\x16\xd9\xa9\x8f\xb5\x2e\xbd\x0c\x60\x5f\xbb\x70\x74\x9c\x4e\x3e\x5d\xa3\xdb\xac"); // SHA512 let sha2 = HashFactory::new("SHA512").unwrap(); assert_eq!(sha2.output_len(), 64); - assert_eq!(sha2.hash(DUMMY_SEED_512), b"\xed\xb9\xbe\xd7\x21\xaa\x6a\x5f\x6f\xbc\x66\x19\xd3\xa3\xc2\xbe\x3d\x04\x30\x43\xf0\x5a\x9a\xeb\xc7\xb1\x19\x7a\x2a\xa9\xc4\x9a\x57\xd5\xdd\xd4\x67\x4c\x17\x85\x78\x50\x88\xd9\xf1\xff\x42\xc7\x97\xa0\x2a\xdc\x9b\x81\x7a\x13\x9a\x50\x97\x0d\xa6\xc9\x95\x24"); + assert_eq!(sha2.hash(&DUMMY_SEED[..512]), b"\xed\xb9\xbe\xd7\x21\xaa\x6a\x5f\x6f\xbc\x66\x19\xd3\xa3\xc2\xbe\x3d\x04\x30\x43\xf0\x5a\x9a\xeb\xc7\xb1\x19\x7a\x2a\xa9\xc4\x9a\x57\xd5\xdd\xd4\x67\x4c\x17\x85\x78\x50\x88\xd9\xf1\xff\x42\xc7\x97\xa0\x2a\xdc\x9b\x81\x7a\x13\x9a\x50\x97\x0d\xa6\xc9\x95\x24"); let sha2 = HashFactory::new(sha2::SHA512_NAME).unwrap(); assert_eq!(sha2.output_len(), 64); - assert_eq!(sha2.hash(DUMMY_SEED_512), b"\xed\xb9\xbe\xd7\x21\xaa\x6a\x5f\x6f\xbc\x66\x19\xd3\xa3\xc2\xbe\x3d\x04\x30\x43\xf0\x5a\x9a\xeb\xc7\xb1\x19\x7a\x2a\xa9\xc4\x9a\x57\xd5\xdd\xd4\x67\x4c\x17\x85\x78\x50\x88\xd9\xf1\xff\x42\xc7\x97\xa0\x2a\xdc\x9b\x81\x7a\x13\x9a\x50\x97\x0d\xa6\xc9\x95\x24"); + assert_eq!(sha2.hash(&DUMMY_SEED[..512]), b"\xed\xb9\xbe\xd7\x21\xaa\x6a\x5f\x6f\xbc\x66\x19\xd3\xa3\xc2\xbe\x3d\x04\x30\x43\xf0\x5a\x9a\xeb\xc7\xb1\x19\x7a\x2a\xa9\xc4\x9a\x57\xd5\xdd\xd4\x67\x4c\x17\x85\x78\x50\x88\xd9\xf1\xff\x42\xc7\x97\xa0\x2a\xdc\x9b\x81\x7a\x13\x9a\x50\x97\x0d\xa6\xc9\x95\x24"); } #[test] @@ -61,85 +61,85 @@ mod hash_factory_tests { // SHA3-224 let sha3 = HashFactory::new("SHA3-224").unwrap(); assert_eq!(sha3.output_len(), 28); - assert_eq!(sha3.hash(DUMMY_SEED_512), b"\xFE\x51\xC5\xD7\x62\x48\xE1\xE9\xD3\x01\x29\x6A\xE8\xAB\x94\x69\xD2\x86\x34\xB4\xAD\x3E\x9E\x78\xC8\xB0\x9D\x47"); + assert_eq!(sha3.hash(&DUMMY_SEED[..512]), b"\xFE\x51\xC5\xD7\x62\x48\xE1\xE9\xD3\x01\x29\x6A\xE8\xAB\x94\x69\xD2\x86\x34\xB4\xAD\x3E\x9E\x78\xC8\xB0\x9D\x47"); let sha3 = HashFactory::new(sha3::SHA3_224_NAME).unwrap(); assert_eq!(sha3.output_len(), 28); - assert_eq!(sha3.hash(DUMMY_SEED_512), b"\xFE\x51\xC5\xD7\x62\x48\xE1\xE9\xD3\x01\x29\x6A\xE8\xAB\x94\x69\xD2\x86\x34\xB4\xAD\x3E\x9E\x78\xC8\xB0\x9D\x47"); + assert_eq!(sha3.hash(&DUMMY_SEED[..512]), b"\xFE\x51\xC5\xD7\x62\x48\xE1\xE9\xD3\x01\x29\x6A\xE8\xAB\x94\x69\xD2\x86\x34\xB4\xAD\x3E\x9E\x78\xC8\xB0\x9D\x47"); // SHA3-256 let sha3 = HashFactory::new("SHA3-256").unwrap(); assert_eq!(sha3.output_len(), 32); - assert_eq!(sha3.hash(DUMMY_SEED_512), b"\xD4\x72\x8E\xA5\xE9\xF3\x81\x9F\x2B\x47\x60\x15\x1A\x8F\x80\x2D\xBE\x9F\x94\x1F\xD6\xFB\x59\xB3\x71\x58\x92\x43\x65\x55\x77\x2A"); + assert_eq!(sha3.hash(&DUMMY_SEED[..512]), b"\xD4\x72\x8E\xA5\xE9\xF3\x81\x9F\x2B\x47\x60\x15\x1A\x8F\x80\x2D\xBE\x9F\x94\x1F\xD6\xFB\x59\xB3\x71\x58\x92\x43\x65\x55\x77\x2A"); let sha3 = HashFactory::new(sha3::SHA3_256_NAME).unwrap(); assert_eq!(sha3.output_len(), 32); - assert_eq!(sha3.hash(DUMMY_SEED_512), b"\xD4\x72\x8E\xA5\xE9\xF3\x81\x9F\x2B\x47\x60\x15\x1A\x8F\x80\x2D\xBE\x9F\x94\x1F\xD6\xFB\x59\xB3\x71\x58\x92\x43\x65\x55\x77\x2A"); + assert_eq!(sha3.hash(&DUMMY_SEED[..512]), b"\xD4\x72\x8E\xA5\xE9\xF3\x81\x9F\x2B\x47\x60\x15\x1A\x8F\x80\x2D\xBE\x9F\x94\x1F\xD6\xFB\x59\xB3\x71\x58\x92\x43\x65\x55\x77\x2A"); // SHA3-384 let sha3 = HashFactory::new("SHA3-384").unwrap(); assert_eq!(sha3.output_len(), 48); - assert_eq!(sha3.hash(DUMMY_SEED_512), b"\xd5\x3b\x51\x68\x53\xf5\xac\xb4\xaa\xfd\xa5\x9d\x6f\x74\x0f\x69\x99\xc9\xe5\x21\x1c\x51\x03\x9c\x6d\x64\x5b\xf9\x83\xd7\xba\x0b\xdf\x12\x31\xb5\x50\x90\xb5\x5e\x35\x99\xee\x7a\xaa\x62\xd3\xbf"); + assert_eq!(sha3.hash(&DUMMY_SEED[..512]), b"\xd5\x3b\x51\x68\x53\xf5\xac\xb4\xaa\xfd\xa5\x9d\x6f\x74\x0f\x69\x99\xc9\xe5\x21\x1c\x51\x03\x9c\x6d\x64\x5b\xf9\x83\xd7\xba\x0b\xdf\x12\x31\xb5\x50\x90\xb5\x5e\x35\x99\xee\x7a\xaa\x62\xd3\xbf"); let sha3 = HashFactory::new(sha3::SHA3_384_NAME).unwrap(); assert_eq!(sha3.output_len(), 48); - assert_eq!(sha3.hash(DUMMY_SEED_512), b"\xd5\x3b\x51\x68\x53\xf5\xac\xb4\xaa\xfd\xa5\x9d\x6f\x74\x0f\x69\x99\xc9\xe5\x21\x1c\x51\x03\x9c\x6d\x64\x5b\xf9\x83\xd7\xba\x0b\xdf\x12\x31\xb5\x50\x90\xb5\x5e\x35\x99\xee\x7a\xaa\x62\xd3\xbf"); + assert_eq!(sha3.hash(&DUMMY_SEED[..512]), b"\xd5\x3b\x51\x68\x53\xf5\xac\xb4\xaa\xfd\xa5\x9d\x6f\x74\x0f\x69\x99\xc9\xe5\x21\x1c\x51\x03\x9c\x6d\x64\x5b\xf9\x83\xd7\xba\x0b\xdf\x12\x31\xb5\x50\x90\xb5\x5e\x35\x99\xee\x7a\xaa\x62\xd3\xbf"); // SHA3-512 let sha3 = HashFactory::new("SHA3-512").unwrap(); assert_eq!(sha3.output_len(), 64); - assert_eq!(sha3.hash(DUMMY_SEED_512), b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); + assert_eq!(sha3.hash(&DUMMY_SEED[..512]), b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); let sha3 = HashFactory::new(sha3::SHA3_512_NAME).unwrap(); assert_eq!(sha3.output_len(), 64); - assert_eq!(sha3.hash(DUMMY_SEED_512), b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); + assert_eq!(sha3.hash(&DUMMY_SEED[..512]), b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); } #[test] fn sha3_xof_tests() { - assert_eq!(XOFFactory::new("SHAKE128").unwrap().hash_xof(DUMMY_SEED_512, 32), b"\x88\x90\xed\x20\x4d\x22\x89\xe1\x72\xe9\xae\x68\x48\x18\x23\x77\x08\x20\x90\x80\x60\xa4\xdf\x33\x51\xa3\xf1\x84\xeb\xb6\xdd\x0f"); - assert_eq!(XOFFactory::new("SHAKE256").unwrap().hash_xof(DUMMY_SEED_512, 32), b"\xa1\xd7\x18\x85\xb0\xa8\x41\xf0\x3d\x1d\xc7\xf2\x73\x8a\x15\xcc\x98\x40\x71\xa1\x7f\xfe\xd5\xec\xac\xb9\xf5\x87\x20\xa4\x73\xbe"); + assert_eq!(XOFFactory::new("SHAKE128").unwrap().hash_xof(&DUMMY_SEED[..512], 32), b"\x88\x90\xed\x20\x4d\x22\x89\xe1\x72\xe9\xae\x68\x48\x18\x23\x77\x08\x20\x90\x80\x60\xa4\xdf\x33\x51\xa3\xf1\x84\xeb\xb6\xdd\x0f"); + assert_eq!(XOFFactory::new("SHAKE256").unwrap().hash_xof(&DUMMY_SEED[..512], 32), b"\xa1\xd7\x18\x85\xb0\xa8\x41\xf0\x3d\x1d\xc7\xf2\x73\x8a\x15\xcc\x98\x40\x71\xa1\x7f\xfe\xd5\xec\xac\xb9\xf5\x87\x20\xa4\x73\xbe"); } #[test] fn test_defaults() { // All the ways to get "default" let hash = HashFactory::default(); - let out = hash.hash(DUMMY_SEED_512); + let out = hash.hash(DUMMY_SEED); assert_ne!(out, vec![0u8; out.len()]); let hash = HashFactory::new("Default").unwrap(); - let out = hash.hash(DUMMY_SEED_512); + let out = hash.hash(DUMMY_SEED); assert_ne!(out, vec![0u8; out.len()]); let hash = HashFactory::new(factory::DEFAULT).unwrap(); - let out = hash.hash(DUMMY_SEED_512); + let out = hash.hash(DUMMY_SEED); assert_ne!(out, vec![0u8; out.len()]); // All the ways to get "default_128_bit" let hash = HashFactory::default_128_bit(); - let out = hash.hash(DUMMY_SEED_512); + let out = hash.hash(DUMMY_SEED); assert_ne!(out, vec![0u8; out.len()]); let hash = HashFactory::new("Default128Bit").unwrap(); - let out = hash.hash(DUMMY_SEED_512); + let out = hash.hash(DUMMY_SEED); assert_ne!(out, vec![0u8; out.len()]); let hash = HashFactory::new(factory::DEFAULT_128_BIT).unwrap(); - let out = hash.hash(DUMMY_SEED_512); + let out = hash.hash(DUMMY_SEED); assert_ne!(out, vec![0u8; out.len()]); // All the ways to get "default_256_bit" let hash = HashFactory::default_256_bit(); - let out = hash.hash(DUMMY_SEED_512); + let out = hash.hash(DUMMY_SEED); assert_ne!(out, vec![0u8; out.len()]); let hash = HashFactory::new("Default256Bit").unwrap(); - let out = hash.hash(DUMMY_SEED_512); + let out = hash.hash(DUMMY_SEED); assert_ne!(out, vec![0u8; out.len()]); let hash = HashFactory::new(factory::DEFAULT_256_BIT).unwrap(); - let out = hash.hash(DUMMY_SEED_512); + let out = hash.hash(DUMMY_SEED); assert_ne!(out, vec![0u8; out.len()]); } } diff --git a/crypto/factory/tests/kdf_factory_tests.rs b/crypto/factory/tests/kdf_factory_tests.rs index 16e6949..c3d68fa 100644 --- a/crypto/factory/tests/kdf_factory_tests.rs +++ b/crypto/factory/tests/kdf_factory_tests.rs @@ -4,7 +4,7 @@ mod kdf_factory_tests { KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; use bouncycastle_core::traits::KDF; - use bouncycastle_core_test_framework::DUMMY_SEED_512; + use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_factory as factory; use bouncycastle_factory::AlgorithmFactory; use bouncycastle_factory::kdf_factory::KDFFactory; @@ -12,7 +12,7 @@ mod kdf_factory_tests { #[test] fn sha3_kdf_tests() { - let key_material = KeyMaterial256::from_bytes(&DUMMY_SEED_512[..32]).unwrap(); + let key_material = KeyMaterial256::from_bytes(&DUMMY_SEED[..32]).unwrap(); // SHA3_224 let derived_key = @@ -65,7 +65,7 @@ mod kdf_factory_tests { // Note: this value is not checked against any external reference implementation, // I just hard-coded the value to make sure it stays consistent. let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::MACKey).unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap(); let derived_key = KDFFactory::new("HKDF-SHA256").unwrap().derive_key(&key_material, &[0u8; 0]).unwrap(); let expected_key = KeyMaterial256::from_bytes(b"\x37\xad\x29\x10\x9f\x43\x26\x52\x87\x80\x4b\x67\x4e\x26\x53\xd0\xa5\x13\x71\x89\x07\xf9\x7f\xca\x97\xc9\x5b\xde\xd8\x10\x4b\xbf").unwrap(); @@ -74,7 +74,7 @@ mod kdf_factory_tests { /* HKDF-SHA512 */ // Note: this value is not checked against any external reference implementation, // I just hard-coded the value to make sure it stays consistent. - let key_material = KeyMaterial512::from_bytes(&DUMMY_SEED_512[..64]).unwrap(); + let key_material = KeyMaterial512::from_bytes(&DUMMY_SEED[..64]).unwrap(); let derived_key = KDFFactory::new("HKDF-SHA512").unwrap().derive_key(&key_material, &[0u8; 0]).unwrap(); let expected_key = KeyMaterial512::from_bytes(b"\x8f\x5a\x29\x79\xfe\x16\x4d\x3a\x01\x72\x02\x32\x6c\x61\x97\xae\xa2\x58\x56\x3d\x90\x9b\x01\x20\x12\x1c\x37\x22\x6c\xb3\xd3\x68\xf4\x31\xf9\x79\x9d\x33\x8c\xe3\x0e\xfc\x5f\x41\xaf\xfc\x3d\x38\x54\x44\xa0\x65\xae\x80\x78\x60\x59\x45\x79\x50\xa1\xe6\x5e\x57").unwrap(); @@ -83,7 +83,7 @@ mod kdf_factory_tests { #[test] fn test_defaults() { - let key = KeyMaterial256::from_bytes(&DUMMY_SEED_512[..32]).unwrap(); + let key = KeyMaterial256::from_bytes(&DUMMY_SEED[..32]).unwrap(); // All the ways to get "default" let _ = KDFFactory::default().derive_key(&key, &[0u8; 0]).unwrap(); diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index cbe332b..bf20cc9 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -350,7 +350,7 @@ impl Default for HKDF { } impl HKDF { - /// Creates a new, uninstantiated HKDF object. + /// Get a new, uninstantiated HKDF object. pub fn new() -> Self { Self { hmac: None, entropy: HkdfEntropyTracker::new(), state: HkdfStates::Uninitialized } } diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index fee0444..4613b4a 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -7,7 +7,7 @@ mod hkdf_tests { KeyMaterialTrait, KeyType, }; use bouncycastle_core::traits::{HashAlgParams, KDF, SecurityStrength}; - use bouncycastle_core_test_framework::DUMMY_SEED_512; + use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; use bouncycastle_hex as hex; use bouncycastle_hkdf::{HKDF, HKDF_SHA256, HKDF_SHA512}; @@ -17,10 +17,9 @@ mod hkdf_tests { #[test] fn test_streaming_apis() { // setup variables - let salt = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::MACKey).unwrap(); - let ikm = KeyMaterial256::from_bytes(&DUMMY_SEED_512[16..48]).unwrap(); - let info = &DUMMY_SEED_512[48..64]; + let salt = KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::MACKey).unwrap(); + let ikm = KeyMaterial256::from_bytes(&DUMMY_SEED[16..48]).unwrap(); + let info = &DUMMY_SEED[48..64]; let mut okm = KeyMaterial512::new(); _ = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, info, 64, &mut okm).unwrap(); @@ -46,12 +45,11 @@ mod hkdf_tests { let info: &[u8] = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\0xF"; let zero_key = KeyMaterial0::new(); - let key1 = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::MACKey).unwrap(); + let key1 = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap(); let key2 = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[32..64], KeyType::MACKey).unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[32..64], KeyType::MACKey).unwrap(); let key3 = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[64..96], KeyType::MACKey).unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[64..96], KeyType::MACKey).unwrap(); /* test case: 0 input keys (ie empty salt and no ikm's) */ let mut expected_okm = KeyMaterial512::new(); @@ -114,7 +112,7 @@ mod hkdf_tests { /* test case: 3 input keys (ie salt and two ikm's) */ let key23 = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[32..96], KeyType::MACKey).unwrap(); + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[32..96], KeyType::MACKey).unwrap(); let mut expected_okm = KeyMaterial512::new(); HKDF_SHA256::extract_and_expand_out(&key1, &key23, info, 32, &mut expected_okm).unwrap(); @@ -129,11 +127,11 @@ mod hkdf_tests { fn test_entropy_tracking() { // test the thresholds of HMAC-SHA256 let key255 = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..31], KeyType::MACKey).unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..31], KeyType::MACKey).unwrap(); let key256 = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::MACKey).unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap(); let key512 = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::MACKey).unwrap(); + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::MACKey).unwrap(); let zero_key = KeyMaterial0::new(); // not enough @@ -163,9 +161,9 @@ mod hkdf_tests { // test the thresholds of HMAC-SHA512 let key511 = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..63], KeyType::MACKey).unwrap(); + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[..63], KeyType::MACKey).unwrap(); let key512 = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::MACKey).unwrap(); + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::MACKey).unwrap(); let zero_key = KeyMaterial0::new(); // not enough @@ -182,7 +180,7 @@ mod hkdf_tests { // variable setup let low_entropy_key = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Unknown).unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Unknown).unwrap(); let mut okm = KeyMaterial256::new(); // failure case: should complain if low entropy bytes are provided @@ -296,13 +294,11 @@ mod hkdf_tests { // salt and ikm are full-entropy, but not enough to seed the HKDF, according to FIPS // first, error case; not a MACKey let salt = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..8], KeyType::CryptographicRandom) + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..8], KeyType::CryptographicRandom) + .unwrap(); + let ikm = + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[8..16], KeyType::CryptographicRandom) .unwrap(); - let ikm = KeyMaterial128::from_bytes_as_type( - &DUMMY_SEED_512[8..16], - KeyType::CryptographicRandom, - ) - .unwrap(); match HKDF_SHA256::extract_and_expand_out(&salt, &ikm, &[], 32, &mut okm) { Ok(_) => { @@ -335,13 +331,10 @@ mod hkdf_tests { }; // success case -- insufficient entropy returns KeyType::BytesLowEntropy - let salt = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..8], KeyType::MACKey).unwrap(); - let ikm = KeyMaterial128::from_bytes_as_type( - &DUMMY_SEED_512[8..16], - KeyType::CryptographicRandom, - ) - .unwrap(); + let salt = KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..8], KeyType::MACKey).unwrap(); + let ikm = + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[8..16], KeyType::CryptographicRandom) + .unwrap(); _ = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, &[], 32, &mut okm); assert_eq!(okm.key_type(), KeyType::Unknown); @@ -356,19 +349,15 @@ mod hkdf_tests { // success case -- sufficient entropy returns the highest input key type -- KeyType::BytesFullEntropy // Note that FIPS requires it to be seeded to a full internal block (which is, for example 512 bits for SHA256) // Note: will still return BytesFullEntropy because that one was first in the inputs. - let salt = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::MACKey).unwrap(); - let ikm = KeyMaterial256::from_bytes_as_type( - &DUMMY_SEED_512[32..64], - KeyType::CryptographicRandom, - ) - .unwrap(); + let salt = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap(); + let ikm = + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[32..64], KeyType::CryptographicRandom) + .unwrap(); _ = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, &[], 32, &mut okm); assert_eq!(okm.key_type(), KeyType::CryptographicRandom); - let salt1 = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::MACKey).unwrap(); + let salt1 = KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::MACKey).unwrap(); _ = HKDF_SHA256::new().derive_key_out(&salt1, &[], &mut okm); assert_eq!(okm.key_type(), KeyType::CryptographicRandom); @@ -378,10 +367,9 @@ mod hkdf_tests { // success case -- insufficient entropy due to key types -- KeyType::BytesLowEntropy // Note: will still return MACKey because that one was first in the inputs. - let salt = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::MACKey).unwrap(); + let salt = KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::MACKey).unwrap(); let ikm = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[16..32], KeyType::Unknown).unwrap(); + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[16..32], KeyType::Unknown).unwrap(); _ = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, &[], 32, &mut okm); assert_eq!(okm.key_type(), KeyType::Unknown); @@ -395,16 +383,14 @@ mod hkdf_tests { /* get_entropy */ // This requires using the stateful streaming API and check the amount of entropy it tracks after each addition. let salt16 = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::MACKey).unwrap(); + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::MACKey).unwrap(); let salt64 = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::MACKey).unwrap(); + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::MACKey).unwrap(); let low_entropy_key16 = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::Unknown).unwrap(); - let full_entropy_key16 = KeyMaterial128::from_bytes_as_type( - &DUMMY_SEED_512[16..32], - KeyType::CryptographicRandom, - ) - .unwrap(); + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::Unknown).unwrap(); + let full_entropy_key16 = + KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[16..32], KeyType::CryptographicRandom) + .unwrap(); // can't test with a low entropy salt because the salt has to be full entropy or zero. // but can test with a zeroized key @@ -607,8 +593,7 @@ mod hkdf_tests { #[test] fn hkdf_state_tests() { // setup - let key = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::MACKey).unwrap(); + let key = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap(); // error case: try to initialize twice let mut hkdf = HKDF_SHA256::new(); @@ -747,9 +732,8 @@ mod hkdf_tests { use bouncycastle_hkdf::{SUSPENDED_HKDF_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA512_STATE_LEN}; // HKDF is keyed by its salt: the salt is NOT serialized and is re-supplied on resume. - let salt = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::MACKey).unwrap(); - let ikm = &DUMMY_SEED_512[16..64]; + let salt = KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::MACKey).unwrap(); + let ikm = &DUMMY_SEED[16..64]; let (part1, part2) = ikm.split_at(20); // A helper that exercises the full round-trip for one HKDF variant. A concrete `&KeyMaterial128` diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/hmac/tests/hmac_tests.rs index 817a89d..7d9022b 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/hmac/tests/hmac_tests.rs @@ -6,7 +6,7 @@ mod hmac_tests { KeyMaterial, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; use bouncycastle_core::traits::{Algorithm, Hash, MAC, SecurityStrength}; - use bouncycastle_core_test_framework::DUMMY_SEED_512; + use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::mac::TestFrameworkMAC; use bouncycastle_hex as hex; use bouncycastle_hmac::*; @@ -184,7 +184,7 @@ mod hmac_tests { // init let mut key = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::MACKey).unwrap(); + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::MACKey).unwrap(); assert_eq!(key.security_strength(), SecurityStrength::_256bit); key.set_security_strength(SecurityStrength::_128bit).unwrap(); // complains at first @@ -609,8 +609,7 @@ mod hmac_tests { use bouncycastle_core::traits::SuspendableKeyed; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; - let key = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::MACKey).unwrap(); + let key = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap(); let msg = b"Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one HMAC variant. HMAC is keyed, so the @@ -662,8 +661,7 @@ mod hmac_tests { // test suspend / resume with a key larger than block size let long_key = - KeyMaterial::<200>::from_bytes_as_type(&DUMMY_SEED_512[..200], KeyType::MACKey) - .unwrap(); + KeyMaterial::<200>::from_bytes_as_type(&DUMMY_SEED[..200], KeyType::MACKey).unwrap(); round_trip(HMAC_SHA256::new(&long_key).unwrap(), &long_key, msg); } diff --git a/crypto/mldsa-lowmemory/src/lib.rs b/crypto/mldsa-lowmemory/src/lib.rs index d0b0192..7cfa928 100644 --- a/crypto/mldsa-lowmemory/src/lib.rs +++ b/crypto/mldsa-lowmemory/src/lib.rs @@ -200,8 +200,8 @@ //! constant-time after compilation. #![no_std] -#![forbid(missing_docs)] #![forbid(unsafe_code)] +#![forbid(missing_docs)] // These are because I'm matching variable names exactly against FIPS 204, for example both 'K' and 'k', // or 'A' and 'a' are used and have specific meanings. // But need to tell the rust linter to not care. diff --git a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs index c195e63..428537a 100644 --- a/crypto/mldsa-lowmemory/tests/mldsa_tests.rs +++ b/crypto/mldsa-lowmemory/tests/mldsa_tests.rs @@ -9,7 +9,7 @@ mod mldsa_tests { RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, Suspendable, }; - use bouncycastle_core_test_framework::DUMMY_SEED_1024; + use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; @@ -354,11 +354,11 @@ mod mldsa_tests { // Then with the message broken into chunks let mut s = MLDSA44::sign_init(&sk, Some(b"streaming API chunked")).unwrap(); s.set_signer_rnd(rnd); - for msg_chunk in DUMMY_SEED_1024.chunks(100) { + for msg_chunk in DUMMY_SEED.chunks(100) { s.sign_update(msg_chunk); } let sig_val = s.sign_final().unwrap(); - MLDSA44::verify(&sk.derive_pk(), DUMMY_SEED_1024, Some(b"streaming API chunked"), &sig_val) + MLDSA44::verify(&sk.derive_pk(), DUMMY_SEED, Some(b"streaming API chunked"), &sig_val) .unwrap(); // ML-DSA-65 diff --git a/crypto/mldsa/src/lib.rs b/crypto/mldsa/src/lib.rs index e0f2b4f..ca8b667 100644 --- a/crypto/mldsa/src/lib.rs +++ b/crypto/mldsa/src/lib.rs @@ -112,8 +112,8 @@ //! constant-time after compilation. #![no_std] -#![forbid(missing_docs)] #![forbid(unsafe_code)] +#![forbid(missing_docs)] // These are because I'm matching variable names exactly against FIPS 204, for example both 'K' and 'k', // or 'A' and 'a' are used and have specific meanings. // But need to tell the rust linter to not care. diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index d46bf98..0fd6432 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -10,7 +10,7 @@ mod mldsa_tests { RNG, SecurityStrength, SignaturePrivateKey, SignaturePublicKey, SignatureVerifier, Signer, Suspendable, }; - use bouncycastle_core_test_framework::DUMMY_SEED_1024; + use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::signature::*; use bouncycastle_hex as hex; @@ -388,11 +388,11 @@ mod mldsa_tests { // Then with the message broken into chunks let mut s = MLDSA44::sign_init(&sk, Some(b"streaming API chunked")).unwrap(); s.set_signer_rnd(rnd); - for msg_chunk in DUMMY_SEED_1024.chunks(100) { + for msg_chunk in DUMMY_SEED.chunks(100) { s.sign_update(msg_chunk); } let sig_val = s.sign_final().unwrap(); - MLDSA44::verify(&sk.derive_pk(), DUMMY_SEED_1024, Some(b"streaming API chunked"), &sig_val) + MLDSA44::verify(&sk.derive_pk(), DUMMY_SEED, Some(b"streaming API chunked"), &sig_val) .unwrap(); // ML-DSA-65 diff --git a/crypto/mlkem/src/lib.rs b/crypto/mlkem/src/lib.rs index c7ffde8..0fc793d 100644 --- a/crypto/mlkem/src/lib.rs +++ b/crypto/mlkem/src/lib.rs @@ -137,8 +137,8 @@ //! constant-time after compilation. #![no_std] -#![forbid(missing_docs)] #![forbid(unsafe_code)] +#![forbid(missing_docs)] // These are because I'm matching variable names exactly against FIPS 204, for example both 'K' and 'k', // or 'A' and 'a' are used and have specific meanings. // But need to tell the rust linter to not care. diff --git a/crypto/rng/src/hash_drbg80090a.rs b/crypto/rng/src/hash_drbg80090a.rs index 6a96060..2c84f73 100644 --- a/crypto/rng/src/hash_drbg80090a.rs +++ b/crypto/rng/src/hash_drbg80090a.rs @@ -34,14 +34,13 @@ trait HashDRBG80090AParams { const RESEED_INTERVAL: u64; } +/// The parameters for HashDRBG with SHA256. #[allow(non_camel_case_types)] pub struct HashDRBG80090AParams_SHA256 {} impl HashDRBG80090AParams for HashDRBG80090AParams_SHA256 { const HASH: SupportedHash = SupportedHash::SHA256; - // const OUT_LEN: usize = 64; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; - // const SEED_LEN: usize = 440 / 8; const MAX_LENGTH: u64 = (1u64 << 35) / 8; // 2^35 bits const MAX_PERSONALIZATION_STRING_LENGTH: u64 = (1u64 << 35) / 8; // 2^35 bits const MAX_ADDITIONAL_INPUT_LENGTH: u64 = (1u64 << 35) / 8; // 2^35 bits @@ -49,13 +48,12 @@ impl HashDRBG80090AParams for HashDRBG80090AParams_SHA256 { const RESEED_INTERVAL: u64 = 1u64 << 48; // 2^48 requests } +/// The parameters for HashDRBG with SHA256. #[allow(non_camel_case_types)] pub struct HashDRBG80090AParams_SHA512 {} impl HashDRBG80090AParams for HashDRBG80090AParams_SHA512 { const HASH: SupportedHash = SupportedHash::SHA512; - // const OUT_LEN: usize = 64; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; - // const SEED_LEN: usize = 888 / 8; const MAX_LENGTH: u64 = (1u64 << 35) / 8; // 2^35 bits const MAX_PERSONALIZATION_STRING_LENGTH: u64 = (1u64 << 35) / 8; // 2^35 bits const MAX_ADDITIONAL_INPUT_LENGTH: u64 = (1u64 << 35) / 8; // 2^35 bits @@ -63,14 +61,14 @@ impl HashDRBG80090AParams for HashDRBG80090AParams_SHA512 { const RESEED_INTERVAL: u64 = 1u64 << 48; // 2^48 requests } -// TODO: is there a rustacious way to extract this from HASH? +// TODO: replace this once the generic_const_exprs feature lands in the stable rust compiler. const LARGEST_HASHER_OUTPUT_LEN: usize = 64; #[allow(private_bounds)] /// Implementation of the Hash_DRBG algorithm as specified in NIST SP 800-90Ar1. pub struct HashDRBG80090A { _phantom: core::marker::PhantomData, - // Rust is stupid. What's the point of having a generic parameter if we can't use constants inside it? + // TODO: replace this once the generic_const_exprs feature lands in the stable rust compiler. // state: WorkingState, state: WorkingState, admin_info: AdministrativeInfo, diff --git a/crypto/rng/src/lib.rs b/crypto/rng/src/lib.rs index 19c60bc..1f36c5d 100644 --- a/crypto/rng/src/lib.rs +++ b/crypto/rng/src/lib.rs @@ -28,6 +28,7 @@ //! cryptographic application. #![forbid(unsafe_code)] +#![forbid(missing_docs)] use crate::hash_drbg80090a::{ HashDRBG80090A, HashDRBG80090AParams_SHA256, HashDRBG80090AParams_SHA512, @@ -44,18 +45,25 @@ use bouncycastle_core::key_material::KeyType; pub mod hash_drbg80090a; /*** String constants ***/ +/// pub const HASH_DRBG_SHA256_NAME: &str = "HashDRBG-SHA256"; +/// pub const HASH_DRBG_SHA512_NAME: &str = "HashDRBG-SHA512"; /*** pub types ***/ +/// Public type for HashDRBG using SHA256. #[allow(non_camel_case_types)] pub type HashDRBG_SHA256 = HashDRBG80090A; +/// Public type for HashDRBG using SHA512. #[allow(non_camel_case_types)] pub type HashDRBG_SHA512 = HashDRBG80090A; /*** Defaults ***/ +/// The library's default RNG. pub type DefaultRNG = HashDRBG_SHA512; +/// The library's default RNG at the 128-bit security level. pub type Default128BitRNG = HashDRBG_SHA256; +/// The library's default RNG at the 256-bit security level. pub type Default256BitRNG = HashDRBG_SHA512; /// Implements the five functions specified in SP 800-90A section 7.4 are instantate, generate, reseed, uninstantiate, and health_test. diff --git a/crypto/rng/tests/hash_drbg80090a_tests.rs b/crypto/rng/tests/hash_drbg80090a_tests.rs index c03f71b..beb779b 100644 --- a/crypto/rng/tests/hash_drbg80090a_tests.rs +++ b/crypto/rng/tests/hash_drbg80090a_tests.rs @@ -5,7 +5,7 @@ mod tests { KeyMaterial, KeyMaterial0, KeyMaterial256, KeyMaterialTrait, KeyType, }; use bouncycastle_core::traits::{RNG, SecurityStrength}; - use bouncycastle_core_test_framework::DUMMY_SEED_512; + use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_rng::Sp80090ADrbg; use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; @@ -52,8 +52,7 @@ mod tests { Err(RNGError::Uninitialized) => { /* good */ } _ => panic!("Expected Uninitialized error"), } - let seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Seed).unwrap(); + let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap(); rng.instantiate(false, seed, &KeyMaterial0::new(), &[], SecurityStrength::_128bit).unwrap(); rng.generate_out(&[], &mut out).unwrap(); assert_ne!(out, [0u8; 32]); @@ -61,8 +60,7 @@ mod tests { // Success case: seed len equals required entropy let mut rng = HashDRBG_SHA256::new_unititialized(); let mut out = [0u8; 32]; - let seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::Seed).unwrap(); + let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::Seed).unwrap(); rng.instantiate(false, seed, &KeyMaterial0::new(), &[], SecurityStrength::_128bit).unwrap(); rng.generate_out(&[], &mut out).unwrap(); assert_ne!(out, [0u8; 32]); @@ -70,7 +68,7 @@ mod tests { // Error case: seed != KeyType::Seed let mut rng = HashDRBG_SHA256::new_unititialized(); let seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::SymmetricCipherKey) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::SymmetricCipherKey) .unwrap(); match rng.instantiate(false, seed, &KeyMaterial0::new(), &[], SecurityStrength::_128bit) { Err(RNGError::KeyMaterialError(_)) => { /* good */ } @@ -79,7 +77,7 @@ mod tests { // Error case: seed too short let mut rng = HashDRBG_SHA256::new_unititialized(); - let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..8], KeyType::Seed).unwrap(); + let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..8], KeyType::Seed).unwrap(); match rng.instantiate(false, seed, &KeyMaterial0::new(), &[], SecurityStrength::_128bit) { Err(RNGError::KeyMaterialError(_)) => { /* good */ } _ => panic!("Expected KeyMaterialError error"), @@ -89,8 +87,7 @@ mod tests { // Error case: security strength requested at init is higher than the underlying hash function's max security strength let mut rng = HashDRBG_SHA256::new_unititialized(); - let seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Seed).unwrap(); + let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap(); match rng.instantiate(false, seed, &KeyMaterial0::new(), &[], SecurityStrength::_256bit) { Err(RNGError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) => { /* good */ } _ => panic!("Expected KeyMaterialError error"), @@ -99,22 +96,18 @@ mod tests { // Success case: security strength requested at init is lower than the underlying hash function's max security strength // ... 112 bit let mut rng = HashDRBG_SHA256::new_unititialized(); - let seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Seed).unwrap(); + let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap(); rng.instantiate(false, seed, &KeyMaterial0::new(), &[], SecurityStrength::_128bit).unwrap(); // ... 128 bit let mut rng = HashDRBG_SHA256::new_unititialized(); - let seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Seed).unwrap(); + let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap(); rng.instantiate(false, seed, &KeyMaterial0::new(), &[], SecurityStrength::_128bit).unwrap(); // Error case: double initialize let mut rng = HashDRBG_SHA256::new_unititialized(); - let seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Seed).unwrap(); + let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap(); rng.instantiate(false, seed, &KeyMaterial0::new(), &[], SecurityStrength::_128bit).unwrap(); - let seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Seed).unwrap(); + let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap(); match rng.instantiate(false, seed, &KeyMaterial0::new(), &[], SecurityStrength::_128bit) { Err(RNGError::GenericError(_)) => { /*good*/ } _ => panic!("Expected GenericError error"), @@ -125,20 +118,17 @@ mod tests { fn test_reseed() { // Basic success case let mut rng = HashDRBG_SHA256::new_from_os(); - let seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Seed).unwrap(); + let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap(); rng.reseed(&seed, &[0u8; 32]).unwrap(); // Success case: seed len equals required entropy let mut rng = HashDRBG_SHA256::new_from_os(); - let seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::Seed).unwrap(); + let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::Seed).unwrap(); rng.reseed(&seed, &[0u8; 32]).unwrap(); // Error case: uninitialized let mut rng = HashDRBG_SHA256::new_unititialized(); - let seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Seed).unwrap(); + let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap(); match rng.reseed(&seed, &[0u8; 32]) { Err(RNGError::Uninitialized) => { /*good*/ } _ => panic!("Expected Uninitialized error"), @@ -147,7 +137,7 @@ mod tests { // Error case: seed != KeyType::Seed let mut rng = HashDRBG_SHA256::new_from_os(); let seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::SymmetricCipherKey) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::SymmetricCipherKey) .unwrap(); match rng.reseed(&seed, &[0u8; 32]) { Err(RNGError::KeyMaterialError(_)) => { /* good */ } @@ -156,7 +146,7 @@ mod tests { // Error case: seed too short let mut rng = HashDRBG_SHA256::new(); - let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..8], KeyType::Seed).unwrap(); + let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..8], KeyType::Seed).unwrap(); match rng.reseed(&seed, &[0u8; 32]) { Err(RNGError::KeyMaterialError(_)) => { /* good */ } _ => panic!("Expected KeyMaterialError error"), @@ -298,8 +288,7 @@ mod tests { #[test] fn test_rng_trait() { let mut rng = HashDRBG_SHA256::new_from_os(); - let seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Seed).unwrap(); + let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap(); /* test add_seed_keymaterial */ rng.add_seed_keymaterial(&seed).unwrap(); diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index 77060a5..4d00a77 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -66,6 +66,7 @@ //! ``` #![forbid(unsafe_code)] +#![forbid(missing_docs)] #![allow(private_bounds)] mod sha256; @@ -80,19 +81,27 @@ use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams, Security use bouncycastle_core::traits::Suspendable; /*** String constants ***/ +/// pub const SHA224_NAME: &str = "SHA224"; +/// pub const SHA256_NAME: &str = "SHA256"; +/// pub const SHA384_NAME: &str = "SHA384"; +/// pub const SHA512_NAME: &str = "SHA512"; /*** pub types ***/ +/// Public type for SHA224. pub type SHA224 = SHA256Internal; +/// Public type for SHA256. pub type SHA256 = SHA256Internal; +/// Public type for SHA384. pub type SHA384 = SHA512Internal; +/// Public type for SHA512. pub type SHA512 = SHA512Internal; /*** Param traits ***/ - +/// Private trait on purpose so that only the NIST-approved params can be used. trait SHA2Params: HashAlgParams {} /*** SHA224 ***/ @@ -100,7 +109,7 @@ impl HashAlgParams for SHA224 { const OUTPUT_LEN: usize = 28; const BLOCK_LEN: usize = 64; } -/// Assigned by NIST in the Computer Security Objects Register +/// The parameters for SHA224. #[derive(Clone)] pub struct SHA224Params; impl Algorithm for SHA224Params { @@ -124,6 +133,7 @@ impl HashAlgParams for SHA256 { const OUTPUT_LEN: usize = 32; const BLOCK_LEN: usize = 64; } +/// The parameters for SHA256. #[derive(Clone)] pub struct SHA256Params; impl Algorithm for SHA256Params { @@ -147,6 +157,7 @@ impl HashAlgParams for SHA384 { const OUTPUT_LEN: usize = 48; const BLOCK_LEN: usize = 128; } +/// The parameters for SHA384. #[derive(Clone)] pub struct SHA384Params; impl Algorithm for SHA384Params { @@ -166,6 +177,7 @@ impl HashAlgParams for SHA384Params { impl SHA2Params for SHA384Params {} /*** SHA512 ***/ +/// The parameters for SHA512. #[derive(Clone)] pub struct SHA512Params; impl HashAlgParams for SHA512 { diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index cd92454..f93dc8a 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -144,6 +144,9 @@ impl Sha256State { } } +/// Internal struct for SHA256. +/// This uses a private bound so that you cannot instantiate it directly and have to use the +/// provided and NIST-approved parameters. #[derive(Clone)] pub struct SHA256Internal { _params: core::marker::PhantomData, @@ -161,6 +164,7 @@ impl Drop for SHA256Internal { } impl SHA256Internal { + /// Creates a new SHA256 instance, ready for use. pub fn new() -> Self { Self { _params: core::marker::PhantomData, diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 3a031bc..282efa9 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -158,8 +158,9 @@ impl Sha512State { } } -// todo -- cleanup -// #[derive(Clone, Copy)] +/// Internal struct for SHA512. +/// This uses a private bound so that you cannot instantiate it directly and have to use the +/// provided and NIST-approved parameters. #[derive(Clone)] pub struct SHA512Internal { _params: std::marker::PhantomData, @@ -176,6 +177,7 @@ impl Drop for SHA512Internal { } impl SHA512Internal { + /// Creates a new SHA512 instance, ready for use. pub fn new() -> Self { Self { _params: std::marker::PhantomData, diff --git a/crypto/sha2/tests/sha2_tests.rs b/crypto/sha2/tests/sha2_tests.rs index 38b4e2f..23667eb 100644 --- a/crypto/sha2/tests/sha2_tests.rs +++ b/crypto/sha2/tests/sha2_tests.rs @@ -2,14 +2,13 @@ mod sha2_tests { use bouncycastle_core::errors::SuspendableError; use bouncycastle_core::traits::{Algorithm, Hash, HashAlgParams, SecurityStrength}; - use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::hash::TestFrameworkHash; use bouncycastle_sha2::*; #[cfg(test)] mod core_test_framework_hash { use super::*; - use bouncycastle_core_test_framework::DUMMY_SEED_1024; + use bouncycastle_core_test_framework::DUMMY_SEED; #[test] fn sha224() { @@ -19,8 +18,8 @@ mod sha2_tests { test_framework.test_hash::(b"a", b"\xab\xd3\x75\x34\xc7\xd9\xa2\xef\xb9\x46\x5d\xe9\x31\xcd\x70\x55\xff\xdb\x88\x79\x56\x3a\xe9\x80\x78\xd6\xd6\xd5"); test_framework.test_hash::(b"abc", b"\x23\x09\x7d\x22\x34\x05\xd8\x22\x86\x42\xa4\x77\xbd\xa2\x55\xb3\x2a\xad\xbc\xe4\xbd\xa0\xb3\xf7\xe3\x6c\x9d\xa7"); test_framework.test_hash::(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", b"\x75\x38\x8b\x16\x51\x27\x76\xcc\x5d\xba\x5d\xa1\xfd\x89\x01\x50\xb0\xc6\x45\x5c\xb4\xf5\x8b\x19\x52\x52\x25\x25"); - test_framework.test_hash::(DUMMY_SEED_512, b"\xb8\x06\x0c\xcc\x82\xd4\x0c\x57\x61\x56\xf7\xca\x03\x33\xe4\x38\x9e\x41\x0d\xf0\x27\xd2\xfb\x8f\x76\x4f\xa6\x03"); - test_framework.test_hash::(DUMMY_SEED_1024, b"\x62\x90\x81\x7f\x60\x01\x43\x2c\xd4\x41\x05\x8d\x2b\xb8\x2d\x88\xb3\xf3\x24\x25\xad\xe4\xc9\x3d\x56\x20\x78\x38"); + test_framework.test_hash::(&DUMMY_SEED[..512], b"\xb8\x06\x0c\xcc\x82\xd4\x0c\x57\x61\x56\xf7\xca\x03\x33\xe4\x38\x9e\x41\x0d\xf0\x27\xd2\xfb\x8f\x76\x4f\xa6\x03"); + test_framework.test_hash::(DUMMY_SEED, b"\x62\x90\x81\x7f\x60\x01\x43\x2c\xd4\x41\x05\x8d\x2b\xb8\x2d\x88\xb3\xf3\x24\x25\xad\xe4\xc9\x3d\x56\x20\x78\x38"); } #[test] @@ -31,7 +30,7 @@ mod sha2_tests { test_framework.test_hash::(b"a", b"\xca\x97\x81\x12\xca\x1b\xbd\xca\xfa\xc2\x31\xb3\x9a\x23\xdc\x4d\xa7\x86\xef\xf8\x14\x7c\x4e\x72\xb9\x80\x77\x85\xaf\xee\x48\xbb"); test_framework.test_hash::(b"abc", b"\xba\x78\x16\xbf\x8f\x01\xcf\xea\x41\x41\x40\xde\x5d\xae\x22\x23\xb0\x03\x61\xa3\x96\x17\x7a\x9c\xb4\x10\xff\x61\xf2\x00\x15\xad"); test_framework.test_hash::(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", b"\x24\x8d\x6a\x61\xd2\x06\x38\xb8\xe5\xc0\x26\x93\x0c\x3e\x60\x39\xa3\x3c\xe4\x59\x64\xff\x21\x67\xf6\xec\xed\xd4\x19\xdb\x06\xc1"); - test_framework.test_hash::(DUMMY_SEED_512, b"\x11\x00\x09\xdc\xee\x21\x62\x0b\x16\x6f\x3a\xbf\xec\xb5\xef\xf7\xa8\x73\xbe\x72\x9d\x1c\x2d\x53\x82\x2e\x7a\xcc\x5f\x34\xeb\x9b"); + test_framework.test_hash::(&DUMMY_SEED[..512], b"\x11\x00\x09\xdc\xee\x21\x62\x0b\x16\x6f\x3a\xbf\xec\xb5\xef\xf7\xa8\x73\xbe\x72\x9d\x1c\x2d\x53\x82\x2e\x7a\xcc\x5f\x34\xeb\x9b"); } #[test] @@ -42,7 +41,7 @@ mod sha2_tests { test_framework.test_hash::(b"a", b"\x54\xa5\x9b\x9f\x22\xb0\xb8\x08\x80\xd8\x42\x7e\x54\x8b\x7c\x23\xab\xd8\x73\x48\x6e\x1f\x03\x5d\xce\x9c\xd6\x97\xe8\x51\x75\x03\x3c\xaa\x88\xe6\xd5\x7b\xc3\x5e\xfa\xe0\xb5\xaf\xd3\x14\x5f\x31"); test_framework.test_hash::(b"abc", b"\xcb\x00\x75\x3f\x45\xa3\x5e\x8b\xb5\xa0\x3d\x69\x9a\xc6\x50\x07\x27\x2c\x32\xab\x0e\xde\xd1\x63\x1a\x8b\x60\x5a\x43\xff\x5b\xed\x80\x86\x07\x2b\xa1\xe7\xcc\x23\x58\xba\xec\xa1\x34\xc8\x25\xa7"); test_framework.test_hash::(b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", b"\x09\x33\x0c\x33\xf7\x11\x47\xe8\x3d\x19\x2f\xc7\x82\xcd\x1b\x47\x53\x11\x1b\x17\x3b\x3b\x05\xd2\x2f\xa0\x80\x86\xe3\xb0\xf7\x12\xfc\xc7\xc7\x1a\x55\x7e\x2d\xb9\x66\xc3\xe9\xfa\x91\x74\x60\x39"); - test_framework.test_hash::(DUMMY_SEED_512, b"\x45\x82\xfc\x82\x43\x0e\x52\x68\x86\xa1\x85\x34\x11\xe6\x06\x45\xfe\xf7\xe8\xea\x0c\x85\x46\xb7\xc9\xba\x0c\x84\x16\xd9\xa9\x8f\xb5\x2e\xbd\x0c\x60\x5f\xbb\x70\x74\x9c\x4e\x3e\x5d\xa3\xdb\xac"); + test_framework.test_hash::(&DUMMY_SEED[..512], b"\x45\x82\xfc\x82\x43\x0e\x52\x68\x86\xa1\x85\x34\x11\xe6\x06\x45\xfe\xf7\xe8\xea\x0c\x85\x46\xb7\xc9\xba\x0c\x84\x16\xd9\xa9\x8f\xb5\x2e\xbd\x0c\x60\x5f\xbb\x70\x74\x9c\x4e\x3e\x5d\xa3\xdb\xac"); } #[test] @@ -53,7 +52,7 @@ mod sha2_tests { test_framework.test_hash::(b"a", b"\x1f\x40\xfc\x92\xda\x24\x16\x94\x75\x09\x79\xee\x6c\xf5\x82\xf2\xd5\xd7\xd2\x8e\x18\x33\x5d\xe0\x5a\xbc\x54\xd0\x56\x0e\x0f\x53\x02\x86\x0c\x65\x2b\xf0\x8d\x56\x02\x52\xaa\x5e\x74\x21\x05\x46\xf3\x69\xfb\xbb\xce\x8c\x12\xcf\xc7\x95\x7b\x26\x52\xfe\x9a\x75"); test_framework.test_hash::(b"abc", b"\xdd\xaf\x35\xa1\x93\x61\x7a\xba\xcc\x41\x73\x49\xae\x20\x41\x31\x12\xe6\xfa\x4e\x89\xa9\x7e\xa2\x0a\x9e\xee\xe6\x4b\x55\xd3\x9a\x21\x92\x99\x2a\x27\x4f\xc1\xa8\x36\xba\x3c\x23\xa3\xfe\xeb\xbd\x45\x4d\x44\x23\x64\x3c\xe8\x0e\x2a\x9a\xc9\x4f\xa5\x4c\xa4\x9f"); test_framework.test_hash::(b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", b"\x8e\x95\x9b\x75\xda\xe3\x13\xda\x8c\xf4\xf7\x28\x14\xfc\x14\x3f\x8f\x77\x79\xc6\xeb\x9f\x7f\xa1\x72\x99\xae\xad\xb6\x88\x90\x18\x50\x1d\x28\x9e\x49\x00\xf7\xe4\x33\x1b\x99\xde\xc4\xb5\x43\x3a\xc7\xd3\x29\xee\xb6\xdd\x26\x54\x5e\x96\xe5\x5b\x87\x4b\xe9\x09"); - test_framework.test_hash::(DUMMY_SEED_512, b"\xed\xb9\xbe\xd7\x21\xaa\x6a\x5f\x6f\xbc\x66\x19\xd3\xa3\xc2\xbe\x3d\x04\x30\x43\xf0\x5a\x9a\xeb\xc7\xb1\x19\x7a\x2a\xa9\xc4\x9a\x57\xd5\xdd\xd4\x67\x4c\x17\x85\x78\x50\x88\xd9\xf1\xff\x42\xc7\x97\xa0\x2a\xdc\x9b\x81\x7a\x13\x9a\x50\x97\x0d\xa6\xc9\x95\x24"); + test_framework.test_hash::(&DUMMY_SEED[..512], b"\xed\xb9\xbe\xd7\x21\xaa\x6a\x5f\x6f\xbc\x66\x19\xd3\xa3\xc2\xbe\x3d\x04\x30\x43\xf0\x5a\x9a\xeb\xc7\xb1\x19\x7a\x2a\xa9\xc4\x9a\x57\xd5\xdd\xd4\x67\x4c\x17\x85\x78\x50\x88\xd9\xf1\xff\x42\xc7\x97\xa0\x2a\xdc\x9b\x81\x7a\x13\x9a\x50\x97\x0d\xa6\xc9\x95\x24"); } } diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 810b5a8..d32e391 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -98,7 +98,7 @@ //! let output_key = sha3::SHA3_256::new().derive_key(&input_key, b"Additional input").unwrap(); //!``` //! In the previous example, since [KeyMaterial::from_bytes] cannot know the amount of entropy in the input data, -//! it automatically tags it as [KeyType::Unknown], and thus [SHA3::derive_key] produces an output key +//! it automatically tags it as [KeyType::Unknown], and thus [SHA3Internal::derive_key] produces an output key //! which also has type [KeyType::Unknown]. //! This would also be the case even if the input had type //! [KeyType::CryptographicRandom] since the input [KeyMaterial] is 16 bytes but [SHA3_256] needs at least 32 bytes of @@ -136,6 +136,7 @@ //! ``` #![forbid(unsafe_code)] +#![forbid(missing_docs)] #![allow(private_bounds)] use crate::keccak::KeccakSize; @@ -153,24 +154,36 @@ mod sha3; mod shake; /*** String constants ***/ +/// pub const SHA3_224_NAME: &str = "SHA3-224"; +/// pub const SHA3_256_NAME: &str = "SHA3-256"; +/// pub const SHA3_384_NAME: &str = "SHA3-384"; +/// pub const SHA3_512_NAME: &str = "SHA3-512"; +/// pub const SHAKE128_NAME: &str = "SHAKE128"; +/// pub const SHAKE256_NAME: &str = "SHAKE256"; /*** pub types ***/ -pub use sha3::SHA3; -pub use shake::SHAKE; +pub use sha3::SHA3Internal; +pub use shake::SHAKEInternal; pub use keccak::SUSPENDED_SHA3_STATE_LEN; -pub type SHA3_224 = SHA3; -pub type SHA3_256 = SHA3; -pub type SHA3_384 = SHA3; -pub type SHA3_512 = SHA3; -pub type SHAKE128 = SHAKE; -pub type SHAKE256 = SHAKE; +/// Public type for SHA3_224. +pub type SHA3_224 = SHA3Internal; +/// Public type for SHA3_256. +pub type SHA3_256 = SHA3Internal; +/// Public type for SHA3_384. +pub type SHA3_384 = SHA3Internal; +/// Public type for SHA3_512. +pub type SHA3_512 = SHA3Internal; +/// Public type for SHAKE128. +pub type SHAKE128 = SHAKEInternal; +/// Public type for SHAKE256. +pub type SHAKE256 = SHAKEInternal; /*** Param traits ***/ @@ -188,6 +201,7 @@ impl HashAlgParams for SHA3_224 { // const BLOCK_LEN: usize = 64; const BLOCK_LEN: usize = 144; // FIPS 202 Table 3 } +/// The parameters for SHA3_224. #[derive(Clone)] pub struct SHA3_224Params; impl Algorithm for SHA3_224Params { @@ -215,6 +229,7 @@ impl HashAlgParams for SHA3_256 { // const BLOCK_LEN: usize = 64; const BLOCK_LEN: usize = 136; // FIPS 202 Table 3 } +/// The parameters for SHA3_256. #[derive(Clone)] pub struct SHA3_256Params; impl Algorithm for SHA3_256Params { @@ -236,7 +251,7 @@ impl AlgorithmOID for SHA3_256 { const OID_DER: &'static [u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08]; } - +/// The parameters for SHA3_384. #[derive(Clone)] pub struct SHA3_384Params; impl HashAlgParams for SHA3_384 { @@ -263,7 +278,7 @@ impl AlgorithmOID for SHA3_384 { const OID_DER: &'static [u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x09]; } - +/// The parameters for SHA3_512. #[derive(Clone)] pub struct SHA3_512Params; impl HashAlgParams for SHA3_512 { @@ -296,6 +311,7 @@ trait SHAKEParams: Algorithm { /// See [SHA3Params::STATE_TAG]. Must be distinct from every SHA3 *and* SHAKE variant's tag. const STATE_TAG: u8; } +/// The parameters for SHAKE128. #[derive(Clone)] pub struct SHAKE128Params; impl Algorithm for SHAKE128Params { @@ -312,7 +328,7 @@ impl AlgorithmOID for SHAKE128 { const OID_DER: &'static [u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0b]; } - +/// The parameters for SHAKE256. #[derive(Clone)] pub struct SHAKE256Params; impl Algorithm for SHAKE256Params { diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 705e741..061c893 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -10,8 +10,11 @@ use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Algorithm, Hash, KDF, SecurityStrength, Suspendable}; use bouncycastle_utils::{max, min}; +/// Internal struct for SHA3. +/// This uses a private bound so that you cannot instantiate it directly and have to use the +/// provided and NIST-approved parameters. #[derive(Clone)] -pub struct SHA3 { +pub struct SHA3Internal { _params: std::marker::PhantomData, keccak: KeccakDigest, kdf_key_type: KeyType, @@ -21,7 +24,8 @@ pub struct SHA3 { // Note: don't need a zeroizing Drop here because all the sensitive info is in KeccakDigest, which has one. -impl SHA3 { +impl SHA3Internal { + /// Get a new SHA3 instance, ready for use. pub fn new() -> Self { Self { _params: std::marker::PhantomData, @@ -118,18 +122,18 @@ impl SHA3 { } } -impl Default for SHA3 { +impl Default for SHA3Internal { fn default() -> Self { Self::new() } } -impl Algorithm for SHA3 { +impl Algorithm for SHA3Internal { const ALG_NAME: &'static str = PARAMS::ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = PARAMS::MAX_SECURITY_STRENGTH; } -impl Hash for SHA3 { +impl Hash for SHA3Internal { /// As per FIPS 202 Table 3. /// Required, for example, to compute the pad lengths in HMAC. fn block_bitlen(&self) -> usize { @@ -227,7 +231,7 @@ impl Hash for SHA3 { } /// SHA3 is allowed to be used as a KDF in the form HASH(X) as per NIST SP 800-56C. -impl KDF for SHA3 { +impl KDF for SHA3Internal { /// Returns a [KeyMaterial]. /// For the KDF to be considered "fully-seeded" and be capable of outputting full-entropy KeyMaterials, /// it requires full-entropy input that is at least the bit size (ie 256 bits for SHA3-256, etc). @@ -280,7 +284,7 @@ impl KDF for SHA3 { } } -impl Suspendable for SHA3 { +impl Suspendable for SHA3Internal { fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] { let mut out_to_return = [0u8; SUSPENDED_SHA3_STATE_LEN]; @@ -313,7 +317,7 @@ impl Suspendable for SHA3 let (keccak, kdf_key_type, kdf_security_strength, kdf_entropy) = deserialize_sha3_family_state(input, PARAMS::STATE_TAG, rate)?; - Ok(SHA3 { + Ok(SHA3Internal { _params: std::marker::PhantomData, keccak, kdf_key_type, diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index 551e6fc..94ebea3 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -10,7 +10,15 @@ use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Algorithm, KDF, SecurityStrength, Suspendable, XOF}; use bouncycastle_utils::{max, min}; -/// Note: FIPS 202 section 7 states: +/// Internal struct for SHAKE. +/// This uses a private bound so that you cannot instantiate it directly and have to use the +/// provided and NIST-approved parameters. +/// +/// +/// +/// Note that even though SHAKE is physically capable of acting as a hash function, and in fact is secure +/// as such if the provided message includes the requested length, SHAKE does not implement the [Hash] trait. +/// FIPS 202 section 7 states: /// /// "SHAKE128 and SHAKE256 are approved XOFs, whose approved uses will be specified in /// NIST Special Publications. Although some of those uses may overlap with the uses of approved @@ -21,12 +29,9 @@ use bouncycastle_utils::{max, min}; /// For example, the first 32 bytes of SHAKE128("message", 64) and SHAKE128("message", 128), will be identical /// and equal to SHAKE128("message", 32). Proper hash functions don't do this, and NIST is concerned that /// this could lead to application vulnerabilities. -/// -/// As such, even though SHAKE is physically capable of acting as a hash function, and in fact is secure -/// as such if the provided message includes the requested length, SHAKE does not implement the [Hash] trait. #[derive(Clone)] -pub struct SHAKE { - _phantomdata: std::marker::PhantomData, +pub struct SHAKEInternal { + _phantomdata: core::marker::PhantomData, keccak: KeccakDigest, kdf_key_type: KeyType, kdf_security_strength: SecurityStrength, @@ -35,15 +40,16 @@ pub struct SHAKE { // Note: don't need a zeroizing Drop here because all the sensitive info is in KeccakDigest, which has one. -impl Algorithm for SHAKE { +impl Algorithm for SHAKEInternal { const ALG_NAME: &'static str = PARAMS::ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = PARAMS::MAX_SECURITY_STRENGTH; } -impl SHAKE { +impl SHAKEInternal { + /// Get a new SHA3 instance, ready for use. pub fn new() -> Self { Self { - _phantomdata: std::marker::PhantomData, + _phantomdata: core::marker::PhantomData, keccak: KeccakDigest::new(PARAMS::SIZE), kdf_key_type: KeyType::Zeroized, kdf_security_strength: SecurityStrength::None, @@ -142,7 +148,7 @@ impl SHAKE { } } -impl Suspendable for SHAKE { +impl Suspendable for SHAKEInternal { fn suspend(self) -> [u8; SUSPENDED_SHA3_STATE_LEN] { let mut out_to_return = [0u8; SUSPENDED_SHA3_STATE_LEN]; @@ -175,8 +181,8 @@ impl Suspendable for SHAKE Suspendable for SHAKE KDF for SHAKE { +impl KDF for SHAKEInternal { /// Returns a [KeyMaterial]. /// For the KDF to be considered "fully-seeded" and be capable of outputting full-entropy KeyMaterials, /// it requires full-entropy input that is at least 2x the bit size (ie 256 bits for SHAKE128, and 512 bits for SHAKE256). @@ -248,13 +254,13 @@ impl KDF for SHAKE { } } -impl Default for SHAKE { +impl Default for SHAKEInternal { fn default() -> Self { Self::new() } } -impl XOF for SHAKE { +impl XOF for SHAKEInternal { fn hash_xof(self, data: &[u8], result_len: usize) -> Vec { self.hash_internal(data, result_len) } diff --git a/crypto/sha3/tests/sha3_tests.rs b/crypto/sha3/tests/sha3_tests.rs index 535e2dd..782b18b 100644 --- a/crypto/sha3/tests/sha3_tests.rs +++ b/crypto/sha3/tests/sha3_tests.rs @@ -6,7 +6,7 @@ mod sha3_tests { KeyMaterial, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; use bouncycastle_core::traits::{Hash, HashAlgParams, KDF, SecurityStrength}; - use bouncycastle_core_test_framework::DUMMY_SEED_512; + use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::hash::TestFrameworkHash; use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; use bouncycastle_sha3::{ @@ -34,57 +34,57 @@ mod sha3_tests { #[test] fn test_framework_hash() { let test_framework = TestFrameworkHash::new(); - test_framework.test_hash::(DUMMY_SEED_512, b"\xFE\x51\xC5\xD7\x62\x48\xE1\xE9\xD3\x01\x29\x6A\xE8\xAB\x94\x69\xD2\x86\x34\xB4\xAD\x3E\x9E\x78\xC8\xB0\x9D\x47"); - test_framework.test_hash::(DUMMY_SEED_512, b"\xD4\x72\x8E\xA5\xE9\xF3\x81\x9F\x2B\x47\x60\x15\x1A\x8F\x80\x2D\xBE\x9F\x94\x1F\xD6\xFB\x59\xB3\x71\x58\x92\x43\x65\x55\x77\x2A"); - test_framework.test_hash::(DUMMY_SEED_512, b"\xd5\x3b\x51\x68\x53\xf5\xac\xb4\xaa\xfd\xa5\x9d\x6f\x74\x0f\x69\x99\xc9\xe5\x21\x1c\x51\x03\x9c\x6d\x64\x5b\xf9\x83\xd7\xba\x0b\xdf\x12\x31\xb5\x50\x90\xb5\x5e\x35\x99\xee\x7a\xaa\x62\xd3\xbf"); - test_framework.test_hash::(DUMMY_SEED_512, b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); + test_framework.test_hash::(&DUMMY_SEED[..512], b"\xFE\x51\xC5\xD7\x62\x48\xE1\xE9\xD3\x01\x29\x6A\xE8\xAB\x94\x69\xD2\x86\x34\xB4\xAD\x3E\x9E\x78\xC8\xB0\x9D\x47"); + test_framework.test_hash::(&DUMMY_SEED[..512], b"\xD4\x72\x8E\xA5\xE9\xF3\x81\x9F\x2B\x47\x60\x15\x1A\x8F\x80\x2D\xBE\x9F\x94\x1F\xD6\xFB\x59\xB3\x71\x58\x92\x43\x65\x55\x77\x2A"); + test_framework.test_hash::(&DUMMY_SEED[..512], b"\xd5\x3b\x51\x68\x53\xf5\xac\xb4\xaa\xfd\xa5\x9d\x6f\x74\x0f\x69\x99\xc9\xe5\x21\x1c\x51\x03\x9c\x6d\x64\x5b\xf9\x83\xd7\xba\x0b\xdf\x12\x31\xb5\x50\x90\xb5\x5e\x35\x99\xee\x7a\xaa\x62\xd3\xbf"); + test_framework.test_hash::(&DUMMY_SEED[..512], b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); } #[test] fn test_static_hash() { // success case -- return vec version - assert_eq!(SHA3_224::new().hash(DUMMY_SEED_512), b"\xFE\x51\xC5\xD7\x62\x48\xE1\xE9\xD3\x01\x29\x6A\xE8\xAB\x94\x69\xD2\x86\x34\xB4\xAD\x3E\x9E\x78\xC8\xB0\x9D\x47"); - assert_eq!(SHA3_256::new().hash(DUMMY_SEED_512), b"\xD4\x72\x8E\xA5\xE9\xF3\x81\x9F\x2B\x47\x60\x15\x1A\x8F\x80\x2D\xBE\x9F\x94\x1F\xD6\xFB\x59\xB3\x71\x58\x92\x43\x65\x55\x77\x2A"); - assert_eq!(SHA3_384::new().hash(DUMMY_SEED_512), b"\xd5\x3b\x51\x68\x53\xf5\xac\xb4\xaa\xfd\xa5\x9d\x6f\x74\x0f\x69\x99\xc9\xe5\x21\x1c\x51\x03\x9c\x6d\x64\x5b\xf9\x83\xd7\xba\x0b\xdf\x12\x31\xb5\x50\x90\xb5\x5e\x35\x99\xee\x7a\xaa\x62\xd3\xbf"); - assert_eq!(SHA3_512::new().hash(DUMMY_SEED_512), b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); + assert_eq!(SHA3_224::new().hash(&DUMMY_SEED[..512]), b"\xFE\x51\xC5\xD7\x62\x48\xE1\xE9\xD3\x01\x29\x6A\xE8\xAB\x94\x69\xD2\x86\x34\xB4\xAD\x3E\x9E\x78\xC8\xB0\x9D\x47"); + assert_eq!(SHA3_256::new().hash(&DUMMY_SEED[..512]), b"\xD4\x72\x8E\xA5\xE9\xF3\x81\x9F\x2B\x47\x60\x15\x1A\x8F\x80\x2D\xBE\x9F\x94\x1F\xD6\xFB\x59\xB3\x71\x58\x92\x43\x65\x55\x77\x2A"); + assert_eq!(SHA3_384::new().hash(&DUMMY_SEED[..512]), b"\xd5\x3b\x51\x68\x53\xf5\xac\xb4\xaa\xfd\xa5\x9d\x6f\x74\x0f\x69\x99\xc9\xe5\x21\x1c\x51\x03\x9c\x6d\x64\x5b\xf9\x83\xd7\xba\x0b\xdf\x12\x31\xb5\x50\x90\xb5\x5e\x35\x99\xee\x7a\xaa\x62\xd3\xbf"); + assert_eq!(SHA3_512::new().hash(&DUMMY_SEED[..512]), b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); // success case -- output slice version // We're just gonna hand an output slice that's too big and the result had better get written to the beginning of it. let mut out: [u8; 64] = [0; 64]; assert_eq!(SHA3_224::new().output_len(), 28); - let bytes_written = SHA3_224::new().hash_out(DUMMY_SEED_512, &mut out); + let bytes_written = SHA3_224::new().hash_out(&DUMMY_SEED[..512], &mut out); assert_eq!(bytes_written, 28); assert_eq!(&out[..28], b"\xFE\x51\xC5\xD7\x62\x48\xE1\xE9\xD3\x01\x29\x6A\xE8\xAB\x94\x69\xD2\x86\x34\xB4\xAD\x3E\x9E\x78\xC8\xB0\x9D\x47"); assert_eq!(SHA3_256::new().output_len(), 32); - let bytes_written = SHA3_256::new().hash_out(DUMMY_SEED_512, &mut out); + let bytes_written = SHA3_256::new().hash_out(&DUMMY_SEED[..512], &mut out); assert_eq!(&out[..32], b"\xD4\x72\x8E\xA5\xE9\xF3\x81\x9F\x2B\x47\x60\x15\x1A\x8F\x80\x2D\xBE\x9F\x94\x1F\xD6\xFB\x59\xB3\x71\x58\x92\x43\x65\x55\x77\x2A"); assert_eq!(bytes_written, 32); assert_eq!(SHA3_384::new().output_len(), 48); - let bytes_written = SHA3_384::new().hash_out(DUMMY_SEED_512, &mut out); + let bytes_written = SHA3_384::new().hash_out(&DUMMY_SEED[..512], &mut out); assert_eq!(&out[..48], b"\xd5\x3b\x51\x68\x53\xf5\xac\xb4\xaa\xfd\xa5\x9d\x6f\x74\x0f\x69\x99\xc9\xe5\x21\x1c\x51\x03\x9c\x6d\x64\x5b\xf9\x83\xd7\xba\x0b\xdf\x12\x31\xb5\x50\x90\xb5\x5e\x35\x99\xee\x7a\xaa\x62\xd3\xbf"); assert_eq!(bytes_written, 48); assert_eq!(SHA3_512::new().output_len(), 64); - let bytes_written = SHA3_512::new().hash_out(DUMMY_SEED_512, &mut out); + let bytes_written = SHA3_512::new().hash_out(&DUMMY_SEED[..512], &mut out); assert_eq!(&out, b"\x58\x4c\xc7\x02\xc2\x22\x9a\x0a\xbc\x78\x9b\xfa\x64\xb4\x27\x1f\xb8\xf0\xbb\x78\x67\x15\x88\xb9\xef\x1d\x09\x3e\xa3\xd4\x72\x58\x4c\x6d\x43\xb5\x68\x33\x59\x47\x2f\x44\x1b\x33\x85\x6f\x68\x28\x59\xf0\xc3\x95\x4b\x56\x80\x8f\xd1\xfb\xa0\xb5\x9c\x9d\x19\x54"); assert_eq!(bytes_written, 64); // check that the bytes of an oversized output buffer past the digest length get zeroized. - let mut out = DUMMY_SEED_512.clone(); - SHA3_256::new().hash_out(DUMMY_SEED_512, &mut out); + let mut out = DUMMY_SEED.clone(); + SHA3_256::new().hash_out(DUMMY_SEED, &mut out); assert!(out[32..].iter().all(|&b| b == 0)); } #[test] fn test_do_update() { // success case -- return vec version - let output1 = SHA3_224::new().hash(DUMMY_SEED_512); + let output1 = SHA3_224::new().hash(DUMMY_SEED); let mut sha3 = SHA3_224::new(); - for i in (0..DUMMY_SEED_512.len()).step_by(8) { - sha3.do_update(&DUMMY_SEED_512[i..(i + 8)]); + for i in (0..DUMMY_SEED.len()).step_by(8) { + sha3.do_update(&DUMMY_SEED[i..(i + 8)]); } let output2 = sha3.do_final(); @@ -94,8 +94,8 @@ mod sha3_tests { // let output1 = SHA3_224::new().hashes(DUMMY_SEED); // already have this above let mut sha3 = SHA3_224::new(); - for i in (0..DUMMY_SEED_512.len()).step_by(8) { - sha3.do_update(&DUMMY_SEED_512[i..(i + 8)]); + for i in (0..DUMMY_SEED.len()).step_by(8) { + sha3.do_update(&DUMMY_SEED[i..(i + 8)]); } let mut output2 = [0u8; SHA3_224::OUTPUT_LEN]; sha3.do_final_out(&mut output2); @@ -149,7 +149,7 @@ mod sha3_tests { for len in 0..SHA3_224::OUTPUT_LEN { let mut output = vec![0u8; len]; let mut sha3 = SHA3_224::new(); - sha3.do_update(DUMMY_SEED_512); + sha3.do_update(&DUMMY_SEED[..512]); let bytes_written = sha3.do_final_out(&mut output); assert_eq!(bytes_written, len); assert_eq!(output, expected_output[..len]); @@ -160,7 +160,7 @@ mod sha3_tests { fn test_kdf() { let testframework = TestFrameworkKDF::new(); - let key_material = KeyMaterial256::from_bytes(&DUMMY_SEED_512[..32]).unwrap(); + let key_material = KeyMaterial256::from_bytes(&DUMMY_SEED[..32]).unwrap(); // Without additional input let derived_key = SHA3_256::new().derive_key(&key_material, &[0u8; 0]).unwrap(); @@ -216,7 +216,7 @@ mod sha3_tests { #[test] fn test_kdf_undersized_and_oversized() { - let key_material = KeyMaterial256::from_bytes(&DUMMY_SEED_512[..32]).unwrap(); + let key_material = KeyMaterial256::from_bytes(&DUMMY_SEED[..32]).unwrap(); // at size let mut derived_key = KeyMaterial::<32>::new(); @@ -251,7 +251,7 @@ mod sha3_tests { // Exact entropy let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::CryptographicRandom) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHA3_256::new().derive_key(&key_material, &[0u8; 0]).unwrap(); let expected_key = KeyMaterial256::from_bytes(b"\x05\x0a\x48\x73\x3b\xd5\xc2\x75\x6b\xa9\x5c\x58\x28\xcc\x83\xee\x16\xfa\xbc\xd3\xc0\x86\x88\x5b\x77\x44\xf8\x4a\x0f\x9e\x0d\x94").unwrap(); @@ -261,7 +261,7 @@ mod sha3_tests { // more entropy than needed -- single input key let key_material = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::CryptographicRandom) + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHA3_256::new().derive_key(&key_material, &[0u8; 0]).unwrap(); assert_eq!(derived_key.key_type(), KeyType::CryptographicRandom); @@ -270,7 +270,7 @@ mod sha3_tests { // more entropy than needed -- single input key // but if you use SHA512 then you get SecurityStrength::_256bit let key_material = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::CryptographicRandom) + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHA3_512::new().derive_key(&key_material, &[0u8; 0]).unwrap(); assert_eq!(derived_key.key_type(), KeyType::CryptographicRandom); @@ -278,7 +278,7 @@ mod sha3_tests { // more entropy than needed -- multiple input keys let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::CryptographicRandom) .unwrap(); let keys = [&key_material, &key_material]; let derived_key = SHA3_256::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); @@ -288,9 +288,9 @@ mod sha3_tests { // more entropy than needed -- multiple input keys of different full-entropy types; // should get the type of the first one let key_material1 = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::MACKey).unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::MACKey).unwrap(); let key_material2 = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::SymmetricCipherKey) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::SymmetricCipherKey) .unwrap(); let keys = [&key_material1, &key_material2]; let derived_key = SHA3_256::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); @@ -307,14 +307,14 @@ mod sha3_tests { // less entropy than needed -- various permutations, but not exhaustive let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHA3_256::new().derive_key(&key_material, &[0u8; 0]).unwrap(); assert_eq!(derived_key.key_type(), KeyType::Unknown); assert_eq!(derived_key.security_strength(), SecurityStrength::None); let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::CryptographicRandom) .unwrap(); let keys = [&key_material, &key_material]; let derived_key = SHA3_512::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); @@ -322,16 +322,16 @@ mod sha3_tests { assert_eq!(derived_key.security_strength(), SecurityStrength::None); let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..8], KeyType::CryptographicRandom) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..8], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHA3_224::new().derive_key(&key_material, &[0u8; 0]).unwrap(); assert_eq!(derived_key.key_type(), KeyType::Unknown); assert_eq!(derived_key.security_strength(), SecurityStrength::None); let key_low_entropy = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Unknown).unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Unknown).unwrap(); let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::CryptographicRandom) .unwrap(); let keys = [&key_material, &key_low_entropy]; let derived_key = SHA3_256::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); @@ -343,7 +343,7 @@ mod sha3_tests { fn kdf_key_type_conversions() { // This will fail because the input is automatically tagged as BytesLowEntropy, // which is preserved by a call to KDF::new().derive_key(), and cannot by safely converted to MACKey. - let input_seed = KeyMaterial256::from_bytes(&DUMMY_SEED_512[..32]).expect("Error happened"); + let input_seed = KeyMaterial256::from_bytes(&DUMMY_SEED[..32]).expect("Error happened"); let mut output_seed = SHA3_256::new().derive_key(&input_seed, b"nytimes.com").expect("Error happened"); match output_seed.set_key_type(KeyType::MACKey) { @@ -356,7 +356,7 @@ mod sha3_tests { } // This works because we allow hazardous conversions before doing the conversion. - let input_seed = KeyMaterial256::from_bytes(&DUMMY_SEED_512[..32]).expect("Error happened"); + let input_seed = KeyMaterial256::from_bytes(&DUMMY_SEED[..32]).expect("Error happened"); let mut output_seed = SHA3_256::new() .derive_key(&input_seed, b"some addtional input to the KDF") .expect("Error happened"); @@ -369,7 +369,7 @@ mod sha3_tests { // This works because we explicitly tag the input data as BytesFullEntropy. // This is the preferred and better way to do it. let input_seed = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::CryptographicRandom) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::CryptographicRandom) .expect("Error happened"); let output_seed = SHA3_256::new().derive_key(&input_seed, b"nytimes.com").expect("Error happened"); diff --git a/crypto/sha3/tests/shake_tests.rs b/crypto/sha3/tests/shake_tests.rs index 0140874..07ee29c 100644 --- a/crypto/sha3/tests/shake_tests.rs +++ b/crypto/sha3/tests/shake_tests.rs @@ -7,7 +7,7 @@ mod shake_tests { KeyMaterial, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; use bouncycastle_core::traits::{KDF, SecurityStrength, XOF}; - use bouncycastle_core_test_framework::DUMMY_SEED_512; + use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::kdf::TestFrameworkKDF; use bouncycastle_sha3::{SHA3_256, SHAKE128, SHAKE256}; @@ -81,7 +81,7 @@ mod shake_tests { fn test_kdf() { let testframework = TestFrameworkKDF::new(); - let key_material = KeyMaterial256::from_bytes(&DUMMY_SEED_512[..32]).unwrap(); + let key_material = KeyMaterial256::from_bytes(&DUMMY_SEED[..32]).unwrap(); // println!("{:x?}", &DUMMY_SEED[..32]); // Without additional input -- SHAKE128 @@ -128,7 +128,7 @@ mod shake_tests { #[test] fn test_kdf_undersized_and_oversized() { - let key_material = KeyMaterial256::from_bytes(&DUMMY_SEED_512[..32]).unwrap(); + let key_material = KeyMaterial256::from_bytes(&DUMMY_SEED[..32]).unwrap(); // at size let mut derived_key = KeyMaterial::<32>::new(); @@ -161,7 +161,7 @@ mod shake_tests { fn kdf_input_entropy() { // Exact entropy let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::CryptographicRandom) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHAKE128::new().derive_key(&key_material, &[0u8; 0]).unwrap(); let expected_key = KeyMaterial256::from_bytes(b"\x06\x6a\x36\x1d\xc6\x75\xf8\x56\xce\xcd\xc0\x2b\x25\x21\x8a\x10\xce\xc0\xce\xcf\x79\x85\x9e\xc0\xfe\xc3\xd4\x09\xe5\x84\x7a\x92").unwrap(); @@ -170,14 +170,14 @@ mod shake_tests { // more entropy than needed -- single input key let key_material = - KeyMaterial512::from_bytes_as_type(&DUMMY_SEED_512[..64], KeyType::CryptographicRandom) + KeyMaterial512::from_bytes_as_type(&DUMMY_SEED[..64], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHAKE128::new().derive_key(&key_material, &[0u8; 0]).unwrap(); assert_eq!(derived_key.key_type(), KeyType::CryptographicRandom); // // more entropy than needed -- multiple input keys let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::CryptographicRandom) .unwrap(); let keys = [&key_material, &key_material]; let derived_key = SHAKE128::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); @@ -186,9 +186,9 @@ mod shake_tests { // more entropy than needed -- multiple input keys of different full-entropy types; // should get the type of the first one let key_material1 = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::MACKey).unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::MACKey).unwrap(); let key_material2 = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::SymmetricCipherKey) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::SymmetricCipherKey) .unwrap(); let keys = [&key_material1, &key_material2]; let derived_key = SHAKE128::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); @@ -197,28 +197,28 @@ mod shake_tests { // // less entropy than needed -- various permutations, but not exhaustive let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..31], KeyType::CryptographicRandom) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..31], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHAKE128::new().derive_key(&key_material, &[0u8; 0]).unwrap(); assert_eq!(derived_key.key_type(), KeyType::Unknown); let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::CryptographicRandom) .unwrap(); let keys = [&key_material, &key_material]; let derived_key = SHAKE256::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); assert_eq!(derived_key.key_type(), KeyType::Unknown); let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..8], KeyType::CryptographicRandom) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..8], KeyType::CryptographicRandom) .unwrap(); let derived_key = SHAKE128::new().derive_key(&key_material, &[0u8; 0]).unwrap(); assert_eq!(derived_key.key_type(), KeyType::Unknown); let key_low_entropy = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..32], KeyType::Unknown).unwrap(); + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Unknown).unwrap(); let key_material = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED_512[..16], KeyType::CryptographicRandom) + KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::CryptographicRandom) .unwrap(); let keys = [&key_material, &key_low_entropy]; let derived_key = SHAKE128::new().derive_key_from_multiple(&keys, &[0u8; 0]).unwrap(); diff --git a/crypto/utils/src/ct.rs b/crypto/utils/src/ct.rs index 96b1950..6f87b8a 100644 --- a/crypto/utils/src/ct.rs +++ b/crypto/utils/src/ct.rs @@ -7,12 +7,12 @@ use core::ops::*; mod sealed { - pub trait Sealed {} + pub(super) trait Sealed {} } -pub struct MaskType(core::marker::PhantomData); +struct MaskType(core::marker::PhantomData); -pub trait SupportedMaskType: sealed::Sealed {} +trait SupportedMaskType: sealed::Sealed {} macro_rules! supported_mask_type { ($($t:ty),+) => { @@ -25,6 +25,7 @@ macro_rules! supported_mask_type { supported_mask_type!(i64, u64); +/// Helper functions for checking some condition on some data using constant-time operations. #[derive(Clone, Copy)] #[must_use] #[repr(transparent)] @@ -42,57 +43,57 @@ impl Condition { pub const TRUE: Self = Self(-1); /// FALSE is the bit vector of all 0's pub const FALSE: Self = Self(0); - + /// pub const fn from_bool() -> Self { Self(-(VALUE as i64)) } - + /// pub const fn from_bool_var(value: bool) -> Self { Self(-(value as i64)) } - + /// pub const fn is_bit_set(value: i64, bit: i64) -> Self { Self(-((value >> bit) & 1)) } - + /// pub const fn is_negative(value: i64) -> Self { Self(value >> 63) } - + /// pub const fn is_not_zero(value: i64) -> Self { Self::is_negative(-Self::or_halves(value)) } - + /// pub const fn is_zero(value: i64) -> Self { Self::is_negative(Self::or_halves(value) - 1) } - + /// pub const fn is_equal(x: i64, y: i64) -> Self { Self::is_zero(x ^ y) } - + /// pub const fn is_lt(x: i64, y: i64) -> Self { Self::is_negative(x - y) } - + /// // Note: haven't found a clever way to make this const, since it either needs a (non-const) not (!) or a boolean OR is_zero. pub fn is_lte(x: i64, y: i64) -> Self { !Self::is_gt(x, y) } - + /// pub const fn is_gt(x: i64, y: i64) -> Self { Self::is_lt(y, x) } - + /// // Note: haven't found a clever way to make this const, since it either needs a (non-const) not (!) or a boolean OR is_zero. pub fn is_gte(x: i64, y: i64) -> Self { !Self::is_lt(x, y) } - + /// pub fn is_within_range(value: i64, min: i64, max: i64) -> Self { Self::is_gte(value, min) & Self::is_lte(value, max) } - + /// pub fn is_in_list(value: i64, list: &[i64]) -> Self { // Research question: is this actually constant-time? // A clever compiler might turn this into a short-circuiting loop. @@ -136,21 +137,19 @@ impl Condition { pub const fn negate(self, value: i64) -> i64 { (value ^ self.0).wrapping_sub(self.0) } - + /// pub const fn or_halves(value: i64) -> i64 { (value | (value >> 32)) & 0xFFFFFFFF } - /// Conditional selection: return `true_value` if the condition is true, otherwise return `false_value`. pub const fn select(self, true_value: i64, false_value: i64) -> i64 { (true_value & self.0) | (false_value & !self.0) } - /// Conditional swap: returns (lhs, rhs) if the condition is true, otherwise returns (rhs, lhs). pub const fn swap(self, lhs: i64, rhs: i64) -> (i64, i64) { (self.select(rhs, lhs), self.select(lhs, rhs)) } - + /// pub const fn to_bool_var(self) -> bool { self.0 != 0 } @@ -166,22 +165,21 @@ impl Condition { /// FALSE is the bit vector of all 0's pub const FALSE: Self = Self(0); - // this is the core logic for constant-time mask generation for unsigned integers - // Unlike signed integers where we can rely on Two's Complement via negation `-(v as i64)`, - // for u64 we must use wrapping subtraction to achieve the all-ones bit pattern (u64::MAX) for true + /// this is the core logic for constant-time mask generation for unsigned integers + /// Unlike signed integers where we can rely on Two's Complement via negation `-(v as i64)`, + /// for u64 we must use wrapping subtraction to achieve the all-ones bit pattern (u64::MAX) for true pub const fn from_bool() -> Self { // If VALUE is true (1) -> 0 - 1 = u64::MAX (All 1s) // If VALUE is false (0) -> 0 - 0 = 0 (All 0s) Self(0u64.wrapping_sub(VALUE as u64)) } - - // the select function manually for u64 - // although a fully generic impl would be the ultimate long-term goal + /// impl the select function manually for u64 + /// although a fully generic impl would be the ultimate long-term goal pub fn select(self, a: u64, b: u64) -> u64 { let mask = self.0; (a & mask) | (b & !mask) } - + /// pub fn is_true(&self) -> bool { self.0 != 0 } diff --git a/crypto/utils/src/lib.rs b/crypto/utils/src/lib.rs index 67644be..fd61120 100644 --- a/crypto/utils/src/lib.rs +++ b/crypto/utils/src/lib.rs @@ -1,4 +1,15 @@ +//! Basic utilities for the crypto crates. +//! +//! The functions contained here are not really intended to be used by end users, but you +//! are welcome to do so if you wish. +//! +//! That said, beware that this crate is not necessarily documented to the same standard as other crates. +//! Since many of the contained helpers are security-critical (such as the constant time module), +//! we will prioritize fixing security bugs over maintaining a stable API for this crate. + #![forbid(unsafe_code)] +#![forbid(missing_docs)] +#![allow(private_bounds)] pub mod ct; From bbf49ad82dbf11416fe1241fd7a3e25eeff715e0 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Fri, 10 Jul 2026 17:39:23 -0500 Subject: [PATCH 25/26] rustfmt --- crypto/base64/src/lib.rs | 2 +- crypto/core/src/traits.rs | 1 - crypto/factory/src/lib.rs | 2 +- crypto/hkdf/tests/hkdf_tests.rs | 3 +-- crypto/hmac/src/lib.rs | 2 +- crypto/hmac/tests/hmac_tests.rs | 6 ++---- crypto/utils/src/lib.rs | 2 +- 7 files changed, 7 insertions(+), 11 deletions(-) diff --git a/crypto/base64/src/lib.rs b/crypto/base64/src/lib.rs index 41a97ee..93f4101 100644 --- a/crypto/base64/src/lib.rs +++ b/crypto/base64/src/lib.rs @@ -102,7 +102,7 @@ pub enum Base64Error { /// The [Base64Decoder::do_update] method must not be called on a block that contains padding. /// If this error is returned, then the provided input has not been processed and the caller must instead /// pass the same input to [Base64Decoder::do_final]. Note that do_final() is tolerant of incomplete padding blocks, - /// so even if an additional padding character is contained in the next chunk of input, do_final() + /// so even if an additional padding character is contained in the next chunk of input, do_final() /// will still produce the correct output -- ie any additional chunks held by the caller can be discarded. PaddingEnconteredDuringDoUpdate, diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 3460d00..6a736f3 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -267,7 +267,6 @@ pub trait StreamCipher: ) -> Result; } - /// A hash function is a cryptographic primitive that takes an input of any length and produces a fixed-size output. /// Formally: `H: {0,1}^* -> {0,1}^n`. /// A cryptographic hash function will typically satisfy several security properties, including: diff --git a/crypto/factory/src/lib.rs b/crypto/factory/src/lib.rs index cc30f6b..602f613 100644 --- a/crypto/factory/src/lib.rs +++ b/crypto/factory/src/lib.rs @@ -24,7 +24,7 @@ //! get the either the default algorithm or the default algorithm at the 128-bit or 256-bit security level. //! It also exposes [AlgorithmFactory::new] which can be used to create an instance of the algorithm //! by string name according to the string constants associated with the respective factory type. -//! +//! //! This crate compiles with STD; ie it is explicitly not tagged as `no_std` and it makes use of `Vec` and other //! dynamically-sized nice things. diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index 2ddca4c..4613b4a 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -732,8 +732,7 @@ mod hkdf_tests { use bouncycastle_hkdf::{SUSPENDED_HKDF_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA512_STATE_LEN}; // HKDF is keyed by its salt: the salt is NOT serialized and is re-supplied on resume. - let salt = - KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::MACKey).unwrap(); + let salt = KeyMaterial128::from_bytes_as_type(&DUMMY_SEED[..16], KeyType::MACKey).unwrap(); let ikm = &DUMMY_SEED[16..64]; let (part1, part2) = ikm.split_at(20); diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 0e35fe4..acd55c3 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -633,7 +633,7 @@ impl< macro_rules! impl_hmac_keygen { ($hash:ty, $block_len:literal, $n:literal, $drbg:ty) => { impl HMAC<$hash, $block_len> { - /// Generate a key of the appropriate length for the given HMAC + /// Generate a key of the appropriate length for the given HMAC pub fn keygen() -> Result, RNGError> { let mut key = KeyMaterial::<$n>::new(); let mut os_rng = <$drbg>::new_from_os(); diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/hmac/tests/hmac_tests.rs index b272844..e3c82d2 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/hmac/tests/hmac_tests.rs @@ -609,8 +609,7 @@ mod hmac_tests { use bouncycastle_core::traits::SuspendableKeyed; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; - let key = - KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap(); + let key = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap(); let msg = b"Colorless green ideas sleep furiously"; // A helper that exercises the full round-trip for one HMAC variant. HMAC is keyed, so the @@ -662,8 +661,7 @@ mod hmac_tests { // test suspend / resume with a key larger than block size let long_key = - KeyMaterial::<200>::from_bytes_as_type(&DUMMY_SEED[..200], KeyType::MACKey) - .unwrap(); + KeyMaterial::<200>::from_bytes_as_type(&DUMMY_SEED[..200], KeyType::MACKey).unwrap(); round_trip(HMAC_SHA256::new(&long_key).unwrap(), &long_key, msg); } diff --git a/crypto/utils/src/lib.rs b/crypto/utils/src/lib.rs index fd61120..7928819 100644 --- a/crypto/utils/src/lib.rs +++ b/crypto/utils/src/lib.rs @@ -2,7 +2,7 @@ //! //! The functions contained here are not really intended to be used by end users, but you //! are welcome to do so if you wish. -//! +//! //! That said, beware that this crate is not necessarily documented to the same standard as other crates. //! Since many of the contained helpers are security-critical (such as the constant time module), //! we will prioritize fixing security bugs over maintaining a stable API for this crate. From b7352968bf23ce06e8ad34ac15c630f1020bf054 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Fri, 10 Jul 2026 17:42:55 -0500 Subject: [PATCH 26/26] rustfmt --- rustfmt.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/rustfmt.toml b/rustfmt.toml index 7402b5d..6309cbd 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,3 @@ -hex_literal_case = "Upper" max_width = 100 short_array_element_width_threshold = 24 use_small_heuristics = "Max"