code wiki / _hdl_build / nx_connect_pubkey_bind_gate.nx
nx_connect_pubkey_bind_gate.nx source
↩ module page · 220 lines · 11868 B
1// nx_connect_pubkey_bind_gate.nx -- proves the CONNECT pubkey directory BINDS keys to an authenticated
2// identity, so trust-on-first-use is dead: once a handle is verified under an account, no other account
3// and no anonymous caller can substitute a different key for it. A relay operator or impostor therefore
4// cannot man-in-the-middle the key directory. Tests cs_sh_pub_put2 directly AND through the live
5// /connect/pubkey route (ctx[CS_UID] stamped exactly as the daemon does), plus a compaction round-trip
6// proving the owner+verified binding is DURABLE across a boot replay.
7// license_tier: ORIGINAL expect_exit: 0
8import "nx_connect_serve.nx"
9
10const PB_BUF: i64 = 262144
11
12func pb_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
13func pb_n(v: i64) -> i64 { let t: *u8=sys_mmap(32); let o: i64=cu_putn(t,0,v); t[o]=0 as u8; pb_w(t); return 0 }
14func pb_check(pass: i64, label: *u8, fails: *i64) -> i64 {
15 pb_w(" " as *u8); pb_w(label); pb_w(": " as *u8)
16 if pass==1 { pb_w("PASS\n" as *u8) } else { pb_w("FAIL\n" as *u8); fails[0]=fails[0]+1 }
17 return 0
18}
19func pb_has(buf: *u8, n: i64, needle: *u8) -> i64 {
20 let nl: i64 = cs_slen(needle)
21 if nl==0 { return 0 }
22 var i: i64=0
23 while i+nl<=n {
24 var k: i64=0
25 var hit: i64=1
26 while k<nl { if buf[i+k]!=needle[k] { hit=0; k=nl } else { k=k+1 } }
27 if hit==1 { return 1 }
28 i=i+1
29 }
30 return 0
31}
32func pb_post(dst: *u8, path: *u8, body: *u8) -> i64 {
33 let bl: i64 = cs_slen(body)
34 var o: i64 = cs_cat(dst, 0, "POST " as *u8)
35 o = cs_cat(dst, o, path)
36 o = cs_cat(dst, o, " HTTP/1.1\r\nHost: x\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: " as *u8)
37 o = cs_catn(dst, o, bl)
38 o = cs_cat(dst, o, "\r\n\r\n" as *u8)
39 o = cs_cat(dst, o, body)
40 return o
41}
42func pb_get(dst: *u8, path: *u8) -> i64 {
43 var o: i64 = cs_cat(dst, 0, "GET " as *u8)
44 o = cs_cat(dst, o, path)
45 o = cs_cat(dst, o, " HTTP/1.1\r\nHost: x\r\n\r\n" as *u8)
46 return o
47}
48// fill a 32-byte buffer with a distinct fake user-id, the way whoami hands us a 32-byte hash
49func pb_uid(buf: *u8, tag: i64) -> i64 { var i: i64=0; while i<32 { buf[i]=((tag+i) & 0xff) as u8; i=i+1 } return 32 }
50
51func main() -> i64 {
52 let fails: *i64 = sys_mmap(16) as *i64
53 fails[0]=0
54 pb_w("=== nx_connect_pubkey_bind_gate -- identity-bound keys kill trust-on-first-use ===\n" as *u8)
55
56 let KA: *u8 = "1111111111111111111111111111111111111111111111111111111111111111" as *u8
57 let KB: *u8 = "2222222222222222222222222222222222222222222222222222222222222222" as *u8
58 let KEVIL: *u8 = "6666666666666666666666666666666666666666666666666666666666666666" as *u8
59 let KROT: *u8 = "7777777777777777777777777777777777777777777777777777777777777777" as *u8
60 let uidA: *u8 = sys_mmap(64); pb_uid(uidA, 10)
61 let uidB: *u8 = sys_mmap(64); pb_uid(uidB, 200)
62 let dummy: *u8 = sys_mmap(64)
63
64 // ============ direct cs_sh_pub_put2 -- the anti-spoof core ============
65 let sh: *i64 = cs_shared_new()
66
67 // ---- T1: anonymous publish stores UNVERIFIED ----
68 var t1: i64 = 1
69 let r1: i64 = cs_sh_pub_put2(sh, "alice" as *u8, KA, dummy, 0)
70 if r1!=1 { t1=0 }
71 let owa: *u8 = sh_own_at(sh, cs_sh_pub_find(sh, "alice" as *u8))
72 if (owa[32] as i64)!=0 { t1=0 }
73 pb_check(t1, "T1 anonymous publish stored UNVERIFIED (verified flag 0)" as *u8, fails)
74
75 // ---- T2: an AUTHENTICATED publish binds owner + verifies ----
76 var t2: i64 = 1
77 let r2: i64 = cs_sh_pub_put2(sh, "bob" as *u8, KB, uidA, 1)
78 if r2!=1 { t2=0 }
79 let fb: i64 = cs_sh_pub_find(sh, "bob" as *u8)
80 let owb: *u8 = sh_own_at(sh, fb)
81 if (owb[32] as i64)!=1 { t2=0 } // verified
82 if cs_eq32(owb, uidA)==0 { t2=0 } // owner bound to uidA
83 pb_check(t2, "T2 authenticated publish binds the owner id and marks the key VERIFIED" as *u8, fails)
84
85 // ---- T3: a DIFFERENT account cannot overwrite bob's verified key (the MITM is REFUSED) ----
86 var t3: i64 = 1
87 let r3: i64 = cs_sh_pub_put2(sh, "bob" as *u8, KEVIL, uidB, 1)
88 if r3!=(0-4) { t3=0 } // REFUSED
89 let rowb: *u8 = sh_pub_at(sh, fb)
90 if cs_seq((rowb as i64 + SH_NAME) as *u8, KB)==0 { t3=0 } // original key intact
91 pb_check(t3, "T3 a different account is REFUSED (-4) from overwriting a verified handle; key unchanged" as *u8, fails)
92
93 // ---- T4: an ANONYMOUS publish also cannot touch a verified handle ----
94 var t4: i64 = 1
95 let r4: i64 = cs_sh_pub_put2(sh, "bob" as *u8, KEVIL, dummy, 0)
96 if r4!=(0-4) { t4=0 }
97 if cs_seq((rowb as i64 + SH_NAME) as *u8, KB)==0 { t4=0 }
98 pb_check(t4, "T4 an anonymous caller is REFUSED from rebinding a verified handle" as *u8, fails)
99
100 // ---- T5: the TRUE owner CAN rotate the key ----
101 var t5: i64 = 1
102 let r5: i64 = cs_sh_pub_put2(sh, "bob" as *u8, KROT, uidA, 1)
103 if r5!=2 { t5=0 }
104 if cs_seq((rowb as i64 + SH_NAME) as *u8, KROT)==0 { t5=0 }
105 if (owb[32] as i64)!=1 { t5=0 }
106 pb_check(t5, "T5 the owning account CAN rotate its own verified key" as *u8, fails)
107
108 // ---- T6: an unverified handle is claimable by the first account to authenticate, then locked ----
109 var t6: i64 = 1
110 let r6: i64 = cs_sh_pub_put2(sh, "alice" as *u8, KA, uidB, 1) // alice was anonymous (T1)
111 if r6!=2 { t6=0 }
112 let owa2: *u8 = sh_own_at(sh, cs_sh_pub_find(sh, "alice" as *u8))
113 if (owa2[32] as i64)!=1 { t6=0 }
114 if cs_eq32(owa2, uidB)==0 { t6=0 }
115 let r6b: i64 = cs_sh_pub_put2(sh, "alice" as *u8, KEVIL, uidA, 1) // now a different account is locked out
116 if r6b!=(0-4) { t6=0 }
117 pb_check(t6, "T6 an unverified handle is claimable by the first authenticated account, then locked to it" as *u8, fails)
118
119 // ============ through the LIVE /connect/pubkey route (ctx stamped as the daemon does) ============
120 let ctx: *i64 = cs_world_new()
121 let sh2: *i64 = cs_shared_new()
122 let req: *u8 = sys_mmap(65536)
123 let out: *u8 = sys_mmap(PB_BUF)
124
125 // ---- T7: anonymous route publish -> verified:0 in the JSON ----
126 var t7: i64 = 1
127 ctx[CS_AUTH]=0; ctx[CS_UID]=0
128 var rn: i64 = pb_post(req, "/connect/pubkey" as *u8, "n=carol&p=3333333333333333333333333333333333333333333333333333333333333333" as *u8)
129 var n: i64 = cs_handle2(ctx, sh2, req, rn, out, PB_BUF)
130 if pb_has(out, n, "\"ok\":1" as *u8)==0 { t7=0 }
131 if pb_has(out, n, "\"verified\":0" as *u8)==0 { t7=0 }
132 pb_check(t7, "T7 route: an anonymous publish reports verified:0" as *u8, fails)
133
134 // ---- T8: an authenticated route publish (ctx[CS_UID] set) reports verified:1; /pubkeys carries flags ----
135 var t8: i64 = 1
136 ctx[CS_AUTH]=1; ctx[CS_UID]=uidA as i64
137 rn = pb_post(req, "/connect/pubkey" as *u8, "n=dave&p=4444444444444444444444444444444444444444444444444444444444444444" as *u8)
138 n = cs_handle2(ctx, sh2, req, rn, out, PB_BUF)
139 if pb_has(out, n, "\"verified\":1" as *u8)==0 { t8=0 }
140 rn = pb_get(req, "/connect/pubkeys" as *u8)
141 n = cs_handle2(ctx, sh2, req, rn, out, PB_BUF)
142 if pb_has(out, n, "\"n\":\"dave\"" as *u8)==0 { t8=0 }
143 if pb_has(out, n, "\"verified\":1" as *u8)==0 { t8=0 } // dave is verified
144 if pb_has(out, n, "\"verified\":0" as *u8)==0 { t8=0 } // carol is not
145 pb_check(t8, "T8 route: an authenticated publish reports verified:1; /pubkeys shows per-key flags" as *u8, fails)
146
147 // ---- T9: a DIFFERENT account is refused at the route from hijacking dave ----
148 var t9: i64 = 1
149 ctx[CS_AUTH]=1; ctx[CS_UID]=uidB as i64
150 rn = pb_post(req, "/connect/pubkey" as *u8, "n=dave&p=9999999999999999999999999999999999999999999999999999999999999999" as *u8)
151 n = cs_handle2(ctx, sh2, req, rn, out, PB_BUF)
152 if pb_has(out, n, "\"ok\":0" as *u8)==0 { t9=0 }
153 if pb_has(out, n, "\"r\":-4" as *u8)==0 { t9=0 }
154 rn = pb_get(req, "/connect/pubkeys" as *u8)
155 n = cs_handle2(ctx, sh2, req, rn, out, PB_BUF)
156 if pb_has(out, n, "9999999999999999" as *u8)==1 { t9=0 } // the impostor key never landed
157 if pb_has(out, n, "4444444444444444" as *u8)==0 { t9=0 } // dave's real key intact
158 pb_check(t9, "T9 route: a different account posting dave's handle is REFUSED (-4); the real key stands" as *u8, fails)
159
160 // ============ compaction round-trip preserves owner + verified ============
161 // ---- T10: emit sh (from the direct tests) -> replay -> bob still verified+owned+rotated, and a fresh
162 // impostor is STILL refused after replay (the binding is durable across a boot) ----
163 var t10: i64 = 1
164 let logb: *u8 = sys_mmap(65536)
165 let lo: i64 = cs_sh_compact_emit(sh, logb)
166 let sh3: *i64 = cs_shared_new()
167 cs_sh_replay(sh3, logb, lo)
168 let fb3: i64 = cs_sh_pub_find(sh3, "bob" as *u8)
169 if fb3<0 { t10=0 }
170 let owb3: *u8 = sh_own_at(sh3, fb3)
171 if (owb3[32] as i64)!=1 { t10=0 } // still verified after replay
172 if cs_eq32(owb3, uidA)==0 { t10=0 } // still owned by uidA
173 let rowb3: *u8 = sh_pub_at(sh3, fb3)
174 if cs_seq((rowb3 as i64 + SH_NAME) as *u8, KROT)==0 { t10=0 } // rotated key survived
175 let rimp: i64 = cs_sh_pub_put2(sh3, "bob" as *u8, KEVIL, uidB, 1)
176 if rimp!=(0-4) { t10=0 }
177 pb_check(t10, "T10 compaction round-trip: owner+verified survive replay; impostor still refused post-boot" as *u8, fails)
178
179 // ---- T11: ANTI-SQUAT -- a signed-in account may publish ONLY under its OWN handle. Without this, a
180 // verified account could claim (and permanently LOCK) somebody else's name: the mirror image of the
181 // impostor hole, and just as fatal to "nobody can publish a key under your name".
182 var t11: i64 = 1
183 let hdave: *u8 = "dave" as *u8
184 ctx[CS_AUTH]=1; ctx[CS_UID]=uidA as i64; ctx[CS_HANDLE]=hdave as i64
185 rn = pb_post(req, "/connect/pubkey" as *u8, "n=erin&p=5555555555555555555555555555555555555555555555555555555555555555" as *u8)
186 n = cs_handle2(ctx, sh2, req, rn, out, PB_BUF)
187 if pb_has(out, n, "\"ok\":0" as *u8)==0 { t11=0 }
188 if pb_has(out, n, "\"r\":-5" as *u8)==0 { t11=0 }
189 rn = pb_get(req, "/connect/pubkeys" as *u8)
190 n = cs_handle2(ctx, sh2, req, rn, out, PB_BUF)
191 if pb_has(out, n, "\"n\":\"erin\"" as *u8)==1 { t11=0 } // the squatted name was never created
192 // ...and the SAME account publishing under its own handle still succeeds (the rule is narrow, not a block)
193 rn = pb_post(req, "/connect/pubkey" as *u8, "n=dave&p=4444444444444444444444444444444444444444444444444444444444444444" as *u8)
194 n = cs_handle2(ctx, sh2, req, rn, out, PB_BUF)
195 if pb_has(out, n, "\"verified\":1" as *u8)==0 { t11=0 }
196 pb_check(t11, "T11 ANTI-SQUAT: signing in does NOT let you claim another name (-5); your own name still works" as *u8, fails)
197
198 // ---- T12: /connect/me is the identity read-back the device page uses (handle + boolean, no secret) ----
199 var t12: i64 = 1
200 ctx[CS_AUTH]=0; ctx[CS_UID]=0; ctx[CS_HANDLE]=0
201 rn = pb_get(req, "/connect/me" as *u8)
202 n = cs_handle2(ctx, sh2, req, rn, out, PB_BUF)
203 if pb_has(out, n, "application/json" as *u8)==0 { t12=0 }
204 if pb_has(out, n, "\"authed\":0" as *u8)==0 { t12=0 }
205 ctx[CS_AUTH]=1; ctx[CS_UID]=uidA as i64; ctx[CS_HANDLE]=hdave as i64
206 rn = pb_get(req, "/connect/me" as *u8)
207 n = cs_handle2(ctx, sh2, req, rn, out, PB_BUF)
208 if pb_has(out, n, "\"authed\":1" as *u8)==0 { t12=0 }
209 if pb_has(out, n, "\"handle\":\"dave\"" as *u8)==0 { t12=0 }
210 pb_check(t12, "T12 /connect/me: authed:0 anonymous; authed:1 + handle when signed in" as *u8, fails)
211
212 pb_w(" fails=" as *u8); pb_n(fails[0]); pb_w("\n" as *u8)
213 if fails[0]==0 {
214 pb_w("VERDICT: verdict=GREEN (pubkey directory binds keys to authenticated identity; trust-on-first-use is dead)\n" as *u8)
215 sys_exit(0)
216 }
217 pb_w("VERDICT: verdict=RED\n" as *u8)
218 sys_exit(1)
219 return 1
220}