From 49df1f2fa00a3dabde67d0102119b2e072a98fc5 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 8 Sep 2026 11:54:07 -0500 Subject: [PATCH 1/7] Added docs to SHA2 and SHA3 about cloning mid-stream (cherry picked from commit 1f4a84eabf1f864acb29bad263c48a6e7c365b7f) Replayed onto the HMAC/HKDF utility-crate split, which had restructured both crate doc headers. Adapted while resolving the conflict: * link [`SHA256Internal`] / [`SHA512Internal`] and [`SHA3Internal`] rather than [`SHA2`] / [`SHA3`]; the latter two are not type names in either crate and would have been dead intra-doc links. * keep the "## HMAC" pointer that the split added to the sha3 header. * move sha2's "Memory Usage" and "Security Considerations" below "Suspending and resuming execution", so that both crates' doc headers present the same section order. sha3 was already in that order. Assisted-by: Claude Code:claude-opus-5 Co-Authored-By: Claude Opus 5 (1M context) --- crypto/sha2/src/lib.rs | 24 ++++++++++++++++++++++++ crypto/sha3/src/lib.rs | 30 +++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index e54c733f7..b6cf18510 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -66,6 +66,30 @@ //! See [`SHA512t`] for documentation around defining a custom truncation length of SHA512 other //! than the [`SHA512_224`] and [`SHA512_256`] defined in FIPS 180-4. //! +//! # Cloning mid-stream +//! +//! Sometimes it is necessary to clone the state of the hash function after absorbing some input, for +//! example, if you have absorbed a large file and need to hash it with two different trailer suffixes. +//! +//! The [`SHA256Internal`] and [`SHA512Internal`] structs impl [`Clone`] for this purpose. +//! +//! ```rust +//! use bouncycastle_core::traits::Hash; +//! use bouncycastle_sha2 as sha2; +//! +//! let mut sha2 = sha2::SHA256::new(); +//! sha2.do_update(b"Some input"); +//! // Clone it so that we can finish this instance, and then continue feeding in more input. +//! let mut sha2_2 = sha2.clone(); +//! +//! // Finish the first instance +//! let output: Vec = sha2.do_final(); +//! +//! // Feed more into the second instance then squeeze it +//! sha2_2.do_update(b"Some more input"); +//! let output2 = sha2_2.do_final(); +//! ``` +//! //! # Suspending and resuming execution //! //! When hashing a large message, it can be advantageous to be able to suspend the operation diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 6d96a0346..60e937433 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -151,11 +151,35 @@ //! * [`parallelhash`] //! * [`tuplehash`] //! +//! # Cloning mid-stream +//! +//! Sometimes it is necessary to clone the state of the hash function after absorbing some input, for +//! example, if you have absorbed a large file and need to hash it with two different trailer suffixes. +//! +//! The [`SHA3Internal`] struct impls [`Clone`] for this purpose. +//! +//! ```rust +//! use bouncycastle_core::traits::Hash; +//! use bouncycastle_sha3 as sha3; +//! +//! let mut sha3 = sha3::SHA3_256::new(); +//! sha3.do_update(b"Some input"); +//! // Clone it so that we can finish this instance, and then continue feeding in more input. +//! let mut sha3_2 = sha3.clone(); +//! +//! // Finish the first instance +//! let output: Vec = sha3.do_final(); +//! +//! // Feed more into the second instance then squeeze it +//! sha3_2.do_update(b"Some more input"); +//! let output2 = sha3_2.do_final(); +//! ``` +//! //! # 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. +//! Suspending is similar to cloning, but compresses the hash function's state into a byte array that can be +//! cached and then resumed at a later time, potentially from a different machine. The typical usage for this +//! is when waiting for a slow IO operation. //! //! For this reason, every SHA3, SHAKE and SP 800-185 type impls [`Suspendable`], squeezers included, //! so a long output stream can be paused as well as a long input. HMAC is keyed and impls From ab2590c6340dc804daf9be6783eed742e05c09e9 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sat, 12 Sep 2026 13:02:43 -0500 Subject: [PATCH 2/7] hmac, sha2, sha3: move the HMAC tests to the crates that publish the aliases The HMAC test suite exercised HMAC over SHA-2 and SHA-3, which forced bouncycastle-hmac to carry bouncycastle-sha2, -sha3, -rng and -hex as dev-dependencies purely in order to test itself. That was a deliberate cycle through dev-dependencies, flagged with a todo, left over from the utility-crate split. Split the suite by the hash it exercises and move each half to the crate that publishes the aliases under test: * crypto/sha2/tests/hmac_tests.rs -- HMAC-SHA224/256/384/512: the full RFC 4231 suite, suspend/resume, keygen, and weak-RNG rejection. * crypto/sha3/tests/hmac_tests.rs -- HMAC-SHA3-224/256/384/512: the 143-byte long-key regression (SHA3-224 has the largest block at 144 bytes), truncated-tag rejection, suspend/resume, and keygen. No test bodies or expected values were changed; the suite was partitioned, not rewritten. bouncycastle-hmac's [dev-dependencies] are gone entirely and with them the dependency cycle. The hex dev-dependency follows the tests to bouncycastle-sha2, which did not previously have one. This leaves bouncycastle-hmac with no tests of its own. That is inherent to it being a utility crate rather than a gap in coverage: HMAC cannot be instantiated without a hash, and this crate must not depend on the hash crates. Every behaviour it had is still exercised, from the two suites above. On rebasing onto feature/simple-ciphers, its HMAC-SHA512/224 and -SHA512/256 tests follow SHA-2 here, and its HMAC-SM3 tests move to a new crypto/sm3/tests/hmac_tests.rs. Assisted-by: Claude Code:claude-opus-5 Co-Authored-By: Claude Opus 5 (1M context) --- crypto/{hmac => sha2}/tests/hmac_tests.rs | 140 +------------------- crypto/sha3/tests/hmac_tests.rs | 142 ++++++++++++++++++++ crypto/sm3/tests/hmac_tests.rs | 154 ++++++++++++++++++++++ 3 files changed, 299 insertions(+), 137 deletions(-) rename crypto/{hmac => sha2}/tests/hmac_tests.rs (87%) create mode 100644 crypto/sha3/tests/hmac_tests.rs create mode 100644 crypto/sm3/tests/hmac_tests.rs diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/sha2/tests/hmac_tests.rs similarity index 87% rename from crypto/hmac/tests/hmac_tests.rs rename to crypto/sha2/tests/hmac_tests.rs index 481b0e42f..40d29fa86 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/sha2/tests/hmac_tests.rs @@ -1,5 +1,5 @@ #[cfg(test)] -mod hmac_tests { +mod hmac_sha2_tests { use bouncycastle_core::errors::{KeyMaterialError, MACError, RNGError}; use bouncycastle_core::hazmat::do_hazardous_operations; use bouncycastle_core::key_material::{ @@ -10,14 +10,10 @@ mod hmac_tests { use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::mac::TestFrameworkMAC; use bouncycastle_hex as hex; - use bouncycastle_hmac::*; + use bouncycastle_hmac::{HMAC, MIN_FIPS_DIGEST_LEN}; use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; use bouncycastle_sha2::hmac::*; use bouncycastle_sha2::*; - use bouncycastle_sha3::hmac::*; - use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; - use bouncycastle_sm3::SM3; - use bouncycastle_sm3::hmac::*; #[test] fn simple_tests() { @@ -62,46 +58,6 @@ 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(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA224::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA256::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA384::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA512::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA512_224::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA512_256::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA3_224::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA3_256::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA3_384::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA3_512::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SM3::new(&key).unwrap(); - } - #[test] fn constructor_tests() { let short_key = KeyMaterial256::from_bytes_as_type( @@ -171,13 +127,6 @@ mod hmac_tests { 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] @@ -269,21 +218,6 @@ mod hmac_tests { let bytes_written = mac.do_final_out(&mut out).unwrap(); assert_eq!(bytes_written, MIN_FIPS_DIGEST_LEN); assert_eq!(&out, b"\x89\x6f\xb1\x12"); - - // fail case: mac value is correct but truncated - let mac = HMAC_SHA3_224::new(&key).unwrap(); - let mut mac_val = mac.mac(b"Polly want a cracker?"); - let verifier = HMAC_SHA3_224::new(&key).unwrap(); - assert!(verifier.verify(b"Polly want a cracker?", &mac_val)); - - // truncation of the mac value is considered a fail - let verifier = HMAC_SHA3_224::new(&key).unwrap(); - assert!(!verifier.verify(b"Polly want a cracker?", &mac_val[..mac_val.len() - 1])); - - // .. as is some extra bytes at the end - let verifier = HMAC_SHA3_224::new(&key).unwrap(); - mac_val.extend_from_slice(&[0u8; 4]); - assert!(!verifier.verify(b"Polly want a cracker?", &mac_val)); } #[test] @@ -299,11 +233,6 @@ mod hmac_tests { assert_eq!(HMAC_SHA512_256_NAME, "HMAC-SHA512/256"); assert_eq!(HMAC_SHA512_224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); assert_eq!(HMAC_SHA512_256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); - assert_eq!(HMAC_SHA3_224::ALG_NAME, HMAC_SHA3_224_NAME); - assert_eq!(HMAC_SHA3_256::ALG_NAME, HMAC_SHA3_256_NAME); - assert_eq!(HMAC_SHA3_384::ALG_NAME, HMAC_SHA3_384_NAME); - assert_eq!(HMAC_SHA3_512::ALG_NAME, HMAC_SHA3_512_NAME); - assert_eq!(HMAC_SM3::ALG_NAME, HMAC_SM3_NAME); } #[cfg(test)] @@ -717,62 +646,6 @@ mod hmac_tests { } } - /// HMAC-SM3 known answers. There is no RFC 4231 equivalent for SM3, so these reuse the RFC 4231 - /// keys/messages (cases 1, 2 and 6) with expected values generated by - /// `openssl dgst -sm3 -mac HMAC` and independently confirmed with bc-java's - /// `HMac(new SM3Digest())`, plus a zero-length key. - #[test] - fn hmac_sm3_known_answers() { - use bouncycastle_core::key_material::KeyMaterial; - let test_framework = TestFrameworkMAC::new(); - - // RFC4231 Test Case 1 key/message - test_framework.test_mac::( - &KeyMaterial::<20>::from_bytes_as_type( - &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), - KeyType::MACKey, - ) - .unwrap(), - b"Hi There", - &hex::decode("51b00d1fb49832bfb01c3ce27848e59f871d9ba938dc563b338ca964755cce70") - .unwrap(), - ); - // RFC4231 Test Case 2 key/message - test_framework.test_mac::( - &KeyMaterial::<4>::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), - b"what do ya want for nothing?", - &hex::decode("2e87f1d16862e6d964b50a5200bf2b10b764faa9680a296a2405f24bec39f882") - .unwrap(), - ); - // RFC4231 Test Case 6 key/message: key larger than the 64-byte block, so it is hashed first - test_framework.test_mac::( - &KeyMaterial::<131>::from_bytes_as_type(&[0xaa; 131], KeyType::MACKey).unwrap(), - b"Test Using Larger Than Block-Size Key - Hash Key First", - &hex::decode("b4fd844e13342002f0b2e0690ea7741f1497d993a70494cea601e657bedf67a0") - .unwrap(), - ); - - // zero-length key (weak; needs new_allow_weak_key) - let mut zero_length_key = KeyMaterial256::default(); - do_hazardous_operations(&mut zero_length_key, |k| k.set_key_type(KeyType::MACKey)).unwrap(); - let mut mac = HMAC_SM3::new_allow_weak_key(&zero_length_key).unwrap(); - mac.do_update(b"abc"); - assert_eq!( - mac.do_final(), - hex::decode("36525058ca466791502435c910517f1a7e86613d5f35ac1f18a94def0eaac81f") - .unwrap() - ); - - assert_eq!( - HMAC_SM3::new( - &KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap() - ) - .unwrap() - .output_len(), - 32 - ); - } - #[test] fn suspendable_keyed_state() { use bouncycastle_core::errors::SuspendableError; @@ -830,8 +703,6 @@ mod hmac_tests { round_trip(HMAC_SHA512::new(&key).unwrap(), &key, msg); round_trip(HMAC_SHA512_224::new(&key).unwrap(), &key, msg); round_trip(HMAC_SHA512_256::new(&key).unwrap(), &key, msg); - round_trip(HMAC_SHA3_256::new(&key).unwrap(), &key, msg); - round_trip(HMAC_SM3::new(&key).unwrap(), &key, msg); // test suspend / resume with a key larger than block size let long_key = @@ -863,7 +734,7 @@ mod hmac_tests { /// * `HMAC::new(&key)` accepts the freshly generated key, without error. /// /// HashDRBG_SHA512 is used throughout because it is the only built-in DRBG that meets the - /// 256-bit strength that HMAC-SHA512 and HMAC-SHA3-512 claim; see `keygen_rejects_weak_rng`. + /// 256-bit strength that HMAC-SHA512 claims; see `keygen_rejects_weak_rng`. macro_rules! keygen_test { ($test_name:ident, $hmac:ident, $n:literal) => { #[test] @@ -889,11 +760,6 @@ mod hmac_tests { keygen_test!(keygen_hmac_sha512, HMAC_SHA512, 64); keygen_test!(keygen_hmac_sha512_224, HMAC_SHA512_224, 28); keygen_test!(keygen_hmac_sha512_256, HMAC_SHA512_256, 32); - keygen_test!(keygen_hmac_sha3_224, HMAC_SHA3_224, 28); - keygen_test!(keygen_hmac_sha3_256, HMAC_SHA3_256, 32); - keygen_test!(keygen_hmac_sha3_384, HMAC_SHA3_384, 48); - keygen_test!(keygen_hmac_sha3_512, HMAC_SHA3_512, 64); - keygen_test!(keygen_hmac_sm3, HMAC_SM3, 32); /// `keygen_from_rng` must refuse an RNG whose security strength is below the strength the HMAC /// claims, otherwise the returned key would be tagged stronger than the entropy behind it. diff --git a/crypto/sha3/tests/hmac_tests.rs b/crypto/sha3/tests/hmac_tests.rs new file mode 100644 index 000000000..d4654f43a --- /dev/null +++ b/crypto/sha3/tests/hmac_tests.rs @@ -0,0 +1,142 @@ +#[cfg(test)] +mod hmac_sha3_tests { + use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; + use bouncycastle_core::traits::{Algorithm, MAC}; + use bouncycastle_core_test_framework::DUMMY_SEED; + use bouncycastle_rng::HashDRBG_SHA512; + use bouncycastle_sha3::hmac::*; + + #[test] + 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 is designed to detect an overflow-write and panic on HMAC's internal key buffer. + + // 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 negative_tests() { + let key = KeyMaterial256::from_bytes_as_type( + b"\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", + KeyType::MACKey, + ) + .unwrap(); + + // fail case: mac value is correct but truncated + let mac = HMAC_SHA3_224::new(&key).unwrap(); + let mut mac_val = mac.mac(b"Polly want a cracker?"); + let verifier = HMAC_SHA3_224::new(&key).unwrap(); + assert!(verifier.verify(b"Polly want a cracker?", &mac_val)); + + // truncation of the mac value is considered a fail + let verifier = HMAC_SHA3_224::new(&key).unwrap(); + assert!(!verifier.verify(b"Polly want a cracker?", &mac_val[..mac_val.len() - 1])); + + // .. as is some extra bytes at the end + let verifier = HMAC_SHA3_224::new(&key).unwrap(); + mac_val.extend_from_slice(&[0u8; 4]); + assert!(!verifier.verify(b"Polly want a cracker?", &mac_val)); + } + + #[test] + fn algorithm_tests() { + // Test the type aliases and string constants + assert_eq!(HMAC_SHA3_224::ALG_NAME, HMAC_SHA3_224_NAME); + assert_eq!(HMAC_SHA3_256::ALG_NAME, HMAC_SHA3_256_NAME); + assert_eq!(HMAC_SHA3_384::ALG_NAME, HMAC_SHA3_384_NAME); + assert_eq!(HMAC_SHA3_512::ALG_NAME, HMAC_SHA3_512_NAME); + } + + #[test] + fn suspendable_keyed_state() { + use bouncycastle_core::errors::SuspendableError; + use bouncycastle_core::traits::SuspendableKeyed; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; + use bouncycastle_utils::suspendable_state::LIB_VERSION; + + 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 + // 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 + SuspendableKeyed, + { + hmac.do_update(&input[..10]); + + // do the default trait-conformance tests + TestFrameworkSuspendableKeyedState::new().test(&hmac, key); + + // serialize the in-progress state (on a clone), then finish the original + 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(); + 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_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_suspended(busted, key) { + Err(SuspendableError::IncompatibleVersion) => { /* good */ } + _ => panic!("Expected IncompatibleVersion for a zeroed version header"), + } + } + + round_trip(HMAC_SHA3_256::new(&key).unwrap(), &key, msg); + } + + /// Exercises the `keygen_from_rng()` function of each HMAC type alias: + /// * the generated key must not be the all-zero array, + /// * `keygen_from_rng()` returns a ready-to-use `KeyType::MACKey` key, so that + /// * `HMAC::new(&key)` accepts the freshly generated key, without error. + /// + /// HashDRBG_SHA512 is used throughout because it is the only built-in DRBG that meets the + /// 256-bit strength that HMAC-SHA3-512 claims. + macro_rules! keygen_test { + ($test_name:ident, $hmac:ident, $n:literal) => { + #[test] + fn $test_name() { + let mut rng = HashDRBG_SHA512::new_from_os(); + let key = $hmac::keygen_from_rng(&mut rng).expect("keygen_from_rng should succeed"); + + assert_eq!(key.key_len(), $n, "key should be the hash's output length"); + assert_eq!(key.key_type(), KeyType::MACKey, "keygen should return a MAC key"); + assert!( + key.ref_to_bytes().iter().any(|&b| b != 0), + "keygen produced an all-zero key" + ); + + $hmac::new(&key).expect("HMAC::new should accept a freshly generated key"); + } + }; + } + + keygen_test!(keygen_hmac_sha3_224, HMAC_SHA3_224, 28); + keygen_test!(keygen_hmac_sha3_256, HMAC_SHA3_256, 32); + keygen_test!(keygen_hmac_sha3_384, HMAC_SHA3_384, 48); + keygen_test!(keygen_hmac_sha3_512, HMAC_SHA3_512, 64); +} diff --git a/crypto/sm3/tests/hmac_tests.rs b/crypto/sm3/tests/hmac_tests.rs new file mode 100644 index 000000000..91e9a0042 --- /dev/null +++ b/crypto/sm3/tests/hmac_tests.rs @@ -0,0 +1,154 @@ +#[cfg(test)] +mod hmac_sm3_tests { + use bouncycastle_core::hazmat::do_hazardous_operations; + use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; + use bouncycastle_core::traits::{Algorithm, MAC}; + use bouncycastle_core_test_framework::DUMMY_SEED; + use bouncycastle_core_test_framework::mac::TestFrameworkMAC; + use bouncycastle_hex as hex; + use bouncycastle_rng::HashDRBG_SHA512; + use bouncycastle_sm3::hmac::*; + + /// HMAC-SM3 known answers. There is no RFC 4231 equivalent for SM3, so these reuse the RFC 4231 + /// keys/messages (cases 1, 2 and 6) with expected values generated by + /// `openssl dgst -sm3 -mac HMAC` and independently confirmed with bc-java's + /// `HMac(new SM3Digest())`, plus a zero-length key. + #[test] + fn hmac_sm3_known_answers() { + use bouncycastle_core::key_material::KeyMaterial; + let test_framework = TestFrameworkMAC::new(); + + // RFC4231 Test Case 1 key/message + test_framework.test_mac::( + &KeyMaterial::<20>::from_bytes_as_type( + &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), + KeyType::MACKey, + ) + .unwrap(), + b"Hi There", + &hex::decode("51b00d1fb49832bfb01c3ce27848e59f871d9ba938dc563b338ca964755cce70") + .unwrap(), + ); + // RFC4231 Test Case 2 key/message + test_framework.test_mac::( + &KeyMaterial::<4>::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), + b"what do ya want for nothing?", + &hex::decode("2e87f1d16862e6d964b50a5200bf2b10b764faa9680a296a2405f24bec39f882") + .unwrap(), + ); + // RFC4231 Test Case 6 key/message: key larger than the 64-byte block, so it is hashed first + test_framework.test_mac::( + &KeyMaterial::<131>::from_bytes_as_type(&[0xaa; 131], KeyType::MACKey).unwrap(), + b"Test Using Larger Than Block-Size Key - Hash Key First", + &hex::decode("b4fd844e13342002f0b2e0690ea7741f1497d993a70494cea601e657bedf67a0") + .unwrap(), + ); + + // zero-length key (weak; needs new_allow_weak_key) + let mut zero_length_key = KeyMaterial256::default(); + do_hazardous_operations(&mut zero_length_key, |k| k.set_key_type(KeyType::MACKey)).unwrap(); + let mut mac = HMAC_SM3::new_allow_weak_key(&zero_length_key).unwrap(); + mac.do_update(b"abc"); + assert_eq!( + mac.do_final(), + hex::decode("36525058ca466791502435c910517f1a7e86613d5f35ac1f18a94def0eaac81f") + .unwrap() + ); + + assert_eq!( + HMAC_SM3::new( + &KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap() + ) + .unwrap() + .output_len(), + 32 + ); + } + + #[test] + fn algorithm_tests() { + assert_eq!(HMAC_SM3::ALG_NAME, HMAC_SM3_NAME); + } + + #[test] + fn suspendable_keyed_state() { + use bouncycastle_core::errors::SuspendableError; + use bouncycastle_core::traits::SuspendableKeyed; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; + use bouncycastle_utils::suspendable_state::LIB_VERSION; + + 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 + // 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 + SuspendableKeyed, + { + hmac.do_update(&input[..10]); + + // do the default trait-conformance tests + TestFrameworkSuspendableKeyedState::new().test(&hmac, key); + + // serialize the in-progress state (on a clone), then finish the original + 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(); + 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_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_suspended(busted, key) { + Err(SuspendableError::IncompatibleVersion) => { /* good */ } + _ => panic!("Expected IncompatibleVersion for a zeroed version header"), + } + } + + round_trip(HMAC_SM3::new(&key).unwrap(), &key, msg); + } + + /// Exercises the `keygen_from_rng()` function of each HMAC type alias: + /// * the generated key must not be the all-zero array, + /// * `keygen_from_rng()` returns a ready-to-use `KeyType::MACKey` key, so that + /// * `HMAC::new(&key)` accepts the freshly generated key, without error. + /// + macro_rules! keygen_test { + ($test_name:ident, $hmac:ident, $n:literal) => { + #[test] + fn $test_name() { + let mut rng = HashDRBG_SHA512::new_from_os(); + let key = $hmac::keygen_from_rng(&mut rng).expect("keygen_from_rng should succeed"); + + assert_eq!(key.key_len(), $n, "key should be the hash's output length"); + assert_eq!(key.key_type(), KeyType::MACKey, "keygen should return a MAC key"); + assert!( + key.ref_to_bytes().iter().any(|&b| b != 0), + "keygen produced an all-zero key" + ); + + $hmac::new(&key).expect("HMAC::new should accept a freshly generated key"); + } + }; + } + + keygen_test!(keygen_hmac_sm3, HMAC_SM3, 32); +} From 0d933301b5e2e701941e29687afa6441de7bf4b4 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sat, 12 Sep 2026 13:04:23 -0500 Subject: [PATCH 3/7] hmac: take the HMAC's security strength from the hash instead of restating it Assisted-by: Claude Code:claude-opus-5 --- crypto/hmac/src/lib.rs | 62 ++++++++++++++++++++++++++++------------- crypto/sha2/src/hmac.rs | 7 ----- crypto/sha3/src/hmac.rs | 5 ---- crypto/sm3/src/hmac.rs | 2 -- 4 files changed, 43 insertions(+), 33 deletions(-) diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 11cf01e1d..4ecffbaa0 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -26,10 +26,12 @@ //! //! 1. Have a hash type that implements [`Hash`] + [`HashAlgParams`] + [`Default`]. Implementing //! [`Hash`] is documented in `bouncycastle-core`; nothing about it is HMAC-specific. -//! 2. Implement [`HMACParams`] for that hash type, supplying the HMAC's name, claimed security -//! strength and OID, plus the key type that [`HMAC::keygen_from_rng`] should return (typically a -//! `KeyMaterial` for an L that matches the size of the underlying hash function. This allows -//! this crate to provide blanket [`Algorithm`], [`AlgorithmOID`] and [`HMAC::keygen_from_rng`] impls. +//! 2. Implement [`HMACParams`] for that hash type, supplying the HMAC's name and OID, plus the key +//! type that [`HMAC::keygen_from_rng`] should return (typically a `KeyMaterial` for an L that +//! matches the size of the underlying hash function). This allows this crate to provide blanket +//! [`Algorithm`], [`AlgorithmOID`] and [`HMAC::keygen_from_rng`] impls. The claimed security +//! strength is not supplied here -- it is read off the hash's own +//! [`Algorithm::MAX_SECURITY_STRENGTH`]. //! 3. Publish a type alias for the instantiation, passing [`HashAlgParams::BLOCK_LEN`] as the key //! buffer length. Per RFC 2104 a key no longer than the hash's block is used verbatim, and only //! longer keys are pre-hashed down to the output length, so the buffer must hold a full block. @@ -49,7 +51,6 @@ //! impl HMACParams for SHA256 { //! type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; //! const HMAC_ALG_NAME: &'static str = "HMAC-SHA256"; -//! const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; //! /// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } //! const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 9]; //! const HMAC_OID_DER: &'static [u8] = @@ -67,11 +68,11 @@ //! These apply to every instantiation; the hash crates' `hmac` modules repeat the ones that matter //! most in day-to-day use. //! -//! * [`HMACParams::HMAC_MAX_SECURITY_STRENGTH`] is a claim that [`MAC::new`] enforces against the -//! key's tagged strength, and that [`HMAC::keygen_from_rng`] enforces against the RNG's. Declaring -//! a strength the underlying hash cannot support does not make the construction stronger, it just -//! makes the check wrong. NIST SP 800-107-r1 Section 5.3.4 gives the ceiling: the effective -//! strength is `min(strength of K, 2C)` for an internal chaining value of `C` bits. +//! * An HMAC claims the security strength of its underlying hash, and that claim is enforced in two +//! places: [`MAC::new`] checks it against the key's tagged strength, and +//! [`HMAC::keygen_from_rng`] checks it against the RNG's. NIST SP 800-107-r1 Section 5.3.4 gives +//! the ceiling: the effective strength is `min(strength of K, 2C)` for an internal chaining value +//! of `C` bits. //! * [`MAC::new_allow_weak_key`] deliberately skips the key-strength check. It exists for protocols //! that call for a weak or all-zero key -- an all-zero HKDF salt, for example -- and should not be //! used to silence an error from [`MAC::new`]. @@ -109,12 +110,27 @@ use core::fmt::{Debug, Display, Formatter}; /// [`HMAC::keygen_from_rng`] can be impl'd generically rather than being written out once /// per hash. /// -/// Each hash crate is expected to implement this trait for its own hash types and publishes the resulting type -/// alias. For example,`HMAC_SHA256` lives in `bouncycastle_sha2::hmac` and `HMAC_SHA3_256` in -/// `bouncycastle_sha3::hmac`. +/// Each hash crate is expected to implement this trait for its own hash types and publish the +/// resulting type alias. For example, `HMAC_SHA256` lives in `bouncycastle_sha2::hmac` and +/// `HMAC_SHA3_256` in `bouncycastle_sha3::hmac`. +/// +/// # Why the metadata comes in through a trait on the hash +/// +/// It is tempting to delete this trait and have each hash crate write the impls directly onto its +/// own alias instead -- `impl Algorithm for HMAC_SHA256` over in `bouncycastle-sha2`. That does not +/// compile. [`Algorithm`] belongs to `bouncycastle-core` and [`HMAC`] belongs to this crate, so both +/// are foreign there, and `HMAC` does not become a local type merely because `SHA256` +/// is: the orphan rule counts `Foreign` as foreign (RFC 2451), and rustc rejects the impl +/// with E0117. Only this crate may write `impl ... for HMAC<..>`, and this crate must not depend on +/// the hash crates -- breaking that cycle is the whole reason HMAC is a utility crate. A blanket +/// impl fed by a trait on the hash is therefore the only available shape, not a stylistic choice. /// /// The block length and the generated-key length do not need to be restated here since they are already /// carried by the hash itself as [`HashAlgParams::BLOCK_LEN`] and [`HashAlgParams::OUTPUT_LEN`]. +/// Neither does the claimed security strength: an HMAC claims the strength of its underlying hash, +/// so the blanket [`Algorithm`] impl reads [`Algorithm::MAX_SECURITY_STRENGTH`] straight off the +/// hash. Restating it here would only create a second copy that could disagree with the one +/// [`MAC::new`] actually enforces. pub trait HMACParams: Hash + HashAlgParams + Default { /// The key type produced by [`HMAC::keygen_from_rng`], sized to this hash's output length. /// @@ -125,11 +141,15 @@ pub trait HMACParams: Hash + HashAlgParams + Default { type MACKey: KeyMaterialTrait + Default; /// The name of the HMAC over this hash, as reported by [`Algorithm::ALG_NAME`]. + /// + /// This is always "HMAC-" followed by the hash's own [`Algorithm::ALG_NAME`] -- which is exactly + /// what [`HMAC`]'s [`Display`] impl formats at runtime -- but a `const` cannot concatenate two + /// `&'static str`s without an external crate, so it is restated here. const HMAC_ALG_NAME: &'static str; - /// The strength claimed by the HMAC over this hash, as reported by - /// [`Algorithm::MAX_SECURITY_STRENGTH`]. - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength; /// The OID of the HMAC over this hash in component form, as reported by [`AlgorithmOID::OID`]. + /// + /// Every hash/HMAC pair is assigned its own OID -- RFC 4231 for the SHA-2 family, the NIST + /// Computer Security Objects Register for SHA-3 -- so this cannot be derived from the hash's OID. const HMAC_OID: &'static [u32]; /// The DER encoding of [`HMACParams::HMAC_OID`], as reported by [`AlgorithmOID::OID_DER`]. const HMAC_OID_DER: &'static [u8]; @@ -137,7 +157,10 @@ pub trait HMACParams: Hash + HashAlgParams + Default { impl Algorithm for HMAC { const ALG_NAME: &'static str = HASH::HMAC_ALG_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = HASH::HMAC_MAX_SECURITY_STRENGTH; + // An HMAC claims the security strength of the hash underneath it. Reading it off the hash rather + // than restating it per instantiation keeps the advertised strength identical to the one + // `HMAC::init` enforces against the key, which it takes from `Hash::max_security_strength`. + const MAX_SECURITY_STRENGTH: SecurityStrength = HASH::MAX_SECURITY_STRENGTH; } impl AlgorithmOID for HMAC { @@ -148,7 +171,8 @@ impl AlgorithmOID for HMAC` is written without an explicit buffer size; it is the // largest block length across all supported hashes, so it is always large enough. @@ -430,7 +454,7 @@ impl HMAC { pub fn keygen_from_rng(rng: &mut dyn RNG) -> Result { // Refuse to generate a key from an RNG that cannot back the strength this HMAC claims; // otherwise the key's tagged security strength would overstate its true entropy. - if rng.security_strength() < HASH::HMAC_MAX_SECURITY_STRENGTH { + if rng.security_strength() < HASH::MAX_SECURITY_STRENGTH { return Err(RNGError::SecurityStrengthInsufficientForAlgorithm); } diff --git a/crypto/sha2/src/hmac.rs b/crypto/sha2/src/hmac.rs index ab35cb8f8..624109ad6 100644 --- a/crypto/sha2/src/hmac.rs +++ b/crypto/sha2/src/hmac.rs @@ -247,7 +247,6 @@ use crate::{SHA224, SHA256, SHA384, SHA512, SHA512_224, SHA512_256}; use crate::{SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::HashAlgParams; use bouncycastle_hmac::{HMAC, HMACParams}; @@ -280,7 +279,6 @@ pub type HMAC_SHA224 = HMAC::BLOCK_LEN }>; impl HMACParams for SHA224 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA224_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; /// Defined in RFC 4231: id-hmacWithSHA224 { digestAlgorithm 8 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 8]; const HMAC_OID_DER: &'static [u8] = @@ -293,7 +291,6 @@ pub type HMAC_SHA256 = HMAC::BLOCK_LEN }>; impl HMACParams for SHA256 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA256_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; /// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 9]; const HMAC_OID_DER: &'static [u8] = @@ -306,7 +303,6 @@ pub type HMAC_SHA384 = HMAC::BLOCK_LEN }>; impl HMACParams for SHA384 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA384_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; /// Defined in RFC 4231: id-hmacWithSHA384 { digestAlgorithm 10 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 10]; const HMAC_OID_DER: &'static [u8] = @@ -319,7 +315,6 @@ pub type HMAC_SHA512 = HMAC::BLOCK_LEN }>; impl HMACParams for SHA512 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA512_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; /// Defined in RFC 4231: id-hmacWithSHA512 { digestAlgorithm 11 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 11]; const HMAC_OID_DER: &'static [u8] = @@ -332,7 +327,6 @@ pub type HMAC_SHA512_224 = HMAC::BLO impl HMACParams for SHA512_224 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA512_224_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-224 { digestAlgorithm 12 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 12]; const HMAC_OID_DER: &'static [u8] = @@ -345,7 +339,6 @@ pub type HMAC_SHA512_256 = HMAC::BLO impl HMACParams for SHA512_256 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA512_256_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-256 { digestAlgorithm 13 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 13]; const HMAC_OID_DER: &'static [u8] = diff --git a/crypto/sha3/src/hmac.rs b/crypto/sha3/src/hmac.rs index 2e67b8b2f..99fbf3045 100644 --- a/crypto/sha3/src/hmac.rs +++ b/crypto/sha3/src/hmac.rs @@ -258,7 +258,6 @@ use crate::SUSPENDED_SHA3_STATE_LEN; use crate::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::HashAlgParams; use bouncycastle_hmac::{HMAC, HMACParams}; @@ -287,7 +286,6 @@ pub type HMAC_SHA3_224 = HMAC::BLOCK_LEN impl HMACParams for SHA3_224 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA3_224_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-224 { hashAlgs 13 } const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 13]; const HMAC_OID_DER: &'static [u8] = @@ -300,7 +298,6 @@ pub type HMAC_SHA3_256 = HMAC::BLOCK_LEN impl HMACParams for SHA3_256 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA3_256_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-256 { hashAlgs 14 } const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 14]; const HMAC_OID_DER: &'static [u8] = @@ -313,7 +310,6 @@ pub type HMAC_SHA3_384 = HMAC::BLOCK_LEN impl HMACParams for SHA3_384 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA3_384_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-384 { hashAlgs 15 } const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 15]; const HMAC_OID_DER: &'static [u8] = @@ -326,7 +322,6 @@ pub type HMAC_SHA3_512 = HMAC::BLOCK_LEN impl HMACParams for SHA3_512 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA3_512_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-512 { hashAlgs 16 } const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 16]; const HMAC_OID_DER: &'static [u8] = diff --git a/crypto/sm3/src/hmac.rs b/crypto/sm3/src/hmac.rs index 29fcababe..6a3af3cf7 100644 --- a/crypto/sm3/src/hmac.rs +++ b/crypto/sm3/src/hmac.rs @@ -43,7 +43,6 @@ use crate::{SM3, SUSPENDED_SM3_STATE_LEN}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::HashAlgParams; use bouncycastle_hmac::{HMAC, HMACParams}; @@ -67,7 +66,6 @@ pub type HMAC_SM3 = HMAC::BLOCK_LEN }>; impl HMACParams for SM3 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SM3_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; /// Assigned by the Chinese OSCCA (GM/T 0006): hmac-sm3 { sm3 2 } = 1.2.156.10197.1.401.2 const HMAC_OID: &'static [u32] = &[1, 2, 156, 10197, 1, 401, 2]; const HMAC_OID_DER: &'static [u8] = From 7d300c3c4e7a12bcaef28bd52906169eb5cb955b Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sat, 12 Sep 2026 15:09:37 -0500 Subject: [PATCH 4/7] Refactoring of how the HMACParams work. --- crypto/hkdf/src/lib.rs | 46 +++-- crypto/hkdf/tests/hkdf_tests.rs | 29 +-- crypto/hkdf/tests/hkdf_wycheproof.rs | 11 +- crypto/hmac/src/lib.rs | 242 ++++++++++++------------- crypto/sha2/src/hkdf.rs | 38 ++-- crypto/sha2/src/hmac.rs | 258 +++++++++++++++++++++------ crypto/sha2/tests/hmac_tests.rs | 114 ++++++------ crypto/sha3/src/hmac.rs | 160 +++++++++++++---- crypto/sha3/tests/hmac_tests.rs | 13 +- crypto/sm3/src/hmac.rs | 40 +++-- 10 files changed, 633 insertions(+), 318 deletions(-) diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 0d74a1574..a7ebcb06e 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -47,6 +47,7 @@ //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; //! use bouncycastle_core::traits::{KDF, SuspendableKeyed}; //! use bouncycastle_hkdf::HKDF; +//! use bouncycastle_sha2::hmac::HMAC_SHA224Params; //! use bouncycastle_sha2::{SHA224, SUSPENDED_SHA256_STATE_LEN}; //! //! // SHA-224 is a member of the SHA-256 family, so its suspended state is the SHA-256 one. @@ -54,7 +55,7 @@ //! //! #[allow(non_camel_case_types)] //! pub type HKDF_SHA224 = -//! HKDF; +//! HKDF; //! //! pub const HKDF_SHA224_NAME: &str = "HKDF-SHA224"; //! @@ -113,7 +114,7 @@ use bouncycastle_core::key_material::{ }; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{Hash, HashAlgParams, KDF, MAC, Suspendable, SuspendableKeyed}; -use bouncycastle_hmac::HMAC; +use bouncycastle_hmac::{HMAC, HMACParams}; use bouncycastle_utils::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_utils::{max, min}; use std::marker::PhantomData; @@ -159,13 +160,14 @@ pub const MAX_HMAC_OUTPUT_LEN: usize = 64; #[derive(Clone)] pub struct HKDF< H: Hash + HashAlgParams + Default, + PARAMS: HMACParams, const HASH_STATE_LEN: usize, const HKDF_STATE_LEN: usize, > { // Optional because an HMAC cannot be constructed until a key is provided // to initialize it with. // None must correspond to a state of Uninitialized. - hmac: Option>, + hmac: Option>, entropy: HkdfEntropyTracker, state: HkdfStates, } @@ -243,16 +245,24 @@ impl HkdfEntropyTracker { } } -impl - Default for HKDF +impl< + H: Hash + HashAlgParams + Default, + PARAMS: HMACParams, + const HASH_STATE_LEN: usize, + const HKDF_STATE_LEN: usize, +> Default for HKDF { fn default() -> Self { Self::new() } } -impl - HKDF +impl< + H: Hash + HashAlgParams + Default, + PARAMS: HMACParams, + const HASH_STATE_LEN: usize, + const HKDF_STATE_LEN: usize, +> HKDF { /// Get a new, uninstantiated HKDF object. pub fn new() -> Self { @@ -398,7 +408,7 @@ impl::new(&prk_as_mac_key) + let mut hmac = HMAC::::new(&prk_as_mac_key) .map_err(|_| KeyMaterialError::GenericError("HMAC initialization failed"))?; hmac.do_update(&T[..t_len]); hmac.do_update(info); @@ -420,7 +430,7 @@ impl::new(&prk_as_mac_key)?; + let mut hmac = HMAC::::new(&prk_as_mac_key)?; hmac.do_update(&T[..t_len]); hmac.do_update(info); hmac.do_update(&[i]); @@ -490,7 +500,7 @@ impl::new_allow_weak_key(salt)?); + self.hmac = Some(HMAC::::new_allow_weak_key(salt)?); let additional_entropy = self.entropy.credit_entropy(salt); self.state = HkdfStates::Initialized; @@ -523,7 +533,7 @@ impl = self.hmac.as_mut().unwrap(); + let hmac_ref: &mut HMAC = self.hmac.as_mut().unwrap(); hmac_ref.do_update(ikm.ref_to_bytes()); // self.hmac.as_mut().unwrap().do_update(ikm.ref_to_bytes()); @@ -612,8 +622,12 @@ impl - KDF for HKDF +impl< + H: Hash + HashAlgParams + Default, + PARAMS: HMACParams, + const HASH_STATE_LEN: usize, + const HKDF_STATE_LEN: usize, +> KDF for HKDF { /// This invokes [`HKDF::extract_and_expand_out`] with a zero salt and using the provided key as ikm. /// This provides a fixed-length output, which may be truncated as needed. @@ -739,8 +753,8 @@ impl SuspendableKeyed - for HKDF +impl + SuspendableKeyed for HKDF where H: Hash + HashAlgParams + Default + Suspendable, { @@ -788,7 +802,7 @@ where let hmac = match state[3] { 0 => None, // infallible: the sub-slice is exactly HASH_STATE_LEN bytes by const construction. - 1 => Some(HMAC::::from_suspended( + 1 => Some(HMAC::::from_suspended( state[4..4 + HASH_STATE_LEN].try_into().unwrap(), salt, )?), diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index 9736a1e82..d7a58b33a 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -743,21 +743,22 @@ mod hkdf_tests { // 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( + fn round_trip( salt: &KeyMaterial128, part1: &[u8], part2: &[u8], ) where H: Hash + HashAlgParams + Default, - HKDF: Clone + SuspendableKeyed, + P: bouncycastle_hmac::HMACParams, + HKDF: Clone + SuspendableKeyed, { - let hkdf = HKDF::::new(); + 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::::from_suspended(serialized_state, salt).unwrap(); hkdf.do_extract_init(salt).unwrap(); hkdf.do_extract_update_bytes(part1).unwrap(); @@ -774,19 +775,25 @@ mod hkdf_tests { // resume (re-supplying the salt), feed the identical remaining IKM, and compare PRKs let mut resumed = - HKDF::::from_suspended(serialized_state, salt).unwrap(); + 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, - ); + round_trip::< + SUSPENDED_SHA256_STATE_LEN, + SUSPENDED_HKDF_SHA256_STATE_LEN, + SHA256, + bouncycastle_sha2::hmac::HMAC_SHA256Params, + >(&salt, part1, part2); + round_trip::< + SUSPENDED_SHA512_STATE_LEN, + SUSPENDED_HKDF_SHA512_STATE_LEN, + SHA512, + bouncycastle_sha2::hmac::HMAC_SHA512Params, + >(&salt, part1, part2); // Test the guard for invalid states // testing just on HKDF_SHA256 diff --git a/crypto/hkdf/tests/hkdf_wycheproof.rs b/crypto/hkdf/tests/hkdf_wycheproof.rs index 85e161cb1..a4f25f9ee 100644 --- a/crypto/hkdf/tests/hkdf_wycheproof.rs +++ b/crypto/hkdf/tests/hkdf_wycheproof.rs @@ -15,10 +15,12 @@ use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{Hash, HashAlgParams}; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; use bouncycastle_hkdf::HKDF; +use bouncycastle_hmac::HMACParams; use bouncycastle_sha2::hkdf::{ SUSPENDED_HKDF_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA384_STATE_LEN, SUSPENDED_HKDF_SHA512_STATE_LEN, }; +use bouncycastle_sha2::hmac::{HMAC_SHA256Params, HMAC_SHA384Params, HMAC_SHA512Params}; use bouncycastle_sha2::{ SHA256, SHA384, SHA512, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN, }; @@ -32,6 +34,7 @@ const MAX_OKM_LEN: usize = 255 * 64; /// those of `bouncycastle_sha2::hkdf`'s aliases, which cannot be passed as a type here. fn run< H: Hash + HashAlgParams + Default, + PARAMS: HMACParams, const HASH_STATE_LEN: usize, const HKDF_STATE_LEN: usize, >( @@ -62,7 +65,7 @@ fn run< let size = test.get("size").and_then(Value::as_u64).expect("size") as usize; let mut okm = KeyMaterial::::new(); - let result = HKDF::::extract_and_expand_out( + let result = HKDF::::extract_and_expand_out( &salt, &ikm, &info, size, &mut okm, ); @@ -95,21 +98,21 @@ fn run< #[test] fn wycheproof_hkdf_sha256() { - run::( + run::( "hkdf_sha256_test.json", "HKDF-SHA-256", ); } #[test] fn wycheproof_hkdf_sha384() { - run::( + run::( "hkdf_sha384_test.json", "HKDF-SHA-384", ); } #[test] fn wycheproof_hkdf_sha512() { - run::( + run::( "hkdf_sha512_test.json", "HKDF-SHA-512", ); } diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 4ecffbaa0..e49c0703a 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -2,8 +2,8 @@ //! taking into account NIST Implementation Guidance in FIPS 140-2 IG A.8 and NIST SP 800-107-r1. //! //! This is a utility crate and is not intended to be used directly. It provides [`HMAC`] -- the -//! construction, generic over any struct that implements [`Hash`] and [`HMACParams`], the extension -//! point through which a hash declares the metadata from which the HMAC instance is built. +//! construction, generic over any struct that implements [`Hash`], paired with an [`HMACParams`] +//! marker type that carries the metadata the resulting instantiation reports about itself. //! The library provides the following concrete instantiations of HMAC: //! //! | Hash family | Instantiations | @@ -11,8 +11,9 @@ //! | SHA-2 | `bouncycastle_sha2::hmac` -- `HMAC_SHA224` .. `HMAC_SHA512_256` | //! | SHA-3 | `bouncycastle_sha3::hmac` -- `HMAC_SHA3_224` .. `HMAC_SHA3_512` | //! -//! Although users are free to implement [`Hash`] and [`HMACParams`] for a a hash function not included with the library, -//! and will then be able to instantiate [`HMAC`] for it as well. +//! Users are free to implement [`Hash`] for a hash function not included with the library and +//! declare their own [`HMACParams`] marker type for it, and will then be able to instantiate +//! [`HMAC`] over it as well. //! //! # Instantiating HMAC over a Hash //! @@ -22,21 +23,20 @@ //! rather than deriving it from the hash's OID. Supplying that metadata is what makes an HMAC a //! first-class algorithm in this library rather than an anonymous `HMAC`. //! -//! There are four steps, of which only the second is mandatory: +//! That metadata is supplied by a *params marker type*. +//! +//! There are four steps to implementing a new HMAC type: //! //! 1. Have a hash type that implements [`Hash`] + [`HashAlgParams`] + [`Default`]. Implementing //! [`Hash`] is documented in `bouncycastle-core`; nothing about it is HMAC-specific. -//! 2. Implement [`HMACParams`] for that hash type, supplying the HMAC's name and OID, plus the key -//! type that [`HMAC::keygen_from_rng`] should return (typically a `KeyMaterial` for an L that -//! matches the size of the underlying hash function). This allows this crate to provide blanket -//! [`Algorithm`], [`AlgorithmOID`] and [`HMAC::keygen_from_rng`] impls. The claimed security -//! strength is not supplied here -- it is read off the hash's own -//! [`Algorithm::MAX_SECURITY_STRENGTH`]. -//! 3. Publish a type alias for the instantiation, passing [`HashAlgParams::BLOCK_LEN`] as the key -//! buffer length. Per RFC 2104 a key no longer than the hash's block is used verbatim, and only -//! longer keys are pre-hashed down to the output length, so the buffer must hold a full block. -//! Reading the length off the hash rather than writing a literal means the two cannot drift apart. -//! 4. Optionally publish the suspended-state length as a constant. [`SuspendableKeyed`] is +//! 2. Declare a params type for the instantiation and give it [`Algorithm`] (the name and claimed +//! strength), [`AlgorithmOID`] (the OID and its DER encoding) and [`HMACParams`] (the key type +//! that [`HMAC::keygen_from_rng`] should return, and the internal key buffer). This crate then +//! provides the blanket [`Algorithm`], [`AlgorithmOID`] and [`HMAC::keygen_from_rng`] impls for +//! any [`HMAC`]. Take the buffer from [`HashAlgParams::BLOCK_LEN`] rather than writing a literal, +//! so it cannot drift away from the hash it has to hold a block of. +//! 3. Optionally, publish a type alias pairing the hash with the params. +//! 4. Optionally, publish the suspended-state length as a convenience constant. [`SuspendableKeyed`] is //! implemented automatically for any hash that implements [`Suspendable`], and HMAC's suspended //! state is exactly the inner hash's -- the key is deliberately excluded -- so the constant is //! just an alias for the hash's own. @@ -46,33 +46,52 @@ //! As an example, the `bouncycastle-sha2` crate follows exactly the recipe above; its entry for SHA-256 reduces to: //! //! ```rust,ignore -//! pub type HMAC_SHA256 = HMAC::BLOCK_LEN }>; +//! /// The parameters for HMAC-SHA256. +//! #[derive(Clone)] +//! pub struct HMAC_SHA256Params; //! -//! impl HMACParams for SHA256 { -//! type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; -//! const HMAC_ALG_NAME: &'static str = "HMAC-SHA256"; -//! /// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } -//! const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 9]; -//! const HMAC_OID_DER: &'static [u8] = +//! impl Algorithm for HMAC_SHA256Params { +//! const ALG_NAME: &'static str = "HMAC-SHA256"; +//! const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +//! } +//! +//! /// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } +//! impl AlgorithmOID for HMAC_SHA256Params { +//! 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]; //! } //! +//! impl HMACParams for HMAC_SHA256Params { +//! type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; +//! type KeyBuf = [u8; ::BLOCK_LEN]; +//! } +//! +//! pub type HMAC_SHA256 = HMAC; +//! //! pub const SUSPENDED_HMAC_SHA256_STATE_LEN: usize = SUSPENDED_SHA256_STATE_LEN; //! ``` //! //! [`HMACParams`] is deliberately **not** sealed, so the same recipe works for a hash function //! defined in any other crate. Simply follow the recipe above! //! +//! Every [`HMAC`] carries a params type; there is no anonymous form. That includes an HMAC used as +//! an internal construction step: HKDF's extract phase is `PRK = HMAC-Hash(salt, IKM)` (RFC 5869 +//! Section 2.2), so the HMAC inside `HKDF_SHA256` really is HMAC-SHA256 and names +//! `HMAC_SHA256Params` accordingly rather than hiding behind a placeholder. +//! //! # 🚨 Security Considerations 🚨 //! //! These apply to every instantiation; the hash crates' `hmac` modules repeat the ones that matter //! most in day-to-day use. //! -//! * An HMAC claims the security strength of its underlying hash, and that claim is enforced in two -//! places: [`MAC::new`] checks it against the key's tagged strength, and -//! [`HMAC::keygen_from_rng`] checks it against the RNG's. NIST SP 800-107-r1 Section 5.3.4 gives +//! * The strength an HMAC claims is declared by its params, and is enforced in two places: +//! [`MAC::new`] checks it against the key's tagged strength, and [`HMAC::keygen_from_rng`] +//! checks it against the RNG's. It is not the underlying hash's strength: HMAC does not rest on +//! collision resistance, so the figures are independent. NIST SP 800-107-r1 Section 5.3.4 gives //! the ceiling: the effective strength is `min(strength of K, 2C)` for an internal chaining value -//! of `C` bits. +//! of `C` bits. Declaring a strength the construction cannot support does not make it stronger, +//! it just makes both checks wrong. //! * [`MAC::new_allow_weak_key`] deliberately skips the key-strength check. It exists for protocols //! that call for a weak or all-zero key -- an all-zero HKDF salt, for example -- and should not be //! used to silence an error from [`MAC::new`]. @@ -95,110 +114,61 @@ use bouncycastle_core::errors::{KeyMaterialError, MACError, RNGError, Suspendabl use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ - Algorithm, AlgorithmOID, Hash, HashAlgParams, MAC, RNG, Suspendable, SuspendableKeyed, + Algorithm, AlgorithmOID, Hash, MAC, RNG, Suspendable, SuspendableKeyed, }; +use bouncycastle_utils::secret::ZeroizablePrimitive; use bouncycastle_utils::{ct, secret::Secret}; use core::fmt::{Debug, Display, Formatter}; +use core::marker::PhantomData; + +/*** Imports needed for docs ***/ +#[allow(unused_imports)] +use bouncycastle_core::traits::HashAlgParams; /*** Parameters ***/ -/// The HMAC-specific parameters for one underlying hash function. -/// -/// [`HMAC`] itself is fully generic: it works with any [`Hash`], including hashes supplied by crates -/// outside this library. What HMAC cannot derive on its own is the *metadata* of the resulting -/// construction -- this trait supplies exactly that metadata, so the blanket [`Algorithm`], [`AlgorithmOID`] and -/// [`HMAC::keygen_from_rng`] can be impl'd generically rather than being written out once -/// per hash. -/// -/// Each hash crate is expected to implement this trait for its own hash types and publish the -/// resulting type alias. For example, `HMAC_SHA256` lives in `bouncycastle_sha2::hmac` and -/// `HMAC_SHA3_256` in `bouncycastle_sha3::hmac`. -/// -/// # Why the metadata comes in through a trait on the hash -/// -/// It is tempting to delete this trait and have each hash crate write the impls directly onto its -/// own alias instead -- `impl Algorithm for HMAC_SHA256` over in `bouncycastle-sha2`. That does not -/// compile. [`Algorithm`] belongs to `bouncycastle-core` and [`HMAC`] belongs to this crate, so both -/// are foreign there, and `HMAC` does not become a local type merely because `SHA256` -/// is: the orphan rule counts `Foreign` as foreign (RFC 2451), and rustc rejects the impl -/// with E0117. Only this crate may write `impl ... for HMAC<..>`, and this crate must not depend on -/// the hash crates -- breaking that cycle is the whole reason HMAC is a utility crate. A blanket -/// impl fed by a trait on the hash is therefore the only available shape, not a stylistic choice. -/// -/// The block length and the generated-key length do not need to be restated here since they are already -/// carried by the hash itself as [`HashAlgParams::BLOCK_LEN`] and [`HashAlgParams::OUTPUT_LEN`]. -/// Neither does the claimed security strength: an HMAC claims the strength of its underlying hash, -/// so the blanket [`Algorithm`] impl reads [`Algorithm::MAX_SECURITY_STRENGTH`] straight off the -/// hash. Restating it here would only create a second copy that could disagree with the one -/// [`MAC::new`] actually enforces. -pub trait HMACParams: Hash + HashAlgParams + Default { - /// The key type produced by [`HMAC::keygen_from_rng`], sized to this hash's output length. +/// The metadata of one concrete HMAC instantiation, supplied as a marker type. +pub trait HMACParams: Algorithm + AlgorithmOID { + /// The type of key that this HMAC instance needs, sized to the underlying hash's output length. /// - /// Implementors should set this to `KeyMaterial<{Self::OUTPUT_LEN}>`. + /// Implementors should set this to `KeyMaterial<{ ::OUTPUT_LEN }>`. /// // todo: once rust stabilizes generic_const_exprs, delete this and return // `KeyMaterial<{Self::OUTPUT_LEN}>` from `keygen_from_rng` instead. type MACKey: KeyMaterialTrait + Default; - /// The name of the HMAC over this hash, as reported by [`Algorithm::ALG_NAME`]. + /// The internal key buffer. /// - /// This is always "HMAC-" followed by the hash's own [`Algorithm::ALG_NAME`] -- which is exactly - /// what [`HMAC`]'s [`Display`] impl formats at runtime -- but a `const` cannot concatenate two - /// `&'static str`s without an external crate, so it is restated here. - const HMAC_ALG_NAME: &'static str; - /// The OID of the HMAC over this hash in component form, as reported by [`AlgorithmOID::OID`]. + /// It 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, and only longer keys are pre-hashed down to + /// the output length, so the buffer has to fit a whole block. /// - /// Every hash/HMAC pair is assigned its own OID -- RFC 4231 for the SHA-2 family, the NIST - /// Computer Security Objects Register for SHA-3 -- so this cannot be derived from the hash's OID. - const HMAC_OID: &'static [u32]; - /// The DER encoding of [`HMACParams::HMAC_OID`], as reported by [`AlgorithmOID::OID_DER`]. - const HMAC_OID_DER: &'static [u8]; -} - -impl Algorithm for HMAC { - const ALG_NAME: &'static str = HASH::HMAC_ALG_NAME; - // An HMAC claims the security strength of the hash underneath it. Reading it off the hash rather - // than restating it per instantiation keeps the advertised strength identical to the one - // `HMAC::init` enforces against the key, which it takes from `Hash::max_security_strength`. - const MAX_SECURITY_STRENGTH: SecurityStrength = HASH::MAX_SECURITY_STRENGTH; -} - -impl AlgorithmOID for HMAC { - const OID: &'static [u32] = HASH::HMAC_OID; - const OID_DER: &'static [u8] = HASH::HMAC_OID_DER; + /// Implementors should set this to `[u8; { ::BLOCK_LEN }]`. + type KeyBuf: ZeroizablePrimitive + AsRef<[u8]> + AsMut<[u8]>; } -// 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; - /// 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 { +pub struct HMAC { + _phantom_params: PhantomData, hasher: HASH, - // todo: once rust stable merges generic_const_exprs, we can remove this hack and delete the KEY_BUF_LEN param. - // key: [u8; HASH::OUTPUT_LEN]; - key: Secret<[u8; KEY_BUF_LEN]>, + // Sized by the params ([`HMACParams::KeyBuf`]), which take it from the hash's block length, so + // the caller cannot pick a buffer that does not fit the hash. + key: Secret, key_len: Secret, // Doing it this way to avoid needing a vec, so that this can be made no_std friendly. } -impl Debug for HMAC { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "HMAC-{} instance", HASH::ALG_NAME,) +impl Debug for HMAC { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{} instance", PARAMS::ALG_NAME,) } } -impl Display 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<'_>) -> core::fmt::Result { + write!(f, "{} instance", PARAMS::ALG_NAME,) } } @@ -218,13 +188,13 @@ const OPAD_BYTE: u8 = 0x5C; /// = 32 bits / 8 = 4 bytes; pub const MIN_FIPS_DIGEST_LEN: usize = 4; -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]; - padded[..*self.key_len].copy_from_slice(&self.key[..*self.key_len]); + padded[..*self.key_len].copy_from_slice(&self.key.as_ref()[..*self.key_len]); // XXX: easier way to xor over Vec? for entry in &mut padded { @@ -243,16 +213,16 @@ impl HMAC { 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()]); + HASH::default().hash_out(key_bytes, &mut self.key.as_mut()[..self.hasher.output_len()]); *self.key_len = self.hasher.output_len(); } else { - self.key[..key_bytes.len()].copy_from_slice(key_bytes); + self.key.as_mut()[..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, + *self.key_len <= self.key.as_ref().len(), "Fatal error: Key length exceeds HMAC internal buffer length" ); } @@ -279,7 +249,7 @@ impl HMAC { self.pad_key_into_hasher(IPAD_BYTE); // check that the key had enough security level - if !allow_weak_keys && key.security_strength() < HASH::default().max_security_strength() { + if !allow_weak_keys && key.security_strength() < PARAMS::MAX_SECURITY_STRENGTH { Err(KeyMaterialError::SecurityStrength( "HMAC::init(): provided key has a lower security strength than the instantiated HMAC", ))? @@ -323,15 +293,25 @@ 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: Secret::new(), key_len: Secret::new() }; + let mut hmac = Self { + _phantom_params: PhantomData, + hasher: HASH::default(), + key: Secret::new(), + key_len: Secret::new(), + }; hmac.init(key, false)?; Ok(hmac) } fn new_allow_weak_key(key: &impl KeyMaterialTrait) -> Result { - let mut hmac = Self { hasher: HASH::default(), key: Secret::new(), key_len: Secret::new() }; + let mut hmac = Self { + _phantom_params: PhantomData, + hasher: HASH::default(), + key: Secret::new(), + key_len: Secret::new(), + }; hmac.init(key, true)?; Ok(hmac) } @@ -384,7 +364,9 @@ impl MAC for HMAC SecurityStrength { - HASH::default().max_security_strength() + // Same source as the key check in `init()`, so the strength this reports and the strength + // that gets enforced cannot drift apart. + PARAMS::MAX_SECURITY_STRENGTH } } @@ -405,9 +387,9 @@ impl MAC for HMAC, -> SuspendableKeyed for HMAC + PARAMS: HMACParams, +> 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 @@ -432,7 +414,12 @@ impl< // 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: Secret::new(), key_len: Secret::new() }; + let mut hmac = HMAC { + _phantom_params: PhantomData, + hasher, + key: Secret::new(), + key_len: Secret::new(), + }; hmac.load_key_material(key.ref_to_bytes()); Ok(hmac) @@ -441,7 +428,7 @@ impl< /* KeyGen functions */ -impl HMAC { +impl HMAC { /// Generates a key of the appropriate length for this HMAC from the provided RNG, tagged /// [`KeyType::MACKey`] and ready to hand to [`MAC::new`]. /// @@ -451,16 +438,29 @@ impl HMAC { // Dev note: done this way to avoid this crate needing a dependency on the `bouncycastle-rng` crate, // which itself has a dependency on `bouncycastle-sha2` which depends on this hmac crate, // which creates a circular cargo dependency. - pub fn keygen_from_rng(rng: &mut dyn RNG) -> Result { + pub fn keygen_from_rng(rng: &mut dyn RNG) -> Result { // Refuse to generate a key from an RNG that cannot back the strength this HMAC claims; // otherwise the key's tagged security strength would overstate its true entropy. - if rng.security_strength() < HASH::MAX_SECURITY_STRENGTH { + if rng.security_strength() < PARAMS::MAX_SECURITY_STRENGTH { return Err(RNGError::SecurityStrengthInsufficientForAlgorithm); } - let mut key = HASH::MACKey::default(); + let mut key = PARAMS::MACKey::default(); rng.fill_keymaterial_out(&mut key)?; key.set_key_type(KeyType::MACKey)?; Ok(key) } } + +impl Algorithm for HMAC { + const ALG_NAME: &'static str = PARAMS::ALG_NAME; + // An HMAC claims the security strength of the hash underneath it. Reading it off the hash rather + // than restating it per instantiation keeps the advertised strength identical to the one + // `HMAC::init` enforces against the key, which it takes from `P::max_security_strength`. + const MAX_SECURITY_STRENGTH: SecurityStrength = PARAMS::MAX_SECURITY_STRENGTH; +} + +impl AlgorithmOID for HMAC { + const OID: &'static [u32] = PARAMS::OID; + const OID_DER: &'static [u8] = PARAMS::OID_DER; +} diff --git a/crypto/sha2/src/hkdf.rs b/crypto/sha2/src/hkdf.rs index d20886775..6db337cc0 100644 --- a/crypto/sha2/src/hkdf.rs +++ b/crypto/sha2/src/hkdf.rs @@ -186,20 +186,19 @@ //! //! | Object | Size (bytes) | //! |---------------------------------------------------|--------------| -//! | `HKDF_SHA256` | 296 | -//! | `HKDF_SHA384` | 392 | -//! | `HKDF_SHA512` | 392 | +//! | `HKDF_SHA256` | 216 | +//! | `HKDF_SHA384` | 376 | +//! | `HKDF_SHA512` | 376 | //! | Suspended `HKDF_SHA256` state | 122 | //! | Suspended `HKDF_SHA384` state | 218 | //! | Suspended `HKDF_SHA512` state | 218 | //! -//! The object is an `Option` of the inner extract-phase HMAC -- 272 bytes for SHA-256, 368 for +//! The object is an `Option` of the inner extract-phase HMAC -- 192 bytes for SHA-256, 352 for //! SHA-384 and SHA-512 -- plus 24 bytes of bookkeeping (the entropy counter, the accumulated security strength -//! and the state-machine tag, with padding). Note that the inner HMAC is written as `HMAC`, which -//! takes the *default* key buffer length: the largest block length across all supported hashes -//! (144 bytes) rather than the 64 or 128 that SHA-256 and SHA-512 actually need. So the inner -//! `HMAC` is 264 bytes where the published [`crate::hmac::HMAC_SHA256`] is 184, and an -//! `HKDF_SHA256` is correspondingly larger than the HMAC it is built on. +//! and the state-machine tag, with padding). The inner HMAC is the published +//! [`crate::hmac::HMAC_SHA256`] rather than an anonymous one, so its key buffer is sized to the +//! hash's block length like any other: an `HKDF_SHA256` is 32 bytes larger than the HMAC it is +//! built on, not 112. //! //! The suspended state is the inner HMAC's suspended state (which is the hash's) plus 14 bytes; the //! salt is deliberately excluded and must be re-supplied on resume. @@ -256,11 +255,26 @@ pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_L /*** Type aliases ***/ /// Public type for HKDF using SHA256. #[allow(non_camel_case_types)] -pub type HKDF_SHA256 = HKDF; +pub type HKDF_SHA256 = HKDF< + SHA256, + crate::hmac::HMAC_SHA256Params, + SUSPENDED_SHA256_STATE_LEN, + SUSPENDED_HKDF_SHA256_STATE_LEN, +>; /// Public type for HKDF using SHA384. SHA-384 is a member of the SHA-512 family, so it shares /// SHA-512's suspended-state length. #[allow(non_camel_case_types)] -pub type HKDF_SHA384 = HKDF; +pub type HKDF_SHA384 = HKDF< + SHA384, + crate::hmac::HMAC_SHA384Params, + SUSPENDED_SHA512_STATE_LEN, + SUSPENDED_HKDF_SHA384_STATE_LEN, +>; /// Public type for HKDF using SHA512. #[allow(non_camel_case_types)] -pub type HKDF_SHA512 = HKDF; +pub type HKDF_SHA512 = HKDF< + SHA512, + crate::hmac::HMAC_SHA512Params, + SUSPENDED_SHA512_STATE_LEN, + SUSPENDED_HKDF_SHA512_STATE_LEN, +>; diff --git a/crypto/sha2/src/hmac.rs b/crypto/sha2/src/hmac.rs index 624109ad6..4261ed468 100644 --- a/crypto/sha2/src/hmac.rs +++ b/crypto/sha2/src/hmac.rs @@ -4,14 +4,17 @@ //! Uses [`bouncycastle_hmac`] to provide the HMAC-SHA2 instantiations: [`HMAC_SHA224`], //! [`HMAC_SHA256`], [`HMAC_SHA384`] and [`HMAC_SHA512`]. //! -//! HMAC itself is implemented generically in [`bouncycastle_hmac`]; this module supplies the -//! SHA-2-specific parameters via [`HMACParams`] and publishes the resulting type aliases, so that +//! HMAC itself is implemented generically in [`bouncycastle_hmac`]; this module declares one +//! [`HMACParams`] marker type per instantiation (such as [`HMAC_SHA256Params`]), carrying that +//! HMAC's name, claimed strength, OID and key type, and publishes the type alias pairing each +//! marker with its hash. This mirrors how the hashes themselves are built, where `SHA256` is +//! `SHA256Internal`. The upshot is that //! HMAC over a SHA2 hash is found in this crate, and [`bouncycastle_hmac`] serves as a utility crate //! rather than as part of library's public API. //! -//! The key buffer length of each alias is the underlying hash's block length: per RFC 2104, a key no -//! longer than the block is used verbatim, and only longer keys are pre-hashed down to the output -//! length, so the buffer must be able to hold a full block. It is taken from +//! Each params type sizes the internal key buffer to its hash's block length: per RFC 2104, a key +//! no longer than the block is used verbatim, and only longer keys are pre-hashed down to the +//! output length, so the buffer must be able to hold a full block. It is taken from //! [`HashAlgParams::BLOCK_LEN`] rather than restated as a literal so the two cannot drift apart. //! //! # Usage @@ -247,7 +250,8 @@ use crate::{SHA224, SHA256, SHA384, SHA512, SHA512_224, SHA512_256}; use crate::{SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::traits::HashAlgParams; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams}; use bouncycastle_hmac::{HMAC, HMACParams}; /*** Imports needed for docs ***/ @@ -272,79 +276,223 @@ pub const HMAC_SHA512_224_NAME: &str = "HMAC-SHA512/224"; /// pub const HMAC_SHA512_256_NAME: &str = "HMAC-SHA512/256"; -/*** Type aliases ***/ -/// Public type for HMAC using SHA224. +/*** Params types and type aliases ***/ +// TODO: revise the MAX_SECURITY_STRENGTH that each params type below declares. +// +// They currently declare the underlying hash's collision strength (output length / 2): 112 bits for +// HMAC-SHA224, 128 for HMAC-SHA256, and so on. That figure is a leftover from when the strength was +// read off the hash, and NIST SP 800-107 Revision 1 (August 2012) Section 5.3.4 says collision +// resistance is the wrong basis for an HMAC: +// +// "The effective security strength of the HMAC key is the minimum of the security strength of +// K and the value of 2C. That is, security strength = min(security strength of K, 2C)." +// +// where C is "the bit length of the internal hash value that is denoted H in FIPS 180-4" -- the +// chaining value, not the output. Footnote 4 of that section is explicit that collisions are out of +// scope: "the collision attack is not considered in this document. In this Recommendation, the +// strength of the HMAC key is considered to be the amount of work required for an attacker who +// performs a brute-force attack to discover the HMAC key K or the first hash values (Hs) ...". +// +// Recommendation: raise them. Section 5.3.4 gives C = 256 for SHA-256 ("L = C for SHA-1, SHA-256, +// and SHA-512") and C = 512 for SHA-384 and SHA-512/t, so 2C is 512 and 1024 bits respectively -- +// far above any key this library accepts. It does not state C for SHA-224, but C >= L = 224 there, +// so 2C >= 448 and the conclusion is unchanged: the key's own strength is the binding term in every +// SHA-2 case. Since `MACKey` is `KeyMaterial`, a full-length key carries 224, 256, 384 +// and 512 bits for HMAC-SHA224/256/384/512, every one of which exceeds what is declared today, and +// all but the first exceed the top of `SecurityStrength`, so they would cap at `_256bit` (and +// HMAC-SHA224 at `_192bit`). +// +// Do NOT extrapolate this to the HMAC-SHA3 values in bouncycastle-sha3. SP 800-107r1 predates SHA-3 +// and Section 5.3.4 is written in terms of the FIPS 180-4 chaining value, which a sponge +// construction does not have. Those need their own reference (FIPS 202 / SP 800-185) read first. +// +// This is not a mechanical edit. MAX_SECURITY_STRENGTH is enforced, not merely reported, so raising +// it makes `MAC::new` reject keys it accepts today and `keygen_from_rng` reject RNGs it accepts +// today. Raising HMAC-SHA256 to `_256bit`, for example, would make HashDRBG_SHA256 insufficient to +// generate its own key and would invert the existing `keygen_rejects_weak_rng` test. Settle the +// policy question first: does this constant describe the ceiling the construction can reach, or the +// minimum a caller is required to bring to it? Those give different numbers. + +/// The parameters for HMAC-SHA224 -- see [`HMAC_SHA224`]. +#[derive(Clone)] #[allow(non_camel_case_types)] -pub type HMAC_SHA224 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA224 { +pub struct HMAC_SHA224Params; + +impl Algorithm for HMAC_SHA224Params { + const ALG_NAME: &'static str = HMAC_SHA224_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA224: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; +} + +/// Defined in RFC 4231: id-hmacWithSHA224 { digestAlgorithm 8 } +impl AlgorithmOID for HMAC_SHA224Params { + 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]; +} + +impl HMACParams for HMAC_SHA224Params { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA224_NAME; - /// Defined in RFC 4231: id-hmacWithSHA224 { digestAlgorithm 8 } - const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 8]; - const HMAC_OID_DER: &'static [u8] = - &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x08]; + type KeyBuf = [u8; ::BLOCK_LEN]; } -/// Public type for HMAC using SHA256. +/// Public type for HMAC using SHA224. +#[allow(non_camel_case_types)] +pub type HMAC_SHA224 = HMAC; + +/// The parameters for HMAC-SHA256 -- see [`HMAC_SHA256`]. +#[derive(Clone)] #[allow(non_camel_case_types)] -pub type HMAC_SHA256 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA256 { +pub struct HMAC_SHA256Params; + +impl Algorithm for HMAC_SHA256Params { + const ALG_NAME: &'static str = HMAC_SHA256_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA256: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } +impl AlgorithmOID for HMAC_SHA256Params { + 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]; +} + +impl HMACParams for HMAC_SHA256Params { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA256_NAME; - /// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } - const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 9]; - const HMAC_OID_DER: &'static [u8] = - &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09]; + type KeyBuf = [u8; ::BLOCK_LEN]; } -/// Public type for HMAC using SHA384. +/// Public type for HMAC using SHA256. #[allow(non_camel_case_types)] -pub type HMAC_SHA384 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA384 { +pub type HMAC_SHA256 = HMAC; + +/// The parameters for HMAC-SHA384 -- see [`HMAC_SHA384`]. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct HMAC_SHA384Params; + +impl Algorithm for HMAC_SHA384Params { + const ALG_NAME: &'static str = HMAC_SHA384_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA384: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; +} + +/// Defined in RFC 4231: id-hmacWithSHA384 { digestAlgorithm 10 } +impl AlgorithmOID for HMAC_SHA384Params { + 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]; +} + +impl HMACParams for HMAC_SHA384Params { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA384_NAME; - /// Defined in RFC 4231: id-hmacWithSHA384 { digestAlgorithm 10 } - const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 10]; - const HMAC_OID_DER: &'static [u8] = - &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0a]; + type KeyBuf = [u8; ::BLOCK_LEN]; } -/// Public type for HMAC using SHA512. +/// Public type for HMAC using SHA384. #[allow(non_camel_case_types)] -pub type HMAC_SHA512 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA512 { +pub type HMAC_SHA384 = HMAC; + +/// The parameters for HMAC-SHA512 -- see [`HMAC_SHA512`]. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct HMAC_SHA512Params; + +impl Algorithm for HMAC_SHA512Params { + const ALG_NAME: &'static str = HMAC_SHA512_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA512: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; +} + +/// Defined in RFC 4231: id-hmacWithSHA512 { digestAlgorithm 11 } +impl AlgorithmOID for HMAC_SHA512Params { + 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]; +} + +impl HMACParams for HMAC_SHA512Params { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA512_NAME; - /// Defined in RFC 4231: id-hmacWithSHA512 { digestAlgorithm 11 } - const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 11]; - const HMAC_OID_DER: &'static [u8] = - &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0b]; + type KeyBuf = [u8; ::BLOCK_LEN]; } -/// Public type for HMAC using SHA512/224. +/// Public type for HMAC using SHA512. +#[allow(non_camel_case_types)] +pub type HMAC_SHA512 = HMAC; + +/// The parameters for HMAC-SHA512/224 -- see [`HMAC_SHA512_224`]. +#[derive(Clone)] #[allow(non_camel_case_types)] -pub type HMAC_SHA512_224 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA512_224 { +pub struct HMAC_SHA512_224Params; + +impl Algorithm for HMAC_SHA512_224Params { + const ALG_NAME: &'static str = HMAC_SHA512_224_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA512_224: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; +} + +/// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-224 { digestAlgorithm 12 } +impl AlgorithmOID for HMAC_SHA512_224Params { + const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 12]; + const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0c]; +} + +impl HMACParams for HMAC_SHA512_224Params { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA512_224_NAME; - /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-224 { digestAlgorithm 12 } - const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 12]; - const HMAC_OID_DER: &'static [u8] = - &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0c]; + type KeyBuf = [u8; ::BLOCK_LEN]; } -/// Public type for HMAC using SHA512/256. +/// Public type for HMAC using SHA512/224. #[allow(non_camel_case_types)] -pub type HMAC_SHA512_256 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA512_256 { +pub type HMAC_SHA512_224 = HMAC; + +/// The parameters for HMAC-SHA512/256 -- see [`HMAC_SHA512_256`]. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct HMAC_SHA512_256Params; + +impl Algorithm for HMAC_SHA512_256Params { + const ALG_NAME: &'static str = HMAC_SHA512_256_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA512_256: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-256 { digestAlgorithm 13 } +impl AlgorithmOID for HMAC_SHA512_256Params { + const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 13]; + const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0d]; +} + +impl HMACParams for HMAC_SHA512_256Params { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA512_256_NAME; - /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-256 { digestAlgorithm 13 } - const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 13]; - const HMAC_OID_DER: &'static [u8] = - &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0d]; + type KeyBuf = [u8; ::BLOCK_LEN]; } +/// Public type for HMAC using SHA512/256. +#[allow(non_camel_case_types)] +pub type HMAC_SHA512_256 = HMAC; + /*** Serialized-state length constants ***/ // HMAC's suspended state is exactly the inner hasher's state -- the key is deliberately excluded and // must be re-supplied on resume -- so each of these is the underlying hash's own state length. diff --git a/crypto/sha2/tests/hmac_tests.rs b/crypto/sha2/tests/hmac_tests.rs index 40d29fa86..b0971269d 100644 --- a/crypto/sha2/tests/hmac_tests.rs +++ b/crypto/sha2/tests/hmac_tests.rs @@ -6,11 +6,11 @@ mod hmac_sha2_tests { KeyMaterial, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; use bouncycastle_core::security_strength::SecurityStrength; - use bouncycastle_core::traits::{Algorithm, Hash, MAC}; + use bouncycastle_core::traits::{Algorithm, AlgorithmOID, Hash, MAC}; use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::mac::TestFrameworkMAC; use bouncycastle_hex as hex; - use bouncycastle_hmac::{HMAC, MIN_FIPS_DIGEST_LEN}; + use bouncycastle_hmac::MIN_FIPS_DIGEST_LEN; use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; use bouncycastle_sha2::hmac::*; use bouncycastle_sha2::*; @@ -26,7 +26,7 @@ mod hmac_sha2_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - let mut mac = HMAC::::new_allow_weak_key(&zero_length_key).unwrap(); + let mut mac = HMAC_SHA256::new_allow_weak_key(&zero_length_key).unwrap(); mac.do_update("Hi There".as_bytes()); let output = mac.do_final(); assert_eq!(output, b"\xe4\x84\x11\x26\x27\x15\xc8\x37\x0c\xd5\xe7\xbf\x8e\x82\xbe\xf5\x3b\xd5\x37\x12\xd0\x07\xf3\x42\x93\x51\x84\x3b\x77\xc7\xbb\x9b"); @@ -37,7 +37,7 @@ mod hmac_sha2_tests { KeyType::MACKey, ) .unwrap(); - let mut mac = HMAC::::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new(&key).unwrap(); mac.do_update(b"Hi There"); let output = mac.do_final(); assert_eq!(output, b"\x89\x6f\xb1\x12\x8a\xbb\xdf\x19\x68\x32\x10\x7c\xd4\x9d\xf3\x3f\x47\xb4\xb1\x16\x99\x12\xba\x4f\x53\x68\x4b\x22"); @@ -48,7 +48,7 @@ mod hmac_sha2_tests { KeyType::MACKey, ) .unwrap(); - let mac = HMAC::::new(&key).unwrap(); + let mac = HMAC_SHA256::new(&key).unwrap(); // mac.do_update(b"").unwrap(); let output = mac.do_final(); assert_eq!( @@ -67,7 +67,7 @@ mod hmac_sha2_tests { .unwrap(); assert_eq!(short_key.security_strength(), SecurityStrength::_112bit); // key is too short, so it is expected to fail - match HMAC::::new(&short_key) { + match HMAC_SHA256::new(&short_key) { Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) => { /* good */ } _ => panic!( "This should have thrown a KeyMaterialError::SecurityStrength error but it didn't" @@ -75,7 +75,7 @@ mod hmac_sha2_tests { } // It works after allowing weak keys - HMAC::::new_allow_weak_key(&short_key).unwrap(); + HMAC_SHA256::new_allow_weak_key(&short_key).unwrap(); // It works with a long enough key let key = KeyMaterial256::from_bytes_as_type( @@ -83,7 +83,7 @@ mod hmac_sha2_tests { KeyType::MACKey, ) .unwrap(); - HMAC::::new(&key).unwrap(); + HMAC_SHA256::new(&key).unwrap(); } #[test] @@ -151,7 +151,7 @@ mod hmac_sha2_tests { assert_eq!(key.security_strength(), SecurityStrength::_256bit); key.set_security_strength(SecurityStrength::_128bit).unwrap(); // The call should fail, as the key's security strength is set below the required threshold - match HMAC::::new(&key) { + match HMAC_SHA512::new(&key) { Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) => { /* fine */ } _ => { panic!( @@ -160,17 +160,17 @@ mod hmac_sha2_tests { } } // It passes after setting .allow_weak_keys() - let mut hmac = HMAC::::new_allow_weak_key(&key).unwrap(); + let mut hmac = HMAC_SHA512::new_allow_weak_key(&key).unwrap(); hmac.do_update(b"Hi There"); hmac.do_final(); // one-shot APIs still work with a weak key - let out = HMAC::::new_allow_weak_key(&key).unwrap().mac(b"Hi There"); - assert!(HMAC::::new_allow_weak_key(&key).unwrap().verify(b"Hi There", &out)); + let out = HMAC_SHA512::new_allow_weak_key(&key).unwrap().mac(b"Hi There"); + assert!(HMAC_SHA512::new_allow_weak_key(&key).unwrap().verify(b"Hi There", &out)); // likewise with pre-allocated buffers let mut out = [0u8; 64]; - HMAC::::new_allow_weak_key(&key).unwrap().mac_out(b"Hi There", &mut out).unwrap(); - assert!(HMAC::::new_allow_weak_key(&key).unwrap().verify(b"Hi There", &out)); + HMAC_SHA512::new_allow_weak_key(&key).unwrap().mac_out(b"Hi There", &mut out).unwrap(); + assert!(HMAC_SHA512::new_allow_weak_key(&key).unwrap().verify(b"Hi There", &out)); } #[test] @@ -182,15 +182,15 @@ mod hmac_sha2_tests { .unwrap(); // get the known-good output - let out = HMAC::::new(&key).unwrap().mac(b"Hi There"); + let out = HMAC_SHA224::new(&key).unwrap().mac(b"Hi There"); // test output that's the wrong length, should simply return False - let mut mac = HMAC::::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new(&key).unwrap(); mac.do_update(b"Hi There"); assert!(!mac.do_verify_final(&out[..out.len() - 1])); // test output that's the right length but wrong value -- do_verify - let mut mac = HMAC::::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new(&key).unwrap(); mac.do_update(b"Hi There"); assert!(!mac.do_verify_final(&[0x01_u8; 28])); @@ -198,7 +198,7 @@ mod hmac_sha2_tests { assert!(!HMAC_SHA224::new(&key).unwrap().verify(b"Hi There", &[0x01_u8; 28])); // error case: test that it'll refuse to truncate below MIN_FIPS_DIGEST_LEN - let mut mac = HMAC::::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new(&key).unwrap(); mac.do_update(b"Hi There"); let mut out = vec![0u8; MIN_FIPS_DIGEST_LEN - 1]; match mac.do_final_out(&mut out) { @@ -212,7 +212,7 @@ mod hmac_sha2_tests { } // success case: ... but it will truncate to exactly MIN_FIPS_DIGEST_LEN - let mut mac = HMAC::::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new(&key).unwrap(); mac.do_update(b"Hi There"); let mut out = vec![0u8; MIN_FIPS_DIGEST_LEN]; let bytes_written = mac.do_final_out(&mut out).unwrap(); @@ -233,6 +233,18 @@ mod hmac_sha2_tests { assert_eq!(HMAC_SHA512_256_NAME, "HMAC-SHA512/256"); assert_eq!(HMAC_SHA512_224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); assert_eq!(HMAC_SHA512_256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + + assert_eq!(HMAC_SHA224::OID, [1, 2, 840, 113549, 2, 8]); + assert_eq!(HMAC_SHA256::OID, [1, 2, 840, 113549, 2, 9]); + assert_eq!(HMAC_SHA384::OID, [1, 2, 840, 113549, 2, 10]); + assert_eq!(HMAC_SHA512::OID, [1, 2, 840, 113549, 2, 11]); + + assert_eq!(HMAC_SHA224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); + assert_eq!(HMAC_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(HMAC_SHA384::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); + assert_eq!(HMAC_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + assert_eq!(HMAC_SHA512_224::OID, [1, 2, 840, 113549, 2, 12]); + assert_eq!(HMAC_SHA512_256::OID, [1, 2, 840, 113549, 2, 13]); } #[cfg(test)] @@ -345,7 +357,7 @@ mod hmac_sha2_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - test_framework.test_mac::>( + test_framework.test_mac::( &zero_length_key, b"Hello, world", &hex::decode("57454372e6a8780b11150274d7056c6fbcffef902f9c23f24fbbfee9").unwrap(), @@ -353,7 +365,7 @@ mod hmac_sha2_tests { // RFC4231 Test Case 1 let test_framework = TestFrameworkMAC::new(); - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type( &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), KeyType::MACKey, @@ -364,7 +376,7 @@ mod hmac_sha2_tests { ); // RFC4231 Test Case 2 -- Test with a key shorter than the length of the HMAC output. - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), b"what do ya want for nothing?", &hex::decode("a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44").unwrap(), @@ -372,14 +384,14 @@ mod hmac_sha2_tests { // RFC4231 Test Case 3 -- 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("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").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(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), &hex::decode("6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a").unwrap(), ); @@ -391,13 +403,13 @@ mod hmac_sha2_tests { ) .unwrap(); let mut out = [0u8; 128 / 8]; - HMAC::::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); + HMAC_SHA224::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); assert_eq!(&Vec::from(out), &hex::decode("0e2aea68a90c8d37c988bcdb9fca6fa8").unwrap()); // RFC4231 Test Case 6 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). let key = KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(); - test_framework.test_mac::>( + test_framework.test_mac::( &key, b"Test Using Larger Than Block-Size Key - Hash Key First", &hex::decode("95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e").unwrap(), @@ -405,7 +417,7 @@ mod hmac_sha2_tests { // 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(), + 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(), ); @@ -423,7 +435,7 @@ mod hmac_sha2_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - test_framework.test_mac::>( + test_framework.test_mac::( &zero_length_key, b"Hello, world", &hex::decode("c0fa4c55880318c31c1020e7a2cf830c2c695716387795c7a0eb918ba84e4bf0") @@ -432,7 +444,7 @@ mod hmac_sha2_tests { // RFC4231 Test Case 1 let test_framework = TestFrameworkMAC::new(); - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type( &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), KeyType::MACKey, @@ -444,7 +456,7 @@ mod hmac_sha2_tests { ); // RFC4231 Test Case 2 -- Test with a key shorter than the length of the HMAC output. - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), b"what do ya want for nothing?", &hex::decode("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843") @@ -453,14 +465,14 @@ mod hmac_sha2_tests { // RFC4231 Test Case 3 -- 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("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").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(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), &hex::decode("82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b").unwrap(), ); @@ -472,19 +484,19 @@ mod hmac_sha2_tests { ) .unwrap(); let mut out = [0u8; 128 / 8]; - HMAC::::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); + HMAC_SHA256::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); assert_eq!(&Vec::from(out), &hex::decode("a3b6167473100ee06e0c796c2955552b").unwrap()); // RFC4231 Test Case 6 -- 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::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + 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(), ); // 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(), + 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(), ); @@ -502,7 +514,7 @@ mod hmac_sha2_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - test_framework.test_mac::>( + test_framework.test_mac::( &zero_length_key, b"Hello, world", &hex::decode("fbd41442f749049355175277afbaff610539e5bfa874c9cf86ef867a43a30b09a5eac6578d5c0cb1ceddc95f97598af7").unwrap(), @@ -510,7 +522,7 @@ mod hmac_sha2_tests { // RFC4231 Test Case 1 let test_framework = TestFrameworkMAC::new(); - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type( &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), KeyType::MACKey, @@ -520,7 +532,7 @@ mod hmac_sha2_tests { ); // RFC4231 Test Case 2 -- Test with a key shorter than the length of the HMAC output. - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), b"what do ya want for nothing?", &hex::decode("af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649").unwrap(), @@ -528,14 +540,14 @@ mod hmac_sha2_tests { // RFC4231 Test Case 3 -- 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("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").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(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), &hex::decode("3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb").unwrap(), ); @@ -548,20 +560,20 @@ mod hmac_sha2_tests { .unwrap(); let mut out = [0u8; 128 / 8]; // Key is shorter than HMAC security strength, so it needs to use new_allow_weak_keys() - let hmac = HMAC::::new_allow_weak_key(&key).unwrap(); + let hmac = HMAC_SHA384::new_allow_weak_key(&key).unwrap(); hmac.mac_out(b"Test With Truncation", &mut out).unwrap(); assert_eq!(&Vec::from(out), &hex::decode("3abf34c3503b2a23a46efc619baef897").unwrap()); // RFC4231 Test Case 6 -- 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::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + 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(), ); // 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(), + 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(), ); @@ -579,7 +591,7 @@ mod hmac_sha2_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - test_framework.test_mac::>( + test_framework.test_mac::( &zero_length_key, b"Hello, world", &hex::decode("e8f7176e01bf9bb883f71f42c143681e86cfafe0b61f3bc0d824e2cde13b5f80199e82d865aebb725461c86a54086aeacac37a86a9f1cf07db567ba5a10f1cc1").unwrap(), @@ -587,7 +599,7 @@ mod hmac_sha2_tests { // RFC4231 Test Case 1 let test_framework = TestFrameworkMAC::new(); - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type( &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), KeyType::MACKey, @@ -598,7 +610,7 @@ mod hmac_sha2_tests { ); // RFC4231 Test Case 2 -- Test with a key shorter than the length of the HMAC output. - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), b"what do ya want for nothing?", &hex::decode("164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737").unwrap(), @@ -606,14 +618,14 @@ mod hmac_sha2_tests { // RFC4231 Test Case 3 -- 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("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").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(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), &hex::decode("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd").unwrap(), ); @@ -626,20 +638,20 @@ mod hmac_sha2_tests { .unwrap(); let mut out = [0u8; 128 / 8]; // Key is shorter than HMAC security strength, so need to use new_allow_weak_keys() - let hmac = HMAC::::new_allow_weak_key(&key).unwrap(); + let hmac = HMAC_SHA512::new_allow_weak_key(&key).unwrap(); hmac.mac_out(b"Test With Truncation", &mut out).unwrap(); assert_eq!(&Vec::from(out), &hex::decode("415fad6271580a531d4179bc891d87a6").unwrap()); // RFC4231 Test Case 6 -- 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::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + 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(), ); // 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(), + 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(), ); diff --git a/crypto/sha3/src/hmac.rs b/crypto/sha3/src/hmac.rs index 99fbf3045..62216af67 100644 --- a/crypto/sha3/src/hmac.rs +++ b/crypto/sha3/src/hmac.rs @@ -4,12 +4,15 @@ //! Uses [`bouncycastle_hmac`] to provide the HMAC-SHA3 instantiations: [`HMAC_SHA3_224`], //! [`HMAC_SHA3_256`], [`HMAC_SHA3_384`] and [`HMAC_SHA3_512`]. //! -//! HMAC itself is implemented generically in [`bouncycastle_hmac`]; this module supplies the -//! SHA-3-specific parameters via [`HMACParams`] and publishes the resulting type aliases, so that +//! HMAC itself is implemented generically in [`bouncycastle_hmac`]; this module declares one +//! [`HMACParams`] marker type per instantiation (such as [`HMAC_SHA3_256Params`]), carrying that +//! HMAC's name, claimed strength, OID and key type, and publishes the type alias pairing each +//! marker with its hash. This mirrors how the hashes themselves are built, where `SHA256` is +//! `SHA256Internal`. The upshot is that //! HMAC over a SHA3 hash is found in this crate, and [`bouncycastle_hmac`] serves as a utility crate //! rather than as part of library's public API. //! -//! The key buffer length of each alias is the underlying hash's block length: per RFC 2104, a key no +//! Each params type sizes the internal key buffer to its hash's block length: per RFC 2104, a key no //! longer than the block is used verbatim, and only longer keys are pre-hashed down to the output //! length, so the buffer must be able to hold a full block. It is taken from //! [`HashAlgParams::BLOCK_LEN`] -- the values FIPS 202 Table 3 ("Input block sizes for HMAC") gives @@ -258,7 +261,8 @@ use crate::SUSPENDED_SHA3_STATE_LEN; use crate::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::traits::HashAlgParams; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams}; use bouncycastle_hmac::{HMAC, HMACParams}; /*** Imports needed for docs ***/ @@ -279,55 +283,137 @@ 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 SHA3_224. +/*** Params types and type aliases ***/ +// TODO: the MAX_SECURITY_STRENGTH values below are each hash's collision strength, which NIST +// SP 800-107r1 Section 5.3.4 says is not the right basis for an HMAC. See the TODO above the +// equivalent params types in bouncycastle-sha2 for the analysis. Note that its recommendation +// covers SHA-2 only: SP 800-107r1 predates SHA-3 and reasons about a Merkle-Damgard chaining +// value, which a sponge does not have, so these need FIPS 202 / SP 800-185 read first. + +/// The parameters for HMAC-SHA3_224 -- see [`HMAC_SHA3_224`]. +#[derive(Clone)] #[allow(non_camel_case_types)] -pub type HMAC_SHA3_224 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA3_224 { - type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA3_224_NAME; - /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-224 { hashAlgs 13 } - const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 13]; - const HMAC_OID_DER: &'static [u8] = +pub struct HMAC_SHA3_224Params; + +impl Algorithm for HMAC_SHA3_224Params { + const ALG_NAME: &'static str = HMAC_SHA3_224_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA3_224: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + 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_224Params { + 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]; } -/// Public type for HMAC using SHA3_256. +impl HMACParams for HMAC_SHA3_224Params { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + type KeyBuf = [u8; ::BLOCK_LEN]; +} + +/// Public type for HMAC using SHA3_224. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_256 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA3_256 { - type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA3_256_NAME; - /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-256 { hashAlgs 14 } - const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 14]; - const HMAC_OID_DER: &'static [u8] = +pub type HMAC_SHA3_224 = HMAC; + +/// The parameters for HMAC-SHA3_256 -- see [`HMAC_SHA3_256`]. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct HMAC_SHA3_256Params; + +impl Algorithm for HMAC_SHA3_256Params { + const ALG_NAME: &'static str = HMAC_SHA3_256_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA3_256: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + 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_256Params { + 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]; } -/// Public type for HMAC using SHA3_384. +impl HMACParams for HMAC_SHA3_256Params { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + type KeyBuf = [u8; ::BLOCK_LEN]; +} + +/// Public type for HMAC using SHA3_256. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_384 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA3_384 { - type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA3_384_NAME; - /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-384 { hashAlgs 15 } - const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 15]; - const HMAC_OID_DER: &'static [u8] = +pub type HMAC_SHA3_256 = HMAC; + +/// The parameters for HMAC-SHA3_384 -- see [`HMAC_SHA3_384`]. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct HMAC_SHA3_384Params; + +impl Algorithm for HMAC_SHA3_384Params { + const ALG_NAME: &'static str = HMAC_SHA3_384_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA3_384: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + 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_384Params { + 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]; } -/// Public type for HMAC using SHA3_512. +impl HMACParams for HMAC_SHA3_384Params { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + type KeyBuf = [u8; ::BLOCK_LEN]; +} + +/// Public type for HMAC using SHA3_384. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_512 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA3_512 { - type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA3_512_NAME; - /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-512 { hashAlgs 16 } - const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 16]; - const HMAC_OID_DER: &'static [u8] = +pub type HMAC_SHA3_384 = HMAC; + +/// The parameters for HMAC-SHA3_512 -- see [`HMAC_SHA3_512`]. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct HMAC_SHA3_512Params; + +impl Algorithm for HMAC_SHA3_512Params { + const ALG_NAME: &'static str = HMAC_SHA3_512_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA3_512: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + 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_512Params { + 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]; } +impl HMACParams for HMAC_SHA3_512Params { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + type KeyBuf = [u8; ::BLOCK_LEN]; +} + +/// Public type for HMAC using SHA3_512. +#[allow(non_camel_case_types)] +pub type HMAC_SHA3_512 = HMAC; + /*** Serialized-state length constants ***/ // HMAC's suspended state is exactly the inner hasher's state -- the key is deliberately excluded and // must be re-supplied on resume -- so each of these is the underlying hash's own state length. All diff --git a/crypto/sha3/tests/hmac_tests.rs b/crypto/sha3/tests/hmac_tests.rs index d4654f43a..b5f43153f 100644 --- a/crypto/sha3/tests/hmac_tests.rs +++ b/crypto/sha3/tests/hmac_tests.rs @@ -1,7 +1,8 @@ #[cfg(test)] mod hmac_sha3_tests { use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; - use bouncycastle_core::traits::{Algorithm, MAC}; + use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::traits::{Algorithm, AlgorithmOID, MAC}; use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::hmac::*; @@ -51,6 +52,16 @@ mod hmac_sha3_tests { assert_eq!(HMAC_SHA3_256::ALG_NAME, HMAC_SHA3_256_NAME); assert_eq!(HMAC_SHA3_384::ALG_NAME, HMAC_SHA3_384_NAME); assert_eq!(HMAC_SHA3_512::ALG_NAME, HMAC_SHA3_512_NAME); + + assert_eq!(HMAC_SHA3_224::OID, [2, 16, 840, 1, 101, 3, 4, 2, 13]); + assert_eq!(HMAC_SHA3_256::OID, [2, 16, 840, 1, 101, 3, 4, 2, 14]); + assert_eq!(HMAC_SHA3_384::OID, [2, 16, 840, 1, 101, 3, 4, 2, 15]); + assert_eq!(HMAC_SHA3_512::OID, [2, 16, 840, 1, 101, 3, 4, 2, 16]); + + assert_eq!(HMAC_SHA3_224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); + assert_eq!(HMAC_SHA3_256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(HMAC_SHA3_384::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); + assert_eq!(HMAC_SHA3_512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); } #[test] diff --git a/crypto/sm3/src/hmac.rs b/crypto/sm3/src/hmac.rs index 6a3af3cf7..2abca2e70 100644 --- a/crypto/sm3/src/hmac.rs +++ b/crypto/sm3/src/hmac.rs @@ -43,7 +43,8 @@ use crate::{SM3, SUSPENDED_SM3_STATE_LEN}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::traits::HashAlgParams; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams}; use bouncycastle_hmac::{HMAC, HMACParams}; /*** Imports needed for docs ***/ @@ -59,19 +60,38 @@ use bouncycastle_hmac::MIN_FIPS_DIGEST_LEN; /// Algorithm name string for HMAC-SM3, as used by the factories and CLI. pub const HMAC_SM3_NAME: &str = "HMAC-SM3"; -/*** Type aliases ***/ -/// Public type for HMAC using SM3. +/*** Params types and type aliases ***/ +/// The parameters for HMAC-SM3 -- see [`HMAC_SM3`]. +#[derive(Clone)] #[allow(non_camel_case_types)] -pub type HMAC_SM3 = HMAC::BLOCK_LEN }>; -impl HMACParams for SM3 { - type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SM3_NAME; - /// Assigned by the Chinese OSCCA (GM/T 0006): hmac-sm3 { sm3 2 } = 1.2.156.10197.1.401.2 - const HMAC_OID: &'static [u32] = &[1, 2, 156, 10197, 1, 401, 2]; - const HMAC_OID_DER: &'static [u8] = +pub struct HMAC_SM3Params; + +impl Algorithm for HMAC_SM3Params { + const ALG_NAME: &'static str = HMAC_SM3_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SM3: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// Assigned by the Chinese OSCCA (GM/T 0006): hmac-sm3 { sm3 2 } = 1.2.156.10197.1.401.2 +impl AlgorithmOID for HMAC_SM3Params { + const OID: &'static [u32] = &[1, 2, 156, 10197, 1, 401, 2]; + const OID_DER: &'static [u8] = &[0x06, 0x09, 0x2A, 0x81, 0x1C, 0xCF, 0x55, 0x01, 0x83, 0x11, 0x02]; } +impl HMACParams for HMAC_SM3Params { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + type KeyBuf = [u8; ::BLOCK_LEN]; +} + +/// Public type for HMAC using SM3. +#[allow(non_camel_case_types)] +pub type HMAC_SM3 = HMAC; + /*** Serialized-state length constants ***/ // HMAC's suspended state is exactly the inner hasher's state -- the key is deliberately excluded and // must be re-supplied on resume -- so this is SM3's own state length. From 9d3ea898561169dfd380e3383557a35e000a6912 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sat, 12 Sep 2026 15:48:18 -0500 Subject: [PATCH 5/7] Adjusting HMAC::MAX_SECURITY_STRENGTH to higher values as allowed by SP 800-107r1 --- crypto/factory/tests/mac_factory_tests.rs | 28 ++--- crypto/hmac/src/lib.rs | 41 +++++-- crypto/sha2/src/hmac.rs | 141 +++++++++------------- crypto/sha2/tests/hmac_tests.rs | 80 +++++++----- crypto/sha3/src/hmac.rs | 85 +++++++------ crypto/sha3/tests/hmac_tests.rs | 21 ++-- crypto/sm3/src/hmac.rs | 10 +- 7 files changed, 212 insertions(+), 194 deletions(-) diff --git a/crypto/factory/tests/mac_factory_tests.rs b/crypto/factory/tests/mac_factory_tests.rs index a71214656..fee9fd886 100644 --- a/crypto/factory/tests/mac_factory_tests.rs +++ b/crypto/factory/tests/mac_factory_tests.rs @@ -10,16 +10,19 @@ mod hash_factory_tests { #[test] fn sha2_hash_tests() { - // HMAC-SHA224 - let key = KeyMaterial::<32>::from_bytes_as_type( - &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), + // HMAC-SHA224, RFC 4231 Test Case 6. MACFactory has no weak-key constructor, so this + // needs a vector whose key reaches the strength HMAC-SHA224 claims; Test Case 1's + // 20-byte key does not. + let key = KeyMaterial::<131>::from_bytes_as_type( + &hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + .unwrap(), KeyType::MACKey, ) .unwrap(); let hmac = MACFactory::new("HMAC-SHA224", &key).unwrap(); assert!(hmac.verify( - b"Hi There", - &hex::decode("896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22").unwrap(), + b"Test Using Larger Than Block-Size Key - Hash Key First", + &hex::decode("95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e").unwrap(), )); // HMAC-SHA512/224 -- NIST ACVP HMAC-SHA2-512/224 2.0, tgId 1, tcId 106 (MAC truncated to 160 bits) @@ -145,21 +148,18 @@ mod hash_factory_tests { #[test] fn hmac_sm3_tests() { - // RFC4231 Test Case 1 key/message; expected value from `openssl dgst -sm3 -mac HMAC`, - // confirmed with bc-java's HMac(new SM3Digest()). - let key = KeyMaterial::<32>::from_bytes_as_type( - &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), - KeyType::MACKey, - ) - .unwrap(); + // RFC4231 Test Case 6 key/message, for the same reason as HMAC-SHA224 above; expected + // value from `openssl dgst -sm3 -mac HMAC`, confirmed with bc-java's HMac(new SM3Digest()). + let key = + KeyMaterial::<131>::from_bytes_as_type(&[0xaa; 131], KeyType::MACKey).unwrap(); for name in ["HMAC-SM3", bouncycastle_sm3::hmac::HMAC_SM3_NAME] { let hmac = MACFactory::new(name, &key).unwrap(); assert_eq!(hmac.output_len(), 32); assert!( hmac.verify( - b"Hi There", + b"Test Using Larger Than Block-Size Key - Hash Key First", &hex::decode( - "51b00d1fb49832bfb01c3ce27848e59f871d9ba938dc563b338ca964755cce70" + "b4fd844e13342002f0b2e0690ea7741f1497d993a70494cea601e657bedf67a0" ) .unwrap(), ) diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index e49c0703a..25b4bbca2 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -85,16 +85,21 @@ //! These apply to every instantiation; the hash crates' `hmac` modules repeat the ones that matter //! most in day-to-day use. //! -//! * The strength an HMAC claims is declared by its params, and is enforced in two places: -//! [`MAC::new`] checks it against the key's tagged strength, and [`HMAC::keygen_from_rng`] -//! checks it against the RNG's. It is not the underlying hash's strength: HMAC does not rest on -//! collision resistance, so the figures are independent. NIST SP 800-107-r1 Section 5.3.4 gives -//! the ceiling: the effective strength is `min(strength of K, 2C)` for an internal chaining value -//! of `C` bits. Declaring a strength the construction cannot support does not make it stronger, -//! it just makes both checks wrong. -//! * [`MAC::new_allow_weak_key`] deliberately skips the key-strength check. It exists for protocols -//! that call for a weak or all-zero key -- an all-zero HKDF salt, for example -- and should not be -//! used to silence an error from [`MAC::new`]. +//! * The strength an HMAC claims is declared by its params, and is the most the instantiation can +//! deliver. It is not the underlying hash's collision strength: NIST SP 800-107-r1 Section 5.3.4 +//! puts it at `min(strength of K, 2C)` for a `C`-bit chaining value, and footnote 4 there rules +//! collision attacks out of scope entirely. Declaring a strength the construction cannot support +//! does not make it stronger, it just makes the two gates below wrong. +//! * Both gates follow from that claim, and both ask the same question: can this input actually +//! back the strength the algorithm advertises? [`MAC::new`] checks the key's tagged strength and +//! [`HMAC::keygen_from_rng`] checks the RNG's, each refusing anything weaker. So HMAC-SHA256 +//! wants a 256-bit key and a 256-bit generator. Truncating the *output* still lowers the strength +//! of a given tag, which is why this is a ceiling rather than a guarantee. +//! * The escape hatch is on the key only. [`MAC::new_allow_weak_key`] skips the key check for +//! protocols that require a weak or all-zero key, such as an all-zero HKDF salt. +//! [`HMAC::keygen_from_rng`] has no equivalent, because a generator cannot be asked to produce +//! entropy it does not have; the key it returned would carry a tag that overstates it. +//! It should not be used merely to silence an error from [`MAC::new`]. //! * Verification via [`MAC::verify`] / [`MAC::do_verify_final`] uses a constant-time comparison. //! Recomputing the MAC and comparing it with `==` leaks how many leading bytes matched. //! * [`MIN_FIPS_DIGEST_LEN`] (4 bytes) is the shortest truncation this crate will produce, per @@ -435,6 +440,22 @@ impl HMAC { /// The key length is the underlying hash's output length ([`HashAlgParams::OUTPUT_LEN`], carried /// as [`HMACParams::MACKey`]); see that associated type for why. /// + /// # Contract + /// + /// The returned key is tagged at this HMAC's [`Algorithm::MAX_SECURITY_STRENGTH`], and that tag + /// has to be backed by real entropy -- A generator cannot produce + /// more entropy than it has -- so this refuses any RNG that cannot supply it: if + /// [`RNG::security_strength`] is below the claimed strength you get + /// [`RNGError::SecurityStrengthInsufficientForAlgorithm`] and no key. + /// + /// In practice this means a 256-bit generator for HMAC-SHA256 and above. `DefaultRNG` is + /// `HashDRBG_SHA512` and qualifies for every instantiation in this library; `HashDRBG_SHA256` + /// offers 128 bits and so is accepted only where the HMAC claims no more than that. + /// + /// There is deliberately no weak-RNG escape hatch, unlike [`MAC::new_allow_weak_key`]. A + /// caller who genuinely wants a key weaker than the algorithm claims can build one themselves + /// and pass it to [`MAC::new_allow_weak_key`], which makes that choice visible at the call site. + /// // Dev note: done this way to avoid this crate needing a dependency on the `bouncycastle-rng` crate, // which itself has a dependency on `bouncycastle-sha2` which depends on this hmac crate, // which creates a circular cargo dependency. diff --git a/crypto/sha2/src/hmac.rs b/crypto/sha2/src/hmac.rs index 4261ed468..4882d3b5a 100644 --- a/crypto/sha2/src/hmac.rs +++ b/crypto/sha2/src/hmac.rs @@ -44,10 +44,10 @@ //! ``` //! use bouncycastle_core::key_material::KeyMaterial256; //! use bouncycastle_core::traits::MAC; -//! use bouncycastle_rng::HashDRBG_SHA256; +//! use bouncycastle_rng::DefaultRNG; //! use bouncycastle_sha2::hmac::HMAC_SHA256; //! -//! let mut rng = HashDRBG_SHA256::new_from_os(); +//! let mut rng = DefaultRNG::new_from_os(); //! let key: KeyMaterial256 = HMAC_SHA256::keygen_from_rng(&mut rng) //! .expect("Will only fail if the system RNG can't start up."); //! @@ -64,7 +64,8 @@ //! use bouncycastle_sha2::hmac::HMAC_SHA256; //! //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! 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", //! KeyType::MACKey).unwrap(); //! //! let hmac = HMAC_SHA256::new(&key).expect( @@ -80,10 +81,10 @@ //! ``` //! use bouncycastle_core::key_material::KeyMaterial256; //! use bouncycastle_core::traits::MAC; -//! use bouncycastle_rng::HashDRBG_SHA256; +//! use bouncycastle_rng::DefaultRNG; //! use bouncycastle_sha2::hmac::HMAC_SHA256; //! -//! let mut rng = HashDRBG_SHA256::new_from_os(); +//! let mut rng = DefaultRNG::new_from_os(); //! let key: KeyMaterial256 = HMAC_SHA256::keygen_from_rng(&mut rng) //! .expect("Will only fail if the system RNG can't start up."); //! @@ -98,10 +99,10 @@ //! ``` //! use bouncycastle_core::key_material::KeyMaterial256; //! use bouncycastle_core::traits::MAC; -//! use bouncycastle_rng::HashDRBG_SHA256; +//! use bouncycastle_rng::DefaultRNG; //! use bouncycastle_sha2::hmac::HMAC_SHA256; //! -//! let mut rng = HashDRBG_SHA256::new_from_os(); +//! let mut rng = DefaultRNG::new_from_os(); //! let key: KeyMaterial256 = HMAC_SHA256::keygen_from_rng(&mut rng) //! .expect("Will only fail if the system RNG can't start up."); //! @@ -127,7 +128,8 @@ //! // For this example to work, we are hard-coding both the key and the MAC value that it generates //! // for this data. //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! 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", //! KeyType::MACKey).unwrap(); //! //! let data: &[u8] = b"Hello, world!"; @@ -135,8 +137,8 @@ //! // .verify() returns a bool: true if the MAC is valid, false otherwise. //! if HMAC_SHA256::new(&key).unwrap() //! .verify(data, -//! b"\xa2\xd1\x2e\xcf\xfc\x41\xba\xf1\x23\xd6\x3e\x44\xfc\x27\x88\x90 -//! \x47\xcd\x08\xe7\x05\xd7\x0f\xa3\xb8\xaa\x8a\x5c\x18\x7c\x6c\xa9" +//! b"\x76\xd0\x69\x2c\x75\x6f\x89\x94\x96\xf3\x51\x63\x6a\x69\x69\xe5 +//! \x4e\xbf\xb2\x3a\xbb\x09\xfd\x61\x40\x86\x13\x6a\xc9\xab\x26\x77" //! ) //! { //! println!("MAC is valid!"); @@ -156,12 +158,13 @@ //! // For this example to work, we are hard-coding both the key and the MAC value that it generates //! // for this data. //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! 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", //! KeyType::MACKey).unwrap(); //! let mut hmac = HMAC_SHA256::new(&key).unwrap(); //! hmac.do_update(b"Hello,"); //! hmac.do_update(b" world!"); -//! if hmac.do_verify_final(b"\xa2\xd1\x2e\xcf\xfc\x41\xba\xf1\x23\xd6\x3e\x44\xfc\x27\x88\x90\x47\xcd\x08\xe7\x05\xd7\x0f\xa3\xb8\xaa\x8a\x5c\x18\x7c\x6c\xa9" +//! if hmac.do_verify_final(b"\x76\xd0\x69\x2c\x75\x6f\x89\x94\x96\xf3\x51\x63\x6a\x69\x69\xe5\x4e\xbf\xb2\x3a\xbb\x09\xfd\x61\x40\x86\x13\x6a\xc9\xab\x26\x77" //! ) //! { //! println!("MAC is valid!"); @@ -192,7 +195,8 @@ //! 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", +//! 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", //! KeyType::MACKey).unwrap(); //! //! let mut hmac = HMAC_SHA256::new(&key).unwrap(); @@ -232,10 +236,26 @@ //! //! # 🚨 Security Considerations 🚨 //! -//! * The key must carry at least the security strength claimed by the HMAC, and [`MAC::new`] -//! enforces that. [`MAC::new_allow_weak_key`] deliberately skips the check; use it only where a -//! weak or all-zero key is called for by the protocol (an all-zero HKDF salt, for example), not to -//! silence an error. +//! * Each of these HMACs claims the strength NIST SP 800-107r1 Section 5.3.4 gives it, which is +//! `min(strength of K, 2C)` and works out to the key length for the whole SHA-2 family: 224 bits +//! for HMAC-SHA224 and HMAC-SHA512/224 and 256 or more for the rest. `SecurityStrength` has no +//! 224-bit category and tops out at 256, so the declared values are `_192bit` for HMAC-SHA224 and +//! HMAC-SHA512/224 and `_256bit` for HMAC-SHA256, HMAC-SHA384, HMAC-SHA512 and HMAC-SHA512/256. +//! Note these are *not* the underlying hashes' +//! collision strengths, which are half as large; footnote 4 of that section puts collision +//! attacks out of scope for HMAC. +//! * The key must carry at least the strength claimed by the HMAC, and [`MAC::new`] enforces that. +//! A 20-byte key is therefore no longer enough for HMAC-SHA256; a full 32-byte key is. +//! [`MAC::new_allow_weak_key`] deliberately skips the check; use it only where a weak or all-zero +//! key is called for by the protocol (an all-zero HKDF salt, or a fixed test vector such as +//! RFC 4231's 20-byte keys), not to silence an error. +//! * The same rule applies to the generator. [`HMAC::keygen_from_rng`] tags the key it returns at +//! the HMAC's claimed strength, so it refuses any RNG that cannot back that tag: a 256-bit +//! generator is required for HMAC-SHA256 and above, and `bouncycastle_rng::DefaultRNG` is +//! `HashDRBG_SHA512` and qualifies for all of them. `HashDRBG_SHA256` offers 128 bits and is now +//! refused by every HMAC in this module. There is no weak-RNG opt-out, because a generator cannot +//! be asked for entropy it does not have; build the key yourself and use +//! [`MAC::new_allow_weak_key`] if that is genuinely what you want. //! * Verify with [`MAC::verify`] or [`MAC::do_verify_final`] rather than computing the MAC yourself //! and comparing: those use a constant-time comparison, while `==` on the byte slices leaks how //! many leading bytes matched. @@ -277,41 +297,6 @@ pub const HMAC_SHA512_224_NAME: &str = "HMAC-SHA512/224"; pub const HMAC_SHA512_256_NAME: &str = "HMAC-SHA512/256"; /*** Params types and type aliases ***/ -// TODO: revise the MAX_SECURITY_STRENGTH that each params type below declares. -// -// They currently declare the underlying hash's collision strength (output length / 2): 112 bits for -// HMAC-SHA224, 128 for HMAC-SHA256, and so on. That figure is a leftover from when the strength was -// read off the hash, and NIST SP 800-107 Revision 1 (August 2012) Section 5.3.4 says collision -// resistance is the wrong basis for an HMAC: -// -// "The effective security strength of the HMAC key is the minimum of the security strength of -// K and the value of 2C. That is, security strength = min(security strength of K, 2C)." -// -// where C is "the bit length of the internal hash value that is denoted H in FIPS 180-4" -- the -// chaining value, not the output. Footnote 4 of that section is explicit that collisions are out of -// scope: "the collision attack is not considered in this document. In this Recommendation, the -// strength of the HMAC key is considered to be the amount of work required for an attacker who -// performs a brute-force attack to discover the HMAC key K or the first hash values (Hs) ...". -// -// Recommendation: raise them. Section 5.3.4 gives C = 256 for SHA-256 ("L = C for SHA-1, SHA-256, -// and SHA-512") and C = 512 for SHA-384 and SHA-512/t, so 2C is 512 and 1024 bits respectively -- -// far above any key this library accepts. It does not state C for SHA-224, but C >= L = 224 there, -// so 2C >= 448 and the conclusion is unchanged: the key's own strength is the binding term in every -// SHA-2 case. Since `MACKey` is `KeyMaterial`, a full-length key carries 224, 256, 384 -// and 512 bits for HMAC-SHA224/256/384/512, every one of which exceeds what is declared today, and -// all but the first exceed the top of `SecurityStrength`, so they would cap at `_256bit` (and -// HMAC-SHA224 at `_192bit`). -// -// Do NOT extrapolate this to the HMAC-SHA3 values in bouncycastle-sha3. SP 800-107r1 predates SHA-3 -// and Section 5.3.4 is written in terms of the FIPS 180-4 chaining value, which a sponge -// construction does not have. Those need their own reference (FIPS 202 / SP 800-185) read first. -// -// This is not a mechanical edit. MAX_SECURITY_STRENGTH is enforced, not merely reported, so raising -// it makes `MAC::new` reject keys it accepts today and `keygen_from_rng` reject RNGs it accepts -// today. Raising HMAC-SHA256 to `_256bit`, for example, would make HashDRBG_SHA256 insufficient to -// generate its own key and would invert the existing `keygen_rejects_weak_rng` test. Settle the -// policy question first: does this constant describe the ceiling the construction can reach, or the -// minimum a caller is required to bring to it? Those give different numbers. /// The parameters for HMAC-SHA224 -- see [`HMAC_SHA224`]. #[derive(Clone)] @@ -320,12 +305,9 @@ pub struct HMAC_SHA224Params; impl Algorithm for HMAC_SHA224Params { const ALG_NAME: &'static str = HMAC_SHA224_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA224: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; + // SP 800-107r1 s.5.3.4: min(strength of K, 2C). SHA-224 has C = 256, so 2C = 512, + // and the key is OUTPUT_LEN = 224 bits, so the key binds: 224 bits, rounded down to the nearest category. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } /// Defined in RFC 4231: id-hmacWithSHA224 { digestAlgorithm 8 } @@ -350,12 +332,9 @@ pub struct HMAC_SHA256Params; impl Algorithm for HMAC_SHA256Params { const ALG_NAME: &'static str = HMAC_SHA256_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA256: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + // SP 800-107r1 s.5.3.4: min(strength of K, 2C). SHA-256 has C = 256, so 2C = 512, + // and the key is OUTPUT_LEN = 256 bits, so the key binds: 256 bits. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } /// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } @@ -380,12 +359,9 @@ pub struct HMAC_SHA384Params; impl Algorithm for HMAC_SHA384Params { const ALG_NAME: &'static str = HMAC_SHA384_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA384: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; + // SP 800-107r1 s.5.3.4: min(strength of K, 2C). SHA-384 has C = 512, so 2C = 1024, + // and the key is OUTPUT_LEN = 384 bits, so the key binds: 384 bits, capped at the top of `SecurityStrength`. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } /// Defined in RFC 4231: id-hmacWithSHA384 { digestAlgorithm 10 } @@ -410,11 +386,8 @@ pub struct HMAC_SHA512Params; impl Algorithm for HMAC_SHA512Params { const ALG_NAME: &'static str = HMAC_SHA512_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA512: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + // SP 800-107r1 s.5.3.4: min(strength of K, 2C). SHA-512 has C = 512, so 2C = 1024, + // and the key is OUTPUT_LEN = 512 bits, so the key binds: 512 bits, capped at the top of `SecurityStrength`. const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } @@ -440,12 +413,9 @@ pub struct HMAC_SHA512_224Params; impl Algorithm for HMAC_SHA512_224Params { const ALG_NAME: &'static str = HMAC_SHA512_224_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA512_224: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; + // SP 800-107r1 s.5.3.4: min(strength of K, 2C). SHA-512/224 has C = 512, so 2C = 1024, + // and the key is OUTPUT_LEN = 224 bits, so the key binds: 224 bits, rounded down to the nearest category. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-224 { digestAlgorithm 12 } @@ -470,12 +440,9 @@ pub struct HMAC_SHA512_256Params; impl Algorithm for HMAC_SHA512_256Params { const ALG_NAME: &'static str = HMAC_SHA512_256_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA512_256: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + // SP 800-107r1 s.5.3.4: min(strength of K, 2C). SHA-512/256 has C = 512, so 2C = 1024, + // and the key is OUTPUT_LEN = 256 bits, so the key binds: 256 bits. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-256 { digestAlgorithm 13 } diff --git a/crypto/sha2/tests/hmac_tests.rs b/crypto/sha2/tests/hmac_tests.rs index b0971269d..e2a209f07 100644 --- a/crypto/sha2/tests/hmac_tests.rs +++ b/crypto/sha2/tests/hmac_tests.rs @@ -37,7 +37,7 @@ mod hmac_sha2_tests { KeyType::MACKey, ) .unwrap(); - let mut mac = HMAC_SHA224::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new_allow_weak_key(&key).unwrap(); mac.do_update(b"Hi There"); let output = mac.do_final(); assert_eq!(output, b"\x89\x6f\xb1\x12\x8a\xbb\xdf\x19\x68\x32\x10\x7c\xd4\x9d\xf3\x3f\x47\xb4\xb1\x16\x99\x12\xba\x4f\x53\x68\x4b\x22"); @@ -48,7 +48,7 @@ mod hmac_sha2_tests { KeyType::MACKey, ) .unwrap(); - let mac = HMAC_SHA256::new(&key).unwrap(); + let mac = HMAC_SHA256::new_allow_weak_key(&key).unwrap(); // mac.do_update(b"").unwrap(); let output = mac.do_final(); assert_eq!( @@ -77,12 +77,15 @@ mod hmac_sha2_tests { // It works after allowing weak keys HMAC_SHA256::new_allow_weak_key(&short_key).unwrap(); - // It works with a long enough key + // It works with a long enough key. HMAC-SHA256 claims 256 bits (SP 800-107r1 s.5.3.4), so + // "long enough" is a full 32-byte key. let key = KeyMaterial256::from_bytes_as_type( - &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), + &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") + .unwrap(), KeyType::MACKey, ) .unwrap(); + assert_eq!(key.security_strength(), SecurityStrength::_256bit); HMAC_SHA256::new(&key).unwrap(); } @@ -182,23 +185,25 @@ mod hmac_sha2_tests { .unwrap(); // get the known-good output - let out = HMAC_SHA224::new(&key).unwrap().mac(b"Hi There"); + let out = HMAC_SHA224::new_allow_weak_key(&key).unwrap().mac(b"Hi There"); // test output that's the wrong length, should simply return False - let mut mac = HMAC_SHA224::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new_allow_weak_key(&key).unwrap(); mac.do_update(b"Hi There"); assert!(!mac.do_verify_final(&out[..out.len() - 1])); // test output that's the right length but wrong value -- do_verify - let mut mac = HMAC_SHA224::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new_allow_weak_key(&key).unwrap(); mac.do_update(b"Hi There"); assert!(!mac.do_verify_final(&[0x01_u8; 28])); // test output that's the right length but wrong value -- static verify - assert!(!HMAC_SHA224::new(&key).unwrap().verify(b"Hi There", &[0x01_u8; 28])); + assert!( + !HMAC_SHA224::new_allow_weak_key(&key).unwrap().verify(b"Hi There", &[0x01_u8; 28]) + ); // error case: test that it'll refuse to truncate below MIN_FIPS_DIGEST_LEN - let mut mac = HMAC_SHA224::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new_allow_weak_key(&key).unwrap(); mac.do_update(b"Hi There"); let mut out = vec![0u8; MIN_FIPS_DIGEST_LEN - 1]; match mac.do_final_out(&mut out) { @@ -212,7 +217,7 @@ mod hmac_sha2_tests { } // success case: ... but it will truncate to exactly MIN_FIPS_DIGEST_LEN - let mut mac = HMAC_SHA224::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new_allow_weak_key(&key).unwrap(); mac.do_update(b"Hi There"); let mut out = vec![0u8; MIN_FIPS_DIGEST_LEN]; let bytes_written = mac.do_final_out(&mut out).unwrap(); @@ -231,18 +236,21 @@ mod hmac_sha2_tests { assert_eq!(HMAC_SHA512_256::ALG_NAME, HMAC_SHA512_256_NAME); assert_eq!(HMAC_SHA512_224_NAME, "HMAC-SHA512/224"); assert_eq!(HMAC_SHA512_256_NAME, "HMAC-SHA512/256"); - assert_eq!(HMAC_SHA512_224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); - assert_eq!(HMAC_SHA512_256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); assert_eq!(HMAC_SHA224::OID, [1, 2, 840, 113549, 2, 8]); assert_eq!(HMAC_SHA256::OID, [1, 2, 840, 113549, 2, 9]); assert_eq!(HMAC_SHA384::OID, [1, 2, 840, 113549, 2, 10]); assert_eq!(HMAC_SHA512::OID, [1, 2, 840, 113549, 2, 11]); - assert_eq!(HMAC_SHA224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); - assert_eq!(HMAC_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); - assert_eq!(HMAC_SHA384::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); - assert_eq!(HMAC_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + // Per SP 800-107r1 s.5.3.4 these are min(strength of K, 2C), which for every SHA-2 HMAC + // resolves to the key: OUTPUT_LEN bits, rounded down to a representable category. + assert_eq!(HMAC_SHA224::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); // 224 bits + assert_eq!(HMAC_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 256 bits + assert_eq!(HMAC_SHA384::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 384, capped + assert_eq!(HMAC_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 512, capped + assert_eq!(HMAC_SHA512_224::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); // 224 bits + assert_eq!(HMAC_SHA512_256::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 256 bits + assert_eq!(HMAC_SHA512_224::OID, [1, 2, 840, 113549, 2, 12]); assert_eq!(HMAC_SHA512_256::OID, [1, 2, 840, 113549, 2, 13]); } @@ -403,7 +411,10 @@ mod hmac_sha2_tests { ) .unwrap(); let mut out = [0u8; 128 / 8]; - HMAC_SHA224::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); + HMAC_SHA224::new_allow_weak_key(&key) + .unwrap() + .mac_out(b"Test With Truncation", &mut out) + .unwrap(); assert_eq!(&Vec::from(out), &hex::decode("0e2aea68a90c8d37c988bcdb9fca6fa8").unwrap()); // RFC4231 Test Case 6 -- Test with a combined length of key and data that is larger than 64 @@ -484,7 +495,10 @@ mod hmac_sha2_tests { ) .unwrap(); let mut out = [0u8; 128 / 8]; - HMAC_SHA256::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); + HMAC_SHA256::new_allow_weak_key(&key) + .unwrap() + .mac_out(b"Test With Truncation", &mut out) + .unwrap(); assert_eq!(&Vec::from(out), &hex::decode("a3b6167473100ee06e0c796c2955552b").unwrap()); // RFC4231 Test Case 6 -- Test with a combined length of key and data that is larger than 64 @@ -730,7 +744,7 @@ mod hmac_sha2_tests { KeyType::MACKey, ) .unwrap(); - let hmac = HMAC_SHA256::new(&key).unwrap(); + let hmac = HMAC_SHA256::new_allow_weak_key(&key).unwrap(); // test fmt let fmt_str = format!("{}", &hmac); @@ -775,20 +789,26 @@ mod hmac_sha2_tests { /// `keygen_from_rng` must refuse an RNG whose security strength is below the strength the HMAC /// claims, otherwise the returned key would be tagged stronger than the entropy behind it. - /// HashDRBG_SHA256 offers 128 bits, which is enough for HMAC-SHA256 but not for HMAC-SHA512. + /// HashDRBG_SHA256 offers 128 bits. Since SP 800-107r1 s.5.3.4 puts every SHA-2 HMAC at 192 + /// bits or more, it is now too weak for all of them, and a 256-bit generator is required. + /// `DefaultRNG` resolves to HashDRBG_SHA512 and so qualifies out of the box. #[test] fn keygen_rejects_weak_rng() { - let mut weak_rng = HashDRBG_SHA256::new_from_os(); - assert!( - matches!( - HMAC_SHA512::keygen_from_rng(&mut weak_rng), - Err(RNGError::SecurityStrengthInsufficientForAlgorithm) - ), - "a 128-bit RNG must not be accepted for a 256-bit HMAC" - ); + for refused in [ + HMAC_SHA224::keygen_from_rng(&mut HashDRBG_SHA256::new_from_os()).err(), + HMAC_SHA256::keygen_from_rng(&mut HashDRBG_SHA256::new_from_os()).err(), + HMAC_SHA384::keygen_from_rng(&mut HashDRBG_SHA256::new_from_os()).err(), + HMAC_SHA512::keygen_from_rng(&mut HashDRBG_SHA256::new_from_os()).err(), + ] { + assert!( + matches!(refused, Some(RNGError::SecurityStrengthInsufficientForAlgorithm)), + "a 128-bit RNG must not be accepted for an HMAC claiming 192 bits or more" + ); + } - let mut ok_rng = HashDRBG_SHA256::new_from_os(); + // A 256-bit generator backs every SHA-2 HMAC in the crate. + let mut ok_rng = HashDRBG_SHA512::new_from_os(); HMAC_SHA256::keygen_from_rng(&mut ok_rng) - .expect("a 128-bit RNG is sufficient for a 128-bit HMAC"); + .expect("a 256-bit RNG is sufficient for a 256-bit HMAC"); } } diff --git a/crypto/sha3/src/hmac.rs b/crypto/sha3/src/hmac.rs index 62216af67..c52f06ed0 100644 --- a/crypto/sha3/src/hmac.rs +++ b/crypto/sha3/src/hmac.rs @@ -68,7 +68,8 @@ //! use bouncycastle_sha3::hmac::HMAC_SHA3_256; //! //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! 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", //! KeyType::MACKey).unwrap(); //! //! let hmac = HMAC_SHA3_256::new(&key).expect( @@ -131,7 +132,8 @@ //! // For this example to work, we are hard-coding both the key and the MAC value that it generates //! // for this data. //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! 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", //! KeyType::MACKey).unwrap(); //! //! let data: &[u8] = b"Hello, world!"; @@ -139,8 +141,8 @@ //! // .verify() returns a bool: true if the MAC is valid, false otherwise. //! if HMAC_SHA3_256::new(&key).unwrap() //! .verify(data, -//! b"\x9c\x49\x05\x83\xff\xf3\x59\x6a\x59\x01\x4a\x0d\x95\xb4\x64\x00 -//! \x7d\x5b\xb7\x40\xb3\x84\x20\x7a\x3c\x76\x76\xd8\xc9\x93\xda\xd7" +//! b"\x5d\x16\xf1\xc4\xcc\x22\x83\x8a\xd0\x53\xe6\xb6\x9b\xb2\xd1\x5a +//! \x2a\x79\x35\x76\xb0\x80\x7d\xec\x50\x78\xa1\x36\x99\x33\x7d\xfd" //! ) //! { //! println!("MAC is valid!"); @@ -160,12 +162,13 @@ //! // For this example to work, we are hard-coding both the key and the MAC value that it generates //! // for this data. //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! 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", //! KeyType::MACKey).unwrap(); //! let mut hmac = HMAC_SHA3_256::new(&key).unwrap(); //! hmac.do_update(b"Hello,"); //! hmac.do_update(b" world!"); -//! if hmac.do_verify_final(b"\x9c\x49\x05\x83\xff\xf3\x59\x6a\x59\x01\x4a\x0d\x95\xb4\x64\x00\x7d\x5b\xb7\x40\xb3\x84\x20\x7a\x3c\x76\x76\xd8\xc9\x93\xda\xd7" +//! if hmac.do_verify_final(b"\x5d\x16\xf1\xc4\xcc\x22\x83\x8a\xd0\x53\xe6\xb6\x9b\xb2\xd1\x5a\x2a\x79\x35\x76\xb0\x80\x7d\xec\x50\x78\xa1\x36\x99\x33\x7d\xfd" //! ) //! { //! println!("MAC is valid!"); @@ -199,7 +202,8 @@ //! 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", +//! 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", //! KeyType::MACKey).unwrap(); //! //! let mut hmac = HMAC_SHA3_256::new(&key).unwrap(); @@ -240,9 +244,27 @@ //! //! # 🚨 Security Considerations 🚨 //! -//! * The key must carry at least the security strength claimed by the HMAC, and [`MAC::new`] -//! enforces that. [`MAC::new_allow_weak_key`] deliberately skips the check; use it only where a -//! weak or all-zero key is called for by the protocol, not to silence an error. +//! * Each of these HMACs claims the strength NIST SP 800-107r1 Section 5.3.4 gives it, which is +//! `min(strength of K, 2C)` and works out to the key length for the whole family: 224 bits for +//! HMAC-SHA3-224 and 256 or more for the rest. `SecurityStrength` has no 224-bit category and +//! tops out at 256, so the declared values are `_192bit` for HMAC-SHA3-224 and `_256bit` for +//! HMAC-SHA3-256, HMAC-SHA3-384 and HMAC-SHA3-512. Note these are *not* the underlying hashes' +//! collision strengths, which are half as large; footnote 4 of that section puts collision +//! attacks out of scope for HMAC. +//! * That figure is an **extrapolation**. SP 800-107r1 is older than SHA-3 and does not cover it: +//! `C` there is the FIPS 180-4 chaining value, which a sponge does not have. See the note above +//! the params types for the analogue used and why the choice does not change the answer. +//! * The key must carry at least the strength claimed by the HMAC, and [`MAC::new`] enforces that. +//! A 20-byte key is therefore not enough for HMAC-SHA3-256; a full 32-byte key is. +//! [`MAC::new_allow_weak_key`] deliberately skips the check; use it only where a weak or all-zero +//! key is called for by the protocol, or by a fixed test vector, not to silence an error. +//! * The same rule applies to the generator. [`HMAC::keygen_from_rng`] tags the key it returns at +//! the HMAC's claimed strength, so it refuses any RNG that cannot back that tag: a 256-bit +//! generator is required for HMAC-SHA3-256 and above, and `bouncycastle_rng::DefaultRNG` is +//! `HashDRBG_SHA512` and qualifies for all of them. `HashDRBG_SHA256` offers 128 bits and is +//! refused by every HMAC in this module. There is no weak-RNG opt-out, because a generator cannot +//! be asked for entropy it does not have; build the key yourself and use +//! [`MAC::new_allow_weak_key`] if that is genuinely what you want. //! * Verify with [`MAC::verify`] or [`MAC::do_verify_final`] rather than computing the MAC yourself //! and comparing: those use a constant-time comparison, while `==` on the byte slices leaks how //! many leading bytes matched. @@ -284,11 +306,6 @@ pub const HMAC_SHA3_384_NAME: &str = "HMAC-SHA3-384"; pub const HMAC_SHA3_512_NAME: &str = "HMAC-SHA3-512"; /*** Params types and type aliases ***/ -// TODO: the MAX_SECURITY_STRENGTH values below are each hash's collision strength, which NIST -// SP 800-107r1 Section 5.3.4 says is not the right basis for an HMAC. See the TODO above the -// equivalent params types in bouncycastle-sha2 for the analysis. Note that its recommendation -// covers SHA-2 only: SP 800-107r1 predates SHA-3 and reasons about a Merkle-Damgard chaining -// value, which a sponge does not have, so these need FIPS 202 / SP 800-185 read first. /// The parameters for HMAC-SHA3_224 -- see [`HMAC_SHA3_224`]. #[derive(Clone)] @@ -297,12 +314,10 @@ pub struct HMAC_SHA3_224Params; impl Algorithm for HMAC_SHA3_224Params { const ALG_NAME: &'static str = HMAC_SHA3_224_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA3_224: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; + // SP 800-107r1 s.5.3.4, extrapolated from SHA2 to SHA3: min(strength of K, 2C). + // SHA3-224 has capacity c = 448, so 2c = 896, and the key is OUTPUT_LEN = 224 bits, + // so the key binds: 224 bits, rounded down to the nearest category. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-224 { hashAlgs 13 } @@ -328,12 +343,10 @@ pub struct HMAC_SHA3_256Params; impl Algorithm for HMAC_SHA3_256Params { const ALG_NAME: &'static str = HMAC_SHA3_256_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA3_256: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + // SP 800-107r1 s.5.3.4, extrapolated from SHA2 to SHA3: min(strength of K, 2C). + // SHA3-256 has capacity c = 512, so 2c = 1024, and the key is OUTPUT_LEN = 256 bits, + // so the key binds: 256 bits. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-256 { hashAlgs 14 } @@ -359,12 +372,10 @@ pub struct HMAC_SHA3_384Params; impl Algorithm for HMAC_SHA3_384Params { const ALG_NAME: &'static str = HMAC_SHA3_384_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA3_384: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; + // SP 800-107r1 s.5.3.4, extrapolated from SHA2 to SHA3: min(strength of K, 2C). + // SHA3-384 has capacity c = 768, so 2c = 1536, and the key is OUTPUT_LEN = 384 bits, + // so the key binds: 384 bits, capped at the top of `SecurityStrength`. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-384 { hashAlgs 15 } @@ -390,11 +401,9 @@ pub struct HMAC_SHA3_512Params; impl Algorithm for HMAC_SHA3_512Params { const ALG_NAME: &'static str = HMAC_SHA3_512_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA3_512: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + // SP 800-107r1 s.5.3.4, extrapolated from SHA2 to SHA3: min(strength of K, 2C). + // SHA3-512 has capacity c = 1024, so 2c = 2048, and the key is OUTPUT_LEN = 512 bits, + // so the key binds: 512 bits, capped at the top of `SecurityStrength`. const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } diff --git a/crypto/sha3/tests/hmac_tests.rs b/crypto/sha3/tests/hmac_tests.rs index b5f43153f..85af1f5e0 100644 --- a/crypto/sha3/tests/hmac_tests.rs +++ b/crypto/sha3/tests/hmac_tests.rs @@ -29,18 +29,19 @@ mod hmac_sha3_tests { ) .unwrap(); - // fail case: mac value is correct but truncated - let mac = HMAC_SHA3_224::new(&key).unwrap(); + // fail case: mac value is correct but truncated. The 20-byte key is below the strength + // HMAC-SHA3-224 claims, so these opt in to a weak key. + let mac = HMAC_SHA3_224::new_allow_weak_key(&key).unwrap(); let mut mac_val = mac.mac(b"Polly want a cracker?"); - let verifier = HMAC_SHA3_224::new(&key).unwrap(); + let verifier = HMAC_SHA3_224::new_allow_weak_key(&key).unwrap(); assert!(verifier.verify(b"Polly want a cracker?", &mac_val)); // truncation of the mac value is considered a fail - let verifier = HMAC_SHA3_224::new(&key).unwrap(); + let verifier = HMAC_SHA3_224::new_allow_weak_key(&key).unwrap(); assert!(!verifier.verify(b"Polly want a cracker?", &mac_val[..mac_val.len() - 1])); // .. as is some extra bytes at the end - let verifier = HMAC_SHA3_224::new(&key).unwrap(); + let verifier = HMAC_SHA3_224::new_allow_weak_key(&key).unwrap(); mac_val.extend_from_slice(&[0u8; 4]); assert!(!verifier.verify(b"Polly want a cracker?", &mac_val)); } @@ -58,10 +59,12 @@ mod hmac_sha3_tests { assert_eq!(HMAC_SHA3_384::OID, [2, 16, 840, 1, 101, 3, 4, 2, 15]); assert_eq!(HMAC_SHA3_512::OID, [2, 16, 840, 1, 101, 3, 4, 2, 16]); - assert_eq!(HMAC_SHA3_224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); - assert_eq!(HMAC_SHA3_256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); - assert_eq!(HMAC_SHA3_384::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); - assert_eq!(HMAC_SHA3_512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + // Per SP 800-107r1 s.5.3.4, extrapolated to SHA-3: min(strength of K, 2C) resolves to the + // key in every case, so these are OUTPUT_LEN rounded down to a representable category. + assert_eq!(HMAC_SHA3_224::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); // 224 bits + assert_eq!(HMAC_SHA3_256::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 256 bits + assert_eq!(HMAC_SHA3_384::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 384, capped + assert_eq!(HMAC_SHA3_512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 512, capped } #[test] diff --git a/crypto/sm3/src/hmac.rs b/crypto/sm3/src/hmac.rs index 2abca2e70..bccb5558c 100644 --- a/crypto/sm3/src/hmac.rs +++ b/crypto/sm3/src/hmac.rs @@ -68,12 +68,10 @@ pub struct HMAC_SM3Params; impl Algorithm for HMAC_SM3Params { const ALG_NAME: &'static str = HMAC_SM3_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SM3: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + // SP 800-107r1 s.5.3.4, extrapolated from SHA-2 to SM3, which is likewise Merkle-Damgard: + // min(strength of K, 2C). SM3 has C = 256, so 2C = 512, and the key is OUTPUT_LEN = 256 bits, + // so the key binds: 256 bits. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } /// Assigned by the Chinese OSCCA (GM/T 0006): hmac-sm3 { sm3 2 } = 1.2.156.10197.1.401.2 From 18adb6334db13c7eb992a51bc5c62ffca703fb74 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sat, 12 Sep 2026 16:06:02 -0500 Subject: [PATCH 6/7] Added a release note --- alpha_0.1.3_release_notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index e848ccc27..7392d2a69 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -36,3 +36,7 @@ extension traits, so the call needs the `hazmat` import. * New cipher-related features `ElectronicCodeBook`, `KeyStream`, the `AES*Internal` types, `Ecb` and `AES_ECB_*`, and `CtrKeyStream` under their crates' `hazmat` modules. +* Re-arranged the HMAC and HKDF crates so that they are utility crates, and the pub types HMAC_SHA256, HMAC_SHA3_256, + HKDF_SHA256, and so on now live in the `bouncycastle_sha2::hmac`, `bouncycastle_sha3::hmac`, and + `bouncycastle_sha2::hkdf` namespaces. At the same time, adjustments were made to the MAX_SECURITY_STRENGTH of the HMAC + and HKDF algorithms because SP 800-107r1 allows them to be higher than what we had previously. From 9aefa6ee7bedb0f798702da5a6073cd5f166203e Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sun, 25 Oct 2026 08:19:27 -0500 Subject: [PATCH 7/7] Further consolidated HMACParams by absorbing HASH into the HMACParams. Assisted-by: Claude Opus 5.5 --- INTRODUCTION.md | 19 +++--- alpha_0.1.3_release_notes.md | 2 + crypto/hkdf/src/lib.rs | 88 ++++++++++++---------------- crypto/hkdf/tests/hkdf_tests.rs | 19 +++--- crypto/hkdf/tests/hkdf_wycheproof.rs | 22 +++---- crypto/hmac/src/lib.rs | 70 +++++++++++----------- crypto/sha2/src/hkdf.rs | 4 -- crypto/sha2/src/hmac.rs | 24 +++++--- crypto/sha3/src/hmac.rs | 18 +++--- crypto/sm3/src/hmac.rs | 3 +- 10 files changed, 126 insertions(+), 143 deletions(-) diff --git a/INTRODUCTION.md b/INTRODUCTION.md index 270c3720a..e671965c0 100644 --- a/INTRODUCTION.md +++ b/INTRODUCTION.md @@ -33,27 +33,28 @@ exposed parameters. If your application requires you to use the fully-parametriz these public APIs of BC-Rust's HKDF implementation: ```rust -impl HKDF { - do_extract_init(salt: & impl KeyMaterial) -> Result; +impl HKDF { + do_extract_init(salt: & impl KeyMaterialTrait) -> Result; - do_extract_update_key(ikm: & impl KeyMaterial) -> Result; + do_extract_update_key(ikm: & impl KeyMaterialTrait) -> Result; do_extract_update_bytes(ikm_chunk: & [u8]) -> Result; - do_extract_final() -> Result; + do_extract_final() -> Result; expand_out( - prk: & impl KeyMaterial, + prk: & impl KeyMaterialTrait, info: & [u8], L: usize, - okm: & mut impl KeyMaterial, + okm: & mut impl KeyMaterialTrait, ) -> Result } ``` -That is, including the choice of hash function `H`, 7 adjustable input parameters spread across 5 function calls, which -is enough to make a novice cryptographer's head spin! Plenty of rope to hang yourself with. To this end, we also offer a -much simplified KDF trait and KDFFactory that lets you do the whole operation in two very straightforward lines of code: +That is, including the choice of hash function (named by the HMAC parameters `PARAMS`), 7 adjustable input parameters +spread across 5 function calls, which is enough to make a novice cryptographer's head spin! Plenty of rope to hang +yourself with. To this end, we also offer a much simplified KDF trait and KDFFactory that lets you do the whole +operation in two very straightforward lines of code: ```rust let mut kdf = KDFFactory::new("HKDF-SHA256") ?; diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index 7392d2a69..8b4276a22 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -40,3 +40,5 @@ HKDF_SHA256, and so on now live in the `bouncycastle_sha2::hmac`, `bouncycastle_sha3::hmac`, and `bouncycastle_sha2::hkdf` namespaces. At the same time, adjustments were made to the MAX_SECURITY_STRENGTH of the HMAC and HKDF algorithms because SP 800-107r1 allows them to be higher than what we had previously. +* `HMAC` and `HKDF` take their hash from the new `HMACParams::Hash` associated type instead of a separate type + parameter, so `HMAC` is now `HMAC

