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