code wiki / _hdl_build / nx_tor_ntor.nx

nx_tor_ntor.nx source

↩ module page · 156 lines · 9234 B

1// nx_tor_ntor.nx -- SOVEREIGN Tor ntor / CREATE2 circuit handshake (tor-spec.txt §5.1.4 + KDF §5.2.2). 2// Phase 1 of the anonymizing-transport arc: the foundational Tor circuit key-agreement. It COMPOSES the 3// ecosystem's already-gated primitives -- x25519 (RFC 7748), hmac_sha256 (RFC 2104/4231), hkdf_expand 4// (RFC 5869) -- into Tor's exact ntor construction. NO new crypto is rolled here. 5// 6// tor-spec §5.1.4 (verbatim mapping): 7// PROTOID = "ntor-curve25519-sha256-1" 8// t_mac = PROTOID | ":mac" t_key = PROTOID | ":key_extract" 9// t_verify = PROTOID | ":verify" m_expand = PROTOID | ":key_expand" 10// H(msg, tweak) = HMAC-SHA256(key=tweak, msg=msg) 11// client onion skin = NODEID(ID,20) | KEYID(B,32) | CLIENT_PK(X,32) [CREATE2 HDATA, htype=ntor] 12// server: secret_input = EXP(X,y) | EXP(X,b) | ID | B | X | Y | PROTOID 13// client: secret_input = EXP(Y,x) | EXP(B,x) | ID | B | X | Y | PROTOID (equal by DH commutativity) 14// KEY_SEED = H(secret_input, t_key) verify = H(secret_input, t_verify) 15// auth_input = verify | ID | B | Y | X | PROTOID | "Server" 16// AUTH = H(auth_input, t_mac) server reply = SERVER_PK(Y,32) | AUTH(32) [CREATED2 HDATA] 17// key material = HKDF-Expand(PRK=KEY_SEED, info=m_expand) -> Df|Db|Kf|Kb|KH (20|20|16|16|20 = 92) 18// 19// EXP(pk, sk) in the spec = X25519(sk, pk) here (scalar sk on point pk). 20// 21// HONEST SCOPE: this is the ntor COMPOSITION, spec-faithful. It does NOT by itself prove byte-exact interop 22// with the live Tor network -- that requires (1) Tor's own published ntor test vector (not present in-tree; 23// zero external calls this run) AND (2) the x25519 base-point fix (nx_x25519_test.nx documents a scalar- 24// pattern bug in base-point multiplication that ntor's X=x*G / Y=y*G / B=b*G derivations depend on). Until 25// a full circuit works, anon_is_anonymizing() stays 0. Sovereign: nx_syscalls + x25519 + hmac + hkdf. 26// license_tier: ORIGINAL 27import "nx_syscalls.nx" 28import "nx_x25519_ephemeral.nx" // x25519_keypair_public (pub=priv*G) + x25519_shared_secret (X25519 DH) 29import "nx_hmac.nx" // hmac_sha256(key,klen,msg,mlen,out32) -- RFC 2104/4231 30import "nx_hkdf.nx" // hkdf_expand(prk,info,ilen,L,out) -- RFC 5869 31const NTOR_MAGIC_2104: i64 = 2104 32const NTOR_MAGIC_4231: i64 = 4231 33const NTOR_MAGIC_5869: i64 = 5869 34 35const NTOR_ID_LEN: i64 = 20 // router identity digest length 36const NTOR_PUB: i64 = 32 // curve25519 public/secret length 37const NTOR_H: i64 = 32 // SHA-256 / HMAC output length 38const NTOR_KEYS_LEN: i64 = 92 // Df(20)|Db(20)|Kf(16)|Kb(16)|KH(20) 39const NTOR_SKIN_LEN: i64 = 84 // ID(20)|B(32)|X(32) 40const NTOR_REPLY_LEN:i64 = 64 // Y(32)|AUTH(32) 41 42const NTOR_OK: i64 = 0 43const NTOR_E_AUTH: i64 = 0 - 1 // client: recomputed AUTH != received AUTH (reject the relay) 44const NTOR_E_BADPOINT:i64 = 0 - 2 // degenerate DH result (all-zero shared secret) -> reject 45 46// NUL-terminated string length (protocol tweak strings). 47func nt_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){ n=n+1 } return n } 48// append src[0..n) into dst at off; return new offset. 49func nt_cat(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { var i: i64=0; while i<n { dst[off+i]=src[i]; i=i+1 } return off+n } 50// constant-time equality of a[0..n) and b[0..n): 1 if equal, 0 otherwise. No early exit (HM2 hygiene). 51func nt_ct_eq(a: *u8, b: *u8, n: i64) -> i64 { var d: i64=0; var i: i64=0; while i<n { d = d | ((a[i] as i64) ^ (b[i] as i64)); i=i+1 } if d==0 { return 1 } return 0 } 52// 1 if buf[0..n) is all-zero (a degenerate/low-order DH output to reject). 53func nt_is_zero(buf: *u8, n: i64) -> i64 { var i: i64=0; while i<n { if buf[i]!=(0 as u8) { return 0 } i=i+1 } return 1 } 54 55// secret_input = xy | xb | ID | B | X | Y | PROTOID -> buf ; returns length (204). 56func ntor_build_secret_input(xy: *u8, xb: *u8, id: *u8, bpub: *u8, xpub: *u8, ypub: *u8, buf: *u8) -> i64 { 57 let protoid: *u8 = "ntor-curve25519-sha256-1" as *u8 58 var o: i64 = 0 59 o = nt_cat(buf, o, xy, NTOR_PUB) 60 o = nt_cat(buf, o, xb, NTOR_PUB) 61 o = nt_cat(buf, o, id, NTOR_ID_LEN) 62 o = nt_cat(buf, o, bpub, NTOR_PUB) 63 o = nt_cat(buf, o, xpub, NTOR_PUB) 64 o = nt_cat(buf, o, ypub, NTOR_PUB) 65 o = nt_cat(buf, o, protoid, nt_slen(protoid)) 66 return o 67} 68 69// auth_input = verify | ID | B | Y | X | PROTOID | "Server" -> buf ; returns length (178). 70func ntor_build_auth_input(verify: *u8, id: *u8, bpub: *u8, ypub: *u8, xpub: *u8, buf: *u8) -> i64 { 71 let protoid: *u8 = "ntor-curve25519-sha256-1" as *u8 72 let server: *u8 = "Server" as *u8 73 var o: i64 = 0 74 o = nt_cat(buf, o, verify, NTOR_H) 75 o = nt_cat(buf, o, id, NTOR_ID_LEN) 76 o = nt_cat(buf, o, bpub, NTOR_PUB) 77 o = nt_cat(buf, o, ypub, NTOR_PUB) 78 o = nt_cat(buf, o, xpub, NTOR_PUB) 79 o = nt_cat(buf, o, protoid, nt_slen(protoid)) 80 o = nt_cat(buf, o, server, nt_slen(server)) 81 return o 82} 83 84// From secret_input -> KEY_SEED = H(si, t_key) and verify = H(si, t_verify). Shared by both parties. 85func ntor_kdf_seed_verify(si: *u8, si_len: i64, key_seed_out: *u8, verify_out: *u8) -> i64 { 86 let t_key: *u8 = "ntor-curve25519-sha256-1:key_extract" as *u8 87 let t_verify: *u8 = "ntor-curve25519-sha256-1:verify" as *u8 88 hmac_sha256(t_key, nt_slen(t_key), si, si_len, key_seed_out) 89 hmac_sha256(t_verify, nt_slen(t_verify), si, si_len, verify_out) 90 return 0 91} 92 93// AUTH = H(auth_input, t_mac). 94func ntor_compute_auth(verify: *u8, id: *u8, bpub: *u8, ypub: *u8, xpub: *u8, auth_out: *u8) -> i64 { 95 let t_mac: *u8 = "ntor-curve25519-sha256-1:mac" as *u8 96 let abuf: *u8 = sys_mmap(256) 97 let an: i64 = ntor_build_auth_input(verify, id, bpub, ypub, xpub, abuf) 98 hmac_sha256(t_mac, nt_slen(t_mac), abuf, an, auth_out) 99 return 0 100} 101 102// Expand KEY_SEED -> NTOR_KEYS_LEN bytes of circuit key material (HKDF-Expand, info=m_expand). 103func ntor_expand_keys(key_seed: *u8, keys_out: *u8) -> i64 { 104 let m_expand: *u8 = "ntor-curve25519-sha256-1:key_expand" as *u8 105 return hkdf_expand(key_seed, m_expand, nt_slen(m_expand), NTOR_KEYS_LEN, keys_out) 106} 107 108// ---- CLIENT: build the onion skin (CREATE2 HDATA). x = client ephemeral secret (caller-supplied for 109// determinism, else from x25519_keypair_private). Writes X (client eph pub) and onion_skin = ID|B|X. ---- 110func ntor_client_init(id: *u8, bpub: *u8, x_priv: *u8, x_pub_out: *u8, onion_skin_out: *u8) -> i64 { 111 x25519_keypair_public(x_priv, x_pub_out) // X = x * G 112 var o: i64 = 0 113 o = nt_cat(onion_skin_out, o, id, NTOR_ID_LEN) 114 o = nt_cat(onion_skin_out, o, bpub, NTOR_PUB) 115 o = nt_cat(onion_skin_out, o, x_pub_out, NTOR_PUB) 116 return o // 84 117} 118 119// ---- SERVER (relay): given the client's ID/B/X and its own onion key (b,B) + ephemeral secret y, 120// produce Y|AUTH (CREATED2 HDATA) and the derived key material. ---- 121func ntor_server_respond(id: *u8, bpub: *u8, b_priv: *u8, x_pub: *u8, y_priv: *u8, 122 y_pub_out: *u8, auth_out: *u8, keys_out: *u8) -> i64 { 123 x25519_keypair_public(y_priv, y_pub_out) // Y = y * G 124 let xy: *u8 = sys_mmap(32); x25519_shared_secret(y_priv, x_pub, xy) // EXP(X,y)=X25519(y,X) 125 let xb: *u8 = sys_mmap(32); x25519_shared_secret(b_priv, x_pub, xb) // EXP(X,b)=X25519(b,X) 126 if nt_is_zero(xy, 32)==1 { return NTOR_E_BADPOINT } 127 if nt_is_zero(xb, 32)==1 { return NTOR_E_BADPOINT } 128 let si: *u8 = sys_mmap(256) 129 let si_len: i64 = ntor_build_secret_input(xy, xb, id, bpub, x_pub, y_pub_out, si) 130 let key_seed: *u8 = sys_mmap(32); let verify: *u8 = sys_mmap(32) 131 ntor_kdf_seed_verify(si, si_len, key_seed, verify) 132 ntor_compute_auth(verify, id, bpub, y_pub_out, x_pub, auth_out) 133 ntor_expand_keys(key_seed, keys_out) 134 return NTOR_OK 135} 136 137// ---- CLIENT finish: on receiving Y|AUTH, recompute + verify AUTH (const-time), derive the SAME keys. 138// x_priv/x_pub are the client's own ephemeral pair from ntor_client_init. Returns NTOR_OK iff AUTH verifies 139// (fail-closed on a bad point or an AUTH mismatch -> the relay is rejected). Also writes KEY_SEED for the CLI. ---- 140func ntor_client_finish(id: *u8, bpub: *u8, x_priv: *u8, x_pub: *u8, y_pub: *u8, auth_recv: *u8, 141 key_seed_out: *u8, keys_out: *u8) -> i64 { 142 if nt_is_zero(y_pub, 32)==1 { return NTOR_E_BADPOINT } // reject identity/degenerate Y 143 let xy: *u8 = sys_mmap(32); x25519_shared_secret(x_priv, y_pub, xy) // EXP(Y,x)=X25519(x,Y) 144 let xb: *u8 = sys_mmap(32); x25519_shared_secret(x_priv, bpub, xb) // EXP(B,x)=X25519(x,B) 145 if nt_is_zero(xy, 32)==1 { return NTOR_E_BADPOINT } 146 if nt_is_zero(xb, 32)==1 { return NTOR_E_BADPOINT } 147 let si: *u8 = sys_mmap(256) 148 let si_len: i64 = ntor_build_secret_input(xy, xb, id, bpub, x_pub, y_pub, si) 149 let verify: *u8 = sys_mmap(32) 150 ntor_kdf_seed_verify(si, si_len, key_seed_out, verify) 151 let auth_calc: *u8 = sys_mmap(32) 152 ntor_compute_auth(verify, id, bpub, y_pub, x_pub, auth_calc) 153 if nt_ct_eq(auth_calc, auth_recv, NTOR_H) != 1 { return NTOR_E_AUTH } // AUTH mismatch -> reject relay 154 ntor_expand_keys(key_seed_out, keys_out) 155 return NTOR_OK 156}