Skip to content
This repository was archived by the owner on Apr 22, 2023. It is now read-only.

Commit 42acbf8

Browse files
committed
tls: wrap tls inside tls using legacy API
Allow wrapping TLSSocket inside another TLSSocket, emulate it using SecurePair in legacy APIs. fix #6204
1 parent 970bdcc commit 42acbf8

File tree

4 files changed

+242
-43
lines changed

4 files changed

+242
-43
lines changed

lib/_tls_legacy.js

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ try {
3434
throw new Error('node.js not compiled with openssl crypto support.');
3535
}
3636

37-
var debug = util.debuglog('tls');
37+
var debug = util.debuglog('tls-legacy');
3838

3939
function SlabBuffer() {
4040
this.create();
@@ -820,3 +820,61 @@ SecurePair.prototype.error = function(returnOnly) {
820820
}
821821
return err;
822822
};
823+
824+
825+
exports.pipe = function pipe(pair, socket) {
826+
pair.encrypted.pipe(socket);
827+
socket.pipe(pair.encrypted);
828+
829+
pair.encrypted.on('close', function() {
830+
process.nextTick(function() {
831+
// Encrypted should be unpiped from socket to prevent possible
832+
// write after destroy.
833+
pair.encrypted.unpipe(socket);
834+
socket.destroy();
835+
});
836+
});
837+
838+
pair.fd = socket.fd;
839+
var cleartext = pair.cleartext;
840+
cleartext.socket = socket;
841+
cleartext.encrypted = pair.encrypted;
842+
cleartext.authorized = false;
843+
844+
// cycle the data whenever the socket drains, so that
845+
// we can pull some more into it. normally this would
846+
// be handled by the fact that pipe() triggers read() calls
847+
// on writable.drain, but CryptoStreams are a bit more
848+
// complicated. Since the encrypted side actually gets
849+
// its data from the cleartext side, we have to give it a
850+
// light kick to get in motion again.
851+
socket.on('drain', function() {
852+
if (pair.encrypted._pending)
853+
pair.encrypted._writePending();
854+
if (pair.cleartext._pending)
855+
pair.cleartext._writePending();
856+
pair.encrypted.read(0);
857+
pair.cleartext.read(0);
858+
});
859+
860+
function onerror(e) {
861+
if (cleartext._controlReleased) {
862+
cleartext.emit('error', e);
863+
}
864+
}
865+
866+
function onclose() {
867+
socket.removeListener('error', onerror);
868+
socket.removeListener('timeout', ontimeout);
869+
}
870+
871+
function ontimeout() {
872+
cleartext.emit('timeout');
873+
}
874+
875+
socket.on('error', onerror);
876+
socket.on('close', onclose);
877+
socket.on('timeout', ontimeout);
878+
879+
return cleartext;
880+
}

lib/_tls_wrap.js

Lines changed: 94 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ var util = require('util');
2929
var Timer = process.binding('timer_wrap').Timer;
3030
var tls_wrap = process.binding('tls_wrap');
3131

32+
// Lazy load
33+
var tls_legacy;
34+
3235
var debug = util.debuglog('tls');
3336

