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
19 changes: 10 additions & 9 deletions INTRODUCTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<H: Hash + Default> HKDF<H> {
do_extract_init(salt: & impl KeyMaterial) -> Result<usize, MACError>;
impl<PARAMS: HMACParams, ...> HKDF<PARAMS, ...> {
do_extract_init(salt: & impl KeyMaterialTrait) -> Result<usize, MACError>;

do_extract_update_key(ikm: & impl KeyMaterial) -> Result<usize, MACError>;
do_extract_update_key(ikm: & impl KeyMaterialTrait) -> Result<usize, MACError>;

do_extract_update_bytes(ikm_chunk: & [u8]) -> Result<usize, MACError>;

do_extract_final() -> Result<impl KeyMaterial, MACError>;
do_extract_final() -> Result<impl KeyMaterialTrait, MACError>;

expand_out(
prk: & impl KeyMaterial,
prk: & impl KeyMaterialTrait,
info: & [u8],
L: usize,
okm: & mut impl KeyMaterial,
okm: & mut impl KeyMaterialTrait,
) -> Result<usize, KDFError>
}
```

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") ?;
Expand Down
6 changes: 6 additions & 0 deletions alpha_0.1.3_release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@
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.
* `HMAC` and `HKDF` take their hash from the new `HMACParams::Hash` associated type instead of a separate type
parameter, so `HMAC<H, P>` is now `HMAC<P>` and `HKDF<H, P, ..>` is now `HKDF<P, ..>`.
28 changes: 14 additions & 14 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(),
));

// HMAC-SHA512/224 -- NIST ACVP HMAC-SHA2-512/224 2.0, tgId 1, tcId 106 (MAC truncated to 160 bits)
Expand Down Expand Up @@ -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(),
)
Expand Down
78 changes: 38 additions & 40 deletions crypto/hkdf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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<N>`.
//! `PARAMS::Hash: Suspendable<N>`.
//! * `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
Expand All @@ -47,14 +47,15 @@
//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType};
//! use bouncycastle_core::traits::{KDF, SuspendableKeyed};
//! use bouncycastle_hkdf::HKDF;
//! use bouncycastle_sha2::{SHA224, SUSPENDED_SHA256_STATE_LEN};
//! use bouncycastle_sha2::hmac::HMAC_SHA224Params;
//! 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<SHA224, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA224_STATE_LEN>;
//! HKDF<HMAC_SHA224Params, SUSPENDED_SHA256_STATE_LEN, SUSPENDED_HKDF_SHA224_STATE_LEN>;
//!
//! pub const HKDF_SHA224_NAME: &str = "HKDF-SHA224";
//!
Expand Down Expand Up @@ -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;
Expand All @@ -131,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<H::OUTPUT_LEN>` instead of this hack.
/// and declare `prk: &mut KeyMaterial<PARAMS::Hash::OUTPUT_LEN>` instead of this hack.
pub const MAX_HMAC_OUTPUT_LEN: usize = 64;

/*** Types ***/
Expand All @@ -144,29 +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<HASH_STATE_LEN>`) and
/// `HASH_STATE_LEN` is the suspended-state length of the hash (as in
/// `PARAMS::Hash: Suspendable<HASH_STATE_LEN>`) 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<HASH_STATE_LEN>`.
/// `SuspendableKeyed<{HASH_STATE_LEN + 14}>` over `PARAMS::Hash: Suspendable<HASH_STATE_LEN>`.
#[derive(Clone)]
pub struct HKDF<
H: Hash + HashAlgParams + Default,
const HASH_STATE_LEN: usize,
const HKDF_STATE_LEN: usize,
> {
pub struct HKDF<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>>,
entropy: HkdfEntropyTracker<H>,
hmac: Option<HMAC<PARAMS>>,
entropy: HkdfEntropyTracker<PARAMS::Hash>,
state: HkdfStates,
}

Expand Down Expand Up @@ -243,16 +241,16 @@ 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<PARAMS: HMACParams, const HASH_STATE_LEN: usize, const HKDF_STATE_LEN: usize> Default
for HKDF<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<PARAMS: HMACParams, const HASH_STATE_LEN: usize, const HKDF_STATE_LEN: usize>
HKDF<PARAMS, HASH_STATE_LEN, HKDF_STATE_LEN>
{
/// Get a new, uninstantiated HKDF object.
pub fn new() -> Self {
Expand Down Expand Up @@ -362,7 +360,7 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
// (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",
Expand All @@ -375,7 +373,7 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
));
}

let mut entropy = HkdfEntropyTracker::<H>::new();
let mut entropy = HkdfEntropyTracker::<PARAMS::Hash>::new();
entropy.credit_entropy(prk);

#[allow(non_snake_case)]
Expand All @@ -398,7 +396,7 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
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::<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 @@ -420,7 +418,7 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
// 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::<H>::new(&prk_as_mac_key)?;
let mut hmac = HMAC::<PARAMS>::new(&prk_as_mac_key)?;
hmac.do_update(&T[..t_len]);
hmac.do_update(info);
hmac.do_update(&[i]);
Expand Down Expand Up @@ -490,7 +488,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::<PARAMS>::new_allow_weak_key(salt)?);

let additional_entropy = self.entropy.credit_entropy(salt);
self.state = HkdfStates::Initialized;
Expand Down Expand Up @@ -523,7 +521,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<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 @@ -598,7 +596,7 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
})?;
// By RFC5869, the output size of prk is HashLen denotes the length of the
// hash function output in octets
debug_assert_eq!(prk.key_len(), H::OUTPUT_LEN);
debug_assert_eq!(prk.key_len(), PARAMS::Hash::OUTPUT_LEN);
Ok(bytes_written)
}
}
Expand All @@ -612,8 +610,8 @@ 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<PARAMS: HMACParams, const HASH_STATE_LEN: usize, const HKDF_STATE_LEN: usize> KDF
for HKDF<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 All @@ -624,7 +622,7 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
) -> Result<Box<dyn KeyMaterialTrait>, 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))
}

Expand Down Expand Up @@ -662,7 +660,7 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
) -> Result<Box<dyn KeyMaterialTrait>, 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))
}

Expand All @@ -675,7 +673,7 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
output_key: &mut impl KeyMaterialTrait,
) -> Result<usize, KDFError> {
let mut hkdf = Self::new();
let mut entropy = HkdfEntropyTracker::<H>::new();
let mut entropy = HkdfEntropyTracker::<PARAMS::Hash>::new();

if keys.len() >= 1 {
hkdf.do_extract_init(keys[0])?;
Expand Down Expand Up @@ -710,7 +708,7 @@ impl<H: Hash + HashAlgParams + Default, const HASH_STATE_LEN: usize, const HKDF_
}

fn max_security_strength(&self) -> SecurityStrength {
H::default().max_security_strength()
PARAMS::Hash::default().max_security_strength()
}
}

Expand Down Expand Up @@ -739,10 +737,10 @@ 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<PARAMS: HMACParams, const HASH_STATE_LEN: usize, const HKDF_STATE_LEN: usize>
SuspendableKeyed<HKDF_STATE_LEN> for HKDF<PARAMS, HASH_STATE_LEN, HKDF_STATE_LEN>
where
H: Hash + HashAlgParams + Default + Suspendable<HASH_STATE_LEN>,
PARAMS::Hash: Suspendable<HASH_STATE_LEN>,
{
// 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
Expand Down Expand Up @@ -788,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::<H>::from_suspended(
1 => Some(HMAC::<PARAMS>::from_suspended(
state[4..4 + HASH_STATE_LEN].try_into().unwrap(),
salt,
)?),
Expand Down
Loading
Loading