master
  1//! AES-CCM (Counter with CBC-MAC) authenticated encryption.
  2//! AES-CCM* extends CCM to support encryption-only mode (tag_len=0).
  3//!
  4//! References:
  5//! - NIST SP 800-38C: https://csrc.nist.gov/publications/detail/sp/800-38c/final
  6//! - RFC 3610: https://datatracker.ietf.org/doc/html/rfc3610
  7
  8const std = @import("std");
  9const assert = std.debug.assert;
 10const crypto = std.crypto;
 11const mem = std.mem;
 12const modes = crypto.core.modes;
 13const AuthenticationError = crypto.errors.AuthenticationError;
 14const cbc_mac = @import("cbc_mac.zig");
 15
 16/// AES-128-CCM* with no authentication (encryption-only, 13-byte nonce).
 17pub const Aes128Ccm0 = AesCcm(crypto.core.aes.Aes128, 0, 13);
 18/// AES-128-CCM with 8-byte authentication tag and 13-byte nonce.
 19pub const Aes128Ccm8 = AesCcm(crypto.core.aes.Aes128, 8, 13);
 20/// AES-128-CCM with 16-byte authentication tag and 13-byte nonce.
 21pub const Aes128Ccm16 = AesCcm(crypto.core.aes.Aes128, 16, 13);
 22/// AES-256-CCM* with no authentication (encryption-only, 13-byte nonce).
 23pub const Aes256Ccm0 = AesCcm(crypto.core.aes.Aes256, 0, 13);
 24/// AES-256-CCM with 8-byte authentication tag and 13-byte nonce.
 25pub const Aes256Ccm8 = AesCcm(crypto.core.aes.Aes256, 8, 13);
 26/// AES-256-CCM with 16-byte authentication tag and 13-byte nonce.
 27pub const Aes256Ccm16 = AesCcm(crypto.core.aes.Aes256, 16, 13);
 28
 29/// AES-CCM authenticated encryption (NIST SP 800-38C, RFC 3610).
 30/// CCM* mode extends CCM to support encryption-only mode when tag_len=0.
 31///
 32/// `BlockCipher`: Block cipher type (must have 16-byte blocks).
 33/// `tag_len`: Authentication tag length in bytes (0, 4, 6, 8, 10, 12, 14, or 16).
 34///            When tag_len=0, CCM* provides encryption-only (no authentication).
 35/// `nonce_len`: Nonce length in bytes (7 to 13).
 36fn AesCcm(comptime BlockCipher: type, comptime tag_len: usize, comptime nonce_len: usize) type {
 37    const block_length = BlockCipher.block.block_length;
 38
 39    comptime {
 40        assert(block_length == 16); // CCM requires 16-byte blocks
 41        if (tag_len != 0 and (tag_len < 4 or tag_len > 16 or tag_len % 2 != 0)) {
 42            @compileError("CCM tag_length must be 0, 4, 6, 8, 10, 12, 14, or 16 bytes");
 43        }
 44        if (nonce_len < 7 or nonce_len > 13) {
 45            @compileError("CCM nonce_length must be between 7 and 13 bytes");
 46        }
 47    }
 48
 49    const L = 15 - nonce_len; // Counter size in bytes (2 to 8)
 50
 51    return struct {
 52        pub const key_length = BlockCipher.key_bits / 8;
 53        pub const tag_length = tag_len;
 54        pub const nonce_length = nonce_len;
 55
 56        /// `c`: Ciphertext output buffer (must be same length as m).
 57        /// `tag`: Authentication tag output.
 58        /// `m`: Plaintext message to encrypt.
 59        /// `ad`: Associated data to authenticate.
 60        /// `npub`: Public nonce (must be unique for each message with same key).
 61        /// `key`: Encryption key.
 62        pub fn encrypt(
 63            c: []u8,
 64            tag: *[tag_length]u8,
 65            m: []const u8,
 66            ad: []const u8,
 67            npub: [nonce_length]u8,
 68            key: [key_length]u8,
 69        ) void {
 70            assert(c.len == m.len);
 71
 72            // Validate message length fits in L bytes
 73            const max_msg_len: u64 = if (L >= 8) std.math.maxInt(u64) else (@as(u64, 1) << @as(u6, @intCast(L * 8))) - 1;
 74            assert(m.len <= max_msg_len);
 75
 76            const cipher_ctx = BlockCipher.initEnc(key);
 77
 78            // CCM*: Skip authentication if tag_length is 0 (encryption-only mode)
 79            if (tag_length > 0) {
 80                // Compute CBC-MAC using the reusable CBC-MAC module
 81                var mac_result: [block_length]u8 = undefined;
 82                computeCbcMac(&mac_result, &key, m, ad, npub);
 83
 84                // Construct counter block for tag encryption (counter = 0)
 85                var ctr_block: [block_length]u8 = undefined;
 86                formatCtrBlock(&ctr_block, npub, 0);
 87
 88                // Encrypt the MAC tag
 89                var s0: [block_length]u8 = undefined;
 90                cipher_ctx.encrypt(&s0, &ctr_block);
 91                for (tag, mac_result[0..tag_length], s0[0..tag_length]) |*t, mac_byte, s_byte| {
 92                    t.* = mac_byte ^ s_byte;
 93                }
 94
 95                crypto.secureZero(u8, &mac_result);
 96                crypto.secureZero(u8, &s0);
 97            }
 98
 99            // Encrypt the plaintext using CTR mode (starting from counter = 1)
100            var ctr_block: [block_length]u8 = undefined;
101            formatCtrBlock(&ctr_block, npub, 1);
102            // CCM counter is in the last L bytes of the block
103            modes.ctrSlice(@TypeOf(cipher_ctx), cipher_ctx, c, m, ctr_block, .big, 1 + nonce_len, L);
104        }
105
106        /// `m`: Plaintext output buffer (must be same length as c).
107        /// `c`: Ciphertext to decrypt.
108        /// `tag`: Authentication tag to verify.
109        /// `ad`: Associated data (must match encryption).
110        /// `npub`: Public nonce (must match encryption).
111        /// `key`: Private key.
112        ///
113        /// Asserts `c.len == m.len`.
114        /// Contents of `m` are undefined if an error is returned.
115        pub fn decrypt(
116            m: []u8,
117            c: []const u8,
118            tag: [tag_length]u8,
119            ad: []const u8,
120            npub: [nonce_length]u8,
121            key: [key_length]u8,
122        ) AuthenticationError!void {
123            assert(m.len == c.len);
124
125            const cipher_ctx = BlockCipher.initEnc(key);
126
127            // Decrypt the ciphertext using CTR mode (starting from counter = 1)
128            var ctr_block: [block_length]u8 = undefined;
129            formatCtrBlock(&ctr_block, npub, 1);
130            // CCM counter is in the last L bytes of the block
131            modes.ctrSlice(@TypeOf(cipher_ctx), cipher_ctx, m, c, ctr_block, .big, 1 + nonce_len, L);
132
133            // CCM*: Skip authentication if tag_length is 0 (encryption-only mode)
134            if (tag_length > 0) {
135                // Compute CBC-MAC over decrypted plaintext
136                var mac_result: [block_length]u8 = undefined;
137                computeCbcMac(&mac_result, &key, m, ad, npub);
138
139                // Decrypt the received tag
140                formatCtrBlock(&ctr_block, npub, 0);
141                var s0: [block_length]u8 = undefined;
142                cipher_ctx.encrypt(&s0, &ctr_block);
143
144                // Reconstruct the expected MAC
145                var expected_mac: [tag_length]u8 = undefined;
146                for (&expected_mac, mac_result[0..tag_length], s0[0..tag_length]) |*e, mac_byte, s_byte| {
147                    e.* = mac_byte ^ s_byte;
148                }
149
150                // Constant-time tag comparison
151                const valid = crypto.timing_safe.eql([tag_length]u8, expected_mac, tag);
152                if (!valid) {
153                    crypto.secureZero(u8, &expected_mac);
154                    crypto.secureZero(u8, &mac_result);
155                    crypto.secureZero(u8, &s0);
156                    crypto.secureZero(u8, m);
157                    return error.AuthenticationFailed;
158                }
159
160                crypto.secureZero(u8, &expected_mac);
161                crypto.secureZero(u8, &mac_result);
162                crypto.secureZero(u8, &s0);
163            }
164        }
165
166        /// Format the counter block for CTR mode
167        /// Counter block format: [flags | nonce | counter]
168        /// flags = L - 1
169        fn formatCtrBlock(block: *[block_length]u8, npub: [nonce_length]u8, counter: u64) void {
170            @memset(block, 0);
171            block[0] = L - 1; // flags
172            @memcpy(block[1..][0..nonce_length], &npub);
173            // Counter goes in the last L bytes
174            const CounterInt = std.meta.Int(.unsigned, L * 8);
175            mem.writeInt(CounterInt, block[1 + nonce_length ..][0..L], @as(CounterInt, @intCast(counter)), .big);
176        }
177
178        /// Compute CBC-MAC over the message and associated data.
179        /// CCM uses plain CBC-MAC, not CMAC (RFC 3610).
180        fn computeCbcMac(mac: *[block_length]u8, key: *const [key_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8) void {
181            const CbcMac = cbc_mac.CbcMac(BlockCipher);
182            var ctx = CbcMac.init(key);
183
184            // Process B_0 block
185            var b0: [block_length]u8 = undefined;
186            formatB0Block(&b0, m.len, ad.len, npub);
187            ctx.update(&b0);
188
189            // Process associated data if present
190            // RFC 3610: AD is (encoded_length || ad) padded to block boundary
191            if (ad.len > 0) {
192                // Encode and add associated data length
193                var ad_len_encoding: [10]u8 = undefined;
194                const ad_len_size = encodeAdLength(&ad_len_encoding, ad.len);
195
196                // Process AD with padding to block boundary
197                ctx.update(ad_len_encoding[0..ad_len_size]);
198                ctx.update(ad);
199
200                // Add zero padding to reach block boundary
201                const total_ad_size = ad_len_size + ad.len;
202                const remainder = total_ad_size % block_length;
203                if (remainder > 0) {
204                    const padding = [_]u8{0} ** block_length;
205                    ctx.update(padding[0 .. block_length - remainder]);
206                }
207            }
208
209            // Process plaintext message
210            ctx.update(m);
211
212            // Finalize MAC
213            ctx.final(mac);
214        }
215
216        /// Format the B_0 block for CBC-MAC
217        /// B_0 format: [flags | nonce | message_length]
218        /// flags = 64*Adata + 8*M' + L'
219        /// where: Adata = (ad.len > 0), M' = (tag_length - 2)/2 if M>0 else 0, L' = L - 1
220        /// CCM*: When tag_length=0, M' is encoded as 0
221        fn formatB0Block(block: *[block_length]u8, msg_len: usize, ad_len: usize, npub: [nonce_length]u8) void {
222            @memset(block, 0);
223
224            const Adata: u8 = if (ad_len > 0) 1 else 0;
225            const M_prime: u8 = if (tag_length > 0) @intCast((tag_length - 2) / 2) else 0;
226            const L_prime: u8 = L - 1;
227
228            block[0] = (Adata << 6) | (M_prime << 3) | L_prime;
229            @memcpy(block[1..][0..nonce_length], &npub);
230
231            // Encode message length in last L bytes
232            const LengthInt = std.meta.Int(.unsigned, L * 8);
233            mem.writeInt(LengthInt, block[1 + nonce_length ..][0..L], @as(LengthInt, @intCast(msg_len)), .big);
234        }
235
236        /// Encode associated data length according to CCM specification
237        /// Returns the number of bytes written
238        fn encodeAdLength(buf: *[10]u8, ad_len: usize) usize {
239            if (ad_len < 65280) { // 2^16 - 2^8
240                // Encode as 2 bytes
241                mem.writeInt(u16, buf[0..2], @as(u16, @intCast(ad_len)), .big);
242                return 2;
243            } else if (ad_len <= std.math.maxInt(u32)) {
244                // Encode as 0xff || 0xfe || 4 bytes
245                buf[0] = 0xff;
246                buf[1] = 0xfe;
247                mem.writeInt(u32, buf[2..6], @as(u32, @intCast(ad_len)), .big);
248                return 6;
249            } else {
250                // Encode as 0xff || 0xff || 8 bytes
251                buf[0] = 0xff;
252                buf[1] = 0xff;
253                mem.writeInt(u64, buf[2..10], @as(u64, @intCast(ad_len)), .big);
254                return 10;
255            }
256        }
257    };
258}
259
260// Tests
261
262const testing = std.testing;
263const fmt = std.fmt;
264const hexToBytes = fmt.hexToBytes;
265
266test "Aes256Ccm8 - Encrypt decrypt round-trip" {
267    const key: [32]u8 = [_]u8{0x42} ** 32;
268    const nonce: [13]u8 = [_]u8{0x11} ** 13;
269    const m = "Hello, World! This is a test message.";
270    var c: [m.len]u8 = undefined;
271    var m2: [m.len]u8 = undefined;
272    var tag: [Aes256Ccm8.tag_length]u8 = undefined;
273
274    Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key);
275
276    try Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key);
277
278    try testing.expectEqualSlices(u8, m[0..], m2[0..]);
279}
280
281test "Aes256Ccm8 - Associated data" {
282    const key: [32]u8 = [_]u8{0x42} ** 32;
283    const nonce: [13]u8 = [_]u8{0x11} ** 13;
284    const m = "secret message";
285    const ad = "additional authenticated data";
286    var c: [m.len]u8 = undefined;
287    var m2: [m.len]u8 = undefined;
288    var tag: [Aes256Ccm8.tag_length]u8 = undefined;
289
290    Aes256Ccm8.encrypt(&c, &tag, m, ad, nonce, key);
291
292    try Aes256Ccm8.decrypt(&m2, &c, tag, ad, nonce, key);
293    try testing.expectEqualSlices(u8, m[0..], m2[0..]);
294
295    var m3: [m.len]u8 = undefined;
296    const wrong_adata = "wrong data";
297    const result = Aes256Ccm8.decrypt(&m3, &c, tag, wrong_adata, nonce, key);
298    try testing.expectError(error.AuthenticationFailed, result);
299}
300
301test "Aes256Ccm8 - Wrong key" {
302    const key: [32]u8 = [_]u8{0x42} ** 32;
303    const wrong_key: [32]u8 = [_]u8{0x43} ** 32;
304    const nonce: [13]u8 = [_]u8{0x11} ** 13;
305    const m = "secret";
306    var c: [m.len]u8 = undefined;
307    var m2: [m.len]u8 = undefined;
308    var tag: [Aes256Ccm8.tag_length]u8 = undefined;
309
310    Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key);
311
312    const result = Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, wrong_key);
313    try testing.expectError(error.AuthenticationFailed, result);
314}
315
316test "Aes256Ccm8 - Corrupted ciphertext" {
317    const key: [32]u8 = [_]u8{0x42} ** 32;
318    const nonce: [13]u8 = [_]u8{0x11} ** 13;
319    const m = "secret message";
320    var c: [m.len]u8 = undefined;
321    var m2: [m.len]u8 = undefined;
322    var tag: [Aes256Ccm8.tag_length]u8 = undefined;
323
324    Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key);
325
326    c[5] ^= 0xFF;
327
328    const result = Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key);
329    try testing.expectError(error.AuthenticationFailed, result);
330}
331
332test "Aes256Ccm8 - Empty plaintext" {
333    const key: [32]u8 = [_]u8{0x42} ** 32;
334    const nonce: [13]u8 = [_]u8{0x11} ** 13;
335    const m = "";
336    var c: [m.len]u8 = undefined;
337    var m2: [m.len]u8 = undefined;
338    var tag: [Aes256Ccm8.tag_length]u8 = undefined;
339
340    Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key);
341
342    try Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key);
343
344    try testing.expectEqual(@as(usize, 0), m2.len);
345}
346
347test "Aes128Ccm8 - Basic functionality" {
348    const key: [16]u8 = [_]u8{0x42} ** 16;
349    const nonce: [13]u8 = [_]u8{0x11} ** 13;
350    const m = "Test AES-128-CCM";
351    var c: [m.len]u8 = undefined;
352    var m2: [m.len]u8 = undefined;
353    var tag: [Aes128Ccm8.tag_length]u8 = undefined;
354
355    Aes128Ccm8.encrypt(&c, &tag, m, "", nonce, key);
356
357    try Aes128Ccm8.decrypt(&m2, &c, tag, "", nonce, key);
358
359    try testing.expectEqualSlices(u8, m[0..], m2[0..]);
360}
361
362test "Aes256Ccm16 - 16-byte tag" {
363    const key: [32]u8 = [_]u8{0x42} ** 32;
364    const nonce: [13]u8 = [_]u8{0x11} ** 13;
365    const m = "Test 16-byte tag";
366    var c: [m.len]u8 = undefined;
367    var m2: [m.len]u8 = undefined;
368    var tag: [Aes256Ccm16.tag_length]u8 = undefined;
369
370    Aes256Ccm16.encrypt(&c, &tag, m, "", nonce, key);
371
372    try testing.expectEqual(@as(usize, 16), tag.len);
373
374    try Aes256Ccm16.decrypt(&m2, &c, tag, "", nonce, key);
375
376    try testing.expectEqualSlices(u8, m[0..], m2[0..]);
377}
378
379test "Aes256Ccm8 - Edge case short nonce" {
380    const Aes256Ccm8_7 = AesCcm(crypto.core.aes.Aes256, 8, 7);
381    var key: [32]u8 = undefined;
382    _ = try hexToBytes(&key, "eda32f751456e33195f1f499cf2dc7c97ea127b6d488f211ccc5126fbb24afa6");
383    var nonce: [7]u8 = undefined;
384    _ = try hexToBytes(&nonce, "a544218dadd3c1");
385    var m: [1]u8 = undefined;
386    _ = try hexToBytes(&m, "00");
387
388    var c: [m.len]u8 = undefined;
389    var tag: [Aes256Ccm8_7.tag_length]u8 = undefined;
390
391    Aes256Ccm8_7.encrypt(&c, &tag, &m, "", nonce, key);
392
393    var m2: [c.len]u8 = undefined;
394
395    try Aes256Ccm8_7.decrypt(&m2, &c, tag, "", nonce, key);
396    try testing.expectEqualSlices(u8, &m, &m2);
397}
398
399test "Aes256Ccm8 - Edge case long nonce" {
400    var key: [32]u8 = undefined;
401    _ = try hexToBytes(&key, "e1b8a927a95efe94656677b692662000278b441c79e879dd5c0ddc758bdc9ee8");
402    var nonce: [13]u8 = undefined;
403    _ = try hexToBytes(&nonce, "a544218dadd3c10583db49cf39");
404    var m: [1]u8 = undefined;
405    _ = try hexToBytes(&m, "00");
406
407    var c: [m.len]u8 = undefined;
408    var tag: [Aes256Ccm8.tag_length]u8 = undefined;
409
410    Aes256Ccm8.encrypt(&c, &tag, &m, "", nonce, key);
411
412    var m2: [c.len]u8 = undefined;
413
414    try Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key);
415    try testing.expectEqualSlices(u8, &m, &m2);
416}
417
418test "Aes256Ccm8 - With AAD and wrong AAD detection" {
419    var key: [32]u8 = undefined;
420    _ = try hexToBytes(&key, "8c5cf3457ff22228c39c051c4e05ed4093657eb303f859a9d4b0f8be0127d88a");
421    var nonce: [13]u8 = undefined;
422    _ = try hexToBytes(&nonce, "a544218dadd3c10583db49cf39");
423    var m: [1]u8 = undefined;
424    _ = try hexToBytes(&m, "00");
425    var ad: [32]u8 = undefined;
426    _ = try hexToBytes(&ad, "3c0e2815d37d844f7ac240ba9d6e3a0b2a86f706e885959e09a1005e024f6907");
427
428    var c: [m.len]u8 = undefined;
429    var tag: [Aes256Ccm8.tag_length]u8 = undefined;
430
431    Aes256Ccm8.encrypt(&c, &tag, &m, &ad, nonce, key);
432
433    var m2: [c.len]u8 = undefined;
434
435    try Aes256Ccm8.decrypt(&m2, &c, tag, &ad, nonce, key);
436    try testing.expectEqualSlices(u8, &m, &m2);
437
438    var wrong_ad: [32]u8 = undefined;
439    _ = try hexToBytes(&wrong_ad, "0000000000000000000000000000000000000000000000000000000000000000");
440    var m3: [c.len]u8 = undefined;
441    const result = Aes256Ccm8.decrypt(&m3, &c, tag, &wrong_ad, nonce, key);
442    try testing.expectError(error.AuthenticationFailed, result);
443}
444
445test "Aes256Ccm8 - Multi-block payload" {
446    const Aes256Ccm8_12 = AesCcm(crypto.core.aes.Aes256, 8, 12);
447
448    // Test with 32-byte payload (2 AES blocks)
449    var key: [32]u8 = undefined;
450    _ = try hexToBytes(&key, "af063639e66c284083c5cf72b70d8bc277f5978e80d9322d99f2fdc718cda569");
451    var nonce: [12]u8 = undefined;
452    _ = try hexToBytes(&nonce, "a544218dadd3c10583db49cf");
453    var m: [32]u8 = undefined;
454    _ = try hexToBytes(&m, "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff");
455
456    // Encrypt
457    var c: [32]u8 = undefined;
458    var tag: [Aes256Ccm8_12.tag_length]u8 = undefined;
459
460    Aes256Ccm8_12.encrypt(&c, &tag, &m, "", nonce, key);
461
462    // Decrypt and verify
463    var m2: [32]u8 = undefined;
464
465    try Aes256Ccm8_12.decrypt(&m2, &c, tag, "", nonce, key);
466    try testing.expectEqualSlices(u8, &m, &m2);
467}
468
469test "Aes256Ccm8 - Multi-block with AAD" {
470    const Aes256Ccm8_12 = AesCcm(crypto.core.aes.Aes256, 8, 12);
471
472    // Test with multi-block payload (3 AES blocks) and AAD
473    var key: [32]u8 = undefined;
474    _ = try hexToBytes(&key, "f7079dfa3b5c7b056347d7e437bcded683abd6e2c9e069d333284082cbb5d453");
475    var nonce: [12]u8 = undefined;
476    _ = try hexToBytes(&nonce, "5b8e40746f6b98e00f1d13ff");
477
478    // 48-byte payload (3 AES blocks)
479    var m: [48]u8 = undefined;
480    _ = try hexToBytes(&m, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f");
481
482    // 16-byte AAD
483    var ad: [16]u8 = undefined;
484    _ = try hexToBytes(&ad, "000102030405060708090a0b0c0d0e0f");
485
486    // Encrypt
487    var c: [48]u8 = undefined;
488    var tag: [Aes256Ccm8_12.tag_length]u8 = undefined;
489
490    Aes256Ccm8_12.encrypt(&c, &tag, &m, &ad, nonce, key);
491
492    // Decrypt and verify
493    var m2: [48]u8 = undefined;
494
495    try Aes256Ccm8_12.decrypt(&m2, &c, tag, &ad, nonce, key);
496    try testing.expectEqualSlices(u8, &m, &m2);
497}
498
499test "Aes256Ccm8 - Minimum nonce length" {
500    const Aes256Ccm8_7 = AesCcm(crypto.core.aes.Aes256, 8, 7);
501
502    // Test with 7-byte nonce (minimum allowed by CCM spec)
503    var key: [32]u8 = undefined;
504    _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f");
505    var nonce: [7]u8 = undefined;
506    _ = try hexToBytes(&nonce, "10111213141516");
507    const m = "Test message with minimum nonce length";
508
509    // Encrypt
510    var c: [m.len]u8 = undefined;
511    var tag: [Aes256Ccm8_7.tag_length]u8 = undefined;
512
513    Aes256Ccm8_7.encrypt(&c, &tag, m, "", nonce, key);
514
515    // Decrypt and verify
516    var m2: [m.len]u8 = undefined;
517
518    try Aes256Ccm8_7.decrypt(&m2, &c, tag, "", nonce, key);
519    try testing.expectEqualSlices(u8, m[0..], m2[0..]);
520}
521
522test "Aes256Ccm8 - Maximum nonce length" {
523    // Test with 13-byte nonce (maximum allowed by CCM spec)
524    var key: [32]u8 = undefined;
525    _ = try hexToBytes(&key, "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f");
526    var nonce: [13]u8 = undefined;
527    _ = try hexToBytes(&nonce, "101112131415161718191a1b1c");
528    const m = "Test message with maximum nonce length";
529
530    // Encrypt
531    var c: [m.len]u8 = undefined;
532    var tag: [Aes256Ccm8.tag_length]u8 = undefined;
533
534    Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key);
535
536    // Decrypt and verify
537    var m2: [m.len]u8 = undefined;
538
539    try Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key);
540    try testing.expectEqualSlices(u8, m[0..], m2[0..]);
541}
542
543// RFC 3610 test vectors
544
545test "Aes128Ccm8 - RFC 3610 Packet Vector #1" {
546    const Aes128Ccm8_13 = AesCcm(crypto.core.aes.Aes128, 8, 13);
547
548    // RFC 3610 Appendix A, Packet Vector #1
549    var key: [16]u8 = undefined;
550    _ = try hexToBytes(&key, "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF");
551    var nonce: [13]u8 = undefined;
552    _ = try hexToBytes(&nonce, "00000003020100A0A1A2A3A4A5");
553    var ad: [8]u8 = undefined;
554    _ = try hexToBytes(&ad, "0001020304050607");
555    var plaintext: [23]u8 = undefined;
556    _ = try hexToBytes(&plaintext, "08090A0B0C0D0E0F101112131415161718191A1B1C1D1E");
557
558    // Expected ciphertext and tag from RFC
559    var expected_ciphertext: [23]u8 = undefined;
560    _ = try hexToBytes(&expected_ciphertext, "588C979A61C663D2F066D0C2C0F989806D5F6B61DAC384");
561    var expected_tag: [8]u8 = undefined;
562    _ = try hexToBytes(&expected_tag, "17E8D12CFDF926E0");
563
564    // Encrypt
565    var c: [plaintext.len]u8 = undefined;
566    var tag: [Aes128Ccm8_13.tag_length]u8 = undefined;
567
568    Aes128Ccm8_13.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
569
570    // Verify ciphertext matches RFC expected output
571    try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
572
573    // Verify tag matches RFC expected output
574    try testing.expectEqualSlices(u8, &expected_tag, &tag);
575
576    // Decrypt and verify round-trip
577    var m: [plaintext.len]u8 = undefined;
578    try Aes128Ccm8_13.decrypt(&m, &c, tag, &ad, nonce, key);
579    try testing.expectEqualSlices(u8, &plaintext, &m);
580}
581
582test "Aes128Ccm8 - RFC 3610 Packet Vector #2" {
583    const Aes128Ccm8_13 = AesCcm(crypto.core.aes.Aes128, 8, 13);
584
585    // RFC 3610 Appendix A, Packet Vector #2 (8-byte tag, M=8)
586    var key: [16]u8 = undefined;
587    _ = try hexToBytes(&key, "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF");
588    var nonce: [13]u8 = undefined;
589    _ = try hexToBytes(&nonce, "00000004030201A0A1A2A3A4A5");
590    var ad: [8]u8 = undefined;
591    _ = try hexToBytes(&ad, "0001020304050607");
592    var plaintext: [24]u8 = undefined;
593    _ = try hexToBytes(&plaintext, "08090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
594
595    // Expected ciphertext and tag from RFC (from total packet: header + ciphertext + tag)
596    var expected_ciphertext: [24]u8 = undefined;
597    _ = try hexToBytes(&expected_ciphertext, "72C91A36E135F8CF291CA894085C87E3CC15C439C9E43A3B");
598    var expected_tag: [8]u8 = undefined;
599    _ = try hexToBytes(&expected_tag, "A091D56E10400916");
600
601    // Encrypt
602    var c: [plaintext.len]u8 = undefined;
603    var tag: [Aes128Ccm8_13.tag_length]u8 = undefined;
604
605    Aes128Ccm8_13.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
606
607    // Verify ciphertext matches RFC expected output
608    try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
609
610    // Verify tag matches RFC expected output
611    try testing.expectEqualSlices(u8, &expected_tag, &tag);
612
613    // Decrypt and verify round-trip
614    var m: [plaintext.len]u8 = undefined;
615    try Aes128Ccm8_13.decrypt(&m, &c, tag, &ad, nonce, key);
616    try testing.expectEqualSlices(u8, &plaintext, &m);
617}
618
619test "Aes128Ccm8 - RFC 3610 Packet Vector #3" {
620    const Aes128Ccm8_13 = AesCcm(crypto.core.aes.Aes128, 8, 13);
621
622    // RFC 3610 Appendix A, Packet Vector #3 (8-byte tag, 25-byte payload)
623    var key: [16]u8 = undefined;
624    _ = try hexToBytes(&key, "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF");
625    var nonce: [13]u8 = undefined;
626    _ = try hexToBytes(&nonce, "00000005040302A0A1A2A3A4A5");
627    var ad: [8]u8 = undefined;
628    _ = try hexToBytes(&ad, "0001020304050607");
629    var plaintext: [25]u8 = undefined;
630    _ = try hexToBytes(&plaintext, "08090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20");
631
632    // Expected ciphertext and tag from RFC
633    var expected_ciphertext: [25]u8 = undefined;
634    _ = try hexToBytes(&expected_ciphertext, "51B1E5F44A197D1DA46B0F8E2D282AE871E838BB64DA859657");
635    var expected_tag: [8]u8 = undefined;
636    _ = try hexToBytes(&expected_tag, "4ADAA76FBD9FB0C5");
637
638    // Encrypt
639    var c: [plaintext.len]u8 = undefined;
640    var tag: [Aes128Ccm8_13.tag_length]u8 = undefined;
641
642    Aes128Ccm8_13.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
643
644    // Verify ciphertext matches RFC expected output
645    try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
646
647    // Verify tag matches RFC expected output
648    try testing.expectEqualSlices(u8, &expected_tag, &tag);
649
650    // Decrypt and verify round-trip
651    var m: [plaintext.len]u8 = undefined;
652    try Aes128Ccm8_13.decrypt(&m, &c, tag, &ad, nonce, key);
653    try testing.expectEqualSlices(u8, &plaintext, &m);
654}
655
656// NIST SP 800-38C test vectors
657
658test "Aes128Ccm4 - NIST SP 800-38C Example 1" {
659    const Aes128Ccm4_7 = AesCcm(crypto.core.aes.Aes128, 4, 7);
660
661    // Example 1 (C.1): Klen=128, Tlen=32, Nlen=56, Alen=64, Plen=32
662    var key: [16]u8 = undefined;
663    _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f");
664    var nonce: [7]u8 = undefined;
665    _ = try hexToBytes(&nonce, "10111213141516");
666    var ad: [8]u8 = undefined;
667    _ = try hexToBytes(&ad, "0001020304050607");
668    var plaintext: [4]u8 = undefined;
669    _ = try hexToBytes(&plaintext, "20212223");
670
671    // Expected ciphertext and tag from NIST
672    var expected_ciphertext: [4]u8 = undefined;
673    _ = try hexToBytes(&expected_ciphertext, "7162015b");
674    var expected_tag: [4]u8 = undefined;
675    _ = try hexToBytes(&expected_tag, "4dac255d");
676
677    // Encrypt
678    var c: [plaintext.len]u8 = undefined;
679    var tag: [Aes128Ccm4_7.tag_length]u8 = undefined;
680
681    Aes128Ccm4_7.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
682
683    // Verify ciphertext matches NIST expected output
684    try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
685
686    // Verify tag matches NIST expected output
687    try testing.expectEqualSlices(u8, &expected_tag, &tag);
688
689    // Decrypt and verify round-trip
690    var m: [plaintext.len]u8 = undefined;
691    try Aes128Ccm4_7.decrypt(&m, &c, tag, &ad, nonce, key);
692    try testing.expectEqualSlices(u8, &plaintext, &m);
693}
694
695test "Aes128Ccm6 - NIST SP 800-38C Example 2" {
696    const Aes128Ccm6_8 = AesCcm(crypto.core.aes.Aes128, 6, 8);
697
698    // Example 2 (C.2): Klen=128, Tlen=48, Nlen=64, Alen=128, Plen=128
699    var key: [16]u8 = undefined;
700    _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f");
701    var nonce: [8]u8 = undefined;
702    _ = try hexToBytes(&nonce, "1011121314151617");
703    var ad: [16]u8 = undefined;
704    _ = try hexToBytes(&ad, "000102030405060708090a0b0c0d0e0f");
705    var plaintext: [16]u8 = undefined;
706    _ = try hexToBytes(&plaintext, "202122232425262728292a2b2c2d2e2f");
707
708    // Expected ciphertext and tag from NIST
709    var expected_ciphertext: [16]u8 = undefined;
710    _ = try hexToBytes(&expected_ciphertext, "d2a1f0e051ea5f62081a7792073d593d");
711    var expected_tag: [6]u8 = undefined;
712    _ = try hexToBytes(&expected_tag, "1fc64fbfaccd");
713
714    // Encrypt
715    var c: [plaintext.len]u8 = undefined;
716    var tag: [Aes128Ccm6_8.tag_length]u8 = undefined;
717
718    Aes128Ccm6_8.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
719
720    // Verify ciphertext matches NIST expected output
721    try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
722
723    // Verify tag matches NIST expected output
724    try testing.expectEqualSlices(u8, &expected_tag, &tag);
725
726    // Decrypt and verify round-trip
727    var m: [plaintext.len]u8 = undefined;
728    try Aes128Ccm6_8.decrypt(&m, &c, tag, &ad, nonce, key);
729    try testing.expectEqualSlices(u8, &plaintext, &m);
730}
731
732test "Aes128Ccm8 - NIST SP 800-38C Example 3" {
733    const Aes128Ccm8_12 = AesCcm(crypto.core.aes.Aes128, 8, 12);
734
735    // Example 3 (C.3): Klen=128, Tlen=64, Nlen=96, Alen=160, Plen=192
736    var key: [16]u8 = undefined;
737    _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f");
738    var nonce: [12]u8 = undefined;
739    _ = try hexToBytes(&nonce, "101112131415161718191a1b");
740    var ad: [20]u8 = undefined;
741    _ = try hexToBytes(&ad, "000102030405060708090a0b0c0d0e0f10111213");
742    var plaintext: [24]u8 = undefined;
743    _ = try hexToBytes(&plaintext, "202122232425262728292a2b2c2d2e2f3031323334353637");
744
745    // Expected ciphertext and tag from NIST
746    var expected_ciphertext: [24]u8 = undefined;
747    _ = try hexToBytes(&expected_ciphertext, "e3b201a9f5b71a7a9b1ceaeccd97e70b6176aad9a4428aa5");
748    var expected_tag: [8]u8 = undefined;
749    _ = try hexToBytes(&expected_tag, "484392fbc1b09951");
750
751    // Encrypt
752    var c: [plaintext.len]u8 = undefined;
753    var tag: [Aes128Ccm8_12.tag_length]u8 = undefined;
754
755    Aes128Ccm8_12.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
756
757    // Verify ciphertext matches NIST expected output
758    try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
759
760    // Verify tag matches NIST expected output
761    try testing.expectEqualSlices(u8, &expected_tag, &tag);
762
763    // Decrypt and verify round-trip
764    var m: [plaintext.len]u8 = undefined;
765    try Aes128Ccm8_12.decrypt(&m, &c, tag, &ad, nonce, key);
766    try testing.expectEqualSlices(u8, &plaintext, &m);
767}
768
769test "Aes128Ccm14 - NIST SP 800-38C Example 4" {
770    const Aes128Ccm14_13 = AesCcm(crypto.core.aes.Aes128, 14, 13);
771
772    // Example 4 (C.4): Klen=128, Tlen=112, Nlen=104, Alen=524288, Plen=256
773    // Note: Associated data is 65536 bytes (256-byte pattern repeated 256 times)
774    var key: [16]u8 = undefined;
775    _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f");
776    var nonce: [13]u8 = undefined;
777    _ = try hexToBytes(&nonce, "101112131415161718191a1b1c");
778    var plaintext: [32]u8 = undefined;
779    _ = try hexToBytes(&plaintext, "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");
780
781    // Generate 65536-byte associated data (256-byte pattern repeated 256 times)
782    var pattern: [256]u8 = undefined;
783    _ = try hexToBytes(&pattern, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
784
785    var ad: [65536]u8 = undefined;
786    for (0..256) |i| {
787        @memcpy(ad[i * 256 .. (i + 1) * 256], &pattern);
788    }
789
790    // Expected ciphertext and tag from NIST
791    var expected_ciphertext: [32]u8 = undefined;
792    _ = try hexToBytes(&expected_ciphertext, "69915dad1e84c6376a68c2967e4dab615ae0fd1faec44cc484828529463ccf72");
793    var expected_tag: [14]u8 = undefined;
794    _ = try hexToBytes(&expected_tag, "b4ac6bec93e8598e7f0dadbcea5b");
795
796    // Encrypt
797    var c: [plaintext.len]u8 = undefined;
798    var tag: [Aes128Ccm14_13.tag_length]u8 = undefined;
799
800    Aes128Ccm14_13.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
801
802    // Verify ciphertext matches NIST expected output
803    try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
804
805    // Verify tag matches NIST expected output
806    try testing.expectEqualSlices(u8, &expected_tag, &tag);
807
808    // Decrypt and verify round-trip
809    var m: [plaintext.len]u8 = undefined;
810    try Aes128Ccm14_13.decrypt(&m, &c, tag, &ad, nonce, key);
811    try testing.expectEqualSlices(u8, &plaintext, &m);
812}
813
814// CCM* test vectors (encryption-only mode with M=0)
815
816test "Aes128Ccm0 - IEEE 802.15.4 Data Frame (Encryption-only)" {
817    // IEEE 802.15.4 test vector from section 2.7
818    // Security level 0x04 (ENC, encryption without authentication)
819    var key: [16]u8 = undefined;
820    _ = try hexToBytes(&key, "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF");
821    var nonce: [13]u8 = undefined;
822    _ = try hexToBytes(&nonce, "ACDE48000000000100000005" ++ "04");
823    var plaintext: [4]u8 = undefined;
824    _ = try hexToBytes(&plaintext, "61626364");
825    var ad: [26]u8 = undefined;
826    _ = try hexToBytes(&ad, "69DC84214302000000004DEAC010000000048DEAC04050000");
827
828    // Expected ciphertext from IEEE spec
829    var expected_ciphertext: [4]u8 = undefined;
830    _ = try hexToBytes(&expected_ciphertext, "D43E022B");
831
832    // Encrypt
833    var c: [plaintext.len]u8 = undefined;
834    var tag: [Aes128Ccm0.tag_length]u8 = undefined;
835
836    Aes128Ccm0.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
837
838    // Verify ciphertext matches IEEE expected output
839    try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
840
841    // Decrypt and verify round-trip
842    var m: [plaintext.len]u8 = undefined;
843    try Aes128Ccm0.decrypt(&m, &c, tag, &ad, nonce, key);
844    try testing.expectEqualSlices(u8, &plaintext, &m);
845}
846
847test "Aes128Ccm0 - Zero-length plaintext with encryption-only" {
848    const key: [16]u8 = [_]u8{0x42} ** 16;
849    const nonce: [13]u8 = [_]u8{0x11} ** 13;
850    const m = "";
851    const ad = "some associated data";
852    var c: [m.len]u8 = undefined;
853    var m2: [m.len]u8 = undefined;
854    var tag: [Aes128Ccm0.tag_length]u8 = undefined;
855
856    Aes128Ccm0.encrypt(&c, &tag, m, ad, nonce, key);
857
858    try Aes128Ccm0.decrypt(&m2, &c, tag, ad, nonce, key);
859
860    try testing.expectEqual(@as(usize, 0), m2.len);
861}
862
863test "Aes256Ccm0 - Basic encryption-only round-trip" {
864    const key: [32]u8 = [_]u8{0x42} ** 32;
865    const nonce: [13]u8 = [_]u8{0x11} ** 13;
866    const m = "Hello, CCM* encryption-only mode!";
867    var c: [m.len]u8 = undefined;
868    var m2: [m.len]u8 = undefined;
869    var tag: [Aes256Ccm0.tag_length]u8 = undefined;
870
871    Aes256Ccm0.encrypt(&c, &tag, m, "", nonce, key);
872
873    try Aes256Ccm0.decrypt(&m2, &c, tag, "", nonce, key);
874
875    try testing.expectEqualSlices(u8, m[0..], m2[0..]);
876}