code wiki / _hdl_build / nx_connect_auth_gate.nx

nx_connect_auth_gate.nx source

↩ module page · 159 lines · 9424 B

1// nx_connect_auth_gate.nx -- proves TWO things about CONNECT's real-login rung: 2// PART A (rendering, pure): the serve core renders the signed-out vs signed-in app correctly from the 3// per-request auth slots the daemon stamps -- login/register forms when out, identity + sign-out when in, 4// the nav identity area tracks it, and each auth event maps to the right banner. No crypto here. 5// PART B (the REAL OPAQUE crypto, end-to-end): using the SAME nx_opaque_login seam the daemon uses, in a 6// throwaway realm, register -> login -> whoami round-trips a handle, a WRONG passphrase is REJECTED, and 7// an UNKNOWN handle is REJECTED -- so "signed in" can never be faked. This is the anti-stub proof. 8// license_tier: ORIGINAL expect_exit: 0 9import "nx_connect_serve.nx" 10import "nx_opaque_login.nx" // olg_ctx_setup / olg_register / olg_login / olg_whoami + NxAuthContext + NX_MAUTH_OK 11 12const AU_BUF: i64 = 262144 13 14func au_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 15func au_check(pass: i64, label: *u8, fails: *i64) -> i64 { 16 au_w(" " as *u8); au_w(label); au_w(": " as *u8) 17 if pass==1 { au_w("PASS\n" as *u8) } else { au_w("FAIL\n" as *u8); fails[0]=fails[0]+1 } 18 return 0 19} 20func au_has(buf: *u8, n: i64, needle: *u8) -> i64 { 21 let nl: i64 = cs_slen(needle) 22 if nl==0 { return 0 } 23 var i: i64=0 24 while i+nl<=n { var k: i64=0; var hit: i64=1; while k<nl { if buf[i+k]!=needle[k] { hit=0; k=nl } else { k=k+1 } } if hit==1 { return 1 } i=i+1 } 25 return 0 26} 27func au_count(buf: *u8, n: i64, needle: *u8) -> i64 { 28 let nl: i64 = cs_slen(needle) 29 if nl==0 { return 0 } 30 var hits: i64=0 31 var i: i64=0 32 while i+nl<=n { var k: i64=0; var hit: i64=1; while k<nl { if buf[i+k]!=needle[k] { hit=0; k=nl } else { k=k+1 } } if hit==1 { hits=hits+1; i=i+nl } else { i=i+1 } } 33 return hits 34} 35func au_get(dst: *u8, path: *u8) -> i64 { 36 var o: i64 = cs_cat(dst, 0, "GET " as *u8) 37 o = cs_cat(dst, o, path) 38 o = cs_cat(dst, o, " HTTP/1.1\r\nHost: x\r\n\r\n" as *u8) 39 return o 40} 41 42func main() -> i64 { 43 let fails: *i64 = sys_mmap(16) as *i64 44 fails[0]=0 45 let req: *u8 = sys_mmap(65536) 46 let out: *u8 = sys_mmap(AU_BUF) 47 let ctx: *i64 = cs_world_new() 48 au_w("=== nx_connect_auth_gate -- CONNECT real login: rendering + REAL OPAQUE crypto ===\n" as *u8) 49 50 // ---- PART A: RENDERING FROM THE AUTH SLOTS ---- 51 // A1: logged OUT -> the login page shows the form + the nav offers Sign in / Register, and the app never 52 // claims an identity. 53 ctx[CS_AUTH]=0; ctx[CS_HANDLE]=0; ctx[CS_AUTH_EVENT]=CS_AE_NONE 54 var rn: i64 = au_get(req, "/connect/login" as *u8) 55 var n: i64 = cs_handle(ctx, req, rn, out, AU_BUF) 56 var a1: i64=1 57 if au_has(out, n, "action=\"/connect/login\"" as *u8)==0 { a1=0 } 58 if au_has(out, n, "type=\"password\"" as *u8)==0 { a1=0 } 59 if au_has(out, n, "/login\" aria-current=\"page\">Sign in</a>" as *u8)==0 { a1=0 } 60 if au_has(out, n, "/register\">Register</a>" as *u8)==0 { a1=0 } 61 if au_has(out, n, "/account\">@" as *u8)==1 { a1=0 } // no identity/account link shown when signed out 62 au_check(a1, "A1 signed-out /login renders the OPAQUE form + Sign in/Register nav, no identity claimed" as *u8, fails) 63 64 // A2: logged IN as 'vera' -> account page shows the identity + a POST sign-out, nav shows @vera, no forms. 65 ctx[CS_AUTH]=1; ctx[CS_HANDLE]="vera\x00" as *u8 as i64; ctx[CS_AUTH_EVENT]=CS_AE_NONE 66 rn = au_get(req, "/connect/account" as *u8) 67 n = cs_handle(ctx, req, rn, out, AU_BUF) 68 var a2: i64=1 69 if au_has(out, n, "/account\" aria-current=\"page\">@vera</a>" as *u8)==0 { a2=0 } 70 if au_has(out, n, "action=\"/connect/logout\"" as *u8)==0 { a2=0 } 71 if au_has(out, n, "Sign in</a>" as *u8)==1 { a2=0 } // no signed-out nav when signed in 72 au_check(a2, "A2 signed-in /account shows @handle + POST sign-out; the signed-out nav is gone" as *u8, fails) 73 74 // A3: exactly ONE aria-current tab holds on an auth page too (the nav invariant survives the identity area) 75 var a3: i64=1 76 if au_count(out, n, "aria-current=\"page\"" as *u8)!=1 { a3=0 } 77 au_check(a3, "A3 nav invariant: exactly one aria-current tab on the account page" as *u8, fails) 78 79 // A4: the auth EVENT slot maps to the right banner and survives cs_handle's banner reset. 80 ctx[CS_AUTH]=1; ctx[CS_HANDLE]="vera\x00" as *u8 as i64; ctx[CS_AUTH_EVENT]=CS_AE_REGISTERED 81 rn = au_get(req, "/connect/account" as *u8) 82 n = cs_handle(ctx, req, rn, out, AU_BUF) 83 var a4: i64=1 84 if au_has(out, n, "Account created" as *u8)==0 { a4=0 } 85 if au_has(out, n, "recovery phrase" as *u8)==0 { a4=0 } 86 ctx[CS_AUTH]=0; ctx[CS_HANDLE]=0; ctx[CS_AUTH_EVENT]=CS_AE_LOGIN_FAIL 87 rn = au_get(req, "/connect/login" as *u8) 88 n = cs_handle(ctx, req, rn, out, AU_BUF) 89 if au_has(out, n, "Sign-in failed" as *u8)==0 { a4=0 } 90 au_check(a4, "A4 auth events map to banners (registered->recovery notice, fail->indistinguishable error)" as *u8, fails) 91 92 // reset the render ctx so it can't leak identity into the crypto part 93 ctx[CS_AUTH]=0; ctx[CS_HANDLE]=0; ctx[CS_AUTH_EVENT]=CS_AE_NONE 94 95 // ---- PART B: THE REAL OPAQUE CRYPTO (the anti-stub proof, throwaway realm) ---- 96 let actx: *NxAuthContext = sys_mmap(256) as *NxAuthContext 97 // light argon (256 KiB/t1/p1) so the gate runs fast; the daemon uses a real OWASP cost. Realm/keys/store 98 // are throwaway paths in /tmp -- olg_ctx_setup auto-inits fresh server keys. 99 let cs_ok: i64 = olg_ctx_setup(actx, "/tmp/nxcauth_keys.bin" as *u8, "/tmp/nxcauth_store.log" as *u8, "nishi_connect_test" as *u8, 18, "Connect test" as *u8, 12, 256, 1, 1) 100 au_check((cs_ok==0), "B0 OPAQUE context init (fresh realm keys auto-provisioned)" as *u8, fails) 101 102 let mn: *u8 = sys_mmap(600) 103 let mnn: *i64 = sys_mmap(16) as *i64 104 let tok: *u8 = sys_mmap(600) 105 let tokn: *i64 = sys_mmap(16) as *i64 106 let who: *u8 = sys_mmap(128) 107 let whon: *i64 = sys_mmap(16) as *i64 108 let now: i64 = sys_now_realtime_sec() 109 110 // B1: REGISTER a fresh handle succeeds and yields a recovery mnemonic 111 let rrc: i64 = olg_register(actx, "alice_c\x00" as *u8, 7, "correct horse battery\x00" as *u8, 21, mn, 600, mnn) 112 au_check((rrc==NX_MAUTH_OK) & (mnn[0] > 0), "B1 register: new handle stored (OPAQUE envelope) + recovery mnemonic issued" as *u8, fails) 113 114 // B2: LOGIN with the correct passphrase yields a session token 115 let lrc: i64 = olg_login(actx, "alice_c\x00" as *u8, 7, "correct horse battery\x00" as *u8, 21, tok, 600, tokn) 116 au_check((lrc==NX_MAUTH_OK) & (tokn[0] > 0), "B2 login: correct passphrase mints a signed session token" as *u8, fails) 117 118 // B3: WHOAMI validates the token to a STABLE 32-byte user-id hash (the authoritative identity CONNECT 119 // binds the world to; the display handle is resolved from a per-account file the daemon writes). 120 let wrc: i64 = olg_whoami(actx, tok, tokn[0], now, who, 128, whon) 121 au_check((wrc==NX_MAUTH_OK) & (whon[0]==32), "B3 whoami: a valid token yields a 32-byte user-id hash (the real identity)" as *u8, fails) 122 123 // B3b: a FRESH login for the SAME handle resolves to the SAME uid -> the account identity is DETERMINISTIC 124 // and stable, so a returning user gets their own persisted world (not a new one) every time. 125 let who2: *u8 = sys_mmap(128) 126 let whon2: *i64 = sys_mmap(16) as *i64 127 olg_login(actx, "alice_c\x00" as *u8, 7, "correct horse battery\x00" as *u8, 21, tok, 600, tokn) 128 let wrc2: i64 = olg_whoami(actx, tok, tokn[0], now, who2, 128, whon2) 129 var b3b: i64 = 0 130 if wrc2==NX_MAUTH_OK { if whon2[0]==32 { 131 var eq: i64 = 1 132 var bi: i64 = 0 133 while bi<32 { if who[bi]!=who2[bi] { eq=0; bi=32 } else { bi=bi+1 } } 134 b3b = eq 135 } } 136 au_check(b3b, "B3b re-login for @alice_c yields the SAME uid (identity is deterministic -> stable per-account world)" as *u8, fails) 137 138 // B4: WRONG passphrase is REJECTED (no token) 139 let brc: i64 = olg_login(actx, "alice_c\x00" as *u8, 7, "wrong passphrase here\x00" as *u8, 21, tok, 600, tokn) 140 au_check((brc!=NX_MAUTH_OK), "B4 NEG-CONTROL: a wrong passphrase is refused (no session minted)" as *u8, fails) 141 142 // B5: UNKNOWN handle is REJECTED 143 let urc: i64 = olg_login(actx, "nobody_x\x00" as *u8, 8, "correct horse battery\x00" as *u8, 21, tok, 600, tokn) 144 au_check((urc!=NX_MAUTH_OK), "B5 NEG-CONTROL: an unknown handle is refused" as *u8, fails) 145 146 // B6: a TAMPERED token fails whoami (flip one byte) 147 olg_login(actx, "alice_c\x00" as *u8, 7, "correct horse battery\x00" as *u8, 21, tok, 600, tokn) 148 tok[3] = (tok[3] ^ (0x40 as u8)) 149 let trc: i64 = olg_whoami(actx, tok, tokn[0], now, who, 128, whon) 150 au_check((trc!=NX_MAUTH_OK), "B6 NEG-CONTROL: a tampered session token fails validation (Ed25519 signature holds)" as *u8, fails) 151 152 au_w(" fails=" as *u8) 153 let nb: *u8=sys_mmap(28); var m: i64=fails[0]; var kk: i64=0; if m==0{nb[0]=48 as u8;kk=1} while m>0{nb[kk]=(48+(m%10)) as u8;m=m/10;kk=kk+1} let bb: *u8=sys_mmap(28); var j: i64=0; while j<kk{bb[j]=nb[kk-1-j];j=j+1} sys_write(1,bb,kk) 154 au_w("\n" as *u8) 155 if fails[0]==0 { au_w("VERDICT: verdict=GREEN (real OPAQUE login: register/login/whoami round-trip, wrong-pw + unknown + tamper all refused)\n" as *u8); sys_exit(0) } 156 au_w("VERDICT: verdict=RED\n" as *u8) 157 sys_exit(1) 158 return 1 159}