throw new Error('node.js not compiled with openssl crypto support.');
}
-var debug = util.debuglog('tls');
+var debug = util.debuglog('tls-legacy');
function SlabBuffer() {
this.create();
}
return err;
};
+
+
+exports.pipe = function pipe(pair, socket) {
+ pair.encrypted.pipe(socket);
+ socket.pipe(pair.encrypted);
+
+ pair.encrypted.on('close', function() {
+ process.nextTick(function() {
+ // Encrypted should be unpiped from socket to prevent possible
+ // write after destroy.
+ pair.encrypted.unpipe(socket);
+ socket.destroy();
+ });
+ });
+
+ pair.fd = socket.fd;
+ var cleartext = pair.cleartext;
+ cleartext.socket = socket;
+ cleartext.encrypted = pair.encrypted;
+ cleartext.authorized = false;
+
+ // cycle the data whenever the socket drains, so that
+ // we can pull some more into it. normally this would
+ // be handled by the fact that pipe() triggers read() calls
+ // on writable.drain, but CryptoStreams are a bit more
+ // complicated. Since the encrypted side actually gets
+ // its data from the cleartext side, we have to give it a
+ // light kick to get in motion again.
+ socket.on('drain', function() {
+ if (pair.encrypted._pending)
+ pair.encrypted._writePending();
+ if (pair.cleartext._pending)
+ pair.cleartext._writePending();
+ pair.encrypted.read(0);
+ pair.cleartext.read(0);
+ });
+
+ function onerror(e) {
+ if (cleartext._controlReleased) {
+ cleartext.emit('error', e);
+ }
+ }
+
+ function onclose() {
+ socket.removeListener('error', onerror);
+ socket.removeListener('timeout', ontimeout);
+ }
+
+ function ontimeout() {
+ cleartext.emit('timeout');
+ }
+
+ socket.on('error', onerror);
+ socket.on('close', onclose);
+ socket.on('timeout', ontimeout);
+
+ return cleartext;
+}
var Timer = process.binding('timer_wrap').Timer;
var tls_wrap = process.binding('tls_wrap');
+// Lazy load
+var tls_legacy;
+
var debug = util.debuglog('tls');
function onhandshakestart() {
*/
function TLSSocket(socket, options) {
+ // Disallow wrapping TLSSocket in TLSSocket
+ assert(!(socket instanceof TLSSocket));
+
net.Socket.call(this, socket && {
handle: socket._handle,
allowHalfOpen: socket.allowHalfOpen,
return (cb) ? [options, cb] : [options];
}
+function legacyConnect(hostname, options, NPN, credentials) {
+ assert(options.socket);
+ if (!tls_legacy)
+ tls_legacy = require('_tls_legacy');
+
+ var pair = tls_legacy.createSecurePair(credentials,
+ false,
+ true,
+ !!options.rejectUnauthorized,
+ {
+ NPNProtocols: NPN.NPNProtocols,
+ servername: hostname
+ });
+ tls_legacy.pipe(pair, options.socket);
+ pair.cleartext._controlReleased = true;
+ pair.on('error', function(err) {
+ pair.cleartext.emit('error', err);
+ });
+
+ return pair;
+}
+
exports.connect = function(/* [port, host], options, cb */) {
var args = normalizeConnectArgs(arguments);
var options = args[0];
options = util._extend(defaults, options || {});
var hostname = options.servername || options.host || 'localhost',
- NPN = {};
+ NPN = {},
+ credentials = crypto.createCredentials(options);
tls.convertNPNProtocols(options.NPNProtocols, NPN);
- var socket = new TLSSocket(options.socket, {
- credentials: crypto.createCredentials(options),
- isServer: false,
- requestCert: true,
- rejectUnauthorized: options.rejectUnauthorized,
- NPNProtocols: NPN.NPNProtocols
- });
+ // Wrapping TLS socket inside another TLS socket was requested -
+ // create legacy secure pair
+ var socket;
+ var legacy;
+ var result;
+ if (options.socket instanceof TLSSocket) {
+ debug('legacy connect');
+ legacy = true;
+ socket = legacyConnect(hostname, options, NPN, credentials);
+ result = socket.cleartext;
+ } else {
+ legacy = false;
+ socket = new TLSSocket(options.socket, {
+ credentials: credentials,
+ isServer: false,
+ requestCert: true,
+ rejectUnauthorized: options.rejectUnauthorized,
+ NPNProtocols: NPN.NPNProtocols
+ });
+ result = socket;
+ }
+
+ if (socket._handle)
+ onHandle();
+ else
+ socket.once('connect', onHandle);
+
+ if (cb)
+ result.once('secureConnect', cb);
+
+ if (!options.socket) {
+ assert(!legacy);
+ var connect_opt;
+ if (options.path && !options.port) {
+ connect_opt = { path: options.path };
+ } else {
+ connect_opt = {
+ port: options.port,
+ host: options.host,
+ localAddress: options.localAddress
+ };
+ };
+ socket.connect(connect_opt);
+ }
+
+ return result;
function onHandle() {
- socket._releaseControl();
+ if (!legacy)
+ socket._releaseControl();
if (options.session)
socket.setSession(options.session);
- if (options.servername)
- socket.setServername(options.servername);
+ if (!legacy) {
+ if (options.servername)
+ socket.setServername(options.servername);
- socket._start();
+ socket._start();
+ }
socket.on('secure', function() {
var verifyError = socket.ssl.verifyError();
// Verify that server's identity matches it's certificate's names
if (!verifyError) {
- var validCert = tls.checkServerIdentity(hostname,
- socket.getPeerCertificate());
+ var cert = result.getPeerCertificate();
+ var validCert = tls.checkServerIdentity(hostname, cert);
if (!validCert) {
verifyError = new Error('Hostname/IP doesn\'t match certificate\'s ' +
'altnames');
}
if (verifyError) {
- socket.authorizationError = verifyError.message;
+ result.authorized = false;
+ result.authorizationError = verifyError.message;
if (options.rejectUnauthorized) {
- socket.emit('error', verifyError);
- socket.destroy();
+ result.emit('error', verifyError);
+ result.destroy();
return;
} else {
- socket.emit('secureConnect');
+ result.emit('secureConnect');
}
} else {
- socket.authorized = true;
- socket.emit('secureConnect');
+ result.authorized = true;
+ result.emit('secureConnect');
}
// Uncork incoming data
- socket.removeListener('end', onHangUp);
+ result.removeListener('end', onHangUp);
});
function onHangUp() {
socket.emit('error', error);
}
}
- socket.once('end', onHangUp);
- }
- if (socket._handle)
- onHandle();
- else
- socket.once('connect', onHandle);
-
- if (cb)
- socket.once('secureConnect', cb);
-
- if (!options.socket) {
- var connect_opt = (options.path && !options.port) ? {path: options.path} : {
- port: options.port,
- host: options.host,
- localAddress: options.localAddress
- };
- socket.connect(connect_opt);
+ result.once('end', onHangUp);
}
-
- return socket;
};
SSL_CTX_set_next_protos_advertised_cb(
sc->ctx_,
SSLWrap<Connection>::AdvertiseNextProtoCallback,
- NULL);
+ conn);
} else {
// Client should select protocol from advertised
// If server supports NPN
SSL_CTX_set_next_proto_select_cb(
sc->ctx_,
SSLWrap<Connection>::SelectNextProtoCallback,
- NULL);
+ conn);
}
#endif
--- /dev/null
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+if (!process.versions.openssl) {
+ console.error('Skipping because node compiled without OpenSSL.');
+ process.exit(0);
+}
+
+var common = require('../common');
+var fs = require('fs');
+var path = require('path');
+var net = require('net');
+var tls = require('tls');
+var assert = require('assert');
+
+var options, a, b, portA, portB;
+var gotHello = false;
+
+options = {
+ key: fs.readFileSync(path.join(common.fixturesDir, 'test_key.pem')),
+ cert: fs.readFileSync(path.join(common.fixturesDir, 'test_cert.pem'))
+};
+
+// the "proxy" server
+a = tls.createServer(options, function (socket) {
+ var options = {
+ host: '127.0.0.1',
+ port: b.address().port,
+ rejectUnauthorized: false
+ };
+ var dest = net.connect(options);
+ dest.pipe(socket);
+ socket.pipe(dest);
+});
+
+// the "target" server
+b = tls.createServer(options, function (socket) {
+ socket.end('hello');
+});
+
+process.on('exit', function () {
+ assert(gotHello);
+});
+
+a.listen(common.PORT, function () {
+ b.listen(common.PORT + 1, function () {
+ options = {
+ host: '127.0.0.1',
+ port: a.address().port,
+ rejectUnauthorized: false
+ };
+ var socket = tls.connect(options);
+ var ssl;
+ ssl = tls.connect({
+ socket: socket,
+ rejectUnauthorized: false
+ });
+ ssl.setEncoding('utf8');
+ ssl.once('data', function (data) {
+ assert.equal('hello', data);
+ gotHello = true;
+ });
+ ssl.on('end', function () {
+ ssl.end();
+ a.close();
+ b.close();
+ });
+ });
+});