code wiki / (root) / nx_acme_directory_live_real_ca_test.nx

nx_acme_directory_live_real_ca_test.nx source

↩ module page · 244 lines · 9215 B

1// nx_acme_directory_live_real_ca_test.nx -- L11 step 2d. 2// 3// Live HTTPS GET against Let's Encrypt PRODUCTION /directory using the 4// already-shipped modular pipeline (url_for_fetch -> url_connect -> 5// tls13_client_session_run -> https_get_complete -> response_parse + 6// dechunk) and the real Mozilla trust store. 7// 8// Production rather than staging: LE STAGING certs chain to the 9// "(STAGING) Pretend Pear X1" root which is NOT in Mozilla's public 10// trust store -- by design. Hitting production /directory is safe 11// (GET, no order placed, no rate-limit consumption) AND validates the 12// real ISRG Root X1 chain that Mozilla anchors. 13// 14// What this proves: 15// - DNS resolves acme-v02.api.letsencrypt.org 16// - TCP connects to LE production 17// - TLS 1.3 handshake completes 18// - LE's cert chain validates against our shipped Mozilla store 19// (ISRG Root X1 is one of the 167 anchors loaded earlier) 20// - HTTP/1.1 GET succeeds, response parses, body dechunks 21// - JSON body contains the expected ACME endpoint keys 22// 23// What this does NOT prove: 24// - any POST against LE (that's step 2e -- needs JOSE-signed body 25// via nx_https_post_complete shipped in step 2c) 26// - staging cert chain support (separate task: add the staging 27// trust anchor or implement a staging trust mode) 28// 29// expect_exit: 0 30// license_tier: ORIGINAL 31 32import "nx_syscalls.nx" 33import "nx_x509_trust_store.nx" 34import "nx_trust_store_load_from_certdata.nx" 35import "nx_tls13_client_validate_certificate.nx" 36import "nx_tls13_client_session_run.nx" 37import "nx_https_url_for_fetch.nx" 38import "nx_https_url_connect.nx" 39import "nx_https_get.nx" 40import "nx_https_get_complete.nx" 41import "nx_http_response_parse.nx" 42 43func dump_dec(label0: i64, label1: i64, v: i64) -> i64 { 44 let lab: *u8 = sys_mmap(8) 45 lab[0] = label0 as u8; lab[1] = label1 as u8; lab[2] = 0x3D 46 sys_write(2, lab, 3) 47 var av: i64 = v 48 if av < 0 { 49 let neg: *u8 = sys_mmap(8); neg[0]=0x2D; sys_write(2, neg, 1) 50 av = 0 - av 51 } 52 if av == 0 { 53 let z: *u8 = sys_mmap(8); z[0]=0x30; sys_write(2, z, 1) 54 } else { 55 let buf: *u8 = sys_mmap(16) 56 var pos: i64 = 0 57 var x: i64 = av 58 while x > 0 { 59 buf[pos] = (0x30 + (x % 10)) as u8 60 x = x / 10 61 pos = pos + 1 62 } 63 let out: *u8 = sys_mmap(16) 64 var oi: i64 = 0 65 while oi < pos { 66 out[oi] = buf[pos - 1 - oi] 67 oi = oi + 1 68 } 69 sys_write(2, out, pos) 70 } 71 let nl: *u8 = sys_mmap(8); nl[0]=0x0A; sys_write(2, nl, 1) 72 return 0 73} 74 75// Substring search: returns 1 if needle (n bytes) is found inside 76// haystack (h bytes), 0 otherwise. Linear scan -- ACME directory 77// body is <2 KB so O(h*n) is fine. 78func _contains(haystack: *u8, h: i64, needle: *u8, n: i64) -> i64 { 79 if n <= 0 { return 1 } 80 if n > h { return 0 } 81 var i: i64 = 0 82 while i <= h - n { 83 var k: i64 = 0 84 var hit: i64 = 1 85 while k < n { 86 if haystack[i + k] != needle[k] { hit = 0; k = n } 87 else { k = k + 1 } 88 } 89 if hit == 1 { return 1 } 90 i = i + 1 91 } 92 return 0 93} 94 95func main() -> i64 { 96 // ---- Load Mozilla trust store ---- 97 let path: *u8 = sys_mmap(64) 98 path[0]=0x2F; path[1]=0x74; path[2]=0x6D; path[3]=0x70 // /tmp 99 path[4]=0x2F // / 100 path[5]=0x6D; path[6]=0x6F; path[7]=0x7A; path[8]=0x69 // mozi 101 path[9]=0x6C; path[10]=0x6C; path[11]=0x61 // lla 102 path[12]=0x5F // _ 103 path[13]=0x63; path[14]=0x65; path[15]=0x72; path[16]=0x74 104 path[17]=0x64; path[18]=0x61; path[19]=0x74; path[20]=0x61 // certdata 105 path[21]=0x2E; path[22]=0x74; path[23]=0x78; path[24]=0x74 // .txt 106 path[25]=0 107 108 let r: i64 = nx_trust_store_load_from_certdata(path, 300, 4194304) 109 dump_dec(0x4C, 0x4F, r) // "LO=" 110 if r <= 0 { return 1 } 111 let store: *TrustStore = r as *TrustStore 112 let n: i64 = trust_store_count(store) 113 dump_dec(0x43, 0x41, n) // "CA=" 114 if n < 100 { return 3 } 115 116 // ---- URL: https://acme-v02.api.letsencrypt.org/directory ---- 117 // 46 bytes total + NUL at offset 46. 118 let url: *u8 = sys_mmap(80) 119 // "https://" 8 120 url[0]=0x68; url[1]=0x74; url[2]=0x74; url[3]=0x70; url[4]=0x73 121 url[5]=0x3A; url[6]=0x2F; url[7]=0x2F 122 // "acme-v02.api.letsencrypt.org" 28 123 url[8]=0x61; url[9]=0x63; url[10]=0x6D; url[11]=0x65 // acme 124 url[12]=0x2D; url[13]=0x76; url[14]=0x30; url[15]=0x32 // -v02 125 url[16]=0x2E; url[17]=0x61; url[18]=0x70; url[19]=0x69 // .api 126 url[20]=0x2E // . 127 url[21]=0x6C; url[22]=0x65; url[23]=0x74; url[24]=0x73 // lets 128 url[25]=0x65; url[26]=0x6E; url[27]=0x63; url[28]=0x72 // encr 129 url[29]=0x79; url[30]=0x70; url[31]=0x74 // ypt 130 url[32]=0x2E; url[33]=0x6F; url[34]=0x72; url[35]=0x67 // .org 131 // "/directory" 10 132 url[36]=0x2F; url[37]=0x64; url[38]=0x69; url[39]=0x72 // /dir 133 url[40]=0x65; url[41]=0x63; url[42]=0x74; url[43]=0x6F // ecto 134 url[44]=0x72; url[45]=0x79 // ry 135 url[46]=0 136 137 // ---- Client random + private (caller-supplied; pseudo) ---- 138 let cr: *u8 = sys_mmap(32) 139 var i: i64 = 0 140 while i < 32 { cr[i] = (0xC0 + i) as u8; i = i + 1 } 141 let priv: *u8 = sys_mmap(32) 142 i = 0 143 while i < 32 { priv[i] = (0xA0 + i) as u8; i = i + 1 } 144 145 // ---- URL parse + connect ---- 146 let url_p: *NxUrl = nx_url_new() 147 let target_raw: *u8 = sys_mmap(32) 148 let target: *NxHttpsTarget = target_raw as *NxHttpsTarget 149 target.url = url_p 150 target.port = 0 151 if nx_https_url_for_fetch(url, target) != NX_HTTPS_URL_OK { return 41 } 152 153 let fd_p: *i64 = sys_mmap(16) as *i64 154 if nx_https_url_connect(target, url, 1779284141, fd_p) != NX_HTTPS_CONNECT_OK { return 42 } 155 let fd: i64 = *fd_p 156 157 // ---- TLS validation ctx ---- 158 let val_ctx_raw: *u8 = sys_mmap(64) 159 let val_ctx: *TlsValidationContext = val_ctx_raw as *TlsValidationContext 160 val_ctx.store = store 161 val_ctx.sni_host = url + target.url.host_off 162 val_ctx.sni_host_len = target.url.host_len 163 val_ctx.now_epoch = sys_now_realtime_sec() // REAL clock: hardcoded epochs rot when live sites renew certs (B1 root cause) 164 165 let sr: i64 = nx_tls13_client_session_run( 166 fd, url + target.url.host_off, target.url.host_len, 167 cr, priv, val_ctx 168 ) 169 dump_dec(0x53, 0x52, sr) 170 if sr <= 0 { 171 sys_close(fd) 172 return 200 + (0 - sr) 173 } 174 175 let session: *Tls13ClientSession = sr as *Tls13ClientSession 176 177 // ---- GET /directory ---- 178 let buf: *u8 = sys_mmap(65536) 179 let req_path: *u8 = sys_mmap(16) 180 req_path[0]=0x2F; req_path[1]=0x64; req_path[2]=0x69; req_path[3]=0x72 // /dir 181 req_path[4]=0x65; req_path[5]=0x63; req_path[6]=0x74; req_path[7]=0x6F // ecto 182 req_path[8]=0x72; req_path[9]=0x79 // ry 183 184 let gc: i64 = nx_https_get_complete( 185 session, fd, 186 req_path, 10, 187 url + target.url.host_off, target.url.host_len, 188 buf, 65536 189 ) 190 sys_close(fd) 191 dump_dec(0x47, 0x43, gc) 192 if gc < 0 { return 100 + (0 - gc) } 193 194 // ---- Parse response ---- 195 let rr: *i64 = sys_mmap(128) as *i64 196 let pv: i64 = nx_http_response_parse(buf, gc, rr) 197 dump_dec(0x50, 0x56, pv) 198 if pv != 0 { return 50 } 199 let status: i64 = rr[1] 200 dump_dec(0x53, 0x54, status) 201 if status != 200 { return 51 } 202 let body_off: i64 = rr[6] 203 let body_kind: i64 = rr[8] 204 dump_dec(0x42, 0x4F, body_off) 205 dump_dec(0x42, 0x4B, body_kind) 206 207 // ---- Dechunk if necessary ---- 208 let body: *u8 = sys_mmap(16384) 209 var body_len: i64 = 0 210 if body_kind == 2 { 211 body_len = nx_http_dechunk(buf + body_off, gc - body_off, body, 16384) 212 if body_len < 0 { return 60 } 213 } else { 214 body_len = gc - body_off 215 var k: i64 = 0 216 while k < body_len { body[k] = buf[body_off + k]; k = k + 1 } 217 } 218 dump_dec(0x42, 0x4C, body_len) // "BL=" body len 219 220 // ---- Assert the JSON contains expected ACME directory keys ---- 221 let key_na: *u8 = "newAccount" as *u8 222 let key_no: *u8 = "newOrder" as *u8 223 let key_nn: *u8 = "newNonce" as *u8 224 let key_rv: *u8 = "revokeCert" as *u8 225 226 if _contains(body, body_len, key_na, 10) != 1 { return 70 } 227 if _contains(body, body_len, key_no, 8) != 1 { return 71 } 228 if _contains(body, body_len, key_nn, 8) != 1 { return 72 } 229 if _contains(body, body_len, key_rv, 10) != 1 { return 73 } 230 231 // ---- Save body for archaeology ---- 232 let outpath: *u8 = "/tmp/le_directory.json\x00" 233 let ofd: i64 = sys_openat_wr(outpath, 0x1A4) 234 if ofd <= 0 { return 80 } 235 sys_write(ofd, body, body_len) 236 sys_close(ofd) 237 238 let banner: *u8 = sys_mmap(64) 239 banner[0]=0x4C; banner[1]=0x45 // "LE" 240 banner[2]=0x5F; banner[3]=0x4F; banner[4]=0x4B // _OK 241 banner[5]=0x0A 242 sys_write(1, banner, 6) 243 return 0 244}