3437
function onhandshakestart() {
@@ -145,6 +148,9 @@ function onnewsession(key, session) {
145148
*/
146149

147150
function TLSSocket(socket, options) {
151+
// Disallow wrapping TLSSocket in TLSSocket
152+
assert(!(socket instanceof TLSSocket));
153+
148154
net.Socket.call(this, socket && {
149155
handle: socket._handle,
150156
allowHalfOpen: socket.allowHalfOpen,
@@ -645,6 +651,28 @@ function normalizeConnectArgs(listArgs) {
645651
return (cb) ? [options, cb] : [options];
646652
}
647653

654+
function legacyConnect(hostname, options, NPN, credentials) {
655+
assert(options.socket);
656+
if (!tls_legacy)
657+
tls_legacy = require('_tls_legacy');
658+
659+
var pair = tls_legacy.createSecurePair(credentials,
660+
false,
661+
true,
662+
!!options.rejectUnauthorized,
663+
{
664+
NPNProtocols: NPN.NPNProtocols,
665+
servername: hostname
666+
});
667+
tls_legacy.pipe(pair, options.socket);
668+
pair.cleartext._controlReleased = true;
669+
pair.on('error', function(err) {
670+
pair.cleartext.emit('error', err);
671+
});
672+
673+
return pair;
674+
}
675+
648676
exports.connect = function(/* [port, host], options, cb */) {
649677
var args = normalizeConnectArgs(arguments);
650678
var options = args[0];
@@ -656,57 +684,101 @@ exports.connect = function(/* [port, host], options, cb */) {
656684
options = util._extend(defaults, options || {});
657685

658686
var hostname = options.servername || options.host || 'localhost',
659-
NPN = {};
687+
NPN = {},
688+
credentials = crypto.createCredentials(options);
660689
tls.convertNPNProtocols(options.NPNProtocols, NPN);
661690

662-
var socket = new TLSSocket(options.socket, {
663-
credentials: crypto.createCredentials(options),
664-
isServer: false,
665-
requestCert: true,
666-
rejectUnauthorized: options.rejectUnauthorized,
667-
NPNProtocols: NPN.NPNProtocols
668-
});
691+
// Wrapping TLS socket inside another TLS socket was requested -
692+
// create legacy secure pair
693+
var socket;
694+
var legacy;
695+
var result;
696+
if (options.socket instanceof TLSSocket) {
697+
debug('legacy connect');
698+
legacy = true;
699+
socket = legacyConnect(hostname, options, NPN, credentials);
700+
result = socket.cleartext;
701+
} else {
702+
legacy = false;
703+
socket = new TLSSocket(options.socket, {
704+
credentials: credentials,
705+
isServer: false,
706+
requestCert: true,
707+
rejectUnauthorized: options.rejectUnauthorized,
708+
NPNProtocols: NPN.NPNProtocols
709+
});
710+
result = socket;
711+
}
712+
713+
if (socket._handle)
714+
onHandle();
715+
else
716+
socket.once('connect', onHandle);
717+
718+
if (cb)
719+
result.once('secureConnect', cb);
720+
721+
if (!options.socket) {
722+
assert(!legacy);
723+
var connect_opt;
724+
if (options.path && !options.port) {
725+
connect_opt = { path: options.path };
726+
} else {
727+
connect_opt = {
728+
port: options.port,
729+
host: options.host,
730+
localAddress: options.localAddress
731+
};
732+
};
733+
socket.connect(connect_opt);
734+
}
735+
736+
return result;
669737

670738
function onHandle() {
671-
socket._releaseControl();
739+
if (!legacy)
740+
socket._releaseControl();
672741

673742
if (options.session)
674743
socket.setSession(options.session);
675744

676-
if (options.servername)
677-
socket.setServername(options.servername);
745+
if (!legacy) {
746+
if (options.servername)
747+
socket.setServername(options.servername);
678748

679-
socket._start();
749+
socket._start();
750+
}
680751
socket.on('secure', function() {
681752
var verifyError = socket.ssl.verifyError();
682753

683754
// Verify that server's identity matches it's certificate's names
684755
if (!verifyError) {
685-
var validCert = tls.checkServerIdentity(hostname,
686-
socket.getPeerCertificate());
756+
var cert = result.getPeerCertificate();
757+
var validCert = tls.checkServerIdentity(hostname, cert);
687758
if (!validCert) {
688759
verifyError = new Error('Hostname/IP doesn\'t match certificate\'s ' +
689760
'altnames');
690761
}
691762
}
692763

693764
if (verifyError) {
694-
socket.authorizationError = verifyError.message;
765+
result.authorized = false;
766+
result.authorizationError = verifyError.message;
695767

696768
if (options.rejectUnauthorized) {
697-
socket.emit('error', verifyError);
698-
socket.destroy();
769+
result.emit('error', verifyError);
770+
result.destroy();
699771
return;
700772
} else {
701-
socket.emit('secureConnect');
773+
result.emit('secureConnect');
702774
}
703775
} else {
704-
socket.authorized = true;
705-
socket.emit('secureConnect');
776+
result.authorized = true;
777+
result.emit('secureConnect');
706778
}
707779

708780
// Uncork incoming data
709-
socket.removeListener('end', onHangUp);
781+
result.removeListener('end', onHangUp);
710782
});
711783

712784
function onHangUp() {
@@ -719,24 +791,6 @@ exports.connect = function(/* [port, host], options, cb */) {
719791
socket.emit('error', error);
720792
}
721793
}
722-
socket.once('end', onHangUp);
723-
}
724-
if (socket._handle)
725-
onHandle();
726-
else
727-
socket.once('connect', onHandle);
728-
729-
if (cb)
730-
socket.once('secureConnect', cb);
731-
732-
if (!options.socket) {
733-
var connect_opt = (options.path && !options.port) ? {path: options.path} : {
734-
port: options.port,
735-
host: options.host,
736-
localAddress: options.localAddress
737-
};
738-
socket.connect(connect_opt);
794+
result.once('end', onHangUp);
739795
}
740-
741-
return socket;
742796
};

src/node_crypto.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,14 +1689,14 @@ void Connection::New(const FunctionCallbackInfo<Value>& args) {
16891689
SSL_CTX_set_next_protos_advertised_cb(
16901690
sc->ctx_,
16911691
SSLWrap<Connection>::AdvertiseNextProtoCallback,
1692-
NULL);
1692+
conn);
16931693
} else {
16941694
// Client should select protocol from advertised
16951695
// If server supports NPN
16961696
SSL_CTX_set_next_proto_select_cb(
16971697
sc->ctx_,
16981698
SSLWrap<Connection>::SelectNextProtoCallback,
1699-
NULL);
1699+
conn);
17001700
}
17011701
#endif
17021702

test/simple/test-tls-inception.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright Joyent, Inc. and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
if (!process.versions.openssl) {
23+
console.error('Skipping because node compiled without OpenSSL.');
24+
process.exit(0);
25+
}
26+
27+
var common = require('../common');
28+
var fs = require('fs');
29+
var path = require('path');
30+
var net = require('net');
31+
var tls = require('tls');
32+
var assert = require('assert');
33+
34+
var options, a, b, portA, portB;
35+
var gotHello = false;
36+
37+
options = {
38+
key: fs.readFileSync(path.join(common.fixturesDir, 'test_key.pem')),
39+
cert: fs.readFileSync(path.join(common.fixturesDir, 'test_cert.pem'))
40+
};
41+
42+
// the "proxy" server
43+
a = tls.createServer(options, function (socket) {
44+
var options = {
45+
host: '127.0.0.1',
46+
port: b.address().port,
47+
rejectUnauthorized: false
48+
};
49+
var dest = net.connect(options);
50+
dest.pipe(socket);
51+
socket.pipe(dest);
52+
});
53+
54+
// the "target" server
55+
b = tls.createServer(options, function (socket) {
56+
socket.end('hello');
57+
});
58+
59+
process.on('exit', function () {
60+
assert(gotHello);
61+
});
62+
63+
a.listen(common.PORT, function () {
64+
b.listen(common.PORT + 1, function () {
65+
options = {
66+
host: '127.0.0.1',
67+
port: a.address().port,
68+
rejectUnauthorized: false
69+
};
70+
var socket = tls.connect(options);
71+
var ssl;
72+
ssl = tls.connect({
73+
socket: socket,
74+
rejectUnauthorized: false
75+
});
76+
ssl.setEncoding('utf8');
77+
ssl.once('data', function (data) {
78+
assert.equal('hello', data);
79+
gotHello = true;
80+
});
81+
ssl.on('end', function () {
82+
ssl.end();
83+
a.close();
84+
b.close();
85+
});
86+
});
87+
});

0 commit comments

Comments
 (0)