| // 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 |
| |
| // @dart = 2.9 |
| |
| 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(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(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); |
| } |