937a716b09
This is follow-up to https://dart-review.googlesource.com/c/sdk/+/174340, which moved construction of macos certificate list from boringssl structures from worker thread back to main thread, however it seems to have resulted in certificates in certificate list being prematurely released. This cl explicitly passes/retains certificates lists to worker thread to be released after certificate trust is evaluated. Avoid using deprecated SecTrustEvaluate if running on recent versions of OS. Fixes https://github.com/flutter/flutter/issues/73971 TEST=flutter/examples/image_list on mac osx 10.13.6 simulator 10.3.1 Change-Id: I36829a9b236fab2bbbe952314ce277c436ea0e10 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/183620 Commit-Queue: Alexander Aprelev <aam@google.com> Reviewed-by: Siva Annamalai <asiva@google.com>
353 lines
12 KiB
C++
353 lines
12 KiB
C++
// Copyright (c) 2017, 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.
|
|
|
|
#if !defined(DART_IO_SECURE_SOCKET_DISABLED)
|
|
|
|
#include "platform/globals.h"
|
|
#if defined(HOST_OS_MACOS)
|
|
|
|
#include "bin/security_context.h"
|
|
|
|
#include <Availability.h>
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include <Security/SecureTransport.h>
|
|
#include <Security/Security.h>
|
|
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/x509.h>
|
|
|
|
#include "bin/secure_socket_filter.h"
|
|
|
|
namespace dart {
|
|
namespace bin {
|
|
|
|
const intptr_t SSLCertContext::kApproximateSize = sizeof(SSLCertContext);
|
|
|
|
template <typename T>
|
|
class ScopedCFType {
|
|
public:
|
|
explicit ScopedCFType(T obj) : obj_(obj) {}
|
|
|
|
~ScopedCFType() {
|
|
if (obj_ != NULL) {
|
|
CFRelease(obj_);
|
|
}
|
|
}
|
|
|
|
T get() { return obj_; }
|
|
T* ptr() { return &obj_; }
|
|
const T get() const { return obj_; }
|
|
|
|
DART_WARN_UNUSED_RESULT T release() {
|
|
T temp = obj_;
|
|
obj_ = NULL;
|
|
return temp;
|
|
}
|
|
|
|
void set(T obj) { obj_ = obj; }
|
|
|
|
bool operator==(T other) { return other == get(); }
|
|
|
|
bool operator!=(T other) { return other != get(); }
|
|
|
|
private:
|
|
T obj_;
|
|
|
|
DISALLOW_ALLOCATION();
|
|
DISALLOW_COPY_AND_ASSIGN(ScopedCFType);
|
|
};
|
|
|
|
static void releaseObjects(const void* val, void* context) {
|
|
CFRelease(val);
|
|
}
|
|
|
|
template <>
|
|
ScopedCFType<CFMutableArrayRef>::~ScopedCFType() {
|
|
if (obj_ != NULL) {
|
|
CFIndex count = 0;
|
|
CFArrayApplyFunction(obj_, CFRangeMake(0, CFArrayGetCount(obj_)),
|
|
releaseObjects, &count);
|
|
CFRelease(obj_);
|
|
}
|
|
}
|
|
|
|
typedef ScopedCFType<CFMutableArrayRef> ScopedCFMutableArrayRef;
|
|
typedef ScopedCFType<CFDataRef> ScopedCFDataRef;
|
|
typedef ScopedCFType<CFStringRef> ScopedCFStringRef;
|
|
typedef ScopedCFType<SecPolicyRef> ScopedSecPolicyRef;
|
|
typedef ScopedCFType<SecCertificateRef> ScopedSecCertificateRef;
|
|
typedef ScopedCFType<SecTrustRef> ScopedSecTrustRef;
|
|
|
|
const int kNumTrustEvaluateRequestParams = 5;
|
|
|
|
static SecCertificateRef CreateSecCertificateFromX509(X509* cert) {
|
|
if (cert == NULL) {
|
|
return NULL;
|
|
}
|
|
int length = i2d_X509(cert, NULL);
|
|
if (length < 0) {
|
|
return NULL;
|
|
}
|
|
// This can be `std::make_unique<unsigned char[]>(length)` in C++14
|
|
// But the Mac toolchain is still using C++11.
|
|
auto deb_cert = std::unique_ptr<unsigned char[]>(new unsigned char[length]);
|
|
unsigned char* temp = deb_cert.get();
|
|
if (i2d_X509(cert, &temp) != length) {
|
|
return NULL;
|
|
}
|
|
// TODO(bkonyi): we create a copy of the deb_cert here since it's unclear
|
|
// whether or not SecCertificateCreateWithData takes ownership of the CFData.
|
|
// Implementation here:
|
|
// https://opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-55050.2/lib/SecCertificate.cpp.auto.html
|
|
ScopedCFDataRef cert_buf(CFDataCreate(NULL, deb_cert.get(), length));
|
|
return SecCertificateCreateWithData(NULL, cert_buf.get());
|
|
}
|
|
|
|
static ssl_verify_result_t CertificateVerificationCallback(SSL* ssl,
|
|
uint8_t* out_alert) {
|
|
SSLFilter* filter = static_cast<SSLFilter*>(
|
|
SSL_get_ex_data(ssl, SSLFilter::filter_ssl_index));
|
|
SSLCertContext* context = static_cast<SSLCertContext*>(
|
|
SSL_get_ex_data(ssl, SSLFilter::ssl_cert_context_index));
|
|
|
|
const X509TrustState* certificate_trust_state =
|
|
filter->certificate_trust_state();
|
|
if (certificate_trust_state != nullptr) {
|
|
// Callback have been previously called to explicitly evaluate root_cert.
|
|
STACK_OF(X509)* unverified = sk_X509_dup(SSL_get_peer_full_cert_chain(ssl));
|
|
X509* root_cert = nullptr;
|
|
for (uintptr_t i = sk_X509_num(unverified); i > 0; i--) {
|
|
root_cert = sk_X509_shift(unverified);
|
|
if (root_cert == nullptr) {
|
|
break;
|
|
}
|
|
}
|
|
if (certificate_trust_state->x509() == root_cert) {
|
|
return certificate_trust_state->is_trusted() ? ssl_verify_ok
|
|
: ssl_verify_invalid;
|
|
}
|
|
}
|
|
|
|
STACK_OF(X509)* unverified = sk_X509_dup(SSL_get_peer_full_cert_chain(ssl));
|
|
|
|
// Convert BoringSSL formatted certificates to SecCertificate certificates.
|
|
ScopedCFMutableArrayRef cert_chain(NULL);
|
|
X509* root_cert = NULL;
|
|
int num_certs = sk_X509_num(unverified);
|
|
int current_cert = 0;
|
|
cert_chain.set(CFArrayCreateMutable(NULL, num_certs, NULL));
|
|
X509* ca;
|
|
// Look for the last certificate in the chain - it's a root certificate.
|
|
while ((ca = sk_X509_shift(unverified)) != NULL) {
|
|
ScopedSecCertificateRef cert(CreateSecCertificateFromX509(ca));
|
|
if (cert == NULL) {
|
|
return ssl_verify_invalid;
|
|
}
|
|
CFArrayAppendValue(cert_chain.get(), cert.release());
|
|
++current_cert;
|
|
|
|
if (current_cert == num_certs) {
|
|
break;
|
|
}
|
|
}
|
|
ASSERT(current_cert == num_certs);
|
|
root_cert = ca;
|
|
X509_up_ref(ca);
|
|
|
|
SSL_CTX* ssl_ctx = SSL_get_SSL_CTX(ssl);
|
|
X509_STORE* store = SSL_CTX_get_cert_store(ssl_ctx);
|
|
// Convert all trusted certificates provided by the user via
|
|
// setTrustedCertificatesBytes or the command line into SecCertificates.
|
|
ScopedCFMutableArrayRef trusted_certs(CFArrayCreateMutable(NULL, 0, NULL));
|
|
ASSERT(store != NULL);
|
|
|
|
if (store->objs != NULL) {
|
|
for (uintptr_t i = 0; i < sk_X509_OBJECT_num(store->objs); ++i) {
|
|
X509* ca = sk_X509_OBJECT_value(store->objs, i)->data.x509;
|
|
ScopedSecCertificateRef cert(CreateSecCertificateFromX509(ca));
|
|
if (cert == NULL) {
|
|
return ssl_verify_invalid;
|
|
}
|
|
CFArrayAppendValue(trusted_certs.get(), cert.release());
|
|
}
|
|
}
|
|
|
|
// Generate a policy for validating chains for SSL.
|
|
CFStringRef cfhostname = NULL;
|
|
if (filter->hostname() != NULL) {
|
|
cfhostname = CFStringCreateWithCString(NULL, filter->hostname(),
|
|
kCFStringEncodingUTF8);
|
|
}
|
|
ScopedCFStringRef hostname(cfhostname);
|
|
ScopedSecPolicyRef policy(
|
|
SecPolicyCreateSSL(filter->is_client(), hostname.get()));
|
|
|
|
// Create the trust object with the certificates provided by the user.
|
|
ScopedSecTrustRef trust(NULL);
|
|
OSStatus status = SecTrustCreateWithCertificates(cert_chain.get(),
|
|
policy.get(), trust.ptr());
|
|
if (status != noErr) {
|
|
return ssl_verify_invalid;
|
|
}
|
|
|
|
// If the user provided any additional CA certificates, add them to the trust
|
|
// object.
|
|
if (CFArrayGetCount(trusted_certs.get()) > 0) {
|
|
status = SecTrustSetAnchorCertificates(trust.get(), trusted_certs.get());
|
|
if (status != noErr) {
|
|
return ssl_verify_invalid;
|
|
}
|
|
}
|
|
|
|
// Specify whether or not to use the built-in CA certificates for
|
|
// verification.
|
|
status =
|
|
SecTrustSetAnchorCertificatesOnly(trust.get(), !context->trust_builtin());
|
|
if (status != noErr) {
|
|
return ssl_verify_invalid;
|
|
}
|
|
|
|
// TrustEvaluateHandler should release all handles.
|
|
Dart_CObject dart_cobject_trust;
|
|
dart_cobject_trust.type = Dart_CObject_kInt64;
|
|
dart_cobject_trust.value.as_int64 =
|
|
reinterpret_cast<intptr_t>(trust.release());
|
|
|
|
Dart_CObject dart_cobject_cert_chain;
|
|
dart_cobject_cert_chain.type = Dart_CObject_kInt64;
|
|
dart_cobject_cert_chain.value.as_int64 =
|
|
reinterpret_cast<intptr_t>(cert_chain.release());
|
|
|
|
Dart_CObject dart_cobject_trusted_certs;
|
|
dart_cobject_trusted_certs.type = Dart_CObject_kInt64;
|
|
dart_cobject_trusted_certs.value.as_int64 =
|
|
reinterpret_cast<intptr_t>(trusted_certs.release());
|
|
|
|
Dart_CObject dart_cobject_root_cert;
|
|
dart_cobject_root_cert.type = Dart_CObject_kInt64;
|
|
dart_cobject_root_cert.value.as_int64 = reinterpret_cast<intptr_t>(root_cert);
|
|
|
|
Dart_CObject reply_send_port;
|
|
reply_send_port.type = Dart_CObject_kSendPort;
|
|
reply_send_port.value.as_send_port.id = filter->reply_port();
|
|
|
|
Dart_CObject array;
|
|
array.type = Dart_CObject_kArray;
|
|
array.value.as_array.length = kNumTrustEvaluateRequestParams;
|
|
Dart_CObject* values[] = {&dart_cobject_trust, &dart_cobject_cert_chain,
|
|
&dart_cobject_trusted_certs,
|
|
&dart_cobject_root_cert, &reply_send_port};
|
|
array.value.as_array.values = values;
|
|
|
|
Dart_PostCObject(filter->trust_evaluate_reply_port(), &array);
|
|
return ssl_verify_retry;
|
|
}
|
|
|
|
static void postReply(Dart_Port reply_port_id,
|
|
bool success,
|
|
X509* certificate = nullptr) {
|
|
Dart_CObject dart_cobject_success;
|
|
dart_cobject_success.type = Dart_CObject_kBool;
|
|
dart_cobject_success.value.as_bool = success;
|
|
|
|
Dart_CObject dart_cobject_certificate;
|
|
dart_cobject_certificate.type = Dart_CObject_kInt64;
|
|
dart_cobject_certificate.value.as_int64 =
|
|
reinterpret_cast<intptr_t>(certificate);
|
|
|
|
Dart_CObject array;
|
|
array.type = Dart_CObject_kArray;
|
|
array.value.as_array.length = 2;
|
|
Dart_CObject* values[] = {&dart_cobject_success, &dart_cobject_certificate};
|
|
array.value.as_array.values = values;
|
|
|
|
Dart_PostCObject(reply_port_id, &array);
|
|
}
|
|
|
|
static void TrustEvaluateHandler(Dart_Port dest_port_id,
|
|
Dart_CObject* message) {
|
|
// This is used for testing to confirm that trust evaluation doesn't block
|
|
// dart isolate.
|
|
// First sleep exposes problem where ssl data structures are released/freed
|
|
// by main isolate before this handler had a chance to access them.
|
|
// Second sleep(below) is there to maintain same long delay of certificate
|
|
// verification.
|
|
if (SSLCertContext::long_ssl_cert_evaluation()) {
|
|
usleep(2000 * 1000 /* 2 s*/);
|
|
}
|
|
|
|
CObjectArray request(message);
|
|
if (request.Length() != kNumTrustEvaluateRequestParams) {
|
|
FATAL2("Malformed trust evaluate message: got %" Pd
|
|
" parameters "
|
|
"expected %d\n",
|
|
request.Length(), kNumTrustEvaluateRequestParams);
|
|
}
|
|
CObjectIntptr trust_cobject(request[0]);
|
|
ScopedSecTrustRef trust(reinterpret_cast<SecTrustRef>(trust_cobject.Value()));
|
|
CObjectIntptr cert_chain_cobject(request[1]);
|
|
ScopedCFMutableArrayRef cert_chain(
|
|
reinterpret_cast<CFMutableArrayRef>(cert_chain_cobject.Value()));
|
|
CObjectIntptr trusted_certs_cobject(request[2]);
|
|
ScopedCFMutableArrayRef trusted_certs(
|
|
reinterpret_cast<CFMutableArrayRef>(trusted_certs_cobject.Value()));
|
|
CObjectIntptr root_cert_cobject(request[3]);
|
|
X509* root_cert = reinterpret_cast<X509*>(root_cert_cobject.Value());
|
|
CObjectSendPort reply_port(request[4]);
|
|
Dart_Port reply_port_id = reply_port.Value();
|
|
|
|
SecTrustResultType trust_result;
|
|
if (SSLCertContext::long_ssl_cert_evaluation()) {
|
|
usleep(3000 * 1000 /* 3 s*/);
|
|
}
|
|
|
|
OSStatus status = noErr;
|
|
// Perform the certificate verification.
|
|
if (__builtin_available(iOS 12.0, macOS 10.14, *)) {
|
|
// SecTrustEvaluateWithError available as of OSX 10.14 and iOS 12.
|
|
// The result is ignored as we get more information from the following call
|
|
// to SecTrustGetTrustResult which also happens to match the information we
|
|
// get from calling SecTrustEvaluate.
|
|
bool res = SecTrustEvaluateWithError(trust.get(), NULL);
|
|
USE(res);
|
|
status = SecTrustGetTrustResult(trust.get(), &trust_result);
|
|
} else {
|
|
// SecTrustEvaluate is deprecated as of OSX 10.15 and iOS 13.
|
|
status = SecTrustEvaluate(trust.get(), &trust_result);
|
|
}
|
|
|
|
postReply(reply_port_id,
|
|
status == noErr && (trust_result == kSecTrustResultProceed ||
|
|
trust_result == kSecTrustResultUnspecified),
|
|
root_cert);
|
|
}
|
|
|
|
void SSLCertContext::RegisterCallbacks(SSL* ssl) {
|
|
SSL_set_custom_verify(ssl, SSL_VERIFY_PEER, CertificateVerificationCallback);
|
|
}
|
|
|
|
TrustEvaluateHandlerFunc SSLCertContext::GetTrustEvaluateHandler() const {
|
|
return &TrustEvaluateHandler;
|
|
}
|
|
|
|
void SSLCertContext::TrustBuiltinRoots() {
|
|
// First, try to use locations specified on the command line.
|
|
if (root_certs_file() != NULL) {
|
|
LoadRootCertFile(root_certs_file());
|
|
return;
|
|
}
|
|
if (root_certs_cache() != NULL) {
|
|
LoadRootCertCache(root_certs_cache());
|
|
return;
|
|
}
|
|
set_trust_builtin(true);
|
|
}
|
|
|
|
} // namespace bin
|
|
} // namespace dart
|
|
|
|
#endif // defined(HOST_OS_MACOS)
|
|
|
|
#endif // !defined(DART_IO_SECURE_SOCKET_DISABLED)
|