code wiki / _hdl_build / nx_roku_install_gate.nx

nx_roku_install_gate.nx source

↩ module page · 196 lines · 12200 B

1import "nx_gate_gn.nx" 2// nx_roku_install_gate.nx -- SOVEREIGN loopback proof of nx_roku_install (the Roku sideloader). The gate ITSELF 3// is the mock Roku Development Application Installer (a Nishi HTTP-Digest server): it binds a loopback socket, 4// issues the 401 Digest challenge, validates the client's response with the (now-correct) nx_digest_auth, and 5// checks the uploaded multipart actually carries the channel zip (PK\x03\x04). It forks nx_roku_install against 6// itself. NO python, NO shell -- Nishi all the way up; the only non-Nishi thing in the real loop is the Roku. 7// R1 correct password -> ROKU-INSTALL-OK (client exit 0) 8// R2 (liar-kill) wrong password -> server 401 -> ROKU-INSTALL-FAIL (client exit 1) 9// GREEN iff 2/2. Requires /tmp/nx_roku_install.sov.elf + /tmp/nx_roku_emit.sov.elf (built first). 10// Durable knowledge/status/roku_install_gate.log. license_tier: ORIGINAL 11import "nx_syscalls.nx" 12import "nx_digest_auth.nx" 13 14func gp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 15func gw(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 } 16func gwn(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=(48 as u8);k=1}; while m>0{t[k]=((48+(m%10)) as u8);m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(fd,bb,k); return 0 } 17func gstrlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 18func gcat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ dst[off+i]=s[i]; i=i+1 } return off+i } 19func gcatb(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { var i: i64=0; while i<n { dst[off+i]=src[i]; i=i+1 } return off+n } 20func gcatn(dst: *u8, off: i64, v: i64) -> i64 { var m: i64=v; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var o: i64=off; var i: i64=0; while i<k{dst[o]=t[k-1-i];o=o+1;i=i+1} return o } 21func gread_file(path: *u8, buf: *u8, cap: i64) -> i64 { 22 let fd: i64=sys_openat_rd(path); if fd<0 { return 0 } 23 var total: i64=0; var nrd: i64=sys_read(fd, buf, cap) 24 while nrd>0 { total=total+nrd; if total>=cap { nrd=0 } else { nrd=sys_read(fd, ((buf as i64)+total) as *u8, cap-total) } } 25 sys_close(fd); return total 26} 27func gwrite_file(path: *u8, data: *u8, len: i64) -> i64 { let fd: i64=sys_openat_wr(path, 0x1a4); if fd<0 { return 0-1 } sys_write(fd, data, len); sys_close(fd); return len } 28func gfind(hay: *u8, n: i64, needle: *u8) -> i64 { 29 let nl: i64=gstrlen(needle); if nl==0 { return 0-1 } 30 var i: i64=0 31 while i+nl<=n { var j: i64=0; var hit: i64=1; while j<nl { if hay[i+j]!=needle[j] { hit=0; j=nl } else { j=j+1 } } if hit==1 { return i+nl } i=i+1 } 32 return 0-1 33} 34func gcontains(hay: *u8, n: i64, needle: *u8) -> i64 { if gfind(hay,n,needle)>=0 { return 1 } return 0 } 35func gextract_q(hay: *u8, n: i64, key: *u8, out: *u8, cap: i64) -> i64 { 36 let start: i64=gfind(hay, n, key); if start<0 { return 0-1 } 37 var i: i64=start; var o: i64=0 38 while i<n { if hay[i]==(0x22 as u8) { i=n } else { if o<cap { out[o]=hay[i]; o=o+1 } i=i+1 } } 39 out[o]=0 as u8; return o 40} 41func gextract_tok(hay: *u8, n: i64, key: *u8, out: *u8, cap: i64) -> i64 { 42 let start: i64=gfind(hay, n, key); if start<0 { return 0-1 } 43 var i: i64=start; var o: i64=0 44 while i<n { 45 let c: i64=hay[i] as i64; var stop: i64=0 46 if c==44 { stop=1 } if c==32 { stop=1 } if c==13 { stop=1 } if c==10 { stop=1 } 47 if stop==1 { i=n } else { if o<cap { out[o]=hay[i]; o=o+1 } i=i+1 } 48 } 49 out[o]=0 as u8; return o 50} 51func gparse_clen(buf: *u8, n: i64) -> i64 { 52 let p: i64=gfind(buf, n, "Content-Length:" as *u8); if p<0 { return 0 } 53 var i: i64=p; var sp: i64=1 54 while sp==1 { if i<n { if buf[i]==(0x20 as u8) { i=i+1 } else { sp=0 } } else { sp=0 } } 55 var v: i64=0; var d: i64=1 56 while d==1 { if i<n { let c: i64=buf[i] as i64; if c>=48 { if c<=57 { v=v*10+(c-48); i=i+1 } else { d=0 } } else { d=0 } } else { d=0 } } 57 return v 58} 59func has_pk(buf: *u8, n: i64) -> i64 { 60 var i: i64=0 61 while i+4<=n { if buf[i]==(0x50 as u8) { if buf[i+1]==(0x4b as u8) { if buf[i+2]==(0x03 as u8) { if buf[i+3]==(0x04 as u8) { return 1 } } } } i=i+1 } 62 return 0 63} 64func gsockaddr(buf: *u8, port: i64, a: i64, b: i64, c: i64, d: i64) -> i64 { 65 var i: i64=0; while i<16 { buf[i]=0 as u8; i=i+1 } 66 buf[0]=2 as u8; buf[2]=((port>>8)&0xff) as u8; buf[3]=(port&0xff) as u8 67 buf[4]=a as u8; buf[5]=b as u8; buf[6]=c as u8; buf[7]=d as u8; return 0 68} 69func gbind_listen(port: i64) -> i64 { 70 let lfd: i64=sys_socket(AF_INET, SOCK_STREAM, 0); if lfd<0 { return 0-1 } 71 let one: *i64=sys_mmap(8) as *i64; one[0]=1 72 sys_setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, one as *u8, 4) 73 let addr: *u8=sys_mmap(16); gsockaddr(addr, port, 127, 0, 0, 1) 74 if sys_bind(lfd, addr, 16)!=0 { sys_close(lfd); return 0-2 } 75 if sys_listen(lfd, 8)!=0 { sys_close(lfd); return 0-3 } 76 return lfd 77} 78// read a full HTTP request (headers + Content-Length body) -- not until EOF (client waits for our response) 79func grecv_request(cfd: i64, buf: *u8, cap: i64) -> i64 { 80 var total: i64=0; var hdr_end: i64=0-1; var clen: i64=0; var reading: i64=1 81 while reading==1 { 82 let r: i64=sys_read(cfd, ((buf as i64)+total) as *u8, cap-total) 83 if r<=0 { reading=0 } else { 84 total=total+r 85 if hdr_end<0 { let he: i64=gfind(buf, total, "\r\n\r\n" as *u8); if he>=0 { hdr_end=he; clen=gparse_clen(buf, total) } } 86 if hdr_end>=0 { if total>=hdr_end+clen { reading=0 } } 87 if total>=cap { reading=0 } 88 } 89 } 90 return total 91} 92func gsend_401(cfd: i64) -> i64 { 93 let b: *u8=sys_mmap(512); var o: i64=0 94 o=gcat(b,o,"HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Digest realm=\"rokudev\", nonce=\"deadbeefcafe0001\", qop=\"auth\", algorithm=MD5\r\nContent-Length: 0\r\nConnection: close\r\n\r\n" as *u8) 95 sys_write(cfd, b, o); return 0 96} 97func gsend_200(cfd: i64) -> i64 { 98 let page: *u8="<html><body><font color=red>Install Success.</font></body></html>" as *u8 99 let pl: i64=gstrlen(page) 100 let b: *u8=sys_mmap(512); var o: i64=0 101 o=gcat(b,o,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: " as *u8); o=gcatn(b,o,pl); o=gcat(b,o,"\r\nConnection: close\r\n\r\n" as *u8); o=gcatb(b,o,page,pl) 102 sys_write(cfd, b, o); return 0 103} 104// validate the client's Digest response against an independent recompute (server knows realm+password+nonce) 105func gvalidate(req: *u8, n: i64) -> i64 { 106 let user: *u8=sys_mmap(128); let ul: i64=gextract_q(req,n,"username=\"" as *u8,user,127) 107 let uri: *u8=sys_mmap(128); let url: i64=gextract_q(req,n,"uri=\"" as *u8,uri,127) 108 let nonce: *u8=sys_mmap(128);let nl: i64=gextract_q(req,n,"nonce=\"" as *u8,nonce,127) 109 let cnonce: *u8=sys_mmap(128);let cnl: i64=gextract_q(req,n,"cnonce=\"" as *u8,cnonce,127) 110 let qop: *u8=sys_mmap(64); let ql: i64=gextract_tok(req,n,"qop=" as *u8,qop,63) 111 let nc: *u8=sys_mmap(64); let ncl: i64=gextract_tok(req,n,"nc=" as *u8,nc,63) 112 let cresp: *u8=sys_mmap(64); let crl: i64=gextract_q(req,n,"response=\"" as *u8,cresp,63) 113 if crl<0 { return 0 } 114 if ul<0 { return 0 } 115 let ha1: *u8=sys_mmap(64); digest_auth_ha1(user, ul, "rokudev" as *u8, 7, "secretpass" as *u8, 10, ha1) 116 let ha2: *u8=sys_mmap(64); digest_auth_ha2("POST" as *u8, 4, uri, url, ha2) 117 let re: *u8=sys_mmap(64); digest_auth_response(ha1, nonce, nl, nc, ncl, cnonce, cnl, qop, ql, ha2, re) 118 var i: i64=0; var eqd: i64=1; while i<32 { if re[i]!=cresp[i] { eqd=0; i=32 } else { i=i+1 } } 119 gp(" [srv] digest match=" as *u8); gn(eqd); gp(" pk=" as *u8); gn(has_pk(req,n)); gp("\n" as *u8) 120 if eqd==0 { return 0 } 121 if has_pk(req,n)==0 { return 0 } 122 return 1 123} 124func gserve_round(lfd: i64) -> i64 { 125 let req: *u8=sys_mmap(1048576) 126 var c: i64=0 127 while c<2 { 128 let cfd: i64=sys_accept(lfd) 129 if cfd<0 { c=2 } else { 130 let n: i64=grecv_request(cfd, req, 1048576) 131 if gcontains(req, n, "Authorization: Digest" as *u8)==1 { 132 if gvalidate(req, n)==1 { gsend_200(cfd) } else { gsend_401(cfd) } 133 } else { gsend_401(cfd) } 134 sys_close(cfd); c=c+1 135 } 136 } 137 return 0 138} 139// blocking fork+exec to a file (for the one-shot emit) 140func g_run(elf: *u8, args: *i64, nargs: i64, out_path: *u8) -> i64 { 141 let pid: i64=sys_fork() 142 if pid==0 { 143 let o: i64=sys_openat_wr(out_path, 0x1a4); if o>=0 { sys_dup3(o,1,0) } 144 let argv: *i64=sys_mmap(8*16) as *i64; argv[0]=elf as i64 145 var i: i64=0; while i<nargs { argv[1+i]=args[i]; i=i+1 } argv[1+nargs]=0 146 let envp: *i64=sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 147 sys_execve(elf, argv, envp); sys_exit(127) 148 } 149 let st: *i64=sys_mmap(16) as *i64; sys_wait4(pid, st, 0); return (st[0]>>8)&0xff 150} 151// bind first (no race), fork the install client, serve its challenge+upload, reap; return client exit code 152func ginstall_round(port: i64, portstr: *u8, zippath: *u8, passfile: *u8, clilog: *u8) -> i64 { 153 let lfd: i64=gbind_listen(port); if lfd<0 { gp(" BIND-FAIL rc=" as *u8); gn(lfd); gp("\n" as *u8); return 0-99 } 154 let pid: i64=sys_fork() 155 if pid==0 { 156 let o: i64=sys_openat_wr(clilog, 0x1a4); if o>=0 { sys_dup3(o,1,0); sys_dup3(o,2,0) } 157 let argv: *i64=sys_mmap(8*16) as *i64 158 argv[0]="/tmp/nx_roku_install.sov.elf" as *u8 as i64 159 argv[1]="127.0.0.1" as *u8 as i64; argv[2]=portstr as i64; argv[3]=zippath as i64 160 argv[4]="rokudev" as *u8 as i64; argv[5]=passfile as i64; argv[6]=0 161 let envp: *i64=sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 162 sys_execve("/tmp/nx_roku_install.sov.elf" as *u8, argv, envp); sys_exit(127) 163 } 164 gserve_round(lfd) 165 sys_close(lfd) 166 let st: *i64=sys_mmap(16) as *i64; sys_wait4(pid, st, 0) 167 return (st[0]>>8)&0xff 168} 169func dump_log(path: *u8) -> i64 { let b: *u8=sys_mmap(8192); let n: i64=gread_file(path, b, 8191); sys_write(1, b, n); return 0 } 170 171func main() -> i64 { 172 gp("=== ROKU INSTALL GATE: sovereign loopback (Nishi mock installer; no python/shell) ===\n" as *u8) 173 // emit a fresh channel zip to push 174 let a: *i64=sys_mmap(64) as *i64; a[0]="/tmp/roku_inst.zip" as *u8 as i64 175 g_run("/tmp/nx_roku_emit.sov.elf" as *u8, a, 1, "/tmp/roku_inst_emit.log" as *u8) 176 gwrite_file("/tmp/roku_inst_ok.txt" as *u8, "secretpass" as *u8, 10) 177 gwrite_file("/tmp/roku_inst_bad.txt" as *u8, "wrongpass" as *u8, 9) 178 179 gp("-- R1 POSITIVE: correct password --\n" as *u8) 180 let e1: i64=ginstall_round(8099, "8099" as *u8, "/tmp/roku_inst.zip" as *u8, "/tmp/roku_inst_ok.txt" as *u8, "/tmp/roku_cli_ok.log" as *u8) 181 gp(" client: " as *u8); dump_log("/tmp/roku_cli_ok.log" as *u8); gp(" exit=" as *u8); gn(e1); gp("\n" as *u8) 182 183 gp("-- R2 NEGATIVE (liar-kill): wrong password must be rejected --\n" as *u8) 184 let e2: i64=ginstall_round(8100, "8100" as *u8, "/tmp/roku_inst.zip" as *u8, "/tmp/roku_inst_bad.txt" as *u8, "/tmp/roku_cli_bad.log" as *u8) 185 gp(" client: " as *u8); dump_log("/tmp/roku_cli_bad.log" as *u8); gp(" exit=" as *u8); gn(e2); gp("\n" as *u8) 186 187 var pass: i64=0; let total: i64=2 188 gp("----\n" as *u8) 189 gp(" R1 correct-pass -> install OK (exit 0)? " as *u8); if e1==0 { pass=pass+1; gp("YES PASS\n" as *u8) } else { gp("NO FAIL\n" as *u8) } 190 gp(" R2 wrong-pass -> rejected (exit != 0)? " as *u8); if e2!=0 { pass=pass+1; gp("YES PASS\n" as *u8) } else { gp("NO FAIL\n" as *u8) } 191 192 let lg: i64=sys_openat_append("knowledge/status/roku_install_gate.log" as *u8, 0x1a4) 193 if lg>=0 { gw(lg, "ROKU-INSTALL rows=" as *u8); gwn(lg, total); gw(lg, " pass=" as *u8); gwn(lg, pass); if pass==total { gw(lg, " verdict=GREEN\n" as *u8) } else { gw(lg, " verdict=RED\n" as *u8) } sys_close(lg) } 194 if pass==total { gp("ROKU-INSTALL GREEN (sovereign Digest+multipart sideload proven against a Nishi mock)\n" as *u8); sys_exit(0); return 0 } 195 gp("ROKU-INSTALL RED\n" as *u8); sys_exit(1); return 1 196}