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}