mirror of
https://github.com/dart-lang/sdk
synced 2024-09-20 00:01:59 +00:00
1508 lines
50 KiB
C++
1508 lines
50 KiB
C++
// Copyright (c) 2016, 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_DISABLED) && !defined(DART_IO_SECURE_SOCKET_DISABLED)
|
|
|
|
#include "platform/globals.h"
|
|
#if TARGET_OS_IOS
|
|
|
|
#include "bin/secure_socket.h"
|
|
#include "bin/secure_socket_ios.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/syslimits.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include <Security/SecureTransport.h>
|
|
#include <Security/Security.h>
|
|
|
|
#include "bin/builtin.h"
|
|
#include "bin/dartutils.h"
|
|
#include "bin/lockers.h"
|
|
#include "bin/log.h"
|
|
#include "bin/socket.h"
|
|
#include "bin/thread.h"
|
|
#include "bin/utils.h"
|
|
|
|
#include "platform/text_buffer.h"
|
|
#include "platform/utils.h"
|
|
|
|
#include "include/dart_api.h"
|
|
|
|
// Return the error from the containing function if handle is an error handle.
|
|
#define RETURN_IF_ERROR(handle) \
|
|
{ \
|
|
Dart_Handle __handle = handle; \
|
|
if (Dart_IsError((__handle))) { \
|
|
return __handle; \
|
|
} \
|
|
}
|
|
|
|
namespace dart {
|
|
namespace bin {
|
|
|
|
static const int kSSLFilterNativeFieldIndex = 0;
|
|
static const int kSecurityContextNativeFieldIndex = 0;
|
|
static const int kX509NativeFieldIndex = 0;
|
|
|
|
static const bool SSL_LOG_STATUS = false;
|
|
static const bool SSL_LOG_DATA = false;
|
|
static const bool SSL_LOG_CERTS = false;
|
|
static const int SSL_ERROR_MESSAGE_BUFFER_SIZE = 1000;
|
|
static const intptr_t PEM_BUFSIZE = 1024;
|
|
|
|
static char* CFStringRefToCString(CFStringRef cfstring) {
|
|
CFIndex len = CFStringGetLength(cfstring);
|
|
CFIndex max_len =
|
|
CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;
|
|
char* result = reinterpret_cast<char*>(Dart_ScopeAllocate(max_len));
|
|
ASSERT(result != NULL);
|
|
bool success =
|
|
CFStringGetCString(cfstring, result, max_len, kCFStringEncodingUTF8);
|
|
return success ? result : NULL;
|
|
}
|
|
|
|
|
|
// Handle an error reported from the SecureTransport library.
|
|
static void ThrowIOException(OSStatus status,
|
|
const char* exception_type,
|
|
const char* message) {
|
|
TextBuffer status_message(SSL_ERROR_MESSAGE_BUFFER_SIZE);
|
|
status_message.Printf("OSStatus = %ld: https://www.osstatus.com",
|
|
static_cast<intptr_t>(status));
|
|
OSError os_error_struct(status, status_message.buf(), OSError::kBoringSSL);
|
|
Dart_Handle os_error = DartUtils::NewDartOSError(&os_error_struct);
|
|
Dart_Handle exception =
|
|
DartUtils::NewDartIOException(exception_type, message, os_error);
|
|
ASSERT(!Dart_IsError(exception));
|
|
Dart_ThrowException(exception);
|
|
UNREACHABLE();
|
|
}
|
|
|
|
|
|
static void CheckStatus(OSStatus status,
|
|
const char* type,
|
|
const char* message) {
|
|
if (status == noErr) {
|
|
return;
|
|
}
|
|
ThrowIOException(status, type, message);
|
|
}
|
|
|
|
|
|
static SSLFilter* GetFilter(Dart_NativeArguments args) {
|
|
SSLFilter* filter;
|
|
Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
|
|
ASSERT(Dart_IsInstance(dart_this));
|
|
ThrowIfError(
|
|
Dart_GetNativeInstanceField(dart_this, kSSLFilterNativeFieldIndex,
|
|
reinterpret_cast<intptr_t*>(&filter)));
|
|
return filter;
|
|
}
|
|
|
|
|
|
static void DeleteFilter(void* isolate_data,
|
|
Dart_WeakPersistentHandle handle,
|
|
void* context_pointer) {
|
|
SSLFilter* filter = reinterpret_cast<SSLFilter*>(context_pointer);
|
|
filter->Release();
|
|
}
|
|
|
|
|
|
static Dart_Handle SetFilter(Dart_NativeArguments args, SSLFilter* filter) {
|
|
ASSERT(filter != NULL);
|
|
const int approximate_size_of_filter = 1500;
|
|
Dart_Handle dart_this = Dart_GetNativeArgument(args, 0);
|
|
RETURN_IF_ERROR(dart_this);
|
|
ASSERT(Dart_IsInstance(dart_this));
|
|
Dart_Handle err =
|
|
Dart_SetNativeInstanceField(dart_this, kSSLFilterNativeFieldIndex,
|
|
reinterpret_cast<intptr_t>(filter));
|
|
RETURN_IF_ERROR(err);
|
|
Dart_NewWeakPersistentHandle(dart_this, reinterpret_cast<void*>(filter),
|
|
approximate_size_of_filter, DeleteFilter);
|
|
return Dart_Null();
|
|
}
|
|
|
|
|
|
static SSLCertContext* GetSecurityContext(Dart_NativeArguments args) {
|
|
SSLCertContext* context;
|
|
Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
|
|
ASSERT(Dart_IsInstance(dart_this));
|
|
ThrowIfError(
|
|
Dart_GetNativeInstanceField(dart_this, kSecurityContextNativeFieldIndex,
|
|
reinterpret_cast<intptr_t*>(&context)));
|
|
return context;
|
|
}
|
|
|
|
|
|
static void DeleteCertContext(void* isolate_data,
|
|
Dart_WeakPersistentHandle handle,
|
|
void* context_pointer) {
|
|
SSLCertContext* context = static_cast<SSLCertContext*>(context_pointer);
|
|
context->Release();
|
|
}
|
|
|
|
|
|
static Dart_Handle SetSecurityContext(Dart_NativeArguments args,
|
|
SSLCertContext* context) {
|
|
const int approximate_size_of_context = 1500;
|
|
Dart_Handle dart_this = Dart_GetNativeArgument(args, 0);
|
|
RETURN_IF_ERROR(dart_this);
|
|
ASSERT(Dart_IsInstance(dart_this));
|
|
Dart_Handle err =
|
|
Dart_SetNativeInstanceField(dart_this, kSecurityContextNativeFieldIndex,
|
|
reinterpret_cast<intptr_t>(context));
|
|
RETURN_IF_ERROR(err);
|
|
Dart_NewWeakPersistentHandle(dart_this, context, approximate_size_of_context,
|
|
DeleteCertContext);
|
|
return Dart_Null();
|
|
}
|
|
|
|
|
|
static SecCertificateRef GetX509Certificate(Dart_NativeArguments args) {
|
|
SecCertificateRef certificate;
|
|
Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
|
|
ASSERT(Dart_IsInstance(dart_this));
|
|
ThrowIfError(
|
|
Dart_GetNativeInstanceField(dart_this, kX509NativeFieldIndex,
|
|
reinterpret_cast<intptr_t*>(&certificate)));
|
|
return certificate;
|
|
}
|
|
|
|
|
|
static void ReleaseCertificate(void* isolate_data,
|
|
Dart_WeakPersistentHandle handle,
|
|
void* context_pointer) {
|
|
SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(context_pointer);
|
|
CFRelease(cert);
|
|
}
|
|
|
|
|
|
static Dart_Handle WrappedX509Certificate(SecCertificateRef certificate) {
|
|
const intptr_t approximate_size_of_certificate = 1500;
|
|
if (certificate == NULL) {
|
|
return Dart_Null();
|
|
}
|
|
Dart_Handle x509_type =
|
|
DartUtils::GetDartType(DartUtils::kIOLibURL, "X509Certificate");
|
|
if (Dart_IsError(x509_type)) {
|
|
return x509_type;
|
|
}
|
|
Dart_Handle arguments[] = {NULL};
|
|
|
|
Dart_Handle result =
|
|
Dart_New(x509_type, DartUtils::NewString("_"), 0, arguments);
|
|
if (Dart_IsError(result)) {
|
|
return result;
|
|
}
|
|
ASSERT(Dart_IsInstance(result));
|
|
|
|
// CFRetain in case the returned Dart object outlives the SecurityContext.
|
|
// CFRelease is in the Dart object's finalizer
|
|
CFRetain(certificate);
|
|
Dart_NewWeakPersistentHandle(result, reinterpret_cast<void*>(certificate),
|
|
approximate_size_of_certificate,
|
|
ReleaseCertificate);
|
|
|
|
Dart_Handle status = Dart_SetNativeInstanceField(
|
|
result, kX509NativeFieldIndex, reinterpret_cast<intptr_t>(certificate));
|
|
if (Dart_IsError(status)) {
|
|
return status;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
static const char* GetPasswordArgument(Dart_NativeArguments args,
|
|
intptr_t index) {
|
|
Dart_Handle password_object =
|
|
ThrowIfError(Dart_GetNativeArgument(args, index));
|
|
const char* password = NULL;
|
|
if (Dart_IsString(password_object)) {
|
|
ThrowIfError(Dart_StringToCString(password_object, &password));
|
|
if (strlen(password) > PEM_BUFSIZE - 1) {
|
|
Dart_ThrowException(DartUtils::NewDartArgumentError(
|
|
"Password length is greater than 1023 bytes."));
|
|
}
|
|
} else if (Dart_IsNull(password_object)) {
|
|
password = "";
|
|
} else {
|
|
Dart_ThrowException(
|
|
DartUtils::NewDartArgumentError("Password is not a String or null"));
|
|
}
|
|
return password;
|
|
}
|
|
|
|
|
|
static OSStatus TryPKCS12Import(CFDataRef cfdata,
|
|
CFStringRef password,
|
|
CFArrayRef* out_certs,
|
|
SecIdentityRef* out_identity) {
|
|
const void* keys[] = {kSecImportExportPassphrase};
|
|
const void* values[] = {password};
|
|
CFDictionaryRef params =
|
|
CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
|
|
CFArrayRef items = NULL;
|
|
OSStatus status = SecPKCS12Import(cfdata, params, &items);
|
|
CFRelease(params);
|
|
|
|
if (status != noErr) {
|
|
if (SSL_LOG_STATUS) {
|
|
Log::PrintErr("SecPKCS12Import: status = %ld",
|
|
static_cast<intptr_t>(status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
CFIndex items_length = (items == NULL) ? 0 : CFArrayGetCount(items);
|
|
if (SSL_LOG_CERTS) {
|
|
Log::PrintErr("TryPKCS12Import succeeded, count = %ld\n", items_length);
|
|
}
|
|
|
|
// Empty list indicates a decoding failure of some sort.
|
|
if ((items != NULL) && (items_length == 0)) {
|
|
CFRelease(items);
|
|
return errSSLBadCert;
|
|
}
|
|
|
|
CFMutableArrayRef result_certs =
|
|
CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
|
|
SecIdentityRef result_identity = NULL;
|
|
|
|
for (CFIndex i = 0; i < items_length; i++) {
|
|
CFTypeRef item =
|
|
reinterpret_cast<CFTypeRef>(CFArrayGetValueAtIndex(items, i));
|
|
ASSERT(CFGetTypeID(item) == CFDictionaryGetTypeID());
|
|
CFDictionaryRef dict = reinterpret_cast<CFDictionaryRef>(item);
|
|
|
|
// Trust.
|
|
CFTypeRef trust_item = CFDictionaryGetValue(dict, kSecImportItemTrust);
|
|
if (trust_item != NULL) {
|
|
ASSERT(CFGetTypeID(trust_item) == SecTrustGetTypeID());
|
|
if (SSL_LOG_CERTS) {
|
|
Log::PrintErr("\titem %ld has a trust object\n", i);
|
|
}
|
|
// TODO(zra): Is this useful for anything?
|
|
}
|
|
|
|
// Identity.
|
|
CFTypeRef identity_item =
|
|
CFDictionaryGetValue(dict, kSecImportItemIdentity);
|
|
if (identity_item != NULL) {
|
|
ASSERT(CFGetTypeID(identity_item) == SecIdentityGetTypeID());
|
|
if (SSL_LOG_CERTS) {
|
|
Log::PrintErr("\titem %ld has an identity object\n", i);
|
|
}
|
|
// Only extract the first identity we find.
|
|
if (result_identity == NULL) {
|
|
result_identity =
|
|
reinterpret_cast<SecIdentityRef>(const_cast<void*>(identity_item));
|
|
CFRetain(result_identity);
|
|
}
|
|
}
|
|
|
|
// Certificates.
|
|
CFTypeRef cert_items = CFDictionaryGetValue(dict, kSecImportItemCertChain);
|
|
if (cert_items != NULL) {
|
|
ASSERT(CFGetTypeID(cert_items) == CFArrayGetTypeID());
|
|
CFArrayRef certs = reinterpret_cast<CFArrayRef>(cert_items);
|
|
if (SSL_LOG_CERTS) {
|
|
CFIndex count = CFArrayGetCount(certs);
|
|
Log::PrintErr("\titem %ld has a cert chain %ld certs long\n", i, count);
|
|
}
|
|
CFArrayAppendArray(result_certs, certs,
|
|
CFRangeMake(0, CFArrayGetCount(certs)));
|
|
}
|
|
}
|
|
|
|
if (out_certs == NULL) {
|
|
if (result_certs != NULL) {
|
|
CFRelease(result_certs);
|
|
}
|
|
} else {
|
|
*out_certs = result_certs;
|
|
}
|
|
|
|
if (out_identity == NULL) {
|
|
if (result_identity != NULL) {
|
|
CFRelease(result_identity);
|
|
}
|
|
} else {
|
|
*out_identity = result_identity;
|
|
}
|
|
|
|
// On failure, don't return any objects.
|
|
ASSERT((status == noErr) ||
|
|
((result_certs == NULL) && (result_identity == NULL)));
|
|
return status;
|
|
}
|
|
|
|
|
|
static OSStatus ExtractSecItems(uint8_t* buffer,
|
|
intptr_t length,
|
|
const char* password,
|
|
CFArrayRef* out_certs,
|
|
SecIdentityRef* out_identity) {
|
|
ASSERT(buffer != NULL);
|
|
ASSERT(password != NULL);
|
|
OSStatus status = noErr;
|
|
|
|
CFDataRef cfdata =
|
|
CFDataCreateWithBytesNoCopy(NULL, buffer, length, kCFAllocatorNull);
|
|
CFStringRef cfpassword = CFStringCreateWithCStringNoCopy(
|
|
NULL, password, kCFStringEncodingUTF8, kCFAllocatorNull);
|
|
ASSERT(cfdata != NULL);
|
|
ASSERT(cfpassword != NULL);
|
|
|
|
status = TryPKCS12Import(cfdata, cfpassword, out_certs, out_identity);
|
|
|
|
CFRelease(cfdata);
|
|
CFRelease(cfpassword);
|
|
return status;
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_Init)(Dart_NativeArguments args) {
|
|
Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
|
|
SSLFilter* filter = new SSLFilter(); // Deleted in DeleteFilter finalizer.
|
|
Dart_Handle err = SetFilter(args, filter);
|
|
if (Dart_IsError(err)) {
|
|
filter->Release();
|
|
Dart_PropagateError(err);
|
|
}
|
|
err = filter->Init(dart_this);
|
|
if (Dart_IsError(err)) {
|
|
// The finalizer was set up by SetFilter. It will delete `filter` if there
|
|
// is an error.
|
|
filter->Destroy();
|
|
Dart_PropagateError(err);
|
|
}
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_Connect)(Dart_NativeArguments args) {
|
|
Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
|
|
Dart_Handle host_name_object = ThrowIfError(Dart_GetNativeArgument(args, 1));
|
|
Dart_Handle context_object = ThrowIfError(Dart_GetNativeArgument(args, 2));
|
|
bool is_server = DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 3));
|
|
bool request_client_certificate =
|
|
DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 4));
|
|
bool require_client_certificate =
|
|
DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 5));
|
|
|
|
const char* host_name = NULL;
|
|
// TODO(whesse): Is truncating a Dart string containing \0 what we want?
|
|
ThrowIfError(Dart_StringToCString(host_name_object, &host_name));
|
|
|
|
SSLCertContext* context = NULL;
|
|
if (!Dart_IsNull(context_object)) {
|
|
ThrowIfError(Dart_GetNativeInstanceField(
|
|
context_object, kSecurityContextNativeFieldIndex,
|
|
reinterpret_cast<intptr_t*>(&context)));
|
|
}
|
|
|
|
GetFilter(args)->Connect(dart_this, host_name, context, is_server,
|
|
request_client_certificate,
|
|
require_client_certificate);
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_Destroy)(Dart_NativeArguments args) {
|
|
SSLFilter* filter = GetFilter(args);
|
|
// The SSLFilter is deleted in the finalizer for the Dart object created by
|
|
// SetFilter. There is no need to NULL-out the native field for the SSLFilter
|
|
// here because the SSLFilter won't be deleted until the finalizer for the
|
|
// Dart object runs while the Dart object is being GCd. This approach avoids a
|
|
// leak if Destroy isn't called, and avoids a NULL-dereference if Destroy is
|
|
// called more than once.
|
|
filter->Destroy();
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_Handshake)(Dart_NativeArguments args) {
|
|
OSStatus status = GetFilter(args)->CheckHandshake();
|
|
CheckStatus(status, "HandshakeException", "Handshake error");
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_GetSelectedProtocol)(
|
|
Dart_NativeArguments args) {
|
|
Dart_SetReturnValue(args, Dart_Null());
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_Renegotiate)(Dart_NativeArguments args) {
|
|
bool use_session_cache =
|
|
DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 1));
|
|
bool request_client_certificate =
|
|
DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 2));
|
|
bool require_client_certificate =
|
|
DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 3));
|
|
GetFilter(args)->Renegotiate(use_session_cache, request_client_certificate,
|
|
require_client_certificate);
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_RegisterHandshakeCompleteCallback)(
|
|
Dart_NativeArguments args) {
|
|
Dart_Handle handshake_complete =
|
|
ThrowIfError(Dart_GetNativeArgument(args, 1));
|
|
if (!Dart_IsClosure(handshake_complete)) {
|
|
Dart_ThrowException(DartUtils::NewDartArgumentError(
|
|
"Illegal argument to RegisterHandshakeCompleteCallback"));
|
|
}
|
|
GetFilter(args)->RegisterHandshakeCompleteCallback(handshake_complete);
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_RegisterBadCertificateCallback)(
|
|
Dart_NativeArguments args) {
|
|
Dart_Handle callback = ThrowIfError(Dart_GetNativeArgument(args, 1));
|
|
if (!Dart_IsClosure(callback) && !Dart_IsNull(callback)) {
|
|
Dart_ThrowException(DartUtils::NewDartArgumentError(
|
|
"Illegal argument to RegisterBadCertificateCallback"));
|
|
}
|
|
GetFilter(args)->RegisterBadCertificateCallback(callback);
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_PeerCertificate)(Dart_NativeArguments args) {
|
|
Dart_SetReturnValue(args, GetFilter(args)->PeerCertificate());
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_FilterPointer)(Dart_NativeArguments args) {
|
|
SSLFilter* filter = GetFilter(args);
|
|
// This filter pointer is passed to the IO Service thread. The IO Service
|
|
// thread must Release() the pointer when it is done with it.
|
|
filter->Retain();
|
|
intptr_t filter_pointer = reinterpret_cast<intptr_t>(filter);
|
|
Dart_SetReturnValue(args, Dart_NewInteger(filter_pointer));
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecurityContext_Allocate)(Dart_NativeArguments args) {
|
|
SSLCertContext* cert_context = new SSLCertContext();
|
|
// cert_context deleted in DeleteCertContext finalizer.
|
|
Dart_Handle err = SetSecurityContext(args, cert_context);
|
|
if (Dart_IsError(err)) {
|
|
cert_context->Release();
|
|
Dart_PropagateError(err);
|
|
}
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecurityContext_UsePrivateKeyBytes)(
|
|
Dart_NativeArguments args) {
|
|
SSLCertContext* context = GetSecurityContext(args);
|
|
const char* password = GetPasswordArgument(args, 2);
|
|
|
|
OSStatus status;
|
|
CFArrayRef cert_chain = NULL;
|
|
SecIdentityRef identity = NULL;
|
|
{
|
|
ScopedMemBuffer buffer(ThrowIfError(Dart_GetNativeArgument(args, 1)));
|
|
status = ExtractSecItems(buffer.get(), buffer.length(), password,
|
|
&cert_chain, &identity);
|
|
}
|
|
|
|
// Set the context fields. Repeated calls to usePrivateKeyBytes are an error.
|
|
bool set_failure = false;
|
|
if ((identity != NULL) && !context->set_identity(identity)) {
|
|
CFRelease(identity);
|
|
if (cert_chain != NULL) {
|
|
CFRelease(cert_chain);
|
|
}
|
|
set_failure = true;
|
|
}
|
|
|
|
// We can't have set a cert_chain without also having set an identity.
|
|
// That is, if context->set_identity() succeeds, then it is impossible for
|
|
// context->set_cert_chain() to fail. This is because SecPKCS12Import never
|
|
// returns a cert chain without also returning a private key.
|
|
ASSERT(set_failure || (context->cert_chain() == NULL));
|
|
if (!set_failure && (cert_chain != NULL)) {
|
|
context->set_cert_chain(cert_chain);
|
|
}
|
|
|
|
if (set_failure) {
|
|
Dart_ThrowException(DartUtils::NewDartArgumentError(
|
|
"usePrivateKeyBytes has already been called on the given context."));
|
|
}
|
|
CheckStatus(status, "TlsException", "Failure in usePrivateKeyBytes");
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecurityContext_SetTrustedCertificatesBytes)(
|
|
Dart_NativeArguments args) {
|
|
SSLCertContext* context = GetSecurityContext(args);
|
|
|
|
OSStatus status = noErr;
|
|
SecCertificateRef cert = NULL;
|
|
{
|
|
ScopedMemBuffer buffer(ThrowIfError(Dart_GetNativeArgument(args, 1)));
|
|
CFDataRef cfdata = CFDataCreateWithBytesNoCopy(
|
|
NULL, buffer.get(), buffer.length(), kCFAllocatorNull);
|
|
cert = SecCertificateCreateWithData(NULL, cfdata);
|
|
CFRelease(cfdata);
|
|
}
|
|
|
|
// Add the certs to the context.
|
|
if (cert != NULL) {
|
|
context->add_trusted_cert(cert);
|
|
} else {
|
|
status = errSSLBadCert;
|
|
}
|
|
CheckStatus(status, "TlsException", "Failure in setTrustedCertificatesBytes");
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecurityContext_AlpnSupported)(Dart_NativeArguments args) {
|
|
Dart_SetReturnValue(args, Dart_NewBoolean(false));
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecurityContext_TrustBuiltinRoots)(
|
|
Dart_NativeArguments args) {
|
|
SSLCertContext* context = GetSecurityContext(args);
|
|
context->set_trust_builtin(true);
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecurityContext_UseCertificateChainBytes)(
|
|
Dart_NativeArguments args) {
|
|
// This is a no-op on iOS. We get the cert chain along with the private key
|
|
// in UsePrivateyKeyBytes().
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecurityContext_SetClientAuthoritiesBytes)(
|
|
Dart_NativeArguments args) {
|
|
Dart_ThrowException(DartUtils::NewDartUnsupportedError(
|
|
"SecurityContext.setClientAuthoritiesBytes is not supported on this "
|
|
"platform."));
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecurityContext_SetAlpnProtocols)(
|
|
Dart_NativeArguments args) {
|
|
Dart_ThrowException(DartUtils::NewDartUnsupportedError(
|
|
"ALPN is not supported on this platform"));
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(X509_Subject)(Dart_NativeArguments args) {
|
|
SecCertificateRef certificate = GetX509Certificate(args);
|
|
CFStringRef cfsubject = SecCertificateCopySubjectSummary(certificate);
|
|
if (cfsubject != NULL) {
|
|
char* csubject = CFStringRefToCString(cfsubject);
|
|
CFRelease(cfsubject);
|
|
Dart_SetReturnValue(args, Dart_NewStringFromCString(csubject));
|
|
} else {
|
|
Dart_ThrowException(DartUtils::NewDartArgumentError(
|
|
"X509.subject failed to find subject's common name."));
|
|
}
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(X509_Issuer)(Dart_NativeArguments args) {
|
|
Dart_ThrowException(DartUtils::NewDartUnsupportedError(
|
|
"X509Certificate.issuer is not supported on this platform."));
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(X509_StartValidity)(Dart_NativeArguments args) {
|
|
Dart_ThrowException(DartUtils::NewDartUnsupportedError(
|
|
"X509Certificate.startValidity is not supported on this platform."));
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(X509_EndValidity)(Dart_NativeArguments args) {
|
|
Dart_ThrowException(DartUtils::NewDartUnsupportedError(
|
|
"X509Certificate.endValidity is not supported on this platform."));
|
|
}
|
|
|
|
|
|
// Pushes data through the SSL filter, reading and writing from circular
|
|
// buffers shared with Dart. Called from the IOService thread.
|
|
//
|
|
// The Dart _SecureFilterImpl class contains 4 ExternalByteArrays used to
|
|
// pass encrypted and plaintext data to and from the C++ SSLFilter object.
|
|
//
|
|
// ProcessFilter is called with a CObject array containing the pointer to
|
|
// the SSLFilter, encoded as an int, and the start and end positions of the
|
|
// valid data in the four circular buffers. The function only reads from
|
|
// the valid data area of the input buffers, and only writes to the free
|
|
// area of the output buffers. The function returns the new start and end
|
|
// positions in the buffers, but it only updates start for input buffers, and
|
|
// end for output buffers. Therefore, the Dart thread can simultaneously
|
|
// write to the free space and end pointer of input buffers, and read from
|
|
// the data space of output buffers, and modify the start pointer.
|
|
//
|
|
// When ProcessFilter returns, the Dart thread is responsible for combining
|
|
// the updated pointers from Dart and C++, to make the new valid state of
|
|
// the circular buffer.
|
|
CObject* SSLFilter::ProcessFilterRequest(const CObjectArray& request) {
|
|
CObjectIntptr filter_object(request[0]);
|
|
SSLFilter* filter = reinterpret_cast<SSLFilter*>(filter_object.Value());
|
|
RefCntReleaseScope<SSLFilter> rs(filter);
|
|
|
|
bool in_handshake = CObjectBool(request[1]).Value();
|
|
intptr_t starts[SSLFilter::kNumBuffers];
|
|
intptr_t ends[SSLFilter::kNumBuffers];
|
|
for (intptr_t i = 0; i < SSLFilter::kNumBuffers; ++i) {
|
|
starts[i] = CObjectInt32(request[2 * i + 2]).Value();
|
|
ends[i] = CObjectInt32(request[2 * i + 3]).Value();
|
|
}
|
|
|
|
OSStatus status = filter->ProcessAllBuffers(starts, ends, in_handshake);
|
|
if (status == noErr) {
|
|
CObjectArray* result =
|
|
new CObjectArray(CObject::NewArray(SSLFilter::kNumBuffers * 2));
|
|
for (intptr_t i = 0; i < SSLFilter::kNumBuffers; ++i) {
|
|
result->SetAt(2 * i, new CObjectInt32(CObject::NewInt32(starts[i])));
|
|
result->SetAt(2 * i + 1, new CObjectInt32(CObject::NewInt32(ends[i])));
|
|
}
|
|
return result;
|
|
} else {
|
|
TextBuffer status_message(SSL_ERROR_MESSAGE_BUFFER_SIZE);
|
|
status_message.Printf("OSStatus = %ld: https://www.osstatus.com",
|
|
static_cast<intptr_t>(status));
|
|
CObjectArray* result = new CObjectArray(CObject::NewArray(2));
|
|
result->SetAt(0, new CObjectInt32(CObject::NewInt32(status)));
|
|
result->SetAt(1,
|
|
new CObjectString(CObject::NewString(status_message.buf())));
|
|
return result;
|
|
}
|
|
}
|
|
|
|
|
|
// Usually buffer_starts_ and buffer_ends_ are populated by ProcessAllBuffers,
|
|
// called from ProcessFilterRequest, called from the IOService thread.
|
|
// However, the first call to SSLHandshake comes from the Dart thread, and so
|
|
// doesn't go through there. This results in calls to SSLReadCallback and
|
|
// SSLWriteCallback in which buffer_starts_ and buffer_ends_ haven't been set
|
|
// up. In that case, since we're coming from Dart anyway, we can access the
|
|
// fieds directly from the Dart objects.
|
|
intptr_t SSLFilter::GetBufferStart(intptr_t idx) const {
|
|
if (buffer_starts_[idx] != NULL) {
|
|
return *buffer_starts_[idx];
|
|
}
|
|
Dart_Handle buffer_handle =
|
|
ThrowIfError(Dart_HandleFromPersistent(dart_buffer_objects_[idx]));
|
|
Dart_Handle start_handle =
|
|
ThrowIfError(Dart_GetField(buffer_handle, DartUtils::NewString("start")));
|
|
int64_t start = DartUtils::GetIntegerValue(start_handle);
|
|
return static_cast<intptr_t>(start);
|
|
}
|
|
|
|
|
|
intptr_t SSLFilter::GetBufferEnd(intptr_t idx) const {
|
|
if (buffer_ends_[idx] != NULL) {
|
|
return *buffer_ends_[idx];
|
|
}
|
|
Dart_Handle buffer_handle =
|
|
ThrowIfError(Dart_HandleFromPersistent(dart_buffer_objects_[idx]));
|
|
Dart_Handle end_handle =
|
|
ThrowIfError(Dart_GetField(buffer_handle, DartUtils::NewString("end")));
|
|
int64_t end = DartUtils::GetIntegerValue(end_handle);
|
|
return static_cast<intptr_t>(end);
|
|
}
|
|
|
|
|
|
void SSLFilter::SetBufferStart(intptr_t idx, intptr_t value) {
|
|
if (buffer_starts_[idx] != NULL) {
|
|
*buffer_starts_[idx] = value;
|
|
return;
|
|
}
|
|
Dart_Handle buffer_handle =
|
|
ThrowIfError(Dart_HandleFromPersistent(dart_buffer_objects_[idx]));
|
|
ThrowIfError(DartUtils::SetIntegerField(buffer_handle, "start",
|
|
static_cast<int64_t>(value)));
|
|
}
|
|
|
|
|
|
void SSLFilter::SetBufferEnd(intptr_t idx, intptr_t value) {
|
|
if (buffer_ends_[idx] != NULL) {
|
|
*buffer_ends_[idx] = value;
|
|
return;
|
|
}
|
|
Dart_Handle buffer_handle =
|
|
ThrowIfError(Dart_HandleFromPersistent(dart_buffer_objects_[idx]));
|
|
ThrowIfError(DartUtils::SetIntegerField(buffer_handle, "end",
|
|
static_cast<int64_t>(value)));
|
|
}
|
|
|
|
|
|
OSStatus SSLFilter::ProcessAllBuffers(intptr_t starts[kNumBuffers],
|
|
intptr_t ends[kNumBuffers],
|
|
bool in_handshake) {
|
|
for (intptr_t i = 0; i < kNumBuffers; ++i) {
|
|
buffer_starts_[i] = &starts[i];
|
|
buffer_ends_[i] = &ends[i];
|
|
}
|
|
|
|
if (in_handshake) {
|
|
OSStatus status = Handshake();
|
|
if (status != noErr) {
|
|
return status;
|
|
}
|
|
} else {
|
|
for (intptr_t i = 0; i < kNumBuffers; ++i) {
|
|
intptr_t start = starts[i];
|
|
intptr_t end = ends[i];
|
|
intptr_t size =
|
|
isBufferEncrypted(i) ? encrypted_buffer_size_ : buffer_size_;
|
|
if (start < 0 || end < 0 || start >= size || end >= size) {
|
|
FATAL("Out-of-bounds internal buffer access in dart:io SecureSocket");
|
|
}
|
|
switch (i) {
|
|
case kReadPlaintext:
|
|
// Write data to the circular buffer's free space. If the buffer
|
|
// is full, neither if statement is executed and nothing happens.
|
|
if (start <= end) {
|
|
// If the free space may be split into two segments,
|
|
// then the first is [end, size), unless start == 0.
|
|
// Then, since the last free byte is at position start - 2,
|
|
// the interval is [end, size - 1).
|
|
intptr_t buffer_end = (start == 0) ? size - 1 : size;
|
|
intptr_t bytes = 0;
|
|
OSStatus status =
|
|
ProcessReadPlaintextBuffer(end, buffer_end, &bytes);
|
|
if ((status != noErr) && (status != errSSLWouldBlock)) {
|
|
return status;
|
|
}
|
|
end += bytes;
|
|
ASSERT(end <= size);
|
|
if (end == size) {
|
|
end = 0;
|
|
}
|
|
}
|
|
if (start > end + 1) {
|
|
intptr_t bytes = 0;
|
|
OSStatus status =
|
|
ProcessReadPlaintextBuffer(end, start - 1, &bytes);
|
|
if ((status != noErr) && (status != errSSLWouldBlock)) {
|
|
return status;
|
|
}
|
|
end += bytes;
|
|
ASSERT(end < start);
|
|
}
|
|
ends[i] = end;
|
|
break;
|
|
case kWritePlaintext:
|
|
// Read/Write data from circular buffer. If the buffer is empty,
|
|
// neither if statement's condition is true.
|
|
if (end < start) {
|
|
// Data may be split into two segments. In this case,
|
|
// the first is [start, size).
|
|
intptr_t bytes = 0;
|
|
OSStatus status = ProcessWritePlaintextBuffer(start, size, &bytes);
|
|
if ((status != noErr) && (status != errSSLWouldBlock)) {
|
|
return status;
|
|
}
|
|
start += bytes;
|
|
ASSERT(start <= size);
|
|
if (start == size) {
|
|
start = 0;
|
|
}
|
|
}
|
|
if (start < end) {
|
|
intptr_t bytes = 0;
|
|
OSStatus status = ProcessWritePlaintextBuffer(start, end, &bytes);
|
|
if ((status != noErr) && (status != errSSLWouldBlock)) {
|
|
return status;
|
|
}
|
|
start += bytes;
|
|
ASSERT(start <= end);
|
|
}
|
|
starts[i] = start;
|
|
break;
|
|
case kReadEncrypted:
|
|
case kWriteEncrypted:
|
|
// These buffers are advanced through SSLReadCallback and
|
|
// SSLWriteCallback, which are called from SSLRead, SSLWrite, and
|
|
// SSLHandshake.
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
}
|
|
|
|
for (intptr_t i = 0; i < kNumBuffers; ++i) {
|
|
buffer_starts_[i] = NULL;
|
|
buffer_ends_[i] = NULL;
|
|
}
|
|
return noErr;
|
|
}
|
|
|
|
|
|
Dart_Handle SSLFilter::Init(Dart_Handle dart_this) {
|
|
ASSERT(string_start_ == NULL);
|
|
string_start_ = Dart_NewPersistentHandle(DartUtils::NewString("start"));
|
|
ASSERT(string_start_ != NULL);
|
|
ASSERT(string_length_ == NULL);
|
|
string_length_ = Dart_NewPersistentHandle(DartUtils::NewString("length"));
|
|
ASSERT(string_length_ != NULL);
|
|
ASSERT(bad_certificate_callback_ == NULL);
|
|
bad_certificate_callback_ = Dart_NewPersistentHandle(Dart_Null());
|
|
ASSERT(bad_certificate_callback_ != NULL);
|
|
|
|
// Caller handles cleanup on an error.
|
|
return InitializeBuffers(dart_this);
|
|
}
|
|
|
|
|
|
Dart_Handle SSLFilter::InitializeBuffers(Dart_Handle dart_this) {
|
|
// Create SSLFilter buffers as ExternalUint8Array objects.
|
|
Dart_Handle buffers_string = DartUtils::NewString("buffers");
|
|
RETURN_IF_ERROR(buffers_string);
|
|
Dart_Handle dart_buffers_object = Dart_GetField(dart_this, buffers_string);
|
|
RETURN_IF_ERROR(dart_buffers_object);
|
|
Dart_Handle secure_filter_impl_type = Dart_InstanceGetType(dart_this);
|
|
RETURN_IF_ERROR(secure_filter_impl_type);
|
|
Dart_Handle size_string = DartUtils::NewString("SIZE");
|
|
RETURN_IF_ERROR(size_string);
|
|
Dart_Handle dart_buffer_size =
|
|
Dart_GetField(secure_filter_impl_type, size_string);
|
|
RETURN_IF_ERROR(dart_buffer_size);
|
|
|
|
int64_t buffer_size = 0;
|
|
Dart_Handle err = Dart_IntegerToInt64(dart_buffer_size, &buffer_size);
|
|
RETURN_IF_ERROR(err);
|
|
|
|
Dart_Handle encrypted_size_string = DartUtils::NewString("ENCRYPTED_SIZE");
|
|
RETURN_IF_ERROR(encrypted_size_string);
|
|
|
|
Dart_Handle dart_encrypted_buffer_size =
|
|
Dart_GetField(secure_filter_impl_type, encrypted_size_string);
|
|
RETURN_IF_ERROR(dart_encrypted_buffer_size);
|
|
|
|
int64_t encrypted_buffer_size = 0;
|
|
err = Dart_IntegerToInt64(dart_encrypted_buffer_size, &encrypted_buffer_size);
|
|
RETURN_IF_ERROR(err);
|
|
|
|
if (buffer_size <= 0 || buffer_size > 1 * MB) {
|
|
FATAL("Invalid buffer size in _ExternalBuffer");
|
|
}
|
|
if (encrypted_buffer_size <= 0 || encrypted_buffer_size > 1 * MB) {
|
|
FATAL("Invalid encrypted buffer size in _ExternalBuffer");
|
|
}
|
|
buffer_size_ = static_cast<intptr_t>(buffer_size);
|
|
encrypted_buffer_size_ = static_cast<intptr_t>(encrypted_buffer_size);
|
|
|
|
Dart_Handle data_identifier = DartUtils::NewString("data");
|
|
RETURN_IF_ERROR(data_identifier);
|
|
|
|
for (int i = 0; i < kNumBuffers; i++) {
|
|
int size = isBufferEncrypted(i) ? encrypted_buffer_size_ : buffer_size_;
|
|
buffers_[i] = new uint8_t[size];
|
|
ASSERT(buffers_[i] != NULL);
|
|
buffer_starts_[i] = NULL;
|
|
buffer_ends_[i] = NULL;
|
|
dart_buffer_objects_[i] = NULL;
|
|
}
|
|
|
|
Dart_Handle result = Dart_Null();
|
|
for (int i = 0; i < kNumBuffers; ++i) {
|
|
int size = isBufferEncrypted(i) ? encrypted_buffer_size_ : buffer_size_;
|
|
result = Dart_ListGetAt(dart_buffers_object, i);
|
|
if (Dart_IsError(result)) {
|
|
break;
|
|
}
|
|
|
|
dart_buffer_objects_[i] = Dart_NewPersistentHandle(result);
|
|
ASSERT(dart_buffer_objects_[i] != NULL);
|
|
Dart_Handle data =
|
|
Dart_NewExternalTypedData(Dart_TypedData_kUint8, buffers_[i], size);
|
|
if (Dart_IsError(data)) {
|
|
result = data;
|
|
break;
|
|
}
|
|
result = Dart_HandleFromPersistent(dart_buffer_objects_[i]);
|
|
if (Dart_IsError(result)) {
|
|
break;
|
|
}
|
|
result = Dart_SetField(result, data_identifier, data);
|
|
if (Dart_IsError(result)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Caller handles cleanup on an error.
|
|
return result;
|
|
}
|
|
|
|
|
|
void SSLFilter::RegisterHandshakeCompleteCallback(Dart_Handle complete) {
|
|
ASSERT(NULL == handshake_complete_);
|
|
handshake_complete_ = Dart_NewPersistentHandle(complete);
|
|
ASSERT(handshake_complete_ != NULL);
|
|
}
|
|
|
|
|
|
void SSLFilter::RegisterBadCertificateCallback(Dart_Handle callback) {
|
|
ASSERT(bad_certificate_callback_ != NULL);
|
|
Dart_DeletePersistentHandle(bad_certificate_callback_);
|
|
bad_certificate_callback_ = Dart_NewPersistentHandle(callback);
|
|
ASSERT(bad_certificate_callback_ != NULL);
|
|
}
|
|
|
|
|
|
Dart_Handle SSLFilter::PeerCertificate() {
|
|
if (peer_certs_ == NULL) {
|
|
return Dart_Null();
|
|
}
|
|
|
|
CFTypeRef item = CFArrayGetValueAtIndex(peer_certs_, 0);
|
|
ASSERT(CFGetTypeID(item) == SecCertificateGetTypeID());
|
|
SecCertificateRef cert =
|
|
reinterpret_cast<SecCertificateRef>(const_cast<void*>(item));
|
|
if (cert == NULL) {
|
|
return Dart_Null();
|
|
}
|
|
|
|
return WrappedX509Certificate(cert);
|
|
}
|
|
|
|
|
|
void SSLFilter::Connect(Dart_Handle dart_this,
|
|
const char* hostname,
|
|
SSLCertContext* context,
|
|
bool is_server,
|
|
bool request_client_certificate,
|
|
bool require_client_certificate) {
|
|
if (in_handshake_) {
|
|
FATAL("Connect called twice on the same _SecureFilter.");
|
|
}
|
|
|
|
// Create the underlying context
|
|
SSLContextRef ssl_context = SSLCreateContext(
|
|
NULL, is_server ? kSSLServerSide : kSSLClientSide, kSSLStreamType);
|
|
|
|
// Configure the context.
|
|
OSStatus status;
|
|
status = SSLSetPeerDomainName(ssl_context, hostname, strlen(hostname));
|
|
CheckStatus(status, "TlsException", "Failed to set peer domain name");
|
|
|
|
status = SSLSetIOFuncs(ssl_context, SSLFilter::SSLReadCallback,
|
|
SSLFilter::SSLWriteCallback);
|
|
CheckStatus(status, "TlsException", "Failed to set IO Callbacks");
|
|
|
|
status =
|
|
SSLSetConnection(ssl_context, reinterpret_cast<SSLConnectionRef>(this));
|
|
CheckStatus(status, "TlsException", "Failed to set connection object");
|
|
|
|
// Always evaluate the certs manually so that we can cache the peer
|
|
// certificates in the context for calls to peerCertificate.
|
|
status = SSLSetSessionOption(ssl_context, kSSLSessionOptionBreakOnServerAuth,
|
|
true);
|
|
CheckStatus(status, "TlsException", "Failed to set BreakOnServerAuth option");
|
|
|
|
status = SSLSetProtocolVersionMin(ssl_context, kTLSProtocol1);
|
|
CheckStatus(status, "TlsException",
|
|
"Failed to set minimum protocol version to kTLSProtocol1");
|
|
|
|
// If the context has an identity pass it to SSLSetCertificate().
|
|
if (context->identity() != NULL) {
|
|
CFMutableArrayRef chain =
|
|
CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
|
|
CFArrayAppendValue(chain, context->identity());
|
|
|
|
// Append the certificate chain if there is one.
|
|
if (context->cert_chain() != NULL) {
|
|
// Skip the first one, it's already included in the identity.
|
|
CFIndex chain_length = CFArrayGetCount(context->cert_chain());
|
|
if (chain_length > 1) {
|
|
CFArrayAppendArray(chain, context->cert_chain(),
|
|
CFRangeMake(1, chain_length));
|
|
}
|
|
}
|
|
|
|
status = SSLSetCertificate(ssl_context, chain);
|
|
CFRelease(chain);
|
|
CheckStatus(status, "TlsException", "SSLSetCertificate failed");
|
|
}
|
|
|
|
if (is_server) {
|
|
SSLAuthenticate auth =
|
|
require_client_certificate
|
|
? kAlwaysAuthenticate
|
|
: (request_client_certificate ? kTryAuthenticate
|
|
: kNeverAuthenticate);
|
|
status = SSLSetClientSideAuthenticate(ssl_context, auth);
|
|
CheckStatus(status, "TlsException",
|
|
"Failed to set client authentication mode");
|
|
|
|
// If we're at least trying client authentication, then break handshake
|
|
// for client authentication.
|
|
if (auth != kNeverAuthenticate) {
|
|
status = SSLSetSessionOption(ssl_context,
|
|
kSSLSessionOptionBreakOnClientAuth, true);
|
|
CheckStatus(status, "TlsException",
|
|
"Failed to set client authentication mode");
|
|
}
|
|
}
|
|
|
|
// Add the contexts to our wrapper.
|
|
cert_context_.set(context);
|
|
ssl_context_ = ssl_context;
|
|
is_server_ = is_server;
|
|
|
|
// Kick-off the handshake. Expect the handshake to need more data.
|
|
// SSLHandshake calls our SSLReadCallback and SSLWriteCallback.
|
|
status = SSLHandshake(ssl_context);
|
|
ASSERT(status != noErr);
|
|
if (status == errSSLWouldBlock) {
|
|
status = noErr;
|
|
in_handshake_ = true;
|
|
}
|
|
CheckStatus(status, "HandshakeException", is_server_
|
|
? "Handshake error in server"
|
|
: "Handshake error in client");
|
|
}
|
|
|
|
|
|
OSStatus SSLFilter::EvaluatePeerTrust() {
|
|
OSStatus status = noErr;
|
|
|
|
if (SSL_LOG_STATUS) {
|
|
Log::PrintErr("Handshake evaluating trust.\n");
|
|
}
|
|
SecTrustRef peer_trust = NULL;
|
|
status = SSLCopyPeerTrust(ssl_context_, &peer_trust);
|
|
if (status != noErr) {
|
|
if (is_server_ && (status == errSSLBadCert)) {
|
|
// A client certificate was requested, but not required, and wasn't sent.
|
|
return noErr;
|
|
}
|
|
if (SSL_LOG_STATUS) {
|
|
Log::PrintErr("Handshake error from SSLCopyPeerTrust(): %ld.\n",
|
|
static_cast<intptr_t>(status));
|
|
}
|
|
return status;
|
|
}
|
|
|
|
CFArrayRef trusted_certs = NULL;
|
|
if (cert_context_.get()->trusted_certs() != NULL) {
|
|
trusted_certs =
|
|
CFArrayCreateCopy(NULL, cert_context_.get()->trusted_certs());
|
|
} else {
|
|
trusted_certs = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
|
|
}
|
|
|
|
status = SecTrustSetAnchorCertificates(peer_trust, trusted_certs);
|
|
if (status != noErr) {
|
|
if (SSL_LOG_STATUS) {
|
|
Log::PrintErr("Handshake error from SecTrustSetAnchorCertificates: %ld\n",
|
|
static_cast<intptr_t>(status));
|
|
}
|
|
CFRelease(trusted_certs);
|
|
CFRelease(peer_trust);
|
|
return status;
|
|
}
|
|
|
|
if (SSL_LOG_STATUS) {
|
|
Log::PrintErr(
|
|
"Handshake %s built in root certs\n",
|
|
cert_context_.get()->trust_builtin() ? "trusting" : "not trusting");
|
|
}
|
|
|
|
status = SecTrustSetAnchorCertificatesOnly(
|
|
peer_trust, !cert_context_.get()->trust_builtin());
|
|
if (status != noErr) {
|
|
CFRelease(trusted_certs);
|
|
CFRelease(peer_trust);
|
|
return status;
|
|
}
|
|
|
|
SecTrustResultType trust_result;
|
|
status = SecTrustEvaluate(peer_trust, &trust_result);
|
|
if (status != noErr) {
|
|
CFRelease(trusted_certs);
|
|
CFRelease(peer_trust);
|
|
return status;
|
|
}
|
|
|
|
// Grab the peer's certificate chain.
|
|
CFIndex peer_chain_length = SecTrustGetCertificateCount(peer_trust);
|
|
CFMutableArrayRef peer_certs =
|
|
CFArrayCreateMutable(NULL, peer_chain_length, &kCFTypeArrayCallBacks);
|
|
for (CFIndex i = 0; i < peer_chain_length; ++i) {
|
|
CFArrayAppendValue(peer_certs,
|
|
SecTrustGetCertificateAtIndex(peer_trust, i));
|
|
}
|
|
peer_certs_ = peer_certs;
|
|
|
|
CFRelease(trusted_certs);
|
|
CFRelease(peer_trust);
|
|
|
|
if ((trust_result == kSecTrustResultProceed) ||
|
|
(trust_result == kSecTrustResultUnspecified)) {
|
|
// Trusted.
|
|
return noErr;
|
|
} else {
|
|
if (SSL_LOG_STATUS) {
|
|
Log::PrintErr("Trust eval failed: trust_result = %d\n", trust_result);
|
|
}
|
|
bad_cert_ = true;
|
|
return errSSLBadCert;
|
|
}
|
|
}
|
|
|
|
|
|
OSStatus SSLFilter::Handshake() {
|
|
ASSERT(cert_context_.get() != NULL);
|
|
ASSERT(ssl_context_ != NULL);
|
|
// Try and push handshake along.
|
|
if (SSL_LOG_STATUS) {
|
|
Log::PrintErr("Doing SSLHandshake\n");
|
|
}
|
|
OSStatus status = SSLHandshake(ssl_context_);
|
|
if (SSL_LOG_STATUS) {
|
|
Log::PrintErr("SSLHandshake returned %ld\n", static_cast<intptr_t>(status));
|
|
}
|
|
|
|
if ((status == errSSLServerAuthCompleted) ||
|
|
(status == errSSLClientAuthCompleted)) {
|
|
status = EvaluatePeerTrust();
|
|
if (status == errSSLBadCert) {
|
|
// Need to invoke the bad certificate callback.
|
|
return noErr;
|
|
} else if (status != noErr) {
|
|
return status;
|
|
}
|
|
// When trust evaluation succeeds, we can call SSLHandshake again
|
|
// immediately.
|
|
status = SSLHandshake(ssl_context_);
|
|
}
|
|
|
|
if (status == errSSLWouldBlock) {
|
|
in_handshake_ = true;
|
|
return noErr;
|
|
}
|
|
|
|
// Handshake succeeded.
|
|
if ((in_handshake_) && (status == noErr)) {
|
|
if (SSL_LOG_STATUS) {
|
|
Log::PrintErr("Finished with the Handshake\n");
|
|
}
|
|
connected_ = true;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
// Returns false if Handshake should fail, and true if Handshake should
|
|
// proceed.
|
|
Dart_Handle SSLFilter::InvokeBadCertCallback(SecCertificateRef peer_cert) {
|
|
Dart_Handle callback = bad_certificate_callback_;
|
|
if (Dart_IsNull(callback)) {
|
|
return callback;
|
|
}
|
|
Dart_Handle args[1];
|
|
args[0] = WrappedX509Certificate(peer_cert);
|
|
if (Dart_IsError(args[0])) {
|
|
return args[0];
|
|
}
|
|
Dart_Handle result = Dart_InvokeClosure(callback, 1, args);
|
|
if (!Dart_IsError(result) && !Dart_IsBoolean(result)) {
|
|
result = Dart_NewUnhandledExceptionError(DartUtils::NewDartIOException(
|
|
"HandshakeException",
|
|
"BadCertificateCallback returned a value that was not a boolean",
|
|
Dart_Null()));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
OSStatus SSLFilter::CheckHandshake() {
|
|
if (bad_cert_ && in_handshake_) {
|
|
if (SSL_LOG_STATUS) {
|
|
Log::PrintErr("Invoking bad certificate callback\n");
|
|
}
|
|
ASSERT(peer_certs_ != NULL);
|
|
CFIndex peer_certs_len = CFArrayGetCount(peer_certs_);
|
|
ASSERT(peer_certs_len > 0);
|
|
CFTypeRef item = CFArrayGetValueAtIndex(peer_certs_, peer_certs_len - 1);
|
|
ASSERT(item != NULL);
|
|
ASSERT(CFGetTypeID(item) == SecCertificateGetTypeID());
|
|
SecCertificateRef peer_cert =
|
|
reinterpret_cast<SecCertificateRef>(const_cast<void*>(item));
|
|
Dart_Handle result = InvokeBadCertCallback(peer_cert);
|
|
ThrowIfError(result);
|
|
if (Dart_IsNull(result)) {
|
|
return errSSLBadCert;
|
|
} else {
|
|
bool good_cert = DartUtils::GetBooleanValue(result);
|
|
bad_cert_ = !good_cert;
|
|
return good_cert ? noErr : errSSLBadCert;
|
|
}
|
|
}
|
|
|
|
if (connected_ && in_handshake_) {
|
|
if (SSL_LOG_STATUS) {
|
|
Log::PrintErr("Invoking handshake complete callback\n");
|
|
}
|
|
ThrowIfError(Dart_InvokeClosure(
|
|
Dart_HandleFromPersistent(handshake_complete_), 0, NULL));
|
|
in_handshake_ = false;
|
|
}
|
|
return noErr;
|
|
}
|
|
|
|
|
|
void SSLFilter::Renegotiate(bool use_session_cache,
|
|
bool request_client_certificate,
|
|
bool require_client_certificate) {
|
|
// The SSL_REQUIRE_CERTIFICATE option only takes effect if the
|
|
// SSL_REQUEST_CERTIFICATE option is also set, so set it.
|
|
request_client_certificate =
|
|
request_client_certificate || require_client_certificate;
|
|
// TODO(24070, 24069): Implement setting the client certificate parameters,
|
|
// and triggering rehandshake.
|
|
}
|
|
|
|
|
|
SSLFilter::~SSLFilter() {
|
|
if (ssl_context_ != NULL) {
|
|
CFRelease(ssl_context_);
|
|
ssl_context_ = NULL;
|
|
}
|
|
if (peer_certs_ != NULL) {
|
|
CFRelease(peer_certs_);
|
|
peer_certs_ = NULL;
|
|
}
|
|
if (hostname_ != NULL) {
|
|
free(hostname_);
|
|
hostname_ = NULL;
|
|
}
|
|
for (int i = 0; i < kNumBuffers; ++i) {
|
|
if (buffers_[i] != NULL) {
|
|
delete[] buffers_[i];
|
|
buffers_[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SSLFilter::Destroy() {
|
|
if (ssl_context_ != NULL) {
|
|
SSLClose(ssl_context_);
|
|
}
|
|
for (int i = 0; i < kNumBuffers; ++i) {
|
|
if (dart_buffer_objects_[i] != NULL) {
|
|
Dart_DeletePersistentHandle(dart_buffer_objects_[i]);
|
|
dart_buffer_objects_[i] = NULL;
|
|
}
|
|
}
|
|
if (string_start_ != NULL) {
|
|
Dart_DeletePersistentHandle(string_start_);
|
|
string_start_ = NULL;
|
|
}
|
|
if (string_length_ != NULL) {
|
|
Dart_DeletePersistentHandle(string_length_);
|
|
string_length_ = NULL;
|
|
}
|
|
if (handshake_complete_ != NULL) {
|
|
Dart_DeletePersistentHandle(handshake_complete_);
|
|
handshake_complete_ = NULL;
|
|
}
|
|
if (bad_certificate_callback_ != NULL) {
|
|
Dart_DeletePersistentHandle(bad_certificate_callback_);
|
|
bad_certificate_callback_ = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
OSStatus SSLFilter::SSLReadCallback(SSLConnectionRef connection,
|
|
void* data,
|
|
size_t* data_requested) {
|
|
// Copy at most `data_requested` bytes from `buffers_[kReadEncrypted]` into
|
|
// `data`
|
|
ASSERT(connection != NULL);
|
|
ASSERT(data != NULL);
|
|
ASSERT(data_requested != NULL);
|
|
|
|
SSLFilter* filter =
|
|
const_cast<SSLFilter*>(reinterpret_cast<const SSLFilter*>(connection));
|
|
uint8_t* datap = reinterpret_cast<uint8_t*>(data);
|
|
uint8_t* buffer = filter->buffers_[kReadEncrypted];
|
|
intptr_t start = filter->GetBufferStart(kReadEncrypted);
|
|
intptr_t end = filter->GetBufferEnd(kReadEncrypted);
|
|
intptr_t size = filter->encrypted_buffer_size_;
|
|
intptr_t requested = static_cast<intptr_t>(*data_requested);
|
|
intptr_t data_read = 0;
|
|
|
|
if (end < start) {
|
|
// Data may be split into two segments. In this case,
|
|
// the first is [start, size).
|
|
intptr_t buffer_end = (start == 0) ? size - 1 : size;
|
|
intptr_t available = buffer_end - start;
|
|
intptr_t bytes = requested < available ? requested : available;
|
|
memmove(datap, &buffer[start], bytes);
|
|
start += bytes;
|
|
datap += bytes;
|
|
data_read += bytes;
|
|
requested -= bytes;
|
|
ASSERT(start <= size);
|
|
if (start == size) {
|
|
start = 0;
|
|
}
|
|
}
|
|
if ((requested > 0) && (start < end)) {
|
|
intptr_t available = end - start;
|
|
intptr_t bytes = requested < available ? requested : available;
|
|
memmove(datap, &buffer[start], bytes);
|
|
start += bytes;
|
|
datap += bytes;
|
|
data_read += bytes;
|
|
requested -= bytes;
|
|
ASSERT(start <= end);
|
|
}
|
|
|
|
if (SSL_LOG_DATA) {
|
|
Log::PrintErr("SSLReadCallback: requested: %ld, read %ld bytes\n",
|
|
*data_requested, data_read);
|
|
}
|
|
|
|
filter->SetBufferStart(kReadEncrypted, start);
|
|
bool short_read = data_read < static_cast<intptr_t>(*data_requested);
|
|
*data_requested = data_read;
|
|
return short_read ? errSSLWouldBlock : noErr;
|
|
}
|
|
|
|
|
|
// Read decrypted data from the filter to the circular buffer.
|
|
OSStatus SSLFilter::ProcessReadPlaintextBuffer(intptr_t start,
|
|
intptr_t end,
|
|
intptr_t* bytes_processed) {
|
|
ASSERT(bytes_processed != NULL);
|
|
intptr_t length = end - start;
|
|
OSStatus status = noErr;
|
|
size_t bytes = 0;
|
|
if (length > 0) {
|
|
status =
|
|
SSLRead(ssl_context_,
|
|
reinterpret_cast<void*>((buffers_[kReadPlaintext] + start)),
|
|
length, &bytes);
|
|
if (SSL_LOG_STATUS) {
|
|
Log::PrintErr("SSLRead: status = %ld\n", static_cast<intptr_t>(status));
|
|
}
|
|
if ((status != noErr) && (status != errSSLWouldBlock)) {
|
|
*bytes_processed = 0;
|
|
return status;
|
|
}
|
|
}
|
|
if (SSL_LOG_DATA) {
|
|
Log::PrintErr(
|
|
"ProcessReadPlaintextBuffer: requested: %ld, read %ld bytes\n", length,
|
|
bytes);
|
|
}
|
|
*bytes_processed = static_cast<intptr_t>(bytes);
|
|
return status;
|
|
}
|
|
|
|
|
|
OSStatus SSLFilter::SSLWriteCallback(SSLConnectionRef connection,
|
|
const void* data,
|
|
size_t* data_provided) {
|
|
// Copy at most `data_provided` bytes from data into
|
|
// `buffers_[kWriteEncrypted]`.
|
|
ASSERT(connection != NULL);
|
|
ASSERT(data != NULL);
|
|
ASSERT(data_provided != NULL);
|
|
|
|
SSLFilter* filter =
|
|
const_cast<SSLFilter*>(reinterpret_cast<const SSLFilter*>(connection));
|
|
const uint8_t* datap = reinterpret_cast<const uint8_t*>(data);
|
|
uint8_t* buffer = filter->buffers_[kWriteEncrypted];
|
|
intptr_t start = filter->GetBufferStart(kWriteEncrypted);
|
|
intptr_t end = filter->GetBufferEnd(kWriteEncrypted);
|
|
intptr_t size = filter->encrypted_buffer_size_;
|
|
intptr_t provided = static_cast<intptr_t>(*data_provided);
|
|
intptr_t data_written = 0;
|
|
|
|
// is full, neither if statement is executed and nothing happens.
|
|
if (start <= end) {
|
|
// If the free space may be split into two segments,
|
|
// then the first is [end, size), unless start == 0.
|
|
// Then, since the last free byte is at position start - 2,
|
|
// the interval is [end, size - 1).
|
|
intptr_t buffer_end = (start == 0) ? size - 1 : size;
|
|
intptr_t available = buffer_end - end;
|
|
intptr_t bytes = provided < available ? provided : available;
|
|
memmove(&buffer[end], datap, bytes);
|
|
end += bytes;
|
|
datap += bytes;
|
|
data_written += bytes;
|
|
provided -= bytes;
|
|
ASSERT(end <= size);
|
|
if (end == size) {
|
|
end = 0;
|
|
}
|
|
}
|
|
if ((provided > 0) && (start > end + 1)) {
|
|
intptr_t available = (start - 1) - end;
|
|
intptr_t bytes = provided < available ? provided : available;
|
|
memmove(&buffer[end], datap, bytes);
|
|
end += bytes;
|
|
datap += bytes;
|
|
data_written += bytes;
|
|
provided -= bytes;
|
|
ASSERT(end < start);
|
|
}
|
|
|
|
if (SSL_LOG_DATA) {
|
|
Log::PrintErr("SSLWriteCallback: provided: %ld, written %ld bytes\n",
|
|
*data_provided, data_written);
|
|
}
|
|
|
|
filter->SetBufferEnd(kWriteEncrypted, end);
|
|
*data_provided = data_written;
|
|
return (data_written == 0) ? errSSLWouldBlock : noErr;
|
|
}
|
|
|
|
|
|
OSStatus SSLFilter::ProcessWritePlaintextBuffer(intptr_t start,
|
|
intptr_t end,
|
|
intptr_t* bytes_processed) {
|
|
ASSERT(bytes_processed != NULL);
|
|
intptr_t length = end - start;
|
|
OSStatus status = noErr;
|
|
size_t bytes = 0;
|
|
if (length > 0) {
|
|
status =
|
|
SSLWrite(ssl_context_,
|
|
reinterpret_cast<void*>(buffers_[kWritePlaintext] + start),
|
|
length, &bytes);
|
|
if (SSL_LOG_STATUS) {
|
|
Log::PrintErr("SSLWrite: status = %ld\n", static_cast<intptr_t>(status));
|
|
}
|
|
if ((status != noErr) && (status != errSSLWouldBlock)) {
|
|
*bytes_processed = 0;
|
|
return status;
|
|
}
|
|
}
|
|
if (SSL_LOG_DATA) {
|
|
Log::PrintErr("ProcessWritePlaintextBuffer: requested: %ld, written: %ld\n",
|
|
length, bytes);
|
|
}
|
|
*bytes_processed = static_cast<intptr_t>(bytes);
|
|
return status;
|
|
}
|
|
|
|
} // namespace bin
|
|
} // namespace dart
|
|
|
|
#endif // TARGET_OS_IOS
|
|
|
|
#endif // !defined(DART_IO_SECURE_SOCKET_DISABLED)
|