code wiki / _hdl_build / nx_email_send_gate.nx
nx_email_send_gate.nx source
↩ module page · 103 lines · 4724 B
1// nx_email_send_gate.nx -- GATE for outbound DKIM send (nx_email_send).
2//
3// COMPLETENESS : compose a message, DKIM ed25519-sha256 sign it, and
4// verify the signature validates against the public key.
5// TAMPER : flip a BODY byte -> verify fails (bh mismatch); flip a
6// signed HEADER byte -> verify fails (sig mismatch).
7// NEG-CONTROL : verify under a DIFFERENT public key -> fails (it truly
8// checks the signature, not just structure).
9//
10// Evidence -> knowledge/status/email_send.log (SENDGATE ... verdict=GREEN)
11// license_tier: ORIGINAL
12import "nx_email_send.nx"
13import "nx_csprng.nx"
14import "nx_syscalls.nx"
15import "nx_gate_verdict.nx"
16
17const SEND_LOG: *u8 = "knowledge/status/email_send.log"
18
19func ew(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 }
20func epf(fd: i64, label: *u8, pass: i64) -> i64 { ew(fd, label); if pass == 1 { ew(fd, "PASS" as *u8) } else { ew(fd, "FAIL" as *u8) } return 0 }
21func slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
22func cp(dst: *u8, src: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { dst[i] = src[i]; i = i + 1 } return 0 }
23func g_find(hay: *u8, hlen: i64, needle: *u8) -> i64 {
24 let nl: i64 = slen(needle); var i: i64 = 0
25 while i + nl <= hlen {
26 var k: i64 = 0; var hit: i64 = 1
27 while k < nl { if (hay[i + k] & 0xff) != (needle[k] & 0xff) { hit = 0; k = nl } else { k = k + 1 } }
28 if hit == 1 { return i }
29 i = i + 1
30 }
31 return 0 - 1
32}
33func contains(hay: *u8, hlen: i64, needle: *u8) -> i64 { if g_find(hay, hlen, needle) >= 0 { return 1 } return 0 }
34
35func main() -> i64 {
36 var ok: i64 = 1
37
38 // keypair
39 let priv: *u8 = sys_mmap(32)
40 nx_csprng_fill(priv, 32)
41 let pub: *u8 = sys_mmap(32)
42 ed25519_pub_from_priv(priv, pub)
43
44 // compose + sign
45 let msg: *u8 = sys_mmap(4096)
46 let mlen: i64 = nx_email_compose("self@jasonewest.com" as *u8, "bob@remote.test" as *u8, "Hello from self" as *u8, "This is a DKIM-signed message from the sovereign stack.\r\n" as *u8, msg, 4096)
47 let signed: *u8 = sys_mmap(8192)
48 let sln: i64 = nx_email_dkim_sign_message(msg, mlen, priv, "nishi1" as *u8, "jasonewest.com" as *u8, signed, 8192)
49
50 var header_present: i64 = contains(signed, sln, "DKIM-Signature: v=1; a=ed25519-sha256" as *u8)
51 if header_present != 1 { ok = 0 }
52
53 var verify_self: i64 = 0
54 if nx_email_dkim_verify_message(signed, sln, pub) == 1 { verify_self = 1 } else { ok = 0 }
55
56 // tamper body (flip a byte near the end = in the body)
57 let t1: *u8 = sys_mmap(8192)
58 cp(t1, signed, sln)
59 t1[sln - 3] = (t1[sln - 3] + 1) as u8
60 var tamper_body: i64 = 0
61 if nx_email_dkim_verify_message(t1, sln, pub) == 0 { tamper_body = 1 } else { ok = 0 }
62
63 // tamper a signed header (flip a byte in the Subject value)
64 let t2: *u8 = sys_mmap(8192)
65 cp(t2, signed, sln)
66 let sp: i64 = g_find(t2, sln, "Hello from self" as *u8)
67 if sp >= 0 { t2[sp] = (t2[sp] + 1) as u8 }
68 var tamper_header: i64 = 0
69 if nx_email_dkim_verify_message(t2, sln, pub) == 0 { tamper_header = 1 } else { ok = 0 }
70
71 // wrong key
72 let priv2: *u8 = sys_mmap(32)
73 nx_csprng_fill(priv2, 32)
74 let pub2: *u8 = sys_mmap(32)
75 ed25519_pub_from_priv(priv2, pub2)
76 var wrongkey: i64 = 0
77 if nx_email_dkim_verify_message(signed, sln, pub2) == 0 { wrongkey = 1 } else { ok = 0 }
78
79 var fd: i64 = 1
80 while fd >= 1 {
81 ew(fd, "SENDGATE authored=organ dkim=ed25519-sha256 domain=jasonewest.com " as *u8)
82 epf(fd, "header_present=" as *u8, header_present)
83 epf(fd, " verify_self=" as *u8, verify_self)
84 epf(fd, " tamper_body=" as *u8, tamper_body)
85 epf(fd, " tamper_header=" as *u8, tamper_header)
86 epf(fd, " wrongkey_negctrl=" as *u8, wrongkey)
87 if ok == 1 { ew(fd, " verdict=GREEN\n" as *u8) } else { ew(fd, " verdict=RED\n" as *u8) }
88 if fd == 1 {
89 let lf: i64 = sys_openat_append(SEND_LOG, 420)
90 if lf >= 1 { fd = lf } else { fd = 0 }
91 } else { sys_close(fd); fd = 0 }
92 }
93
94 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
95 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
96 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
97 let ctr__dry: *i64 = gv_ctr()
98 ctr__dry[0] = ok
99 ctr__dry[1] = 1
100 let rc__dry: i64 = gv_verdict("EMAIL-SEND-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
101 sys_exit(rc__dry)
102 return rc__dry
103}