Skip to content

Commit e464f68

Browse files
committed
Fixed a bug in HMAC that could cause panics.
1 parent d9b06bd commit e464f68

3 files changed

Lines changed: 64 additions & 38 deletions

File tree

crypto/hkdf/src/lib.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,10 @@ pub type HKDF_SHA256 = HKDF<SHA256>;
179179
pub type HKDF_SHA512 = HKDF<SHA512>;
180180

181181
pub struct HKDF<H: Hash + HashAlgParams + Default> {
182-
hmac: Option<HMAC<H>>, // Optional because we can't construct an HMAC until they give us a key
182+
// Optional because we can't construct an HMAC until they give us a key
183183
// to initialize it with.
184184
// None should correspond to a state of Uninitialized.
185+
hmac: Option<HMAC<H>>,
185186
entropy: HkdfEntropyTracker<H>,
186187
state: HkdfStates,
187188
}
@@ -410,7 +411,6 @@ impl<H: Hash + HashAlgParams + Default> HKDF<H> {
410411
key_material::do_hazardous_operations(okm, |okm| {
411412
let out = okm.ref_to_bytes_mut()?;
412413
while i < N {
413-
// todo: might need this to be new_allow_weak_key()
414414
let mut hmac = HMAC::<H>::new(&prk_as_mac_key)
415415
.map_err(|_| KeyMaterialError::GenericError("HMAC initialization failed"))?;
416416
hmac.do_update(&T[..t_len]);
@@ -430,7 +430,6 @@ impl<H: Hash + HashAlgParams + Default> HKDF<H> {
430430

431431
// On the last iteration, we don't take all of the output.
432432
let remaining = L - bytes_written;
433-
// todo: might need this to be new_allow_weak_key()
434433
let mut hmac = HMAC::<H>::new(&prk_as_mac_key)?;
435434
hmac.do_update(&T[..t_len]);
436435
hmac.do_update(info);
@@ -489,8 +488,7 @@ impl<H: Hash + HashAlgParams + Default> HKDF<H> {
489488
/// The output KeyMaterial will be of fixed size, with a capacity large enough to cover any
490489
/// underlying hash function, but the actual key length will be appropriate to the underlying hash function.
491490
///
492-
/// Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero,
493-
/// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt.
491+
/// Salt is optional; to omit it, provide a KeyMaterial0, which will cause HKDF to use the default all-zero salt.
494492
///
495493
/// Returns the number of bits of entropy credited to this input key material.
496494
pub fn do_extract_init(&mut self, salt: &impl KeyMaterialTrait) -> Result<usize, MACError> {

crypto/hmac/src/lib.rs

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -205,69 +205,76 @@ pub const HMAC_SHA3_512_NAME: &str = "HMAC-SHA3-512";
205205

206206
/*** Type aliases ***/
207207
#[allow(non_camel_case_types)]
208-
pub type HMAC_SHA224 = HMAC<SHA224>;
208+
pub type HMAC_SHA224 = HMAC<SHA224, 64>;
209209
impl Algorithm for HMAC_SHA224 {
210210
const ALG_NAME: &'static str = HMAC_SHA224_NAME;
211211
const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit;
212212
}
213213

214214
#[allow(non_camel_case_types)]
215-
pub type HMAC_SHA256 = HMAC<SHA256>;
215+
pub type HMAC_SHA256 = HMAC<SHA256, 64>;
216216
impl Algorithm for HMAC_SHA256 {
217217
const ALG_NAME: &'static str = HMAC_SHA256_NAME;
218218
const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit;
219219
}
220220

221221
#[allow(non_camel_case_types)]
222-
pub type HMAC_SHA384 = HMAC<SHA384>;
222+
pub type HMAC_SHA384 = HMAC<SHA384, 128>;
223223
impl Algorithm for HMAC_SHA384 {
224224
const ALG_NAME: &'static str = HMAC_SHA384_NAME;
225225
const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit;
226226
}
227227

228228
#[allow(non_camel_case_types)]
229-
pub type HMAC_SHA512 = HMAC<SHA512>;
229+
pub type HMAC_SHA512 = HMAC<SHA512, 128>;
230230
impl Algorithm for HMAC_SHA512 {
231231
const ALG_NAME: &'static str = HMAC_SHA512_NAME;
232232
const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit;
233233
}
234234

235235
#[allow(non_camel_case_types)]
236-
pub type HMAC_SHA3_224 = HMAC<SHA3_224>;
236+
pub type HMAC_SHA3_224 = HMAC<SHA3_224, 144>;
237237
impl Algorithm for HMAC_SHA3_224 {
238238
const ALG_NAME: &'static str = HMAC_SHA3_224_NAME;
239239
const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit;
240240
}
241241

242242
#[allow(non_camel_case_types)]
243-
pub type HMAC_SHA3_256 = HMAC<SHA3_256>;
243+
pub type HMAC_SHA3_256 = HMAC<SHA3_256, 136>;
244244
impl Algorithm for HMAC_SHA3_256 {
245245
const ALG_NAME: &'static str = HMAC_SHA3_256_NAME;
246246
const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit;
247247
}
248248

249249
#[allow(non_camel_case_types)]
250-
pub type HMAC_SHA3_384 = HMAC<SHA3_384>;
250+
pub type HMAC_SHA3_384 = HMAC<SHA3_384, 104>;
251251
impl Algorithm for HMAC_SHA3_384 {
252252
const ALG_NAME: &'static str = HMAC_SHA3_384_NAME;
253253
const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit;
254254
}
255255

256256
#[allow(non_camel_case_types)]
257-
pub type HMAC_SHA3_512 = HMAC<SHA3_512>;
257+
pub type HMAC_SHA3_512 = HMAC<SHA3_512, 72>;
258258
impl Algorithm for HMAC_SHA3_512 {
259259
const ALG_NAME: &'static str = HMAC_SHA3_512_NAME;
260260
const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit;
261261
}
262262

263-
// TODO: is there a rustacious way to extract this from HASH?
264-
const LARGEST_HASHER_OUTPUT_LEN: usize = 64;
263+
// The internal key buffer must be able to hold a key up to the *block length* of the underlying hash:
264+
// per RFC 2104, a key no longer than the block is used verbatim (only longer keys are pre-hashed down
265+
// to the output length). So the buffer size is a const parameter of the struct, set per hash to its
266+
// block length by the type aliases below. Block lengths (bytes): SHA-224/256 = 64, SHA-384/512 = 128,
267+
// SHA3-224 = 144, SHA3-256 = 136, SHA3-384 = 104, SHA3-512 = 72.
268+
//
269+
// The default is used only when `HMAC<HASH>` is written without an explicit buffer size; it is the
270+
// largest block length across all supported hashes, so it is always large enough.
271+
const LARGEST_HASHER_BLOCK_LEN: usize = 144;
265272

266273
// HMAC implements RFC 2104.
267274
#[derive(Clone)]
268-
pub struct HMAC<HASH: Hash + Default> {
275+
pub struct HMAC<HASH: Hash + Default, const KEY_BUF_LEN: usize = LARGEST_HASHER_BLOCK_LEN> {
269276
hasher: HASH,
270-
key: [u8; LARGEST_HASHER_OUTPUT_LEN],
277+
key: [u8; KEY_BUF_LEN],
271278
key_len: usize, // Doing it this way to avoid needing a vec, so that this can be made no_std friendly.
272279
}
273280

@@ -286,21 +293,13 @@ const OPAD_BYTE: u8 = 0x5C;
286293
// be too strict about it,
287294
pub const MIN_FIPS_DIGEST_LEN: usize = 4; // 32 / 8;
288295

289-
impl<HASH: Hash + Default> HMAC<HASH> {
296+
impl<HASH: Hash + Default, const KEY_BUF_LEN: usize> HMAC<HASH, KEY_BUF_LEN> {
290297
fn pad_key_into_hasher(&mut self, padding: u8) {
291298
// 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.
292299
// TODO: make this no_std-friendly
293300
let mut padded = vec![0u8; self.hasher.block_bitlen() / 8];
294-
295-
// Per RFC 2104 Section 2, if the application key exceeds the block
296-
// length of the underlying hashes algorithm, we apply a hash invocation
297-
// over the key first.
298-
// if self.key_len > self.hasher.block_bitlen() / 8 {
299-
// HASH::default().hash_out(&self.key[..self.key_len], &mut padded[..self.hasher.output_len()])?;
300-
// } else {
301-
// TODO: does this need a guard for a key_len longer than the block length?
301+
302302
padded[..self.key_len].copy_from_slice(&self.key[..self.key_len]);
303-
// }
304303

305304
// XXX: easier way to xor over Vec?
306305
for entry in &mut padded {
@@ -312,9 +311,10 @@ impl<HASH: Hash + Default> HMAC<HASH> {
312311
self.hasher.do_update(&padded)
313312
}
314313

315-
/// Loads the raw key bytes into `self.key` / `self.key_len`, pre-hashing them first if they
316-
/// exceed the underlying hash's block length (per RFC 2104 Section 2). This does NOT absorb the
317-
/// key into the hasher; that is done separately via [HMAC::pad_key_into_hasher].
314+
/// Per RFC 2104 Section 2, if the application key exceeds the block
315+
/// length of the underlying hashes algorithm, we apply a hash invocation
316+
/// over the key first.
317+
/// This does NOT absorb the key into the hasher; that is done separately via [HMAC::pad_key_into_hasher].
318318
fn load_key_material(&mut self, key_bytes: &[u8]) {
319319
if key_bytes.len() > self.hasher.block_bitlen() / 8 {
320320
// then we have to pre-hash it -- use a new instance of the hasher rather than the internal one
@@ -324,6 +324,12 @@ impl<HASH: Hash + Default> HMAC<HASH> {
324324
self.key[..key_bytes.len()].copy_from_slice(key_bytes);
325325
self.key_len = key_bytes.len();
326326
}
327+
328+
// Just as a sanity-check.
329+
assert!(
330+
self.key_len <= KEY_BUF_LEN,
331+
"Fatal error: Key length exceeds HMAC internal buffer length"
332+
);
327333
}
328334

329335
/// Private init so that users are forced to go through one of the public new methods and thus we
@@ -390,17 +396,15 @@ impl<HASH: Hash + Default> HMAC<HASH> {
390396
// TODO: This is essentially a "batch mode" where you want to perform many MACs or Verifications with the same key
391397
// TODO: against different data.
392398

393-
impl<HASH: Hash + Default> MAC for HMAC<HASH> {
399+
impl<HASH: Hash + Default, const KEY_BUF_LEN: usize> MAC for HMAC<HASH, KEY_BUF_LEN> {
394400
fn new(key: &impl KeyMaterialTrait) -> Result<Self, MACError> {
395-
let mut hmac =
396-
Self { hasher: HASH::default(), key: [0u8; LARGEST_HASHER_OUTPUT_LEN], key_len: 0 };
401+
let mut hmac = Self { hasher: HASH::default(), key: [0u8; KEY_BUF_LEN], key_len: 0 };
397402
hmac.init(key, false)?;
398403
Ok(hmac)
399404
}
400405

401406
fn new_allow_weak_key(key: &impl KeyMaterialTrait) -> Result<Self, MACError> {
402-
let mut hmac =
403-
Self { hasher: HASH::default(), key: [0u8; LARGEST_HASHER_OUTPUT_LEN], key_len: 0 };
407+
let mut hmac = Self { hasher: HASH::default(), key: [0u8; KEY_BUF_LEN], key_len: 0 };
404408
hmac.init(key, true)?;
405409
Ok(hmac)
406410
}
@@ -470,8 +474,11 @@ impl<HASH: Hash + Default> MAC for HMAC<HASH> {
470474
/// There is no way to detect a mismatched key on
471475
/// resume: the caller MUST supply the same key the HMAC was created with, otherwise the resumed
472476
/// operation will silently produce an incorrect MAC.
473-
impl<const HASH_STATE_LEN: usize, HASH: Hash + Default + SerializableState<HASH_STATE_LEN>>
474-
SerializableKeyedState<HASH_STATE_LEN> for HMAC<HASH>
477+
impl<
478+
const HASH_STATE_LEN: usize,
479+
const KEY_BUF_LEN: usize,
480+
HASH: Hash + Default + SerializableState<HASH_STATE_LEN>,
481+
> SerializableKeyedState<HASH_STATE_LEN> for HMAC<HASH, KEY_BUF_LEN>
475482
{
476483
// HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait`
477484
// rather than a single concrete key type. The key is only used (by reference) to reload the key
@@ -494,7 +501,7 @@ impl<const HASH_STATE_LEN: usize, HASH: Hash + Default + SerializableState<HASH_
494501
// Re-load the key material exactly as `new()` did (pre-hashing an over-length key), but do
495502
// NOT re-absorb `K ⊕ ipad` — the deserialized hasher already contains it. The key is only
496503
// needed for the outer `K ⊕ opad` step at finalization.
497-
let mut hmac = HMAC { hasher, key: [0u8; LARGEST_HASHER_OUTPUT_LEN], key_len: 0 };
504+
let mut hmac = HMAC { hasher, key: [0u8; KEY_BUF_LEN], key_len: 0 };
498505
hmac.load_key_material(key.ref_to_bytes());
499506

500507
Ok(hmac)

crypto/hmac/tests/hmac_tests.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,27 @@ mod hmac_tests {
144144
);
145145
}
146146

147+
#[test]
148+
fn long_key() {
149+
// Regression test: a key just under the maximum length before HMAC will hash it down.
150+
// (RFC 2104 only pre-hashes keys *longer* than the block).
151+
// This test wil detect an overflow-write and panic on HMAC's internal key buffer.
152+
153+
// SHA-512 has a 128-byte block, so use a 127-byte key
154+
let key = KeyMaterial::<200>::from_bytes_as_type(&[0x0B; 127], KeyType::MACKey).unwrap();
155+
let mut mac = HMAC_SHA512::new(&key).unwrap();
156+
mac.do_update(b"Hi There");
157+
let tag = mac.do_final();
158+
assert!(HMAC_SHA512::new(&key).unwrap().verify(b"Hi There", &tag));
159+
160+
// SHA3-224 has the largest block (144 bytes); a 143-byte key exercises the top of the range.
161+
let key = KeyMaterial::<200>::from_bytes_as_type(&[0x0B; 143], KeyType::MACKey).unwrap();
162+
let mut mac = HMAC_SHA3_224::new(&key).unwrap();
163+
mac.do_update(b"Hi There");
164+
let tag = mac.do_final();
165+
assert!(HMAC_SHA3_224::new(&key).unwrap().verify(b"Hi There", &tag));
166+
}
167+
147168
#[test]
148169
fn security_strength_tests() {
149170
// test: provided key has the correct length, but insufficient tagged security strength

0 commit comments

Comments
 (0)