wip accessing onion address
This commit is contained in:
parent
a5230b9105
commit
40ffd49b89
27
package-lock.json
generated
27
package-lock.json
generated
|
@ -2,6 +2,14 @@
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"base32": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/base32/-/base32-0.0.6.tgz",
|
||||||
|
"integrity": "sha1-eQOLy1rsLY8ivMHChAKST1Cm0qw=",
|
||||||
|
"requires": {
|
||||||
|
"optimist": ">=0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"curve25519-js": {
|
"curve25519-js": {
|
||||||
"version": "0.0.4",
|
"version": "0.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/curve25519-js/-/curve25519-js-0.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/curve25519-js/-/curve25519-js-0.0.4.tgz",
|
||||||
|
@ -12,10 +20,29 @@
|
||||||
"resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.3.2.tgz",
|
||||||
"integrity": "sha512-3EVi3ETTyJg5PSXlxLCaUVVn0pSbDf62L3Gwxne7Uq+d8adOSNWQAad4gg7WToHkcgnCJb3Wlb1P8r4Evj4GPw=="
|
"integrity": "sha512-3EVi3ETTyJg5PSXlxLCaUVVn0pSbDf62L3Gwxne7Uq+d8adOSNWQAad4gg7WToHkcgnCJb3Wlb1P8r4Evj4GPw=="
|
||||||
},
|
},
|
||||||
|
"minimist": {
|
||||||
|
"version": "0.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||||
|
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
|
||||||
|
},
|
||||||
|
"optimist": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
|
||||||
|
"integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
|
||||||
|
"requires": {
|
||||||
|
"minimist": "~0.0.1",
|
||||||
|
"wordwrap": "~0.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tweetnacl": {
|
"tweetnacl": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
|
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
|
||||||
|
},
|
||||||
|
"wordwrap": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||||
|
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"base32": "0.0.6",
|
||||||
"curve25519-js": "0.0.4",
|
"curve25519-js": "0.0.4",
|
||||||
"futoin-hkdf": "^1.3.2",
|
"futoin-hkdf": "^1.3.2",
|
||||||
"tweetnacl": "^1.0.3"
|
"tweetnacl": "^1.0.3"
|
||||||
|
|
26
src/index.js
26
src/index.js
|
@ -1,8 +1,7 @@
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const url = require("url");
|
const url = require("url");
|
||||||
const http = require("http");
|
const util = require("util");
|
||||||
const https = require("https");
|
|
||||||
const Consensus = require("./tor/Consensus");
|
const Consensus = require("./tor/Consensus");
|
||||||
const OR = require("./tor/OnionRouter");
|
const OR = require("./tor/OnionRouter");
|
||||||
const Socket = require("./tor/Socket");
|
const Socket = require("./tor/Socket");
|
||||||
|
@ -24,19 +23,23 @@ function timestamp() {
|
||||||
const logger = {
|
const logger = {
|
||||||
debug(...args) {
|
debug(...args) {
|
||||||
if (LogLevels.debug > logLevel) return;
|
if (LogLevels.debug > logLevel) return;
|
||||||
console.debug(timestamp(), "D", ...args);
|
const [format, ...rest] = args;
|
||||||
|
console.debug(timestamp(), "D", util.format(format, ...rest));
|
||||||
},
|
},
|
||||||
info(...args) {
|
info(...args) {
|
||||||
if (LogLevels.info > logLevel) return;
|
if (LogLevels.info > logLevel) return;
|
||||||
console.log(timestamp(), "I", ...args);
|
const [format, ...rest] = args;
|
||||||
|
console.debug(timestamp(), "I", util.format(format, ...rest));
|
||||||
},
|
},
|
||||||
warn(...args) {
|
warn(...args) {
|
||||||
if (LogLevels.warn > logLevel) return;
|
if (LogLevels.warn > logLevel) return;
|
||||||
console.warn(timestamp(), "W", ...args);
|
const [format, ...rest] = args;
|
||||||
|
console.debug(timestamp(), "W", util.format(format, ...rest));
|
||||||
},
|
},
|
||||||
error(...args) {
|
error(...args) {
|
||||||
if (LogLevels.error > logLevel) return;
|
if (LogLevels.error > logLevel) return;
|
||||||
console.error(timestamp(), "E", ...args);
|
const [format, ...rest] = args;
|
||||||
|
console.debug(timestamp(), "E", util.format(format, ...rest));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -71,7 +74,8 @@ const logger = {
|
||||||
/** @type Circuit */
|
/** @type Circuit */
|
||||||
let circuit;
|
let circuit;
|
||||||
try {
|
try {
|
||||||
const app_cache_path = path.join(process.env.HOME, '.cache', 'mini-tor-js');
|
const home = process.env.HOME || process.env.USERPROFILE;
|
||||||
|
const app_cache_path = path.join(home, '.cache', 'mini-tor-js');
|
||||||
const cached_consensus_path = path.join(app_cache_path, 'cached-consensus')
|
const cached_consensus_path = path.join(app_cache_path, 'cached-consensus')
|
||||||
fs.mkdirSync(app_cache_path, { recursive: true });
|
fs.mkdirSync(app_cache_path, { recursive: true });
|
||||||
// Load consensus
|
// Load consensus
|
||||||
|
@ -131,7 +135,13 @@ const logger = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/** @type TorStream */
|
/** @type TorStream */
|
||||||
const tor_stream = await circuit.create_stream(uri.hostname, uri.port);
|
let tor_stream;
|
||||||
|
if (uri.hostname.endsWith('.onion')) {
|
||||||
|
const onion = uri.hostname.substr(0, uri.hostname.length - 6);
|
||||||
|
tor_stream = await circuit.create_onion_stream(onion, uri.port);
|
||||||
|
} else {
|
||||||
|
tor_stream = await circuit.create_stream(uri.hostname, uri.port);
|
||||||
|
}
|
||||||
if (!tor_stream) {
|
if (!tor_stream) {
|
||||||
logger.error("Could not create stream to", uri.href);
|
logger.error("Could not create stream to", uri.href);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
const HiddenServiceConnector = require('./HiddenServiceConnector');
|
||||||
const CircuitNode = require('./CircuitNode');
|
const CircuitNode = require('./CircuitNode');
|
||||||
const Cell = require('./Cell')
|
const Cell = require('./Cell')
|
||||||
const RelayCell = require('./RelayCell')
|
const RelayCell = require('./RelayCell')
|
||||||
const Stream = require('./TorStream')
|
const Stream = require('./TorStream')
|
||||||
|
const {sha1} = require('../utils/crypto')
|
||||||
const { parseIp } = require('../utils/net')
|
const { parseIp } = require('../utils/net')
|
||||||
const { defer } = require('../utils/time')
|
const { defer } = require('../utils/time')
|
||||||
|
|
||||||
|
@ -49,6 +51,10 @@ class Circuit {
|
||||||
return this._node_list;
|
return this._node_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get tor_socket() {
|
||||||
|
return this._socket;
|
||||||
|
}
|
||||||
|
|
||||||
get state() { return this._state };
|
get state() { return this._state };
|
||||||
|
|
||||||
set state(state) {
|
set state(state) {
|
||||||
|
@ -417,7 +423,20 @@ class Circuit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extend(or, handshake_type) {
|
async create_onion_stream(onion, port) {
|
||||||
|
const hidden_service_connector = new HiddenServiceConnector(this, onion);
|
||||||
|
|
||||||
|
return await hidden_service_connector.connect()
|
||||||
|
? this.create_stream(onion, port)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
create_dir_stream() {
|
||||||
|
//TODO
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
extend(or, handshake_type = 'ntor') {
|
||||||
switch (handshake_type) {
|
switch (handshake_type) {
|
||||||
case 'ntor':
|
case 'ntor':
|
||||||
return this._extend_ntor(or);
|
return this._extend_ntor(or);
|
||||||
|
@ -615,6 +634,132 @@ class Circuit {
|
||||||
(this._waits[desired_state] = this._waits[desired_state] || []).push(d);
|
(this._waits[desired_state] = this._waits[desired_state] || []).push(d);
|
||||||
return d.promise;
|
return d.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async rendezvous_establish(rendezvous_cookie) {
|
||||||
|
//TODO
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
async rendezvous_introduce(rendezvous_circuit, rendezvous_cookie) {
|
||||||
|
//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;
|
||||||
|
|
||||||
|
this._logger.debug("circuit::rendezvous_introduce() [or: %s, state: introducing]", introduction_point.name);
|
||||||
|
this.state = States.rendezvous_introducing;
|
||||||
|
|
||||||
|
this._logger.debug("circuit::rendezvous_introduce() [or: %s, state: completing]", introduction_point.name);
|
||||||
|
rendezvous_circuit.state = States.rendezvous_completing;
|
||||||
|
|
||||||
|
//
|
||||||
|
// payload of the RELAY_COMMAND_INTRODUCE1
|
||||||
|
// command:
|
||||||
|
//
|
||||||
|
// PK_ID Identifier for Bob's PK [20 octets]
|
||||||
|
// VER Version byte: set to 2. [1 octet]
|
||||||
|
// IP Rendezvous point's address [4 octets]
|
||||||
|
// PORT Rendezvous point's OR port [2 octets]
|
||||||
|
// ID Rendezvous point identity ID [20 octets]
|
||||||
|
// KLEN Length of onion key [2 octets]
|
||||||
|
// KEY Rendezvous point onion key [KLEN octets]
|
||||||
|
// RC Rendezvous cookie [20 octets]
|
||||||
|
// g^x Diffie-Hellman data, part 1 [128 octets]
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// compute PK_ID, aka hash of the service key.
|
||||||
|
//
|
||||||
|
const service_key_hash = sha1(introduction_point.service_key);
|
||||||
|
|
||||||
|
//
|
||||||
|
// create rest of the payload in separate buffer;
|
||||||
|
// it will be encrypted.
|
||||||
|
//
|
||||||
|
const handshake_bytes = Buffer.alloc(
|
||||||
|
1 + // version
|
||||||
|
4 + // ip address
|
||||||
|
2 + // port
|
||||||
|
20 + // identity_fingerprint
|
||||||
|
2 + // onion key size
|
||||||
|
32 + // onion key
|
||||||
|
20 + // rendezvous cookie
|
||||||
|
128); // DH
|
||||||
|
|
||||||
|
//io::memory_stream handshake_stream(handshake_bytes);
|
||||||
|
//io::stream_wrapper handshake_buffer(handshake_stream, endianness::big_endian);
|
||||||
|
|
||||||
|
rendezvous_circuit._extend_node = new CircuitNode(this, introduction_point, circuit_node_type::introduction_point);
|
||||||
|
|
||||||
|
handshake_buffer.write(static_cast<uint8_t>(2));
|
||||||
|
handshake_buffer.write(swap_endianness(introducee->get_ip_address().to_int()));
|
||||||
|
handshake_buffer.write(introducee->get_or_port());
|
||||||
|
handshake_buffer.write(introducee->get_identity_fingerprint());
|
||||||
|
handshake_buffer.write(static_cast<payload_size_type>(introducee->get_onion_key().get_size()));
|
||||||
|
handshake_buffer.write(introducee->get_onion_key());
|
||||||
|
handshake_buffer.write(rendezvous_cookie);
|
||||||
|
handshake_buffer.write(rendezvous_circuit->_extend_node->get_key_agreement().get_public_key());
|
||||||
|
|
||||||
|
const handshake_encrypted = hybrid_encryption::encrypt(
|
||||||
|
handshake_bytes,
|
||||||
|
introduction_point.service_key);
|
||||||
|
|
||||||
|
//
|
||||||
|
// compose the final payload.
|
||||||
|
//
|
||||||
|
const relay_payload_bytes = Buffer.concat([
|
||||||
|
service_key_hash,
|
||||||
|
handshake_encrypted
|
||||||
|
], service_key_hash.length + handshake_encrypted.length);
|
||||||
|
|
||||||
|
//
|
||||||
|
// send the cell.
|
||||||
|
//
|
||||||
|
this.send_relay_cell(
|
||||||
|
0,
|
||||||
|
Cell.commands.relay_command_introduce1,
|
||||||
|
relay_payload_bytes);
|
||||||
|
|
||||||
|
if (await this.wait_for_state(States.rendezvous_introduced))
|
||||||
|
{
|
||||||
|
this._logger.debug("circuit::rendezvous_introduce() [or: %s, state: introduced]", introduction_point.name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._logger.error("circuit::rendezvous_introduce() [or: %s, is_rendezvous_introduced() == false]", introduction_point.name);
|
||||||
|
|
||||||
|
//
|
||||||
|
// we cannot expect the rendezvous will be completed.
|
||||||
|
//
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await rendezvous_circuit.wait_for_state(States.rendezvous_completed))
|
||||||
|
{
|
||||||
|
this._logger.debug("circuit::rendezvous_introduce() [or: %s, state: completed]", introduction_point.name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._logger.error("circuit::rendezvous_introduce() [or: %s, is_rendezvous_completed() == false]", introduction_point.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is_ready() {
|
||||||
|
return this.state === States.ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_rendezvous_established() {
|
||||||
|
return this.state === States.rendezvous_established;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_rendezvous_completed() {
|
||||||
|
return this.state === States.rendezvous_completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_rendezvous_introduced() {
|
||||||
|
return this.state === States.rendezvous_introduced;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Circuit;
|
module.exports = Circuit;
|
||||||
|
|
|
@ -18,7 +18,7 @@ const authorities = [
|
||||||
authority_onion_router("moria1", "128.31.0.34", 9101, 9131),
|
authority_onion_router("moria1", "128.31.0.34", 9101, 9131),
|
||||||
//authority_onion_router("tor26", "86.59.21.38", 443, 80},
|
//authority_onion_router("tor26", "86.59.21.38", 443, 80},
|
||||||
authority_onion_router("bastet", "204.13.164.118", 443, 80),
|
authority_onion_router("bastet", "204.13.164.118", 443, 80),
|
||||||
authority_onion_router("maatuska", "171.25.193.9", 80, 443),
|
//authority_onion_router("maatuska", "171.25.193.9", 80, 443),
|
||||||
authority_onion_router("dannenberg", "193.23.244.244", 443, 80),
|
authority_onion_router("dannenberg", "193.23.244.244", 443, 80),
|
||||||
authority_onion_router("Faravahar", "154.35.175.225", 443, 80),
|
authority_onion_router("Faravahar", "154.35.175.225", 443, 80),
|
||||||
authority_onion_router("gabelmoo", "131.188.40.189", 443, 80),
|
authority_onion_router("gabelmoo", "131.188.40.189", 443, 80),
|
||||||
|
@ -197,7 +197,7 @@ class Consensus {
|
||||||
port = router.dir_port;
|
port = router.dir_port;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._logger.debug(`consensus::download_from_random_authority() [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);
|
return get('http:', ip, port, path);
|
||||||
}
|
}
|
||||||
|
@ -236,6 +236,14 @@ class Consensus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Buffer} identity_fingerprint
|
||||||
|
* @returns {OnionRouter}
|
||||||
|
*/
|
||||||
|
get_onion_router_by_identity_fingerprint(identity_fingerprint) {
|
||||||
|
return this._onion_router_map[identity_fingerprint.toString('hex')];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Consensus;
|
module.exports = Consensus;
|
||||||
|
|
346
src/tor/HiddenServiceConnector.js
Normal file
346
src/tor/HiddenServiceConnector.js
Normal file
|
@ -0,0 +1,346 @@
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const base32 = require('base32')
|
||||||
|
const OnionRouter = require('./OnionRouter')
|
||||||
|
const {get} = require('../utils/http')
|
||||||
|
const {time} = require('../utils/time')
|
||||||
|
const {sha1} = require('../utils/crypto')
|
||||||
|
|
||||||
|
class HiddenServiceConnector {
|
||||||
|
/** @type Consensus */
|
||||||
|
_consensus = null;
|
||||||
|
/** @type OnionRouter[] */
|
||||||
|
_introduction_point_list = [];
|
||||||
|
/** @type OnionRouter[] */
|
||||||
|
_responsible_directory_list = [];
|
||||||
|
/** @type Buffer */
|
||||||
|
_rendezvous_cookie = null;
|
||||||
|
|
||||||
|
constructor (rendezvous_circuit, onion) {
|
||||||
|
this._rendezvous_circuit = rendezvous_circuit;
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
this.find_responsible_directories();
|
||||||
|
|
||||||
|
if (this._responsible_directory_list.length)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// create rendezvous cookie.
|
||||||
|
//
|
||||||
|
this._rendezvous_cookie = crypto.randomBytes(20);
|
||||||
|
|
||||||
|
//
|
||||||
|
// establish rendezvous.
|
||||||
|
//
|
||||||
|
await this._rendezvous_circuit.rendezvous_establish(this._rendezvous_cookie);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
await this.introduce();
|
||||||
|
|
||||||
|
if (this._rendezvous_circuit.is_rendezvous_completed())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
find_responsible_directories() {
|
||||||
|
//
|
||||||
|
// rend-spec.txt
|
||||||
|
// 1.4.
|
||||||
|
// At any time, there are 6 hidden service directories responsible for
|
||||||
|
// keeping replicas of a descriptor; they consist of 2 sets of 3 hidden
|
||||||
|
// service directories with consecutive onion IDs. Bob's OP learns about
|
||||||
|
// the complete list of hidden service directories by filtering the
|
||||||
|
// consensus status document received from the directory authorities. A
|
||||||
|
// hidden service directory is deemed responsible for a descriptor ID if
|
||||||
|
// it has the HSDir flag and its identity digest is one of the first three
|
||||||
|
// identity digests of HSDir relays following the descriptor ID in a
|
||||||
|
// circular list. A hidden service directory will only accept a descriptor
|
||||||
|
// whose timestamp is no more than three days before or one day after the
|
||||||
|
// current time according to the directory's clock.
|
||||||
|
//
|
||||||
|
|
||||||
|
this._responsible_directory_list = [];
|
||||||
|
|
||||||
|
const directory_list = this._consensus.get_onion_routers_by_criteria({
|
||||||
|
flags: OnionRouter.hsdir
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// search for the 2 sets of 3 hidden service directories.
|
||||||
|
//
|
||||||
|
for (let replica = 0; replica < 2; replica++)
|
||||||
|
{
|
||||||
|
const descriptor_id = this.get_descriptor_id(replica);
|
||||||
|
|
||||||
|
const index = directory_list.findIndex(x => Buffer.compare(x.identity_fingerprint, descriptor_id) < 0);
|
||||||
|
|
||||||
|
for (let i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
this._responsible_directory_list.push(directory_list[(index + i) % directory_list.length]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_secret_id(replica) {
|
||||||
|
const permanent_id_byte = this._permanent_id[0];
|
||||||
|
|
||||||
|
//
|
||||||
|
// rend-spec.txt
|
||||||
|
// 1.3.
|
||||||
|
//
|
||||||
|
// "time-period" changes periodically as a function of time and
|
||||||
|
// "permanent-id". The current value for "time-period" can be calculated
|
||||||
|
// using the following formula:
|
||||||
|
//
|
||||||
|
// time-period = (current-time + permanent-id-byte * 86400 / 256)
|
||||||
|
// / 86400
|
||||||
|
//
|
||||||
|
const time_period = (time() + (permanent_id_byte * 86400 / 256)) / 86400;
|
||||||
|
|
||||||
|
const secret_bytes = Buffer.alloc(5);
|
||||||
|
secret_bytes.writeInt32BE(time_period);
|
||||||
|
secret_bytes.writeUInt8(replica);
|
||||||
|
|
||||||
|
return sha1(secret_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param replica
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
get_descriptor_id(replica) {
|
||||||
|
const secret_id = this.get_secret_id(replica);
|
||||||
|
|
||||||
|
const descriptor_id_bytes = Buffer.concat([
|
||||||
|
this._permanent_id,
|
||||||
|
secret_id
|
||||||
|
], this._permanent_id.length + secret_id.length);
|
||||||
|
|
||||||
|
return sha1(descriptor_id_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
//
|
||||||
|
// create new circuit and extend it with responsible directory.
|
||||||
|
//
|
||||||
|
this._logger.info(
|
||||||
|
"\tCreating circuit for hidden service (try #%i), connecting to '%s' (%s:%i)",
|
||||||
|
i + 1,
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (!directory_circuit)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// either tor socket is destroyed
|
||||||
|
// or we couldn't create circuit with the first
|
||||||
|
// onion router. try it again anyway.
|
||||||
|
// but if the socket is destroyed, we're out of luck.
|
||||||
|
//
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._logger.info(
|
||||||
|
"\tExtending circuit for hidden service, connecting to responsible directory '%s' (%s:%u)",
|
||||||
|
responsible_directory.name,
|
||||||
|
responsible_directory.ip,
|
||||||
|
responsible_directory.or_port);
|
||||||
|
|
||||||
|
await directory_circuit.extend(responsible_directory);
|
||||||
|
|
||||||
|
if (!directory_circuit.is_ready())
|
||||||
|
{
|
||||||
|
this._logger.warn("\tError while extending the directory circuit");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// circuit must have exactly 2 nodes now.
|
||||||
|
//
|
||||||
|
//mini_assert(directory_circuit->get_circuit_node_list_size() == 2);
|
||||||
|
|
||||||
|
this._logger.info("\tExtended...");
|
||||||
|
|
||||||
|
const replica = i >= 3;
|
||||||
|
|
||||||
|
//
|
||||||
|
// create the directory stream on the directory circuit.
|
||||||
|
//
|
||||||
|
const directory_stream = directory_circuit.create_dir_stream();
|
||||||
|
|
||||||
|
if (!directory_stream)
|
||||||
|
{
|
||||||
|
this._logger.warn("\tError while establishing the directory stream");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// request the hidden service descriptor.
|
||||||
|
//
|
||||||
|
const descriptor_path = "/tor/rendezvous2/" + base32.encode(this.get_descriptor_id(replica));
|
||||||
|
|
||||||
|
this._logger.debug(
|
||||||
|
"hidden_service::fetch_hidden_service_descriptor() [path: %s]",
|
||||||
|
descriptor_path);
|
||||||
|
|
||||||
|
this._logger.info("\tSending request for hidden service descriptor...");
|
||||||
|
|
||||||
|
const hidden_service_descriptor = await get(
|
||||||
|
'http:',
|
||||||
|
responsible_directory.ip,
|
||||||
|
responsible_directory.dir_port.toString(),
|
||||||
|
descriptor_path,
|
||||||
|
directory_stream);
|
||||||
|
|
||||||
|
this._logger.info("\tHidden service descriptor received...");
|
||||||
|
|
||||||
|
//
|
||||||
|
// parse hidden service descriptor.
|
||||||
|
//
|
||||||
|
if (!hidden_service_descriptor &&
|
||||||
|
!hidden_service_descriptor.includes("404 Not found"))
|
||||||
|
{
|
||||||
|
this._logger.info("\tHidden service descriptor is valid...");
|
||||||
|
|
||||||
|
let lines = hidden_service_descriptor.split('\n');
|
||||||
|
let desc = '', capture = false;
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line === '-----BEGIN MESSAGE-----') {
|
||||||
|
capture = true;
|
||||||
|
} else if (line === '-----END MESSAGE-----') {
|
||||||
|
capture = false;
|
||||||
|
break;
|
||||||
|
} else if (capture) {
|
||||||
|
desc += line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const introduction_point_list = [];
|
||||||
|
const introduction_point_desc = Buffer.from(desc, 'base64').toString();
|
||||||
|
lines = introduction_point_desc.split('\n');
|
||||||
|
let current_router, serviceKey = null;
|
||||||
|
for (const line of lines) {
|
||||||
|
const parts = line.split(' ');
|
||||||
|
if (parts[0] === 'introduction-point') {
|
||||||
|
const identity_fingerprint = Buffer.from(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) {
|
||||||
|
capture = true;
|
||||||
|
} else if (parts[0] === '-----END RSA PUBLIC KEY-----' && serviceKey !== null) {
|
||||||
|
current_router.service_key = serviceKey;
|
||||||
|
introduction_point_list.push(current_router);
|
||||||
|
capture = false;
|
||||||
|
serviceKey = null;
|
||||||
|
} else if (capture) {
|
||||||
|
serviceKey += line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//mini_assert(!parser.introduction_point_list.is_empty());
|
||||||
|
|
||||||
|
if (introduction_point_list.length)
|
||||||
|
{
|
||||||
|
this._introduction_point_list = introduction_point_list
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._logger.warn("\tHidden service descriptor contains no introduction points...");
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._logger.warn("\tHidden service descriptor is invalid...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async introduce() {
|
||||||
|
for (const introduction_point of this._introduction_point_list)
|
||||||
|
{
|
||||||
|
this._logger.info(
|
||||||
|
"\tCreating circuit for hidden service introduce, connecting to '%s' (%s:%i)",
|
||||||
|
this._socket.onion_router.name,
|
||||||
|
this._socket.onion_router.ip,
|
||||||
|
this._socket.onion_router.or_port);
|
||||||
|
|
||||||
|
/** @type Circuit */
|
||||||
|
const introduce_circuit = await this._socket.create_circuit();
|
||||||
|
|
||||||
|
if (!introduce_circuit)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// either tor socket is destroyed
|
||||||
|
// or we couldn't create circuit with the first
|
||||||
|
// onion router. try it again anyway.
|
||||||
|
// but if the socket is destroyed, we're out of luck.
|
||||||
|
//
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._logger.info("\tConnected...");
|
||||||
|
|
||||||
|
this._logger.info(
|
||||||
|
"\tExtending circuit to introduction point '%s' (%s:%u)",
|
||||||
|
introduction_point.name,
|
||||||
|
introduction_point.ip,
|
||||||
|
introduction_point.or_port);
|
||||||
|
|
||||||
|
await introduce_circuit.extend(introduction_point);
|
||||||
|
|
||||||
|
if (!introduce_circuit.is_ready())
|
||||||
|
{
|
||||||
|
this._logger.warn("\tError while extending the introduce circuit");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// circuit must have exactly 2 nodes now.
|
||||||
|
//
|
||||||
|
//mini_assert(introduce_circuit->get_circuit_node_list_size() == 2);
|
||||||
|
|
||||||
|
this._logger.info("\tExtended...");
|
||||||
|
this._logger.info("\tSending introduce...");
|
||||||
|
|
||||||
|
await introduce_circuit.rendezvous_introduce(this._rendezvous_circuit, this._rendezvous_cookie);
|
||||||
|
|
||||||
|
if (introduce_circuit.is_rendezvous_introduced())
|
||||||
|
{
|
||||||
|
this._logger.info("\tIntroduced successfully...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._logger.warn("\tIntroduce failed...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = HiddenServiceConnector;
|
|
@ -24,6 +24,7 @@ class OnionRouter {
|
||||||
_identity_fingerprint = null;
|
_identity_fingerprint = null;
|
||||||
_descriptor_fetched = false;
|
_descriptor_fetched = false;
|
||||||
_ntor_onion_key = null;
|
_ntor_onion_key = null;
|
||||||
|
_service_key = null;
|
||||||
|
|
||||||
constructor (consensus, nickname, ip, or_port, dir_port, identity_fingerprint) {
|
constructor (consensus, nickname, ip, or_port, dir_port, identity_fingerprint) {
|
||||||
this._consensus = consensus;
|
this._consensus = consensus;
|
||||||
|
@ -34,17 +35,20 @@ class OnionRouter {
|
||||||
this._identity_fingerprint = identity_fingerprint;
|
this._identity_fingerprint = identity_fingerprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() { return this._nickname };
|
get consensus() { return this._consensus; }
|
||||||
get ip() { return this._ip };
|
get name() { return this._nickname; }
|
||||||
get flags() { return this._flags };
|
get ip() { return this._ip; }
|
||||||
get or_port() { return this._or_port };
|
get flags() { return this._flags; }
|
||||||
get dir_port() { return this._dir_port };
|
get or_port() { return this._or_port; }
|
||||||
|
get dir_port() { return this._dir_port; }
|
||||||
set flags(flags) { this._flags = flags; }
|
set flags(flags) { this._flags = flags; }
|
||||||
get ntor_onion_key() {
|
get ntor_onion_key() {
|
||||||
if (!this._descriptor_fetched) throw new Error('forgot to call fetch_descriptor for onion router');
|
if (!this._descriptor_fetched) throw new Error('forgot to call fetch_descriptor for onion router');
|
||||||
return this._ntor_onion_key;
|
return this._ntor_onion_key;
|
||||||
}
|
}
|
||||||
get identity_fingerprint() { return this._identity_fingerprint; }
|
get identity_fingerprint() { return this._identity_fingerprint; }
|
||||||
|
set service_key(key) { this._service_key = key; }
|
||||||
|
get service_key() { return this._service_key; }
|
||||||
|
|
||||||
async fetch_descriptor() {
|
async fetch_descriptor() {
|
||||||
const descriptor = await this._consensus.get_onion_router_descriptor(this._identity_fingerprint);
|
const descriptor = await this._consensus.get_onion_router_descriptor(this._identity_fingerprint);
|
||||||
|
|
|
@ -34,6 +34,9 @@ class Socket {
|
||||||
this._logger = logger;
|
this._logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get onion_router() {
|
||||||
|
return this._onion_router;
|
||||||
|
}
|
||||||
get state() {
|
get state() {
|
||||||
return this._state;
|
return this._state;
|
||||||
}
|
}
|
||||||
|
@ -90,6 +93,10 @@ class Socket {
|
||||||
return this._socket && !this._socket.connecting;
|
return this._socket && !this._socket.connecting;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param handshake
|
||||||
|
* @returns {Promise<Circuit>}
|
||||||
|
*/
|
||||||
async create_circuit(handshake = 'ntor') {
|
async create_circuit(handshake = 'ntor') {
|
||||||
if (this.state !== States.ready)
|
if (this.state !== States.ready)
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,3 +7,13 @@ function hmac_sha256(key, data) {
|
||||||
return h.update(data).digest();
|
return h.update(data).digest();
|
||||||
}
|
}
|
||||||
exports.hmac_sha256 = hmac_sha256;
|
exports.hmac_sha256 = hmac_sha256;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {BinaryLike} data
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
function sha1(data) {
|
||||||
|
const h = crypto.createHash('sha1');
|
||||||
|
return h.update(data).digest();
|
||||||
|
}
|
||||||
|
exports.sha1 = sha1;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user