mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
https: add client support for TLS keylog events
The keylog event is implemented on TLS sockets, but client HTTPS uses TLS sockets managed by an agent, so accessing the underlying socket before the TLS handshake completed was not possible. Note that server HTTPS already supports the keylog event because it inherits from the TLS server. PR-URL: https://github.com/nodejs/node/pull/30053 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
9d09969f4c
commit
b0cf62b3a0
@ -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
|
||||
|
@ -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
|
||||
@ -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);
|
||||
};
|
||||
// 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;
|
||||
@ -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) {
|
||||
|
44
test/parallel/test-https-agent-keylog.js
Normal file
44
test/parallel/test-https-agent-keylog.js
Normal file
@ -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));
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user