Skip to content

Commit 62ad1d0

Browse files
Shigeki Ohtsurvagg
Shigeki Ohtsu
authored andcommitted
tls, crypto: add ALPN Support
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 1a968e6 commit 62ad1d0

11 files changed

+771
-31
lines changed

doc/api/tls.markdown

+32-9
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,15 @@ and tap `R<CR>` (that's the letter `R` followed by a carriage return) a few
6666
times.
6767

6868

69-
## NPN and SNI
69+
## ALPN, NPN and SNI
7070

7171
<!-- type=misc -->
7272

73-
NPN (Next Protocol Negotiation) and SNI (Server Name Indication) are TLS
73+
ALPN (Application-Layer Protocol Negotiation Extension), NPN (Next
74+
Protocol Negotiation) and SNI (Server Name Indication) are TLS
7475
handshake extensions allowing you:
7576

76-
* NPN - to use one TLS server for multiple protocols (HTTP, SPDY)
77+
* ALPN/NPN - to use one TLS server for multiple protocols (HTTP, SPDY, HTTP/2)
7778
* SNI - to use one TLS server for multiple hostnames with different SSL
7879
certificates.
7980

@@ -249,6 +250,12 @@ automatically set as a listener for the [secureConnection][] event. The
249250
- `NPNProtocols`: An array or `Buffer` of possible NPN protocols. (Protocols
250251
should be ordered by their priority).
251252

253+
- `ALPNProtocols`: An array or `Buffer` of possible ALPN
254+
protocols. (Protocols should be ordered by their priority). When
255+
the server receives both NPN and ALPN extensions from the client,
256+
ALPN takes precedence over NPN and the server does not send an NPN
257+
extension to the client.
258+
252259
- `SNICallback(servername, cb)`: A function that will be called if client
253260
supports SNI TLS extension. Two argument will be passed to it: `servername`,
254261
and `cb`. `SNICallback` should invoke `cb(null, ctx)`, where `ctx` is a
@@ -372,9 +379,16 @@ Creates a new client connection to the given `port` and `host` (old API) or
372379
fails; `err.code` contains the OpenSSL error code. Default: `true`.
373380

374381
- `NPNProtocols`: An array of strings or `Buffer`s containing supported NPN
375-
protocols. `Buffer`s should have following format: `0x05hello0x05world`,
376-
where first byte is next protocol name's length. (Passing array should
377-
usually be much simpler: `['hello', 'world']`.)
382+
protocols. `Buffer`s should have the following format:
383+
`0x05hello0x05world`, where first byte is next protocol name's
384+
length. (Passing array should usually be much simpler:
385+
`['hello', 'world']`.)
386+
387+
- `ALPNProtocols`: An array of strings or `Buffer`s containing
388+
supported ALPN protocols. `Buffer`s should have following format:
389+
`0x05hello0x05world`, where the first byte is the next protocol
390+
name's length. (Passing array should usually be much simpler:
391+
`['hello', 'world']`.)
378392

379393
- `servername`: Servername for SNI (Server Name Indication) TLS extension.
380394

@@ -476,6 +490,8 @@ Construct a new TLSSocket object from existing TCP socket.
476490

477491
- `NPNProtocols`: Optional, see [tls.createServer][]
478492

493+
- `ALPNProtocols`: Optional, see [tls.createServer][]
494+
479495
- `SNICallback`: Optional, see [tls.createServer][]
480496

481497
- `session`: Optional, a `Buffer` instance, containing TLS session
@@ -571,7 +587,13 @@ server. If `socket.authorized` is false, then
571587
`socket.authorizationError` is set to describe how authorization
572588
failed. Implied but worth mentioning: depending on the settings of the TLS
573589
server, you unauthorized connections may be accepted.
574-
`socket.npnProtocol` is a string containing selected NPN protocol.
590+
591+
`socket.npnProtocol` is a string containing the selected NPN protocol
592+
and `socket.alpnProtocol` is a string containing the selected ALPN
593+
protocol, When both NPN and ALPN extensions are received, ALPN takes
594+
precedence over NPN and the next protocol is selected by ALPN. When
595+
ALPN has no selected protocol, this returns false.
596+
575597
`socket.servername` is a string containing servername requested with
576598
SNI.
577599

@@ -744,8 +766,9 @@ The listener will be called no matter if the server's certificate was
744766
authorized or not. It is up to the user to test `tlsSocket.authorized`
745767
to see if the server certificate was signed by one of the specified CAs.
746768
If `tlsSocket.authorized === false` then the error can be found in
747-
`tlsSocket.authorizationError`. Also if NPN was used - you can check
748-
`tlsSocket.npnProtocol` for negotiated protocol.
769+
`tlsSocket.authorizationError`. Also if ALPN or NPN was used - you can
770+
check `tlsSocket.alpnProtocol` or `tlsSocket.npnProtocol` for the
771+
negotiated protocol.
749772

750773
### Event: 'OCSPResponse'
751774

lib/_tls_legacy.js

+13-2
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
@@ -726,6 +726,13 @@ function SecurePair(context, isServer, requestCert, rejectUnauthorized,
726726
this.npnProtocol = null;
727727
}
728728

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

@@ -778,6 +785,10 @@ SecurePair.prototype.maybeInitFinished = function() {
778785
this.npnProtocol = this.ssl.getNegotiatedProtocol();
779786
}
780787

