code wiki / _hdl_build / nx_tls12_prf_gate.nx
nx_tls12_prf_gate.nx source
↩ module page · 136 lines · 6558 B
1// nx_tls12_prf_gate.nx -- TLS 1.2 PRF (P_SHA256) KNOWN-ANSWER GATE, rung 1 of the TLS-1.2 client arc (debt seq861).
2// WHY THIS EXISTS even though nx_tls12_prf_test.nx already passed: that test is SILENT (prints nothing, exit 0)
3// and carries NO `verdict=` anchor, so (a) the ecosystem judge cannot read it, (b) an hourly beat cannot report
4// it, and (c) a silent pass is the weakest possible evidence -- an organ that returned 0 without testing looks
5// IDENTICAL. This gate emits the anchor AND carries its own non-vacuity control (T4), so it can prove that its
6// comparator is capable of reporting a mismatch at all.
7// VECTOR PROVENANCE (honest): secret/label/seed and the 100-byte expected output are the widely-circulated
8// IETF TLS-WG P_SHA256 test vector (secret 9bbe...db35, label "test label", seed a16c...f9da). NOTE the
9// older test file's header credits "RFC5246 section 5" -- RFC 5246 defines the PRF but publishes NO test
10// vectors, so that citation is wrong; the vector is the TLS-WG mailing-list one. Recorded rather than repeated.
11// D001: emits a `verdict=` anchor. license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
12import "nx_syscalls.nx"
13import "nx_tls12_prf.nx"
14import "nx_gate_lib.nx"
15
16const TPG_BUF: i64 = 4096
17const TPG_EXIT_RED: i64 = 1
18
19// CONVERTED 2026-07-25 (seq938): the private tpg_ helpers are gone -- this gate now composes the shared
20// nx_gate_lib.nx base. Thin one-line wrappers keep every call site below untouched, which is the
21// lowest-risk way to fold an organ onto a base (the same technique used to convert nx_flip_gate).
22func tpg_puts(s: *u8) -> i64 { return gl_puts(s) }
23func tpg_putn(v: i64) -> i64 { return gl_putn(v) }
24func tpg_hexv(c: i64) -> i64 {
25 if c >= 48 { if c <= 57 { return c - 48 } }
26 if c >= 97 { if c <= 102 { return c - 87 } }
27 if c >= 65 { if c <= 70 { return c - 55 } }
28 return 0 - 1
29}
30func tpg_unhex(s: *u8, out: *u8) -> i64 {
31 var i: i64 = 0
32 var n: i64 = 0
33 var bad: i64 = 0
34 while s[i] != (0 as u8) {
35 let hi: i64 = tpg_hexv(s[i] as i64)
36 let lo: i64 = tpg_hexv(s[i+1] as i64)
37 if hi < 0 { bad = 1 }
38 if lo < 0 { bad = 1 }
39 if bad == 0 { out[n] = ((hi << 4) | lo) as u8; n = n + 1 }
40 i = i + 2
41 }
42 if bad == 1 { return 0 - 1 }
43 return n
44}
45func tpg_eq(a: *u8, b: *u8, n: i64) -> i64 {
46 var i: i64 = 0
47 var ok: i64 = 1
48 while i < n { if (a[i] & 0xff) != (b[i] & 0xff) { ok = 0 } i = i + 1 }
49 return ok
50}
51func tpg_check(nm: *u8, ok: i64, counts: *i64) -> i64 { return gl_check(nm, ok, counts) }
52
53func main() -> i64 {
54 let counts: *i64 = sys_mmap(32) as *i64
55 counts[0] = 0
56 counts[1] = 0
57
58 let hsecret: *u8 = "9bbe436ba940f017b17652849a71db35" as *u8
59 let hlabel: *u8 = "74657374206c6162656c" as *u8
60 let hseed: *u8 = "a16c5b0cb94fc128b81f38e07b06f9da" as *u8
61 let hexp100: *u8 = "d68bf86ac1c1100c8e20444099c74d90b97d0057966cf2b315cb496ae5dfd923fa79cbadb6d91d8a478f1b4c18fc3e7498b77e30b97391bf6208902fe15aa6eb32c723bc30ff34e7e2bccf6f8369e5e20d92f56c64856dfd1344e6191f5f545bfb2317e3" as *u8
62
63 let secret: *u8 = sys_mmap(64)
64 let label: *u8 = sys_mmap(64)
65 let seed: *u8 = sys_mmap(64)
66 let exp: *u8 = sys_mmap(256)
67 let ns: i64 = tpg_unhex(hsecret, secret)
68 let nl: i64 = tpg_unhex(hlabel, label)
69 let nd: i64 = tpg_unhex(hseed, seed)
70 let ne: i64 = tpg_unhex(hexp100, exp)
71
72 // T0: vectors decode to the expected lengths (a malformed literal would fake every later case)
73 var ok0: i64 = 1
74 if ns != 16 { ok0 = 0 }
75 if nl != 10 { ok0 = 0 }
76 if nd != 16 { ok0 = 0 }
77 if ne < 100 { ok0 = 0 }
78 tpg_check("T0-vectors-decode" as *u8, ok0, counts)
79
80 // T1: 100-byte PRF output matches the published vector
81 let o100: *u8 = sys_mmap(256)
82 nx_tls12_prf_sha256(secret, 16, label, 10, seed, 16, o100, 100)
83 tpg_check("T1-prf-100B-matches-vector" as *u8, tpg_eq(o100, exp, 100), counts)
84
85 // T2: 48-byte request (the master-secret length TLS 1.2 actually uses) matches the same stream
86 let o48: *u8 = sys_mmap(128)
87 nx_tls12_prf_sha256(secret, 16, label, 10, seed, 16, o48, 48)
88 tpg_check("T2-prf-48B-matches-vector" as *u8, tpg_eq(o48, exp, 48), counts)
89
90 // T3: STRUCTURAL -- P_hash is a truncated stream, so the 48B output MUST be a prefix of the 100B one.
91 // Independent of the vector: catches a length-dependent bug the KAT alone could miss.
92 tpg_check("T3-48B-is-prefix-of-100B" as *u8, tpg_eq(o48, o100, 48), counts)
93
94 // T4: NON-VACUITY CONTROL -- corrupt one byte of the EXPECTED buffer and require the comparator to say
95 // MISMATCH. Without this, a comparator that always returned "equal" would make T1/T2/T3 pass vacuously.
96 let bad: *u8 = sys_mmap(256)
97 var ci: i64 = 0
98 while ci < 100 { bad[ci] = exp[ci]; ci = ci + 1 }
99 bad[50] = (bad[50] ^ (1 as u8)) as u8
100 var ok4: i64 = 0
101 if tpg_eq(o100, bad, 100) == 0 { ok4 = 1 }
102 tpg_check("T4-nonvacuity-comparator-can-fail" as *u8, ok4, counts)
103
104 // T5: NEG -- a flipped secret byte must change the output (the PRF actually consumes the secret)
105 secret[0] = (secret[0] ^ (1 as u8)) as u8
106 let ob: *u8 = sys_mmap(256)
107 nx_tls12_prf_sha256(secret, 16, label, 10, seed, 16, ob, 100)
108 var ok5: i64 = 0
109 if tpg_eq(ob, exp, 100) == 0 { ok5 = 1 }
110 tpg_check("T5-neg-flipped-secret-changes-output" as *u8, ok5, counts)
111
112 // T6: NEG -- a flipped seed byte must change the output (the PRF actually consumes the seed)
113 secret[0] = (secret[0] ^ (1 as u8)) as u8 // restore
114 seed[0] = (seed[0] ^ (1 as u8)) as u8
115 let oc: *u8 = sys_mmap(256)
116 nx_tls12_prf_sha256(secret, 16, label, 10, seed, 16, oc, 100)
117 var ok6: i64 = 0
118 if tpg_eq(oc, exp, 100) == 0 { ok6 = 1 }
119 tpg_check("T6-neg-flipped-seed-changes-output" as *u8, ok6, counts)
120
121 // DURABLE EVIDENCE (seq986): the clock dispatcher records only THAT a job ran, never its exit code,
122
123 // and discards stdout -- so without this line an hourly RED is invisible. Written for nx_gate_rollup.
124
125 gl_log("knowledge/status/tls12_prf_gate.log" as *u8, "NX-TLS12-PRF-GATE" as *u8, counts)
126
127 tpg_puts("NX-TLS12-PRF-GATE\n" as *u8)
128 tpg_puts("verdict=" as *u8)
129 if counts[1] == counts[0] { tpg_puts("GREEN" as *u8) } else { tpg_puts("RED" as *u8) }
130 tpg_puts(" pass=" as *u8); tpg_putn(counts[1])
131 tpg_puts("/" as *u8); tpg_putn(counts[0])
132 tpg_puts(" ref=TLS-WG-P_SHA256-testvector\n" as *u8)
133 if counts[1] == counts[0] { sys_exit(0); return 0 }
134 sys_exit(TPG_EXIT_RED)
135 return TPG_EXIT_RED
136}