diff options
author | Sam Roberts <vieuxtech@gmail.com> | 2019-02-13 14:54:07 -0800 |
---|---|---|
committer | Sam Roberts <vieuxtech@gmail.com> | 2019-04-29 10:57:55 -0700 |
commit | adedbb12e52d6ae2f256c3e796490d2424ca0ef9 (patch) | |
tree | 8788b00641cc4c79f56df1881269d444bf518072 | |
parent | 439bc6ee0ee84ddb93767d5b6d96ce1bc9ded3e8 (diff) | |
download | node-new-adedbb12e52d6ae2f256c3e796490d2424ca0ef9.tar.gz |
tls: allow enabling the TLS debug trace
Enable the same trace output that the OpenSSL s_client and s_server
support with their `-trace` option. This is invaluable when debugging
reports of TLS bugs as well as when debugging the internal TLS
implementation.
See:
- https://github.com/nodejs/node/issues/25383
- https://github.com/nodejs/node/issues/17936
- https://github.com/postmanlabs/postman-app-support/issues/5918#issuecomment-465311423
PR-URL: https://github.com/nodejs/node/pull/27376
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Richard Lau <riclau@uk.ibm.com>
-rw-r--r-- | doc/api/tls.md | 18 | ||||
-rw-r--r-- | lib/_tls_wrap.js | 13 | ||||
-rw-r--r-- | src/tls_wrap.cc | 26 | ||||
-rw-r--r-- | src/tls_wrap.h | 1 | ||||
-rw-r--r-- | test/parallel/test-tls-enable-trace.js | 58 |
5 files changed, 116 insertions, 0 deletions
diff --git a/doc/api/tls.md b/doc/api/tls.md index 6a834c65ff..228de39794 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -725,6 +725,19 @@ added: v8.4.0 Disables TLS renegotiation for this `TLSSocket` instance. Once called, attempts to renegotiate will trigger an `'error'` event on the `TLSSocket`. +### tlsSocket.enableTrace() +<!-- YAML +added: REPLACEME +--> + +When enabled, TLS packet trace information is written to `stderr`. This can be +used to debug TLS connection problems. + +Note: The format of the output is identical to the output of `openssl s_client +-trace` or `openssl s_server -trace`. While it is produced by OpenSSL's +`SSL_trace()` function, the format is undocumented, can change without notice, +and should not be relied on. + ### tlsSocket.encrypted <!-- YAML added: v0.11.4 @@ -1438,6 +1451,10 @@ changes: `['hello', 'world']`. (Protocols should be ordered by their priority.) * `clientCertEngine` {string} Name of an OpenSSL engine which can provide the client certificate. + * `enableTrace` {boolean} If `true`, [`tls.TLSSocket.enableTrace()`][] will be + called on new connections. Tracing can be enabled after the secure + connection is established, but this option must be used to trace the secure + connection setup. **Default:** `false`. * `handshakeTimeout` {number} Abort the connection if the SSL/TLS handshake does not finish in the specified number of milliseconds. A `'tlsClientError'` is emitted on the `tls.Server` object whenever @@ -1693,6 +1710,7 @@ where `secureSocket` has the same API as `pair.cleartext`. [`tls.DEFAULT_MAX_VERSION`]: #tls_tls_default_max_version [`tls.DEFAULT_MIN_VERSION`]: #tls_tls_default_min_version [`tls.Server`]: #tls_class_tls_server +[`tls.TLSSocket.enableTrace()`]: #tls_tlssocket_enabletrace [`tls.TLSSocket.getPeerCertificate()`]: #tls_tlssocket_getpeercertificate_detailed [`tls.TLSSocket.getSession()`]: #tls_tlssocket_getsession [`tls.TLSSocket.getTLSTicket()`]: #tls_tlssocket_gettlsticket diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 0b844e6a82..b999c73329 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -63,6 +63,7 @@ const kErrorEmitted = Symbol('error-emitted'); const kHandshakeTimeout = Symbol('handshake-timeout'); const kRes = Symbol('res'); const kSNICallback = Symbol('snicallback'); +const kEnableTrace = Symbol('enableTrace'); const noop = () => {}; @@ -811,6 +812,7 @@ function makeSocketMethodProxy(name) { 'getSession', 'getTLSTicket', 'isSessionReused', + 'enableTrace', ].forEach((method) => { TLSSocket.prototype[method] = makeSocketMethodProxy(method); }); @@ -872,6 +874,8 @@ function tlsConnectionListener(rawSocket) { ALPNProtocols: this.ALPNProtocols, SNICallback: this[kSNICallback] || SNICallback }); + if (this[kEnableTrace] && socket._handle) + socket._handle.enableTrace(); socket.on('secure', onServerSocketSecure); @@ -992,6 +996,15 @@ function Server(options, listener) { if (listener) { this.on('secureConnection', listener); } + + const enableTrace = options.enableTrace; + if (enableTrace === true) + this[kEnableTrace] = true; + else if (enableTrace === false || enableTrace == null) + ; // Tracing explicitly disabled, or defaulting to disabled. + else + throw new ERR_INVALID_ARG_TYPE( + 'options.enableTrace', 'boolean', enableTrace); } Object.setPrototypeOf(Server.prototype, net.Server.prototype); diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index a034dd42a6..4c5d002295 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -912,6 +912,29 @@ void TLSWrap::EnableSessionCallbacks( wrap); } +// Check required capabilities were not excluded from the OpenSSL build: +// - OPENSSL_NO_SSL_TRACE excludes SSL_trace() +// - OPENSSL_NO_STDIO excludes BIO_new_fp() +// HAVE_SSL_TRACE is available on the internal tcp_wrap binding for the tests. +#if defined(OPENSSL_NO_SSL_TRACE) || defined(OPENSSL_NO_STDIO) +# define HAVE_SSL_TRACE 0 +#else +# define HAVE_SSL_TRACE 1 +#endif + +void TLSWrap::EnableTrace( + const FunctionCallbackInfo<Value>& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + +#if HAVE_SSL_TRACE + if (wrap->ssl_) { + BIO* b = BIO_new_fp(stderr, BIO_NOCLOSE | BIO_FP_TEXT); + SSL_set_msg_callback(wrap->ssl_.get(), SSL_trace); + SSL_set_msg_callback_arg(wrap->ssl_.get(), b); + } +#endif +} void TLSWrap::DestroySSL(const FunctionCallbackInfo<Value>& args) { TLSWrap* wrap; @@ -1057,6 +1080,8 @@ void TLSWrap::Initialize(Local<Object> target, env->SetMethod(target, "wrap", TLSWrap::Wrap); + NODE_DEFINE_CONSTANT(target, HAVE_SSL_TRACE); + Local<FunctionTemplate> t = BaseObject::MakeLazilyInitializedJSTemplate(env); Local<String> tlsWrapString = FIXED_ONE_BYTE_STRING(env->isolate(), "TLSWrap"); @@ -1080,6 +1105,7 @@ void TLSWrap::Initialize(Local<Object> target, env->SetProtoMethod(t, "start", Start); env->SetProtoMethod(t, "setVerifyMode", SetVerifyMode); env->SetProtoMethod(t, "enableSessionCallbacks", EnableSessionCallbacks); + env->SetProtoMethod(t, "enableTrace", EnableTrace); env->SetProtoMethod(t, "destroySSL", DestroySSL); env->SetProtoMethod(t, "enableCertCb", EnableCertCb); diff --git a/src/tls_wrap.h b/src/tls_wrap.h index 85a53f236d..41e16ea9ac 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -160,6 +160,7 @@ class TLSWrap : public AsyncWrap, static void SetVerifyMode(const v8::FunctionCallbackInfo<v8::Value>& args); static void EnableSessionCallbacks( const v8::FunctionCallbackInfo<v8::Value>& args); + static void EnableTrace(const v8::FunctionCallbackInfo<v8::Value>& args); static void EnableCertCb(const v8::FunctionCallbackInfo<v8::Value>& args); static void DestroySSL(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetServername(const v8::FunctionCallbackInfo<v8::Value>& args); diff --git a/test/parallel/test-tls-enable-trace.js b/test/parallel/test-tls-enable-trace.js new file mode 100644 index 0000000000..a3b1721ade --- /dev/null +++ b/test/parallel/test-tls-enable-trace.js @@ -0,0 +1,58 @@ +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) common.skip('missing crypto'); +const fixtures = require('../common/fixtures'); + +// Test enableTrace: option for TLS. + +const assert = require('assert'); +const { fork } = require('child_process'); + +if (process.argv[2] === 'test') + return test(); + +const binding = require('internal/test/binding').internalBinding; + +if (!binding('tls_wrap').HAVE_SSL_TRACE) + return common.skip('no SSL_trace() compiled into openssl'); + +const child = fork(__filename, ['test'], { silent: true }); + +let stderr = ''; +child.stderr.setEncoding('utf8'); +child.stderr.on('data', (data) => stderr += data); +child.on('close', common.mustCall(() => { + assert(/Received Record/.test(stderr)); + assert(/ClientHello/.test(stderr)); +})); + +// For debugging and observation of actual trace output. +child.stderr.pipe(process.stderr); +child.stdout.pipe(process.stdout); + +child.on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); +})); + +function test() { + const { + connect, keys + } = require(fixtures.path('tls-connect')); + + connect({ + client: { + checkServerIdentity: (servername, cert) => { }, + ca: `${keys.agent1.cert}\n${keys.agent6.ca}`, + }, + server: { + cert: keys.agent6.cert, + key: keys.agent6.key, + enableTrace: true, + }, + }, common.mustCall((err, pair, cleanup) => { + pair.client.conn.enableTrace(); + + return cleanup(); + })); +} |