Skip to content

Commit e84c9d7

Browse files
jasnelladdaleax
authored andcommittedAug 14, 2017
tls: add tlsSocket.disableRenegotiation()
Allows TLS renegotiation to be disabled per `TLSSocket` instance. Per HTTP/2, TLS renegotiation is forbidden after the initial connection prefix is exchanged. Backport-PR-URL: #14813 Backport-Reviewed-By: Anna Henningsen <[email protected]> Backport-Reviewed-By: Timothy Gu <[email protected]> PR-URL: #14239 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent e0001dc commit e84c9d7

File tree

3 files changed

+85
-0
lines changed

3 files changed

+85
-0
lines changed
 

‎doc/api/tls.md

+8
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,14 @@ added: v0.11.4
552552
Returns `true` if the peer certificate was signed by one of the CAs specified
553553
when creating the `tls.TLSSocket` instance, otherwise `false`.
554554

555+
### tlsSocket.disableRenegotiation()
556+
<!-- YAML
557+
added: REPLACEME
558+
-->
559+
560+
Disables TLS renegotiation for this `TLSSocket` instance. Once called, attempts
561+
to renegotiate will trigger an `'error'` event on the `TLSSocket`.
562+
555563
### tlsSocket.encrypted
556564
<!-- YAML
557565
added: v0.11.4

‎lib/_tls_wrap.js

+10
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const Timer = process.binding('timer_wrap').Timer;
3636
const tls_wrap = process.binding('tls_wrap');
3737
const TCP = process.binding('tcp_wrap').TCP;
3838
const Pipe = process.binding('pipe_wrap').Pipe;
39+
const kDisableRenegotiation = Symbol('disable-renegotiation');
3940

4041
function onhandshakestart() {
4142
debug('onhandshakestart');
@@ -63,6 +64,11 @@ function onhandshakestart() {
6364
self._emitTLSError(err);
6465
});
6566
}
67+
68+
if (this[kDisableRenegotiation] && ssl.handshakes > 0) {
69+
const err = new Error('TLS session renegotiation disabled for this socket');
70+
self._emitTLSError(err);
71+
}
6672
}
6773

6874

@@ -357,6 +363,10 @@ tls_wrap.TLSWrap.prototype.close = function close(cb) {
357363
return this._parent.close(done);
358364
};
359365

366+
TLSSocket.prototype.disableRenegotiation = function disableRenegotiation() {
367+
this[kDisableRenegotiation] = true;
368+
};
369+
360370
TLSSocket.prototype._wrapHandle = function(wrap) {
361371
var res;
362372
var handle;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const fs = require('fs');
5+
6+
// Tests that calling disableRenegotiation on a TLSSocket stops renegotiation.
7+
8+
if (!common.hasCrypto) {
9+
common.skip('missing crypto');
10+
return;
11+
}
12+
const tls = require('tls');
13+
14+
const options = {
15+
key: fs.readFileSync(`${common.fixturesDir}/keys/agent1-key.pem`),
16+
cert: fs.readFileSync(`${common.fixturesDir}/keys/agent1-cert.pem`)
17+
};
18+
19+
const server = tls.Server(options, common.mustCall((socket) => {
20+
socket.on('error', common.mustCall((err) => {
21+
assert.strictEqual(
22+
err.message,
23+
'TLS session renegotiation disabled for this socket');
24+
socket.destroy();
25+
server.close();
26+
}));
27+
// Disable renegotiation after the first chunk of data received.
28+
// Demonstrates that renegotiation works successfully up until
29+
// disableRenegotiation is called.
30+
socket.on('data', common.mustCall((chunk) => {
31+
socket.write(chunk);
32+
socket.disableRenegotiation();
33+
}));
34+
socket.on('secure', common.mustCall(() => {
35+
assert(socket._handle.handshakes < 2,
36+
`Too many handshakes [${socket._handle.handshakes}]`);
37+
}));
38+
}));
39+
40+
41+
server.listen(0, common.mustCall(() => {
42+
const port = server.address().port;
43+
const client =
44+
tls.connect({rejectUnauthorized: false, port: port}, common.mustCall(() => {
45+
client.write('');
46+
// Negotiation is still permitted for this first
47+
// attempt. This should succeed.
48+
client.renegotiate(
49+
{rejectUnauthorized: false},
50+
common.mustCall(() => {
51+
// Once renegotiation completes, we write some
52+
// data to the socket, which triggers the on
53+
// data event on the server. After that data
54+
// is received, disableRenegotiation is called.
55+
client.write('data', common.mustCall(() => {
56+
client.write('');
57+
// This second renegotiation attempt should fail
58+
// and the callback should never be invoked. The
59+
// server will simply drop the connection after
60+
// emitting the error.
61+
client.renegotiate(
62+
{rejectUnauthorized: false},
63+
common.mustNotCall());
64+
}));
65+
}));
66+
}));
67+
}));

0 commit comments

Comments
 (0)