Skip to content

Commit a2b0e9e

Browse files
mildsunriseBethGriggs
authored andcommitted
tls: expose keylog event on TLSSocket
Exposes SSL_CTX_set_keylog_callback in the form of a `keylog` event that is emitted on clients and servers. This enables easy debugging of TLS connections with i.e. Wireshark, which is a long-requested feature. PR-URL: #27654 Backport-PR-URL: #31582 Refs: #2363 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Sam Roberts <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent 05f5b3e commit a2b0e9e

File tree

8 files changed

+145
-0
lines changed

8 files changed

+145
-0
lines changed

‎doc/api/tls.md‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,34 @@ added: v0.3.2
299299
The `tls.Server` class is a subclass of `net.Server` that accepts encrypted
300300
connections using TLS or SSL.
301301

302+
### Event: 'keylog'
303+
<!-- YAML
304+
added: REPLACEME
305+
-->
306+
307+
*`line`{Buffer} Line of ASCII text, in NSS `SSLKEYLOGFILE` format.
308+
*`tlsSocket`{tls.TLSSocket} The `tls.TLSSocket` instance on which it was
309+
generated.
310+
311+
The `keylog` event is emitted when key material is generated or received by
312+
a connection to this server (typically before handshake has completed, but not
313+
necessarily). This keying material can be stored for debugging, as it allows
314+
captured TLS traffic to be decrypted. It may be emitted multiple times for
315+
each socket.
316+
317+
A typical use case is to append received lines to a common text file, which
318+
is later used by software (such as Wireshark) to decrypt the traffic:
319+
320+
```js
321+
constlogFile=fs.createWriteStream('/tmp/ssl-keys.log',{flags:'a' });
322+
// ...
323+
server.on('keylog', (line, tlsSocket) =>{
324+
if (tlsSocket.remoteAddress!=='...')
325+
return; // Only log keys for a particular IP
326+
logFile.write(line);
327+
});
328+
```
329+
302330
### Event: 'newSession'
303331
<!-- YAML
304332
added: v0.9.2
@@ -573,6 +601,27 @@ changes:
573601

574602
Construct a new `tls.TLSSocket` object from an existing TCP socket.
575603

604+
### Event: 'keylog'
605+
<!-- YAML
606+
added: REPLACEME
607+
-->
608+
609+
*`line`{Buffer} Line of ASCII text, in NSS `SSLKEYLOGFILE` format.
610+
611+
The `keylog` event is emitted on a client `tls.TLSSocket` when key material
612+
is generated or received by the socket. This keying material can be stored
613+
for debugging, as it allows captured TLS traffic to be decrypted. It may
614+
be emitted multiple times, before or after the handshake completes.
615+
616+
A typical use case is to append received lines to a common text file, which
617+
is later used by software (such as Wireshark) to decrypt the traffic:
618+
619+
```js
620+
constlogFile=fs.createWriteStream('/tmp/ssl-keys.log',{flags:'a' });
621+
// ...
622+
tlsSocket.on('keylog', (line) =>logFile.write(line));
623+
```
624+
576625
### Event: 'OCSPResponse'
577626
<!-- YAML
578627
added: v0.11.13

‎lib/_tls_wrap.js‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,18 @@ function onnewsession(key, session){
243243
}
244244

245245

246+
functiononkeylogclient(line){
247+
debug('client onkeylog');
248+
this[owner_symbol].emit('keylog',line);
249+
}
250+
251+
functiononkeylog(line){
252+
debug('server onkeylog');
253+
constowner=this[owner_symbol];
254+
if(owner.server)
255+
owner.server.emit('keylog',line,owner);
256+
}
257+
246258
functiononocspresponse(resp){
247259
this[owner_symbol].emit('OCSPResponse',resp);
248260
}
@@ -492,6 +504,7 @@ TLSSocket.prototype._init = function(socket, wrap){
492504
ssl.onclienthello=loadSession;
493505
ssl.oncertcb=loadSNI;
494506
ssl.onnewsession=onnewsession;
507+
ssl.onkeylog=onkeylog;
495508
ssl.lastHandshakeTime=0;
496509
ssl.handshakes=0;
497510

@@ -500,6 +513,8 @@ TLSSocket.prototype._init = function(socket, wrap){
500513
this.server.listenerCount('newSession')>0){
501514
ssl.enableSessionCallbacks();
502515
}
516+
if(this.server.listenerCount('keylog')>0)
517+
ssl.enableKeylogCallback();
503518
if(this.server.listenerCount('OCSPRequest')>0)
504519
ssl.enableCertCb();
505520
}
@@ -510,6 +525,21 @@ TLSSocket.prototype._init = function(socket, wrap){
510525

511526
if(options.session)
512527
ssl.setSession(options.session);
528+
529+
ssl.onkeylog=onkeylogclient;
530+
531+
// Only call .onkeylog if there is a keylog listener.
532+
this.on('newListener',keylogNewListener);
533+
534+
functionkeylogNewListener(event){
535+
if(event!=='keylog')
536+
return;
537+
538+
ssl.enableKeylogCallback();
539+
540+
// Remove this listener since it's no longer needed.
541+
this.removeListener('newListener',keylogNewListener);
542+
}
513543
}
514544

515545
ssl.onerror=onerror;

