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: https://github.com/nodejs/node/pull/2564
Reviewed-By: Fedor Indutny <fedor@indutny.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This commit is contained in:
Shigeki Ohtsu 2015-04-23 15:25:15 +09:00
parent df738ac56c
commit 802a2e79e1
11 changed files with 769 additions and 29 deletions

View File

@ -66,14 +66,15 @@ and tap `R<CR>` (that's the letter `R` followed by a carriage return) a few
times.
## NPN and SNI
## ALPN, NPN and SNI
<!-- type=misc -->
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'

View File

@ -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();
}

View File

@ -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
});

View File

@ -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;

View File

@ -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));
function convertProtocols(protocols) {
var buff = new Buffer(protocols.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);
protocols.reduce(function(offset, c) {
var clen = Buffer.byteLength(c);
buff[offset] = clen;
buff.write(c, offset + 1);
return offset + 1 + clen;
}, 0);
return offset + 1 + clen;
}, 0);
NPNProtocols = buff;
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 (NPNProtocols instanceof Buffer) {
out.NPNProtocols = NPNProtocols;
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 (protocols instanceof Buffer) {
// copy new buffer not to be modified by user
out.ALPNProtocols = new Buffer(protocols);
}
};

View File

@ -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") \

View File

@ -2582,6 +2582,13 @@ static Local<Object> GetFeatures(Environment* env) {
#endif
obj->Set(env->tls_npn_string(), tls_npn);
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
Local<Boolean> tls_alpn = True(env->isolate());
#else
Local<Boolean> tls_alpn = False(env->isolate());
#endif
obj->Set(env->tls_alpn_string(), tls_alpn);
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
Local<Boolean> tls_sni = True(env->isolate());
#else

View File

@ -935,6 +935,11 @@ void DefineOpenSSLConstants(Local<Object> 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

View File

@ -167,6 +167,15 @@ template void SSLWrap<TLSWrap>::DestroySSL();
template int SSLWrap<TLSWrap>::SSLCertCallback(SSL* s, void* arg);
template void SSLWrap<TLSWrap>::WaitForCertCb(CertCb cb, void* arg);
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
template int SSLWrap<TLSWrap>::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<Base>::AddMethods(Environment* env, Local<FunctionTemplate> 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<Base>::SetNPNProtocols(const FunctionCallbackInfo<Value>& args) {
}
#endif // OPENSSL_NPN_NEGOTIATED
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
typedef struct tlsextalpnctx_st {
unsigned char* data;
unsigned short len;
} tlsextalpnctx;
template <class Base>
int SSLWrap<Base>::SelectALPNCallback(SSL* s,
const unsigned char** out,
unsigned char* outlen,
const unsigned char* in,
unsigned int inlen,
void* arg) {
Base* w = static_cast<Base*>(SSL_get_app_data(s));
Environment* env = w->env();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
Local<Value> alpn_buffer =
w->object()->GetHiddenValue(env->alpn_buffer_string());
CHECK(Buffer::HasInstance(alpn_buffer));
const unsigned char* alpn_protos =
reinterpret_cast<const unsigned char*>(Buffer::Data(alpn_buffer));
unsigned alpn_protos_len = Buffer::Length(alpn_buffer);
int status = SSL_select_next_proto(const_cast<unsigned char**>(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 <class Base>
void SSLWrap<Base>::GetALPNNegotiatedProto(
const FunctionCallbackInfo<v8::Value>& args) {
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
HandleScope scope(args.GetIsolate());
Base* w = Unwrap<Base>(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 <class Base>
void SSLWrap<Base>::SetALPNProtocols(
const FunctionCallbackInfo<v8::Value>& args) {
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
HandleScope scope(args.GetIsolate());
Base* w = Unwrap<Base>(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<const unsigned char*>(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<Value> alpn_buffer = Local<Value>::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 <class Base>

View File

@ -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<v8::Value>& args);
static void SetALPNProtocols(const v8::FunctionCallbackInfo<v8::Value>& 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<v8::String> property,

View File

@ -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();