| /** @file |
| |
| A brief file description |
| |
| @section license License |
| |
| 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. |
| */ |
| |
| /*************************** -*- Mod: C++ -*- ****************************** |
| SSLConfig.cc |
| Created On : 07/20/2000 |
| |
| Description: |
| SSL Configurations |
| ****************************************************************************/ |
| |
| #include "P_SSLConfig.h" |
| |
| #include <cstring> |
| #include <cmath> |
| |
| #include "tscore/ink_platform.h" |
| #include "tscore/I_Layout.h" |
| #include "records/I_RecHttp.h" |
| |
| #include "HttpConfig.h" |
| |
| #include "P_Net.h" |
| #include "P_SSLClientUtils.h" |
| #include "P_SSLCertLookup.h" |
| #include "SSLDiags.h" |
| #include "SSLSessionCache.h" |
| #include "SSLSessionTicket.h" |
| #include "YamlSNIConfig.h" |
| |
| int SSLConfig::configid = 0; |
| int SSLCertificateConfig::configid = 0; |
| int SSLTicketKeyConfig::configid = 0; |
| int SSLConfigParams::ssl_maxrecord = 0; |
| int SSLConfigParams::ssl_misc_max_iobuffer_size_index = 8; |
| bool SSLConfigParams::ssl_allow_client_renegotiation = false; |
| bool SSLConfigParams::ssl_ocsp_enabled = false; |
| int SSLConfigParams::ssl_ocsp_cache_timeout = 3600; |
| int SSLConfigParams::ssl_ocsp_request_timeout = 10; |
| int SSLConfigParams::ssl_ocsp_update_period = 60; |
| int SSLConfigParams::ssl_handshake_timeout_in = 0; |
| size_t SSLConfigParams::session_cache_number_buckets = 1024; |
| bool SSLConfigParams::session_cache_skip_on_lock_contention = false; |
| size_t SSLConfigParams::session_cache_max_bucket_size = 100; |
| init_ssl_ctx_func SSLConfigParams::init_ssl_ctx_cb = nullptr; |
| load_ssl_file_func SSLConfigParams::load_ssl_file_cb = nullptr; |
| IpMap *SSLConfigParams::proxy_protocol_ipmap = nullptr; |
| |
| const uint32_t EARLY_DATA_DEFAULT_SIZE = 16384; |
| uint32_t SSLConfigParams::server_max_early_data = 0; |
| uint32_t SSLConfigParams::server_recv_max_early_data = EARLY_DATA_DEFAULT_SIZE; |
| bool SSLConfigParams::server_allow_early_data_params = false; |
| |
| int SSLConfigParams::async_handshake_enabled = 0; |
| char *SSLConfigParams::engine_conf_file = nullptr; |
| |
| static std::unique_ptr<ConfigUpdateHandler<SSLCertificateConfig>> sslCertUpdate; |
| static std::unique_ptr<ConfigUpdateHandler<SSLConfig>> sslConfigUpdate; |
| static std::unique_ptr<ConfigUpdateHandler<SSLTicketKeyConfig>> sslTicketKey; |
| |
| SSLConfigParams::SSLConfigParams() |
| { |
| ink_mutex_init(&ctxMapLock); |
| reset(); |
| } |
| |
| SSLConfigParams::~SSLConfigParams() |
| { |
| cleanup(); |
| ink_mutex_destroy(&ctxMapLock); |
| } |
| |
| void |
| SSLConfigInit(IpMap *global) |
| { |
| SSLConfigParams::proxy_protocol_ipmap = global; |
| } |
| |
| void |
| SSLConfigParams::reset() |
| { |
| serverCertPathOnly = serverCertChainFilename = configFilePath = serverCACertFilename = serverCACertPath = clientCertPath = |
| clientKeyPath = clientCACertFilename = clientCACertPath = cipherSuite = client_cipherSuite = dhparamsFile = serverKeyPathOnly = |
| clientKeyPathOnly = clientCertPathOnly = nullptr; |
| server_tls13_cipher_suites = nullptr; |
| client_tls13_cipher_suites = nullptr; |
| server_groups_list = nullptr; |
| client_groups_list = nullptr; |
| client_ctx = nullptr; |
| clientCertLevel = client_verify_depth = verify_depth = 0; |
| verifyServerPolicy = YamlSNIConfig::Policy::DISABLED; |
| verifyServerProperties = YamlSNIConfig::Property::NONE; |
| ssl_ctx_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; |
| ssl_client_ctx_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; |
| ssl_session_cache = SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL; |
| ssl_session_cache_size = 1024 * 100; |
| ssl_session_cache_num_buckets = 1024; // Sessions per bucket is ceil(ssl_session_cache_size / ssl_session_cache_num_buckets) |
| ssl_session_cache_skip_on_contention = 0; |
| ssl_session_cache_timeout = 0; |
| ssl_session_cache_auto_clear = 1; |
| configExitOnLoadError = 1; |
| } |
| |
| void |
| SSLConfigParams::cleanup() |
| { |
| serverCertChainFilename = static_cast<char *>(ats_free_null(serverCertChainFilename)); |
| serverCACertFilename = static_cast<char *>(ats_free_null(serverCACertFilename)); |
| serverCACertPath = static_cast<char *>(ats_free_null(serverCACertPath)); |
| clientCertPath = static_cast<char *>(ats_free_null(clientCertPath)); |
| clientCertPathOnly = static_cast<char *>(ats_free_null(clientCertPathOnly)); |
| clientKeyPath = static_cast<char *>(ats_free_null(clientKeyPath)); |
| clientKeyPathOnly = static_cast<char *>(ats_free_null(clientKeyPathOnly)); |
| clientCACertFilename = static_cast<char *>(ats_free_null(clientCACertFilename)); |
| clientCACertPath = static_cast<char *>(ats_free_null(clientCACertPath)); |
| configFilePath = static_cast<char *>(ats_free_null(configFilePath)); |
| serverCertPathOnly = static_cast<char *>(ats_free_null(serverCertPathOnly)); |
| serverKeyPathOnly = static_cast<char *>(ats_free_null(serverKeyPathOnly)); |
| cipherSuite = static_cast<char *>(ats_free_null(cipherSuite)); |
| client_cipherSuite = static_cast<char *>(ats_free_null(client_cipherSuite)); |
| dhparamsFile = static_cast<char *>(ats_free_null(dhparamsFile)); |
| |
| server_tls13_cipher_suites = static_cast<char *>(ats_free_null(server_tls13_cipher_suites)); |
| client_tls13_cipher_suites = static_cast<char *>(ats_free_null(client_tls13_cipher_suites)); |
| server_groups_list = static_cast<char *>(ats_free_null(server_groups_list)); |
| client_groups_list = static_cast<char *>(ats_free_null(client_groups_list)); |
| |
| cleanupCTXTable(); |
| reset(); |
| } |
| |
| /** set_paths_helper |
| |
| If path is *not* absolute, consider it relative to PREFIX |
| if it's empty, just take SYSCONFDIR, otherwise we can take it as-is |
| if final_path is nullptr, it will not be updated. |
| |
| XXX: Add handling for Windows? |
| */ |
| static void |
| set_paths_helper(const char *path, const char *filename, char **final_path, char **final_filename) |
| { |
| if (final_path) { |
| if (path && path[0] != '/') { |
| *final_path = ats_stringdup(Layout::get()->relative_to(Layout::get()->prefix, path)); |
| } else if (!path || path[0] == '\0') { |
| *final_path = ats_stringdup(RecConfigReadConfigDir()); |
| } else { |
| *final_path = ats_strdup(path); |
| } |
| } |
| |
| if (final_filename && path) { |
| *final_filename = filename ? ats_stringdup(Layout::get()->relative_to(path, filename)) : nullptr; |
| } |
| } |
| |
| void |
| SSLConfigParams::initialize() |
| { |
| char *serverCertRelativePath = nullptr; |
| char *ssl_server_private_key_path = nullptr; |
| char *CACertRelativePath = nullptr; |
| char *ssl_client_cert_filename = nullptr; |
| char *ssl_client_cert_path = nullptr; |
| char *ssl_client_private_key_filename = nullptr; |
| char *ssl_client_private_key_path = nullptr; |
| char *clientCACertRelativePath = nullptr; |
| char *ssl_server_ca_cert_filename = nullptr; |
| char *ssl_client_ca_cert_filename = nullptr; |
| char *ssl_ocsp_response_path = nullptr; |
| |
| cleanup(); |
| |
| //+++++++++++++++++++++++++ Server part +++++++++++++++++++++++++++++++++ |
| verify_depth = 7; |
| |
| REC_ReadConfigInt32(clientCertLevel, "proxy.config.ssl.client.certification_level"); |
| REC_ReadConfigStringAlloc(cipherSuite, "proxy.config.ssl.server.cipher_suite"); |
| REC_ReadConfigStringAlloc(client_cipherSuite, "proxy.config.ssl.client.cipher_suite"); |
| REC_ReadConfigStringAlloc(server_tls13_cipher_suites, "proxy.config.ssl.server.TLSv1_3.cipher_suites"); |
| REC_ReadConfigStringAlloc(client_tls13_cipher_suites, "proxy.config.ssl.client.TLSv1_3.cipher_suites"); |
| |
| dhparamsFile = ats_stringdup(RecConfigReadConfigPath("proxy.config.ssl.server.dhparams_file")); |
| |
| int option = 0; |
| |
| REC_ReadConfigInteger(option, "proxy.config.ssl.TLSv1"); |
| if (!option) { |
| ssl_ctx_options |= SSL_OP_NO_TLSv1; |
| } |
| |
| REC_ReadConfigInteger(option, "proxy.config.ssl.client.TLSv1"); |
| if (!option) { |
| ssl_client_ctx_options |= SSL_OP_NO_TLSv1; |
| } |
| |
| REC_ReadConfigInteger(option, "proxy.config.ssl.TLSv1_1"); |
| if (!option) { |
| ssl_ctx_options |= SSL_OP_NO_TLSv1_1; |
| } |
| |
| REC_ReadConfigInteger(option, "proxy.config.ssl.client.TLSv1_1"); |
| if (!option) { |
| ssl_client_ctx_options |= SSL_OP_NO_TLSv1_1; |
| } |
| |
| REC_ReadConfigInteger(option, "proxy.config.ssl.TLSv1_2"); |
| if (!option) { |
| ssl_ctx_options |= SSL_OP_NO_TLSv1_2; |
| } |
| |
| REC_ReadConfigInteger(option, "proxy.config.ssl.client.TLSv1_2"); |
| if (!option) { |
| ssl_client_ctx_options |= SSL_OP_NO_TLSv1_2; |
| } |
| |
| #ifdef SSL_OP_NO_TLSv1_3 |
| REC_ReadConfigInteger(option, "proxy.config.ssl.TLSv1_3"); |
| if (!option) { |
| ssl_ctx_options |= SSL_OP_NO_TLSv1_3; |
| } |
| |
| REC_ReadConfigInteger(option, "proxy.config.ssl.client.TLSv1_3"); |
| if (!option) { |
| ssl_client_ctx_options |= SSL_OP_NO_TLSv1_3; |
| } |
| #endif |
| |
| #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE |
| REC_ReadConfigInteger(option, "proxy.config.ssl.server.honor_cipher_order"); |
| if (option) { |
| ssl_ctx_options |= SSL_OP_CIPHER_SERVER_PREFERENCE; |
| } |
| #endif |
| |
| #ifdef SSL_OP_PRIORITIZE_CHACHA |
| REC_ReadConfigInteger(option, "proxy.config.ssl.server.prioritize_chacha"); |
| if (option) { |
| ssl_ctx_options |= SSL_OP_PRIORITIZE_CHACHA; |
| } |
| #endif |
| |
| #ifdef SSL_OP_NO_COMPRESSION |
| ssl_ctx_options |= SSL_OP_NO_COMPRESSION; |
| ssl_client_ctx_options |= SSL_OP_NO_COMPRESSION; |
| #else |
| sk_SSL_COMP_zero(SSL_COMP_get_compression_methods()); |
| #endif |
| |
| // Enable ephemeral DH parameters for the case where we use a cipher with DH forward security. |
| #ifdef SSL_OP_SINGLE_DH_USE |
| ssl_ctx_options |= SSL_OP_SINGLE_DH_USE; |
| ssl_client_ctx_options |= SSL_OP_SINGLE_DH_USE; |
| #endif |
| |
| #ifdef SSL_OP_SINGLE_ECDH_USE |
| ssl_ctx_options |= SSL_OP_SINGLE_ECDH_USE; |
| ssl_client_ctx_options |= SSL_OP_SINGLE_ECDH_USE; |
| #endif |
| |
| // Enable all SSL compatibility workarounds. |
| ssl_ctx_options |= SSL_OP_ALL; |
| ssl_client_ctx_options |= SSL_OP_ALL; |
| |
| // According to OpenSSL source, applications must enable this if they support the Server Name extension. Since |
| // we do, then we ought to enable this. Httpd also enables this unconditionally. |
| #ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
| ssl_ctx_options |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; |
| ssl_client_ctx_options |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; |
| #endif |
| |
| REC_ReadConfigInteger(server_max_early_data, "proxy.config.ssl.server.max_early_data"); |
| REC_ReadConfigInt32(server_allow_early_data_params, "proxy.config.ssl.server.allow_early_data_params"); |
| |
| // According to OpenSSL the default value is 16384, |
| // we keep it unless "server_max_early_data" is higher. |
| server_recv_max_early_data = std::max(server_max_early_data, EARLY_DATA_DEFAULT_SIZE); |
| |
| REC_ReadConfigStringAlloc(serverCertChainFilename, "proxy.config.ssl.server.cert_chain.filename"); |
| REC_ReadConfigStringAlloc(serverCertRelativePath, "proxy.config.ssl.server.cert.path"); |
| set_paths_helper(serverCertRelativePath, nullptr, &serverCertPathOnly, nullptr); |
| ats_free(serverCertRelativePath); |
| |
| configFilePath = ats_stringdup(RecConfigReadConfigPath("proxy.config.ssl.server.multicert.filename")); |
| REC_ReadConfigInteger(configExitOnLoadError, "proxy.config.ssl.server.multicert.exit_on_load_fail"); |
| |
| REC_ReadConfigStringAlloc(ssl_server_private_key_path, "proxy.config.ssl.server.private_key.path"); |
| set_paths_helper(ssl_server_private_key_path, nullptr, &serverKeyPathOnly, nullptr); |
| ats_free(ssl_server_private_key_path); |
| |
| REC_ReadConfigStringAlloc(ssl_server_ca_cert_filename, "proxy.config.ssl.CA.cert.filename"); |
| REC_ReadConfigStringAlloc(CACertRelativePath, "proxy.config.ssl.CA.cert.path"); |
| set_paths_helper(CACertRelativePath, ssl_server_ca_cert_filename, &serverCACertPath, &serverCACertFilename); |
| ats_free(ssl_server_ca_cert_filename); |
| ats_free(CACertRelativePath); |
| |
| // SSL session cache configurations |
| REC_ReadConfigInteger(ssl_session_cache, "proxy.config.ssl.session_cache"); |
| REC_ReadConfigInteger(ssl_session_cache_size, "proxy.config.ssl.session_cache.size"); |
| REC_ReadConfigInteger(ssl_session_cache_num_buckets, "proxy.config.ssl.session_cache.num_buckets"); |
| REC_ReadConfigInteger(ssl_session_cache_skip_on_contention, "proxy.config.ssl.session_cache.skip_cache_on_bucket_contention"); |
| REC_ReadConfigInteger(ssl_session_cache_timeout, "proxy.config.ssl.session_cache.timeout"); |
| REC_ReadConfigInteger(ssl_session_cache_auto_clear, "proxy.config.ssl.session_cache.auto_clear"); |
| |
| SSLConfigParams::session_cache_max_bucket_size = |
| static_cast<size_t>(ceil(static_cast<double>(ssl_session_cache_size) / ssl_session_cache_num_buckets)); |
| SSLConfigParams::session_cache_skip_on_lock_contention = ssl_session_cache_skip_on_contention; |
| SSLConfigParams::session_cache_number_buckets = ssl_session_cache_num_buckets; |
| |
| if (ssl_session_cache == SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL) { |
| session_cache = new SSLSessionCache(); |
| } |
| |
| // SSL record size |
| REC_EstablishStaticConfigInt32(ssl_maxrecord, "proxy.config.ssl.max_record_size"); |
| |
| // SSL OCSP Stapling configurations |
| REC_ReadConfigInt32(ssl_ocsp_enabled, "proxy.config.ssl.ocsp.enabled"); |
| REC_EstablishStaticConfigInt32(ssl_ocsp_cache_timeout, "proxy.config.ssl.ocsp.cache_timeout"); |
| REC_EstablishStaticConfigInt32(ssl_ocsp_request_timeout, "proxy.config.ssl.ocsp.request_timeout"); |
| REC_EstablishStaticConfigInt32(ssl_ocsp_update_period, "proxy.config.ssl.ocsp.update_period"); |
| REC_ReadConfigStringAlloc(ssl_ocsp_response_path, "proxy.config.ssl.ocsp.response.path"); |
| set_paths_helper(ssl_ocsp_response_path, nullptr, &ssl_ocsp_response_path_only, nullptr); |
| ats_free(ssl_ocsp_response_path); |
| |
| REC_ReadConfigInt32(ssl_handshake_timeout_in, "proxy.config.ssl.handshake_timeout_in"); |
| |
| REC_ReadConfigInt32(async_handshake_enabled, "proxy.config.ssl.async.handshake.enabled"); |
| REC_ReadConfigStringAlloc(engine_conf_file, "proxy.config.ssl.engine.conf_file"); |
| |
| REC_ReadConfigStringAlloc(server_groups_list, "proxy.config.ssl.server.groups_list"); |
| |
| // ++++++++++++++++++++++++ Client part ++++++++++++++++++++ |
| client_verify_depth = 7; |
| |
| char *verify_server = nullptr; |
| REC_ReadConfigStringAlloc(verify_server, "proxy.config.ssl.client.verify.server.policy"); |
| if (strcmp(verify_server, "DISABLED") == 0) { |
| verifyServerPolicy = YamlSNIConfig::Policy::DISABLED; |
| } else if (strcmp(verify_server, "PERMISSIVE") == 0) { |
| verifyServerPolicy = YamlSNIConfig::Policy::PERMISSIVE; |
| } else if (strcmp(verify_server, "ENFORCED") == 0) { |
| verifyServerPolicy = YamlSNIConfig::Policy::ENFORCED; |
| } else { |
| Warning("%s is invalid for proxy.config.ssl.client.verify.server.policy. Should be one of DISABLED, PERMISSIVE, or ENFORCED", |
| verify_server); |
| verifyServerPolicy = YamlSNIConfig::Policy::DISABLED; |
| } |
| |
| REC_ReadConfigStringAlloc(verify_server, "proxy.config.ssl.client.verify.server.properties"); |
| if (strcmp(verify_server, "SIGNATURE") == 0) { |
| verifyServerProperties = YamlSNIConfig::Property::SIGNATURE_MASK; |
| } else if (strcmp(verify_server, "NAME") == 0) { |
| verifyServerProperties = YamlSNIConfig::Property::NAME_MASK; |
| } else if (strcmp(verify_server, "ALL") == 0) { |
| verifyServerProperties = YamlSNIConfig::Property::ALL_MASK; |
| } else if (strcmp(verify_server, "NONE") == 0) { |
| verifyServerProperties = YamlSNIConfig::Property::NONE; |
| } else { |
| Warning("%s is invalid for proxy.config.ssl.client.verify.server.properties. Should be one of SIGNATURE, NAME, or ALL", |
| verify_server); |
| verifyServerProperties = YamlSNIConfig::Property::NONE; |
| } |
| ats_free(verify_server); |
| |
| ssl_client_cert_filename = nullptr; |
| ssl_client_cert_path = nullptr; |
| REC_ReadConfigStringAlloc(ssl_client_cert_filename, "proxy.config.ssl.client.cert.filename"); |
| REC_ReadConfigStringAlloc(ssl_client_cert_path, "proxy.config.ssl.client.cert.path"); |
| set_paths_helper(ssl_client_cert_path, ssl_client_cert_filename, &clientCertPathOnly, &clientCertPath); |
| ats_free_null(ssl_client_cert_filename); |
| ats_free_null(ssl_client_cert_path); |
| |
| REC_ReadConfigStringAlloc(ssl_client_private_key_filename, "proxy.config.ssl.client.private_key.filename"); |
| REC_ReadConfigStringAlloc(ssl_client_private_key_path, "proxy.config.ssl.client.private_key.path"); |
| set_paths_helper(ssl_client_private_key_path, ssl_client_private_key_filename, &clientKeyPathOnly, &clientKeyPath); |
| ats_free_null(ssl_client_private_key_filename); |
| ats_free_null(ssl_client_private_key_path); |
| |
| REC_ReadConfigStringAlloc(ssl_client_ca_cert_filename, "proxy.config.ssl.client.CA.cert.filename"); |
| REC_ReadConfigStringAlloc(clientCACertRelativePath, "proxy.config.ssl.client.CA.cert.path"); |
| set_paths_helper(clientCACertRelativePath, ssl_client_ca_cert_filename, &clientCACertPath, &clientCACertFilename); |
| ats_free(clientCACertRelativePath); |
| ats_free(ssl_client_ca_cert_filename); |
| |
| REC_ReadConfigStringAlloc(client_groups_list, "proxy.config.ssl.client.groups_list"); |
| |
| REC_ReadConfigInt32(ssl_allow_client_renegotiation, "proxy.config.ssl.allow_client_renegotiation"); |
| |
| REC_ReadConfigInt32(ssl_misc_max_iobuffer_size_index, "proxy.config.ssl.misc.io.max_buffer_index"); |
| |
| // Enable client regardless of config file settings as remap file |
| // can cause HTTP layer to connect using SSL. But only if SSL |
| // initialization hasn't failed already. |
| client_ctx = this->getCTX(this->clientCertPath, this->clientKeyPath, this->clientCACertFilename, this->clientCACertPath); |
| if (!client_ctx) { |
| SSLError("Can't initialize the SSL client, HTTPS in remap rules will not function"); |
| } |
| } |
| |
| shared_SSL_CTX |
| SSLConfigParams::getClientSSL_CTX() const |
| { |
| return client_ctx; |
| } |
| |
| void |
| SSLConfig::startup() |
| { |
| sslConfigUpdate.reset(new ConfigUpdateHandler<SSLConfig>()); |
| sslConfigUpdate->attach("proxy.config.ssl.client.cert.path"); |
| sslConfigUpdate->attach("proxy.config.ssl.client.cert.filename"); |
| sslConfigUpdate->attach("proxy.config.ssl.client.private_key.path"); |
| sslConfigUpdate->attach("proxy.config.ssl.client.private_key.filename"); |
| reconfigure(); |
| } |
| |
| void |
| SSLConfig::reconfigure() |
| { |
| SSLConfigParams *params; |
| params = new SSLConfigParams; |
| params->initialize(); // re-read configuration |
| configid = configProcessor.set(configid, params); |
| } |
| |
| SSLConfigParams * |
| SSLConfig::acquire() |
| { |
| return static_cast<SSLConfigParams *>(configProcessor.get(configid)); |
| } |
| |
| void |
| SSLConfig::release(SSLConfigParams *params) |
| { |
| configProcessor.release(configid, params); |
| } |
| |
| bool |
| SSLCertificateConfig::startup() |
| { |
| sslCertUpdate.reset(new ConfigUpdateHandler<SSLCertificateConfig>()); |
| sslCertUpdate->attach("proxy.config.ssl.server.multicert.filename"); |
| sslCertUpdate->attach("proxy.config.ssl.server.cert.path"); |
| sslCertUpdate->attach("proxy.config.ssl.server.private_key.path"); |
| sslCertUpdate->attach("proxy.config.ssl.server.cert_chain.filename"); |
| sslCertUpdate->attach("proxy.config.ssl.server.session_ticket.enable"); |
| // Exit if there are problems on the certificate loading and the |
| // proxy.config.ssl.server.multicert.exit_on_load_fail is true |
| SSLConfig::scoped_config params; |
| if (!reconfigure() && params->configExitOnLoadError) { |
| Fatal("failed to load SSL certificate file, %s", params->configFilePath); |
| } |
| |
| return true; |
| } |
| |
| bool |
| SSLCertificateConfig::reconfigure() |
| { |
| bool retStatus = true; |
| SSLConfig::scoped_config params; |
| SSLCertLookup *lookup = new SSLCertLookup(); |
| |
| // Test SSL certificate loading startup. With large numbers of certificates, reloading can take time, so delay |
| // twice the healthcheck period to simulate a loading a large certificate set. |
| if (is_action_tag_set("test.multicert.delay")) { |
| const int secs = 60; |
| Debug("ssl", "delaying certificate reload by %d secs", secs); |
| ink_hrtime_sleep(HRTIME_SECONDS(secs)); |
| } |
| |
| SSLMultiCertConfigLoader loader(params); |
| loader.load(lookup); |
| |
| if (!lookup->is_valid) { |
| retStatus = false; |
| } |
| // If there are errors in the certificate configs and we had wanted to exit on error |
| // we won't want to reset the config |
| if (lookup->is_valid || !params->configExitOnLoadError) { |
| configid = configProcessor.set(configid, lookup); |
| } else { |
| delete lookup; |
| } |
| |
| if (retStatus) { |
| Note("%s finished loading", params->configFilePath); |
| } else { |
| Error("%s failed to load", params->configFilePath); |
| } |
| |
| return retStatus; |
| } |
| |
| SSLCertLookup * |
| SSLCertificateConfig::acquire() |
| { |
| return static_cast<SSLCertLookup *>(configProcessor.get(configid)); |
| } |
| |
| void |
| SSLCertificateConfig::release(SSLCertLookup *lookup) |
| { |
| configProcessor.release(configid, lookup); |
| } |
| |
| bool |
| SSLTicketParams::LoadTicket(bool &nochange) |
| { |
| cleanup(); |
| nochange = true; |
| |
| #if TS_HAVE_OPENSSL_SESSION_TICKETS |
| ssl_ticket_key_block *keyblock = nullptr; |
| |
| SSLConfig::scoped_config params; |
| time_t last_load_time = 0; |
| bool no_default_keyblock = true; |
| |
| SSLTicketKeyConfig::scoped_config ticket_params; |
| if (ticket_params) { |
| last_load_time = ticket_params->load_time; |
| no_default_keyblock = ticket_params->default_global_keyblock == nullptr; |
| } |
| |
| // elevate/allow file access to root read only files/certs |
| uint32_t elevate_setting = 0; |
| REC_ReadConfigInteger(elevate_setting, "proxy.config.ssl.cert.load_elevated"); |
| ElevateAccess elevate_access(elevate_setting ? ElevateAccess::FILE_PRIVILEGE : 0); // destructor will demote for us |
| |
| if (REC_ReadConfigStringAlloc(ticket_key_filename, "proxy.config.ssl.server.ticket_key.filename") == REC_ERR_OKAY && |
| ticket_key_filename != nullptr) { |
| ats_scoped_str ticket_key_path(Layout::relative_to(params->serverCertPathOnly, ticket_key_filename)); |
| // See if the file changed since we last loaded |
| struct stat sdata; |
| if (last_load_time && (stat(ticket_key_filename, &sdata) >= 0)) { |
| if (sdata.st_mtime && sdata.st_mtime <= last_load_time) { |
| Debug("ssl", "ticket key %s has not changed", ticket_key_filename); |
| // No updates since last load |
| return true; |
| } |
| } |
| nochange = false; |
| keyblock = ssl_create_ticket_keyblock(ticket_key_path); |
| // Initialize if we don't have one yet |
| } else if (no_default_keyblock) { |
| nochange = false; |
| keyblock = ssl_create_ticket_keyblock(nullptr); |
| } else { |
| // No need to update. Keep the previous ticket param |
| return true; |
| } |
| if (!keyblock) { |
| Error("Could not load ticket key from %s", ticket_key_filename); |
| return false; |
| } |
| default_global_keyblock = keyblock; |
| load_time = time(nullptr); |
| |
| Debug("ssl", "ticket key reloaded from %s", ticket_key_filename); |
| return true; |
| #endif |
| } |
| |
| void |
| SSLTicketParams::LoadTicketData(char *ticket_data, int ticket_data_len) |
| { |
| cleanup(); |
| #if TS_HAVE_OPENSSL_SESSION_TICKETS |
| if (ticket_data != nullptr && ticket_data_len > 0) { |
| default_global_keyblock = ticket_block_create(ticket_data, ticket_data_len); |
| } else { |
| default_global_keyblock = ssl_create_ticket_keyblock(nullptr); |
| } |
| load_time = time(nullptr); |
| #endif |
| } |
| |
| void |
| SSLTicketKeyConfig::startup() |
| { |
| sslTicketKey.reset(new ConfigUpdateHandler<SSLTicketKeyConfig>()); |
| |
| sslTicketKey->attach("proxy.config.ssl.server.ticket_key.filename"); |
| SSLConfig::scoped_config params; |
| if (!reconfigure() && params->configExitOnLoadError) { |
| Fatal("Failed to load SSL ticket key file"); |
| } |
| } |
| |
| bool |
| SSLTicketKeyConfig::reconfigure() |
| { |
| SSLTicketParams *ticketKey = new SSLTicketParams(); |
| |
| if (ticketKey) { |
| bool nochange = false; |
| if (!ticketKey->LoadTicket(nochange)) { |
| delete ticketKey; |
| return false; |
| } |
| // Nothing updated, leave the original configuration |
| if (nochange) { |
| delete ticketKey; |
| return true; |
| } |
| } |
| configid = configProcessor.set(configid, ticketKey); |
| return true; |
| } |
| |
| bool |
| SSLTicketKeyConfig::reconfigure_data(char *ticket_data, int ticket_data_len) |
| { |
| SSLTicketParams *ticketKey = new SSLTicketParams(); |
| if (ticketKey) { |
| ticketKey->LoadTicketData(ticket_data, ticket_data_len); |
| } |
| configid = configProcessor.set(configid, ticketKey); |
| return true; |
| } |
| |
| void |
| SSLTicketParams::cleanup() |
| { |
| ticket_block_free(default_global_keyblock); |
| ticket_key_filename = static_cast<char *>(ats_free_null(ticket_key_filename)); |
| } |
| |
| shared_SSL_CTX |
| SSLConfigParams::getCTX(const char *client_cert, const char *key_file, const char *ca_bundle_file, const char *ca_bundle_path) const |
| { |
| shared_SSL_CTX client_ctx = nullptr; |
| std::string top_level_key, ctx_key; |
| ts::bwprint(top_level_key, "{}:{}", ca_bundle_file, ca_bundle_path); |
| ts::bwprint(ctx_key, "{}:{}", client_cert, key_file); |
| |
| auto ctx_map_iter = top_level_ctx_map.find(top_level_key); |
| if (ctx_map_iter != top_level_ctx_map.end()) { |
| auto ctx_iter = ctx_map_iter->second.find(ctx_key); |
| if (ctx_iter != ctx_map_iter->second.end()) { |
| client_ctx = ctx_iter->second; |
| } |
| } |
| |
| // Create context if doesn't exists |
| if (!client_ctx) { |
| client_ctx = shared_SSL_CTX(SSLInitClientContext(this), SSLReleaseContext); |
| |
| if (client_cert) { |
| // Set public and private keys |
| if (!SSL_CTX_use_certificate_chain_file(client_ctx.get(), client_cert)) { |
| SSLError("failed to load client certificate from %s", client_cert); |
| goto fail; |
| } |
| if (!key_file || key_file[0] == '\0') { |
| key_file = client_cert; |
| } |
| if (!SSL_CTX_use_PrivateKey_file(client_ctx.get(), key_file, SSL_FILETYPE_PEM)) { |
| SSLError("failed to load client private key file from %s", key_file); |
| goto fail; |
| } |
| |
| if (!SSL_CTX_check_private_key(client_ctx.get())) { |
| SSLError("client private key (%s) does not match the certificate public key (%s)", key_file, client_cert); |
| goto fail; |
| } |
| } |
| |
| // Set CA information for verifying peer cert |
| if (ca_bundle_file != nullptr || ca_bundle_path != nullptr) { |
| if (!SSL_CTX_load_verify_locations(client_ctx.get(), ca_bundle_file, ca_bundle_path)) { |
| SSLError("invalid client CA Certificate file (%s) or CA Certificate path (%s)", ca_bundle_file, ca_bundle_path); |
| goto fail; |
| } |
| } else if (!SSL_CTX_set_default_verify_paths(client_ctx.get())) { |
| SSLError("failed to set the default verify paths"); |
| goto fail; |
| } |
| |
| // Try to update the context in mapping with lock acquired. If a valid context exists, return it without changing the structure. |
| ink_mutex_acquire(&ctxMapLock); |
| auto ctx_iter = top_level_ctx_map[top_level_key].find(ctx_key); |
| if (ctx_iter == top_level_ctx_map[top_level_key].end() || ctx_iter->second == nullptr) { |
| top_level_ctx_map[top_level_key][ctx_key] = client_ctx; |
| } else { |
| client_ctx = ctx_iter->second; |
| } |
| ink_mutex_release(&ctxMapLock); |
| } |
| return client_ctx; |
| |
| fail: |
| return nullptr; |
| } |
| |
| void |
| SSLConfigParams::cleanupCTXTable() |
| { |
| ink_mutex_acquire(&ctxMapLock); |
| top_level_ctx_map.clear(); |
| ink_mutex_release(&ctxMapLock); |
| } |