// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "bin/secure_socket.h" #include #include #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/utils.h" #include "include/dart_api.h" namespace dart { namespace bin { bool SSLFilter::library_initialized_ = false; // To protect library initialization. Mutex* SSLFilter::mutex_ = new Mutex(); int SSLFilter::filter_ssl_index; 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 int SSL_ERROR_MESSAGE_BUFFER_SIZE = 1000; /* Get the error messages from BoringSSL, and put them in buffer as a * null-terminated string. */ static void FetchErrorString(char* buffer, int length) { buffer[0] = '\0'; int error = ERR_get_error(); while (error != 0) { int used = strlen(buffer); int free_length = length - used; if (free_length > 16) { // Enough room for error code at least. if (used > 0) { buffer[used] = '\n'; buffer[used + 1] = '\0'; used++; free_length--; } ERR_error_string_n(error, buffer + used, free_length); // ERR_error_string_n is guaranteed to leave a null-terminated string. } error = ERR_get_error(); } } /* Handle an error reported from the BoringSSL library. */ static void ThrowIOException(int status, const char* exception_type, const char* message, bool free_message = false) { char error_string[SSL_ERROR_MESSAGE_BUFFER_SIZE]; FetchErrorString(error_string, SSL_ERROR_MESSAGE_BUFFER_SIZE); OSError os_error_struct(status, error_string, 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)); if (free_message) { free(const_cast(message)); } Dart_ThrowException(exception); UNREACHABLE(); } 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 SetFilter(Dart_NativeArguments args, SSLFilter* filter) { Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0)); ASSERT(Dart_IsInstance(dart_this)); ThrowIfError(Dart_SetNativeInstanceField( dart_this, kSSLFilterNativeFieldIndex, reinterpret_cast(filter))); } static SSL_CTX* GetSecurityContext(Dart_NativeArguments args) { SSL_CTX* 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 FreeSecurityContext( void* isolate_data, Dart_WeakPersistentHandle handle, void* context_pointer) { SSL_CTX* context = static_cast(context_pointer); SSL_CTX_free(context); } static void SetSecurityContext(Dart_NativeArguments args, SSL_CTX* context) { const int approximate_size_of_context = 1500; Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0)); ASSERT(Dart_IsInstance(dart_this)); ThrowIfError(Dart_SetNativeInstanceField( dart_this, kSecurityContextNativeFieldIndex, reinterpret_cast(context))); Dart_NewWeakPersistentHandle(dart_this, context, approximate_size_of_context, FreeSecurityContext); } static X509* GetX509Certificate(Dart_NativeArguments args) { X509* 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; } // Forward declaration. static void SetAlpnProtocolList(Dart_Handle protocols_handle, SSL* ssl, SSL_CTX* context, bool is_server); void FUNCTION_NAME(SecureSocket_Init)(Dart_NativeArguments args) { Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0)); SSLFilter* filter = new SSLFilter; SetFilter(args, filter); filter->Init(dart_this); } void FUNCTION_NAME(SecureSocket_Connect)(Dart_NativeArguments args) { 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)); Dart_Handle protocols_handle = ThrowIfError(Dart_GetNativeArgument(args, 6)); 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)); SSL_CTX* context = NULL; if (!Dart_IsNull(context_object)) { ThrowIfError(Dart_GetNativeInstanceField( context_object, kSecurityContextNativeFieldIndex, reinterpret_cast(&context))); } // The protocols_handle is guaranteed to be a valid Uint8List. // It will have the correct length encoding of the protocols array. ASSERT(!Dart_IsNull(protocols_handle)); GetFilter(args)->Connect(host_name, context, is_server, request_client_certificate, require_client_certificate, protocols_handle); } void FUNCTION_NAME(SecureSocket_Destroy)(Dart_NativeArguments args) { SSLFilter* filter = GetFilter(args); SetFilter(args, NULL); filter->Destroy(); delete filter; } void FUNCTION_NAME(SecureSocket_Handshake)(Dart_NativeArguments args) { GetFilter(args)->Handshake(); } void FUNCTION_NAME(SecureSocket_GetSelectedProtocol)( Dart_NativeArguments args) { GetFilter(args)->GetSelectedProtocol(args); } 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(GetFilter(args)); Dart_SetReturnValue(args, Dart_NewInteger(filter_pointer)); } static Dart_Handle WrappedX509Certificate(X509* certificate) { 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)); Dart_Handle status = Dart_SetNativeInstanceField( result, kX509NativeFieldIndex, reinterpret_cast(certificate)); if (Dart_IsError(status)) { return status; } return result; } int CertificateCallback(int preverify_ok, X509_STORE_CTX* store_ctx) { if (preverify_ok == 1) return 1; Dart_Isolate isolate = Dart_CurrentIsolate(); if (isolate == NULL) { FATAL("CertificateCallback called with no current isolate\n"); } X509* certificate = X509_STORE_CTX_get_current_cert(store_ctx); int ssl_index = SSL_get_ex_data_X509_STORE_CTX_idx(); SSL* ssl = static_cast( X509_STORE_CTX_get_ex_data(store_ctx, ssl_index)); SSLFilter* filter = static_cast( SSL_get_ex_data(ssl, SSLFilter::filter_ssl_index)); Dart_Handle callback = filter->bad_certificate_callback(); if (Dart_IsNull(callback)) return 0; Dart_Handle args[1]; args[0] = WrappedX509Certificate(certificate); if (Dart_IsError(args[0])) { filter->callback_error = args[0]; return 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())); } if (Dart_IsError(result)) { filter->callback_error = result; return 0; } return DartUtils::GetBooleanValue(result); } void FUNCTION_NAME(SecurityContext_Allocate)(Dart_NativeArguments args) { SSLFilter::InitializeLibrary(); SSL_CTX* context = SSL_CTX_new(TLS_method()); SSL_CTX_set_verify(context, SSL_VERIFY_PEER, CertificateCallback); SSL_CTX_set_min_version(context, TLS1_VERSION); SSL_CTX_set_cipher_list(context, "HIGH:MEDIUM"); SSL_CTX_set_cipher_list_tls11(context, "HIGH:MEDIUM"); SetSecurityContext(args, context); } int PasswordCallback(char* buf, int size, int rwflag, void* userdata) { char* password = static_cast(userdata); ASSERT(size == PEM_BUFSIZE); strncpy(buf, password, size); return strlen(password); } void CheckStatus(int status, const char* type, const char* message) { // TODO(24183): Take appropriate action on failed calls, // throw exception that includes all messages from the error stack. if (status == 1) return; if (SSL_LOG_STATUS) { int error = ERR_get_error(); Log::PrintErr("Failed: %s status %d", message, status); char error_string[SSL_ERROR_MESSAGE_BUFFER_SIZE]; ERR_error_string_n(error, error_string, SSL_ERROR_MESSAGE_BUFFER_SIZE); Log::PrintErr("ERROR: %d %s\n", error, error_string); } ThrowIOException(status, type, message); } void FUNCTION_NAME(SecurityContext_UsePrivateKeyAsBytes)( Dart_NativeArguments args) { SSL_CTX* context = GetSecurityContext(args); Dart_Handle key_object = ThrowIfError(Dart_GetNativeArgument(args, 1)); if (!Dart_IsTypedData(key_object) && !Dart_IsList(key_object)) { Dart_ThrowException(DartUtils::NewDartArgumentError( "keyBytes argument to SecurityContext.usePrivateKey " "is not a List")); } Dart_Handle password_object = ThrowIfError(Dart_GetNativeArgument(args, 2)); 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( "SecurityContext.usePrivateKey password length is greater than" " 1023 (PEM_BUFSIZE)")); } } else if (Dart_IsNull(password_object)) { password = ""; } else { Dart_ThrowException(DartUtils::NewDartArgumentError( "SecurityContext.usePrivateKey password is not a String or null")); } uint8_t* key_bytes = NULL; intptr_t key_bytes_len = 0; bool is_typed_data = false; if (Dart_IsTypedData(key_object)) { is_typed_data = true; Dart_TypedData_Type typ; ThrowIfError(Dart_TypedDataAcquireData( key_object, &typ, reinterpret_cast(&key_bytes), &key_bytes_len)); } else { ASSERT(Dart_IsList(key_object)); ThrowIfError(Dart_ListLength(key_object, &key_bytes_len)); key_bytes = new uint8_t[key_bytes_len]; Dart_Handle err = Dart_ListGetAsBytes(key_object, 0, key_bytes, key_bytes_len); if (Dart_IsError(err)) { delete[] key_bytes; Dart_PropagateError(err); } } ASSERT(key_bytes != NULL); BIO* bio = BIO_new_mem_buf(key_bytes, key_bytes_len); EVP_PKEY *key = PEM_read_bio_PrivateKey( bio, NULL, PasswordCallback, const_cast(password)); int status = SSL_CTX_use_PrivateKey(context, key); BIO_free(bio); if (is_typed_data) { ThrowIfError(Dart_TypedDataReleaseData(key_object)); } else { delete[] key_bytes; } // TODO(24184): Handle different expected errors here - file missing, // incorrect password, file not a PEM, and throw exceptions. // CheckStatus should also throw an exception in uncaught cases. CheckStatus(status, "TlsException", "Failure in usePrivateKey"); } void FUNCTION_NAME(SecurityContext_SetTrustedCertificates)( Dart_NativeArguments args) { SSL_CTX* context = GetSecurityContext(args); Dart_Handle filename_object = ThrowIfError(Dart_GetNativeArgument(args, 1)); const char* filename = NULL; if (Dart_IsString(filename_object)) { ThrowIfError(Dart_StringToCString(filename_object, &filename)); } Dart_Handle directory_object = ThrowIfError(Dart_GetNativeArgument(args, 2)); const char* directory = NULL; if (Dart_IsString(directory_object)) { ThrowIfError(Dart_StringToCString(directory_object, &directory)); } else if (Dart_IsNull(directory_object)) { directory = NULL; } else { Dart_ThrowException(DartUtils::NewDartArgumentError( "Directory argument to SecurityContext.setTrustedCertificates is not " "a String or null")); } int status = SSL_CTX_load_verify_locations(context, filename, directory); CheckStatus( status, "TlsException", "SSL_CTX_load_verify_locations"); } void FUNCTION_NAME(SecurityContext_TrustBuiltinRoots)( Dart_NativeArguments args) { SSL_CTX* context = GetSecurityContext(args); X509_STORE* store = SSL_CTX_get_cert_store(context); BIO* roots_bio = BIO_new_mem_buf(const_cast(root_certificates_pem), root_certificates_pem_length); X509* root_cert; // PEM_read_bio_X509 reads PEM-encoded certificates from a bio (in our case, // backed by a memory buffer), and returns X509 objects, one by one. // When the end of the bio is reached, it returns null. while ((root_cert = PEM_read_bio_X509(roots_bio, NULL, NULL, NULL))) { X509_STORE_add_cert(store, root_cert); } BIO_free(roots_bio); } void FUNCTION_NAME(SecurityContext_UseCertificateChain)( Dart_NativeArguments args) { SSL_CTX* context = GetSecurityContext(args); Dart_Handle filename_object = ThrowIfError(Dart_GetNativeArgument(args, 1)); const char* filename = NULL; if (Dart_IsString(filename_object)) { ThrowIfError(Dart_StringToCString(filename_object, &filename)); } else { Dart_ThrowException(DartUtils::NewDartArgumentError( "file argument in SecurityContext.useCertificateChain" " is not a String")); } int status = SSL_CTX_use_certificate_chain_file(context, filename); CheckStatus(status, "TlsException", "Failure in useCertificateChain"); } void FUNCTION_NAME(SecurityContext_SetClientAuthorities)( Dart_NativeArguments args) { SSL_CTX* context = GetSecurityContext(args); Dart_Handle filename_object = ThrowIfError(Dart_GetNativeArgument(args, 1)); const char* filename = NULL; if (Dart_IsString(filename_object)) { ThrowIfError(Dart_StringToCString(filename_object, &filename)); } else { Dart_ThrowException(DartUtils::NewDartArgumentError( "file argument in SecurityContext.setClientAuthorities" " is not a String")); } STACK_OF(X509_NAME)* certificate_names; certificate_names = SSL_load_client_CA_file(filename); if (certificate_names != NULL) { SSL_CTX_set_client_CA_list(context, certificate_names); } else { Dart_ThrowException(DartUtils::NewDartArgumentError( "Could not load certificate names from file in SetClientAuthorities")); } } void FUNCTION_NAME(SecurityContext_SetAlpnProtocols)( Dart_NativeArguments args) { SSL_CTX* context = GetSecurityContext(args); Dart_Handle protocols_handle = ThrowIfError(Dart_GetNativeArgument(args, 1)); Dart_Handle is_server_handle = ThrowIfError(Dart_GetNativeArgument(args, 2)); if (Dart_IsBoolean(is_server_handle)) { bool is_server = DartUtils::GetBooleanValue(is_server_handle); SetAlpnProtocolList(protocols_handle, NULL, context, is_server); } else { Dart_ThrowException(DartUtils::NewDartArgumentError( "Non-boolean is_server argument passed to SetAlpnProtocols")); } } void FUNCTION_NAME(X509_Subject)( Dart_NativeArguments args) { X509* certificate = GetX509Certificate(args); X509_NAME* subject = X509_get_subject_name(certificate); char* subject_string = X509_NAME_oneline(subject, NULL, 0); Dart_SetReturnValue(args, Dart_NewStringFromCString(subject_string)); OPENSSL_free(subject_string); } void FUNCTION_NAME(X509_Issuer)( Dart_NativeArguments args) { X509* certificate = GetX509Certificate(args); X509_NAME* issuer = X509_get_issuer_name(certificate); char* issuer_string = X509_NAME_oneline(issuer, NULL, 0); Dart_SetReturnValue(args, Dart_NewStringFromCString(issuer_string)); OPENSSL_free(issuer_string); } static Dart_Handle ASN1TimeToMilliseconds(ASN1_TIME* aTime) { ASN1_UTCTIME* epoch_start = M_ASN1_UTCTIME_new(); ASN1_UTCTIME_set_string(epoch_start, "700101000000Z"); int days; int seconds; int result = ASN1_TIME_diff(&days, &seconds, epoch_start, aTime); M_ASN1_UTCTIME_free(epoch_start); if (result != 1) { // TODO(whesse): Propagate an error to Dart. Log::PrintErr("ASN1Time error %d\n", result); } return Dart_NewInteger((86400LL * days + seconds) * 1000LL); } void FUNCTION_NAME(X509_StartValidity)( Dart_NativeArguments args) { X509* certificate = GetX509Certificate(args); ASN1_TIME* not_before = X509_get_notBefore(certificate); Dart_SetReturnValue(args, ASN1TimeToMilliseconds(not_before)); } void FUNCTION_NAME(X509_EndValidity)( Dart_NativeArguments args) { X509* certificate = GetX509Certificate(args); ASN1_TIME* not_after = X509_get_notAfter(certificate); Dart_SetReturnValue(args, ASN1TimeToMilliseconds(not_after)); } /** * Pushes data through the SSL filter, reading and writing from circular * buffers shared with Dart. * * 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()); bool in_handshake = CObjectBool(request[1]).Value(); int starts[SSLFilter::kNumBuffers]; int ends[SSLFilter::kNumBuffers]; for (int i = 0; i < SSLFilter::kNumBuffers; ++i) { starts[i] = CObjectInt32(request[2 * i + 2]).Value(); ends[i] = CObjectInt32(request[2 * i + 3]).Value(); } if (filter->ProcessAllBuffers(starts, ends, in_handshake)) { CObjectArray* result = new CObjectArray( CObject::NewArray(SSLFilter::kNumBuffers * 2)); for (int 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 { int32_t error_code = static_cast(ERR_peek_error()); char error_string[SSL_ERROR_MESSAGE_BUFFER_SIZE]; FetchErrorString(error_string, SSL_ERROR_MESSAGE_BUFFER_SIZE); CObjectArray* result = new CObjectArray(CObject::NewArray(2)); result->SetAt(0, new CObjectInt32(CObject::NewInt32(error_code))); result->SetAt(1, new CObjectString(CObject::NewString(error_string))); return result; } } bool SSLFilter::ProcessAllBuffers(int starts[kNumBuffers], int ends[kNumBuffers], bool in_handshake) { for (int i = 0; i < kNumBuffers; ++i) { if (in_handshake && (i == kReadPlaintext || i == kWritePlaintext)) continue; int start = starts[i]; int end = ends[i]; int 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: case kWriteEncrypted: // 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). int buffer_end = (start == 0) ? size - 1 : size; int bytes = (i == kReadPlaintext) ? ProcessReadPlaintextBuffer(end, buffer_end) : ProcessWriteEncryptedBuffer(end, buffer_end); if (bytes < 0) return false; end += bytes; ASSERT(end <= size); if (end == size) end = 0; } if (start > end + 1) { int bytes = (i == kReadPlaintext) ? ProcessReadPlaintextBuffer(end, start - 1) : ProcessWriteEncryptedBuffer(end, start - 1); if (bytes < 0) return false; end += bytes; ASSERT(end < start); } ends[i] = end; break; case kReadEncrypted: 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). int bytes = (i == kReadEncrypted) ? ProcessReadEncryptedBuffer(start, size) : ProcessWritePlaintextBuffer(start, size); if (bytes < 0) return false; start += bytes; ASSERT(start <= size); if (start == size) start = 0; } if (start < end) { int bytes = (i == kReadEncrypted) ? ProcessReadEncryptedBuffer(start, end) : ProcessWritePlaintextBuffer(start, end); if (bytes < 0) return false; start += bytes; ASSERT(start <= end); } starts[i] = start; break; default: UNREACHABLE(); } } return true; } void SSLFilter::Init(Dart_Handle dart_this) { if (!library_initialized_) { InitializeLibrary(); } 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); InitializeBuffers(dart_this); } void SSLFilter::InitializeBuffers(Dart_Handle dart_this) { // Create SSLFilter buffers as ExternalUint8Array objects. Dart_Handle dart_buffers_object = ThrowIfError( Dart_GetField(dart_this, DartUtils::NewString("buffers"))); Dart_Handle secure_filter_impl_type = Dart_InstanceGetType(dart_this); Dart_Handle dart_buffer_size = ThrowIfError( Dart_GetField(secure_filter_impl_type, DartUtils::NewString("SIZE"))); int64_t buffer_size = DartUtils::GetIntegerValue(dart_buffer_size); Dart_Handle dart_encrypted_buffer_size = ThrowIfError( Dart_GetField(secure_filter_impl_type, DartUtils::NewString("ENCRYPTED_SIZE"))); int64_t encrypted_buffer_size = DartUtils::GetIntegerValue(dart_encrypted_buffer_size); 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"); for (int i = 0; i < kNumBuffers; ++i) { int size = isBufferEncrypted(i) ? encrypted_buffer_size_ : buffer_size_; dart_buffer_objects_[i] = Dart_NewPersistentHandle(Dart_ListGetAt(dart_buffers_object, i)); ASSERT(dart_buffer_objects_[i] != NULL); buffers_[i] = new uint8_t[size]; Dart_Handle data = ThrowIfError( Dart_NewExternalTypedData(Dart_TypedData_kUint8, buffers_[i], size)); ThrowIfError( Dart_SetField(Dart_HandleFromPersistent(dart_buffer_objects_[i]), data_identifier, data)); } } 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); } void SSLFilter::InitializeLibrary() { MutexLocker locker(mutex_); if (!library_initialized_) { SSL_library_init(); filter_ssl_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); ASSERT(filter_ssl_index >= 0); library_initialized_ = true; } } Dart_Handle SSLFilter::PeerCertificate() { X509* certificate = SSL_get_peer_certificate(ssl_); Dart_Handle x509_object = WrappedX509Certificate(certificate); if (Dart_IsError(x509_object)) { Dart_PropagateError(x509_object); } return x509_object; } int AlpnCallback(SSL *ssl, const uint8_t **out, uint8_t *outlen, const uint8_t *in, unsigned int inlen, void *arg) { // 'in' and 'arg' are sequences of (length, data) strings with 1-byte lengths. // 'arg' is 0-terminated. Finds the first string in 'arg' that is in 'in'. uint8_t* server_list = static_cast(arg); while (*server_list != 0) { uint8_t protocol_length = *server_list++; const uint8_t* client_list = in; while (client_list < in + inlen) { uint8_t client_protocol_length = *client_list++; if (client_protocol_length == protocol_length) { if (0 == memcmp(server_list, client_list, protocol_length)) { *out = client_list; *outlen = client_protocol_length; return SSL_TLSEXT_ERR_OK; // Success } } client_list += client_protocol_length; } server_list += protocol_length; } // TODO(23580): Make failure send a fatal alert instead of ignoring ALPN. return SSL_TLSEXT_ERR_NOACK; } // Sets the protocol list for ALPN on a SSL object or a context. static void SetAlpnProtocolList(Dart_Handle protocols_handle, SSL* ssl, SSL_CTX* context, bool is_server) { // Enable ALPN (application layer protocol negotiation) if the caller provides // a valid list of supported protocols. Dart_TypedData_Type protocols_type; uint8_t* protocol_string = NULL; uint8_t* protocol_string_copy = NULL; intptr_t protocol_string_len = 0; int status; Dart_Handle result = Dart_TypedDataAcquireData( protocols_handle, &protocols_type, reinterpret_cast(&protocol_string), &protocol_string_len); if (Dart_IsError(result)) { Dart_PropagateError(result); } if (protocols_type != Dart_TypedData_kUint8) { Dart_TypedDataReleaseData(protocols_handle); Dart_PropagateError(Dart_NewApiError( "Unexpected type for protocols (expected valid Uint8List).")); } if (protocol_string_len > 0) { if (is_server) { // ALPN on server connections must be set on an SSL_CTX object, // not on the SSL object of the individual connection. ASSERT(context != NULL); ASSERT(ssl == NULL); // Because it must be passed as a single void*, terminate // the list of (length, data) strings with a length 0 string. protocol_string_copy = static_cast(malloc(protocol_string_len + 1)); memmove(protocol_string_copy, protocol_string, protocol_string_len); protocol_string_copy[protocol_string_len] = '\0'; SSL_CTX_set_alpn_select_cb(context, AlpnCallback, protocol_string_copy); // TODO(whesse): If this function is called again, free the previous // protocol_string_copy. It may be better to keep this as a native // field on the Dart object, since fetching it from the structure is // not in the public api. // Also free protocol_string_copy when the context is destroyed, // in FreeSecurityContext() } else { // The function makes a local copy of protocol_string, which it owns. if (ssl != NULL) { ASSERT(context == NULL); status = SSL_set_alpn_protos(ssl, protocol_string, protocol_string_len); } else { ASSERT(context != NULL); ASSERT(ssl == NULL); status = SSL_CTX_set_alpn_protos( context, protocol_string, protocol_string_len); } ASSERT(status == 0); // The function returns a non-standard status. } } Dart_TypedDataReleaseData(protocols_handle); } void SSLFilter::Connect(const char* hostname, SSL_CTX* context, bool is_server, bool request_client_certificate, bool require_client_certificate, Dart_Handle protocols_handle) { is_server_ = is_server; if (in_handshake_) { FATAL("Connect called twice on the same _SecureFilter."); } int status; int error; BIO* ssl_side; status = BIO_new_bio_pair(&ssl_side, 10000, &socket_side_, 10000); CheckStatus(status, "TlsException", "BIO_new_bio_pair"); assert(context != NULL); ssl_ = SSL_new(context); SSL_set_bio(ssl_, ssl_side, ssl_side); SSL_set_mode(ssl_, SSL_MODE_AUTO_RETRY); // TODO(whesse): Is this right? SSL_set_ex_data(ssl_, filter_ssl_index, this); if (is_server_) { int certificate_mode = request_client_certificate ? SSL_VERIFY_PEER : SSL_VERIFY_NONE; if (require_client_certificate) { certificate_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; } SSL_set_verify(ssl_, certificate_mode, NULL); } else { SetAlpnProtocolList(protocols_handle, ssl_, NULL, false); status = SSL_set_tlsext_host_name(ssl_, hostname); CheckStatus(status, "TlsException", "Set SNI host name"); // Sets the hostname in the certificate-checking object, so it is checked // against the certificate presented by the server. X509_VERIFY_PARAM* certificate_checking_parameters = SSL_get0_param(ssl_); hostname_ = strdup(hostname); X509_VERIFY_PARAM_set_flags(certificate_checking_parameters, X509_V_FLAG_PARTIAL_CHAIN | X509_V_FLAG_TRUSTED_FIRST); X509_VERIFY_PARAM_set_hostflags(certificate_checking_parameters, 0); status = X509_VERIFY_PARAM_set1_host(certificate_checking_parameters, hostname_, strlen(hostname_)); CheckStatus(status, "TlsException", "Set hostname for certificate checking"); } // Make the connection: if (is_server_) { status = SSL_accept(ssl_); if (SSL_LOG_STATUS) Log::Print("SSL_accept status: %d\n", status); if (status != 1) { // TODO(whesse): expect a needs-data error here. Handle other errors. error = SSL_get_error(ssl_, status); if (SSL_LOG_STATUS) Log::Print("SSL_accept error: %d\n", error); } } else { status = SSL_connect(ssl_); if (SSL_LOG_STATUS) Log::Print("SSL_connect status: %d\n", status); if (status != 1) { // TODO(whesse): expect a needs-data error here. Handle other errors. error = SSL_get_error(ssl_, status); if (SSL_LOG_STATUS) Log::Print("SSL_connect error: %d\n", error); } } Handshake(); } int printErrorCallback(const char *str, size_t len, void *ctx) { Log::PrintErr("%.*s\n", static_cast(len), str); return 1; } void SSLFilter::Handshake() { // Try and push handshake along. int status; status = SSL_do_handshake(ssl_); if (callback_error != NULL) { // The SSL_do_handshake will try performing a handshake and might call // a CertificateCallback. If the certificate validation // failed the 'callback_error" will be set by the certificateCallback // logic and we propagate the error" Dart_PropagateError(callback_error); } if (SSL_want_write(ssl_) || SSL_want_read(ssl_)) { in_handshake_ = true; return; } CheckStatus(status, "HandshakeException", is_server_ ? "Handshake error in server" : "Handshake error in client"); // Handshake succeeded. if (in_handshake_) { // TODO(24071): Check return value of SSL_get_verify_result, this // should give us the hostname check. int result = SSL_get_verify_result(ssl_); if (SSL_LOG_STATUS) { Log::Print("Handshake verification status: %d\n", result); X509* peer_certificate = SSL_get_peer_certificate(ssl_); if (peer_certificate == NULL) { Log::Print("No peer certificate received\n"); } else { X509_NAME* s_name = X509_get_subject_name(peer_certificate); printf("Peer certificate SN: "); X509_NAME_print_ex_fp(stdout, s_name, 4, 0); printf("\n"); } } ThrowIfError(Dart_InvokeClosure( Dart_HandleFromPersistent(handshake_complete_), 0, NULL)); in_handshake_ = false; } } void SSLFilter::GetSelectedProtocol(Dart_NativeArguments args) { const uint8_t* protocol; unsigned length; SSL_get0_alpn_selected(ssl_, &protocol, &length); if (length == 0) { Dart_SetReturnValue(args, Dart_Null()); } else { Dart_SetReturnValue(args, Dart_NewStringFromUTF8(protocol, length)); } } 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. } void SSLFilter::Destroy() { if (ssl_ != NULL) { SSL_free(ssl_); ssl_ = NULL; } if (socket_side_ != NULL) { BIO_free(socket_side_); socket_side_ = NULL; } if (hostname_ != NULL) { free(hostname_); hostname_ = NULL; } for (int i = 0; i < kNumBuffers; ++i) { Dart_DeletePersistentHandle(dart_buffer_objects_[i]); delete[] buffers_[i]; } Dart_DeletePersistentHandle(string_start_); Dart_DeletePersistentHandle(string_length_); Dart_DeletePersistentHandle(handshake_complete_); Dart_DeletePersistentHandle(bad_certificate_callback_); } /* Read decrypted data from the filter to the circular buffer */ int SSLFilter::ProcessReadPlaintextBuffer(int start, int end) { int length = end - start; int bytes_processed = 0; if (length > 0) { bytes_processed = SSL_read( ssl_, reinterpret_cast((buffers_[kReadPlaintext] + start)), length); if (bytes_processed < 0) { int error = SSL_get_error(ssl_, bytes_processed); USE(error); bytes_processed = 0; } } return bytes_processed; } int SSLFilter::ProcessWritePlaintextBuffer(int start, int end) { int length = end - start; int bytes_processed = SSL_write( ssl_, buffers_[kWritePlaintext] + start, length); if (bytes_processed < 0) { if (SSL_LOG_DATA) { Log::Print("SSL_write returned error %d\n", bytes_processed); } return 0; } return bytes_processed; } /* Read encrypted data from the circular buffer to the filter */ int SSLFilter::ProcessReadEncryptedBuffer(int start, int end) { int length = end - start; if (SSL_LOG_DATA) Log::Print( "Entering ProcessReadEncryptedBuffer with %d bytes\n", length); int bytes_processed = 0; if (length > 0) { bytes_processed = BIO_write(socket_side_, buffers_[kReadEncrypted] + start, length); if (bytes_processed <= 0) { bool retry = BIO_should_retry(socket_side_); if (!retry) { if (SSL_LOG_DATA) Log::Print( "BIO_write failed in ReadEncryptedBuffer\n"); } bytes_processed = 0; } } if (SSL_LOG_DATA) Log::Print( "Leaving ProcessReadEncryptedBuffer wrote %d bytes\n", bytes_processed); return bytes_processed; } int SSLFilter::ProcessWriteEncryptedBuffer(int start, int end) { int length = end - start; int bytes_processed = 0; if (length > 0) { bytes_processed = BIO_read(socket_side_, buffers_[kWriteEncrypted] + start, length); if (bytes_processed < 0) { if (SSL_LOG_DATA) Log::Print( "WriteEncrypted BIO_read returned error %d\n", bytes_processed); return 0; } else { if (SSL_LOG_DATA) Log::Print( "WriteEncrypted BIO_read wrote %d bytes\n", bytes_processed); } } return bytes_processed; } } // namespace bin } // namespace dart