blob: 6e35250e1e4bb18519977d683eb90843e867037e [file] [log] [blame] [edit]
// Copyright 2015 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
//! HMAC-based Extract-and-Expand Key Derivation Function.
//!
//! HKDF is specified in [RFC 5869].
//!
//! [RFC 5869]: https://tools.ietf.org/html/rfc5869
//!
//! # Example
//! ```
//! use aws_lc_rs::{aead, hkdf, hmac, rand};
//!
//! // Generate a (non-secret) salt value
//! let mut salt_bytes = [0u8; 32];
//! rand::fill(&mut salt_bytes).unwrap();
//!
//! // Extract pseudo-random key from secret keying materials
//! let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, &salt_bytes);
//! let pseudo_random_key = salt.extract(b"secret input keying material");
//!
//! // Derive HMAC key
//! let hmac_key_material = pseudo_random_key
//! .expand(
//! &[b"hmac contextual info"],
//! hkdf::HKDF_SHA256.hmac_algorithm(),
//! )
//! .unwrap();
//! let hmac_key = hmac::Key::from(hmac_key_material);
//!
//! // Derive UnboundKey for AES-128-GCM
//! let aes_keying_material = pseudo_random_key
//! .expand(&[b"aes contextual info"], &aead::AES_128_GCM)
//! .unwrap();
//! let aead_unbound_key = aead::UnboundKey::from(aes_keying_material);
//! ```
use crate::aws_lc::{HKDF_expand, HKDF};
use crate::error::Unspecified;
use crate::fips::indicator_check;
use crate::{digest, hmac};
use alloc::sync::Arc;
use core::fmt;
use zeroize::Zeroize;
/// An HKDF algorithm.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Algorithm(hmac::Algorithm);
impl Algorithm {
/// The underlying HMAC algorithm.
#[inline]
#[must_use]
pub fn hmac_algorithm(&self) -> hmac::Algorithm {
self.0
}
}
/// HKDF using HMAC-SHA-1. Obsolete.
pub static HKDF_SHA1_FOR_LEGACY_USE_ONLY: Algorithm =
Algorithm(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY);
/// HKDF using HMAC-SHA-256.
pub static HKDF_SHA256: Algorithm = Algorithm(hmac::HMAC_SHA256);
/// HKDF using HMAC-SHA-384.
pub static HKDF_SHA384: Algorithm = Algorithm(hmac::HMAC_SHA384);
/// HKDF using HMAC-SHA-512.
pub static HKDF_SHA512: Algorithm = Algorithm(hmac::HMAC_SHA512);
/// General Salt length's for HKDF don't normally exceed 256 bits.
/// We set the limit to something tolerable, so that the Salt structure can be stack allocatable.
const MAX_HKDF_SALT_LEN: usize = 80;
/// General Info length's for HKDF don't normally exceed 256 bits.
/// We set the default capacity to a value larger than should be needed
/// so that the value passed to |`HKDF_expand`| is only allocated once.
const HKDF_INFO_DEFAULT_CAPACITY_LEN: usize = 300;
/// The maximum output size of a PRK computed by |`HKDF_extract`| is the maximum digest
/// size that can be outputted by *AWS-LC*.
const MAX_HKDF_PRK_LEN: usize = digest::MAX_OUTPUT_LEN;
impl KeyType for Algorithm {
fn len(&self) -> usize {
self.0.digest_algorithm().output_len
}
}
/// A salt for HKDF operations.
pub struct Salt {
algorithm: Algorithm,
bytes: [u8; MAX_HKDF_SALT_LEN],
len: usize,
}
#[allow(clippy::missing_fields_in_debug)]
impl fmt::Debug for Salt {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("hkdf::Salt")
.field("algorithm", &self.algorithm.0)
.finish()
}
}
impl Drop for Salt {
fn drop(&mut self) {
self.bytes.zeroize();
}
}
impl Salt {
/// Constructs a new `Salt` with the given value based on the given digest
/// algorithm.
///
/// Constructing a `Salt` is relatively expensive so it is good to reuse a
/// `Salt` object instead of re-constructing `Salt`s with the same value.
///
// # FIPS
// The following conditions must be met:
// * Algorithm is one of the following:
// * `HKDF_SHA1_FOR_LEGACY_USE_ONLY`
// * `HKDF_SHA256`
// * `HKDF_SHA384`
// * `HKDF_SHA512`
// * `value.len() > 0` is true
//
/// # Panics
/// `new` panics if the salt length exceeds the limit
#[must_use]
pub fn new(algorithm: Algorithm, value: &[u8]) -> Self {
Salt::try_new(algorithm, value).expect("Salt length limit exceeded.")
}
fn try_new(algorithm: Algorithm, value: &[u8]) -> Result<Salt, Unspecified> {
let salt_len = value.len();
if salt_len > MAX_HKDF_SALT_LEN {
return Err(Unspecified);
}
let mut salt_bytes = [0u8; MAX_HKDF_SALT_LEN];
salt_bytes[0..salt_len].copy_from_slice(value);
Ok(Self {
algorithm,
bytes: salt_bytes,
len: salt_len,
})
}
/// The [HKDF-Extract] operation.
///
/// [HKDF-Extract]: https://tools.ietf.org/html/rfc5869#section-2.2
///
/// # Panics
/// Panics if the extract operation is unable to be performed
#[inline]
#[must_use]
pub fn extract(&self, secret: &[u8]) -> Prk {
Prk {
algorithm: self.algorithm,
mode: PrkMode::ExtractExpand {
secret: Arc::from(ZeroizeBoxSlice::from(secret)),
salt: self.bytes,
salt_len: self.len,
},
}
}
/// The algorithm used to derive this salt.
#[inline]
#[must_use]
pub fn algorithm(&self) -> Algorithm {
Algorithm(self.algorithm.hmac_algorithm())
}
}
#[allow(clippy::assertions_on_constants)]
const _: () = assert!(MAX_HKDF_PRK_LEN <= MAX_HKDF_SALT_LEN);
impl From<Okm<'_, Algorithm>> for Salt {
fn from(okm: Okm<'_, Algorithm>) -> Self {
let algorithm = okm.prk.algorithm;
let mut salt_bytes = [0u8; MAX_HKDF_SALT_LEN];
let salt_len = okm.len().len();
okm.fill(&mut salt_bytes[..salt_len]).unwrap();
Self {
algorithm,
bytes: salt_bytes,
len: salt_len,
}
}
}
/// The length of the OKM (Output Keying Material) for a `Prk::expand()` call.
#[allow(clippy::len_without_is_empty)]
pub trait KeyType {
/// The length that `Prk::expand()` should expand its input to.
fn len(&self) -> usize;
}
#[derive(Clone)]
enum PrkMode {
Expand {
key_bytes: [u8; MAX_HKDF_PRK_LEN],
key_len: usize,
},
ExtractExpand {
secret: Arc<ZeroizeBoxSlice<u8>>,
salt: [u8; MAX_HKDF_SALT_LEN],
salt_len: usize,
},
}
impl PrkMode {
fn fill(&self, algorithm: Algorithm, out: &mut [u8], info: &[u8]) -> Result<(), Unspecified> {
let digest = *digest::match_digest_type(&algorithm.0.digest_algorithm().id);
match &self {
PrkMode::Expand { key_bytes, key_len } => unsafe {
if 1 != indicator_check!(HKDF_expand(
out.as_mut_ptr(),
out.len(),
digest,
key_bytes.as_ptr(),
*key_len,
info.as_ptr(),
info.len(),
)) {
return Err(Unspecified);
}
},
PrkMode::ExtractExpand {
secret,
salt,
salt_len,
} => {
if 1 != indicator_check!(unsafe {
HKDF(
out.as_mut_ptr(),
out.len(),
digest,
secret.as_ptr(),
secret.len(),
salt.as_ptr(),
*salt_len,
info.as_ptr(),
info.len(),
)
}) {
return Err(Unspecified);
}
}
}
Ok(())
}
}
impl fmt::Debug for PrkMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Expand { .. } => f.debug_struct("Expand").finish_non_exhaustive(),
Self::ExtractExpand { .. } => f.debug_struct("ExtractExpand").finish_non_exhaustive(),
}
}
}
struct ZeroizeBoxSlice<T: Zeroize>(Box<[T]>);
impl<T: Zeroize> core::ops::Deref for ZeroizeBoxSlice<T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: Clone + Zeroize> From<&[T]> for ZeroizeBoxSlice<T> {
fn from(value: &[T]) -> Self {
Self(Vec::from(value).into_boxed_slice())
}
}
impl<T: Zeroize> Drop for ZeroizeBoxSlice<T> {
fn drop(&mut self) {
self.0.zeroize();
}
}
/// A HKDF PRK (pseudorandom key).
#[derive(Clone)]
pub struct Prk {
algorithm: Algorithm,
mode: PrkMode,
}
#[allow(clippy::missing_fields_in_debug)]
impl fmt::Debug for Prk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("hkdf::Prk")
.field("algorithm", &self.algorithm.0)
.field("mode", &self.mode)
.finish()
}
}
impl Prk {
/// Construct a new `Prk` directly with the given value.
///
/// Usually one can avoid using this. It is useful when the application
/// intentionally wants to leak the PRK secret, e.g. to implement
/// `SSLKEYLOGFILE` functionality.
///
// # FIPS
// This function must not be used.
//
// See [`Salt::extract`].
//
/// # Panics
/// Panics if the given Prk length exceeds the limit
#[must_use]
pub fn new_less_safe(algorithm: Algorithm, value: &[u8]) -> Self {
Prk::try_new_less_safe(algorithm, value).expect("Prk length limit exceeded.")
}
fn try_new_less_safe(algorithm: Algorithm, value: &[u8]) -> Result<Prk, Unspecified> {
let key_len = value.len();
if key_len > MAX_HKDF_PRK_LEN {
return Err(Unspecified);
}
let mut key_bytes = [0u8; MAX_HKDF_PRK_LEN];
key_bytes[0..key_len].copy_from_slice(value);
Ok(Self {
algorithm,
mode: PrkMode::Expand { key_bytes, key_len },
})
}
/// The [HKDF-Expand] operation.
///
/// [HKDF-Expand]: https://tools.ietf.org/html/rfc5869#section-2.3
///
/// # Errors
/// Returns `error::Unspecified` if:
/// * `len` is more than 255 times the digest algorithm's output length.
// # FIPS
// The following conditions must be met:
// * `Prk` must be constructed using `Salt::extract` prior to calling
// this method.
// * After concatination of the `info` slices the resulting `[u8].len() > 0` is true.
#[inline]
pub fn expand<'a, L: KeyType>(
&'a self,
info: &'a [&'a [u8]],
len: L,
) -> Result<Okm<'a, L>, Unspecified> {
let len_cached = len.len();
if len_cached > 255 * self.algorithm.0.digest_algorithm().output_len {
return Err(Unspecified);
}
let mut info_bytes: Vec<u8> = Vec::with_capacity(HKDF_INFO_DEFAULT_CAPACITY_LEN);
let mut info_len = 0;
for &byte_ary in info {
info_bytes.extend_from_slice(byte_ary);
info_len += byte_ary.len();
}
let info_bytes = info_bytes.into_boxed_slice();
Ok(Okm {
prk: self,
info_bytes,
info_len,
len,
})
}
}
impl From<Okm<'_, Algorithm>> for Prk {
fn from(okm: Okm<Algorithm>) -> Self {
let algorithm = okm.len;
let key_len = okm.len.len();
let mut key_bytes = [0u8; MAX_HKDF_PRK_LEN];
okm.fill(&mut key_bytes[0..key_len]).unwrap();
Self {
algorithm,
mode: PrkMode::Expand { key_bytes, key_len },
}
}
}
/// An HKDF OKM (Output Keying Material)
///
/// Intentionally not `Clone` or `Copy` as an OKM is generally only safe to
/// use once.
pub struct Okm<'a, L: KeyType> {
prk: &'a Prk,
info_bytes: Box<[u8]>,
info_len: usize,
len: L,
}
impl<L: KeyType> fmt::Debug for Okm<'_, L> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("hkdf::Okm").field("prk", &self.prk).finish()
}
}
impl<L: KeyType> Drop for Okm<'_, L> {
fn drop(&mut self) {
self.info_bytes.zeroize();
}
}
impl<L: KeyType> Okm<'_, L> {
/// The `OkmLength` given to `Prk::expand()`.
#[inline]
pub fn len(&self) -> &L {
&self.len
}
/// Fills `out` with the output of the HKDF-Expand operation for the given
/// inputs.
///
// # FIPS
// The following conditions must be met:
// * Algorithm is one of the following:
// * `HKDF_SHA1_FOR_LEGACY_USE_ONLY`
// * `HKDF_SHA256`
// * `HKDF_SHA384`
// * `HKDF_SHA512`
// * The [`Okm`] was constructed from a [`Prk`] created with [`Salt::extract`] and:
// * The `value.len()` passed to [`Salt::new`] was non-zero.
// * The `info_len` from [`Prk::expand`] was non-zero.
//
/// # Errors
/// `error::Unspecified` if the requested output length differs from the length specified by
/// `L: KeyType`.
#[inline]
pub fn fill(self, out: &mut [u8]) -> Result<(), Unspecified> {
if out.len() != self.len.len() {
return Err(Unspecified);
}
self.prk
.mode
.fill(self.prk.algorithm, out, &self.info_bytes[..self.info_len])?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::hkdf::{Salt, HKDF_SHA256, HKDF_SHA384};
#[cfg(feature = "fips")]
mod fips;
#[test]
fn hkdf_coverage() {
// Something would have gone horribly wrong for this to not pass, but we test this so our
// coverage reports will look better.
assert_ne!(HKDF_SHA256, HKDF_SHA384);
assert_eq!("Algorithm(Algorithm(SHA256))", format!("{HKDF_SHA256:?}"));
}
#[test]
fn test_debug() {
const SALT: &[u8; 32] = &[
29, 113, 120, 243, 11, 202, 39, 222, 206, 81, 163, 184, 122, 153, 52, 192, 98, 195,
240, 32, 34, 19, 160, 128, 178, 111, 97, 232, 113, 101, 221, 143,
];
const SECRET1: &[u8; 32] = &[
157, 191, 36, 107, 110, 131, 193, 6, 175, 226, 193, 3, 168, 133, 165, 181, 65, 120,
194, 152, 31, 92, 37, 191, 73, 222, 41, 112, 207, 236, 196, 174,
];
const INFO1: &[&[u8]] = &[
&[
2, 130, 61, 83, 192, 248, 63, 60, 211, 73, 169, 66, 101, 160, 196, 212, 250, 113,
],
&[
80, 46, 248, 123, 78, 204, 171, 178, 67, 204, 96, 27, 131, 24,
],
];
let alg = HKDF_SHA256;
let salt = Salt::new(alg, SALT);
let prk = salt.extract(SECRET1);
let okm = prk.expand(INFO1, alg).unwrap();
assert_eq!(
"hkdf::Salt { algorithm: Algorithm(SHA256) }",
format!("{salt:?}")
);
assert_eq!(
"hkdf::Prk { algorithm: Algorithm(SHA256), mode: ExtractExpand { .. } }",
format!("{prk:?}")
);
assert_eq!(
"hkdf::Okm { prk: hkdf::Prk { algorithm: Algorithm(SHA256), mode: ExtractExpand { .. } } }",
format!("{okm:?}")
);
}
}