Skip to content

Commit

Permalink
tls: cli option to enable TLS key logging to file
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sam-github committed Nov 19, 2019
1 parent 0f58bfd commit 88b645b
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 0 deletions.
10 changes: 10 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down
5 changes: 5 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
.
Expand Down
22 changes: 22 additions & 0 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
57 changes: 57 additions & 0 deletions test/parallel/test-tls-enable-keylog-cli.js
Original file line number Diff line number Diff line change
@@ -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();
}));
}

0 comments on commit 88b645b

Please sign in to comment.