Skip to content

Commit e750f14

Browse files
Shigeki OhtsuMylesBorins
Shigeki Ohtsu
authored andcommitted
tls, crypto: add ALPN Support
cherry-pick 802a2e7 from v6-staging. ALPN is added to tls according to RFC7301, which supersedes NPN. When the server receives both NPN and ALPN extensions from the client, ALPN takes precedence over NPN and the server does not send NPN extension to the client. alpnProtocol in TLSSocket always returns false when no selected protocol exists by ALPN. In https server, http/1.1 token is always set when no options.ALPNProtocols exists. PR-URL: #2564 Reviewed-By: Fedor Indutny <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]>
1 parent 7a9c8d8 commit e750f14

11 files changed

+769
-33
lines changed

doc/api/tls.md

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,15 @@ of an application. The `--tls-cipher-list` switch should by used only if
120120
absolutely necessary.
121121

122122

123-
## NPN and SNI
123+
## ALPN, NPN and SNI
124124

125125
<!-- type=misc -->
126126

127-
NPN (Next Protocol Negotiation) and SNI (Server Name Indication) are TLS
127+
ALPN (Application-Layer Protocol Negotiation Extension), NPN (Next
128+
Protocol Negotiation) and SNI (Server Name Indication) are TLS
128129
handshake extensions allowing you:
129130

130-
* NPN - to use one TLS server for multiple protocols (HTTP, SPDY)
131+
* ALPN/NPN - to use one TLS server for multiple protocols (HTTP, SPDY, HTTP/2)
131132
* SNI - to use one TLS server for multiple hostnames with different SSL
132133
certificates.
133134

@@ -305,7 +306,13 @@ server. If `socket.authorized` is false, then
305306
`socket.authorizationError` is set to describe how authorization
306307
failed. Implied but worth mentioning: depending on the settings of the TLS
307308
server, you unauthorized connections may be accepted.
308-
`socket.npnProtocol` is a string containing selected NPN protocol.
309+
310+
`socket.npnProtocol` is a string containing the selected NPN protocol
311+
and `socket.alpnProtocol` is a string containing the selected ALPN
312+
protocol, When both NPN and ALPN extensions are received, ALPN takes
313+
precedence over NPN and the next protocol is selected by ALPN. When
314+
ALPN has no selected protocol, this returns false.
315+
309316
`socket.servername` is a string containing servername requested with
310317
SNI.
311318

@@ -429,6 +436,8 @@ Construct a new TLSSocket object from existing TCP socket.
429436

430437
- `NPNProtocols`: Optional, see [`tls.createServer()`][]
431438

439+
- `ALPNProtocols`: Optional, see [tls.createServer][]
440+
432441
- `SNICallback`: Optional, see [`tls.createServer()`][]
433442

434443
- `session`: Optional, a `Buffer` instance, containing TLS session
@@ -460,8 +469,9 @@ The listener will be called no matter if the server's certificate was
460469
authorized or not. It is up to the user to test `tlsSocket.authorized`
461470
to see if the server certificate was signed by one of the specified CAs.
462471
If `tlsSocket.authorized === false` then the error can be found in
463-
`tlsSocket.authorizationError`. Also if NPN was used you can check
464-
`tlsSocket.npnProtocol` for negotiated protocol.
472+
`tlsSocket.authorizationError`. Also if ALPN or NPN was used - you can
473+
check `tlsSocket.alpnProtocol` or `tlsSocket.npnProtocol` for the
474+
negotiated protocol.
465475

466476
### tlsSocket.address()
467477
<!-- YAML
@@ -684,6 +694,12 @@ Creates a new client connection to the given `port` and `host` (old API) or
684694
where first byte is next protocol name's length. (Passing array should
685695
usually be much simpler: `['hello', 'world']`.)
686696

