Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions alpha_0.1.3_release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@
* bug fixes to the way SHA3/SHAKE handled absorbing and squeezing a partial final byte.
* Design discussions about whether core::traits::XOF (in the abstract) should allow interleaving absorb -> squeeze ->
absorb (ie "absorb-after-squeeze). Outcome: absorb-after-squeeze forbidden. Could be changed in the future.
* 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.
13 changes: 8 additions & 5 deletions crypto/factory/tests/mac_factory_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
));

// TODO: at least one test for each type
Expand Down
46 changes: 30 additions & 16 deletions crypto/hkdf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,15 @@
//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType};
//! use bouncycastle_core::traits::{KDF, SuspendableKeyed};
//! use bouncycastle_hkdf::HKDF;
//! use bouncycastle_sha2::hmac::HMAC_SHA384Params;
//! use bouncycastle_sha2::{SHA384, SUSPENDED_SHA512_STATE_LEN};
//!
//! // SHA-384 is a member of the SHA-512 family, so its suspended state is the SHA-512 one.
//! const SUSPENDED_HKDF_SHA384_STATE_LEN: usize = SUSPENDED_SHA512_STATE_LEN + 14;
//!
//! #[allow(non_camel_case_types)]
//! pub type HKDF_SHA384 =
//! HKDF<SHA384, SUSPENDED_SHA512_STATE_LEN, SUSPENDED_HKDF_SHA384_STATE_LEN>;
//! HKDF<SHA384, HMAC_SHA384Params, SUSPENDED_SHA512_STATE_LEN, SUSPENDED_HKDF_SHA384_STATE_LEN>;
//!
//! pub const HKDF_SHA384_NAME: &str = "HKDF-SHA384";
//!
Expand Down Expand Up @@ -115,7 +116,7 @@ use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver};
use bouncycastle_core::traits::{
Hash, HashAlgParams, KDF, MAC, SecurityStrength, Suspendable, SuspendableKeyed,
};
use bouncycastle_hmac::HMAC;
use bouncycastle_hmac::{HMAC, HMACParams};
use bouncycastle_utils::{max, min};
use std::marker::PhantomData;
// Imports needed only for docs
Expand Down Expand Up @@ -160,13 +161,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<H>>,
hmac: Option<HMAC<H, PARAMS>>,
entropy: HkdfEntropyTracker<H>,
state: HkdfStates,
}
Expand Down Expand Up @@ -244,16 +246,24 @@ impl<H: Hash + HashAlgParams + Default> HkdfEntropyTracker<H> {
}
}

impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_STATE_LEN: usize>
Default for HKDF<H, HASH_STATE_LEN, HKDF_STATE_LEN>
impl<
H: Hash + HashAlgParams + Default,
PARAMS: HMACParams,
const HASH_STATE_LEN: usize,
const HKDF_STATE_LEN: usize,
> Default for HKDF<H, PARAMS, HASH_STATE_LEN, HKDF_STATE_LEN>
{
fn default() -> Self {
Self::new()
}
}

impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_STATE_LEN: usize>
HKDF<H, HASH_STATE_LEN, HKDF_STATE_LEN>
impl<
H: Hash + HashAlgParams + Default,
PARAMS: HMACParams,
const HASH_STATE_LEN: usize,
const HKDF_STATE_LEN: usize,
> HKDF<H, PARAMS, HASH_STATE_LEN, HKDF_STATE_LEN>
{
/// Get a new, uninstantiated HKDF object.
pub fn new() -> Self {
Expand Down Expand Up @@ -399,7 +409,7 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
key_material::do_hazardous_operations(okm, |okm| {
let out = okm.ref_to_bytes_mut()?;
while i < N {
let mut hmac = HMAC::<H>::new(&prk_as_mac_key)
let mut hmac = HMAC::<H, PARAMS>::new(&prk_as_mac_key)
.map_err(|_| KeyMaterialError::GenericError("HMAC initialization failed"))?;
hmac.do_update(&T[..t_len]);
hmac.do_update(info);
Expand All @@ -418,7 +428,7 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_

// Part of the output is not taken on the last iteration
let remaining = L - bytes_written;
let mut hmac = HMAC::<H>::new(&prk_as_mac_key)?;
let mut hmac = HMAC::<H, PARAMS>::new(&prk_as_mac_key)?;
hmac.do_update(&T[..t_len]);
hmac.do_update(info);
hmac.do_update(&[i]);
Expand Down Expand Up @@ -488,7 +498,7 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
// 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::<H>::new_allow_weak_key(salt)?);
self.hmac = Some(HMAC::<H, PARAMS>::new_allow_weak_key(salt)?);

let additional_entropy = self.entropy.credit_entropy(salt);
self.state = HkdfStates::Initialized;
Expand Down Expand Up @@ -521,7 +531,7 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
debug_assert!(self.hmac.is_some());

let additional_entropy = self.entropy.credit_entropy(ikm);
let hmac_ref: &mut HMAC<H> = self.hmac.as_mut().unwrap();
let hmac_ref: &mut HMAC<H, PARAMS> = self.hmac.as_mut().unwrap();
hmac_ref.do_update(ikm.ref_to_bytes());
// self.hmac.as_mut().unwrap().do_update(ikm.ref_to_bytes());

Expand Down Expand Up @@ -610,8 +620,12 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
/// [`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, const HASH_STATE_LEN: usize, const HKDF_STATE_LEN: usize>
KDF for HKDF<H, HASH_STATE_LEN, HKDF_STATE_LEN>
impl<
H: Hash + HashAlgParams + Default,
PARAMS: HMACParams,
const HASH_STATE_LEN: usize,
const HKDF_STATE_LEN: usize,
> KDF for HKDF<H, PARAMS, HASH_STATE_LEN, HKDF_STATE_LEN>
{
/// 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.
Expand Down Expand Up @@ -737,8 +751,8 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
/// 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<H, const HASH_STATE_LEN: usize, const HKDF_STATE_LEN: usize> SuspendableKeyed<HKDF_STATE_LEN>
for HKDF<H, HASH_STATE_LEN, HKDF_STATE_LEN>
impl<H, PARAMS: HMACParams, const HASH_STATE_LEN: usize, const HKDF_STATE_LEN: usize>
SuspendableKeyed<HKDF_STATE_LEN> for HKDF<H, PARAMS, HASH_STATE_LEN, HKDF_STATE_LEN>
where
H: Hash + HashAlgParams + Default + Suspendable<HASH_STATE_LEN>,
{
Expand Down Expand Up @@ -786,7 +800,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::<H>::from_suspended(
1 => Some(HMAC::<H, PARAMS>::from_suspended(
state[4..4 + HASH_STATE_LEN].try_into().unwrap(),
salt,
)?),
Expand Down
29 changes: 18 additions & 11 deletions crypto/hkdf/tests/hkdf_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -744,21 +744,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<const HASH_LEN: usize, const LEN: usize, H>(
fn round_trip<const HASH_LEN: usize, const LEN: usize, H, P>(
salt: &KeyMaterial128,
part1: &[u8],
part2: &[u8],
) where
H: Hash + HashAlgParams + Default,
HKDF<H, HASH_LEN, LEN>: Clone + SuspendableKeyed<LEN, Key = dyn KeyMaterialTrait>,
P: bouncycastle_hmac::HMACParams,
HKDF<H, P, HASH_LEN, LEN>: Clone + SuspendableKeyed<LEN, Key = dyn KeyMaterialTrait>,
{
let hkdf = HKDF::<H, HASH_LEN, LEN>::new();
let hkdf = HKDF::<H, P, HASH_LEN, LEN>::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::<H, HASH_LEN, LEN>::from_suspended(serialized_state, salt).unwrap();
HKDF::<H, P, HASH_LEN, LEN>::from_suspended(serialized_state, salt).unwrap();

hkdf.do_extract_init(salt).unwrap();
hkdf.do_extract_update_bytes(part1).unwrap();
Expand All @@ -775,19 +776,25 @@ mod hkdf_tests {

// resume (re-supplying the salt), feed the identical remaining IKM, and compare PRKs
let mut resumed =
HKDF::<H, HASH_LEN, LEN>::from_suspended(serialized_state, salt).unwrap();
HKDF::<H, P, HASH_LEN, LEN>::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::<SUSPENDED_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA256_STATE_LEN, SHA256>(
&salt, part1, part2,
);
round_trip::<SUSPENDED_SHA512_STATE_LEN, SUSPENDED_HKDF_SHA512_STATE_LEN, SHA512>(
&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
Expand Down
12 changes: 0 additions & 12 deletions crypto/hmac/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,3 @@ edition.workspace = true
[dependencies]
bouncycastle-core.workspace = true
bouncycastle-utils.workspace = true

# bouncycastle-sha2, -sha3 and -rng are dev-dependencies so that the tests, benches and doc examples
# here can still exercise HMAC over the library's own hashes; Cargo permits cycles through
# dev-dependencies.
# todo -- we're about to change that and move them to their respective crates in the next phase.
[dev-dependencies]
bouncycastle-core-test-framework.workspace = true
criterion.workspace = true
bouncycastle-hex.workspace = true
bouncycastle-rng.workspace = true
bouncycastle-sha2.workspace = true
bouncycastle-sha3.workspace = true
Loading
Loading