diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown index 4907c65328cab0..ca891bb40fc5ce 100644 --- a/doc/api/tls.markdown +++ b/doc/api/tls.markdown @@ -66,14 +66,15 @@ and tap `R` (that's the letter `R` followed by a carriage return) a few times. -## NPN and SNI +## ALPN, NPN and SNI -NPN (Next Protocol Negotiation) and SNI (Server Name Indication) are TLS +ALPN (Application-Layer Protocol Negotiation Extension), NPN (Next +Protocol Negotiation) and SNI (Server Name Indication) are TLS handshake extensions allowing you: - * NPN - to use one TLS server for multiple protocols (HTTP, SPDY) + * ALPN/NPN - to use one TLS server for multiple protocols (HTTP, SPDY, HTTP/2) * SNI - to use one TLS server for multiple hostnames with different SSL certificates. @@ -249,6 +250,12 @@ automatically set as a listener for the [secureConnection][] event. The - `NPNProtocols`: An array or `Buffer` of possible NPN protocols. (Protocols should be ordered by their priority). + - `ALPNProtocols`: An array or `Buffer` of possible ALPN + protocols. (Protocols should be ordered by their priority). When + the server receives both NPN and ALPN extensions from the client, + ALPN takes precedence over NPN and the server does not send an NPN + extension to the client. + - `SNICallback(servername, cb)`: A function that will be called if client supports SNI TLS extension. Two argument will be passed to it: `servername`, 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 fails; `err.code` contains the OpenSSL error code. Default: `true`. - `NPNProtocols`: An array of strings or `Buffer`s containing supported NPN - protocols. `Buffer`s should have following format: `0x05hello0x05world`, - where first byte is next protocol name's length. (Passing array should - usually be much simpler: `['hello', 'world']`.) + protocols. `Buffer`s should have the following format: + `0x05hello0x05world`, where first byte is next protocol name's + length. (Passing array should usually be much simpler: + `['hello', 'world']`.) + + - `ALPNProtocols`: An array of strings or `Buffer`s containing + supported ALPN protocols. `Buffer`s should have following format: + `0x05hello0x05world`, where the first byte is the next protocol + name's length. (Passing array should usually be much simpler: + `['hello', 'world']`.) - `servername`: Servername for SNI (Server Name Indication) TLS extension. @@ -476,6 +490,8 @@ Construct a new TLSSocket object from existing TCP socket. - `NPNProtocols`: Optional, see [tls.createServer][] + - `ALPNProtocols`: Optional, see [tls.createServer][] + - `SNICallback`: Optional, see [tls.createServer][] - `session`: Optional, a `Buffer` instance, containing TLS session @@ -571,7 +587,13 @@ server. If `socket.authorized` is false, then `socket.authorizationError` is set to describe how authorization failed. Implied but worth mentioning: depending on the settings of the TLS server, you unauthorized connections may be accepted. -`socket.npnProtocol` is a string containing selected NPN protocol. + +`socket.npnProtocol` is a string containing the selected NPN protocol +and `socket.alpnProtocol` is a string containing the selected ALPN +protocol, When both NPN and ALPN extensions are received, ALPN takes +precedence over NPN and the next protocol is selected by ALPN. When +ALPN has no selected protocol, this returns false. + `socket.servername` is a string containing servername requested with SNI. @@ -744,8 +766,9 @@ The listener will be called no matter if the server's certificate was authorized or not. It is up to the user to test `tlsSocket.authorized` to see if the server certificate was signed by one of the specified CAs. If `tlsSocket.authorized === false` then the error can be found in -`tlsSocket.authorizationError`. Also if NPN was used - you can check -`tlsSocket.npnProtocol` for negotiated protocol. +`tlsSocket.authorizationError`. Also if ALPN or NPN was used - you can +check `tlsSocket.alpnProtocol` or `tlsSocket.npnProtocol` for the +negotiated protocol. ### Event: 'OCSPResponse' diff --git a/lib/_tls_legacy.js b/lib/_tls_legacy.js index 7f7707d149dfa2..8c079e341b5b05 100644 --- a/lib/_tls_legacy.js +++ b/lib/_tls_legacy.js @@ -177,7 +177,7 @@ CryptoStream.prototype._write = function write(data, encoding, cb) { if (this.pair.encrypted._internallyPendingBytes()) this.pair.encrypted.read(0); - // Get NPN and Server name when ready + // Get ALPN, NPN and Server name when ready this.pair.maybeInitFinished(); // Whole buffer was written @@ -273,7 +273,7 @@ CryptoStream.prototype._read = function read(size) { bytesRead < size && this.pair.ssl !== null); - // Get NPN and Server name when ready + // Get ALPN, NPN and Server name when ready this.pair.maybeInitFinished(); // Create new buffer if previous was filled up @@ -726,6 +726,13 @@ function SecurePair(context, isServer, requestCert, rejectUnauthorized, this.npnProtocol = null; } + if (process.features.tls_alpn && options.ALPNProtocols) { + // keep reference in secureContext not to be GC-ed + this.ssl._secureContext.alpnBuffer = options.ALPNProtocols; + this.ssl.setALPNrotocols(this.ssl._secureContext.alpnBuffer); + this.alpnProtocol = null; + } + /* Acts as a r/w stream to the cleartext side of the stream. */ this.cleartext = new CleartextStream(this, options.cleartext); @@ -778,6 +785,10 @@ SecurePair.prototype.maybeInitFinished = function() { this.npnProtocol = this.ssl.getNegotiatedProtocol(); } + if (process.features.tls_alpn) { + this.alpnProtocol = this.ssl.getALPNNegotiatedProtocol(); + } + if (process.features.tls_sni) { this.servername = this.ssl.getServername(); } diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index f0273471772137..d918656a360c7a 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -239,6 +239,7 @@ function TLSSocket(socket, options) { this._SNICallback = null; this.servername = null; this.npnProtocol = null; + this.alpnProtocol = null; this.authorized = false; this.authorizationError = null; @@ -453,6 +454,12 @@ TLSSocket.prototype._init = function(socket, wrap) { if (process.features.tls_npn && options.NPNProtocols) ssl.setNPNProtocols(options.NPNProtocols); + if (process.features.tls_alpn && options.ALPNProtocols) { + // keep reference in secureContext not to be GC-ed + ssl._secureContext.alpnBuffer = options.ALPNProtocols; + ssl.setALPNProtocols(ssl._secureContext.alpnBuffer); + } + if (options.handshakeTimeout > 0) this.setTimeout(options.handshakeTimeout, this._handleTimeout); @@ -559,6 +566,10 @@ TLSSocket.prototype._finishInit = function() { this.npnProtocol = this._handle.getNegotiatedProtocol(); } + if (process.features.tls_alpn) { + this.alpnProtocol = this.ssl.getALPNNegotiatedProtocol(); + } + if (process.features.tls_sni && this._tlsOptions.isServer) { this.servername = this._handle.getServername(); } @@ -766,6 +777,7 @@ function Server(/* [options], listener */) { rejectUnauthorized: self.rejectUnauthorized, handshakeTimeout: timeout, NPNProtocols: self.NPNProtocols, + ALPNProtocols: self.ALPNProtocols, SNICallback: options.SNICallback || SNICallback }); @@ -876,6 +888,8 @@ Server.prototype.setOptions = function(options) { this.honorCipherOrder = true; if (secureOptions) this.secureOptions = secureOptions; if (options.NPNProtocols) tls.convertNPNProtocols(options.NPNProtocols, this); + if (options.ALPNProtocols) + tls.convertALPNProtocols(options.ALPNProtocols, this); if (options.sessionIdContext) { this.sessionIdContext = options.sessionIdContext; } else { @@ -968,8 +982,10 @@ exports.connect = function(/* [port, host], options, cb */) { (options.socket && options.socket._host) || 'localhost', NPN = {}, + ALPN = {}, context = tls.createSecureContext(options); tls.convertNPNProtocols(options.NPNProtocols, NPN); + tls.convertALPNProtocols(options.ALPNProtocols, ALPN); var socket = new TLSSocket(options.socket, { pipe: options.path && !options.port, @@ -979,6 +995,7 @@ exports.connect = function(/* [port, host], options, cb */) { rejectUnauthorized: options.rejectUnauthorized, session: options.session, NPNProtocols: NPN.NPNProtocols, + ALPNProtocols: ALPN.ALPNProtocols, requestOCSP: options.requestOCSP }); diff --git a/lib/https.js b/lib/https.js index abe4a20907dfdd..edf0aa4432f82d 100644 --- a/lib/https.js +++ b/lib/https.js @@ -14,6 +14,13 @@ function Server(opts, requestListener) { opts.NPNProtocols = ['http/1.1', 'http/1.0']; } + if (process.features.tls_alpn && !opts.ALPNProtocols) { + // http/1.0 is not defined as Protocol IDs in IANA + // http://www.iana.org/assignments/tls-extensiontype-values + // /tls-extensiontype-values.xhtml#alpn-protocol-ids + opts.ALPNProtocols = ['http/1.1']; + } + tls.Server.call(this, opts, http._connectionListener); this.httpAllowHalfOpen = false; diff --git a/lib/tls.js b/lib/tls.js index 0d85a948dcc511..e269e800d31d1c 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -33,27 +33,42 @@ exports.getCiphers = function() { // Convert protocols array into valid OpenSSL protocols list // ("\x06spdy/2\x08http/1.1\x08http/1.0") -exports.convertNPNProtocols = function convertNPNProtocols(NPNProtocols, out) { - // If NPNProtocols is Array - translate it into buffer - if (Array.isArray(NPNProtocols)) { - var buff = new Buffer(NPNProtocols.reduce(function(p, c) { - return p + 1 + Buffer.byteLength(c); - }, 0)); - - NPNProtocols.reduce(function(offset, c) { - var clen = Buffer.byteLength(c); - buff[offset] = clen; - buff.write(c, offset + 1); - - return offset + 1 + clen; - }, 0); - - NPNProtocols = buff; +function convertProtocols(protocols) { + var buff = new Buffer(protocols.reduce(function(p, c) { + return p + 1 + Buffer.byteLength(c); + }, 0)); + + protocols.reduce(function(offset, c) { + var clen = Buffer.byteLength(c); + buff[offset] = clen; + buff.write(c, offset + 1); + + return offset + 1 + clen; + }, 0); + + return buff; +}; + +exports.convertNPNProtocols = function(protocols, out) { + // If protocols is Array - translate it into buffer + if (Array.isArray(protocols)) { + protocols = convertProtocols(protocols); } + // If it's already a Buffer - store it + if (protocols instanceof Buffer) { + out.NPNProtocols = protocols; + } +}; +exports.convertALPNProtocols = function(protocols, out) { + // If protocols is Array - translate it into buffer + if (Array.isArray(protocols)) { + protocols = convertProtocols(protocols); + } // If it's already a Buffer - store it - if (NPNProtocols instanceof Buffer) { - out.NPNProtocols = NPNProtocols; + if (protocols instanceof Buffer) { + // copy new buffer not to be modified by user + out.ALPNProtocols = new Buffer(protocols); } }; diff --git a/src/env.h b/src/env.h index b79ef4ae3e6587..b3f55c173a239e 100644 --- a/src/env.h +++ b/src/env.h @@ -42,6 +42,7 @@ namespace node { // for the sake of convenience. Strings should be ASCII-only. #define PER_ISOLATE_STRING_PROPERTIES(V) \ V(address_string, "address") \ + V(alpn_buffer_string, "alpnBuffer") \ V(args_string, "args") \ V(argv_string, "argv") \ V(arrow_message_string, "arrowMessage") \ @@ -205,6 +206,7 @@ namespace node { V(timestamp_string, "timestamp") \ V(title_string, "title") \ V(tls_npn_string, "tls_npn") \ + V(tls_alpn_string, "tls_alpn") \ V(tls_ocsp_string, "tls_ocsp") \ V(tls_sni_string, "tls_sni") \ V(tls_string, "tls") \ diff --git a/src/node.cc b/src/node.cc index 10e7da125d49a7..4de2f97491cf46 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2582,6 +2582,13 @@ static Local GetFeatures(Environment* env) { #endif obj->Set(env->tls_npn_string(), tls_npn); +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + Local tls_alpn = True(env->isolate()); +#else + Local tls_alpn = False(env->isolate()); +#endif + obj->Set(env->tls_alpn_string(), tls_alpn); + #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB Local tls_sni = True(env->isolate()); #else diff --git a/src/node_constants.cc b/src/node_constants.cc index 51c2ee814ae504..1259f83697c015 100644 --- a/src/node_constants.cc +++ b/src/node_constants.cc @@ -935,6 +935,11 @@ void DefineOpenSSLConstants(Local target) { NODE_DEFINE_CONSTANT(target, NPN_ENABLED); #endif +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation +#define ALPN_ENABLED 1 + NODE_DEFINE_CONSTANT(target, ALPN_ENABLED); +#endif + #ifdef RSA_PKCS1_PADDING NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PADDING); #endif diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 0185970c1cfcfa..1f50b643b5900f 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -167,6 +167,15 @@ template void SSLWrap::DestroySSL(); template int SSLWrap::SSLCertCallback(SSL* s, void* arg); template void SSLWrap::WaitForCertCb(CertCb cb, void* arg); +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation +template int SSLWrap::SelectALPNCallback( + SSL* s, + const unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg); +#endif // TLSEXT_TYPE_application_layer_protocol_negotiation static void crypto_threadid_cb(CRYPTO_THREADID* tid) { static_assert(sizeof(uv_thread_t) <= sizeof(void*), // NOLINT(runtime/sizeof) @@ -1148,6 +1157,9 @@ void SSLWrap::AddMethods(Environment* env, Local t) { env->SetProtoMethod(t, "setNPNProtocols", SetNPNProtocols); #endif + env->SetProtoMethod(t, "getALPNNegotiatedProtocol", GetALPNNegotiatedProto); + env->SetProtoMethod(t, "setALPNProtocols", SetALPNProtocols); + t->PrototypeTemplate()->SetAccessor( FIXED_ONE_BYTE_STRING(env->isolate(), "_external"), SSLGetter, @@ -2010,6 +2022,98 @@ void SSLWrap::SetNPNProtocols(const FunctionCallbackInfo& args) { } #endif // OPENSSL_NPN_NEGOTIATED +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation +typedef struct tlsextalpnctx_st { + unsigned char* data; + unsigned short len; +} tlsextalpnctx; + +template +int SSLWrap::SelectALPNCallback(SSL* s, + const unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg) { + Base* w = static_cast(SSL_get_app_data(s)); + Environment* env = w->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local alpn_buffer = + w->object()->GetHiddenValue(env->alpn_buffer_string()); + CHECK(Buffer::HasInstance(alpn_buffer)); + const unsigned char* alpn_protos = + reinterpret_cast(Buffer::Data(alpn_buffer)); + unsigned alpn_protos_len = Buffer::Length(alpn_buffer); + int status = SSL_select_next_proto(const_cast(out), outlen, + alpn_protos, alpn_protos_len, in, inlen); + + switch (status) { + case OPENSSL_NPN_NO_OVERLAP: + // According to 3.2. Protocol Selection of RFC7301, + // fatal no_application_protocol alert shall be sent + // but current openssl does not support it yet. See + // https://rt.openssl.org/Ticket/Display.html?id=3463&user=guest&pass=guest + // Instead, we send a warning alert for now. + return SSL_TLSEXT_ERR_ALERT_WARNING; + case OPENSSL_NPN_NEGOTIATED: + return SSL_TLSEXT_ERR_OK; + default: + return SSL_TLSEXT_ERR_ALERT_FATAL; + } +} +#endif // TLSEXT_TYPE_application_layer_protocol_negotiation + + +template +void SSLWrap::GetALPNNegotiatedProto( + const FunctionCallbackInfo& args) { +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + HandleScope scope(args.GetIsolate()); + Base* w = Unwrap(args.Holder()); + + const unsigned char* alpn_proto; + unsigned int alpn_proto_len; + + SSL_get0_alpn_selected(w->ssl_, &alpn_proto, &alpn_proto_len); + + if (!alpn_proto) + return args.GetReturnValue().Set(false); + + args.GetReturnValue().Set( + OneByteString(args.GetIsolate(), alpn_proto, alpn_proto_len)); +#endif // TLSEXT_TYPE_application_layer_protocol_negotiation +} + + +template +void SSLWrap::SetALPNProtocols( + const FunctionCallbackInfo& args) { +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + HandleScope scope(args.GetIsolate()); + Base* w = Unwrap(args.Holder()); + Environment* env = w->env(); + if (args.Length() < 1 || !Buffer::HasInstance(args[0])) + return env->ThrowTypeError("Must give a Buffer as first argument"); + + if (w->is_client()) { + const unsigned char* alpn_protos = + reinterpret_cast(Buffer::Data(args[0])); + unsigned alpn_protos_len = Buffer::Length(args[0]); + int r = SSL_set_alpn_protos(w->ssl_, alpn_protos, alpn_protos_len); + CHECK_EQ(r, 0); + } else { + Local alpn_buffer = Local::New(env->isolate(), args[0]); + bool ret = w->object()->SetHiddenValue(env->alpn_buffer_string(), + alpn_buffer); + CHECK(ret); + // Server should select ALPN protocol from list of advertised by client + SSL_CTX_set_alpn_select_cb(w->ssl_->ctx, SelectALPNCallback, nullptr); + } +#endif // TLSEXT_TYPE_application_layer_protocol_negotiation +} + #ifdef NODE__HAVE_TLSEXT_STATUS_CB template diff --git a/src/node_crypto.h b/src/node_crypto.h index c276df04748942..4aceb41cb8ce19 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -5,9 +5,7 @@ #include "node_crypto_clienthello.h" // ClientHelloParser #include "node_crypto_clienthello-inl.h" -#ifdef OPENSSL_NPN_NEGOTIATED #include "node_buffer.h" -#endif #include "env.h" #include "async-wrap.h" @@ -187,6 +185,7 @@ class SSLWrap { #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB sni_context_.Reset(); #endif + #ifdef NODE__HAVE_TLSEXT_STATUS_CB ocsp_response_.Reset(); #endif // NODE__HAVE_TLSEXT_STATUS_CB @@ -259,6 +258,16 @@ class SSLWrap { unsigned int inlen, void* arg); #endif // OPENSSL_NPN_NEGOTIATED + + static void GetALPNNegotiatedProto( + const v8::FunctionCallbackInfo& args); + static void SetALPNProtocols(const v8::FunctionCallbackInfo& args); + static int SelectALPNCallback(SSL* s, + const unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg); static int TLSExtStatusCallback(SSL* s, void* arg); static int SSLCertCallback(SSL* s, void* arg); static void SSLGetter(v8::Local property, diff --git a/test/parallel/test-tls-alpn-server-client.js b/test/parallel/test-tls-alpn-server-client.js new file mode 100644 index 00000000000000..e5d809d1489aee --- /dev/null +++ b/test/parallel/test-tls-alpn-server-client.js @@ -0,0 +1,540 @@ +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) { + console.log('1..0 # Skipped: missing crypto'); + return; +} + +if (!process.features.tls_alpn) { + console.error('Skipping because node compiled without OpenSSL or ' + + 'with old OpenSSL version.'); + process.exit(0); +} + +const assert = require('assert'); +const fs = require('fs'); +const tls = require('tls'); + +function filenamePEM(n) { + return require('path').join(common.fixturesDir, 'keys', n + '.pem'); +} + +function loadPEM(n) { + return fs.readFileSync(filenamePEM(n)); +} + +var serverPort = common.PORT; +var serverIP = common.localhostIPv4; + +function checkResults(result, expected) { + assert.strictEqual(result.server.ALPN, expected.server.ALPN); + assert.strictEqual(result.server.NPN, expected.server.NPN); + assert.strictEqual(result.client.ALPN, expected.client.ALPN); + assert.strictEqual(result.client.NPN, expected.client.NPN); +} + +function runTest(clientsOptions, serverOptions, cb) { + serverOptions.key = loadPEM('agent2-key'); + serverOptions.cert = loadPEM('agent2-cert'); + var results = []; + var index = 0; + var server = tls.createServer(serverOptions, function(c) { + results[index].server = {ALPN: c.alpnProtocol, NPN: c.npnProtocol}; + }); + + server.listen(serverPort, serverIP, function() { + connectClient(clientsOptions); + }); + + function connectClient(options) { + var opt = options.shift(); + opt.port = serverPort; + opt.host = serverIP; + opt.rejectUnauthorized = false; + + results[index] = {}; + var client = tls.connect(opt, function() { + results[index].client = {ALPN: client.alpnProtocol, + NPN: client.npnProtocol}; + client.destroy(); + if (options.length) { + index++; + connectClient(options); + } else { + server.close(); + cb(results); + } + }); + }; + +} + +// Server: ALPN/NPN, Client: ALPN/NPN +function Test1() { + var serverOptions = { + ALPNProtocols: ['a', 'b', 'c'], + NPNProtocols: ['a', 'b', 'c'] + }; + + var clientsOptions = [{ + ALPNProtocols: ['a', 'b', 'c'], + NPNProtocols: ['a', 'b', 'c'] + }, { + ALPNProtocols: ['c', 'b', 'e'], + NPNProtocols: ['c', 'b', 'e'] + }, { + ALPNProtocols: ['first-priority-unsupported', 'x', 'y'], + NPNProtocols: ['first-priority-unsupported', 'x', 'y'] + }]; + + runTest(clientsOptions, serverOptions, function(results) { + // 'a' is selected by ALPN + checkResults(results[0], + {server: {ALPN: 'a', NPN: false}, + client: {ALPN: 'a', NPN: undefined}}); + // 'b' is selected by ALPN + checkResults(results[1], + {server: {ALPN: 'b', NPN: false}, + client: {ALPN: 'b', NPN: undefined}}); + // nothing is selected by ALPN + checkResults(results[2], + {server: {ALPN: false, NPN: false}, + client: {ALPN: false, NPN: undefined}}); + // execute next test + Test2(); + }); +} + +// Server: ALPN/NPN, Client: ALPN +function Test2() { + var serverOptions = { + ALPNProtocols: ['a', 'b', 'c'], + NPNProtocols: ['a', 'b', 'c'] + }; + + var clientsOptions = [{ + ALPNProtocols: ['a', 'b', 'c'] + }, { + ALPNProtocols: ['c', 'b', 'e'] + }, { + ALPNProtocols: ['first-priority-unsupported', 'x', 'y'] + }]; + + runTest(clientsOptions, serverOptions, function(results) { + // 'a' is selected by ALPN + checkResults(results[0], + {server: {ALPN: 'a', NPN: false}, + client: {ALPN: 'a', NPN: undefined}}); + // 'b' is selected by ALPN + checkResults(results[1], + {server: {ALPN: 'b', NPN: false}, + client: {ALPN: 'b', NPN: undefined}}); + // nothing is selected by ALPN + checkResults(results[2], + {server: {ALPN: false, NPN: false}, + client: {ALPN: false, NPN: undefined}}); + // execute next test + Test3(); + }); +} + +// Server: ALPN/NPN, Client: NPN +function Test3() { + var serverOptions = { + ALPNProtocols: ['a', 'b', 'c'], + NPNProtocols: ['a', 'b', 'c'] + }; + + var clientsOptions = [{ + NPNProtocols: ['a', 'b', 'c'] + }, { + NPPNProtocols: ['c', 'b', 'e'] + }, { + NPPNProtocols: ['first-priority-unsupported', 'x', 'y'] + }]; + + runTest(clientsOptions, serverOptions, function(results) { + // 'a' is selected by NPN + checkResults(results[0], + {server: {ALPN: false, NPN: 'a'}, + client: {ALPN: false, NPN: 'a'}}); + // nothing is selected by ALPN + checkResults(results[1], + {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected by ALPN + checkResults(results[2], + {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // execute next test + Test4(); + }); +} + +// Server: ALPN/NPN, Client: Nothing +function Test4() { + var serverOptions = { + ALPNProtocols: ['a', 'b', 'c'], + NPNProtocols: ['a', 'b', 'c'] + }; + + var clientsOptions = [{}, {}, {}]; + + runTest(clientsOptions, serverOptions, function(results) { + // nothing is selected by ALPN + checkResults(results[0], + {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected by ALPN + checkResults(results[1], + {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected by ALPN + checkResults(results[2], + {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // execute next test + Test5(); + }); +} + +// Server: ALPN, Client: ALPN/NPN +function Test5() { + var serverOptions = { + ALPNProtocols: ['a', 'b', 'c'] + }; + + var clientsOptions = [{ + ALPNProtocols: ['a', 'b', 'c'], + NPNProtocols: ['a', 'b', 'c'] + }, { + ALPNProtocols: ['c', 'b', 'e'], + NPNProtocols: ['c', 'b', 'e'] + }, { + ALPNProtocols: ['first-priority-unsupported', 'x', 'y'], + NPNProtocols: ['first-priority-unsupported', 'x', 'y'] + }]; + + runTest(clientsOptions, serverOptions, function(results) { + // 'a' is selected by ALPN + checkResults(results[0], {server: {ALPN: 'a', NPN: false}, + client: {ALPN: 'a', NPN: undefined}}); + // 'b' is selected by ALPN + checkResults(results[1], {server: {ALPN: 'b', NPN: false}, + client: {ALPN: 'b', NPN: undefined}}); + // nothing is selected by ALPN + checkResults(results[2], {server: {ALPN: false, NPN: false}, + client: {ALPN: false, NPN: undefined}}); + // execute next test + Test6(); + }); +} + +// Server: ALPN, Client: ALPN +function Test6() { + var serverOptions = { + ALPNProtocols: ['a', 'b', 'c'] + }; + + var clientsOptions = [{ + ALPNProtocols: ['a', 'b', 'c'] + }, { + ALPNProtocols: ['c', 'b', 'e'] + }, { + ALPNProtocols: ['first-priority-unsupported', 'x', 'y'] + }]; + + runTest(clientsOptions, serverOptions, function(results) { + // 'a' is selected by ALPN + checkResults(results[0], {server: {ALPN: 'a', NPN: false}, + client: {ALPN: 'a', NPN: undefined}}); + // 'b' is selected by ALPN + checkResults(results[1], {server: {ALPN: 'b', NPN: false}, + client: {ALPN: 'b', NPN: undefined}}); + // nothing is selected by ALPN + checkResults(results[2], {server: {ALPN: false, NPN: false}, + client: {ALPN: false, NPN: undefined}}); + // execute next test + Test7(); + }); +} + +// Server: ALPN, Client: NPN +function Test7() { + var serverOptions = { + ALPNProtocols: ['a', 'b', 'c'] + }; + + var clientsOptions = [{ + NPNProtocols: ['a', 'b', 'c'] + }, { + NPNProtocols: ['c', 'b', 'e'] + }, { + NPNProtocols: ['first-priority-unsupported', 'x', 'y'] + }]; + + runTest(clientsOptions, serverOptions, function(results) { + // nothing is selected by ALPN + checkResults(results[0], {server: {ALPN: false, NPN: 'a'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected by ALPN + checkResults(results[1], {server: {ALPN: false, NPN: 'c'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected by ALPN + checkResults(results[2], + {server: {ALPN: false, NPN: 'first-priority-unsupported'}, + client: {ALPN: false, NPN: false}}); + // execute next test + Test8(); + }); +} + +// Server: ALPN, Client: Nothing +function Test8() { + var serverOptions = { + ALPNProtocols: ['a', 'b', 'c'] + }; + + var clientsOptions = [{}, {}, {}]; + + runTest(clientsOptions, serverOptions, function(results) { + // nothing is selected by ALPN + checkResults(results[0], {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected by ALPN + checkResults(results[1], {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected by ALPN + checkResults(results[2], + {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // execute next test + Test9(); + }); +} + +// Server: NPN, Client: ALPN/NPN +function Test9() { + var serverOptions = { + NPNProtocols: ['a', 'b', 'c'] + }; + + var clientsOptions = [{ + ALPNrotocols: ['a', 'b', 'c'], + NPNProtocols: ['a', 'b', 'c'] + }, { + ALPNProtocols: ['c', 'b', 'e'], + NPNProtocols: ['c', 'b', 'e'] + }, { + ALPNProtocols: ['first-priority-unsupported', 'x', 'y'], + NPNProtocols: ['first-priority-unsupported', 'x', 'y'] + }]; + + runTest(clientsOptions, serverOptions, function(results) { + // 'a' is selected by NPN + checkResults(results[0], {server: {ALPN: false, NPN: 'a'}, + client: {ALPN: false, NPN: 'a'}}); + // 'b' is selected by NPN + checkResults(results[1], {server: {ALPN: false, NPN: 'b'}, + client: {ALPN: false, NPN: 'b'}}); + // nothing is selected + checkResults(results[2], + {server: {ALPN: false, NPN: 'first-priority-unsupported'}, + client: {ALPN: false, NPN: false}}); + // execute next test + Test10(); + }); +} + +// Server: NPN, Client: ALPN +function Test10() { + var serverOptions = { + NPNProtocols: ['a', 'b', 'c'] + }; + + var clientsOptions = [{ + ALPNProtocols: ['a', 'b', 'c'] + }, { + ALPNProtocols: ['c', 'b', 'e'] + }, { + ALPNProtocols: ['first-priority-unsupported', 'x', 'y'] + }]; + + runTest(clientsOptions, serverOptions, function(results) { + // nothing is selected + checkResults(results[0], {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected + checkResults(results[1], {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected + checkResults(results[2], {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // execute next test + Test11(); + }); +} + +// Server: NPN, Client: NPN +function Test11() { + var serverOptions = { + NPNProtocols: ['a', 'b', 'c'] + }; + + var clientsOptions = [{ + NPNProtocols: ['a', 'b', 'c'] + }, { + NPNProtocols: ['c', 'b', 'e'] + }, { + NPNProtocols: ['first-priority-unsupported', 'x', 'y'] + }]; + + runTest(clientsOptions, serverOptions, function(results) { + // 'a' is selected by NPN + checkResults(results[0], {server: {ALPN: false, NPN: 'a'}, + client: {ALPN: false, NPN: 'a'}}); + // 'b' is selected by NPN + checkResults(results[1], {server: {ALPN: false, NPN: 'b'}, + client: {ALPN: false, NPN: 'b'}}); + // nothing is selected + checkResults(results[2], + {server: {ALPN: false, NPN: 'first-priority-unsupported'}, + client: {ALPN: false, NPN: false}}); + // execute next test + Test12(); + }); +} + +// Server: NPN, Client: Nothing +function Test12() { + var serverOptions = { + NPNProtocols: ['a', 'b', 'c'] + }; + + var clientsOptions = [{}, {}, {}]; + + runTest(clientsOptions, serverOptions, function(results) { + // nothing is selected + checkResults(results[0], {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected + checkResults(results[1], {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected + checkResults(results[2], + {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // execute next test + Test13(); + }); +} + +// Server: Nothing, Client: ALPN/NPN +function Test13() { + var serverOptions = {}; + + var clientsOptions = [{ + ALPNrotocols: ['a', 'b', 'c'], + NPNProtocols: ['a', 'b', 'c'] + }, { + ALPNProtocols: ['c', 'b', 'e'], + NPNProtocols: ['c', 'b', 'e'] + }, { + ALPNProtocols: ['first-priority-unsupported', 'x', 'y'], + NPNProtocols: ['first-priority-unsupported', 'x', 'y'] + }]; + + runTest(clientsOptions, serverOptions, function(results) { + // nothing is selected + checkResults(results[0], {server: {ALPN: false, NPN: 'a'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected + checkResults(results[1], {server: {ALPN: false, NPN: 'c'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected + checkResults(results[2], + {server: {ALPN: false, NPN: 'first-priority-unsupported'}, + client: {ALPN: false, NPN: false}}); + // execute next test + Test14(); + }); +} + +// Server: Nothing, Client: ALPN +function Test14() { + var serverOptions = {}; + + var clientsOptions = [{ + ALPNrotocols: ['a', 'b', 'c'] + }, { + ALPNProtocols: ['c', 'b', 'e'] + }, { + ALPNProtocols: ['first-priority-unsupported', 'x', 'y'] + }]; + + runTest(clientsOptions, serverOptions, function(results) { + // nothing is selected + checkResults(results[0], {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected + checkResults(results[1], {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected + checkResults(results[2], + {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // execute next test + Test15(); + }); +} + +// Server: Nothing, Client: NPN +function Test15() { + var serverOptions = {}; + + var clientsOptions = [{ + NPNProtocols: ['a', 'b', 'c'] + }, { + NPNProtocols: ['c', 'b', 'e'] + }, { + NPNProtocols: ['first-priority-unsupported', 'x', 'y'] + }]; + + runTest(clientsOptions, serverOptions, function(results) { + // nothing is selected + checkResults(results[0], {server: {ALPN: false, NPN: 'a'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected + checkResults(results[1], {server: {ALPN: false, NPN: 'c'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected + checkResults(results[2], + {server: {ALPN: false, NPN: 'first-priority-unsupported'}, + client: {ALPN: false, NPN: false}}); + // execute next test + Test16(); + }); +} + +// Server: Nothing, Client: Nothing +function Test16() { + var serverOptions = {}; + + var clientsOptions = [{}, {}, {}]; + + runTest(clientsOptions, serverOptions, function(results) { + // nothing is selected + checkResults(results[0], {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected + checkResults(results[1], {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + // nothing is selected + checkResults(results[2], + {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}); + }); +} + +Test1();