1#![no_std]
2#![doc = include_str!("../README.md")]
3#![doc(
4 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
5 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
6)]
7#![cfg_attr(docsrs, feature(doc_cfg))]
8
9use hmac::{
10 Hmac, SimpleHmac,
11 digest::{Output, OutputSizeUser, array::typenum::Unsigned},
12};
13
14mod errors;
15mod hmac_impl;
16
17pub use errors::{InvalidLength, InvalidPrkLength};
18pub use hmac;
19pub use hmac_impl::HmacImpl;
20
21#[cfg(feature = "kdf")]
22pub use kdf::{self, Kdf};
23
24pub type HkdfExtract<H> = GenericHkdfExtract<Hmac<H>>;
26pub type Hkdf<H> = GenericHkdf<Hmac<H>>;
28
29pub type SimpleHkdfExtract<H> = GenericHkdfExtract<SimpleHmac<H>>;
31pub type SimpleHkdf<H> = GenericHkdf<SimpleHmac<H>>;
33
34#[derive(Clone, Debug)]
39pub struct GenericHkdfExtract<H: HmacImpl> {
40 hmac: H,
41}
42
43impl<H: HmacImpl> GenericHkdfExtract<H> {
44 #[must_use]
46 pub fn new(salt: Option<&[u8]>) -> Self {
47 let default_salt = Output::<H>::default();
48 let salt = salt.unwrap_or(&default_salt);
49 let hmac = H::new_from_slice(salt);
50 Self { hmac }
51 }
52
53 pub fn input_ikm(&mut self, ikm: &[u8]) {
55 self.hmac.update(ikm);
56 }
57
58 #[allow(clippy::missing_panics_doc, reason = "PRK size is correct")]
61 pub fn finalize(self) -> (Output<H>, GenericHkdf<H>) {
62 let prk = self.hmac.finalize();
63 let hkdf = GenericHkdf::<H>::from_prk(&prk).expect("PRK size is correct");
64 (prk, hkdf)
65 }
66}
67
68#[cfg(feature = "kdf")]
69impl<H: HmacImpl> Kdf for GenericHkdfExtract<H> {
70 fn derive_key(&self, secret: &[u8], info: &[u8], out: &mut [u8]) -> kdf::Result<()> {
71 let mut extract = self.clone();
72 extract.input_ikm(secret);
73 let (_, hkdf) = extract.finalize();
74 hkdf.expand(info, out).map_err(|_| kdf::Error)
75 }
76}
77
78#[derive(Clone, Debug)]
85pub struct GenericHkdf<H: HmacImpl> {
86 hmac: H,
87}
88
89impl<H: HmacImpl> GenericHkdf<H> {
90 #[must_use]
94 pub fn new(salt: Option<&[u8]>, ikm: &[u8]) -> Self {
95 let (_, hkdf) = Self::extract(salt, ikm);
96 hkdf
97 }
98
99 pub fn from_prk(prk: &[u8]) -> Result<Self, InvalidPrkLength> {
105 let hash_len = <H as OutputSizeUser>::OutputSize::to_usize();
107 if prk.len() < hash_len {
108 return Err(InvalidPrkLength);
109 }
110 let hmac = H::new_from_slice(prk);
111 Ok(Self { hmac })
112 }
113
114 #[must_use]
117 pub fn extract(salt: Option<&[u8]>, ikm: &[u8]) -> (Output<H>, Self) {
118 let mut extract_ctx = GenericHkdfExtract::<H>::new(salt);
119 extract_ctx.input_ikm(ikm);
120 extract_ctx.finalize()
121 }
122
123 #[allow(clippy::missing_panics_doc, reason = "expect should not fail")]
130 pub fn expand_multi_info(
131 &self,
132 info_components: &[&[u8]],
133 okm: &mut [u8],
134 ) -> Result<(), InvalidLength> {
135 let mut prev: Option<Output<H>> = None;
136
137 let chunk_len = <H as OutputSizeUser>::OutputSize::USIZE;
138 if okm.len() > chunk_len * 255 {
139 return Err(InvalidLength);
140 }
141
142 for (block_n, block) in okm.chunks_mut(chunk_len).enumerate() {
143 let mut hmac = self.hmac.clone();
144
145 if let Some(ref prev) = prev {
146 hmac.update(prev);
147 };
148
149 for info in info_components {
152 hmac.update(info);
153 }
154
155 hmac.update(&[u8::try_from(block_n).expect("should convert") + 1]);
156
157 let output = hmac.finalize();
158
159 let block_len = block.len();
160 block.copy_from_slice(&output[..block_len]);
161
162 prev = Some(output);
163 }
164
165 Ok(())
166 }
167
168 pub fn expand(&self, info: &[u8], okm: &mut [u8]) -> Result<(), InvalidLength> {
175 self.expand_multi_info(&[info], okm)
176 }
177}