blob: 61d9381bfc89874436ca91b9d9189310a9e16810 [file] [log] [blame]
// Copyright (c) 2012, 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.
part of "common_patch.dart";
@patch
class SecureSocket {
@patch
factory SecureSocket._(RawSecureSocket rawSocket) => _SecureSocket(rawSocket);
}
@patch
class _SecureFilter {
@patch
factory _SecureFilter._() => _SecureFilterImpl._();
}
@patch
@pragma("vm:entry-point")
class X509Certificate {
@patch
@pragma("vm:entry-point")
factory X509Certificate._() => _X509CertificateImpl._();
}
class _SecureSocket extends _Socket implements SecureSocket {
_RawSecureSocket? get _raw => super._raw as _RawSecureSocket?;
_SecureSocket(RawSecureSocket raw) : super(raw);
void renegotiate({
bool useSessionCache = true,
bool requestClientCertificate = false,
bool requireClientCertificate = false,
}) {}
X509Certificate? get peerCertificate {
if (_raw == null) {
throw StateError("peerCertificate called on destroyed SecureSocket");
}
return _raw!.peerCertificate;
}
String? get selectedProtocol {
if (_raw == null) {
throw StateError("selectedProtocol called on destroyed SecureSocket");
}
return _raw!.selectedProtocol;
}
}
/**
* _SecureFilterImpl wraps a filter that encrypts and decrypts data travelling
* over an encrypted socket. The filter also handles the handshaking
* and certificate verification.
*
* The filter exposes its input and output buffers as Dart objects that
* are backed by an external C array of bytes, so that both Dart code and
* native code can access the same data.
*/
@pragma("vm:entry-point")
base class _SecureFilterImpl extends NativeFieldWrapperClass1
implements _SecureFilter {
// Performance is improved if a full buffer of plaintext fits
// in the encrypted buffer, when encrypted.
// SIZE and ENCRYPTED_SIZE are referenced from C++.
@pragma("vm:entry-point")
static final int SIZE = 8 * 1024;
@pragma("vm:entry-point")
static final int ENCRYPTED_SIZE = 10 * 1024;
_SecureFilterImpl._() {
buffers = <_ExternalBuffer>[
for (int i = 0; i < _RawSecureSocket.bufferCount; ++i)
_ExternalBuffer(
_RawSecureSocket._isBufferEncrypted(i) ? ENCRYPTED_SIZE : SIZE,
),
];
}
@pragma("vm:external-name", "SecureSocket_Connect")
external void connect(
String hostName,
SecurityContext context,
bool isServer,
bool requestClientCertificate,
bool requireClientCertificate,
Uint8List protocols,
);
void destroy() {
buffers = null;
_destroy();
}
@pragma("vm:external-name", "SecureSocket_Destroy")
external void _destroy();
@pragma("vm:external-name", "SecureSocket_Handshake")
external int _handshake(SendPort replyPort);
@pragma("vm:external-name", "SecureSocket_MarkAsTrusted")
external void _markAsTrusted(int certificatePtr, bool isTrusted);
@pragma("vm:external-name", "SecureSocket_NewX509CertificateWrapper")
external static X509Certificate _newX509CertificateWrapper(
int certificatePtr,
);
Future<bool> handshake() {
Completer<bool> evaluatorCompleter = Completer<bool>();
ReceivePort rpEvaluateResponse = ReceivePort();
rpEvaluateResponse.listen((data) {
List list = data as List;
// incoming messages (bool isTrusted, int certificatePtr) is
// sent by TrustEvaluator native port after system evaluates
// the certificate chain
if (list.length != 2) {
throw Exception("Invalid number of arguments in evaluate response");
}
bool isTrusted = list[0] as bool;
int certificatePtr = list[1] as int;
// Make sure certificatePtr gets released.
X509Certificate certificate = _newX509CertificateWrapper(certificatePtr);
if (!isTrusted) {
if (badCertificateCallback != null) {
try {
isTrusted = badCertificateCallback!(certificate);
} catch (e, st) {
evaluatorCompleter.completeError(e, st);
rpEvaluateResponse.close();
return;
}
}
}
_markAsTrusted(certificatePtr, isTrusted);
evaluatorCompleter.complete(true);
rpEvaluateResponse.close();
});
const int kSslErrorWantCertificateVerify = 16; // ssl.h:558
int handshakeResult;
try {
handshakeResult = _handshake(rpEvaluateResponse.sendPort);
} catch (e, st) {
rpEvaluateResponse.close();
rethrow;
}
if (handshakeResult == kSslErrorWantCertificateVerify) {
return evaluatorCompleter.future;
} else {
// Response is ready, no need for evaluate response receive port
rpEvaluateResponse.close();
return Future<bool>.value(false);
}
}
void rehandshake() => throw UnimplementedError();
int processBuffer(int bufferIndex) => throw UnimplementedError();
@pragma("vm:external-name", "SecureSocket_GetSelectedProtocol")
external String? selectedProtocol();
@pragma("vm:external-name", "SecureSocket_Init")
external void init();
@pragma("vm:external-name", "SecureSocket_PeerCertificate")
external X509Certificate? get peerCertificate;
@pragma("vm:external-name", "SecureSocket_RegisterBadCertificateCallback")
external void _registerBadCertificateCallback(
bool Function(X509Certificate) callback,
);
bool Function(X509Certificate)? badCertificateCallback;
void registerBadCertificateCallback(bool Function(X509Certificate) callback) {
badCertificateCallback = callback;
_registerBadCertificateCallback(callback);
}
@pragma("vm:external-name", "SecureSocket_RegisterHandshakeCompleteCallback")
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();
@pragma("vm:entry-point")
List<_ExternalBuffer>? buffers;
}
@patch
class SecurityContext {
@patch
factory SecurityContext({bool withTrustedRoots = false}) {
return _SecurityContext(withTrustedRoots);
}
@patch
static SecurityContext get defaultContext {
return _SecurityContext.defaultContext;
}
@patch
static bool get alpnSupported => true;
}
base class _SecurityContext extends NativeFieldWrapperClass1
implements SecurityContext {
bool _allowLegacyUnsafeRenegotiation = false;
_SecurityContext(bool withTrustedRoots) {
_createNativeContext();
if (withTrustedRoots) {
_trustBuiltinRoots();
}
}
set allowLegacyUnsafeRenegotiation(bool allow) {
_allowLegacyUnsafeRenegotiation = allow;
_setAllowTlsRenegotiation(allow);
}
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();
static final SecurityContext defaultContext = _SecurityContext(true);
void usePrivateKey(String file, {String? password}) {
List<int> bytes = (File(file)).readAsBytesSync();
usePrivateKeyBytes(bytes, password: password);
}
@pragma("vm:external-name", "SecurityContext_UsePrivateKeyBytes")
external void usePrivateKeyBytes(List<int> keyBytes, {String? password});
void setTrustedCertificates(String file, {String? password}) {
List<int> bytes = (File(file)).readAsBytesSync();
setTrustedCertificatesBytes(bytes, password: password);
}
@pragma("vm:external-name", "SecurityContext_SetTrustedCertificatesBytes")
external void setTrustedCertificatesBytes(
List<int> certBytes, {
String? password,
});
void useCertificateChain(String file, {String? password}) {
List<int> bytes = (File(file)).readAsBytesSync();
useCertificateChainBytes(bytes, password: password);
}
@pragma("vm:external-name", "SecurityContext_UseCertificateChainBytes")
external void useCertificateChainBytes(
List<int> chainBytes, {
String? password,
});
void setClientAuthorities(String file, {String? password}) {
List<int> bytes = (File(file)).readAsBytesSync();
setClientAuthoritiesBytes(bytes, password: password);
}
@pragma("vm:external-name", "SecurityContext_SetClientAuthoritiesBytes")
external void setClientAuthoritiesBytes(
List<int> authCertBytes, {
String? password,
});
void setAlpnProtocols(List<String> protocols, bool isServer) {
Uint8List encodedProtocols = SecurityContext._protocolsToLengthEncoding(
protocols,
);
_setAlpnProtocols(encodedProtocols, isServer);
}
@pragma("vm:external-name", "SecurityContext_SetAlpnProtocols")
external void _setAlpnProtocols(Uint8List protocols, bool isServer);
@pragma("vm:external-name", "SecurityContext_TrustBuiltinRoots")
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();
}
/**
* _X509CertificateImpl wraps an X509 certificate object held by the BoringSSL
* library. It exposes the fields of the certificate object.
*/
base class _X509CertificateImpl extends NativeFieldWrapperClass1
implements X509Certificate {
// The native field must be set manually on a new object, in native code.
// This is done by WrappedX509Certificate in security_context.cc.
_X509CertificateImpl._();
@pragma("vm:external-name", "X509_Der")
external Uint8List get _der;
late final Uint8List der = _der;
@pragma("vm:external-name", "X509_Pem")
external String get _pem;
late final String pem = _pem;
@pragma("vm:external-name", "X509_Sha1")
external Uint8List get _sha1;
late final Uint8List sha1 = _sha1;
@pragma("vm:external-name", "X509_Subject")
external String get subject;
@pragma("vm:external-name", "X509_Issuer")
external String get issuer;
DateTime get startValidity {
return DateTime.fromMillisecondsSinceEpoch(_startValidity(), isUtc: true);
}
DateTime get endValidity {
return DateTime.fromMillisecondsSinceEpoch(_endValidity(), isUtc: true);
}
@pragma("vm:external-name", "X509_StartValidity")
external int _startValidity();
@pragma("vm:external-name", "X509_EndValidity")
external int _endValidity();
}