diff options
author | Sam Roberts <vieuxtech@gmail.com> | 2019-10-22 06:44:20 +0300 |
---|---|---|
committer | Myles Borins <mylesborins@google.com> | 2019-11-21 08:29:29 +0300 |
commit | 01fa18c99cd076bb29b6f3bf3085319dcb4e4d95 (patch) | |
tree | a5bbe78ab480ca4afc6fdad3c8450289fc29072f | |
parent | f15a3b02816513b7a0b32a39b99cd0138b699f99 (diff) |
tls: cli option to enable TLS key logging to file
Debugging HTTPS or TLS connections from a Node.js app with (for example)
Wireshark is unreasonably difficult without the ability to get the TLS
key log. In theory, the application can be modified to use the
`'keylog'` event directly, but for complex apps, or apps that define
there own HTTPS Agent (like npm), this is unreasonably difficult.
Use of the option triggers a warning to be emitted so the user is
clearly notified of what is happening and its effect.
PR-URL: https://github.com/nodejs/node/pull/30055
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
-rw-r--r-- | doc/api/cli.md | 10 | ||||
-rw-r--r-- | doc/node.1 | 5 | ||||
-rw-r--r-- | lib/_tls_wrap.js | 22 | ||||
-rw-r--r-- | src/node_options.cc | 4 | ||||
-rw-r--r-- | src/node_options.h | 1 | ||||
-rw-r--r-- | test/parallel/test-tls-enable-keylog-cli.js | 57 |
6 files changed, 99 insertions, 0 deletions
diff --git a/doc/api/cli.md b/doc/api/cli.md index e88b16b1ead..26513946536 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -679,6 +679,15 @@ added: v4.0.0 Specify an alternative default TLS cipher list. Requires Node.js to be built with crypto support (default). +### `--tls-keylog=file` +<!-- YAML +added: REPLACEME +--> + +Log TLS key material to a file. The key material is in NSS `SSLKEYLOGFILE` +format and can be used by software (such as Wireshark) to decrypt the TLS +traffic. + ### `--tls-max-v1.2` <!-- YAML added: v12.0.0 @@ -1073,6 +1082,7 @@ Node.js options that are allowed are: * `--throw-deprecation` * `--title` * `--tls-cipher-list` +* `--tls-keylog` * `--tls-max-v1.2` * `--tls-max-v1.3` * `--tls-min-v1.0` diff --git a/doc/node.1 b/doc/node.1 index 4b653e702d6..e3628034e83 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -302,6 +302,11 @@ Specify process.title on startup. Specify an alternative default TLS cipher list. Requires Node.js to be built with crypto support. (Default) . +.It Fl -tls-keylog Ns = Ns Ar file +Log TLS key material to a file. The key material is in NSS SSLKEYLOGFILE +format and can be used by software (such as Wireshark) to decrypt the TLS +traffic. +. .It Fl -tls-max-v1.2 Set default maxVersion to 'TLSv1.2'. Use to disable support for TLSv1.3. . diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 778afa73286..69fc0547589 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -60,6 +60,8 @@ const { const { getOptionValue } = require('internal/options'); const { validateString } = require('internal/validators'); const traceTls = getOptionValue('--trace-tls'); +const tlsKeylog = getOptionValue('--tls-keylog'); +const { appendFile } = require('fs'); const kConnectOptions = Symbol('connect-options'); const kDisableRenegotiation = Symbol('disable-renegotiation'); const kErrorEmitted = Symbol('error-emitted'); @@ -560,6 +562,8 @@ TLSSocket.prototype._destroySSL = function _destroySSL() { }; // Constructor guts, arbitrarily factored out. +let warnOnTlsKeylog = true; +let warnOnTlsKeylogError = true; TLSSocket.prototype._init = function(socket, wrap) { const options = this._tlsOptions; const ssl = this._handle; @@ -643,6 +647,24 @@ TLSSocket.prototype._init = function(socket, wrap) { } } + if (tlsKeylog) { + if (warnOnTlsKeylog) { + warnOnTlsKeylog = false; + process.emitWarning('Using --tls-keylog makes TLS connections insecure ' + + 'by writing secret key material to file ' + tlsKeylog); + ssl.enableKeylogCallback(); + this.on('keylog', (line) => { + appendFile(tlsKeylog, line, { mode: 0o600 }, (err) => { + if (err && warnOnTlsKeylogError) { + warnOnTlsKeylogError = false; + process.emitWarning('Failed to write TLS keylog (this warning ' + + 'will not be repeated): ' + err); + } + }); + }); + } + } + ssl.onerror = onerror; // If custom SNICallback was given, or if diff --git a/src/node_options.cc b/src/node_options.cc index 85256a7e0a8..0bc6730156c 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -506,6 +506,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { AddOption("--napi-modules", "", NoOp{}, kAllowedInEnvironment); + AddOption("--tls-keylog", + "log TLS decryption keys to named file for traffic analysis", + &EnvironmentOptions::tls_keylog, kAllowedInEnvironment); + AddOption("--tls-min-v1.0", "set default TLS minimum to TLSv1.0 (default: TLSv1.2)", &EnvironmentOptions::tls_min_v1_0, diff --git a/src/node_options.h b/src/node_options.h index 30a976f48d5..ce0cee5fe56 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -161,6 +161,7 @@ class EnvironmentOptions : public Options { bool tls_min_v1_3 = false; bool tls_max_v1_2 = false; bool tls_max_v1_3 = false; + std::string tls_keylog; std::vector<std::string> preload_modules; diff --git a/test/parallel/test-tls-enable-keylog-cli.js b/test/parallel/test-tls-enable-keylog-cli.js new file mode 100644 index 00000000000..5d05069b15f --- /dev/null +++ b/test/parallel/test-tls-enable-keylog-cli.js @@ -0,0 +1,57 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) common.skip('missing crypto'); +const fixtures = require('../common/fixtures'); + +// Test --tls-keylog CLI flag. + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); +const { fork } = require('child_process'); + +if (process.argv[2] === 'test') + return test(); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); +const file = path.resolve(tmpdir.path, 'keylog.log'); + +const child = fork(__filename, ['test'], { + execArgv: ['--tls-keylog=' + file] +}); + +child.on('close', common.mustCall((code, signal) => { + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + const log = fs.readFileSync(file, 'utf8'); + assert(/SECRET/.test(log)); +})); + +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) => { + if (pair.server.err) { + console.trace('server', pair.server.err); + } + if (pair.client.err) { + console.trace('client', pair.client.err); + } + assert.ifError(pair.server.err); + assert.ifError(pair.client.err); + + return cleanup(); + })); +} |