Skip to content

Commit

Permalink
Illustration for RustCrypto#80
Browse files Browse the repository at this point in the history
  • Loading branch information
ia0 committed Jun 29, 2023
1 parent 7753c7e commit 34fcb85
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 19 deletions.
6 changes: 1 addition & 5 deletions hkdf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
extern crate std;

pub use hmac;
pub use sealed::HmacImpl;

use core::fmt;
use core::marker::PhantomData;
Expand Down Expand Up @@ -282,8 +283,3 @@ where
f.write_str("> { ... }")
}
}

/// Sealed trait implemented for [`Hmac`] and [`SimpleHmac`].
pub trait HmacImpl<H: OutputSizeUser>: sealed::Sealed<H> {}

impl<H: OutputSizeUser, T: sealed::Sealed<H>> HmacImpl<H> for T {}
8 changes: 5 additions & 3 deletions hkdf/src/sealed.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(missing_docs)]

use hmac::digest::{
block_buffer::Eager,
core_api::{
Expand All @@ -9,7 +11,7 @@ use hmac::digest::{
};
use hmac::{Hmac, HmacCore, SimpleHmac};

pub trait Sealed<H: OutputSizeUser> {
pub trait HmacImpl<H: OutputSizeUser> {
type Core: Clone;

fn new_from_slice(key: &[u8]) -> Self;
Expand All @@ -23,7 +25,7 @@ pub trait Sealed<H: OutputSizeUser> {
fn finalize(self) -> Output<H>;
}

impl<H> Sealed<H> for Hmac<H>
impl<H> HmacImpl<H> for Hmac<H>
where
H: CoreProxy + OutputSizeUser,
H::Core: HashMarker
Expand Down Expand Up @@ -65,7 +67,7 @@ where
}
}

impl<H: Digest + BlockSizeUser + Clone> Sealed<H> for SimpleHmac<H> {
impl<H: Digest + BlockSizeUser + Clone> HmacImpl<H> for SimpleHmac<H> {
type Core = Self;

#[inline(always)]
Expand Down
95 changes: 84 additions & 11 deletions hkdf/tests/tests.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,77 @@
use core::iter;

use hex_literal::hex;
use hkdf::HmacImpl;
use hkdf::{Hkdf, HkdfExtract, SimpleHkdf, SimpleHkdfExtract};
use hmac::digest::KeyInit;
use hmac::Hmac;
use sha1::Sha1;
use sha2::digest::{Digest, FixedOutput, Output};
use sha2::{Sha256, Sha384, Sha512};
use std::convert::TryInto;
use std::sync::atomic::{AtomicBool, Ordering};

// Those functions are provided by hardware. We implement them for testing purposes only.
fn hw_hash(input: &[u8], output: &mut [u8; 32]) {
output.copy_from_slice(&Sha256::digest(input));
}
fn hw_init(key: &[u8]) -> Result<(), ()> {
let ctxt = unsafe { &mut HW_CTXT };
*ctxt = Some(<Hmac<_> as KeyInit>::new_from_slice(key).map_err(|_| ())?);
Ok(())
}
fn hw_update(data: &[u8]) -> Result<(), ()> {
let ctxt = unsafe { &mut HW_CTXT }.as_mut().ok_or(())?;
ctxt.update(data);
Ok(())
}
fn hw_finalize(output: &mut [u8; 32]) -> Result<(), ()> {
let ctxt = unsafe { &mut HW_CTXT }.take().ok_or(())?;
ctxt.finalize_into(output.into());
Ok(())
}
// The hmac context is stored in hardware (unique and global). We need to run the tests with a
// single thread because of that: `cargo test -- --test-threads=1`.
static mut HW_CTXT: Option<Hmac<Sha256>> = None;

// How the user would implement HmacImpl on top of hardware to be used in HKDF.
struct HmacContext; // should not be publicly constructible
static HMAC_INITIALIZED: AtomicBool = AtomicBool::new(false);
impl HmacImpl<Sha256> for HmacContext {
type Core = [u8; 64];

fn new_from_slice(key: &[u8]) -> Self {
Self::from_core(&Self::new_core(key))
}

fn new_core(key: &[u8]) -> Self::Core {
let mut core = [0; 64];
if key.len() <= 64 {
core[..key.len()].copy_from_slice(key);
} else {
hw_hash(key, (&mut core[..32]).try_into().unwrap());
}
core
}

fn from_core(core: &Self::Core) -> Self {
assert!(!HMAC_INITIALIZED.swap(true, Ordering::SeqCst));
hw_init(core).unwrap(); // RustCrypto API does not support errors.
HmacContext
}

fn update(&mut self, data: &[u8]) {
assert!(HMAC_INITIALIZED.load(Ordering::SeqCst));
hw_update(data).unwrap(); // RustCrypto API does not support errors.
}

fn finalize(self) -> Output<Sha256> {
assert!(HMAC_INITIALIZED.swap(false, Ordering::SeqCst));
let mut output = [0; 32];
hw_finalize(&mut output).unwrap(); // RustCrypto API does not support errors.
output.into()
}
}

struct Test<'a> {
ikm: &'a [u8],
Expand Down Expand Up @@ -91,15 +159,15 @@ fn test_rfc5869_sha256() {
} else {
Some(&salt[..])
};
let (prk2, hkdf) = Hkdf::<Sha256>::extract(salt, ikm);
let (prk2, hkdf) = Hkdf::<Sha256, HmacContext>::extract(salt, ikm);
let mut okm2 = vec![0u8; okm.len()];
assert!(hkdf.expand(&info[..], &mut okm2).is_ok());

assert_eq!(prk2[..], prk[..]);
assert_eq!(okm2[..], okm[..]);

okm2.iter_mut().for_each(|b| *b = 0);
let hkdf = Hkdf::<Sha256>::from_prk(prk).unwrap();
let hkdf = Hkdf::<Sha256, HmacContext>::from_prk(prk).unwrap();
assert!(hkdf.expand(&info[..], &mut okm2).is_ok());
assert_eq!(okm2[..], okm[..]);
}
Expand Down Expand Up @@ -203,7 +271,7 @@ const MAX_SHA256_LENGTH: usize = 255 * (256 / 8); // =8160

#[test]
fn test_lengths() {
let hkdf = Hkdf::<Sha256>::new(None, &[]);
let hkdf = Hkdf::<Sha256, HmacContext>::new(None, &[]);
let mut longest = vec![0u8; MAX_SHA256_LENGTH];
assert!(hkdf.expand(&[], &mut longest).is_ok());
// Runtime is O(length), so exhaustively testing all legal lengths
Expand All @@ -222,21 +290,21 @@ fn test_lengths() {

#[test]
fn test_max_length() {
let hkdf = Hkdf::<Sha256>::new(Some(&[]), &[]);
let hkdf = Hkdf::<Sha256, HmacContext>::new(Some(&[]), &[]);
let mut okm = vec![0u8; MAX_SHA256_LENGTH];
assert!(hkdf.expand(&[], &mut okm).is_ok());
}

#[test]
fn test_max_length_exceeded() {
let hkdf = Hkdf::<Sha256>::new(Some(&[]), &[]);
let hkdf = Hkdf::<Sha256, HmacContext>::new(Some(&[]), &[]);
let mut okm = vec![0u8; MAX_SHA256_LENGTH + 1];
assert!(hkdf.expand(&[], &mut okm).is_err());
}

#[test]
fn test_unsupported_length() {
let hkdf = Hkdf::<Sha256>::new(Some(&[]), &[]);
let hkdf = Hkdf::<Sha256, HmacContext>::new(Some(&[]), &[]);
let mut okm = vec![0u8; 90000];
assert!(hkdf.expand(&[], &mut okm).is_err());
}
Expand All @@ -247,7 +315,7 @@ fn test_prk_too_short() {

let output_len = Sha256::output_size();
let prk = vec![0; output_len - 1];
assert!(Hkdf::<Sha256>::from_prk(&prk).is_err());
assert!(Hkdf::<Sha256, HmacContext>::from_prk(&prk).is_err());
}

#[test]
Expand Down Expand Up @@ -284,7 +352,7 @@ fn test_expand_multi_info() {
&b"1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d"[..],
];

let (_, hkdf_ctx) = Hkdf::<Sha256>::extract(None, b"some ikm here");
let (_, hkdf_ctx) = Hkdf::<Sha256, HmacContext>::extract(None, b"some ikm here");

// Compute HKDF-Expand on the concatenation of all the info components
let mut oneshot_res = [0u8; 16];
Expand Down Expand Up @@ -328,7 +396,8 @@ fn test_extract_streaming() {
let salt = b"mysalt";

// Compute HKDF-Extract on the concatenation of all the IKM components
let (oneshot_res, _) = Hkdf::<Sha256>::extract(Some(&salt[..]), &ikm_components.concat());
let (oneshot_res, _) =
Hkdf::<Sha256, HmacContext>::extract(Some(&salt[..]), &ikm_components.concat());

// Now iteratively join the components of ikm_components until it's all 1 component. The value
// of HKDF-Extract should be the same throughout
Expand All @@ -340,7 +409,7 @@ fn test_extract_streaming() {

// Make a new extraction context and build the new input to be the IKM head followed by the
// remaining components
let mut extract_ctx = HkdfExtract::<Sha256>::new(Some(&salt[..]));
let mut extract_ctx = HkdfExtract::<Sha256, HmacContext>::new(Some(&salt[..]));
let input = iter::once(ikm_head.as_slice())
.chain(ikm_components.iter().cloned().skip(num_concatted + 1));

Expand Down Expand Up @@ -422,7 +491,11 @@ macro_rules! new_test {
}

new_test!(wycheproof_sha1, "wycheproof-sha1", Hkdf::<Sha1>);
new_test!(wycheproof_sha256, "wycheproof-sha256", Hkdf::<Sha256>);
new_test!(
wycheproof_sha256,
"wycheproof-sha256",
Hkdf::<Sha256, HmacContext>
);
new_test!(wycheproof_sha384, "wycheproof-sha384", Hkdf::<Sha384>);
new_test!(wycheproof_sha512, "wycheproof-sha512", Hkdf::<Sha512>);

Expand Down

0 comments on commit 34fcb85

Please sign in to comment.