package/package.json000644 000765 000024 0000001573 12611657554013037 0ustar00000000 000000 { "name": "starttls", "description": "Upgrade a regular `net.Stream` connection to a secure `tls` connection.", "version": "1.0.1", "main": "lib/starttls.js", "homepage": "https://github.com/mattcg/starttls", "implements": ["CommonJS/Modules/1.0"], "contributors": [ { "name": "Nathan Rajlich", "email": "nathan@tootallnate.net" }, { "name": "Andris Reinman", "email": "andris.reinman@gmail.com" }, { "name": "Matthew Caruana Galizia", "email": "m@m.cg" } ], "keywords": [ "tls", "stream", "net", "upgrade", "ssl" ], "scripts": { "test": "make test" }, "devDependencies": { "mocha": "1.x" }, "bugs": { "url": "https://github.com/mattcg/starttls/issues" }, "repository": { "type": "git", "url": "https://github.com/mattcg/starttls.git" }, "licenses": [ { "type": "MIT", "url": "http://opensource.org/licenses/MIT" } ] } package/.npmignore000644 000765 000024 0000000030 12473225644012530 0ustar00000000 000000 .DS_Store node_modules/ package/README.md000644 000765 000024 0000005463 12473225644012027 0ustar00000000 000000 # Start TLS # [![Build Status](https://travis-ci.org/mattcg/starttls.png?branch=master)](https://travis-ci.org/mattcg/starttls) Upgrade a regular [`net.Stream`](http://nodejs.org/api/net.html#net_class_net_socket) connection to a secure [`tls`](http://nodejs.org/api/tls.html) connection. Based on code by [Andris Reinman](https://github.com/andris9/rai/blob/master/lib/starttls.js), itself based on an older version by [Nathan Rajlich](https://gist.github.com/TooTallNate/848444). ## Usage ## This library has one method and accepts either an options hash or a prepared socket as the first argument. It returns a [`SecurePair`](http://nodejs.org/api/tls.html#tls_class_securepair). ### starttls(options, [onSecure]), starttls(socket, [onSecure]) ### The following options are supported: - `socket` - if not provided, a socket will be created using [`net.createConnection`](http://nodejs.org/api/net.html#net_net_createconnection_options_connectionlistener) - `host` - used to perform automatic certificate identity checking, to guard against MITM attacks - `port` - only used to create a socket (along with the `host` option) if `socket` is not provided - `pair` - if you want to provide your own [`SecurePair`](http://nodejs.org/api/tls.html#tls_class_securepair) object The `onSecure` callback is optional and receives `null` or an error object as the first argument (see below for error cases). Within the callback context, `this` refers to the same [`SecurePair`](http://nodejs.org/api/tls.html#tls_class_securepair) object returned by `starttls`. ```javascript var net = require('net'); var starttls = require('starttls'); var options = { port: 21, host: example.com }; net.createConnection(options, function() { options.socket = this; starttls(options, function(err) { if (err) { // Something bad happened! return; } this.cleartext.write('garbage'); }); }); ``` You should always check for an error before writing to the stream to avoid man-in-the-middle attacks. Errors are produced in the following cases: - the certificate authority authorization check failed or was negative - the server identity check was negative If you only pass a socket object, server identity checking will not be performed automatically. In that case you should perform the check manually. ```javascript starttls(socket, function(err) { if (!tls.checkServerIdentity(host, this.cleartext.getPeerCertificate())) { // Hostname mismatch! // Report error and end connection... } }); ``` ## Example ## See [socks5-https-client](https://github.com/mattcg/socks5-https-client) for use-case. ## Tests ## Run `make test` or `npm test` to run tests. ## License ## Portions of this code copyright (c) 2012, Andris Reinman and copyright (c) 2011, Nathan Rajlich. Modified and redistributed under an [MIT license](http://mattcg.mit-license.org/). package/.travis.yml000644 000765 000024 0000000105 12473232350012635 0ustar00000000 000000 language: node_js node_js: - "0.12" - "0.10" script: make test package/Makefile000644 000765 000024 0000000252 12473225644012177 0ustar00000000 000000 test: lib/*.js node_modules ./node_modules/.bin/mocha \ --reporter dot \ --check-leaks \ --ui tdd node_modules: package.json npm install touch $@ .PHONY: test package/lib/starttls.js000644 000765 000024 0000010232 12524741165013520 0ustar00000000 000000 /** * Original: https://gist.github.com/TooTallNate/848444 * Adapted: https://github.com/andris9/rai/blob/master/lib/starttls.js * * @overview * @author Matthew Caruana Galizia * @author Andris Reinman * @author Nathan Rajlich * @copyright Copyright (c) 2012, Andris Reinman * @copyright Copyright (c) 2011, Nathan Rajlich * @license MIT * @preserve */ 'use strict'; /*jshint node:true*/ /*global exports:true*/ var net = require('net'); var tls = require('tls'); var crypto = require('crypto'); module.exports = exports = function(options, onSecure) { var socket, credentials, securePair; if (options instanceof net.Socket) { socket = options; options = { socket: socket }; } else if (options.socket) { socket = options.socket; } else { socket = options.socket = net.createConnection(options); } if (options.pair) { securePair = options.pair; } else { // Node v0.12.0 deprecated crypto.createCredentials. if (tls.createSecureContext) { credentials = tls.createSecureContext(); } else { credentials = crypto.createCredentials(); } securePair = tls.createSecurePair(credentials, false); options.pair = securePair; } // In Node < 0.9.0, socket.readable is undefined. if (socket.readable || undefined === socket.readable) { return startTls(options, onSecure); } // In Node > 0.9.0, if the socket is still unconnected then wait for connect. socket.once('connect', function() { startTls(options, onSecure); }); return securePair; }; function startTls(options, onSecure) { var socket, host, securePair, clearText; socket = options.socket; host = options.host; securePair = options.pair; socket.ondata = null; socket.removeAllListeners('data'); clearText = pipe(securePair, socket); securePair.once('secure', function() { var err; // A cleartext stream has the boolean property 'authorized' to determine if it was verified by the CA. If 'authorized' is false, a property 'authorizationError' is set on the stream. err = securePair.ssl.verifyError(); if (err) { clearText.authorized = false; clearText.authorizationError = err; } else { clearText.authorized = true; } // The callback parameter is optional. if (!onSecure) { return; } if (host) { err = tls.checkServerIdentity(host, clearText.getPeerCertificate()); // As of node v0.12.0, `tls.checkServerIdentity` returns an error object if there was an error, `undefined` otherwise. // On previous versions it returned `false` if there was an error and `true` otherwise. if (false === err) { err = new Error('Server identity mismatch: invalid certificate for ' + host + '.'); } else if (true === err) { err = null; } } onSecure.call(securePair, err); }); clearText._controlReleased = true; return securePair; } function forwardEvents(events, emitterSource, emitterDestination) { var i, l, event, handler, forwardEvent; forwardEvent = function() { this.emit.apply(this, arguments); }; for (i = 0, l = events.length; i < l; i++) { event = events[i]; handler = forwardEvent.bind(emitterDestination, event); emitterSource.on(event, handler); } } function removeEvents(events, emitterSource) { var i, l; for (i = 0, l = events.length; i < l; i++){ emitterSource.removeAllListeners(events[i]); } } function pipe(securePair, socket) { var clearText, onError, onClose, events; events = ['timeout', 'end', 'drain']; clearText = securePair.cleartext; onError = function(err) { if (clearText._controlReleased) { clearText.emit('error', err); } }; onClose = function() { socket.removeListener('error', onError); socket.removeListener('close', onClose); removeEvents(events, socket); }; // Forward event emissions from the socket to the cleartext stream. forwardEvents(events, socket, clearText); socket.on('error', onError); socket.on('close', onClose); securePair.on('error', function(err) { onError(err); }); securePair.encrypted.pipe(socket); socket.pipe(securePair.encrypted); securePair.fd = socket.fd; clearText.socket = socket; clearText.encrypted = securePair.encrypted; clearText.authorized = false; return clearText; } package/test/starttls.js000644 000765 000024 0000005002 12473232054013723 0ustar00000000 000000 /** * @overview * @author Matthew Caruana Galizia * @copyright Copyright (c) 2013, Matthew Caruana Galizia * @license MIT * @preserve */ 'use strict'; /*jshint node:true*/ /*global test, suite*/ var assert = require('assert'); var net = require('net'); var tls = require('tls'); var starttls = require('../lib/starttls'); suite('starttls tests', function() { var options; options = { host: 'www.google.com', port: 443 }; test('simple connect with prepared socket', function(done) { net.createConnection(options, function() { var pair; pair = starttls(this, function(err) { assert.ifError(err); assert(pair.cleartext.authorized); assert.ifError(pair.cleartext.authorizationError); assert(this === pair); done(); }); }); }); test('identity check with prepared socket', function(done) { net.createConnection(options, function() { var pair; pair = starttls(this, function(err) { var cert, check; cert = pair.cleartext.getPeerCertificate(); // Returns error or undefined on node v0.12.0 and true or false on previous versions. check = tls.checkServerIdentity(options.host, cert); assert.equal(check === true || typeof check === 'undefined', true); check = tls.checkServerIdentity('www.facebook.com', cert); assert.equal(check === false || check instanceof Error, true); done(); }); }); }); test('simple connect with options and prepared socket', function(done) { starttls({ socket: net.createConnection(options) }, function(err) { var pair = this; assert.ifError(err); assert(pair.cleartext); assert(pair.cleartext.authorized); assert.ifError(pair.cleartext.authorizationError); done(); }); }); test('simple connect with options', function(done) { starttls(options, function(err) { var pair = this; assert.ifError(err); assert(pair.cleartext); assert(pair.cleartext.authorized); assert.ifError(pair.cleartext.authorizationError); done(); }); }); test('host is checked', function(done) { var options; options = { // Take advantage of the fact that this domain has an SSL certificate error. host: 'projects.icij.org.s3.amazonaws.com', port: 443 }; starttls(options, function(err) { assert(err); done(); }); }); test('host is checked even if socket is provided', function(done) { var falseHost = 'www.facebook.com'; starttls({ host: falseHost, socket: net.createConnection(options) }, function(err) { assert(err); done(); }); }); });