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}