This is an automated email from the ASF dual-hosted git repository. masaori pushed a commit to branch quic-latest in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/quic-latest by this push: new 5e3ca19 QUIC: Load multiple certs 5e3ca19 is described below commit 5e3ca198bc73f493c1df3b97aae6ecb058adf06f Author: Masaori Koshiba <masa...@apache.org> AuthorDate: Wed Feb 20 12:16:14 2019 +0900 QUIC: Load multiple certs --- iocore/net/QUICNetProcessor.cc | 1 + iocore/net/QUICNetVConnection.cc | 4 +- iocore/net/SSLUtils.cc | 75 ------------ iocore/net/quic/QUICConfig.cc | 255 ++++++++++++++++++++++++++++++++++----- iocore/net/quic/QUICConfig.h | 32 ++++- iocore/net/quic/QUICGlobals.cc | 63 +++++++++- iocore/net/quic/QUICGlobals.h | 2 + 7 files changed, 321 insertions(+), 111 deletions(-) diff --git a/iocore/net/QUICNetProcessor.cc b/iocore/net/QUICNetProcessor.cc index 8c63668..5a2e373 100644 --- a/iocore/net/QUICNetProcessor.cc +++ b/iocore/net/QUICNetProcessor.cc @@ -63,6 +63,7 @@ QUICNetProcessor::start(int, size_t stacksize) // This initialization order matters ... // QUICInitializeLibrary(); QUICConfig::startup(); + QUICCertConfig::startup(); #ifdef TLS1_3_VERSION_DRAFT_TXT // FIXME: remove this when TLS1_3_VERSION_DRAFT_TXT is removed diff --git a/iocore/net/QUICNetVConnection.cc b/iocore/net/QUICNetVConnection.cc index 410b235..e8c7b53 100644 --- a/iocore/net/QUICNetVConnection.cc +++ b/iocore/net/QUICNetVConnection.cc @@ -240,10 +240,12 @@ QUICNetVConnection::start() this->_five_tuple.update(this->local_addr, this->remote_addr, SOCK_DGRAM); // Version 0x00000001 uses stream 0 for cryptographic handshake with TLS 1.3, but newer version may not if (this->direction() == NET_VCONNECTION_IN) { + QUICCertConfig::scoped_config server_cert; + this->_pp_key_info.set_context(QUICPacketProtectionKeyInfo::Context::SERVER); this->_ack_frame_manager.set_ack_delay_exponent(params->ack_delay_exponent_in()); this->_reset_token = QUICStatelessResetToken(this->_quic_connection_id, params->instance_id()); - this->_hs_protocol = this->_setup_handshake_protocol(params->server_ssl_ctx()); + this->_hs_protocol = this->_setup_handshake_protocol(server_cert->ssl_default); this->_handshake_handler = new QUICHandshake(this, this->_hs_protocol, this->_reset_token, params->stateless_retry()); this->_ack_frame_manager.set_max_ack_delay(params->max_ack_delay_in()); this->_schedule_ack_manager_periodic(params->max_ack_delay_in()); diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc index 7c7c7fe..c83b314 100644 --- a/iocore/net/SSLUtils.cc +++ b/iocore/net/SSLUtils.cc @@ -1557,81 +1557,6 @@ ssl_extract_certificate(const matcher_line *line_info, SSLMultiCertConfigParams return true; } -// TODO: remove this function and setup SSL_CTX for QUIC somehow -bool -SSLParseCertificateConfiguration(const SSLConfigParams *params, SSL_CTX *ssl_ctx) -{ - char *tok_state = nullptr; - char *line = nullptr; - ats_scoped_str file_buf; - unsigned line_num = 0; - matcher_line line_info; - - const matcher_tags sslCertTags = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, false}; - - Note("loading SSL certificate configuration from %s", params->configFilePath); - - if (params->configFilePath) { - file_buf = readIntoBuffer(params->configFilePath, __func__, nullptr); - } - - if (!file_buf) { - Error("failed to read SSL certificate configuration from %s", params->configFilePath); - return false; - } - - // Optionally elevate/allow file access to read root-only - // certificates. The destructor will drop privilege for us. - uint32_t elevate_setting = 0; - REC_ReadConfigInteger(elevate_setting, "proxy.config.ssl.cert.load_elevated"); - ElevateAccess elevate_access(elevate_setting ? ElevateAccess::FILE_PRIVILEGE : 0); - - line = tokLine(file_buf, &tok_state); - while (line != nullptr) { - line_num++; - - // Skip all blank spaces at beginning of line. - while (*line && isspace(*line)) { - line++; - } - - if (*line != '\0' && *line != '#') { - SSLMultiCertConfigParams sslMultiCertSettings; - const char *errPtr; - - errPtr = parseConfigLine(line, &line_info, &sslCertTags); - - if (errPtr != nullptr) { - RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "%s: discarding %s entry at line %d: %s", __func__, params->configFilePath, - line_num, errPtr); - } else { - if (ssl_extract_certificate(&line_info, sslMultiCertSettings)) { - // There must be a certificate specified unless the tunnel action is set - if (sslMultiCertSettings.cert || sslMultiCertSettings.opt != SSLCertContext::OPT_TUNNEL) { - if (SSL_CTX_use_PrivateKey_file(ssl_ctx, sslMultiCertSettings.key.get(), SSL_FILETYPE_PEM) != 1) { - Error("Couldn't load private_key: %s", sslMultiCertSettings.key.get()); - return false; - } - - if (SSL_CTX_use_certificate_chain_file(ssl_ctx, sslMultiCertSettings.cert.get()) != 1) { - Error("Couldn't load cert: %s", sslMultiCertSettings.cert.get()); - return false; - } - - return true; - - } else { - Warning("No ssl_cert_name specified and no tunnel action set"); - } - } - } - } - - line = tokLine(nullptr, &tok_state); - } - return true; -} - bool SSLMultiCertConfigLoader::load(SSLCertLookup *lookup) { diff --git a/iocore/net/quic/QUICConfig.cc b/iocore/net/quic/QUICConfig.cc index 8ec9e4f..139df1a 100644 --- a/iocore/net/quic/QUICConfig.cc +++ b/iocore/net/quic/QUICConfig.cc @@ -28,10 +28,13 @@ #include <records/I_RecHttp.h> #include "P_SSLConfig.h" +#include "P_OCSPStapling.h" #include "QUICGlobals.h" #include "QUICTransportParameters.h" +#define QUICConfDebug(fmt, ...) Debug("quic_conf", fmt, ##__VA_ARGS__) + // OpenSSL protocol-lists format (vector of 8-bit length-prefixed, byte strings) // https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_protos.html // Should be integrate with IP_PROTO_TAG_HTTP_QUIC in ts/ink_inet.h ? @@ -40,6 +43,7 @@ static constexpr std::string_view QUIC_ALPN_PROTO_LIST("\5hq-18"sv); int QUICConfig::_config_id = 0; int QUICConfigParams::_connection_table_size = 65521; +int QUICCertConfig::_config_id = 0; static SSL_CTX * quic_new_ssl_ctx() @@ -71,29 +75,6 @@ quic_new_ssl_ctx() } static SSL_CTX * -quic_init_server_ssl_ctx(const QUICConfigParams *params) -{ - SSL_CTX *ssl_ctx = quic_new_ssl_ctx(); - - SSLConfig::scoped_config ssl_params; - SSLParseCertificateConfiguration(ssl_params, ssl_ctx); - - if (SSL_CTX_check_private_key(ssl_ctx) != 1) { - Error("check private key failed"); - } - - SSL_CTX_set_alpn_select_cb(ssl_ctx, QUIC::ssl_select_next_protocol, nullptr); - - if (params->server_supported_groups() != nullptr) { - if (SSL_CTX_set1_groups_list(ssl_ctx, params->server_supported_groups()) != 1) { - Error("SSL_CTX_set1_groups_list failed"); - } - } - - return ssl_ctx; -} - -static SSL_CTX * quic_init_client_ssl_ctx(const QUICConfigParams *params) { SSL_CTX *ssl_ctx = quic_new_ssl_ctx(); @@ -125,7 +106,6 @@ QUICConfigParams::~QUICConfigParams() this->_server_supported_groups = (char *)ats_free_null(this->_server_supported_groups); this->_client_supported_groups = (char *)ats_free_null(this->_client_supported_groups); - SSL_CTX_free(this->_server_ssl_ctx); SSL_CTX_free(this->_client_ssl_ctx); }; @@ -194,7 +174,6 @@ QUICConfigParams::initialize() REC_EstablishStaticConfigInt32U(this->_cc_persistent_congestion_threshold, "proxy.config.quic.congestion_control.persistent_congestion_threshold"); - this->_server_ssl_ctx = quic_init_server_ssl_ctx(this); this->_client_ssl_ctx = quic_init_client_ssl_ctx(this); } @@ -375,12 +354,6 @@ QUICConfigParams::client_supported_groups() const } SSL_CTX * -QUICConfigParams::server_ssl_ctx() const -{ - return this->_server_ssl_ctx; -} - -SSL_CTX * QUICConfigParams::client_ssl_ctx() const { return this->_client_ssl_ctx; @@ -489,3 +462,223 @@ QUICConfig::release(QUICConfigParams *params) { configProcessor.release(_config_id, params); } + +// +// QUICCertConfig +// +void +QUICCertConfig::startup() +{ + reconfigure(); +} + +void +QUICCertConfig::reconfigure() +{ + SSLConfig::scoped_config params; + SSLCertLookup *lookup = new SSLCertLookup(); + + QUICMultiCertConfigLoader loader(params); + loader.load(lookup); + + _config_id = configProcessor.set(_config_id, lookup); +} + +SSLCertLookup * +QUICCertConfig::acquire() +{ + return static_cast<SSLCertLookup *>(configProcessor.get(_config_id)); +} + +void +QUICCertConfig::release(SSLCertLookup *lookup) +{ + configProcessor.release(_config_id, lookup); +} + +// +// QUICMultiCertConfigLoader +// +SSL_CTX * +QUICMultiCertConfigLoader::default_server_ssl_ctx() +{ + return quic_new_ssl_ctx(); +} + +SSL_CTX * +QUICMultiCertConfigLoader::init_server_ssl_ctx(std::vector<X509 *> &cert_list, const SSLMultiCertConfigParams *multi_cert_params) +{ + const SSLConfigParams *params = this->_params; + + SSL_CTX *ctx = this->default_server_ssl_ctx(); + + if (multi_cert_params) { + if (multi_cert_params->dialog) { + // TODO: dialog support + } + + if (multi_cert_params->cert) { + if (!SSLMultiCertConfigLoader::load_certs(ctx, cert_list, params, multi_cert_params)) { + goto fail; + } + } + + // SSL_CTX_load_verify_locations() builds the cert chain from the + // serverCACertFilename if that is not nullptr. Otherwise, it uses the hashed + // symlinks in serverCACertPath. + // + // if ssl_ca_name is NOT configured for this cert in ssl_multicert.config + // AND + // if proxy.config.ssl.CA.cert.filename and proxy.config.ssl.CA.cert.path + // are configured + // pass that file as the chain (include all certs in that file) + // else if proxy.config.ssl.CA.cert.path is configured (and + // proxy.config.ssl.CA.cert.filename is nullptr) + // use the hashed symlinks in that directory to build the chain + if (!multi_cert_params->ca && params->serverCACertPath != nullptr) { + if ((!SSL_CTX_load_verify_locations(ctx, params->serverCACertFilename, params->serverCACertPath)) || + (!SSL_CTX_set_default_verify_paths(ctx))) { + Error("invalid CA Certificate file or CA Certificate path"); + goto fail; + } + } + } + + if (params->clientCertLevel != 0) { + // TODO: client cert support + } + + if (!SSLMultiCertConfigLoader::set_session_id_context(ctx, params, multi_cert_params)) { + goto fail; + } + + if (params->server_tls13_cipher_suites != nullptr) { + if (!SSL_CTX_set_ciphersuites(ctx, params->server_tls13_cipher_suites)) { + Error("invalid tls server cipher suites in records.config"); + goto fail; + } + } + + if (params->server_groups_list != nullptr) { + if (!SSL_CTX_set1_groups_list(ctx, params->server_groups_list)) { + Error("invalid groups list for server in records.config"); + goto fail; + } + } + + // SSL_CTX_set_info_callback(ctx, ssl_callback_info); + + SSL_CTX_set_alpn_select_cb(ctx, QUIC::ssl_select_next_protocol, nullptr); + + if (SSLConfigParams::ssl_ocsp_enabled) { + QUICConfDebug("SSL OCSP Stapling is enabled"); + SSL_CTX_set_tlsext_status_cb(ctx, ssl_callback_ocsp_stapling); + const char *cert_name = multi_cert_params ? multi_cert_params->cert.get() : nullptr; + + for (auto cert : cert_list) { + if (!ssl_stapling_init_cert(ctx, cert, cert_name)) { + Warning("failed to configure SSL_CTX for OCSP Stapling info for certificate at %s", cert_name); + } + } + } else { + QUICConfDebug("SSL OCSP Stapling is disabled"); + } + + if (SSLConfigParams::init_ssl_ctx_cb) { + SSLConfigParams::init_ssl_ctx_cb(ctx, true); + } + + return ctx; + +fail: + SSLReleaseContext(ctx); + for (auto cert : cert_list) { + X509_free(cert); + } + + return nullptr; +} + +SSL_CTX * +QUICMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const SSLMultiCertConfigParams *multi_cert_params) +{ + std::vector<X509 *> cert_list; + SSL_CTX *ctx = this->init_server_ssl_ctx(cert_list, multi_cert_params); + ssl_ticket_key_block *keyblock = nullptr; + bool inserted = false; + + if (!ctx || !multi_cert_params) { + lookup->is_valid = false; + return nullptr; + } + + const char *certname = multi_cert_params->cert.get(); + for (auto cert : cert_list) { + if (0 > SSLMultiCertConfigLoader::check_server_cert_now(cert, certname)) { + /* At this point, we know cert is bad, and we've already printed a + descriptive reason as to why cert is bad to the log file */ + QUICConfDebug("Marking certificate as NOT VALID: %s", certname); + lookup->is_valid = false; + } + } + + // Index this certificate by the specified IP(v6) address. If the address is "*", make it the default context. + if (multi_cert_params->addr) { + if (strcmp(multi_cert_params->addr, "*") == 0) { + if (lookup->insert(multi_cert_params->addr, SSLCertContext(ctx, multi_cert_params->opt, keyblock)) >= 0) { + inserted = true; + lookup->ssl_default = ctx; + this->_set_handshake_callbacks(ctx); + } + } else { + IpEndpoint ep; + + if (ats_ip_pton(multi_cert_params->addr, &ep) == 0) { + QUICConfDebug("mapping '%s' to certificate %s", (const char *)multi_cert_params->addr, (const char *)certname); + if (lookup->insert(ep, SSLCertContext(ctx, multi_cert_params->opt, keyblock)) >= 0) { + inserted = true; + } + } else { + Error("'%s' is not a valid IPv4 or IPv6 address", (const char *)multi_cert_params->addr); + lookup->is_valid = false; + } + } + } + + // Insert additional mappings. Note that this maps multiple keys to the same value, so when + // this code is updated to reconfigure the SSL certificates, it will need some sort of + // refcounting or alternate way of avoiding double frees. + QUICConfDebug("importing SNI names from %s", (const char *)certname); + for (auto cert : cert_list) { + if (SSLMultiCertConfigLoader::index_certificate(lookup, SSLCertContext(ctx, multi_cert_params->opt), cert, certname)) { + inserted = true; + } + } + + if (inserted) { + if (SSLConfigParams::init_ssl_ctx_cb) { + SSLConfigParams::init_ssl_ctx_cb(ctx, true); + } + } + + if (!inserted) { + SSLReleaseContext(ctx); + ctx = nullptr; + } + + for (auto &i : cert_list) { + X509_free(i); + } + + return ctx; +} + +void +QUICMultiCertConfigLoader::_set_handshake_callbacks(SSL_CTX *ssl_ctx) +{ + SSL_CTX_set_cert_cb(ssl_ctx, QUIC::ssl_cert_cb, nullptr); + SSL_CTX_set_tlsext_servername_callback(ssl_ctx, QUIC::ssl_sni_cb); + + // Set client hello callback if needed + // SSL_CTX_set_client_hello_cb(ctx, QUIC::ssl_client_hello_cb, nullptr); +} diff --git a/iocore/net/quic/QUICConfig.h b/iocore/net/quic/QUICConfig.h index f40e012..4ab237a 100644 --- a/iocore/net/quic/QUICConfig.h +++ b/iocore/net/quic/QUICConfig.h @@ -26,6 +26,8 @@ #include <openssl/ssl.h> #include "ProxyConfig.h" +#include "P_SSLCertLookup.h" +#include "P_SSLUtils.h" class QUICConfigParams : public ConfigInfo { @@ -45,7 +47,6 @@ public: const char *client_supported_groups() const; const char *session_file() const; - SSL_CTX *server_ssl_ctx() const; SSL_CTX *client_ssl_ctx() const; // Transport Parameters @@ -101,8 +102,6 @@ private: char *_client_supported_groups = nullptr; char *_session_file = nullptr; - // TODO: integrate with SSLCertLookup or SNIConfigParams - SSL_CTX *_server_ssl_ctx = nullptr; SSL_CTX *_client_ssl_ctx = nullptr; // Transport Parameters @@ -156,3 +155,30 @@ public: private: static int _config_id; }; + +class QUICCertConfig +{ +public: + static void startup(); + static void reconfigure(); + static SSLCertLookup *acquire(); + static void release(SSLCertLookup *lookup); + + using scoped_config = ConfigProcessor::scoped_config<QUICCertConfig, SSLCertLookup>; + +private: + static int _config_id; +}; + +class QUICMultiCertConfigLoader : public SSLMultiCertConfigLoader +{ +public: + QUICMultiCertConfigLoader(const SSLConfigParams *p) : SSLMultiCertConfigLoader(p) {} + + virtual SSL_CTX *default_server_ssl_ctx() override; + virtual SSL_CTX *init_server_ssl_ctx(std::vector<X509 *> &cert_list, const SSLMultiCertConfigParams *multi_cert_params) override; + +private: + virtual SSL_CTX *_store_ssl_ctx(SSLCertLookup *lookup, const SSLMultiCertConfigParams *multi_cert_params) override; + virtual void _set_handshake_callbacks(SSL_CTX *ssl_ctx) override; +}; diff --git a/iocore/net/quic/QUICGlobals.cc b/iocore/net/quic/QUICGlobals.cc index 0520f3e..611bddb 100644 --- a/iocore/net/quic/QUICGlobals.cc +++ b/iocore/net/quic/QUICGlobals.cc @@ -26,10 +26,14 @@ #include <cstring> #include "P_SSLNextProtocolSet.h" + #include "QUICStats.h" #include "QUICConfig.h" #include "QUICConnection.h" +#define QUICGlobalDebug(fmt, ...) Debug("quic_global", fmt, ##__VA_ARGS__) +#define QUICGlobalQCDebug(qc, fmt, ...) Debug("quic_global", "[%s] " fmt, qc->cids().data(), ##__VA_ARGS__) + RecRawStatBlock *quic_rsb; int QUIC::ssl_quic_qc_index = -1; @@ -67,7 +71,7 @@ QUIC::ssl_client_new_session(SSL *ssl, SSL_SESSION *session) QUICConfig::scoped_config params; auto file = BIO_new_file(params->session_file(), "w"); if (file == nullptr) { - Debug("quic_global", "Could not write TLS session in %s", params->session_file()); + QUICGlobalDebug("Could not write TLS session in %s", params->session_file()); return 0; } @@ -76,6 +80,63 @@ QUIC::ssl_client_new_session(SSL *ssl, SSL_SESSION *session) return 0; } +int +QUIC::ssl_cert_cb(SSL *ssl, void * /*arg*/) +{ + SSL_CTX *ctx = nullptr; + SSLCertContext *cc = nullptr; + QUICCertConfig::scoped_config lookup; + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + QUICConnection *qc = static_cast<QUICConnection *>(SSL_get_ex_data(ssl, QUIC::ssl_quic_qc_index)); + + if (servername == nullptr) { + servername = ""; + } + QUICGlobalQCDebug(qc, "SNI=%s", servername); + + // The incoming SSL_CTX is either the one mapped from the inbound IP address or the default one. If we + // don't find a name-based match at this point, we *do not* want to mess with the context because we've + // already made a best effort to find the best match. + if (likely(servername)) { + cc = lookup->find((char *)servername); + if (cc && cc->ctx) { + ctx = cc->ctx; + } + } + + // If there's no match on the server name, try to match on the peer address. + if (ctx == nullptr) { + QUICFiveTuple five_tuple = qc->five_tuple(); + IpEndpoint ip = five_tuple.destination(); + cc = lookup->find(ip); + + if (cc && cc->ctx) { + ctx = cc->ctx; + } + } + + bool found = true; + if (ctx != nullptr) { + SSL_set_SSL_CTX(ssl, ctx); + } else { + found = false; + } + + ctx = SSL_get_SSL_CTX(ssl); + + QUICGlobalQCDebug(qc, "%s SSL_CTX %p for requested name '%s'", found ? "found" : "using", ctx, servername); + + return 1; +} + +int +QUIC::ssl_sni_cb(SSL *ssl, int * /*ad*/, void * /*arg*/) +{ + // XXX: add SNIConfig support ? + // XXX: add TRANSPORT_BLIND_TUNNEL support ? + return 1; +} + void QUIC::_register_stats() { diff --git a/iocore/net/quic/QUICGlobals.h b/iocore/net/quic/QUICGlobals.h index 4da21d1..cd4ac54 100644 --- a/iocore/net/quic/QUICGlobals.h +++ b/iocore/net/quic/QUICGlobals.h @@ -34,6 +34,8 @@ public: static int ssl_select_next_protocol(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned inlen, void *); static int ssl_client_new_session(SSL *ssl, SSL_SESSION *session); + static int ssl_cert_cb(SSL *ssl, void *arg); + static int ssl_sni_cb(SSL *ssl, int *ad, void *arg); static int ssl_quic_qc_index; static int ssl_quic_tls_index;