| Index: dart/sdk/lib/io/secure_socket.dart
|
| diff --git a/dart/sdk/lib/io/secure_socket.dart b/dart/sdk/lib/io/secure_socket.dart
|
| index f11355ae5a2dd5fb5c5e3213c207bbcc5eabe29f..0faafd49d13c5e36644972085e7cdb9f9f7a7418 100644
|
| --- a/dart/sdk/lib/io/secure_socket.dart
|
| +++ b/dart/sdk/lib/io/secure_socket.dart
|
| @@ -39,12 +39,14 @@ abstract class SecureSocket implements Socket {
|
| int port,
|
| {bool sendClientCertificate: false,
|
| String certificateName,
|
| - bool onBadCertificate(X509Certificate certificate)}) {
|
| + bool onBadCertificate(X509Certificate certificate),
|
| + List<String> supportedProtocols}) {
|
| return RawSecureSocket.connect(host,
|
| port,
|
| sendClientCertificate: sendClientCertificate,
|
| certificateName: certificateName,
|
| - onBadCertificate: onBadCertificate)
|
| + onBadCertificate: onBadCertificate,
|
| + supportedProtocols: supportedProtocols)
|
| .then((rawSocket) => new SecureSocket._(rawSocket));
|
| }
|
|
|
| @@ -122,7 +124,8 @@ abstract class SecureSocket implements Socket {
|
| String certificateName,
|
| {List<int> bufferedData,
|
| bool requestClientCertificate: false,
|
| - bool requireClientCertificate: false}) {
|
| + bool requireClientCertificate: false,
|
| + List<String> supportedProtocols}) {
|
| var completer = new Completer();
|
| (socket as dynamic)._detachRaw()
|
| .then((detachedRaw) {
|
| @@ -132,7 +135,8 @@ abstract class SecureSocket implements Socket {
|
| subscription: detachedRaw[1],
|
| bufferedData: bufferedData,
|
| requestClientCertificate: requestClientCertificate,
|
| - requireClientCertificate: requireClientCertificate);
|
| + requireClientCertificate: requireClientCertificate,
|
| + supportedProtocols: supportedProtocols);
|
| })
|
| .then((raw) {
|
| completer.complete(new SecureSocket._(raw));
|
| @@ -150,6 +154,11 @@ abstract class SecureSocket implements Socket {
|
| X509Certificate get peerCertificate;
|
|
|
| /**
|
| + * Get the protocol which was selected during protocol negotiation.
|
| + */
|
| + String get selectedProtocol;
|
| +
|
| + /**
|
| * Renegotiate an existing secure connection, renewing the session keys
|
| * and possibly changing the connection properties.
|
| *
|
| @@ -245,7 +254,8 @@ abstract class RawSecureSocket implements RawSocket {
|
| int port,
|
| {bool sendClientCertificate: false,
|
| String certificateName,
|
| - bool onBadCertificate(X509Certificate certificate)}) {
|
| + bool onBadCertificate(X509Certificate certificate),
|
| + List<String> supportedProtocols}) {
|
| _RawSecureSocket._verifyFields(
|
| host,
|
| port,
|
| @@ -260,7 +270,8 @@ abstract class RawSecureSocket implements RawSocket {
|
| return secure(socket,
|
| sendClientCertificate: sendClientCertificate,
|
| certificateName: certificateName,
|
| - onBadCertificate: onBadCertificate);
|
| + onBadCertificate: onBadCertificate,
|
| + supportedProtocols: supportedProtocols);
|
| });
|
| }
|
|
|
| @@ -298,7 +309,8 @@ abstract class RawSecureSocket implements RawSocket {
|
| host,
|
| bool sendClientCertificate: false,
|
| String certificateName,
|
| - bool onBadCertificate(X509Certificate certificate)}) {
|
| + bool onBadCertificate(X509Certificate certificate),
|
| + List<String> supportedProtocols}) {
|
| socket.readEventsEnabled = false;
|
| socket.writeEventsEnabled = false;
|
| return _RawSecureSocket.connect(
|
| @@ -309,7 +321,8 @@ abstract class RawSecureSocket implements RawSocket {
|
| socket: socket,
|
| subscription: subscription,
|
| sendClientCertificate: sendClientCertificate,
|
| - onBadCertificate: onBadCertificate);
|
| + onBadCertificate: onBadCertificate,
|
| + supportedProtocols: supportedProtocols);
|
| }
|
|
|
| /**
|
| @@ -341,7 +354,8 @@ abstract class RawSecureSocket implements RawSocket {
|
| {StreamSubscription subscription,
|
| List<int> bufferedData,
|
| bool requestClientCertificate: false,
|
| - bool requireClientCertificate: false}) {
|
| + bool requireClientCertificate: false,
|
| + List<String> supportedProtocols}) {
|
| socket.readEventsEnabled = false;
|
| socket.writeEventsEnabled = false;
|
| return _RawSecureSocket.connect(
|
| @@ -353,7 +367,8 @@ abstract class RawSecureSocket implements RawSocket {
|
| subscription: subscription,
|
| bufferedData: bufferedData,
|
| requestClientCertificate: requestClientCertificate,
|
| - requireClientCertificate: requireClientCertificate);
|
| + requireClientCertificate: requireClientCertificate,
|
| + supportedProtocols: supportedProtocols);
|
| }
|
|
|
| /**
|
| @@ -375,6 +390,11 @@ abstract class RawSecureSocket implements RawSocket {
|
| * [peerCertificate] will return the server's certificate.
|
| */
|
| X509Certificate get peerCertificate;
|
| +
|
| + /**
|
| + * Get the protocol which was selected during protocol negotiation.
|
| + */
|
| + String get selectedProtocol;
|
| }
|
|
|
|
|
| @@ -459,6 +479,7 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
|
|
|
| _SecureFilter _secureFilter = new _SecureFilter();
|
| int _filterPointer;
|
| + String _selectedProtocol;
|
|
|
| static Future<_RawSecureSocket> connect(
|
| host,
|
| @@ -471,7 +492,8 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
|
| bool requestClientCertificate: false,
|
| bool requireClientCertificate: false,
|
| bool sendClientCertificate: false,
|
| - bool onBadCertificate(X509Certificate certificate)}) {
|
| + bool onBadCertificate(X509Certificate certificate),
|
| + List<String> supportedProtocols}) {
|
| _verifyFields(host, requestedPort, certificateName, is_server,
|
| requestClientCertificate, requireClientCertificate,
|
| sendClientCertificate, onBadCertificate);
|
| @@ -488,7 +510,8 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
|
| requestClientCertificate,
|
| requireClientCertificate,
|
| sendClientCertificate,
|
| - onBadCertificate)
|
| + onBadCertificate,
|
| + supportedProtocols)
|
| ._handshakeComplete.future;
|
| }
|
|
|
| @@ -503,7 +526,8 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
|
| this.requestClientCertificate,
|
| this.requireClientCertificate,
|
| this.sendClientCertificate,
|
| - this.onBadCertificate(X509Certificate certificate)) {
|
| + this.onBadCertificate(X509Certificate certificate),
|
| + List<String> supportedProtocols) {
|
| _controller = new StreamController<RawSocketEvent>(
|
| sync: true,
|
| onListen: _onSubscriptionStateChange,
|
| @@ -548,13 +572,136 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
|
| requestClientCertificate ||
|
| requireClientCertificate,
|
| requireClientCertificate,
|
| - sendClientCertificate);
|
| + sendClientCertificate,
|
| + _protocolsToLengthEncoding(supportedProtocols));
|
| _secureHandshake();
|
| } catch (e, s) {
|
| _reportError(e, s);
|
| }
|
| }
|
|
|
| + /// Encodes a set of supported protocols for ALPN/NPN usage.
|
| + ///
|
| + /// The `protocols` list is expected to contain protocols in descending order
|
| + /// of preference.
|
| + ///
|
| + /// See RFC 7301 (https://tools.ietf.org/html/rfc7301) for the encoding of
|
| + /// `List<String> protocols`:
|
| + /// opaque ProtocolName<1..2^8-1>;
|
| + ///
|
| + /// struct {
|
| + /// ProtocolName protocol_name_list<2..2^16-1>
|
| + /// } ProtocolNameList;
|
| + ///
|
| + /// The encoding of the opaque `ProtocolName<lower..upper>` vector is
|
| + /// described in RFC 2246: 4.3 Vectors.
|
| + ///
|
| + /// Note: Even though this encoding scheme would allow a total
|
| + /// `ProtocolNameList` length of 65535, this limit cannot be reached. Testing
|
| + /// showed that more than ~ 65480 bytes will fail to negogiate a protocol.
|
| + /// We will be conservative and support only messages up to (1<<15) -1 bytes.
|
| + ///
|
| + /// Our NSS implementation will support ALPN and NPN transparently. The
|
| + /// default protocol will be the first in the encoded Uint8List.
|
| + ///
|
| + /// NOTE: The NSS library will treat the first protocol as the fallback
|
| + /// protocol. The remaining ones are sorted in (decreasing) priority order.
|
| + /// We therefore put the protocol least desired to the front, to make it the
|
| + /// default.
|
| + Uint8List _protocolsToLengthEncoding(List<String> protocols) {
|
| + if (protocols == null || protocols.length == 0) {
|
| + return new Uint8List(0);
|
| + }
|
| + int protocolsLength = protocols.length;
|
| +
|
| + // Calculate the number of bytes we will need if it is ASCII.
|
| + int expectedLength = protocolsLength;
|
| + for (int i = 0; i < protocolsLength; i++) {
|
| + int length = protocols[i].length;
|
| + if (length > 0 && length <= 255) {
|
| + expectedLength += length;
|
| + } else {
|
| + throw new ArgumentError(
|
| + 'Length of protocol must be between 1 and 255 (was: $length).');
|
| + }
|
| + }
|
| +
|
| + if (expectedLength >= (1 << 15)) {
|
| + throw new ArgumentError(
|
| + 'The maximum message length supported is 2^15-1.');
|
| + }
|
| +
|
| + // Try encoding the `List<String> protocols` array using fast ASCII path.
|
| + var bytes = new Uint8List(expectedLength);
|
| + int bytesOffset = 0;
|
| + for (int i = 0; i < protocolsLength; i++) {
|
| + // The last protocol will be encoded as the first/default one in the list.
|
| + // (i.e. rotate `protocols` by 1 to the right).
|
| + int index = i;
|
| + if (index == 0) index = protocols.length;
|
| + String proto = protocols[index - 1];
|
| +
|
| + // Add length byte.
|
| + bytes[bytesOffset++] = proto.length;
|
| + int bits = 0;
|
| +
|
| + // Add protocol bytes.
|
| + for (int j = 0; j < proto.length; j++) {
|
| + var char = proto.codeUnitAt(j);
|
| + bits |= char;
|
| + bytes[bytesOffset++] = char & 0xff;
|
| + }
|
| +
|
| + // Go slow case if we have encountered anything non-ascii.
|
| + if (bits > 0x7f) {
|
| + return _protocolsToLengthEncodingNonAsciiBailout(protocols);
|
| + }
|
| + }
|
| + return bytes;
|
| + }
|
| +
|
| + Uint8List _protocolsToLengthEncodingNonAsciiBailout(List<String> protocols) {
|
| + void addProtocol(List<int> outBytes, String protocol) {
|
| + var protocolBytes = UTF8.encode(protocol);
|
| + var len = protocolBytes.length;
|
| +
|
| + if (len > 255) {
|
| + throw new ArgumentError(
|
| + 'Length of protocol must be between 1 and 255 (was: $len)');
|
| + }
|
| + // Add length byte.
|
| + outBytes.add(len);
|
| +
|
| + // Add protocol bytes.
|
| + outBytes.addAll(protocolBytes);
|
| + }
|
| +
|
| + List<int> bytes = [];
|
| + addProtocol(bytes, protocols.last);
|
| + for (var i = 0; i < protocols.length -1; i++) {
|
| + addProtocol(bytes, protocols[i]);
|
| + }
|
| +
|
| + if (bytes.length >= (1 << 15)) {
|
| + throw new ArgumentError(
|
| + 'The maximum message length supported is 2^15-1.');
|
| + }
|
| +
|
| + return new Uint8List.fromList(bytes);
|
| + }
|
| +
|
| + void _addProtocolBytes(List<int> outBytes, String protocol) {
|
| + var protocolBytes = UTF8.encode(protocol);
|
| + var len = protocolBytes.length;
|
| +
|
| + if (len > 255) {
|
| + throw new ArgumentError(
|
| + 'Cannot support protocols with more than 255 characters');
|
| + }
|
| + outBytes.add(len);
|
| + outBytes.addAll(protocolBytes);
|
| + }
|
| +
|
| StreamSubscription listen(void onData(RawSocketEvent data),
|
| {Function onError,
|
| void onDone(),
|
| @@ -732,6 +879,8 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
|
|
|
| X509Certificate get peerCertificate => _secureFilter.peerCertificate;
|
|
|
| + String get selectedProtocol => _selectedProtocol;
|
| +
|
| bool _onBadCertificateWrapper(X509Certificate certificate) {
|
| if (onBadCertificate == null) return false;
|
| var result = onBadCertificate(certificate);
|
| @@ -845,8 +994,13 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
|
| _status = CONNECTED;
|
| if (_connectPending) {
|
| _connectPending = false;
|
| - // We don't want user code to run synchronously in this callback.
|
| - Timer.run(() => _handshakeComplete.complete(this));
|
| + try {
|
| + _selectedProtocol = _secureFilter.selectedProtocol();
|
| + // We don't want user code to run synchronously in this callback.
|
| + Timer.run(() => _handshakeComplete.complete(this));
|
| + } catch (error, stack) {
|
| + _handshakeComplete.completeError(error, stack);
|
| + }
|
| }
|
| }
|
|
|
| @@ -1206,7 +1360,8 @@ abstract class _SecureFilter {
|
| String certificateName,
|
| bool requestClientCertificate,
|
| bool requireClientCertificate,
|
| - bool sendClientCertificate);
|
| + bool sendClientCertificate,
|
| + Uint8List protocols);
|
| void destroy();
|
| void handshake();
|
| void rehandshake();
|
|
|