| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 // | 4 // |
| 5 // OtherResources=certificates/server_chain.pem | 5 // OtherResources=certificates/server_chain.pem |
| 6 // OtherResources=certificates/server_key.pem | 6 // OtherResources=certificates/server_key.pem |
| 7 // OtherResources=certificates/trusted_certs.pem | 7 // OtherResources=certificates/trusted_certs.pem |
| 8 | 8 |
| 9 import 'dart:io'; | 9 import 'dart:io'; |
| 10 import 'dart:convert'; | 10 import 'dart:convert'; |
| 11 | 11 |
| 12 import 'package:expect/expect.dart'; | 12 import 'package:expect/expect.dart'; |
| 13 import 'package:async_helper/async_helper.dart'; | 13 import 'package:async_helper/async_helper.dart'; |
| 14 | 14 |
| 15 const String NAME_LENGTH_ERROR = | 15 const String NAME_LENGTH_ERROR = 'Length of protocol must be between 1 and 255'; |
| 16 'Length of protocol must be between 1 and 255'; | |
| 17 | 16 |
| 18 const String MESSAGE_LENGTH_ERROR = | 17 const String MESSAGE_LENGTH_ERROR = |
| 19 'The maximum message length supported is 2^13-1'; | 18 'The maximum message length supported is 2^13-1'; |
| 20 | 19 |
| 21 String localFile(path) => Platform.script.resolve(path).toFilePath(); | 20 String localFile(path) => Platform.script.resolve(path).toFilePath(); |
| 22 | 21 |
| 23 SecurityContext clientContext() => new SecurityContext() | 22 SecurityContext clientContext() => new SecurityContext() |
| 24 ..setTrustedCertificates(localFile('certificates/trusted_certs.pem')); | 23 ..setTrustedCertificates(localFile('certificates/trusted_certs.pem')); |
| 25 | 24 |
| 26 SecurityContext serverContext() => new SecurityContext() | 25 SecurityContext serverContext() => new SecurityContext() |
| 27 ..useCertificateChain(localFile('certificates/server_chain.pem')) | 26 ..useCertificateChain(localFile('certificates/server_chain.pem')) |
| 28 ..usePrivateKey(localFile('certificates/server_key.pem'), | 27 ..usePrivateKey(localFile('certificates/server_key.pem'), |
| 29 password: 'dartdart'); | 28 password: 'dartdart'); |
| 30 | 29 |
| 31 // Tests that client/server with same protocol can securely establish a | 30 // Tests that client/server with same protocol can securely establish a |
| 32 // connection, negotiate the protocol and can send data to each other. | 31 // connection, negotiate the protocol and can send data to each other. |
| 33 void testSuccessfulAlpnNegotiationConnection(List<String> clientProtocols, | 32 void testSuccessfulAlpnNegotiationConnection(List<String> clientProtocols, |
| 34 List<String> serverProtocols, | 33 List<String> serverProtocols, String selectedProtocol) { |
| 35 String selectedProtocol) { | |
| 36 asyncStart(); | 34 asyncStart(); |
| 37 var sContext = serverContext()..setAlpnProtocols(serverProtocols, true); | 35 var sContext = serverContext()..setAlpnProtocols(serverProtocols, true); |
| 38 SecureServerSocket.bind('localhost', 0, sContext) | 36 SecureServerSocket |
| 39 .then((SecureServerSocket server) { | 37 .bind('localhost', 0, sContext) |
| 40 | 38 .then((SecureServerSocket server) { |
| 41 asyncStart(); | 39 asyncStart(); |
| 42 server.first.then((SecureSocket socket) { | 40 server.first.then((SecureSocket socket) { |
| 43 Expect.equals(selectedProtocol, socket.selectedProtocol); | 41 Expect.equals(selectedProtocol, socket.selectedProtocol); |
| 44 socket..write('server message')..close(); | 42 socket |
| 43 ..write('server message') |
| 44 ..close(); |
| 45 socket.transform(ASCII.decoder).join('').then((String s) { | 45 socket.transform(ASCII.decoder).join('').then((String s) { |
| 46 Expect.equals('client message', s); | 46 Expect.equals('client message', s); |
| 47 asyncEnd(); | 47 asyncEnd(); |
| 48 }); | 48 }); |
| 49 }); | 49 }); |
| 50 | 50 |
| 51 asyncStart(); | 51 asyncStart(); |
| 52 SecureSocket.connect('localhost', server.port, context: clientContext(), | 52 SecureSocket |
| 53 supportedProtocols: clientProtocols).then((socket) { | 53 .connect('localhost', server.port, |
| 54 context: clientContext(), supportedProtocols: clientProtocols) |
| 55 .then((socket) { |
| 54 Expect.equals(selectedProtocol, socket.selectedProtocol); | 56 Expect.equals(selectedProtocol, socket.selectedProtocol); |
| 55 socket..write('client message')..close(); | 57 socket |
| 58 ..write('client message') |
| 59 ..close(); |
| 56 socket.transform(ASCII.decoder).join('').then((String s) { | 60 socket.transform(ASCII.decoder).join('').then((String s) { |
| 57 Expect.equals('server message', s); | 61 Expect.equals('server message', s); |
| 58 server.close(); | 62 server.close(); |
| 59 asyncEnd(); | 63 asyncEnd(); |
| 60 }); | 64 }); |
| 61 }); | 65 }); |
| 62 | 66 |
| 63 asyncEnd(); | 67 asyncEnd(); |
| 64 }); | 68 }); |
| 65 } | 69 } |
| 66 | 70 |
| 67 void testInvalidArgument(List<String> protocols, String errorIncludes) { | 71 void testInvalidArgument(List<String> protocols, String errorIncludes) { |
| 68 testInvalidArgumentServerContext(protocols, errorIncludes); | 72 testInvalidArgumentServerContext(protocols, errorIncludes); |
| 69 testInvalidArgumentClientContext(protocols, errorIncludes); | 73 testInvalidArgumentClientContext(protocols, errorIncludes); |
| 70 testInvalidArgumentClientConnect(protocols, errorIncludes); | 74 testInvalidArgumentClientConnect(protocols, errorIncludes); |
| 71 } | 75 } |
| 72 | 76 |
| 73 void testInvalidArgumentServerContext(List<String> protocols, | 77 void testInvalidArgumentServerContext( |
| 74 String errorIncludes) { | 78 List<String> protocols, String errorIncludes) { |
| 75 Expect.throws(() => serverContext().setAlpnProtocols(protocols, true), (e) { | 79 Expect.throws(() => serverContext().setAlpnProtocols(protocols, true), (e) { |
| 76 Expect.isTrue(e is ArgumentError); | 80 Expect.isTrue(e is ArgumentError); |
| 77 Expect.isTrue(e.toString().contains(errorIncludes)); | 81 Expect.isTrue(e.toString().contains(errorIncludes)); |
| 78 return true; | 82 return true; |
| 79 }); | 83 }); |
| 80 } | 84 } |
| 81 | 85 |
| 82 void testInvalidArgumentClientContext(List<String> protocols, | 86 void testInvalidArgumentClientContext( |
| 83 String errorIncludes) { | 87 List<String> protocols, String errorIncludes) { |
| 84 Expect.throws(() => clientContext().setAlpnProtocols(protocols, false), (e) { | 88 Expect.throws(() => clientContext().setAlpnProtocols(protocols, false), (e) { |
| 85 Expect.isTrue(e is ArgumentError); | 89 Expect.isTrue(e is ArgumentError); |
| 86 Expect.isTrue(e.toString().contains(errorIncludes)); | 90 Expect.isTrue(e.toString().contains(errorIncludes)); |
| 87 return true; | 91 return true; |
| 88 }); | 92 }); |
| 89 } | 93 } |
| 90 | 94 |
| 91 void testInvalidArgumentClientConnect(List<String> protocols, | 95 void testInvalidArgumentClientConnect( |
| 92 String errorIncludes) { | 96 List<String> protocols, String errorIncludes) { |
| 93 asyncStart(); | 97 asyncStart(); |
| 94 var sContext = serverContext()..setAlpnProtocols(['abc'], true); | 98 var sContext = serverContext()..setAlpnProtocols(['abc'], true); |
| 95 SecureServerSocket.bind('localhost', 0, sContext).then((server) async { | 99 SecureServerSocket.bind('localhost', 0, sContext).then((server) async { |
| 96 asyncStart(); | 100 asyncStart(); |
| 97 server.listen((SecureSocket socket) { | 101 server.listen((SecureSocket socket) { |
| 98 Expect.fail( | 102 Expect.fail( |
| 99 "Unexpected connection made to server, with bad client argument"); | 103 "Unexpected connection made to server, with bad client argument"); |
| 100 }, onError: (e) { | 104 }, onError: (e) { |
| 101 Expect.fail("Unexpected error on server stream: $e"); | 105 Expect.fail("Unexpected error on server stream: $e"); |
| 102 }, onDone: () { asyncEnd();}); | 106 }, onDone: () { |
| 107 asyncEnd(); |
| 108 }); |
| 103 | 109 |
| 104 asyncStart(); | 110 asyncStart(); |
| 105 SecureSocket.connect('localhost', server.port, context: clientContext(), | 111 SecureSocket |
| 106 supportedProtocols: protocols).then((socket) { | 112 .connect('localhost', server.port, |
| 113 context: clientContext(), supportedProtocols: protocols) |
| 114 .then((socket) { |
| 107 Expect.fail( | 115 Expect.fail( |
| 108 "Unexpected connection made from client, with bad client argument"); | 116 "Unexpected connection made from client, with bad client argument"); |
| 109 }, onError: (e) { | 117 }, onError: (e) { |
| 110 Expect.isTrue(e is ArgumentError); | 118 Expect.isTrue(e is ArgumentError); |
| 111 Expect.isTrue(e.toString().contains(errorIncludes)); | 119 Expect.isTrue(e.toString().contains(errorIncludes)); |
| 112 server.close(); | 120 server.close(); |
| 113 asyncEnd(); | 121 asyncEnd(); |
| 114 }); | 122 }); |
| 115 asyncEnd(); | 123 asyncEnd(); |
| 116 }); | 124 }); |
| 117 } | 125 } |
| 118 | 126 |
| 119 main() { | 127 main() { |
| 120 if (!SecurityContext.alpnSupported) { | 128 if (!SecurityContext.alpnSupported) { |
| 121 return 0; | 129 return 0; |
| 122 } | 130 } |
| 123 final longname256 = 'p' * 256; | 131 final longname256 = 'p' * 256; |
| 124 final String longname255 = 'p' * 255; | 132 final String longname255 = 'p' * 255; |
| 125 final String strangelongname255 = 'ø' + 'p' * 253; | 133 final String strangelongname255 = 'ø' + 'p' * 253; |
| 126 final String strangelongname256 = 'ø' + 'p' * 254; | 134 final String strangelongname256 = 'ø' + 'p' * 254; |
| 127 | 135 |
| 128 // This produces a message of (1 << 13) - 2 bytes. 2^12 -1 strings are each | 136 // This produces a message of (1 << 13) - 2 bytes. 2^12 -1 strings are each |
| 129 // encoded by 1 length byte and 1 ascii byte. | 137 // encoded by 1 length byte and 1 ascii byte. |
| 130 final List<String> manyProtocols = new Iterable.generate( | 138 final List<String> manyProtocols = |
| 131 (1 << 12) - 1, (i) => '0').toList(); | 139 new Iterable.generate((1 << 12) - 1, (i) => '0').toList(); |
| 132 | 140 |
| 133 // This produces a message of (1 << 13) bytes. 2^12 strings are each | 141 // This produces a message of (1 << 13) bytes. 2^12 strings are each |
| 134 // encoded by 1 length byte and 1 ascii byte. | 142 // encoded by 1 length byte and 1 ascii byte. |
| 135 final List<String> tooManyProtocols = new Iterable.generate( | 143 final List<String> tooManyProtocols = |
| 136 (1 << 12), (i) => '0').toList(); | 144 new Iterable.generate((1 << 12), (i) => '0').toList(); |
| 137 | 145 |
| 138 // Protocols are in order of decreasing priority. The server will select | 146 // Protocols are in order of decreasing priority. The server will select |
| 139 // the first protocol from its list that has a match in the client list. | 147 // the first protocol from its list that has a match in the client list. |
| 140 // Test successful negotiation, including priority. | 148 // Test successful negotiation, including priority. |
| 141 testSuccessfulAlpnNegotiationConnection(['a'], | 149 testSuccessfulAlpnNegotiationConnection(['a'], ['a'], 'a'); |
| 142 ['a'], | |
| 143 'a'); | |
| 144 | 150 |
| 145 testSuccessfulAlpnNegotiationConnection([longname255], | 151 testSuccessfulAlpnNegotiationConnection( |
| 146 [longname255], | 152 [longname255], [longname255], longname255); |
| 147 longname255); | |
| 148 | 153 |
| 149 testSuccessfulAlpnNegotiationConnection([strangelongname255], | 154 testSuccessfulAlpnNegotiationConnection( |
| 150 [strangelongname255], | 155 [strangelongname255], [strangelongname255], strangelongname255); |
| 151 strangelongname255); | 156 testSuccessfulAlpnNegotiationConnection(manyProtocols, manyProtocols, '0'); |
| 152 testSuccessfulAlpnNegotiationConnection(manyProtocols, | 157 testSuccessfulAlpnNegotiationConnection( |
| 153 manyProtocols, | 158 ['a', 'b', 'c'], ['a', 'b', 'c'], 'a'); |
| 154 '0'); | |
| 155 testSuccessfulAlpnNegotiationConnection(['a', 'b', 'c'], | |
| 156 ['a', 'b', 'c'], | |
| 157 'a'); | |
| 158 | 159 |
| 159 testSuccessfulAlpnNegotiationConnection(['a', 'b', 'c'], | 160 testSuccessfulAlpnNegotiationConnection(['a', 'b', 'c'], ['c'], 'c'); |
| 160 ['c'], | |
| 161 'c'); | |
| 162 | 161 |
| 163 // Server precedence. | 162 // Server precedence. |
| 164 testSuccessfulAlpnNegotiationConnection(['a', 'b', 'c'], | 163 testSuccessfulAlpnNegotiationConnection( |
| 165 ['c', 'b', 'a'], | 164 ['a', 'b', 'c'], ['c', 'b', 'a'], 'c'); |
| 166 'c'); | |
| 167 | 165 |
| 168 testSuccessfulAlpnNegotiationConnection(['c'], | 166 testSuccessfulAlpnNegotiationConnection(['c'], ['a', 'b', 'c'], 'c'); |
| 169 ['a', 'b', 'c'], | |
| 170 'c'); | |
| 171 | 167 |
| 172 testSuccessfulAlpnNegotiationConnection(['s1', 'b', 'e1'], | 168 testSuccessfulAlpnNegotiationConnection( |
| 173 ['s2', 'b', 'e2'], | 169 ['s1', 'b', 'e1'], ['s2', 'b', 'e2'], 'b'); |
| 174 'b'); | |
| 175 // Test no protocol negotiation support | 170 // Test no protocol negotiation support |
| 176 testSuccessfulAlpnNegotiationConnection(null, | 171 testSuccessfulAlpnNegotiationConnection(null, null, null); |
| 177 null, | |
| 178 null); | |
| 179 | 172 |
| 180 testSuccessfulAlpnNegotiationConnection(['a', 'b', 'c'], | 173 testSuccessfulAlpnNegotiationConnection(['a', 'b', 'c'], null, null); |
| 181 null, | |
| 182 null); | |
| 183 | 174 |
| 184 testSuccessfulAlpnNegotiationConnection(null, | 175 testSuccessfulAlpnNegotiationConnection(null, ['a', 'b', 'c'], null); |
| 185 ['a', 'b', 'c'], | |
| 186 null); | |
| 187 | 176 |
| 188 testSuccessfulAlpnNegotiationConnection([], | 177 testSuccessfulAlpnNegotiationConnection([], [], null); |
| 189 [], | |
| 190 null); | |
| 191 | 178 |
| 192 testSuccessfulAlpnNegotiationConnection(['a', 'b', 'c'], | 179 testSuccessfulAlpnNegotiationConnection(['a', 'b', 'c'], [], null); |
| 193 [], | |
| 194 null); | |
| 195 | 180 |
| 196 testSuccessfulAlpnNegotiationConnection([], | 181 testSuccessfulAlpnNegotiationConnection([], ['a', 'b', 'c'], null); |
| 197 ['a', 'b', 'c'], | |
| 198 null); | |
| 199 | 182 |
| 200 // Test non-overlapping protocols. The ALPN RFC says the connection | 183 // Test non-overlapping protocols. The ALPN RFC says the connection |
| 201 // should be terminated, but OpenSSL continues as if no ALPN is present. | 184 // should be terminated, but OpenSSL continues as if no ALPN is present. |
| 202 // Issue https://github.com/dart-lang/sdk/issues/23580 | 185 // Issue https://github.com/dart-lang/sdk/issues/23580 |
| 203 // Chromium issue https://code.google.com/p/chromium/issues/detail?id=497770 | 186 // Chromium issue https://code.google.com/p/chromium/issues/detail?id=497770 |
| 204 testSuccessfulAlpnNegotiationConnection(['a'], ['b'], null); | 187 testSuccessfulAlpnNegotiationConnection(['a'], ['b'], null); |
| 205 | 188 |
| 206 // Test too short / too long protocol names. | 189 // Test too short / too long protocol names. |
| 207 testInvalidArgument([longname256], NAME_LENGTH_ERROR); | 190 testInvalidArgument([longname256], NAME_LENGTH_ERROR); |
| 208 testInvalidArgument([strangelongname256], NAME_LENGTH_ERROR); | 191 testInvalidArgument([strangelongname256], NAME_LENGTH_ERROR); |
| 209 testInvalidArgument([''], NAME_LENGTH_ERROR); | 192 testInvalidArgument([''], NAME_LENGTH_ERROR); |
| 210 testInvalidArgument(tooManyProtocols, MESSAGE_LENGTH_ERROR); | 193 testInvalidArgument(tooManyProtocols, MESSAGE_LENGTH_ERROR); |
| 211 } | 194 } |
| OLD | NEW |