Harden key material zeroization paths

Reduce secret lifetime in HKDF and HMAC internals, avoid extra shared-secret copying in key exchange, and add SHA384 HMAC coverage to guard output sizing.
This commit is contained in:
Jan Rüth 2026-04-10 14:13:22 +02:00 committed by Jan
commit bbd0ccf0b8
3 changed files with 32 additions and 12 deletions

View file

@ -2,6 +2,7 @@ use std::marker::PhantomData;
use boring::hash::MessageDigest;
use rustls::crypto::tls13::{self, Hkdf as RustlsHkdf};
use zeroize::Zeroizing;
use crate::helper::{cvt, cvt_p};
@ -67,7 +68,7 @@ impl<T: BoringHash> RustlsHkdf for Hkdf<T> {
let digest = T::new_hash();
let hash_size = digest.size();
let mut prk = [0u8; boring_sys::EVP_MAX_MD_SIZE as usize];
let mut prk = Zeroizing::new([0u8; boring_sys::EVP_MAX_MD_SIZE as usize]);
let mut prk_len = 0;
// if salt isn't set we usen these bytes here as salt
@ -103,7 +104,7 @@ impl<T: BoringHash> RustlsHkdf for Hkdf<T> {
okm: &rustls::crypto::tls13::OkmBlock,
) -> Box<dyn rustls::crypto::tls13::HkdfExpander> {
let okm = okm.as_ref();
let mut prk = [0u8; boring_sys::EVP_MAX_MD_SIZE as usize];
let mut prk = Zeroizing::new([0u8; boring_sys::EVP_MAX_MD_SIZE as usize]);
let prk_len = okm.len();
prk[..prk_len].copy_from_slice(okm);
@ -121,7 +122,7 @@ impl<T: BoringHash> RustlsHkdf for Hkdf<T> {
message: &[u8],
) -> rustls::crypto::hmac::Tag {
let digest = T::new_hash();
let mut hash = [0u8; boring_sys::EVP_MAX_MD_SIZE as usize];
let mut hash = Zeroizing::new([0u8; boring_sys::EVP_MAX_MD_SIZE as usize]);
let mut hash_len = 0u32;
unsafe {
cvt_p(boring_sys::HMAC(
@ -140,7 +141,7 @@ impl<T: BoringHash> RustlsHkdf for Hkdf<T> {
}
struct HkdfExpander {
prk: [u8; boring_sys::EVP_MAX_MD_SIZE as usize],
prk: Zeroizing<[u8; boring_sys::EVP_MAX_MD_SIZE as usize]>,
prk_len: usize,
digest: MessageDigest,
}
@ -187,7 +188,7 @@ impl tls13::HkdfExpander for HkdfExpander {
/// This is infallible, because by definition `OkmBlock` is always exactly
/// `HashLen` bytes long.
fn expand_block(&self, info: &[&[u8]]) -> tls13::OkmBlock {
let mut output = [0u8; boring_sys::EVP_MAX_MD_SIZE as usize];
let mut output = Zeroizing::new([0u8; boring_sys::EVP_MAX_MD_SIZE as usize]);
let output_len = self.hash_len();
self.expand_slice(info, &mut output[..output_len])

View file

@ -4,6 +4,7 @@ use boring::hash::MessageDigest;
use boring_additions::hmac::HmacCtx;
use foreign_types::ForeignType;
use rustls::crypto;
use zeroize::Zeroizing;
use crate::helper::{cvt, cvt_p};
@ -30,7 +31,7 @@ impl crypto::hmac::Hmac for BoringHmac {
Box::new(BoringHmacKey {
ctx,
md,
key: key.to_vec(),
key: Zeroizing::new(key.to_vec()),
})
}
@ -41,11 +42,10 @@ impl crypto::hmac::Hmac for BoringHmac {
}
}
#[derive(Clone)]
struct BoringHmacKey {
ctx: HmacCtx,
md: MessageDigest,
key: Vec<u8>,
key: Zeroizing<Vec<u8>>,
}
impl BoringHmacKey {
@ -99,8 +99,8 @@ impl crypto::hmac::Key for BoringHmacKey {
self.update(last);
let mut out = [0u8; 32];
let out_len = self.finish(&mut out);
let mut out = Zeroizing::new([0u8; boring_sys::EVP_MAX_MD_SIZE as usize]);
let out_len = self.finish(&mut out[..]);
crypto::hmac::Tag::new(&out[..out_len])
}
@ -112,7 +112,7 @@ impl crypto::hmac::Key for BoringHmacKey {
#[cfg(test)]
mod tests {
use super::SHA256;
use super::{SHA256, SHA384};
use hex_literal::hex;
#[test]
@ -141,4 +141,23 @@ mod tests {
hex!("11fa4a6ee97bebfad9e1087145c556fec9a786cad0659aa10702d21bd2968305")
);
}
#[test]
fn test_sha384_hmac_len() {
let hasher = SHA384.with_key("Very Secret".as_bytes());
let tag = hasher.sign_concat(
&[],
&[
"yay".as_bytes(),
"this".as_bytes(),
"works".as_bytes(),
"well".as_bytes(),
],
&[],
);
assert_eq!(tag.as_ref().len(), hasher.tag_len());
assert_eq!(tag.as_ref().len(), 48);
}
}

View file

@ -139,7 +139,7 @@ impl crypto::ActiveKeyExchange for KeyExchange {
peer_pub_key: &[u8],
) -> Result<crypto::SharedSecret, rustls::Error> {
self.diffie_hellman(peer_pub_key)
.map(|x| crypto::SharedSecret::from(x.as_slice()))
.map(crypto::SharedSecret::from)
.map_err(|e| {
log_and_map(
"ex::KeyExchange::diffie_hellman",