2013-02-21 11:58:11 +00:00
|
|
|
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
2012-11-14 13:38:32 +00:00
|
|
|
// 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.
|
|
|
|
|
2012-12-18 21:31:34 +00:00
|
|
|
part of dart.io;
|
|
|
|
|
2012-11-14 13:38:32 +00:00
|
|
|
/**
|
2013-02-21 11:58:11 +00:00
|
|
|
* 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.
|
2012-11-14 13:38:32 +00:00
|
|
|
*/
|
2012-11-23 09:21:48 +00:00
|
|
|
abstract class SecureSocket implements Socket {
|
2013-02-21 11:58:11 +00:00
|
|
|
external factory SecureSocket._(RawSecureSocket rawSocket);
|
|
|
|
|
2012-11-14 13:38:32 +00:00
|
|
|
/**
|
2012-11-20 17:45:18 +00:00
|
|
|
* Constructs a new secure client socket and connect it to the given
|
2013-02-21 11:58:11 +00:00
|
|
|
* [host] on port [port]. The returned Future will complete with a
|
|
|
|
* [SecureSocket] that is connected and ready for subscription.
|
|
|
|
*
|
|
|
|
* If [sendClientCertificate] is set to true, the socket will send a client
|
|
|
|
* certificate if one is requested by the server.
|
|
|
|
*
|
|
|
|
* If [certificateName] is the nickname of a certificate in the certificate
|
|
|
|
* database, that certificate will be sent.
|
|
|
|
*
|
|
|
|
* If [certificateName] is null, which is the usual use case, an
|
2012-12-11 09:26:23 +00:00
|
|
|
* appropriate certificate will be searched for in the database and
|
|
|
|
* sent automatically, based on what the server says it will accept.
|
2013-02-21 11:58:11 +00:00
|
|
|
*
|
|
|
|
* [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.
|
2012-11-14 13:38:32 +00:00
|
|
|
*/
|
2013-02-21 11:58:11 +00:00
|
|
|
static Future<SecureSocket> connect(
|
2013-04-24 09:26:58 +00:00
|
|
|
host,
|
2013-02-21 11:58:11 +00:00
|
|
|
int port,
|
|
|
|
{bool sendClientCertificate: false,
|
|
|
|
String certificateName,
|
|
|
|
bool onBadCertificate(X509Certificate certificate)}) {
|
|
|
|
return RawSecureSocket.connect(host,
|
|
|
|
port,
|
|
|
|
sendClientCertificate: sendClientCertificate,
|
|
|
|
certificateName: certificateName,
|
|
|
|
onBadCertificate: onBadCertificate)
|
|
|
|
.then((rawSocket) => new SecureSocket._(rawSocket));
|
2012-12-11 09:26:23 +00:00
|
|
|
}
|
2012-11-14 13:38:32 +00:00
|
|
|
|
2013-04-23 06:50:59 +00:00
|
|
|
/**
|
|
|
|
* 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
|
2013-06-19 09:07:43 +00:00
|
|
|
* `pause` on this subscription before starting TLS handshake is
|
2013-04-23 06:50:59 +00:00
|
|
|
* the right thing to do.
|
|
|
|
*
|
2013-04-29 13:30:12 +00:00
|
|
|
* 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].
|
|
|
|
*
|
2013-06-19 09:07:43 +00:00
|
|
|
* 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.
|
|
|
|
*
|
2013-04-23 06:50:59 +00:00
|
|
|
* See [connect] for more information on the arguments.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static Future<SecureSocket> secure(
|
|
|
|
Socket socket,
|
2013-04-29 13:30:12 +00:00
|
|
|
{host,
|
|
|
|
bool sendClientCertificate: false,
|
2013-04-23 06:50:59 +00:00
|
|
|
String certificateName,
|
|
|
|
bool onBadCertificate(X509Certificate certificate)}) {
|
|
|
|
var completer = new Completer();
|
|
|
|
(socket as dynamic)._detachRaw()
|
|
|
|
.then((detachedRaw) {
|
|
|
|
return RawSecureSocket.secure(
|
|
|
|
detachedRaw[0],
|
|
|
|
subscription: detachedRaw[1],
|
2013-04-29 13:30:12 +00:00
|
|
|
host: host,
|
2013-04-23 06:50:59 +00:00
|
|
|
sendClientCertificate: sendClientCertificate,
|
|
|
|
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
|
2013-06-13 15:59:44 +00:00
|
|
|
* from the socket this data can be passed in the [bufferedData]
|
2013-04-23 06:50:59 +00:00
|
|
|
* 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,
|
|
|
|
String certificateName,
|
2013-06-13 15:59:44 +00:00
|
|
|
{List<int> bufferedData,
|
2013-04-23 06:50:59 +00:00
|
|
|
bool requestClientCertificate: false,
|
|
|
|
bool requireClientCertificate: false}) {
|
|
|
|
var completer = new Completer();
|
|
|
|
(socket as dynamic)._detachRaw()
|
|
|
|
.then((detachedRaw) {
|
|
|
|
return RawSecureSocket.secureServer(
|
|
|
|
detachedRaw[0],
|
|
|
|
certificateName,
|
|
|
|
subscription: detachedRaw[1],
|
2013-06-13 15:59:44 +00:00
|
|
|
bufferedData: bufferedData,
|
2013-04-23 06:50:59 +00:00
|
|
|
requestClientCertificate: requestClientCertificate,
|
|
|
|
requireClientCertificate: requireClientCertificate);
|
|
|
|
})
|
|
|
|
.then((raw) {
|
|
|
|
completer.complete(new SecureSocket._(raw));
|
|
|
|
});
|
|
|
|
return completer.future;
|
|
|
|
}
|
|
|
|
|
2012-12-05 14:31:51 +00:00
|
|
|
/**
|
2013-02-21 11:58:11 +00:00
|
|
|
* 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.
|
2012-12-11 09:26:23 +00:00
|
|
|
*/
|
|
|
|
X509Certificate get peerCertificate;
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
/**
|
2012-12-20 18:17:07 +00:00
|
|
|
* Initializes the NSS library. If [initialize] is not called, the library
|
|
|
|
* is automatically initialized as if [initialize] were called with no
|
|
|
|
* arguments.
|
|
|
|
*
|
|
|
|
* The optional argument [database] is the path to a certificate database
|
2013-05-31 09:08:51 +00:00
|
|
|
* directory containing root certificates for verifying certificate paths on
|
2012-11-14 13:38:32 +00:00
|
|
|
* client connections, and server certificates to provide on server
|
2012-12-20 18:17:07 +00:00
|
|
|
* connections. The argument [password] should be used when creating
|
2012-11-20 17:45:18 +00:00
|
|
|
* secure server sockets, to allow the private key of the server
|
2012-12-20 18:17:07 +00:00
|
|
|
* certificate to be fetched. If [useBuiltinRoots] is true (the default),
|
2012-12-03 13:15:51 +00:00
|
|
|
* then a built-in set of root certificates for trusted certificate
|
|
|
|
* authorities is merged with the certificates in the database.
|
2013-05-31 09:08:51 +00:00
|
|
|
* The list of built-in root certificates, and documentation about this
|
|
|
|
* default database, is available at
|
|
|
|
* http://www.mozilla.org/projects/security/certs/included/ .
|
|
|
|
*
|
|
|
|
* If the [database] argument is omitted, then only the
|
|
|
|
* builtin root certificates are used. If [useBuiltinRoots] is also false,
|
|
|
|
* then no certificates are available.
|
2012-12-03 13:15:51 +00:00
|
|
|
*
|
|
|
|
* Examples:
|
|
|
|
* 1) Use only the builtin root certificates:
|
|
|
|
* SecureSocket.initialize(); or
|
|
|
|
*
|
2013-05-31 09:08:51 +00:00
|
|
|
* 2) Use a specified database directory and the builtin roots:
|
2012-12-03 13:15:51 +00:00
|
|
|
* SecureSocket.initialize(database: 'path/to/my/database',
|
|
|
|
* password: 'my_password');
|
|
|
|
*
|
2013-05-31 09:08:51 +00:00
|
|
|
* 3) Use a specified database directory, without builtin roots:
|
2012-12-03 13:15:51 +00:00
|
|
|
* SecureSocket.initialize(database: 'path/to/my/database',
|
|
|
|
* password: 'my_password'.
|
|
|
|
* useBuiltinRoots: false);
|
2012-11-20 17:45:18 +00:00
|
|
|
*
|
|
|
|
* The database should be an NSS certificate database directory
|
|
|
|
* containing a cert9.db file, not a cert8.db file. This version of
|
|
|
|
* the database can be created using the NSS certutil tool with "sql:" in
|
|
|
|
* front of the absolute path of the database directory, or setting the
|
2012-12-20 18:17:07 +00:00
|
|
|
* environment variable [[NSS_DEFAULT_DB_TYPE]] to "sql".
|
2012-11-14 13:38:32 +00:00
|
|
|
*/
|
2012-12-03 13:15:51 +00:00
|
|
|
external static void initialize({String database,
|
|
|
|
String password,
|
|
|
|
bool useBuiltinRoots: true});
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
/**
|
|
|
|
* 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 certificate database provided in SecureSocket.initialize, and/or
|
|
|
|
* the default built-in root certificates.
|
|
|
|
*/
|
|
|
|
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 certificate
|
|
|
|
* database provided in [SecureSocket.initialize], and/or the default built-in
|
|
|
|
* root certificates. If [sendClientCertificate] is
|
|
|
|
* set to true, the socket will send a client certificate if one is
|
|
|
|
* requested by the server. If [certificateName] is the nickname of
|
|
|
|
* a certificate in the certificate database, that certificate will be sent.
|
|
|
|
* If [certificateName] is null, which is the usual use case, an
|
|
|
|
* appropriate certificate will be searched for in the database and
|
|
|
|
* sent automatically, based on what the server says it will accept.
|
|
|
|
*
|
|
|
|
* [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(
|
2013-04-24 09:26:58 +00:00
|
|
|
host,
|
2013-02-21 11:58:11 +00:00
|
|
|
int port,
|
|
|
|
{bool sendClientCertificate: false,
|
|
|
|
String certificateName,
|
|
|
|
bool onBadCertificate(X509Certificate certificate)}) {
|
|
|
|
return _RawSecureSocket.connect(
|
|
|
|
host,
|
|
|
|
port,
|
|
|
|
certificateName,
|
|
|
|
is_server: false,
|
|
|
|
sendClientCertificate: sendClientCertificate,
|
|
|
|
onBadCertificate: onBadCertificate);
|
|
|
|
}
|
|
|
|
|
2013-04-19 07:37:37 +00:00
|
|
|
/**
|
|
|
|
* 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 socket
|
|
|
|
* will take over the subscription and process any subsequent
|
2013-06-19 09:07:43 +00:00
|
|
|
* 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 this passed in [host] as its
|
|
|
|
* host value and the internet address of the already connected
|
|
|
|
* socket as its address value.
|
2013-04-19 07:37:37 +00:00
|
|
|
*
|
|
|
|
* See [connect] for more information on the arguments.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static Future<RawSecureSocket> secure(
|
|
|
|
RawSocket socket,
|
|
|
|
{StreamSubscription subscription,
|
2013-04-29 13:30:12 +00:00
|
|
|
host,
|
2013-04-19 07:37:37 +00:00
|
|
|
bool sendClientCertificate: false,
|
|
|
|
String certificateName,
|
|
|
|
bool onBadCertificate(X509Certificate certificate)}) {
|
2013-04-30 12:01:37 +00:00
|
|
|
socket.readEventsEnabled = false;
|
|
|
|
socket.writeEventsEnabled = false;
|
2013-04-19 07:37:37 +00:00
|
|
|
return _RawSecureSocket.connect(
|
2013-04-29 13:30:12 +00:00
|
|
|
host != null ? host : socket.address,
|
2013-04-19 07:37:37 +00:00
|
|
|
socket.port,
|
|
|
|
certificateName,
|
|
|
|
is_server: false,
|
|
|
|
socket: socket,
|
|
|
|
subscription: subscription,
|
|
|
|
sendClientCertificate: sendClientCertificate,
|
|
|
|
onBadCertificate: onBadCertificate);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 secure socket
|
|
|
|
* will take over the subscription and process any subsequent
|
|
|
|
* events.
|
|
|
|
*
|
|
|
|
* If some of the data of the TLS handshake has already been read
|
2013-06-13 15:59:44 +00:00
|
|
|
* from the socket this data can be passed in the [bufferedData]
|
2013-04-19 07:37:37 +00:00
|
|
|
* 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,
|
|
|
|
String certificateName,
|
|
|
|
{StreamSubscription subscription,
|
2013-06-13 15:59:44 +00:00
|
|
|
List<int> bufferedData,
|
2013-04-19 07:37:37 +00:00
|
|
|
bool requestClientCertificate: false,
|
|
|
|
bool requireClientCertificate: false}) {
|
2013-04-30 12:01:37 +00:00
|
|
|
socket.readEventsEnabled = false;
|
|
|
|
socket.writeEventsEnabled = false;
|
2013-04-19 07:37:37 +00:00
|
|
|
return _RawSecureSocket.connect(
|
2013-05-01 14:27:57 +00:00
|
|
|
socket.address,
|
2013-04-19 07:37:37 +00:00
|
|
|
socket.remotePort,
|
|
|
|
certificateName,
|
|
|
|
is_server: true,
|
|
|
|
socket: socket,
|
|
|
|
subscription: subscription,
|
2013-06-13 15:59:44 +00:00
|
|
|
bufferedData: bufferedData,
|
2013-04-19 07:37:37 +00:00
|
|
|
requestClientCertificate: requestClientCertificate,
|
|
|
|
requireClientCertificate: requireClientCertificate);
|
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-12-05 14:31:51 +00:00
|
|
|
/**
|
|
|
|
* X509Certificate represents an SSL certificate, with accessors to
|
|
|
|
* get the fields of the certificate.
|
|
|
|
*/
|
|
|
|
class X509Certificate {
|
|
|
|
X509Certificate(this.subject,
|
|
|
|
this.issuer,
|
|
|
|
this.startValidity,
|
|
|
|
this.endValidity);
|
|
|
|
final String subject;
|
|
|
|
final String issuer;
|
2013-01-24 12:16:37 +00:00
|
|
|
final DateTime startValidity;
|
|
|
|
final DateTime endValidity;
|
2012-12-05 14:31:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
class _RawSecureSocket extends Stream<RawSocketEvent>
|
|
|
|
implements RawSecureSocket {
|
2012-11-14 13:38:32 +00:00
|
|
|
// Status states
|
|
|
|
static final int NOT_CONNECTED = 200;
|
|
|
|
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;
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
RawSocket _socket;
|
|
|
|
final Completer<_RawSecureSocket> _handshakeComplete =
|
|
|
|
new Completer<_RawSecureSocket>();
|
|
|
|
StreamController<RawSocketEvent> _controller;
|
|
|
|
Stream<RawSocketEvent> _stream;
|
|
|
|
StreamSubscription<RawSocketEvent> _socketSubscription;
|
2013-06-13 15:59:44 +00:00
|
|
|
List<int> _bufferedData;
|
|
|
|
int _bufferedDataIndex = 0;
|
2013-04-24 09:26:58 +00:00
|
|
|
final InternetAddress address;
|
2013-02-21 11:58:11 +00:00
|
|
|
final bool is_server;
|
|
|
|
final String certificateName;
|
|
|
|
final bool requestClientCertificate;
|
|
|
|
final bool requireClientCertificate;
|
|
|
|
final bool sendClientCertificate;
|
|
|
|
final Function onBadCertificate;
|
|
|
|
|
|
|
|
var _status = NOT_CONNECTED;
|
|
|
|
bool _writeEventsEnabled = true;
|
|
|
|
bool _readEventsEnabled = true;
|
|
|
|
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.
|
|
|
|
bool _filterReadEmpty = true; // There is no buffered data to read.
|
|
|
|
bool _filterWriteEmpty = true; // There is no buffered data to be written.
|
|
|
|
bool _connectPending = false;
|
|
|
|
_SecureFilter _secureFilter = new _SecureFilter();
|
|
|
|
|
|
|
|
static Future<_RawSecureSocket> connect(
|
2013-04-24 09:26:58 +00:00
|
|
|
host,
|
2013-02-21 11:58:11 +00:00
|
|
|
int requestedPort,
|
|
|
|
String certificateName,
|
|
|
|
{bool is_server,
|
|
|
|
RawSocket socket,
|
2013-04-19 07:37:37 +00:00
|
|
|
StreamSubscription subscription,
|
2013-06-13 15:59:44 +00:00
|
|
|
List<int> bufferedData,
|
2013-02-21 11:58:11 +00:00
|
|
|
bool requestClientCertificate: false,
|
|
|
|
bool requireClientCertificate: false,
|
|
|
|
bool sendClientCertificate: false,
|
2013-04-24 09:26:58 +00:00
|
|
|
bool onBadCertificate(X509Certificate certificate)}) {
|
|
|
|
var future;
|
|
|
|
if (host is String) {
|
2013-06-19 09:07:43 +00:00
|
|
|
if (socket != null) {
|
|
|
|
future = new Future.value(
|
|
|
|
(socket.address as dynamic)._cloneWithNewHost(host));
|
|
|
|
} else {
|
|
|
|
future = InternetAddress.lookup(host).then((addrs) => addrs.first);
|
|
|
|
}
|
2013-04-24 09:26:58 +00:00
|
|
|
} else {
|
|
|
|
future = new Future.value(host);
|
|
|
|
}
|
|
|
|
return future.then((addr) {
|
|
|
|
return new _RawSecureSocket(addr,
|
2013-02-21 11:58:11 +00:00
|
|
|
requestedPort,
|
|
|
|
certificateName,
|
|
|
|
is_server,
|
|
|
|
socket,
|
2013-04-19 07:37:37 +00:00
|
|
|
subscription,
|
2013-06-13 15:59:44 +00:00
|
|
|
bufferedData,
|
2013-02-21 11:58:11 +00:00
|
|
|
requestClientCertificate,
|
|
|
|
requireClientCertificate,
|
|
|
|
sendClientCertificate,
|
|
|
|
onBadCertificate)
|
|
|
|
._handshakeComplete.future;
|
2013-04-24 09:26:58 +00:00
|
|
|
});
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_RawSecureSocket(
|
2013-04-24 09:26:58 +00:00
|
|
|
InternetAddress this.address,
|
2013-02-21 11:58:11 +00:00
|
|
|
int requestedPort,
|
|
|
|
String this.certificateName,
|
|
|
|
bool this.is_server,
|
|
|
|
RawSocket socket,
|
2013-04-19 07:37:37 +00:00
|
|
|
StreamSubscription this._socketSubscription,
|
2013-06-13 15:59:44 +00:00
|
|
|
List<int> this._bufferedData,
|
2013-02-21 11:58:11 +00:00
|
|
|
bool this.requestClientCertificate,
|
|
|
|
bool this.requireClientCertificate,
|
|
|
|
bool this.sendClientCertificate,
|
|
|
|
bool this.onBadCertificate(X509Certificate certificate)) {
|
|
|
|
_controller = new StreamController<RawSocketEvent>(
|
2013-05-31 06:07:39 +00:00
|
|
|
sync: true,
|
2013-04-15 16:34:26 +00:00
|
|
|
onListen: _onSubscriptionStateChange,
|
|
|
|
onPause: _onPauseStateChange,
|
|
|
|
onResume: _onPauseStateChange,
|
|
|
|
onCancel: _onSubscriptionStateChange);
|
2013-02-21 11:58:11 +00:00
|
|
|
_stream = _controller.stream;
|
|
|
|
// Throw an ArgumentError if any field is invalid. After this, all
|
|
|
|
// errors will be reported through the future or the stream.
|
2012-12-11 09:26:23 +00:00
|
|
|
_verifyFields();
|
2013-02-21 11:58:11 +00:00
|
|
|
_secureFilter.init();
|
2013-06-13 15:59:44 +00:00
|
|
|
if (_bufferedData != null) _readFromBuffered();
|
2013-02-21 11:58:11 +00:00
|
|
|
_secureFilter.registerHandshakeCompleteCallback(
|
2012-12-11 09:26:23 +00:00
|
|
|
_secureHandshakeCompleteHandler);
|
2013-02-21 11:58:11 +00:00
|
|
|
if (onBadCertificate != null) {
|
|
|
|
_secureFilter.registerBadCertificateCallback(onBadCertificate);
|
|
|
|
}
|
|
|
|
var futureSocket;
|
|
|
|
if (socket == null) {
|
2013-04-24 09:26:58 +00:00
|
|
|
futureSocket = RawSocket.connect(address, requestedPort);
|
2013-02-21 11:58:11 +00:00
|
|
|
} else {
|
2013-04-15 21:24:27 +00:00
|
|
|
futureSocket = new Future.value(socket);
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
|
|
|
futureSocket.then((rawSocket) {
|
|
|
|
_socket = rawSocket;
|
2013-04-19 12:27:58 +00:00
|
|
|
_socket.readEventsEnabled = true;
|
|
|
|
_socket.writeEventsEnabled = false;
|
2013-04-19 07:37:37 +00:00
|
|
|
if (_socketSubscription == null) {
|
|
|
|
// If a current subscription is provided use this otherwise
|
|
|
|
// create a new one.
|
|
|
|
_socketSubscription = _socket.listen(_eventDispatcher,
|
|
|
|
onError: _errorHandler,
|
|
|
|
onDone: _doneHandler);
|
|
|
|
} else {
|
|
|
|
_socketSubscription.onData(_eventDispatcher);
|
|
|
|
_socketSubscription.onError(_errorHandler);
|
|
|
|
_socketSubscription.onDone(_doneHandler);
|
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
_connectPending = true;
|
2013-05-01 14:27:57 +00:00
|
|
|
_secureFilter.connect(address.host,
|
2013-06-19 09:07:43 +00:00
|
|
|
(address as dynamic)._sockaddr_storage,
|
2013-02-21 11:58:11 +00:00
|
|
|
port,
|
|
|
|
is_server,
|
|
|
|
certificateName,
|
|
|
|
requestClientCertificate ||
|
|
|
|
requireClientCertificate,
|
|
|
|
requireClientCertificate,
|
|
|
|
sendClientCertificate);
|
|
|
|
_status = HANDSHAKE;
|
|
|
|
_secureHandshake();
|
|
|
|
})
|
|
|
|
.catchError((error) {
|
|
|
|
_handshakeComplete.completeError(error);
|
2013-02-27 13:23:13 +00:00
|
|
|
_close();
|
2013-02-21 11:58:11 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
StreamSubscription listen(void onData(RawSocketEvent data),
|
2013-04-15 18:58:32 +00:00
|
|
|
{void onError(error),
|
2013-02-21 11:58:11 +00:00
|
|
|
void onDone(),
|
2013-04-15 16:07:36 +00:00
|
|
|
bool cancelOnError}) {
|
2013-02-21 11:58:11 +00:00
|
|
|
if (_writeEventsEnabled) {
|
|
|
|
_writeEventsEnabled = false;
|
|
|
|
_controller.add(RawSocketEvent.WRITE);
|
|
|
|
}
|
|
|
|
return _stream.listen(onData,
|
|
|
|
onError: onError,
|
|
|
|
onDone: onDone,
|
2013-04-15 16:07:36 +00:00
|
|
|
cancelOnError: cancelOnError);
|
2012-12-11 09:26:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void _verifyFields() {
|
|
|
|
assert(is_server is bool);
|
2013-02-21 11:58:11 +00:00
|
|
|
assert(_socket == null || _socket is RawSocket);
|
2013-04-24 09:26:58 +00:00
|
|
|
if (address is! InternetAddress) {
|
2012-12-11 09:26:23 +00:00
|
|
|
throw new ArgumentError(
|
2013-04-24 09:26:58 +00:00
|
|
|
"RawSecureSocket constructor: host is not an InternetAddress");
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
|
|
|
if (certificateName != null && certificateName is! String) {
|
|
|
|
throw new ArgumentError("certificateName is not null or a String");
|
2012-12-11 09:26:23 +00:00
|
|
|
}
|
|
|
|
if (certificateName == null && is_server) {
|
2013-02-21 11:58:11 +00:00
|
|
|
throw new ArgumentError("certificateName is null on a server");
|
2012-12-11 09:26:23 +00:00
|
|
|
}
|
|
|
|
if (requestClientCertificate is! bool) {
|
2013-02-21 11:58:11 +00:00
|
|
|
throw new ArgumentError("requestClientCertificate is not a bool");
|
2012-12-11 09:26:23 +00:00
|
|
|
}
|
|
|
|
if (requireClientCertificate is! bool) {
|
2013-02-21 11:58:11 +00:00
|
|
|
throw new ArgumentError("requireClientCertificate is not a bool");
|
2012-12-11 09:26:23 +00:00
|
|
|
}
|
|
|
|
if (sendClientCertificate is! bool) {
|
2013-02-21 11:58:11 +00:00
|
|
|
throw new ArgumentError("sendClientCertificate is not a bool");
|
2012-12-11 09:26:23 +00:00
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
if (onBadCertificate != null && onBadCertificate is! Function) {
|
|
|
|
throw new ArgumentError("onBadCertificate is not null or a Function");
|
2012-11-22 16:37:10 +00:00
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
2012-11-22 16:37:10 +00:00
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
int get port => _socket.port;
|
2012-11-14 13:38:32 +00:00
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
String get remoteHost => _socket.remoteHost;
|
2012-11-22 16:37:10 +00:00
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
int get remotePort => _socket.remotePort;
|
2012-11-14 13:38:32 +00:00
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
int available() {
|
|
|
|
if (_status != CONNECTED) return 0;
|
|
|
|
_readEncryptedData();
|
|
|
|
return _secureFilter.buffers[READ_PLAINTEXT].length;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
void close() {
|
2013-02-27 13:23:13 +00:00
|
|
|
shutdown(SocketDirection.BOTH);
|
|
|
|
}
|
|
|
|
|
|
|
|
void _close() {
|
2013-02-21 11:58:11 +00:00
|
|
|
_closedWrite = true;
|
|
|
|
_closedRead = true;
|
|
|
|
if (_socket != null) {
|
|
|
|
_socket.close();
|
2012-12-05 14:31:51 +00:00
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
_socketClosedWrite = true;
|
|
|
|
_socketClosedRead = true;
|
|
|
|
if (_secureFilter != null) {
|
|
|
|
_secureFilter.destroy();
|
|
|
|
_secureFilter = null;
|
2012-11-22 16:37:10 +00:00
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
if (_socketSubscription != null) {
|
|
|
|
_socketSubscription.cancel();
|
2012-11-22 16:37:10 +00:00
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
_controller.close();
|
|
|
|
_status = CLOSED;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
void shutdown(SocketDirection direction) {
|
2013-02-27 13:23:13 +00:00
|
|
|
if (direction == SocketDirection.SEND ||
|
|
|
|
direction == SocketDirection.BOTH) {
|
2012-11-22 15:45:54 +00:00
|
|
|
_closedWrite = true;
|
|
|
|
_writeEncryptedData();
|
|
|
|
if (_filterWriteEmpty) {
|
2013-02-21 11:58:11 +00:00
|
|
|
_socket.shutdown(SocketDirection.SEND);
|
2012-11-22 15:45:54 +00:00
|
|
|
_socketClosedWrite = true;
|
2012-12-06 11:06:35 +00:00
|
|
|
if (_closedRead) {
|
2013-02-27 13:23:13 +00:00
|
|
|
_close();
|
2012-12-06 11:06:35 +00:00
|
|
|
}
|
2012-11-22 15:45:54 +00:00
|
|
|
}
|
2013-02-27 13:23:13 +00:00
|
|
|
}
|
|
|
|
if (direction == SocketDirection.RECEIVE ||
|
|
|
|
direction == SocketDirection.BOTH) {
|
2012-11-22 15:45:54 +00:00
|
|
|
_closedRead = true;
|
|
|
|
_socketClosedRead = true;
|
2013-02-21 11:58:11 +00:00
|
|
|
_socket.shutdown(SocketDirection.RECEIVE);
|
|
|
|
if (_socketClosedWrite) {
|
2013-02-27 13:23:13 +00:00
|
|
|
_close();
|
2012-11-22 15:45:54 +00:00
|
|
|
}
|
|
|
|
}
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
bool get writeEventsEnabled => _writeEventsEnabled;
|
|
|
|
|
|
|
|
void set writeEventsEnabled(bool value) {
|
|
|
|
if (value &&
|
2013-04-12 09:30:05 +00:00
|
|
|
_controller.hasListener &&
|
2013-02-21 11:58:11 +00:00
|
|
|
_secureFilter != null &&
|
|
|
|
_secureFilter.buffers[WRITE_PLAINTEXT].free > 0) {
|
2013-02-28 13:24:12 +00:00
|
|
|
Timer.run(() => _controller.add(RawSocketEvent.WRITE));
|
2013-02-21 11:58:11 +00:00
|
|
|
} else {
|
|
|
|
_writeEventsEnabled = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool get readEventsEnabled => _readEventsEnabled;
|
|
|
|
|
|
|
|
void set readEventsEnabled(bool value) {
|
|
|
|
_readEventsEnabled = value;
|
2013-02-28 17:16:03 +00:00
|
|
|
if (value &&
|
|
|
|
((_secureFilter != null &&
|
|
|
|
_secureFilter.buffers[READ_PLAINTEXT].length > 0) ||
|
|
|
|
_socketClosedRead)) {
|
|
|
|
// We might not have no underlying socket to set off read events.
|
|
|
|
Timer.run(_readHandler);
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
|
|
|
}
|
2012-11-22 16:37:10 +00:00
|
|
|
|
2012-11-14 13:38:32 +00:00
|
|
|
List<int> read([int len]) {
|
2012-11-22 15:45:54 +00:00
|
|
|
if (_closedRead) {
|
2013-06-11 13:15:46 +00:00
|
|
|
throw new SocketException("Reading from a closed socket");
|
2012-11-22 15:45:54 +00:00
|
|
|
}
|
2012-11-22 16:37:10 +00:00
|
|
|
if (_status != CONNECTED) {
|
2013-02-22 11:24:35 +00:00
|
|
|
return null;
|
2012-11-22 16:37:10 +00:00
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
var buffer = _secureFilter.buffers[READ_PLAINTEXT];
|
2012-11-14 13:38:32 +00:00
|
|
|
_readEncryptedData();
|
|
|
|
int toRead = buffer.length;
|
|
|
|
if (len != null) {
|
|
|
|
if (len is! int || len < 0) {
|
|
|
|
throw new ArgumentError(
|
2012-11-23 09:21:48 +00:00
|
|
|
"Invalid len parameter in SecureSocket.read (len: $len)");
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
if (len < toRead) {
|
|
|
|
toRead = len;
|
|
|
|
}
|
|
|
|
}
|
2012-12-14 13:32:54 +00:00
|
|
|
List<int> result = (toRead == 0) ? null :
|
2013-03-15 09:15:39 +00:00
|
|
|
buffer.data.sublist(buffer.start, buffer.start + toRead);
|
2012-11-14 13:38:32 +00:00
|
|
|
buffer.advanceStart(toRead);
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
// Set up a read event if the filter still has data.
|
|
|
|
if (!_filterReadEmpty) {
|
2013-02-28 13:24:12 +00:00
|
|
|
Timer.run(_readHandler);
|
2012-11-22 16:37:10 +00:00
|
|
|
}
|
2012-11-14 13:38:32 +00:00
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
if (_socketClosedRead) { // An onClose event is pending.
|
|
|
|
// _closedRead is false, since we are in a read call.
|
|
|
|
if (!_filterReadEmpty) {
|
|
|
|
// _filterReadEmpty may be out of date since read empties
|
|
|
|
// the plaintext buffer after calling _readEncryptedData.
|
|
|
|
// TODO(whesse): Fix this as part of fixing read.
|
|
|
|
_readEncryptedData();
|
|
|
|
}
|
|
|
|
if (_filterReadEmpty) {
|
|
|
|
// This can't be an else clause: the value of _filterReadEmpty changes.
|
|
|
|
// This must be asynchronous, because we are in a read call.
|
2013-02-28 13:24:12 +00:00
|
|
|
Timer.run(_closeHandler);
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
return result;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Write the data to the socket, and flush it as much as possible
|
|
|
|
// until it would block. If the write would block, _writeEncryptedData sets
|
|
|
|
// up handlers to flush the pipeline when possible.
|
2013-02-21 11:58:11 +00:00
|
|
|
int write(List<int> data, [int offset, int bytes]) {
|
2012-11-22 15:45:54 +00:00
|
|
|
if (_closedWrite) {
|
2013-06-11 13:15:46 +00:00
|
|
|
_controller.addError(new SocketException("Writing to a closed socket"));
|
2013-02-21 11:58:11 +00:00
|
|
|
return 0;
|
2012-11-22 15:45:54 +00:00
|
|
|
}
|
2012-11-22 16:37:10 +00:00
|
|
|
if (_status != CONNECTED) return 0;
|
2013-04-15 13:52:29 +00:00
|
|
|
|
|
|
|
if (offset == null) offset = 0;
|
|
|
|
if (bytes == null) bytes = data.length - offset;
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
var buffer = _secureFilter.buffers[WRITE_PLAINTEXT];
|
2012-11-14 13:38:32 +00:00
|
|
|
if (bytes > buffer.free) {
|
|
|
|
bytes = buffer.free;
|
|
|
|
}
|
|
|
|
if (bytes > 0) {
|
2013-04-15 13:52:29 +00:00
|
|
|
int startIndex = buffer.start + buffer.length;
|
|
|
|
buffer.data.setRange(startIndex, startIndex + bytes, data, offset);
|
2012-11-14 13:38:32 +00:00
|
|
|
buffer.length += bytes;
|
|
|
|
}
|
|
|
|
_writeEncryptedData(); // Tries to flush all pipeline stages.
|
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
X509Certificate get peerCertificate => _secureFilter.peerCertificate;
|
2012-11-14 13:38:32 +00:00
|
|
|
|
2013-03-14 12:46:54 +00:00
|
|
|
bool setOption(SocketOption option, bool enabled) {
|
|
|
|
if (_socket == null) return false;
|
|
|
|
return _socket.setOption(option, enabled);
|
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
void _writeHandler() {
|
|
|
|
if (_status == CLOSED) return;
|
2012-11-22 15:45:54 +00:00
|
|
|
_writeEncryptedData();
|
|
|
|
if (_filterWriteEmpty && _closedWrite && !_socketClosedWrite) {
|
2013-02-21 11:58:11 +00:00
|
|
|
// Close _socket for write, by calling shutdown(), to avoid cloning the
|
|
|
|
// socket closing code in shutdown().
|
|
|
|
shutdown(SocketDirection.SEND);
|
2012-11-22 15:45:54 +00:00
|
|
|
}
|
2012-11-14 13:38:32 +00:00
|
|
|
if (_status == HANDSHAKE) {
|
2013-02-21 11:58:11 +00:00
|
|
|
try {
|
|
|
|
_secureHandshake();
|
|
|
|
} catch (e) { _reportError(e, "RawSecureSocket error"); }
|
2012-11-22 15:45:54 +00:00
|
|
|
} else if (_status == CONNECTED &&
|
2013-04-12 09:30:05 +00:00
|
|
|
_controller.hasListener &&
|
2013-02-21 11:58:11 +00:00
|
|
|
_writeEventsEnabled &&
|
|
|
|
_secureFilter.buffers[WRITE_PLAINTEXT].free > 0) {
|
2012-11-22 15:45:54 +00:00
|
|
|
// Reset the one-shot handler.
|
2013-02-21 11:58:11 +00:00
|
|
|
_writeEventsEnabled = false;
|
|
|
|
_controller.add(RawSocketEvent.WRITE);
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
void _eventDispatcher(RawSocketEvent event) {
|
|
|
|
if (event == RawSocketEvent.READ) {
|
|
|
|
_readHandler();
|
|
|
|
} else if (event == RawSocketEvent.WRITE) {
|
|
|
|
_writeHandler();
|
|
|
|
} else if (event == RawSocketEvent.READ_CLOSED) {
|
|
|
|
_closeHandler();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-13 15:59:44 +00:00
|
|
|
void _readFromBuffered() {
|
|
|
|
assert(_bufferedData != null);
|
2013-04-19 07:37:37 +00:00
|
|
|
var encrypted = _secureFilter.buffers[READ_ENCRYPTED];
|
2013-06-13 15:59:44 +00:00
|
|
|
var bytes = _bufferedData.length - _bufferedDataIndex;
|
2013-04-19 07:37:37 +00:00
|
|
|
int startIndex = encrypted.start + encrypted.length;
|
|
|
|
encrypted.data.setRange(startIndex,
|
|
|
|
startIndex + bytes,
|
2013-06-13 15:59:44 +00:00
|
|
|
_bufferedData,
|
|
|
|
_bufferedDataIndex);
|
2013-04-19 07:37:37 +00:00
|
|
|
encrypted.length += bytes;
|
2013-06-13 15:59:44 +00:00
|
|
|
_bufferedDataIndex += bytes;
|
|
|
|
if (_bufferedData.length == _bufferedDataIndex) {
|
|
|
|
_bufferedData = null;
|
2013-04-19 07:37:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
void _readHandler() {
|
|
|
|
if (_status == CLOSED) {
|
|
|
|
return;
|
|
|
|
} else if (_status == HANDSHAKE) {
|
2012-12-03 13:15:51 +00:00
|
|
|
try {
|
|
|
|
_secureHandshake();
|
2013-02-21 11:58:11 +00:00
|
|
|
if (_status != HANDSHAKE) _readHandler();
|
|
|
|
} catch (e) { _reportError(e, "RawSecureSocket error"); }
|
2012-11-14 13:38:32 +00:00
|
|
|
} else {
|
2013-02-21 11:58:11 +00:00
|
|
|
if (_status != CONNECTED) {
|
|
|
|
// Cannot happen.
|
2013-06-11 13:15:46 +00:00
|
|
|
throw new SocketException("Internal SocketIO Error");
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
2012-12-03 13:15:51 +00:00
|
|
|
try {
|
|
|
|
_readEncryptedData();
|
2013-02-21 11:58:11 +00:00
|
|
|
} catch (e) { _reportError(e, "RawSecureSocket error"); }
|
2012-11-22 15:45:54 +00:00
|
|
|
if (!_filterReadEmpty) {
|
2013-02-21 11:58:11 +00:00
|
|
|
if (_readEventsEnabled) {
|
2013-02-22 11:55:57 +00:00
|
|
|
if (_secureFilter.buffers[READ_PLAINTEXT].length > 0) {
|
|
|
|
_controller.add(RawSocketEvent.READ);
|
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
if (_socketClosedRead) {
|
|
|
|
// Keep firing read events until we are paused or buffer is empty.
|
2013-02-28 13:24:12 +00:00
|
|
|
Timer.run(_readHandler);
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
2012-12-06 11:06:35 +00:00
|
|
|
} else if (_socketClosedRead) {
|
2013-02-21 11:58:11 +00:00
|
|
|
_closeHandler();
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
void _doneHandler() {
|
|
|
|
if (_filterReadEmpty) {
|
2013-02-27 13:23:13 +00:00
|
|
|
_close();
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _errorHandler(e) {
|
|
|
|
_reportError(e, 'Error on underlying RawSocket');
|
2012-11-27 10:09:35 +00:00
|
|
|
}
|
|
|
|
|
2013-04-15 18:58:32 +00:00
|
|
|
void _reportError(e, String message) {
|
2012-11-27 10:09:35 +00:00
|
|
|
// TODO(whesse): Call _reportError from all internal functions that throw.
|
2013-06-11 13:15:46 +00:00
|
|
|
if (e is SocketException) {
|
|
|
|
e = new SocketException('$message (${e.message})', e.osError);
|
2013-04-15 19:45:46 +00:00
|
|
|
} else if (e is OSError) {
|
2013-06-11 13:15:46 +00:00
|
|
|
e = new SocketException(message, e);
|
2012-11-27 10:09:35 +00:00
|
|
|
} else {
|
2013-06-11 13:15:46 +00:00
|
|
|
e = new SocketException('$message (${e.toString()})', null);
|
2012-11-27 10:09:35 +00:00
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
if (_connectPending) {
|
|
|
|
_handshakeComplete.completeError(e);
|
|
|
|
} else {
|
2013-03-08 11:31:19 +00:00
|
|
|
_controller.addError(e);
|
2012-11-27 10:09:35 +00:00
|
|
|
}
|
2013-02-27 13:23:13 +00:00
|
|
|
_close();
|
2012-11-27 10:09:35 +00:00
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
void _closeHandler() {
|
|
|
|
if (_status == CONNECTED) {
|
|
|
|
if (_closedRead) return;
|
|
|
|
_socketClosedRead = true;
|
|
|
|
if (_filterReadEmpty) {
|
|
|
|
_closedRead = true;
|
|
|
|
_controller.add(RawSocketEvent.READ_CLOSED);
|
|
|
|
if (_socketClosedWrite) {
|
2013-02-27 13:23:13 +00:00
|
|
|
_close();
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
2012-11-22 15:45:54 +00:00
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
} else if (_status == HANDSHAKE) {
|
|
|
|
_reportError(
|
2013-06-11 13:15:46 +00:00
|
|
|
new SocketException('Connection terminated during handshake'),
|
2013-02-21 11:58:11 +00:00
|
|
|
'handshake error');
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-23 09:21:48 +00:00
|
|
|
void _secureHandshake() {
|
2012-11-22 15:45:54 +00:00
|
|
|
_readEncryptedData();
|
2013-02-21 11:58:11 +00:00
|
|
|
_secureFilter.handshake();
|
2012-11-22 15:45:54 +00:00
|
|
|
_writeEncryptedData();
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
2012-11-23 09:21:48 +00:00
|
|
|
void _secureHandshakeCompleteHandler() {
|
2012-11-14 13:38:32 +00:00
|
|
|
_status = CONNECTED;
|
2013-02-21 11:58:11 +00:00
|
|
|
if (_connectPending) {
|
2012-11-14 13:38:32 +00:00
|
|
|
_connectPending = false;
|
2013-02-21 11:58:11 +00:00
|
|
|
// If we complete the future synchronously, user code will run here,
|
|
|
|
// and modify the state of the RawSecureSocket. For example, it
|
|
|
|
// could close the socket, and set _filter to null.
|
2013-02-28 13:24:12 +00:00
|
|
|
Timer.run(() => _handshakeComplete.complete(this));
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void _onPauseStateChange() {
|
|
|
|
if (!_socketClosedRead || !_socketClosedWrite) {
|
|
|
|
if (_controller.isPaused) {
|
|
|
|
_socketSubscription.pause();
|
|
|
|
} else {
|
|
|
|
_socketSubscription.resume();
|
|
|
|
}
|
2012-11-22 16:37:10 +00:00
|
|
|
}
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
void _onSubscriptionStateChange() {
|
2013-04-12 09:30:05 +00:00
|
|
|
if (_controller.hasListener) {
|
2013-02-21 11:58:11 +00:00
|
|
|
// TODO(ajohnsen): Do something here?
|
|
|
|
}
|
|
|
|
}
|
2012-11-14 13:38:32 +00:00
|
|
|
|
|
|
|
void _readEncryptedData() {
|
|
|
|
// Read from the socket, and push it through the filter as far as
|
|
|
|
// possible.
|
2013-02-21 11:58:11 +00:00
|
|
|
var encrypted = _secureFilter.buffers[READ_ENCRYPTED];
|
|
|
|
var plaintext = _secureFilter.buffers[READ_PLAINTEXT];
|
2012-11-14 13:38:32 +00:00
|
|
|
bool progress = true;
|
|
|
|
while (progress) {
|
|
|
|
progress = false;
|
|
|
|
// Do not try to read plaintext from the filter while handshaking.
|
2012-11-22 15:45:54 +00:00
|
|
|
if ((_status == CONNECTED) && plaintext.free > 0) {
|
2013-02-21 11:58:11 +00:00
|
|
|
int bytes = _secureFilter.processBuffer(READ_PLAINTEXT);
|
2012-11-14 13:38:32 +00:00
|
|
|
if (bytes > 0) {
|
|
|
|
plaintext.length += bytes;
|
|
|
|
progress = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (encrypted.length > 0) {
|
2013-02-21 11:58:11 +00:00
|
|
|
int bytes = _secureFilter.processBuffer(READ_ENCRYPTED);
|
2012-11-14 13:38:32 +00:00
|
|
|
if (bytes > 0) {
|
|
|
|
encrypted.advanceStart(bytes);
|
|
|
|
progress = true;
|
|
|
|
}
|
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
if (!_socketClosedRead && encrypted.free > 0) {
|
2013-06-13 15:59:44 +00:00
|
|
|
if (_bufferedData != null) {
|
2013-06-13 16:25:55 +00:00
|
|
|
_readFromBuffered();
|
2012-11-14 13:38:32 +00:00
|
|
|
progress = true;
|
2013-04-19 07:37:37 +00:00
|
|
|
} else {
|
|
|
|
List<int> data = _socket.read(encrypted.free);
|
|
|
|
if (data != null) {
|
|
|
|
int bytes = data.length;
|
|
|
|
int startIndex = encrypted.start + encrypted.length;
|
|
|
|
encrypted.data.setRange(startIndex, startIndex + bytes, data);
|
|
|
|
encrypted.length += bytes;
|
|
|
|
progress = true;
|
|
|
|
}
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-11-22 15:45:54 +00:00
|
|
|
// If there is any data in any stages of the filter, there should
|
|
|
|
// be data in the plaintext buffer after this process.
|
|
|
|
// TODO(whesse): Verify that this is true, and there can be no
|
2012-11-23 09:21:48 +00:00
|
|
|
// partial encrypted block stuck in the secureFilter.
|
2012-11-22 15:45:54 +00:00
|
|
|
_filterReadEmpty = (plaintext.length == 0);
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void _writeEncryptedData() {
|
2012-11-22 15:45:54 +00:00
|
|
|
if (_socketClosedWrite) return;
|
2013-02-21 11:58:11 +00:00
|
|
|
var encrypted = _secureFilter.buffers[WRITE_ENCRYPTED];
|
|
|
|
var plaintext = _secureFilter.buffers[WRITE_PLAINTEXT];
|
2012-11-14 13:38:32 +00:00
|
|
|
while (true) {
|
2012-11-22 15:45:54 +00:00
|
|
|
if (encrypted.length > 0) {
|
|
|
|
// Write from the filter to the socket.
|
2013-02-21 11:58:11 +00:00
|
|
|
int bytes = _socket.write(encrypted.data,
|
|
|
|
encrypted.start,
|
|
|
|
encrypted.length);
|
|
|
|
encrypted.advanceStart(bytes);
|
2013-02-22 09:06:20 +00:00
|
|
|
if (encrypted.length > 0) {
|
2012-11-14 13:38:32 +00:00
|
|
|
// The socket has blocked while we have data to write.
|
|
|
|
// We must be notified when it becomes unblocked.
|
2013-02-21 11:58:11 +00:00
|
|
|
_socket.writeEventsEnabled = true;
|
2012-11-22 15:45:54 +00:00
|
|
|
_filterWriteEmpty = false;
|
2012-11-14 13:38:32 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
2013-02-21 11:58:11 +00:00
|
|
|
var plaintext = _secureFilter.buffers[WRITE_PLAINTEXT];
|
2012-11-14 13:38:32 +00:00
|
|
|
if (plaintext.length > 0) {
|
2013-02-21 11:58:11 +00:00
|
|
|
int plaintext_bytes = _secureFilter.processBuffer(WRITE_PLAINTEXT);
|
2012-11-14 13:38:32 +00:00
|
|
|
plaintext.advanceStart(plaintext_bytes);
|
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
int bytes = _secureFilter.processBuffer(WRITE_ENCRYPTED);
|
2012-11-22 15:45:54 +00:00
|
|
|
if (bytes <= 0) {
|
|
|
|
// We know the WRITE_ENCRYPTED buffer is empty, and the
|
|
|
|
// filter wrote zero bytes to it, so the filter must be empty.
|
|
|
|
// Also, the WRITE_PLAINTEXT buffer must have been empty, or
|
|
|
|
// it would have written to the filter.
|
|
|
|
// TODO(whesse): Verify that the filter works this way.
|
|
|
|
_filterWriteEmpty = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
encrypted.length += bytes;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-11-23 09:21:48 +00:00
|
|
|
class _ExternalBuffer {
|
2013-01-10 14:24:57 +00:00
|
|
|
// Performance is improved if a full buffer of plaintext fits
|
|
|
|
// in the encrypted buffer, when encrypted.
|
2012-11-14 13:38:32 +00:00
|
|
|
static final int SIZE = 8 * 1024;
|
2013-01-10 14:24:57 +00:00
|
|
|
static final int ENCRYPTED_SIZE = 10 * 1024;
|
2012-11-23 09:21:48 +00:00
|
|
|
_ExternalBuffer() : start = 0, length = 0;
|
2012-11-14 13:38:32 +00:00
|
|
|
|
|
|
|
// TODO(whesse): Consider making this a circular buffer. Only if it helps.
|
|
|
|
void advanceStart(int numBytes) {
|
|
|
|
start += numBytes;
|
|
|
|
length -= numBytes;
|
|
|
|
if (length == 0) {
|
|
|
|
start = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-10 14:24:57 +00:00
|
|
|
int get free => data.length - (start + length);
|
2012-11-14 13:38:32 +00:00
|
|
|
|
|
|
|
List data; // This will be a ExternalByteArray, backed by C allocated data.
|
|
|
|
int start;
|
|
|
|
int length;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-11-23 09:21:48 +00:00
|
|
|
abstract class _SecureFilter {
|
|
|
|
external factory _SecureFilter();
|
2012-11-14 13:38:32 +00:00
|
|
|
|
2012-11-20 17:45:18 +00:00
|
|
|
void connect(String hostName,
|
2013-06-19 09:07:43 +00:00
|
|
|
Uint8List addr,
|
2012-11-20 17:45:18 +00:00
|
|
|
int port,
|
|
|
|
bool is_server,
|
2012-12-11 09:26:23 +00:00
|
|
|
String certificateName,
|
|
|
|
bool requestClientCertificate,
|
|
|
|
bool requireClientCertificate,
|
|
|
|
bool sendClientCertificate);
|
2012-11-14 13:38:32 +00:00
|
|
|
void destroy();
|
|
|
|
void handshake();
|
|
|
|
void init();
|
2012-12-11 09:26:23 +00:00
|
|
|
X509Certificate get peerCertificate;
|
2012-11-14 13:38:32 +00:00
|
|
|
int processBuffer(int bufferIndex);
|
2012-12-05 14:31:51 +00:00
|
|
|
void registerBadCertificateCallback(Function callback);
|
2012-11-14 13:38:32 +00:00
|
|
|
void registerHandshakeCompleteCallback(Function handshakeCompleteHandler);
|
2012-11-20 17:45:18 +00:00
|
|
|
|
2012-11-23 09:21:48 +00:00
|
|
|
List<_ExternalBuffer> get buffers;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|