Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

https: client support for TLS keylog events #30053

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions doc/api/https.md
Expand Up @@ -45,6 +45,31 @@ changes:

See [`Session Resumption`][] for information about TLS session reuse.

#### Event: 'keylog'
<!-- YAML
added: REPLACEME
-->

* `line` {Buffer} Line of ASCII text, in NSS `SSLKEYLOGFILE` format.
* `tlsSocket` {tls.TLSSocket} The `tls.TLSSocket` instance on which it was
generated.

The `keylog` event is emitted when key material is generated or received by a
connection managed by this agent (typically before handshake has completed, but
not necessarily). This keying material can be stored for debugging, as it
allows captured TLS traffic to be decrypted. It may be emitted multiple times
for each socket.

A typical use case is to append received lines to a common text file, which is
later used by software (such as Wireshark) to decrypt the traffic:

```js
// ...
https.globalAgent.on('keylog', (line, tlsSocket) => {
fs.appendFileSync('/tmp/ssl-keys.log', line, { mode: 0o600 });
});
```

## Class: https.Server
<!-- YAML
added: v0.3.4
Expand Down
24 changes: 24 additions & 0 deletions lib/_http_agent.js
Expand Up @@ -32,6 +32,7 @@ const {
ERR_INVALID_ARG_TYPE,
},
} = require('internal/errors');
const kOnKeylog = Symbol('onkeylog');
// New Agent code.

// The largest departure from the previous implementation is that
Expand Down Expand Up @@ -124,10 +125,29 @@ function Agent(options) {
}
}
});

// Don't emit keylog events unless there is a listener for them.
this.on('newListener', maybeEnableKeylog);
}
Object.setPrototypeOf(Agent.prototype, EventEmitter.prototype);
Object.setPrototypeOf(Agent, EventEmitter);

function maybeEnableKeylog(eventName) {
if (eventName === 'keylog') {
this.removeListener('newListener', maybeEnableKeylog);
// Future sockets will listen on keylog at creation.
const agent = this;
this[kOnKeylog] = function onkeylog(keylog) {
agent.emit('keylog', keylog, this);
bnoordhuis marked this conversation as resolved.
Show resolved Hide resolved
};
// Existing sockets will start listening on keylog now.
const sockets = Object.values(this.sockets);
for (let i = 0; i < sockets.length; i++) {
sockets[i].on('keylog', this[kOnKeylog]);
}
}
}

Agent.defaultMaxSockets = Infinity;

Agent.prototype.createConnection = net.createConnection;
Expand Down Expand Up @@ -306,6 +326,10 @@ function installListeners(agent, s, options) {
s.removeListener('agentRemove', onRemove);
}
s.on('agentRemove', onRemove);

if (agent[kOnKeylog]) {
s.on('keylog', agent[kOnKeylog]);
}
}

Agent.prototype.removeSocket = function removeSocket(s, options) {
Expand Down
44 changes: 44 additions & 0 deletions test/parallel/test-https-agent-keylog.js
@@ -0,0 +1,44 @@
'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const https = require('https');
const fixtures = require('../common/fixtures');

const server = https.createServer({
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem'),
// Amount of keylog events depends on negotiated protocol
// version, so force a specific one:
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3',
}, (req, res) => {
res.end('bye');
}).listen(() => {
https.get({
port: server.address().port,
rejectUnauthorized: false,
}, (res) => {
res.resume();
res.on('end', () => {
// Trigger TLS connection reuse
https.get({
port: server.address().port,
rejectUnauthorized: false,
}, (res) => {
server.close();
res.resume();
});
});
});
});

const verifyKeylog = (line, tlsSocket) => {
assert(Buffer.isBuffer(line));
assert.strictEqual(tlsSocket.encrypted, true);
};
server.on('keylog', common.mustCall(verifyKeylog, 10));
https.globalAgent.on('keylog', common.mustCall(verifyKeylog, 10));
10 changes: 7 additions & 3 deletions test/parallel/test-tls-keylog-tlsv13.js
Expand Up @@ -21,9 +21,13 @@ const server = tls.createServer({
rejectUnauthorized: false,
});

const verifyBuffer = (line) => assert(Buffer.isBuffer(line));
server.on('keylog', common.mustCall(verifyBuffer, 5));
client.on('keylog', common.mustCall(verifyBuffer, 5));
server.on('keylog', common.mustCall((line, tlsSocket) => {
assert(Buffer.isBuffer(line));
assert.strictEqual(tlsSocket.encrypted, true);
}, 5));
client.on('keylog', common.mustCall((line) => {
assert(Buffer.isBuffer(line));
}, 5));

client.once('secureConnect', () => {
server.close();
Expand Down