dart:io | Support connection renegotiation (rehandshake) on SecureSocket.
BUG=dartbug.com/11384
R=ajohnsen@google.com
Review URL: https://codereview.chromium.org//18984008
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@24916 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc
index c2a3a96..97b3ad7 100644
--- a/runtime/bin/io_natives.cc
+++ b/runtime/bin/io_natives.cc
@@ -48,6 +48,7 @@
V(SecureSocket_PeerCertificate, 1) \
V(SecureSocket_RegisterBadCertificateCallback, 2) \
V(SecureSocket_RegisterHandshakeCompleteCallback, 2) \
+ V(SecureSocket_Renegotiate, 4) \
V(SecureSocket_InitializeLibrary, 3) \
V(SecureSocket_NewServicePort, 0) \
V(SecureSocket_FilterPointer, 1) \
diff --git a/runtime/bin/secure_socket.cc b/runtime/bin/secure_socket.cc
index d308b3b..7b5cd37 100644
--- a/runtime/bin/secure_socket.cc
+++ b/runtime/bin/secure_socket.cc
@@ -189,6 +189,21 @@
}
+void FUNCTION_NAME(SecureSocket_Renegotiate)(Dart_NativeArguments args) {
+ Dart_EnterScope();
+ bool use_session_cache =
+ DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 1));
+ bool request_client_certificate =
+ DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 2));
+ bool require_client_certificate =
+ DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 3));
+ GetFilter(args)->Renegotiate(use_session_cache,
+ request_client_certificate,
+ require_client_certificate);
+ Dart_ExitScope();
+}
+
+
void FUNCTION_NAME(SecureSocket_RegisterHandshakeCompleteCallback)(
Dart_NativeArguments args) {
Dart_EnterScope();
@@ -732,8 +747,9 @@
ThrowPRException("TlsException",
"Failed SSL_OptionSet(REQUEST_CERTIFICATE) call");
}
- PRBool require_cert = require_client_certificate ? PR_TRUE : PR_FALSE;
- status = SSL_OptionSet(filter_, SSL_REQUIRE_CERTIFICATE, require_cert);
+ status = SSL_OptionSet(filter_,
+ SSL_REQUIRE_CERTIFICATE,
+ require_client_certificate);
if (status != SECSuccess) {
ThrowPRException("TlsException",
"Failed SSL_OptionSet(REQUIRE_CERTIFICATE) call");
@@ -772,8 +788,7 @@
BadCertificateCallback,
static_cast<void*>(this));
- PRBool as_server = is_server ? PR_TRUE : PR_FALSE;
- status = SSL_ResetHandshake(filter_, as_server);
+ status = SSL_ResetHandshake(filter_, is_server);
if (status != SECSuccess) {
ThrowPRException("TlsException",
"Failed SSL_ResetHandshake call");
@@ -827,6 +842,43 @@
}
+void SSLFilter::Renegotiate(bool use_session_cache,
+ bool request_client_certificate,
+ bool require_client_certificate) {
+ SECStatus status;
+ // The SSL_REQUIRE_CERTIFICATE option only takes effect if the
+ // SSL_REQUEST_CERTIFICATE option is also set, so set it.
+ request_client_certificate =
+ request_client_certificate || require_client_certificate;
+
+ status = SSL_OptionSet(filter_,
+ SSL_REQUEST_CERTIFICATE,
+ request_client_certificate);
+ if (status != SECSuccess) {
+ ThrowPRException("TlsException",
+ "Failure in (Raw)SecureSocket.renegotiate request_client_certificate");
+ }
+ status = SSL_OptionSet(filter_,
+ SSL_REQUIRE_CERTIFICATE,
+ require_client_certificate);
+ if (status != SECSuccess) {
+ ThrowPRException("TlsException",
+ "Failure in (Raw)SecureSocket.renegotiate require_client_certificate");
+ }
+ bool flush_cache = !use_session_cache;
+ status = SSL_ReHandshake(filter_, flush_cache);
+ if (status != SECSuccess) {
+ if (is_server_) {
+ ThrowPRException("HandshakeException",
+ "Failure in (Raw)SecureSocket.renegotiate in server");
+ } else {
+ ThrowPRException("HandshakeException",
+ "Failure in (Raw)SecureSocket.renegotiate in client");
+ }
+ }
+}
+
+
void SSLFilter::Destroy() {
for (int i = 0; i < kNumBuffers; ++i) {
Dart_DeletePersistentHandle(dart_buffer_objects_[i]);
diff --git a/runtime/bin/secure_socket.h b/runtime/bin/secure_socket.h
index ce011b6..817be4a 100644
--- a/runtime/bin/secure_socket.h
+++ b/runtime/bin/secure_socket.h
@@ -66,6 +66,9 @@
bool send_client_certificate);
void Destroy();
void Handshake();
+ void Renegotiate(bool use_session_cache,
+ bool request_client_certificate,
+ bool require_client_certificate);
void RegisterHandshakeCompleteCallback(Dart_Handle handshake_complete);
void RegisterBadCertificateCallback(Dart_Handle callback);
Dart_Handle bad_certificate_callback() {
diff --git a/runtime/bin/secure_socket_patch.dart b/runtime/bin/secure_socket_patch.dart
index 4b7c5ab..104f562 100644
--- a/runtime/bin/secure_socket_patch.dart
+++ b/runtime/bin/secure_socket_patch.dart
@@ -31,6 +31,14 @@
_raw.onBadCertificate = callback;
}
+ void renegotiate({bool useSessionCache: true,
+ bool requestClientCertificate: false,
+ bool requireClientCertificate: false}) {
+ _raw.renegotiate(useSessionCache: useSessionCache,
+ requestClientCertificate: requestClientCertificate,
+ requireClientCertificate: requireClientCertificate);
+ }
+
X509Certificate get peerCertificate {
if (_raw == null) {
throw new StateError("peerCertificate called on destroyed SecureSocket");
@@ -84,6 +92,10 @@
void handshake() native "SecureSocket_Handshake";
+ void renegotiate(bool useSessionCache,
+ bool requestClientCertificate,
+ bool requireClientCertificate)
+ native "SecureSocket_Renegotiate";
void init() native "SecureSocket_Init";
X509Certificate get peerCertificate native "SecureSocket_PeerCertificate";
diff --git a/sdk/lib/io/secure_socket.dart b/sdk/lib/io/secure_socket.dart
index 417534a..6da8ec6 100644
--- a/sdk/lib/io/secure_socket.dart
+++ b/sdk/lib/io/secure_socket.dart
@@ -150,6 +150,17 @@
X509Certificate get peerCertificate;
/**
+ * 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});
+
+ /**
* Initializes the NSS library. If [initialize] is not called, the library
* is automatically initialized as if [initialize] were called with no
* arguments. If [initialize] is called more than once, or called after
@@ -334,6 +345,17 @@
}
/**
+ * Renegotiate an existing secure connection, renewing the session keys
+ * and possibly changing the connection properties.
+ *
+ * This repeats the SSL or TLS handshake, with options that allow clearing
+ * the session cache and requesting a client certificate.
+ */
+ void renegotiate({bool useSessionCache: true,
+ bool requestClientCertificate: false,
+ bool requireClientCertificate: false});
+
+ /**
* Get the peer certificate for a connected RawSecureSocket. If this
* RawSecureSocket is the server end of a secure socket connection,
* [peerCertificate] will return the client certificate, or null, if no
@@ -785,6 +807,21 @@
}
}
+ void renegotiate({bool useSessionCache: true,
+ bool requestClientCertificate: false,
+ bool requireClientCertificate: false}) {
+ if (_status != CONNECTED) {
+ throw new HandshakeException(
+ "Called renegotiate on a non-connected socket");
+ }
+ _secureFilter.renegotiate(useSessionCache,
+ requestClientCertificate,
+ requireClientCertificate);
+ _status = HANDSHAKE;
+ _filterStatus.writeEmpty = false;
+ _scheduleFilter();
+ }
+
void _secureHandshakeCompleteHandler() {
_status = CONNECTED;
if (_connectPending) {
@@ -1158,6 +1195,10 @@
bool sendClientCertificate);
void destroy();
void handshake();
+ void rehandshake();
+ void renegotiate(bool useSessionCache,
+ bool requestClientCertificate,
+ bool requireClientCertificate);
void init();
X509Certificate get peerCertificate;
int processBuffer(int bufferIndex);
diff --git a/tests/standalone/io/secure_socket_renegotiate_client.dart b/tests/standalone/io/secure_socket_renegotiate_client.dart
new file mode 100644
index 0000000..9ac2192
--- /dev/null
+++ b/tests/standalone/io/secure_socket_renegotiate_client.dart
@@ -0,0 +1,79 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Client for secure_socket_renegotiate_test, that runs in a subprocess.
+// The test verifies that client certificates work, if the client and server
+// are in separate processes, and that connection renegotiation can request
+// a client certificate to be sent.
+
+import "dart:async";
+import "dart:io";
+
+const HOST_NAME = "localhost";
+const CERTIFICATE = "localhost_cert";
+
+
+class ExpectException implements Exception {
+ ExpectException(this.message);
+ String toString() => message;
+ String message;
+}
+
+
+void expectEquals(expected, actual) {
+ if (actual != expected) {
+ throw new ExpectException('Expected $expected, found $actual');
+ }
+}
+
+
+void expect(condition) {
+ if (!condition) {
+ throw new ExpectException('');
+ }
+}
+
+
+void runClient(int port) {
+ SecureSocket.connect(HOST_NAME, port, sendClientCertificate: true)
+ .then((SecureSocket socket) {
+ X509Certificate certificate = socket.peerCertificate;
+ expect(certificate != null);
+ expectEquals('CN=localhost', certificate.subject);
+ expectEquals('CN=myauthority', certificate.issuer);
+ StreamIterator<String> input = new StreamIterator(socket
+ .transform(new StringDecoder())
+ .transform(new LineTransformer()));
+ socket.writeln('first');
+ input.moveNext()
+ .then((success) {
+ expect(success);
+ expectEquals('first reply', input.current);
+ socket.renegotiate();
+ socket.writeln('renegotiated');
+ return input.moveNext();
+ })
+ .then((success) {
+ expect(success);
+ expectEquals('server renegotiated', input.current);
+ X509Certificate certificate = socket.peerCertificate;
+ expect(certificate != null);
+ expectEquals("CN=localhost", certificate.subject);
+ expectEquals("CN=myauthority", certificate.issuer);
+ socket.writeln('second');
+ return input.moveNext();
+ })
+ .then((success) {
+ expect(success != true);
+ socket.close();
+ });
+ });
+}
+
+
+void main() {
+ final args = new Options().arguments;
+ SecureSocket.initialize(database: args[1], password: 'dartdart');
+ runClient(int.parse(args[0]));
+}
diff --git a/tests/standalone/io/secure_socket_renegotiate_test.dart b/tests/standalone/io/secure_socket_renegotiate_test.dart
new file mode 100644
index 0000000..49c1644
--- /dev/null
+++ b/tests/standalone/io/secure_socket_renegotiate_test.dart
@@ -0,0 +1,85 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This test verifies that client certificates work, if the client and server
+// are in separate processes, and that connection renegotiation works, and
+// can request a client certificate to be sent.
+
+import "package:expect/expect.dart";
+import "package:pathos/path.dart" as path;
+import "dart:async";
+import "dart:io";
+
+const HOST_NAME = "localhost";
+const CERTIFICATE = "localhost_cert";
+
+
+String certificateDatabase() =>
+ path.join(path.dirname(new Options().script), 'pkcert', '');
+
+
+Future<SecureServerSocket> runServer() {
+ SecureSocket.initialize(database: certificateDatabase(),
+ password: 'dartdart');
+
+ return SecureServerSocket.bind(HOST_NAME, 0, CERTIFICATE)
+ .then((SecureServerSocket server) {
+ server.listen((SecureSocket socket) {
+ Expect.isNull(socket.peerCertificate);
+
+ StreamIterator<String> input =
+ new StreamIterator(socket.transform(new StringDecoder())
+ .transform(new LineTransformer()));
+ input.moveNext().then((success) {
+ Expect.isTrue(success);
+ Expect.equals('first', input.current);
+ socket.writeln('first reply');
+ return input.moveNext();
+ }).then((success) {
+ Expect.isTrue(success);
+ Expect.equals('renegotiated', input.current);
+ Expect.isNull(socket.peerCertificate);
+ socket.renegotiate(requestClientCertificate: true,
+ requireClientCertificate: true,
+ useSessionCache: false);
+ socket.writeln('server renegotiated');
+ return input.moveNext();
+ }).then((success) {
+ Expect.isTrue(success);
+ Expect.equals('second', input.current);
+ X509Certificate certificate = socket.peerCertificate;
+ Expect.isNotNull(certificate);
+ Expect.equals("CN=localhost", certificate.subject);
+ Expect.equals("CN=myauthority", certificate.issuer);
+ server.close();
+ socket.close();
+ });
+ });
+ return server;
+ });
+}
+
+
+void main() {
+ runServer()
+ .then((SecureServerSocket server) {
+ final options = new Options();
+ var clientScript =
+ options.script.replaceFirst("_test.dart", "_client.dart");
+ Expect.isTrue(clientScript.endsWith("_client.dart"));
+ Process.run(options.executable,
+ [clientScript,
+ server.port.toString(),
+ certificateDatabase()])
+ .then((ProcessResult result) {
+ if (result.exitCode != 0) {
+ print("Client failed, stdout:");
+ print(result.stdout);
+ print(" stderr:");
+ print(result.stderr);
+ Expect.fail('Client subprocess exit code: ${result.exitCode}');
+ }
+ });
+ });
+}