starttls
Version:
Upgrade a regular `net.Stream` connection to a secure `tls` connection.
176 lines (136 loc) • 4.25 kB
JavaScript
/**
* 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
*/
;
/*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;
}