| // 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" | |
| 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"); | |
| } | |
| 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)); | |
| if (filter == nullptr) { | |
| return 0; | |
| } | |
| Dart_Handle callback = filter->bad_certificate_callback(); | |
| if (Dart_IsNull(callback)) { | |
| return 0; | |
| } | |
| X509* certificate = X509_STORE_CTX_get0_cert(store_ctx); | |
| if (certificate == nullptr) { | |
| return 0; | |
| } | |
| // Upref since the Dart X509 object may outlive the SecurityContext. | |
| 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 DartUtils::GetBooleanValue(result) ? 1 : 0; | |
| } | |
| 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) |