code wiki / (root) / nx_aes128_gcm.nx

nx_aes128_gcm.nx source

↩ module page · 281 lines · 9645 B

1// nx_aes128_gcm.nx -- canonical AES-128-GCM (AEAD) per NIST SP 800-38D 2// + RFC 5288 (TLS). 96-bit IV form only (the only form TLS 1.3 uses). 3// 4// COMPOSED, not re-implemented: 5// - AES-128 block encrypt + key expansion -> nx_aes.nx (canonical 6// S-box, MixColumns, ShiftRows, AddRoundKey) 7// - GF(2^128) multiplication / GHASH -> nx_ghash.nx 8// 9// Per the bit-level no-tool-proliferation cardinal, this file MUST 10// NOT re-implement either AES or GHASH inline. Future hardware- 11// accelerated variants (AES-NI on x86, AES instructions on aarch64) 12// ship as alternative backends behind the same API surface. 13// 14// Public API (TLS 1.3 record-layer compatible): 15// nx_aes128_gcm_seal(key16, iv12, aad, aad_len, pt, pt_len, 16// ct_out, tag16_out) -> 0 17// nx_aes128_gcm_open(key16, iv12, aad, aad_len, ct, ct_len, tag16, 18// pt_out) -> 0 | -1 (tag fail) 19// 20// Per Cardinals 9 (single-responsibility), 22 (composition). 21// 22// license_tier: INDEPENDENT_REDERIVE 23// genealogy_id: international-research-sources/nist/sp_800_38d + ietf/rfc_5288 24// lineage_id: nishi_aes128_gcm_q10 25 26// nx_safety_envelope: 27// intended_use: "TLS 1.3 AES-128-GCM cipher suite AEAD" 28// sil_target: SIL3 29// evidence: [composed_from_nx_aes_canonical, composed_from_nx_ghash, 30// NIST_SP_800_38D_test_vectors_VERIFIED] 31// verdict: NOT_YET_EVALUATED 32 33import "nx_syscalls.nx" 34import "nx_aes.nx" 35import "nx_ghash.nx" 36 37// Increment the rightmost 32 bits of a 16-byte counter block. 38// Per SP 800-38D ยง6.2 (inc32). 39func nx_aes128_gcm_inc32(ctr: *u8) -> i64 { 40 var i: i64 = 15 41 var done: i64 = 0 42 while done == 0 { 43 if i < 12 { done = 1 } 44 if done == 0 { 45 let nb: i64 = (ctr[i] + 1) & 0xff 46 ctr[i] = nb as u8 47 if nb != 0 { done = 1 } 48 else { i = i - 1 } 49 } 50 } 51 return 0 52} 53 54// Constant-time(ish) 16-byte buffer equality. 55func nx_aes128_gcm_tag_eq(a: *u8, b: *u8) -> i64 { 56 var diff: i64 = 0 57 var i: i64 = 0 58 while i < 16 { 59 diff = diff | ((a[i] ^ b[i]) & 0xff) 60 i = i + 1 61 } 62 if diff == 0 { return 1 } 63 return 0 64} 65 66// Encrypt one FULL CTR block (word ops, no per-byte loop): ks = E_K(ctr); 67// ct_out[pos..pos+16] = pt[pos..pos+16] XOR ks; ctr += 1 (inc32). Ciphertext is left in 68// ct_out for the GHASH pass to reflect. Caller guarantees pos + 16 <= pt_len. 69func gcm_ctr_store(ctr: *u8, sched: *u8, pt: *u8, ct_out: *u8, pos: i64, ks: *u8) -> i64 { 70 let ctw: *i64 = ctr as *i64 71 let ksw: *i64 = ks as *i64 72 ksw[0] = ctw[0]; ksw[1] = ctw[1] 73 let _e: i64 = __aes128_enc_block(ks, sched) 74 let pw: *i64 = ((pt as i64) + pos) as *i64 75 let ow: *i64 = ((ct_out as i64) + pos) as *i64 76 ow[0] = pw[0] ^ ksw[0] 77 ow[1] = pw[1] ^ ksw[1] 78 nx_aes128_gcm_inc32(ctr) 79 return 0 80} 81 82// AES-128-GCM seal (encrypt + authenticate). 83// Inputs: 84// key16: 16-byte AES key 85// iv12: 12-byte IV (TLS 1.3 supplies record nonce here) 86// aad: additional authenticated data (TLS record header for 87// TLS 1.3, length aad_len) 88// pt: plaintext bytes (length pt_len) 89// Outputs: 90// ct_out: pt_len ciphertext bytes (caller allocates) 91// tag16_out: 16-byte AEAD tag 92// Returns 0 on success. 93func nx_aes128_gcm_seal(key16: *u8, iv12: *u8, 94 aad: *u8, aad_len: i64, 95 pt: *u8, pt_len: i64, 96 ct_out: *u8, tag16_out: *u8) -> i64 { 97 let sched: *u8 = sys_mmap(176) 98 aes128_expand_key(key16, sched) 99 100 // H = E_K(0^128) 101 let zero: *u8 = sys_mmap(16) 102 var i: i64 = 0 103 while i < 16 { zero[i] = 0; i = i + 1 } 104 let h: *u8 = sys_mmap(16) 105 aes128_encrypt_block(zero, sched, h) 106 107 // J0 = iv12 || 0x00000001 108 let j0: *u8 = sys_mmap(16) 109 i = 0 110 while i < 12 { j0[i] = iv12[i]; i = i + 1 } 111 j0[12] = 0; j0[13] = 0; j0[14] = 0; j0[15] = 1 112 113 // E_K(J0) for tag mask 114 let ekj0: *u8 = sys_mmap(16) 115 aes128_encrypt_block(j0, sched, ekj0) 116 117 // FAST PATH: keep the GHASH accumulator in the reflected (PCLMULQDQ) domain so H is 118 // reflected ONCE here (rh) instead of every block, and the result reflected back ONCE 119 // at the end. py = phi(Y); phi(0)=0. Field-identical to the Horner path (gh_clmul_core 120 // under nx_ghash_clmul_gate). 121 let rh: *u8 = sys_mmap(16) 122 nx_ghash_reflect16(rh, h) 123 let py: *u8 = sys_mmap(16) 124 i = 0 125 while i < 16 { py[i] = 0; i = i + 1 } 126 127 // Reflected H-powers for 4-way aggregation: rh1..rh4 = phi(H^1..H^4). Computed once. 128 let rh2: *u8 = sys_mmap(16) 129 let rh3: *u8 = sys_mmap(16) 130 let rh4: *u8 = sys_mmap(16) 131 nx_ghash_mul_rev(rh, rh, rh2) // phi(H^2) 132 nx_ghash_mul_rev(rh2, rh, rh3) // phi(H^3) 133 nx_ghash_mul_rev(rh3, rh, rh4) // phi(H^4) 134 135 // AAD, zero-padded to 16-byte blocks (matches nx_ghash_update_buf semantics). 136 let ablk: *u8 = sys_mmap(16) 137 var apos: i64 = 0 138 while apos < aad_len { 139 var ab: i64 = 0 140 while ab < 16 { 141 if apos + ab < aad_len { ablk[ab] = aad[apos + ab] } 142 else { ablk[ab] = 0 } 143 ab = ab + 1 144 } 145 nx_ghash_upd_rev(py, rh, ablk) 146 apos = apos + 16 147 } 148 149 // CTR starts at J0+1. Encrypt + simultaneous GHASH of ciphertext. 150 let ctr: *u8 = sys_mmap(16) 151 i = 0 152 while i < 16 { ctr[i] = j0[i]; i = i + 1 } 153 nx_aes128_gcm_inc32(ctr) 154 155 let ks: *u8 = sys_mmap(16) 156 let ct_block: *u8 = sys_mmap(16) 157 let ksw: *i64 = ks as *i64 158 var pos: i64 = 0 159 // 4-way aggregated blocks: encrypt 4 CTR blocks (contiguous ciphertext into ct_out), 160 // then ONE aggregated GHASH update (single reduction over the group). 161 while pos + 64 <= pt_len { 162 gcm_ctr_store(ctr, sched, pt, ct_out, pos, ks) 163 gcm_ctr_store(ctr, sched, pt, ct_out, pos + 16, ks) 164 gcm_ctr_store(ctr, sched, pt, ct_out, pos + 32, ks) 165 gcm_ctr_store(ctr, sched, pt, ct_out, pos + 48, ks) 166 nx_ghash_upd_rev4(py, rh, rh2, rh3, rh4, ((ct_out as i64) + pos) as *u8) 167 pos = pos + 64 168 } 169 // leftover full 16-byte blocks (fewer than 4): single-block reflected update. 170 while pos + 16 <= pt_len { 171 gcm_ctr_store(ctr, sched, pt, ct_out, pos, ks) 172 nx_ghash_upd_rev(py, rh, ((ct_out as i64) + pos) as *u8) 173 pos = pos + 16 174 } 175 // ragged tail block (0 < pt_len - pos < 16): byte path, ciphertext zero-padded for GHASH. 176 if pos < pt_len { 177 let ctw2: *i64 = ctr as *i64 178 ksw[0] = ctw2[0]; ksw[1] = ctw2[1] 179 let _e2: i64 = __aes128_enc_block(ks, sched) 180 var b: i64 = 0 181 while b < 16 { 182 if pos + b >= pt_len { ct_block[b] = 0 } 183 else { 184 let c: i64 = (pt[pos + b] ^ ks[b]) & 0xff 185 ct_out[pos + b] = c as u8 186 ct_block[b] = c as u8 187 } 188 b = b + 1 189 } 190 nx_ghash_upd_rev(py, rh, ct_block) 191 nx_aes128_gcm_inc32(ctr) 192 pos = pos + 16 193 } 194 195 // Length block [aad_len_bits BE64][pt_len_bits BE64] + final GHASH. 196 let lenblk: *u8 = sys_mmap(16) 197 nx_ghash_be64_put(lenblk, aad_len * 8) 198 nx_ghash_be64_put(lenblk + 8, pt_len * 8) 199 nx_ghash_upd_rev(py, rh, lenblk) 200 201 // Y = phi(py); Tag = Y XOR E_K(J0). 202 let y: *u8 = sys_mmap(16) 203 nx_ghash_reflect16(y, py) 204 i = 0 205 while i < 16 { 206 tag16_out[i] = (y[i] ^ ekj0[i]) & 0xff 207 i = i + 1 208 } 209 return 0 210} 211 212// AES-128-GCM open (verify + decrypt). 213// Returns 0 on tag verify, -1 on tag mismatch. pt_out is only 214// populated on success. 215func nx_aes128_gcm_open(key16: *u8, iv12: *u8, 216 aad: *u8, aad_len: i64, 217 ct: *u8, ct_len: i64, tag16: *u8, 218 pt_out: *u8) -> i64 { 219 let sched: *u8 = sys_mmap(176) 220 aes128_expand_key(key16, sched) 221 222 // Same H + J0 + E_K(J0) as seal. 223 let zero: *u8 = sys_mmap(16) 224 var i: i64 = 0 225 while i < 16 { zero[i] = 0; i = i + 1 } 226 let h: *u8 = sys_mmap(16) 227 aes128_encrypt_block(zero, sched, h) 228 229 let j0: *u8 = sys_mmap(16) 230 i = 0 231 while i < 12 { j0[i] = iv12[i]; i = i + 1 } 232 j0[12] = 0; j0[13] = 0; j0[14] = 0; j0[15] = 1 233 234 let ekj0: *u8 = sys_mmap(16) 235 aes128_encrypt_block(j0, sched, ekj0) 236 237 // GHASH AAD || CT (note: opens authenticate over CIPHERTEXT, 238 // not plaintext, so the same Y is computed regardless of decrypt 239 // ordering). 240 let y: *u8 = sys_mmap(16) 241 i = 0 242 while i < 16 { y[i] = 0; i = i + 1 } 243 nx_ghash_update_buf(y, h, aad, aad_len) 244 nx_ghash_update_buf(y, h, ct, ct_len) 245 nx_ghash_finalize(y, h, aad_len, ct_len) 246 247 // Expected tag = Y XOR E_K(J0). Compare against supplied tag16. 248 let expected: *u8 = sys_mmap(16) 249 i = 0 250 while i < 16 { 251 expected[i] = (y[i] ^ ekj0[i]) & 0xff 252 i = i + 1 253 } 254 if nx_aes128_gcm_tag_eq(expected, tag16) != 1 { return 0 - 1 } 255 256 // Tag OK -- decrypt by re-running CTR mode. 257 let ctr: *u8 = sys_mmap(16) 258 i = 0 259 while i < 16 { ctr[i] = j0[i]; i = i + 1 } 260 nx_aes128_gcm_inc32(ctr) 261 262 let ks: *u8 = sys_mmap(16) 263 var pos: i64 = 0 264 while pos < ct_len { 265 aes128_encrypt_block(ctr, sched, ks) 266 var b: i64 = 0 267 while b < 16 { 268 if pos + b < ct_len { 269 pt_out[pos + b] = (ct[pos + b] ^ ks[b]) & 0xff 270 } 271 b = b + 1 272 } 273 nx_aes128_gcm_inc32(ctr) 274 pos = pos + 16 275 } 276 return 0 277} 278 279func main() -> i64 { 280 return 0 281}