// 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 HOST_OS_IOS #include "bin/secure_socket.h" #include "bin/secure_socket_ios.h" #include #include #include #include #include #include #include #include #include #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(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(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(&filter))); return filter; } static void DeleteFilter(void* isolate_data, Dart_WeakPersistentHandle handle, void* context_pointer) { SSLFilter* filter = reinterpret_cast(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(filter)); RETURN_IF_ERROR(err); Dart_NewWeakPersistentHandle(dart_this, reinterpret_cast(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(&context))); return context; } static void DeleteCertContext(void* isolate_data, Dart_WeakPersistentHandle handle, void* context_pointer) { SSLCertContext* context = static_cast(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(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(&certificate))); return certificate; } static void ReleaseCertificate(void* isolate_data, Dart_WeakPersistentHandle handle, void* context_pointer) { SecCertificateRef cert = reinterpret_cast(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(certificate), approximate_size_of_certificate, ReleaseCertificate); Dart_Handle status = Dart_SetNativeInstanceField( result, kX509NativeFieldIndex, reinterpret_cast(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(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(CFArrayGetValueAtIndex(items, i)); ASSERT(CFGetTypeID(item) == CFDictionaryGetTypeID()); CFDictionaryRef dict = reinterpret_cast(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(const_cast(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(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(&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(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(filter_object.Value()); RefCntReleaseScope 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(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(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(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(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(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(buffer_size); encrypted_buffer_size_ = static_cast(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(const_cast(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(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(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(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(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(const_cast(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(reinterpret_cast(connection)); uint8_t* datap = reinterpret_cast(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(*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(*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((buffers_[kReadPlaintext] + start)), length, &bytes); if (SSL_LOG_STATUS) { Log::PrintErr("SSLRead: status = %ld\n", static_cast(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(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(reinterpret_cast(connection)); const uint8_t* datap = reinterpret_cast(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(*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(buffers_[kWritePlaintext] + start), length, &bytes); if (SSL_LOG_STATUS) { Log::PrintErr("SSLWrite: status = %ld\n", static_cast(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(bytes); return status; } } // namespace bin } // namespace dart #endif // HOST_OS_IOS #endif // !defined(DART_IO_SECURE_SOCKET_DISABLED)