| // Licensed 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 "openssl.hpp" |
| |
| #ifndef __WINDOWS__ |
| #include <sys/param.h> |
| #endif // __WINDOWS__ |
| |
| #include <openssl/err.h> |
| #include <openssl/rand.h> |
| #include <openssl/ssl.h> |
| #include <openssl/x509v3.h> |
| |
| #include <map> |
| #include <mutex> |
| #include <string> |
| #include <thread> |
| |
| #include <process/once.hpp> |
| |
| #include <process/ssl/flags.hpp> |
| |
| #include <stout/os.hpp> |
| #include <stout/strings.hpp> |
| |
| #ifdef __WINDOWS__ |
| // OpenSSL on Windows requires this adapter module to be compiled as part of the |
| // consuming project to deal with Windows runtime library differences. Not doing |
| // so manifests itself as the "no OPENSSL_Applink" runtime error. |
| // |
| // https://www.openssl.org/docs/faq.html |
| #include <openssl/applink.c> |
| #endif // __WINDOWS__ |
| |
| using std::map; |
| using std::ostringstream; |
| using std::string; |
| |
| // Must be defined by us for OpenSSL in order to capture the necessary |
| // data for doing locking. Note, this needs to be defined in the |
| // global namespace as well. |
| struct CRYPTO_dynlock_value |
| { |
| std::mutex mutex; |
| }; |
| |
| |
| namespace process { |
| namespace network { |
| namespace openssl { |
| |
| // _Global_ OpenSSL context, initialized via 'initialize'. |
| static SSL_CTX* ctx = nullptr; |
| |
| |
| Flags::Flags() |
| { |
| add(&Flags::enabled, |
| "enabled", |
| "Whether SSL is enabled.", |
| false); |
| |
| add(&Flags::support_downgrade, |
| "support_downgrade", |
| "Enable downgrading SSL accepting sockets to non-SSL traffic. When this " |
| "is enabled, no protocol may be used on non-SSL connections that " |
| "conflics with the protocol headers for SSL.", |
| false); |
| |
| add(&Flags::cert_file, |
| "cert_file", |
| "Path to certifcate."); |
| |
| add(&Flags::key_file, |
| "key_file", |
| "Path to key."); |
| |
| add(&Flags::verify_cert, |
| "verify_cert", |
| "Whether or not to verify peer certificates.", |
| false); |
| |
| add(&Flags::require_cert, |
| "require_cert", |
| "Whether or not to require peer certificates. Requiring a peer " |
| "certificate implies verifying it.", |
| false); |
| |
| add(&Flags::verify_ipadd, |
| "verify_ipadd", |
| "Enable IP address verification in subject alternative name certificate " |
| "extension.", |
| false); |
| |
| add(&Flags::verification_depth, |
| "verification_depth", |
| "Maximum depth for the certificate chain verification that shall be " |
| "allowed.", |
| 4); |
| |
| add(&Flags::ca_dir, |
| "ca_dir", |
| "Path to certifcate authority (CA) directory."); |
| |
| add(&Flags::ca_file, |
| "ca_file", |
| "Path to certifcate authority (CA) file."); |
| |
| add(&Flags::ciphers, |
| "ciphers", |
| "Cryptographic ciphers to use.", |
| // Default TLSv1 ciphers chosen based on Amazon's security |
| // policy, see: |
| // http://docs.aws.amazon.com/ElasticLoadBalancing/latest/ |
| // DeveloperGuide/elb-security-policy-table.html |
| "AES128-SHA:AES256-SHA:RC4-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:" |
| "DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA"); |
| |
| add(&Flags::ecdh_curves, |
| "ecdh_curves", |
| "Colon separated list of curve NID or names, e.g. 'P-521:P-384:P-256'. " |
| "The curves are in preference order. If no list is provided, the most " |
| "appropriate curve for a client will be selected. This behavior can be " |
| "explicitly enabled by setting this flag to 'auto'." |
| "NOTE: Old versions of OpenSSL support only one curve, check " |
| "the documentation of your OpenSSL.", |
| "auto"); |
| |
| // We purposely don't have a flag for SSLv2. We do this because most |
| // systems have disabled SSLv2 at compilation due to having so many |
| // security vulnerabilities. |
| |
| add(&Flags::enable_ssl_v3, |
| "enable_ssl_v3", |
| "Enable SSLV3.", |
| false); |
| |
| add(&Flags::enable_tls_v1_0, |
| "enable_tls_v1_0", |
| "Enable SSLV1.0.", |
| false); |
| |
| add(&Flags::enable_tls_v1_1, |
| "enable_tls_v1_1", |
| "Enable SSLV1.1.", |
| false); |
| |
| add(&Flags::enable_tls_v1_2, |
| "enable_tls_v1_2", |
| "Enable SSLV1.2.", |
| true); |
| } |
| |
| |
| static Flags* ssl_flags = new Flags(); |
| |
| |
| const Flags& flags() |
| { |
| openssl::initialize(); |
| return *ssl_flags; |
| } |
| |
| |
| // Mutexes necessary to support OpenSSL locking on shared data |
| // structures. See 'locking_function' for more information. |
| static std::mutex* mutexes = nullptr; |
| |
| |
| // Callback needed to perform locking on shared data structures. From |
| // the OpenSSL documentation: |
| // |
| // OpenSSL uses a number of global data structures that will be |
| // implicitly shared whenever multiple threads use OpenSSL. |
| // Multi-threaded applications will crash at random if [the locking |
| // function] is not set. |
| void locking_function(int mode, int n, const char* /*file*/, int /*line*/) |
| { |
| if (mode & CRYPTO_LOCK) { |
| mutexes[n].lock(); |
| } else { |
| mutexes[n].unlock(); |
| } |
| } |
| |
| |
| // OpenSSL callback that returns the current thread ID, necessary for |
| // OpenSSL threading. |
| unsigned long id_function() |
| { |
| static_assert(sizeof(std::thread::id) == sizeof(unsigned long), |
| "sizeof(std::thread::id) must be equal to sizeof(unsigned long)" |
| " for std::thread::id to be used as a function for determining " |
| "a thread id"); |
| |
| // We use the std::thread id and convert it to an unsigned long. |
| const std::thread::id id = std::this_thread::get_id(); |
| return *reinterpret_cast<const unsigned long*>(&id); |
| } |
| |
| |
| // OpenSSL callback for creating new dynamic "locks", abstracted by |
| // the CRYPTO_dynlock_value structure. |
| CRYPTO_dynlock_value* dyn_create_function(const char* /*file*/, int /*line*/) |
| { |
| CRYPTO_dynlock_value* value = new CRYPTO_dynlock_value(); |
| |
| if (value == nullptr) { |
| return nullptr; |
| } |
| |
| return value; |
| } |
| |
| |
| // OpenSSL callback for locking and unlocking dynamic "locks", |
| // abstracted by the CRYPTO_dynlock_value structure. |
| void dyn_lock_function( |
| int mode, |
| CRYPTO_dynlock_value* value, |
| const char* /*file*/, |
| int /*line*/) |
| { |
| if (mode & CRYPTO_LOCK) { |
| value->mutex.lock(); |
| } else { |
| value->mutex.unlock(); |
| } |
| } |
| |
| |
| // OpenSSL callback for destroying dynamic "locks", abstracted by the |
| // CRYPTO_dynlock_value structure. |
| void dyn_destroy_function( |
| CRYPTO_dynlock_value* value, |
| const char* /*file*/, |
| int /*line*/) |
| { |
| delete value; |
| } |
| |
| |
| // Callback for OpenSSL peer certificate verification. |
| int verify_callback(int ok, X509_STORE_CTX* store) |
| { |
| if (ok != 1) { |
| // Construct and log a warning message. |
| ostringstream message; |
| |
| X509* cert = X509_STORE_CTX_get_current_cert(store); |
| int error = X509_STORE_CTX_get_error(store); |
| int depth = X509_STORE_CTX_get_error_depth(store); |
| |
| message << "Error with certificate at depth: " << stringify(depth) << "\n"; |
| |
| char buffer[256] {}; |
| |
| // TODO(jmlvanre): use X509_NAME_print_ex instead. |
| X509_NAME_oneline(X509_get_issuer_name(cert), buffer, sizeof(buffer) - 1); |
| |
| message << "Issuer: " << stringify(buffer) << "\n"; |
| |
| // TODO(jmlvanre): use X509_NAME_print_ex instead. |
| memset(buffer, 0, sizeof(buffer)); |
| X509_NAME_oneline(X509_get_subject_name(cert), buffer, sizeof(buffer) - 1); |
| |
| message << "Subject: " << stringify(buffer) << "\n"; |
| |
| message << "Error (" << stringify(error) << "): " << |
| stringify(X509_verify_cert_error_string(error)); |
| |
| LOG(WARNING) << message.str(); |
| } |
| |
| return ok; |
| } |
| |
| |
| string error_string(unsigned long code) |
| { |
| // SSL library guarantees to stay within 120 bytes. |
| char buffer[128]; |
| |
| ERR_error_string_n(code, buffer, sizeof(buffer)); |
| string s(buffer); |
| |
| if (code == SSL_ERROR_SYSCALL) { |
| s += error_string(ERR_get_error()); |
| } |
| |
| return s; |
| } |
| |
| |
| #if OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_ECDH) |
| // Sets the elliptic curve parameters for the given context in order |
| // to enable ECDH ciphers. |
| // Adapted from NGINX SSL initialization code: |
| // https://github.com/nginx/nginx/blob/bfe36ba3185a477d2f8ce120577308646173b736/ |
| // src/event/ngx_event_openssl.c#L1080-L1161 |
| static Try<Nothing> initialize_ecdh_curve(SSL_CTX* ctx, const Flags& ssl_flags) |
| { |
| #if defined(SSL_OP_SINGLE_ECDH_USE) |
| // Let OpenSSL compute new ECDH parameters for each new handshake. |
| // In newer versions (1.0.2+) of OpenSSL this is the default, and |
| // this call has no effect. |
| SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); |
| #endif // SSL_OP_SINGLE_ECDH_USE |
| |
| #if (defined SSL_CTX_set1_curves_list || defined SSL_CTRL_SET_CURVES_LIST) |
| // If `SSL_CTX_set_ecdh_auto` is not defined, OpenSSL will ignore the |
| // preference order of the curve list and use its own algorithm to chose |
| // the right curve for a connection. |
| #if defined(SSL_CTX_set_ecdh_auto) |
| SSL_CTX_set_ecdh_auto(ctx, 1); |
| #endif // SSL_CTX_set_ecdh_auto |
| |
| if (ssl_flags.ecdh_curves == "auto") { |
| return Nothing(); |
| } |
| |
| if (SSL_CTX_set1_curves_list(ctx, ssl_flags.ecdh_curves.c_str()) != 1) { |
| unsigned long error = ERR_get_error(); |
| return Error( |
| "Could not load ECDH curves '" + ssl_flags.ecdh_curves + "' " + |
| "(OpenSSL error #" + stringify(error) + "): " + error_string(error)); |
| } |
| |
| VLOG(2) << "Using ecdh curves: " << ssl_flags.ecdh_curves; |
| #else // SSL_CTX_set1_curves_list || SSL_CTRL_SET_CURVES_LIST |
| string curve = |
| ssl_flags.ecdh_curves == "auto" ? "prime256v1" : ssl_flags.ecdh_curves; |
| |
| int nid = OBJ_sn2nid(curve.c_str()); |
| if (nid == 0) { |
| unsigned long error = ERR_get_error(); |
| return Error( |
| "Unknown curve '" + curve + "' (OpenSSL error #" + stringify(error) + |
| "): " + error_string(error)); |
| } |
| |
| EC_KEY* ecdh = EC_KEY_new_by_curve_name(nid); |
| if (ecdh == nullptr) { |
| unsigned long error = ERR_get_error(); |
| return Error( |
| "Error generating key from curve " + curve + "' (OpenSSL error #" + |
| stringify(error) + "): " + error_string(error)); |
| } |
| |
| SSL_CTX_set_tmp_ecdh(ctx, ecdh); |
| EC_KEY_free(ecdh); |
| |
| VLOG(2) << "Using ecdh curve: " << ssl_flags.ecdh_curves; |
| #endif // SSL_CTX_set1_curves_list || SSL_CTRL_SET_CURVES_LIST |
| return Nothing(); |
| } |
| #endif // OPENSSL_VERSION_NUMBER >= 0x0090800fL && !OPENSSL_NO_ECDH |
| |
| // Tests can declare this function and use it to re-configure the SSL |
| // environment variables programatically. Without explicitly declaring |
| // this function, it is not visible. This is the preferred behavior as |
| // we do not want applications changing these settings while they are |
| // running (this would be undefined behavior). |
| // NOTE: This does not change the configuration of existing sockets, such |
| // as the server socket spawned during libprocess initialization. |
| // See `reinitialize` in `process.cpp`. |
| void reinitialize() |
| { |
| // Wipe out and recreate the default flags. |
| // This is especially important for tests, which might repeatedly |
| // change environment variables and call `reinitialize`. |
| *ssl_flags = Flags(); |
| |
| // Load all the flags prefixed by LIBPROCESS_SSL_ from the |
| // environment. See comment at top of openssl.hpp for a full list. |
| // |
| // NOTE: We used to look for environment variables prefixed by SSL_. |
| // To be backward compatible, we interpret environment variables |
| // prefixed with either SSL_ and LIBPROCESS_SSL_ where the latter |
| // one takes precedence. See details in MESOS-5863. |
| map<string, Option<string>> environment_ssl = |
| ssl_flags->extract("SSL_"); |
| map<string, Option<string>> environments = |
| ssl_flags->extract("LIBPROCESS_SSL_"); |
| foreachpair ( |
| const string& key, const Option<string>& value, environment_ssl) { |
| if (environments.count(key) > 0 && environments.at(key) != value) { |
| LOG(WARNING) << "Mismatched values for SSL environment variables " |
| << "SSL_" << key << " and " |
| << "LIBPROCESS_SSL_" << key; |
| } |
| } |
| environments.insert(environment_ssl.begin(), environment_ssl.end()); |
| |
| Try<flags::Warnings> load = ssl_flags->load(environments); |
| if (load.isError()) { |
| EXIT(EXIT_FAILURE) |
| << "Failed to load flags from environment variables " |
| << "prefixed by LIBPROCESS_SSL_ or SSL_ (deprecated): " |
| << load.error(); |
| } |
| |
| // Log any flag warnings. |
| foreach (const flags::Warning& warning, load->warnings) { |
| LOG(WARNING) << warning.message; |
| } |
| |
| // Exit early if SSL is not enabled. |
| if (!ssl_flags->enabled) { |
| return; |
| } |
| |
| static Once* initialized_single_entry = new Once(); |
| |
| // We don't want to initialize everything multiple times, as we |
| // don't clean up some of these structures. The things we DO tend |
| // to re-initialize are things that are overwrites of settings, |
| // rather than allocations of new data structures. |
| if (!initialized_single_entry->once()) { |
| // We MUST have entropy, or else there's no point to crypto. |
| if (!RAND_poll()) { |
| EXIT(EXIT_FAILURE) << "SSL socket requires entropy"; |
| } |
| |
| // Initialize the OpenSSL library. |
| SSL_library_init(); |
| SSL_load_error_strings(); |
| |
| // Prepare mutexes for threading callbacks. |
| mutexes = new std::mutex[CRYPTO_num_locks()]; |
| |
| // Install SSL threading callbacks. |
| // TODO(jmlvanre): the id mechanism is deprecated in OpenSSL. |
| CRYPTO_set_id_callback(&id_function); |
| CRYPTO_set_locking_callback(&locking_function); |
| CRYPTO_set_dynlock_create_callback(&dyn_create_function); |
| CRYPTO_set_dynlock_lock_callback(&dyn_lock_function); |
| CRYPTO_set_dynlock_destroy_callback(&dyn_destroy_function); |
| |
| initialized_single_entry->done(); |
| } |
| |
| // Clean up if we had a previous SSL context object. We want to |
| // re-initialize this to get rid of any non-default settings. |
| if (ctx != nullptr) { |
| SSL_CTX_free(ctx); |
| ctx = nullptr; |
| } |
| |
| // Replace with `TLS_method` once our minimum OpenSSL version |
| // supports it. |
| ctx = SSL_CTX_new(SSLv23_method()); |
| CHECK(ctx) << "Failed to create SSL context: " |
| << ERR_error_string(ERR_get_error(), nullptr); |
| |
| // Disable SSL session caching. |
| SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); |
| |
| // Set a session id to avoid connection termination upon |
| // re-connect. We can use something more relevant when we care |
| // about session caching. |
| const uint64_t session_ctx = 7; |
| |
| const unsigned char* session_id = |
| reinterpret_cast<const unsigned char*>(&session_ctx); |
| |
| if (SSL_CTX_set_session_id_context( |
| ctx, |
| session_id, |
| sizeof(session_ctx)) != 1) { |
| LOG(FATAL) << "Session id context size exceeds maximum"; |
| } |
| |
| // Notify users of the 'SSL_SUPPORT_DOWNGRADE' flag that this |
| // setting allows insecure connections. |
| if (ssl_flags->support_downgrade) { |
| LOG(WARNING) << |
| "Failed SSL connections will be downgraded to a non-SSL socket"; |
| } |
| |
| // Now do some validation of the flags/environment variables. |
| if (ssl_flags->key_file.isNone()) { |
| EXIT(EXIT_FAILURE) |
| << "SSL requires key! NOTE: Set path with LIBPROCESS_SSL_KEY_FILE"; |
| } |
| |
| if (ssl_flags->cert_file.isNone()) { |
| EXIT(EXIT_FAILURE) |
| << "SSL requires certificate! NOTE: Set path with " |
| << "LIBPROCESS_SSL_CERT_FILE"; |
| } |
| |
| if (ssl_flags->ca_file.isNone()) { |
| LOG(INFO) << "CA file path is unspecified! NOTE: " |
| << "Set CA file path with LIBPROCESS_SSL_CA_FILE=<filepath>"; |
| } |
| |
| if (ssl_flags->ca_dir.isNone()) { |
| LOG(INFO) << "CA directory path unspecified! NOTE: " |
| << "Set CA directory path with LIBPROCESS_SSL_CA_DIR=<dirpath>"; |
| } |
| |
| if (!ssl_flags->verify_cert) { |
| LOG(INFO) << "Will not verify peer certificate!\n" |
| << "NOTE: Set LIBPROCESS_SSL_VERIFY_CERT=1 to enable " |
| << "peer certificate verification"; |
| } |
| |
| if (!ssl_flags->require_cert) { |
| LOG(INFO) << "Will only verify peer certificate if presented!\n" |
| << "NOTE: Set LIBPROCESS_SSL_REQUIRE_CERT=1 to require " |
| << "peer certificate verification"; |
| } |
| |
| if (ssl_flags->verify_ipadd) { |
| LOG(INFO) << "Will use IP address verification in subject alternative name " |
| << "certificate extension."; |
| } |
| |
| if (ssl_flags->require_cert && !ssl_flags->verify_cert) { |
| // Requiring a certificate implies that is should be verified. |
| ssl_flags->verify_cert = true; |
| |
| LOG(INFO) << "LIBPROCESS_SSL_REQUIRE_CERT implies " |
| << "peer certificate verification.\n" |
| << "LIBPROCESS_SSL_VERIFY_CERT set to true"; |
| } |
| |
| // Initialize OpenSSL if we've been asked to do verification of peer |
| // certificates. |
| if (ssl_flags->verify_cert) { |
| // Set CA locations. |
| if (ssl_flags->ca_file.isSome() || ssl_flags->ca_dir.isSome()) { |
| const char* ca_file = |
| ssl_flags->ca_file.isSome() ? ssl_flags->ca_file->c_str() : nullptr; |
| |
| const char* ca_dir = |
| ssl_flags->ca_dir.isSome() ? ssl_flags->ca_dir->c_str() : nullptr; |
| |
| if (SSL_CTX_load_verify_locations(ctx, ca_file, ca_dir) != 1) { |
| unsigned long error = ERR_get_error(); |
| EXIT(EXIT_FAILURE) |
| << "Could not load CA file and/or directory (OpenSSL error #" |
| << stringify(error) << "): " |
| << error_string(error) << " -> " |
| << (ca_file != nullptr ? (stringify("FILE: ") + ca_file) : "") |
| << (ca_dir != nullptr ? (stringify("DIR: ") + ca_dir) : ""); |
| } |
| |
| if (ca_file != nullptr) { |
| LOG(INFO) << "Using CA file: " << ca_file; |
| } |
| if (ca_dir != nullptr) { |
| LOG(INFO) << "Using CA dir: " << ca_dir; |
| } |
| } else { |
| if (SSL_CTX_set_default_verify_paths(ctx) != 1) { |
| EXIT(EXIT_FAILURE) << "Could not load default CA file and/or directory"; |
| } |
| |
| // For getting the defaults for ca-directory and/or ca-file from |
| // openssl, we have to mimic parts of its logic; if the user has |
| // set the openssl-specific environment variable, use that one - |
| // if the user has not set that variable, use the compiled in |
| // defaults. |
| string ca_dir; |
| |
| const map<string, string> environment = os::environment(); |
| |
| if (environment.count(X509_get_default_cert_dir_env()) > 0) { |
| ca_dir = environment.at(X509_get_default_cert_dir_env()); |
| } else { |
| ca_dir = X509_get_default_cert_dir(); |
| } |
| |
| string ca_file; |
| |
| if (environment.count(X509_get_default_cert_file_env()) > 0) { |
| ca_file = environment.at(X509_get_default_cert_file_env()); |
| } else { |
| ca_file = X509_get_default_cert_file(); |
| } |
| |
| LOG(INFO) << "Using default CA file '" << ca_file |
| << "' and/or directory '" << ca_dir << "'"; |
| } |
| |
| // Set SSL peer verification callback. |
| int mode = SSL_VERIFY_PEER; |
| |
| if (ssl_flags->require_cert) { |
| mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; |
| } |
| |
| SSL_CTX_set_verify(ctx, mode, &verify_callback); |
| |
| SSL_CTX_set_verify_depth(ctx, ssl_flags->verification_depth); |
| } else { |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr); |
| } |
| |
| // Set certificate chain. |
| if (SSL_CTX_use_certificate_chain_file( |
| ctx, |
| ssl_flags->cert_file->c_str()) != 1) { |
| unsigned long error = ERR_get_error(); |
| EXIT(EXIT_FAILURE) |
| << "Could not load cert file '" << ssl_flags->cert_file.get() << "' " |
| << "(OpenSSL error #" << stringify(error) << "): " << error_string(error); |
| } |
| |
| // Set private key. |
| if (SSL_CTX_use_PrivateKey_file( |
| ctx, ssl_flags->key_file->c_str(), SSL_FILETYPE_PEM) != 1) { |
| unsigned long error = ERR_get_error(); |
| EXIT(EXIT_FAILURE) |
| << "Could not load key file '" << ssl_flags->key_file.get() << "' " |
| << "(OpenSSL error #" << stringify(error) << "): " << error_string(error); |
| } |
| |
| // Validate key. |
| if (SSL_CTX_check_private_key(ctx) != 1) { |
| unsigned long error = ERR_get_error(); |
| EXIT(EXIT_FAILURE) |
| << "Private key does not match the certificate public key " |
| << "(OpenSSL error #" << stringify(error) << "): " << error_string(error); |
| } |
| |
| VLOG(2) << "Using ciphers: " << ssl_flags->ciphers; |
| |
| if (SSL_CTX_set_cipher_list(ctx, ssl_flags->ciphers.c_str()) == 0) { |
| unsigned long error = ERR_get_error(); |
| EXIT(EXIT_FAILURE) |
| << "Could not set ciphers '" << ssl_flags->ciphers << "' " |
| << "(OpenSSL error #" << stringify(error) << "): " << error_string(error); |
| } |
| |
| // Clear all the protocol options. They will be reset if needed |
| // below. We do this because 'SSL_CTX_set_options' only augments, it |
| // does not do an overwrite. |
| SSL_CTX_clear_options( |
| ctx, |
| SSL_OP_NO_SSLv2 | |
| SSL_OP_NO_SSLv3 | |
| SSL_OP_NO_TLSv1 | |
| SSL_OP_NO_TLSv1_1 | |
| SSL_OP_NO_TLSv1_2); |
| |
| // Use server preference for cipher. |
| long ssl_options = SSL_OP_CIPHER_SERVER_PREFERENCE; |
| |
| // Always disable SSLv2. We do this because most systems have |
| // disabled SSLv2 at compilation due to having so many security |
| // vulnerabilities. |
| ssl_options |= SSL_OP_NO_SSLv2; |
| |
| // Disable SSLv3. |
| if (!ssl_flags->enable_ssl_v3) { ssl_options |= SSL_OP_NO_SSLv3; } |
| // Disable TLSv1. |
| if (!ssl_flags->enable_tls_v1_0) { ssl_options |= SSL_OP_NO_TLSv1; } |
| // Disable TLSv1.1. |
| if (!ssl_flags->enable_tls_v1_1) { ssl_options |= SSL_OP_NO_TLSv1_1; } |
| // Disable TLSv1.2. |
| if (!ssl_flags->enable_tls_v1_2) { ssl_options |= SSL_OP_NO_TLSv1_2; } |
| |
| SSL_CTX_set_options(ctx, ssl_options); |
| |
| #if OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_ECDH) |
| Try<Nothing> ecdh_initialized = initialize_ecdh_curve(ctx, *ssl_flags); |
| if (ecdh_initialized.isError()) { |
| EXIT(EXIT_FAILURE) << ecdh_initialized.error(); |
| } |
| #endif // OPENSSL_VERSION_NUMBER >= 0x0090800fL && !OPENSSL_NO_ECDH |
| } |
| |
| |
| void initialize() |
| { |
| static Once* initialized = new Once(); |
| |
| if (initialized->once()) { |
| return; |
| } |
| |
| // We delegate to 'reinitialize()' so that tests can change the SSL |
| // configuration programatically. |
| reinitialize(); |
| |
| initialized->done(); |
| } |
| |
| |
| SSL_CTX* context() |
| { |
| // TODO(benh): Always call 'initialize' just in case? |
| return ctx; |
| } |
| |
| |
| Try<Nothing> verify( |
| const SSL* const ssl, |
| const Option<string>& hostname, |
| const Option<net::IP>& ip) |
| { |
| // Return early if we don't need to verify. |
| if (!ssl_flags->verify_cert) { |
| return Nothing(); |
| } |
| |
| // The X509 object must be freed if this call succeeds. |
| // TODO(jmlvanre): handle this better. How about RAII? |
| X509* cert = SSL_get_peer_certificate(ssl); |
| |
| if (cert == nullptr) { |
| return ssl_flags->require_cert |
| ? Error("Peer did not provide certificate") |
| : Try<Nothing>(Nothing()); |
| } |
| |
| if (SSL_get_verify_result(ssl) != X509_V_OK) { |
| X509_free(cert); |
| return Error("Could not verify peer certificate"); |
| } |
| |
| if (!ssl_flags->verify_ipadd && hostname.isNone()) { |
| X509_free(cert); |
| return ssl_flags->require_cert |
| ? Error("Cannot verify peer certificate: peer hostname unknown") |
| : Try<Nothing>(Nothing()); |
| } |
| |
| // From https://wiki.openssl.org/index.php/Hostname_validation. |
| // Check the Subject Alternate Name extension (SAN). This is useful |
| // for certificates that serve multiple physical hosts. |
| STACK_OF(GENERAL_NAME)* san_names = |
| reinterpret_cast<STACK_OF(GENERAL_NAME)*>(X509_get_ext_d2i( |
| reinterpret_cast<X509*>(cert), |
| NID_subject_alt_name, |
| nullptr, |
| nullptr)); |
| |
| if (san_names != nullptr) { |
| int san_names_num = sk_GENERAL_NAME_num(san_names); |
| |
| // Check each name within the extension. |
| for (int i = 0; i < san_names_num; i++) { |
| const GENERAL_NAME* current_name = sk_GENERAL_NAME_value(san_names, i); |
| |
| switch(current_name->type) { |
| case GEN_DNS: { |
| if (hostname.isSome()) { |
| // Current name is a DNS name, let's check it. |
| const string dns_name = |
| reinterpret_cast<char*>(ASN1_STRING_data( |
| current_name->d.dNSName)); |
| |
| // Make sure there isn't an embedded NUL character in the DNS name. |
| const size_t length = ASN1_STRING_length(current_name->d.dNSName); |
| if (length != dns_name.length()) { |
| sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); |
| X509_free(cert); |
| return Error( |
| "X509 certificate malformed: " |
| "embedded NUL character in DNS name"); |
| } else { |
| VLOG(2) << "Matching dNSName(" << i << "): " << dns_name; |
| |
| // Compare expected hostname with the DNS name. |
| if (hostname.get() == dns_name) { |
| sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); |
| X509_free(cert); |
| |
| VLOG(2) << "dNSName match found for " << hostname.get(); |
| |
| return Nothing(); |
| } |
| } |
| } |
| break; |
| } |
| case GEN_IPADD: { |
| if (ssl_flags->verify_ipadd && ip.isSome()) { |
| // Current name is an IPAdd, let's check it. |
| const ASN1_OCTET_STRING* current_ipadd = current_name->d.iPAddress; |
| |
| if (current_ipadd->type == V_ASN1_OCTET_STRING && |
| current_ipadd->data != nullptr && |
| current_ipadd->length == sizeof(uint32_t)) { |
| const net::IP ip_add(ntohl( |
| *reinterpret_cast<uint32_t*>(current_ipadd->data))); |
| |
| VLOG(2) << "Matching iPAddress(" << i << "): " << ip_add; |
| |
| if (ip.get() == ip_add) { |
| sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); |
| X509_free(cert); |
| |
| VLOG(2) << "iPAddress match found for " << ip.get(); |
| |
| return Nothing(); |
| } |
| } |
| } |
| break; |
| } |
| } |
| } |
| |
| sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); |
| } |
| |
| if (hostname.isSome()) { |
| // If we still haven't verified the hostname, try doing it via |
| // the certificate subject name. |
| X509_NAME* name = X509_get_subject_name(cert); |
| |
| if (name != nullptr) { |
| char text[MAXHOSTNAMELEN] {}; |
| |
| if (X509_NAME_get_text_by_NID( |
| name, |
| NID_commonName, |
| text, |
| sizeof(text)) > 0) { |
| VLOG(2) << "Matching common name: " << text; |
| |
| if (hostname.get() != text) { |
| X509_free(cert); |
| return Error( |
| "Presented Certificate Name: " + stringify(text) + |
| " does not match peer hostname name: " + hostname.get()); |
| } |
| |
| VLOG(2) << "Common name match found for " << hostname.get(); |
| |
| X509_free(cert); |
| return Nothing(); |
| } |
| } |
| } |
| |
| // If we still haven't exited, we haven't verified it, and we give up. |
| X509_free(cert); |
| |
| std::vector<string> details; |
| |
| if (hostname.isSome()) { |
| details.push_back("hostname " + hostname.get()); |
| } |
| |
| if (ip.isSome()) { |
| details.push_back("IP " + stringify(ip.get())); |
| } |
| |
| return Error( |
| "Could not verify presented certificate with " + |
| strings::join(", ", details)); |
| } |
| |
| } // namespace openssl { |
| } // namespace network { |
| } // namespace process { |