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