code wiki / (root) / nx_x509_san_test.nx

nx_x509_san_test.nx source

↩ module page · 259 lines · 12049 B

1// nx_x509_san_test.nx -- KAT for the SAN extension walk + hostname 2// match primitive. Exercises every verdict path: 3// - exact dNSName match 4// - leftmost-label wildcard match 5// - wildcard rejection for non-leftmost labels 6// - bad pattern (non-leftmost or mid-label '*') 7// - cert without v3 extensions -> NO_EXTENSIONS 8// - cert with extensions but no SAN OID -> NO_SAN 9// - malformed extension SEQUENCE -> BAD_FORMAT 10// - sealed verdict gate 11// 12// expect_exit: 0 13// license_tier: ORIGINAL 14 15import "nx_syscalls.nx" 16import "nx_x509.nx" 17import "nx_x509_san.nx" 18 19// ---- Builders ---------------------------------------------------- 20 21// Write `n` bytes from src to buf[off..off+n]. Returns n. 22func write_bytes(buf: *u8, off: i64, src: *u8, n: i64) -> i64 { 23 var i: i64 = 0 24 while i < n { 25 buf[off + i] = src[i] 26 i = i + 1 27 } 28 return n 29} 30 31// Write a single dNSName entry: 0x82 <len> <bytes>. Returns bytes 32// written = 2 + len. 33func emit_dnsname(buf: *u8, off: i64, name: *u8, name_len: i64) -> i64 { 34 buf[off] = 0x82 as u8 35 buf[off + 1] = name_len as u8 36 var i: i64 = 0 37 while i < name_len { 38 buf[off + 2 + i] = name[i] 39 i = i + 1 40 } 41 return 2 + name_len 42} 43 44func main() -> i64 { 45 let buf: *u8 = sys_mmap(1024) 46 let cert_raw: *u8 = sys_mmap(256) 47 let cert: *X509Cert = cert_raw as *X509Cert 48 cert.tbs_off = 0; cert.tbs_len = 0 49 cert.serial_off = 0; cert.serial_len = 0 50 cert.sig_alg_off = 0; cert.sig_alg_len = 0 51 cert.spki_off = 0; cert.spki_len = 0 52 cert.sig_off = 0; cert.sig_len = 0 53 cert.pubkey_off = 0; cert.pubkey_len = 0 54 cert.pubkey_alg_off = 0; cert.pubkey_alg_len = 0 55 cert.validity_off = 0; cert.validity_len = 0 56 cert.extensions_off = 0 - 1 57 cert.extensions_len = 0 58 59 // ASCII hostname pools (kept as small inline arrays). 60 let host_exam: *u8 = sys_mmap(32) 61 host_exam[0]=0x65; host_exam[1]=0x78; host_exam[2]=0x61; host_exam[3]=0x6D 62 host_exam[4]=0x70; host_exam[5]=0x6C; host_exam[6]=0x65; host_exam[7]=0x2E 63 host_exam[8]=0x63; host_exam[9]=0x6F; host_exam[10]=0x6D // "example.com" (11) 64 65 let host_foo_test: *u8 = sys_mmap(32) 66 host_foo_test[0]=0x66; host_foo_test[1]=0x6F; host_foo_test[2]=0x6F 67 host_foo_test[3]=0x2E; host_foo_test[4]=0x74; host_foo_test[5]=0x65 68 host_foo_test[6]=0x73; host_foo_test[7]=0x74; host_foo_test[8]=0x2E 69 host_foo_test[9]=0x6F; host_foo_test[10]=0x72; host_foo_test[11]=0x67 // "foo.test.org" (12) 70 71 let host_bad: *u8 = sys_mmap(32) 72 host_bad[0]=0x68; host_bad[1]=0x6F; host_bad[2]=0x73; host_bad[3]=0x74 73 host_bad[4]=0x2E; host_bad[5]=0x62; host_bad[6]=0x61; host_bad[7]=0x64 74 host_bad[8]=0x2E; host_bad[9]=0x63; host_bad[10]=0x6F; host_bad[11]=0x6D // "host.bad.com" (12) 75 76 let host_evil: *u8 = sys_mmap(32) 77 host_evil[0]=0x65; host_evil[1]=0x76; host_evil[2]=0x69; host_evil[3]=0x6C 78 host_evil[4]=0x2E; host_evil[5]=0x66; host_evil[6]=0x6F; host_evil[7]=0x6F 79 host_evil[8]=0x2E; host_evil[9]=0x74; host_evil[10]=0x65; host_evil[11]=0x73 80 host_evil[12]=0x74; host_evil[13]=0x2E; host_evil[14]=0x6F; host_evil[15]=0x72 81 host_evil[16]=0x67 // "evil.foo.test.org" (17) 82 83 // SAN dNSName patterns. 84 let pat_exam: *u8 = sys_mmap(32) 85 pat_exam[0]=0x65; pat_exam[1]=0x78; pat_exam[2]=0x61; pat_exam[3]=0x6D 86 pat_exam[4]=0x70; pat_exam[5]=0x6C; pat_exam[6]=0x65; pat_exam[7]=0x2E 87 pat_exam[8]=0x63; pat_exam[9]=0x6F; pat_exam[10]=0x6D // "example.com" (11) 88 89 let pat_wild_test: *u8 = sys_mmap(32) 90 pat_wild_test[0]=0x2A; pat_wild_test[1]=0x2E; pat_wild_test[2]=0x74 91 pat_wild_test[3]=0x65; pat_wild_test[4]=0x73; pat_wild_test[5]=0x74 92 pat_wild_test[6]=0x2E; pat_wild_test[7]=0x6F; pat_wild_test[8]=0x72 93 pat_wild_test[9]=0x67 // "*.test.org" (10) 94 95 // ---- Phase A: standalone wildcard_match unit tests ---- 96 // (No cert involved; pure function-under-test.) 97 if hostname_wildcard_match(pat_exam, 11, host_exam, 11) != NX_X509_SAN_OK { return 1 } 98 if hostname_wildcard_match(pat_exam, 11, host_foo_test, 12) != NX_X509_SAN_NO_MATCH { return 2 } 99 if hostname_wildcard_match(pat_wild_test, 10, host_foo_test, 12) != NX_X509_SAN_OK { return 3 } 100 if hostname_wildcard_match(pat_wild_test, 10, host_evil, 17) != NX_X509_SAN_NO_MATCH { return 4 } 101 if hostname_wildcard_match(pat_wild_test, 10, host_exam, 11) != NX_X509_SAN_NO_MATCH { return 5 } 102 103 // Reject mid-label wildcard "foo.*.com" 104 let pat_mid: *u8 = sys_mmap(32) 105 pat_mid[0]=0x66; pat_mid[1]=0x6F; pat_mid[2]=0x6F; pat_mid[3]=0x2E 106 pat_mid[4]=0x2A; pat_mid[5]=0x2E; pat_mid[6]=0x63; pat_mid[7]=0x6F 107 pat_mid[8]=0x6D // "foo.*.com" (9) 108 if hostname_wildcard_match(pat_mid, 9, host_foo_test, 12) != NX_X509_SAN_BAD_PATTERN { return 6 } 109 110 // Reject "*foo.com" (partial leftmost label) -- '*' must be 111 // followed by '.' to claim the whole leftmost label. 112 let pat_partial: *u8 = sys_mmap(32) 113 pat_partial[0]=0x2A; pat_partial[1]=0x66; pat_partial[2]=0x6F 114 pat_partial[3]=0x6F; pat_partial[4]=0x2E; pat_partial[5]=0x63 115 pat_partial[6]=0x6F; pat_partial[7]=0x6D // "*foo.com" (8) 116 if hostname_wildcard_match(pat_partial, 8, host_exam, 11) != NX_X509_SAN_BAD_PATTERN { return 7 } 117 118 // Reject two wildcards "*.*.com" 119 let pat_two: *u8 = sys_mmap(32) 120 pat_two[0]=0x2A; pat_two[1]=0x2E; pat_two[2]=0x2A; pat_two[3]=0x2E 121 pat_two[4]=0x63; pat_two[5]=0x6F; pat_two[6]=0x6D // "*.*.com" (7) 122 if hostname_wildcard_match(pat_two, 7, host_foo_test, 12) != NX_X509_SAN_BAD_PATTERN { return 8 } 123 124 // Case-insensitive exact match. 125 let host_upper: *u8 = sys_mmap(32) 126 host_upper[0]=0x45; host_upper[1]=0x58; host_upper[2]=0x41; host_upper[3]=0x4D 127 host_upper[4]=0x50; host_upper[5]=0x4C; host_upper[6]=0x45; host_upper[7]=0x2E 128 host_upper[8]=0x43; host_upper[9]=0x4F; host_upper[10]=0x4D // "EXAMPLE.COM" 129 if hostname_wildcard_match(pat_exam, 11, host_upper, 11) != NX_X509_SAN_OK { return 9 } 130 131 // ---- Phase B: build extensions block with SAN ---- 132 // 133 // Layout (assembled bottom-up so length fields are known): 134 // 135 // SAN GeneralNames body: 136 // dNSName "example.com" = 0x82 11 <bytes> (13) 137 // dNSName "*.test.org" = 0x82 10 <bytes> (12) 138 // SAN GeneralNames SEQUENCE wrapper: 0x30 <len=25> (27) 139 // extnValue OCTET STRING wrapper: 0x04 <len=27> (29) 140 // extnID OID 2.5.29.17: 0x06 03 55 1D 11 (5) 141 // Extension SEQUENCE wrapper: 0x30 <len=34> (36) 142 // Outer Extensions SEQUENCE wrapper: 0x30 <len=36> (38) 143 144 var o: i64 = 0 145 // Outer SEQUENCE header (Extensions list) 146 buf[o]=0x30; buf[o+1]=36; o = o + 2 147 // Extension SEQUENCE header 148 buf[o]=0x30; buf[o+1]=34; o = o + 2 149 // extnID OID (SAN) 150 buf[o]=0x06; buf[o+1]=0x03; buf[o+2]=0x55; buf[o+3]=0x1D; buf[o+4]=0x11 151 o = o + 5 152 // extnValue OCTET STRING header 153 buf[o]=0x04; buf[o+1]=27; o = o + 2 154 // GeneralNames SEQUENCE header 155 buf[o]=0x30; buf[o+1]=25; o = o + 2 156 // dNSName #1 "example.com" 157 o = o + emit_dnsname(buf, o, pat_exam, 11) 158 // dNSName #2 "*.test.org" 159 o = o + emit_dnsname(buf, o, pat_wild_test, 10) 160 let total: i64 = o // should equal 38 161 162 if total != 38 { return 20 } 163 164 cert.extensions_off = 0 165 cert.extensions_len = total 166 167 // ---- Phase B-1: locate SAN ---- 168 let san_off_p: *i64 = sys_mmap(16) as *i64 169 let san_len_p: *i64 = sys_mmap(16) as *i64 170 let loc: i64 = x509_san_locate(buf, cert, san_off_p, san_len_p) 171 if loc != NX_X509_SAN_OK { return 21 } 172 if *san_len_p != 25 { return 22 } 173 174 // ---- Phase B-2: exact match wins ---- 175 if x509_san_match_hostname(buf, cert, host_exam, 11) != NX_X509_SAN_OK { return 23 } 176 177 // ---- Phase B-3: wildcard match wins ---- 178 if x509_san_match_hostname(buf, cert, host_foo_test, 12) != NX_X509_SAN_OK { return 24 } 179 180 // ---- Phase B-4: no match returns NO_MATCH ---- 181 if x509_san_match_hostname(buf, cert, host_bad, 12) != NX_X509_SAN_NO_MATCH { return 25 } 182 183 // ---- Phase B-5: wildcard does NOT match multi-label leftmost ---- 184 if x509_san_match_hostname(buf, cert, host_evil, 17) != NX_X509_SAN_NO_MATCH { return 26 } 185 186 // ---- Phase B-6: case-insensitive match ---- 187 if x509_san_match_hostname(buf, cert, host_upper, 11) != NX_X509_SAN_OK { return 27 } 188 189 // ---- Phase C: cert with no extensions ---- 190 cert.extensions_off = 0 - 1 191 cert.extensions_len = 0 192 if x509_san_match_hostname(buf, cert, host_exam, 11) != NX_X509_SAN_NO_EXTENSIONS { return 30 } 193 194 // ---- Phase D: cert with extensions but no SAN OID ---- 195 // Build a single Extension with a DIFFERENT OID (subjectKeyIdentifier 196 // 2.5.29.14 = 55 1D 0E) and an OCTET STRING extnValue. 197 let buf2: *u8 = sys_mmap(256) 198 var p: i64 = 0 199 // Outer SEQUENCE 200 buf2[p]=0x30; buf2[p+1]=15; p = p + 2 201 // Extension SEQUENCE 202 buf2[p]=0x30; buf2[p+1]=13; p = p + 2 203 // OID 2.5.29.14 204 buf2[p]=0x06; buf2[p+1]=0x03; buf2[p+2]=0x55; buf2[p+3]=0x1D; buf2[p+4]=0x0E 205 p = p + 5 206 // extnValue OCTET STRING wrapping 6 arbitrary bytes 207 buf2[p]=0x04; buf2[p+1]=6; p = p + 2 208 buf2[p]=0xAA; buf2[p+1]=0xBB; buf2[p+2]=0xCC; buf2[p+3]=0xDD 209 buf2[p+4]=0xEE; buf2[p+5]=0xFF; p = p + 6 210 cert.extensions_off = 0 211 cert.extensions_len = p 212 if x509_san_match_hostname(buf2, cert, host_exam, 11) != NX_X509_SAN_NO_SAN { return 35 } 213 214 // ---- Phase E: bad-format extensions (truncated outer SEQUENCE) ---- 215 let buf3: *u8 = sys_mmap(64) 216 buf3[0]=0x30; buf3[1]=0xFF // claim 255-byte outer, only have 2 217 cert.extensions_off = 0 218 cert.extensions_len = 2 219 let bad: i64 = x509_san_match_hostname(buf3, cert, host_exam, 11) 220 if bad != NX_X509_SAN_BAD_FORMAT { return 40 } 221 222 // ---- Phase F: SAN entry with bad pattern (no other entry matches) ---- 223 // Build SAN with single dNSName "*foo" (illegal partial wildcard). 224 // Bottom-up sizes: 225 // dNSName "*foo" = 0x82 04 <4 bytes> (6) 226 // GeneralNames SEQUENCE = 0x30 06 <6 bytes> (8) 227 // OCTET STRING wrap = 0x04 08 <8 bytes> (10) 228 // OID 2.5.29.17 = 0x06 03 55 1D 11 (5) 229 // Extension SEQUENCE = 0x30 0F <15 bytes> (17) 230 // Outer Extensions SEQ = 0x30 11 <17 bytes> (19) 231 let buf4: *u8 = sys_mmap(128) 232 var q: i64 = 0 233 buf4[q]=0x30; buf4[q+1]=17; q = q + 2 // outer SEQUENCE 234 buf4[q]=0x30; buf4[q+1]=15; q = q + 2 // Extension SEQUENCE 235 buf4[q]=0x06; buf4[q+1]=0x03; buf4[q+2]=0x55 // SAN OID 236 buf4[q+3]=0x1D; buf4[q+4]=0x11; q = q + 5 237 buf4[q]=0x04; buf4[q+1]=8; q = q + 2 // OCTET STRING (8 bytes follow) 238 buf4[q]=0x30; buf4[q+1]=6; q = q + 2 // GeneralNames SEQUENCE 239 q = q + emit_dnsname(buf4, q, pat_partial, 4) // dNSName "*foo" (4 chars, illegal) 240 if q != 19 { return 50 } 241 cert.extensions_off = 0 242 cert.extensions_len = q 243 let badpat: i64 = x509_san_match_hostname(buf4, cert, host_exam, 11) 244 if badpat != NX_X509_SAN_BAD_PATTERN { return 51 } 245 246 // ---- Phase G: sealed verdict gate ---- 247 if nx_x509_san_verdict_is_valid(NX_X509_SAN_OK) != 1 { return 60 } 248 if nx_x509_san_verdict_is_valid(NX_X509_SAN_NO_MATCH) != 1 { return 61 } 249 if nx_x509_san_verdict_is_valid(NX_X509_SAN_NO_EXTENSIONS) != 1 { return 62 } 250 if nx_x509_san_verdict_is_valid(NX_X509_SAN_NO_SAN) != 1 { return 63 } 251 if nx_x509_san_verdict_is_valid(NX_X509_SAN_BAD_FORMAT) != 1 { return 64 } 252 if nx_x509_san_verdict_is_valid(NX_X509_SAN_BAD_PATTERN) != 1 { return 65 } 253 if nx_x509_san_verdict_is_valid(NX_X509_SAN_VERDICT_N) != 0 { return 66 } 254 if nx_x509_san_verdict_is_valid(0) != 0 { return 67 } 255 if nx_x509_san_verdict_is_valid(0 - 1) != 0 { return 68 } 256 if nx_x509_san_verdict_is_valid(999) != 0 { return 69 } 257 258 return 0 259}