Skip to content

Commit c286b76

Browse files
brianquinlanCommit Bot
authored and
Commit Bot
committed
Allow sockets to enable TLS renegotiation.
TESTED=unit + manually tested user issue. Bug: #47841 Change-Id: Iad13899135fd34f15abba3a499132d88e7f597dc Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/234821 Reviewed-by: Alexander Aprelev <[email protected]> Commit-Queue: Brian Quinlan <[email protected]>
1 parent 10bbfbe commit c286b76

10 files changed

+224
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@
7474
- Deprecate `SecureSocket.renegotiate` and `RawSecureSocket.renegotiate`,
7575
which were no-ops.
7676

77+
- **Breaking Change** [#48513](https://github.com/dart-lang/sdk/issues/48513):
78+
Add a new `allowLegacyUnsafeRenegotiation` poperty to `SecurityContext`,
79+
which allows TLS renegotiation for client secure sockets.
80+
7781
#### `dart:isolate`
7882

7983
- Add `Isolate.run` to run a function in a new isolate.

runtime/bin/io_natives.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ namespace bin {
137137
V(SecurityContext_SetClientAuthoritiesBytes, 3) \
138138
V(SecurityContext_SetTrustedCertificatesBytes, 3) \
139139
V(SecurityContext_TrustBuiltinRoots, 1) \
140+
V(SecurityContext_SetAllowTlsRenegotiation, 2) \
140141
V(SecurityContext_UseCertificateChainBytes, 3) \
141142
V(ServerSocket_Accept, 2) \
142143
V(ServerSocket_CreateBindListen, 7) \

runtime/bin/secure_socket_filter.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,10 @@ void SSLFilter::Connect(const char* hostname,
504504
SSL_set_bio(ssl_, ssl_side, ssl_side);
505505
SSL_set_mode(ssl_, SSL_MODE_AUTO_RETRY); // TODO(whesse): Is this right?
506506
SSL_set_ex_data(ssl_, filter_ssl_index, this);
507+
508+
if (context->allow_tls_renegotiation()) {
509+
SSL_set_renegotiate_mode(ssl_, ssl_renegotiate_freely);
510+
}
507511
context->RegisterCallbacks(ssl_);
508512
SSL_set_ex_data(ssl_, ssl_cert_context_index, context);
509513

runtime/bin/secure_socket_unsupported.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ void FUNCTION_NAME(SecurityContext_TrustBuiltinRoots)(
130130
"Secure Sockets unsupported on this platform"));
131131
}
132132

133+
void FUNCTION_NAME(SecurityContext_SetAllowTlsRenegotiation)(
134+
Dart_NativeArguments args) {
135+
Dart_ThrowException(DartUtils::NewDartArgumentError(
136+
"Secure Sockets unsupported on this platform"));
137+
}
138+
133139
void FUNCTION_NAME(SecurityContext_UseCertificateChainBytes)(
134140
Dart_NativeArguments args) {
135141
Dart_ThrowException(DartUtils::NewDartArgumentError(

runtime/bin/security_context.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,22 @@ void FUNCTION_NAME(SecurityContext_TrustBuiltinRoots)(
877877
context->TrustBuiltinRoots();
878878
}
879879

880+
void FUNCTION_NAME(SecurityContext_SetAllowTlsRenegotiation)(
881+
Dart_NativeArguments args) {
882+
SSLCertContext* context = SSLCertContext::GetSecurityContext(args);
883+
Dart_Handle allow_tls_handle = ThrowIfError(Dart_GetNativeArgument(args, 1));
884+
885+
ASSERT(context != NULL);
886+
ASSERT(allow_tls_handle != NULL);
887+
888+
if (!Dart_IsBoolean(allow_tls_handle)) {
889+
Dart_ThrowException(DartUtils::NewDartArgumentError(
890+
"Non-boolean argument passed to SetAllowTlsRenegotiation"));
891+
}
892+
bool allow = DartUtils::GetBooleanValue(allow_tls_handle);
893+
context->set_allow_tls_renegotiation(allow);
894+
}
895+
880896
void FUNCTION_NAME(X509_Der)(Dart_NativeArguments args) {
881897
Dart_SetReturnValue(args, X509Helper::GetDer(args));
882898
}

runtime/bin/security_context.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ class SSLCertContext : public ReferenceCounted<SSLCertContext> {
3131
: ReferenceCounted(),
3232
context_(context),
3333
alpn_protocol_string_(nullptr),
34-
trust_builtin_(false) {}
34+
trust_builtin_(false),
35+
allow_tls_renegotiation_(false) {}
3536

3637
~SSLCertContext() {
3738
SSL_CTX_free(context_);
@@ -82,6 +83,11 @@ class SSLCertContext : public ReferenceCounted<SSLCertContext> {
8283

8384
bool trust_builtin() const { return trust_builtin_; }
8485

86+
void set_allow_tls_renegotiation(bool allow) {
87+
allow_tls_renegotiation_ = allow;
88+
}
89+
bool allow_tls_renegotiation() const { return allow_tls_renegotiation_; }
90+
8591
void set_trust_builtin(bool trust_builtin) { trust_builtin_ = trust_builtin; }
8692

8793
void RegisterCallbacks(SSL* ssl);
@@ -112,7 +118,7 @@ class SSLCertContext : public ReferenceCounted<SSLCertContext> {
112118
uint8_t* alpn_protocol_string_;
113119

114120
bool trust_builtin_;
115-
121+
bool allow_tls_renegotiation_;
116122
static bool long_ssl_cert_evaluation_;
117123
static bool bypass_trusting_system_roots_;
118124

sdk/lib/_internal/vm/bin/secure_socket_patch.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,22 @@ class SecurityContext {
209209

210210
class _SecurityContext extends NativeFieldWrapperClass1
211211
implements SecurityContext {
212+
bool _allowLegacyUnsafeRenegotiation = false;
213+
212214
_SecurityContext(bool withTrustedRoots) {
213215
_createNativeContext();
214216
if (withTrustedRoots) {
215217
_trustBuiltinRoots();
216218
}
217219
}
218220

221+
set allowLegacyUnsafeRenegotiation(bool allow) {
222+
_allowLegacyUnsafeRenegotiation = allow;
223+
_setAllowTlsRenegotiation(allow);
224+
}
225+
226+
bool get allowLegacyUnsafeRenegotiation => _allowLegacyUnsafeRenegotiation;
227+
219228
@pragma("vm:external-name", "SecurityContext_Allocate")
220229
external void _createNativeContext();
221230

@@ -266,6 +275,8 @@ class _SecurityContext extends NativeFieldWrapperClass1
266275
external void _setAlpnProtocols(Uint8List protocols, bool isServer);
267276
@pragma("vm:external-name", "SecurityContext_TrustBuiltinRoots")
268277
external void _trustBuiltinRoots();
278+
@pragma("vm:external-name", "SecurityContext_SetAllowTlsRenegotiation")
279+
external void _setAllowTlsRenegotiation(bool allow);
269280
}
270281

271282
/**

sdk/lib/io/security_context.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,16 @@ abstract class SecurityContext {
160160
/// or client connections.
161161
void setAlpnProtocols(List<String> protocols, bool isServer);
162162

163+
/// If `true`, the [SecurityContext] will allow TLS renegotiation.
164+
/// Renegotiation is only supported as a client and the HelloRequest must be
165+
/// received at a quiet point in the application protocol. This is sufficient
166+
/// to support the legacy use case of requesting a new client certificate
167+
/// between an HTTP request and response in (unpipelined) HTTP/1.1.
168+
/// NOTE: Renegotiation is an extremely problematic protocol feature and
169+
/// should only be used to communicate with legacy servers in environments
170+
/// where it is known to be safe.
171+
abstract bool allowLegacyUnsafeRenegotiation;
172+
163173
/// Encodes a set of supported protocols for ALPN/NPN usage.
164174
///
165175
/// The [protocols] list is expected to contain protocols in descending order
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
//
5+
// VMOptions=
6+
// VMOptions=--short_socket_read
7+
// VMOptions=--short_socket_write
8+
// VMOptions=--short_socket_read --short_socket_write
9+
// OtherResources=certificates/server_chain.pem
10+
// OtherResources=certificates/server_key.pem
11+
// OtherResources=certificates/trusted_certs.pem
12+
//
13+
// It is not possible to initiate TLS-renegotiation from a pure-Dart server so
14+
// just test that the `allowLegacyUnsafeRenegotiation` in `SecurityContext`
15+
// does not affect connections that do *not* do renegotiation.
16+
17+
import "dart:async";
18+
import 'dart:convert';
19+
import "dart:io";
20+
21+
import "package:async_helper/async_helper.dart";
22+
import "package:expect/expect.dart";
23+
24+
late InternetAddress HOST;
25+
26+
String localFile(path) => Platform.script.resolve(path).toFilePath();
27+
28+
SecurityContext serverContext = new SecurityContext()
29+
..useCertificateChain(localFile('certificates/server_chain.pem'))
30+
..usePrivateKey(localFile('certificates/server_key.pem'),
31+
password: 'dartdart');
32+
33+
Future<SecureServerSocket> startEchoServer() {
34+
return SecureServerSocket.bind(HOST, 0, serverContext).then((server) {
35+
server.listen((SecureSocket client) {
36+
client.fold<List<int>>(
37+
<int>[], (message, data) => message..addAll(data)).then((message) {
38+
client.add(message);
39+
client.close();
40+
});
41+
});
42+
return server;
43+
});
44+
}
45+
46+
testSuccess(SecureServerSocket server) async {
47+
// NOTE: this test only verifies that `allowLegacyUnsafeRenegotiation` does
48+
// not cause incorrect behavior when enabled - the server does *not* actually
49+
// trigger TLS renegotiation.
50+
SecurityContext clientContext = new SecurityContext()
51+
..allowLegacyUnsafeRenegotiation = true
52+
..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
53+
54+
await SecureSocket.connect(HOST, server.port, context: clientContext)
55+
.then((socket) async {
56+
socket.write("Hello server.");
57+
socket.close();
58+
Expect.isTrue(await utf8.decoder.bind(socket).contains("Hello server."));
59+
});
60+
}
61+
62+
testProperty() {
63+
SecurityContext context = new SecurityContext();
64+
Expect.isFalse(context.allowLegacyUnsafeRenegotiation);
65+
context.allowLegacyUnsafeRenegotiation = true;
66+
Expect.isTrue(context.allowLegacyUnsafeRenegotiation);
67+
context.allowLegacyUnsafeRenegotiation = false;
68+
Expect.isFalse(context.allowLegacyUnsafeRenegotiation);
69+
}
70+
71+
void main() async {
72+
asyncStart();
73+
await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
74+
final server = await startEchoServer();
75+
76+
await testSuccess(server);
77+
testProperty();
78+
79+
await server.close();
80+
asyncEnd();
81+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
//
5+
// VMOptions=
6+
// VMOptions=--short_socket_read
7+
// VMOptions=--short_socket_write
8+
// VMOptions=--short_socket_read --short_socket_write
9+
// OtherResources=certificates/server_chain.pem
10+
// OtherResources=certificates/server_key.pem
11+
// OtherResources=certificates/trusted_certs.pem
12+
//
13+
// It is not possible to initiate TLS-renegotiation from a pure-Dart server so
14+
// just test that the `allowLegacyUnsafeRenegotiation` in `SecurityContext`
15+
// does not affect connections that do *not* do renegotiation.
16+
17+
// @dart = 2.9
18+
19+
import "dart:async";
20+
import 'dart:convert';
21+
import "dart:io";
22+
23+
import "package:async_helper/async_helper.dart";
24+
import "package:expect/expect.dart";
25+
26+
InternetAddress HOST;
27+
28+
String localFile(path) => Platform.script.resolve(path).toFilePath();
29+
30+
SecurityContext serverContext = new SecurityContext()
31+
..useCertificateChain(localFile('certificates/server_chain.pem'))
32+
..usePrivateKey(localFile('certificates/server_key.pem'),
33+
password: 'dartdart');
34+
35+
Future<SecureServerSocket> startEchoServer() {
36+
return SecureServerSocket.bind(HOST, 0, serverContext).then((server) {
37+
server.listen((SecureSocket client) {
38+
client.fold<List<int>>(
39+
<int>[], (message, data) => message..addAll(data)).then((message) {
40+
client.add(message);
41+
client.close();
42+
});
43+
});
44+
return server;
45+
});
46+
}
47+
48+
testSuccess(SecureServerSocket server) async {
49+
// NOTE: this test only verifies that `allowLegacyUnsafeRenegotiation` does
50+
// not cause incorrect behavior when enabled - the server does *not* actually
51+
// trigger TLS renegotiation.
52+
SecurityContext clientContext = new SecurityContext()
53+
..allowLegacyUnsafeRenegotiation = true
54+
..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
55+
56+
await SecureSocket.connect(HOST, server.port, context: clientContext)
57+
.then((socket) async {
58+
socket.write("Hello server.");
59+
socket.close();
60+
Expect.isTrue(await utf8.decoder.bind(socket).contains("Hello server."));
61+
});
62+
}
63+
64+
testProperty() {
65+
SecurityContext context = new SecurityContext();
66+
Expect.isFalse(context.allowLegacyUnsafeRenegotiation);
67+
context.allowLegacyUnsafeRenegotiation = true;
68+
Expect.isTrue(context.allowLegacyUnsafeRenegotiation);
69+
context.allowLegacyUnsafeRenegotiation = false;
70+
Expect.isFalse(context.allowLegacyUnsafeRenegotiation);
71+
}
72+
73+
void main() async {
74+
asyncStart();
75+
await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
76+
final server = await startEchoServer();
77+
78+
await testSuccess(server);
79+
testProperty();
80+
81+
await server.close();
82+
asyncEnd();
83+
}

0 commit comments

Comments
 (0)