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 boring::hash::MessageDigest;
use rustls::crypto::tls13::{self, Hkdf as RustlsHkdf}; use rustls::crypto::tls13::{self, Hkdf as RustlsHkdf};
use zeroize::Zeroizing;
use crate::helper::{cvt, cvt_p}; use crate::helper::{cvt, cvt_p};
@ -67,7 +68,7 @@ impl<T: BoringHash> RustlsHkdf for Hkdf<T> {
let digest = T::new_hash(); let digest = T::new_hash();
let hash_size = digest.size(); 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; let mut prk_len = 0;
// if salt isn't set we usen these bytes here as salt // 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, okm: &rustls::crypto::tls13::OkmBlock,
) -> Box<dyn rustls::crypto::tls13::HkdfExpander> { ) -> Box<dyn rustls::crypto::tls13::HkdfExpander> {
let okm = okm.as_ref(); 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(); let prk_len = okm.len();
prk[..prk_len].copy_from_slice(okm); prk[..prk_len].copy_from_slice(okm);
@ -121,7 +122,7 @@ impl<T: BoringHash> RustlsHkdf for Hkdf<T> {
message: &[u8], message: &[u8],
) -> rustls::crypto::hmac::Tag { ) -> rustls::crypto::hmac::Tag {
let digest = T::new_hash(); 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; let mut hash_len = 0u32;
unsafe { unsafe {
cvt_p(boring_sys::HMAC( cvt_p(boring_sys::HMAC(
@ -140,7 +141,7 @@ impl<T: BoringHash> RustlsHkdf for Hkdf<T> {
} }
struct HkdfExpander { 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, prk_len: usize,
digest: MessageDigest, digest: MessageDigest,
} }
@ -187,7 +188,7 @@ impl tls13::HkdfExpander for HkdfExpander {
/// This is infallible, because by definition `OkmBlock` is always exactly /// This is infallible, because by definition `OkmBlock` is always exactly
/// `HashLen` bytes long. /// `HashLen` bytes long.
fn expand_block(&self, info: &[&[u8]]) -> tls13::OkmBlock { 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(); let output_len = self.hash_len();
self.expand_slice(info, &mut output[..output_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 boring_additions::hmac::HmacCtx;
use foreign_types::ForeignType; use foreign_types::ForeignType;
use rustls::crypto; use rustls::crypto;
use zeroize::Zeroizing;
use crate::helper::{cvt, cvt_p}; use crate::helper::{cvt, cvt_p};
@ -30,7 +31,7 @@ impl crypto::hmac::Hmac for BoringHmac {
Box::new(BoringHmacKey { Box::new(BoringHmacKey {
ctx, ctx,
md, 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 { struct BoringHmacKey {
ctx: HmacCtx, ctx: HmacCtx,
md: MessageDigest, md: MessageDigest,
key: Vec<u8>, key: Zeroizing<Vec<u8>>,
} }
impl BoringHmacKey { impl BoringHmacKey {
@ -99,8 +99,8 @@ impl crypto::hmac::Key for BoringHmacKey {
self.update(last); self.update(last);
let mut out = [0u8; 32]; let mut out = Zeroizing::new([0u8; boring_sys::EVP_MAX_MD_SIZE as usize]);
let out_len = self.finish(&mut out); let out_len = self.finish(&mut out[..]);
crypto::hmac::Tag::new(&out[..out_len]) crypto::hmac::Tag::new(&out[..out_len])
} }
@ -112,7 +112,7 @@ impl crypto::hmac::Key for BoringHmacKey {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::SHA256; use super::{SHA256, SHA384};
use hex_literal::hex; use hex_literal::hex;
#[test] #[test]
@ -141,4 +141,23 @@ mod tests {
hex!("11fa4a6ee97bebfad9e1087145c556fec9a786cad0659aa10702d21bd2968305") 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], peer_pub_key: &[u8],
) -> Result<crypto::SharedSecret, rustls::Error> { ) -> Result<crypto::SharedSecret, rustls::Error> {
self.diffie_hellman(peer_pub_key) self.diffie_hellman(peer_pub_key)
.map(|x| crypto::SharedSecret::from(x.as_slice())) .map(crypto::SharedSecret::from)
.map_err(|e| { .map_err(|e| {
log_and_map( log_and_map(
"ex::KeyExchange::diffie_hellman", "ex::KeyExchange::diffie_hellman",