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}