Chromium Code Reviews| 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..d6d24b7054ce119dde73ec748bd4a38ea6ed7e90 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 tothe front, to make it the |
|
Søren Gjesse
2014/11/10 11:37:59
tothe -> to the
kustermann
2014/11/10 12:30:21
Done.
|
| + /// 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(); |