code wiki / _hdl_build / nx_tor_relay.nx

nx_tor_relay.nx source

↩ module page · 162 lines · 7585 B

1// nx_tor_relay.nx -- Tor circuit-handshake + RELAY-cell payloads (tor-spec sec.5.1/sec.5.1.2/sec.6.1). 2// Phase 3 onion layer: CREATE2/CREATED2 (wrap the Phase-1 ntor onion skin), the RELAY cell format with 3// its running digest + recognized field, per-hop AES-128-CTR onion (de)cryption, and EXTEND2/EXTENDED2 4// for multi-hop circuit build. STRUCTURAL (no live relay); composes nx_tor_cell + nx_tor_aes + nx_sha1. 5// 6// HONEST SCOPE: this is the cell/onion FRAMING + the recognized/digest mechanism, gated byte-exact and by 7// round-trip/self-consistency. The running digest here is SHA-1(Df_seed || payload) per cell (equivalent to 8// Tor's stateful running digest for the first cell); byte-exact interop vs a live relay is a later phase. 9// anon_is_anonymizing() stays 0. license_tier: ORIGINAL 10import "nx_syscalls.nx" 11import "nx_tor_cell.nx" // tc_put/get u16/u32, TCELL_* commands, TCELL_PAYLOAD_LEN=509 12import "nx_tor_aes.nx" // tor_aes128_ctr (relay cipher) 13import "nx_sha1.nx" // sha1(data,n,out) -- running digest 14 15// ---- handshake types (tor-spec sec.5.1.2) ---- 16const TOR_HTYPE_NTOR: i64 = 2 17// ---- relay commands (tor-spec sec.6.1) ---- 18const TOR_RELAY_BEGIN: i64 = 1 19const TOR_RELAY_DATA: i64 = 2 20const TOR_RELAY_END: i64 = 3 21const TOR_RELAY_EXTEND2: i64 = 14 22const TOR_RELAY_EXTENDED2: i64 = 15 23// ---- link specifier types (tor-spec sec.5.1.2) ---- 24const TOR_LS_IPV4: i64 = 0 25const TOR_LS_IPV6: i64 = 1 26const TOR_LS_LEGACY_ID: i64 = 2 27const TOR_LS_ED25519: i64 = 3 28 29const TOR_RELAY_HDR: i64 = 11 // relay_cmd(1) recognized(2) streamid(2) digest(4) length(2) 30 31const TOR_E_SHORT: i64 = 0 - 1 32const TOR_E_FORMAT: i64 = 0 - 2 33 34// ============ CREATE2 / CREATED2 (fixed-cell payloads) ============ 35// CREATE2 payload: HTYPE[2] | HLEN[2] | HDATA[HLEN]. For ntor, HDATA = onion skin ID|B|X (84 bytes). 36func tor_build_create2(htype: i64, hdata: *u8, hlen: i64, out: *u8) -> i64 { 37 var o: i64 = 0 38 o = tc_put_u16(out, o, htype) 39 o = tc_put_u16(out, o, hlen) 40 var i: i64 = 0 41 while i < hlen { out[o+i] = hdata[i]; i = i + 1 } 42 return o + hlen 43} 44func tor_parse_create2(pl: *u8, pllen: i64, out_htype: *i64, out_hlen: *i64, out_hoff: *i64) -> i64 { 45 if pllen < 4 { return TOR_E_SHORT } 46 out_htype[0] = tc_get_u16(pl, 0) 47 let hl: i64 = tc_get_u16(pl, 2) 48 if pllen < 4 + hl { return TOR_E_SHORT } 49 out_hlen[0] = hl; out_hoff[0] = 4 50 return 0 51} 52// CREATED2 (and EXTENDED2) payload: HLEN[2] | HDATA[HLEN]. For ntor, HDATA = Y|AUTH (64 bytes). 53func tor_build_created2(hdata: *u8, hlen: i64, out: *u8) -> i64 { 54 var o: i64 = tc_put_u16(out, 0, hlen) 55 var i: i64 = 0 56 while i < hlen { out[o+i] = hdata[i]; i = i + 1 } 57 return o + hlen 58} 59func tor_parse_created2(pl: *u8, pllen: i64, out_hlen: *i64, out_hoff: *i64) -> i64 { 60 if pllen < 2 { return TOR_E_SHORT } 61 let hl: i64 = tc_get_u16(pl, 0) 62 if pllen < 2 + hl { return TOR_E_SHORT } 63 out_hlen[0] = hl; out_hoff[0] = 2 64 return 0 65} 66 67// ============ RELAY cell payload (509 bytes) + running digest ============ 68// Running digest (structural): first 4 bytes of SHA-1(Df_seed[20] || payload[509] with Digest field zeroed). 69func tor_running_digest4(df_seed: *u8, payload509: *u8, out4: *u8) -> i64 { 70 let buf: *u8 = sys_mmap(20 + 512) 71 var i: i64 = 0 72 while i < 20 { buf[i] = df_seed[i]; i = i + 1 } 73 i = 0 74 while i < TCELL_PAYLOAD_LEN { buf[20 + i] = payload509[i]; i = i + 1 } 75 let dig: *u8 = sys_mmap(20) 76 sha1(buf, 20 + TCELL_PAYLOAD_LEN, dig) 77 out4[0]=dig[0]; out4[1]=dig[1]; out4[2]=dig[2]; out4[3]=dig[3] 78 return 0 79} 80// Build a RELAY payload: recognized=0, digest computed over the (digest-zeroed) payload, then embedded. 81func tor_relay_build(relay_cmd: i64, streamid: i64, data: *u8, dlen: i64, df_seed: *u8, out509: *u8) -> i64 { 82 if dlen > (TCELL_PAYLOAD_LEN - TOR_RELAY_HDR) { return TOR_E_FORMAT } 83 var i: i64 = 0 84 while i < TCELL_PAYLOAD_LEN { out509[i] = 0 as u8; i = i + 1 } 85 out509[0] = relay_cmd as u8 86 tc_put_u16(out509, 1, 0) // Recognized = 0x0000 87 tc_put_u16(out509, 3, streamid) // StreamID 88 tc_put_u32(out509, 5, 0) // Digest placeholder = 0 89 tc_put_u16(out509, 9, dlen) // Length 90 i = 0 91 while i < dlen { out509[TOR_RELAY_HDR + i] = data[i]; i = i + 1 } 92 let d4: *u8 = sys_mmap(4); tor_running_digest4(df_seed, out509, d4) 93 out509[5]=d4[0]; out509[6]=d4[1]; out509[7]=d4[2]; out509[8]=d4[3] 94 return 0 95} 96// Recognized check: Recognized==0 AND the digest field matches the recomputed running digest -> 1 (this hop 97// is the target). Non-destructive (restores the digest field). Returns 0 if not recognized (forward it). 98func tor_relay_recognized(pl509: *u8, df_seed: *u8) -> i64 { 99 if tc_get_u16(pl509, 1) != 0 { return 0 } 100 let s: *u8 = sys_mmap(4); s[0]=pl509[5];s[1]=pl509[6];s[2]=pl509[7];s[3]=pl509[8] 101 pl509[5]=0 as u8; pl509[6]=0 as u8; pl509[7]=0 as u8; pl509[8]=0 as u8 102 let d4: *u8 = sys_mmap(4); tor_running_digest4(df_seed, pl509, d4) 103 pl509[5]=s[0]; pl509[6]=s[1]; pl509[7]=s[2]; pl509[8]=s[3] 104 var ok: i64 = 1 105 if s[0]!=d4[0] { ok=0 } if s[1]!=d4[1] { ok=0 } if s[2]!=d4[2] { ok=0 } if s[3]!=d4[3] { ok=0 } 106 return ok 107} 108// One onion layer: AES-128-CTR over the 509-byte payload with this hop's key Kf/Kb. IV = zeros (first cell of 109// the circuit). Symmetric: encrypt at the OP, decrypt at the hop (same call). 110func tor_onion_crypt(kf16: *u8, in509: *u8, out509: *u8) -> i64 { 111 let iv: *u8 = sys_mmap(16); var i: i64=0; while i<16 { iv[i]=0 as u8; i=i+1 } 112 return tor_aes128_ctr(kf16, iv, in509, TCELL_PAYLOAD_LEN, out509) 113} 114 115// ============ EXTEND2 / EXTENDED2 (relay-cell payloads for circuit extension) ============ 116// Link specifier: LSTYPE[1] LSLEN[1] LSPEC[LSLEN]. IPv4: type 0, len 6 = 4-byte IP + 2-byte port. 117func tor_ls_ipv4(ip4: *u8, port: i64, out: *u8) -> i64 { 118 out[0]=TOR_LS_IPV4 as u8; out[1]=6 as u8 119 out[2]=ip4[0]; out[3]=ip4[1]; out[4]=ip4[2]; out[5]=ip4[3] 120 tc_put_u16(out, 6, port) 121 return 8 122} 123// Legacy (RSA) identity link specifier: type 2, len 20. 124func tor_ls_legacy_id(id20: *u8, out: *u8) -> i64 { 125 out[0]=TOR_LS_LEGACY_ID as u8; out[1]=20 as u8 126 var i: i64=0; while i<20 { out[2+i]=id20[i]; i=i+1 } 127 return 22 128} 129// EXTEND2 relay data: NSPEC[1] | specs | HTYPE[2] | HLEN[2] | HDATA[HLEN]. `specs` is the concatenation of 130// `nspec` already-serialized link specifiers. HDATA = the ntor onion skin for the NEXT hop. 131func tor_build_extend2(nspec: i64, specs: *u8, specs_len: i64, htype: i64, hdata: *u8, hlen: i64, out: *u8) -> i64 { 132 out[0] = nspec as u8 133 var o: i64 = 1 134 var i: i64 = 0 135 while i < specs_len { out[o+i] = specs[i]; i = i + 1 } 136 o = o + specs_len 137 o = tc_put_u16(out, o, htype) 138 o = tc_put_u16(out, o, hlen) 139 i = 0 140 while i < hlen { out[o+i] = hdata[i]; i = i + 1 } 141 return o + hlen 142} 143// Parse EXTEND2 header: nspec + (skip specs) -> htype/hlen/hdata offset. 144func tor_parse_extend2(pl: *u8, pllen: i64, out_nspec: *i64, out_htype: *i64, out_hlen: *i64, out_hoff: *i64) -> i64 { 145 if pllen < 1 { return TOR_E_SHORT } 146 let nspec: i64 = pl[0] as i64 147 var o: i64 = 1 148 var i: i64 = 0 149 while i < nspec { 150 if o + 2 > pllen { return TOR_E_SHORT } 151 let lslen: i64 = pl[o+1] as i64 152 o = o + 2 + lslen 153 if o > pllen { return TOR_E_SHORT } 154 i = i + 1 155 } 156 if o + 4 > pllen { return TOR_E_SHORT } 157 out_nspec[0] = nspec 158 out_htype[0] = tc_get_u16(pl, o) 159 out_hlen[0] = tc_get_u16(pl, o + 2) 160 out_hoff[0] = o + 4 161 return 0 162}