wip accessing onion address
This commit is contained in:
		
							
								
								
									
										27
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										27
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -2,6 +2,14 @@
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "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": {
 | 
			
		||||
      "version": "0.0.4",
 | 
			
		||||
      "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",
 | 
			
		||||
      "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": {
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
 | 
			
		||||
      "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": {
 | 
			
		||||
    "base32": "0.0.6",
 | 
			
		||||
    "curve25519-js": "0.0.4",
 | 
			
		||||
    "futoin-hkdf": "^1.3.2",
 | 
			
		||||
    "tweetnacl": "^1.0.3"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								src/index.js
									
									
									
									
									
								
							@@ -1,8 +1,7 @@
 | 
			
		||||
const fs = require("fs");
 | 
			
		||||
const path = require("path");
 | 
			
		||||
const url = require("url");
 | 
			
		||||
const http = require("http");
 | 
			
		||||
const https = require("https");
 | 
			
		||||
const util = require("util");
 | 
			
		||||
const Consensus = require("./tor/Consensus");
 | 
			
		||||
const OR = require("./tor/OnionRouter");
 | 
			
		||||
const Socket = require("./tor/Socket");
 | 
			
		||||
@@ -24,19 +23,23 @@ function timestamp() {
 | 
			
		||||
const logger = {
 | 
			
		||||
  debug(...args) {
 | 
			
		||||
    if (LogLevels.debug > logLevel) return;
 | 
			
		||||
    console.debug(timestamp(), "D", ...args);
 | 
			
		||||
    const [format, ...rest] = args;
 | 
			
		||||
    console.debug(timestamp(), "D", util.format(format, ...rest));
 | 
			
		||||
  },
 | 
			
		||||
  info(...args) {
 | 
			
		||||
    if (LogLevels.info > logLevel) return;
 | 
			
		||||
    console.log(timestamp(), "I", ...args);
 | 
			
		||||
    const [format, ...rest] = args;
 | 
			
		||||
    console.debug(timestamp(), "I", util.format(format, ...rest));
 | 
			
		||||
  },
 | 
			
		||||
  warn(...args) {
 | 
			
		||||
    if (LogLevels.warn > logLevel) return;
 | 
			
		||||
    console.warn(timestamp(), "W", ...args);
 | 
			
		||||
    const [format, ...rest] = args;
 | 
			
		||||
    console.debug(timestamp(), "W", util.format(format, ...rest));
 | 
			
		||||
  },
 | 
			
		||||
  error(...args) {
 | 
			
		||||
    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 */
 | 
			
		||||
  let circuit;
 | 
			
		||||
  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')
 | 
			
		||||
    fs.mkdirSync(app_cache_path, { recursive: true });
 | 
			
		||||
    // Load consensus
 | 
			
		||||
@@ -131,7 +135,13 @@ const logger = {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    /** @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) {
 | 
			
		||||
      logger.error("Could not create stream to", uri.href);
 | 
			
		||||
      return;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
const HiddenServiceConnector = require('./HiddenServiceConnector');
 | 
			
		||||
const CircuitNode = require('./CircuitNode');
 | 
			
		||||
const Cell = require('./Cell')
 | 
			
		||||
const RelayCell = require('./RelayCell')
 | 
			
		||||
const Stream = require('./TorStream')
 | 
			
		||||
const {sha1} = require('../utils/crypto')
 | 
			
		||||
const { parseIp } = require('../utils/net')
 | 
			
		||||
const { defer } = require('../utils/time')
 | 
			
		||||
 | 
			
		||||
@@ -49,6 +51,10 @@ class Circuit {
 | 
			
		||||
    return this._node_list;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get tor_socket() {
 | 
			
		||||
    return this._socket;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get state() { return this._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) {
 | 
			
		||||
      case 'ntor':
 | 
			
		||||
        return this._extend_ntor(or);
 | 
			
		||||
@@ -615,6 +634,132 @@ class Circuit {
 | 
			
		||||
    (this._waits[desired_state] = this._waits[desired_state] || []).push(d);
 | 
			
		||||
    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;
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ const authorities = [
 | 
			
		||||
  authority_onion_router("moria1",     "128.31.0.34",     9101, 9131),
 | 
			
		||||
  //authority_onion_router("tor26",      "86.59.21.38",     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("Faravahar",  "154.35.175.225",  443,  80),
 | 
			
		||||
  authority_onion_router("gabelmoo",   "131.188.40.189",  443,  80),
 | 
			
		||||
@@ -197,7 +197,7 @@ class Consensus {
 | 
			
		||||
      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);
 | 
			
		||||
  }
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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;
 | 
			
		||||
  _descriptor_fetched = false;
 | 
			
		||||
  _ntor_onion_key = null;
 | 
			
		||||
  _service_key = null;
 | 
			
		||||
 | 
			
		||||
  constructor (consensus, nickname, ip, or_port, dir_port, identity_fingerprint) {
 | 
			
		||||
    this._consensus = consensus;
 | 
			
		||||
@@ -34,17 +35,20 @@ class OnionRouter {
 | 
			
		||||
    this._identity_fingerprint = identity_fingerprint;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get name() { return this._nickname };
 | 
			
		||||
  get ip() { return this._ip };
 | 
			
		||||
  get flags() { return this._flags };
 | 
			
		||||
  get or_port() { return this._or_port };
 | 
			
		||||
  get dir_port() { return this._dir_port };
 | 
			
		||||
  get consensus() { return this._consensus; }
 | 
			
		||||
  get name() { return this._nickname; }
 | 
			
		||||
  get ip() { return this._ip; }
 | 
			
		||||
  get flags() { return this._flags; }
 | 
			
		||||
  get or_port() { return this._or_port; }
 | 
			
		||||
  get dir_port() { return this._dir_port; }
 | 
			
		||||
  set flags(flags) { this._flags = flags; }
 | 
			
		||||
  get ntor_onion_key() {
 | 
			
		||||
    if (!this._descriptor_fetched) throw new Error('forgot to call fetch_descriptor for onion router');
 | 
			
		||||
    return this._ntor_onion_key;
 | 
			
		||||
  }
 | 
			
		||||
  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() {
 | 
			
		||||
    const descriptor = await this._consensus.get_onion_router_descriptor(this._identity_fingerprint);
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,9 @@ class Socket {
 | 
			
		||||
    this._logger = logger;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get onion_router() {
 | 
			
		||||
    return this._onion_router;
 | 
			
		||||
  }
 | 
			
		||||
  get state() {
 | 
			
		||||
    return this._state;
 | 
			
		||||
  }
 | 
			
		||||
@@ -90,6 +93,10 @@ class Socket {
 | 
			
		||||
    return this._socket && !this._socket.connecting;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param handshake
 | 
			
		||||
   * @returns {Promise<Circuit>}
 | 
			
		||||
   */
 | 
			
		||||
  async create_circuit(handshake = 'ntor') {
 | 
			
		||||
    if (this.state !== States.ready)
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,3 +7,13 @@ function hmac_sha256(key, data) {
 | 
			
		||||
  return h.update(data).digest();
 | 
			
		||||
}
 | 
			
		||||
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;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user