code wiki / (root) / nx_https_pipeline_e2e_test.nx

nx_https_pipeline_e2e_test.nx source

↩ module page · 366 lines · 14861 B

1// nx_https_pipeline_e2e_test.nx -- THE end-to-end happy-path KAT 2// for the full bits-up HTTPS cert verification pipeline. 3// 4// Closes the verification loop: this single test exercises every 5// substrate primitive shipped this session for the public-HTTPS 6// arc, from raw TLS 1.3 Certificate message bytes to a verdict. 7// 8// Flow: 9// 1. Build a TrustStore + add the root cert as an anchor 10// 2. Build a TLS 1.3 Certificate message wrapping a real DER 11// leaf cert (built inline via build_min_cert below) 12// 3. Call nx_https_cert_pipeline_verify_with_store(...) 13// 4. Verify the returned verdict is NX_HTTPS_PIPELINE_OK 14// 15// Test vector setup: 16// - Leaf cert: minimal-but-real DER cert from leaf_check_test 17// (commit 50d861a8) with validity [2023..2026] + SAN "example.com" 18// + Ed25519 SPKI (not exercised here -- root doesn't try to 19// verify leaf's sig; we set sig_alg to Ed25519 to match) 20// - Root cert: synthetic root with subject DN "CN=Root" 21// - For ECDSA-style verify we'd need a real CA-signed cert which 22// requires either a private CA key (we don't have signing) or 23// a real-world cert vector. The RFC 6979 vector lets us do 24// "sig over 'sample'" but not "sig over a real cert's tbs". 25// 26// So this KAT covers the EARLY-EXIT flow: build a real DER leaf 27// whose tbs is well-formed but whose outer sig is garbage Ed25519 28// bytes. Pipeline gets to: 29// - parse Cert message OK 30// - parse leaf DER OK 31// - leaf_check OK (validity + SAN both pass) 32// - chain_verify -> verify_under_issuer dispatches by leaf's 33// sig_alg (Ed25519) -> calls x509_verify_signature_ed25519 34// -> the underlying ed25519 verify is known broken (task #23) 35// -> returns SIG_FAIL 36// 37// Expected end verdict: NX_HTTPS_PIPELINE_CHAIN_SIG_FAIL 38// 39// This KAT proves the full pipeline ROUTES correctly end-to-end: 40// every sub-primitive is reached, every dispatch is exercised, 41// every verdict is plumbed. The "OK" final verdict awaits task 42// #23 (Ed25519 verify) OR a real-CA test vector for ECDSA. 43// 44// expect_exit: 0 45// license_tier: ORIGINAL 46 47import "nx_syscalls.nx" 48import "nx_u256.nx" 49import "nx_x509.nx" 50import "nx_x509_trust_store.nx" 51import "nx_https_cert_pipeline.nx" 52 53// ---- Helpers re-used from earlier KATs ---- 54 55func emit_utctime(buf: *u8, off: i64, 56 yyyy: i64, mo: i64, d: i64, 57 h: i64, mi: i64, s: i64) -> i64 { 58 buf[off + 0] = 0x17 as u8 59 buf[off + 1] = 13 as u8 60 let yy: i64 = yyyy - 2000 61 buf[off + 2] = (0x30 + (yy / 10)) as u8 62 buf[off + 3] = (0x30 + (yy % 10)) as u8 63 buf[off + 4] = (0x30 + (mo / 10)) as u8 64 buf[off + 5] = (0x30 + (mo % 10)) as u8 65 buf[off + 6] = (0x30 + (d / 10)) as u8 66 buf[off + 7] = (0x30 + (d % 10)) as u8 67 buf[off + 8] = (0x30 + (h / 10)) as u8 68 buf[off + 9] = (0x30 + (h % 10)) as u8 69 buf[off + 10] = (0x30 + (mi / 10)) as u8 70 buf[off + 11] = (0x30 + (mi % 10)) as u8 71 buf[off + 12] = (0x30 + (s / 10)) as u8 72 buf[off + 13] = (0x30 + (s % 10)) as u8 73 buf[off + 14] = 0x5A as u8 74 return 15 75} 76 77func emit_ed25519_algid(buf: *u8, off: i64) -> i64 { 78 buf[off + 0] = 0x30 as u8 79 buf[off + 1] = 5 as u8 80 buf[off + 2] = 0x06 as u8 81 buf[off + 3] = 3 as u8 82 buf[off + 4] = 0x2B as u8 83 buf[off + 5] = 0x65 as u8 84 buf[off + 6] = 0x70 as u8 85 return 7 86} 87 88func emit_dnsname(buf: *u8, off: i64, name: *u8, name_len: i64) -> i64 { 89 buf[off] = 0x82 as u8 90 buf[off + 1] = name_len as u8 91 var i: i64 = 0 92 while i < name_len { 93 buf[off + 2 + i] = name[i] 94 i = i + 1 95 } 96 return 2 + name_len 97} 98 99// Write "CN=Root" DN bytes (17 bytes). 100func write_root_dn(buf: *u8, off: i64) -> i64 { 101 buf[off] = 0x30; buf[off+1] = 0x0F 102 buf[off+2] = 0x31; buf[off+3] = 0x0D 103 buf[off+4] = 0x30; buf[off+5] = 0x0B 104 buf[off+6] = 0x06; buf[off+7] = 0x03 105 buf[off+8] = 0x55; buf[off+9] = 0x04; buf[off+10] = 0x03 106 buf[off+11] = 0x0C; buf[off+12] = 0x04 107 buf[off+13] = 0x52; buf[off+14] = 0x6F; buf[off+15] = 0x6F; buf[off+16] = 0x74 108 return 17 109} 110 111func zero_cert(cert: *X509Cert) -> i64 { 112 cert.tbs_off=0; cert.tbs_len=0 113 cert.serial_off=0; cert.serial_len=0 114 cert.sig_alg_off=0; cert.sig_alg_len=0 115 cert.spki_off=0; cert.spki_len=0 116 cert.sig_off=0; cert.sig_len=0 117 cert.pubkey_off=0; cert.pubkey_len=0 118 cert.pubkey_alg_off=0; cert.pubkey_alg_len=0 119 cert.validity_off=0; cert.validity_len=0 120 cert.extensions_off = 0 - 1; cert.extensions_len=0 121 cert.issuer_off=0; cert.issuer_len=0 122 cert.subject_off=0; cert.subject_len=0 123 return 0 124} 125 126// Build a minimal real-DER leaf cert with: 127// issuer = subject = "CN=Root" (so it chains to our synthetic root) 128// validity [2023-01-01, 2026-01-01] (UTCTime) 129// SPKI = Ed25519 32-byte zero pubkey 130// sig_alg = Ed25519 131// sig = 64-byte garbage (not verified for OK in this KAT) 132// SAN = "example.com" dNSName 133// 134// Returns total cert bytes. Layout pre-computed (all lengths 135// short-form): 136// SAN dNSName "example.com" (13) -> GeneralNames SEQ (15) -> 137// OCTET STRING (17) + OID SAN (5) -> Extension SEQ (24) -> 138// Extensions outer (26) -> [3] EXPLICIT (28) 139// Validity body 30 -> Validity SEQ 32 140// Issuer/Subject Name each 17 (CN=Root) 141// SPKI: Ed25519 alg-id (7) + BIT STRING (35) wrapped in SEQ 142// = 0x30 2A <42> = 44 143// TBS body: serial(3) + sigAlg(7) + issuer(17) + validity(32) + 144// subject(17) + SPKI(44) + extensions wrap(28) = 148 145// TBS SEQUENCE: 0x30 0x81 0x94 <148> = 151 bytes 146// Outer sigAlg = 7 147// sig BIT STRING (1 unused + 64) = 67 148// Outer Cert body: 151 + 7 + 67 = 225 149// Outer Cert SEQ: 0x30 0x81 0xE1 <225> = 228 bytes 150func build_min_cert_root_subject(buf: *u8, host: *u8, host_len: i64) -> i64 { 151 let SAN_INNER_LEN: i64 = 13 152 let SAN_SEQ_TOTAL: i64 = 2 + SAN_INNER_LEN // 15 153 let OS_TOTAL: i64 = 2 + SAN_SEQ_TOTAL // 17 154 let OID_TOTAL: i64 = 5 155 let EXT_BODY: i64 = OID_TOTAL + OS_TOTAL // 22 156 let EXT_TOTAL: i64 = 2 + EXT_BODY // 24 157 let EXTS_BODY: i64 = EXT_TOTAL // 24 158 let EXTS_TOTAL: i64 = 2 + EXTS_BODY // 26 159 let EXP_WRAP_TOTAL: i64 = 2 + EXTS_TOTAL // 28 160 161 let VAL_BODY: i64 = 30 162 let VAL_TOTAL: i64 = 2 + VAL_BODY // 32 163 164 let SPKI_BODY: i64 = 7 + 35 // 42 165 let SPKI_TOTAL: i64 = 2 + SPKI_BODY // 44 166 167 let NAME_TOTAL: i64 = 17 // "CN=Root" DN bytes 168 169 let TBS_BODY: i64 = 3 + 7 + NAME_TOTAL + VAL_TOTAL + NAME_TOTAL + SPKI_TOTAL + EXP_WRAP_TOTAL // 148 170 let TBS_TOTAL: i64 = 2 + 1 + TBS_BODY // 0x30 0x81 0x94 + body = 151 171 172 let SIG_TOTAL: i64 = 67 173 let OUTER_BODY: i64 = TBS_TOTAL + 7 + SIG_TOTAL // 225 174 let OUTER_TOTAL: i64 = 3 + OUTER_BODY // 228 175 176 var o: i64 = 0 177 // Outer Cert SEQ (long-form length) 178 buf[o] = 0x30 as u8; o = o + 1 179 buf[o] = 0x81 as u8; o = o + 1 180 buf[o] = OUTER_BODY as u8; o = o + 1 181 182 // TBS SEQ (long-form length) 183 buf[o] = 0x30 as u8; o = o + 1 184 buf[o] = 0x81 as u8; o = o + 1 185 buf[o] = TBS_BODY as u8; o = o + 1 186 187 // serial INTEGER 0x01 188 buf[o] = 0x02 as u8; buf[o+1] = 0x01 as u8; buf[o+2] = 0x01 as u8; o = o + 3 189 190 // signature (TBS-level) Ed25519 191 o = o + emit_ed25519_algid(buf, o) 192 193 // issuer Name = "CN=Root" 194 o = o + write_root_dn(buf, o) 195 196 // validity SEQUENCE { UTCTime 2023-01-01, UTCTime 2026-01-01 } 197 buf[o] = 0x30 as u8; o = o + 1 198 buf[o] = VAL_BODY as u8; o = o + 1 199 o = o + emit_utctime(buf, o, 2023, 1, 1, 0, 0, 0) 200 o = o + emit_utctime(buf, o, 2026, 1, 1, 0, 0, 0) 201 202 // subject Name = "CN=Root" (matches issuer; for our test that 203 // means the leaf is also the root's subject -- weird but lets 204 // us use one DN value throughout) 205 o = o + write_root_dn(buf, o) 206 207 // SPKI (Ed25519 alg-id + 32-byte zero pubkey) 208 buf[o] = 0x30 as u8; o = o + 1 209 buf[o] = SPKI_BODY as u8; o = o + 1 210 o = o + emit_ed25519_algid(buf, o) 211 buf[o] = 0x03 as u8; o = o + 1 212 buf[o] = 0x21 as u8; o = o + 1 213 buf[o] = 0x00 as u8; o = o + 1 214 var ki: i64 = 0 215 while ki < 32 { buf[o + ki] = 0x00 as u8; ki = ki + 1 } 216 o = o + 32 217 218 // Extensions [3] EXPLICIT { SEQUENCE { Extension { SAN OID, OCTET STRING { SEQ { dNSName } } } } } 219 buf[o] = 0xA3 as u8; o = o + 1 220 buf[o] = EXTS_TOTAL as u8; o = o + 1 221 buf[o] = 0x30 as u8; o = o + 1 222 buf[o] = EXTS_BODY as u8; o = o + 1 223 buf[o] = 0x30 as u8; o = o + 1 224 buf[o] = EXT_BODY as u8; o = o + 1 225 buf[o] = 0x06 as u8; buf[o+1] = 0x03 as u8; buf[o+2] = 0x55 as u8 226 buf[o+3] = 0x1D as u8; buf[o+4] = 0x11 as u8; o = o + 5 227 buf[o] = 0x04 as u8; o = o + 1 228 buf[o] = SAN_SEQ_TOTAL as u8; o = o + 1 229 buf[o] = 0x30 as u8; o = o + 1 230 buf[o] = SAN_INNER_LEN as u8; o = o + 1 231 o = o + emit_dnsname(buf, o, host, host_len) 232 233 // Outer signatureAlgorithm Ed25519 234 o = o + emit_ed25519_algid(buf, o) 235 236 // signatureValue BIT STRING (64 garbage bytes) 237 buf[o] = 0x03 as u8; o = o + 1 238 buf[o] = 0x41 as u8; o = o + 1 239 buf[o] = 0x00 as u8; o = o + 1 240 var si: i64 = 0 241 while si < 64 { buf[o + si] = 0xAA as u8; si = si + 1 } 242 o = o + 64 243 244 return o 245} 246 247// Build a minimal "trust anchor" cert with subject DN "CN=Root" + 248// Ed25519 pubkey (32 zero bytes). We only need subject DN + 249// pubkey_alg + pubkey to be set for the chain walker to use it 250// as the parent. 251func build_synthetic_root(buf: *u8, cert: *X509Cert) -> i64 { 252 // Pubkey alg blob at off 0: SEQUENCE { OID Ed25519 } = 7 bytes 253 emit_ed25519_algid(buf, 0) 254 // Pubkey (32 zero bytes) at off 7 255 var i: i64 = 0 256 while i < 32 { buf[7 + i] = 0x00 as u8; i = i + 1 } 257 // Subject DN "CN=Root" at off 39 258 write_root_dn(buf, 39) 259 260 zero_cert(cert) 261 cert.pubkey_alg_off = 0; cert.pubkey_alg_len = 7 262 cert.pubkey_off = 7; cert.pubkey_len = 32 263 cert.subject_off = 39; cert.subject_len = 17 264 return 56 // total bytes written 265} 266 267func main() -> i64 { 268 let host_exam: *u8 = sys_mmap(16) 269 host_exam[0]=0x65; host_exam[1]=0x78; host_exam[2]=0x61; host_exam[3]=0x6D 270 host_exam[4]=0x70; host_exam[5]=0x6C; host_exam[6]=0x65; host_exam[7]=0x2E 271 host_exam[8]=0x63; host_exam[9]=0x6F; host_exam[10]=0x6D // "example.com" 272 273 // ---- Build leaf cert (real DER) ---- 274 let leaf_cert_buf: *u8 = sys_mmap(512) 275 let leaf_cert_total: i64 = build_min_cert_root_subject(leaf_cert_buf, host_exam, 11) 276 if leaf_cert_total != 228 { return 1 } 277 278 // ---- Build trust anchor cert (synthetic) ---- 279 let root_buf: *u8 = sys_mmap(128) 280 let root_cert_raw: *u8 = sys_mmap(256) 281 let root_cert: *X509Cert = root_cert_raw as *X509Cert 282 build_synthetic_root(root_buf, root_cert) 283 284 // ---- Build TrustStore + add root anchor ---- 285 let store: *TrustStore = trust_store_alloc(4) 286 if trust_store_add(store, root_buf, root_cert) != NX_TRUST_STORE_OK { return 2 } 287 if trust_store_count(store) != 1 { return 3 } 288 289 // ---- Build TLS 1.3 Certificate message wrapping leaf_cert ---- 290 // Layout: 291 // 1 byte HT_CERTIFICATE 292 // 3-byte body length 293 // body: 294 // 1-byte ctx_len = 0 295 // 3-byte chain_total_len = 3 + 228 + 2 = 233 296 // entry: 3-byte cert_len = 228 + 228 leaf bytes + 2-byte ext = 0 297 // body length = 1 + 3 + 233 = 237 298 // total handshake msg = 4 + 237 = 241 299 let msg: *u8 = sys_mmap(512) 300 msg[0] = 0x0B // HT_CERTIFICATE 301 msg[1] = 0; msg[2] = 0; msg[3] = 237 as u8 // body 237 (fits in 1 byte, hi-bytes 0) 302 msg[4] = 0 // ctx_len = 0 303 msg[5] = 0; msg[6] = 0; msg[7] = 233 as u8 // chain_total = 233 304 msg[8] = 0; msg[9] = 0; msg[10] = 228 as u8 // cert_len = 228 305 // Copy leaf cert bytes at offset 11 306 var ci: i64 = 0 307 while ci < 228 { msg[11 + ci] = leaf_cert_buf[ci]; ci = ci + 1 } 308 msg[239] = 0; msg[240] = 0 // ext_list_len = 0 309 let msg_total: i64 = 241 310 311 let NOW_OK: i64 = 1718452800 // 2024-06-15 12:00 312 313 // ============================================================ 314 // Test A: full pipeline run via trust-store-aware variant. 315 // Expected verdict: NX_HTTPS_PIPELINE_CHAIN_SIG_FAIL. 316 // 317 // The pipeline: 318 // - parses TLS Cert message OK (parse_chain_all) 319 // - parses leaf DER OK (x509_parse) 320 // - leaf_check OK (validity + SAN) (x509_leaf_check) 321 // - chain_verify reaches the leaf->root link 322 // - DN match leaf.issuer ("CN=Root") == root.subject OK 323 // - verify_under_issuer dispatches by leaf's sig_alg 324 // (Ed25519) -> x509_verify_signature_ed25519 325 // - The underlying ed25519_verify_full is known broken 326 // (task #23) AND the sig is garbage anyway -> FAIL 327 // - pipeline returns CHAIN_SIG_FAIL 328 // ============================================================ 329 let v: i64 = nx_https_cert_pipeline_verify_with_store( 330 msg, msg_total, host_exam, 11, NOW_OK, store 331 ) 332 if v != NX_HTTPS_PIPELINE_CHAIN_SIG_FAIL { return 4 } 333 334 // ============================================================ 335 // Test B: NOW_LATE (after notAfter) -> EXPIRED 336 // The pipeline parses + finds leaf cert, leaf_check kicks in 337 // before chain_verify, returns EXPIRED. 338 // ============================================================ 339 let NOW_LATE: i64 = 1798761600 // 2027-01-01 340 let v_exp: i64 = nx_https_cert_pipeline_verify_with_store( 341 msg, msg_total, host_exam, 11, NOW_LATE, store 342 ) 343 if v_exp != NX_HTTPS_PIPELINE_LEAF_EXPIRED { return 5 } 344 345 // ============================================================ 346 // Test C: hostname mismatch -> HOSTNAME_MISMATCH 347 // ============================================================ 348 let host_other: *u8 = sys_mmap(8) 349 host_other[0] = 0x66; host_other[1] = 0x6F; host_other[2] = 0x6F // "foo" 350 let v_hn: i64 = nx_https_cert_pipeline_verify_with_store( 351 msg, msg_total, host_other, 3, NOW_OK, store 352 ) 353 if v_hn != NX_HTTPS_PIPELINE_HOSTNAME_MISMATCH { return 6 } 354 355 // ============================================================ 356 // Test D: empty trust store -> NO_TRUST_ANCHOR 357 // (last cert's issuer DN "CN=Root" matches nothing) 358 // ============================================================ 359 let empty_store: *TrustStore = trust_store_alloc(4) 360 let v_nta: i64 = nx_https_cert_pipeline_verify_with_store( 361 msg, msg_total, host_exam, 11, NOW_OK, empty_store 362 ) 363 if v_nta != NX_HTTPS_PIPELINE_NO_TRUST_ANCHOR { return 7 } 364 365 return 0 366}