Skip to main content

native_ossl/
mac.rs

1//! `MacAlg` — `EVP_MAC` algorithm descriptor.
2//!
3//! Phase 3.3 delivers `MacAlg`; Phase 4.3 extends this module with
4//! `MacCtx`, `HmacCtx`, and `CmacCtx`.
5
6use crate::error::ErrorStack;
7use native_ossl_sys as sys;
8use std::ffi::CStr;
9use std::sync::Arc;
10
11// ── MacAlg — algorithm descriptor ─────────────────────────────────────────────
12
13/// An OpenSSL MAC algorithm descriptor (`EVP_MAC*`).
14///
15/// Fetched once and reused.  Implements `Clone` via `EVP_MAC_up_ref`.
16pub struct MacAlg {
17    ptr: *mut sys::EVP_MAC,
18    /// Keeps the library context alive while this descriptor is in use.
19    lib_ctx: Option<Arc<crate::lib_ctx::LibCtx>>,
20}
21
22impl MacAlg {
23    /// Fetch a MAC algorithm from the global default library context.
24    ///
25    /// # Errors
26    ///
27    /// Returns `Err` if the algorithm is not available.
28    pub fn fetch(name: &CStr, props: Option<&CStr>) -> Result<Self, ErrorStack> {
29        let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
30        let ptr = unsafe { sys::EVP_MAC_fetch(std::ptr::null_mut(), name.as_ptr(), props_ptr) };
31        if ptr.is_null() {
32            return Err(ErrorStack::drain());
33        }
34        Ok(MacAlg { ptr, lib_ctx: None })
35    }
36
37    /// Fetch a MAC algorithm from an explicit library context.
38    ///
39    /// # Errors
40    pub fn fetch_in(
41        ctx: &Arc<crate::lib_ctx::LibCtx>,
42        name: &CStr,
43        props: Option<&CStr>,
44    ) -> Result<Self, ErrorStack> {
45        let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
46        let ptr = unsafe { sys::EVP_MAC_fetch(ctx.as_ptr(), name.as_ptr(), props_ptr) };
47        if ptr.is_null() {
48            return Err(ErrorStack::drain());
49        }
50        Ok(MacAlg {
51            ptr,
52            lib_ctx: Some(Arc::clone(ctx)),
53        })
54    }
55
56    /// Return the raw `EVP_MAC*` pointer.  Valid for the lifetime of `self`.
57    #[must_use]
58    pub fn as_ptr(&self) -> *const sys::EVP_MAC {
59        self.ptr
60    }
61
62    /// Return the canonical name of this MAC algorithm (e.g. `"HMAC"`, `"CMAC"`).
63    ///
64    /// The returned reference is valid for the lifetime of `self`.
65    #[must_use]
66    pub fn name(&self) -> &CStr {
67        // SAFETY: EVP_MAC_get0_name returns a pointer into the EVP_MAC object's
68        // internal storage.  It is valid for the lifetime of the EVP_MAC*, which
69        // is at least as long as &self.
70        unsafe { CStr::from_ptr(sys::EVP_MAC_get0_name(self.ptr)) }
71    }
72}
73
74impl Clone for MacAlg {
75    fn clone(&self) -> Self {
76        unsafe { sys::EVP_MAC_up_ref(self.ptr) };
77        MacAlg {
78            ptr: self.ptr,
79            lib_ctx: self.lib_ctx.clone(),
80        }
81    }
82}
83
84impl Drop for MacAlg {
85    fn drop(&mut self) {
86        unsafe { sys::EVP_MAC_free(self.ptr) };
87    }
88}
89
90// SAFETY: `EVP_MAC*` is reference-counted and immutable after fetch.
91unsafe impl Send for MacAlg {}
92unsafe impl Sync for MacAlg {}
93
94// ── MacCtx — stateful context (Phase 4.3) ────────────────────────────────────
95
96/// Stateful MAC context (`EVP_MAC_CTX*`).
97///
98/// `!Clone` — use `fork()` to duplicate mid-stream state.
99/// All stateful operations require `&mut self`.
100pub struct MacCtx {
101    ptr: *mut sys::EVP_MAC_CTX,
102}
103
104impl MacCtx {
105    /// Create a new (uninitialised) MAC context from an algorithm descriptor.
106    ///
107    /// Call [`MacCtx::init`] with a key before feeding data.
108    ///
109    /// # Errors
110    pub fn new(alg: &MacAlg) -> Result<Self, ErrorStack> {
111        let ptr = unsafe { sys::EVP_MAC_CTX_new(alg.ptr) };
112        if ptr.is_null() {
113            return Err(ErrorStack::drain());
114        }
115        Ok(MacCtx { ptr })
116    }
117
118    /// Initialise (or re-initialise) with a key and optional parameters.
119    ///
120    /// **`key` is always copied** into the MAC context by `EVP_MAC_init`.
121    /// HMAC additionally pads the key into IPAD/OPAD.  The copy is unavoidable.
122    ///
123    /// # Errors
124    pub fn init(
125        &mut self,
126        key: &[u8],
127        params: Option<&crate::params::Params<'_>>,
128    ) -> Result<(), ErrorStack> {
129        let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
130        crate::ossl_call!(sys::EVP_MAC_init(
131            self.ptr,
132            key.as_ptr(),
133            key.len(),
134            params_ptr
135        ))
136    }
137
138    /// Feed data into the ongoing MAC computation.
139    ///
140    /// # Errors
141    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
142        crate::ossl_call!(sys::EVP_MAC_update(self.ptr, data.as_ptr(), data.len()))
143    }
144
145    /// Finalise the MAC and write into `out`.
146    ///
147    /// `out` must be at least `self.mac_size()` bytes.
148    /// Returns the number of bytes written.
149    ///
150    /// # Errors
151    pub fn finish(&mut self, out: &mut [u8]) -> Result<usize, ErrorStack> {
152        let mut outl: usize = 0;
153        crate::ossl_call!(sys::EVP_MAC_final(
154            self.ptr,
155            out.as_mut_ptr(),
156            std::ptr::addr_of_mut!(outl),
157            out.len()
158        ))?;
159        Ok(outl)
160    }
161
162    /// Finalise with variable-length XOF output (KMAC-128, KMAC-256).
163    ///
164    /// # Errors
165    pub fn finish_xof(&mut self, out: &mut [u8]) -> Result<(), ErrorStack> {
166        crate::ossl_call!(sys::EVP_MAC_finalXOF(self.ptr, out.as_mut_ptr(), out.len()))
167    }
168
169    /// Expected MAC output length in bytes.  Available after `init`.
170    #[must_use]
171    pub fn mac_size(&self) -> usize {
172        unsafe { sys::EVP_MAC_CTX_get_mac_size(self.ptr) }
173    }
174
175    /// Fork the current mid-stream state into a new context (`EVP_MAC_CTX_dup`).
176    ///
177    /// Named `fork` (not `clone`) to signal this is a potentially expensive deep copy.
178    ///
179    /// # Errors
180    pub fn fork(&self) -> Result<MacCtx, ErrorStack> {
181        let ptr = unsafe { sys::EVP_MAC_CTX_dup(self.ptr) };
182        if ptr.is_null() {
183            return Err(ErrorStack::drain());
184        }
185        Ok(MacCtx { ptr })
186    }
187}
188
189impl Drop for MacCtx {
190    fn drop(&mut self) {
191        unsafe { sys::EVP_MAC_CTX_free(self.ptr) };
192    }
193}
194
195unsafe impl Send for MacCtx {}
196
197// ── HmacCtx — typed HMAC wrapper ─────────────────────────────────────────────
198
199/// HMAC context bound to a specific digest algorithm.
200pub struct HmacCtx(MacCtx);
201
202impl HmacCtx {
203    /// Create an HMAC context.
204    ///
205    /// The digest name is passed to `EVP_MAC_init` as an `OSSL_PARAM`.
206    ///
207    /// # Errors
208    pub fn new(digest: &crate::digest::DigestAlg, key: &[u8]) -> Result<Self, ErrorStack> {
209        // HMAC algorithm name — must pass the digest name as a parameter.
210        let alg = MacAlg::fetch(c"HMAC", None)?;
211        let mut ctx = MacCtx::new(&alg)?;
212
213        // Build params: { digest = "<name>" }
214        // We use push_utf8_string (copies) since the digest NID→name is dynamic.
215        let nid = digest.nid();
216        let name_cstr: std::ffi::CString = {
217            let name_ptr = unsafe { native_ossl_sys::OBJ_nid2sn(nid) };
218            if name_ptr.is_null() {
219                return Err(crate::error::ErrorStack::drain());
220            }
221            unsafe { std::ffi::CStr::from_ptr(name_ptr) }.to_owned()
222        };
223
224        let params = crate::params::ParamBuilder::new()?
225            .push_utf8_string(c"digest", &name_cstr)?
226            .build()?;
227
228        ctx.init(key, Some(&params))?;
229        Ok(HmacCtx(ctx))
230    }
231
232    /// Feed data into the HMAC computation.
233    ///
234    /// # Errors
235    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
236        self.0.update(data)
237    }
238
239    /// Finalise the HMAC.
240    ///
241    /// # Errors
242    pub fn finish(&mut self, out: &mut [u8]) -> Result<usize, ErrorStack> {
243        self.0.finish(out)
244    }
245
246    /// Finalise and return the HMAC in a freshly allocated `Vec<u8>`.
247    ///
248    /// # Errors
249    pub fn finish_to_vec(&mut self) -> Result<Vec<u8>, ErrorStack> {
250        let size = self.0.mac_size();
251        let mut out = vec![0u8; size];
252        let n = self.0.finish(&mut out)?;
253        out.truncate(n);
254        Ok(out)
255    }
256
257    /// Expected MAC size in bytes.
258    #[must_use]
259    pub fn mac_size(&self) -> usize {
260        self.0.mac_size()
261    }
262
263    /// One-shot HMAC computation.
264    ///
265    /// # Errors
266    pub fn oneshot(
267        digest: &crate::digest::DigestAlg,
268        key: &[u8],
269        data: &[u8],
270        out: &mut [u8],
271    ) -> Result<usize, ErrorStack> {
272        let mut ctx = HmacCtx::new(digest, key)?;
273        ctx.update(data)?;
274        ctx.finish(out)
275    }
276}
277
278// ── CmacCtx — typed CMAC wrapper ─────────────────────────────────────────────
279
280/// CMAC context bound to a specific block cipher.
281pub struct CmacCtx(MacCtx);
282
283impl CmacCtx {
284    /// Create a CMAC context.
285    ///
286    /// # Errors
287    pub fn new(cipher: &crate::cipher::CipherAlg, key: &[u8]) -> Result<Self, ErrorStack> {
288        let alg = MacAlg::fetch(c"CMAC", None)?;
289        let mut ctx = MacCtx::new(&alg)?;
290
291        // For CMAC we need the cipher name. Use OBJ_nid2sn to get it.
292        // Actually, CMAC needs the cipher name as a string parameter.
293        // Use a simple string: AES-256-CBC etc. (caller chose the cipher).
294        // We'll use push_octet_slice since we don't have a name accessor yet.
295        // For now: use a known cipher name based on key_len and block_size.
296        // Better: expose CipherAlg::name() when we have it.
297        // Workaround: use OSSL_CIPHER_PARAM_NAME if available.
298        // For the test, just hardcode based on the cipher's key_len.
299        let key_len = cipher.key_len();
300        // Map key length to the corresponding AES-CBC cipher name.
301        // Return Err for any length that does not correspond to a known AES variant
302        // rather than silently using the wrong algorithm.
303        // TODO: replace with CipherAlg::name() via EVP_CIPHER_get0_name once available.
304        let cipher_name: &std::ffi::CStr = match key_len {
305            16 => c"AES-128-CBC",
306            24 => c"AES-192-CBC",
307            32 => c"AES-256-CBC",
308            _ => return Err(ErrorStack::drain()),
309        };
310
311        let params = crate::params::ParamBuilder::new()?
312            .push_utf8_string(c"cipher", cipher_name)?
313            .build()?;
314
315        ctx.init(key, Some(&params))?;
316        Ok(CmacCtx(ctx))
317    }
318
319    /// Feed data into the CMAC computation.
320    ///
321    /// # Errors
322    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
323        self.0.update(data)
324    }
325
326    /// Finalise the CMAC.
327    ///
328    /// # Errors
329    pub fn finish(&mut self, out: &mut [u8]) -> Result<usize, ErrorStack> {
330        self.0.finish(out)
331    }
332}
333
334// ── Tests ─────────────────────────────────────────────────────────────────────
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339    use crate::digest::DigestAlg;
340
341    #[test]
342    fn fetch_hmac_succeeds() {
343        let alg = MacAlg::fetch(c"HMAC", None).unwrap();
344        drop(alg);
345    }
346
347    #[test]
348    fn fetch_nonexistent_fails() {
349        assert!(MacAlg::fetch(c"NONEXISTENT_MAC_XYZ", None).is_err());
350    }
351
352    #[test]
353    fn clone_then_drop_both() {
354        let alg = MacAlg::fetch(c"HMAC", None).unwrap();
355        let alg2 = alg.clone();
356        drop(alg);
357        drop(alg2);
358    }
359
360    /// HMAC-SHA256 RFC 4231 test vector #1.
361    /// Key  = 0b0b...0b (20 bytes)
362    /// Data = "Hi There"
363    /// MAC  = b0344c61...
364    #[test]
365    fn hmac_sha256_rfc4231_tv1() {
366        let digest = DigestAlg::fetch(c"SHA2-256", None).unwrap();
367        let key = [0x0b_u8; 20];
368        let data = b"Hi There";
369
370        let mut ctx = HmacCtx::new(&digest, &key).unwrap();
371        ctx.update(data).unwrap();
372        let mac = ctx.finish_to_vec().unwrap();
373
374        assert_eq!(
375            hex::encode(&mac),
376            "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
377        );
378    }
379
380    /// Oneshot HMAC — same vector.
381    #[test]
382    fn hmac_sha256_oneshot() {
383        let digest = DigestAlg::fetch(c"SHA2-256", None).unwrap();
384        let key = [0x0b_u8; 20];
385        let mut out = [0u8; 32];
386        let n = HmacCtx::oneshot(&digest, &key, b"Hi There", &mut out).unwrap();
387        assert_eq!(n, 32);
388        assert_eq!(
389            hex::encode(out),
390            "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
391        );
392    }
393
394    /// `MacCtx::fork` mid-stream — two different suffixes produce different MACs.
395    #[test]
396    fn mac_ctx_fork_mid_stream() {
397        let alg = MacAlg::fetch(c"HMAC", None).unwrap();
398        let digest = DigestAlg::fetch(c"SHA2-256", None).unwrap();
399        let nid = digest.nid();
400        let name_ptr = unsafe { native_ossl_sys::OBJ_nid2sn(nid) };
401        let name = unsafe { std::ffi::CStr::from_ptr(name_ptr) };
402        let params = crate::params::ParamBuilder::new()
403            .unwrap()
404            .push_utf8_string(c"digest", name)
405            .unwrap()
406            .build()
407            .unwrap();
408
409        let mut ctx = MacCtx::new(&alg).unwrap();
410        ctx.init(&[0u8; 32], Some(&params)).unwrap();
411        ctx.update(b"common").unwrap();
412
413        let mut fork = ctx.fork().unwrap();
414        ctx.update(b" A").unwrap();
415        fork.update(b" B").unwrap();
416
417        let mut out_a = [0u8; 32];
418        let mut out_b = [0u8; 32];
419        ctx.finish(&mut out_a).unwrap();
420        fork.finish(&mut out_b).unwrap();
421
422        assert_ne!(out_a, out_b);
423    }
424}