Skip to main content

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}