From feb2a8a728f7e8cfdd87db713b2c4d918743c041 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Fri, 10 Jun 2022 14:38:50 +0900 Subject: [PATCH 1/3] Enables switching SSL certificates on QUIC with QUICHE This enables using certificates loaded by QUICMultiCertConfigLoader (not only the default cert but also certs for specific servernames and IP addresses). This also makes other SNI stuff work (SNIAction, cssn log field) on QUIC. --- iocore/net/P_QUICNetVConnection_quiche.h | 26 +++++- iocore/net/QUICMultiCertConfigLoader.cc | 71 --------------- iocore/net/QUICMultiCertConfigLoader.h | 4 - iocore/net/QUICNetProcessor_quiche.cc | 8 -- iocore/net/QUICNetVConnection_quiche.cc | 108 ++++++++++++++++++++++- iocore/net/QUICPacketHandler_quiche.cc | 18 ++-- 6 files changed, 140 insertions(+), 95 deletions(-) diff --git a/iocore/net/P_QUICNetVConnection_quiche.h b/iocore/net/P_QUICNetVConnection_quiche.h index 400d363a93d..16511fbf383 100644 --- a/iocore/net/P_QUICNetVConnection_quiche.h +++ b/iocore/net/P_QUICNetVConnection_quiche.h @@ -39,6 +39,9 @@ #include "P_UDPNet.h" #include "P_ALPNSupport.h" #include "TLSBasicSupport.h" +#include "TLSSessionResumptionSupport.h" +#include "TLSSNISupport.h" +#include "TLSCertSwitchSupport.h" #include "tscore/ink_apidefs.h" #include "tscore/List.h" @@ -57,6 +60,9 @@ class QUICNetVConnection : public UnixNetVConnection, public QUICConnection, public RefCountObj, public ALPNSupport, + public TLSSNISupport, + public TLSSessionResumptionSupport, + public TLSCertSwitchSupport, public TLSBasicSupport { using super = UnixNetVConnection; ///< Parent type. @@ -66,7 +72,7 @@ class QUICNetVConnection : public UnixNetVConnection, ~QUICNetVConnection(); void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *, QUICPacketHandler *); void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid, - QUICConnectionId retry_cid, UDPConnection *, quiche_conn *, QUICPacketHandler *, QUICConnectionTable *ctable); + QUICConnectionId retry_cid, UDPConnection *, quiche_conn *, QUICPacketHandler *, QUICConnectionTable *ctable, SSL *); // Event handlers int acceptEvent(int event, Event *e); @@ -88,6 +94,7 @@ class QUICNetVConnection : public UnixNetVConnection, VIO *do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner = false) override; int connectUp(EThread *t, int fd) override; int64_t load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs) override; + bool getSSLHandShakeComplete() const override; // NetEvent virtual void net_read_io(NetHandler *nh, EThread *lthread) override; @@ -95,6 +102,8 @@ class QUICNetVConnection : public UnixNetVConnection, // NetVConnection int populate_protocol(std::string_view *results, int n) const override; const char *protocol_contains(std::string_view tag) const override; + const char *get_server_name() const override; + bool support_sni() const override; // QUICConnection QUICStreamManager *stream_manager() override; @@ -144,7 +153,19 @@ class QUICNetVConnection : public UnixNetVConnection, SSL *_get_ssl_object() const override; ssl_curve_id _get_tls_curve() const override; + // TLSSNISupport + void _fire_ssl_servername_event() override; + + // TLSSessionResumptionSupport + const IpEndpoint &_getLocalEndpoint() override; + + // TLSCertSwitchSupport + bool _isTryingRenegotiation() const override; + shared_SSL_CTX _lookupContextByName(const std::string &servername, SSLCertContextType ctxType) override; + shared_SSL_CTX _lookupContextByIP() override; + private: + SSL *_ssl; QUICConfig::scoped_config _quic_config; QUICConnectionId _peer_quic_connection_id; // dst cid in local @@ -159,6 +180,9 @@ class QUICNetVConnection : public UnixNetVConnection, quiche_conn *_quiche_con = nullptr; QUICConnectionTable *_ctable = nullptr; + void _bindSSLObject(); + void _unbindSSLObject(); + void _schedule_packet_write_ready(bool delay = false); void _unschedule_packet_write_ready(); void _close_packet_write_ready(Event *data); diff --git a/iocore/net/QUICMultiCertConfigLoader.cc b/iocore/net/QUICMultiCertConfigLoader.cc index f8c52fd6521..653aec4d1ce 100644 --- a/iocore/net/QUICMultiCertConfigLoader.cc +++ b/iocore/net/QUICMultiCertConfigLoader.cc @@ -109,77 +109,6 @@ QUICMultiCertConfigLoader::_set_npn_callback(SSL_CTX *ctx) return true; } -void -QUICMultiCertConfigLoader::_set_handshake_callbacks(SSL_CTX *ssl_ctx) -{ - SSL_CTX_set_cert_cb(ssl_ctx, QUICMultiCertConfigLoader::ssl_cert_cb, nullptr); - SSL_CTX_set_tlsext_servername_callback(ssl_ctx, QUICMultiCertConfigLoader::ssl_sni_cb); - - // Set client hello callback if needed - // SSL_CTX_set_client_hello_cb(ctx, QUIC::ssl_client_hello_cb, nullptr); -} - -int -QUICMultiCertConfigLoader::ssl_sni_cb(SSL *ssl, int * /*ad*/, void * /*arg*/) -{ - // XXX: add SNIConfig support ? - // XXX: add TRANSPORT_BLIND_TUNNEL support ? - return 1; -} - -int -QUICMultiCertConfigLoader::ssl_cert_cb(SSL *ssl, void * /*arg*/) -{ - shared_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(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(const_cast(servername)); - if (cc && cc->getCtx()) { - ctx = cc->getCtx(); - } - } - - // 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->getCtx()) { - ctx = cc->getCtx(); - } - } - - bool found = true; - if (ctx != nullptr) { - SSL_set_SSL_CTX(ssl, ctx.get()); - } else { - found = false; - } - - SSL_CTX *verify_ctx = nullptr; - verify_ctx = SSL_get_SSL_CTX(ssl); - QUICGlobalQCDebug(qc, "%s SSL_CTX %p for requested name '%s'", found ? "found" : "using", verify_ctx, servername); - - if (verify_ctx == nullptr) { - return 0; - } - - return 1; -} - const char * QUICMultiCertConfigLoader::_debug_tag() const { diff --git a/iocore/net/QUICMultiCertConfigLoader.h b/iocore/net/QUICMultiCertConfigLoader.h index 1f97ee31aae..6ee7df7bc03 100644 --- a/iocore/net/QUICMultiCertConfigLoader.h +++ b/iocore/net/QUICMultiCertConfigLoader.h @@ -49,12 +49,8 @@ class QUICMultiCertConfigLoader : public SSLMultiCertConfigLoader private: const char *_debug_tag() const override; - virtual void _set_handshake_callbacks(SSL_CTX *ssl_ctx) override; virtual bool _setup_session_cache(SSL_CTX *ctx) override; virtual bool _set_cipher_suites_for_legacy_versions(SSL_CTX *ctx) override; virtual bool _set_info_callback(SSL_CTX *ctx) override; virtual bool _set_npn_callback(SSL_CTX *ctx) override; - - static int ssl_cert_cb(SSL *ssl, void *arg); - static int ssl_sni_cb(SSL *ssl, int *ad, void *arg); }; diff --git a/iocore/net/QUICNetProcessor_quiche.cc b/iocore/net/QUICNetProcessor_quiche.cc index b6882a7c706..2f680582ca9 100644 --- a/iocore/net/QUICNetProcessor_quiche.cc +++ b/iocore/net/QUICNetProcessor_quiche.cc @@ -75,19 +75,11 @@ QUICNetProcessor::start(int, size_t stacksize) // QUICInitializeLibrary(); QUICConfig::startup(); QUICCertConfig::startup(); - QUICCertConfig::scoped_config certs; QUICConfig::scoped_config params; - SSLCertContext *context = certs->get(0); quiche_enable_debug_logging(debug_log, NULL); this->_quiche_config = quiche_config_new(QUICHE_PROTOCOL_VERSION); quiche_config_set_application_protos(this->_quiche_config, (uint8_t *)"\02h3\x05h3-29\x05hq-29\x05h3-27\x05hq-27", 27); - if (context->userconfig->cert != nullptr) { - quiche_config_load_cert_chain_from_pem_file(this->_quiche_config, context->userconfig->cert); - } - if (context->userconfig->key != nullptr) { - quiche_config_load_priv_key_from_pem_file(this->_quiche_config, context->userconfig->key); - } quiche_config_set_max_idle_timeout(this->_quiche_config, params->no_activity_timeout_in()); quiche_config_set_max_recv_udp_payload_size(this->_quiche_config, params->get_max_recv_udp_payload_size_in()); diff --git a/iocore/net/QUICNetVConnection_quiche.cc b/iocore/net/QUICNetVConnection_quiche.cc index 278b3b27a22..20bb1439cd2 100644 --- a/iocore/net/QUICNetVConnection_quiche.cc +++ b/iocore/net/QUICNetVConnection_quiche.cc @@ -23,7 +23,9 @@ #include "P_QUICNetVConnection_quiche.h" #include "P_QUICPacketHandler_quiche.h" +#include "QUICMultiCertConfigLoader.h" #include "quic/QUICStream_quiche.h" +#include "quic/QUICGlobals.h" #include static constexpr ink_hrtime WRITE_READY_INTERVAL = HRTIME_MSECONDS(2); @@ -46,7 +48,7 @@ QUICNetVConnection::init(QUICVersion version, QUICConnectionId peer_cid, QUICCon void QUICNetVConnection::init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid, QUICConnectionId retry_cid, UDPConnection *udp_con, quiche_conn *quiche_con, - QUICPacketHandler *packet_handler, QUICConnectionTable *ctable) + QUICPacketHandler *packet_handler, QUICConnectionTable *ctable, SSL *ssl) { SET_HANDLER((NetVConnHandler)&QUICNetVConnection::acceptEvent); this->_udp_con = udp_con; @@ -61,6 +63,10 @@ QUICNetVConnection::init(QUICVersion version, QUICConnectionId peer_cid, QUICCon this->_ctable->insert(this->_quic_connection_id, this); this->_ctable->insert(this->_original_quic_connection_id, this); } + + this->_ssl = ssl; + SSL_set_ex_data(ssl, QUIC::ssl_quic_qc_index, static_cast(this)); + this->_bindSSLObject(); } void @@ -110,6 +116,7 @@ QUICNetVConnection::free(EThread *t) this->_context->trigger(QUICContext::CallbackEvent::CONNECTION_CLOSE); ALPNSupport::clear(); TLSBasicSupport::clear(); + TLSCertSwitchSupport::_clear(); this->_packet_handler->close_connection(this); this->_packet_handler = nullptr; @@ -485,6 +492,32 @@ QUICNetVConnection::load_buffer_and_write(int64_t towrite, MIOBufferAccessor &bu return 0; } +bool +QUICNetVConnection::getSSLHandShakeComplete() const +{ + return quiche_conn_is_established(this->_quiche_con); +} + +void +QUICNetVConnection::_bindSSLObject() +{ + TLSBasicSupport::bind(this->_ssl, this); + ALPNSupport::bind(this->_ssl, this); + TLSSessionResumptionSupport::bind(this->_ssl, this); + TLSSNISupport::bind(this->_ssl, this); + TLSCertSwitchSupport::bind(this->_ssl, this); +} + +void +QUICNetVConnection::_unbindSSLObject() +{ + TLSBasicSupport::unbind(this->_ssl); + ALPNSupport::unbind(this->_ssl); + TLSSessionResumptionSupport::unbind(this->_ssl); + TLSSNISupport::unbind(this->_ssl); + TLSCertSwitchSupport::unbind(this->_ssl); +} + void QUICNetVConnection::_schedule_packet_write_ready(bool delay) { @@ -630,14 +663,83 @@ QUICNetVConnection::protocol_contains(std::string_view tag) const return ""; } +const char * +QUICNetVConnection::get_server_name() const +{ + return get_sni_server_name(); +} + +bool +QUICNetVConnection::support_sni() const +{ + return true; +} + SSL * QUICNetVConnection::_get_ssl_object() const { - return nullptr; + return this->_ssl; } ssl_curve_id QUICNetVConnection::_get_tls_curve() const { - return 0; + if (getSSLSessionCacheHit()) { + return getSSLCurveNID(); + } else { + return SSLGetCurveNID(this->_ssl); + } +} + +void +QUICNetVConnection::_fire_ssl_servername_event() +{ +} + +const IpEndpoint & +QUICNetVConnection::_getLocalEndpoint() +{ + return this->local_addr; +} + +bool +QUICNetVConnection::_isTryingRenegotiation() const +{ + // Renegotiation is not allowed on QUIC (TLS 1.3) connections. + // If handshake is completed when this function is called, that should be unallowed attempt of renegotiation. + if (this->getSSLHandShakeComplete()) { + return true; + } else { + return false; + } +} + +shared_SSL_CTX +QUICNetVConnection::_lookupContextByName(const std::string &servername, SSLCertContextType ctxType) +{ + shared_SSL_CTX ctx = nullptr; + QUICCertConfig::scoped_config lookup; + SSLCertContext *cc = lookup->find(servername, ctxType); + + if (cc && cc->getCtx()) { + ctx = cc->getCtx(); + } + + return ctx; +} + +shared_SSL_CTX +QUICNetVConnection::_lookupContextByIP() +{ + shared_SSL_CTX ctx = nullptr; + QUICCertConfig::scoped_config lookup; + QUICFiveTuple five_tuple = this->five_tuple(); + IpEndpoint ip = five_tuple.destination(); + SSLCertContext *cc = lookup->find(ip); + + if (cc && cc->getCtx()) { + ctx = cc->getCtx(); + } + + return ctx; } diff --git a/iocore/net/QUICPacketHandler_quiche.cc b/iocore/net/QUICPacketHandler_quiche.cc index 34aa812e191..2a0fbf83bb2 100644 --- a/iocore/net/QUICPacketHandler_quiche.cc +++ b/iocore/net/QUICPacketHandler_quiche.cc @@ -29,6 +29,7 @@ #include "P_QUICNetProcessor_quiche.h" #include "P_QUICClosedConCollector.h" #include "quic/QUICConnectionTable.h" +#include "QUICMultiCertConfigLoader.h" #include static constexpr char debug_tag[] = "quic_sec"; @@ -274,13 +275,14 @@ QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet) } QUICConnectionId new_cid; - quiche_conn *quiche_con = - quiche_accept(new_cid, new_cid.length(), retry_token.original_dcid(), retry_token.original_dcid().length(), -#ifdef HAVE_QUICHE_CONFIG_SET_ACTIVE_CONNECTION_ID_LIMIT - &udp_packet->to.sa, udp_packet->to.isIp4() ? sizeof(udp_packet->to.sin) : sizeof(udp_packet->to.sin6), -#endif - &udp_packet->from.sa, udp_packet->from.isIp4() ? sizeof(udp_packet->from.sin) : sizeof(udp_packet->from.sin6), - &this->_quiche_config); + + QUICCertConfig::scoped_config server_cert; + SSL *ssl = SSL_new(server_cert->defaultContext()); + + quiche_conn *quiche_con = quiche_conn_new_with_tls( + new_cid, new_cid.length(), retry_token.original_dcid(), retry_token.original_dcid().length(), &udp_packet->to.sa, + udp_packet->to.isIp4() ? sizeof(udp_packet->to.sin) : sizeof(udp_packet->to.sin6), &udp_packet->from.sa, + udp_packet->from.isIp4() ? sizeof(udp_packet->from.sin) : sizeof(udp_packet->from.sin6), &this->_quiche_config, ssl, true); if (params->get_qlog_file_base_name() != nullptr) { char qlog_filepath[PATH_MAX]; @@ -299,7 +301,7 @@ QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet) // vc->init(version, peer_cid, original_cid, ocid_in_retry_token, rcid_in_retry_token, udp_packet->getConnection(), this, // &this->_ctable); vc->init(version, peer_cid, new_cid, QUICConnectionId::ZERO(), QUICConnectionId::ZERO(), udp_packet->getConnection(), - quiche_con, this, &this->_ctable); + quiche_con, this, &this->_ctable, ssl); vc->id = net_next_connection_number(); vc->con.move(con); vc->submit_time = Thread::get_hrtime(); From b8da06b290bf26358fc36e8397d9a5f4bebf500c Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 11 Apr 2023 14:26:13 -0600 Subject: [PATCH 2/3] Only run HTTP/3 autests if TS uses Quiche --- tests/gold_tests/timeout/active_timeout.test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gold_tests/timeout/active_timeout.test.py b/tests/gold_tests/timeout/active_timeout.test.py index 8c8b8bc650d..80282efbedc 100644 --- a/tests/gold_tests/timeout/active_timeout.test.py +++ b/tests/gold_tests/timeout/active_timeout.test.py @@ -64,7 +64,7 @@ tr3.Processes.Default.Command = 'curl -k -i --http2 https://127.0.0.1:{0}/file'.format(ts.Variables.ssl_port) tr3.Processes.Default.Streams.stdout = Testers.ContainsExpression("Activity Timeout", "Request should fail with active timeout") -if Condition.HasATSFeature('TS_USE_QUIC') and Condition.HasCurlFeature('http3'): +if Condition.HasATSFeature('TS_HAS_QUICHE') and Condition.HasCurlFeature('http3'): tr4 = Test.AddTestRun("tr") tr4.Processes.Default.Command = 'curl -k -i --http3 https://127.0.0.1:{0}/file'.format(ts.Variables.ssl_port) tr4.Processes.Default.Streams.stdout = Testers.ContainsExpression("Activity Timeout", "Request should fail with active timeout") From 3810d204c8d67410592afb66a7bd7b8b5a3ca357 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 18 Apr 2023 11:46:39 -0600 Subject: [PATCH 3/3] Simplify the code --- iocore/net/QUICNetVConnection_quiche.cc | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/iocore/net/QUICNetVConnection_quiche.cc b/iocore/net/QUICNetVConnection_quiche.cc index 744a05fe542..7d4329f5fa1 100644 --- a/iocore/net/QUICNetVConnection_quiche.cc +++ b/iocore/net/QUICNetVConnection_quiche.cc @@ -709,11 +709,7 @@ QUICNetVConnection::_isTryingRenegotiation() const { // Renegotiation is not allowed on QUIC (TLS 1.3) connections. // If handshake is completed when this function is called, that should be unallowed attempt of renegotiation. - if (this->getSSLHandShakeComplete()) { - return true; - } else { - return false; - } + return this->getSSLHandShakeComplete(); } shared_SSL_CTX