code wiki / _hdl_build / nx_tls12_record_gcm_gate.nx
nx_tls12_record_gcm_gate.nx source
↩ module page · 151 lines · 8172 B
1// nx_tls12_record_gcm_gate.nx -- SOVEREIGN gate for the TLS 1.2 AES-128-GCM
2// record layer (RFC 5246 §6.2.3.3 + RFC 5288), validating the shipped
3// nx_tls12_record.nx organ (tls12_record_seal / tls12_record_open) and the
4// AES-128-GCM AEAD it composes (nx_aes128_gcm.nx).
5//
6// NOTE on organ name: the task brief calls the organ nx_tls12_record_gcm.nx;
7// the equivalent organ already shipped as nx_tls12_record.nx (verify-don't-
8// rebuild, no duplicate crypto). This gate targets that existing organ.
9//
10// ---- PUBLISHED KAT: AES-128-GCM "Test Case 4" ----
11// Source: McGrew & Viega, "The Galois/Counter Mode of Operation (GCM)",
12// Appendix B, Test Case 4 -- the reference test set adopted by NIST
13// SP 800-38D. Reproduced byte-identically across openssl/ruby-openssl/etc.
14// K = feffe9928665731c6d6a8f9467308308 (16)
15// IV= cafebabefacedbaddecaf888 (12)
16// A = feedfacedeadbeeffeedfacedeadbeefabaddad2 (20)
17// P = d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318
18// a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39 (60)
19// C = 42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329ac
20// a12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091 (60)
21// T = 5bc94fbc3221a5db94fae95ae7121a47 (16)
22// This proves the AEAD primitive the record layer composes, byte-exact,
23// with a non-empty AAD and a non-block-aligned (60-byte) plaintext.
24//
25// ---- TLS 1.2 record framing (the organ's own logic) ----
26// CRITICAL: TLS 1.2's AAD DIFFERS from TLS 1.3's. Here
27// nonce(12) = salt(4, implicit from key_block) || explicit_nonce(8, on wire)
28// AAD(13) = seq_num(8) || type(1) || version(2=0x0303) || plaintext_len(2)
29// record = explicit_nonce(8) || ciphertext || tag(16)
30// Proven by: exact 13-byte AAD layout (RFC 5246 §6.2.3.3), exact nonce split,
31// composition vs the published AEAD, full seal->open round-trip, a tampered
32// tag (open MUST fail, constant-time compare in nx_aes128_gcm_tag_eq), and an
33// AAD-binding check (open under the wrong seq MUST fail).
34// expect_exit: 0
35// license_tier: ORIGINAL
36
37import "nx_syscalls.nx"
38import "nx_gate_emit_lib.nx"
39import "nx_aes128_gcm.nx"
40import "nx_tls12_record.nx"
41
42func ghx_nyb(c: i64) -> i64 {
43 if c >= 0x30 { if c <= 0x39 { return c - 0x30 } }
44 if c >= 0x61 { if c <= 0x66 { return c - 0x61 + 10 } }
45 if c >= 0x41 { if c <= 0x46 { return c - 0x41 + 10 } }
46 return 0
47}
48func ghx(hex: *u8, hexlen: i64, out: *u8) -> i64 {
49 var i: i64 = 0
50 while i < hexlen {
51 let hi: i64 = ghx_nyb(hex[i] as i64)
52 let lo: i64 = ghx_nyb(hex[i + 1] as i64)
53 out[i / 2] = ((hi << 4) | lo) as u8
54 i = i + 2
55 }
56 return hexlen / 2
57}
58func g_eqb(a: *u8, b: *u8, n: i64) -> i64 {
59 var i: i64 = 0
60 while i < n { if (a[i]&0xff)!=(b[i]&0xff) { return 0 } i = i + 1 }
61 return 1
62}
63
64func main() -> i64 {
65 g_puts("nx_tls12_record_gcm gate (GCM Test Case 4 KAT + TLS1.2 framing)\n" as *u8)
66 var pass: i64 = 0
67 var total: i64 = 0
68
69 // ---------- PUBLISHED AES-128-GCM Test Case 4 ----------
70 let k: *u8 = sys_mmap(16); ghx("feffe9928665731c6d6a8f9467308308" as *u8, 32, k)
71 let iv: *u8 = sys_mmap(12); ghx("cafebabefacedbaddecaf888" as *u8, 24, iv)
72 let a: *u8 = sys_mmap(20); ghx("feedfacedeadbeeffeedfacedeadbeefabaddad2" as *u8, 40, a)
73 let p: *u8 = sys_mmap(60); ghx("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39" as *u8, 120, p)
74 let expc: *u8 = sys_mmap(60); ghx("42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091" as *u8, 120, expc)
75 let expt: *u8 = sys_mmap(16); ghx("5bc94fbc3221a5db94fae95ae7121a47" as *u8, 32, expt)
76
77 let gct: *u8 = sys_mmap(60); let gtag: *u8 = sys_mmap(16)
78 nx_aes128_gcm_seal(k, iv, a, 20, p, 60, gct, gtag)
79 pass = pass + g_check("GCM TC4 seal: ciphertext == published C" as *u8, g_eqb(gct, expc, 60)); total = total + 1
80 pass = pass + g_check("GCM TC4 seal: tag == published T" as *u8, g_eqb(gtag, expt, 16)); total = total + 1
81
82 let gpt: *u8 = sys_mmap(60)
83 let ov: i64 = nx_aes128_gcm_open(k, iv, a, 20, expc, 60, expt, gpt)
84 var open_ok: i64 = 0
85 if ov == 0 { if g_eqb(gpt, p, 60) == 1 { open_ok = 1 } }
86 pass = pass + g_check("GCM TC4 open: verifies (rc 0) + recovers P" as *u8, open_ok); total = total + 1
87
88 let badt: *u8 = sys_mmap(16)
89 var bi: i64 = 0
90 while bi < 16 { badt[bi] = expt[bi]; bi = bi + 1 }
91 badt[0] = (badt[0] ^ 0x80) as u8
92 let nv: i64 = nx_aes128_gcm_open(k, iv, a, 20, expc, 60, badt, gpt)
93 pass = pass + g_check("GCM TC4 open: tampered tag -> auth FAIL (rc -1)" as *u8, (nv == (0 - 1)) as i64); total = total + 1
94
95 // ---------- TLS 1.2 record framing ----------
96 let rkey: *u8 = sys_mmap(16); ghx("404142434445464748494a4b4c4d4e4f" as *u8, 32, rkey)
97 let salt: *u8 = sys_mmap(4); ghx("deadbeef" as *u8, 8, salt)
98 let seq: *u8 = sys_mmap(8); ghx("0000000000000001" as *u8, 16, seq)
99 let rpt: *u8 = sys_mmap(20); ghx("6e69736869732d736f7665726569676e2d746c73" as *u8, 40, rpt) // "nishis-sovereign-tls" (20 bytes)
100 // content_type = 23 (application_data); plaintext_len = 20 (0x0014).
101
102 // 1) AAD layout == seq(8) || type(0x17) || version(0x0303) || len(0x0014)
103 let aad: *u8 = sys_mmap(13)
104 _r12_aad(seq, 23, 20, aad)
105 let expaad: *u8 = sys_mmap(13); ghx("00000000000000011703030014" as *u8, 26, expaad)
106 pass = pass + g_check("record AAD == seq||type||0x0303||len (RFC 5246 6.2.3.3)" as *u8,
107 g_eqb(aad, expaad, 13)); total = total + 1
108
109 // 2) nonce == salt(4) || explicit(8)
110 let non: *u8 = sys_mmap(12)
111 _r12_nonce(salt, seq, non)
112 let expnon: *u8 = sys_mmap(12); ghx("deadbeef0000000000000001" as *u8, 24, expnon)
113 pass = pass + g_check("record nonce == salt||explicit_nonce (RFC 5288)" as *u8,
114 g_eqb(non, expnon, 12)); total = total + 1
115
116 // 3) framing composition: record == explicit || AEAD(key,nonce,aad,pt)
117 let rout: *u8 = sys_mmap(64)
118 let rlen: i64 = tls12_record_seal(rkey, salt, seq, 23, rpt, 20, rout)
119 let refct: *u8 = sys_mmap(20); let reftag: *u8 = sys_mmap(16)
120 nx_aes128_gcm_seal(rkey, non, aad, 13, rpt, 20, refct, reftag)
121 var frame_ok: i64 = 1
122 if rlen != 8 + 20 + 16 { frame_ok = 0 }
123 if g_eqb(rout, seq, 8) != 1 { frame_ok = 0 } // explicit nonce echoed
124 let rout_ct: *u8 = (rout as i64 + 8) as *u8
125 let rout_tag: *u8 = (rout as i64 + 8 + 20) as *u8
126 if g_eqb(rout_ct, refct, 20) != 1 { frame_ok = 0 }
127 if g_eqb(rout_tag, reftag, 16) != 1 { frame_ok = 0 }
128 pass = pass + g_check("record seal == explicit||AEAD(key,nonce,aad,pt)" as *u8, frame_ok); total = total + 1
129
130 // 4) round-trip: open(seal) recovers plaintext, returns its length
131 let ropt: *u8 = sys_mmap(20)
132 let rv: i64 = tls12_record_open(rkey, salt, seq, 23, rout, rlen, ropt)
133 var rt_ok: i64 = 0
134 if rv == 20 { if g_eqb(ropt, rpt, 20) == 1 { rt_ok = 1 } }
135 pass = pass + g_check("record round-trip: open recovers plaintext (len 20)" as *u8, rt_ok); total = total + 1
136
137 // 5) tamper: flip a tag byte in the sealed record -> open MUST fail
138 rout_tag[3] = (rout_tag[3] ^ 0x01) as u8
139 let tv: i64 = tls12_record_open(rkey, salt, seq, 23, rout, rlen, ropt)
140 pass = pass + g_check("record tamper: flipped tag -> open FAILS (-1)" as *u8, (tv == (0 - 1)) as i64); total = total + 1
141 rout_tag[3] = (rout_tag[3] ^ 0x01) as u8 // restore
142
143 // 6) AAD binding: open under the WRONG sequence number -> MUST fail
144 let badseq: *u8 = sys_mmap(8); ghx("0000000000000002" as *u8, 16, badseq)
145 let av: i64 = tls12_record_open(rkey, salt, badseq, 23, rout, rlen, ropt)
146 pass = pass + g_check("record AAD binding: wrong seq -> open FAILS (-1)" as *u8, (av == (0 - 1)) as i64); total = total + 1
147
148 g_puts("gate: " as *u8); g_pn(pass); g_puts("/" as *u8); g_pn(total); g_puts("\n" as *u8)
149 if pass == total { return 0 }
150 return 1
151}