bssl_crypto/hkdf.rs
1/* Copyright 2023 The BoringSSL Authors
2 *
3 * Permission to use, copy, modify, and/or distribute this software for any
4 * purpose with or without fee is hereby granted, provided that the above
5 * copyright notice and this permission notice appear in all copies.
6 *
7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 */
15
16//! Implements the HMAC-based Key Derivation Function from
17//! <https://datatracker.ietf.org/doc/html/rfc5869>.
18//!
19//! One-shot operation:
20//!
21//! ```
22//! use bssl_crypto::{hkdf, hkdf::HkdfSha256};
23//!
24//! let key: [u8; 32] = HkdfSha256::derive(b"secret", hkdf::Salt::NonEmpty(b"salt"),
25//! b"info");
26//! ```
27//!
28//! If deriving several keys that vary only in the `info` parameter, then part
29//! of the computation can be shared by calculating the "pseudo-random key".
30//! This is purely a performance optimisation.
31//!
32//! ```
33//! use bssl_crypto::{hkdf, hkdf::HkdfSha256};
34//!
35//! let prk = HkdfSha256::extract(b"secret", hkdf::Salt::NonEmpty(b"salt"));
36//! let key1 : [u8; 32] = prk.expand(b"info1");
37//! let key2 : [u8; 32] = prk.expand(b"info2");
38//!
39//! assert_eq!(key1, HkdfSha256::derive(b"secret", hkdf::Salt::NonEmpty(b"salt"),
40//! b"info1"));
41//! assert_eq!(key2, HkdfSha256::derive(b"secret", hkdf::Salt::NonEmpty(b"salt"),
42//! b"info2"));
43//! ```
44//!
45//! The above examples assume that the size of the outputs is known at compile
46//! time. (And only output lengths less than 256 bytes are supported.)
47//!
48//! ```compile_fail
49//! use bssl_crypto::{hkdf, hkdf::HkdfSha256};
50//!
51//! let key: [u8; 256] = HkdfSha256::derive(b"secret", hkdf::Salt::None, b"info");
52//! ```
53//!
54//! To use HKDF with longer, or run-time, lengths, use `derive_into` and
55//! `extract_into`:
56//!
57//! ```
58//! use bssl_crypto::{hkdf, hkdf::HkdfSha256};
59//!
60//! let mut out = [0u8; 50];
61//! HkdfSha256::derive_into(b"secret", hkdf::Salt::None, b"info", &mut out).expect(
62//! "HKDF can't produce that much");
63//!
64//! assert_eq!(out, HkdfSha256::derive(b"secret", hkdf::Salt::None, b"info"));
65//! ```
66//!
67//! To expand output from the explicit bytes of a PRK, use `Prk::new`:
68//!
69//! ```
70//! use bssl_crypto::{digest::Sha256, digest::Algorithm, hkdf};
71//!
72//! let prk: [u8; Sha256::OUTPUT_LEN] = bssl_crypto::rand_array();
73//! // unwrap: only fails if the input is not equal to the digest length, which
74//! // cannot happen here.
75//! let prk = hkdf::Prk::new::<Sha256>(&prk).unwrap();
76//! let mut out = vec![0u8; 42];
77//! prk.expand_into(b"info", &mut out)?;
78//! # Ok::<(), hkdf::TooLong>(())
79//! ```
80
81use crate::{digest, sealed, with_output_array, FfiMutSlice, FfiSlice, ForeignTypeRef};
82use core::marker::PhantomData;
83
84/// Implementation of HKDF-SHA-256
85pub type HkdfSha256 = Hkdf<digest::Sha256>;
86
87/// Implementation of HKDF-SHA-512
88pub type HkdfSha512 = Hkdf<digest::Sha512>;
89
90/// Error type returned when too much output is requested from an HKDF operation.
91#[derive(Debug)]
92pub struct TooLong;
93
94/// HKDF's optional salt values. See <https://datatracker.ietf.org/doc/html/rfc5869#section-3.1>
95pub enum Salt<'a> {
96 /// No salt.
97 None,
98 /// An explicit salt. Note that an empty value here is interpreted the same
99 /// as if passing `None`.
100 NonEmpty(&'a [u8]),
101}
102
103impl Salt<'_> {
104 fn as_ffi_ptr(&self) -> *const u8 {
105 match self {
106 Salt::None => core::ptr::null(),
107 Salt::NonEmpty(salt) => salt.as_ffi_ptr(),
108 }
109 }
110
111 fn len(&self) -> usize {
112 match self {
113 Salt::None => 0,
114 Salt::NonEmpty(salt) => salt.len(),
115 }
116 }
117}
118
119/// HKDF for any of the implemented hash functions. The aliases [`HkdfSha256`]
120/// and [`HkdfSha512`] are provided for the most common cases.
121pub struct Hkdf<MD: digest::Algorithm>(PhantomData<MD>);
122
123impl<MD: digest::Algorithm> Hkdf<MD> {
124 /// The maximum number of bytes of key material that can be produced.
125 pub const MAX_OUTPUT_LEN: usize = MD::OUTPUT_LEN * 255;
126
127 /// Derive key material from the given secret, salt, and info. Attempting
128 /// to derive more than 255 bytes is a compile-time error, see `derive_into`
129 /// for longer outputs.
130 ///
131 /// The semantics of the arguments are complex. See
132 /// <https://datatracker.ietf.org/doc/html/rfc5869#section-3>.
133 pub fn derive<const N: usize>(secret: &[u8], salt: Salt, info: &[u8]) -> [u8; N] {
134 Self::extract(secret, salt).expand(info)
135 }
136
137 /// Derive key material from the given secret, salt, and info. Attempting
138 /// to derive more than `MAX_OUTPUT_LEN` bytes is a run-time error.
139 ///
140 /// The semantics of the arguments are complex. See
141 /// <https://datatracker.ietf.org/doc/html/rfc5869#section-3>.
142 pub fn derive_into(
143 secret: &[u8],
144 salt: Salt,
145 info: &[u8],
146 out: &mut [u8],
147 ) -> Result<(), TooLong> {
148 Self::extract(secret, salt).expand_into(info, out)
149 }
150
151 /// Extract a pseudo-random key from the given secret and salt. This can
152 /// be used to avoid redoing computation when computing several keys that
153 /// vary only in the `info` parameter.
154 pub fn extract(secret: &[u8], salt: Salt) -> Prk {
155 let mut prk = [0u8; bssl_sys::EVP_MAX_MD_SIZE as usize];
156 let mut prk_len = 0usize;
157 let evp_md = MD::get_md(sealed::Sealed).as_ptr();
158 unsafe {
159 // Safety: `EVP_MAX_MD_SIZE` is the maximum output size of
160 // `HKDF_extract` so it'll never overrun the buffer.
161 bssl_sys::HKDF_extract(
162 prk.as_mut_ffi_ptr(),
163 &mut prk_len,
164 evp_md,
165 secret.as_ffi_ptr(),
166 secret.len(),
167 salt.as_ffi_ptr(),
168 salt.len(),
169 );
170 }
171 // This is documented to be always be true.
172 assert!(prk_len <= prk.len());
173 Prk {
174 prk,
175 len: prk_len,
176 evp_md,
177 }
178 }
179}
180
181/// A pseudo-random key, an intermediate value in the HKDF computation.
182pub struct Prk {
183 prk: [u8; bssl_sys::EVP_MAX_MD_SIZE as usize],
184 len: usize,
185 evp_md: *const bssl_sys::EVP_MD,
186}
187
188#[allow(clippy::let_unit_value,
189 clippy::unwrap_used,
190)]
191impl Prk {
192 /// Creates a Prk from bytes.
193 pub fn new<MD: digest::Algorithm>(prk_bytes: &[u8]) -> Option<Self> {
194 if prk_bytes.len() != MD::OUTPUT_LEN {
195 return None;
196 }
197
198 let mut prk = [0u8; bssl_sys::EVP_MAX_MD_SIZE as usize];
199 prk.get_mut(..MD::OUTPUT_LEN)
200 // unwrap: `EVP_MAX_MD_SIZE` must be greater than the length of any
201 // digest function thus this is always successful.
202 .unwrap()
203 .copy_from_slice(prk_bytes);
204
205 Some(Prk {
206 prk,
207 len: MD::OUTPUT_LEN,
208 evp_md: MD::get_md(sealed::Sealed).as_ptr(),
209 })
210 }
211
212 /// Returns the bytes of the pseudorandom key.
213 pub fn as_bytes(&self) -> &[u8] {
214 self.prk.get(..self.len)
215 // unwrap:`self.len` must be less than the length of `self.prk` thus
216 // this is always in bounds.
217 .unwrap()
218 }
219
220 /// Derive key material for the given info parameter. Attempting
221 /// to derive more than 255 bytes is a compile-time error, see `expand_into`
222 /// for longer outputs.
223 pub fn expand<const N: usize>(&self, info: &[u8]) -> [u8; N] {
224 // This is the odd way to write a static assertion that uses a const
225 // parameter in Rust. Even then, Rust cannot reference `MAX_OUTPUT_LEN`.
226 // But if we safely assume that all hash functions output at least a
227 // byte then 255 is a safe lower bound on `MAX_OUTPUT_LEN`.
228 // A doctest at the top of the module checks that this assert is effective.
229 struct StaticAssert<const N: usize, const BOUND: usize>;
230 impl<const N: usize, const BOUND: usize> StaticAssert<N, BOUND> {
231 const BOUNDS_CHECK: () = assert!(N < BOUND, "Large outputs not supported");
232 }
233 let _ = StaticAssert::<N, 256>::BOUNDS_CHECK;
234
235 unsafe {
236 with_output_array(|out, out_len| {
237 // Safety: `HKDF_expand` writes exactly `out_len` bytes or else
238 // returns zero. `evp_md` is valid by construction.
239 let result = bssl_sys::HKDF_expand(
240 out,
241 out_len,
242 self.evp_md,
243 self.prk.as_ffi_ptr(),
244 self.len,
245 info.as_ffi_ptr(),
246 info.len(),
247 );
248 // The output length is known to be within bounds so the only other
249 // possibily is an allocation failure, which we don't attempt to
250 // handle.
251 assert_eq!(result, 1);
252 })
253 }
254 }
255
256 /// Derive key material from the given info parameter. Attempting
257 /// to derive more than the HKDF's `MAX_OUTPUT_LEN` bytes is a run-time
258 /// error.
259 pub fn expand_into(&self, info: &[u8], out: &mut [u8]) -> Result<(), TooLong> {
260 // Safety: writes at most `out.len()` bytes into `out`.
261 // `evp_md` is valid by construction.
262 let result = unsafe {
263 bssl_sys::HKDF_expand(
264 out.as_mut_ffi_ptr(),
265 out.len(),
266 self.evp_md,
267 self.prk.as_ffi_ptr(),
268 self.len,
269 info.as_ffi_ptr(),
270 info.len(),
271 )
272 };
273 if result == 1 {
274 Ok(())
275 } else {
276 Err(TooLong)
277 }
278 }
279}
280
281#[cfg(test)]
282#[allow(
283 clippy::expect_used,
284 clippy::panic,
285 clippy::indexing_slicing,
286 clippy::unwrap_used
287)]
288mod tests {
289 use crate::{
290 digest::Sha256,
291 hkdf::{HkdfSha256, HkdfSha512, Prk, Salt},
292 test_helpers::{decode_hex, decode_hex_into_vec},
293 };
294
295 #[test]
296 fn sha256() {
297 let ikm = decode_hex_into_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
298 let salt_vec = decode_hex_into_vec("000102030405060708090a0b0c");
299 let salt = Salt::NonEmpty(&salt_vec);
300 let info = decode_hex_into_vec("f0f1f2f3f4f5f6f7f8f9");
301 let okm: [u8; 42] = HkdfSha256::derive(ikm.as_slice(), salt, info.as_slice());
302 let expected = decode_hex(
303 "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
304 );
305 assert_eq!(okm, expected);
306 }
307
308 #[test]
309 fn sha512() {
310 let ikm = decode_hex_into_vec("5d3db20e8238a90b62a600fa57fdb318");
311 let salt_vec = decode_hex_into_vec("1d6f3b38a1e607b5e6bcd4af1800a9d3");
312 let salt = Salt::NonEmpty(&salt_vec);
313 let info = decode_hex_into_vec("2bc5f39032b6fc87da69ba8711ce735b169646fd");
314 let okm: [u8; 42] = HkdfSha512::derive(ikm.as_slice(), salt, info.as_slice());
315 let expected = decode_hex(
316 "8c3cf7122dcb5eb7efaf02718f1faf70bca20dcb75070e9d0871a413a6c05fc195a75aa9ffc349d70aae",
317 );
318 assert_eq!(okm, expected);
319 }
320
321 // Test Vectors from https://tools.ietf.org/html/rfc5869.
322 #[test]
323 fn rfc5869_sha256() {
324 struct Test {
325 ikm: Vec<u8>,
326 salt: Vec<u8>,
327 info: Vec<u8>,
328 prk: Vec<u8>,
329 okm: Vec<u8>,
330 }
331 let tests = [
332 Test {
333 // Test Case 1
334 ikm: decode_hex_into_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
335 salt: decode_hex_into_vec("000102030405060708090a0b0c"),
336 info: decode_hex_into_vec("f0f1f2f3f4f5f6f7f8f9"),
337 prk: decode_hex_into_vec(
338 "077709362c2e32df0ddc3f0dc47bba63\
339 90b6c73bb50f9c3122ec844ad7c2b3e5",
340 ),
341 okm: decode_hex_into_vec("3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865")
342 },
343 Test {
344 // Test Case 2
345 ikm: decode_hex_into_vec(
346 "000102030405060708090a0b0c0d0e0f\
347 101112131415161718191a1b1c1d1e1f\
348 202122232425262728292a2b2c2d2e2f\
349 303132333435363738393a3b3c3d3e3f\
350 404142434445464748494a4b4c4d4e4f",
351 ),
352 salt: decode_hex_into_vec(
353 "606162636465666768696a6b6c6d6e6f\
354 707172737475767778797a7b7c7d7e7f\
355 808182838485868788898a8b8c8d8e8f\
356 909192939495969798999a9b9c9d9e9f\
357 a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
358 ),
359 info: decode_hex_into_vec(
360 "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf\
361 c0c1c2c3c4c5c6c7c8c9cacbcccdcecf\
362 d0d1d2d3d4d5d6d7d8d9dadbdcdddedf\
363 e0e1e2e3e4e5e6e7e8e9eaebecedeeef\
364 f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
365 ),
366 prk: decode_hex_into_vec(
367 "06a6b88c5853361a06104c9ceb35b45c\
368 ef760014904671014a193f40c15fc244",
369 ),
370 okm: decode_hex_into_vec(
371 "b11e398dc80327a1c8e7f78c596a4934\
372 4f012eda2d4efad8a050cc4c19afa97c\
373 59045a99cac7827271cb41c65e590e09\
374 da3275600c2f09b8367793a9aca3db71\
375 cc30c58179ec3e87c14c01d5c1f3434f\
376 1d87",
377 )
378 },
379 Test {
380 // Test Case 3
381 ikm: decode_hex_into_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
382 salt: Vec::new(),
383 info: Vec::new(),
384 prk: decode_hex_into_vec(
385 "19ef24a32c717b167f33a91d6f648bdf\
386 96596776afdb6377ac434c1c293ccb04",
387 ),
388 okm: decode_hex_into_vec(
389 "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8"),
390 },
391 ];
392
393 for Test {
394 ikm,
395 salt,
396 info,
397 prk,
398 okm,
399 } in tests.iter()
400 {
401 let salt = if salt.is_empty() {
402 Salt::None
403 } else {
404 Salt::NonEmpty(&salt)
405 };
406 let mut okm2 = vec![0u8; okm.len()];
407 assert!(
408 HkdfSha256::derive_into(ikm.as_slice(), salt, info.as_slice(), &mut okm2).is_ok()
409 );
410 assert_eq!(okm2.as_slice(), okm.as_slice());
411
412 let prk2 = Prk::new::<Sha256>(prk.as_slice()).unwrap();
413 assert_eq!(prk2.as_bytes(), prk.as_slice());
414 let mut okm3 = vec![0u8; okm.len()];
415 let _ = prk2.expand_into(info.as_slice(), &mut okm3);
416 assert_eq!(okm3.as_slice(), okm.as_slice());
417 }
418 }
419
420 #[test]
421 fn max_output() {
422 let hkdf = HkdfSha256::extract(b"", Salt::None);
423 let mut longest = vec![0u8; HkdfSha256::MAX_OUTPUT_LEN];
424 assert!(hkdf.expand_into(b"", &mut longest).is_ok());
425
426 let mut too_long = vec![0u8; HkdfSha256::MAX_OUTPUT_LEN + 1];
427 assert!(hkdf.expand_into(b"", &mut too_long).is_err());
428 }
429
430 #[test]
431 fn wrong_prk_len() {
432 assert!(Prk::new::<Sha256>(
433 decode_hex_into_vec("077709362c2e32df0ddc3f0dc47bba63").as_slice()
434 )
435 .is_none());
436 assert!(Prk::new::<Sha256>(
437 decode_hex_into_vec("077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e590b6c73bb50f9c3122ec844ad7c2b3e5").as_slice())
438 .is_none()
439 );
440 }
441}