// 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/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 clientProtocols, List 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 protocols, String errorIncludes) { testInvalidArgumentServerContext(protocols, errorIncludes); testInvalidArgumentClientContext(protocols, errorIncludes); testInvalidArgumentClientConnect(protocols, errorIncludes); } void testInvalidArgumentServerContext( List 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 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 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 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 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); }