a323c55162
Change-Id: I4d492a63a41880ef8cd69321372a4853825b9efd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/426321 Reviewed-by: Liam Appelbe <liama@google.com> Auto-Submit: Bob Nystrom <rnystrom@google.com> Commit-Queue: Liam Appelbe <liama@google.com>
232 lines
6.8 KiB
Dart
232 lines
6.8 KiB
Dart
// 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.
|
|
//
|
|
// OtherResources=certificates/server_chain.pem
|
|
// OtherResources=certificates/server_key.pem
|
|
// OtherResources=certificates/trusted_certs.pem
|
|
|
|
import 'dart:io';
|
|
import 'dart:convert';
|
|
|
|
import 'package:expect/async_helper.dart';
|
|
import 'package:expect/expect.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(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();
|
|
ascii.decoder.bind(socket).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();
|
|
ascii.decoder.bind(socket).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([], ['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);
|
|
}
|