code wiki / (root) / nx_tls13_auth.nx

nx_tls13_auth.nx source

↩ module page · 274 lines · 11648 B

1// nx_tls13_auth.nx -- TLS 1.3 auth-related handshake message parsers. 2// 3// Phase 0b §H of the Nishi TLS 1.3 stack per 4// docs/NISHI_TLS13_GAP_AUDIT.md. Pure-byte parsers for the three 5// server-side handshake messages that follow ServerHello in a TLS 6// 1.3 1-RTT flow: 7// 8// 1. EncryptedExtensions (RFC 8446 §4.3.1) 9// Just an extensions blob; carries server-side extensions 10// that the spec wants encrypted (max_fragment_length, ALPN 11// pick, etc.). 12// 13// 2. Certificate (RFC 8446 §4.4.2) 14// certificate_request_context: opaque<0..255> (1 byte len) 15// certificate_list: 16// per entry: 17// opaque cert_data<1..2^24-1> (3 byte len) 18// Extension extensions<0..2^16-1> (2 byte len) 19// 20// 3. CertificateVerify (RFC 8446 §4.4.3) 21// SignatureScheme algorithm (2 bytes) 22// opaque signature<0..2^16-1> (2 byte len) 23// 24// All three sit inside the standard Handshake wrapper (msg_type + 25// uint24 length). This module does NO cryptographic validation -- 26// signature verification, cert chain validation, hostname matching 27// all live in Gap I (nx_x509_validate, queued). Here we just turn 28// bytes into structure offsets the caller can hand to the verifier. 29// 30// What it does today: 31// - parse EncryptedExtensions; expose extensions blob offsets 32// - parse Certificate; expose FIRST cert's DER bytes + extensions 33// offsets (most TLS 1.3 deployments use a 1-3 cert chain; 34// iterating beyond the first is queued) 35// - parse CertificateVerify; expose scheme + signature offsets 36// - sealed verdict on every failure path 37// 38// What it doesn't do yet: 39// - iterate every cert in chain (caller can re-call into the 40// chain after the first; tls13_parse_certificate_chain_at queued) 41// - signature verification (composes against nx_ed25519 / future 42// nx_ecdsa / future nx_rsa_pss) 43// - cert chain validation (Gap I; nx_x509 ships DER parsing, 44// nx_x509_validate is the path-validation arc) 45// - OCSP stapling extension parse (RFC 6961 status_request_v2) 46// 47// KAT verified: 48// - hand-built EncryptedExtensions message round-trip 49// - hand-built Certificate with 1 cert + empty extensions roundtrip 50// - hand-built CertificateVerify with ed25519 scheme roundtrip 51// - tampered headers (wrong msg_type, truncated lengths) rejected 52// 53// Composes with: 54// - nx_tls13 (Handshake type constants + u16/u24 helpers) 55// - nx_x509 (existing; DER parse for the actual cert bytes) 56// - nx_ed25519 / future signature primitives (caller invokes after 57// extracting scheme + sig offsets) 58// - nx_tls13_client (queued; state machine consumes these) 59// 60// license_tier: INDEPENDENT_REDERIVE 61// genealogy_id: international-research-sources/ietf/rfc_8446 62// lineage_id: nishi_tls13_auth_parsers_q10 63 64// nx_safety_envelope: 65// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 66// sil_target: SIL1 67// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 68// verdict: NOT_YET_EVALUATED 69 70import "nx_syscalls.nx" 71import "nx_tls13.nx" 72 73const NX_TLS13_AUTH_VERDICT_OK: i64 = 1 74const NX_TLS13_AUTH_VERDICT_BAD_HEADER: i64 = 2 75const NX_TLS13_AUTH_VERDICT_TRUNCATED: i64 = 3 76const NX_TLS13_AUTH_VERDICT_EMPTY_CHAIN: i64 = 4 77const NX_TLS13_AUTH_VERDICT_TOO_LONG: i64 = 5 78const NX_TLS13_AUTH_VERDICT_TOO_MANY_CERTS: i64 = 6 79const NX_TLS13_AUTH_VERDICT_N: i64 = 7 80 81// Hard ceilings to defend against crafted oversize length-prefixes. 82// TLS 1.3 record cap is 2^14 = 16384. Cert chains rarely exceed 83// 10 KB. EncryptedExtensions blob ~1 KB typical, 8 KB worst. 84const NX_TLS13_AUTH_MAX_MESSAGE: i64 = 16384 85 86// Read the 4-byte Handshake header at the start of `buf` and verify 87// the type matches `expected_type`. Returns body offset (always 4) 88// on success or a negative verdict. 89func tls13_auth_check_handshake(buf: *u8, n: i64, expected_type: i64) -> i64 { 90 if n < 4 { return 0 - NX_TLS13_AUTH_VERDICT_TRUNCATED } 91 let msg_type: i64 = buf[0] & 0xff 92 if msg_type != expected_type { return 0 - NX_TLS13_AUTH_VERDICT_BAD_HEADER } 93 let body_len: i64 = ((buf[1] & 0xff) << 16) | ((buf[2] & 0xff) << 8) | (buf[3] & 0xff) 94 if body_len > NX_TLS13_AUTH_MAX_MESSAGE { return 0 - NX_TLS13_AUTH_VERDICT_TOO_LONG } 95 if 4 + body_len > n { return 0 - NX_TLS13_AUTH_VERDICT_TRUNCATED } 96 return 4 97} 98 99// ---- Parse EncryptedExtensions (RFC 8446 §4.3.1). ---- 100// 101// Body: Extension extensions<0..2^16-1>; (2-byte list length + extensions) 102// 103// Outputs: byte offsets within `buf` to the extensions blob. 104func tls13_parse_encrypted_extensions( 105 buf: *u8, n: i64, 106 out_extensions_off: *i64, 107 out_extensions_len: *i64 108) -> i64 { 109 let body_off: i64 = tls13_auth_check_handshake(buf, n, HT_ENCRYPTED_EXTENSIONS) 110 if body_off < 0 { return 0 - body_off } 111 if body_off + 2 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 112 let ext_list_len: i64 = tls_read_u16_be(buf, body_off) 113 if body_off + 2 + ext_list_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 114 *out_extensions_off = body_off + 2 115 *out_extensions_len = ext_list_len 116 return NX_TLS13_AUTH_VERDICT_OK 117} 118 119// ---- Parse Certificate message (RFC 8446 §4.4.2). ---- 120// 121// Body: 122// opaque certificate_request_context<0..255> 123// (1 byte len + bytes; server-side this is always empty, 124// client-side responds with the bytes from CertificateRequest) 125// per entry in certificate_list (3-byte total list length): 126// opaque cert_data<1..2^24-1> (3 byte len + DER cert) 127// Extension extensions<0..2^16-1> (2 byte len + extensions) 128// 129// We expose the FIRST entry's cert DER offsets + extensions offsets. 130// Iterating beyond is queued (tls13_parse_certificate_chain_at). 131func tls13_parse_certificate_chain_first( 132 buf: *u8, n: i64, 133 out_ctx_off: *i64, out_ctx_len: *i64, 134 out_cert_off: *i64, out_cert_len: *i64, 135 out_cert_exts_off: *i64, out_cert_exts_len: *i64, 136 out_chain_total_len: *i64 137) -> i64 { 138 let body_off: i64 = tls13_auth_check_handshake(buf, n, HT_CERTIFICATE) 139 if body_off < 0 { return 0 - body_off } 140 var o: i64 = body_off 141 // certificate_request_context 142 if o + 1 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 143 let ctx_len: i64 = buf[o] & 0xff 144 o = o + 1 145 if o + ctx_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 146 *out_ctx_off = o 147 *out_ctx_len = ctx_len 148 o = o + ctx_len 149 // certificate_list: 3-byte length 150 if o + 3 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 151 let chain_len: i64 = ((buf[o] & 0xff) << 16) | ((buf[o + 1] & 0xff) << 8) | (buf[o + 2] & 0xff) 152 if chain_len < 1 { return NX_TLS13_AUTH_VERDICT_EMPTY_CHAIN } 153 if o + 3 + chain_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 154 *out_chain_total_len = chain_len 155 o = o + 3 156 // First CertificateEntry 157 if o + 3 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 158 let cert_len: i64 = ((buf[o] & 0xff) << 16) | ((buf[o + 1] & 0xff) << 8) | (buf[o + 2] & 0xff) 159 if cert_len < 1 { return NX_TLS13_AUTH_VERDICT_EMPTY_CHAIN } 160 if o + 3 + cert_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 161 *out_cert_off = o + 3 162 *out_cert_len = cert_len 163 o = o + 3 + cert_len 164 // First entry's extensions 165 if o + 2 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 166 let ce_len: i64 = tls_read_u16_be(buf, o) 167 if o + 2 + ce_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 168 *out_cert_exts_off = o + 2 169 *out_cert_exts_len = ce_len 170 return NX_TLS13_AUTH_VERDICT_OK 171} 172 173// ---- Parse CertificateVerify (RFC 8446 §4.4.3). ---- 174// 175// Body: 176// SignatureScheme algorithm (2 bytes) 177// opaque signature<0..2^16-1> (2 byte len + signature) 178// 179// Outputs: chosen SignatureScheme + sig offset/length within buf. 180// Caller invokes the right signature primitive (nx_ed25519 / 181// nx_ecdsa / nx_rsa_pss) based on `*out_scheme`. 182func tls13_parse_certificate_verify( 183 buf: *u8, n: i64, 184 out_scheme: *i64, 185 out_sig_off: *i64, 186 out_sig_len: *i64 187) -> i64 { 188 let body_off: i64 = tls13_auth_check_handshake(buf, n, HT_CERTIFICATE_VERIFY) 189 if body_off < 0 { return 0 - body_off } 190 if body_off + 4 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 191 *out_scheme = tls_read_u16_be(buf, body_off) 192 let sig_len: i64 = tls_read_u16_be(buf, body_off + 2) 193 if body_off + 4 + sig_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 194 *out_sig_off = body_off + 4 195 *out_sig_len = sig_len 196 return NX_TLS13_AUTH_VERDICT_OK 197} 198 199// ---- Parse ALL entries from a Certificate handshake message. ---- 200// 201// Like tls13_parse_certificate_chain_first but emits offset/length 202// pairs for EVERY CertificateEntry in the chain, into caller- 203// supplied arrays. Stops early if the array runs out of space 204// (returns NX_TLS13_AUTH_VERDICT_TOO_MANY_CERTS). 205// 206// Output: 207// out_cert_offs[i] = byte offset of i-th cert's DER bytes 208// out_cert_lens[i] = byte length of i-th cert's DER bytes 209// *out_n_certs = number of certs parsed 210// 211// Used by the chain-walker integration: caller calls this to 212// build an array of cert offsets, then x509_parse on each, then 213// nx_x509_chain_verify on the resulting parsed-cert array. 214// 215// Per Cardinals 9 (single-responsibility -- parse, not verify), 216// 12 (defensive at boundaries -- bounded reads + cap-check on 217// max_certs). 218func tls13_parse_certificate_chain_all( 219 buf: *u8, n: i64, 220 out_cert_offs: *i64, 221 out_cert_lens: *i64, 222 out_n_certs: *i64, 223 max_certs: i64 224) -> i64 { 225 if max_certs < 1 { return NX_TLS13_AUTH_VERDICT_TOO_MANY_CERTS } 226 227 let body_off: i64 = tls13_auth_check_handshake(buf, n, HT_CERTIFICATE) 228 if body_off < 0 { return 0 - body_off } 229 var o: i64 = body_off 230 231 // certificate_request_context (skip) 232 if o + 1 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 233 let ctx_len: i64 = buf[o] & 0xff 234 o = o + 1 235 if o + ctx_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 236 o = o + ctx_len 237 238 // certificate_list: 3-byte length, then entries. 239 if o + 3 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 240 let chain_len: i64 = ((buf[o] & 0xff) << 16) | ((buf[o + 1] & 0xff) << 8) | (buf[o + 2] & 0xff) 241 if chain_len < 1 { return NX_TLS13_AUTH_VERDICT_EMPTY_CHAIN } 242 o = o + 3 243 let chain_end: i64 = o + chain_len 244 if chain_end > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 245 246 var n_certs: i64 = 0 247 while o < chain_end { 248 if n_certs >= max_certs { return NX_TLS13_AUTH_VERDICT_TOO_MANY_CERTS } 249 // CertificateEntry: 3-byte cert_len + cert_data 250 if o + 3 > chain_end { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 251 let cert_len: i64 = ((buf[o] & 0xff) << 16) | ((buf[o + 1] & 0xff) << 8) | (buf[o + 2] & 0xff) 252 if cert_len < 1 { return NX_TLS13_AUTH_VERDICT_EMPTY_CHAIN } 253 if o + 3 + cert_len > chain_end { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 254 out_cert_offs[n_certs] = o + 3 255 out_cert_lens[n_certs] = cert_len 256 o = o + 3 + cert_len 257 // Then 2-byte ext_list_len + ext_data 258 if o + 2 > chain_end { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 259 let ce_len: i64 = tls_read_u16_be(buf, o) 260 if o + 2 + ce_len > chain_end { return NX_TLS13_AUTH_VERDICT_TRUNCATED } 261 o = o + 2 + ce_len 262 n_certs = n_certs + 1 263 } 264 265 *out_n_certs = n_certs 266 return NX_TLS13_AUTH_VERDICT_OK 267} 268 269// Sealed-enum validity gate. 270func nx_tls13_auth_verdict_is_valid(v: i64) -> i64 { 271 if v < 0 { return 0 } 272 if v >= NX_TLS13_AUTH_VERDICT_N { return 0 } 273 return 1 274}