697+
- `ALPNProtocols`: An array of strings or `Buffer`s containing
698+
supported ALPN protocols. `Buffer`s should have following format:
699+
`0x05hello0x05world`, where the first byte is the next protocol
700+
name's length. (Passing array should usually be much simpler:
701+
`['hello', 'world']`.)
702+
687703
- `servername`: Servername for SNI (Server Name Indication) TLS extension.
688704

689705
- `checkServerIdentity(servername, cert)`: Provide an override for checking
@@ -925,6 +941,12 @@ automatically set as a listener for the [`'secureConnection'`][] event. The
925941
- `NPNProtocols`: An array or `Buffer` of possible NPN protocols. (Protocols
926942
should be ordered by their priority).
927943

944+
- `ALPNProtocols`: An array or `Buffer` of possible ALPN
945+
protocols. (Protocols should be ordered by their priority). When
946+
the server receives both NPN and ALPN extensions from the client,
947+
ALPN takes precedence over NPN and the server does not send an NPN
948+
extension to the client.
949+
928950
- `SNICallback(servername, cb)`: A function that will be called if client
929951
supports SNI TLS extension. Two argument will be passed to it: `servername`,
930952
and `cb`. `SNICallback` should invoke `cb(null, ctx)`, where `ctx` is a

lib/_tls_legacy.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ CryptoStream.prototype._write = function _write(data, encoding, cb) {
177177
if (this.pair.encrypted._internallyPendingBytes())
178178
this.pair.encrypted.read(0);
179179

180-
// Get NPN and Server name when ready
180+
// Get ALPN, NPN and Server name when ready
181181
this.pair.maybeInitFinished();
182182

183183
// Whole buffer was written
@@ -273,7 +273,7 @@ CryptoStream.prototype._read = function _read(size) {
273273
bytesRead < size &&
274274
this.pair.ssl !== null);
275275

276-
// Get NPN and Server name when ready
276+
// Get ALPN, NPN and Server name when ready
277277
this.pair.maybeInitFinished();
278278

279279
// Create new buffer if previous was filled up
@@ -729,6 +729,13 @@ function SecurePair(context, isServer, requestCert, rejectUnauthorized,
729729
this.npnProtocol = null;
730730
}
731731

732+
if (process.features.tls_alpn && options.ALPNProtocols) {
733+
// keep reference in secureContext not to be GC-ed
734+
this.ssl._secureContext.alpnBuffer = options.ALPNProtocols;
735+
this.ssl.setALPNrotocols(this.ssl._secureContext.alpnBuffer);
736+
this.alpnProtocol = null;
737+
}
738+
732739
/* Acts as a r/w stream to the cleartext side of the stream. */
733740
this.cleartext = new CleartextStream(this, options.cleartext);
734741

@@ -781,6 +788,10 @@ SecurePair.prototype.maybeInitFinished = function() {
781788
this.npnProtocol = this.ssl.getNegotiatedProtocol();
782789
}
783790

791+
if (process.features.tls_alpn) {
792+
this.alpnProtocol = this.ssl.getALPNNegotiatedProtocol();
793+
}
794+
784795
if (process.features.tls_sni) {
785796
this.servername = this.ssl.getServername();
786797
}

lib/_tls_wrap.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ function TLSSocket(socket, options) {
258258
this._SNICallback = null;
259259
this.servername = null;
260260
this.npnProtocol = null;
261+
this.alpnProtocol = null;
261262
this.authorized = false;
262263
this.authorizationError = null;
263264

@@ -486,6 +487,12 @@ TLSSocket.prototype._init = function(socket, wrap) {
486487
if (process.features.tls_npn && options.NPNProtocols)
487488
ssl.setNPNProtocols(options.NPNProtocols);
488489

490+
if (process.features.tls_alpn && options.ALPNProtocols) {
491+
// keep reference in secureContext not to be GC-ed
492+
ssl._secureContext.alpnBuffer = options.ALPNProtocols;
493+
ssl.setALPNProtocols(ssl._secureContext.alpnBuffer);
494+
}
495+
489496
if (options.handshakeTimeout > 0)
490497
this.setTimeout(options.handshakeTimeout, this._handleTimeout);
491498

@@ -592,6 +599,10 @@ TLSSocket.prototype._finishInit = function() {
592599
this.npnProtocol = this._handle.getNegotiatedProtocol();
593600
}
594601

602+
if (process.features.tls_alpn) {
603+
this.alpnProtocol = this.ssl.getALPNNegotiatedProtocol();
604+
}
605+
595606
if (process.features.tls_sni && this._tlsOptions.isServer) {
596607
this.servername = this._handle.getServername();
597608
}
@@ -792,6 +803,7 @@ function Server(/* [options], listener */) {
792803
rejectUnauthorized: self.rejectUnauthorized,
793804
handshakeTimeout: timeout,
794805
NPNProtocols: self.NPNProtocols,
806+
ALPNProtocols: self.ALPNProtocols,
795807
SNICallback: options.SNICallback || SNICallback
796808
});
797809

@@ -902,6 +914,8 @@ Server.prototype.setOptions = function(options) {
902914
this.honorCipherOrder = true;
903915
if (secureOptions) this.secureOptions = secureOptions;
904916
if (options.NPNProtocols) tls.convertNPNProtocols(options.NPNProtocols, this);
917+
if (options.ALPNProtocols)
918+
tls.convertALPNProtocols(options.ALPNProtocols, this);
905919
if (options.sessionIdContext) {
906920
this.sessionIdContext = options.sessionIdContext;
907921
} else {
@@ -986,12 +1000,14 @@ exports.connect = function(/* [port, host], options, cb */) {
9861000
assert(typeof options.checkServerIdentity === 'function');
9871001

9881002
var hostname = options.servername ||
989-
options.host ||
990-
(options.socket && options.socket._host) ||
991-
'localhost';
992-
const NPN = {};
993-
const context = options.secureContext || tls.createSecureContext(options);
1003+
options.host ||
1004+
(options.socket && options.socket._host) ||
1005+
'localhost';
1006+
var NPN = {};
1007+
var ALPN = {};
1008+
var context = options.secureContext || tls.createSecureContext(options);
9941009
tls.convertNPNProtocols(options.NPNProtocols, NPN);
1010+
tls.convertALPNProtocols(options.ALPNProtocols, ALPN);
9951011

9961012
var socket = new TLSSocket(options.socket, {
9971013
pipe: options.path && !options.port,
@@ -1001,6 +1017,7 @@ exports.connect = function(/* [port, host], options, cb */) {
10011017
rejectUnauthorized: options.rejectUnauthorized,
10021018
session: options.session,
10031019
NPNProtocols: NPN.NPNProtocols,
1020+
ALPNProtocols: ALPN.ALPNProtocols,
10041021
requestOCSP: options.requestOCSP
10051022
});
10061023

lib/https.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ function Server(opts, requestListener) {
1414
opts.NPNProtocols = ['http/1.1', 'http/1.0'];
1515
}
1616

17+
if (process.features.tls_alpn && !opts.ALPNProtocols) {
18+
// http/1.0 is not defined as Protocol IDs in IANA
19+
// http://www.iana.org/assignments/tls-extensiontype-values
20+
// /tls-extensiontype-values.xhtml#alpn-protocol-ids
21+
opts.ALPNProtocols = ['http/1.1'];
22+
}
23+
1724
tls.Server.call(this, opts, http._connectionListener);
1825

1926
this.httpAllowHalfOpen = false;

lib/tls.js

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,42 @@ exports.getCiphers = function() {
3232

3333
// Convert protocols array into valid OpenSSL protocols list
3434
// ("\x06spdy/2\x08http/1.1\x08http/1.0")
35-
exports.convertNPNProtocols = function convertNPNProtocols(NPNProtocols, out) {
36-
// If NPNProtocols is Array - translate it into buffer
37-
if (Array.isArray(NPNProtocols)) {
38-
var buff = new Buffer(NPNProtocols.reduce(function(p, c) {
39-
return p + 1 + Buffer.byteLength(c);
40-
}, 0));
41-
42-
NPNProtocols.reduce(function(offset, c) {
43-
var clen = Buffer.byteLength(c);
44-
buff[offset] = clen;
45-
buff.write(c, offset + 1);
46-
47-
return offset + 1 + clen;
48-
}, 0);
49-
50-
NPNProtocols = buff;
35+
function convertProtocols(protocols) {
36+
var buff = new Buffer(protocols.reduce(function(p, c) {
37+
return p + 1 + Buffer.byteLength(c);
38+
}, 0));
39+
40+
protocols.reduce(function(offset, c) {
41+
var clen = Buffer.byteLength(c);
42+
buff[offset] = clen;
43+
buff.write(c, offset + 1);
44+
45+
return offset + 1 + clen;
46+
}, 0);
47+
48+
return buff;
49+
}
50+
51+
exports.convertNPNProtocols = function(protocols, out) {
52+
// If protocols is Array - translate it into buffer
53+
if (Array.isArray(protocols)) {
54+
protocols = convertProtocols(protocols);
5155
}
56+
// If it's already a Buffer - store it
57+
if (protocols instanceof Buffer) {
58+
out.NPNProtocols = protocols;
59+
}
60+
};
5261

62+
exports.convertALPNProtocols = function(protocols, out) {
63+
// If protocols is Array - translate it into buffer
64+
if (Array.isArray(protocols)) {
65+
protocols = convertProtocols(protocols);
66+
}
5367
// If it's already a Buffer - store it
54-
if (NPNProtocols instanceof Buffer) {
55-
out.NPNProtocols = Buffer.from(NPNProtocols);
68+
if (protocols instanceof Buffer) {
69+
// copy new buffer not to be modified by user
70+
out.ALPNProtocols = Buffer.from(protocols);
5671
}
5772
};
5873

src/env.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ namespace node {
4343
// for the sake of convenience. Strings should be ASCII-only.
4444
#define PER_ISOLATE_STRING_PROPERTIES(V) \
4545
V(address_string, "address") \
46+
V(alpn_buffer_string, "alpnBuffer") \
4647
V(args_string, "args") \
4748
V(argv_string, "argv") \
4849
V(arrow_message_string, "arrowMessage") \
@@ -208,6 +209,7 @@ namespace node {
208209
V(timestamp_string, "timestamp") \
209210
V(title_string, "title") \
210211
V(tls_npn_string, "tls_npn") \
212+
V(tls_alpn_string, "tls_alpn") \
211213
V(tls_ocsp_string, "tls_ocsp") \
212214
V(tls_sni_string, "tls_sni") \
213215
V(tls_string, "tls") \

src/node.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2740,6 +2740,13 @@ static Local<Object> GetFeatures(Environment* env) {
27402740
#endif
27412741
obj->Set(env->tls_npn_string(), tls_npn);
27422742

2743+
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2744+
Local<Boolean> tls_alpn = True(env->isolate());
2745+
#else
2746+
Local<Boolean> tls_alpn = False(env->isolate());
2747+
#endif
2748+
obj->Set(env->tls_alpn_string(), tls_alpn);
2749+
27432750
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
27442751
Local<Boolean> tls_sni = True(env->isolate());
27452752
#else

src/node_constants.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,11 @@ void DefineOpenSSLConstants(Local<Object> target) {
935935
NODE_DEFINE_CONSTANT(target, NPN_ENABLED);
936936
#endif
937937

938+
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
939+
#define ALPN_ENABLED 1
940+
NODE_DEFINE_CONSTANT(target, ALPN_ENABLED);
941+
#endif
942+
938943
#ifdef RSA_PKCS1_PADDING
939944
NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PADDING);
940945
#endif

0 commit comments

Comments
 (0)