diff --git a/doc/api/cli.md b/doc/api/cli.md index c0682d864c9d0a..50185b0cc094c3 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -632,6 +632,14 @@ added: v2.1.0 Prints a stack trace whenever synchronous I/O is detected after the first turn of the event loop. +### `--trace-tls` + + +Prints TLS packet trace information to `stderr`. This can be used to debug TLS +connection problems. + ### `--trace-warnings` * `options` {Object} + * `enableTrace`: See [`tls.createServer()`][] * `host` {string} Host the client should connect to. **Default:** `'localhost'`. * `port` {number} Port the client should connect to. @@ -1647,6 +1655,7 @@ changes: * `rejectUnauthorized` {boolean} If not `false` a server automatically reject clients with invalid certificates. Only applies when `isServer` is `true`. * `options` + * `enableTrace`: See [`tls.createServer()`][] * `secureContext`: A TLS context object from [`tls.createSecureContext()`][] * `isServer`: If `true` the TLS socket will be instantiated in server-mode. **Default:** `false`. diff --git a/doc/node.1 b/doc/node.1 index 1fa5ce7f650e99..5c0e23d7014606 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -302,6 +302,9 @@ Enable the collection of trace event tracing information. .It Fl -trace-sync-io Print a stack trace whenever synchronous I/O is detected after the first turn of the event loop. . +.It Fl -trace-tls +Prints TLS packet trace information to stderr. +. .It Fl -trace-warnings Print stack traces for process warnings (including deprecations). . diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index b999c7332941c5..a04f0014c76b90 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -56,7 +56,9 @@ const { ERR_TLS_SESSION_ATTACK, ERR_TLS_SNI_FROM_SERVER } = require('internal/errors').codes; +const { getOptionValue } = require('internal/options'); const { validateString } = require('internal/validators'); +const traceTls = getOptionValue('--trace-tls'); const kConnectOptions = Symbol('connect-options'); const kDisableRenegotiation = Symbol('disable-renegotiation'); const kErrorEmitted = Symbol('error-emitted'); @@ -68,6 +70,7 @@ const kEnableTrace = Symbol('enableTrace'); const noop = () => {}; let ipServernameWarned = false; +let tlsTracingWarned = false; // Server side times how long a handshake is taking to protect against slow // handshakes being used for DoS. @@ -343,6 +346,20 @@ function initRead(tlsSocket, socket) { function TLSSocket(socket, opts) { const tlsOptions = { ...opts }; + let enableTrace = tlsOptions.enableTrace; + + if (enableTrace == null) { + enableTrace = traceTls; + + if (enableTrace && !tlsTracingWarned) { + tlsTracingWarned = true; + process.emitWarning('Enabling --trace-tls can expose sensitive data in ' + + 'the resulting log.'); + } + } else if (typeof enableTrace !== 'boolean') { + throw new ERR_INVALID_ARG_TYPE( + 'options.enableTrace', 'boolean', enableTrace); + } if (tlsOptions.ALPNProtocols) tls.convertALPNProtocols(tlsOptions.ALPNProtocols, tlsOptions); @@ -397,6 +414,9 @@ function TLSSocket(socket, opts) { this.readable = true; this.writable = true; + if (enableTrace && this._handle) + this._handle.enableTrace(); + // Read on next tick so the caller has a chance to setup listeners process.nextTick(initRead, this, socket); } @@ -872,10 +892,9 @@ function tlsConnectionListener(rawSocket) { rejectUnauthorized: this.rejectUnauthorized, handshakeTimeout: this[kHandshakeTimeout], ALPNProtocols: this.ALPNProtocols, - SNICallback: this[kSNICallback] || SNICallback + SNICallback: this[kSNICallback] || SNICallback, + enableTrace: this[kEnableTrace] }); - if (this[kEnableTrace] && socket._handle) - socket._handle.enableTrace(); socket.on('secure', onServerSocketSecure); @@ -997,14 +1016,7 @@ function Server(options, 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); + this[kEnableTrace] = options.enableTrace; } Object.setPrototypeOf(Server.prototype, net.Server.prototype); @@ -1365,7 +1377,8 @@ exports.connect = function connect(...args) { rejectUnauthorized: options.rejectUnauthorized !== false, session: options.session, ALPNProtocols: options.ALPNProtocols, - requestOCSP: options.requestOCSP + requestOCSP: options.requestOCSP, + enableTrace: options.enableTrace }); tlssock[kConnectOptions] = options; diff --git a/src/node_options.cc b/src/node_options.cc index adc6f61586853b..552997e58cf5a2 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -373,6 +373,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "first tick", &EnvironmentOptions::trace_sync_io, kAllowedInEnvironment); + AddOption("--trace-tls", + "prints TLS packet trace information to stderr", + &EnvironmentOptions::trace_tls, + kAllowedInEnvironment); AddOption("--trace-warnings", "show stack traces on process warnings", &EnvironmentOptions::trace_warnings, diff --git a/src/node_options.h b/src/node_options.h index 3b2513a183bc1b..2aca1f327c889e 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -118,6 +118,7 @@ class EnvironmentOptions : public Options { bool throw_deprecation = false; bool trace_deprecation = false; bool trace_sync_io = false; + bool trace_tls = false; bool trace_warnings = false; std::string unhandled_rejections; std::string userland_loader; diff --git a/test/parallel/test-tls-enable-trace-cli.js b/test/parallel/test-tls-enable-trace-cli.js new file mode 100644 index 00000000000000..5b7189af702dad --- /dev/null +++ b/test/parallel/test-tls-enable-trace-cli.js @@ -0,0 +1,59 @@ +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) common.skip('missing crypto'); +const fixtures = require('../common/fixtures'); + +// Test --trace-tls CLI flag. + +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, + execArgv: ['--trace-tls'] +}); + +let stderr = ''; +child.stderr.setEncoding('utf8'); +child.stderr.on('data', (data) => stderr += data); +child.on('close', common.mustCall(() => { + assert(/Warning: Enabling --trace-tls can expose sensitive/.test(stderr)); + 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 + }, + }, common.mustCall((err, pair, cleanup) => { + return cleanup(); + })); +}