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}