Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(230)

Unified Diff: runtime/bin/secure_socket_macos.cc

Issue 1721283002: Implements secure sockets on Mac OS with SecureTransport API (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Address comments Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « runtime/bin/secure_socket_macos.h ('k') | runtime/bin/secure_socket_patch.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: runtime/bin/secure_socket_macos.cc
diff --git a/runtime/bin/secure_socket_macos.cc b/runtime/bin/secure_socket_macos.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2df25e140afea60f0b26d882f1a818e14a0db78d
--- /dev/null
+++ b/runtime/bin/secure_socket_macos.cc
@@ -0,0 +1,2074 @@
+// 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.
+
+#include "platform/globals.h"
+#if defined(TARGET_OS_MACOS)
+
+#include "bin/secure_socket.h"
+#include "bin/secure_socket_macos.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; \
+ } \
+ }
+
+// We need to access this private API function to create a SecIdentityRef
+// without writing a custom keychain to the filesystem. This is the approach
+// taken in WebKit:
+// https://webkit.googlesource.com/WebKit/+/master/Source/WebKit2/Shared/cf/ArgumentCodersCF.cpp
+extern "C" {
+SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef private_key);
+}
+
+namespace dart {
+namespace bin {
+
+// SSLCertContext wraps the certificates needed for a SecureTransport
+// connection. Fields are protected by the mutex_ field, and may only be set
+// once. This is to allow access by both the Dart thread and the IOService
+// thread. Setters return false if the field was already set.
+class SSLCertContext {
+ public:
+ SSLCertContext() :
+ mutex_(new Mutex()),
+ private_key_(NULL),
+ keychain_(NULL),
+ cert_chain_(NULL),
+ trusted_certs_(NULL),
+ cert_authorities_(NULL),
+ trust_builtin_(false) {}
+
+ ~SSLCertContext() {
+ if (private_key_ != NULL) {
+ CFRelease(private_key_);
+ }
+ if (keychain_ != NULL) {
+ SecKeychainDelete(keychain_);
+ CFRelease(keychain_);
+ }
+ if (cert_chain_ != NULL) {
+ CFRelease(cert_chain_);
+ }
+ if (trusted_certs_ != NULL) {
+ CFRelease(trusted_certs_);
+ }
+ if (cert_authorities_ != NULL) {
+ CFRelease(cert_authorities_);
+ }
+ delete mutex_;
+ }
+
+ SecKeyRef private_key() {
+ MutexLocker m(mutex_);
+ return private_key_;
+ }
+ bool set_private_key(SecKeyRef private_key) {
+ MutexLocker m(mutex_);
+ if (private_key_ != NULL) {
+ return false;
+ }
+ private_key_ = private_key;
+ return true;
+ }
+
+ SecKeychainRef keychain() {
+ MutexLocker m(mutex_);
+ return keychain_;
+ }
+ bool set_keychain(SecKeychainRef keychain) {
+ MutexLocker m(mutex_);
+ if (keychain_ != NULL) {
+ return false;
+ }
+ keychain_ = keychain;
+ return true;
+ }
+
+ CFArrayRef cert_chain() {
+ MutexLocker m(mutex_);
+ return cert_chain_;
+ }
+ bool set_cert_chain(CFArrayRef cert_chain) {
+ MutexLocker m(mutex_);
+ if (cert_chain_ != NULL) {
+ return false;
+ }
+ cert_chain_ = cert_chain;
+ return true;
+ }
+
+ CFArrayRef trusted_certs() {
+ MutexLocker m(mutex_);
+ return trusted_certs_;
+ }
+ bool set_trusted_certs(CFArrayRef trusted_certs) {
+ MutexLocker m(mutex_);
+ if (trusted_certs_ != NULL) {
+ return false;
+ }
+ trusted_certs_ = trusted_certs;
+ return true;
+ }
+
+ CFArrayRef cert_authorities() {
+ MutexLocker m(mutex_);
+ return cert_authorities_;
+ }
+ bool set_cert_authorities(CFArrayRef cert_authorities) {
+ MutexLocker m(mutex_);
+ if (cert_authorities_ != NULL) {
+ return false;
+ }
+ cert_authorities_ = cert_authorities;
+ return true;
+ }
+
+ bool trust_builtin() {
+ MutexLocker m(mutex_);
+ return trust_builtin_;
+ }
+ void set_trust_builtin(bool trust_builtin) {
+ MutexLocker m(mutex_);
+ trust_builtin_ = trust_builtin;
+ }
+
+ private:
+ // The context is accessed both by Dart code and the IOService. This mutex
+ // protects all fields.
+ Mutex* mutex_;
+
+ SecKeyRef private_key_;
+ SecKeychainRef keychain_;
+
+ // CFArrays of SecCertificateRef.
+ CFArrayRef cert_chain_;
+ CFArrayRef trusted_certs_;
+ CFArrayRef cert_authorities_;
+
+ bool trust_builtin_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLCertContext);
+};
+
+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);
+ CFStringRef error_string = SecCopyErrorMessageString(status, NULL);
+ if (error_string == NULL) {
+ status_message.Printf("OSStatus = %ld: https://www.osstatus.com",
+ static_cast<intptr_t>(status));
+ } else {
+ char* error = CFStringRefToCString(error_string);
+ status_message.Printf("OSStatus = %ld: %s",
+ static_cast<intptr_t>(status), error);
+ CFRelease(error_string);
+ }
+ 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);
+ delete filter;
+}
+
+
+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);
+ delete context;
+}
+
+
+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;
+}
+
+
+// Where the argument to the constructor is the handle for an object
+// implementing List<int>, this class creates a scope in which the memory
+// backing the list can be accessed.
+//
+// Do not make Dart_ API calls while in a ScopedMemBuffer.
+// Do not call Dart_PropagateError while in a ScopedMemBuffer.
+class ScopedMemBuffer {
+ public:
+ explicit ScopedMemBuffer(Dart_Handle object) {
+ if (!Dart_IsTypedData(object) && !Dart_IsList(object)) {
+ Dart_ThrowException(DartUtils::NewDartArgumentError(
+ "Argument is not a List<int>"));
+ }
+
+ uint8_t* bytes = NULL;
+ intptr_t bytes_len = 0;
+ bool is_typed_data = false;
+ if (Dart_IsTypedData(object)) {
+ is_typed_data = true;
+ Dart_TypedData_Type typ;
+ ThrowIfError(Dart_TypedDataAcquireData(
+ object,
+ &typ,
+ reinterpret_cast<void**>(&bytes),
+ &bytes_len));
+ } else {
+ ASSERT(Dart_IsList(object));
+ ThrowIfError(Dart_ListLength(object, &bytes_len));
+ bytes = Dart_ScopeAllocate(bytes_len);
+ ASSERT(bytes != NULL);
+ ThrowIfError(Dart_ListGetAsBytes(object, 0, bytes, bytes_len));
+ }
+
+ object_ = object;
+ bytes_ = bytes;
+ bytes_len_ = bytes_len;
+ is_typed_data_ = is_typed_data;
+ }
+
+ ~ScopedMemBuffer() {
+ if (is_typed_data_) {
+ ThrowIfError(Dart_TypedDataReleaseData(object_));
+ }
+ }
+
+ uint8_t* get() const { return bytes_; }
+ intptr_t length() const { return bytes_len_; }
+
+ private:
+ Dart_Handle object_;
+ uint8_t* bytes_;
+ intptr_t bytes_len_;
+ bool is_typed_data_;
+
+ DISALLOW_ALLOCATION();
+ DISALLOW_COPY_AND_ASSIGN(ScopedMemBuffer);
+};
+
+
+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 GetKeyAndCerts(CFArrayRef items,
+ CFIndex items_length,
+ CFArrayRef* out_certs,
+ SecKeyRef* out_key) {
+ OSStatus status = noErr;
+
+ // Loop through the items, take only the first private key/identity, ignore
+ // any others, populate out_certs.
+ CFMutableArrayRef certs =
+ CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ SecKeyRef key = NULL;
+
+ for (CFIndex i = 0; i < items_length; ++i) {
+ CFTypeRef item =
+ reinterpret_cast<CFTypeRef>(CFArrayGetValueAtIndex(items, i));
+ CFTypeID item_type = CFGetTypeID(item);
+ if (item_type == SecCertificateGetTypeID()) {
+ if (SSL_LOG_CERTS) {
+ Log::Print("\titem %ld: Certificate\n", i);
+ }
+ CFArrayAppendValue(certs, item);
+ } else if ((item_type == SecKeyGetTypeID()) && (key == NULL)) {
+ if (SSL_LOG_CERTS) {
+ Log::Print("\titem %ld: Key\n", i);
+ }
+ key = reinterpret_cast<SecKeyRef>(const_cast<void*>(item));
+ CFRetain(key);
+ } else if ((item_type == SecIdentityGetTypeID()) && (key == NULL)) {
+ if (SSL_LOG_CERTS) {
+ Log::Print("\titem %ld: Identity\n", i);
+ }
+ SecIdentityRef identity =
+ reinterpret_cast<SecIdentityRef>(const_cast<void*>(item));
+ SecCertificateRef cert = NULL;
+
+ status = SecIdentityCopyPrivateKey(identity, &key);
+ if (status != noErr) {
+ CFRelease(certs);
+ return status;
+ }
+
+ status = SecIdentityCopyCertificate(identity, &cert);
+ if (status != noErr) {
+ CFRelease(key);
+ CFRelease(certs);
+ return status;
+ }
+ CFArrayAppendValue(certs, cert);
+ CFRelease(cert);
+ }
+ // Other item types are ignored.
+ }
+
+ if (out_key == NULL) {
+ if (key != NULL) {
+ CFRelease(key);
+ }
+ } else {
+ *out_key = key;
+ }
+
+ if (out_certs == NULL) {
+ if (certs != NULL) {
+ CFRelease(certs);
+ }
+ } else {
+ *out_certs = certs;
+ }
+ return status;
+}
+
+
+static OSStatus TryPEMImport(CFDataRef cfdata,
+ CFStringRef password,
+ CFArrayRef* out_certs,
+ SecKeyRef* out_key) {
+ OSStatus status = noErr;
+
+ SecExternalFormat format = kSecFormatPEMSequence;
+ SecExternalItemType sitem_type = kSecItemTypeAggregate;
+
+ SecKeyImportExportParameters params;
+ memset(&params, 0, sizeof(params));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ params.flags = kSecKeyNoAccessControl;
+ params.passphrase = password;
+ params.keyAttributes = CSSM_KEYATTR_EXTRACTABLE;
+
+ CFArrayRef items = NULL;
+ status = SecKeychainItemImport(
+ cfdata, NULL, &format, &sitem_type, 0, &params, NULL, &items);
+
+ if (status != noErr) {
+ if (SSL_LOG_CERTS) {
+ Log::Print("TrySecItemImport failed with: %ld, type = %d, format = %d\n",
+ static_cast<intptr_t>(status), sitem_type, format);
+ }
+ return status;
+ }
+
+ CFIndex items_length = (items == NULL) ? 0 : CFArrayGetCount(items);
+ if (SSL_LOG_CERTS) {
+ Log::Print(
+ "TrySecItemImport succeeded, type = %d, format = %d, count = %ld\n",
+ sitem_type, format, items_length);
+ }
+
+ // Empty list indicates a decoding failure of some sort.
+ if ((items != NULL) && (items_length == 0)) {
+ CFRelease(items);
+ return errSSLBadCert;
+ }
+
+ status = GetKeyAndCerts(items, items_length, out_certs, out_key);
+ CFRelease(items);
+ return status;
+}
+
+
+static char* TempKeychainPath() {
+ const char* exes = "keychaindir.XXXX";
+ const char* fname = "keychain";
+ const char* temp_dir = getenv("TMPDIR");
+ if (temp_dir == NULL) {
+ temp_dir = getenv("TMP");
+ }
+ if (temp_dir == NULL) {
+ temp_dir = "/tmp/";
+ }
+ ASSERT(temp_dir != NULL);
+
+ TextBuffer path(PATH_MAX);
+ path.Printf("%s/%s", temp_dir, exes);
+ char* ret = mkdtemp(path.buf());
+ ASSERT(ret != NULL);
+ path.Printf("/%s", fname);
+
+ char* result =
+ reinterpret_cast<char*>(Dart_ScopeAllocate(path.length() + 1));
+ return strncpy(result, path.buf(), path.length() + 1);
+}
+
+
+static OSStatus CreateKeychain(SecKeychainRef* keychain) {
+ ASSERT(keychain != NULL);
+ OSStatus status = noErr;
+ const char* temp_keychain_pwd = "dartdart";
+ char* temp_file_path = TempKeychainPath();
+ ASSERT(temp_file_path != NULL);
+ if (SSL_LOG_CERTS) {
+ Log::Print("Temporary keychain at: '%s'\n", temp_file_path);
+ }
+ status = SecKeychainCreate(temp_file_path,
+ strlen(temp_keychain_pwd) + 1,
+ reinterpret_cast<const void*>(temp_keychain_pwd),
+ FALSE, // Prompt user? Definitely no.
+ NULL, // Default access rights.
+ keychain);
+ if (status != noErr) {
+ return status;
+ }
+ ASSERT(*keychain != NULL);
+ return status;
+}
+
+
+static OSStatus TryPKCS12Import(CFDataRef cfdata,
+ CFStringRef password,
+ CFArrayRef* out_certs,
+ SecKeyRef* out_key,
+ SecKeychainRef* out_keychain) {
+ OSStatus status = noErr;
+
+ SecExternalFormat format = kSecFormatPKCS12;
+ SecExternalItemType sitem_type = kSecItemTypeAggregate;
+
+ SecKeyImportExportParameters params;
+ memset(&params, 0, sizeof(params));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ params.flags = kSecKeyNoAccessControl;
+ params.passphrase = password;
+ params.keyAttributes = CSSM_KEYATTR_EXTRACTABLE;
+
+ CFArrayRef items = NULL;
+ if (SSL_LOG_CERTS) {
+ Log::Print("Trying PKCS12 import with: type = %d, format = %d\n",
+ sitem_type, format);
+ }
+
+ // The documentation for SecKeychainItemImport here:
+ //
+ // https://developer.apple.com/library/mac/documentation/Security/Reference/keychainservices/index.html
+ //
+ // states that when the SecKeychainRef argument is NULL, the CFArrayRef*
+ // argument will be populated by an array containing all keys, identities,
+ // and certificates from the data in the CFDataRef argument.
+ //
+ // Unfortunately, this is not true. The code to populate the CFArrayRef with
+ // keys and identities from PKCS12 data has been skipped and/or commented out,
+ // here:
+ //
+ // https://github.com/Apple-FOSS-Mirror/Security/blob/master/libsecurity_keychain/lib/SecImportExportAgg.cpp#L636
+ //
+ // as "floating" SecKeyRefs from the PKCS12 decoder haven't been implemented.
+ // That is, each private key instance coming from the PKCS12 decoder has to be
+ // associated with a keychain instance. Thus, as a workaround, we create a
+ // temporary keychain here if one is needed, and stash it below in a
+ // SecurityContext. This has the drawbacks:
+ // 1.) We need to make a temporary directory to hold the keychain file, and
+ // 2.) SecKeychainItemImport() probably does blocking IO to create and
+ // manipulate the keychain file.
+ // So if the API is updated, this keychain should not be used.
+ SecKeychainRef keychain = NULL;
+ if (out_key != NULL) {
+ ASSERT(out_keychain != NULL);
+ status = CreateKeychain(&keychain);
+ if (status != noErr) {
+ return status;
+ }
+ *out_keychain = keychain;
+ }
+
+ status = SecKeychainItemImport(
+ cfdata, NULL, &format, &sitem_type, 0, &params, keychain, &items);
+ if (status != noErr) {
+ if (SSL_LOG_CERTS) {
+ Log::Print("TrySecItemImport failed with: %ld, it = %d, format = %d\n",
+ static_cast<intptr_t>(status), sitem_type, format);
+ }
+ return status;
+ }
+
+ CFIndex items_length = (items == NULL) ? 0 : CFArrayGetCount(items);
+ if (SSL_LOG_CERTS) {
+ Log::Print("TrySecItemImport 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;
+ }
+
+ status = GetKeyAndCerts(items, items_length, out_certs, out_key);
+ CFRelease(items);
+ return status;
+}
+
+
+static OSStatus ExtractSecItems(uint8_t* buffer,
+ intptr_t length,
+ const char* password,
+ CFArrayRef* out_certs,
+ SecKeyRef* out_key,
+ SecKeychainRef* out_keychain) {
+ 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 = TryPEMImport(cfdata, cfpassword, out_certs, out_key);
+ if (status != noErr) {
+ status =
+ TryPKCS12Import(cfdata, cfpassword, out_certs, out_key, out_keychain);
+ }
+
+ 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)) {
+ delete filter;
+ 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) {
+ intptr_t filter_pointer = reinterpret_cast<intptr_t>(GetFilter(args));
+ 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)) {
+ delete cert_context;
+ Dart_PropagateError(err);
+ }
+}
+
+
+void FUNCTION_NAME(SecurityContext_UsePrivateKeyBytes)(
+ Dart_NativeArguments args) {
+ SSLCertContext* context = GetSecurityContext(args);
+ const char* password = GetPasswordArgument(args, 2);
+
+ OSStatus status;
+ SecKeyRef key = NULL;
+ SecKeychainRef keychain = NULL;
+ {
+ ScopedMemBuffer buffer(ThrowIfError(Dart_GetNativeArgument(args, 1)));
+ status = ExtractSecItems(
+ buffer.get(), buffer.length(), password, NULL, &key, &keychain);
+ }
+
+ // Set the context fields. If there's a failure, release the items.
+ bool set_failure = false;
+ if ((key != NULL) && !context->set_private_key(key)) {
+ CFRelease(key);
+ SecKeychainDelete(keychain);
+ CFRelease(keychain);
+ set_failure = true;
+ }
+ if (!set_failure && (keychain != NULL) && !context->set_keychain(keychain)) {
+ SecKeychainDelete(keychain);
+ CFRelease(keychain);
+ }
+
+ 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);
+ const char* password = GetPasswordArgument(args, 2);
+
+ OSStatus status;
+ CFArrayRef certs = NULL;
+ {
+ ScopedMemBuffer buffer(ThrowIfError(Dart_GetNativeArgument(args, 1)));
+ status = ExtractSecItems(
+ buffer.get(), buffer.length(), password, &certs, NULL, NULL);
+ }
+
+ // Set the field in the context. If there's a failure, release the certs,
+ // and throw an exception.
+ if ((certs != NULL) && !context->set_trusted_certs(certs)) {
+ CFRelease(certs);
+ Dart_ThrowException(DartUtils::NewDartArgumentError(
+ "setTrustedCertificatesBytes has already been called "
+ "on the given context."));
+ }
+
+ 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) {
+ SSLCertContext* context = GetSecurityContext(args);
+
+ const char* password = GetPasswordArgument(args, 2);
+ OSStatus status;
+ CFArrayRef certs = NULL;
+ {
+ ScopedMemBuffer buffer(ThrowIfError(Dart_GetNativeArgument(args, 1)));
+ status = ExtractSecItems(
+ buffer.get(), buffer.length(), password, &certs, NULL, NULL);
+ }
+
+ // Set the field in the context. If there's a failure, release the certs,
+ // and throw an exception.
+ if ((certs != NULL) && !context->set_cert_chain(certs)) {
+ CFRelease(certs);
+ Dart_ThrowException(DartUtils::NewDartArgumentError(
+ "useCertificateChainBytes has already been called "
+ "on the given context."));
+ }
+
+ CheckStatus(status, "TlsException", "Failure in useCertificateChainBytes");
+}
+
+
+void FUNCTION_NAME(SecurityContext_SetClientAuthoritiesBytes)(
+ Dart_NativeArguments args) {
+ SSLCertContext* context = GetSecurityContext(args);
+ const char* password = GetPasswordArgument(args, 2);
+
+ OSStatus status;
+ CFArrayRef certs = NULL;
+ {
+ ScopedMemBuffer buffer(ThrowIfError(Dart_GetNativeArgument(args, 1)));
+ status = ExtractSecItems(
+ buffer.get(), buffer.length(), password, &certs, NULL, NULL);
+ }
+
+ // Set the field in the context. If there's a failure, release the certs,
+ // and throw an exception.
+ if ((certs != NULL) && !context->set_cert_authorities(certs)) {
+ CFRelease(certs);
+ Dart_ThrowException(DartUtils::NewDartArgumentError(
+ "setClientAuthoritiesBytes has already been called "
+ "on the given context."));
+ }
+
+ CheckStatus(status, "TlsException", "Failure in setClientAuthoritiesBytes");
+}
+
+
+void FUNCTION_NAME(SecurityContext_SetAlpnProtocols)(
+ Dart_NativeArguments args) {
+ Dart_ThrowException(DartUtils::NewDartUnsupportedError(
+ "ALPN is not supported on this platform"));
+}
+
+
+static char* GetNameFromCert(SecCertificateRef certificate,
+ CFTypeRef field,
+ CFStringRef name) {
+ char* issuer_name = NULL;
+
+ CFTypeRef keys[] = { field };
+ CFArrayRef key_array = CFArrayCreate(NULL, keys, 1, &kCFTypeArrayCallBacks);
+ CFErrorRef error = NULL;
+ CFDictionaryRef cert_dict =
+ SecCertificateCopyValues(certificate, key_array, &error);
+ if (cert_dict == NULL) {
+ CFRelease(key_array);
+ Dart_ThrowException(DartUtils::NewDartArgumentError(
+ "X509.issuer failed to copy issuer field out of certificate"));
+ }
+
+ CFTypeRef item = CFDictionaryGetValue(cert_dict, keys[0]);
+ ASSERT(CFGetTypeID(item) == CFDictionaryGetTypeID());
+ CFDictionaryRef val_dict = reinterpret_cast<CFDictionaryRef>(item);
+
+ item = CFDictionaryGetValue(val_dict, kSecPropertyKeyValue);
+ ASSERT(CFGetTypeID(item) == CFArrayGetTypeID());
+ CFArrayRef val_array = reinterpret_cast<CFArrayRef>(item);
+
+ for (intptr_t i = 0; i < CFArrayGetCount(val_array); i++) {
+ item = CFArrayGetValueAtIndex(val_array, i);
+ ASSERT(CFGetTypeID(item) == CFDictionaryGetTypeID());
+ CFDictionaryRef val_dict2 = reinterpret_cast<CFDictionaryRef>(item);
+
+ item = CFDictionaryGetValue(val_dict2, kSecPropertyKeyLabel);
+ ASSERT(CFGetTypeID(item) == CFStringGetTypeID());
+ CFStringRef label = reinterpret_cast<CFStringRef>(item);
+
+ if (CFStringCompare(label, name, 0) == kCFCompareEqualTo) {
+ item = CFDictionaryGetValue(val_dict2, kSecPropertyKeyValue);
+ ASSERT(CFGetTypeID(item) == CFStringGetTypeID());
+ CFStringRef value = reinterpret_cast<CFStringRef>(item);
+ issuer_name = CFStringRefToCString(value);
+ break;
+ }
+ }
+
+ CFRelease(cert_dict);
+ CFRelease(key_array);
+ return issuer_name;
+}
+
+
+void FUNCTION_NAME(X509_Subject)(Dart_NativeArguments args) {
+ SecCertificateRef certificate = GetX509Certificate(args);
+ char* subject_name = GetNameFromCert(certificate,
+ kSecOIDX509V1SubjectName,
+ kSecOIDCommonName);
+ if (subject_name == NULL) {
+ Dart_ThrowException(DartUtils::NewDartArgumentError(
+ "X509.subject failed to find issuer's common name."));
+ } else {
+ Dart_SetReturnValue(args, Dart_NewStringFromCString(subject_name));
+ }
+}
+
+
+void FUNCTION_NAME(X509_Issuer)(Dart_NativeArguments args) {
+ SecCertificateRef certificate = GetX509Certificate(args);
+ char* issuer_name = GetNameFromCert(certificate,
+ kSecOIDX509V1IssuerName,
+ kSecOIDCommonName);
+ if (issuer_name == NULL) {
+ Dart_ThrowException(DartUtils::NewDartArgumentError(
+ "X509.issuer failed to find issuer's common name."));
+ } else {
+ Dart_SetReturnValue(args, Dart_NewStringFromCString(issuer_name));
+ }
+}
+
+
+// Returns the number of seconds since the epoch from 'field'.
+static int64_t GetTimeFromCert(SecCertificateRef certificate, CFTypeRef field) {
+ CFTypeRef keys[] = { field };
+ CFArrayRef key_array = CFArrayCreate(NULL, keys, 1, &kCFTypeArrayCallBacks);
+ CFErrorRef error = NULL;
+ CFDictionaryRef cert_dict =
+ SecCertificateCopyValues(certificate, key_array, &error);
+ if (cert_dict == NULL) {
+ CFRelease(key_array);
+ Dart_ThrowException(DartUtils::NewDartArgumentError(
+ "X509.startValidity: failed to copy issuer field out of certificate"));
+ }
+
+ CFTypeRef item = CFDictionaryGetValue(cert_dict, keys[0]);
+ ASSERT(CFGetTypeID(item) == CFDictionaryGetTypeID());
+ CFDictionaryRef val_dict = reinterpret_cast<CFDictionaryRef>(item);
+
+ item = CFDictionaryGetValue(val_dict, kSecPropertyKeyValue);
+ ASSERT(CFGetTypeID(item) == CFNumberGetTypeID());
+ CFNumberRef date_number = reinterpret_cast<CFNumberRef>(item);
+
+ CFAbsoluteTime date_abs_time;
+ CFNumberGetValue(date_number, kCFNumberDoubleType, &date_abs_time);
+ CFAbsoluteTime seconds_since_epoch =
+ date_abs_time + kCFAbsoluteTimeIntervalSince1970;
+ return static_cast<int64_t>(seconds_since_epoch) * 1000LL;
+}
+
+
+void FUNCTION_NAME(X509_StartValidity)(Dart_NativeArguments args) {
+ SecCertificateRef certificate = GetX509Certificate(args);
+ int64_t seconds_since_epoch = GetTimeFromCert(certificate,
+ kSecOIDX509V1ValidityNotBefore);
+ Dart_SetReturnValue(args,
+ Dart_NewInteger(static_cast<int64_t>(seconds_since_epoch) * 1000LL));
+}
+
+
+void FUNCTION_NAME(X509_EndValidity)(Dart_NativeArguments args) {
+ SecCertificateRef certificate = GetX509Certificate(args);
+ int64_t seconds_since_epoch = GetTimeFromCert(certificate,
+ kSecOIDX509V1ValidityNotAfter);
+ Dart_SetReturnValue(args,
+ Dart_NewInteger(static_cast<int64_t>(seconds_since_epoch) * 1000LL));
+}
+
+
+// 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());
+ 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);
+ CFStringRef error_string = SecCopyErrorMessageString(status, NULL);
+ if (error_string == NULL) {
+ status_message.Printf("OSStatus = %ld: https://www.osstatus.com",
+ static_cast<intptr_t>(status));
+ } else {
+ char* error = CFStringRefToCString(error_string);
+ status_message.Printf("OSStatus = %ld: %s",
+ static_cast<intptr_t>(status), error);
+ CFRelease(error_string);
+ }
+ 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 a private key and certificate chain, combine the
+ // private key and first certificate into a SecIdentityRef, and place that
+ // and the remaining certs in an array to pass to SSLSetCertificate().
+ if ((context->private_key() != NULL) && (context->cert_chain() != NULL)) {
+ CFIndex chain_length = CFArrayGetCount(context->cert_chain());
+ CFMutableArrayRef certs =
+ CFArrayCreateMutable(NULL, chain_length, &kCFTypeArrayCallBacks);
+ CFTypeRef item = CFArrayGetValueAtIndex(context->cert_chain(), 0);
+ ASSERT(CFGetTypeID(item) == SecCertificateGetTypeID());
+ SecCertificateRef first_cert =
+ reinterpret_cast<SecCertificateRef>(const_cast<void*>(item));
+ SecIdentityRef identity =
+ SecIdentityCreate(NULL, first_cert, context->private_key());
+ CFArrayAppendValue(certs, identity);
+ for (CFIndex i = 0; i < chain_length; i++) {
+ CFArrayAppendValue(certs,
+ CFArrayGetValueAtIndex(context->cert_chain(), i));
+ }
+ CFRelease(identity);
+ status = SSLSetCertificate(ssl_context, certs);
+ CFRelease(certs);
+ CheckStatus(status, "TlsException", "SSLSetCertificate failed");
+ }
+
+ if (context->cert_authorities() != NULL) {
+ status = SSLSetCertificateAuthorities(
+ ssl_context, context->cert_authorities(), true);
+ CheckStatus(status,
+ "TlsException",
+ "Failed to set certificate authorities");
+ }
+
+ 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_ = 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::Print("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::Print("Handshake error from SSLCopyPeerTrust(): %ld.\n",
+ static_cast<intptr_t>(status));
+ }
+ return status;
+ }
+
+ CFArrayRef trusted_certs = NULL;
+ if (cert_context_->trusted_certs() != NULL) {
+ trusted_certs = CFArrayCreateCopy(NULL, cert_context_->trusted_certs());
+ } else {
+ trusted_certs = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
+ }
+
+ status = SecTrustSetAnchorCertificates(peer_trust, trusted_certs);
+ if (status != noErr) {
+ if (SSL_LOG_STATUS) {
+ Log::Print("Handshake error from SecTrustSetAnchorCertificates: %ld\n",
+ static_cast<intptr_t>(status));
+ }
+ CFRelease(trusted_certs);
+ CFRelease(peer_trust);
+ return status;
+ }
+
+ if (SSL_LOG_STATUS) {
+ Log::Print("Handshake %s built in root certs\n",
+ cert_context_->trust_builtin() ? "trusting" : "not trusting");
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(
+ peer_trust, !cert_context_->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::Print("Trust eval failed: trust_restul = %d\n", trust_result);
+ }
+ bad_cert_ = true;
+ return errSSLBadCert;
+ }
+}
+
+
+OSStatus SSLFilter::Handshake() {
+ ASSERT(cert_context_ != NULL);
+ ASSERT(ssl_context_ != NULL);
+ // Try and push handshake along.
+ if (SSL_LOG_STATUS) {
+ Log::Print("Doing SSLHandshake\n");
+ }
+ OSStatus status = SSLHandshake(ssl_context_);
+ if (SSL_LOG_STATUS) {
+ Log::Print("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::Print("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::Print("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::Print("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() {
+ // cert_context_ deleted by finalizer. Don't delete here.
+ cert_context_ = NULL;
+ 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;
+ }
+}
+
+
+static intptr_t AvailableToRead(intptr_t start, intptr_t end, intptr_t size) {
+ intptr_t data_available = 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;
+ start += available;
+ data_available += available;
+ ASSERT(start <= size);
+ if (start == size) {
+ start = 0;
+ }
+ }
+ if (start < end) {
+ intptr_t available = end - start;
+ start += available;
+ data_available += available;
+ ASSERT(start <= end);
+ }
+ return data_available;
+}
+
+
+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 (AvailableToRead(start, end, size) < requested) {
+ *data_requested = 0;
+ return errSSLWouldBlock;
+ }
+
+ 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::Print("SSLReadCallback: requested: %ld, read %ld bytes\n",
+ *data_requested, data_read);
+ }
+
+ filter->SetBufferStart(kReadEncrypted, start);
+ *data_requested = data_read;
+ return 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 ((status != noErr) && (status != errSSLWouldBlock)) {
+ *bytes_processed = 0;
+ return status;
+ }
+ }
+ if (SSL_LOG_DATA) {
+ Log::Print("ProcessReadPlaintextBuffer: requested: %ld, read %ld bytes\n",
+ length, bytes);
+ }
+ *bytes_processed = static_cast<intptr_t>(bytes);
+ return status;
+}
+
+
+intptr_t SpaceToWrite(intptr_t start, intptr_t end, intptr_t size) {
+ intptr_t writable_space = 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;
+ end += available;
+ writable_space += available;
+ ASSERT(end <= size);
+ if (end == size) {
+ end = 0;
+ }
+ }
+ if (start > end + 1) {
+ intptr_t available = (start - 1) - end;
+ end += available;
+ writable_space += available;
+ ASSERT(end < start);
+ }
+
+ return writable_space;
+}
+
+
+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;
+
+ if (SpaceToWrite(start, end, size) < provided) {
+ *data_provided = 0;
+ return errSSLWouldBlock;
+ }
+
+ // 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::Print("SSLWriteCallback: provided: %ld, written %ld bytes\n",
+ *data_provided, data_written);
+ }
+
+ filter->SetBufferEnd(kWriteEncrypted, end);
+ *data_provided = data_written;
+ return 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 ((status != noErr) && (status != errSSLWouldBlock)) {
+ *bytes_processed = 0;
+ return status;
+ }
+ }
+ *bytes_processed = static_cast<intptr_t>(bytes);
+ return status;
+}
+
+} // namespace bin
+} // namespace dart
+
+#endif // defined(TARGET_OS_MACOS)
« no previous file with comments | « runtime/bin/secure_socket_macos.h ('k') | runtime/bin/secure_socket_patch.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698