blob: 3ad9112b1e2eadd9442eaf67167d4b2dff10a2d1 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
import 'dart:convert';
import 'package:expect/expect.dart';
import 'package:async_helper/async_helper.dart';
const String NAME_LENGTH_ERROR =
'Length of protocol must be between 1 and 255';
const String MESSAGE_LENGTH_ERROR =
'The maximum message length supported is 2^13-1';
String localFile(path) => Platform.script.resolve(path).toFilePath();
SecurityContext clientContext() => new SecurityContext()
..setTrustedCertificates(file: localFile('certificates/trusted_certs.pem'));
SecurityContext serverContext() => new SecurityContext()
..useCertificateChain(localFile('certificates/server_chain.pem'))
..usePrivateKey(localFile('certificates/server_key.pem'),
password: 'dartdart');
// Tests that client/server with same protocol can securely establish a
// connection, negotiate the protocol and can send data to each other.
void testSuccessfulAlpnNegotiationConnection(List<String> clientProtocols,
List<String> serverProtocols,
String selectedProtocol) {
asyncStart();
var sContext = serverContext()..setAlpnProtocols(serverProtocols, true);
SecureServerSocket.bind('localhost', 0, sContext)
.then((SecureServerSocket server) {
asyncStart();
server.first.then((SecureSocket socket) {
Expect.equals(selectedProtocol, socket.selectedProtocol);
socket..write('server message')..close();
socket.transform(ASCII.decoder).join('').then((String s) {
Expect.equals('client message', s);
asyncEnd();
});
});
asyncStart();
SecureSocket.connect('localhost', server.port, context: clientContext(),
supportedProtocols: clientProtocols).then((socket) {
Expect.equals(selectedProtocol, socket.selectedProtocol);
socket..write('client message')..close();
socket.transform(ASCII.decoder).join('').then((String s) {
Expect.equals('server message', s);
server.close();
asyncEnd();
});
});
asyncEnd();
});
}
void testInvalidArgument(List<String> protocols, String errorIncludes) {
testInvalidArgumentServerContext(protocols, errorIncludes);
testInvalidArgumentClientContext(protocols, errorIncludes);
testInvalidArgumentClientConnect(protocols, errorIncludes);
}
void testInvalidArgumentServerContext(List<String> protocols,
String errorIncludes) {
Expect.throws(() => serverContext().setAlpnProtocols(protocols, true), (e) {
Expect.isTrue(e is ArgumentError);
Expect.isTrue(e.toString().contains(errorIncludes));
return true;
});
}
void testInvalidArgumentClientContext(List<String> protocols,
String errorIncludes) {
Expect.throws(() => clientContext().setAlpnProtocols(protocols, false), (e) {
Expect.isTrue(e is ArgumentError);
Expect.isTrue(e.toString().contains(errorIncludes));
return true;
});
}
void testInvalidArgumentClientConnect(List<String> protocols,
String errorIncludes) {
asyncStart();
var sContext = serverContext()..setAlpnProtocols(['abc'], true);
SecureServerSocket.bind('localhost', 0, sContext).then((server) async {
asyncStart();
server.listen((SecureSocket socket) {
Expect.fail(
"Unexpected connection made to server, with bad client argument");
}, onError: (e) {
Expect.fail("Unexpected error on server stream: $e");
}, onDone: () { asyncEnd();});
asyncStart();
SecureSocket.connect('localhost', server.port, context: clientContext(),
supportedProtocols: protocols).then((socket) {
Expect.fail(
"Unexpected connection made from client, with bad client argument");
}, onError: (e) {
Expect.isTrue(e is ArgumentError);
Expect.isTrue(e.toString().contains(errorIncludes));
server.close();
asyncEnd();
});
asyncEnd();
});
}
main() {
final longname256 = 'p' * 256;
final String longname255 = 'p' * 255;
final String strangelongname255 = 'ø' + 'p' * 253;
final String strangelongname256 = 'ø' + 'p' * 254;
// This produces a message of (1 << 13) - 2 bytes. 2^12 -1 strings are each
// encoded by 1 length byte and 1 ascii byte.
final List<String> manyProtocols = new Iterable.generate(
(1 << 12) - 1, (i) => '0').toList();
// This produces a message of (1 << 13) bytes. 2^12 strings are each
// encoded by 1 length byte and 1 ascii byte.
final List<String> tooManyProtocols = new Iterable.generate(
(1 << 12), (i) => '0').toList();
// Protocols are in order of decreasing priority. The server will select
// the first protocol from its list that has a match in the client list.
// Test successful negotiation, including priority.
testSuccessfulAlpnNegotiationConnection(['a'],
['a'],
'a');
testSuccessfulAlpnNegotiationConnection([longname255],
[longname255],
longname255);
testSuccessfulAlpnNegotiationConnection([strangelongname255],
[strangelongname255],
strangelongname255);
testSuccessfulAlpnNegotiationConnection(manyProtocols,
manyProtocols,
'0');
testSuccessfulAlpnNegotiationConnection(['a', 'b', 'c'],
['a', 'b', 'c'],
'a');
testSuccessfulAlpnNegotiationConnection(['a', 'b', 'c'],
['c'],
'c');
// Server precedence.
testSuccessfulAlpnNegotiationConnection(['a', 'b', 'c'],
['c', 'b', 'a'],
'c');
testSuccessfulAlpnNegotiationConnection(['c'],
['a', 'b', 'c'],
'c');
testSuccessfulAlpnNegotiationConnection(['s1', 'b', 'e1'],
['s2', 'b', 'e2'],
'b');
// Test no protocol negotiation support
testSuccessfulAlpnNegotiationConnection(null,
null,
null);
testSuccessfulAlpnNegotiationConnection(['a', 'b', 'c'],
null,
null);
testSuccessfulAlpnNegotiationConnection(null,
['a', 'b', 'c'],
null);
testSuccessfulAlpnNegotiationConnection([],
[],
null);
testSuccessfulAlpnNegotiationConnection(['a', 'b', 'c'],
[],
null);
testSuccessfulAlpnNegotiationConnection([],
['a', 'b', 'c'],
null);
// Test non-overlapping protocols. The ALPN RFC says the connection
// should be terminated, but OpenSSL continues as if no ALPN is present.
// Issue https://github.com/dart-lang/sdk/issues/23580
// Chromium issue https://code.google.com/p/chromium/issues/detail?id=497770
testSuccessfulAlpnNegotiationConnection(['a'], ['b'], null);
// Test too short / too long protocol names.
testInvalidArgument([longname256], NAME_LENGTH_ERROR);
testInvalidArgument([strangelongname256], NAME_LENGTH_ERROR);
testInvalidArgument([''], NAME_LENGTH_ERROR);
testInvalidArgument(tooManyProtocols, MESSAGE_LENGTH_ERROR);
}