code wiki / (root) / nx_aes256_gcm.nx

nx_aes256_gcm.nx source

↩ module page · 142 lines · 6729 B

1// nx_aes256_gcm.nx -- canonical AES-256-GCM (AEAD) per NIST SP 800-38D + RFC 5288, the TLS_AES_256_GCM_SHA384 2// cipher-suite AEAD. COMPOSED (Cardinals 9/15/22): reuses nx_aes.nx's canonical S-box + round functions 3// (_aes_sub_bytes/_aes_shift_rows/_aes_mix_columns/_aes_add_round_key/_aes_sbox/_aes_rcon) and nx_ghash.nx's 4// GF(2^128)/GHASH VERBATIM -- AES-256 differs from AES-128 ONLY in the key schedule (32-byte key -> 240-byte 5// schedule, 15 round keys, extra SubWord at word%8==4) and 14 rounds. GHASH is key-size-independent so it is 6// unchanged. This is the "full cipher coverage" so we can complete a handshake whichever TLS-1.3 suite a 7// Cloudflare-class CDN picks (0x1301 AES-128-GCM / 0x1302 AES-256-GCM / 0x1303 ChaCha20) after JA3-mimicry. 8// 96-bit IV form only (all TLS 1.3 uses). Correctness-first software path (no AES-NI-256); GHASH via the simple 9// Horner API. NIST SP 800-38D test vectors gated in nx_aes256_gcm_gate. license_tier: INDEPENDENT_REDERIVE 10import "nx_syscalls.nx" 11import "nx_aes.nx" 12import "nx_ghash.nx" 13 14// AES-256 key expansion (FIPS 197 §5.2, Nk=8, Nr=14). key=32 bytes -> out=240 bytes (60 words). 15func aes256_expand_key(key: *u8, out: *u8) -> i64 { 16 var i: i64 = 0 17 while i < 32 { out[i] = key[i]; i = i + 1 } // first 8 words = the key 18 var w: i64 = 8 19 while w < 60 { 20 let off: i64 = w * 4 21 var t0: i64 = (out[off - 4] as i64) & 0xff 22 var t1: i64 = (out[off - 3] as i64) & 0xff 23 var t2: i64 = (out[off - 2] as i64) & 0xff 24 var t3: i64 = (out[off - 1] as i64) & 0xff 25 if (w % 8) == 0 { 26 // RotWord -> SubWord -> XOR Rcon(w/8) on byte 0 27 let r0: i64 = t1; let r1: i64 = t2; let r2: i64 = t3; let r3: i64 = t0 28 let rc: i64 = _aes_rcon(w / 8) 29 t0 = _aes_sbox(r0) ^ rc 30 t1 = _aes_sbox(r1) 31 t2 = _aes_sbox(r2) 32 t3 = _aes_sbox(r3) 33 } else { if (w % 8) == 4 { 34 // AES-256-specific: SubWord (no RotWord/Rcon) 35 t0 = _aes_sbox(t0); t1 = _aes_sbox(t1); t2 = _aes_sbox(t2); t3 = _aes_sbox(t3) 36 } } 37 // word[w] = word[w-8] XOR temp 38 let p0: i64 = (out[off - 32] as i64) & 0xff 39 let p1: i64 = (out[off - 31] as i64) & 0xff 40 let p2: i64 = (out[off - 30] as i64) & 0xff 41 let p3: i64 = (out[off - 29] as i64) & 0xff 42 out[off + 0] = (p0 ^ t0) & 0xff 43 out[off + 1] = (p1 ^ t1) & 0xff 44 out[off + 2] = (p2 ^ t2) & 0xff 45 out[off + 3] = (p3 ^ t3) & 0xff 46 w = w + 1 47 } 48 return 0 49} 50 51// AES-256 block encrypt (FIPS 197 §5.1, 14 rounds). sched = 240-byte schedule. out may alias in. 52func aes256_encrypt_block(in_block: *u8, sched: *u8, out_block: *u8) -> i64 { 53 var i: i64 = 0 54 while i < 16 { out_block[i] = in_block[i]; i = i + 1 } 55 _aes_add_round_key(out_block, sched, 0) 56 var r: i64 = 1 57 while r < 14 { 58 _aes_sub_bytes(out_block) 59 _aes_shift_rows(out_block) 60 _aes_mix_columns(out_block) 61 _aes_add_round_key(out_block, sched, r) 62 r = r + 1 63 } 64 _aes_sub_bytes(out_block) 65 _aes_shift_rows(out_block) 66 _aes_add_round_key(out_block, sched, 14) 67 return 0 68} 69 70func gcm256_inc32(ctr: *u8) -> i64 { 71 var i: i64 = 15 72 var done: i64 = 0 73 while done == 0 { 74 if i < 12 { done = 1 } 75 if done == 0 { 76 let nb: i64 = ((ctr[i] as i64) + 1) & 0xff 77 ctr[i] = nb as u8 78 if nb != 0 { done = 1 } else { i = i - 1 } 79 } 80 } 81 return 0 82} 83func gcm256_tag_eq(a: *u8, b: *u8) -> i64 { 84 var diff: i64 = 0; var i: i64 = 0 85 while i < 16 { diff = diff | (((a[i] as i64) ^ (b[i] as i64)) & 0xff); i = i + 1 } 86 if diff == 0 { return 1 } 87 return 0 88} 89 90// AES-256-GCM seal. key32/iv12/aad/pt -> ct_out(pt_len) + tag16_out. Returns 0. 91func nx_aes256_gcm_seal(key32: *u8, iv12: *u8, aad: *u8, aad_len: i64, pt: *u8, pt_len: i64, ct_out: *u8, tag16_out: *u8) -> i64 { 92 let sched: *u8 = sys_mmap(240) 93 aes256_expand_key(key32, sched) 94 let zero: *u8 = sys_mmap(16); var i: i64 = 0; while i < 16 { zero[i] = 0; i = i + 1 } 95 let h: *u8 = sys_mmap(16); aes256_encrypt_block(zero, sched, h) 96 let j0: *u8 = sys_mmap(16); i = 0; while i < 12 { j0[i] = iv12[i]; i = i + 1 } j0[12]=0; j0[13]=0; j0[14]=0; j0[15]=1 97 let ekj0: *u8 = sys_mmap(16); aes256_encrypt_block(j0, sched, ekj0) 98 // CTR mode encrypt starting J0+1 99 let ctr: *u8 = sys_mmap(16); i = 0; while i < 16 { ctr[i] = j0[i]; i = i + 1 } gcm256_inc32(ctr) 100 let ks: *u8 = sys_mmap(16) 101 var pos: i64 = 0 102 while pos < pt_len { 103 aes256_encrypt_block(ctr, sched, ks) 104 var b: i64 = 0 105 while b < 16 { if pos + b < pt_len { ct_out[pos + b] = (((pt[pos + b] as i64) ^ (ks[b] as i64)) & 0xff) as u8 } b = b + 1 } 106 gcm256_inc32(ctr); pos = pos + 16 107 } 108 // GHASH over AAD || CT (Horner) + length block, tag = Y XOR E_K(J0) 109 let y: *u8 = sys_mmap(16); i = 0; while i < 16 { y[i] = 0; i = i + 1 } 110 nx_ghash_update_buf(y, h, aad, aad_len) 111 nx_ghash_update_buf(y, h, ct_out, pt_len) 112 nx_ghash_finalize(y, h, aad_len, pt_len) 113 i = 0; while i < 16 { tag16_out[i] = (((y[i] as i64) ^ (ekj0[i] as i64)) & 0xff) as u8; i = i + 1 } 114 return 0 115} 116 117// AES-256-GCM open (verify + decrypt). Returns 0 on tag OK (pt_out filled), -1 on tag mismatch. 118func nx_aes256_gcm_open(key32: *u8, iv12: *u8, aad: *u8, aad_len: i64, ct: *u8, ct_len: i64, tag16: *u8, pt_out: *u8) -> i64 { 119 let sched: *u8 = sys_mmap(240) 120 aes256_expand_key(key32, sched) 121 let zero: *u8 = sys_mmap(16); var i: i64 = 0; while i < 16 { zero[i] = 0; i = i + 1 } 122 let h: *u8 = sys_mmap(16); aes256_encrypt_block(zero, sched, h) 123 let j0: *u8 = sys_mmap(16); i = 0; while i < 12 { j0[i] = iv12[i]; i = i + 1 } j0[12]=0; j0[13]=0; j0[14]=0; j0[15]=1 124 let ekj0: *u8 = sys_mmap(16); aes256_encrypt_block(j0, sched, ekj0) 125 let y: *u8 = sys_mmap(16); i = 0; while i < 16 { y[i] = 0; i = i + 1 } 126 nx_ghash_update_buf(y, h, aad, aad_len) 127 nx_ghash_update_buf(y, h, ct, ct_len) 128 nx_ghash_finalize(y, h, aad_len, ct_len) 129 let expected: *u8 = sys_mmap(16) 130 i = 0; while i < 16 { expected[i] = (((y[i] as i64) ^ (ekj0[i] as i64)) & 0xff) as u8; i = i + 1 } 131 if gcm256_tag_eq(expected, tag16) != 1 { return 0 - 1 } 132 let ctr: *u8 = sys_mmap(16); i = 0; while i < 16 { ctr[i] = j0[i]; i = i + 1 } gcm256_inc32(ctr) 133 let ks: *u8 = sys_mmap(16) 134 var pos: i64 = 0 135 while pos < ct_len { 136 aes256_encrypt_block(ctr, sched, ks) 137 var b: i64 = 0 138 while b < 16 { if pos + b < ct_len { pt_out[pos + b] = (((ct[pos + b] as i64) ^ (ks[b] as i64)) & 0xff) as u8 } b = b + 1 } 139 gcm256_inc32(ctr); pos = pos + 16 140 } 141 return 0 142}