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-07-11 14:02:49 +00:00
|
|
|
/**
|
|
|
|
* 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});
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
/**
|
2013-06-25 13:54:42 +00:00
|
|
|
* Initializes the NSS library. If [initialize] is not called, the library
|
2012-12-20 18:17:07 +00:00
|
|
|
* is automatically initialized as if [initialize] were called with no
|
2013-06-25 13:54:42 +00:00
|
|
|
* arguments. If [initialize] is called more than once, or called after
|
|
|
|
* automatic initialization has happened (when a secure connection is made),
|
|
|
|
* then a TlsException is thrown.
|
2012-12-20 18:17:07 +00:00
|
|
|
*
|
|
|
|
* 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
|
2013-06-25 13:54:42 +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
|
2013-06-25 13:54:42 +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
|
2013-06-25 13:54:42 +00:00
|
|
|
* builtin root certificates are used. If [useBuiltinRoots] is also false,
|
2013-05-31 09:08:51 +00:00
|
|
|
* 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,
|
2013-08-15 14:55:53 +00:00
|
|
|
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)}) {
|
2014-05-02 06:52:11 +00:00
|
|
|
_RawSecureSocket._verifyFields(
|
2013-02-21 11:58:11 +00:00
|
|
|
host,
|
|
|
|
port,
|
|
|
|
certificateName,
|
2014-05-02 06:52:11 +00:00
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
sendClientCertificate,
|
|
|
|
onBadCertificate);
|
|
|
|
return RawSocket.connect(host, port)
|
|
|
|
.then((socket) {
|
|
|
|
return secure(socket,
|
|
|
|
sendClientCertificate: sendClientCertificate,
|
|
|
|
certificateName: certificateName,
|
|
|
|
onBadCertificate: onBadCertificate);
|
|
|
|
});
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
|
|
|
|
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
|
2014-02-10 13:43:28 +00:00
|
|
|
* 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.
|
2013-06-19 09:07:43 +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].
|
|
|
|
*
|
|
|
|
* 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(
|
2014-05-02 09:25:09 +00:00
|
|
|
host != null ? host : socket.address.host,
|
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
|
2014-02-10 13:43:28 +00:00
|
|
|
* 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.
|
2013-04-19 07:37:37 +00:00
|
|
|
*
|
|
|
|
* 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-07-11 14:02:49 +00:00
|
|
|
/**
|
|
|
|
* 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});
|
|
|
|
|
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-06-20 15:06:43 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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 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-06-20 15:06:43 +00:00
|
|
|
// Is a buffer identifier for an encrypted buffer?
|
2013-08-15 14:55:53 +00:00
|
|
|
static bool _isBufferEncrypted(int identifier) => identifier >= READ_ENCRYPTED;
|
2013-06-20 15:06:43 +00:00
|
|
|
|
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;
|
|
|
|
|
2013-06-20 15:06:43 +00:00
|
|
|
var _status = HANDSHAKE;
|
2013-02-21 11:58:11 +00:00
|
|
|
bool _writeEventsEnabled = true;
|
|
|
|
bool _readEventsEnabled = true;
|
2013-06-20 15:06:43 +00:00
|
|
|
int _pauseCount = 0;
|
|
|
|
bool _pendingReadEvent = false;
|
2013-02-21 11:58:11 +00:00
|
|
|
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.
|
2013-07-26 13:45:50 +00:00
|
|
|
Completer _closeCompleter = new Completer(); // The network socket is gone.
|
2013-06-20 15:06:43 +00:00
|
|
|
_FilterStatus _filterStatus = new _FilterStatus();
|
2013-08-20 12:53:52 +00:00
|
|
|
bool _connectPending = true;
|
2013-06-20 15:06:43 +00:00
|
|
|
bool _filterPending = false;
|
|
|
|
bool _filterActive = false;
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
_SecureFilter _secureFilter = new _SecureFilter();
|
2013-06-20 15:06:43 +00:00
|
|
|
int _filterPointer;
|
2013-02-21 11:58:11 +00:00
|
|
|
|
|
|
|
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)}) {
|
2013-06-25 13:54:42 +00:00
|
|
|
_verifyFields(host, requestedPort, certificateName, is_server,
|
|
|
|
requestClientCertificate, requireClientCertificate,
|
|
|
|
sendClientCertificate, onBadCertificate);
|
2014-05-02 09:25:09 +00:00
|
|
|
if (host is InternetAddress) host = host.host;
|
|
|
|
var address = socket.address;
|
|
|
|
if (host != null) address = address._cloneWithNewHost(host);
|
|
|
|
return new _RawSecureSocket(address,
|
|
|
|
requestedPort,
|
|
|
|
certificateName,
|
|
|
|
is_server,
|
|
|
|
socket,
|
|
|
|
subscription,
|
|
|
|
bufferedData,
|
|
|
|
requestClientCertificate,
|
|
|
|
requireClientCertificate,
|
|
|
|
sendClientCertificate,
|
|
|
|
onBadCertificate)
|
|
|
|
._handshakeComplete.future;
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_RawSecureSocket(
|
2014-01-08 12:03:11 +00:00
|
|
|
this.address,
|
2013-02-21 11:58:11 +00:00
|
|
|
int requestedPort,
|
2014-01-08 12:03:11 +00:00
|
|
|
this.certificateName,
|
|
|
|
this.is_server,
|
2014-05-02 09:25:09 +00:00
|
|
|
RawSocket this._socket,
|
2014-01-08 12:03:11 +00:00
|
|
|
this._socketSubscription,
|
|
|
|
this._bufferedData,
|
|
|
|
this.requestClientCertificate,
|
|
|
|
this.requireClientCertificate,
|
|
|
|
this.sendClientCertificate,
|
|
|
|
this.onBadCertificate(X509Certificate certificate)) {
|
2013-02-21 11:58:11 +00:00
|
|
|
_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.
|
|
|
|
_secureFilter.init();
|
2013-06-20 15:06:43 +00:00
|
|
|
_filterPointer = _secureFilter._pointer();
|
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) {
|
2013-07-26 13:39:18 +00:00
|
|
|
_secureFilter.registerBadCertificateCallback(_onBadCertificateWrapper);
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
2014-05-02 09:25:09 +00:00
|
|
|
_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);
|
2013-02-21 11:58:11 +00:00
|
|
|
} else {
|
2014-05-02 09:25:09 +00:00
|
|
|
if (_socketSubscription.isPaused) {
|
|
|
|
_socket.close();
|
|
|
|
throw new ArgumentError(
|
|
|
|
"Subscription passed to TLS upgrade is paused");
|
2013-04-19 07:37:37 +00:00
|
|
|
}
|
2014-05-02 09:25:09 +00:00
|
|
|
_socketSubscription
|
|
|
|
..onData(_eventDispatcher)
|
|
|
|
..onError(_reportError)
|
|
|
|
..onDone(_doneHandler);
|
|
|
|
}
|
|
|
|
try {
|
2013-05-01 14:27:57 +00:00
|
|
|
_secureFilter.connect(address.host,
|
2013-12-17 15:35:11 +00:00
|
|
|
(address as dynamic)._in_addr,
|
2013-02-21 11:58:11 +00:00
|
|
|
port,
|
|
|
|
is_server,
|
|
|
|
certificateName,
|
|
|
|
requestClientCertificate ||
|
|
|
|
requireClientCertificate,
|
|
|
|
requireClientCertificate,
|
|
|
|
sendClientCertificate);
|
|
|
|
_secureHandshake();
|
2014-05-02 09:25:09 +00:00
|
|
|
} catch (e, s) {
|
|
|
|
_reportError(e, s);
|
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
StreamSubscription listen(void onData(RawSocketEvent data),
|
2013-10-11 12:02:20 +00:00
|
|
|
{Function onError,
|
2013-02-21 11:58:11 +00:00
|
|
|
void onDone(),
|
2013-04-15 16:07:36 +00:00
|
|
|
bool cancelOnError}) {
|
2013-06-20 15:06:43 +00:00
|
|
|
_sendWriteEvent();
|
2013-02-21 11:58:11 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2013-06-25 13:54:42 +00:00
|
|
|
static void _verifyFields(host,
|
|
|
|
int requestedPort,
|
|
|
|
String certificateName,
|
|
|
|
bool is_server,
|
|
|
|
bool requestClientCertificate,
|
|
|
|
bool requireClientCertificate,
|
|
|
|
bool sendClientCertificate,
|
|
|
|
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");
|
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-11-05 13:24:49 +00:00
|
|
|
InternetAddress get remoteAddress => _socket.remoteAddress;
|
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() {
|
2014-01-08 12:03:11 +00:00
|
|
|
return _status != CONNECTED ? 0
|
|
|
|
: _secureFilter.buffers[READ_PLAINTEXT].length;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
2013-07-26 13:45:50 +00:00
|
|
|
Future<RawSecureSocket> close() {
|
2013-02-27 13:23:13 +00:00
|
|
|
shutdown(SocketDirection.BOTH);
|
2013-07-26 13:45:50 +00:00
|
|
|
return _closeCompleter.future;
|
|
|
|
}
|
|
|
|
|
|
|
|
void _completeCloseCompleter([dummy]) {
|
|
|
|
if (!_closeCompleter.isCompleted) _closeCompleter.complete(this);
|
2013-02-27 13:23:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void _close() {
|
2013-02-21 11:58:11 +00:00
|
|
|
_closedWrite = true;
|
|
|
|
_closedRead = true;
|
|
|
|
if (_socket != null) {
|
2013-07-26 13:45:50 +00:00
|
|
|
_socket.close().then(_completeCloseCompleter);
|
|
|
|
} else {
|
|
|
|
_completeCloseCompleter();
|
2012-12-05 14:31:51 +00:00
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
_socketClosedWrite = true;
|
|
|
|
_socketClosedRead = true;
|
2013-06-20 15:06:43 +00:00
|
|
|
if (!_filterActive && _secureFilter != null) {
|
2013-02-21 11:58:11 +00:00
|
|
|
_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;
|
2013-06-20 15:06:43 +00:00
|
|
|
if (_filterStatus.writeEmpty) {
|
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) {
|
2013-06-20 15:06:43 +00:00
|
|
|
_writeEventsEnabled = value;
|
|
|
|
if (value) {
|
|
|
|
Timer.run(() => _sendWriteEvent());
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool get readEventsEnabled => _readEventsEnabled;
|
|
|
|
|
|
|
|
void set readEventsEnabled(bool value) {
|
2013-06-20 15:06:43 +00:00
|
|
|
_readEventsEnabled = value;
|
|
|
|
_scheduleReadEvent();
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
2012-11-22 16:37:10 +00:00
|
|
|
|
2013-06-20 15:06:43 +00:00
|
|
|
List<int> read([int length]) {
|
|
|
|
if (length != null && (length is! int || length < 0)) {
|
|
|
|
throw new ArgumentError(
|
|
|
|
"Invalid length parameter in SecureSocket.read (length: $length)");
|
|
|
|
}
|
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-06-20 15:06:43 +00:00
|
|
|
var result = _secureFilter.buffers[READ_PLAINTEXT].read(length);
|
|
|
|
_scheduleFilter();
|
2013-02-21 11:58:11 +00:00
|
|
|
return result;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
2013-06-20 15:06:43 +00:00
|
|
|
// Write the data to the socket, and schedule the filter to encrypt it.
|
2013-02-21 11:58:11 +00:00
|
|
|
int write(List<int> data, [int offset, int bytes]) {
|
2013-06-20 15:06:43 +00:00
|
|
|
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)");
|
|
|
|
}
|
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-06-20 15:06:43 +00:00
|
|
|
int written =
|
|
|
|
_secureFilter.buffers[WRITE_PLAINTEXT].write(data, offset, bytes);
|
|
|
|
if (written > 0) {
|
|
|
|
_filterStatus.writeEmpty = false;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
2013-06-20 15:06:43 +00:00
|
|
|
_scheduleFilter();
|
|
|
|
return written;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
X509Certificate get peerCertificate => _secureFilter.peerCertificate;
|
2012-11-14 13:38:32 +00:00
|
|
|
|
2013-07-26 13:39:18 +00:00
|
|
|
bool _onBadCertificateWrapper(X509Certificate certificate) {
|
|
|
|
if (onBadCertificate == null) return false;
|
|
|
|
var result = onBadCertificate(certificate);
|
|
|
|
if (result is bool) return result;
|
|
|
|
throw new ArgumentError(
|
|
|
|
"onBadCertificate callback returned non-boolean $result");
|
|
|
|
}
|
|
|
|
|
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 _eventDispatcher(RawSocketEvent event) {
|
2013-06-25 13:54:42 +00:00
|
|
|
try {
|
|
|
|
if (event == RawSocketEvent.READ) {
|
|
|
|
_readHandler();
|
|
|
|
} else if (event == RawSocketEvent.WRITE) {
|
|
|
|
_writeHandler();
|
|
|
|
} else if (event == RawSocketEvent.READ_CLOSED) {
|
|
|
|
_closeHandler();
|
|
|
|
}
|
2013-10-11 12:02:20 +00:00
|
|
|
} catch (e, stackTrace) {
|
|
|
|
_reportError(e, stackTrace);
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-20 15:06:43 +00:00
|
|
|
void _readHandler() {
|
|
|
|
_readSocket();
|
|
|
|
_scheduleFilter();
|
2013-04-19 07:37:37 +00:00
|
|
|
}
|
|
|
|
|
2013-06-20 15:06:43 +00:00
|
|
|
void _writeHandler() {
|
|
|
|
_writeSocket();
|
|
|
|
_scheduleFilter();
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
void _doneHandler() {
|
2013-06-20 15:06:43 +00:00
|
|
|
if (_filterStatus.readEmpty) {
|
2013-02-27 13:23:13 +00:00
|
|
|
_close();
|
2013-02-21 11:58:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-11 12:02:20 +00:00
|
|
|
void _reportError(e, [StackTrace stackTrace]) {
|
2013-07-04 09:21:01 +00:00
|
|
|
if (_status == CLOSED) {
|
|
|
|
return;
|
|
|
|
} else if (_connectPending) {
|
2013-08-20 12:53:52 +00:00
|
|
|
// _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.
|
2013-10-11 12:02:20 +00:00
|
|
|
_handshakeComplete.completeError(e, stackTrace);
|
2013-02-21 11:58:11 +00:00
|
|
|
} else {
|
2013-10-11 12:02:20 +00:00
|
|
|
_controller.addError(e, stackTrace);
|
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;
|
2013-06-20 15:06:43 +00:00
|
|
|
if (_filterStatus.readEmpty) {
|
2013-02-21 11:58:11 +00:00
|
|
|
_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
|
|
|
}
|
2013-06-20 15:06:43 +00:00
|
|
|
} else {
|
|
|
|
_scheduleFilter();
|
2012-11-22 15:45:54 +00:00
|
|
|
}
|
2013-02-21 11:58:11 +00:00
|
|
|
} else if (_status == HANDSHAKE) {
|
2013-06-20 15:06:43 +00:00
|
|
|
_socketClosedRead = true;
|
|
|
|
if (_filterStatus.readEmpty) {
|
2013-02-21 11:58:11 +00:00
|
|
|
_reportError(
|
2013-10-11 12:02:20 +00:00
|
|
|
new HandshakeException('Connection terminated during handshake'),
|
|
|
|
null);
|
2013-06-20 15:06:43 +00:00
|
|
|
} else {
|
|
|
|
_secureHandshake();
|
|
|
|
}
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-23 09:21:48 +00:00
|
|
|
void _secureHandshake() {
|
2013-06-20 15:06:43 +00:00
|
|
|
try {
|
|
|
|
_secureFilter.handshake();
|
|
|
|
_filterStatus.writeEmpty = false;
|
|
|
|
_readSocket();
|
|
|
|
_writeSocket();
|
|
|
|
_scheduleFilter();
|
2013-10-11 12:02:20 +00:00
|
|
|
} catch (e, stackTrace) {
|
|
|
|
_reportError(e, stackTrace);
|
2013-06-20 15:06:43 +00:00
|
|
|
}
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
2013-07-11 14:02:49 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
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-06-20 15:06:43 +00:00
|
|
|
// We don't want user code to run synchronously in this callback.
|
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() {
|
2013-06-20 15:06:43 +00:00
|
|
|
if (_controller.isPaused) {
|
|
|
|
_pauseCount++;
|
|
|
|
} else {
|
|
|
|
_pauseCount--;
|
|
|
|
if (_pauseCount == 0) {
|
|
|
|
_scheduleReadEvent();
|
|
|
|
_sendWriteEvent(); // Can send event synchronously.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-21 11:58:11 +00:00
|
|
|
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
|
|
|
|
2013-06-20 15:06:43 +00:00
|
|
|
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;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
2014-02-19 10:46:45 +00:00
|
|
|
_socket.readEventsEnabled = true;
|
2013-06-20 15:06:43 +00:00
|
|
|
if (_filterStatus.writeEmpty && _closedWrite && !_socketClosedWrite) {
|
|
|
|
// Checks for and handles all cases of partially closed sockets.
|
|
|
|
shutdown(SocketDirection.SEND);
|
|
|
|
if (_status == CLOSED) return;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
2013-06-20 15:06:43 +00:00
|
|
|
if (_filterStatus.readEmpty && _socketClosedRead && !_closedRead) {
|
|
|
|
if (_status == HANDSHAKE) {
|
|
|
|
_secureFilter.handshake();
|
|
|
|
if (_status == HANDSHAKE) {
|
2013-06-25 13:54:42 +00:00
|
|
|
throw new HandshakeException(
|
|
|
|
'Connection terminated during handshake');
|
2013-06-20 15:06:43 +00:00
|
|
|
}
|
2013-04-19 07:37:37 +00:00
|
|
|
}
|
2013-06-20 15:06:43 +00:00
|
|
|
_closeHandler();
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
2013-06-20 15:06:43 +00:00
|
|
|
if (_status == CLOSED) return;
|
|
|
|
if (_filterStatus.progress) {
|
|
|
|
_filterPending = true;
|
|
|
|
if (_filterStatus.writePlaintextNoLongerFull) _sendWriteEvent();
|
|
|
|
if (_filterStatus.readEncryptedNoLongerFull) _readSocket();
|
|
|
|
if (_filterStatus.writeEncryptedNoLongerEmpty) _writeSocket();
|
|
|
|
if (_filterStatus.readPlaintextNoLongerEmpty) _scheduleReadEvent();
|
|
|
|
if (_status == HANDSHAKE) _secureHandshake();
|
|
|
|
}
|
|
|
|
_tryFilter();
|
2013-06-25 13:54:42 +00:00
|
|
|
}).catchError(_reportError);
|
2013-06-20 15:06:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2013-06-25 13:54:42 +00:00
|
|
|
return _socket.read(bytes);
|
2013-06-20 15:06:43 +00:00
|
|
|
} else {
|
|
|
|
return null;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-20 15:06:43 +00:00
|
|
|
void _readSocket() {
|
|
|
|
if (_status == CLOSED) return;
|
|
|
|
var buffer = _secureFilter.buffers[READ_ENCRYPTED];
|
|
|
|
if (buffer.writeFromSource(_readSocketOrBufferedData) > 0) {
|
|
|
|
_filterStatus.readEmpty = false;
|
2014-02-19 10:46:45 +00:00
|
|
|
} else {
|
|
|
|
_socket.readEventsEnabled = false;
|
2013-06-20 15:06:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _writeSocket() {
|
2012-11-22 15:45:54 +00:00
|
|
|
if (_socketClosedWrite) return;
|
2013-06-20 15:06:43 +00:00
|
|
|
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;
|
2014-02-19 09:44:26 +00:00
|
|
|
if (_status != CLOSED &&
|
|
|
|
_readEventsEnabled &&
|
2013-06-20 15:06:43 +00:00
|
|
|
_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() {
|
2013-09-25 15:03:24 +00:00
|
|
|
bool wasInHandshake = _status != CONNECTED;
|
|
|
|
List args = new List(2 + NUM_BUFFERS * 2);
|
|
|
|
args[0] = _filterPointer;
|
|
|
|
args[1] = wasInHandshake;
|
2013-06-20 15:06:43 +00:00
|
|
|
var bufs = _secureFilter.buffers;
|
|
|
|
for (var i = 0; i < NUM_BUFFERS; ++i) {
|
2013-09-25 15:03:24 +00:00
|
|
|
args[2 * i + 2] = bufs[i].start;
|
|
|
|
args[2 * i + 3] = bufs[i].end;
|
2013-06-20 15:06:43 +00:00
|
|
|
}
|
|
|
|
|
2013-09-25 15:53:54 +00:00
|
|
|
return _IOService.dispatch(_SSL_PROCESS_FILTER, args).then((response) {
|
2013-07-04 09:21:01 +00:00
|
|
|
if (response.length == 2) {
|
2013-10-11 12:02:20 +00:00
|
|
|
_reportError(new TlsException('${response[1]} error ${response[0]}'),
|
|
|
|
null);
|
2013-07-04 09:21:01 +00:00
|
|
|
}
|
2013-09-25 15:03:24 +00:00
|
|
|
int start(int index) => response[2 * index];
|
|
|
|
int end(int index) => response[2 * index + 1];
|
2013-06-20 15:06:43 +00:00
|
|
|
|
|
|
|
_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;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
2013-06-20 15:06:43 +00:00
|
|
|
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;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
2013-06-20 15:06:43 +00:00
|
|
|
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;
|
2012-11-22 15:45:54 +00:00
|
|
|
}
|
2013-06-20 15:06:43 +00:00
|
|
|
buffer.end = new_end;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
2013-06-20 15:06:43 +00:00
|
|
|
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;
|
|
|
|
});
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-06-20 15:06:43 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2012-11-23 09:21:48 +00:00
|
|
|
class _ExternalBuffer {
|
2014-01-08 12:03:11 +00:00
|
|
|
List data; // This will be a ExternalByteArray, backed by C allocated data.
|
|
|
|
int start;
|
|
|
|
int end;
|
|
|
|
final size;
|
|
|
|
|
2013-06-20 15:06:43 +00:00
|
|
|
_ExternalBuffer(this.size) {
|
2014-01-08 12:03:11 +00:00
|
|
|
start = end = size ~/ 2;
|
2013-06-20 15:06:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2014-01-08 12:03:11 +00:00
|
|
|
int get length =>
|
|
|
|
start > end ? size + end - start : end - start;
|
2013-06-20 15:06:43 +00:00
|
|
|
|
2014-01-08 12:03:11 +00:00
|
|
|
int get linearLength =>
|
|
|
|
start > end ? size - start : end - start;
|
2013-06-20 15:06:43 +00:00
|
|
|
|
2014-01-08 12:03:11 +00:00
|
|
|
int get free =>
|
|
|
|
start > end ? start - end - 1 : size + start - end - 1;
|
2013-06-20 15:06:43 +00:00
|
|
|
|
|
|
|
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) {
|
2013-07-03 13:31:50 +00:00
|
|
|
int toRead = min(bytes, linearLength);
|
2013-06-20 15:06:43 +00:00
|
|
|
result.setRange(bytesRead,
|
|
|
|
bytesRead + toRead,
|
|
|
|
data,
|
|
|
|
start);
|
|
|
|
advanceStart(toRead);
|
|
|
|
bytesRead += toRead;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2012-11-14 13:38:32 +00:00
|
|
|
|
2013-06-20 15:06:43 +00:00
|
|
|
int write(List<int> inputData, int offset, int bytes) {
|
|
|
|
if (bytes > free) {
|
|
|
|
bytes = free;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
2013-06-20 15:06:43 +00:00
|
|
|
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;
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
2013-06-20 15:06:43 +00:00
|
|
|
int writeFromSource(List<int> getData(int requested)) {
|
|
|
|
int written = 0;
|
|
|
|
int toWrite = linearFree;
|
|
|
|
// Loop over zero, one, or two linear data ranges.
|
2014-02-19 10:46:45 +00:00
|
|
|
while (toWrite > 0) {
|
2013-06-20 15:06:43 +00:00
|
|
|
// Source returns at most toWrite bytes, and it returns null when empty.
|
|
|
|
var inputData = getData(toWrite);
|
2014-02-19 09:44:26 +00:00
|
|
|
if (inputData == null || inputData.length == 0) break;
|
2013-06-20 15:06:43 +00:00
|
|
|
var len = inputData.length;
|
|
|
|
data.setRange(end, end + len, inputData);
|
|
|
|
advanceEnd(len);
|
|
|
|
written += len;
|
|
|
|
toWrite = linearFree;
|
2014-02-19 10:46:45 +00:00
|
|
|
}
|
2013-06-20 15:06:43 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-11-14 13:38:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
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();
|
2013-07-11 14:02:49 +00:00
|
|
|
void rehandshake();
|
|
|
|
void renegotiate(bool useSessionCache,
|
|
|
|
bool requestClientCertificate,
|
|
|
|
bool requireClientCertificate);
|
2012-11-14 13:38:32 +00:00
|
|
|
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);
|
2013-06-20 15:06:43 +00:00
|
|
|
int _pointer();
|
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
|
|
|
}
|
2013-06-25 13:54:42 +00:00
|
|
|
|
|
|
|
/** 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);
|
|
|
|
|
2014-01-08 12:03:11 +00:00
|
|
|
const TlsException._(this.type, this.message, this.osError);
|
2013-06-25 13:54:42 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|