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}