| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| #include "ssl.hpp" |
| |
| #include "logger.hpp" |
| #include "utils.hpp" |
| |
| #include "third_party/curl/hostcheck.hpp" |
| |
| #include <openssl/crypto.h> |
| #include <openssl/engine.h> |
| #include <openssl/err.h> |
| #include <openssl/rand.h> |
| #include <openssl/tls1.h> |
| #include <openssl/x509v3.h> |
| #include <string.h> |
| |
| #define DEBUG_SSL 0 |
| |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) |
| #define ASN1_STRING_get0_data ASN1_STRING_data |
| #else |
| #define SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE SSL_F_USE_CERTIFICATE_CHAIN_FILE |
| #endif |
| |
| #if defined(OPENSSL_VERSION_NUMBER) && \ |
| !defined(LIBRESSL_VERSION_NUMBER) // Required as OPENSSL_VERSION_NUMBER for LibreSSL is defined |
| // as 2.0.0 |
| #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) |
| #define SSL_CAN_SET_MIN_VERSION |
| #define SSL_CLIENT_METHOD TLS_client_method |
| #else |
| #define SSL_CLIENT_METHOD SSLv23_client_method |
| #endif |
| #else |
| #if (LIBRESSL_VERSION_NUMBER >= 0x20302000L) |
| #define SSL_CAN_SET_MIN_VERSION |
| #define SSL_CLIENT_METHOD TLS_client_method |
| #else |
| #define SSL_CLIENT_METHOD SSLv23_client_method |
| #endif |
| #endif |
| |
| using namespace datastax; |
| using namespace datastax::internal; |
| using namespace datastax::internal::core; |
| |
| #if DEBUG_SSL |
| #define SSL_PRINT_INFO(ssl, w, flag, msg) \ |
| do { \ |
| if (w & flag) { \ |
| fprintf(stderr, "%s - %s - %s\n", msg, SSL_state_string(ssl), SSL_state_string_long(ssl)); \ |
| } \ |
| } while (0); |
| |
| static void ssl_info_callback(const SSL* ssl, int where, int ret) { |
| if (ret == 0) { |
| fprintf(stderr, "ssl_info_callback, error occurred.\n"); |
| return; |
| } |
| SSL_PRINT_INFO(ssl, where, SSL_CB_LOOP, "LOOP"); |
| SSL_PRINT_INFO(ssl, where, SSL_CB_EXIT, "EXIT"); |
| SSL_PRINT_INFO(ssl, where, SSL_CB_READ, "READ"); |
| SSL_PRINT_INFO(ssl, where, SSL_CB_WRITE, "WRITE"); |
| SSL_PRINT_INFO(ssl, where, SSL_CB_ALERT, "ALERT"); |
| SSL_PRINT_INFO(ssl, where, SSL_CB_HANDSHAKE_DONE, "HANDSHAKE DONE"); |
| } |
| |
| #undef SSL_PRINT_INFO |
| #endif |
| |
| static int ssl_no_verify_callback(int ok, X509_STORE_CTX* store) { |
| // Verification happens after in SslSession::verify() |
| // via SSL_get_verify_result(). |
| return 1; |
| } |
| |
| static void ssl_log_errors(const char* context) { |
| const char* data; |
| int flags; |
| int err; |
| while ((err = |
| #if (OPENSSL_VERSION_NUMBER >= 0x30000000L) |
| ERR_get_error_all(NULL, NULL, NULL, &data, &flags) |
| #else |
| ERR_get_error_line_data(NULL, NULL, &data, &flags) |
| #endif |
| ) != 0) { |
| char buf[256]; |
| ERR_error_string_n(err, buf, sizeof(buf)); |
| LOG_ERROR("%s: %s:%s", context, buf, (flags & ERR_TXT_STRING) ? data : ""); |
| } |
| ERR_print_errors_fp(stderr); |
| } |
| |
| static String ssl_error_string() { |
| const char* data; |
| int flags; |
| int err; |
| String error; |
| while ((err = |
| #if (OPENSSL_VERSION_NUMBER >= 0x30000000L) |
| ERR_get_error_all(NULL, NULL, NULL, &data, &flags) |
| #else |
| ERR_get_error_line_data(NULL, NULL, &data, &flags) |
| #endif |
| ) != 0) { |
| char buf[256]; |
| ERR_error_string_n(err, buf, sizeof(buf)); |
| if (!error.empty()) error.push_back(','); |
| error.append(buf); |
| if (flags & ERR_TXT_STRING) { |
| error.push_back(':'); |
| error.append(data); |
| } |
| } |
| return error; |
| } |
| |
| static int pem_password_callback(char* buf, int size, int rwflag, void* u) { |
| if (u == NULL) return 0; |
| |
| int len = strlen(static_cast<const char*>(u)); |
| if (len == 0) return 0; |
| |
| int to_copy = size; |
| if (len < to_copy) { |
| to_copy = len; |
| } |
| memcpy(buf, u, to_copy); |
| |
| return len; |
| } |
| |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) |
| static uv_rwlock_t* crypto_locks = NULL; |
| |
| static void crypto_locking_callback(int mode, int n, const char* file, int line) { |
| if (mode & CRYPTO_LOCK) { |
| if (mode & CRYPTO_READ) { |
| uv_rwlock_rdlock(crypto_locks + n); |
| } else { |
| uv_rwlock_wrlock(crypto_locks + n); |
| } |
| } else { |
| if (mode & CRYPTO_READ) { |
| uv_rwlock_rdunlock(crypto_locks + n); |
| } else { |
| uv_rwlock_wrunlock(crypto_locks + n); |
| } |
| } |
| } |
| |
| static unsigned long crypto_id_callback() { |
| #if defined(WIN32) || defined(_WIN32) |
| return static_cast<unsigned long>(GetCurrentThreadId()); |
| #else |
| return copy_cast<uv_thread_t, unsigned long>(uv_thread_self()); |
| #endif |
| } |
| #endif |
| |
| // Implementation taken from OpenSSL's SSL_CTX_use_certificate_chain_file() |
| // (https://github.com/openssl/openssl/blob/OpenSSL_0_9_8-stable/ssl/ssl_rsa.c#L705). |
| // Modified to be used for in-memory certificate chains and formatting. |
| static int SSL_CTX_use_certificate_chain_bio(SSL_CTX* ctx, BIO* in) { |
| int ret = 0; |
| X509* x = NULL; |
| |
| x = PEM_read_bio_X509_AUX(in, NULL, pem_password_callback, NULL); |
| if (x == NULL) { |
| SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_PEM_LIB); |
| goto end; |
| } |
| |
| ret = SSL_CTX_use_certificate(ctx, x); |
| |
| if (ERR_peek_error() != 0) { |
| // Key/certificate mismatch doesn't imply ret==0 ... |
| ret = 0; |
| } |
| |
| if (ret) { |
| // If we could set up our certificate, now proceed to |
| // the CA certificates. |
| |
| X509* ca; |
| int r; |
| unsigned long err; |
| |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ |
| (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2090100fL) |
| if (ctx->extra_certs != NULL) { |
| sk_X509_pop_free(ctx->extra_certs, X509_free); |
| ctx->extra_certs = NULL; |
| } |
| #else |
| SSL_CTX_clear_chain_certs(ctx); |
| #endif |
| |
| while ((ca = PEM_read_bio_X509(in, NULL, pem_password_callback, NULL)) != NULL) { |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) |
| r = SSL_CTX_add_extra_chain_cert(ctx, ca); |
| #else |
| r = SSL_CTX_add0_chain_cert(ctx, ca); |
| #endif |
| if (!r) { |
| X509_free(ca); |
| ret = 0; |
| goto end; |
| } |
| // Note that we must not free r if it was successfully |
| // added to the chain (while we must free the main |
| // certificate, since its reference count is increased |
| // by SSL_CTX_use_certificate). |
| } |
| // When the while loop ends, it's usually just EOF. |
| err = ERR_peek_last_error(); |
| if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE) { |
| ERR_clear_error(); |
| } else { |
| // Some real error |
| ret = 0; |
| } |
| } |
| |
| end: |
| if (x != NULL) X509_free(x); |
| return ret; |
| } |
| |
| static EVP_PKEY* load_key(const char* key, size_t key_size, const char* password) { |
| BIO* bio = BIO_new_mem_buf(const_cast<char*>(key), key_size); |
| if (bio == NULL) { |
| return NULL; |
| } |
| |
| EVP_PKEY* pkey = |
| PEM_read_bio_PrivateKey(bio, NULL, pem_password_callback, const_cast<char*>(password)); |
| if (pkey == NULL) { |
| ssl_log_errors("Unable to load private key"); |
| } |
| |
| BIO_free_all(bio); |
| |
| return pkey; |
| } |
| |
| class OpenSslVerifyIdentity { |
| public: |
| enum Result { INVALID_CERT, MATCH, NO_MATCH, NO_SAN_PRESENT }; |
| |
| static Result match(X509* cert, const Address& address) { |
| Result result = match_subject_alt_names_ipadd(cert, address); |
| if (result == NO_SAN_PRESENT) { |
| result = match_common_name_ipaddr(cert, address.hostname_or_address()); |
| } |
| return result; |
| } |
| |
| static Result match_dns(X509* cert, const String& hostname) { |
| Result result = match_subject_alt_names_dns(cert, hostname); |
| if (result == NO_SAN_PRESENT) { |
| result = match_common_name_dns(cert, hostname); |
| } |
| return result; |
| } |
| |
| private: |
| static Result match_common_name_ipaddr(X509* cert, const String& address) { |
| X509_NAME* name = X509_get_subject_name(cert); |
| if (name == NULL) { |
| return INVALID_CERT; |
| } |
| |
| int i = -1; |
| while ((i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) > 0) { |
| X509_NAME_ENTRY* name_entry = X509_NAME_get_entry(name, i); |
| if (name_entry == NULL) { |
| return INVALID_CERT; |
| } |
| |
| ASN1_STRING* str = X509_NAME_ENTRY_get_data(name_entry); |
| if (str == NULL) { |
| return INVALID_CERT; |
| } |
| |
| const char* common_name = reinterpret_cast<const char*>(ASN1_STRING_get0_data(str)); |
| if (strlen(common_name) != static_cast<size_t>(ASN1_STRING_length(str))) { |
| return INVALID_CERT; |
| } |
| |
| if (address == common_name) { |
| return MATCH; |
| } |
| } |
| |
| return NO_MATCH; |
| } |
| |
| static Result match_common_name_dns(X509* cert, const String& hostname) { |
| X509_NAME* name = X509_get_subject_name(cert); |
| if (name == NULL) { |
| return INVALID_CERT; |
| } |
| |
| int i = -1; |
| while ((i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) >= 0) { |
| X509_NAME_ENTRY* name_entry = X509_NAME_get_entry(name, i); |
| if (name_entry == NULL) { |
| return INVALID_CERT; |
| } |
| |
| ASN1_STRING* str = X509_NAME_ENTRY_get_data(name_entry); |
| if (str == NULL) { |
| return INVALID_CERT; |
| } |
| |
| const char* common_name = reinterpret_cast<const char*>(ASN1_STRING_get0_data(str)); |
| if (strlen(common_name) != static_cast<size_t>(ASN1_STRING_length(str))) { |
| return INVALID_CERT; |
| } |
| |
| // Using Curl's hostcheck because this could be error prone to rewrite |
| if (Curl_cert_hostcheck(common_name, hostname.c_str())) { |
| return MATCH; |
| } |
| } |
| |
| return NO_MATCH; |
| } |
| |
| static Result match_subject_alt_names_ipadd(X509* cert, const Address& addr) { |
| uint8_t addr_buf[16]; |
| size_t addr_buf_size; |
| |
| addr_buf_size = addr.to_inet(addr_buf); |
| if (addr_buf_size == 0) { |
| return NO_MATCH; |
| } |
| |
| STACK_OF(GENERAL_NAME)* names = static_cast<STACK_OF(GENERAL_NAME)*>( |
| X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL)); |
| if (names == NULL) { |
| return NO_SAN_PRESENT; |
| } |
| |
| Result result = NO_MATCH; |
| for (int i = 0; i < sk_GENERAL_NAME_num(names); ++i) { |
| GENERAL_NAME* name = sk_GENERAL_NAME_value(names, i); |
| |
| if (name->type == GEN_IPADD) { |
| ASN1_STRING* str = name->d.iPAddress; |
| if (str == NULL) { |
| result = INVALID_CERT; |
| break; |
| } |
| |
| const unsigned char* ip = ASN1_STRING_get0_data(str); |
| int ip_len = ASN1_STRING_length(str); |
| if (ip_len != 4 && ip_len != 16) { |
| result = INVALID_CERT; |
| break; |
| } |
| |
| if (static_cast<size_t>(ip_len) == addr_buf_size && |
| memcmp(ip, addr_buf, addr_buf_size) == 0) { |
| result = MATCH; |
| break; |
| } |
| } |
| } |
| sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); |
| |
| return result; |
| } |
| |
| static Result match_subject_alt_names_dns(X509* cert, const String& hostname) { |
| STACK_OF(GENERAL_NAME)* names = static_cast<STACK_OF(GENERAL_NAME)*>( |
| X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL)); |
| if (names == NULL) { |
| return NO_SAN_PRESENT; |
| } |
| |
| Result result = NO_MATCH; |
| for (int i = 0; i < sk_GENERAL_NAME_num(names); ++i) { |
| GENERAL_NAME* name = sk_GENERAL_NAME_value(names, i); |
| |
| if (name->type == GEN_DNS) { |
| ASN1_STRING* str = name->d.dNSName; |
| if (str == NULL) { |
| result = INVALID_CERT; |
| break; |
| } |
| |
| const char* common_name = reinterpret_cast<const char*>(ASN1_STRING_get0_data(str)); |
| if (strlen(common_name) != static_cast<size_t>(ASN1_STRING_length(str))) { |
| result = INVALID_CERT; |
| break; |
| } |
| |
| // Using Curl's hostcheck because this could be error prone to rewrite |
| if (Curl_cert_hostcheck(common_name, hostname.c_str())) { |
| result = MATCH; |
| break; |
| } |
| } |
| } |
| sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); |
| |
| return result; |
| } |
| }; |
| |
| OpenSslSession::OpenSslSession(const Address& address, const String& hostname, |
| const String& sni_server_name, int flags, SSL_CTX* ssl_ctx) |
| : SslSession(address, hostname, sni_server_name, flags) |
| , ssl_(SSL_new(ssl_ctx)) |
| , incoming_state_(&incoming_) |
| , outgoing_state_(&outgoing_) |
| , incoming_bio_(rb::RingBufferBio::create(&incoming_state_)) |
| , outgoing_bio_(rb::RingBufferBio::create(&outgoing_state_)) { |
| SSL_set_bio(ssl_, incoming_bio_, outgoing_bio_); |
| SSL_set_connect_state(ssl_); |
| |
| if (!sni_server_name_.empty()) { |
| SSL_set_tlsext_host_name(ssl_, const_cast<char*>(sni_server_name_.c_str())); |
| } |
| } |
| |
| OpenSslSession::~OpenSslSession() { SSL_free(ssl_); } |
| |
| void OpenSslSession::do_handshake() { |
| int rc = SSL_connect(ssl_); |
| if (rc <= 0) check_error(rc); |
| } |
| |
| void OpenSslSession::verify() { |
| if (!verify_flags_) return; |
| |
| X509* peer_cert = SSL_get_peer_certificate(ssl_); |
| if (peer_cert == NULL) { |
| error_code_ = CASS_ERROR_SSL_NO_PEER_CERT; |
| error_message_ = "No peer certificate found"; |
| return; |
| } |
| |
| if (verify_flags_ & CASS_SSL_VERIFY_PEER_CERT) { |
| int rc = SSL_get_verify_result(ssl_); |
| if (rc != X509_V_OK) { |
| error_code_ = CASS_ERROR_SSL_INVALID_PEER_CERT; |
| error_message_ = X509_verify_cert_error_string(rc); |
| X509_free(peer_cert); |
| return; |
| } |
| } |
| |
| if (verify_flags_ & CASS_SSL_VERIFY_PEER_IDENTITY) { // Match using IP addresses |
| switch (OpenSslVerifyIdentity::match(peer_cert, address_)) { |
| case OpenSslVerifyIdentity::MATCH: |
| // Success |
| break; |
| |
| case OpenSslVerifyIdentity::INVALID_CERT: |
| error_code_ = CASS_ERROR_SSL_INVALID_PEER_CERT; |
| error_message_ = "Peer certificate has malformed name field(s)"; |
| X509_free(peer_cert); |
| return; |
| |
| default: |
| error_code_ = CASS_ERROR_SSL_IDENTITY_MISMATCH; |
| error_message_ = "Peer certificate subject name does not match"; |
| X509_free(peer_cert); |
| return; |
| } |
| } else if (verify_flags_ & |
| CASS_SSL_VERIFY_PEER_IDENTITY_DNS) { // Match using hostnames (including wildcards) |
| switch (OpenSslVerifyIdentity::match_dns(peer_cert, hostname_)) { |
| case OpenSslVerifyIdentity::MATCH: |
| // Success |
| break; |
| |
| case OpenSslVerifyIdentity::INVALID_CERT: |
| error_code_ = CASS_ERROR_SSL_INVALID_PEER_CERT; |
| error_message_ = "Peer certificate has malformed name field(s)"; |
| X509_free(peer_cert); |
| return; |
| |
| default: |
| error_code_ = CASS_ERROR_SSL_IDENTITY_MISMATCH; |
| error_message_ = "Peer certificate subject name does not match"; |
| X509_free(peer_cert); |
| return; |
| } |
| } |
| |
| X509_free(peer_cert); |
| } |
| |
| int OpenSslSession::encrypt(const char* buf, size_t size) { |
| int rc = SSL_write(ssl_, buf, size); |
| if (rc <= 0) check_error(rc); |
| return rc; |
| } |
| |
| int OpenSslSession::decrypt(char* buf, size_t size) { |
| int rc = SSL_read(ssl_, buf, size); |
| if (rc <= 0) check_error(rc); |
| return rc; |
| } |
| |
| void OpenSslSession::check_error(int rc) { |
| int err = SSL_get_error(ssl_, rc); |
| if (err == SSL_ERROR_ZERO_RETURN) { |
| error_code_ = CASS_ERROR_SSL_CLOSED; |
| } else if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_NONE) { |
| error_code_ = CASS_ERROR_SSL_PROTOCOL_ERROR; |
| error_message_ = ssl_error_string(); |
| } |
| } |
| |
| OpenSslContext::OpenSslContext() |
| : ssl_ctx_(SSL_CTX_new(SSL_CLIENT_METHOD())) |
| , trusted_store_(X509_STORE_new()) { |
| SSL_CTX_set_cert_store(ssl_ctx_, trusted_store_); |
| SSL_CTX_set_verify(ssl_ctx_, SSL_VERIFY_NONE, ssl_no_verify_callback); |
| #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) |
| // Limit to TLS 1.2 for now. TLS 1.3 has broken the handshake code. |
| SSL_CTX_set_max_proto_version(ssl_ctx_, TLS1_2_VERSION); |
| #endif |
| #if DEBUG_SSL |
| SSL_CTX_set_info_callback(ssl_ctx_, ssl_info_callback); |
| #endif |
| } |
| |
| OpenSslContext::~OpenSslContext() { SSL_CTX_free(ssl_ctx_); } |
| |
| SslSession* OpenSslContext::create_session(const Address& address, const String& hostname, |
| const String& sni_server_name) { |
| return new OpenSslSession(address, hostname, sni_server_name, verify_flags_, ssl_ctx_); |
| } |
| |
| CassError OpenSslContext::add_trusted_cert(const char* cert, size_t cert_length) { |
| BIO* bio = BIO_new_mem_buf(const_cast<char*>(cert), cert_length); |
| if (bio == NULL) { |
| return CASS_ERROR_SSL_INVALID_CERT; |
| } |
| |
| int num_certs = 0; |
| |
| // Iterate over the bio, reading out as many certificates as possible. |
| for (X509* cert = PEM_read_bio_X509(bio, NULL, pem_password_callback, NULL); cert != NULL; |
| cert = PEM_read_bio_X509(bio, NULL, pem_password_callback, NULL)) { |
| X509_STORE_add_cert(trusted_store_, cert); |
| X509_free(cert); |
| num_certs++; |
| } |
| |
| // Retrieve and discard the error tht terminated the loop, |
| // so it doesn't cause the next PEM operation to fail mysteriously. |
| ERR_get_error(); |
| |
| BIO_free_all(bio); |
| |
| // If no certificates were read from the bio, that is an error. |
| if (num_certs == 0) { |
| ssl_log_errors("Unable to load certificate(s)"); |
| return CASS_ERROR_SSL_INVALID_CERT; |
| } |
| |
| return CASS_OK; |
| } |
| |
| CassError OpenSslContext::set_cert(const char* cert, size_t cert_length) { |
| BIO* bio = BIO_new_mem_buf(const_cast<char*>(cert), cert_length); |
| if (bio == NULL) { |
| return CASS_ERROR_SSL_INVALID_CERT; |
| } |
| |
| int rc = SSL_CTX_use_certificate_chain_bio(ssl_ctx_, bio); |
| |
| BIO_free_all(bio); |
| |
| if (!rc) { |
| ssl_log_errors("Unable to load certificate chain"); |
| return CASS_ERROR_SSL_INVALID_CERT; |
| } |
| |
| return CASS_OK; |
| } |
| |
| CassError OpenSslContext::set_private_key(const char* key, size_t key_length, const char* password, |
| size_t password_length) { |
| // TODO: Password buffer |
| EVP_PKEY* pkey = load_key(key, key_length, password); |
| if (pkey == NULL) { |
| return CASS_ERROR_SSL_INVALID_PRIVATE_KEY; |
| } |
| |
| SSL_CTX_use_PrivateKey(ssl_ctx_, pkey); |
| EVP_PKEY_free(pkey); |
| |
| return CASS_OK; |
| } |
| |
| CassError OpenSslContext::set_min_protocol_version(CassSslTlsVersion min_version) { |
| #ifdef SSL_CAN_SET_MIN_VERSION |
| int method; |
| switch (min_version) { |
| case CassSslTlsVersion::CASS_SSL_VERSION_TLS1: |
| method = TLS1_VERSION; |
| break; |
| case CassSslTlsVersion::CASS_SSL_VERSION_TLS1_1: |
| method = TLS1_1_VERSION; |
| break; |
| case CassSslTlsVersion::CASS_SSL_VERSION_TLS1_2: |
| method = TLS1_2_VERSION; |
| break; |
| default: |
| // unsupported version |
| return CASS_ERROR_LIB_BAD_PARAMS; |
| } |
| SSL_CTX_set_min_proto_version(ssl_ctx_, method); |
| return CASS_OK; |
| #else |
| // If we don't have the `set_min_proto_version` function then we do this via |
| // the (deprecated in later versions) options function. |
| int options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; |
| switch (min_version) { |
| case CassSslTlsVersion::CASS_SSL_VERSION_TLS1: |
| break; |
| case CassSslTlsVersion::CASS_SSL_VERSION_TLS1_1: |
| options |= SSL_OP_NO_TLSv1; |
| break; |
| case CassSslTlsVersion::CASS_SSL_VERSION_TLS1_2: |
| options |= SSL_OP_NO_TLSv1; |
| options |= SSL_OP_NO_TLSv1_1; |
| break; |
| default: |
| // unsupported version |
| return CASS_ERROR_LIB_BAD_PARAMS; |
| } |
| SSL_CTX_set_options(ssl_ctx_, options); |
| return CASS_OK; |
| #endif |
| } |
| |
| SslContext::Ptr OpenSslContextFactory::create() { return SslContext::Ptr(new OpenSslContext()); } |
| |
| namespace openssl { |
| |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) |
| void* malloc(size_t size) { return Memory::malloc(size); } |
| |
| void* realloc(void* ptr, size_t size) { return Memory::realloc(ptr, size); } |
| |
| void free(void* ptr) { Memory::free(ptr); } |
| #else |
| void* malloc(size_t size, const char* file, int line) { return Memory::malloc(size); } |
| |
| void* realloc(void* ptr, size_t size, const char* file, int line) { |
| return Memory::realloc(ptr, size); |
| } |
| |
| void free(void* ptr, const char* file, int line) { Memory::free(ptr); } |
| #endif |
| |
| } // namespace openssl |
| |
| void OpenSslContextFactory::internal_init() { |
| CRYPTO_set_mem_functions(openssl::malloc, openssl::realloc, openssl::free); |
| |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| SSL_library_init(); |
| SSL_load_error_strings(); |
| OpenSSL_add_all_algorithms(); |
| #endif |
| |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) |
| // We have to set the lock/id callbacks for use of OpenSSL thread safety. |
| // It's not clear what's thread-safe in OpenSSL. Writing/Reading to |
| // a single "SSL" object is NOT and we don't do that, but we do create multiple |
| // "SSL" objects from a single "SSL_CTX" in different threads. That seems to be |
| // okay with the following callbacks set. |
| int num_locks = CRYPTO_num_locks(); |
| crypto_locks = reinterpret_cast<uv_rwlock_t*>(Memory::malloc(sizeof(uv_rwlock_t) * num_locks)); |
| for (int i = 0; i < num_locks; ++i) { |
| if (uv_rwlock_init(crypto_locks + i)) { |
| fprintf(stderr, "Unable to init read/write lock"); |
| abort(); |
| } |
| } |
| |
| CRYPTO_set_locking_callback(crypto_locking_callback); |
| CRYPTO_set_id_callback(crypto_id_callback); |
| |
| #else |
| rb::RingBufferBio::initialize(); |
| #endif |
| } |
| |
| void OpenSslContextFactory::internal_thread_cleanup() { |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) |
| ERR_remove_thread_state(NULL); |
| #endif |
| } |
| |
| void OpenSslContextFactory::internal_cleanup() { |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| RAND_cleanup(); |
| ENGINE_cleanup(); |
| #endif |
| CONF_modules_unload(1); |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| CONF_modules_free(); |
| EVP_cleanup(); |
| ERR_free_strings(); |
| CRYPTO_cleanup_all_ex_data(); |
| CRYPTO_set_locking_callback(NULL); |
| CRYPTO_set_id_callback(NULL); |
| #endif |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L && OPENSSL_VERSION_NUMBER > 0x10002000L |
| SSL_COMP_free_compression_methods(); |
| #endif |
| |
| thread_cleanup(); |
| |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) |
| if (crypto_locks != NULL) { |
| int num_locks = CRYPTO_num_locks(); |
| for (int i = 0; i < num_locks; ++i) { |
| uv_rwlock_destroy(crypto_locks + i); |
| } |
| Memory::free(crypto_locks); |
| crypto_locks = NULL; |
| } |
| #else |
| rb::RingBufferBio::cleanup(); |
| #endif |
| } |