788+
if (process.features.tls_alpn) {
789+
this.alpnProtocol = this.ssl.getALPNNegotiatedProtocol();
790+
}
791+
781792
if (process.features.tls_sni) {
782793
this.servername = this.ssl.getServername();
783794
}

lib/_tls_wrap.js

+17
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ function TLSSocket(socket, options) {
239239
this._SNICallback = null;
240240
this.servername = null;
241241
this.npnProtocol = null;
242+
this.alpnProtocol = null;
242243
this.authorized = false;
243244
this.authorizationError = null;
244245

@@ -453,6 +454,12 @@ TLSSocket.prototype._init = function(socket, wrap) {
453454
if (process.features.tls_npn && options.NPNProtocols)
454455
ssl.setNPNProtocols(options.NPNProtocols);
455456

457+
if (process.features.tls_alpn && options.ALPNProtocols) {
458+
// keep reference in secureContext not to be GC-ed
459+
ssl._secureContext.alpnBuffer = options.ALPNProtocols;
460+
ssl.setALPNProtocols(ssl._secureContext.alpnBuffer);
461+
}
462+
456463
if (options.handshakeTimeout > 0)
457464
this.setTimeout(options.handshakeTimeout, this._handleTimeout);
458465

@@ -559,6 +566,10 @@ TLSSocket.prototype._finishInit = function() {
559566
this.npnProtocol = this._handle.getNegotiatedProtocol();
560567
}
561568

569+
if (process.features.tls_alpn) {
570+
this.alpnProtocol = this.ssl.getALPNNegotiatedProtocol();
571+
}
572+
562573
if (process.features.tls_sni && this._tlsOptions.isServer) {
563574
this.servername = this._handle.getServername();
564575
}
@@ -766,6 +777,7 @@ function Server(/* [options], listener */) {
766777
rejectUnauthorized: self.rejectUnauthorized,
767778
handshakeTimeout: timeout,
768779
NPNProtocols: self.NPNProtocols,
780+
ALPNProtocols: self.ALPNProtocols,
769781
SNICallback: options.SNICallback || SNICallback
770782
});
771783

@@ -876,6 +888,8 @@ Server.prototype.setOptions = function(options) {
876888
this.honorCipherOrder = true;
877889
if (secureOptions) this.secureOptions = secureOptions;
878890
if (options.NPNProtocols) tls.convertNPNProtocols(options.NPNProtocols, this);
891+
if (options.ALPNProtocols)
892+
tls.convertALPNProtocols(options.ALPNProtocols, this);
879893
if (options.sessionIdContext) {
880894
this.sessionIdContext = options.sessionIdContext;
881895
} else {
@@ -968,8 +982,10 @@ exports.connect = function(/* [port, host], options, cb */) {
968982
(options.socket && options.socket._host) ||
969983
'localhost',
970984
NPN = {},
985+
ALPN = {},
971986
context = tls.createSecureContext(options);
972987
tls.convertNPNProtocols(options.NPNProtocols, NPN);
988+
tls.convertALPNProtocols(options.ALPNProtocols, ALPN);
973989

974990
var socket = new TLSSocket(options.socket, {
975991
pipe: options.path && !options.port,
@@ -979,6 +995,7 @@ exports.connect = function(/* [port, host], options, cb */) {
979995
rejectUnauthorized: options.rejectUnauthorized,
980996
session: options.session,
981997
NPNProtocols: NPN.NPNProtocols,
998+
ALPNProtocols: ALPN.ALPNProtocols,
982999
requestOCSP: options.requestOCSP
9831000
});
9841001

lib/https.js

+7
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

+33-18
Original file line numberDiff line numberDiff line change
@@ -33,27 +33,42 @@ exports.getCiphers = function() {
3333

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

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

src/env.h

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ namespace node {
4242
// for the sake of convenience. Strings should be ASCII-only.
4343
#define PER_ISOLATE_STRING_PROPERTIES(V) \
4444
V(address_string, "address") \
45+
V(alpn_buffer_string, "alpnBuffer") \
4546
V(args_string, "args") \
4647
V(argv_string, "argv") \
4748
V(arrow_message_string, "arrowMessage") \
@@ -205,6 +206,7 @@ namespace node {
205206
V(timestamp_string, "timestamp") \
206207
V(title_string, "title") \
207208
V(tls_npn_string, "tls_npn") \
209+
V(tls_alpn_string, "tls_alpn") \
208210
V(tls_ocsp_string, "tls_ocsp") \
209211
V(tls_sni_string, "tls_sni") \
210212
V(tls_string, "tls") \

src/node.cc

+7
Original file line numberDiff line numberDiff line change
@@ -2582,6 +2582,13 @@ static Local<Object> GetFeatures(Environment* env) {
25822582
#endif
25832583
obj->Set(env->tls_npn_string(), tls_npn);
25842584

2585+
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2586+
Local<Boolean> tls_alpn = True(env->isolate());
2587+
#else
2588+
Local<Boolean> tls_alpn = False(env->isolate());
2589+
#endif
2590+
obj->Set(env->tls_alpn_string(), tls_alpn);
2591+
25852592
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
25862593
Local<Boolean> tls_sni = True(env->isolate());
25872594
#else

src/node_constants.cc

+5
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)