code wiki / (root) / nx_mse_wire.nx

nx_mse_wire.nx source

↩ module page · 218 lines · 15496 B

1// nx_mse_wire.nx -- REUSABLE MSE/PE wire library: a drop-in encrypted-socket layer for the torrent transport. 2// 3// nx_mse_handshake PROVED the crypto over loopback; this packages it as an adoptable API so the seeder 4// (inbound) and downloader (outbound) can wrap their peer traffic with ONE small change: swap raw read/write 5// for mse_read/mse_write on an MseCtx. A plaintext ctx (mode=0) is a passthrough, so the SAME serve loop 6// handles both encrypted and legacy plaintext peers -> backward compatible by construction. 7// 8// MseCtx { mode(0=plain,1=rc4), sendS/sendij, recvS/recvij } 9// mse_ctx_plain -> passthrough ctx (legacy peers) 10// mse_connect -> INITIATOR handshake (outbound): fills ctx (send=keyA, recv=keyB) 11// mse_accept -> RECEIVER handshake (inbound, known SKEY): fills ctx (send=keyB, recv=keyA) 12// mse_accept_pfx -> receiver variant when the first Ya byte was already read (demux path) 13// mse_read/write -> transparent decrypt/encrypt (in place); plaintext ctx = raw 14// mse_classify_first_byte -> 0x13 => plaintext BT handshake, else => MSE (Ya) 15// 16// license_tier: ORIGINAL module: nishi-core.torrent.mse_wire depends: mse_keys (dh+rc4+sha1), entropy 17import "nx_mse_keys.nx" 18import "nx_entropy.nx" 19const MSE_MAGIC_1024: i64 = 1024 20const MSE_MAGIC_1200: i64 = 1200 21 22// Generate a fresh random DH private key (192-bit) from the kernel CSPRNG -> per-connection forward secrecy. 23// Falls back to a wall-clock key ONLY if entropy is unavailable (never breaks a download; getrandom ~never fails). 24func mse_gen_priv(x: *i64) -> i64 { 25 bi_zero(x, MSE_N) 26 let rb: *u8 = sys_mmap(64) 27 if ent_fill(rb, 24) == 0 { 28 var i: i64 = 0 29 while i < 6 { x[i] = ((rb[i*4] as i64) | ((rb[i*4+1] as i64)<<8) | ((rb[i*4+2] as i64)<<16) | ((rb[i*4+3] as i64)<<24)) & 0xFFFFFFFF; i = i + 1 } 30 } else { 31 let t: i64 = sys_now_realtime_ms(); x[0]=(t&0xFFFFFFFF); x[1]=(t>>20)&0xFFFFFFFF; x[2]=0x9e3779b9; x[3]=0x7f4a7c15 32 } 33 x[0] = (x[0] | 1) & 0xFFFFFFFF // ensure nonzero/odd 34 return 0 35} 36 37struct MseCtx { 38 mode: i64, // 0 = plaintext passthrough, 1 = RC4 39 sendS: i64, // *u8 256-byte outbound S-box 40 sendij: i64, // *i64 outbound {i,j} 41 recvS: i64, // *u8 256-byte inbound S-box 42 recvij: i64, // *i64 inbound {i,j} 43} 44const MSE_CTX_BYTES: i64 = 48 // 5 i64 fields + slack 45 46func mw_read_n(fd: i64, buf: *u8, n: i64) -> i64 { var g: i64=0; while g<n { let r: i64=sys_read(fd, buf+g, n-g); if r<=0 { return 0-1 } g=g+r } return g } 47func mw_write_n(fd: i64, buf: *u8, n: i64) -> i64 { var o: i64=0; while o<n { let w: i64=sys_write(fd, buf+o, n-o); if w<=0 { return 0-1 } o=o+w } return n } 48func mw_put4(b: *u8, o: i64, v: i64) -> i64 { b[o]=((v>>24)&0xff) as u8; b[o+1]=((v>>16)&0xff) as u8; b[o+2]=((v>>8)&0xff) as u8; b[o+3]=(v&0xff) as u8; return o+4 } 49func mw_memeq(a: *u8, b: *u8, n: i64) -> i64 { var i: i64=0; while i<n { if a[i]!=b[i] { return 0 } i=i+1 } return 1 } 50func mw_xor20(a: *u8, b: *u8, out: *u8) -> i64 { var i: i64=0; while i<20 { out[i]=((a[i] as i64) ^ (b[i] as i64)) as u8; i=i+1 } return 0 } 51// MSE stream-sync: read from fd discarding up to maxskip bytes until the patlen-byte `pat` is matched+CONSUMED 52// (fd then sits right after it). This is how a peer's UNKNOWN-length pad (PadA before req1, PadB before the 53// encrypted VC) is skipped -> interop with mainstream clients that pad. 1=found, 0=not-within-maxskip/EOF. 54// Byte-at-a-time with a sliding window (a handshake is tiny; correctness over speed). Buffers hoisted. 55func mw_recv_scan(fd: i64, pat: *u8, patlen: i64, maxskip: i64) -> i64 { 56 let win: *u8 = sys_mmap(patlen + 8); let b: *u8 = sys_mmap(8) 57 var winlen: i64 = 0; var total: i64 = 0 58 while total <= maxskip + patlen + 4 { 59 if mw_read_n(fd, b, 1) != 1 { return 0 } 60 total = total + 1 61 if winlen < patlen { win[winlen] = b[0]; winlen = winlen + 1 } 62 else { var s: i64=0; while s < patlen-1 { win[s]=win[s+1]; s=s+1 } win[patlen-1]=b[0] } 63 if winlen == patlen { if mw_memeq(win, pat, patlen) == 1 { return 1 } } 64 } 65 return 0 66} 67// read+decrypt (advance the RC4 recv stream) n bytes, discarding them -- skips an encrypted PadC/PadD region. 68func mw_skip_dec(ctx: *MseCtx, fd: i64, n: i64) -> i64 { 69 if n <= 0 { return 0 } 70 let buf: *u8 = sys_mmap(n + 8) 71 if mw_read_n(fd, buf, n) != n { return 0-1 } 72 if ctx.mode == 1 { rc4_crypt(ctx.recvS as *u8, ctx.recvij as *i64, buf, n) } 73 return 0 74} 75// generate a random-length (0-255) random-content pad into out; returns the length. Breaks the fixed-size 76// handshake signature so DPI can't fingerprint us by message SIZES (MSE's traffic-analysis defense). 77func mw_pad_gen(out: *u8) -> i64 { 78 let rb: *u8 = sys_mmap(8); if ent_fill(rb, 1) != 0 { return 0 } 79 let plen: i64 = (rb[0] as i64) & 0xff 80 if plen > 0 { if ent_fill(out, plen) != 0 { return 0 } } 81 return plen 82} 83 84// allocate + open an RC4 stream (KSA + MSE 1024-byte discard). returns {S ptr as i64, ij ptr as i64} via out2. 85func mw_rc4_stream(key20: *u8, out_s: *i64, out_ij: *i64) -> i64 { 86 let S: *u8 = sys_mmap(256); let ij: *i64 = sys_mmap(16) as *i64 87 rc4_ksa(S, key20, 20); ij[0]=0; ij[1]=0; rc4_skip(S, ij, MSE_MAGIC_1024) 88 out_s[0] = S as i64; out_ij[0] = ij as i64 89 return 0 90} 91 92func mse_ctx_plain(ctx: *MseCtx) -> i64 { ctx.mode=0; ctx.sendS=0; ctx.sendij=0; ctx.recvS=0; ctx.recvij=0; return 0 } 93 94// fill ctx from the two derived keys. send_key drives our outbound; recv_key drives inbound. 95func mw_ctx_from_keys(ctx: *MseCtx, send_key: *u8, recv_key: *u8) -> i64 { 96 ctx.mode = 1 97 let sp: *i64 = sys_mmap(8) as *i64; let sij: *i64 = sys_mmap(8) as *i64 98 mw_rc4_stream(send_key, sp, sij); ctx.sendS = sp[0]; ctx.sendij = sij[0] 99 let rp: *i64 = sys_mmap(8) as *i64; let rij: *i64 = sys_mmap(8) as *i64 100 mw_rc4_stream(recv_key, rp, rij); ctx.recvS = rp[0]; ctx.recvij = rij[0] 101 return 0 102} 103 104// transparent write: plaintext ctx -> raw; RC4 ctx -> encrypt buf IN PLACE then send. Returns n or -1. 105func mse_write(ctx: *MseCtx, fd: i64, buf: *u8, n: i64) -> i64 { 106 if ctx.mode == 1 { rc4_crypt(ctx.sendS as *u8, ctx.sendij as *i64, buf, n) } 107 return mw_write_n(fd, buf, n) 108} 109// transparent read: read n bytes, then (RC4 ctx) decrypt in place. Returns n or -1. 110func mse_read(ctx: *MseCtx, fd: i64, buf: *u8, n: i64) -> i64 { 111 if mw_read_n(fd, buf, n) != n { return 0-1 } 112 if ctx.mode == 1 { rc4_crypt(ctx.recvS as *u8, ctx.recvij as *i64, buf, n) } 113 return n 114} 115 116// 0x13 (=19, the BT pstrlen) => plaintext handshake; anything else => an MSE handshake (Ya's first byte). 117func mse_classify_first_byte(b: i64) -> i64 { if b == 0x13 { return 0 } return 1 } 118 119// ===== INITIATOR (outbound): do the MSE handshake on a connected socket, fill ctx. xpriv = our DH private 120// (MSE_N limbs; live = CSPRNG, gate = fixed). skey = the torrent info_hash (20). Returns 1 ok, 0 fail. ===== 121func mse_connect(fd: i64, skey: *u8, xpriv: *i64, ctx: *MseCtx) -> i64 { 122 let p: *i64 = sys_mmap((MSE_N+2)*8) as *i64; md_load_p(p) 123 let ya: *i64 = sys_mmap((MSE_N+2)*8) as *i64; let yb: *i64 = sys_mmap((MSE_N+2)*8) as *i64; let s: *i64 = sys_mmap((MSE_N+2)*8) as *i64 124 mse_dh_public(xpriv, p, ya) 125 let yab: *u8 = sys_mmap(128); bi_to_bytes_be(yab, 96, ya, MSE_N) 126 if mw_write_n(fd, yab, 96) < 0 { return 0 } // send Ya 127 let pab: *u8 = sys_mmap(300); let pa: i64 = mw_pad_gen(pab); if pa>0 { if mw_write_n(fd, pab, pa) < 0 { return 0 } } // + random PadA 128 let ybb: *u8 = sys_mmap(128); if mw_read_n(fd, ybb, 96) != 96 { return 0 } // recv Yb 129 bi_from_bytes_be(yb, MSE_N, ybb, 96); mse_dh_shared(yb, xpriv, p, s) 130 let sbytes: *u8 = sys_mmap(128); bi_to_bytes_be(sbytes, 96, s, MSE_N) 131 let keyA: *u8 = sys_mmap(24); let keyB: *u8 = sys_mmap(24); mk_derive("keyA" as *u8, sbytes, skey, keyA); mk_derive("keyB" as *u8, sbytes, skey, keyB) 132 // stage3: req1(20) | (req2^req3)(20) | RC4_keyA( VC(8) | provide=2(4) | lenPadC(2) | PadC | lenIA=0(2) ) 133 let h1: *u8 = sys_mmap(24); mk_hash_pref("req1" as *u8, sbytes, 96, h1) 134 let h2: *u8 = sys_mmap(24); let h3: *u8 = sys_mmap(24); mk_hash_pref("req2" as *u8, skey, 20, h2); mk_hash_pref("req3" as *u8, sbytes, 96, h3) 135 let xr: *u8 = sys_mmap(24); mw_xor20(h2, h3, xr) 136 let sp: *i64 = sys_mmap(8) as *i64; let sij: *i64 = sys_mmap(8) as *i64; mw_rc4_stream(keyA, sp, sij) // A's outbound = keyA 137 let pcb: *u8 = sys_mmap(300); let pc: i64 = mw_pad_gen(pcb) // random PadC (encrypted below) 138 let p3: *u8 = sys_mmap(pc + 64); var o3: i64=0; var c: i64=0 139 while c<8 { p3[o3]=0 as u8; o3=o3+1; c=c+1 } // VC(8) 140 o3=mw_put4(p3,o3,2) // crypto_provide=2 (RC4) 141 p3[o3]=((pc>>8)&0xff) as u8; p3[o3+1]=(pc&0xff) as u8; o3=o3+2 // lenPadC 142 c=0; while c<pc { p3[o3]=pcb[c]; o3=o3+1; c=c+1 } // PadC 143 p3[o3]=0 as u8; p3[o3+1]=0 as u8; o3=o3+2 // lenIA=0 144 rc4_crypt(sp[0] as *u8, sij[0] as *i64, p3, o3) // encrypt VC..lenIA (o3 = 16 + pc) 145 let st3: *u8 = sys_mmap(o3 + 64); c=0; while c<20 { st3[c]=h1[c]; c=c+1 } c=0; while c<20 { st3[20+c]=xr[c]; c=c+1 } c=0; while c<o3 { st3[40+c]=p3[c]; c=c+1 } 146 if mw_write_n(fd, st3, 40+o3) < 0 { return 0 } 147 // stage4 from B: [PadB raw, unknown len] then RC4_keyB( VC(8) | select(4) | lenPadD(2) | PadD ). SYNC on 148 // ENCRYPT(VC) = keyB-keystream[0..8] (VC=zeros) -> scan the raw stream for it (skips the peer's PadB). 149 let rp: *i64 = sys_mmap(8) as *i64; let rij: *i64 = sys_mmap(8) as *i64; mw_rc4_stream(keyB, rp, rij) // A's inbound = keyB 150 ctx.mode = 1; ctx.sendS = sp[0]; ctx.sendij = sij[0]; ctx.recvS = rp[0]; ctx.recvij = rij[0] 151 let vcpat: *u8 = sys_mmap(16); var vz: i64=0; while vz<8 { vcpat[vz]=0 as u8; vz=vz+1 } rc4_crypt(rp[0] as *u8, rij[0] as *i64, vcpat, 8) // ENCRYPT(VC); recv now at keystream pos 8 152 if mw_recv_scan(fd, vcpat, 8, MSE_MAGIC_1200) != 1 { return 0 } // skip PadB + consume the encrypted VC 153 let sd: *u8 = sys_mmap(16); if mse_read(ctx, fd, sd, 6) != 6 { return 0 } // select(4)+lenPadD(2), decrypted (stream continues from pos 8) 154 let sel: i64 = ((sd[0] as i64)<<24)|((sd[1] as i64)<<16)|((sd[2] as i64)<<8)|(sd[3] as i64) 155 if sel != 2 { return 0 } 156 let pd: i64 = ((sd[4] as i64)<<8)|(sd[5] as i64) 157 if pd > 0 { if mw_skip_dec(ctx, fd, pd) < 0 { return 0 } } // skip the peer's encrypted PadD 158 return 1 159} 160 161// ===== RECEIVER, STAGED (multi-torrent seeder identifies the SKEY between DH and finish) ===== 162// Phase 1: read Ya (prefix already holds the first `prefix_len` bytes), DH, send Yb, then STREAM-SYNC on 163// HASH(req1,S) to skip the peer's unknown-length PadA, read the req2^req3 xor(20). Writes S(96) + xor(20). 164func mse_accept_dh(fd: i64, xpriv: *i64, prefix: *u8, prefix_len: i64, out_sbytes: *u8, out_xor: *u8) -> i64 { 165 let p: *i64 = sys_mmap((MSE_N+2)*8) as *i64; md_load_p(p) 166 let ya: *i64 = sys_mmap((MSE_N+2)*8) as *i64; let yb: *i64 = sys_mmap((MSE_N+2)*8) as *i64; let s: *i64 = sys_mmap((MSE_N+2)*8) as *i64 167 let yab: *u8 = sys_mmap(128); var c: i64=0; while c<prefix_len { yab[c]=prefix[c]; c=c+1 } 168 if mw_read_n(fd, (yab as i64 + prefix_len) as *u8, 96-prefix_len) != 96-prefix_len { return 0 } 169 bi_from_bytes_be(ya, MSE_N, yab, 96) 170 mse_dh_public(xpriv, p, yb); let ybb: *u8 = sys_mmap(128); bi_to_bytes_be(ybb, 96, yb, MSE_N); if mw_write_n(fd, ybb, 96) < 0 { return 0 } 171 let pbb: *u8 = sys_mmap(300); let pbn: i64 = mw_pad_gen(pbb); if pbn>0 { if mw_write_n(fd, pbb, pbn) < 0 { return 0 } } // + random PadB 172 mse_dh_shared(ya, xpriv, p, s); bi_to_bytes_be(out_sbytes, 96, s, MSE_N) 173 let e_req1: *u8 = sys_mmap(24); mk_hash_pref("req1" as *u8, out_sbytes, 96, e_req1) 174 if mw_recv_scan(fd, e_req1, 20, MSE_MAGIC_1200) != 1 { return 0 } // skip peer PadA + consume req1 175 if mw_read_n(fd, out_xor, 20) != 20 { return 0 } // HASH(req2,SKEY) ^ HASH(req3,S) 176 return 1 177} 178// SKEY-scan target: HASH(req2,SKEY) = xor ^ HASH(req3,S). A registered info_hash IS the torrent iff 179// HASH(req2, that_hash) == target. writes 20 bytes. 180func mse_skey_target(sbytes: *u8, xr: *u8, out_target: *u8) -> i64 { 181 let h3: *u8 = sys_mmap(24); mk_hash_pref("req3" as *u8, sbytes, 96, h3) 182 mw_xor20(xr, h3, out_target) 183 return 0 184} 185func mse_skey_matches(skey: *u8, target: *u8) -> i64 { let h2: *u8 = sys_mmap(24); mk_hash_pref("req2" as *u8, skey, 20, h2); return mw_memeq(h2, target, 20) } 186// Phase 2: with the identified skey + S, derive keyA/keyB, decrypt+verify VC/provide, skip the peer's encrypted 187// PadC + IA, send our stage4 (VC|select|lenPadD=0), fill ctx (recv=keyA, send=keyB). Returns 1 ok, 0 fail. 188func mse_accept_finish(fd: i64, skey: *u8, sbytes: *u8, ctx: *MseCtx) -> i64 { 189 let keyA: *u8 = sys_mmap(24); let keyB: *u8 = sys_mmap(24); mk_derive("keyA" as *u8, sbytes, skey, keyA); mk_derive("keyB" as *u8, sbytes, skey, keyB) 190 let rp: *i64 = sys_mmap(8) as *i64; let rij: *i64 = sys_mmap(8) as *i64; mw_rc4_stream(keyA, rp, rij) // inbound = keyA 191 ctx.mode = 1; ctx.recvS = rp[0]; ctx.recvij = rij[0] // recv set now so mse_read/mw_skip_dec work 192 let hdr: *u8 = sys_mmap(24); if mse_read(ctx, fd, hdr, 14) != 14 { return 0 } // VC(8)+provide(4)+lenPadC(2) 193 let VCZ: *u8 = sys_mmap(8); var c: i64=0; while c<8 { VCZ[c]=0 as u8; c=c+1 } 194 if mw_memeq(hdr, VCZ, 8) != 1 { return 0 } 195 let provide: i64 = ((hdr[8] as i64)<<24)|((hdr[9] as i64)<<16)|((hdr[10] as i64)<<8)|(hdr[11] as i64) 196 if (provide & 2) == 0 { return 0 } 197 let lenPadC: i64 = ((hdr[12] as i64)<<8)|(hdr[13] as i64); if lenPadC>0 { if mw_skip_dec(ctx, fd, lenPadC)<0 { return 0 } } 198 let ial: *u8 = sys_mmap(8); if mse_read(ctx, fd, ial, 2) != 2 { return 0 } // lenIA 199 let lenIA: i64 = ((ial[0] as i64)<<8)|(ial[1] as i64); if lenIA>0 { if mw_skip_dec(ctx, fd, lenIA)<0 { return 0 } } // ⚠IA discarded (our nodes send 0; a mainstream IA=BT-handshake would need routing to the caller) 200 let sp: *i64 = sys_mmap(8) as *i64; let sij: *i64 = sys_mmap(8) as *i64; mw_rc4_stream(keyB, sp, sij) // outbound = keyB 201 ctx.sendS = sp[0]; ctx.sendij = sij[0] 202 // stage4: VC(8) | select=2(4) | lenPadD(2) | PadD (encrypted via ctx keyB) 203 let pdb: *u8 = sys_mmap(300); let pdn: i64 = mw_pad_gen(pdb) 204 let p5: *u8 = sys_mmap(pdn + 64); var o5: i64=0; c=0; while c<8 { p5[o5]=0 as u8; o5=o5+1; c=c+1 } o5=mw_put4(p5,o5,2) 205 p5[o5]=((pdn>>8)&0xff) as u8; p5[o5+1]=(pdn&0xff) as u8; o5=o5+2 // lenPadD 206 c=0; while c<pdn { p5[o5]=pdb[c]; o5=o5+1; c=c+1 } // PadD 207 if mse_write(ctx, fd, p5, o5) < 0 { return 0 } 208 return 1 209} 210// One-shot receiver for a SINGLE known SKEY (serve1/gate). first `prefix_len` bytes of Ya already read. 211func mse_accept_pfx(fd: i64, skey: *u8, xpriv: *i64, prefix: *u8, prefix_len: i64, ctx: *MseCtx) -> i64 { 212 let sbytes: *u8 = sys_mmap(128); let xr: *u8 = sys_mmap(24) 213 if mse_accept_dh(fd, xpriv, prefix, prefix_len, sbytes, xr) != 1 { return 0 } 214 let target: *u8 = sys_mmap(24); mse_skey_target(sbytes, xr, target) 215 if mse_skey_matches(skey, target) != 1 { return 0 } // the peer wants THIS torrent 216 return mse_accept_finish(fd, skey, sbytes, ctx) 217} 218func mse_accept(fd: i64, skey: *u8, xpriv: *i64, ctx: *MseCtx) -> i64 { return mse_accept_pfx(fd, skey, xpriv, 0 as *u8, 0, ctx) }