/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
//! HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
//! https://tools.ietf.org/html/rfc5869
use core::fmt;
use digest::{
crypto_common::BlockSizeUser, typenum::Unsigned, Digest, Output, OutputSizeUser, Update,
};
use super::hmac::Hmac;
/// Structure for InvalidPrkLength, used for output error handling.
#[derive(Copy, Clone, Debug)]
pub struct InvalidPrkLength;
impl fmt::Display for InvalidPrkLength {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.write_str("invalid pseudorandom key length, too short")
}
}
/// Structure for InvalidLength, used for output error handling.
#[derive(Copy, Clone, Debug)]
pub struct InvalidLength;
impl fmt::Display for InvalidLength {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.write_str("invalid number of blocks, too large output")
}
}
/// HKDF-Extract for arbitrary hash functions implementing `Digest`
/// and `BlockSizeUser` traits.
#[derive(Clone)]
pub struct HkdfExtract {
hmac: Hmac,
}
impl HkdfExtract {
/// Iniitialize a new `HkdfExtract` with the given salt.
pub fn new(salt: &[u8]) -> Self {
Self { hmac: Hmac::::new_from_slice(salt) }
}
/// Feeds in additional input key material to the HKDF-Extract context.
pub fn input_ikm(&mut self, ikm: &[u8]) {
self.hmac.update(ikm);
}
/// Completes the HKDF-Extract operation, returning both the generated
/// pseudorandom key and `Hkdf` struct for expanding.
pub fn finalize(self) -> (Output, Hkdf) {
let prk = self.hmac.finalize();
let hkdf = Hkdf::from_prk(&prk).expect("PRK size is correct");
(prk, hkdf)
}
}
/// Structure representing the HKDF, capable of HKDF-Expand
/// and HKDF-Extract operations.
#[derive(Clone)]
pub struct Hkdf {
hmac: Hmac,
}
impl Hkdf {
/// Convenience method for `extract` when the generated pseudorandom
/// key can be ignored and only the HKDF-Expand operation is needed.
pub fn new(salt: &[u8], ikm: &[u8]) -> Self {
let (_, hkdf) = Self::extract(salt, ikm);
hkdf
}
/// HKDF-Extract operation returning both the generated pseudorandom
/// key and `Hkdf` struct for expanding.
pub fn extract(salt: &[u8], ikm: &[u8]) -> (Output, Self) {
let mut extract_ctx = HkdfExtract::new(salt);
extract_ctx.input_ikm(ikm);
extract_ctx.finalize()
}
/// Create `Hkdf` from an already cryptographically strong pseudorandom key.
pub fn from_prk(prk: &[u8]) -> Result {
if prk.len() < ::OutputSize::to_usize() {
return Err(InvalidPrkLength)
}
Ok(Self { hmac: Hmac::::new_from_slice(prk) })
}
/// HKDF-Expand operation. If you don't have any `info` to pass, use
/// an empty slice.
pub fn expand(&self, info: &[u8], okm: &mut [u8]) -> Result<(), InvalidLength> {
self.expand_multi_info(&[info], okm)
}
pub fn expand_multi_info(&self, infos: &[&[u8]], okm: &mut [u8]) -> Result<(), InvalidLength> {
let mut prev: Option