| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #if !defined(DART_IO_SECURE_SOCKET_DISABLED) |
| |
| #include "bin/secure_socket_filter.h" |
| |
| #include <openssl/bio.h> |
| #include <openssl/ssl.h> |
| #include <openssl/x509.h> |
| |
| #include "bin/lockers.h" |
| #include "bin/secure_socket_utils.h" |
| #include "bin/security_context.h" |
| #include "bin/socket_base.h" |
| #include "platform/syslog.h" |
| #include "platform/text_buffer.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 { |
| |
| bool SSLFilter::library_initialized_ = false; |
| // To protect library initialization. |
| Mutex* SSLFilter::mutex_ = nullptr; |
| int SSLFilter::filter_ssl_index; |
| int SSLFilter::ssl_cert_context_index; |
| |
| void SSLFilter::Init() { |
| ASSERT(SSLFilter::mutex_ == nullptr); |
| SSLFilter::mutex_ = new Mutex(); |
| } |
| |
| void SSLFilter::Cleanup() { |
| ASSERT(SSLFilter::mutex_ != nullptr); |
| delete SSLFilter::mutex_; |
| SSLFilter::mutex_ = nullptr; |
| } |
| |
| const intptr_t SSLFilter::kInternalBIOSize = 10 * KB; |
| const intptr_t SSLFilter::kApproximateSize = |
| sizeof(SSLFilter) + (2 * SSLFilter::kInternalBIOSize); |
| |
| static SSLFilter* GetFilter(Dart_NativeArguments args) { |
| SSLFilter* filter = nullptr; |
| Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0)); |
| ASSERT(Dart_IsInstance(dart_this)); |
| ThrowIfError(Dart_GetNativeInstanceField( |
| dart_this, SSLFilter::kSSLFilterNativeFieldIndex, |
| reinterpret_cast<intptr_t*>(&filter))); |
| if (filter == nullptr) { |
| Dart_PropagateError(Dart_NewUnhandledExceptionError( |
| DartUtils::NewInternalError("No native peer"))); |
| } |
| return filter; |
| } |
| |
| static void DeleteFilter(void* isolate_data, void* context_pointer) { |
| SSLFilter* filter = reinterpret_cast<SSLFilter*>(context_pointer); |
| filter->Release(); |
| } |
| |
| static Dart_Handle SetFilter(Dart_NativeArguments args, SSLFilter* filter) { |
| ASSERT(filter != nullptr); |
| 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, SSLFilter::kSSLFilterNativeFieldIndex, |
| reinterpret_cast<intptr_t>(filter)); |
| RETURN_IF_ERROR(err); |
| Dart_NewFinalizableHandle(dart_this, reinterpret_cast<void*>(filter), |
| SSLFilter::kApproximateSize, DeleteFilter); |
| return Dart_Null(); |
| } |
| |
| void FUNCTION_NAME(SecureSocket_Init)(Dart_NativeArguments args) { |
| Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0)); |
| SSLFilter* filter = new SSLFilter(); |
| 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 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 = nullptr; |
| // TODO(whesse): Is truncating a Dart string containing \0 what we want? |
| ThrowIfError(Dart_StringToCString(host_name_object, &host_name)); |
| |
| SSLCertContext* context = nullptr; |
| if (!Dart_IsNull(context_object)) { |
| ThrowIfError(Dart_GetNativeInstanceField( |
| context_object, SSLCertContext::kSecurityContextNativeFieldIndex, |
| reinterpret_cast<intptr_t*>(&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); |
| // There are two paths that can clean up an SSLFilter object. First, |
| // there is this explicit call to Destroy(), called from |
| // _SecureFilter.destroy() in Dart code. After a call to destroy(), the Dart |
| // code maintains the invariant that there will be no further SSLFilter |
| // requests sent to the IO Service. Therefore, the internals of the SSLFilter |
| // are safe to deallocate, but not the SSLFilter itself, which is already |
| // set up to be cleaned up by the finalizer. |
| // |
| // The second path is through the finalizer, which we have to do in case |
| // some mishap prevents a call to _SecureFilter.destroy(). |
| filter->Destroy(); |
| } |
| |
| void FUNCTION_NAME(SecureSocket_Handshake)(Dart_NativeArguments args) { |
| Dart_Handle port = ThrowIfError(Dart_GetNativeArgument(args, 1)); |
| ASSERT(!Dart_IsNull(port)); |
| |
| Dart_Port port_id; |
| ThrowIfError(Dart_SendPortGetId(port, &port_id)); |
| int result = GetFilter(args)->Handshake(port_id); |
| Dart_SetReturnValue(args, Dart_NewInteger(result)); |
| } |
| |
| void FUNCTION_NAME(SecureSocket_MarkAsTrusted)(Dart_NativeArguments args) { |
| GetFilter(args)->MarkAsTrusted(args); |
| } |
| |
| void FUNCTION_NAME(SecureSocket_NewX509CertificateWrapper)( |
| Dart_NativeArguments args) { |
| // This is to be used only in conjunction with certificate trust evaluator |
| // running asynchronously, which is only used on mac/ios at the moment. |
| #if !defined(DART_HOST_OS_MACOS) |
| FATAL("This is to be used only on mac/ios platforms"); |
| #endif |
| intptr_t x509_pointer = DartUtils::GetNativeIntptrArgument(args, 0); |
| X509* x509 = reinterpret_cast<X509*>(x509_pointer); |
| Dart_SetReturnValue(args, X509Helper::WrappedX509Certificate(x509)); |
| } |
| |
| void FUNCTION_NAME(SecureSocket_GetSelectedProtocol)( |
| Dart_NativeArguments args) { |
| GetFilter(args)->GetSelectedProtocol(args); |
| } |
| |
| 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_RegisterKeyLogPort)(Dart_NativeArguments args) { |
| Dart_Handle port = ThrowIfError(Dart_GetNativeArgument(args, 1)); |
| ASSERT(!Dart_IsNull(port)); |
| |
| Dart_Port port_id; |
| ThrowIfError(Dart_SendPortGetId(port, &port_id)); |
| GetFilter(args)->RegisterKeyLogPort(port_id); |
| } |
| |
| void FUNCTION_NAME(SecureSocket_PeerCertificate)(Dart_NativeArguments args) { |
| Dart_Handle cert = ThrowIfError(GetFilter(args)->PeerCertificate()); |
| Dart_SetReturnValue(args, cert); |
| } |
| |
| void FUNCTION_NAME(SecureSocket_FilterPointer)(Dart_NativeArguments args) { |
| SSLFilter* filter = GetFilter(args); |
| // This filter pointer is passed to the IO Service thread. The IO Service |
| // thread must Release() the pointer when it is done with it. |
| filter->Retain(); |
| intptr_t filter_pointer = reinterpret_cast<intptr_t>(filter); |
| Dart_SetReturnValue(args, Dart_NewInteger(filter_pointer)); |
| } |
| |
| /** |
| * 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<SSLFilter*>(filter_object.Value()); |
| RefCntReleaseScope<SSLFilter> rs(filter); |
| |
| 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<int32_t>(ERR_peek_error()); |
| TextBuffer error_string(SecureSocketUtils::SSL_ERROR_MESSAGE_BUFFER_SIZE); |
| SecureSocketUtils::FetchErrorString(filter->ssl_, &error_string); |
| 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.buffer()))); |
| 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; |
| } |
| |
| Dart_Handle SSLFilter::Init(Dart_Handle dart_this) { |
| if (!library_initialized_) { |
| InitializeLibrary(); |
| } |
| ASSERT(string_start_ == nullptr); |
| string_start_ = Dart_NewPersistentHandle(DartUtils::NewString("start")); |
| ASSERT(string_start_ != nullptr); |
| ASSERT(string_length_ == nullptr); |
| string_length_ = Dart_NewPersistentHandle(DartUtils::NewString("length")); |
| ASSERT(string_length_ != nullptr); |
| ASSERT(bad_certificate_callback_ == nullptr); |
| bad_certificate_callback_ = Dart_NewPersistentHandle(Dart_Null()); |
| ASSERT(bad_certificate_callback_ != nullptr); |
| // 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<int>(buffer_size); |
| encrypted_buffer_size_ = static_cast<int>(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] != nullptr); |
| memset(buffers_[i], 0, size); |
| dart_buffer_objects_[i] = nullptr; |
| } |
| |
| 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] != nullptr); |
| 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(nullptr == handshake_complete_); |
| handshake_complete_ = Dart_NewPersistentHandle(complete); |
| |
| ASSERT(handshake_complete_ != nullptr); |
| } |
| |
| void SSLFilter::RegisterBadCertificateCallback(Dart_Handle callback) { |
| ASSERT(bad_certificate_callback_ != nullptr); |
| Dart_DeletePersistentHandle(bad_certificate_callback_); |
| bad_certificate_callback_ = Dart_NewPersistentHandle(callback); |
| ASSERT(bad_certificate_callback_ != nullptr); |
| } |
| |
| Dart_Handle SSLFilter::PeerCertificate() { |
| X509* ca = SSL_get_peer_certificate(ssl_); |
| if (ca == nullptr) { |
| return Dart_Null(); |
| } |
| return X509Helper::WrappedX509Certificate(ca); |
| } |
| |
| void SSLFilter::RegisterKeyLogPort(Dart_Port key_log_port) { |
| key_log_port_ = key_log_port; |
| } |
| |
| void SSLFilter::InitializeLibrary() { |
| MutexLocker locker(mutex_); |
| if (!library_initialized_) { |
| SSL_library_init(); |
| filter_ssl_index = |
| SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); |
| ASSERT(filter_ssl_index >= 0); |
| ssl_cert_context_index = |
| SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); |
| ASSERT(ssl_cert_context_index >= 0); |
| library_initialized_ = true; |
| } |
| } |
| |
| void SSLFilter::Connect(const char* hostname, |
| SSLCertContext* 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, kInternalBIOSize, &socket_side_, |
| kInternalBIOSize); |
| SecureSocketUtils::CheckStatusSSL(status, "TlsException", "BIO_new_bio_pair", |
| ssl_); |
| |
| ASSERT(context != nullptr); |
| ASSERT(context->context() != nullptr); |
| ssl_ = SSL_new(context->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 (context->allow_tls_renegotiation()) { |
| SSL_set_renegotiate_mode(ssl_, ssl_renegotiate_freely); |
| } |
| context->RegisterCallbacks(ssl_); |
| SSL_set_ex_data(ssl_, ssl_cert_context_index, context); |
| |
| TrustEvaluateHandlerFunc trust_evaluate_handler = |
| context->GetTrustEvaluateHandler(); |
| if (trust_evaluate_handler != nullptr) { |
| trust_evaluate_reply_port_ = Dart_NewNativePort( |
| "SSLCertContextTrustEvaluate", trust_evaluate_handler, |
| /*handle_concurrently=*/false); |
| } |
| 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, nullptr); |
| } else { |
| SSLCertContext::SetAlpnProtocolList(protocols_handle, ssl_, nullptr, false); |
| status = SSL_set_tlsext_host_name(ssl_, hostname); |
| SecureSocketUtils::CheckStatusSSL(status, "TlsException", |
| "Set SNI host name", ssl_); |
| // 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_ = Utils::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); |
| |
| // Use different check depending on whether the hostname is an IP address |
| // or a DNS name. |
| if (SocketBase::IsValidAddress(hostname_)) { |
| status = X509_VERIFY_PARAM_set1_ip_asc(certificate_checking_parameters, |
| hostname_); |
| } else { |
| status = X509_VERIFY_PARAM_set1_host(certificate_checking_parameters, |
| hostname_, strlen(hostname_)); |
| } |
| SecureSocketUtils::CheckStatusSSL( |
| status, "TlsException", "Set hostname for certificate checking", ssl_); |
| } |
| // Make the connection: |
| if (is_server_) { |
| status = SSL_accept(ssl_); |
| if (SSL_LOG_STATUS) { |
| Syslog::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) { |
| Syslog::Print("SSL_accept error: %d\n", error); |
| } |
| } |
| } else { |
| status = SSL_connect(ssl_); |
| if (SSL_LOG_STATUS) { |
| Syslog::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) { |
| Syslog::Print("SSL_connect error: %d\n", error); |
| } |
| } |
| } |
| // We don't expect certificate evaluation on first attempt, |
| // we expect requests for more bytes, therefore we could get away |
| // with passing illegal port. |
| Handshake(ILLEGAL_PORT); |
| } |
| |
| void SSLFilter::MarkAsTrusted(Dart_NativeArguments args) { |
| intptr_t certificate_pointer = DartUtils::GetNativeIntptrArgument(args, 1); |
| ASSERT(certificate_pointer != 0); |
| certificate_trust_state_.reset( |
| new X509TrustState(reinterpret_cast<X509*>(certificate_pointer), |
| DartUtils::GetNativeBooleanArgument(args, 2))); |
| if (SSL_LOG_STATUS) { |
| Syslog::Print("Mark %p as %strusted certificate\n", |
| certificate_trust_state_->x509(), |
| certificate_trust_state_->is_trusted() ? "" : "not "); |
| } |
| } |
| |
| int SSLFilter::Handshake(Dart_Port reply_port) { |
| // Set reply port to be used by CertificateVerificationCallback |
| // invoked by SSL_do_handshake: this is where results of |
| // certificate evaluation will be communicated to. |
| reply_port_ = reply_port; |
| |
| // Try and push handshake along. |
| int status = SSL_do_handshake(ssl_); |
| int error = SSL_get_error(ssl_, status); |
| if (error == SSL_ERROR_WANT_CERTIFICATE_VERIFY) { |
| return SSL_ERROR_WANT_CERTIFICATE_VERIFY; |
| } |
| if (callback_error != nullptr) { |
| // The SSL_do_handshake will try performing a handshake and might call one |
| // or both of: |
| // SSLCertContext::KeyLogCallback |
| // SSLCertContext::CertificateCallback |
| // |
| // If either of those functions fail, and this.callback_error has not |
| // already been set, then they will set this.callback_error to an error |
| // handle i.e. only the first error will be captured and propagated. |
| Dart_PropagateError(callback_error); |
| } |
| if (SSL_want_write(ssl_) || SSL_want_read(ssl_)) { |
| in_handshake_ = true; |
| return error; |
| } |
| SecureSocketUtils::CheckStatusSSL( |
| status, "HandshakeException", |
| is_server_ ? "Handshake error in server" : "Handshake error in client", |
| ssl_); |
| // 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) { |
| Syslog::Print("Handshake verification status: %d\n", result); |
| X509* peer_certificate = SSL_get_peer_certificate(ssl_); |
| if (peer_certificate == nullptr) { |
| Syslog::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, nullptr)); |
| in_handshake_ = false; |
| } |
| |
| return error; |
| } |
| |
| 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::FreeResources() { |
| if (ssl_ != nullptr) { |
| SSL_free(ssl_); |
| ssl_ = nullptr; |
| } |
| if (socket_side_ != nullptr) { |
| BIO_free(socket_side_); |
| socket_side_ = nullptr; |
| } |
| if (hostname_ != nullptr) { |
| free(hostname_); |
| hostname_ = nullptr; |
| } |
| for (int i = 0; i < kNumBuffers; ++i) { |
| if (buffers_[i] != nullptr) { |
| delete[] buffers_[i]; |
| buffers_[i] = nullptr; |
| } |
| } |
| } |
| |
| SSLFilter::~SSLFilter() { |
| FreeResources(); |
| } |
| |
| void SSLFilter::Destroy() { |
| for (int i = 0; i < kNumBuffers; ++i) { |
| if (dart_buffer_objects_[i] != nullptr) { |
| Dart_DeletePersistentHandle(dart_buffer_objects_[i]); |
| dart_buffer_objects_[i] = nullptr; |
| } |
| } |
| if (string_start_ != nullptr) { |
| Dart_DeletePersistentHandle(string_start_); |
| string_start_ = nullptr; |
| } |
| if (string_length_ != nullptr) { |
| Dart_DeletePersistentHandle(string_length_); |
| string_length_ = nullptr; |
| } |
| if (handshake_complete_ != nullptr) { |
| Dart_DeletePersistentHandle(handshake_complete_); |
| handshake_complete_ = nullptr; |
| } |
| if (bad_certificate_callback_ != nullptr) { |
| Dart_DeletePersistentHandle(bad_certificate_callback_); |
| bad_certificate_callback_ = nullptr; |
| } |
| if (trust_evaluate_reply_port_ != ILLEGAL_PORT) { |
| Dart_CloseNativePort(trust_evaluate_reply_port_); |
| trust_evaluate_reply_port_ = ILLEGAL_PORT; |
| } |
| FreeResources(); |
| } |
| |
| /* 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 (SSL_LOG_DATA) { |
| Syslog::Print("Entering ProcessReadPlaintextBuffer with %d bytes\n", |
| length); |
| } |
| if (length > 0) { |
| bytes_processed = SSL_read( |
| ssl_, reinterpret_cast<char*>((buffers_[kReadPlaintext] + start)), |
| length); |
| if (bytes_processed < 0) { |
| int error = SSL_get_error(ssl_, bytes_processed); |
| if (SSL_LOG_DATA) { |
| Syslog::Print("SSL_read returned error %d\n", error); |
| } |
| switch (error) { |
| case SSL_ERROR_SYSCALL: |
| case SSL_ERROR_SSL: |
| return -1; |
| default: |
| break; |
| } |
| bytes_processed = 0; |
| } |
| } |
| if (SSL_LOG_DATA) { |
| Syslog::Print("Leaving ProcessReadPlaintextBuffer read %d bytes\n", |
| bytes_processed); |
| } |
| return bytes_processed; |
| } |
| |
| int SSLFilter::ProcessWritePlaintextBuffer(int start, int end) { |
| int length = end - start; |
| if (SSL_LOG_DATA) { |
| Syslog::Print("Entering ProcessWritePlaintextBuffer with %d bytes\n", |
| length); |
| } |
| int bytes_processed = |
| SSL_write(ssl_, buffers_[kWritePlaintext] + start, length); |
| if (bytes_processed < 0) { |
| if (SSL_LOG_DATA) { |
| Syslog::Print("SSL_write returned error %d\n", bytes_processed); |
| } |
| return 0; |
| } |
| if (SSL_LOG_DATA) { |
| Syslog::Print("Leaving ProcessWritePlaintextBuffer wrote %d bytes\n", |
| bytes_processed); |
| } |
| 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) { |
| Syslog::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_) != 0; |
| if (!retry) { |
| if (SSL_LOG_DATA) { |
| Syslog::Print("BIO_write failed in ReadEncryptedBuffer\n"); |
| } |
| } |
| bytes_processed = 0; |
| } |
| } |
| if (SSL_LOG_DATA) { |
| Syslog::Print("Leaving ProcessReadEncryptedBuffer read %d bytes\n", |
| bytes_processed); |
| } |
| return bytes_processed; |
| } |
| |
| int SSLFilter::ProcessWriteEncryptedBuffer(int start, int end) { |
| int length = end - start; |
| int bytes_processed = 0; |
| if (SSL_LOG_DATA) { |
| Syslog::Print("Entering ProcessWriteEncryptedBuffer with %d bytes\n", |
| length); |
| } |
| if (length > 0) { |
| bytes_processed = |
| BIO_read(socket_side_, buffers_[kWriteEncrypted] + start, length); |
| if (bytes_processed < 0) { |
| if (SSL_LOG_DATA) { |
| Syslog::Print("WriteEncrypted BIO_read returned error %d\n", |
| bytes_processed); |
| } |
| return 0; |
| } else { |
| if (SSL_LOG_DATA) { |
| Syslog::Print("WriteEncrypted BIO_read wrote %d bytes\n", |
| bytes_processed); |
| } |
| } |
| } |
| return bytes_processed; |
| } |
| |
| } // namespace bin |
| } // namespace dart |
| |
| #endif // !defined(DART_IO_SECURE_SOCKET_DISABLED) |