Add the ability to log TLS keys.

TEST=unit tests
Bug: https://github.com/dart-lang/sdk/issues/47838
Change-Id: I8a64e8623022215cae261eadb25b22deb9f3d910
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/231330
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Brian Quinlan <bquinlan@google.com>
This commit is contained in:
Brian Quinlan 2022-02-17 21:39:31 +00:00 committed by Commit Bot
parent 6cc25b013a
commit 917ae52f70
19 changed files with 553 additions and 10 deletions

View file

@ -40,6 +40,19 @@
throw UnsupportedError("connectionFactory not implemented");
```
- **Breaking Change** [#48093](https://github.com/dart-lang/sdk/issues/48093):
`HttpClient` has a new `keyLog` property, which allows TLS keys to be logged
for debugging purposes. Classes that `implement HttpClient` may be broken by
this change. Add the following method to your classes to fix them:
```dart
void set keyLog(Function(String line)? callback) =>
throw UnsupportedError("keyLog not implemented");
```
- Add a optional `keyLog` parameter to `SecureSocket.connect` and
`SecureSocket.startConnect`.
### Tools
#### Dart command line

View file

@ -658,6 +658,11 @@ bool DartUtils::PostInt64(Dart_Port port_id, int64_t value) {
return Dart_PostCObject(port_id, &object);
}
bool DartUtils::PostString(Dart_Port port_id, const char* value) {
Dart_CObject* object = CObject::NewString(value);
return Dart_PostCObject(port_id, object);
}
Dart_Handle DartUtils::GetDartType(const char* library_url,
const char* class_name) {
return Dart_GetNonNullableType(Dart_LookupLibrary(NewString(library_url)),

View file

@ -173,6 +173,7 @@ class DartUtils {
static bool PostNull(Dart_Port port_id);
static bool PostInt32(Dart_Port port_id, int32_t value);
static bool PostInt64(Dart_Port port_id, int64_t value);
static bool PostString(Dart_Port port_id, const char* value);
static Dart_Handle GetDartType(const char* library_url,
const char* class_name);

View file

@ -129,6 +129,7 @@ namespace bin {
V(SecureSocket_Init, 1) \
V(SecureSocket_PeerCertificate, 1) \
V(SecureSocket_RegisterBadCertificateCallback, 2) \
V(SecureSocket_RegisterKeyLogPort, 2) \
V(SecureSocket_RegisterHandshakeCompleteCallback, 2) \
V(SecureSocket_Renegotiate, 4) \
V(SecurityContext_Allocate, 1) \

View file

@ -207,6 +207,15 @@ void FUNCTION_NAME(SecureSocket_RegisterBadCertificateCallback)(
GetFilter(args)->RegisterBadCertificateCallback(callback);
}
void FUNCTION_NAME(SecureSocket_RegisterKeyLogPort)(Dart_NativeArguments args) {
Dart_Handle port = ThrowIfError(Dart_GetNativeArgument(args, 1));
ASSERT(!Dart_IsNull(port));
Dart_Port port_id;
ThrowIfError(Dart_SendPortGetId(port, &port_id));
GetFilter(args)->RegisterKeyLogPort(port_id);
}
void FUNCTION_NAME(SecureSocket_PeerCertificate)(Dart_NativeArguments args) {
Dart_Handle cert = ThrowIfError(GetFilter(args)->PeerCertificate());
Dart_SetReturnValue(args, cert);
@ -465,6 +474,10 @@ Dart_Handle SSLFilter::PeerCertificate() {
return X509Helper::WrappedX509Certificate(ca);
}
void SSLFilter::RegisterKeyLogPort(Dart_Port key_log_port) {
key_log_port_ = key_log_port;
}
void SSLFilter::InitializeLibrary() {
MutexLocker locker(mutex_);
if (!library_initialized_) {
@ -595,10 +608,14 @@ int SSLFilter::Handshake(Dart_Port reply_port) {
return SSL_ERROR_WANT_CERTIFICATE_VERIFY;
}
if (callback_error != NULL) {
// The SSL_do_handshake will try performing a handshake and might call
// a CertificateCallback. If the certificate validation
// failed the 'callback_error" will be set by the certificateCallback
// logic and we propagate the error"
// The SSL_do_handshake will try performing a handshake and might call one
// or both of:
// SSLCertContext::KeyLogCallback
// SSLCertContext::CertificateCallback
//
// If either of those functions fail, and this.callback_error has not
// already been set, then they will set this.callback_error to an error
// handle i.e. only the first error will be captured and propogated.
Dart_PropagateError(callback_error);
}
if (SSL_want_write(ssl_) || SSL_want_read(ssl_)) {

View file

@ -90,6 +90,8 @@ class SSLFilter : public ReferenceCounted<SSLFilter> {
bool require_client_certificate);
void RegisterHandshakeCompleteCallback(Dart_Handle handshake_complete);
void RegisterBadCertificateCallback(Dart_Handle callback);
void RegisterKeyLogPort(Dart_Port key_log_port);
Dart_Port key_log_port() { return key_log_port_; }
Dart_Handle bad_certificate_callback() {
return Dart_HandleFromPersistent(bad_certificate_callback_);
}
@ -145,6 +147,7 @@ class SSLFilter : public ReferenceCounted<SSLFilter> {
Dart_Port reply_port_ = ILLEGAL_PORT;
Dart_Port trust_evaluate_reply_port_ = ILLEGAL_PORT;
Dart_Port key_log_port_ = ILLEGAL_PORT;
static bool IsBufferEncrypted(int i) {
return static_cast<BufferIndex>(i) >= kFirstEncrypted;

View file

@ -65,6 +65,11 @@ void FUNCTION_NAME(SecureSocket_RegisterBadCertificateCallback)(
"Secure Sockets unsupported on this platform"));
}
void FUNCTION_NAME(SecureSocket_RegisterKeyLogPort)(Dart_NativeArguments args) {
Dart_ThrowException(DartUtils::NewDartArgumentError(
"Secure Sockets unsupported on this platform"));
}
void FUNCTION_NAME(SecureSocket_ProcessBuffer)(Dart_NativeArguments args) {
Dart_ThrowException(DartUtils::NewDartArgumentError(
"Secure Sockets unsupported on this platform"));

View file

@ -74,13 +74,24 @@ int SSLCertContext::CertificateCallback(int preverify_ok,
"BadCertificateCallback returned a value that was not a boolean",
Dart_Null()));
}
if (Dart_IsError(result)) {
// See SSLFilter::Handshake for the semantics of filter->callback_error.
if (Dart_IsError(result) && filter->callback_error == nullptr) {
filter->callback_error = result;
return 0;
}
return static_cast<int>(DartUtils::GetBooleanValue(result));
}
void SSLCertContext::KeyLogCallback(const SSL* ssl, const char* line) {
SSLFilter* filter = static_cast<SSLFilter*>(
SSL_get_ex_data(ssl, SSLFilter::filter_ssl_index));
Dart_Port port = filter->key_log_port();
if (port != ILLEGAL_PORT) {
DartUtils::PostString(port, line);
}
}
SSLCertContext* SSLCertContext::GetSecurityContext(Dart_NativeArguments args) {
SSLCertContext* context;
Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
@ -807,6 +818,7 @@ void FUNCTION_NAME(SecurityContext_Allocate)(Dart_NativeArguments args) {
SSLFilter::InitializeLibrary();
SSL_CTX* ctx = SSL_CTX_new(TLS_method());
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, SSLCertContext::CertificateCallback);
SSL_CTX_set_keylog_callback(ctx, SSLCertContext::KeyLogCallback);
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_cipher_list(ctx, "HIGH:MEDIUM");
SSLCertContext* context = new SSLCertContext(ctx);

View file

@ -30,17 +30,16 @@ class SSLCertContext : public ReferenceCounted<SSLCertContext> {
explicit SSLCertContext(SSL_CTX* context)
: ReferenceCounted(),
context_(context),
alpn_protocol_string_(NULL),
alpn_protocol_string_(nullptr),
trust_builtin_(false) {}
~SSLCertContext() {
SSL_CTX_free(context_);
if (alpn_protocol_string_ != NULL) {
free(alpn_protocol_string_);
}
free(alpn_protocol_string_);
}
static int CertificateCallback(int preverify_ok, X509_STORE_CTX* store_ctx);
static void KeyLogCallback(const SSL* ssl, const char* line);
static SSLCertContext* GetSecurityContext(Dart_NativeArguments args);
static const char* GetPasswordArgument(Dart_NativeArguments args,

View file

@ -1688,6 +1688,21 @@ abstract class HttpClient {
void set badCertificateCallback(
bool Function(X509Certificate cert, String host, int port)? callback);
/// Sets a callback that will be called when new TLS keys are exchanged with
/// the server. It will receive one line of text in
/// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
/// for each call. Writing these lines to a file will allow tools (such as
/// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
/// to decrypt communication between the this client and the server. This is
/// meant to allow network-level debugging of secure sockets and should not
/// be used in production code. For example:
///
/// final log = File('keylog.txt');
/// final client = HttpClient();
/// client.keyLog = (line) => log.writeAsStringSync(line,
/// mode: FileMode.append);
void set keyLog(Function(String line)? callback);
/// Shuts down the HTTP client.
///
/// If [force] is `false` (the default) the [HttpClient] will be kept alive

View file

@ -2447,7 +2447,9 @@ class _ConnectionTarget {
} else {
connectionTask = (isSecure && proxy.isDirect
? SecureSocket.startConnect(host, port,
context: context, onBadCertificate: callback)
context: context,
onBadCertificate: callback,
keyLog: client._keyLog)
: Socket.startConnect(host, port));
}
_connecting++;
@ -2526,6 +2528,7 @@ class _HttpClient implements HttpClient {
String Function(Uri)? _findProxy = HttpClient.findProxyFromEnvironment;
Duration _idleTimeout = const Duration(seconds: 15);
BadCertificateCallback? _badCertificateCallback;
Function(String line)? _keyLog;
Duration get idleTimeout => _idleTimeout;
@ -2555,6 +2558,10 @@ class _HttpClient implements HttpClient {
_badCertificateCallback = callback;
}
void set keyLog(Function(String line)? callback) {
_keyLog = callback;
}
Future<HttpClientRequest> open(
String method, String host, int port, String path) {
const int hashMark = 0x23;

View file

@ -189,6 +189,9 @@ class _SecureFilterImpl extends NativeFieldWrapperClass1
external void registerHandshakeCompleteCallback(
Function handshakeCompleteHandler);
@pragma("vm:external-name", "SecureSocket_RegisterKeyLogPort")
external void registerKeyLogPort(SendPort port);
// This is a security issue, as it exposes a raw pointer to Dart code.
@pragma("vm:external-name", "SecureSocket_FilterPointer")
external int _pointer();

View file

@ -27,6 +27,20 @@ abstract class SecureSocket implements Socket {
/// the connection or not. The handler should return true
/// to continue the [SecureSocket] connection.
///
/// [keyLog] is an optional callback that will be called when new TLS keys
/// are exchanged with the server. [keyLog] will receive one line of text in
/// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
/// for each call. Writing these lines to a file will allow tools (such as
/// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
/// to decrypt content sent through this socket. This is meant to allow
/// network-level debugging of secure sockets and should not be used in
/// production code. For example:
/// ```dart
/// final log = File('keylog.txt');
/// final socket = await SecureSocket.connect('www.example.com', 443,
/// keyLog: (line) => log.writeAsStringSync(line, mode: FileMode.append));
/// ```
///
/// [supportedProtocols] is an optional list of protocols (in decreasing
/// order of preference) to use during the ALPN protocol negotiation with the
/// server. Example values are "http/1.1" or "h2". The selected protocol
@ -40,11 +54,13 @@ abstract class SecureSocket implements Socket {
static Future<SecureSocket> connect(host, int port,
{SecurityContext? context,
bool onBadCertificate(X509Certificate certificate)?,
void keyLog(String line)?,
List<String>? supportedProtocols,
Duration? timeout}) {
return RawSecureSocket.connect(host, port,
context: context,
onBadCertificate: onBadCertificate,
keyLog: keyLog,
supportedProtocols: supportedProtocols,
timeout: timeout)
.then((rawSocket) => new SecureSocket._(rawSocket));
@ -56,10 +72,12 @@ abstract class SecureSocket implements Socket {
static Future<ConnectionTask<SecureSocket>> startConnect(host, int port,
{SecurityContext? context,
bool onBadCertificate(X509Certificate certificate)?,
void keyLog(String line)?,
List<String>? supportedProtocols}) {
return RawSecureSocket.startConnect(host, port,
context: context,
onBadCertificate: onBadCertificate,
keyLog: keyLog,
supportedProtocols: supportedProtocols)
.then((rawState) {
Future<SecureSocket> socket =
@ -88,6 +106,26 @@ abstract class SecureSocket implements Socket {
/// the [socket] will be used. The [host] can be either a [String] or
/// an [InternetAddress].
///
/// [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.
///
/// [keyLog] is an optional callback that will be called when new TLS keys
/// are exchanged with the server. [keyLog] will receive one line of text in
/// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
/// for each call. Writing these lines to a file will allow tools (such as
/// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
/// to decrypt content sent through this socket. This is meant to allow
/// network-level debugging of secure sockets and should not be used in
/// production code. For example:
/// ```dart
/// final log = File('keylog.txt');
/// final socket = await SecureSocket.connect('www.example.com', 443,
/// keyLog: (line) => log.writeAsStringSync(line, mode: FileMode.append));
/// ```
///
/// [supportedProtocols] is an optional list of protocols (in decreasing
/// order of preference) to use during the ALPN protocol negotiation with the
/// server. Example values are "http/1.1" or "h2". The selected protocol
@ -104,6 +142,7 @@ abstract class SecureSocket implements Socket {
{host,
SecurityContext? context,
bool onBadCertificate(X509Certificate certificate)?,
void keyLog(String line)?,
@Since("2.6") List<String>? supportedProtocols}) {
return ((socket as dynamic /*_Socket*/)._detachRaw() as Future)
.then<RawSecureSocket>((detachedRaw) {
@ -112,6 +151,7 @@ abstract class SecureSocket implements Socket {
host: host,
context: context,
onBadCertificate: onBadCertificate,
keyLog: keyLog,
supportedProtocols: supportedProtocols);
}).then<SecureSocket>((raw) => new SecureSocket._(raw));
}
@ -209,6 +249,26 @@ abstract class RawSecureSocket implements RawSocket {
/// the connection or not. The handler should return true
/// to continue the [RawSecureSocket] connection.
///
/// [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.
///
/// [keyLog] is an optional callback that will be called when new TLS keys
/// are exchanged with the server. [keyLog] will receive one line of text in
/// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
/// for each call. Writing these lines to a file will allow tools (such as
/// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
/// to decrypt content sent through this socket. This is meant to allow
/// network-level debugging of secure sockets and should not be used in
/// production code. For example:
/// ```dart
/// final log = File('keylog.txt');
/// final socket = await SecureSocket.connect('www.example.com', 443,
/// keyLog: (line) => log.writeAsStringSync(line, mode: FileMode.append));
/// ```
///
/// [supportedProtocols] is an optional list of protocols (in decreasing
/// order of preference) to use during the ALPN protocol negotiation with the
/// server. Example values are "http/1.1" or "h2". The selected protocol
@ -216,6 +276,7 @@ abstract class RawSecureSocket implements RawSocket {
static Future<RawSecureSocket> connect(host, int port,
{SecurityContext? context,
bool onBadCertificate(X509Certificate certificate)?,
void keyLog(String line)?,
List<String>? supportedProtocols,
Duration? timeout}) {
_RawSecureSocket._verifyFields(host, port, false, false);
@ -223,6 +284,7 @@ abstract class RawSecureSocket implements RawSocket {
return secure(socket,
context: context,
onBadCertificate: onBadCertificate,
keyLog: keyLog,
supportedProtocols: supportedProtocols);
});
}
@ -233,6 +295,7 @@ abstract class RawSecureSocket implements RawSocket {
static Future<ConnectionTask<RawSecureSocket>> startConnect(host, int port,
{SecurityContext? context,
bool onBadCertificate(X509Certificate certificate)?,
void keyLog(String line)?,
List<String>? supportedProtocols}) {
return RawSocket.startConnect(host, port)
.then((ConnectionTask<RawSocket> rawState) {
@ -240,6 +303,7 @@ abstract class RawSecureSocket implements RawSocket {
return secure(rawSocket,
context: context,
onBadCertificate: onBadCertificate,
keyLog: keyLog,
supportedProtocols: supportedProtocols);
});
return new ConnectionTask<RawSecureSocket>._(socket, rawState._onCancel);
@ -266,6 +330,26 @@ abstract class RawSecureSocket implements RawSocket {
/// the [socket] will be used. The [host] can be either a [String] or
/// an [InternetAddress].
///
/// [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.
///
/// [keyLog] is an optional callback that will be called when new TLS keys
/// are exchanged with the server. [keyLog] will receive one line of text in
/// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
/// for each call. Writing these lines to a file will allow tools (such as
/// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
/// to decrypt content sent through this socket. This is meant to allow
/// network-level debugging of secure sockets and should not be used in
/// production code. For example:
/// ```dart
/// final log = File('keylog.txt');
/// final socket = await SecureSocket.connect('www.example.com', 443,
/// keyLog: (line) => log.writeAsStringSync(line, mode: FileMode.append));
/// ```
///
/// [supportedProtocols] is an optional list of protocols (in decreasing
/// order of preference) to use during the ALPN protocol negotiation with the
/// server. Example values are "http/1.1" or "h2". The selected protocol
@ -283,6 +367,7 @@ abstract class RawSecureSocket implements RawSocket {
host,
SecurityContext? context,
bool onBadCertificate(X509Certificate certificate)?,
void keyLog(String line)?,
List<String>? supportedProtocols}) {
socket.readEventsEnabled = false;
socket.writeEventsEnabled = false;
@ -291,6 +376,7 @@ abstract class RawSecureSocket implements RawSocket {
subscription: subscription,
context: context,
onBadCertificate: onBadCertificate,
keyLog: keyLog,
supportedProtocols: supportedProtocols);
}
@ -427,6 +513,8 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
final bool requestClientCertificate;
final bool requireClientCertificate;
final bool Function(X509Certificate certificate)? onBadCertificate;
final void Function(String line)? keyLog;
ReceivePort? keyLogPort;
var _status = handshakeStatus;
bool _writeEventsEnabled = true;
@ -458,6 +546,7 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
bool requestClientCertificate = false,
bool requireClientCertificate = false,
bool onBadCertificate(X509Certificate certificate)?,
void keyLog(String line)?,
List<String>? supportedProtocols}) {
_verifyFields(host, requestedPort, requestClientCertificate,
requireClientCertificate);
@ -477,6 +566,7 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
requestClientCertificate,
requireClientCertificate,
onBadCertificate,
keyLog,
supportedProtocols)
._handshakeComplete
.future;
@ -493,6 +583,7 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
this.requestClientCertificate,
this.requireClientCertificate,
this.onBadCertificate,
this.keyLog,
List<String>? supportedProtocols) {
_controller
..onListen = _onSubscriptionStateChange
@ -505,6 +596,23 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
secureFilter.init();
secureFilter
.registerHandshakeCompleteCallback(_secureHandshakeCompleteHandler);
if (keyLog != null) {
final port = ReceivePort();
port.listen((line) {
try {
keyLog!((line as String) + '\n');
} catch (e, s) {
// There is no obvious place to surface exceptions from the keyLog
// callback so write the details to stderr.
stderr.writeln("Failure in keyLog callback:");
stderr.writeln(s);
}
});
secureFilter.registerKeyLogPort(port.sendPort);
keyLogPort = port;
}
if (onBadCertificate != null) {
secureFilter.registerBadCertificateCallback(_onBadCertificateWrapper);
}
@ -607,6 +715,7 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
_secureFilter!.destroy();
_secureFilter = null;
}
keyLogPort?.close();
if (_socketSubscription != null) {
_socketSubscription.cancel();
}
@ -1240,6 +1349,7 @@ abstract class _SecureFilter {
int processBuffer(int bufferIndex);
void registerBadCertificateCallback(Function callback);
void registerHandshakeCompleteCallback(Function handshakeCompleteHandler);
void registerKeyLogPort(SendPort port);
// This call may cause a reference counted pointer in the native
// implementation to be retained. It should only be called when the resulting

View file

@ -0,0 +1,84 @@
// Copyright (c) 2021, 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.
//
// VMOptions=
// VMOptions=--short_socket_read
// VMOptions=--short_socket_write
// VMOptions=--short_socket_read --short_socket_write
// OtherResources=certificates/server_chain.pem
// OtherResources=certificates/server_key.pem
// OtherResources=certificates/trusted_certs.pem
import "dart:async";
import "dart:io";
import "package:async_helper/async_helper.dart";
import "package:expect/expect.dart";
late InternetAddress HOST;
String localFile(path) => Platform.script.resolve(path).toFilePath();
SecurityContext serverContext = new SecurityContext()
..useCertificateChain(localFile('certificates/server_chain.pem'))
..usePrivateKey(localFile('certificates/server_key.pem'),
password: 'dartdart');
Future<HttpServer> startEchoServer() {
return HttpServer.bindSecure(HOST, 0, serverContext).then((server) {
server.listen((HttpRequest req) {
final res = req.response;
res.write("Test");
res.close();
});
return server;
});
}
testSuccess(HttpServer server) async {
var log = "";
SecurityContext clientContext = new SecurityContext()
..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
final client = HttpClient(context: clientContext);
client.keyLog = (String line) {
log += line;
};
final request =
await client.getUrl(Uri.parse('https://localhost:${server.port}/test'));
final response = await request.close();
await response.drain();
Expect.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET", log);
}
testExceptionInKeyLogFunction(HttpServer server) async {
SecurityContext clientContext = new SecurityContext()
..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
final client = HttpClient(context: clientContext);
var numCalls = 0;
client.keyLog = (String line) {
++numCalls;
throw FileSystemException("Something bad happened");
};
final request =
await client.getUrl(Uri.parse('https://localhost:${server.port}/test'));
final response = await request.close();
await response.drain();
Expect.notEquals(0, numCalls);
}
void main() async {
asyncStart();
await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
final server = await startEchoServer();
await testSuccess(server);
await testExceptionInKeyLogFunction(server);
await server.close();
asyncEnd();
}

View file

@ -52,6 +52,7 @@ class MyHttpClient1 implements HttpClient {
String host, int port, String realm, HttpClientCredentials credentials) {}
set badCertificateCallback(
bool callback(X509Certificate cert, String host, int port)?) {}
void set keyLog(Function(String line)? callback) {}
void close({bool force: false}) {}
}
@ -100,6 +101,7 @@ class MyHttpClient2 implements HttpClient {
String host, int port, String realm, HttpClientCredentials credentials) {}
set badCertificateCallback(
bool callback(X509Certificate cert, String host, int port)?) {}
void set keyLog(Function(String line)? callback) {}
void close({bool force: false}) {}
}

View file

@ -0,0 +1,87 @@
// Copyright (c) 2021, 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.
//
// VMOptions=
// VMOptions=--short_socket_read
// VMOptions=--short_socket_write
// VMOptions=--short_socket_read --short_socket_write
// OtherResources=certificates/server_chain.pem
// OtherResources=certificates/server_key.pem
// OtherResources=certificates/trusted_certs.pem
import "dart:async";
import "dart:io";
import "package:async_helper/async_helper.dart";
import "package:expect/expect.dart";
late InternetAddress HOST;
String localFile(path) => Platform.script.resolve(path).toFilePath();
SecurityContext serverContext = new SecurityContext()
..useCertificateChain(localFile('certificates/server_chain.pem'))
..usePrivateKey(localFile('certificates/server_key.pem'),
password: 'dartdart');
Future<SecureServerSocket> startEchoServer() {
return SecureServerSocket.bind(HOST, 0, serverContext).then((server) {
server.listen((SecureSocket client) {
client.fold<List<int>>(
<int>[], (message, data) => message..addAll(data)).then((message) {
client.add(message);
client.close();
});
});
return server;
});
}
testSuccess(SecureServerSocket server) async {
var log = "";
SecurityContext clientContext = new SecurityContext()
..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
await SecureSocket.connect(HOST, server.port, context: clientContext,
keyLog: (line) {
log += line;
}).then((socket) {
socket.write("Hello server.");
socket.close();
return socket.drain().then((value) {
Expect.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET", log);
return server;
});
});
}
testExceptionInKeyLogFunction(SecureServerSocket server) async {
SecurityContext clientContext = new SecurityContext()
..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
var numCalls = 0;
await SecureSocket.connect(HOST, server.port, context: clientContext,
keyLog: (line) {
++numCalls;
throw FileSystemException("Something bad happened");
}).then((socket) {
socket.close();
return socket.drain().then((value) {
Expect.notEquals(0, numCalls);
return server;
});
});
}
void main() async {
asyncStart();
await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
final server = await startEchoServer();
await testSuccess(server);
await testExceptionInKeyLogFunction(server);
await server.close();
asyncEnd();
}

View file

@ -0,0 +1,86 @@
// Copyright (c) 2021, 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.
//
// VMOptions=
// VMOptions=--short_socket_read
// VMOptions=--short_socket_write
// VMOptions=--short_socket_read --short_socket_write
// OtherResources=certificates/server_chain.pem
// OtherResources=certificates/server_key.pem
// OtherResources=certificates/trusted_certs.pem
// @dart = 2.9
import "dart:async";
import "dart:io";
import "package:async_helper/async_helper.dart";
import "package:expect/expect.dart";
InternetAddress HOST;
String localFile(path) => Platform.script.resolve(path).toFilePath();
SecurityContext serverContext = new SecurityContext()
..useCertificateChain(localFile('certificates/server_chain.pem'))
..usePrivateKey(localFile('certificates/server_key.pem'),
password: 'dartdart');
Future<HttpServer> startEchoServer() {
return HttpServer.bindSecure(HOST, 0, serverContext).then((server) {
server.listen((HttpRequest req) {
final res = req.response;
res.write("Test");
res.close();
});
return server;
});
}
testSuccess(HttpServer server) async {
var log = "";
SecurityContext clientContext = new SecurityContext()
..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
final client = HttpClient(context: clientContext);
client.keyLog = (String line) {
log += line;
};
final request =
await client.getUrl(Uri.parse('https://localhost:${server.port}/test'));
final response = await request.close();
await response.drain();
Expect.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET", log);
}
testExceptionInKeyLogFunction(HttpServer server) async {
SecurityContext clientContext = new SecurityContext()
..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
final client = HttpClient(context: clientContext);
var numCalls = 0;
client.keyLog = (String line) {
++numCalls;
throw FileSystemException("Something bad happened");
};
final request =
await client.getUrl(Uri.parse('https://localhost:${server.port}/test'));
final response = await request.close();
await response.drain();
Expect.notEquals(0, numCalls);
}
void main() async {
asyncStart();
await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
final server = await startEchoServer();
await testSuccess(server);
await testExceptionInKeyLogFunction(server);
await server.close();
asyncEnd();
}

View file

@ -50,6 +50,8 @@ class MyHttpClient1 implements HttpClient {
String host, int port, String realm, HttpClientCredentials credentials) {}
set badCertificateCallback(
bool callback(X509Certificate cert, String host, int port)) {}
void set keyLog(Function(String line) callback) =>
throw UnsupportedError("keyLog not implemented");
void close({bool force: false}) {}
}
@ -94,6 +96,8 @@ class MyHttpClient2 implements HttpClient {
String host, int port, String realm, HttpClientCredentials credentials) {}
set badCertificateCallback(
bool callback(X509Certificate cert, String host, int port)) {}
void set keyLog(Function(String line) callback) =>
throw UnsupportedError("keyLog not implemented");
void close({bool force: false}) {}
}

View file

@ -0,0 +1,89 @@
// Copyright (c) 2021, 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.
//
// VMOptions=
// VMOptions=--short_socket_read
// VMOptions=--short_socket_write
// VMOptions=--short_socket_read --short_socket_write
// OtherResources=certificates/server_chain.pem
// OtherResources=certificates/server_key.pem
// OtherResources=certificates/trusted_certs.pem
//
// @dart = 2.9
import "dart:async";
import "dart:io";
import "package:async_helper/async_helper.dart";
import "package:expect/expect.dart";
InternetAddress HOST;
String localFile(path) => Platform.script.resolve(path).toFilePath();
SecurityContext serverContext = new SecurityContext()
..useCertificateChain(localFile('certificates/server_chain.pem'))
..usePrivateKey(localFile('certificates/server_key.pem'),
password: 'dartdart');
Future<SecureServerSocket> startEchoServer() {
return SecureServerSocket.bind(HOST, 0, serverContext).then((server) {
server.listen((SecureSocket client) {
client.fold<List<int>>(
<int>[], (message, data) => message..addAll(data)).then((message) {
client.add(message);
client.close();
});
});
return server;
});
}
testSuccess(SecureServerSocket server) async {
var log = "";
SecurityContext clientContext = new SecurityContext()
..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
await SecureSocket.connect(HOST, server.port, context: clientContext,
keyLog: (line) {
log += line;
}).then((socket) {
socket.write("Hello server.");
socket.close();
return socket.drain().then((value) {
Expect.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET", log);
return server;
});
});
}
testExceptionInKeyLogFunction(SecureServerSocket server) async {
SecurityContext clientContext = new SecurityContext()
..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
var numCalls = 0;
await SecureSocket.connect(HOST, server.port, context: clientContext,
keyLog: (line) {
++numCalls;
throw FileSystemException("Something bad happened");
}).then((socket) {
socket.close();
return socket.drain().then((value) {
Expect.notEquals(0, numCalls);
return server;
});
});
}
void main() async {
asyncStart();
await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
final server = await startEchoServer();
await testSuccess(server);
await testExceptionInKeyLogFunction(server);
await server.close();
asyncEnd();
}