finalize get from onion
This commit is contained in:
parent
542e31cd96
commit
a9f3348a01
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",
|
"base32": "0.0.6",
|
||||||
"curve25519-js": "0.0.4",
|
"curve25519-js": "0.0.4",
|
||||||
"futoin-hkdf": "^1.3.2",
|
"futoin-hkdf": "^1.3.2",
|
||||||
|
"jest": "^26.6.3",
|
||||||
"tweetnacl": "^1.0.3"
|
"tweetnacl": "^1.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -281,8 +281,34 @@ class Circuit {
|
||||||
}
|
}
|
||||||
|
|
||||||
_on_relay_extended_cell(cell) {
|
_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) {
|
_on_relay_extended2(cell) {
|
||||||
|
@ -752,7 +778,7 @@ class Circuit {
|
||||||
//mini_assert(rendezvous_cookie.get_size() == 20);
|
//mini_assert(rendezvous_cookie.get_size() == 20);
|
||||||
|
|
||||||
const introduction_point = this.get_final_circuit_node().onion_router;
|
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._logger.debug("Circuit.rendezvous_introduce() [or: %s, state: introducing]", introduction_point.name);
|
||||||
this.state = States.rendezvous_introducing;
|
this.state = States.rendezvous_introducing;
|
||||||
|
@ -790,7 +816,7 @@ class Circuit {
|
||||||
2 + // port
|
2 + // port
|
||||||
20 + // identity_fingerprint
|
20 + // identity_fingerprint
|
||||||
2 + // onion key size
|
2 + // onion key size
|
||||||
32 + // onion key
|
introducee.onion_key.length + // onion key
|
||||||
20 + // rendezvous cookie
|
20 + // rendezvous cookie
|
||||||
128); // DH
|
128); // DH
|
||||||
|
|
||||||
|
@ -808,7 +834,14 @@ class Circuit {
|
||||||
rendezvous_cookie.copy(handshake_bytes, 29 + introducee.onion_key.length);
|
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);
|
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.
|
// compose the final payload.
|
||||||
|
@ -841,7 +874,7 @@ class Circuit {
|
||||||
return;
|
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);
|
this._logger.debug("Circuit.rendezvous_introduce() [or: %s, state: completed]", introduction_point.name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const KeyAgreementNtor = require('./KeyAgreementNtor');
|
const KeyAgreementNtor = require('./KeyAgreementNtor');
|
||||||
|
const KeyAgreementTap = require('./KeyAgreementTap');
|
||||||
const CircuitNodeCryptoState = require('./CircuitNodeCryptoState');
|
const CircuitNodeCryptoState = require('./CircuitNodeCryptoState');
|
||||||
|
|
||||||
class CircuitNode {
|
class CircuitNode {
|
||||||
|
@ -6,7 +7,7 @@ class CircuitNode {
|
||||||
/** @type OnionRouter */
|
/** @type OnionRouter */
|
||||||
_onion_router = null;
|
_onion_router = null;
|
||||||
_type = 'normal';
|
_type = 'normal';
|
||||||
/** @type KeyAgreementNtor */
|
/** @type {KeyAgreementNtor|KeyAgreementTap} */
|
||||||
_handshake = null;
|
_handshake = null;
|
||||||
/** @type CircuitNodeCryptoState */
|
/** @type CircuitNodeCryptoState */
|
||||||
_crypto_state = null;
|
_crypto_state = null;
|
||||||
|
@ -22,6 +23,9 @@ class CircuitNode {
|
||||||
this._circuit = circuit;
|
this._circuit = circuit;
|
||||||
this._onion_router = or;
|
this._onion_router = or;
|
||||||
this._type = type;
|
this._type = type;
|
||||||
|
if (type === 'introductionpoint') {
|
||||||
|
this._handshake = new KeyAgreementTap(or);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
create_onion_skin_ntor() {
|
create_onion_skin_ntor() {
|
||||||
|
|
|
@ -193,7 +193,7 @@ class Consensus {
|
||||||
* @return {Promise<string>}
|
* @return {Promise<string>}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_download_from_random_router_impl(path, only_authorities) {
|
async _download_from_random_router_impl(path, only_authorities) {
|
||||||
let ip;
|
let ip;
|
||||||
let port;
|
let port;
|
||||||
|
|
||||||
|
@ -222,7 +222,11 @@ class Consensus {
|
||||||
|
|
||||||
this._logger.debug(`Consensus._download_from_random_router_impl() [path: http://${ip}:${port}${path}]`);
|
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) {
|
_parse_consensus(content) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const base32 = require('base32')
|
const base32 = require('../utils/base32')
|
||||||
const OnionRouter = require('./OnionRouter')
|
const OnionRouter = require('./OnionRouter')
|
||||||
const {get} = require('../utils/http')
|
const {get} = require('../utils/http')
|
||||||
const {time} = require('../utils/time')
|
const {time} = require('../utils/time')
|
||||||
|
@ -18,15 +18,16 @@ class HiddenServiceConnector {
|
||||||
|
|
||||||
constructor (rendezvous_circuit, onion, logger) {
|
constructor (rendezvous_circuit, onion, logger) {
|
||||||
this._rendezvous_circuit = rendezvous_circuit;
|
this._rendezvous_circuit = rendezvous_circuit;
|
||||||
this._socket = rendezvous_circuit.tor_socket;
|
this._socket = rendezvous_circuit?.tor_socket;
|
||||||
this._consensus = rendezvous_circuit.tor_socket.onion_router.consensus;
|
this._consensus = rendezvous_circuit?.tor_socket.onion_router.consensus;
|
||||||
this._onion = onion;
|
this._onion = onion;
|
||||||
this._permanent_id = Buffer.from(base32.decode(onion));
|
this._permanent_id = Buffer.from(base32.decode(onion));
|
||||||
this._logger = logger;
|
this._logger = logger;
|
||||||
|
this._time = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect() {
|
async connect() {
|
||||||
this.find_responsible_directories();
|
this._find_responsible_directories();
|
||||||
|
|
||||||
if (this._responsible_directory_list.length)
|
if (this._responsible_directory_list.length)
|
||||||
{
|
{
|
||||||
|
@ -43,7 +44,7 @@ class HiddenServiceConnector {
|
||||||
if (this._rendezvous_circuit.is_rendezvous_established())
|
if (this._rendezvous_circuit.is_rendezvous_established())
|
||||||
{
|
{
|
||||||
let responsible_directory_index = 0;
|
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();
|
await this.introduce();
|
||||||
|
|
||||||
|
@ -58,7 +59,7 @@ class HiddenServiceConnector {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
find_responsible_directories() {
|
_find_responsible_directories() {
|
||||||
//
|
//
|
||||||
// rend-spec.txt
|
// rend-spec.txt
|
||||||
// 1.4.
|
// 1.4.
|
||||||
|
@ -88,7 +89,7 @@ class HiddenServiceConnector {
|
||||||
{
|
{
|
||||||
const descriptor_id = this.get_descriptor_id(replica);
|
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++)
|
for (let i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
|
@ -111,10 +112,10 @@ class HiddenServiceConnector {
|
||||||
// time-period = (current-time + permanent-id-byte * 86400 / 256)
|
// time-period = (current-time + permanent-id-byte * 86400 / 256)
|
||||||
// / 86400
|
// / 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);
|
const secret_bytes = Buffer.alloc(5);
|
||||||
secret_bytes.writeInt32BE(time_period, 0);
|
secret_bytes.writeUInt32BE(time_period, 0);
|
||||||
secret_bytes.writeUInt8(replica, 4);
|
secret_bytes.writeUInt8(replica, 4);
|
||||||
|
|
||||||
return sha1(secret_bytes);
|
return sha1(secret_bytes);
|
||||||
|
@ -135,7 +136,7 @@ class HiddenServiceConnector {
|
||||||
return sha1(descriptor_id_bytes);
|
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++)
|
for (let i = responsible_directory_index; i < this._responsible_directory_list.length; i++)
|
||||||
{
|
{
|
||||||
const responsible_directory = this._responsible_directory_list[i];
|
const responsible_directory = this._responsible_directory_list[i];
|
||||||
|
@ -149,7 +150,6 @@ class HiddenServiceConnector {
|
||||||
this._socket.onion_router.name,
|
this._socket.onion_router.name,
|
||||||
this._socket.onion_router.ip,
|
this._socket.onion_router.ip,
|
||||||
this._socket.onion_router.or_port);
|
this._socket.onion_router.or_port);
|
||||||
this._logger.info("\tConnected...");
|
|
||||||
|
|
||||||
/** @type Circuit */
|
/** @type Circuit */
|
||||||
const directory_circuit = await this._socket.create_circuit();
|
const directory_circuit = await this._socket.create_circuit();
|
||||||
|
@ -164,9 +164,10 @@ class HiddenServiceConnector {
|
||||||
//
|
//
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
this._logger.info("\tConnected...");
|
||||||
|
|
||||||
this._logger.info(
|
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.name,
|
||||||
responsible_directory.ip,
|
responsible_directory.ip,
|
||||||
responsible_directory.or_port);
|
responsible_directory.or_port);
|
||||||
|
@ -205,10 +206,10 @@ class HiddenServiceConnector {
|
||||||
const descriptor_path = "/tor/rendezvous2/" + base32.encode(this.get_descriptor_id(replica));
|
const descriptor_path = "/tor/rendezvous2/" + base32.encode(this.get_descriptor_id(replica));
|
||||||
|
|
||||||
this._logger.debug(
|
this._logger.debug(
|
||||||
"hidden_service::fetch_hidden_service_descriptor() [path: %s]",
|
"hidden_service::_fetch_hidden_service_descriptor() [path: %s]",
|
||||||
descriptor_path);
|
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(
|
const hidden_service_descriptor = await get(
|
||||||
'http:',
|
'http:',
|
||||||
|
@ -222,7 +223,7 @@ class HiddenServiceConnector {
|
||||||
//
|
//
|
||||||
// parse hidden service descriptor.
|
// parse hidden service descriptor.
|
||||||
//
|
//
|
||||||
if (!hidden_service_descriptor &&
|
if (hidden_service_descriptor &&
|
||||||
!hidden_service_descriptor.includes("404 Not found"))
|
!hidden_service_descriptor.includes("404 Not found"))
|
||||||
{
|
{
|
||||||
this._logger.info("\tHidden service descriptor is valid...");
|
this._logger.info("\tHidden service descriptor is valid...");
|
||||||
|
@ -246,14 +247,14 @@ class HiddenServiceConnector {
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const parts = line.split(' ');
|
const parts = line.split(' ');
|
||||||
if (parts[0] === 'introduction-point') {
|
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);
|
current_router = this._consensus.get_onion_router_by_identity_fingerprint(identity_fingerprint);
|
||||||
} else if (parts[0] === 'service-key') {
|
} else if (parts[0] === 'service-key') {
|
||||||
serviceKey = '';
|
serviceKey = '';
|
||||||
} else if (parts[0] === '-----BEGIN RSA PUBLIC KEY-----' && serviceKey !== null) {
|
} else if (line === '-----BEGIN RSA PUBLIC KEY-----' && serviceKey !== null) {
|
||||||
capture = true;
|
capture = true;
|
||||||
} else if (parts[0] === '-----END RSA PUBLIC KEY-----' && serviceKey !== null) {
|
} else if (line === '-----END RSA PUBLIC KEY-----' && serviceKey !== null) {
|
||||||
current_router.service_key = serviceKey;
|
current_router.service_key = Buffer.from(serviceKey, 'base64');
|
||||||
introduction_point_list.push(current_router);
|
introduction_point_list.push(current_router);
|
||||||
capture = false;
|
capture = false;
|
||||||
serviceKey = null;
|
serviceKey = null;
|
||||||
|
@ -336,7 +337,7 @@ class HiddenServiceConnector {
|
||||||
if (introduce_circuit.is_rendezvous_introduced())
|
if (introduce_circuit.is_rendezvous_introduced())
|
||||||
{
|
{
|
||||||
this._logger.info("\tIntroduced successfully...");
|
this._logger.info("\tIntroduced successfully...");
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
else
|
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);
|
const descriptor = await this._consensus.get_onion_router_descriptor(this._identity_fingerprint);
|
||||||
// parse
|
// parse
|
||||||
const lines = descriptor.split('\n');
|
const lines = descriptor.split('\n');
|
||||||
|
let capture = '', onion_key = '';
|
||||||
for (const line of lines){
|
for (const line of lines){
|
||||||
const parts = line.split(' ');
|
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');
|
this._ntor_onion_key = Buffer.from(parts[1], 'base64');
|
||||||
break;
|
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;
|
this._descriptor_fetched = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,11 @@ class TorStream extends Duplex {
|
||||||
set state(state) {
|
set state(state) {
|
||||||
this._state = state;
|
this._state = state;
|
||||||
if (state === States.destroyed) {
|
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 k in this._waits) {
|
||||||
for (const wait of this._waits[k]) {
|
for (const wait of this._waits[k]) {
|
||||||
wait.resolve(false);
|
wait.resolve(false);
|
||||||
|
@ -57,12 +62,12 @@ class TorStream extends Duplex {
|
||||||
}
|
}
|
||||||
|
|
||||||
append_to_recv_buffer(data) {
|
append_to_recv_buffer(data) {
|
||||||
if (this._canRead) {
|
|
||||||
this._canRead = this.push(data);
|
|
||||||
} else {
|
|
||||||
this._buffer = this._buffer
|
this._buffer = this._buffer
|
||||||
? Buffer.concat([this._buffer, data], this._buffer.length + data.length)
|
? Buffer.concat([this._buffer, data], this._buffer.length + data.length)
|
||||||
: data;
|
: data;
|
||||||
|
if (this._canRead) {
|
||||||
|
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} data
|
||||||
* @param {Buffer} public_key
|
* @param {string} public_key
|
||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
function hybrid_encrypt(data, public_key) {
|
function hybrid_encrypt(data, public_key) {
|
||||||
if (data.length < PK_DATA_LEN)
|
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);
|
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, data.slice(0, PK_DATA_LEN_WITH_KEY)
|
||||||
], random_key.length + 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
|
// 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('data', chunk => data += chunk.toString());
|
||||||
res.on('end', () => resolve(data));
|
res.on('end', () => resolve(data));
|
||||||
res.on('error', (err) => reject(err));
|
res.on('error', (err) => reject(err));
|
||||||
});
|
}).on('error', err => reject(err));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user