├── .gitignore ├── README.md ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tor 3 | .onionservice 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Onion Service 2 | ============= 3 | 4 | Binds and listens for connections on a tor Onion Service. Meant to behave 5 | identically to an `http.Server`. 6 | 7 | ```javascript 8 | var onion = require('onionservice'); 9 | 10 | var server = onion.createServer(function (request, response) { 11 | response.end('Hello, Onion!'); 12 | }).listen(80); 13 | ``` 14 | 15 | # Safety 16 | 17 | Node.js is not known to be a particularly hardened code base, and it is 18 | inadvisable to use this code base in security-critical situations. 19 | It is also worth understanding the NPM package dependencies in a code base and 20 | the risks you take on by relying on a code base with varied lineage. That being 21 | said, onionservice does not expose you to particularly more risk than running a 22 | public Node web server. Just remember that connections from tor will be 23 | connected from the local binary - don't use `remoteAddress` for permissions! 24 | 25 | OnionService can still be an effective tool! In addition 26 | to hidden web services, Onion Services provide NAT avoidance capabilities and 27 | the ability to easily construct P2P overlays. In these circumstances, use a 28 | random high port for listening to limit risk of crawling, and don't publish 29 | addresses publicly. 30 | 31 | ## `createServer([options], [handler])` 32 | 33 | The key material used for construction of the onion service will be directory- 34 | local, and will remain stable across runs. It can be configured with additional 35 | keys in the `options` object, which has the following defaults: 36 | 37 | ```javascript 38 | { 39 | cacheKeyMaterial: true, 40 | keyMaterial: path.join(process.cwd(), ".onionservice") 41 | } 42 | ``` 43 | 44 | If `cacheKeyMaterial` is `false`, a new onion address will be generated each 45 | time the application is run. 46 | 47 | If `keyMaterial` is a string, it will be interpreted as a file path, and used 48 | to read and write key material. If it is a (duplex) stream, key material will be 49 | read from the stream, and updated material will be written back. 50 | 51 | ## `server.address()` 52 | Returns the bound onion address, the family name, and the port of the server. 53 | Useful for learning the generated onion. Returns an object with three 54 | properties, e.g. 55 | `{ port: 80, family: 'Onion', address: '3g2upl4pq6kufc4m.onion'}` 56 | 57 | Example: 58 | ```javascript 59 | var server = onion.createServer((req, resp) => { 60 | resp.end('Onion\n'); 61 | }); 62 | 63 | // Listen on a random port. 64 | server.listen(() => { 65 | address = server.address(); 66 | console.log('Opened server on %j', address); 67 | }); 68 | ``` 69 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Monkeypatching of the http.Server interface to bind locally on a random 3 | * localhost-only high-port - fronted by a tor onoon service. 4 | */ 5 | 6 | var connecttor = require('connecttor'); 7 | var split = require('split'); 8 | var fs = require('fs'); 9 | var http = require('http'); 10 | var path = require('path'); 11 | 12 | function toNumber(x) { return (x = Number(x)) >= 0 ? x : false; } 13 | 14 | /** 15 | * Read a file or stream to recover a stored keyblob. 16 | */ 17 | var getKeyBlob = function (stream, cb) { 18 | if (typeof stream === "string") { 19 | if (fs.existsSync(stream)) { 20 | stream = fs.createReadStream(stream); 21 | } else { 22 | return cb(""); 23 | } 24 | } 25 | var data = ""; 26 | stream.on('readable', function () { 27 | var dat = stream.read(); 28 | if (!dat || dat.length === 0) { 29 | cb(data); 30 | } else { 31 | data =+ dat.toString("hex"); 32 | } 33 | }); 34 | }; 35 | 36 | var setKeyBlob = function (stream, blob) { 37 | if (typeof stream === "string") { 38 | stream = fs.createWriteStream(stream); 39 | } 40 | stream.end(blob); 41 | }; 42 | 43 | var onListening = function () { 44 | this._onionInternalAddress = this.address(); 45 | 46 | this.address = function () { 47 | return this._onionAddress || {}; 48 | }; 49 | 50 | this._onionAttach = function (key) { 51 | var cmd = "ADD_ONION "; 52 | if (key && key.length) { 53 | cmd += key + " "; 54 | } else { 55 | cmd += "NEW:BEST "; 56 | } 57 | if (this._onionOpts.cacheKeyMaterial === false) { 58 | cmd += "Flags=DiscardPK "; 59 | } 60 | cmd += "Port=" + this._onionPort + "," + this._onionInternalAddress.port + "\r\n"; 61 | this._onionSocket.write(cmd); 62 | }.bind(this); 63 | 64 | connecttor.connect(function (controlSocket) { 65 | if (!controlSocket) { 66 | this.emit('error', new Error("Onion Connection failed.")); 67 | this.close(); 68 | } 69 | 70 | this._onionSocket = controlSocket; 71 | // Tor will associate the onion with our control socket that asked for it. 72 | // Closing at the same time as the server means they clean up together. 73 | this._onionSocket.on('close', this.close.bind(this)); 74 | this.on('close', this._onionSocket.end.bind(this._onionSocket)); 75 | 76 | this._onionSocket.pipe(split()).on("data", function (line) { 77 | if (line.indexOf("250-ServiceID=") === 0) { 78 | this._onionAddress = { 79 | family: 'Onion', 80 | address: line.substr(14) + ".onion", 81 | port: this._onionPort 82 | }; 83 | } else if (line.indexOf("250-PrivateKey=") === 0) { 84 | var keyBlob = line.substr(15).trim(); 85 | setKeyBlob(this._onionOpts.keyMaterial, keyBlob); 86 | } else if (line.indexOf("250 OK") === 0) { 87 | this.emit('listening'); 88 | } else { 89 | // Unexpected condition. raise. 90 | this.emit('error', new Error(line)); 91 | } 92 | }.bind(this)); 93 | 94 | if (this._onionOpts.keyMaterial) { 95 | getKeyBlob(this._onionOpts.keyMaterial, this._onionAttach.bind(this)); 96 | } else { 97 | this._onionAttach(); 98 | } 99 | }.bind(this)); 100 | }; 101 | 102 | var listen = function(httpListen, options) { 103 | // based on https://github.com/nodejs/node/blob/v5.4.0/lib/net.js#L1312 104 | var lastArg = arguments[arguments.length - 1]; 105 | if (typeof lastArg === 'function') { 106 | this.once('listening', lastArg); 107 | } 108 | 109 | this._onionPort = toNumber(arguments[2]); 110 | if (this._onionPort === 0) { 111 | this._onionPort = Math.floor(Math.random() * 0xFFFF); 112 | } 113 | this._onionOpts = options || {}; 114 | if (!this._onionOpts.keyMaterial) { 115 | this._onionOpts.keyMaterial = path.join(process.cwd(), ".onionservice"); 116 | } 117 | 118 | // capture first 'listening' event to instead cue starting the onion. 119 | var realEmit = this.emit; 120 | this.emit = function() { 121 | if (arguments[0] === 'listening') { 122 | onListening.call(this); 123 | this.emit = realEmit; 124 | } else { 125 | realEmit.apply(this, arguments); 126 | } 127 | }.bind(this); 128 | 129 | // Assign arbitrary high port on localhost for the http server. 130 | httpListen.call(this, 0, '127.0.0.1'); 131 | }; 132 | 133 | var createServer = function (options, requestListener) { 134 | if (typeof options === 'function' && !requestListener) { 135 | requestListener = options; 136 | options = {}; 137 | } 138 | var server = http.createServer(requestListener); 139 | server.listen = listen.bind(server, server.listen, options); 140 | return server; 141 | }; 142 | 143 | exports.createServer = createServer; 144 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "onionservice", 3 | "version": "0.3.0", 4 | "description": "An Onion Service server for Node", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/willscott/onionservice" 12 | }, 13 | "keywords": [ 14 | "tor", 15 | "privacy", 16 | "anonymity", 17 | "onion", 18 | "hiddenservice", 19 | "onionservice", 20 | "server" 21 | ], 22 | "author": "Will Scott (https://wills.co.tt)", 23 | "license": "GPL-3.0", 24 | "bugs": { 25 | "url": "https://github.com/willscott/onionservice/issues" 26 | }, 27 | "dependencies": { 28 | "connecttor": "^0.2.1", 29 | "split": "^1.0.0" 30 | } 31 | } 32 | --------------------------------------------------------------------------------