Skip to main content

ring_native_ossl/
hmac.rs

1//! HMAC message authentication, mirroring `ring::hmac`.
2//!
3//! Provides HMAC-SHA256, HMAC-SHA384, and HMAC-SHA512.  Key material is stored
4//! in [`native_ossl::util::SecretBuf`] and is erased on drop.  The
5//! [`verify`] function performs a constant-time comparison to prevent
6//! timing-based forgery attacks.
7
8use crate::error::Unspecified;
9use native_ossl::digest::DigestAlg;
10use native_ossl::mac::HmacCtx;
11use native_ossl::util::SecretBuf;
12
13/// HMAC algorithm descriptor, mirroring `ring::hmac::Algorithm`.
14#[derive(Debug, Clone, Copy)]
15pub struct Algorithm(pub(crate) &'static crate::digest::Algorithm);
16
17pub static HMAC_SHA256: Algorithm = Algorithm(&crate::digest::SHA256);
18pub static HMAC_SHA384: Algorithm = Algorithm(&crate::digest::SHA384);
19pub static HMAC_SHA512: Algorithm = Algorithm(&crate::digest::SHA512);
20
21/// An HMAC signing key, mirroring `ring::hmac::Key`.
22pub struct Key {
23    // Debug implemented manually below to redact key material
24    alg: Algorithm,
25    key_bytes: SecretBuf,
26}
27
28impl std::fmt::Debug for Key {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        f.debug_struct("Key")
31            .field("alg", &self.alg)
32            .finish_non_exhaustive()
33    }
34}
35
36impl Key {
37    #[must_use]
38    pub fn new(algorithm: Algorithm, key_value: &[u8]) -> Self {
39        Self {
40            alg: algorithm,
41            key_bytes: SecretBuf::from_slice(key_value),
42        }
43    }
44
45    #[must_use]
46    pub fn algorithm(&self) -> Algorithm {
47        self.alg
48    }
49}
50
51/// An HMAC tag (output), mirroring `ring::hmac::Tag`.
52#[derive(Debug)]
53pub struct Tag {
54    bytes: [u8; 64],
55    len: usize,
56}
57
58impl AsRef<[u8]> for Tag {
59    fn as_ref(&self) -> &[u8] {
60        &self.bytes[..self.len]
61    }
62}
63
64/// Compute HMAC in one shot, mirroring `ring::hmac::sign`.
65///
66/// # Panics
67///
68/// Panics if OpenSSL cannot load the digest algorithm or the HMAC operation fails.
69#[must_use]
70pub fn sign(key: &Key, data: &[u8]) -> Tag {
71    let digest_alg = DigestAlg::fetch(key.alg.0.name, None)
72        .unwrap_or_else(|e| panic!("OpenSSL digest unavailable: {e}"));
73    let mut bytes = [0u8; 64];
74    let len = HmacCtx::oneshot(&digest_alg, key.key_bytes.as_ref(), data, &mut bytes)
75        .unwrap_or_else(|e| panic!("HmacCtx::oneshot failed: {e}"));
76    Tag { bytes, len }
77}
78
79/// Verify HMAC in one shot (constant-time), mirroring `ring::hmac::verify`.
80///
81/// # Errors
82///
83/// Returns `Unspecified` if the computed HMAC does not match `tag`.
84pub fn verify(key: &Key, data: &[u8], tag: &[u8]) -> Result<(), Unspecified> {
85    let computed = sign(key, data);
86    if native_ossl::util::ct_eq(computed.as_ref(), tag) {
87        Ok(())
88    } else {
89        Err(Unspecified)
90    }
91}