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