Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Unified Diff: tool/input_sdk/lib/io/secure_socket.dart

Issue 1976103003: Migrate dart2js stubs for dart:io (Closed) Base URL: https://github.com/dart-lang/dev_compiler.git@master
Patch Set: Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tool/input_sdk/lib/io/secure_server_socket.dart ('k') | tool/input_sdk/lib/io/security_context.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tool/input_sdk/lib/io/secure_socket.dart
diff --git a/tool/input_sdk/lib/io/secure_socket.dart b/tool/input_sdk/lib/io/secure_socket.dart
new file mode 100644
index 0000000000000000000000000000000000000000..380a8c466abd282facda77184ed96728b4e0003b
--- /dev/null
+++ b/tool/input_sdk/lib/io/secure_socket.dart
@@ -0,0 +1,1263 @@
+// Copyright (c) 2013, 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.
+
+part of dart.io;
+
+/**
+ * A high-level class for communicating securely over a TCP socket, using
+ * TLS and SSL. The [SecureSocket] exposes both a [Stream] and an
+ * [IOSink] interface, making it ideal for using together with
+ * other [Stream]s.
+ */
+abstract class SecureSocket implements Socket {
+ external factory SecureSocket._(RawSecureSocket rawSocket);
+
+ /**
+ * Constructs a new secure client socket and connects it to the given
+ * [host] on port [port]. The returned Future will complete with a
+ * [SecureSocket] that is connected and ready for subscription.
+ *
+ * The certificate provided by the server is checked
+ * using the trusted certificates set in the SecurityContext object.
+ * The default SecurityContext object contains a built-in set of trusted
+ * root certificates for well-known certificate authorities.
+ *
+ * [onBadCertificate] is an optional handler for unverifiable certificates.
+ * The handler receives the [X509Certificate], and can inspect it and
+ * decide (or let the user decide) whether to accept
+ * the connection or not. The handler should return true
+ * to continue the [SecureSocket] connection.
+ */
+ static Future<SecureSocket> connect(
+ host,
+ int port,
+ {SecurityContext context,
+ bool onBadCertificate(X509Certificate certificate),
+ List<String> supportedProtocols}) {
+ return RawSecureSocket.connect(host,
+ port,
+ context: context,
+ onBadCertificate: onBadCertificate,
+ supportedProtocols: supportedProtocols)
+ .then((rawSocket) => new SecureSocket._(rawSocket));
+ }
+
+ /**
+ * Takes an already connected [socket] and starts client side TLS
+ * handshake to make the communication secure. When the returned
+ * future completes the [SecureSocket] has completed the TLS
+ * handshake. Using this function requires that the other end of the
+ * connection is prepared for TLS handshake.
+ *
+ * If the [socket] already has a subscription, this subscription
+ * will no longer receive and events. In most cases calling
+ * `pause` on this subscription before starting TLS handshake is
+ * the right thing to do.
+ *
+ * If the [host] argument is passed it will be used as the host name
+ * for the TLS handshake. If [host] is not passed the host name from
+ * the [socket] will be used. The [host] can be either a [String] or
+ * an [InternetAddress].
+ *
+ * Calling this function will _not_ cause a DNS host lookup. If the
+ * [host] passed is a [String] the [InternetAddress] for the
+ * resulting [SecureSocket] will have the passed in [host] as its
+ * host value and the internet address of the already connected
+ * socket as its address value.
+ *
+ * See [connect] for more information on the arguments.
+ *
+ */
+ static Future<SecureSocket> secure(
+ Socket socket,
+ {host,
+ SecurityContext context,
+ bool onBadCertificate(X509Certificate certificate)}) {
+ var completer = new Completer();
+ (socket as dynamic)._detachRaw()
+ .then((detachedRaw) {
+ return RawSecureSocket.secure(
+ detachedRaw[0],
+ subscription: detachedRaw[1],
+ host: host,
+ context: context,
+ onBadCertificate: onBadCertificate);
+ })
+ .then((raw) {
+ completer.complete(new SecureSocket._(raw));
+ });
+ return completer.future;
+ }
+
+ /**
+ * Takes an already connected [socket] and starts server side TLS
+ * handshake to make the communication secure. When the returned
+ * future completes the [SecureSocket] has completed the TLS
+ * handshake. Using this function requires that the other end of the
+ * connection is going to start the TLS handshake.
+ *
+ * If the [socket] already has a subscription, this subscription
+ * will no longer receive and events. In most cases calling
+ * [:pause:] on this subscription before starting TLS handshake is
+ * the right thing to do.
+ *
+ * If some of the data of the TLS handshake has already been read
+ * from the socket this data can be passed in the [bufferedData]
+ * parameter. This data will be processed before any other data
+ * available on the socket.
+ *
+ * See [SecureServerSocket.bind] for more information on the
+ * arguments.
+ *
+ */
+ static Future<SecureSocket> secureServer(
+ Socket socket,
+ SecurityContext context,
+ {List<int> bufferedData,
+ bool requestClientCertificate: false,
+ bool requireClientCertificate: false,
+ List<String> supportedProtocols}) {
+ var completer = new Completer();
+ (socket as dynamic)._detachRaw()
+ .then((detachedRaw) {
+ return RawSecureSocket.secureServer(
+ detachedRaw[0],
+ context,
+ subscription: detachedRaw[1],
+ bufferedData: bufferedData,
+ requestClientCertificate: requestClientCertificate,
+ requireClientCertificate: requireClientCertificate,
+ supportedProtocols: supportedProtocols);
+ })
+ .then((raw) {
+ completer.complete(new SecureSocket._(raw));
+ });
+ return completer.future;
+ }
+
+ /**
+ * Get the peer certificate for a connected SecureSocket. If this
+ * SecureSocket is the server end of a secure socket connection,
+ * [peerCertificate] will return the client certificate, or null, if no
+ * client certificate was received. If it is the client end,
+ * [peerCertificate] will return the server's certificate.
+ */
+ 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.
+ *
+ * This repeats the SSL or TLS handshake, with options that allow clearing
+ * the session cache and requesting a client certificate.
+ */
+ void renegotiate({bool useSessionCache: true,
+ bool requestClientCertificate: false,
+ bool requireClientCertificate: false});
+}
+
+
+/**
+ * RawSecureSocket provides a secure (SSL or TLS) network connection.
+ * Client connections to a server are provided by calling
+ * RawSecureSocket.connect. A secure server, created with
+ * [RawSecureServerSocket], also returns RawSecureSocket objects representing
+ * the server end of a secure connection.
+ * The certificate provided by the server is checked
+ * using the trusted certificates set in the SecurityContext object.
+ * The default [SecurityContext] object contains a built-in set of trusted
+ * root certificates for well-known certificate authorities.
+ */
+abstract class RawSecureSocket implements RawSocket {
+ /**
+ * Constructs a new secure client socket and connect it to the given
+ * host on the given port. The returned [Future] is completed with the
+ * RawSecureSocket when it is connected and ready for subscription.
+ *
+ * The certificate provided by the server is checked using the trusted
+ * certificates set in the SecurityContext object If a certificate and key are
+ * set on the client, using [SecurityContext.useCertificateChain] and
+ * [SecurityContext.usePrivateKey], and the server asks for a client
+ * certificate, then that client certificate is sent to the server.
+ *
+ * [onBadCertificate] is an optional handler for unverifiable certificates.
+ * The handler receives the [X509Certificate], and can inspect it and
+ * decide (or let the user decide) whether to accept
+ * the connection or not. The handler should return true
+ * to continue the [RawSecureSocket] connection.
+ */
+ static Future<RawSecureSocket> connect(
+ host,
+ int port,
+ {SecurityContext context,
+ bool onBadCertificate(X509Certificate certificate),
+ List<String> supportedProtocols}) {
+ _RawSecureSocket._verifyFields(
+ host,
+ port,
+ false,
+ false,
+ false,
+ onBadCertificate);
+ return RawSocket.connect(host, port)
+ .then((socket) {
+ return secure(socket,
+ context: context,
+ onBadCertificate: onBadCertificate,
+ supportedProtocols: supportedProtocols);
+ });
+ }
+
+ /**
+ * Takes an already connected [socket] and starts client side TLS
+ * handshake to make the communication secure. When the returned
+ * future completes the [RawSecureSocket] has completed the TLS
+ * handshake. Using this function requires that the other end of the
+ * connection is prepared for TLS handshake.
+ *
+ * If the [socket] already has a subscription, pass the existing
+ * subscription in the [subscription] parameter. The [secure]
+ * operation will take over the subscription by replacing the
+ * handlers with it own secure processing. The caller must not touch
+ * this subscription anymore. Passing a paused subscription is an
+ * error.
+ *
+ * If the [host] argument is passed it will be used as the host name
+ * for the TLS handshake. If [host] is not passed the host name from
+ * the [socket] will be used. The [host] can be either a [String] or
+ * an [InternetAddress].
+ *
+ * Calling this function will _not_ cause a DNS host lookup. If the
+ * [host] passed is a [String] the [InternetAddress] for the
+ * resulting [SecureSocket] will have this passed in [host] as its
+ * host value and the internet address of the already connected
+ * socket as its address value.
+ *
+ * See [connect] for more information on the arguments.
+ *
+ */
+ static Future<RawSecureSocket> secure(
+ RawSocket socket,
+ {StreamSubscription subscription,
+ host,
+ SecurityContext context,
+ bool onBadCertificate(X509Certificate certificate),
+ List<String> supportedProtocols}) {
+ socket.readEventsEnabled = false;
+ socket.writeEventsEnabled = false;
+ return _RawSecureSocket.connect(
+ host != null ? host : socket.address.host,
+ socket.port,
+ is_server: false,
+ socket: socket,
+ subscription: subscription,
+ context: context,
+ onBadCertificate: onBadCertificate,
+ supportedProtocols: supportedProtocols);
+ }
+
+ /**
+ * Takes an already connected [socket] and starts server side TLS
+ * handshake to make the communication secure. When the returned
+ * future completes the [RawSecureSocket] has completed the TLS
+ * handshake. Using this function requires that the other end of the
+ * connection is going to start the TLS handshake.
+ *
+ * If the [socket] already has a subscription, pass the existing
+ * subscription in the [subscription] parameter. The [secureServer]
+ * operation will take over the subscription by replacing the
+ * handlers with it own secure processing. The caller must not touch
+ * this subscription anymore. Passing a paused subscription is an
+ * error.
+ *
+ * If some of the data of the TLS handshake has already been read
+ * from the socket this data can be passed in the [bufferedData]
+ * parameter. This data will be processed before any other data
+ * available on the socket.
+ *
+ * See [RawSecureServerSocket.bind] for more information on the
+ * arguments.
+ *
+ */
+ static Future<RawSecureSocket> secureServer(
+ RawSocket socket,
+ SecurityContext context,
+ {StreamSubscription subscription,
+ List<int> bufferedData,
+ bool requestClientCertificate: false,
+ bool requireClientCertificate: false,
+ List<String> supportedProtocols}) {
+ socket.readEventsEnabled = false;
+ socket.writeEventsEnabled = false;
+ return _RawSecureSocket.connect(
+ socket.address,
+ socket.remotePort,
+ context: context,
+ is_server: true,
+ socket: socket,
+ subscription: subscription,
+ bufferedData: bufferedData,
+ requestClientCertificate: requestClientCertificate,
+ requireClientCertificate: requireClientCertificate,
+ supportedProtocols: supportedProtocols);
+ }
+
+ /**
+ * Renegotiate an existing secure connection, renewing the session keys
+ * and possibly changing the connection properties.
+ *
+ * This repeats the SSL or TLS handshake, with options that allow clearing
+ * the session cache and requesting a client certificate.
+ */
+ void renegotiate({bool useSessionCache: true,
+ bool requestClientCertificate: false,
+ bool requireClientCertificate: false});
+
+ /**
+ * Get the peer certificate for a connected RawSecureSocket. If this
+ * RawSecureSocket is the server end of a secure socket connection,
+ * [peerCertificate] will return the client certificate, or null, if no
+ * client certificate was received. If it is the client end,
+ * [peerCertificate] will return the server's certificate.
+ */
+ X509Certificate get peerCertificate;
+
+ /**
+ * Get the protocol which was selected during protocol negotiation.
+ */
+ String get selectedProtocol;
+}
+
+
+/**
+ * X509Certificate represents an SSL certificate, with accessors to
+ * get the fields of the certificate.
+ */
+abstract class X509Certificate {
+ external factory X509Certificate._();
+
+ String get subject;
+ String get issuer;
+ DateTime get startValidity;
+ DateTime get endValidity;
+}
+
+
+class _FilterStatus {
+ bool progress = false; // The filter read or wrote data to the buffers.
+ bool readEmpty = true; // The read buffers and decryption filter are empty.
+ bool writeEmpty = true; // The write buffers and encryption filter are empty.
+ // These are set if a buffer changes state from empty or full.
+ bool readPlaintextNoLongerEmpty = false;
+ bool writePlaintextNoLongerFull = false;
+ bool readEncryptedNoLongerFull = false;
+ bool writeEncryptedNoLongerEmpty = false;
+
+ _FilterStatus();
+}
+
+
+class _RawSecureSocket extends Stream<RawSocketEvent>
+ implements RawSecureSocket {
+ // Status states
+ static final int HANDSHAKE = 201;
+ static final int CONNECTED = 202;
+ static final int CLOSED = 203;
+
+ // Buffer identifiers.
+ // These must agree with those in the native C++ implementation.
+ static final int READ_PLAINTEXT = 0;
+ static final int WRITE_PLAINTEXT = 1;
+ static final int READ_ENCRYPTED = 2;
+ static final int WRITE_ENCRYPTED = 3;
+ static final int NUM_BUFFERS = 4;
+
+ // Is a buffer identifier for an encrypted buffer?
+ static bool _isBufferEncrypted(int identifier) => identifier >= READ_ENCRYPTED;
+
+ RawSocket _socket;
+ final Completer<_RawSecureSocket> _handshakeComplete =
+ new Completer<_RawSecureSocket>();
+ StreamController<RawSocketEvent> _controller;
+ Stream<RawSocketEvent> _stream;
+ StreamSubscription<RawSocketEvent> _socketSubscription;
+ List<int> _bufferedData;
+ int _bufferedDataIndex = 0;
+ final InternetAddress address;
+ final bool is_server;
+ SecurityContext context;
+ final bool requestClientCertificate;
+ final bool requireClientCertificate;
+ final Function onBadCertificate;
+
+ var _status = HANDSHAKE;
+ bool _writeEventsEnabled = true;
+ bool _readEventsEnabled = true;
+ int _pauseCount = 0;
+ bool _pendingReadEvent = false;
+ bool _socketClosedRead = false; // The network socket is closed for reading.
+ bool _socketClosedWrite = false; // The network socket is closed for writing.
+ bool _closedRead = false; // The secure socket has fired an onClosed event.
+ bool _closedWrite = false; // The secure socket has been closed for writing.
+ Completer _closeCompleter = new Completer(); // The network socket is gone.
+ _FilterStatus _filterStatus = new _FilterStatus();
+ bool _connectPending = true;
+ bool _filterPending = false;
+ bool _filterActive = false;
+
+ _SecureFilter _secureFilter = new _SecureFilter();
+ String _selectedProtocol;
+
+ static Future<_RawSecureSocket> connect(
+ dynamic/*String|InternetAddress*/ host,
+ int requestedPort,
+ {bool is_server,
+ SecurityContext context,
+ RawSocket socket,
+ StreamSubscription subscription,
+ List<int> bufferedData,
+ bool requestClientCertificate: false,
+ bool requireClientCertificate: false,
+ bool onBadCertificate(X509Certificate certificate),
+ List<String> supportedProtocols}) {
+ _verifyFields(host, requestedPort, is_server,
+ requestClientCertificate, requireClientCertificate,
+ onBadCertificate);
+ if (host is InternetAddress) host = host.host;
+ InternetAddress address = socket.address;
+ if (host != null) {
+ address = InternetAddress._cloneWithNewHost(address, host);
+ }
+ return new _RawSecureSocket(address,
+ requestedPort,
+ is_server,
+ context,
+ socket,
+ subscription,
+ bufferedData,
+ requestClientCertificate,
+ requireClientCertificate,
+ onBadCertificate,
+ supportedProtocols)
+ ._handshakeComplete.future;
+ }
+
+ _RawSecureSocket(
+ this.address,
+ int requestedPort,
+ this.is_server,
+ this.context,
+ RawSocket this._socket,
+ this._socketSubscription,
+ this._bufferedData,
+ this.requestClientCertificate,
+ this.requireClientCertificate,
+ this.onBadCertificate(X509Certificate certificate),
+ List<String> supportedProtocols) {
+ if (context == null) {
+ context = SecurityContext.defaultContext;
+ }
+ _controller = new StreamController<RawSocketEvent>(
+ sync: true,
+ onListen: _onSubscriptionStateChange,
+ onPause: _onPauseStateChange,
+ onResume: _onPauseStateChange,
+ onCancel: _onSubscriptionStateChange);
+ _stream = _controller.stream;
+ // Throw an ArgumentError if any field is invalid. After this, all
+ // errors will be reported through the future or the stream.
+ _secureFilter.init();
+ _secureFilter.registerHandshakeCompleteCallback(
+ _secureHandshakeCompleteHandler);
+ if (onBadCertificate != null) {
+ _secureFilter.registerBadCertificateCallback(_onBadCertificateWrapper);
+ }
+ _socket.readEventsEnabled = true;
+ _socket.writeEventsEnabled = false;
+ if (_socketSubscription == null) {
+ // If a current subscription is provided use this otherwise
+ // create a new one.
+ _socketSubscription = _socket.listen(_eventDispatcher,
+ onError: _reportError,
+ onDone: _doneHandler);
+ } else {
+ if (_socketSubscription.isPaused) {
+ _socket.close();
+ throw new ArgumentError(
+ "Subscription passed to TLS upgrade is paused");
+ }
+ // If we are upgrading a socket that is already closed for read,
+ // report an error as if we received READ_CLOSED during the handshake.
+ dynamic s = _socket; // Cast to dynamic to avoid warning.
+ if (s._socket.closedReadEventSent) {
+ _eventDispatcher(RawSocketEvent.READ_CLOSED);
+ }
+ _socketSubscription
+ ..onData(_eventDispatcher)
+ ..onError(_reportError)
+ ..onDone(_doneHandler);
+ }
+ try {
+ var encodedProtocols =
+ SecurityContext._protocolsToLengthEncoding(supportedProtocols);
+ _secureFilter.connect(address.host,
+ context,
+ is_server,
+ requestClientCertificate ||
+ requireClientCertificate,
+ requireClientCertificate,
+ encodedProtocols);
+ _secureHandshake();
+ } catch (e, s) {
+ _reportError(e, s);
+ }
+ }
+
+ StreamSubscription<RawSocketEvent> listen(void onData(RawSocketEvent data),
+ {Function onError,
+ void onDone(),
+ bool cancelOnError}) {
+ _sendWriteEvent();
+ return _stream.listen(onData,
+ onError: onError,
+ onDone: onDone,
+ cancelOnError: cancelOnError);
+ }
+
+ static void _verifyFields(host,
+ int requestedPort,
+ bool is_server,
+ bool requestClientCertificate,
+ bool requireClientCertificate,
+ Function onBadCertificate) {
+ if (host is! String && host is! InternetAddress) {
+ throw new ArgumentError("host is not a String or an InternetAddress");
+ }
+ if (requestedPort is! int) {
+ throw new ArgumentError("requestedPort is not an int");
+ }
+ if (requestedPort < 0 || requestedPort > 65535) {
+ throw new ArgumentError("requestedPort is not in the range 0..65535");
+ }
+ if (requestClientCertificate is! bool) {
+ throw new ArgumentError("requestClientCertificate is not a bool");
+ }
+ if (requireClientCertificate is! bool) {
+ throw new ArgumentError("requireClientCertificate is not a bool");
+ }
+ if (onBadCertificate != null && onBadCertificate is! Function) {
+ throw new ArgumentError("onBadCertificate is not null or a Function");
+ }
+ }
+
+ int get port => _socket.port;
+
+ InternetAddress get remoteAddress => _socket.remoteAddress;
+
+ int get remotePort => _socket.remotePort;
+
+ void set _owner(owner) {
+ (_socket as dynamic)._owner = owner;
+ }
+
+ int available() {
+ return _status != CONNECTED ? 0
+ : _secureFilter.buffers[READ_PLAINTEXT].length;
+ }
+
+ Future<RawSecureSocket> close() {
+ shutdown(SocketDirection.BOTH);
+ return _closeCompleter.future;
+ }
+
+ void _completeCloseCompleter([dummy]) {
+ if (!_closeCompleter.isCompleted) _closeCompleter.complete(this);
+ }
+
+ void _close() {
+ _closedWrite = true;
+ _closedRead = true;
+ if (_socket != null) {
+ _socket.close().then(_completeCloseCompleter);
+ } else {
+ _completeCloseCompleter();
+ }
+ _socketClosedWrite = true;
+ _socketClosedRead = true;
+ if (!_filterActive && _secureFilter != null) {
+ _secureFilter.destroy();
+ _secureFilter = null;
+ }
+ if (_socketSubscription != null) {
+ _socketSubscription.cancel();
+ }
+ _controller.close();
+ _status = CLOSED;
+ }
+
+ void shutdown(SocketDirection direction) {
+ if (direction == SocketDirection.SEND ||
+ direction == SocketDirection.BOTH) {
+ _closedWrite = true;
+ if (_filterStatus.writeEmpty) {
+ _socket.shutdown(SocketDirection.SEND);
+ _socketClosedWrite = true;
+ if (_closedRead) {
+ _close();
+ }
+ }
+ }
+ if (direction == SocketDirection.RECEIVE ||
+ direction == SocketDirection.BOTH) {
+ _closedRead = true;
+ _socketClosedRead = true;
+ _socket.shutdown(SocketDirection.RECEIVE);
+ if (_socketClosedWrite) {
+ _close();
+ }
+ }
+ }
+
+ bool get writeEventsEnabled => _writeEventsEnabled;
+
+ void set writeEventsEnabled(bool value) {
+ _writeEventsEnabled = value;
+ if (value) {
+ Timer.run(() => _sendWriteEvent());
+ }
+ }
+
+ bool get readEventsEnabled => _readEventsEnabled;
+
+ void set readEventsEnabled(bool value) {
+ _readEventsEnabled = value;
+ _scheduleReadEvent();
+ }
+
+ List<int> read([int length]) {
+ if (length != null && (length is! int || length < 0)) {
+ throw new ArgumentError(
+ "Invalid length parameter in SecureSocket.read (length: $length)");
+ }
+ if (_closedRead) {
+ throw new SocketException("Reading from a closed socket");
+ }
+ if (_status != CONNECTED) {
+ return null;
+ }
+ var result = _secureFilter.buffers[READ_PLAINTEXT].read(length);
+ _scheduleFilter();
+ return result;
+ }
+
+ // Write the data to the socket, and schedule the filter to encrypt it.
+ int write(List<int> data, [int offset, int bytes]) {
+ if (bytes != null && (bytes is! int || bytes < 0)) {
+ throw new ArgumentError(
+ "Invalid bytes parameter in SecureSocket.read (bytes: $bytes)");
+ }
+ if (offset != null && (offset is! int || offset < 0)) {
+ throw new ArgumentError(
+ "Invalid offset parameter in SecureSocket.read (offset: $offset)");
+ }
+ if (_closedWrite) {
+ _controller.addError(new SocketException("Writing to a closed socket"));
+ return 0;
+ }
+ if (_status != CONNECTED) return 0;
+ if (offset == null) offset = 0;
+ if (bytes == null) bytes = data.length - offset;
+
+ int written =
+ _secureFilter.buffers[WRITE_PLAINTEXT].write(data, offset, bytes);
+ if (written > 0) {
+ _filterStatus.writeEmpty = false;
+ }
+ _scheduleFilter();
+ return written;
+ }
+
+ X509Certificate get peerCertificate => _secureFilter.peerCertificate;
+
+ String get selectedProtocol => _selectedProtocol;
+
+ bool _onBadCertificateWrapper(X509Certificate certificate) {
+ if (onBadCertificate == null) return false;
+ var result = onBadCertificate(certificate);
+ if (result is bool) return result;
+ throw new HandshakeException(
+ "onBadCertificate callback returned non-boolean $result");
+ }
+
+ bool setOption(SocketOption option, bool enabled) {
+ if (_socket == null) return false;
+ return _socket.setOption(option, enabled);
+ }
+
+ void _eventDispatcher(RawSocketEvent event) {
+ try {
+ if (event == RawSocketEvent.READ) {
+ _readHandler();
+ } else if (event == RawSocketEvent.WRITE) {
+ _writeHandler();
+ } else if (event == RawSocketEvent.READ_CLOSED) {
+ _closeHandler();
+ }
+ } catch (e, stackTrace) {
+ _reportError(e, stackTrace);
+ }
+ }
+
+ void _readHandler() {
+ _readSocket();
+ _scheduleFilter();
+ }
+
+ void _writeHandler() {
+ _writeSocket();
+ _scheduleFilter();
+ }
+
+ void _doneHandler() {
+ if (_filterStatus.readEmpty) {
+ _close();
+ }
+ }
+
+ void _reportError(e, [StackTrace stackTrace]) {
+ if (_status == CLOSED) {
+ return;
+ } else if (_connectPending) {
+ // _connectPending is true until the handshake has completed, and the
+ // _handshakeComplete future returned from SecureSocket.connect has
+ // completed. Before this point, we must complete it with an error.
+ _handshakeComplete.completeError(e, stackTrace);
+ } else {
+ _controller.addError(e, stackTrace);
+ }
+ _close();
+ }
+
+ void _closeHandler() {
+ if (_status == CONNECTED) {
+ if (_closedRead) return;
+ _socketClosedRead = true;
+ if (_filterStatus.readEmpty) {
+ _closedRead = true;
+ _controller.add(RawSocketEvent.READ_CLOSED);
+ if (_socketClosedWrite) {
+ _close();
+ }
+ } else {
+ _scheduleFilter();
+ }
+ } else if (_status == HANDSHAKE) {
+ _socketClosedRead = true;
+ if (_filterStatus.readEmpty) {
+ _reportError(
+ new HandshakeException('Connection terminated during handshake'),
+ null);
+ } else {
+ _secureHandshake();
+ }
+ }
+ }
+
+ void _secureHandshake() {
+ try {
+ _secureFilter.handshake();
+ _filterStatus.writeEmpty = false;
+ _readSocket();
+ _writeSocket();
+ _scheduleFilter();
+ } catch (e, stackTrace) {
+ _reportError(e, stackTrace);
+ }
+ }
+
+ void renegotiate({bool useSessionCache: true,
+ bool requestClientCertificate: false,
+ bool requireClientCertificate: false}) {
+ if (_status != CONNECTED) {
+ throw new HandshakeException(
+ "Called renegotiate on a non-connected socket");
+ }
+ _secureFilter.renegotiate(useSessionCache,
+ requestClientCertificate,
+ requireClientCertificate);
+ _status = HANDSHAKE;
+ _filterStatus.writeEmpty = false;
+ _scheduleFilter();
+ }
+
+ void _secureHandshakeCompleteHandler() {
+ _status = CONNECTED;
+ if (_connectPending) {
+ _connectPending = false;
+ 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);
+ }
+ }
+ }
+
+ void _onPauseStateChange() {
+ if (_controller.isPaused) {
+ _pauseCount++;
+ } else {
+ _pauseCount--;
+ if (_pauseCount == 0) {
+ _scheduleReadEvent();
+ _sendWriteEvent(); // Can send event synchronously.
+ }
+ }
+
+ if (!_socketClosedRead || !_socketClosedWrite) {
+ if (_controller.isPaused) {
+ _socketSubscription.pause();
+ } else {
+ _socketSubscription.resume();
+ }
+ }
+ }
+
+ void _onSubscriptionStateChange() {
+ if (_controller.hasListener) {
+ // TODO(ajohnsen): Do something here?
+ }
+ }
+
+ void _scheduleFilter() {
+ _filterPending = true;
+ _tryFilter();
+ }
+
+ void _tryFilter() {
+ if (_status == CLOSED) {
+ return;
+ }
+ if (_filterPending && !_filterActive) {
+ _filterActive = true;
+ _filterPending = false;
+ _pushAllFilterStages().then((status) {
+ _filterStatus = status;
+ _filterActive = false;
+ if (_status == CLOSED) {
+ _secureFilter.destroy();
+ _secureFilter = null;
+ return;
+ }
+ _socket.readEventsEnabled = true;
+ if (_filterStatus.writeEmpty && _closedWrite && !_socketClosedWrite) {
+ // Checks for and handles all cases of partially closed sockets.
+ shutdown(SocketDirection.SEND);
+ if (_status == CLOSED) {
+ return;
+ }
+ }
+ if (_filterStatus.readEmpty && _socketClosedRead && !_closedRead) {
+ if (_status == HANDSHAKE) {
+ _secureFilter.handshake();
+ if (_status == HANDSHAKE) {
+ throw new HandshakeException(
+ 'Connection terminated during handshake');
+ }
+ }
+ _closeHandler();
+ }
+ if (_status == CLOSED) {
+ return;
+ }
+ if (_filterStatus.progress) {
+ _filterPending = true;
+ if (_filterStatus.writeEncryptedNoLongerEmpty) {
+ _writeSocket();
+ }
+ if (_filterStatus.writePlaintextNoLongerFull) {
+ _sendWriteEvent();
+ }
+ if (_filterStatus.readEncryptedNoLongerFull) {
+ _readSocket();
+ }
+ if (_filterStatus.readPlaintextNoLongerEmpty) {
+ _scheduleReadEvent();
+ }
+ if (_status == HANDSHAKE) {
+ _secureHandshake();
+ }
+ }
+ _tryFilter();
+ }).catchError(_reportError);
+ }
+ }
+
+ List<int> _readSocketOrBufferedData(int bytes) {
+ if (_bufferedData != null) {
+ if (bytes > _bufferedData.length - _bufferedDataIndex) {
+ bytes = _bufferedData.length - _bufferedDataIndex;
+ }
+ var result = _bufferedData.sublist(_bufferedDataIndex,
+ _bufferedDataIndex + bytes);
+ _bufferedDataIndex += bytes;
+ if (_bufferedData.length == _bufferedDataIndex) {
+ _bufferedData = null;
+ }
+ return result;
+ } else if (!_socketClosedRead) {
+ return _socket.read(bytes);
+ } else {
+ return null;
+ }
+ }
+
+ void _readSocket() {
+ if (_status == CLOSED) return;
+ var buffer = _secureFilter.buffers[READ_ENCRYPTED];
+ if (buffer.writeFromSource(_readSocketOrBufferedData) > 0) {
+ _filterStatus.readEmpty = false;
+ } else {
+ _socket.readEventsEnabled = false;
+ }
+ }
+
+ void _writeSocket() {
+ if (_socketClosedWrite) return;
+ var buffer = _secureFilter.buffers[WRITE_ENCRYPTED];
+ if (buffer.readToSocket(_socket)) { // Returns true if blocked
+ _socket.writeEventsEnabled = true;
+ }
+ }
+
+ // If a read event should be sent, add it to the controller.
+ _scheduleReadEvent() {
+ if (!_pendingReadEvent &&
+ _readEventsEnabled &&
+ _pauseCount == 0 &&
+ _secureFilter != null &&
+ !_secureFilter.buffers[READ_PLAINTEXT].isEmpty) {
+ _pendingReadEvent = true;
+ Timer.run(_sendReadEvent);
+ }
+ }
+
+ _sendReadEvent() {
+ _pendingReadEvent = false;
+ if (_status != CLOSED &&
+ _readEventsEnabled &&
+ _pauseCount == 0 &&
+ _secureFilter != null &&
+ !_secureFilter.buffers[READ_PLAINTEXT].isEmpty) {
+ _controller.add(RawSocketEvent.READ);
+ _scheduleReadEvent();
+ }
+ }
+
+ // If a write event should be sent, add it to the controller.
+ _sendWriteEvent() {
+ if (!_closedWrite &&
+ _writeEventsEnabled &&
+ _pauseCount == 0 &&
+ _secureFilter != null &&
+ _secureFilter.buffers[WRITE_PLAINTEXT].free > 0) {
+ _writeEventsEnabled = false;
+ _controller.add(RawSocketEvent.WRITE);
+ }
+ }
+
+ Future<_FilterStatus> _pushAllFilterStages() {
+ bool wasInHandshake = _status != CONNECTED;
+ List args = new List(2 + NUM_BUFFERS * 2);
+ args[0] = _secureFilter._pointer();
+ args[1] = wasInHandshake;
+ var bufs = _secureFilter.buffers;
+ for (var i = 0; i < NUM_BUFFERS; ++i) {
+ args[2 * i + 2] = bufs[i].start;
+ args[2 * i + 3] = bufs[i].end;
+ }
+
+ return _IOService._dispatch(_SSL_PROCESS_FILTER, args).then((response) {
+ if (response.length == 2) {
+ if (wasInHandshake) {
+ // If we're in handshake, throw a handshake error.
+ _reportError(
+ new HandshakeException('${response[1]} error ${response[0]}'),
+ null);
+ } else {
+ // If we're connected, throw a TLS error.
+ _reportError(new TlsException('${response[1]} error ${response[0]}'),
+ null);
+ }
+ }
+ int start(int index) => response[2 * index];
+ int end(int index) => response[2 * index + 1];
+
+ _FilterStatus status = new _FilterStatus();
+ // Compute writeEmpty as "write plaintext buffer and write encrypted
+ // buffer were empty when we started and are empty now".
+ status.writeEmpty = bufs[WRITE_PLAINTEXT].isEmpty &&
+ start(WRITE_ENCRYPTED) == end(WRITE_ENCRYPTED);
+ // If we were in handshake when this started, _writeEmpty may be false
+ // because the handshake wrote data after we checked.
+ if (wasInHandshake) status.writeEmpty = false;
+
+ // Compute readEmpty as "both read buffers were empty when we started
+ // and are empty now".
+ status.readEmpty = bufs[READ_ENCRYPTED].isEmpty &&
+ start(READ_PLAINTEXT) == end(READ_PLAINTEXT);
+
+ _ExternalBuffer buffer = bufs[WRITE_PLAINTEXT];
+ int new_start = start(WRITE_PLAINTEXT);
+ if (new_start != buffer.start) {
+ status.progress = true;
+ if (buffer.free == 0) {
+ status.writePlaintextNoLongerFull = true;
+ }
+ buffer.start = new_start;
+ }
+ buffer = bufs[READ_ENCRYPTED];
+ new_start = start(READ_ENCRYPTED);
+ if (new_start != buffer.start) {
+ status.progress = true;
+ if (buffer.free == 0) {
+ status.readEncryptedNoLongerFull = true;
+ }
+ buffer.start = new_start;
+ }
+ buffer = bufs[WRITE_ENCRYPTED];
+ int new_end = end(WRITE_ENCRYPTED);
+ if (new_end != buffer.end) {
+ status.progress = true;
+ if (buffer.length == 0) {
+ status.writeEncryptedNoLongerEmpty = true;
+ }
+ buffer.end = new_end;
+ }
+ buffer = bufs[READ_PLAINTEXT];
+ new_end = end(READ_PLAINTEXT);
+ if (new_end != buffer.end) {
+ status.progress = true;
+ if (buffer.length == 0) {
+ status.readPlaintextNoLongerEmpty = true;
+ }
+ buffer.end = new_end;
+ }
+ return status;
+ });
+ }
+}
+
+
+/**
+ * A circular buffer backed by an external byte array. Accessed from
+ * both C++ and Dart code in an unsynchronized way, with one reading
+ * and one writing. All updates to start and end are done by Dart code.
+ */
+class _ExternalBuffer {
+ List data; // This will be a ExternalByteArray, backed by C allocated data.
+ int start;
+ int end;
+ final size;
+
+ _ExternalBuffer(this.size) {
+ start = end = size ~/ 2;
+ }
+
+ void advanceStart(int bytes) {
+ assert(start > end || start + bytes <= end);
+ start += bytes;
+ if (start >= size) {
+ start -= size;
+ assert(start <= end);
+ assert(start < size);
+ }
+ }
+
+ void advanceEnd(int bytes) {
+ assert(start <= end || start > end + bytes);
+ end += bytes;
+ if (end >= size) {
+ end -= size;
+ assert(end < start);
+ assert(end < size);
+ }
+ }
+
+ bool get isEmpty => end == start;
+
+ int get length =>
+ start > end ? size + end - start : end - start;
+
+ int get linearLength =>
+ start > end ? size - start : end - start;
+
+ int get free =>
+ start > end ? start - end - 1 : size + start - end - 1;
+
+ int get linearFree {
+ if (start > end) return start - end - 1;
+ if (start == 0) return size - end - 1;
+ return size - end;
+ }
+
+ List<int> read(int bytes) {
+ if (bytes == null) {
+ bytes = length;
+ } else {
+ bytes = min(bytes, length);
+ }
+ if (bytes == 0) return null;
+ List<int> result = new Uint8List(bytes);
+ int bytesRead = 0;
+ // Loop over zero, one, or two linear data ranges.
+ while (bytesRead < bytes) {
+ int toRead = min(bytes - bytesRead, linearLength);
+ result.setRange(bytesRead,
+ bytesRead + toRead,
+ data,
+ start);
+ advanceStart(toRead);
+ bytesRead += toRead;
+ }
+ return result;
+ }
+
+ int write(List<int> inputData, int offset, int bytes) {
+ if (bytes > free) {
+ bytes = free;
+ }
+ int written = 0;
+ int toWrite = min(bytes, linearFree);
+ // Loop over zero, one, or two linear data ranges.
+ while (toWrite > 0) {
+ data.setRange(end, end + toWrite, inputData, offset);
+ advanceEnd(toWrite);
+ offset += toWrite;
+ written += toWrite;
+ toWrite = min(bytes - written, linearFree);
+ }
+ return written;
+ }
+
+ int writeFromSource(List<int> getData(int requested)) {
+ int written = 0;
+ int toWrite = linearFree;
+ // Loop over zero, one, or two linear data ranges.
+ while (toWrite > 0) {
+ // Source returns at most toWrite bytes, and it returns null when empty.
+ var inputData = getData(toWrite);
+ if (inputData == null || inputData.length == 0) break;
+ var len = inputData.length;
+ data.setRange(end, end + len, inputData);
+ advanceEnd(len);
+ written += len;
+ toWrite = linearFree;
+ }
+ return written;
+ }
+
+ bool readToSocket(RawSocket socket) {
+ // Loop over zero, one, or two linear data ranges.
+ while (true) {
+ var toWrite = linearLength;
+ if (toWrite == 0) return false;
+ int bytes = socket.write(data, start, toWrite);
+ advanceStart(bytes);
+ if (bytes < toWrite) {
+ // The socket has blocked while we have data to write.
+ return true;
+ }
+ }
+ }
+}
+
+
+abstract class _SecureFilter {
+ external factory _SecureFilter();
+
+ void connect(String hostName,
+ SecurityContext context,
+ bool is_server,
+ bool requestClientCertificate,
+ bool requireClientCertificate,
+ Uint8List protocols);
+ void destroy();
+ void handshake();
+ String selectedProtocol();
+ void rehandshake();
+ void renegotiate(bool useSessionCache,
+ bool requestClientCertificate,
+ bool requireClientCertificate);
+ void init();
+ X509Certificate get peerCertificate;
+ int processBuffer(int bufferIndex);
+ void registerBadCertificateCallback(Function callback);
+ void registerHandshakeCompleteCallback(Function handshakeCompleteHandler);
+
+ // This call may cause a reference counted pointer in the native
+ // implementation to be retained. It should only be called when the resulting
+ // value is passed to the IO service through a call to dispatch().
+ int _pointer();
+
+ List<_ExternalBuffer> get buffers;
+}
+
+/** A secure networking exception caused by a failure in the
+ * TLS/SSL protocol.
+ */
+class TlsException implements IOException {
+ final String type;
+ final String message;
+ final OSError osError;
+
+ const TlsException([String message = "",
+ OSError osError = null])
+ : this._("TlsException", message, osError);
+
+ const TlsException._(this.type, this.message, this.osError);
+
+ String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.write(type);
+ if (!message.isEmpty) {
+ sb.write(": $message");
+ if (osError != null) {
+ sb.write(" ($osError)");
+ }
+ } else if (osError != null) {
+ sb.write(": $osError");
+ }
+ return sb.toString();
+ }
+}
+
+
+/**
+ * An exception that happens in the handshake phase of establishing
+ * a secure network connection.
+ */
+class HandshakeException extends TlsException {
+ const HandshakeException([String message = "",
+ OSError osError = null])
+ : super._("HandshakeException", message, osError);
+}
+
+
+/**
+ * An exception that happens in the handshake phase of establishing
+ * a secure network connection, when looking up or verifying a
+ * certificate.
+ */
+class CertificateException extends TlsException {
+ const CertificateException([String message = "",
+ OSError osError = null])
+ : super._("CertificateException", message, osError);
+}
« no previous file with comments | « tool/input_sdk/lib/io/secure_server_socket.dart ('k') | tool/input_sdk/lib/io/security_context.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698