| ... | ... | @@ -0,0 +1,876 @@ |
| 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 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 | |
| 262 | const testing = std.testing; |
| 263 | const fmt = std.fmt; |
| 264 | const hexToBytes = fmt.hexToBytes; |
| 265 | |
| 266 | test "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 | |
| 281 | test "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 | |
| 301 | test "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 | |
| 316 | test "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 | |
| 332 | test "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 | |
| 347 | test "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 | |
| 362 | test "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 | |
| 379 | test "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 | |
| 399 | test "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 | |
| 418 | test "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 | |
| 445 | test "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 | |
| 469 | test "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 | |
| 499 | test "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 | |
| 522 | test "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 | |
| 545 | test "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 | |
| 582 | test "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 | |
| 619 | test "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 | |
| 658 | test "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 | |
| 695 | test "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 | |
| 732 | test "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 | |
| 769 | test "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 | |
| 816 | test "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 | |
| 847 | test "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 | |
| 863 | test "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 | } |