Compare commits
2 Commits
b1189bd237
...
a9f3348a01
Author | SHA1 | Date | |
---|---|---|---|
a9f3348a01 | |||
542e31cd96 |
3625
doc/dir-spec.txt
Normal file
3625
doc/dir-spec.txt
Normal file
File diff suppressed because it is too large
Load Diff
35335
doc/example-descriptors/consensus.txt
Normal file
35335
doc/example-descriptors/consensus.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,59 @@
|
||||
rendezvous-service-descriptor 4frkg4jpbmbjhlsrbyjpbmu3slphz7ts
|
||||
version 2
|
||||
permanent-key
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIGJAoGBALdj5MFZtlrjI54ousrBzA3fSyfn0NC32OBhl61BLgO67vPBiIiFFzo9
|
||||
YIqa4h3jZQrxdI3MeK4xTLQ6HhnQXvcM+ZR57o5zTR7fpqra89i75rwUjW5wqc9O
|
||||
3roxzt1UWbJBtbzOT9FYxGSIczsYdG6MQRg9BK/2v391Oz9NbDb1AgMBAAE=
|
||||
-----END RSA PUBLIC KEY-----
|
||||
secret-id-part 7mnzxwe2gg2eoxp2hek4dzkv2chupfxy
|
||||
publication-time 2016-07-20 08:00:00
|
||||
protocol-versions 2,3
|
||||
introduction-points
|
||||
-----BEGIN MESSAGE-----
|
||||
aW50cm9kdWN0aW9uLXBvaW50IHBkY3B6eWVmZHNtMmp5Y3ZtcGRwa2lnZ3I0dWwz
|
||||
ZWxvCmlwLWFkZHJlc3MgMjEzLjEzNi43MS4yMQpvbmlvbi1wb3J0IDkwMDEKb25p
|
||||
b24ta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFM
|
||||
VXJuT0tqTjZIR0tPa3IwUFFhSWhHNDJQRkd2S0ZpVmJ3QlEwTjhEVk1LLzRWNlZ1
|
||||
T3JEZkhOCjFXemVzTUpVRWx3OWhPWko4MmdjcVZXYUFvYm9sODdJMDgwVU9SNC9a
|
||||
ZTV5cFhtaS9Jd2tFN3RMN08wTW9vaXMKTmliV2Y1Z00vUlh5bzh0czdnaDUrV2wr
|
||||
UnZPcjBoc2pTVHBtdUgxeHlJYVJhem9qWG4zSEFnTUJBQUU9Ci0tLS0tRU5EIFJT
|
||||
QSBQVUJMSUMgS0VZLS0tLS0Kc2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVC
|
||||
TElDIEtFWS0tLS0tCk1JR0pBb0dCQU0wQjM0eE5rWVdxcDdOeE9jMzJSNkh3TXM3
|
||||
T2phODYyWStFZmU5bVhEdUpmL2FGOWFIWVN3NW0KeHB6NVU4bVAxa0d1OU5ZK2FX
|
||||
K1E0YU42YTJNam5LMDJFeWcyYXphVkt6MFp2SVdkQWdCRVdNMUx6TlQ5c3pMTgpn
|
||||
SzR2VEZSOGVOTXY4cUF5aEp2S0liTW5TMFdmR3QzZkphd3RwUmFoVDdwRnd5ZnR1
|
||||
bzBwQWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBLRVktLS0tLQppbnRyb2R1
|
||||
Y3Rpb24tcG9pbnQgY3ZnaHZkM3I0ZjNxenpwaXd0aHk0bm9kaW11M2FuZ2IKaXAt
|
||||
YWRkcmVzcyAxNjMuMTcyLjIxNC43NQpvbmlvbi1wb3J0IDkwMDEKb25pb24ta2V5
|
||||
Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFMWXRlRmt3
|
||||
K1BrcWRPZWprUE1qdjk4djYzS3YzU2xzTmxzS2ZaUkhTSy8ybWZTRVVPMmdxSE43
|
||||
CjRKLzBveGhsV1NTQVBQTis5YmRPejFlbWc2SUVMMk9WZEtKZmdvaWJsSE1oditY
|
||||
eDErak1YS0YwMTZKNjB6azMKV0xBNFMwL0hqZjk0YUhWUmsza1JLNlNTVmlER05K
|
||||
MWZ0ZUVQaTV4cDlDRFFaeUZaMStqVkFnTUJBQUU9Ci0tLS0tRU5EIFJTQSBQVUJM
|
||||
SUMgS0VZLS0tLS0Kc2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVCTElDIEtF
|
||||
WS0tLS0tCk1JR0pBb0dCQUxBQVNWK1FxRlhGdDd5a3JtdlRMeG1Kai82QkNWRWwx
|
||||
eVZ3MFJvcGJWSUxRNWNmaGFPQWVENTcKS0NDbGVXZHR0aE0yUlZKaytvTG9jK0RN
|
||||
VXhvZUVRbUo5cktBbi9ic2Rxa01EU3U1QTJjcFV3ajhXbnpieHdKcApKNzlqTGJZ
|
||||
ZTNIV3NDRXcySkxDWGgvQmRObTFOOVFsN3FXNDJsbnJFQzVMemRKaUYwbklKQWdN
|
||||
QkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBLRVktLS0tLQppbnRyb2R1Y3Rpb24t
|
||||
cG9pbnQgeHZnZ2k1aWljeXh2dHMyZTR0cDRkcXZzeGN1dHE3Z2sKaXAtYWRkcmVz
|
||||
cyAxNjMuMTcyLjM1LjExNQpvbmlvbi1wb3J0IDkwMDEKb25pb24ta2V5Ci0tLS0t
|
||||
QkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFMUmVBUFpmK0sxeWQ3
|
||||
Q0RxQ2dMV2RzdjdqdzhBZXpzYXRxbnJQN3RvakY0dXcyZkxITEpFSk12ClZwZ0Zn
|
||||
SEhSQzNic3owS3ZUcXZIdEVXMFhHWkpXZmhWU1czSkFHaHovWXF1NTNTRUM5WkdN
|
||||
c2xIZnIyeGxSSXIKN2RCRWlDQkFHOGRQa1VMZkM2RWN5SFB6QXBIMWk5d3V6Y2x0
|
||||
dUowekZRbUpFbEIvalNOUEFnTUJBQUU9Ci0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZ
|
||||
LS0tLS0Kc2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0t
|
||||
Ck1JR0pBb0dCQUxOTVc1SG9sZmFTM3NqWk5od3ZUd2pUOStPc1kyYU1XWjVieXZy
|
||||
Q1pzMG5xQ21FaXZFTlNBbVoKZjlvbFQ0em14TUhZNEtJYVVGcW5ZdzV5NTlvSGo4
|
||||
eGx1bDRLMURrTU15dWRRaTNhcHBuaWtBWGZhUkptRXNKRgozbk9Neld6UmN4MG1r
|
||||
MFhRYTh4OWF0R2E1UnMvV2l2aVlYUEVTYklKMGlOZmFScWMyS0hYQWdNQkFBRT0K
|
||||
LS0tLS1FTkQgUlNBIFBVQkxJQyBLRVktLS0tLQoK
|
||||
-----END MESSAGE-----
|
||||
signature
|
||||
-----BEGIN SIGNATURE-----
|
||||
aDxMNhtGWBV7lMJPTfamvHNXVFD7wFTTRz68JEhmmfLVuuZ+erUfjDHsUa3bF3zR
|
||||
tNvPBYbQdWj2vl8WzdzwlchXS56iqgQ672y19/hx4THiuPyjGUj/dtc7L4qINl1V
|
||||
cgBWo0Lezfnyc+5laQboMsEsjQa4SqJyQNamjmS2soo=
|
||||
-----END SIGNATURE-----
|
45
doc/example-descriptors/introduction-point.txt
Normal file
45
doc/example-descriptors/introduction-point.txt
Normal file
@ -0,0 +1,45 @@
|
||||
introduction-point pdcpzyefdsm2jycvmpdpkiggr4ul3elo
|
||||
ip-address 213.136.71.21
|
||||
onion-port 9001
|
||||
onion-key
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIGJAoGBALUrnOKjN6HGKOkr0PQaIhG42PFGvKFiVbwBQ0N8DVMK/4V6VuOrDfHN
|
||||
1WzesMJUElw9hOZJ82gcqVWaAobol87I080UOR4/Ze5ypXmi/IwkE7tL7O0Moois
|
||||
NibWf5gM/RXyo8ts7gh5+Wl+RvOr0hsjSTpmuH1xyIaRazojXn3HAgMBAAE=
|
||||
-----END RSA PUBLIC KEY-----
|
||||
service-key
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIGJAoGBAM0B34xNkYWqp7NxOc32R6HwMs7Oja862Y+Efe9mXDuJf/aF9aHYSw5m
|
||||
xpz5U8mP1kGu9NY+aW+Q4aN6a2MjnK02Eyg2azaVKz0ZvIWdAgBEWM1LzNT9szLN
|
||||
gK4vTFR8eNMv8qAyhJvKIbMnS0WfGt3fJawtpRahT7pFwyftuo0pAgMBAAE=
|
||||
-----END RSA PUBLIC KEY-----
|
||||
introduction-point cvghvd3r4f3qzzpiwthy4nodimu3angb
|
||||
ip-address 163.172.214.75
|
||||
onion-port 9001
|
||||
onion-key
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIGJAoGBALYteFkw+PkqdOejkPMjv98v63Kv3SlsNlsKfZRHSK/2mfSEUO2gqHN7
|
||||
4J/0oxhlWSSAPPN+9bdOz1emg6IEL2OVdKJfgoiblHMhv+Xx1+jMXKF016J60zk3
|
||||
WLA4S0/Hjf94aHVRk3kRK6SSViDGNJ1fteEPi5xp9CDQZyFZ1+jVAgMBAAE=
|
||||
-----END RSA PUBLIC KEY-----
|
||||
service-key
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIGJAoGBALAASV+QqFXFt7ykrmvTLxmJj/6BCVEl1yVw0RopbVILQ5cfhaOAeD57
|
||||
KCCleWdtthM2RVJk+oLoc+DMUxoeEQmJ9rKAn/bsdqkMDSu5A2cpUwj8WnzbxwJp
|
||||
J79jLbYe3HWsCEw2JLCXh/BdNm1N9Ql7qW42lnrEC5LzdJiF0nIJAgMBAAE=
|
||||
-----END RSA PUBLIC KEY-----
|
||||
introduction-point xvggi5iicyxvts2e4tp4dqvsxcutq7gk
|
||||
ip-address 163.172.35.115
|
||||
onion-port 9001
|
||||
onion-key
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIGJAoGBALReAPZf+K1yd7CDqCgLWdsv7jw8AezsatqnrP7tojF4uw2fLHLJEJMv
|
||||
VpgFgHHRC3bsz0KvTqvHtEW0XGZJWfhVSW3JAGhz/Yqu53SEC9ZGMslHfr2xlRIr
|
||||
7dBEiCBAG8dPkULfC6EcyHPzApH1i9wuzcltuJ0zFQmJElB/jSNPAgMBAAE=
|
||||
-----END RSA PUBLIC KEY-----
|
||||
service-key
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIGJAoGBALNMW5HolfaS3sjZNhwvTwjT9+OsY2aMWZ5byvrCZs0nqCmEivENSAmZ
|
||||
f9olT4zmxMHY4KIaUFqnYw5y59oHj8xlul4K1DkMMyudQi3appnikAXfaRJmEsJF
|
||||
3nOMzWzRcx0mk0XQa8x9atGa5Rs/WiviYXPESbIJ0iNfaRqc2KHXAgMBAAE=
|
||||
-----END RSA PUBLIC KEY-----
|
@ -0,0 +1,54 @@
|
||||
router tor26 86.59.21.38 443 0 80
|
||||
identity-ed25519
|
||||
-----BEGIN ED25519 CERT-----
|
||||
AQQABkOxASTqCHFXm3umN0NYOlIfUHpu0elZrvvdJQIKiwEytEVIAQAgBACRd0Dl
|
||||
2YiFduUCq4zHQzQClYkngkZAYuyNRKfzpxshpVAWZQcho/HH9PTCjkntnqfCVBlp
|
||||
Qz0azrqvzrm+ORY6lLoL3SwF6e83iwhXDz3Xl1eQNttdM0py0SgM1guAews=
|
||||
-----END ED25519 CERT-----
|
||||
master-key-ed25519 kXdA5dmIhXblAquMx0M0ApWJJ4JGQGLsjUSn86cbIaU
|
||||
or-address [2001:858:2:2:aabb:0:563b:1526]:443
|
||||
platform Tor 0.2.8.5-rc on Linux
|
||||
protocols Link 1 2 Circuit 1
|
||||
published 2016-07-20 06:17:03
|
||||
fingerprint 847B 1F85 0344 D787 6491 A548 92F9 0493 4E4E B85D
|
||||
uptime 657305
|
||||
bandwidth 76800 1073741824 2436786
|
||||
extra-info-digest 4823F98D08CD81FF18BDFC7539D2117023F4A2BF gh1tclm7Gfwhhus3My4od7NpzOVddwLP9vl2SJEbkkM
|
||||
caches-extra-info
|
||||
onion-key
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIGJAoGBAMMbFxRKUYujEKnXEORyrA1bokmMJ64p6uJn0r9LdKRgOPMnaLFGNLwV
|
||||
VNXWW22XZh/+B/e5SWUZDrrVpDuPc2b+jS91czHC6j4zErP9GljhV2QEi9Y31/ib
|
||||
ilYaEcZh/fgXFUEUCQunw3Hcy1IiCd1ZCCkt644SvJqpPE3SYRUBAgMBAAE=
|
||||
-----END RSA PUBLIC KEY-----
|
||||
signing-key
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIGJAoGBAMQgV2gXLbXgesWgeAsj8P1Uvm/zibrFXqwDq27lLKNgWGYGX2ax3LyT
|
||||
3nzI1Y5oLs4kPKTsMM5ft9aokwf417lKoCRlZc9ptfRbgxDx90c9GtWVmkrmDvCK
|
||||
ae59TMoXIiGfZiwWT6KKq5Zm9/Fu2Il3B2vHGkKJYKixmiBJRKp/AgMBAAE=
|
||||
-----END RSA PUBLIC KEY-----
|
||||
onion-key-crosscert
|
||||
-----BEGIN CROSSCERT-----
|
||||
B6uutyCOCqEzoxldF4jOvWdSinCbmd2vWiRix5qYV8OVxhGlwGvby0cyYzCOuxDW
|
||||
FdKzl+8idwUokJnVrfmIAQT4UvDJrlPYsVrH+OJ/WHV01A7cy4SH7Od8QRIBDhFy
|
||||
AlzCLMWjUS2U6Os2qfLAALIQS5IJXGZFNaKK2KPQuco=
|
||||
-----END CROSSCERT-----
|
||||
ntor-onion-key-crosscert 1
|
||||
-----BEGIN ED25519 CERT-----
|
||||
AQoABjqfAZF3QOXZiIV25QKrjMdDNAKViSeCRkBi7I1Ep/OnGyGlAFFzHPrLfNnI
|
||||
PSczV2yNoywGUwc4P5YLxYXhuzJCcvBuWcJJsqj7OOOqAnZqDwdQoTKoo4XsJW8B
|
||||
Masq4RFHrgo=
|
||||
-----END ED25519 CERT-----
|
||||
family $01C5B10C5C6B1DEB47612E66EBF315B7C64DE803 $03FF94D9E5001DD2290BC3B19FA7F59CE1E30279 $6BD2FF175484CD7925A81DC42D89B3953252DBAD $995D0FE5A89563D79A383CCC2444D0E26C6BE625 $CA1DDA678A477BD2F19714F88A3C0613C7AD3C10
|
||||
hidden-service-dir
|
||||
contact Peter Palfrader
|
||||
ntor-onion-key Bo7lvTWfIi+Wo8zLi7rTU1VTbfUPde/l1xep/YnrHWY=
|
||||
reject *:*
|
||||
tunnelled-dir-server
|
||||
router-sig-ed25519 PPdfXRuQBWE94/ApWcxAg5auci7xpHgfnp6AHQDxGQH0YVag7cEFba4ubJEw+2RNYIFEPXzAE3vTFZUZItRWBQ
|
||||
router-signature
|
||||
-----BEGIN SIGNATURE-----
|
||||
aF7kwzH6UHfviZkAd8Ga/UFmMZqNJt4rXRcCOUG7+cnyVnzJ2c6Y6uuHIfINuarc
|
||||
+rEsE4+OBswnBTQbxi/YpEijZkzambmeLPtY3okyy4PqLA1rPmh9pirp/zB3n6Co
|
||||
kCou0AEO5POcpT1D+yJnYny7qKjkxnvNuGvuRzE7OkE=
|
||||
-----END SIGNATURE-----
|
1075
doc/rend-spec.txt
Normal file
1075
doc/rend-spec.txt
Normal file
File diff suppressed because it is too large
Load Diff
1612
doc/tor-spec.txt
Normal file
1612
doc/tor-spec.txt
Normal file
File diff suppressed because it is too large
Load Diff
4236
package-lock.json
generated
4236
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,7 @@
|
||||
"base32": "0.0.6",
|
||||
"curve25519-js": "0.0.4",
|
||||
"futoin-hkdf": "^1.3.2",
|
||||
"jest": "^26.6.3",
|
||||
"tweetnacl": "^1.0.3"
|
||||
}
|
||||
}
|
||||
|
@ -281,8 +281,34 @@ class Circuit {
|
||||
}
|
||||
|
||||
_on_relay_extended_cell(cell) {
|
||||
//TODO
|
||||
throw new Error('Not implemented')
|
||||
//
|
||||
// The payload of an EXTENDED cell is the same as the payload of a
|
||||
// CREATED cell.
|
||||
//
|
||||
|
||||
//
|
||||
// finish the handshake.
|
||||
//
|
||||
|
||||
const handshake_data = cell.relay_payload;
|
||||
this._extend_node.compute_shared_secret(handshake_data);
|
||||
|
||||
if (this._extend_node.has_valid_crypto_state())
|
||||
{
|
||||
this._node_list.push(this._extend_node);
|
||||
|
||||
//
|
||||
// we're ready here.
|
||||
//
|
||||
this._extend_node = null;
|
||||
this.state = States.ready;
|
||||
}
|
||||
else
|
||||
{
|
||||
this._logger.error("Circuit.handle_relay_extended_cell() extend node [ %s ] has invalid crypto state", this._extend_node.onion_router.name);
|
||||
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
_on_relay_extended2(cell) {
|
||||
@ -752,7 +778,7 @@ class Circuit {
|
||||
//mini_assert(rendezvous_cookie.get_size() == 20);
|
||||
|
||||
const introduction_point = this.get_final_circuit_node().onion_router;
|
||||
const introducee = rendezvous_circuit.get_final_circuit_node.onion_router;
|
||||
const introducee = rendezvous_circuit.get_final_circuit_node().onion_router;
|
||||
|
||||
this._logger.debug("Circuit.rendezvous_introduce() [or: %s, state: introducing]", introduction_point.name);
|
||||
this.state = States.rendezvous_introducing;
|
||||
@ -790,7 +816,7 @@ class Circuit {
|
||||
2 + // port
|
||||
20 + // identity_fingerprint
|
||||
2 + // onion key size
|
||||
32 + // onion key
|
||||
introducee.onion_key.length + // onion key
|
||||
20 + // rendezvous cookie
|
||||
128); // DH
|
||||
|
||||
@ -808,7 +834,14 @@ class Circuit {
|
||||
rendezvous_cookie.copy(handshake_bytes, 29 + introducee.onion_key.length);
|
||||
rendezvous_circuit._extend_node.key_agreement.public_key.copy(handshake_bytes, 29 + introducee.onion_key.length + rendezvous_cookie.length);
|
||||
|
||||
const handshake_encrypted = hybrid_encrypt(handshake_bytes, introduction_point.service_key);
|
||||
const b64 = introduction_point.service_key.toString('base64');
|
||||
const parts = ['-----BEGIN RSA PUBLIC KEY-----'];
|
||||
for (let i = 0; i < b64.length; i += 64) {
|
||||
parts.push(b64.slice(i, i + 64));
|
||||
}
|
||||
parts.push('-----END RSA PUBLIC KEY-----');
|
||||
const service_key = parts.join('\n')
|
||||
const handshake_encrypted = hybrid_encrypt(handshake_bytes, service_key);
|
||||
|
||||
//
|
||||
// compose the final payload.
|
||||
@ -841,7 +874,7 @@ class Circuit {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await rendezvous_circuit.wait_for_state(States.rendezvous_completed))
|
||||
if (await rendezvous_circuit.wait_for_state(States.rendezvous_completed, 90000))
|
||||
{
|
||||
this._logger.debug("Circuit.rendezvous_introduce() [or: %s, state: completed]", introduction_point.name);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
const KeyAgreementNtor = require('./KeyAgreementNtor');
|
||||
const KeyAgreementTap = require('./KeyAgreementTap');
|
||||
const CircuitNodeCryptoState = require('./CircuitNodeCryptoState');
|
||||
|
||||
class CircuitNode {
|
||||
@ -6,7 +7,7 @@ class CircuitNode {
|
||||
/** @type OnionRouter */
|
||||
_onion_router = null;
|
||||
_type = 'normal';
|
||||
/** @type KeyAgreementNtor */
|
||||
/** @type {KeyAgreementNtor|KeyAgreementTap} */
|
||||
_handshake = null;
|
||||
/** @type CircuitNodeCryptoState */
|
||||
_crypto_state = null;
|
||||
@ -22,6 +23,9 @@ class CircuitNode {
|
||||
this._circuit = circuit;
|
||||
this._onion_router = or;
|
||||
this._type = type;
|
||||
if (type === 'introductionpoint') {
|
||||
this._handshake = new KeyAgreementTap(or);
|
||||
}
|
||||
}
|
||||
|
||||
create_onion_skin_ntor() {
|
||||
|
@ -193,7 +193,7 @@ class Consensus {
|
||||
* @return {Promise<string>}
|
||||
* @private
|
||||
*/
|
||||
_download_from_random_router_impl(path, only_authorities) {
|
||||
async _download_from_random_router_impl(path, only_authorities) {
|
||||
let ip;
|
||||
let port;
|
||||
|
||||
@ -222,7 +222,11 @@ class Consensus {
|
||||
|
||||
this._logger.debug(`Consensus._download_from_random_router_impl() [path: http://${ip}:${port}${path}]`);
|
||||
|
||||
return get('http:', ip, port, path);
|
||||
try {
|
||||
return await get('http:', ip, port, path);
|
||||
} catch (err) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
_parse_consensus(content) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
const crypto = require('crypto');
|
||||
const base32 = require('base32')
|
||||
const base32 = require('../utils/base32')
|
||||
const OnionRouter = require('./OnionRouter')
|
||||
const {get} = require('../utils/http')
|
||||
const {time} = require('../utils/time')
|
||||
@ -18,15 +18,16 @@ class HiddenServiceConnector {
|
||||
|
||||
constructor (rendezvous_circuit, onion, logger) {
|
||||
this._rendezvous_circuit = rendezvous_circuit;
|
||||
this._socket = rendezvous_circuit.tor_socket;
|
||||
this._consensus = rendezvous_circuit.tor_socket.onion_router.consensus;
|
||||
this._socket = rendezvous_circuit?.tor_socket;
|
||||
this._consensus = rendezvous_circuit?.tor_socket.onion_router.consensus;
|
||||
this._onion = onion;
|
||||
this._permanent_id = Buffer.from(base32.decode(onion));
|
||||
this._logger = logger;
|
||||
this._time = time;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this.find_responsible_directories();
|
||||
this._find_responsible_directories();
|
||||
|
||||
if (this._responsible_directory_list.length)
|
||||
{
|
||||
@ -43,7 +44,7 @@ class HiddenServiceConnector {
|
||||
if (this._rendezvous_circuit.is_rendezvous_established())
|
||||
{
|
||||
let responsible_directory_index = 0;
|
||||
while ((responsible_directory_index = await this.fetch_hidden_service_descriptor(responsible_directory_index)) !== -1)
|
||||
while ((responsible_directory_index = await this._fetch_hidden_service_descriptor(responsible_directory_index)) !== -1)
|
||||
{
|
||||
await this.introduce();
|
||||
|
||||
@ -58,7 +59,7 @@ class HiddenServiceConnector {
|
||||
return false;
|
||||
}
|
||||
|
||||
find_responsible_directories() {
|
||||
_find_responsible_directories() {
|
||||
//
|
||||
// rend-spec.txt
|
||||
// 1.4.
|
||||
@ -88,7 +89,7 @@ class HiddenServiceConnector {
|
||||
{
|
||||
const descriptor_id = this.get_descriptor_id(replica);
|
||||
|
||||
const index = directory_list.findIndex(x => Buffer.compare(x.identity_fingerprint, descriptor_id) < 0);
|
||||
const index = directory_list.findIndex(x => Buffer.compare(x.identity_fingerprint, descriptor_id) > 0);
|
||||
|
||||
for (let i = 0; i < 3; i++)
|
||||
{
|
||||
@ -111,10 +112,10 @@ class HiddenServiceConnector {
|
||||
// time-period = (current-time + permanent-id-byte * 86400 / 256)
|
||||
// / 86400
|
||||
//
|
||||
const time_period = (time() + (permanent_id_byte * 86400 / 256)) / 86400;
|
||||
const time_period = Math.floor((this._time() + (permanent_id_byte * 86400 / 256)) / 86400);
|
||||
|
||||
const secret_bytes = Buffer.alloc(5);
|
||||
secret_bytes.writeInt32BE(time_period, 0);
|
||||
secret_bytes.writeUInt32BE(time_period, 0);
|
||||
secret_bytes.writeUInt8(replica, 4);
|
||||
|
||||
return sha1(secret_bytes);
|
||||
@ -135,7 +136,7 @@ class HiddenServiceConnector {
|
||||
return sha1(descriptor_id_bytes);
|
||||
}
|
||||
|
||||
async fetch_hidden_service_descriptor(responsible_directory_index) {
|
||||
async _fetch_hidden_service_descriptor(responsible_directory_index) {
|
||||
for (let i = responsible_directory_index; i < this._responsible_directory_list.length; i++)
|
||||
{
|
||||
const responsible_directory = this._responsible_directory_list[i];
|
||||
@ -149,7 +150,6 @@ class HiddenServiceConnector {
|
||||
this._socket.onion_router.name,
|
||||
this._socket.onion_router.ip,
|
||||
this._socket.onion_router.or_port);
|
||||
this._logger.info("\tConnected...");
|
||||
|
||||
/** @type Circuit */
|
||||
const directory_circuit = await this._socket.create_circuit();
|
||||
@ -164,9 +164,10 @@ class HiddenServiceConnector {
|
||||
//
|
||||
continue;
|
||||
}
|
||||
this._logger.info("\tConnected...");
|
||||
|
||||
this._logger.info(
|
||||
"\tExtending circuit for hidden service, connecting to responsible directory '%s' (%s:%u)",
|
||||
"\tExtending circuit for hidden service, connecting to responsible directory '%s' (%s:%i)",
|
||||
responsible_directory.name,
|
||||
responsible_directory.ip,
|
||||
responsible_directory.or_port);
|
||||
@ -205,10 +206,10 @@ class HiddenServiceConnector {
|
||||
const descriptor_path = "/tor/rendezvous2/" + base32.encode(this.get_descriptor_id(replica));
|
||||
|
||||
this._logger.debug(
|
||||
"hidden_service::fetch_hidden_service_descriptor() [path: %s]",
|
||||
"hidden_service::_fetch_hidden_service_descriptor() [path: %s]",
|
||||
descriptor_path);
|
||||
|
||||
this._logger.info("\tSending request for hidden service descriptor...");
|
||||
this._logger.info("\tSending request for hidden service descriptor... %s:%i", responsible_directory.ip, responsible_directory.dir_port);
|
||||
|
||||
const hidden_service_descriptor = await get(
|
||||
'http:',
|
||||
@ -222,7 +223,7 @@ class HiddenServiceConnector {
|
||||
//
|
||||
// parse hidden service descriptor.
|
||||
//
|
||||
if (!hidden_service_descriptor &&
|
||||
if (hidden_service_descriptor &&
|
||||
!hidden_service_descriptor.includes("404 Not found"))
|
||||
{
|
||||
this._logger.info("\tHidden service descriptor is valid...");
|
||||
@ -246,14 +247,14 @@ class HiddenServiceConnector {
|
||||
for (const line of lines) {
|
||||
const parts = line.split(' ');
|
||||
if (parts[0] === 'introduction-point') {
|
||||
const identity_fingerprint = Buffer.from(base32.decode(parts[1]));
|
||||
const identity_fingerprint = base32.decode(parts[1]);
|
||||
current_router = this._consensus.get_onion_router_by_identity_fingerprint(identity_fingerprint);
|
||||
} else if (parts[0] === 'service-key') {
|
||||
serviceKey = '';
|
||||
} else if (parts[0] === '-----BEGIN RSA PUBLIC KEY-----' && serviceKey !== null) {
|
||||
} else if (line === '-----BEGIN RSA PUBLIC KEY-----' && serviceKey !== null) {
|
||||
capture = true;
|
||||
} else if (parts[0] === '-----END RSA PUBLIC KEY-----' && serviceKey !== null) {
|
||||
current_router.service_key = serviceKey;
|
||||
} else if (line === '-----END RSA PUBLIC KEY-----' && serviceKey !== null) {
|
||||
current_router.service_key = Buffer.from(serviceKey, 'base64');
|
||||
introduction_point_list.push(current_router);
|
||||
capture = false;
|
||||
serviceKey = null;
|
||||
@ -336,7 +337,7 @@ class HiddenServiceConnector {
|
||||
if (introduce_circuit.is_rendezvous_introduced())
|
||||
{
|
||||
this._logger.info("\tIntroduced successfully...");
|
||||
break;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
22
src/tor/HiddenServiceConnector.test.js
Normal file
22
src/tor/HiddenServiceConnector.test.js
Normal file
@ -0,0 +1,22 @@
|
||||
const HiddenServiceConnector = require('./HiddenServiceConnector');
|
||||
const base32 = require('../utils/base32');
|
||||
|
||||
test('base32 decode', function() {
|
||||
const decoded = base32.decode('duskgytldkxiuqc6');
|
||||
//const decoded_buf = Array.prototype.map.call(decoded, (_, i) => decoded.charCodeAt(i))
|
||||
expect(decoded).toEqual(Buffer.from([0x1d,0x24,0xa3,0x62,0x6b,0x1a,0xae,0x8a,0x40,0x5e]));
|
||||
})
|
||||
test('base32 encode', function() {
|
||||
const encoded = base32.encode(Buffer.from([0x1d,0x24,0xa3,0x62,0x6b,0x1a,0xae,0x8a,0x40,0x5e]));
|
||||
//const decoded_buf = Array.prototype.map.call(decoded, (_, i) => decoded.charCodeAt(i))
|
||||
expect(encoded).toEqual('duskgytldkxiuqc6');
|
||||
})
|
||||
test('test descriptor', function() {
|
||||
const time = 1611534264;
|
||||
const onion = 'duskgytldkxiuqc6';
|
||||
const sut = new HiddenServiceConnector(null, onion, null);
|
||||
sut._time = () => time;
|
||||
const descriptor = sut.get_descriptor_id(0);
|
||||
const descriptor_base32 = base32.encode(descriptor);
|
||||
expect(descriptor_base32).toBe('ek7vymlparcwqimmmfixlbars4xjz5jl');
|
||||
})
|
123
src/tor/KeyAgreementTap.js
Normal file
123
src/tor/KeyAgreementTap.js
Normal file
@ -0,0 +1,123 @@
|
||||
const crypto = require('crypto')
|
||||
const dh1024 = require('../utils/dh1024')
|
||||
const {sha1} = require('../utils/crypto')
|
||||
|
||||
const DH_P = new Buffer([
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34,
|
||||
0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74,
|
||||
0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd,
|
||||
0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, 0x37,
|
||||
0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6,
|
||||
0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed,
|
||||
0xee, 0x38, 0x6b, 0xfb, 0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11, 0x7c, 0x4b, 0x1f, 0xe6,
|
||||
0x49, 0x28, 0x66, 0x51, 0xec, 0xe6, 0x53, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
]);
|
||||
|
||||
class KeyAgreementTap {
|
||||
constructor (or) {
|
||||
this._router = or;
|
||||
this._dh = crypto.createDiffieHellman(DH_P);
|
||||
this._dh.generateKeys();
|
||||
}
|
||||
|
||||
get public_key() {
|
||||
return this._dh.getPublicKey();
|
||||
}
|
||||
|
||||
get private_key() {
|
||||
return this._dh.getPrivateKey();
|
||||
}
|
||||
|
||||
compute_shared_secret(handshake_data) {
|
||||
return this._compute_shared_secret(
|
||||
handshake_data.slice(0, dh1024.key_size_in_bytes),
|
||||
handshake_data.slice(dh1024.key_size_in_bytes, dh1024.key_size_in_bytes + 20));
|
||||
}
|
||||
|
||||
_compute_shared_secret(other_public_key, verification_data) {
|
||||
//
|
||||
// 5.1.3. The "TAP" handshake
|
||||
//
|
||||
// This handshake uses Diffie-Hellman in Z_p and RSA to compute a set of
|
||||
// shared keys which the client knows are shared only with a particular
|
||||
// server, and the server knows are shared with whomever sent the
|
||||
// original handshake (or with nobody at all). It's not very fast and
|
||||
// not very good. (See Goldberg's "On the Security of the Tor
|
||||
// Authentication Protocol".)
|
||||
//
|
||||
// Define TAP_C_HANDSHAKE_LEN as DH_LEN+KEY_LEN+PK_PAD_LEN.
|
||||
// Define TAP_S_HANDSHAKE_LEN as DH_LEN+HASH_LEN.
|
||||
//
|
||||
// The payload for a CREATE cell is an 'onion skin', which consists of
|
||||
// the first step of the DH handshake data (also known as g^x). This
|
||||
// value is hybrid-encrypted (see 0.3) to the server's onion key, giving
|
||||
// a client handshake of:
|
||||
//
|
||||
// PK-encrypted:
|
||||
// Padding [PK_PAD_LEN bytes]
|
||||
// Symmetric key [KEY_LEN bytes]
|
||||
// First part of g^x [PK_ENC_LEN-PK_PAD_LEN-KEY_LEN bytes]
|
||||
// Symmetrically encrypted:
|
||||
// Second part of g^x [DH_LEN-(PK_ENC_LEN-PK_PAD_LEN-KEY_LEN)
|
||||
// bytes]
|
||||
//
|
||||
// The payload for a CREATED cell, or the relay payload for an
|
||||
// EXTENDED cell, contains:
|
||||
// DH data (g^y) [DH_LEN bytes]
|
||||
// Derivative key data (KH) [HASH_LEN bytes] <see 5.2 below>
|
||||
//
|
||||
// Once the handshake between the OP and an OR is completed, both can
|
||||
// now calculate g^xy with ordinary DH. Before computing g^xy, both parties
|
||||
// MUST verify that the received g^x or g^y value is not degenerate;
|
||||
// that is, it must be strictly greater than 1 and strictly less than p-1
|
||||
// where p is the DH modulus. Implementations MUST NOT complete a handshake
|
||||
// with degenerate keys. Implementations MUST NOT discard other "weak"
|
||||
// g^x values.
|
||||
//
|
||||
// (Discarding degenerate keys is critical for security; if bad keys
|
||||
// are not discarded, an attacker can substitute the OR's CREATED
|
||||
// cell's g^y with 0 or 1, thus creating a known g^xy and impersonating
|
||||
// the OR. Discarding other keys may allow attacks to learn bits of
|
||||
// the private key.)
|
||||
//
|
||||
// Once both parties have g^xy, they derive their shared circuit keys
|
||||
// and 'derivative key data' value via the KDF-TOR function in 5.2.1.
|
||||
//
|
||||
|
||||
//mini_assert(verification_data.get_size() == crypto::sha1::hash_size_in_bytes);
|
||||
|
||||
const shared_secret = this._dh.computeSecret(other_public_key);
|
||||
const derived = this._derive_keys(shared_secret);
|
||||
|
||||
//
|
||||
// first 20 bytes of the derived key is the verification checksum.
|
||||
// rest of it is the key material.
|
||||
//
|
||||
const computed_verification_data = derived.slice(0, 20);
|
||||
const key_material = derived.slice(20);
|
||||
|
||||
if (computed_verification_data.equals(verification_data))
|
||||
{
|
||||
return key_material;
|
||||
}
|
||||
|
||||
return Buffer.alloc(0);
|
||||
}
|
||||
|
||||
_derive_keys(secret) {
|
||||
const key_material = Buffer.alloc(100);
|
||||
|
||||
const hashdata = Buffer.alloc(secret.length + 1);
|
||||
secret.copy(hashdata, 0);
|
||||
|
||||
for (let i = 0; i < 5; i++)
|
||||
{
|
||||
hashdata[secret.length] = i;
|
||||
sha1(hashdata).copy(key_material, i * 20);
|
||||
}
|
||||
|
||||
return key_material;
|
||||
}
|
||||
|
||||
}
|
||||
module.exports = KeyAgreementTap;
|
@ -59,12 +59,26 @@ class OnionRouter {
|
||||
const descriptor = await this._consensus.get_onion_router_descriptor(this._identity_fingerprint);
|
||||
// parse
|
||||
const lines = descriptor.split('\n');
|
||||
let capture = '', onion_key = '';
|
||||
for (const line of lines){
|
||||
const parts = line.split(' ');
|
||||
if (parts[0] === 'ntor-onion-key') {
|
||||
if (parts[0] === 'onion-key') {
|
||||
capture = 'onion-key';
|
||||
}
|
||||
else if (parts[0] === 'ntor-onion-key') {
|
||||
this._ntor_onion_key = Buffer.from(parts[1], 'base64');
|
||||
break;
|
||||
}
|
||||
else if (capture === 'onion-key' && line === '-----BEGIN RSA PUBLIC KEY-----') {
|
||||
onion_key = '';
|
||||
}
|
||||
else if (capture === 'onion-key' && line === '-----END RSA PUBLIC KEY-----') {
|
||||
this._onion_key = Buffer.from(onion_key, 'base64')
|
||||
capture = '';
|
||||
}
|
||||
else if (capture === 'onion-key') {
|
||||
onion_key += line;
|
||||
}
|
||||
}
|
||||
this._descriptor_fetched = true;
|
||||
}
|
||||
|
@ -40,6 +40,11 @@ class TorStream extends Duplex {
|
||||
set state(state) {
|
||||
this._state = state;
|
||||
if (state === States.destroyed) {
|
||||
if (this._buffer) {
|
||||
this.push(this._buffer);
|
||||
this._buffer = null;
|
||||
}
|
||||
this.push(null);
|
||||
for (const k in this._waits) {
|
||||
for (const wait of this._waits[k]) {
|
||||
wait.resolve(false);
|
||||
@ -57,12 +62,12 @@ class TorStream extends Duplex {
|
||||
}
|
||||
|
||||
append_to_recv_buffer(data) {
|
||||
this._buffer = this._buffer
|
||||
? Buffer.concat([this._buffer, data], this._buffer.length + data.length)
|
||||
: data;
|
||||
if (this._canRead) {
|
||||
this._canRead = this.push(data);
|
||||
} else {
|
||||
this._buffer = this._buffer
|
||||
? Buffer.concat([this._buffer, data], this._buffer.length + data.length)
|
||||
: data;
|
||||
this._canRead = this.push(this._buffer);
|
||||
this._buffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
51
src/utils/base32.js
Normal file
51
src/utils/base32.js
Normal file
@ -0,0 +1,51 @@
|
||||
const alphabet = 'abcdefghijklmnopqrstuvwxyz234567'
|
||||
|
||||
function decode_chunk(input, in_offset, output, out_offset) {
|
||||
let b = BigInt(0);
|
||||
for (let i = 0; i < 8; i++) {
|
||||
b = (b << 5n) | BigInt(alphabet.indexOf(input[in_offset + i]));
|
||||
}
|
||||
for (let j = 4; j >= 0; j--) {
|
||||
output[out_offset + 4 - j] = Number(b >> BigInt(j * 8));
|
||||
}
|
||||
}
|
||||
|
||||
function decode(input) {
|
||||
const out_len = Math.ceil((input.length / 8) * 5);
|
||||
const output = Buffer.alloc(out_len);
|
||||
|
||||
const q = Math.floor(input.length / 8);
|
||||
for (let i = 0; i < q; i++) {
|
||||
decode_chunk(input, i * 8, output, i * 5);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
exports.decode = decode;
|
||||
|
||||
function encode_chunk(input, in_offset, output, out_offset) {
|
||||
let b = BigInt(0);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
b = (b << 8n) | BigInt(input[in_offset + i]);
|
||||
}
|
||||
for (let i = 7; i >= 0; i--) {
|
||||
b = b << BigInt(24 + (7 - i) * 5);
|
||||
b = b >> BigInt(24 + (7 - i) * 5);
|
||||
|
||||
const c = Number(b >> BigInt(i * 5)) % 32;
|
||||
output[out_offset + 7 - i] = alphabet.charCodeAt(c);
|
||||
}
|
||||
}
|
||||
|
||||
function encode(input) {
|
||||
const out_len = Math.ceil(input.length / 5 * 8);
|
||||
const output = Buffer.alloc(out_len);
|
||||
|
||||
const q = Math.floor(input.length / 5);
|
||||
for (let i = 0; i < q; i++) {
|
||||
encode_chunk(input, i * 5, output, i * 8);
|
||||
}
|
||||
|
||||
return output.toString('ascii');
|
||||
}
|
||||
exports.encode = encode;
|
@ -44,13 +44,13 @@ const PK_DATA_LEN_WITH_KEY = PK_DATA_LEN - KEY_LEN;
|
||||
|
||||
/**
|
||||
* @param {Buffer} data
|
||||
* @param {Buffer} public_key
|
||||
* @param {string} public_key
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
function hybrid_encrypt(data, public_key) {
|
||||
if (data.length < PK_DATA_LEN)
|
||||
{
|
||||
return rsa_1024(public_key).encrypt(data);
|
||||
return crypto.publicEncrypt(public_key, data);
|
||||
}
|
||||
|
||||
const random_key = crypto.randomBytes(KEY_LEN);
|
||||
@ -62,7 +62,7 @@ function hybrid_encrypt(data, public_key) {
|
||||
random_key, data.slice(0, PK_DATA_LEN_WITH_KEY)
|
||||
], random_key.length + PK_DATA_LEN_WITH_KEY);
|
||||
|
||||
const c1 = rsa_1024(public_key).encrypt(k_and_m1);
|
||||
const c1 = crypto.publicEncrypt(public_key, k_and_m1);
|
||||
|
||||
//
|
||||
// AES_CTR(M2) --> C2
|
||||
|
1
src/utils/dh1024.js
Normal file
1
src/utils/dh1024.js
Normal file
@ -0,0 +1 @@
|
||||
exports.key_size_in_bytes = (1024/8);
|
@ -28,7 +28,7 @@ async function get(protocol, ip, port, path, stream) {
|
||||
res.on('data', chunk => data += chunk.toString());
|
||||
res.on('end', () => resolve(data));
|
||||
res.on('error', (err) => reject(err));
|
||||
});
|
||||
}).on('error', err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user