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}