` and `HKDF` is now `HKDF`. diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index a7ebcb06e..fced6320b 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -2,8 +2,8 @@ //! RFC 5869, as allowed by NIST SP 800-56Cr2. //! //! This is a utility crate and is not intended to be used directly. It provides [`HKDF`] -- the -//! construction, generic over any struct that implements [`Hash`] and [`HashAlgParams`], the extension -//! point through which a hash declares the metadata from which the HKDF instance is built. +//! construction, generic over the [`HMACParams`] of the HMAC it is built on, which name the +//! underlying [`Hash`]. //! The library provides the following concrete instantiations of HKDF: //! //! | Hash family | Instantiations | @@ -13,15 +13,15 @@ //! # Instantiating HKDF over a hash //! //! HKDF uses HMAC as its underlying primitive, so it works with any hash that HMAC works with: -//! [`HKDF`] needs only [`Hash`] + [`HashAlgParams`] + [`Default`]. Unlike HMAC there is no additional -//! HKDF params trait to implement -- HKDF has no per-hash OIDs and derives its name from nothing -- +//! [`HKDF`] takes the HMAC's [`HMACParams`], and through them the hash. Unlike HMAC there is no +//! additional HKDF params trait to implement -- HKDF has no per-hash OIDs and derives its name from nothing -- //! so an instantiation is just a type alias plus, by convention, a name constant and a suspended-state //! length. //! //! What the alias has to supply is [`HKDF`]'s two const parameters: //! //! * `HASH_STATE_LEN` -- the hash's own suspended-state length, i.e. the `N` in -//! `H: Suspendable`. +//! `PARAMS::Hash: Suspendable`. //! * `HKDF_STATE_LEN` -- this HKDF's suspended-state length, which is always `HASH_STATE_LEN + 14`. //! //! Both are const parameters of the struct rather than being derived from the hash because @@ -48,14 +48,14 @@ //! use bouncycastle_core::traits::{KDF, SuspendableKeyed}; //! use bouncycastle_hkdf::HKDF; //! use bouncycastle_sha2::hmac::HMAC_SHA224Params; -//! use bouncycastle_sha2::{SHA224, SUSPENDED_SHA256_STATE_LEN}; +//! use bouncycastle_sha2::SUSPENDED_SHA256_STATE_LEN; //! //! // SHA-224 is a member of the SHA-256 family, so its suspended state is the SHA-256 one. //! const SUSPENDED_HKDF_SHA224_STATE_LEN: usize = SUSPENDED_SHA256_STATE_LEN + 14; //! //! #[allow(non_camel_case_types)] //! pub type HKDF_SHA224 = -//! HKDF; +//! HKDF; //! //! pub const HKDF_SHA224_NAME: &str = "HKDF-SHA224"; //! @@ -132,7 +132,7 @@ use bouncycastle_core::traits::XOF; /// 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. +/// and declare `prk: &mut KeyMaterial` instead of this hack. pub const MAX_HMAC_OUTPUT_LEN: usize = 64; /*** Types ***/ @@ -145,30 +145,26 @@ pub const MAX_HMAC_OUTPUT_LEN: usize = 64; /// /// # Const parameters /// -/// `HASH_STATE_LEN` is the suspended-state length of `H` (as in `H: Suspendable`) and +/// `HASH_STATE_LEN` is the suspended-state length of the hash (as in +/// `PARAMS::Hash: Suspendable`) and /// `HKDF_STATE_LEN` is this HKDF's own suspended-state length, which is always `HASH_STATE_LEN + 14` /// (see the [`SuspendableKeyed`] impl below for the layout that accounts for those 14 bytes). /// -/// Both are const parameters of the struct rather than being derived from `H` because `SuspendableKeyed` +/// Both are const parameters of the struct rather than being derived from the hash because `SuspendableKeyed` /// takes its length as a const generic, and naming `HASH_STATE_LEN + 14` in that position requires /// the `generic_const_exprs` feature. Carrying both on the struct is what lets a single blanket /// `SuspendableKeyed` impl serve every hash while still pinning the two lengths together per concrete /// type. They are deliberately given no defaults: there is no value that is correct for more than one /// hash, so each instantiation must state them. /// todo: once rust stabilizes generic_const_exprs, delete both const parameters and write the impl as -/// `SuspendableKeyed<{HASH_STATE_LEN + 14}>` over `H: Suspendable`. +/// `SuspendableKeyed<{HASH_STATE_LEN + 14}>` over `PARAMS::Hash: Suspendable`. #[derive(Clone)] -pub struct HKDF< - H: Hash + HashAlgParams + Default, - PARAMS: HMACParams, - const HASH_STATE_LEN: usize, - const HKDF_STATE_LEN: usize, -> { +pub struct HKDF { // Optional because an HMAC cannot be constructed until a key is provided // to initialize it with. // None must correspond to a state of Uninitialized. - hmac: Option>, - entropy: HkdfEntropyTracker, + hmac: Option>, + entropy: HkdfEntropyTracker, state: HkdfStates, } @@ -245,24 +241,16 @@ impl HkdfEntropyTracker { } } -impl< - H: Hash + HashAlgParams + Default, - PARAMS: HMACParams, - const HASH_STATE_LEN: usize, - const HKDF_STATE_LEN: usize, -> Default for HKDF +impl Default + for HKDF { fn default() -> Self { Self::new() } } -impl< - H: Hash + HashAlgParams + Default, - PARAMS: HMACParams, - const HASH_STATE_LEN: usize, - const HKDF_STATE_LEN: usize, -> HKDF +impl + HKDF { /// Get a new, uninstantiated HKDF object. pub fn new() -> Self { @@ -372,7 +360,7 @@ impl< // (where the constant concatenated to the end of each T(n) is a // single octet.) - let hash_len = H::OUTPUT_LEN; + let hash_len = PARAMS::Hash::OUTPUT_LEN; if L > 255 * hash_len { return Err(KDFError::InvalidLength( "HMAC can not produce more than 255*HashLen bytes out output", @@ -385,7 +373,7 @@ impl< )); } - let mut entropy = HkdfEntropyTracker::::new(); + let mut entropy = HkdfEntropyTracker::::new(); entropy.credit_entropy(prk); #[allow(non_snake_case)] @@ -408,7 +396,7 @@ impl< do_hazardous_operations(okm, |okm| { let out = okm.ref_to_bytes_mut()?; while i < N { - let mut hmac = HMAC::::new(&prk_as_mac_key) + let mut hmac = HMAC::::new(&prk_as_mac_key) .map_err(|_| KeyMaterialError::GenericError("HMAC initialization failed"))?; hmac.do_update(&T[..t_len]); hmac.do_update(info); @@ -430,7 +418,7 @@ impl< // HMAC for a `remaining`-octet output instead would be a truncated MAC, which HMAC refuses // below MIN_FIPS_DIGEST_LEN: that rejected every L with L mod HashLen in 1..=3. let remaining = L - bytes_written; - let mut hmac = HMAC::::new(&prk_as_mac_key)?; + let mut hmac = HMAC::::new(&prk_as_mac_key)?; hmac.do_update(&T[..t_len]); hmac.do_update(info); hmac.do_update(&[i]); @@ -500,7 +488,7 @@ impl< // Often HMAC is initialized with a zero salt, // Key strength errors are ignored here. // This will all be tabulated correctly via entropy.credit_entropy() - self.hmac = Some(HMAC::::new_allow_weak_key(salt)?); + self.hmac = Some(HMAC::::new_allow_weak_key(salt)?); let additional_entropy = self.entropy.credit_entropy(salt); self.state = HkdfStates::Initialized; @@ -533,7 +521,7 @@ impl< debug_assert!(self.hmac.is_some()); let additional_entropy = self.entropy.credit_entropy(ikm); - let hmac_ref: &mut HMAC = self.hmac.as_mut().unwrap(); + let hmac_ref: &mut HMAC = self.hmac.as_mut().unwrap(); hmac_ref.do_update(ikm.ref_to_bytes()); // self.hmac.as_mut().unwrap().do_update(ikm.ref_to_bytes()); @@ -608,7 +596,7 @@ impl< })?; // 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); + debug_assert_eq!(prk.key_len(), PARAMS::Hash::OUTPUT_LEN); Ok(bytes_written) } } @@ -622,12 +610,8 @@ impl< /// [`KDF::derive_key_from_multiple_out`], or by using the [`HKDF`] impl directly. /// /// Entropy tracking: this implementation will map entropy from the input keys to the output key. -impl< - H: Hash + HashAlgParams + Default, - PARAMS: HMACParams, - const HASH_STATE_LEN: usize, - const HKDF_STATE_LEN: usize, -> KDF for HKDF +impl KDF + for HKDF { /// This invokes [`HKDF::extract_and_expand_out`] with a zero salt and using the provided key as ikm. /// This provides a fixed-length output, which may be truncated as needed. @@ -638,7 +622,7 @@ impl< ) -> Result, KDFError> { let mut output_key = KeyMaterial512::new(); _ = self.derive_key_out(key, additional_input, &mut output_key)?; - output_key.set_key_len(H::OUTPUT_LEN)?; + output_key.set_key_len(PARAMS::Hash::OUTPUT_LEN)?; Ok(Box::new(output_key)) } @@ -676,7 +660,7 @@ impl< ) -> Result, KDFError> { let mut output_key = KeyMaterial512::new(); _ = self.derive_key_from_multiple_out(keys, additional_input, &mut output_key)?; - output_key.set_key_len(*min(&output_key.key_len(), &H::OUTPUT_LEN))?; + output_key.set_key_len(*min(&output_key.key_len(), &PARAMS::Hash::OUTPUT_LEN))?; Ok(Box::new(output_key)) } @@ -689,7 +673,7 @@ impl< output_key: &mut impl KeyMaterialTrait, ) -> Result { let mut hkdf = Self::new(); - let mut entropy = HkdfEntropyTracker::::new(); + let mut entropy = HkdfEntropyTracker::::new(); if keys.len() >= 1 { hkdf.do_extract_init(keys[0])?; @@ -724,7 +708,7 @@ impl< } fn max_security_strength(&self) -> SecurityStrength { - H::default().max_security_strength() + PARAMS::Hash::default().max_security_strength() } } @@ -753,10 +737,10 @@ impl< /// 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`, /// which is the relationship `HKDF_STATE_LEN == HASH_STATE_LEN + 14` asserted below. -impl - SuspendableKeyed for HKDF +impl + SuspendableKeyed for HKDF where - H: Hash + HashAlgParams + Default + Suspendable, + PARAMS::Hash: Suspendable, { // 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 @@ -802,7 +786,7 @@ where let hmac = match state[3] { 0 => None, // infallible: the sub-slice is exactly HASH_STATE_LEN bytes by const construction. - 1 => Some(HMAC::::from_suspended( + 1 => Some(HMAC::::from_suspended( state[4..4 + HASH_STATE_LEN].try_into().unwrap(), salt, )?), diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index d7a58b33a..c72330ab6 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -13,9 +13,7 @@ mod hkdf_tests { use bouncycastle_hex as hex; use bouncycastle_hkdf::HKDF; use bouncycastle_sha2::hkdf::{HKDF_SHA256, HKDF_SHA512}; - use bouncycastle_sha2::{ - SHA256, SHA512, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN, - }; + use bouncycastle_sha2::{SHA256, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN}; use bouncycastle_utils::ct; #[test] @@ -729,7 +727,7 @@ mod hkdf_tests { } #[test] fn serializable_keyed_state() { - use bouncycastle_core::traits::{Hash, SuspendableKeyed}; + use bouncycastle_core::traits::SuspendableKeyed; use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; use bouncycastle_sha2::hkdf::{ SUSPENDED_HKDF_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA512_STATE_LEN, @@ -743,22 +741,21 @@ mod hkdf_tests { // 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( + fn round_trip( salt: &KeyMaterial128, part1: &[u8], part2: &[u8], ) where - H: Hash + HashAlgParams + Default, P: bouncycastle_hmac::HMACParams, - HKDF: Clone + SuspendableKeyed, + HKDF: Clone + SuspendableKeyed, { - let hkdf = HKDF::::new(); + 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::::from_suspended(serialized_state, salt).unwrap(); hkdf.do_extract_init(salt).unwrap(); hkdf.do_extract_update_bytes(part1).unwrap(); @@ -775,7 +772,7 @@ mod hkdf_tests { // resume (re-supplying the salt), feed the identical remaining IKM, and compare PRKs let mut resumed = - HKDF::::from_suspended(serialized_state, salt).unwrap(); + HKDF::::from_suspended(serialized_state, salt).unwrap(); resumed.do_extract_update_bytes(part2).unwrap(); let prk_resumed = resumed.do_extract_final().unwrap(); @@ -785,13 +782,11 @@ mod hkdf_tests { round_trip::< SUSPENDED_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA256_STATE_LEN, - SHA256, bouncycastle_sha2::hmac::HMAC_SHA256Params, >(&salt, part1, part2); round_trip::< SUSPENDED_SHA512_STATE_LEN, SUSPENDED_HKDF_SHA512_STATE_LEN, - SHA512, bouncycastle_sha2::hmac::HMAC_SHA512Params, >(&salt, part1, part2); diff --git a/crypto/hkdf/tests/hkdf_wycheproof.rs b/crypto/hkdf/tests/hkdf_wycheproof.rs index a4f25f9ee..e423283ed 100644 --- a/crypto/hkdf/tests/hkdf_wycheproof.rs +++ b/crypto/hkdf/tests/hkdf_wycheproof.rs @@ -12,7 +12,6 @@ use bouncycastle_core::errors::KDFError; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::{Hash, HashAlgParams}; use bouncycastle_core_test_framework::test_data_loaders::{Value, hex_field, wycheproof_json}; use bouncycastle_hkdf::HKDF; use bouncycastle_hmac::HMACParams; @@ -21,23 +20,16 @@ use bouncycastle_sha2::hkdf::{ SUSPENDED_HKDF_SHA512_STATE_LEN, }; use bouncycastle_sha2::hmac::{HMAC_SHA256Params, HMAC_SHA384Params, HMAC_SHA512Params}; -use bouncycastle_sha2::{ - SHA256, SHA384, SHA512, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN, -}; +use bouncycastle_sha2::{SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN}; /// The longest IKM or salt in any of the files is 80 bytes. const MAX_INPUT_LEN: usize = 80; /// 255 * 64, the most HKDF-SHA-512 can produce. const MAX_OKM_LEN: usize = 255 * 64; -/// Runs every case in one `hkdf_*_test.json` file through `HKDF`. The state lengths are +/// Runs every case in one `hkdf_*_test.json` file through `HKDF`. The state lengths are /// those of `bouncycastle_sha2::hkdf`'s aliases, which cannot be passed as a type here. -fn run< - H: Hash + HashAlgParams + Default, - PARAMS: HMACParams, - const HASH_STATE_LEN: usize, - const HKDF_STATE_LEN: usize, ->( +fn run( filename: &str, algorithm: &str, ) { @@ -65,7 +57,7 @@ fn run< let size = test.get("size").and_then(Value::as_u64).expect("size") as usize; let mut okm = KeyMaterial::::new(); - let result = HKDF::::extract_and_expand_out( + let result = HKDF::::extract_and_expand_out( &salt, &ikm, &info, size, &mut okm, ); @@ -98,21 +90,21 @@ fn run< #[test] fn wycheproof_hkdf_sha256() { - run::( + run::( "hkdf_sha256_test.json", "HKDF-SHA-256", ); } #[test] fn wycheproof_hkdf_sha384() { - run::( + run::( "hkdf_sha384_test.json", "HKDF-SHA-384", ); } #[test] fn wycheproof_hkdf_sha512() { - run::( + run::( "hkdf_sha512_test.json", "HKDF-SHA-512", ); } diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 25b4bbca2..c61e12a4a 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -2,8 +2,8 @@ //! taking into account NIST Implementation Guidance in FIPS 140-2 IG A.8 and NIST SP 800-107-r1. //! //! This is a utility crate and is not intended to be used directly. It provides [`HMAC`] -- the -//! construction, generic over any struct that implements [`Hash`], paired with an [`HMACParams`] -//! marker type that carries the metadata the resulting instantiation reports about itself. +//! construction, generic over an [`HMACParams`] marker type that names the underlying [`Hash`] and +//! carries the metadata the resulting instantiation reports about itself. //! The library provides the following concrete instantiations of HMAC: //! //! | Hash family | Instantiations | @@ -17,7 +17,7 @@ //! //! # Instantiating HMAC over a Hash //! -//! HMAC works with any hash: [`HMAC`](HMAC) needs only [`Hash`]. What HMAC cannot +//! HMAC works with any hash: the construction itself needs only [`Hash`]. What HMAC cannot //! derive on its own is the *metadata* of the resulting construction -- the name "HMAC-SHA256" is not //! mechanically obtainable from "SHA256", and RFC 4231 assigns each hash/HMAC combination its own OID //! rather than deriving it from the hash's OID. Supplying that metadata is what makes an HMAC a @@ -30,12 +30,12 @@ //! 1. Have a hash type that implements [`Hash`] + [`HashAlgParams`] + [`Default`]. Implementing //! [`Hash`] is documented in `bouncycastle-core`; nothing about it is HMAC-specific. //! 2. Declare a params type for the instantiation and give it [`Algorithm`] (the name and claimed -//! strength), [`AlgorithmOID`] (the OID and its DER encoding) and [`HMACParams`] (the key type -//! that [`HMAC::keygen_from_rng`] should return, and the internal key buffer). This crate then +//! strength), [`AlgorithmOID`] (the OID and its DER encoding) and [`HMACParams`] (the hash, the +//! key type that [`HMAC::keygen_from_rng`] should return, and the internal key buffer). This crate then //! provides the blanket [`Algorithm`], [`AlgorithmOID`] and [`HMAC::keygen_from_rng`] impls for //! any [`HMAC`]. Take the buffer from [`HashAlgParams::BLOCK_LEN`] rather than writing a literal, //! so it cannot drift away from the hash it has to hold a block of. -//! 3. Optionally, publish a type alias pairing the hash with the params. +//! 3. Optionally, publish a type alias for `HMAC`. //! 4. Optionally, publish the suspended-state length as a convenience constant. [`SuspendableKeyed`] is //! implemented automatically for any hash that implements [`Suspendable`], and HMAC's suspended //! state is exactly the inner hash's -- the key is deliberately excluded -- so the constant is @@ -63,11 +63,12 @@ //! } //! //! impl HMACParams for HMAC_SHA256Params { +//! type Hash = SHA256; //! type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; //! type KeyBuf = [u8; ::BLOCK_LEN]; //! } //! -//! pub type HMAC_SHA256 = HMAC; +//! pub type HMAC_SHA256 = HMAC; //! //! pub const SUSPENDED_HMAC_SHA256_STATE_LEN: usize = SUSPENDED_SHA256_STATE_LEN; //! ``` @@ -119,24 +120,24 @@ use bouncycastle_core::errors::{KeyMaterialError, MACError, RNGError, Suspendabl use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ - Algorithm, AlgorithmOID, Hash, MAC, RNG, Suspendable, SuspendableKeyed, + Algorithm, AlgorithmOID, Hash, HashAlgParams, MAC, RNG, Suspendable, SuspendableKeyed, }; use bouncycastle_utils::secret::ZeroizablePrimitive; use bouncycastle_utils::{ct, secret::Secret}; use core::fmt::{Debug, Display, Formatter}; use core::marker::PhantomData; -/*** Imports needed for docs ***/ -#[allow(unused_imports)] -use bouncycastle_core::traits::HashAlgParams; - /*** Parameters ***/ /// The metadata of one concrete HMAC instantiation, supplied as a marker type. pub trait HMACParams: Algorithm + AlgorithmOID { + /// The hash function this HMAC is instantiated over. + type Hash: Hash + HashAlgParams + Default; + /// The type of key that this HMAC instance needs, sized to the underlying hash's output length. /// - /// Implementors should set this to `KeyMaterial<{ ::OUTPUT_LEN }>`. + /// Implementors should set this to `KeyMaterial<{ ::OUTPUT_LEN }>`, where `H` + /// is the hash named by [`HMACParams::Hash`]. /// // todo: once rust stabilizes generic_const_exprs, delete this and return // `KeyMaterial<{Self::OUTPUT_LEN}>` from `keygen_from_rng` instead. @@ -148,7 +149,8 @@ pub trait HMACParams: Algorithm + AlgorithmOID { /// a key no longer than the block is used verbatim, and only longer keys are pre-hashed down to /// the output length, so the buffer has to fit a whole block. /// - /// Implementors should set this to `[u8; { ::BLOCK_LEN }]`. + /// Implementors should set this to `[u8; { ::BLOCK_LEN }]`, where `H` is the + /// hash named by [`HMACParams::Hash`]. type KeyBuf: ZeroizablePrimitive + AsRef<[u8]> + AsMut<[u8]>; } @@ -156,22 +158,22 @@ pub trait HMACParams: Algorithm + AlgorithmOID { /// 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 { +pub struct HMAC { _phantom_params: PhantomData, - hasher: HASH, + hasher: PARAMS::Hash, // Sized by the params ([`HMACParams::KeyBuf`]), which take it from the hash's block length, so // the caller cannot pick a buffer that does not fit the hash. key: Secret, key_len: Secret, // Doing it this way to avoid needing a vec, so that this can be made no_std friendly. } -impl Debug for HMAC { +impl Debug for HMAC { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { write!(f, "{} instance", PARAMS::ALG_NAME,) } } -impl Display for HMAC { +impl Display for HMAC { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { write!(f, "{} instance", PARAMS::ALG_NAME,) } @@ -193,7 +195,7 @@ const OPAD_BYTE: u8 = 0x5C; /// = 32 bits / 8 = 4 bytes; pub const MIN_FIPS_DIGEST_LEN: usize = 4; -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 @@ -218,7 +220,8 @@ impl HMAC { 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.as_mut()[..self.hasher.output_len()]); + PARAMS::Hash::default() + .hash_out(key_bytes, &mut self.key.as_mut()[..self.hasher.output_len()]); *self.key_len = self.hasher.output_len(); } else { self.key.as_mut()[..key_bytes.len()].copy_from_slice(key_bytes); @@ -286,7 +289,7 @@ impl HMAC { core::mem::take(&mut self.hasher).do_final_out(&mut ihash); // ohash - self.hasher = HASH::default(); + self.hasher = PARAMS::Hash::default(); self.pad_key_into_hasher(OPAD_BYTE); self.hasher.do_update(&ihash); Ok(core::mem::take(&mut self.hasher).do_final_out(out)) @@ -298,11 +301,11 @@ 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 { _phantom_params: PhantomData, - hasher: HASH::default(), + hasher: PARAMS::Hash::default(), key: Secret::new(), key_len: Secret::new(), }; @@ -313,7 +316,7 @@ impl MAC for HMAC { fn new_allow_weak_key(key: &impl KeyMaterialTrait) -> Result { let mut hmac = Self { _phantom_params: PhantomData, - hasher: HASH::default(), + hasher: PARAMS::Hash::default(), key: Secret::new(), key_len: Secret::new(), }; @@ -360,7 +363,7 @@ impl MAC for HMAC { } fn do_verify_final(self, mac: &[u8]) -> bool { - let mut out = vec![0_u8; HASH::default().output_len()]; + let mut out = vec![0_u8; PARAMS::Hash::default().output_len()]; let output_len = self.do_final_internal_out(&mut out).expect("HMAC::do_final(): should not have failed because we gave it a sufficiently large output buffer to meet FIPS rules."); if mac.len() != output_len { return false; @@ -390,11 +393,10 @@ 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< - const HASH_STATE_LEN: usize, - HASH: Hash + Default + Suspendable, - PARAMS: HMACParams, -> SuspendableKeyed for HMAC +impl SuspendableKeyed + for HMAC +where + PARAMS::Hash: Suspendable, { // 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 @@ -414,7 +416,7 @@ impl< key: &Self::Key, ) -> Result { // Rebuild the inner hasher (version-compatibility is validated by the hash's impl). - let hasher = HASH::from_suspended(state)?; + let hasher = PARAMS::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 @@ -433,7 +435,7 @@ impl< /* KeyGen functions */ -impl HMAC { +impl HMAC { /// Generates a key of the appropriate length for this HMAC from the provided RNG, tagged /// [`KeyType::MACKey`] and ready to hand to [`MAC::new`]. /// @@ -473,7 +475,7 @@ impl HMAC { } } -impl Algorithm for HMAC { +impl Algorithm for HMAC { const ALG_NAME: &'static str = PARAMS::ALG_NAME; // An HMAC claims the security strength of the hash underneath it. Reading it off the hash rather // than restating it per instantiation keeps the advertised strength identical to the one @@ -481,7 +483,7 @@ impl Algorithm for HMAC const MAX_SECURITY_STRENGTH: SecurityStrength = PARAMS::MAX_SECURITY_STRENGTH; } -impl AlgorithmOID for HMAC { +impl AlgorithmOID for HMAC { const OID: &'static [u32] = PARAMS::OID; const OID_DER: &'static [u8] = PARAMS::OID_DER; } diff --git a/crypto/sha2/src/hkdf.rs b/crypto/sha2/src/hkdf.rs index 6db337cc0..c6f458f9b 100644 --- a/crypto/sha2/src/hkdf.rs +++ b/crypto/sha2/src/hkdf.rs @@ -221,7 +221,6 @@ use crate::hmac::{ SUSPENDED_HMAC_SHA256_STATE_LEN, SUSPENDED_HMAC_SHA384_STATE_LEN, SUSPENDED_HMAC_SHA512_STATE_LEN, }; -use crate::{SHA256, SHA384, SHA512}; use crate::{SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN}; use bouncycastle_hkdf::HKDF; @@ -256,7 +255,6 @@ pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_L /// Public type for HKDF using SHA256. #[allow(non_camel_case_types)] pub type HKDF_SHA256 = HKDF< - SHA256, crate::hmac::HMAC_SHA256Params, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA256_STATE_LEN, @@ -265,7 +263,6 @@ pub type HKDF_SHA256 = HKDF< /// SHA-512's suspended-state length. #[allow(non_camel_case_types)] pub type HKDF_SHA384 = HKDF< - SHA384, crate::hmac::HMAC_SHA384Params, SUSPENDED_SHA512_STATE_LEN, SUSPENDED_HKDF_SHA384_STATE_LEN, @@ -273,7 +270,6 @@ pub type HKDF_SHA384 = HKDF< /// Public type for HKDF using SHA512. #[allow(non_camel_case_types)] pub type HKDF_SHA512 = HKDF< - SHA512, crate::hmac::HMAC_SHA512Params, SUSPENDED_SHA512_STATE_LEN, SUSPENDED_HKDF_SHA512_STATE_LEN, diff --git a/crypto/sha2/src/hmac.rs b/crypto/sha2/src/hmac.rs index 4882d3b5a..c2d7b06ac 100644 --- a/crypto/sha2/src/hmac.rs +++ b/crypto/sha2/src/hmac.rs @@ -5,9 +5,9 @@ //! [`HMAC_SHA256`], [`HMAC_SHA384`] and [`HMAC_SHA512`]. //! //! HMAC itself is implemented generically in [`bouncycastle_hmac`]; this module declares one -//! [`HMACParams`] marker type per instantiation (such as [`HMAC_SHA256Params`]), carrying that -//! HMAC's name, claimed strength, OID and key type, and publishes the type alias pairing each -//! marker with its hash. This mirrors how the hashes themselves are built, where `SHA256` is +//! [`HMACParams`] marker type per instantiation (such as [`HMAC_SHA256Params`]), naming its hash +//! and carrying that HMAC's name, claimed strength, OID and key type, and publishes the type alias +//! for each marker. This mirrors how the hashes themselves are built, where `SHA256` is //! `SHA256Internal`. The upshot is that //! HMAC over a SHA2 hash is found in this crate, and [`bouncycastle_hmac`] serves as a utility crate //! rather than as part of library's public API. @@ -317,13 +317,14 @@ impl AlgorithmOID for HMAC_SHA224Params { } impl HMACParams for HMAC_SHA224Params { + type Hash = SHA224; type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; type KeyBuf = [u8; ::BLOCK_LEN]; } /// Public type for HMAC using SHA224. #[allow(non_camel_case_types)] -pub type HMAC_SHA224 = HMAC; +pub type HMAC_SHA224 = HMAC; /// The parameters for HMAC-SHA256 -- see [`HMAC_SHA256`]. #[derive(Clone)] @@ -344,13 +345,14 @@ impl AlgorithmOID for HMAC_SHA256Params { } impl HMACParams for HMAC_SHA256Params { + type Hash = SHA256; type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; type KeyBuf = [u8; ::BLOCK_LEN]; } /// Public type for HMAC using SHA256. #[allow(non_camel_case_types)] -pub type HMAC_SHA256 = HMAC; +pub type HMAC_SHA256 = HMAC; /// The parameters for HMAC-SHA384 -- see [`HMAC_SHA384`]. #[derive(Clone)] @@ -371,13 +373,14 @@ impl AlgorithmOID for HMAC_SHA384Params { } impl HMACParams for HMAC_SHA384Params { + type Hash = SHA384; type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; type KeyBuf = [u8; ::BLOCK_LEN]; } /// Public type for HMAC using SHA384. #[allow(non_camel_case_types)] -pub type HMAC_SHA384 = HMAC; +pub type HMAC_SHA384 = HMAC; /// The parameters for HMAC-SHA512 -- see [`HMAC_SHA512`]. #[derive(Clone)] @@ -398,13 +401,14 @@ impl AlgorithmOID for HMAC_SHA512Params { } impl HMACParams for HMAC_SHA512Params { + type Hash = SHA512; type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; type KeyBuf = [u8; ::BLOCK_LEN]; } /// Public type for HMAC using SHA512. #[allow(non_camel_case_types)] -pub type HMAC_SHA512 = HMAC; +pub type HMAC_SHA512 = HMAC; /// The parameters for HMAC-SHA512/224 -- see [`HMAC_SHA512_224`]. #[derive(Clone)] @@ -425,13 +429,14 @@ impl AlgorithmOID for HMAC_SHA512_224Params { } impl HMACParams for HMAC_SHA512_224Params { + type Hash = SHA512_224; type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; type KeyBuf = [u8; ::BLOCK_LEN]; } /// Public type for HMAC using SHA512/224. #[allow(non_camel_case_types)] -pub type HMAC_SHA512_224 = HMAC; +pub type HMAC_SHA512_224 = HMAC; /// The parameters for HMAC-SHA512/256 -- see [`HMAC_SHA512_256`]. #[derive(Clone)] @@ -452,13 +457,14 @@ impl AlgorithmOID for HMAC_SHA512_256Params { } impl HMACParams for HMAC_SHA512_256Params { + type Hash = SHA512_256; type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; type KeyBuf = [u8; ::BLOCK_LEN]; } /// Public type for HMAC using SHA512/256. #[allow(non_camel_case_types)] -pub type HMAC_SHA512_256 = HMAC; +pub type HMAC_SHA512_256 = HMAC; /*** Serialized-state length constants ***/ // HMAC's suspended state is exactly the inner hasher's state -- the key is deliberately excluded and diff --git a/crypto/sha3/src/hmac.rs b/crypto/sha3/src/hmac.rs index c52f06ed0..7f6323872 100644 --- a/crypto/sha3/src/hmac.rs +++ b/crypto/sha3/src/hmac.rs @@ -5,9 +5,9 @@ //! [`HMAC_SHA3_256`], [`HMAC_SHA3_384`] and [`HMAC_SHA3_512`]. //! //! HMAC itself is implemented generically in [`bouncycastle_hmac`]; this module declares one -//! [`HMACParams`] marker type per instantiation (such as [`HMAC_SHA3_256Params`]), carrying that -//! HMAC's name, claimed strength, OID and key type, and publishes the type alias pairing each -//! marker with its hash. This mirrors how the hashes themselves are built, where `SHA256` is +//! [`HMACParams`] marker type per instantiation (such as [`HMAC_SHA3_256Params`]), naming its hash +//! and carrying that HMAC's name, claimed strength, OID and key type, and publishes the type alias +//! for each marker. This mirrors how the hashes themselves are built, where `SHA256` is //! `SHA256Internal`. The upshot is that //! HMAC over a SHA3 hash is found in this crate, and [`bouncycastle_hmac`] serves as a utility crate //! rather than as part of library's public API. @@ -328,13 +328,14 @@ impl AlgorithmOID for HMAC_SHA3_224Params { } impl HMACParams for HMAC_SHA3_224Params { + type Hash = SHA3_224; type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; type KeyBuf = [u8; ::BLOCK_LEN]; } /// Public type for HMAC using SHA3_224. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_224 = HMAC; +pub type HMAC_SHA3_224 = HMAC; /// The parameters for HMAC-SHA3_256 -- see [`HMAC_SHA3_256`]. #[derive(Clone)] @@ -357,13 +358,14 @@ impl AlgorithmOID for HMAC_SHA3_256Params { } impl HMACParams for HMAC_SHA3_256Params { + type Hash = SHA3_256; type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; type KeyBuf = [u8; ::BLOCK_LEN]; } /// Public type for HMAC using SHA3_256. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_256 = HMAC; +pub type HMAC_SHA3_256 = HMAC; /// The parameters for HMAC-SHA3_384 -- see [`HMAC_SHA3_384`]. #[derive(Clone)] @@ -386,13 +388,14 @@ impl AlgorithmOID for HMAC_SHA3_384Params { } impl HMACParams for HMAC_SHA3_384Params { + type Hash = SHA3_384; type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; type KeyBuf = [u8; ::BLOCK_LEN]; } /// Public type for HMAC using SHA3_384. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_384 = HMAC; +pub type HMAC_SHA3_384 = HMAC; /// The parameters for HMAC-SHA3_512 -- see [`HMAC_SHA3_512`]. #[derive(Clone)] @@ -415,13 +418,14 @@ impl AlgorithmOID for HMAC_SHA3_512Params { } impl HMACParams for HMAC_SHA3_512Params { + type Hash = SHA3_512; type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; type KeyBuf = [u8; ::BLOCK_LEN]; } /// Public type for HMAC using SHA3_512. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_512 = HMAC; +pub type HMAC_SHA3_512 = HMAC; /*** Serialized-state length constants ***/ // HMAC's suspended state is exactly the inner hasher's state -- the key is deliberately excluded and diff --git a/crypto/sm3/src/hmac.rs b/crypto/sm3/src/hmac.rs index bccb5558c..a10411bad 100644 --- a/crypto/sm3/src/hmac.rs +++ b/crypto/sm3/src/hmac.rs @@ -82,13 +82,14 @@ impl AlgorithmOID for HMAC_SM3Params { } impl HMACParams for HMAC_SM3Params { + type Hash = SM3; type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; type KeyBuf = [u8; ::BLOCK_LEN]; } /// Public type for HMAC using SM3. #[allow(non_camel_case_types)] -pub type HMAC_SM3 = HMAC; +pub type HMAC_SM3 = HMAC; /*** Serialized-state length constants ***/ // HMAC's suspended state is exactly the inner hasher's state -- the key is deliberately excluded and