| // 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/security_context.h" |
| |
| #include <openssl/bio.h> |
| #include <openssl/err.h> |
| #include <openssl/pkcs12.h> |
| #include <openssl/ssl.h> |
| #include <openssl/x509.h> |
| |
| #include "platform/globals.h" |
| |
| #include "bin/directory.h" |
| #include "bin/file.h" |
| #include "bin/secure_socket_filter.h" |
| #include "bin/secure_socket_utils.h" |
| #include "platform/syslog.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 { |
| |
| const char* SSLCertContext::root_certs_file_ = nullptr; |
| const char* SSLCertContext::root_certs_cache_ = nullptr; |
| bool SSLCertContext::long_ssl_cert_evaluation_ = false; |
| bool SSLCertContext::bypass_trusting_system_roots_ = false; |
| |
| int SSLCertContext::CertificateCallback(int preverify_ok, |
| X509_STORE_CTX* store_ctx) { |
| if (preverify_ok == 1) { |
| return 1; |
| } |
| Dart_Isolate isolate = Dart_CurrentIsolate(); |
| if (isolate == nullptr) { |
| 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<SSL*>(X509_STORE_CTX_get_ex_data(store_ctx, ssl_index)); |
| SSLFilter* filter = static_cast<SSLFilter*>( |
| SSL_get_ex_data(ssl, SSLFilter::filter_ssl_index)); |
| Dart_Handle callback = filter->bad_certificate_callback(); |
| if (Dart_IsNull(callback)) { |
| return 0; |
| } |
| |
| // Upref since the Dart X509 object may outlive the SecurityContext. |
| if (certificate != nullptr) { |
| X509_up_ref(certificate); |
| } |
| Dart_Handle args[1]; |
| args[0] = X509Helper::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())); |
| } |
| // See SSLFilter::Handshake for the semantics of filter->callback_error. |
| if (Dart_IsError(result) && filter->callback_error == nullptr) { |
| filter->callback_error = result; |
| return 0; |
| } |
| return static_cast<int>(DartUtils::GetBooleanValue(result)); |
| } |
| |
| void SSLCertContext::KeyLogCallback(const SSL* ssl, const char* line) { |
| SSLFilter* filter = static_cast<SSLFilter*>( |
| SSL_get_ex_data(ssl, SSLFilter::filter_ssl_index)); |
| |
| Dart_Port port = filter->key_log_port(); |
| if (port != ILLEGAL_PORT) { |
| DartUtils::PostString(port, line); |
| } |
| } |
| |
| SSLCertContext* 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, SSLCertContext::kSecurityContextNativeFieldIndex, |
| reinterpret_cast<intptr_t*>(&context))); |
| if (context == nullptr) { |
| Dart_PropagateError(Dart_NewUnhandledExceptionError( |
| DartUtils::NewInternalError("No native peer"))); |
| } |
| return context; |
| } |
| |
| static void DeleteSecurityContext(void* isolate_data, void* context_pointer) { |
| SSLCertContext* context = static_cast<SSLCertContext*>(context_pointer); |
| context->Release(); |
| } |
| |
| static Dart_Handle SetSecurityContext(Dart_NativeArguments args, |
| SSLCertContext* context) { |
| 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, SSLCertContext::kSecurityContextNativeFieldIndex, |
| reinterpret_cast<intptr_t>(context)); |
| RETURN_IF_ERROR(err); |
| Dart_NewFinalizableHandle(dart_this, context, |
| SSLCertContext::kApproximateSize, |
| DeleteSecurityContext); |
| return Dart_Null(); |
| } |
| |
| static void ReleaseCertificate(void* isolate_data, void* context_pointer) { |
| X509* cert = reinterpret_cast<X509*>(context_pointer); |
| X509_free(cert); |
| } |
| |
| static intptr_t EstimateX509Size(X509* certificate) { |
| intptr_t length = i2d_X509(certificate, nullptr); |
| length = length > 0 ? length : 0; |
| // An X509 is a tree of structures, which are either opaque or will be opaque |
| // in the future. Estimate the overhead to 512 bytes by rounding up |
| // sizeof(X509) + sizeof(X509_CINF). |
| return length + 512; |
| } |
| |
| // Returns the handle for a Dart object wrapping the X509 certificate object. |
| // The caller should own a reference to the X509 object whose reference count |
| // won't drop to zero before the ReleaseCertificate finalizer runs. |
| Dart_Handle X509Helper::WrappedX509Certificate(X509* certificate) { |
| if (certificate == nullptr) { |
| return Dart_Null(); |
| } |
| Dart_Handle x509_type = |
| DartUtils::GetDartType(DartUtils::kIOLibURL, "X509Certificate"); |
| if (Dart_IsError(x509_type)) { |
| X509_free(certificate); |
| return x509_type; |
| } |
| Dart_Handle arguments[] = {nullptr}; |
| Dart_Handle result = |
| Dart_New(x509_type, DartUtils::NewString("_"), 0, arguments); |
| if (Dart_IsError(result)) { |
| X509_free(certificate); |
| return result; |
| } |
| ASSERT(Dart_IsInstance(result)); |
| Dart_Handle status = |
| Dart_SetNativeInstanceField(result, SSLCertContext::kX509NativeFieldIndex, |
| reinterpret_cast<intptr_t>(certificate)); |
| if (Dart_IsError(status)) { |
| X509_free(certificate); |
| return status; |
| } |
| const intptr_t approximate_size_of_certificate = |
| EstimateX509Size(certificate); |
| ASSERT(approximate_size_of_certificate > 0); |
| Dart_NewFinalizableHandle(result, reinterpret_cast<void*>(certificate), |
| approximate_size_of_certificate, |
| ReleaseCertificate); |
| return result; |
| } |
| |
| static int SetTrustedCertificatesBytesPKCS12(SSL_CTX* context, |
| ScopedMemBIO* bio, |
| const char* password) { |
| CBS cbs; |
| CBS_init(&cbs, bio->data(), bio->length()); |
| |
| EVP_PKEY* key = nullptr; |
| ScopedX509Stack cert_stack(sk_X509_new_null()); |
| int status = PKCS12_get_key_and_certs(&key, cert_stack.get(), &cbs, password); |
| if (status == 0) { |
| return status; |
| } |
| |
| X509_STORE* store = SSL_CTX_get_cert_store(context); |
| X509* ca; |
| while ((ca = sk_X509_shift(cert_stack.get())) != nullptr) { |
| status = X509_STORE_add_cert(store, ca); |
| // X509_STORE_add_cert increments the reference count of cert on success. |
| X509_free(ca); |
| if (status == 0) { |
| return status; |
| } |
| } |
| |
| return status; |
| } |
| |
| static int SetTrustedCertificatesBytesPEM(SSL_CTX* context, BIO* bio) { |
| X509_STORE* store = SSL_CTX_get_cert_store(context); |
| |
| int status = 0; |
| X509* cert = nullptr; |
| while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) != |
| nullptr) { |
| status = X509_STORE_add_cert(store, cert); |
| // X509_STORE_add_cert increments the reference count of cert on success. |
| X509_free(cert); |
| if (status == 0) { |
| return status; |
| } |
| } |
| |
| // If no PEM start line is found, it means that we read to the end of the |
| // file, or that the file isn't PEM. In the first case, status will be |
| // non-zero indicating success. In the second case, status will be 0, |
| // indicating that we should try to read as PKCS12. If there is some other |
| // error, we return it up to the caller. |
| return SecureSocketUtils::NoPEMStartLine() ? status : 0; |
| } |
| |
| void SSLCertContext::SetTrustedCertificatesBytes(Dart_Handle cert_bytes, |
| const char* password) { |
| int status = 0; |
| { |
| ScopedMemBIO bio(cert_bytes); |
| status = SetTrustedCertificatesBytesPEM(context(), bio.bio()); |
| if (status == 0) { |
| if (SecureSocketUtils::NoPEMStartLine()) { |
| ERR_clear_error(); |
| BIO_reset(bio.bio()); |
| status = SetTrustedCertificatesBytesPKCS12(context(), &bio, password); |
| } |
| } else { |
| // The PEM file was successfully parsed. |
| ERR_clear_error(); |
| } |
| } |
| SecureSocketUtils::CheckStatus(status, "TlsException", |
| "Failure trusting builtin roots"); |
| } |
| |
| static int SetClientAuthoritiesPKCS12(SSL_CTX* context, |
| ScopedMemBIO* bio, |
| const char* password) { |
| CBS cbs; |
| CBS_init(&cbs, bio->data(), bio->length()); |
| |
| EVP_PKEY* key = nullptr; |
| ScopedX509Stack cert_stack(sk_X509_new_null()); |
| int status = PKCS12_get_key_and_certs(&key, cert_stack.get(), &cbs, password); |
| if (status == 0) { |
| return status; |
| } |
| |
| X509* ca; |
| while ((ca = sk_X509_shift(cert_stack.get())) != nullptr) { |
| status = SSL_CTX_add_client_CA(context, ca); |
| // SSL_CTX_add_client_CA increments the reference count of ca on success. |
| X509_free(ca); // The name has been extracted. |
| if (status == 0) { |
| return status; |
| } |
| } |
| |
| return status; |
| } |
| |
| static int SetClientAuthoritiesPEM(SSL_CTX* context, BIO* bio) { |
| int status = 0; |
| X509* cert = nullptr; |
| while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) != |
| nullptr) { |
| status = SSL_CTX_add_client_CA(context, cert); |
| X509_free(cert); // The name has been extracted. |
| if (status == 0) { |
| return status; |
| } |
| } |
| return SecureSocketUtils::NoPEMStartLine() ? status : 0; |
| } |
| |
| static int SetClientAuthorities(SSL_CTX* context, |
| ScopedMemBIO* bio, |
| const char* password) { |
| int status = SetClientAuthoritiesPEM(context, bio->bio()); |
| if (status == 0) { |
| if (SecureSocketUtils::NoPEMStartLine()) { |
| ERR_clear_error(); |
| BIO_reset(bio->bio()); |
| status = SetClientAuthoritiesPKCS12(context, bio, password); |
| } |
| } else { |
| // The PEM file was successfully parsed. |
| ERR_clear_error(); |
| } |
| return status; |
| } |
| |
| void SSLCertContext::SetClientAuthoritiesBytes( |
| Dart_Handle client_authorities_bytes, |
| const char* password) { |
| int status; |
| { |
| ScopedMemBIO bio(client_authorities_bytes); |
| status = SetClientAuthorities(context(), &bio, password); |
| } |
| |
| SecureSocketUtils::CheckStatus(status, "TlsException", |
| "Failure in setClientAuthoritiesBytes"); |
| } |
| |
| void SSLCertContext::LoadRootCertFile(const char* file) { |
| if (SSL_LOG_STATUS) { |
| Syslog::Print("Looking for trusted roots in %s\n", file); |
| } |
| if (!File::Exists(nullptr, file)) { |
| SecureSocketUtils::ThrowIOException( |
| -1, "TlsException", "Failed to find root cert file", nullptr); |
| } |
| int status = SSL_CTX_load_verify_locations(context(), file, nullptr); |
| SecureSocketUtils::CheckStatus(status, "TlsException", |
| "Failure trusting builtin roots"); |
| if (SSL_LOG_STATUS) { |
| Syslog::Print("Trusting roots from: %s\n", file); |
| } |
| } |
| |
| void SSLCertContext::AddCompiledInCerts() { |
| if (root_certificates_pem == nullptr) { |
| if (SSL_LOG_STATUS) { |
| Syslog::Print("Missing compiled-in roots\n"); |
| } |
| return; |
| } |
| X509_STORE* store = SSL_CTX_get_cert_store(context()); |
| BIO* roots_bio = |
| BIO_new_mem_buf(const_cast<unsigned char*>(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, nullptr, nullptr, |
| nullptr)) != nullptr) { |
| int status = X509_STORE_add_cert(store, root_cert); |
| // X509_STORE_add_cert increments the reference count of cert on success. |
| X509_free(root_cert); |
| if (status == 0) { |
| break; |
| } |
| } |
| BIO_free(roots_bio); |
| // If there is an error here, it must be the error indicating that we are done |
| // reading PEM certificates. |
| ASSERT((ERR_peek_error() == 0) || SecureSocketUtils::NoPEMStartLine()); |
| ERR_clear_error(); |
| } |
| |
| void SSLCertContext::LoadRootCertCache(const char* cache) { |
| if (SSL_LOG_STATUS) { |
| Syslog::Print("Looking for trusted roots in %s\n", cache); |
| } |
| if (Directory::Exists(nullptr, cache) != Directory::EXISTS) { |
| SecureSocketUtils::ThrowIOException( |
| -1, "TlsException", "Failed to find root cert cache", nullptr); |
| } |
| int status = SSL_CTX_load_verify_locations(context(), nullptr, cache); |
| SecureSocketUtils::CheckStatus(status, "TlsException", |
| "Failure trusting builtin roots"); |
| if (SSL_LOG_STATUS) { |
| Syslog::Print("Trusting roots from: %s\n", cache); |
| } |
| } |
| |
| int PasswordCallback(char* buf, int size, int rwflag, void* userdata) { |
| char* password = static_cast<char*>(userdata); |
| ASSERT(size == PEM_BUFSIZE); |
| strncpy(buf, password, size); |
| return strlen(password); |
| } |
| |
| static EVP_PKEY* GetPrivateKeyPKCS12(BIO* bio, const char* password) { |
| ScopedPKCS12 p12(d2i_PKCS12_bio(bio, nullptr)); |
| if (p12.get() == nullptr) { |
| return nullptr; |
| } |
| |
| EVP_PKEY* key = nullptr; |
| X509* cert = nullptr; |
| STACK_OF(X509)* ca_certs = nullptr; |
| int status = PKCS12_parse(p12.get(), password, &key, &cert, &ca_certs); |
| if (status == 0) { |
| return nullptr; |
| } |
| |
| // We only care about the private key. |
| ScopedX509 delete_cert(cert); |
| ScopedX509Stack delete_ca_certs(ca_certs); |
| return key; |
| } |
| |
| static EVP_PKEY* GetPrivateKey(BIO* bio, const char* password) { |
| EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, nullptr, PasswordCallback, |
| const_cast<char*>(password)); |
| if (key == nullptr) { |
| // We try reading data as PKCS12 only if reading as PEM was unsuccessful and |
| // if there is no indication that the data is malformed PEM. We assume the |
| // data is malformed PEM if it contains the start line, i.e. a line |
| // with ----- BEGIN. |
| if (SecureSocketUtils::NoPEMStartLine()) { |
| // Reset the bio, and clear the error from trying to read as PEM. |
| ERR_clear_error(); |
| BIO_reset(bio); |
| |
| // Try to decode as PKCS12. |
| key = GetPrivateKeyPKCS12(bio, password); |
| } |
| } |
| return key; |
| } |
| |
| const char* SSLCertContext::GetPasswordArgument(Dart_NativeArguments args, |
| intptr_t index) { |
| Dart_Handle password_object = |
| ThrowIfError(Dart_GetNativeArgument(args, index)); |
| const char* password = nullptr; |
| 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 (PEM_BUFSIZE)")); |
| } |
| } else if (Dart_IsNull(password_object)) { |
| password = ""; |
| } else { |
| Dart_ThrowException( |
| DartUtils::NewDartArgumentError("Password is not a String or null")); |
| } |
| return password; |
| } |
| |
| 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<uint8_t*>(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. |
| void SSLCertContext::SetAlpnProtocolList(Dart_Handle protocols_handle, |
| SSL* ssl, |
| SSLCertContext* 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 = nullptr; |
| uint8_t* protocol_string_copy = nullptr; |
| intptr_t protocol_string_len = 0; |
| int status; |
| |
| Dart_Handle result = Dart_TypedDataAcquireData( |
| protocols_handle, &protocols_type, |
| reinterpret_cast<void**>(&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 != nullptr); |
| ASSERT(ssl == nullptr); |
| // 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<uint8_t*>(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->context(), AlpnCallback, |
| protocol_string_copy); |
| context->set_alpn_protocol_string(protocol_string_copy); |
| } else { |
| // The function makes a local copy of protocol_string, which it owns. |
| if (ssl != nullptr) { |
| ASSERT(context == nullptr); |
| status = SSL_set_alpn_protos(ssl, protocol_string, protocol_string_len); |
| } else { |
| ASSERT(context != nullptr); |
| ASSERT(ssl == nullptr); |
| status = SSL_CTX_set_alpn_protos(context->context(), protocol_string, |
| protocol_string_len); |
| } |
| ASSERT(status == 0); // The function returns a non-standard status. |
| } |
| } |
| Dart_TypedDataReleaseData(protocols_handle); |
| } |
| |
| static int UseChainBytesPKCS12(SSL_CTX* context, |
| ScopedMemBIO* bio, |
| const char* password) { |
| CBS cbs; |
| CBS_init(&cbs, bio->data(), bio->length()); |
| |
| EVP_PKEY* key = nullptr; |
| ScopedX509Stack certs(sk_X509_new_null()); |
| int status = PKCS12_get_key_and_certs(&key, certs.get(), &cbs, password); |
| if (status == 0) { |
| return status; |
| } |
| |
| X509* ca = sk_X509_shift(certs.get()); |
| status = SSL_CTX_use_certificate(context, ca); |
| if (ERR_peek_error() != 0) { |
| // Key/certificate mismatch doesn't imply status is 0. |
| status = 0; |
| } |
| X509_free(ca); |
| if (status == 0) { |
| return status; |
| } |
| |
| SSL_CTX_clear_chain_certs(context); |
| |
| while ((ca = sk_X509_shift(certs.get())) != nullptr) { |
| status = SSL_CTX_add0_chain_cert(context, ca); |
| // SSL_CTX_add0_chain_cert does not inc ref count, so don't free unless the |
| // call fails. |
| if (status == 0) { |
| X509_free(ca); |
| return status; |
| } |
| } |
| |
| return status; |
| } |
| |
| static int UseChainBytesPEM(SSL_CTX* context, BIO* bio) { |
| int status = 0; |
| ScopedX509 x509(PEM_read_bio_X509_AUX(bio, nullptr, nullptr, nullptr)); |
| if (x509.get() == nullptr) { |
| return 0; |
| } |
| |
| status = SSL_CTX_use_certificate(context, x509.get()); |
| if (ERR_peek_error() != 0) { |
| // Key/certificate mismatch doesn't imply status is 0. |
| status = 0; |
| } |
| if (status == 0) { |
| return status; |
| } |
| |
| SSL_CTX_clear_chain_certs(context); |
| |
| X509* ca; |
| while ((ca = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) != nullptr) { |
| status = SSL_CTX_add0_chain_cert(context, ca); |
| // SSL_CTX_add0_chain_cert does not inc ref count, so don't free unless the |
| // call fails. |
| if (status == 0) { |
| X509_free(ca); |
| return status; |
| } |
| // Note that we must not free `ca` if it was successfully added to the |
| // chain. We must free the main certificate x509, though since its reference |
| // count is increased by SSL_CTX_use_certificate. |
| } |
| |
| return SecureSocketUtils::NoPEMStartLine() ? status : 0; |
| } |
| |
| static int UseChainBytes(SSL_CTX* context, |
| ScopedMemBIO* bio, |
| const char* password) { |
| int status = UseChainBytesPEM(context, bio->bio()); |
| if (status == 0) { |
| if (SecureSocketUtils::NoPEMStartLine()) { |
| ERR_clear_error(); |
| BIO_reset(bio->bio()); |
| status = UseChainBytesPKCS12(context, bio, password); |
| } |
| } else { |
| // The PEM file was successfully read. |
| ERR_clear_error(); |
| } |
| return status; |
| } |
| |
| int SSLCertContext::UseCertificateChainBytes(Dart_Handle cert_chain_bytes, |
| const char* password) { |
| ScopedMemBIO bio(cert_chain_bytes); |
| return UseChainBytes(context(), &bio, password); |
| } |
| |
| static X509* GetX509Certificate(Dart_NativeArguments args) { |
| X509* certificate = nullptr; |
| Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0)); |
| ASSERT(Dart_IsInstance(dart_this)); |
| ThrowIfError(Dart_GetNativeInstanceField( |
| dart_this, SSLCertContext::kX509NativeFieldIndex, |
| reinterpret_cast<intptr_t*>(&certificate))); |
| if (certificate == nullptr) { |
| Dart_PropagateError(Dart_NewUnhandledExceptionError( |
| DartUtils::NewInternalError("No native peer"))); |
| } |
| return certificate; |
| } |
| |
| Dart_Handle X509Helper::GetDer(Dart_NativeArguments args) { |
| X509* certificate = GetX509Certificate(args); |
| // When the second argument is nullptr, i2d_X509() returns the length of the |
| // DER encoded cert in bytes. |
| intptr_t length = i2d_X509(certificate, nullptr); |
| Dart_Handle cert_handle = Dart_NewTypedData(Dart_TypedData_kUint8, length); |
| if (Dart_IsError(cert_handle)) { |
| Dart_PropagateError(cert_handle); |
| } |
| Dart_TypedData_Type typ; |
| void* dart_cert_bytes = nullptr; |
| Dart_Handle status = |
| Dart_TypedDataAcquireData(cert_handle, &typ, &dart_cert_bytes, &length); |
| if (Dart_IsError(status)) { |
| Dart_PropagateError(status); |
| } |
| |
| // When the second argument points to a non-nullptr buffer address, |
| // i2d_X509 fills that buffer with the DER encoded cert data and increments |
| // the buffer pointer. |
| unsigned char* tmp = static_cast<unsigned char*>(dart_cert_bytes); |
| const intptr_t written_length = i2d_X509(certificate, &tmp); |
| ASSERT(written_length <= length); |
| if (written_length < 0) { |
| Dart_TypedDataReleaseData(cert_handle); |
| SecureSocketUtils::ThrowIOException( |
| -1, "TlsException", "Failed to get certificate bytes", nullptr); |
| // SecureSocketUtils::ThrowIOException() does not return. |
| } |
| |
| status = Dart_TypedDataReleaseData(cert_handle); |
| if (Dart_IsError(status)) { |
| Dart_PropagateError(status); |
| } |
| return cert_handle; |
| } |
| |
| Dart_Handle X509Helper::GetPem(Dart_NativeArguments args) { |
| X509* certificate = GetX509Certificate(args); |
| BIO* cert_bio = BIO_new(BIO_s_mem()); |
| intptr_t status = PEM_write_bio_X509(cert_bio, certificate); |
| if (status == 0) { |
| BIO_free(cert_bio); |
| SecureSocketUtils::ThrowIOException( |
| -1, "TlsException", "Failed to write certificate to PEM", nullptr); |
| // SecureSocketUtils::ThrowIOException() does not return. |
| } |
| |
| BUF_MEM* mem = nullptr; |
| BIO_get_mem_ptr(cert_bio, &mem); |
| Dart_Handle pem_string = Dart_NewStringFromUTF8( |
| reinterpret_cast<const uint8_t*>(mem->data), mem->length); |
| BIO_free(cert_bio); |
| if (Dart_IsError(pem_string)) { |
| Dart_PropagateError(pem_string); |
| } |
| |
| return pem_string; |
| } |
| |
| Dart_Handle X509Helper::GetSha1(Dart_NativeArguments args) { |
| unsigned char sha1_bytes[EVP_MAX_MD_SIZE]; |
| X509* certificate = GetX509Certificate(args); |
| const EVP_MD* hash_type = EVP_sha1(); |
| |
| unsigned int sha1_size; |
| intptr_t status = X509_digest(certificate, hash_type, sha1_bytes, &sha1_size); |
| if (status == 0) { |
| SecureSocketUtils::ThrowIOException( |
| -1, "TlsException", "Failed to compute certificate's sha1", nullptr); |
| // SecureSocketUtils::ThrowIOException() does not return. |
| } |
| |
| Dart_Handle sha1_handle = Dart_NewTypedData(Dart_TypedData_kUint8, sha1_size); |
| if (Dart_IsError(sha1_handle)) { |
| Dart_PropagateError(sha1_handle); |
| } |
| |
| Dart_TypedData_Type typ; |
| void* dart_sha1_bytes; |
| intptr_t length; |
| Dart_Handle result = |
| Dart_TypedDataAcquireData(sha1_handle, &typ, &dart_sha1_bytes, &length); |
| if (Dart_IsError(result)) { |
| Dart_PropagateError(result); |
| } |
| |
| memmove(dart_sha1_bytes, sha1_bytes, length); |
| |
| result = Dart_TypedDataReleaseData(sha1_handle); |
| if (Dart_IsError(result)) { |
| Dart_PropagateError(result); |
| } |
| return sha1_handle; |
| } |
| |
| Dart_Handle X509Helper::GetSubject(Dart_NativeArguments args) { |
| X509* certificate = GetX509Certificate(args); |
| X509_NAME* subject = X509_get_subject_name(certificate); |
| char* subject_string = X509_NAME_oneline(subject, nullptr, 0); |
| if (subject_string == nullptr) { |
| Dart_ThrowException(DartUtils::NewDartArgumentError( |
| "X509.subject failed to find subject's common name.")); |
| } |
| Dart_Handle subject_handle = Dart_NewStringFromCString(subject_string); |
| OPENSSL_free(subject_string); |
| return subject_handle; |
| } |
| |
| Dart_Handle X509Helper::GetIssuer(Dart_NativeArguments args) { |
| X509* certificate = GetX509Certificate(args); |
| X509_NAME* issuer = X509_get_issuer_name(certificate); |
| char* issuer_string = X509_NAME_oneline(issuer, nullptr, 0); |
| if (issuer_string == nullptr) { |
| Dart_ThrowException(DartUtils::NewDartArgumentError( |
| "X509.issuer failed to find issuer's common name.")); |
| } |
| Dart_Handle issuer_handle = Dart_NewStringFromCString(issuer_string); |
| OPENSSL_free(issuer_string); |
| return issuer_handle; |
| } |
| |
| static Dart_Handle ASN1TimeToMilliseconds(ASN1_TIME* aTime) { |
| ASN1_UTCTIME* epoch_start = ASN1_UTCTIME_new(); |
| ASN1_UTCTIME_set_string(epoch_start, "700101000000Z"); |
| int days; |
| int seconds; |
| int result = ASN1_TIME_diff(&days, &seconds, epoch_start, aTime); |
| ASN1_UTCTIME_free(epoch_start); |
| if (result != 1) { |
| // TODO(whesse): Propagate an error to Dart. |
| Syslog::PrintErr("ASN1Time error %d\n", result); |
| } |
| return Dart_NewInteger((86400LL * days + seconds) * 1000LL); |
| } |
| |
| Dart_Handle X509Helper::GetStartValidity(Dart_NativeArguments args) { |
| X509* certificate = GetX509Certificate(args); |
| ASN1_TIME* not_before = X509_get_notBefore(certificate); |
| return ASN1TimeToMilliseconds(not_before); |
| } |
| |
| Dart_Handle X509Helper::GetEndValidity(Dart_NativeArguments args) { |
| X509* certificate = GetX509Certificate(args); |
| ASN1_TIME* not_after = X509_get_notAfter(certificate); |
| return ASN1TimeToMilliseconds(not_after); |
| } |
| |
| void FUNCTION_NAME(SecurityContext_UsePrivateKeyBytes)( |
| Dart_NativeArguments args) { |
| SSLCertContext* context = SSLCertContext::GetSecurityContext(args); |
| const char* password = SSLCertContext::GetPasswordArgument(args, 2); |
| |
| int status; |
| EVP_PKEY* key; |
| { |
| ScopedMemBIO bio(ThrowIfError(Dart_GetNativeArgument(args, 1))); |
| key = GetPrivateKey(bio.bio(), password); |
| } |
| if (key == nullptr) { |
| Dart_ThrowException(DartUtils::NewDartArgumentError( |
| "Expected private key, but none was found")); |
| } |
| status = SSL_CTX_use_PrivateKey(context->context(), key); |
| // SSL_CTX_use_PrivateKey increments the reference count of key on success, |
| // so we have to call EVP_PKEY_free on both success and failure. |
| EVP_PKEY_free(key); |
| |
| // TODO(24184): Handle different expected errors here - file missing, |
| // incorrect password, file not a PEM, and throw exceptions. |
| // SecureSocketUtils::CheckStatus should also throw an exception in uncaught |
| // cases. |
| SecureSocketUtils::CheckStatus(status, "TlsException", |
| "Failure in usePrivateKeyBytes"); |
| } |
| |
| void FUNCTION_NAME(SecurityContext_Allocate)(Dart_NativeArguments args) { |
| SSLFilter::InitializeLibrary(); |
| SSL_CTX* ctx = SSL_CTX_new(TLS_method()); |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, SSLCertContext::CertificateCallback); |
| SSL_CTX_set_keylog_callback(ctx, SSLCertContext::KeyLogCallback); |
| // If we change the minimum protocol version here, then the documentation |
| // for `SecurityContext.minimumTlsProtocolVersion` must also be changed. |
| SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); |
| SSL_CTX_set_cipher_list(ctx, "HIGH:MEDIUM"); |
| SSLCertContext* context = new SSLCertContext(ctx); |
| Dart_Handle err = SetSecurityContext(args, context); |
| if (Dart_IsError(err)) { |
| delete context; |
| Dart_PropagateError(err); |
| } |
| } |
| |
| void FUNCTION_NAME(SecurityContext_SetTrustedCertificatesBytes)( |
| Dart_NativeArguments args) { |
| SSLCertContext* context = SSLCertContext::GetSecurityContext(args); |
| Dart_Handle cert_bytes = ThrowIfError(Dart_GetNativeArgument(args, 1)); |
| const char* password = SSLCertContext::GetPasswordArgument(args, 2); |
| |
| ASSERT(context != nullptr); |
| ASSERT(password != nullptr); |
| context->SetTrustedCertificatesBytes(cert_bytes, password); |
| } |
| |
| void FUNCTION_NAME(SecurityContext_SetClientAuthoritiesBytes)( |
| Dart_NativeArguments args) { |
| SSLCertContext* context = SSLCertContext::GetSecurityContext(args); |
| Dart_Handle client_authorities_bytes = |
| ThrowIfError(Dart_GetNativeArgument(args, 1)); |
| const char* password = SSLCertContext::GetPasswordArgument(args, 2); |
| |
| ASSERT(context != nullptr); |
| ASSERT(password != nullptr); |
| |
| context->SetClientAuthoritiesBytes(client_authorities_bytes, password); |
| } |
| |
| void FUNCTION_NAME(SecurityContext_UseCertificateChainBytes)( |
| Dart_NativeArguments args) { |
| SSLCertContext* context = SSLCertContext::GetSecurityContext(args); |
| Dart_Handle cert_chain_bytes = ThrowIfError(Dart_GetNativeArgument(args, 1)); |
| const char* password = SSLCertContext::GetPasswordArgument(args, 2); |
| |
| ASSERT(context != nullptr); |
| ASSERT(password != nullptr); |
| |
| int status = context->UseCertificateChainBytes(cert_chain_bytes, password); |
| |
| SecureSocketUtils::CheckStatus(status, "TlsException", |
| "Failure in useCertificateChainBytes"); |
| } |
| |
| void FUNCTION_NAME(SecurityContext_TrustBuiltinRoots)( |
| Dart_NativeArguments args) { |
| SSLCertContext* context = SSLCertContext::GetSecurityContext(args); |
| |
| ASSERT(context != nullptr); |
| |
| context->TrustBuiltinRoots(); |
| } |
| |
| void FUNCTION_NAME(SecurityContext_SetAllowTlsRenegotiation)( |
| Dart_NativeArguments args) { |
| SSLCertContext* context = SSLCertContext::GetSecurityContext(args); |
| Dart_Handle allow_tls_handle = ThrowIfError(Dart_GetNativeArgument(args, 1)); |
| |
| ASSERT(context != nullptr); |
| ASSERT(allow_tls_handle != nullptr); |
| |
| if (!Dart_IsBoolean(allow_tls_handle)) { |
| Dart_ThrowException(DartUtils::NewDartArgumentError( |
| "Non-boolean argument passed to SetAllowTlsRenegotiation")); |
| } |
| bool allow = DartUtils::GetBooleanValue(allow_tls_handle); |
| context->set_allow_tls_renegotiation(allow); |
| } |
| |
| void FUNCTION_NAME(SecurityContext_SetMinimumProtocolVersion)( |
| Dart_NativeArguments args) { |
| SSLCertContext* context = SSLCertContext::GetSecurityContext(args); |
| Dart_Handle protocol_version_handle = |
| ThrowIfError(Dart_GetNativeArgument(args, 1)); |
| if (!Dart_IsInteger(protocol_version_handle)) { |
| Dart_ThrowException(DartUtils::NewDartArgumentError( |
| "Non-int argument passed to SetMinimumProtocolVersion")); |
| } |
| |
| int protocol_version = DartUtils::GetIntegerValue(protocol_version_handle); |
| if (SSL_CTX_set_min_proto_version(context->context(), protocol_version) == |
| 0) { |
| Dart_ThrowException(DartUtils::NewDartArgumentError( |
| "Invalid protocol version passed to SetMinimumProtocolVersion")); |
| } |
| } |
| |
| void FUNCTION_NAME(SecurityContext_GetMinimumProtocolVersion)( |
| Dart_NativeArguments args) { |
| SSLCertContext* context = SSLCertContext::GetSecurityContext(args); |
| |
| Dart_SetIntegerReturnValue(args, |
| SSL_CTX_get_min_proto_version(context->context())); |
| } |
| |
| void FUNCTION_NAME(X509_Der)(Dart_NativeArguments args) { |
| Dart_SetReturnValue(args, X509Helper::GetDer(args)); |
| } |
| |
| void FUNCTION_NAME(X509_Pem)(Dart_NativeArguments args) { |
| Dart_SetReturnValue(args, X509Helper::GetPem(args)); |
| } |
| |
| void FUNCTION_NAME(X509_Sha1)(Dart_NativeArguments args) { |
| Dart_SetReturnValue(args, X509Helper::GetSha1(args)); |
| } |
| |
| void FUNCTION_NAME(X509_Subject)(Dart_NativeArguments args) { |
| Dart_SetReturnValue(args, X509Helper::GetSubject(args)); |
| } |
| |
| void FUNCTION_NAME(X509_Issuer)(Dart_NativeArguments args) { |
| Dart_SetReturnValue(args, X509Helper::GetIssuer(args)); |
| } |
| |
| void FUNCTION_NAME(X509_StartValidity)(Dart_NativeArguments args) { |
| Dart_SetReturnValue(args, X509Helper::GetStartValidity(args)); |
| } |
| |
| void FUNCTION_NAME(X509_EndValidity)(Dart_NativeArguments args) { |
| Dart_SetReturnValue(args, X509Helper::GetEndValidity(args)); |
| } |
| |
| void FUNCTION_NAME(SecurityContext_SetAlpnProtocols)( |
| Dart_NativeArguments args) { |
| SSLCertContext* context = SSLCertContext::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); |
| SSLCertContext::SetAlpnProtocolList(protocols_handle, nullptr, context, |
| is_server); |
| } else { |
| Dart_ThrowException(DartUtils::NewDartArgumentError( |
| "Non-boolean is_server argument passed to SetAlpnProtocols")); |
| } |
| } |
| |
| } // namespace bin |
| } // namespace dart |
| |
| #endif // !defined(DART_IO_SECURE_SOCKET_DISABLED) |