‎src/env.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ struct PackageConfig{
225225
V(onhandshakedone_string, "onhandshakedone") \
226226
V(onhandshakestart_string, "onhandshakestart") \
227227
V(onheaders_string, "onheaders") \
228+
V(onkeylog_string, "onkeylog") \
228229
V(onmessage_string, "onmessage") \
229230
V(onnewsession_string, "onnewsession") \
230231
V(onocspresponse_string, "onocspresponse") \

‎src/node_crypto.cc‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ template SSL_SESSION* SSLWrap<TLSWrap>::GetSessionCallback(
132132
int* copy);
133133
template int SSLWrap<TLSWrap>::NewSessionCallback(SSL* s,
134134
SSL_SESSION* sess);
135+
template void SSLWrap<TLSWrap>::KeylogCallback(const SSL* s,
136+
constchar* line);
135137
template void SSLWrap<TLSWrap>::OnClientHello(
136138
void* arg,
137139
const ClientHelloParser::ClientHello& hello);
@@ -1482,6 +1484,21 @@ int SSLWrap<Base>::NewSessionCallback(SSL* s, SSL_SESSION* sess){
14821484
}
14831485

14841486

1487+
template <classBase>
1488+
void SSLWrap<Base>::KeylogCallback(const SSL* s, constchar* line){
1489+
Base* w = static_cast<Base*>(SSL_get_app_data(s));
1490+
Environment* env = w->ssl_env();
1491+
HandleScope handle_scope(env->isolate());
1492+
Context::Scope context_scope(env->context());
1493+
1494+
constsize_t size = strlen(line);
1495+
Local<Value> line_bf = Buffer::Copy(env, line, 1 + size).ToLocalChecked();
1496+
char* data = Buffer::Data(line_bf);
1497+
data[size] = '\n';
1498+
w->MakeCallback(env->onkeylog_string(), 1, &line_bf);
1499+
}
1500+
1501+
14851502
template <classBase>
14861503
void SSLWrap<Base>::OnClientHello(void* arg,
14871504
const ClientHelloParser::ClientHello& hello){

‎src/node_crypto.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ class SSLWrap{
267267
int* copy);
268268
#endif
269269
staticintNewSessionCallback(SSL* s, SSL_SESSION* sess);
270+
staticvoidKeylogCallback(const SSL* s, constchar* line);
270271
staticvoidOnClientHello(void* arg,
271272
const ClientHelloParser::ClientHello& hello);
272273

‎src/tls_wrap.cc‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,16 @@ void TLSWrap::EnableSessionCallbacks(
836836
wrap);
837837
}
838838

839+
voidTLSWrap::EnableKeylogCallback(
840+
const FunctionCallbackInfo<Value>& args){
841+
TLSWrap* wrap;
842+
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
843+
CHECK_NOT_NULL(wrap->sc_);
844+
#if OPENSSL_VERSION_NUMBER >= 0x1010100fL
845+
SSL_CTX_set_keylog_callback(wrap->sc_->ctx_.get(),
846+
SSLWrap<TLSWrap>::KeylogCallback);
847+
#endif
848+
}
839849

840850
voidTLSWrap::DestroySSL(const FunctionCallbackInfo<Value>& args){
841851
TLSWrap* wrap;
@@ -1001,6 +1011,7 @@ void TLSWrap::Initialize(Local<Object> target,
10011011
env->SetProtoMethod(t, "start", Start);
10021012
env->SetProtoMethod(t, "setVerifyMode", SetVerifyMode);
10031013
env->SetProtoMethod(t, "enableSessionCallbacks", EnableSessionCallbacks);
1014+
env->SetProtoMethod(t, "enableKeylogCallback", EnableKeylogCallback);
10041015
env->SetProtoMethod(t, "destroySSL", DestroySSL);
10051016
env->SetProtoMethod(t, "enableCertCb", EnableCertCb);
10061017

‎src/tls_wrap.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ class TLSWrap : public AsyncWrap,
154154
staticvoidSetVerifyMode(const v8::FunctionCallbackInfo<v8::Value>& args);
155155
staticvoidEnableSessionCallbacks(
156156
const v8::FunctionCallbackInfo<v8::Value>& args);
157+
staticvoidEnableKeylogCallback(
158+
const v8::FunctionCallbackInfo<v8::Value>& args);
157159
staticvoidEnableTrace(const v8::FunctionCallbackInfo<v8::Value>& args);
158160
staticvoidEnableCertCb(const v8::FunctionCallbackInfo<v8::Value>& args);
159161
staticvoidDestroySSL(const v8::FunctionCallbackInfo<v8::Value>& args);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
if(!common.hasCrypto)
5+
common.skip('missing crypto');
6+
if(/^(0\.|1\.0\.|1\.1\.0)/.test(process.versions.openssl))
7+
common.skip('keylog support not available');
8+
9+
constassert=require('assert');
10+
consttls=require('tls');
11+
constfixtures=require('../common/fixtures');
12+
13+
constserver=tls.createServer({
14+
key: fixtures.readSync('/keys/agent2-key.pem'),
15+
cert: fixtures.readSync('/keys/agent2-cert.pem'),
16+
// Amount of keylog events depends on negotiated protocol
17+
// version, so force a specific one:
18+
minVersion: 'TLSv1.2',
19+
maxVersion: 'TLSv1.2',
20+
}).listen(()=>{
21+
constclient=tls.connect({
22+
port: server.address().port,
23+
rejectUnauthorized: false,
24+
});
25+
26+
constverifyBuffer=(line)=>assert(Buffer.isBuffer(line));
27+
server.on('keylog',common.mustCall(verifyBuffer,1));
28+
client.on('keylog',common.mustCall(verifyBuffer,1));
29+
30+
client.once('secureConnect',()=>{
31+
server.close();
32+
client.end();
33+
});
34+
});

0 commit comments

Comments
(0)