nx_acme_issue.nx source
↩ module page · 511 lines · 24154 B
1// nx_acme_issue.nx -- live ACME issuance drive loop (ES256, DNS-01).
2//
3// Composes the two ACME layers (both nx_syscalls graph):
4// nx_acme_es256.nx -- ES256 JWS builders + DNS-01 + pubkey_from_priv
5// nx_acme_http.nx -- TLS transport + JSON reader + header reader
6//
7// Built + tested INCREMENTALLY against Let's Encrypt PRODUCTION. The
8// pre-challenge steps (directory / nonce / newAccount / newOrder / authz
9// fetch) are NOT failed-validation-rate-limited, so they are safe to
10// exercise repeatedly; only triggering a challenge that then fails counts
11// against the 5/hour/hostname budget, so that step is gated separately.
12//
13// Stage selector: argv-free; this build does directory -> nonce ->
14// newAccount -> newOrder -> authz fetch, printing each result. The
15// account key is persisted (reused across renewals).
16//
17// license_tier: ORIGINAL
18
19import "nx_acme_es256.nx"
20import "nx_acme_http.nx"
21const ACME_MAGIC_2048: i64 = 2048
22const ACME_MAGIC_8192: i64 = 8192
23const ACME_MAGIC_16384: i64 = 16384
24const ACME_MAGIC_3000: i64 = 3000
25const ACME_MAGIC_4194304: i64 = 4194304
26const ACME_MAGIC_1779284141: i64 = 1779284141
27const ACME_MAGIC_65536: i64 = 65536
28const ACME_MAGIC_4096: i64 = 4096
29
30const ACME_ACCT_KEY_PATH: *u8 = "/tmp/acme_acct_key.bin\x00"
31
32// Copy src[off..off+len] into a fresh NUL-terminated buffer; return it.
33func acme_dup_cstr(src: *u8, off: i64, len: i64) -> *u8 {
34 let out: *u8 = sys_mmap(len + 1)
35 var i: i64 = 0
36 while i < len { out[i] = src[off + i]; i = i + 1 }
37 out[len] = 0 as u8
38 return out
39}
40
41// Load the persistent ECDSA P-256 account key (32-byte big-endian scalar),
42// generating + saving it on first use. Fills priv_limbs.
43func acme_account_key(priv_limbs: *i64) -> i64 {
44 let priv_be: *u8 = sys_mmap(32)
45 let fd: i64 = sys_openat_rd(ACME_ACCT_KEY_PATH)
46 if fd > 0 {
47 let n: i64 = sys_read(fd, priv_be, 32)
48 sys_close(fd)
49 if n == 32 {
50 u256_load_be(priv_limbs, priv_be)
51 return 0
52 }
53 }
54 // generate + persist (0600)
55 nx_csprng_fill(priv_be, 32)
56 let wfd: i64 = sys_openat_wr(ACME_ACCT_KEY_PATH, 0x180) // 0600
57 if wfd > 0 {
58 sys_write(wfd, priv_be, 32)
59 sys_close(wfd)
60 }
61 u256_load_be(priv_limbs, priv_be)
62 return 1
63}
64
65func acme_print(label: *u8, label_n: i64, s: *u8, s_n: i64) -> i64 {
66 sys_write(1, label, label_n)
67 sys_write(1, s, s_n)
68 sys_write(1, "\n" as *u8, 1)
69 return 0
70}
71
72// Extract a fresh Replay-Nonce from a response into a new buffer.
73func acme_nonce_from(resp: *u8, total: i64, r: *i64, out_n: *i64) -> *u8 {
74 let no: *i64 = sys_mmap(8) as *i64
75 let nl: *i64 = sys_mmap(8) as *i64
76 if nx_http_response_header(resp, total, r, "Replay-Nonce" as *u8, 12, no, nl) != 1 {
77 out_n[0] = 0; return 0 as *u8
78 }
79 out_n[0] = nl[0]
80 return acme_dup_cstr(resp, no[0], nl[0])
81}
82
83// Build a kid-form ES256 JWS over `payload` (pay_n may be 0 for the
84// RFC 8555 ยง6.3 POST-as-GET). Returns 0 on success.
85func acme_signed_post(priv: *i64, kid: *u8, kid_n: i64,
86 nonce: *u8, nonce_n: i64, url: *u8, url_n: i64,
87 payload: *u8, pay_n: i64,
88 jws: *u8, jws_cap: i64, jws_n: *i64) -> i64 {
89 let prot: *u8 = sys_mmap(ACME_MAGIC_2048)
90 let poff: *i64 = sys_mmap(8) as *i64
91 poff[0] = 0
92 if nx_acme_es256_protected_kid_form(prot, poff, ACME_MAGIC_2048, kid, kid_n, nonce, nonce_n, url, url_n) != NXACMEES_OK { return 0 - 1 }
93 if nx_jose_sign_es256_flattened(prot, poff[0], payload, pay_n, priv, jws, jws_cap, jws_n) != NXES_OK { return 0 - 2 }
94 return 0
95}
96
97// Locate the dns-01 challenge object inside an authorization JSON body
98// and extract its token + url. The challenges array holds several
99// {type,url,token,status} objects; we find the one whose type is
100// "dns-01" by locating that substring then bounding its enclosing
101// {...} object and reading token/url within. Returns 1 on success.
102func acme_find_http01(body: *u8, start: i64, len: i64,
103 tok_off: *i64, tok_len: *i64,
104 url_off: *i64, url_len: *i64) -> i64 {
105 let end: i64 = start + len
106 // find "http-01" (7 bytes; distinct from "https")
107 var p: i64 = 0 - 1
108 var i: i64 = start
109 while i < end - 7 {
110 if body[i] == 0x68 { // 'h'
111 if body[i+1] == 0x74 { // 't'
112 if body[i+2] == 0x74 { // 't'
113 if body[i+3] == 0x70 { // 'p'
114 if body[i+4] == 0x2d { // '-'
115 if body[i+5] == 0x30 { // '0'
116 if body[i+6] == 0x31 { p = i; break } // '1'
117 }
118 }
119 }
120 }
121 }
122 }
123 i = i + 1
124 }
125 if p < 0 { return 0 }
126 // enclosing object: previous '{' .. next '}'
127 var obj_start: i64 = p
128 while obj_start > start {
129 if body[obj_start] == 0x7b { break } // '{'
130 obj_start = obj_start - 1
131 }
132 var obj_end: i64 = p
133 while obj_end < end {
134 if body[obj_end] == 0x7d { break } // '}'
135 obj_end = obj_end + 1
136 }
137 let span: i64 = obj_end - obj_start + 1
138 if nx_acme_json_str(body, obj_start, span, "token" as *u8, 5, tok_off, tok_len) != 1 { return 0 }
139 if nx_acme_json_str(body, obj_start, span, "url" as *u8, 3, url_off, url_len) != 1 { return 0 }
140 return 1
141}
142
143// Extract the idx-th string element of a JSON "key":[ "..", ".." ] array.
144func acme_json_array_nth(buf: *u8, start: i64, n: i64,
145 key: *u8, key_len: i64, idx: i64,
146 out_off: *i64, out_len: *i64) -> i64 {
147 let end: i64 = start + n
148 var kp: i64 = 0 - 1
149 var i: i64 = start
150 while i < end - key_len - 1 {
151 if buf[i] == 0x22 {
152 var m: i64 = 1
153 var k: i64 = 0
154 while k < key_len { if buf[i + 1 + k] != key[k] { m = 0; break } k = k + 1 }
155 if m == 1 { if buf[i + 1 + key_len] == 0x22 { kp = i; break } }
156 }
157 i = i + 1
158 }
159 if kp < 0 { return 0 }
160 var b: i64 = kp
161 while b < end { if buf[b] == 0x5b { break } b = b + 1 } // '['
162 if b >= end { return 0 }
163 var e: i64 = b
164 while e < end { if buf[e] == 0x5d { break } e = e + 1 } // ']'
165 var c: i64 = b
166 var found: i64 = 0
167 while c < e {
168 if buf[c] == 0x22 {
169 let vs: i64 = c + 1
170 var ve: i64 = vs
171 while ve < e { if buf[ve] == 0x22 { break } ve = ve + 1 }
172 if found == idx { out_off[0] = vs; out_len[0] = ve - vs; return 1 }
173 found = found + 1
174 c = ve + 1
175 } else { c = c + 1 }
176 }
177 return 0
178}
179
180// Validate ONE authorization over HTTP-01: POST-as-GET the authz -> find the
181// http-01 challenge -> compute keyAuth -> write it to the daemon's serve path
182// -> POST the challenge to trigger -> poll the authz until "valid". Returns
183// the latest Replay-Nonce (or 0 on failure); writes its length to out_nn.
184func acme_do_http01(au: *u8, aun: i64,
185 priv: *i64, kid: *u8, kid_n: i64,
186 px: *i64, py: *i64,
187 store: *TrustStore, now: i64, ct: *u8,
188 nonce: *u8, nonce_n: i64, out_nn: *i64) -> *u8 {
189 // fetch authz
190 let aj: *u8 = sys_mmap(ACME_MAGIC_8192)
191 let ajn: *i64 = sys_mmap(8) as *i64
192 if acme_signed_post(priv, kid, kid_n, nonce, nonce_n, au, aun, "" as *u8, 0, aj, ACME_MAGIC_8192, ajn) != 0 { return 0 as *u8 }
193 let zb: *u8 = sys_mmap(ACME_MAGIC_16384)
194 let zbt: *i64 = sys_mmap(8) as *i64
195 if nx_acme_req(au, aun, 1, ct, 21, aj, ajn[0], store, now, zb, ACME_MAGIC_16384, zbt) != NX_ACME_HTTP_OK { return 0 as *u8 }
196 let zr: *i64 = nx_http_resp_alloc()
197 if nx_http_response_parse(zb, zbt[0], zr) != 0 { return 0 as *u8 }
198 if zr[1] != 200 { return 0 as *u8 }
199 // If this authz is already "valid" (LE reuses prior validations), skip
200 // trigger+poll -- the authz's top-level status is the first "status".
201 let aso: *i64 = sys_mmap(8) as *i64
202 let asl: *i64 = sys_mmap(8) as *i64
203 if nx_acme_json_str(zb, zr[6], zbt[0] - zr[6], "status" as *u8, 6, aso, asl) == 1 {
204 if asl[0] == 5 { if zb[aso[0]] == 0x76 { // "valid"
205 sys_write(1, " authz already valid (reused)\n" as *u8, 31)
206 return acme_nonce_from(zb, zbt[0], zr, out_nn)
207 } }
208 }
209 let cto: *i64 = sys_mmap(8) as *i64
210 let ctl: *i64 = sys_mmap(8) as *i64
211 let cuo: *i64 = sys_mmap(8) as *i64
212 let cul: *i64 = sys_mmap(8) as *i64
213 if acme_find_http01(zb, zr[6], zbt[0] - zr[6], cto, ctl, cuo, cul) != 1 { return 0 as *u8 }
214 let token: *u8 = acme_dup_cstr(zb, cto[0], ctl[0])
215 let token_n: i64 = ctl[0]
216 let chal_url: *u8 = acme_dup_cstr(zb, cuo[0], cul[0])
217 let chal_url_n: i64 = cul[0]
218 // keyAuth -> serve path
219 let ka: *u8 = sys_mmap(256)
220 let kan: *i64 = sys_mmap(8) as *i64
221 if nx_acme_key_authorization(token, token_n, px, py, ka, 256, kan) != NXACMEES_OK { return 0 as *u8 }
222 let kfd2: i64 = sys_openat_wr("/volume1/homes/elderwesto/nishihost/acme-challenge/keyauth\x00" as *u8, 0x1A4)
223 if kfd2 > 0 { sys_write(kfd2, ka, kan[0]); sys_close(kfd2) }
224 sys_write(1, " serving keyAuth for token " as *u8, 28); sys_write(1, token, token_n); sys_write(1, "\n" as *u8, 1)
225 // trigger
226 var cur: *u8 = acme_nonce_from(zb, zbt[0], zr, out_nn)
227 if out_nn[0] == 0 { return 0 as *u8 }
228 let tjws: *u8 = sys_mmap(ACME_MAGIC_8192)
229 let tjn: *i64 = sys_mmap(8) as *i64
230 if acme_signed_post(priv, kid, kid_n, cur, out_nn[0], chal_url, chal_url_n, "{}" as *u8, 2, tjws, ACME_MAGIC_8192, tjn) != 0 { return 0 as *u8 }
231 let tb: *u8 = sys_mmap(ACME_MAGIC_16384)
232 let tbt: *i64 = sys_mmap(8) as *i64
233 if nx_acme_req(chal_url, chal_url_n, 1, ct, 21, tjws, tjn[0], store, now, tb, ACME_MAGIC_16384, tbt) != NX_ACME_HTTP_OK { return 0 as *u8 }
234 let tr: *i64 = nx_http_resp_alloc()
235 nx_http_response_parse(tb, tbt[0], tr)
236 cur = acme_nonce_from(tb, tbt[0], tr, out_nn)
237 // poll authz valid
238 var ai: i64 = 0
239 while ai < 40 {
240 sys_sleep_ms(ACME_MAGIC_3000)
241 let pj: *u8 = sys_mmap(ACME_MAGIC_8192)
242 let pjn: *i64 = sys_mmap(8) as *i64
243 if acme_signed_post(priv, kid, kid_n, cur, out_nn[0], au, aun, "" as *u8, 0, pj, ACME_MAGIC_8192, pjn) != 0 { return 0 as *u8 }
244 let pb: *u8 = sys_mmap(ACME_MAGIC_16384)
245 let pbt: *i64 = sys_mmap(8) as *i64
246 if nx_acme_req(au, aun, 1, ct, 21, pj, pjn[0], store, now, pb, ACME_MAGIC_16384, pbt) != NX_ACME_HTTP_OK { return 0 as *u8 }
247 let pr: *i64 = nx_http_resp_alloc()
248 nx_http_response_parse(pb, pbt[0], pr)
249 cur = acme_nonce_from(pb, pbt[0], pr, out_nn)
250 let so: *i64 = sys_mmap(8) as *i64
251 let sl: *i64 = sys_mmap(8) as *i64
252 if nx_acme_json_str(pb, pr[6], pbt[0] - pr[6], "status" as *u8, 6, so, sl) == 1 {
253 if sl[0] == 5 { if pb[so[0]] == 0x76 { return cur } } // "valid"
254 if sl[0] == 7 { if pb[so[0]] == 0x69 { return 0 as *u8 } } // "invalid"
255 }
256 ai = ai + 1
257 }
258 return 0 as *u8
259}
260
261func main() -> i64 {
262 let cdpath: *u8 = "/tmp/mozilla_certdata.txt\x00"
263 let lr: i64 = nx_trust_store_load_from_certdata(cdpath, 300, ACME_MAGIC_4194304)
264 if lr <= 0 { sys_write(2, "trust load FAIL\n" as *u8, 16); return 1 }
265 let store: *TrustStore = lr as *TrustStore
266 let now: i64 = ACME_MAGIC_1779284141
267
268 // ---- 1. directory ----
269 let dir_url: *u8 = "https://acme-v02.api.letsencrypt.org/directory\x00"
270 let buf: *u8 = sys_mmap(ACME_MAGIC_65536)
271 let tot: *i64 = sys_mmap(8) as *i64
272 let drc: i64 = nx_acme_req(dir_url, 46, 0, 0 as *u8, 0, 0 as *u8, 0, store, now, buf, ACME_MAGIC_65536, tot)
273 if drc != NX_ACME_HTTP_OK {
274 sys_write(2, "directory FAIL rc=" as *u8, 18)
275 let rb: *u8 = sys_mmap(8)
276 var av: i64 = drc
277 if av < 0 { sys_write(2, "-" as *u8, 1); av = 0 - av }
278 rb[0] = (0x30 + (av % 10)) as u8
279 sys_write(2, rb, 1); sys_write(2, "\n" as *u8, 1)
280 return 2
281 }
282 let r: *i64 = nx_http_resp_alloc()
283 if nx_http_response_parse(buf, tot[0], r) != 0 { return 3 }
284 if r[1] != 200 { sys_write(2, "dir!=200\n" as *u8, 9); return 4 }
285 let boff: i64 = r[6]
286 let blen: i64 = tot[0] - boff
287 let vo: *i64 = sys_mmap(8) as *i64
288 let vl: *i64 = sys_mmap(8) as *i64
289 if nx_acme_json_str(buf, boff, blen, "newAccount" as *u8, 10, vo, vl) != 1 { return 5 }
290 let acct_url: *u8 = acme_dup_cstr(buf, vo[0], vl[0])
291 let acct_url_n: i64 = vl[0]
292 if nx_acme_json_str(buf, boff, blen, "newNonce" as *u8, 8, vo, vl) != 1 { return 6 }
293 let nonce_url: *u8 = acme_dup_cstr(buf, vo[0], vl[0])
294 let nonce_url_n: i64 = vl[0]
295 if nx_acme_json_str(buf, boff, blen, "newOrder" as *u8, 8, vo, vl) != 1 { return 7 }
296 let order_url: *u8 = acme_dup_cstr(buf, vo[0], vl[0])
297 let order_url_n: i64 = vl[0]
298 acme_print("newAccount: " as *u8, 12, acct_url, acct_url_n)
299
300 // ---- 2. fresh nonce ----
301 let nb: *u8 = sys_mmap(ACME_MAGIC_16384)
302 let nbt: *i64 = sys_mmap(8) as *i64
303 if nx_acme_req(nonce_url, nonce_url_n, 0, 0 as *u8, 0, 0 as *u8, 0, store, now, nb, ACME_MAGIC_16384, nbt) != NX_ACME_HTTP_OK { return 8 }
304 let nr: *i64 = nx_http_resp_alloc()
305 if nx_http_response_parse(nb, nbt[0], nr) != 0 { return 9 }
306 let nno: *i64 = sys_mmap(8) as *i64
307 let nnl: *i64 = sys_mmap(8) as *i64
308 if nx_http_response_header(nb, nbt[0], nr, "Replay-Nonce" as *u8, 12, nno, nnl) != 1 { return 10 }
309 let nonce: *u8 = acme_dup_cstr(nb, nno[0], nnl[0])
310 let nonce_n: i64 = nnl[0]
311 acme_print("nonce: " as *u8, 7, nonce, nonce_n)
312
313 // ---- 3. account key + new-account JWS ----
314 let priv: *i64 = u256_alloc()
315 acme_account_key(priv)
316 let px: *i64 = u256_alloc()
317 let py: *i64 = u256_alloc()
318 nx_p256_pubkey_from_priv(priv, px, py)
319
320 let email: *u8 = "elderwesto@gmail.com" as *u8
321 let jws: *u8 = sys_mmap(ACME_MAGIC_8192)
322 let jwsn: *i64 = sys_mmap(8) as *i64
323 if nx_acme_build_new_account_jws_es256(priv, px, py, nonce, nonce_n,
324 acct_url, acct_url_n, email, 20,
325 jws, ACME_MAGIC_8192, jwsn) != NXACMEES_OK {
326 sys_write(2, "JWS build FAIL\n" as *u8, 15); return 11
327 }
328
329 // ---- 4. POST newAccount ----
330 let ct: *u8 = "application/jose+json" as *u8
331 let ab: *u8 = sys_mmap(ACME_MAGIC_16384)
332 let abt: *i64 = sys_mmap(8) as *i64
333 if nx_acme_req(acct_url, acct_url_n, 1, ct, 21, jws, jwsn[0], store, now, ab, ACME_MAGIC_16384, abt) != NX_ACME_HTTP_OK {
334 sys_write(2, "newAccount req FAIL\n" as *u8, 20); return 12
335 }
336 let ar: *i64 = nx_http_resp_alloc()
337 if nx_http_response_parse(ab, abt[0], ar) != 0 { return 13 }
338
339 // status
340 let sc: i64 = ar[1]
341 let scb: *u8 = sys_mmap(8)
342 scb[0] = (0x30 + (sc / 100)) as u8
343 scb[1] = (0x30 + ((sc / 10) % 10)) as u8
344 scb[2] = (0x30 + (sc % 10)) as u8
345 acme_print("newAccount status: " as *u8, 19, scb, 3)
346
347 if sc != 200 { if sc != 201 {
348 let bo2: i64 = ar[6]
349 sys_write(2, "newAccount FAIL body:\n" as *u8, 22)
350 sys_write(2, ab + bo2, abt[0] - bo2); sys_write(2, "\n" as *u8, 1)
351 return 20
352 } }
353 // account kid (Location header)
354 let lo: *i64 = sys_mmap(8) as *i64
355 let ll: *i64 = sys_mmap(8) as *i64
356 if nx_http_response_header(ab, abt[0], ar, "Location" as *u8, 8, lo, ll) != 1 { return 21 }
357 let kid: *u8 = acme_dup_cstr(ab, lo[0], ll[0])
358 let kid_n: i64 = ll[0]
359 acme_print("account kid: " as *u8, 13, kid, kid_n)
360 // nonce for next request from the newAccount response
361 let n2o: *i64 = sys_mmap(8) as *i64
362 let n2l: *i64 = sys_mmap(8) as *i64
363 if nx_http_response_header(ab, abt[0], ar, "Replay-Nonce" as *u8, 12, n2o, n2l) != 1 { return 22 }
364 var nonce2: *u8 = acme_dup_cstr(ab, n2o[0], n2l[0])
365 var nonce2_n: i64 = n2l[0]
366
367 // ---- 5. newOrder (2 domains: andelinwest.com + nishifamily.com) ----
368 let dp: *i64 = sys_mmap(32) as *i64
369 let dl: *i64 = sys_mmap(32) as *i64
370 dp[0] = ("andelinwest.com" as *u8) as i64; dl[0] = 15
371 dp[1] = ("nishifamily.com" as *u8) as i64; dl[1] = 15
372 let opay: *u8 = sys_mmap(512)
373 let opoff: *i64 = sys_mmap(8) as *i64
374 opoff[0] = 0
375 if nx_acme_payload_new_order(opay, opoff, 512, dp, dl, 2) != NXACMEES_OK { return 30 }
376 let ojws: *u8 = sys_mmap(ACME_MAGIC_8192)
377 let ojn: *i64 = sys_mmap(8) as *i64
378 if acme_signed_post(priv, kid, kid_n, nonce2, nonce2_n, order_url, order_url_n, opay, opoff[0], ojws, ACME_MAGIC_8192, ojn) != 0 { return 31 }
379 let ob: *u8 = sys_mmap(ACME_MAGIC_16384)
380 let obt: *i64 = sys_mmap(8) as *i64
381 if nx_acme_req(order_url, order_url_n, 1, ct, 21, ojws, ojn[0], store, now, ob, ACME_MAGIC_16384, obt) != NX_ACME_HTTP_OK { return 32 }
382 let orr: *i64 = nx_http_resp_alloc()
383 if nx_http_response_parse(ob, obt[0], orr) != 0 { return 33 }
384 if orr[1] != 201 {
385 sys_write(2, "newOrder body:\n" as *u8, 15); sys_write(2, ob + orr[6], obt[0] - orr[6]); sys_write(2, "\n" as *u8, 1)
386 return 34
387 }
388 sys_write(1, "newOrder 201 (2 domains)\n" as *u8, 25)
389 let oolo: *i64 = sys_mmap(8) as *i64
390 let ooll: *i64 = sys_mmap(8) as *i64
391 var the_order_url: *u8 = 0 as *u8
392 var the_order_url_n: i64 = 0
393 if nx_http_response_header(ob, obt[0], orr, "Location" as *u8, 8, oolo, ooll) == 1 {
394 the_order_url = acme_dup_cstr(ob, oolo[0], ooll[0]); the_order_url_n = ooll[0]
395 }
396 let obody: i64 = orr[6]
397 let oblen: i64 = obt[0] - obody
398 let fvo: *i64 = sys_mmap(8) as *i64
399 let fvl: *i64 = sys_mmap(8) as *i64
400 if nx_acme_json_str(ob, obody, oblen, "finalize" as *u8, 8, fvo, fvl) != 1 { return 35 }
401 let finalize_url: *u8 = acme_dup_cstr(ob, fvo[0], fvl[0])
402 let finalize_url_n: i64 = fvl[0]
403 // both authz URLs (one per domain)
404 let a0o: *i64 = sys_mmap(8) as *i64; let a0l: *i64 = sys_mmap(8) as *i64
405 let a1o: *i64 = sys_mmap(8) as *i64; let a1l: *i64 = sys_mmap(8) as *i64
406 if acme_json_array_nth(ob, obody, oblen, "authorizations" as *u8, 14, 0, a0o, a0l) != 1 { return 36 }
407 if acme_json_array_nth(ob, obody, oblen, "authorizations" as *u8, 14, 1, a1o, a1l) != 1 { return 37 }
408 let az0: *u8 = acme_dup_cstr(ob, a0o[0], a0l[0]); let az0n: i64 = a0l[0]
409 let az1: *u8 = acme_dup_cstr(ob, a1o[0], a1l[0]); let az1n: i64 = a1l[0]
410 // nonce from order response
411 let n3o: *i64 = sys_mmap(8) as *i64
412 let n3l: *i64 = sys_mmap(8) as *i64
413 if nx_http_response_header(ob, obt[0], orr, "Replay-Nonce" as *u8, 12, n3o, n3l) != 1 { return 38 }
414 var nonce3: *u8 = acme_dup_cstr(ob, n3o[0], n3l[0])
415 var nn: i64 = n3l[0]
416
417 // ---- 6-9. validate BOTH authorizations over HTTP-01 (sequential) ----
418 sys_write(1, "validating authz #1...\n" as *u8, 23)
419 nonce3 = acme_do_http01(az0, az0n, priv, kid, kid_n, px, py, store, now, ct, nonce3, nn, &nn)
420 if (nonce3 as i64) == 0 { sys_write(2, "authz#1 FAIL\n" as *u8, 12); return 50 }
421 sys_write(1, "authz #1 VALID\n" as *u8, 15)
422 sys_write(1, "validating authz #2...\n" as *u8, 23)
423 nonce3 = acme_do_http01(az1, az1n, priv, kid, kid_n, px, py, store, now, ct, nonce3, nn, &nn)
424 if (nonce3 as i64) == 0 { sys_write(2, "authz#2 FAIL\n" as *u8, 12); return 51 }
425 sys_write(1, "authz #2 VALID\n" as *u8, 15)
426
427 // ---- 10. cert key (separate persistent P-256) ----
428 let cpriv_be: *u8 = sys_mmap(32)
429 let ckfd: i64 = sys_openat_rd("/tmp/acme_cert_key.bin\x00" as *u8)
430 if ckfd > 0 {
431 let cn2: i64 = sys_read(ckfd, cpriv_be, 32); sys_close(ckfd)
432 if cn2 != 32 { nx_csprng_fill(cpriv_be, 32) }
433 } else {
434 nx_csprng_fill(cpriv_be, 32)
435 let cwf: i64 = sys_openat_wr("/tmp/acme_cert_key.bin\x00" as *u8, 0x180)
436 if cwf > 0 { sys_write(cwf, cpriv_be, 32); sys_close(cwf) }
437 }
438 let cpriv: *i64 = u256_alloc(); u256_load_be(cpriv, cpriv_be)
439 let cpx: *i64 = u256_alloc()
440 let cpy: *i64 = u256_alloc()
441 nx_p256_pubkey_from_priv(cpriv, cpx, cpy)
442
443 // ---- 11. CSR (CN=andelinwest.com, SAN both domains) ----
444 let sp: *i64 = sys_mmap(32) as *i64
445 let sln: *i64 = sys_mmap(32) as *i64
446 sp[0] = ("andelinwest.com" as *u8) as i64; sln[0] = 15
447 sp[1] = ("nishifamily.com" as *u8) as i64; sln[1] = 15
448 let csr: *u8 = sys_mmap(ACME_MAGIC_2048)
449 let csrn: *i64 = sys_mmap(8) as *i64
450 if nx_acme_build_csr_ecdsa_p256("andelinwest.com" as *u8, 15, sp, sln, 2, cpriv, cpx, cpy, csr, ACME_MAGIC_2048, csrn) != 0 { return 70 }
451
452 // ---- 12. finalize (POST the CSR) ----
453 let fpay: *u8 = sys_mmap(ACME_MAGIC_4096)
454 let fpo: *i64 = sys_mmap(8) as *i64
455 fpo[0] = 0
456 if nx_acme_payload_finalize(fpay, fpo, ACME_MAGIC_4096, csr, csrn[0]) != NXACMEES_OK { return 71 }
457 let fjws: *u8 = sys_mmap(ACME_MAGIC_8192)
458 let fjn: *i64 = sys_mmap(8) as *i64
459 if acme_signed_post(priv, kid, kid_n, nonce3, nn, finalize_url, finalize_url_n, fpay, fpo[0], fjws, ACME_MAGIC_8192, fjn) != 0 { return 72 }
460 let fb: *u8 = sys_mmap(ACME_MAGIC_16384)
461 let fbt: *i64 = sys_mmap(8) as *i64
462 if nx_acme_req(finalize_url, finalize_url_n, 1, ct, 21, fjws, fjn[0], store, now, fb, ACME_MAGIC_16384, fbt) != NX_ACME_HTTP_OK { return 73 }
463 let fr: *i64 = nx_http_resp_alloc()
464 nx_http_response_parse(fb, fbt[0], fr)
465 nonce3 = acme_nonce_from(fb, fbt[0], fr, &nn)
466 sys_write(1, "finalize posted\n" as *u8, 16)
467
468 // ---- 13. poll the order until the certificate URL appears ----
469 var oi: i64 = 0
470 var cert_url: *u8 = 0 as *u8
471 var cert_url_n: i64 = 0
472 while oi < 40 {
473 sys_sleep_ms(ACME_MAGIC_3000)
474 let qj: *u8 = sys_mmap(ACME_MAGIC_8192)
475 let qjn: *i64 = sys_mmap(8) as *i64
476 if acme_signed_post(priv, kid, kid_n, nonce3, nn, the_order_url, the_order_url_n, "" as *u8, 0, qj, ACME_MAGIC_8192, qjn) != 0 { return 74 }
477 let qb: *u8 = sys_mmap(ACME_MAGIC_16384)
478 let qbt: *i64 = sys_mmap(8) as *i64
479 if nx_acme_req(the_order_url, the_order_url_n, 1, ct, 21, qj, qjn[0], store, now, qb, ACME_MAGIC_16384, qbt) != NX_ACME_HTTP_OK { return 75 }
480 let qr: *i64 = nx_http_resp_alloc()
481 nx_http_response_parse(qb, qbt[0], qr)
482 nonce3 = acme_nonce_from(qb, qbt[0], qr, &nn)
483 let cvo: *i64 = sys_mmap(8) as *i64
484 let cvl: *i64 = sys_mmap(8) as *i64
485 if nx_acme_json_str(qb, qr[6], qbt[0] - qr[6], "certificate" as *u8, 11, cvo, cvl) == 1 {
486 cert_url = acme_dup_cstr(qb, cvo[0], cvl[0]); cert_url_n = cvl[0]; oi = 40
487 }
488 oi = oi + 1
489 }
490 if (cert_url as i64) == 0 { sys_write(2, "order poll: no cert url\n" as *u8, 24); return 76 }
491 acme_print("certificate url: " as *u8, 17, cert_url, cert_url_n)
492
493 // ---- 14. download the cert chain (PEM) ----
494 let dj: *u8 = sys_mmap(ACME_MAGIC_8192)
495 let djn: *i64 = sys_mmap(8) as *i64
496 if acme_signed_post(priv, kid, kid_n, nonce3, nn, cert_url, cert_url_n, "" as *u8, 0, dj, ACME_MAGIC_8192, djn) != 0 { return 77 }
497 let dbb: *u8 = sys_mmap(ACME_MAGIC_65536)
498 let dbt: *i64 = sys_mmap(8) as *i64
499 if nx_acme_req(cert_url, cert_url_n, 1, ct, 21, dj, djn[0], store, now, dbb, ACME_MAGIC_65536, dbt) != NX_ACME_HTTP_OK { return 78 }
500 let dr: *i64 = nx_http_resp_alloc()
501 nx_http_response_parse(dbb, dbt[0], dr)
502 let pem_off: i64 = dr[6]
503 let pem_n: i64 = dbt[0] - pem_off
504 let cfd3: i64 = sys_openat_wr("/volume1/homes/elderwesto/nishihost/certs/andelinwest_fullchain.pem\x00" as *u8, 0x1A4)
505 if cfd3 > 0 { sys_write(cfd3, dbb + pem_off, pem_n); sys_close(cfd3) }
506 let cfd4: i64 = sys_openat_wr("/tmp/andelinwest_fullchain.pem\x00" as *u8, 0x1A4)
507 if cfd4 > 0 { sys_write(cfd4, dbb + pem_off, pem_n); sys_close(cfd4) }
508 sys_write(1, "*** CERT ISSUED -- fullchain PEM saved ***\n" as *u8, 43)
509 sys_write(1, dbb + pem_off, pem_n); sys_write(1, "\n" as *u8, 1)
510 return 0
511}