Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Illustration for #80 #82

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
11 changes: 3 additions & 8 deletions .github/workflows/hkdf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@ jobs:
targets: ${{ matrix.target }}
- run: cargo build --no-default-features --target ${{ matrix.target }}

minimal-versions:
uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
with:
working-directory: ${{ github.workflow }}

test:
runs-on: ubuntu-latest
strategy:
Expand All @@ -55,6 +50,6 @@ jobs:
with:
toolchain: ${{ matrix.rust }}
- run: cargo check --all-features
- run: cargo test --no-default-features
- run: cargo test
- run: cargo test --all-features
- run: cargo test --no-default-features -- --test-threads=1
- run: cargo test -- --test-threads=1
- run: cargo test --all-features -- --test-threads=1
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