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) }