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(); |