nx_cert_autorenew_lib.nx source
↩ module page · 80 lines · 4480 B
1// nx_cert_autorenew_lib.nx -- PURE mapping + threshold logic for the per-domain wildcard TLS
2// auto-renewal system (no I/O, no TLS, no fork). Split out (rule 15 DRY / rule 9 single-responsibility)
3// so BOTH the driver (nx_cert_autorenew.nx) and the gate (nx_cert_autorenew_gate.nx) share ONE copy of
4// the domain->path mapping + the WARN/EXPIRED trigger predicate -- the exact logic the gate proves and
5// the driver acts on. Reuses nx_cert_monitor's verdict vocabulary (CM_OK/WARN/EXPIRED/NOCERT + cm_verdict
6// + CM_WARN_DAYS) so the monitor and the renewer can NEVER disagree about "is this cert expiring".
7//
8// The mapping closes the gap that started this whole arc: a per-domain wildcard cert that silently lapsed
9// because nothing renewed it. Given a dotted domain D, the system knows EXACTLY:
10// issue output -> /tmp/new_<D>_fullchain.der + /tmp/new_<D>_ecdsa_key.bin (nx_acme_dns01_issue argv[1]=D)
11// live SNI path -> /volume1/.../certs/le_<label>_fullchain.der + le_<label>_ecdsa_key.bin
12// (label = D's first DNS label; the names nx_sites_daemon loads per ClientHello SNI)
13// license_tier: ORIGINAL (composes nx_cert_monitor's verdict logic; no new crypto/transport)
14import "nx_cert_monitor.nx" // CM_OK/CM_WARN/CM_EXPIRED/CM_NOCERT, CM_WARN_DAYS, cm_verdict, cm_status, cm_name, X509Cert
15
16// ---- single-source infra paths (named, not magic; nx_sites_daemon loads exactly these) ----
17const CAR_CERT_DIR: *u8 = "/volume1/homes/elderwesto/nishihost/certs/" as *u8
18const CAR_TMP_NEW: *u8 = "/tmp/new_" as *u8
19
20// ---- tiny string helpers (car_-prefixed; no collision with cm_*/x509_*) ----
21func car_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
22func car_streq(a: *u8, b: *u8) -> i64 {
23 var i: i64 = 0
24 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
25 if b[i] == (0 as u8) { return 1 }
26 return 0
27}
28// append the NUL-terminated string s into out at offset off; returns the new offset (caller terminates).
29func car_put(out: *u8, off: i64, s: *u8) -> i64 {
30 var i: i64 = 0
31 while s[i] != (0 as u8) { out[off] = s[i]; off = off + 1; i = i + 1 }
32 return off
33}
34
35// first DNS label of a dotted domain (chars up to the first '.') -> out (NUL-terminated); returns its len.
36// "nishifamily.com" -> "nishifamily" ; "andelinwest.com" -> "andelinwest". This is the <label> token in
37// le_<label>_fullchain.der -- the exact per-domain cert filename nx_sites_daemon selects by SNI.
38func car_label(domain: *u8, out: *u8) -> i64 {
39 var i: i64 = 0
40 while domain[i] != (0 as u8) {
41 if domain[i] == (0x2e as u8) { out[i] = 0 as u8; return i } // '.' -> end of the first label
42 out[i] = domain[i]
43 i = i + 1
44 }
45 out[i] = 0 as u8
46 return i
47}
48
49// /tmp/new_<domain>_fullchain.der -- the DER the issue organ writes for argv[1]=domain (proven naming).
50func car_issue_der(domain: *u8, out: *u8) -> i64 {
51 var o: i64 = 0
52 o = car_put(out, o, CAR_TMP_NEW); o = car_put(out, o, domain); o = car_put(out, o, "_fullchain.der" as *u8)
53 out[o] = 0 as u8; return o
54}
55// /tmp/new_<domain>_ecdsa_key.bin -- the matching 32-byte P-256 key from the SAME issuance.
56func car_issue_key(domain: *u8, out: *u8) -> i64 {
57 var o: i64 = 0
58 o = car_put(out, o, CAR_TMP_NEW); o = car_put(out, o, domain); o = car_put(out, o, "_ecdsa_key.bin" as *u8)
59 out[o] = 0 as u8; return o
60}
61// /volume1/.../certs/le_<label>_fullchain.der -- the LIVE SNI cert path the daemon loads at startup.
62func car_remote_der(label: *u8, out: *u8) -> i64 {
63 var o: i64 = 0
64 o = car_put(out, o, CAR_CERT_DIR); o = car_put(out, o, "le_" as *u8); o = car_put(out, o, label); o = car_put(out, o, "_fullchain.der" as *u8)
65 out[o] = 0 as u8; return o
66}
67// /volume1/.../certs/le_<label>_ecdsa_key.bin -- the LIVE SNI key path (paired with the fullchain above).
68func car_remote_key(label: *u8, out: *u8) -> i64 {
69 var o: i64 = 0
70 o = car_put(out, o, CAR_CERT_DIR); o = car_put(out, o, "le_" as *u8); o = car_put(out, o, label); o = car_put(out, o, "_ecdsa_key.bin" as *u8)
71 out[o] = 0 as u8; return o
72}
73
74// THE trigger predicate: renew ONLY on WARN (inside the 21d window) or EXPIRED. OK = nothing to do;
75// NOCERT = could not read/parse or host unreachable -> do NOT blindly re-issue (an outage is not an expiry).
76func car_should_renew(verdict: i64) -> i64 {
77 if verdict == CM_WARN { return 1 }
78 if verdict == CM_EXPIRED { return 1 }
79 return 0
80}