Skip to main content

spideroak_crypto/
hmac.rs

1//! HMAC per [FIPS PUB 198-1]
2//!
3//! [FIPS PUB 198-1]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.198-1.pdf
4
5#![forbid(unsafe_code)]
6
7use core::{cmp, fmt};
8
9use generic_array::{ArrayLength, GenericArray, LengthError};
10use subtle::{Choice, ConstantTimeEq};
11
12use crate::{
13    block::{Block, BlockSize},
14    csprng::{Csprng, Random},
15    hash::{Digest, Hash},
16    import::{ExportError, Import, ImportError},
17    keys::{SecretKey, SecretKeyBytes},
18    zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing},
19};
20
21/// HMAC per [FIPS PUB 198-1] for some hash `H`.
22///
23/// [FIPS PUB 198-1]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.198-1.pdf
24#[derive(Clone, Debug)]
25pub struct Hmac<H> {
26    /// H(ipad).
27    ipad: H,
28    /// H(opad).
29    opad: H,
30}
31
32impl<H: Hash + BlockSize> Hmac<H> {
33    /// Creates an HMAC using the provided `key`.
34    pub fn new(key: &HmacKey<H>) -> Self {
35        let mut key = Zeroizing::new(key.0.clone());
36
37        // Step 4: K_0 ^ ipad (0x36)
38        for v in key.iter_mut() {
39            *v ^= 0x36;
40        }
41        let mut ipad = H::new();
42        ipad.update(key.as_slice());
43
44        // Step 7: K_0 ^ opad (0x5c)
45        for v in key.iter_mut() {
46            *v ^= 0x36 ^ 0x5c;
47        }
48        let mut opad = H::new();
49        opad.update(key.as_slice());
50
51        Self { ipad, opad }
52    }
53
54    /// Writes `data` to the HMAC.
55    #[inline]
56    pub fn update(&mut self, data: &[u8]) {
57        // Step 5: H((K_0 ^ ipad) || text)
58        self.ipad.update(data)
59    }
60
61    /// Returns the authentication tag.
62    #[inline]
63    pub fn tag(mut self) -> Tag<H::DigestSize> {
64        let d = self.ipad.digest();
65        // Step 8: (K_0 ^ opad) || H((K_0 ^ ipad) || text)
66        self.opad.update(&d);
67        // Step 9: H((K_0 ^ opad) || H((K_0 ^ ipad) || text))
68        Tag(self.opad.digest())
69    }
70
71    /// Computes the single-shot tag from `data` using `key`.
72    pub fn mac_multi<I>(key: &HmacKey<H>, data: I) -> Tag<H::DigestSize>
73    where
74        I: IntoIterator<Item: AsRef<[u8]>>,
75    {
76        let mut h = Self::new(key);
77        data.into_iter().for_each(|s| {
78            h.update(s.as_ref());
79        });
80        h.tag()
81    }
82}
83
84/// An [`Hmac`] authentication code.
85#[derive(Clone, Debug)]
86#[repr(transparent)]
87pub struct Tag<N: ArrayLength>(Digest<N>);
88
89impl<N: ArrayLength> Tag<N> {
90    /// Returns the size in bytes of the tag.
91    #[cfg(feature = "committing-aead")]
92    #[cfg_attr(docsrs, doc(cfg(feature = "committing-aead")))]
93    #[allow(clippy::len_without_is_empty)]
94    pub const fn len(&self) -> usize {
95        self.0.len()
96    }
97
98    // NB: this is hidden because the only safe way to use a MAC
99    // is to compare it for equality using `ConstantTimeEq`. It's
100    // needed by the `hkdf` module and `aranya-crypto` crates,
101    // however.
102    #[doc(hidden)]
103    pub fn into_array(self) -> GenericArray<u8, N> {
104        self.0.into_array()
105    }
106}
107
108// NB: this is intentionally not public by default because the
109// only safe way to use a MAC is to compare it for equality using
110// `ConstantTimeEq`. It's needed by the `hkdf` module, however.
111cfg_if::cfg_if! {
112    if #[cfg(feature = "hazmat")] {
113        impl<N: ArrayLength> Tag<N> {
114            /// Returns the tag as a byte slice.
115            ///
116            /// # ⚠️ Warning
117            /// <div class="warning">
118            /// This is a low-level feature. You should not be
119            /// using it unless you understand what you are
120            /// doing.
121            /// </div>
122            #[cfg_attr(docsrs, doc(cfg(feature = "hazmat")))]
123            pub const fn as_bytes(&self) -> &[u8] {
124                self.0.as_bytes()
125            }
126        }
127    } else {
128        impl<N: ArrayLength> Tag<N> {
129            pub(crate) const fn as_bytes(&self) -> &[u8] {
130                self.0.as_bytes()
131            }
132        }
133    }
134}
135
136impl<N: ArrayLength> ConstantTimeEq for Tag<N> {
137    #[inline]
138    fn ct_eq(&self, other: &Self) -> Choice {
139        self.0.ct_eq(&other.0)
140    }
141}
142
143// Required by `crate::test_util::test_mac`.
144impl<'a, N: ArrayLength> TryFrom<&'a [u8]> for Tag<N> {
145    type Error = LengthError;
146
147    fn try_from(tag: &'a [u8]) -> Result<Self, Self::Error> {
148        let digest = GenericArray::try_from_slice(tag)?;
149        Ok(Self(Digest::new(digest.clone())))
150    }
151}
152
153/// An [`Hmac`] key.
154#[repr(transparent)]
155pub struct HmacKey<H: Hash + BlockSize>(Block<H>);
156
157impl<H: Hash + BlockSize> HmacKey<H> {
158    /// Creates an `HmacKey`.
159    pub fn new(key: &[u8]) -> Self {
160        let mut out = Block::<H>::default();
161        if key.len() <= out.len() {
162            // Steps 1 and 3
163            out[..key.len()].copy_from_slice(key);
164        } else {
165            // Step 2
166            let d = H::hash(key);
167            let n = cmp::min(d.len(), out.len());
168            out[..n].copy_from_slice(&d[..n]);
169        };
170        Self(out)
171    }
172}
173
174impl<H: Hash + BlockSize> Clone for HmacKey<H> {
175    #[inline]
176    fn clone(&self) -> Self {
177        Self(self.0.clone())
178    }
179}
180
181impl<H: Hash + BlockSize> SecretKey for HmacKey<H> {
182    type Size = H::BlockSize;
183
184    /// Exports the raw key.
185    ///
186    /// Note: this returns `h(k)` if the original `k` passed to
187    /// [`HmacKey::new`] was longer than the block size of `h`.
188    #[inline]
189    fn try_export_secret(&self) -> Result<SecretKeyBytes<Self::Size>, ExportError> {
190        Ok(SecretKeyBytes::new(self.0.clone()))
191    }
192}
193
194impl<H: Hash + BlockSize> Random for HmacKey<H> {
195    fn random<R: Csprng>(rng: R) -> Self {
196        Self(Block::<H>::random(rng))
197    }
198}
199
200impl<H: Hash + BlockSize> Import<&[u8]> for HmacKey<H> {
201    #[inline]
202    fn import(data: &[u8]) -> Result<Self, ImportError> {
203        Ok(Self::new(data))
204    }
205}
206
207impl<H: Hash + BlockSize> ConstantTimeEq for HmacKey<H> {
208    #[inline]
209    fn ct_eq(&self, other: &Self) -> Choice {
210        self.0.ct_eq(&other.0)
211    }
212}
213
214impl<H: Hash + BlockSize> fmt::Debug for HmacKey<H> {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        f.debug_struct("HmacKey").finish_non_exhaustive()
217    }
218}
219
220impl<H: Hash + BlockSize> ZeroizeOnDrop for HmacKey<H> {}
221impl<H: Hash + BlockSize> Drop for HmacKey<H> {
222    #[inline]
223    fn drop(&mut self) {
224        self.0.zeroize();
225    }
226}
227
228/// Implements [`Hmac`].
229///
230/// # Example
231///
232/// ```rust
233/// use spideroak_crypto::{
234///     block::BlockSize,
235///     hash::{Digest, Hash},
236///     hmac_impl,
237///     oid::consts::HMAC_WITH_SHA2_256,
238///     typenum::{U32, U64},
239/// };
240///
241/// #[derive(Clone, Debug)]
242/// pub struct Sha256;
243///
244/// impl Hash for Sha256 {
245///     type DigestSize = U32;
246///     fn new() -> Self {
247///         Self
248///     }
249///     fn update(&mut self, _data: &[u8]) {
250///         todo!()
251///     }
252///     fn digest(self) -> Digest<Self::DigestSize> {
253///         todo!()
254///     }
255/// }
256///
257/// impl BlockSize for Sha256 {
258///     type BlockSize = U64;
259/// }
260///
261/// hmac_impl!(HmacSha256, "HMAC-SHA-256", Sha256, HMAC_WITH_SHA2_256);
262/// ```
263#[macro_export]
264macro_rules! hmac_impl {
265    ($name:ident, $doc:expr, $hash:ident $(, $oid:ident)? $(,)?) => {
266        #[doc = concat!($doc, ".")]
267        #[derive(Clone, Debug)]
268        pub struct $name($crate::hmac::Hmac<$hash>);
269
270        impl $crate::mac::Mac for $name {
271            type Tag = $crate::hmac::Tag<Self::TagSize>;
272            type TagSize = <$hash as $crate::hash::Hash>::DigestSize;
273
274            type Key = $crate::hmac::HmacKey<$hash>;
275            type KeySize = <$hash as $crate::block::BlockSize>::BlockSize;
276            type MinKeySize = <$hash as $crate::hash::Hash>::DigestSize;
277
278            #[inline]
279            fn new(key: &Self::Key) -> Self {
280                Self($crate::hmac::Hmac::new(key))
281            }
282
283            #[inline]
284            fn try_new(key: &[u8]) -> ::core::result::Result<Self, $crate::keys::InvalidKey> {
285                use $crate::typenum::Unsigned;
286
287                if key.len() < Self::MinKeySize::USIZE {
288                    ::core::result::Result::Err($crate::keys::InvalidKey)
289                } else {
290                    let key = $crate::hmac::HmacKey::<$hash>::new(key);
291                    ::core::result::Result::Ok(Self::new(&key))
292                }
293            }
294
295            #[inline]
296            fn update(&mut self, data: &[u8]) {
297                self.0.update(data)
298            }
299
300            #[inline]
301            fn tag(self) -> Self::Tag {
302                self.0.tag()
303            }
304        }
305
306        $(impl $crate::oid::Identified for $name {
307            const OID: &'static $crate::oid::Oid = $oid;
308        })?
309    };
310}
311pub(crate) use hmac_impl;
312
313#[cfg(test)]
314#[allow(clippy::wildcard_imports)]
315mod tests {
316    macro_rules! hmac_tests {
317        () => {
318            use crate::{
319                oid::consts::{HMAC_WITH_SHA2_256, HMAC_WITH_SHA2_384, HMAC_WITH_SHA2_512},
320                test_util::test_mac,
321            };
322
323            hmac_impl!(HmacSha2_256, "HMAC-SHA256", Sha256, HMAC_WITH_SHA2_256);
324            hmac_impl!(HmacSha2_384, "HMAC-SHA384", Sha384, HMAC_WITH_SHA2_384);
325            hmac_impl!(HmacSha2_512, "HMAC-SHA512", Sha512, HMAC_WITH_SHA2_512);
326
327            test_mac!(hmac_sha256, HmacSha2_256, MacTest::HmacSha256);
328            test_mac!(hmac_sha384, HmacSha2_384, MacTest::HmacSha384);
329            test_mac!(hmac_sha512, HmacSha2_512, MacTest::HmacSha512);
330        };
331    }
332
333    #[cfg(feature = "bearssl")]
334    mod bearssl {
335        use crate::bearssl::{Sha256, Sha384, Sha512};
336        hmac_tests!();
337    }
338
339    mod rust {
340        use crate::rust::{Sha256, Sha384, Sha512};
341        hmac_tests!();
342    }
343}