UNPKG

starttls

Version:

Upgrade a regular `net.Stream` connection to a secure `tls` connection.

176 lines (136 loc) 4.25 kB
/** * Original: https://gist.github.com/TooTallNate/848444 * Adapted: https://github.com/andris9/rai/blob/master/lib/starttls.js * * @overview * @author Matthew Caruana Galizia <m@m.cg> * @author Andris Reinman <andris.reinman@gmail.com> * @author Nathan Rajlich <nathan@tootallnate.net> * @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; }