nx_aes_cbc.nx source
↩ module page · 98 lines · 6195 B
1// nx_aes_cbc.nx -- AES-128-CBC DECRYPT (NIST SP 800-38A ยง6.2) over the FIPS-197 block primitive in
2// nx_aes (aes128_decrypt_block + aes128_expand_key). The mode HLS uses for #EXT-X-KEY:METHOD=AES-128:
3// each .ts segment is one CBC stream with PKCS7 padding. CBC decrypt: P_i = InvCipher(C_i) XOR C_{i-1}
4// (C_{-1} = IV). In-place safe (the cipher block is saved before the plaintext overwrites it).
5// GATE (main): the canonical NIST SP 800-38A F.2.2 CBC-AES128 4-block vector (authoritative KAT) +
6// a PKCS7 strip check. Crypto is KAT-proven here BEFORE nx_hls_get composes it. license_tier: ORIGINAL
7// module: nishi-core.crypto.aes_cbc
8import "nx_aes.nx"
9import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
10import "nx_syscalls.nx"
11
12// AES-128-CBC decrypt n bytes (n must be a multiple of 16). `sched` = 176-byte expanded key. `iv` = 16
13// bytes. out[0..n) = plaintext (may alias ct). Returns n, or -1 if n is not a multiple of the block size.
14func aes128_cbc_decrypt(ct: *u8, n: i64, sched: *u8, iv: *u8, out: *u8) -> i64 {
15 if n <= 0 { return 0 }
16 if (n % 16) != 0 { return 0 - 1 }
17 let prev: *u8 = sys_mmap(16); var z: i64 = 0; while z < 16 { prev[z] = iv[z]; z = z + 1 }
18 let cur: *u8 = sys_mmap(16); let dec: *u8 = sys_mmap(16)
19 var off: i64 = 0
20 while off < n {
21 var i: i64 = 0; while i < 16 { cur[i] = ct[off + i]; i = i + 1 } // save this cipher block (prev for next)
22 aes128_decrypt_block((ct as i64 + off) as *u8, sched, dec) // dec = InvCipher(C_i)
23 i = 0; while i < 16 { out[off + i] = (dec[i] ^ prev[i]) & 0xff; i = i + 1 } // P_i = dec XOR C_{i-1}
24 i = 0; while i < 16 { prev[i] = cur[i]; i = i + 1 } // C_{i-1} <- C_i
25 off = off + 16
26 }
27 return n
28}
29
30// strip PKCS7 padding (RFC 5652): the last byte is the pad count (1..16). Lenient -- if the trailer is
31// not a valid pad, return n unchanged (never corrupt the payload). Returns the unpadded length.
32func aes128_pkcs7_strip(buf: *u8, n: i64) -> i64 {
33 if n <= 0 { return n }
34 let pad: i64 = buf[n - 1] as i64
35 if pad < 1 { return n }
36 if pad > 16 { return n }
37 if pad > n { return n }
38 return n - pad
39}
40
41// ===================== GATE =====================
42func cbc_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
43// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
44// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
45// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
46// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
47func cbc_putn(v: i64) -> i64 { nxi_out(v); return 0 }
48func cbc_hexv(c: i64) -> i64 { if c>=48 { if c<=57 { return c-48 } } if c>=97 { if c<=102 { return c-97+10 } } if c>=65 { if c<=70 { return c-65+10 } } return 0 }
49// parse `nbytes` from a hex string (2 chars/byte) into out.
50func cbc_hex2bin(hex: *u8, out: *u8, nbytes: i64) -> i64 {
51 var i: i64 = 0
52 while i < nbytes { let hi: i64 = cbc_hexv(hex[i*2] as i64); let lo: i64 = cbc_hexv(hex[i*2+1] as i64); out[i] = ((hi<<4)|lo) & 0xff; i = i + 1 }
53 return nbytes
54}
55
56func main() -> i64 {
57 var pass: i64 = 0; var tot: i64 = 0
58 cbc_puts("nx_aes_cbc gate (AES-128-CBC decrypt, NIST SP 800-38A F.2.2)\n" as *u8)
59
60 // ---- KAT1: NIST SP 800-38A F.2.2 CBC-AES128.Decrypt, 4 blocks (64 bytes), no padding ----
61 let keyhex: *u8 = "2b7e151628aed2a6abf7158809cf4f3c\x00" as *u8
62 let ivhex: *u8 = "000102030405060708090a0b0c0d0e0f\x00" as *u8
63 let cthex: *u8 = "7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a7\x00" as *u8
64 let pthex: *u8 = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710\x00" as *u8
65
66 let key: *u8 = sys_mmap(16); cbc_hex2bin(keyhex, key, 16)
67 let iv: *u8 = sys_mmap(16); cbc_hex2bin(ivhex, iv, 16)
68 let ct: *u8 = sys_mmap(64); cbc_hex2bin(cthex, ct, 64)
69 let pt: *u8 = sys_mmap(64); cbc_hex2bin(pthex, pt, 64)
70 let sched: *u8 = sys_mmap(176); aes128_expand_key(key, sched)
71 let out: *u8 = sys_mmap(64)
72 let rn: i64 = aes128_cbc_decrypt(ct, 64, sched, iv, out)
73 var r1: i64 = 1
74 if rn != 64 { r1 = 0 }
75 if r1 == 1 { var i: i64 = 0; while i < 64 { if (out[i] & 0xff) != (pt[i] & 0xff) { r1 = 0; i = 64 } else { i = i + 1 } } }
76 if r1 == 1 { cbc_puts(" PASS KAT1 4-block CBC decrypt == NIST plaintext\n" as *u8); pass = pass + 1 } else { cbc_puts(" FAIL KAT1 (rn=" as *u8); cbc_putn(rn); cbc_puts(")\n" as *u8) }
77 tot = tot + 1
78
79 // ---- KAT2: in-place decrypt (out aliases ct) yields the same plaintext ----
80 let ct2: *u8 = sys_mmap(64); cbc_hex2bin(cthex, ct2, 64)
81 aes128_cbc_decrypt(ct2, 64, sched, iv, ct2)
82 var r2: i64 = 1; var j: i64 = 0; while j < 64 { if (ct2[j] & 0xff) != (pt[j] & 0xff) { r2 = 0; j = 64 } else { j = j + 1 } }
83 if r2 == 1 { cbc_puts(" PASS KAT2 in-place decrypt matches\n" as *u8); pass = pass + 1 } else { cbc_puts(" FAIL KAT2 in-place\n" as *u8) }
84 tot = tot + 1
85
86 // ---- KAT3: PKCS7 strip (16 bytes, pad=4 -> 12) + reject-bad (pad=0 -> unchanged) ----
87 let pb: *u8 = sys_mmap(16); var z: i64 = 0; while z < 12 { pb[z] = 65 as u8; z = z + 1 } pb[12]=4 as u8; pb[13]=4 as u8; pb[14]=4 as u8; pb[15]=4 as u8
88 var r3: i64 = 1
89 if aes128_pkcs7_strip(pb, 16) != 12 { r3 = 0 }
90 let pb2: *u8 = sys_mmap(16); var z2: i64 = 0; while z2 < 16 { pb2[z2] = 65 as u8; z2 = z2 + 1 } pb2[15]=0 as u8
91 if aes128_pkcs7_strip(pb2, 16) != 16 { r3 = 0 } // pad byte 0 is invalid -> unchanged
92 if r3 == 1 { cbc_puts(" PASS KAT3 PKCS7 strip (valid->12, invalid->unchanged)\n" as *u8); pass = pass + 1 } else { cbc_puts(" FAIL KAT3 pkcs7\n" as *u8) }
93 tot = tot + 1
94
95 cbc_puts("---- nx_aes_cbc gate: passed " as *u8); cbc_putn(pass); cbc_puts(" / " as *u8); cbc_putn(tot)
96 if pass == tot { cbc_puts(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 }
97 cbc_puts(" verdict=RED\n" as *u8); sys_exit(1); return 1
98}