Skip to content

Commit e80103a

Browse files
sam-githubBethGriggs
authored andcommitted
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: #30055 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Daniel Bevenius <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent dbee78c commit e80103a

File tree

6 files changed

+99
-0
lines changed

6 files changed

+99
-0
lines changed

‎doc/api/cli.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,15 @@ added: v4.0.0
708708
Specify an alternative default TLS cipher list. Requires Node.js to be built
709709
with crypto support (default).
710710

711+
### `--tls-keylog=file`
712+
<!-- YAML
713+
added: REPLACEME
714+
-->
715+
716+
Log TLS key material to a file. The key material is in NSS `SSLKEYLOGFILE`
717+
format and can be used by software (such as Wireshark) to decrypt the TLS
718+
traffic.
719+
711720
### `--tls-max-v1.2`
712721
<!-- YAML
713722
added: v12.0.0
@@ -1119,6 +1128,7 @@ Node.js options that are allowed are:
11191128
*`--throw-deprecation`
11201129
*`--title`
11211130
*`--tls-cipher-list`
1131+
*`--tls-keylog`
11221132
*`--tls-max-v1.2`
11231133
*`--tls-max-v1.3`
11241134
*`--tls-min-v1.0`

‎doc/node.1‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,11 @@ Specify process.title on startup.
315315
Specify an alternative default TLS cipher list.
316316
Requires Node.js to be built with crypto support. (Default)
317317
.
318+
.ItFl-tls-keylogNs=NsArfile
319+
Log TLS key material to a file. The key material is in NSS SSLKEYLOGFILE
320+
format and can be used by software (such as Wireshark) to decrypt the TLS
321+
traffic.
322+
.
318323
.ItFl-tls-max-v1.2
319324
Set default maxVersion to 'TLSv1.2'. Use to disable support for TLSv1.3.
320325
.

‎lib/_tls_wrap.js‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ const{
6060
const{ getOptionValue }=require('internal/options');
6161
const{ validateString }=require('internal/validators');
6262
consttraceTls=getOptionValue('--trace-tls');
63+
consttlsKeylog=getOptionValue('--tls-keylog');
64+
const{ appendFile }=require('fs');
6365
constkConnectOptions=Symbol('connect-options');
6466
constkDisableRenegotiation=Symbol('disable-renegotiation');
6567
constkErrorEmitted=Symbol('error-emitted');
@@ -560,6 +562,8 @@ TLSSocket.prototype._destroySSL = function _destroySSL(){
560562
};
561563

562564
// Constructor guts, arbitrarily factored out.
565+
letwarnOnTlsKeylog=true;
566+
letwarnOnTlsKeylogError=true;
563567
TLSSocket.prototype._init=function(socket,wrap){
564568
constoptions=this._tlsOptions;
565569
constssl=this._handle;
@@ -643,6 +647,24 @@ TLSSocket.prototype._init = function(socket, wrap){
643647
}
644648
}
645649

650+
if(tlsKeylog){
651+
if(warnOnTlsKeylog){
652+
warnOnTlsKeylog=false;
653+
process.emitWarning('Using --tls-keylog makes TLS connections insecure '+
654+
'by writing secret key material to file '+tlsKeylog);
655+
ssl.enableKeylogCallback();
656+
this.on('keylog',(line)=>{
657+
appendFile(tlsKeylog,line,{mode: 0o600},(err)=>{
658+
if(err&&warnOnTlsKeylogError){
659+
warnOnTlsKeylogError=false;
660+
process.emitWarning('Failed to write TLS keylog (this warning '+
661+
'will not be repeated): '+err);
662+
}
663+
});
664+
});
665+
}
666+
}
667+
646668
ssl.onerror=onerror;
647669

648670
// If custom SNICallback was given, or if

‎src/node_options.cc‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser(){
571571

572572
AddOption("--napi-modules", "", NoOp{}, kAllowedInEnvironment);
573573

574+
AddOption("--tls-keylog",
575+
"log TLS decryption keys to named file for traffic analysis",
576+
&EnvironmentOptions::tls_keylog, kAllowedInEnvironment);
577+
574578
AddOption("--tls-min-v1.0",
575579
"set default TLS minimum to TLSv1.0 (default: TLSv1.2)",
576580
&EnvironmentOptions::tls_min_v1_0,

‎src/node_options.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ class EnvironmentOptions : public Options{
166166
bool tls_min_v1_3 = false;
167167
bool tls_max_v1_2 = false;
168168
bool tls_max_v1_3 = false;
169+
std::string tls_keylog;
169170

170171
std::vector<std::string> preload_modules;
171172

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
if(!common.hasCrypto)common.skip('missing crypto');
4+
constfixtures=require('../common/fixtures');
5+
6+
// Test --tls-keylog CLI flag.
7+
8+
constassert=require('assert');
9+
constpath=require('path');
10+
constfs=require('fs');
11+
const{ fork }=require('child_process');
12+
13+
if(process.argv[2]==='test')
14+
returntest();
15+
16+
consttmpdir=require('../common/tmpdir');
17+
tmpdir.refresh();
18+
constfile=path.resolve(tmpdir.path,'keylog.log');
19+
20+
constchild=fork(__filename,['test'],{
21+
execArgv: ['--tls-keylog='+file]
22+
});
23+
24+
child.on('close',common.mustCall((code,signal)=>{
25+
assert.strictEqual(code,0);
26+
assert.strictEqual(signal,null);
27+
constlog=fs.readFileSync(file,'utf8');
28+
assert(/SECRET/.test(log));
29+
}));
30+
31+
functiontest(){
32+
const{
33+
connect, keys
34+
}=require(fixtures.path('tls-connect'));
35+
36+
connect({
37+
client: {
38+
checkServerIdentity: (servername,cert)=>{},
39+
ca: `${keys.agent1.cert}\n${keys.agent6.ca}`,
40+
},
41+
server: {
42+
cert: keys.agent6.cert,
43+
key: keys.agent6.key
44+
},
45+
},common.mustCall((err,pair,cleanup)=>{
46+
if(pair.server.err){
47+
console.trace('server',pair.server.err);
48+
}
49+
if(pair.client.err){
50+
console.trace('client',pair.client.err);
51+
}
52+
assert.ifError(pair.server.err);
53+
assert.ifError(pair.client.err);
54+
55+
returncleanup();
56+
}));
57+
}

0 commit comments

Comments
(0)