| /** @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_SSLCertLookup.h" |
| #include "P_SSLClientUtils.h" |
| #include "P_SSLConfig.h" |
| #include "P_SSLUtils.h" |
| #include "P_TLSKeyLogger.h" |
| #include "SSLSessionCache.h" |
| #include "iocore/net/SSLMultiCertConfigLoader.h" |
| #include "iocore/net/SSLDiags.h" |
| #include "iocore/net/TLSEarlyDataSupport.h" |
| #include "iocore/net/YamlSNIConfig.h" |
| #include "tscore/ink_config.h" |
| #include "tscore/Layout.h" |
| #include "records/RecHttp.h" |
| #include "records/RecCore.h" |
| #include "mgmt/config/ConfigContextDiags.h" |
| #include "mgmt/config/ConfigRegistry.h" |
| |
| #include <cinttypes> |
| #include <openssl/pem.h> |
| #include <algorithm> |
| #include <array> |
| #include <cstring> |
| #include <cmath> |
| #include <thread> |
| #include <unordered_map> |
| |
| int SSLConfig::config_index = 0; |
| int SSLConfig::configids[] = {0, 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; |
| bool SSLConfigParams::ssl_ocsp_request_mode = false; |
| int SSLConfigParams::ssl_ocsp_request_timeout = 10; |
| int SSLConfigParams::ssl_ocsp_update_period = 60; |
| char *SSLConfigParams::ssl_ocsp_user_agent = nullptr; |
| int SSLConfigParams::ssl_handshake_timeout_in = 0; |
| int SSLConfigParams::origin_session_cache = 1; |
| size_t SSLConfigParams::origin_session_cache_size = 10240; |
| size_t SSLConfigParams::origin_session_max_size = SSLConfigParams::ORIGIN_SESSION_MAX_SIZE_DEFAULT; |
| init_ssl_ctx_func SSLConfigParams::init_ssl_ctx_cb = nullptr; |
| load_ssl_file_func SSLConfigParams::load_ssl_file_cb = nullptr; |
| swoc::IPRangeSet *SSLConfigParams::proxy_protocol_ip_addrs = nullptr; |
| bool SSLConfigParams::ssl_ktls_enabled = false; |
| |
| 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; |
| |
| namespace |
| { |
| DbgCtl dbg_ctl_ssl_load{"ssl_load"}; |
| DbgCtl dbg_ctl_ssl_config_updateCTX{"ssl_config_updateCTX"}; |
| DbgCtl dbg_ctl_ssl_client_ctx{"ssl_client_ctx"}; |
| |
| } // end anonymous namespace |
| |
| SSLConfigParams::SSLConfigParams() |
| { |
| ink_mutex_init(&ctxMapLock); |
| reset(); |
| } |
| |
| SSLConfigParams::~SSLConfigParams() |
| { |
| cleanup(); |
| ink_mutex_destroy(&ctxMapLock); |
| } |
| |
| void |
| SSLConfigInit(swoc::IPRangeSet *global) |
| { |
| SSLConfigParams::proxy_protocol_ip_addrs = global; |
| } |
| |
| void |
| SSLConfigParams::reset() |
| { |
| serverCertPathOnly = serverCertChainFilename = configFilePath = serverCACertFilename = serverCACertPath = clientCertPath = |
| clientKeyPath = clientCACertFilename = clientCACertPath = cipherSuite = client_cipherSuite = dhparamsFile = serverKeyPathOnly = |
| clientKeyPathOnly = clientCertPathOnly = nullptr; |
| ssl_ocsp_response_path_only = nullptr; |
| server_cert_compression_algorithms = nullptr; |
| client_cert_compression_algorithms = nullptr; |
| server_tls13_cipher_suites = nullptr; |
| client_tls13_cipher_suites = nullptr; |
| server_groups_list = nullptr; |
| client_groups_list = nullptr; |
| keylog_file = 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; |
| configExitOnLoadError = 1; |
| configPartialReload = 0; |
| configLoadConcurrency = 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)); |
| |
| ssl_ocsp_response_path_only = static_cast<char *>(ats_free_null(ssl_ocsp_response_path_only)); |
| |
| server_cert_compression_algorithms = static_cast<char *>(ats_free_null(server_cert_compression_algorithms)); |
| client_cert_compression_algorithms = static_cast<char *>(ats_free_null(client_cert_compression_algorithms)); |
| 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)); |
| keylog_file = static_cast<char *>(ats_free_null(keylog_file)); |
| |
| 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; |
| } |
| } |
| |
| int |
| UpdateServerPolicy(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */, RecData data, |
| void * /* cookie ATS_UNUSED */) |
| { |
| SSLConfigParams *params = SSLConfig::acquire(); |
| char *verify_server = data.rec_string; |
| if (params != nullptr && verify_server != nullptr) { |
| Dbg(dbg_ctl_ssl_load, "New Server Policy %s", verify_server); |
| params->SetServerPolicy(verify_server); |
| } else { |
| Dbg(dbg_ctl_ssl_load, "Failed to load new Server Policy %p %p", verify_server, params); |
| } |
| return 0; |
| } |
| |
| int |
| UpdateServerPolicyProperties(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */, RecData data, |
| void * /* cookie ATS_UNUSED */) |
| { |
| SSLConfigParams *params = SSLConfig::acquire(); |
| char *verify_server = data.rec_string; |
| if (params != nullptr && verify_server != nullptr) { |
| params->SetServerPolicyProperties(verify_server); |
| } |
| return 0; |
| } |
| |
| void |
| SSLConfigParams::SetServerPolicyProperties(const char *verify_server) |
| { |
| 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 ALL, SIGNATURE, NAME, or NONE. " |
| "Default is ALL", |
| verify_server); |
| verifyServerProperties = YamlSNIConfig::Property::NONE; |
| } |
| } |
| |
| void |
| SSLConfigParams::SetServerPolicy(const char *verify_server) |
| { |
| 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. " |
| "Default is DISABLED", |
| verify_server); |
| verifyServerPolicy = YamlSNIConfig::Policy::DISABLED; |
| } |
| } |
| |
| void |
| SSLConfigParams::initialize(ConfigContext ctx) |
| { |
| cleanup(); |
| |
| //+++++++++++++++++++++++++ Server part +++++++++++++++++++++++++++++++++ |
| verify_depth = 7; |
| |
| clientCertLevel = RecGetRecordInt("proxy.config.ssl.client.certification_level").value_or(0); |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.server.cipher_suite")}; rec_str) { |
| cipherSuite = ats_stringdup(rec_str); |
| } |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.client.cipher_suite")}; rec_str) { |
| client_cipherSuite = ats_stringdup(rec_str); |
| } |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.server.TLSv1_3.cipher_suites")}; rec_str) { |
| server_tls13_cipher_suites = ats_stringdup(rec_str); |
| } |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.client.TLSv1_3.cipher_suites")}; rec_str) { |
| client_tls13_cipher_suites = ats_stringdup(rec_str); |
| } |
| |
| dhparamsFile = ats_stringdup(RecConfigReadConfigPath("proxy.config.ssl.server.dhparams_file")); |
| |
| int option = 0; |
| |
| client_tls_ver_min = RecGetRecordInt("proxy.config.ssl.client.version.min").value_or(0); |
| client_tls_ver_max = RecGetRecordInt("proxy.config.ssl.client.version.max").value_or(0); |
| if (client_tls_ver_min < 0 || client_tls_ver_max < 0) { |
| option = RecGetRecordInt("proxy.config.ssl.client.TLSv1").value_or(0); |
| if (!option) { |
| ssl_client_ctx_options |= SSL_OP_NO_TLSv1; |
| } else { |
| // This is disabled by default. It's used if it's enabled. |
| Warning("proxy.config.ssl.client.TLSv1 is deprecated. Use proxy.config.ssl.client.version.min and " |
| "proxy.config.ssl.client.version.max instead."); |
| } |
| |
| option = RecGetRecordInt("proxy.config.ssl.client.TLSv1_1").value_or(0); |
| if (!option) { |
| ssl_client_ctx_options |= SSL_OP_NO_TLSv1_1; |
| } else { |
| // This is disabled by default. It's used if it's enabled. |
| Warning("proxy.config.ssl.client.TLSv1_1 is deprecated. Use proxy.config.ssl.client.version.min and " |
| "proxy.config.ssl.client.version.max instead."); |
| } |
| |
| option = RecGetRecordInt("proxy.config.ssl.client.TLSv1_2").value_or(0); |
| if (!option) { |
| ssl_client_ctx_options |= SSL_OP_NO_TLSv1_2; |
| // This is enabled by default. It's used if it's disabled. |
| Warning("proxy.config.ssl.client.TLSv1_2 is deprecated. Use proxy.config.ssl.client.version.min and " |
| "proxy.config.ssl.client.version.max instead."); |
| } |
| |
| #ifdef SSL_OP_NO_TLSv1_3 |
| option = RecGetRecordInt("proxy.config.ssl.client.TLSv1_3.enabled").value_or(0); |
| if (!option) { |
| ssl_client_ctx_options |= SSL_OP_NO_TLSv1_3; |
| // This is enabled by default. It's used if it's disabled. |
| Warning("proxy.config.ssl.client.TLSv1_3.enabled is deprecated. Use proxy.config.ssl.client.version.min and " |
| "proxy.config.ssl.client.version.max instead."); |
| } |
| #endif |
| } |
| |
| server_tls_ver_min = RecGetRecordInt("proxy.config.ssl.server.version.min").value_or(0); |
| server_tls_ver_max = RecGetRecordInt("proxy.config.ssl.server.version.max").value_or(0); |
| if (server_tls_ver_min < 0 || server_tls_ver_max < 0) { |
| option = RecGetRecordInt("proxy.config.ssl.TLSv1").value_or(0); |
| if (!option) { |
| ssl_ctx_options |= SSL_OP_NO_TLSv1; |
| } else { |
| // This is disabled by default. It's used if it's enabled. |
| Warning("proxy.config.ssl.client.TLSv1 is deprecated. Use proxy.config.ssl.client.version.min and " |
| "proxy.config.ssl.client.version.max instead."); |
| } |
| |
| option = RecGetRecordInt("proxy.config.ssl.TLSv1_1").value_or(0); |
| if (!option) { |
| ssl_ctx_options |= SSL_OP_NO_TLSv1_1; |
| } else { |
| // This is disabled by default. It's used if it's enabled. |
| Warning("proxy.config.ssl.client.TLSv1_1 is deprecated. Use proxy.config.ssl.client.version.min and " |
| "proxy.config.ssl.client.version.max instead."); |
| } |
| |
| option = RecGetRecordInt("proxy.config.ssl.TLSv1_2").value_or(0); |
| if (!option) { |
| ssl_ctx_options |= SSL_OP_NO_TLSv1_2; |
| // This is enabled by default. It's used if it's disabled. |
| Warning("proxy.config.ssl.client.TLSv1_2 is deprecated. Use proxy.config.ssl.client.version.min and " |
| "proxy.config.ssl.client.version.max instead."); |
| } |
| |
| #ifdef SSL_OP_NO_TLSv1_3 |
| option = RecGetRecordInt("proxy.config.ssl.TLSv1_3.enabled").value_or(0); |
| if (!option) { |
| ssl_ctx_options |= SSL_OP_NO_TLSv1_3; |
| // This is enabled by default. It's used if it's disabled. |
| Warning("proxy.config.ssl.client.TLSv1_3.enabled is deprecated. Use proxy.config.ssl.client.version.min and " |
| "proxy.config.ssl.client.version.max instead."); |
| } |
| #endif |
| } |
| |
| // Read in the protocol string for ALPN to origin |
| char *clientALPNProtocols = nullptr; |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.client.alpn_protocols")}; rec_str) { |
| clientALPNProtocols = ats_stringdup(rec_str); |
| } |
| |
| if (clientALPNProtocols) { |
| this->alpn_protocols_array_size = MAX_ALPN_STRING; |
| convert_alpn_to_wire_format(clientALPNProtocols, this->alpn_protocols_array, this->alpn_protocols_array_size); |
| ats_free(clientALPNProtocols); |
| } |
| |
| #if defined(SSL_OP_SERVER_PREFERENCE) || defined(SSL_OP_CIPHER_SERVER_PREFERENCE) |
| option = RecGetRecordInt("proxy.config.ssl.server.honor_cipher_order").value_or(0); |
| if (option) { |
| // Prefer the newer, more accurately named flag when available. |
| #ifdef SSL_OP_SERVER_PREFERENCE |
| ssl_ctx_options |= SSL_OP_SERVER_PREFERENCE; |
| #else |
| ssl_ctx_options |= SSL_OP_CIPHER_SERVER_PREFERENCE; |
| #endif |
| } |
| #endif // defined(SSL_OP_SERVER_PREFERENCE) || defined(SSL_OP_CIPHER_SERVER_PREFERENCE) |
| |
| #ifdef SSL_OP_PRIORITIZE_CHACHA |
| option = RecGetRecordInt("proxy.config.ssl.server.prioritize_chacha").value_or(0); |
| 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 |
| |
| server_max_early_data = RecGetRecordInt("proxy.config.ssl.server.max_early_data").value_or(0); |
| server_allow_early_data_params = RecGetRecordInt("proxy.config.ssl.server.allow_early_data_params").value_or(0); |
| |
| // we keep it unless "server_max_early_data" is higher. |
| server_recv_max_early_data = std::max(server_max_early_data, TLSEarlyDataSupport::DEFAULT_MAX_EARLY_DATA_SIZE); |
| |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.server.cert_chain.filename")}; rec_str) { |
| serverCertChainFilename = ats_stringdup(rec_str); |
| } |
| |
| { |
| auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.server.cert.path")}; |
| auto serverCertRelativePath{ats_as_c_str(rec_str)}; |
| set_paths_helper(serverCertRelativePath, nullptr, &serverCertPathOnly, nullptr); |
| } |
| |
| configFilePath = ats_stringdup(RecConfigReadConfigPath("proxy.config.ssl.server.multicert.filename")); |
| configExitOnLoadError = RecGetRecordInt("proxy.config.ssl.server.multicert.exit_on_load_fail").value_or(0); |
| configPartialReload = RecGetRecordInt("proxy.config.ssl.server.multicert.partial_reload").value_or(0); |
| configLoadConcurrency = RecGetRecordInt("proxy.config.ssl.server.multicert.concurrency").value_or(1); |
| if (configLoadConcurrency == 0) { |
| configLoadConcurrency = std::clamp(static_cast<int>(std::thread::hardware_concurrency()), 1, 256); |
| } |
| |
| { |
| auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.server.private_key.path")}; |
| auto ssl_server_private_key_path{ats_as_c_str(rec_str)}; |
| set_paths_helper(ssl_server_private_key_path, nullptr, &serverKeyPathOnly, nullptr); |
| } |
| |
| { |
| auto pash_str{RecGetRecordStringAlloc("proxy.config.ssl.CA.cert.filename")}; |
| auto ssl_server_ca_cert_filename{ats_as_c_str(pash_str)}; |
| auto filename_str{RecGetRecordStringAlloc("proxy.config.ssl.CA.cert.path")}; |
| auto CACertRelativePath{ats_as_c_str(filename_str)}; |
| set_paths_helper(CACertRelativePath, ssl_server_ca_cert_filename, &serverCACertPath, &serverCACertFilename); |
| } |
| |
| // SSL session cache configurations |
| ssl_origin_session_cache = RecGetRecordInt("proxy.config.ssl.origin_session_cache.enabled").value_or(0); |
| ssl_origin_session_cache_size = RecGetRecordInt("proxy.config.ssl.origin_session_cache.size").value_or(0); |
| // The RECC_INT range on this record is applied when records.yaml is parsed, but |
| // initialize_record() registers a value from RecConfigOverrideFromEnvironment() without |
| // running the validity check -- so an environment override arrives here unbounded, and a |
| // negative one becomes an enormous size_t. The ceiling is the only thing keeping |
| // SSLSessionDup()'s serialization buffer inside the event thread stack, so enforce the |
| // range here rather than trusting the record layer to have done it. |
| { |
| int64_t configured = RecGetRecordInt("proxy.config.ssl.origin_session_cache.max_session_size") |
| .value_or(static_cast<int64_t>(SSLConfigParams::ORIGIN_SESSION_MAX_SIZE_DEFAULT)); |
| int64_t bounded = std::clamp(configured, static_cast<int64_t>(SSLConfigParams::ORIGIN_SESSION_MAX_SIZE_MIN), |
| static_cast<int64_t>(SSLConfigParams::ORIGIN_SESSION_MAX_SIZE_MAX)); |
| if (bounded != configured) { |
| Warning("proxy.config.ssl.origin_session_cache.max_session_size of %" PRId64 " is outside [%zu-%zu], using %" PRId64, |
| configured, SSLConfigParams::ORIGIN_SESSION_MAX_SIZE_MIN, SSLConfigParams::ORIGIN_SESSION_MAX_SIZE_MAX, bounded); |
| } |
| ssl_origin_session_max_size = static_cast<size_t>(bounded); |
| } |
| |
| SSLConfigParams::origin_session_cache = ssl_origin_session_cache; |
| SSLConfigParams::origin_session_cache_size = ssl_origin_session_cache_size; |
| SSLConfigParams::origin_session_max_size = ssl_origin_session_max_size; |
| |
| if (ssl_origin_session_cache == 1 && ssl_origin_session_cache_size > 0 && origin_sess_cache == nullptr) { |
| origin_sess_cache = new SSLOriginSessionCache(); |
| } |
| |
| // SSL record size |
| RecEstablishStaticConfigInt32(ssl_maxrecord, "proxy.config.ssl.max_record_size"); |
| |
| // SSL OCSP Stapling configurations |
| ssl_ocsp_enabled = RecGetRecordInt("proxy.config.ssl.ocsp.enabled").value_or(0); |
| RecEstablishStaticConfigInt32(ssl_ocsp_cache_timeout, "proxy.config.ssl.ocsp.cache_timeout"); |
| ssl_ocsp_request_mode = RecGetRecordInt("proxy.config.ssl.ocsp.request_mode").value_or(0); |
| RecEstablishStaticConfigInt32(ssl_ocsp_request_timeout, "proxy.config.ssl.ocsp.request_timeout"); |
| RecEstablishStaticConfigInt32(ssl_ocsp_update_period, "proxy.config.ssl.ocsp.update_period"); |
| { |
| auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.ocsp.response.path")}; |
| auto ssl_ocsp_response_path{ats_as_c_str(rec_str)}; |
| set_paths_helper(ssl_ocsp_response_path, nullptr, &ssl_ocsp_response_path_only, nullptr); |
| } |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.http.request_via_str")}; rec_str) { |
| ats_free(ssl_ocsp_user_agent); |
| ssl_ocsp_user_agent = ats_stringdup(rec_str); |
| } |
| |
| ssl_handshake_timeout_in = RecGetRecordInt("proxy.config.ssl.handshake_timeout_in").value_or(0); |
| |
| async_handshake_enabled = RecGetRecordInt("proxy.config.ssl.async.handshake.enabled").value_or(0); |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.engine.conf_file")}; rec_str) { |
| engine_conf_file = ats_stringdup(rec_str); |
| } |
| |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.server.groups_list")}; rec_str) { |
| server_groups_list = ats_stringdup(rec_str); |
| } |
| |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.server.cert_compression.algorithms")}; rec_str) { |
| server_cert_compression_algorithms = ats_stringdup(rec_str); |
| } |
| server_cert_compression_cache = RecGetRecordInt("proxy.config.ssl.server.cert_compression.cache").value_or(1); |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.client.cert_compression.algorithms")}; rec_str) { |
| client_cert_compression_algorithms = ats_stringdup(rec_str); |
| } |
| |
| // ++++++++++++++++++++++++ Client part ++++++++++++++++++++ |
| client_verify_depth = 7; |
| |
| { |
| auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.client.verify.server.policy")}; |
| auto verify_server_policy{ats_as_c_str(rec_str)}; |
| this->SetServerPolicy(verify_server_policy); |
| } |
| RecRegisterConfigUpdateCb("proxy.config.ssl.client.verify.server.policy", UpdateServerPolicy, nullptr); |
| |
| { |
| auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.client.verify.server.properties")}; |
| auto verify_server_properties{ats_as_c_str(rec_str)}; |
| this->SetServerPolicyProperties(verify_server_properties); |
| } |
| |
| RecRegisterConfigUpdateCb("proxy.config.ssl.client.verify.server.properties", UpdateServerPolicyProperties, nullptr); |
| |
| { |
| auto filename_str{RecGetRecordStringAlloc("proxy.config.ssl.client.cert.filename")}; |
| auto ssl_client_cert_filename{ats_as_c_str(filename_str)}; |
| auto path_str{RecGetRecordStringAlloc("proxy.config.ssl.client.cert.path")}; |
| auto ssl_client_cert_path{ats_as_c_str(path_str)}; |
| clientCertExitOnLoadError = RecGetRecordInt("proxy.config.ssl.client.cert.exit_on_load_fail").value_or(0); |
| set_paths_helper(ssl_client_cert_path, ssl_client_cert_filename, &clientCertPathOnly, &clientCertPath); |
| } |
| |
| { |
| auto filename_str{RecGetRecordStringAlloc("proxy.config.ssl.client.private_key.filename")}; |
| auto ssl_client_private_key_filename{ats_as_c_str(filename_str)}; |
| auto path_str{RecGetRecordStringAlloc("proxy.config.ssl.client.private_key.path")}; |
| auto ssl_client_private_key_path{ats_as_c_str(path_str)}; |
| set_paths_helper(ssl_client_private_key_path, ssl_client_private_key_filename, &clientKeyPathOnly, &clientKeyPath); |
| } |
| |
| { |
| auto filename_str{RecGetRecordStringAlloc("proxy.config.ssl.client.CA.cert.filename")}; |
| auto ssl_client_ca_cert_filename{ats_as_c_str(filename_str)}; |
| auto path_str{RecGetRecordStringAlloc("proxy.config.ssl.client.CA.cert.path")}; |
| auto clientCACertRelativePath{ats_as_c_str(path_str)}; |
| set_paths_helper(clientCACertRelativePath, ssl_client_ca_cert_filename, &clientCACertPath, &clientCACertFilename); |
| } |
| |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.client.groups_list")}; rec_str) { |
| client_groups_list = ats_stringdup(rec_str); |
| } |
| |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.keylog_file")}; rec_str) { |
| keylog_file = ats_stringdup(rec_str); |
| } |
| if (keylog_file == nullptr) { |
| TLSKeyLogger::disable_keylogging(); |
| } else { |
| TLSKeyLogger::enable_keylogging(keylog_file); |
| } |
| |
| ssl_ktls_enabled = RecGetRecordInt("proxy.config.ssl.ktls.enabled").value_or(0); |
| #ifndef SSL_OP_ENABLE_KTLS |
| if (ssl_ktls_enabled) { |
| CfgLoadLog(ctx, DL_Error, "kTLS configured but not supported by OpenSSL library"); |
| } |
| #endif |
| |
| ssl_allow_client_renegotiation = RecGetRecordInt("proxy.config.ssl.allow_client_renegotiation").value_or(0); |
| |
| ssl_misc_max_iobuffer_size_index = RecGetRecordInt("proxy.config.ssl.misc.io.max_buffer_index").value_or(0); |
| |
| // 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) { |
| return; |
| } |
| // Can't get SSL client context. |
| if (this->clientCertExitOnLoadError) { |
| Emergency("Can't initialize the SSL client, HTTPS in remap rules will not function"); |
| } else { |
| SSLError("Can't initialize the SSL client, HTTPS in remap rules will not function"); |
| ctx.log(DL_Warning, "Can't initialize the SSL client, HTTPS in remap rules will not function"); |
| } |
| } |
| |
| shared_SSL_CTX |
| SSLConfigParams::getClientSSL_CTX() const |
| { |
| return client_ctx; |
| } |
| |
| int |
| SSLConfig::get_config_index() |
| { |
| return config_index; |
| } |
| |
| int |
| SSLConfig::get_loading_config_index() |
| { |
| return config_index == 0 ? 1 : 0; |
| } |
| |
| void |
| SSLConfig::commit_config_id() |
| { |
| // Update the active config index |
| config_index = get_loading_config_index(); |
| |
| if (configids[get_loading_config_index()] != 0) { |
| // Start draining to free the old config |
| configProcessor.set(configids[get_loading_config_index()], nullptr); |
| } |
| } |
| |
| void |
| SSLConfig::startup() |
| { |
| reconfigure(); |
| } |
| |
| void |
| SSLConfig::reconfigure(ConfigContext ctx) |
| { |
| CfgLoadInProgress(ctx, "SSLConfig loading ..."); |
| |
| SSLConfigParams *params = new SSLConfigParams; |
| // start loading the next config |
| int loading_config_index = get_loading_config_index(); |
| |
| configids[loading_config_index] = configProcessor.set(configids[loading_config_index], params); |
| params->initialize(ctx); // re-read configuration |
| // Make the new config available for use. |
| commit_config_id(); |
| ctx.complete("SSLConfig reloaded"); |
| } |
| |
| SSLConfigParams * |
| SSLConfig::acquire() |
| { |
| return static_cast<SSLConfigParams *>(configProcessor.get(configids[get_config_index()])); |
| } |
| |
| SSLConfigParams * |
| SSLConfig::load_acquire() |
| { |
| return static_cast<SSLConfigParams *>(configProcessor.get(configids[get_loading_config_index()])); |
| } |
| |
| void |
| SSLConfig::release(SSLConfigParams *params) |
| { |
| configProcessor.release(configids[get_config_index()], params); |
| } |
| |
| void |
| SSLConfig::load_release(SSLConfigParams *params) |
| { |
| configProcessor.release(configids[get_loading_config_index()], params); |
| } |
| |
| bool |
| SSLCertificateConfig::startup() |
| { |
| // 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) { |
| Emergency("failed to load SSL certificate file, %s", params->configFilePath); |
| } |
| |
| return true; |
| } |
| |
| bool |
| SSLCertificateConfig::reconfigure(ConfigContext ctx) |
| { |
| bool retStatus = true; |
| SSLConfig::scoped_config params; |
| SSLCertLookup *lookup = new SSLCertLookup(); |
| |
| CfgLoadInProgress(ctx, "(ssl) %s loading ...", params->configFilePath); |
| |
| // 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; |
| Dbg(dbg_ctl_ssl_load, "delaying certificate reload by %d secs", secs); |
| ink_hrtime_sleep(HRTIME_SECONDS(secs)); |
| } |
| |
| auto errata = SSLMultiCertConfigLoader(params).load(lookup, configid == 0); |
| if (!lookup->is_valid || (errata.has_severity() && errata.severity() >= ERRATA_ERROR)) { |
| retStatus = false; |
| } |
| |
| // Use user_cert_count, not count(RSA|EC): load() guarantees a default context in ssl_storage |
| // (inserting a bare bootstrap when no wildcard cert loaded), so count() >= 1 even when |
| // all user certs fail, which would spuriously trigger a partial commit. |
| const bool hasAnyCert = lookup->user_cert_count > 0; |
| const bool partialCommit = !retStatus && params->configPartialReload && hasAnyCert; |
| const bool initialLoad = (configid == 0); |
| if (retStatus || initialLoad || partialCommit) { |
| configid = configProcessor.set(configid, lookup); |
| // Only flip retStatus on live reloads, startup must preserve false to honour configExitOnLoadError. |
| if (partialCommit && !initialLoad) { |
| CfgLoadLog(ctx, DL_Warning, "(ssl) %s: partial reload committed %u certificate entries; see diags.log for skipped entries", |
| params->configFilePath, lookup->user_cert_count); |
| retStatus = true; |
| } |
| } else { |
| delete lookup; |
| } |
| |
| if (!errata.empty()) { |
| ctx.log(errata); |
| } |
| |
| if (retStatus) { |
| CfgLoadComplete(ctx, "(ssl) %s finished loading", params->configFilePath); |
| } else { |
| CfgLoadFail(ctx, "(ssl) %s failed to load", params->configFilePath); |
| } |
| |
| return retStatus; |
| } |
| |
| SSLCertLookup * |
| SSLCertificateConfig::acquire() |
| { |
| return static_cast<SSLCertLookup *>(configProcessor.get(configid)); |
| } |
| |
| void |
| SSLCertificateConfig::release(SSLCertLookup *lookup) |
| { |
| if (lookup == nullptr) { |
| return; |
| } |
| configProcessor.release(configid, lookup); |
| } |
| |
| bool |
| SSLTicketParams::LoadTicket(bool &nochange, ConfigContext ctx) |
| { |
| cleanup(); |
| nochange = true; |
| |
| #if TS_HAS_TLS_SESSION_TICKET |
| 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; |
| elevate_setting = RecGetRecordInt("proxy.config.ssl.cert.load_elevated").value_or(0); |
| ElevateAccess elevate_access(elevate_setting ? ElevateAccess::FILE_PRIVILEGE : 0); // destructor will demote for us |
| |
| if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.server.ticket_key.filename")}; |
| rec_str && (ticket_key_filename = ats_stringdup(rec_str)) != 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) { |
| Dbg(dbg_ctl_ssl_load, "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) { |
| CfgLoadFail(ctx, "Could not load ticket key from %s", ticket_key_filename); |
| return false; |
| } |
| default_global_keyblock = keyblock; |
| load_time = time(nullptr); |
| |
| CfgLoadDbg(ctx, dbg_ctl_ssl_load, "ticket key reloaded from %s", ticket_key_filename); |
| #endif |
| return true; |
| } |
| |
| bool |
| SSLTicketParams::LoadTicketData(char *ticket_data, int ticket_data_len) |
| { |
| cleanup(); |
| #if TS_HAS_TLS_SESSION_TICKET |
| 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); |
| |
| if (default_global_keyblock == nullptr) { |
| return false; |
| } |
| #endif |
| return true; |
| } |
| |
| void |
| SSLTicketKeyConfig::startup() |
| { |
| config::ConfigRegistry::Get_Instance().register_record_config("ssl_ticket_key", // key |
| [](ConfigContext ctx) { // handler callback |
| CfgLoadLog(ctx, DL_Note, "SSL ticket key loading ..."); |
| if (SSLTicketKeyConfig::reconfigure(ctx)) { |
| ctx.complete("SSL ticket key reloaded"); |
| } else { |
| ctx.fail("Failed to reload SSL ticket key"); |
| } |
| }, |
| {"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(ConfigContext ctx) |
| { |
| SSLTicketParams *ticketKey = new SSLTicketParams(); |
| |
| if (ticketKey) { |
| bool nochange = false; |
| if (!ticketKey->LoadTicket(nochange, ctx)) { |
| 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) { |
| if (ticketKey->LoadTicketData(ticket_data, ticket_data_len) == false) { |
| delete ticketKey; |
| return false; |
| } |
| } |
| 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)); |
| } |
| |
| void |
| cleanup_bio(BIO *&biop) |
| { |
| // BIO_new_mem_buf sets BIO_FLAGS_MEM_RDONLY which prevents BIO_free from |
| // cleaning up internal BUF_MEM structures. Clear this flag so BIO_free |
| // properly releases them. BIO_NOCLOSE ensures the external data buffer |
| // (owned by the caller's std::string) is not freed. |
| BIO_clear_flags(biop, BIO_FLAGS_MEM_RDONLY); |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wunused-value" |
| BIO_set_close(biop, BIO_NOCLOSE); |
| #pragma GCC diagnostic pop |
| BIO_free(biop); |
| biop = nullptr; |
| } |
| |
| void |
| SSLConfigParams::updateCTX(const std::string &cert_secret_name) const |
| { |
| Dbg(dbg_ctl_ssl_config_updateCTX, "Update cert %s, %p", cert_secret_name.c_str(), this); |
| |
| // Instances of SSLConfigParams should access by one thread at a time only. secret_for_updateCTX is accessed |
| // atomically as a fail-safe. |
| // |
| char const *expected = nullptr; |
| if (!secret_for_updateCTX.compare_exchange_strong(expected, cert_secret_name.c_str())) { |
| if (dbg_ctl_ssl_config_updateCTX.on()) { |
| // As a fail-safe, handle it if secret_for_updateCTX doesn't or no longer points to a null-terminated string. |
| // |
| char const *s{expected}; |
| for (; *s && (static_cast<std::size_t>(s - expected) < cert_secret_name.size()); ++s) {} |
| DbgPrint(dbg_ctl_ssl_config_updateCTX, "Update cert, indirect recursive call caused by call for %.*s", |
| static_cast<int>(s - expected), expected); |
| } |
| return; |
| } |
| |
| // Clear the corresponding client CTXs. They will be lazy loaded later |
| Dbg(dbg_ctl_ssl_load, "Update cert %s", cert_secret_name.c_str()); |
| this->clearCTX(cert_secret_name); |
| |
| // Update the server cert |
| SSLMultiCertConfigLoader loader(this); |
| loader.update_ssl_ctx(cert_secret_name); |
| |
| secret_for_updateCTX = nullptr; |
| } |
| |
| void |
| SSLConfigParams::clearCTX(const std::string &client_cert) const |
| { |
| ink_mutex_acquire(&ctxMapLock); |
| for (auto ctx_map_iter = top_level_ctx_map.begin(); ctx_map_iter != top_level_ctx_map.end(); ++ctx_map_iter) { |
| auto ctx_iter = ctx_map_iter->second.find(client_cert); |
| if (ctx_iter != ctx_map_iter->second.end()) { |
| ctx_iter->second = nullptr; |
| Dbg(dbg_ctl_ssl_load, "Clear client cert %s %s", ctx_map_iter->first.c_str(), ctx_iter->first.c_str()); |
| } |
| } |
| ink_mutex_release(&ctxMapLock); |
| } |
| |
| shared_SSL_CTX |
| SSLConfigParams::getCTX(const char *client_cert, const char *key_file, const char *ca_bundle_file, const char *ca_bundle_path) const |
| { |
| return this->getCTX(std::string(client_cert ? client_cert : ""), std::string(key_file ? key_file : ""), ca_bundle_file, |
| ca_bundle_path); |
| } |
| |
| shared_SSL_CTX |
| SSLConfigParams::getCTX(const std::string &client_cert, const std::string &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; |
| ctx_key = client_cert; |
| swoc::bwprint(top_level_key, "{}:{}", ca_bundle_file, ca_bundle_path); |
| |
| Dbg(dbg_ctl_ssl_client_ctx, "Look for client cert \"%s\" \"%s\"", top_level_key.c_str(), ctx_key.c_str()); |
| |
| ink_mutex_acquire(&ctxMapLock); |
| 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; |
| } |
| } |
| ink_mutex_release(&ctxMapLock); |
| |
| BIO *biop = nullptr; |
| X509 *cert = nullptr; |
| EVP_PKEY *key = nullptr; |
| // Create context if doesn't exists |
| if (!client_ctx) { |
| Dbg(dbg_ctl_ssl_client_ctx, "Load new cert for %s %s", top_level_key.c_str(), ctx_key.c_str()); |
| client_ctx = shared_SSL_CTX(SSLInitClientContext(this), SSLReleaseContext); |
| |
| // Upon configuration, elevate file access to be able to read root-only |
| // certificates. The destructor will drop privilege. |
| uint32_t elevate_setting = 0; |
| elevate_setting = RecGetRecordInt("proxy.config.ssl.cert.load_elevated").value_or(0); |
| ElevateAccess elevate_access(elevate_setting ? ElevateAccess::FILE_PRIVILEGE : 0); |
| // Set public and private keys |
| if (!client_cert.empty()) { |
| std::string secret_data; |
| std::string secret_key_data; |
| |
| // Fetch the client_cert data |
| std::string completeSecretPath{Layout::get()->relative_to(this->clientCertPathOnly, client_cert)}; |
| std::string completeKeySecretPath{!key_file.empty() ? Layout::get()->relative_to(this->clientKeyPathOnly, key_file) : ""}; |
| secrets.getOrLoadSecret(completeSecretPath, completeKeySecretPath, secret_data, secret_key_data); |
| if (secret_data.empty()) { |
| SSLError("failed to access cert %s", client_cert.c_str()); |
| goto fail; |
| } |
| |
| biop = BIO_new_mem_buf(secret_data.data(), secret_data.size()); |
| |
| cert = PEM_read_bio_X509(biop, nullptr, nullptr, nullptr); |
| if (!cert) { |
| SSLError("failed to load cert %s", client_cert.c_str()); |
| goto fail; |
| } |
| if (!SSL_CTX_use_certificate(client_ctx.get(), cert)) { |
| SSLError("failed to attach client certificate from %s", client_cert.c_str()); |
| goto fail; |
| } |
| X509_free(cert); |
| |
| // Continue to fetch certs to associate intermediate certificates |
| cert = PEM_read_bio_X509(biop, nullptr, nullptr, nullptr); |
| while (cert) { |
| if (!SSL_CTX_add_extra_chain_cert(client_ctx.get(), cert)) { |
| SSLError("failed to attach client chain certificate from %s", client_cert.c_str()); |
| goto fail; |
| } |
| // Do not free cert becasue SSL_CTX_add_extra_chain_cert takes ownership of cert if it succeeds, unlike |
| // SSL_CTX_use_certificate. |
| cert = PEM_read_bio_X509(biop, nullptr, nullptr, nullptr); |
| } |
| |
| cleanup_bio(biop); |
| |
| const std::string &key_file_name = (secret_key_data.empty()) ? client_cert : key_file; |
| |
| // If there is a separate key file, fetch the new content |
| // otherwise, continue on with the cert data and hope for the best |
| if (!secret_key_data.empty()) { |
| biop = BIO_new_mem_buf(secret_key_data.data(), secret_key_data.size()); |
| } else { |
| biop = BIO_new_mem_buf(secret_data.data(), secret_data.size()); |
| } |
| |
| pem_password_cb *password_cb = SSL_CTX_get_default_passwd_cb(client_ctx.get()); |
| void *u = SSL_CTX_get_default_passwd_cb_userdata(client_ctx.get()); |
| key = PEM_read_bio_PrivateKey(biop, nullptr, password_cb, u); |
| if (!key) { |
| SSLError("failed to load client private key file from %s", key_file_name.c_str()); |
| goto fail; |
| } |
| if (!SSL_CTX_use_PrivateKey(client_ctx.get(), key)) { |
| SSLError("failed to use client private key file from %s", key_file_name.c_str()); |
| goto fail; |
| } |
| EVP_PKEY_free(key); |
| key = nullptr; |
| |
| if (!SSL_CTX_check_private_key(client_ctx.get())) { |
| SSLError("client private key (%s) does not match the certificate public key (%s)", key_file_name.c_str(), |
| client_cert.c_str()); |
| goto fail; |
| } |
| cleanup_bio(biop); |
| } |
| |
| // 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: |
| if (biop) { |
| cleanup_bio(biop); |
| } |
| if (cert) { |
| X509_free(cert); |
| } |
| if (key) { |
| EVP_PKEY_free(key); |
| } |
| return nullptr; |
| } |
| |
| void |
| SSLConfigParams::cleanupCTXTable() |
| { |
| ink_mutex_acquire(&ctxMapLock); |
| top_level_ctx_map.clear(); |
| ink_mutex_release(&ctxMapLock); |
| } |