bssl_crypto/hmac.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//! Hash-based message authentication from <https://datatracker.ietf.org/doc/html/rfc2104>.
17//!
18//! HMAC-SHA256 and HMAC-SHA512 are supported.
19//!
20//! MACs can be computed in a single shot:
21//!
22//! ```
23//! use bssl_crypto::hmac::HmacSha256;
24//!
25//! let mac: [u8; 32] = HmacSha256::mac(b"key", b"hello");
26//! ```
27//!
28//! Or they can be computed incrementally:
29//!
30//! ```
31//! use bssl_crypto::hmac::HmacSha256;
32//!
33//! let key = bssl_crypto::rand_array();
34//! let mut ctx = HmacSha256::new(&key);
35//! ctx.update(b"hel");
36//! ctx.update(b"lo");
37//! let mac: [u8; 32] = ctx.digest();
38//! ```
39//!
40//! **WARNING** comparing MACs using typical methods will often leak information
41//! about the size of the matching prefix. Use the `verify` method instead.
42//!
43//! If you need to compute many MACs with the same key, contexts can be
44//! cloned:
45//!
46//! ```
47//! use bssl_crypto::hmac::HmacSha256;
48//!
49//! let key = bssl_crypto::rand_array();
50//! let mut keyed_ctx = HmacSha256::new(&key);
51//! let mut ctx1 = keyed_ctx.clone();
52//! ctx1.update(b"foo");
53//! let mut ctx2 = keyed_ctx.clone();
54//! ctx2.update(b"foo");
55//!
56//! assert_eq!(ctx1.digest(), ctx2.digest());
57//! ```
58
59use crate::{
60 digest,
61 digest::{Sha256, Sha512},
62 initialized_struct, sealed, FfiMutSlice, FfiSlice, ForeignTypeRef as _, InvalidSignatureError,
63};
64use core::{ffi::c_uint, marker::PhantomData, ptr};
65
66/// HMAC-SHA256.
67pub struct HmacSha256(Hmac<32, Sha256>);
68
69impl HmacSha256 {
70 /// Computes the HMAC-SHA256 of `data` as a one-shot operation.
71 pub fn mac(key: &[u8], data: &[u8]) -> [u8; 32] {
72 hmac::<32, Sha256>(key, data)
73 }
74
75 /// Creates a new HMAC-SHA256 operation from a fixed-length key.
76 pub fn new(key: &[u8; 32]) -> Self {
77 Self(Hmac::new(key))
78 }
79
80 /// Creates a new HMAC-SHA256 operation from a variable-length key.
81 pub fn new_from_slice(key: &[u8]) -> Self {
82 Self(Hmac::new_from_slice(key))
83 }
84
85 /// Hashes the provided input into the HMAC operation.
86 pub fn update(&mut self, data: &[u8]) {
87 self.0.update(data)
88 }
89
90 /// Computes the final HMAC value, consuming the object.
91 pub fn digest(self) -> [u8; 32] {
92 self.0.digest()
93 }
94
95 /// Checks that the provided tag value matches the computed HMAC value.
96 pub fn verify_slice(self, tag: &[u8]) -> Result<(), InvalidSignatureError> {
97 self.0.verify_slice(tag)
98 }
99
100 /// Checks that the provided tag value matches the computed HMAC value.
101 pub fn verify(self, tag: &[u8; 32]) -> Result<(), InvalidSignatureError> {
102 self.0.verify(tag)
103 }
104
105 /// Checks that the provided tag value matches the computed HMAC, truncated to the input tag's
106 /// length.
107 ///
108 /// Truncating an HMAC reduces the security of the construction. Callers must ensure `tag`'s
109 /// length matches the desired HMAC length and security level.
110 pub fn verify_truncated_left(self, tag: &[u8]) -> Result<(), InvalidSignatureError> {
111 self.0.verify_truncated_left(tag)
112 }
113}
114
115#[cfg(feature = "std")]
116impl std::io::Write for HmacSha256 {
117 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
118 self.update(buf);
119 Ok(buf.len())
120 }
121
122 fn flush(&mut self) -> std::io::Result<()> {
123 Ok(())
124 }
125}
126
127impl Clone for HmacSha256 {
128 fn clone(&self) -> Self {
129 HmacSha256(self.0.clone())
130 }
131}
132
133/// HMAC-SHA512.
134pub struct HmacSha512(Hmac<64, Sha512>);
135
136impl HmacSha512 {
137 /// Computes the HMAC-SHA512 of `data` as a one-shot operation.
138 pub fn mac(key: &[u8], data: &[u8]) -> [u8; 64] {
139 hmac::<64, Sha512>(key, data)
140 }
141
142 /// Creates a new HMAC-SHA512 operation from a fixed-size key.
143 pub fn new(key: &[u8; 64]) -> Self {
144 Self(Hmac::new(key))
145 }
146
147 /// Creates a new HMAC-SHA512 operation from a variable-length key.
148 pub fn new_from_slice(key: &[u8]) -> Self {
149 Self(Hmac::new_from_slice(key))
150 }
151
152 /// Hashes the provided input into the HMAC operation.
153 pub fn update(&mut self, data: &[u8]) {
154 self.0.update(data)
155 }
156
157 /// Computes the final HMAC value, consuming the object.
158 pub fn digest(self) -> [u8; 64] {
159 self.0.digest()
160 }
161
162 /// Checks that the provided tag value matches the computed HMAC value.
163 pub fn verify_slice(self, tag: &[u8]) -> Result<(), InvalidSignatureError> {
164 self.0.verify_slice(tag)
165 }
166
167 /// Checks that the provided tag value matches the computed HMAC value.
168 pub fn verify(self, tag: &[u8; 64]) -> Result<(), InvalidSignatureError> {
169 self.0.verify(tag)
170 }
171
172 /// Checks that the provided tag value matches the computed HMAC, truncated to the input tag's
173 /// length.
174 ///
175 /// Truncating an HMAC reduces the security of the construction. Callers must ensure `tag`'s
176 /// length matches the desired HMAC length and security level.
177 pub fn verify_truncated_left(self, tag: &[u8]) -> Result<(), InvalidSignatureError> {
178 self.0.verify_truncated_left(tag)
179 }
180}
181
182#[cfg(feature = "std")]
183impl std::io::Write for HmacSha512 {
184 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
185 self.update(buf);
186 Ok(buf.len())
187 }
188
189 fn flush(&mut self) -> std::io::Result<()> {
190 Ok(())
191 }
192}
193
194impl Clone for HmacSha512 {
195 fn clone(&self) -> Self {
196 HmacSha512(self.0.clone())
197 }
198}
199
200/// Private generically implemented function for computing HMAC as a oneshot operation.
201/// This should only be exposed publicly by types with the correct output size `N` which corresponds
202/// to the output size of the provided generic hash function. Ideally `N` would just come from `MD`,
203/// but this is not possible until the Rust language can support the `min_const_generics` feature.
204/// Until then we will have to pass both separately: https://github.com/rust-lang/rust/issues/60551
205#[inline]
206fn hmac<const N: usize, MD: digest::Algorithm>(key: &[u8], data: &[u8]) -> [u8; N] {
207 let mut out = [0_u8; N];
208 let mut size: c_uint = 0;
209
210 // Safety:
211 // - buf always contains N bytes of space
212 // - If NULL is returned on error we panic immediately
213 let result = unsafe {
214 bssl_sys::HMAC(
215 MD::get_md(sealed::Sealed).as_ptr(),
216 key.as_ffi_void_ptr(),
217 key.len(),
218 data.as_ffi_ptr(),
219 data.len(),
220 out.as_mut_ffi_ptr(),
221 &mut size as *mut c_uint,
222 )
223 };
224 assert_eq!(size as usize, N);
225 assert!(!result.is_null(), "Result of bssl_sys::HMAC was null");
226
227 out
228}
229
230/// Private generically implemented HMAC instance given a generic hash function and a length `N`,
231/// where `N` is the output size of the hash function. This should only be exposed publicly by
232/// wrapper types with the correct output size `N` which corresponds to the output size of the
233/// provided generic hash function. Ideally `N` would just come from `MD`, but this is not possible
234/// until the Rust language can support the `min_const_generics` feature. Until then we will have to
235/// pass both separately: https://github.com/rust-lang/rust/issues/60551
236struct Hmac<const N: usize, MD: digest::Algorithm> {
237 // Safety: this relies on HMAC_CTX being relocatable via `memcpy`, which is
238 // not generally true of BoringSSL types. This is fine to rely on only
239 // because we do not allow any version skew between bssl-crypto and
240 // BoringSSL. It is *not* safe to copy this code in any other project.
241 ctx: bssl_sys::HMAC_CTX,
242 _marker: PhantomData<MD>,
243}
244
245impl<const N: usize, MD: digest::Algorithm> Hmac<N, MD> {
246 /// Creates a new HMAC operation from a fixed-length key.
247 fn new(key: &[u8; N]) -> Self {
248 Self::new_from_slice(key)
249 }
250
251 /// Creates a new HMAC operation from a variable-length key.
252 fn new_from_slice(key: &[u8]) -> Self {
253 let mut ret = Self {
254 // Safety: type checking will ensure that |ctx| is the correct size
255 // for `HMAC_CTX_init`.
256 ctx: unsafe { initialized_struct(|ctx| bssl_sys::HMAC_CTX_init(ctx)) },
257 _marker: Default::default(),
258 };
259
260 // Safety:
261 // - HMAC_Init_ex must be called with an initialized context, which
262 // `HMAC_CTX_init` provides.
263 // - HMAC_Init_ex may return an error if key is null but the md is different from
264 // before. This is avoided here since key is guaranteed to be non-null.
265 // - HMAC_Init_ex returns 0 on allocation failure in which case we panic
266 let result = unsafe {
267 bssl_sys::HMAC_Init_ex(
268 &mut ret.ctx,
269 key.as_ffi_void_ptr(),
270 key.len(),
271 MD::get_md(sealed::Sealed).as_ptr(),
272 ptr::null_mut(),
273 )
274 };
275 assert!(result > 0, "Allocation failure in bssl_sys::HMAC_Init_ex");
276 ret
277 }
278
279 /// Hashes the provided input into the HMAC operation.
280 fn update(&mut self, data: &[u8]) {
281 // Safety: `HMAC_Update` needs an initialized context, but the only way
282 // to create this object is via `new_from_slice`, which ensures that.
283 let result = unsafe { bssl_sys::HMAC_Update(&mut self.ctx, data.as_ffi_ptr(), data.len()) };
284 // HMAC_Update always returns 1.
285 assert_eq!(result, 1, "failure in bssl_sys::HMAC_Update");
286 }
287
288 /// Computes the final HMAC value, consuming the object.
289 fn digest(mut self) -> [u8; N] {
290 let mut buf = [0_u8; N];
291 let mut size: c_uint = 0;
292 // Safety:
293 // - HMAC has a fixed size output of N which will never exceed the length of an N
294 // length array
295 // - `HMAC_Final` needs an initialized context, but the only way
296 // to create this object is via `new_from_slice`, which ensures that.
297 // - on allocation failure we panic
298 let result =
299 unsafe { bssl_sys::HMAC_Final(&mut self.ctx, buf.as_mut_ffi_ptr(), &mut size) };
300 assert!(result > 0, "Allocation failure in bssl_sys::HMAC_Final");
301 assert_eq!(size as usize, N);
302 buf
303 }
304
305 /// Checks that the provided tag value matches the computed HMAC value.
306 fn verify(self, tag: &[u8; N]) -> Result<(), InvalidSignatureError> {
307 self.verify_slice(tag)
308 }
309
310 /// Checks that the provided tag value matches the computed HMAC value.
311 ///
312 /// Returns `Error` if `tag` is not valid or not equal in length
313 /// to MAC's output.
314 fn verify_slice(self, tag: &[u8]) -> Result<(), InvalidSignatureError> {
315 if tag.len() == N {
316 self.verify_truncated_left(tag)
317 } else {
318 Err(InvalidSignatureError)
319 }
320 }
321
322 /// Checks that the provided tag value matches the computed HMAC, truncated to the input tag's
323 /// length.
324 ///
325 /// Returns `Error` if `tag` is not valid or empty.
326 ///
327 /// Truncating an HMAC reduces the security of the construction. Callers must ensure `tag`'s
328 /// length matches the desired HMAC length and security level.
329 fn verify_truncated_left(self, tag: &[u8]) -> Result<(), InvalidSignatureError> {
330 let len = tag.len();
331 if len == 0 || len > N {
332 return Err(InvalidSignatureError);
333 }
334 let calculated = self.digest();
335
336 // Safety: both `calculated` and `tag` must be at least `len` bytes available.
337 // This is true because `len` is the length of `tag` and `len` is <= N,
338 // the length of `calculated`, which is checked above.
339 let result = unsafe {
340 bssl_sys::CRYPTO_memcmp(calculated.as_ffi_void_ptr(), tag.as_ffi_void_ptr(), len)
341 };
342 if result == 0 {
343 Ok(())
344 } else {
345 Err(InvalidSignatureError)
346 }
347 }
348
349 fn clone(&self) -> Self {
350 let mut ret = Self {
351 // Safety: type checking will ensure that |ctx| is the correct size
352 // for `HMAC_CTX_init`.
353 ctx: unsafe { initialized_struct(|ctx| bssl_sys::HMAC_CTX_init(ctx)) },
354 _marker: Default::default(),
355 };
356 // Safety: `ret.ctx` is initialized and `self.ctx` is valid by
357 // construction.
358 let result = unsafe { bssl_sys::HMAC_CTX_copy(&mut ret.ctx, &self.ctx) };
359 assert_eq!(result, 1);
360 ret
361 }
362}
363
364impl<const N: usize, MD: digest::Algorithm> Drop for Hmac<N, MD> {
365 fn drop(&mut self) {
366 unsafe { bssl_sys::HMAC_CTX_cleanup(&mut self.ctx) }
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use super::*;
373 use alloc::boxed::Box;
374
375 #[test]
376 fn hmac_sha256() {
377 let expected: [u8; 32] = [
378 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
379 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
380 0x2e, 0x32, 0xcf, 0xf7,
381 ];
382 let key: [u8; 20] = [0x0b; 20];
383 let data = b"Hi There";
384
385 let mut hmac = HmacSha256::new_from_slice(&key);
386 hmac.update(data);
387 assert_eq!(hmac.digest(), expected);
388
389 let mut hmac = HmacSha256::new_from_slice(&key);
390 hmac.update(&data[..1]);
391 hmac.update(&data[1..]);
392 assert_eq!(hmac.digest(), expected);
393
394 let mut hmac = HmacSha256::new_from_slice(&key);
395 hmac.update(data);
396 assert!(hmac.verify(&expected).is_ok());
397
398 let mut hmac = HmacSha256::new_from_slice(&key);
399 hmac.update(data);
400 assert!(hmac.verify_truncated_left(&expected[..4]).is_ok());
401
402 let mut hmac = HmacSha256::new_from_slice(&key);
403 hmac.update(data);
404 assert!(hmac.verify_truncated_left(&expected[4..8]).is_err());
405
406 let mut hmac = HmacSha256::new_from_slice(&key);
407 hmac.update(&data[..1]);
408 let mut hmac2 = hmac.clone();
409 let mut hmac3 = Box::new(hmac2.clone());
410 hmac.update(&data[1..]);
411 hmac2.update(&data[1..]);
412 hmac3.update(&data[1..]);
413 assert_eq!(hmac.digest(), expected);
414 assert_eq!(hmac2.digest(), expected);
415 assert_eq!(hmac3.digest(), expected);
416 }
417
418 #[test]
419 fn hmac_sha256_fixed_size_key() {
420 let expected_hmac = [
421 0x19, 0x8a, 0x60, 0x7e, 0xb4, 0x4b, 0xfb, 0xc6, 0x99, 0x3, 0xa0, 0xf1, 0xcf, 0x2b,
422 0xbd, 0xc5, 0xba, 0xa, 0xa3, 0xf3, 0xd9, 0xae, 0x3c, 0x1c, 0x7a, 0x3b, 0x16, 0x96,
423 0xa0, 0xb6, 0x8c, 0xf7,
424 ];
425 let key: [u8; 32] = [0x0b; 32];
426 let data = b"Hi There";
427
428 let mut hmac = HmacSha256::new(&key);
429 hmac.update(data);
430 let hmac_result: [u8; 32] = hmac.digest();
431 assert_eq!(&hmac_result, &expected_hmac);
432 }
433
434 #[test]
435 fn hmac_sha512() {
436 let expected: [u8; 64] = [
437 135, 170, 124, 222, 165, 239, 97, 157, 79, 240, 180, 36, 26, 29, 108, 176, 35, 121,
438 244, 226, 206, 78, 194, 120, 122, 208, 179, 5, 69, 225, 124, 222, 218, 168, 51, 183,
439 214, 184, 167, 2, 3, 139, 39, 78, 174, 163, 244, 228, 190, 157, 145, 78, 235, 97, 241,
440 112, 46, 105, 108, 32, 58, 18, 104, 84,
441 ];
442 let key: [u8; 20] = [0x0b; 20];
443 let data = b"Hi There";
444
445 let mut hmac = HmacSha512::new_from_slice(&key);
446 hmac.update(data);
447 assert_eq!(hmac.digest(), expected);
448
449 let mut hmac = HmacSha512::new_from_slice(&key);
450 hmac.update(&data[..1]);
451 hmac.update(&data[1..]);
452 assert_eq!(hmac.digest(), expected);
453
454 let mut hmac = HmacSha512::new_from_slice(&key);
455 hmac.update(data);
456 assert!(hmac.verify(&expected).is_ok());
457
458 let mut hmac = HmacSha512::new_from_slice(&key);
459 hmac.update(data);
460 assert!(hmac.verify_truncated_left(&expected[..4]).is_ok());
461
462 let mut hmac = HmacSha512::new_from_slice(&key);
463 hmac.update(data);
464 assert!(hmac.verify_truncated_left(&expected[4..8]).is_err());
465
466 let mut hmac = HmacSha512::new_from_slice(&key);
467 hmac.update(&data[..1]);
468 let mut hmac2 = hmac.clone();
469 let mut hmac3 = Box::new(hmac.clone());
470 hmac.update(&data[1..]);
471 hmac2.update(&data[1..]);
472 hmac3.update(&data[1..]);
473 assert_eq!(hmac.digest(), expected);
474 assert_eq!(hmac2.digest(), expected);
475 assert_eq!(hmac3.digest(), expected);
476 }
477}