Support setting the minimum acceptable TLS version.

Change-Id: I24775461fb690abdd0a47c5b4e23776c9fe5bfe0
Bug: https://github.com/dart-lang/sdk/issues/54901
Tested: TLS traffic verified in Wireshark, exception behavior verified with a Python test server, property changes verified by unit test
CoreLibraryReviewExempt: dart:io only
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/365664
Reviewed-by: Brian Quinlan <bquinlan@google.com>
Commit-Queue: Brian Quinlan <bquinlan@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
This commit is contained in:
Brian Quinlan 2024-07-01 21:00:00 +00:00 committed by Commit Queue
parent b963976009
commit 496b3e65f3
6 changed files with 166 additions and 0 deletions

View file

@ -137,6 +137,8 @@ namespace bin {
V(SecurityContext_SetTrustedCertificatesBytes, 3) \
V(SecurityContext_TrustBuiltinRoots, 1) \
V(SecurityContext_SetAllowTlsRenegotiation, 2) \
V(SecurityContext_SetMinimumProtocolVersion, 2) \
V(SecurityContext_GetMinimumProtocolVersion, 1) \
V(SecurityContext_UseCertificateChainBytes, 3) \
V(ServerSocket_Accept, 2) \
V(ServerSocket_CreateBindListen, 7) \

View file

@ -136,6 +136,18 @@ void FUNCTION_NAME(SecurityContext_SetAllowTlsRenegotiation)(
"Secure Sockets unsupported on this platform"));
}
void FUNCTION_NAME(SecurityContext_SetMinimumProtocolVersion)(
Dart_NativeArguments args) {
Dart_ThrowException(DartUtils::NewDartArgumentError(
"Secure Sockets unsupported on this platform"));
}
void FUNCTION_NAME(SecurityContext_GetMinimumProtocolVersion)(
Dart_NativeArguments args) {
Dart_ThrowException(DartUtils::NewDartArgumentError(
"Secure Sockets unsupported on this platform"));
}
void FUNCTION_NAME(SecurityContext_UseCertificateChainBytes)(
Dart_NativeArguments args) {
Dart_ThrowException(DartUtils::NewDartArgumentError(

View file

@ -827,6 +827,8 @@ void FUNCTION_NAME(SecurityContext_Allocate)(Dart_NativeArguments args) {
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);
// If we change the minimum protocol version here, then the documentation
// for `SecurityContext.minimumTlsProtocolVersion` must also be changed.
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_cipher_list(ctx, "HIGH:MEDIUM");
SSLCertContext* context = new SSLCertContext(ctx);
@ -901,6 +903,32 @@ void FUNCTION_NAME(SecurityContext_SetAllowTlsRenegotiation)(
context->set_allow_tls_renegotiation(allow);
}
void FUNCTION_NAME(SecurityContext_SetMinimumProtocolVersion)(
Dart_NativeArguments args) {
SSLCertContext* context = SSLCertContext::GetSecurityContext(args);
Dart_Handle protocol_version_handle =
ThrowIfError(Dart_GetNativeArgument(args, 1));
if (!Dart_IsInteger(protocol_version_handle)) {
Dart_ThrowException(DartUtils::NewDartArgumentError(
"Non-int argument passed to SetMinimumProtocolVersion"));
}
int protocol_version = DartUtils::GetIntegerValue(protocol_version_handle);
if (SSL_CTX_set_min_proto_version(context->context(), protocol_version) ==
0) {
Dart_ThrowException(DartUtils::NewDartArgumentError(
"Invalid protocol version passed to SetMinimumProtocolVersion"));
}
}
void FUNCTION_NAME(SecurityContext_GetMinimumProtocolVersion)(
Dart_NativeArguments args) {
SSLCertContext* context = SSLCertContext::GetSecurityContext(args);
Dart_SetIntegerReturnValue(args,
SSL_CTX_get_min_proto_version(context->context()));
}
void FUNCTION_NAME(X509_Der)(Dart_NativeArguments args) {
Dart_SetReturnValue(args, X509Helper::GetDer(args));
}

View file

@ -227,6 +227,14 @@ base class _SecurityContext extends NativeFieldWrapperClass1
bool get allowLegacyUnsafeRenegotiation => _allowLegacyUnsafeRenegotiation;
set minimumTlsProtocolVersion(TlsProtocolVersion version) {
_setMinimumProtocolVersion(version._version);
}
TlsProtocolVersion get minimumTlsProtocolVersion =>
TlsProtocolVersion._fromProtocolVersionConstant(
_getMinimumProtocolVersion());
@pragma("vm:external-name", "SecurityContext_Allocate")
external void _createNativeContext();
@ -279,6 +287,10 @@ base class _SecurityContext extends NativeFieldWrapperClass1
external void _trustBuiltinRoots();
@pragma("vm:external-name", "SecurityContext_SetAllowTlsRenegotiation")
external void _setAllowTlsRenegotiation(bool allow);
@pragma("vm:external-name", "SecurityContext_SetMinimumProtocolVersion")
external void _setMinimumProtocolVersion(int version);
@pragma("vm:external-name", "SecurityContext_GetMinimumProtocolVersion")
external int _getMinimumProtocolVersion();
}
/**

View file

@ -4,6 +4,32 @@
part of dart.io;
/// A Transport Layer Security (TLS) version.
///
/// Only TLS versions supported by `dart:io` are included.
class TlsProtocolVersion {
/// Transport Layer Security (TLS) Protocol Version 1.2.
///
/// See RFC-5246.
static const tls1_2 = TlsProtocolVersion._(0x0303);
/// Transport Layer Security (TLS) Protocol Version 1.3.
///
/// See RFC-8446.
static const tls1_3 = TlsProtocolVersion._(0x0304);
final int _version;
const TlsProtocolVersion._(this._version);
static TlsProtocolVersion _fromProtocolVersionConstant(int version) =>
switch (version) {
0x0303 => tls1_2,
0x0304 => tls1_3,
_ => throw ArgumentError.value(version, 'version'),
};
}
/// The object containing the certificates to trust when making
/// a secure client connection, and the certificate chain and
/// private key to serve from a secure server.
@ -170,6 +196,18 @@ abstract interface class SecurityContext {
/// where it is known to be safe.
abstract bool allowLegacyUnsafeRenegotiation;
/// The minimum TLS version to use when establishing a secure connection.
///
/// If the peer does not support `minimumTlsProtocolVersion` or later
/// then [SecureSocket.connect] will throw a [TlsException].
///
/// If the value is changed, it will only affect new connections. Existing
/// connections will continue to use the protocol that was negotiated with the
/// peer.
///
/// The default value is [TlsProtocolVersion.tls1_2].
abstract TlsProtocolVersion minimumTlsProtocolVersion;
/// Encodes a set of supported protocols for ALPN/NPN usage.
///
/// The [protocols] list is expected to contain protocols in descending order

View file

@ -0,0 +1,74 @@
// Copyright (c) 2024, 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.
//
// OtherResources=certificates/server_chain.pem
// OtherResources=certificates/server_key.pem
// OtherResources=certificates/trusted_certs.pem
//
// This test does not verify that the value set in `minimumTlsProtocolVersion`
// appears in the supported versions extension as defined in RPC-8446 4.2.1.
import "dart:async";
import 'dart:convert';
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;
});
}
testVersion(SecureServerSocket server, TlsProtocolVersion tlsVersion) async {
// NOTE: this test only verifies that `minimumTlsProtocolVersion` does
// not cause incorrect behavior when used - the server does *not* actually
// verify that the supported versions extension is correctly set.
SecurityContext clientContext = new SecurityContext()
..minimumTlsProtocolVersion = tlsVersion
..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
await SecureSocket.connect(HOST, server.port, context: clientContext)
.then((socket) async {
socket.write("Hello server.");
socket.close();
Expect.isTrue(await utf8.decoder.bind(socket).contains("Hello server."));
});
}
testProperty() {
SecurityContext context = new SecurityContext();
Expect.equals(TlsProtocolVersion.tls1_2, context.minimumTlsProtocolVersion);
context.minimumTlsProtocolVersion = TlsProtocolVersion.tls1_3;
Expect.equals(TlsProtocolVersion.tls1_3, context.minimumTlsProtocolVersion);
}
void main() async {
asyncStart();
await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
final server = await startEchoServer();
testProperty();
await testVersion(server, TlsProtocolVersion.tls1_2);
await testVersion(server, TlsProtocolVersion.tls1_3);
await server.close();
asyncEnd();
}