Skip to main content

ic_mac/
cmac.rs

1//! SP 800-38B CMAC over AES.
2//!
3//! CMAC is the approved way to authenticate with a block cipher when a hash is
4//! unavailable or undesirable, and it underpins the SP 800-90A CTR_DRBG
5//! derivation function and SP 800-108 KDFs in CMAC mode.
6
7//! Indexed loops over fixed-size limb and word arrays are used throughout; they
8//! mirror the index algebra in the specifications these routines implement, so
9//! `needless_range_loop` is allowed rather than obscuring the correspondence.
10#![allow(clippy::needless_range_loop)]
11
12use ic_cipher::aes::{Aes128, Aes192, Aes256, BLOCK_LEN};
13use ic_core::traits::internal::Mac;
14use ic_core::traits::{Algorithm, BlockCipher, SelfTest};
15use ic_core::{ensure, Result, Zeroize};
16
17/// The CMAC subkey generation constant for a 128-bit block, `x^128 + x^7 + x^2 + x + 1`.
18const RB: u8 = 0x87;
19
20/// Double a 128-bit value in GF(2^128), constant-time.
21fn dbl(block: &mut [u8; BLOCK_LEN]) {
22    let msb = block[0] >> 7;
23    let mut carry = 0u8;
24    for byte in block.iter_mut().rev() {
25        let next = *byte >> 7;
26        *byte = (*byte << 1) | carry;
27        carry = next;
28    }
29    block[BLOCK_LEN - 1] ^= RB & msb.wrapping_neg();
30}
31
32/// Generic CMAC state over a 128-bit block cipher.
33#[derive(Clone)]
34pub struct Cmac<C: BlockCipher + Clone> {
35    cipher: C,
36    k1: [u8; BLOCK_LEN],
37    k2: [u8; BLOCK_LEN],
38    acc: [u8; BLOCK_LEN],
39    buf: [u8; BLOCK_LEN],
40    buffered: usize,
41}
42
43impl<C: BlockCipher + Clone> Drop for Cmac<C> {
44    fn drop(&mut self) {
45        self.k1.zeroize();
46        self.k2.zeroize();
47        self.acc.zeroize();
48        self.buf.zeroize();
49    }
50}
51
52impl<C: BlockCipher + Clone> Cmac<C> {
53    fn build(cipher: C) -> Result<Self> {
54        // L = E_K(0^128); K1 = dbl(L); K2 = dbl(K1).
55        let mut l = [0u8; BLOCK_LEN];
56        cipher.encrypt_block(&mut l)?;
57        let mut k1 = l;
58        dbl(&mut k1);
59        let mut k2 = k1;
60        dbl(&mut k2);
61        l.zeroize();
62        Ok(Self {
63            cipher,
64            k1,
65            k2,
66            acc: [0u8; BLOCK_LEN],
67            buf: [0u8; BLOCK_LEN],
68            buffered: 0,
69        })
70    }
71
72    fn absorb(&mut self, block: &[u8]) -> Result<()> {
73        for i in 0..BLOCK_LEN {
74            self.acc[i] ^= block[i];
75        }
76        let mut tmp = self.acc;
77        self.cipher.encrypt_block(&mut tmp)?;
78        self.acc = tmp;
79        Ok(())
80    }
81
82    fn absorb_buffered(&mut self) {
83        let block = self.buf;
84        // The cipher cannot fail on a correctly sized block; a failure here
85        // would be an internal invariant break, so the accumulator is poisoned
86        // rather than silently accepting a short MAC.
87        if self.absorb(&block).is_err() {
88            self.acc = [0xFFu8; BLOCK_LEN];
89        }
90        self.buffered = 0;
91    }
92}
93
94/// A CMAC tag: always one block.
95pub type CmacTag = [u8; BLOCK_LEN];
96
97macro_rules! cmac_variant {
98    (
99        $name:ident, $inner:ty, $id:literal, $disp:literal, $keylen:literal,
100        $kat_key:literal, $kat_tag:literal
101    ) => {
102        #[doc = concat!($disp, " (SP 800-38B).")]
103        pub type $name = Cmac<$inner>;
104
105        impl Algorithm for Cmac<$inner> {
106            const ID: &'static str = $id;
107            const NAME: &'static str = $disp;
108        }
109
110        impl SelfTest for Cmac<$inner> {
111            fn self_test() -> Result<()> {
112                // SP 800-38B example 1: the empty message.
113                let mut key = [0u8; $keylen];
114                ic_core::codec::hex_decode($kat_key.as_bytes(), &mut key)?;
115                let tag = <Self as Mac>::mac(&key, b"")?;
116                let mut want = [0u8; BLOCK_LEN];
117                ic_core::codec::hex_decode($kat_tag.as_bytes(), &mut want)?;
118                key.zeroize();
119                ensure!(ic_core::ct::verify(&want, &tag), SelfTestFailed, $id);
120                Ok(())
121            }
122        }
123    };
124}
125
126impl<C: BlockCipher + Clone> Mac for Cmac<C>
127where
128    Cmac<C>: Algorithm,
129{
130    type Tag = CmacTag;
131    const TAG_LEN: usize = BLOCK_LEN;
132
133    fn new(key: &[u8]) -> Result<Self> {
134        ic_core::module::operational()?;
135        ensure!(
136            C::BLOCK_LEN == BLOCK_LEN,
137            InvalidParameter,
138            "cmac needs a 128-bit block"
139        );
140        Self::build(C::new(key)?)
141    }
142
143    fn update(&mut self, mut data: &[u8]) {
144        if self.buffered > 0 {
145            let take = core::cmp::min(BLOCK_LEN - self.buffered, data.len());
146            self.buf[self.buffered..self.buffered + take].copy_from_slice(&data[..take]);
147            self.buffered += take;
148            data = &data[take..];
149            // The final block is handled in `finalize`, so a full buffer is
150            // only flushed once more data is known to follow.
151            if self.buffered < BLOCK_LEN || data.is_empty() {
152                return;
153            }
154            self.absorb_buffered();
155        }
156        while data.len() > BLOCK_LEN {
157            let (block, rest) = data.split_at(BLOCK_LEN);
158            let _ = self.absorb(block);
159            data = rest;
160        }
161        self.buf[..data.len()].copy_from_slice(data);
162        self.buffered = data.len();
163    }
164
165    fn finalize(mut self) -> CmacTag {
166        let mut last = self.buf;
167        if self.buffered == BLOCK_LEN {
168            // Complete final block: XOR with K1.
169            for i in 0..BLOCK_LEN {
170                last[i] ^= self.k1[i];
171            }
172        } else {
173            // Incomplete (or empty) final block: 10* padding, XOR with K2.
174            last[self.buffered] = 0x80;
175            for b in last[self.buffered + 1..].iter_mut() {
176                *b = 0;
177            }
178            for i in 0..BLOCK_LEN {
179                last[i] ^= self.k2[i];
180            }
181        }
182        if self.absorb(&last).is_err() {
183            return [0xFFu8; BLOCK_LEN];
184        }
185        last.zeroize();
186        self.acc
187    }
188}
189
190cmac_variant!(
191    CmacAes128,
192    Aes128,
193    "cmac-aes-128",
194    "CMAC-AES-128",
195    16,
196    "2b7e151628aed2a6abf7158809cf4f3c",
197    "bb1d6929e95937287fa37d129b756746"
198);
199cmac_variant!(
200    CmacAes192,
201    Aes192,
202    "cmac-aes-192",
203    "CMAC-AES-192",
204    24,
205    "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
206    "d17ddf46adaacde531cac483de7a9367"
207);
208cmac_variant!(
209    CmacAes256,
210    Aes256,
211    "cmac-aes-256",
212    "CMAC-AES-256",
213    32,
214    "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
215    "028962f61b7bf89efc6b551f4667d983"
216);
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221    use ic_core::codec::{hex, unhex};
222
223    /// The SP 800-38B example message, from which each case takes a prefix.
224    const MSG: &str = "6bc1bee22e409f96e93d7e117393172a\
225                       ae2d8a571e03ac9c9eb76fac45af8e51\
226                       30c81c46a35ce411e5fbc1191a0a52ef\
227                       f69f2445df4f9b17ad2b417be66c3710";
228
229    fn msg_prefix(len: usize) -> Vec<u8> {
230        unhex(&MSG.replace(char::is_whitespace, "")).unwrap()[..len].to_vec()
231    }
232
233    #[test]
234    fn sp800_38b_aes128_examples() {
235        let key = unhex("2b7e151628aed2a6abf7158809cf4f3c").unwrap();
236        for (len, want) in [
237            (0usize, "bb1d6929e95937287fa37d129b756746"),
238            (16, "070a16b46b4d4144f79bdd9dd04a287c"),
239            (40, "dfa66747de9ae63030ca32611497c827"),
240            (64, "51f0bebf7e3b9d92fc49741779363cfe"),
241        ] {
242            let tag = CmacAes128::mac(&key, &msg_prefix(len)).unwrap();
243            assert_eq!(hex(&tag), want, "AES-128 CMAC over {len} bytes");
244        }
245    }
246
247    #[test]
248    fn sp800_38b_aes192_examples() {
249        let key = unhex("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b").unwrap();
250        for (len, want) in [
251            (0usize, "d17ddf46adaacde531cac483de7a9367"),
252            (16, "9e99a7bf31e710900662f65e617c5184"),
253            (64, "a1d5df0eed790f794d77589659f39a11"),
254        ] {
255            let tag = CmacAes192::mac(&key, &msg_prefix(len)).unwrap();
256            assert_eq!(hex(&tag), want, "AES-192 CMAC over {len} bytes");
257        }
258    }
259
260    #[test]
261    fn sp800_38b_aes256_examples() {
262        let key =
263            unhex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").unwrap();
264        for (len, want) in [
265            (0usize, "028962f61b7bf89efc6b551f4667d983"),
266            (16, "28a7023f452e8f82bd4bf28d8c37c35c"),
267            (64, "e1992190549f6ed5696a2c056c315410"),
268        ] {
269            let tag = CmacAes256::mac(&key, &msg_prefix(len)).unwrap();
270            assert_eq!(hex(&tag), want, "AES-256 CMAC over {len} bytes");
271        }
272    }
273
274    #[test]
275    fn streaming_matches_one_shot() {
276        let key = unhex("2b7e151628aed2a6abf7158809cf4f3c").unwrap();
277        let data = msg_prefix(64);
278        for split in [0usize, 1, 15, 16, 17, 32, 63, 64] {
279            let mut m = CmacAes128::new(&key).unwrap();
280            m.update(&data[..split]);
281            m.update(&data[split..]);
282            assert_eq!(
283                m.finalize(),
284                CmacAes128::mac(&key, &data).unwrap(),
285                "split at {split}"
286            );
287        }
288    }
289
290    #[test]
291    fn subkey_doubling_reduces() {
292        // A value with the high bit set must pick up the Rb constant.
293        let mut b = [0u8; BLOCK_LEN];
294        b[0] = 0x80;
295        dbl(&mut b);
296        assert_eq!(b[BLOCK_LEN - 1], RB);
297        assert_eq!(b[0], 0);
298    }
299
300    #[test]
301    fn verify_detects_tampering() {
302        let key = unhex("2b7e151628aed2a6abf7158809cf4f3c").unwrap();
303        let tag = CmacAes128::mac(&key, b"data").unwrap();
304        <CmacAes128 as ic_core::traits::Mac>::verify(&key, b"data", &tag).unwrap();
305        assert!(<CmacAes128 as ic_core::traits::Mac>::verify(&key, b"datb", &tag).is_err());
306    }
307
308    #[test]
309    fn self_tests_pass() {
310        CmacAes128::self_test().unwrap();
311        CmacAes192::self_test().unwrap();
312        CmacAes256::self_test().unwrap();
313    }
314}