bneradt commented on code in PR #13186:
URL: https://github.com/apache/trafficserver/pull/13186#discussion_r3685521417
##########
src/iocore/net/QUICMultiCertConfigLoader.cc:
##########
@@ -88,12 +92,256 @@ QUICCertConfig::release(SSLCertLookup *lookup)
SSL_CTX *
QUICMultiCertConfigLoader::default_server_ssl_ctx()
{
- return quic_new_ssl_ctx();
+ return quic_new_server_ssl_ctx();
+}
+
+#if TS_HAS_OPENSSL_QUIC
+namespace
+{
+DbgCtl dbg_ctl_quic{"quic"};
+} // end anonymous namespace
+
+namespace
+{
+void
+free_quic_sni_ex_data(void *, void *ptr, CRYPTO_EX_DATA *, int, long, void *)
+{
+ delete static_cast<std::string *>(ptr);
+}
+
+int
+quic_sni_ex_data_index()
+{
+ static int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr,
free_quic_sni_ex_data);
+
+ return index;
+}
+
+void
+remember_quic_sni(SSL *ssl, std::string_view servername)
+{
+ if (ssl == nullptr || servername.empty()) {
+ return;
+ }
+
+ auto const index = quic_sni_ex_data_index();
+ if (index < 0) {
+ return;
+ }
+
+ auto *stored = static_cast<std::string *>(SSL_get_ex_data(ssl, index));
+ if (stored == nullptr) {
+ stored = new std::string;
+ if (SSL_set_ex_data(ssl, index, stored) != 1) {
+ delete stored;
+ return;
+ }
+ }
+ stored->assign(servername);
+
+ if (auto *snis = TLSSNISupport::getInstance(ssl); snis != nullptr) {
+ snis->set_sni_server_name(servername);
+ }
}
bool
-QUICMultiCertConfigLoader::_setup_session_cache(SSL_CTX * /* ctx ATS_UNUSED */)
+apply_quic_sni_certificate(SSL *ssl, SSL_CTX *ctx)
{
+ X509 *cert = SSL_CTX_get0_certificate(ctx);
+ EVP_PKEY *key = SSL_CTX_get0_privatekey(ctx);
+
+ if (cert == nullptr && key == nullptr) {
+ return true;
+ }
+ if (cert == nullptr || key == nullptr) {
+ return false;
+ }
+
+ if (SSL_use_certificate(ssl, cert) != 1 || SSL_use_PrivateKey(ssl, key) !=
1) {
+ return false;
+ }
+
+ STACK_OF(X509) *chain = nullptr;
+ if (SSL_CTX_get0_chain_certs(ctx, &chain) == 1 && chain != nullptr &&
SSL_set1_chain(ssl, chain) != 1) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+select_quic_sni_context(SSL *ssl, std::string_view servername)
+{
+ if (servername.empty()) {
+ return true;
+ }
+
+ QUICCertConfig::scoped_config lookup;
+ if (!lookup) {
+ Dbg(dbg_ctl_quic, "OpenSSL QUIC SNI context selection could not acquire
cert lookup");
+ return false;
+ }
+
+ SSLCertContext *cert_context = lookup->find(std::string(servername));
+ if (cert_context == nullptr) {
+ return true;
+ }
+
+ shared_SSL_CTX const selected_ctx = cert_context->getCtx();
+ if (selected_ctx == nullptr) {
+ Dbg(dbg_ctl_quic, "OpenSSL QUIC SNI context selection found null context
for SNI '%.*s'", static_cast<int>(servername.size()),
+ servername.data());
+ return false;
+ }
+
+ SSL_set_SSL_CTX(ssl, selected_ctx.get());
+ if (!apply_quic_sni_certificate(ssl, selected_ctx.get())) {
+ Dbg(dbg_ctl_quic, "OpenSSL QUIC SNI context selection failed to apply
SSL_CTX %p certificate to ssl=%p", selected_ctx.get(),
+ ssl);
+ return false;
+ }
+
+ return true;
+}
+
+std::string_view
+servername_from_client_hello(SSL *ssl)
+{
+ unsigned char const *p = nullptr;
+ size_t remaining = 0;
+ if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &p, &remaining)
!= 1 || remaining <= 2) {
+ return {};
+ }
+
+ size_t len = (static_cast<size_t>(*p++) << 8);
+ len += *p++;
+ if (len + 2 != remaining) {
+ return {};
+ }
+ remaining = len;
+
+ if (remaining <= 3 || *p++ != TLSEXT_NAMETYPE_host_name) {
+ return {};
+ }
+ --remaining;
+
+ len = (static_cast<size_t>(*p++) << 8);
+ len += *p++;
+ if (len + 2 > remaining) {
+ return {};
+ }
+
+ return {reinterpret_cast<char const *>(p), len};
+}
+
+int
+quic_client_hello_callback(SSL *ssl, int *, void *)
+{
+ auto const servername = servername_from_client_hello(ssl);
+ remember_quic_sni(ssl, servername);
+
+ return select_quic_sni_context(ssl, servername) ? SSL_CLIENT_HELLO_SUCCESS :
SSL_CLIENT_HELLO_ERROR;
+}
+
+int
+quic_servername_callback(SSL *ssl, int *, void *)
+{
+ char const *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ auto const sni = servername == nullptr ? std::string_view{} :
std::string_view{servername};
+ remember_quic_sni(ssl, sni);
+
+ return select_quic_sni_context(ssl, sni) ? SSL_TLSEXT_ERR_OK :
SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // end anonymous namespace
+
+std::string_view
+quic_sni_server_name(SSL *ssl)
+{
+ if (ssl == nullptr) {
+ return {};
+ }
+
+ auto const index = quic_sni_ex_data_index();
+ if (index < 0) {
+ return {};
+ }
+
+ auto *stored = static_cast<std::string *>(SSL_get_ex_data(ssl, index));
+
+ return stored == nullptr ? std::string_view{} : std::string_view{*stored};
+}
+
+void
+QUICMultiCertConfigLoader::_set_handshake_callbacks(SSL_CTX *ctx)
+{
+ Dbg(dbg_ctl_quic, "installing OpenSSL QUIC cert callback on SSL_CTX %p",
ctx);
+ SSL_CTX_set_client_hello_cb(ctx, quic_client_hello_callback, nullptr);
+ SSL_CTX_set_tlsext_servername_callback(ctx, quic_servername_callback);
+ SSL_CTX_set_cert_cb(
+ ctx,
+ [](SSL *ssl, void *) -> int {
+ char const *servername = SSL_get_servername(ssl,
TLSEXT_NAMETYPE_host_name);
+ auto const sni = servername == nullptr ? std::string_view{} :
std::string_view{servername};
+ remember_quic_sni(ssl, sni);
+
+ return select_quic_sni_context(ssl, sni) ? 1 : 0;
+ },
+ nullptr);
+}
+
+namespace
+{
+int
+quic_alpn_select_callback(SSL * /* ssl ATS_UNUSED */, const unsigned char
**out, unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void * /* arg ATS_UNUSED */)
+{
+ static constexpr unsigned char server_protos[] = {2, 'h', '3', 5, 'h', '3',
'-', '2', '9', 5, 'h', '3', '-', '2', '7'};
+
+ if (SSL_select_next_proto(const_cast<unsigned char **>(out), outlen,
server_protos, sizeof(server_protos), in, inlen) ==
+ OPENSSL_NPN_NEGOTIATED) {
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ *out = nullptr;
+ *outlen = 0;
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // end anonymous namespace
+
+bool
+QUICMultiCertConfigLoader::_set_alpn_callback(SSL_CTX *ctx)
Review Comment:
The existing ssl_alpn_select_callback retrieves ALPNSupport from the SSL
object’s ex-data, which is bound when the QUICNetVConnection is initialized.
With OpenSSL’s native QUIC listener, ALPN negotiation happens before
SSL_accept_connection() returns, while ATS allocates and initializes the
QUICNetVConnection only after that call succeeds, so the existing callback has
no ALPNSupport available. This context-level callback therefore performs only
the early HTTP/3 ALPN selection needed by the listener; after acceptance, the
SSL object is attached to the NetVC and normal ATS protocol handling takes over.
##########
src/iocore/net/QUICMultiCertConfigLoader.cc:
##########
@@ -88,12 +92,256 @@ QUICCertConfig::release(SSLCertLookup *lookup)
SSL_CTX *
QUICMultiCertConfigLoader::default_server_ssl_ctx()
{
- return quic_new_ssl_ctx();
+ return quic_new_server_ssl_ctx();
+}
+
+#if TS_HAS_OPENSSL_QUIC
+namespace
+{
+DbgCtl dbg_ctl_quic{"quic"};
+} // end anonymous namespace
+
+namespace
+{
+void
+free_quic_sni_ex_data(void *, void *ptr, CRYPTO_EX_DATA *, int, long, void *)
+{
+ delete static_cast<std::string *>(ptr);
+}
+
+int
+quic_sni_ex_data_index()
+{
+ static int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr,
free_quic_sni_ex_data);
+
+ return index;
+}
+
+void
+remember_quic_sni(SSL *ssl, std::string_view servername)
+{
+ if (ssl == nullptr || servername.empty()) {
+ return;
+ }
+
+ auto const index = quic_sni_ex_data_index();
+ if (index < 0) {
+ return;
+ }
+
+ auto *stored = static_cast<std::string *>(SSL_get_ex_data(ssl, index));
+ if (stored == nullptr) {
+ stored = new std::string;
+ if (SSL_set_ex_data(ssl, index, stored) != 1) {
+ delete stored;
+ return;
+ }
+ }
+ stored->assign(servername);
+
+ if (auto *snis = TLSSNISupport::getInstance(ssl); snis != nullptr) {
+ snis->set_sni_server_name(servername);
+ }
}
bool
-QUICMultiCertConfigLoader::_setup_session_cache(SSL_CTX * /* ctx ATS_UNUSED */)
+apply_quic_sni_certificate(SSL *ssl, SSL_CTX *ctx)
{
+ X509 *cert = SSL_CTX_get0_certificate(ctx);
+ EVP_PKEY *key = SSL_CTX_get0_privatekey(ctx);
+
+ if (cert == nullptr && key == nullptr) {
+ return true;
+ }
+ if (cert == nullptr || key == nullptr) {
+ return false;
+ }
+
+ if (SSL_use_certificate(ssl, cert) != 1 || SSL_use_PrivateKey(ssl, key) !=
1) {
+ return false;
+ }
+
+ STACK_OF(X509) *chain = nullptr;
+ if (SSL_CTX_get0_chain_certs(ctx, &chain) == 1 && chain != nullptr &&
SSL_set1_chain(ssl, chain) != 1) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+select_quic_sni_context(SSL *ssl, std::string_view servername)
+{
+ if (servername.empty()) {
+ return true;
+ }
+
+ QUICCertConfig::scoped_config lookup;
+ if (!lookup) {
+ Dbg(dbg_ctl_quic, "OpenSSL QUIC SNI context selection could not acquire
cert lookup");
+ return false;
+ }
+
+ SSLCertContext *cert_context = lookup->find(std::string(servername));
+ if (cert_context == nullptr) {
+ return true;
+ }
+
+ shared_SSL_CTX const selected_ctx = cert_context->getCtx();
+ if (selected_ctx == nullptr) {
+ Dbg(dbg_ctl_quic, "OpenSSL QUIC SNI context selection found null context
for SNI '%.*s'", static_cast<int>(servername.size()),
+ servername.data());
+ return false;
+ }
+
+ SSL_set_SSL_CTX(ssl, selected_ctx.get());
+ if (!apply_quic_sni_certificate(ssl, selected_ctx.get())) {
+ Dbg(dbg_ctl_quic, "OpenSSL QUIC SNI context selection failed to apply
SSL_CTX %p certificate to ssl=%p", selected_ctx.get(),
+ ssl);
+ return false;
+ }
+
+ return true;
+}
+
+std::string_view
+servername_from_client_hello(SSL *ssl)
+{
+ unsigned char const *p = nullptr;
+ size_t remaining = 0;
+ if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &p, &remaining)
!= 1 || remaining <= 2) {
+ return {};
+ }
+
+ size_t len = (static_cast<size_t>(*p++) << 8);
+ len += *p++;
+ if (len + 2 != remaining) {
+ return {};
+ }
+ remaining = len;
+
+ if (remaining <= 3 || *p++ != TLSEXT_NAMETYPE_host_name) {
+ return {};
+ }
+ --remaining;
+
+ len = (static_cast<size_t>(*p++) << 8);
+ len += *p++;
+ if (len + 2 > remaining) {
+ return {};
+ }
+
+ return {reinterpret_cast<char const *>(p), len};
+}
+
+int
+quic_client_hello_callback(SSL *ssl, int *, void *)
+{
+ auto const servername = servername_from_client_hello(ssl);
+ remember_quic_sni(ssl, servername);
+
+ return select_quic_sni_context(ssl, servername) ? SSL_CLIENT_HELLO_SUCCESS :
SSL_CLIENT_HELLO_ERROR;
+}
+
+int
+quic_servername_callback(SSL *ssl, int *, void *)
+{
+ char const *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ auto const sni = servername == nullptr ? std::string_view{} :
std::string_view{servername};
+ remember_quic_sni(ssl, sni);
+
+ return select_quic_sni_context(ssl, sni) ? SSL_TLSEXT_ERR_OK :
SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // end anonymous namespace
+
+std::string_view
+quic_sni_server_name(SSL *ssl)
+{
+ if (ssl == nullptr) {
+ return {};
+ }
+
+ auto const index = quic_sni_ex_data_index();
+ if (index < 0) {
+ return {};
+ }
+
+ auto *stored = static_cast<std::string *>(SSL_get_ex_data(ssl, index));
+
+ return stored == nullptr ? std::string_view{} : std::string_view{*stored};
+}
+
+void
+QUICMultiCertConfigLoader::_set_handshake_callbacks(SSL_CTX *ctx)
+{
+ Dbg(dbg_ctl_quic, "installing OpenSSL QUIC cert callback on SSL_CTX %p",
ctx);
+ SSL_CTX_set_client_hello_cb(ctx, quic_client_hello_callback, nullptr);
+ SSL_CTX_set_tlsext_servername_callback(ctx, quic_servername_callback);
+ SSL_CTX_set_cert_cb(
+ ctx,
+ [](SSL *ssl, void *) -> int {
+ char const *servername = SSL_get_servername(ssl,
TLSEXT_NAMETYPE_host_name);
+ auto const sni = servername == nullptr ? std::string_view{} :
std::string_view{servername};
+ remember_quic_sni(ssl, sni);
+
+ return select_quic_sni_context(ssl, sni) ? 1 : 0;
+ },
+ nullptr);
+}
+
+namespace
+{
+int
+quic_alpn_select_callback(SSL * /* ssl ATS_UNUSED */, const unsigned char
**out, unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void * /* arg ATS_UNUSED */)
+{
+ static constexpr unsigned char server_protos[] = {2, 'h', '3', 5, 'h', '3',
'-', '2', '9', 5, 'h', '3', '-', '2', '7'};
+
+ if (SSL_select_next_proto(const_cast<unsigned char **>(out), outlen,
server_protos, sizeof(server_protos), in, inlen) ==
+ OPENSSL_NPN_NEGOTIATED) {
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ *out = nullptr;
+ *outlen = 0;
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // end anonymous namespace
+
+bool
+QUICMultiCertConfigLoader::_set_alpn_callback(SSL_CTX *ctx)
+{
+ SSL_CTX_set_alpn_select_cb(ctx, quic_alpn_select_callback, nullptr);
+ return true;
+}
+
+bool
+QUICMultiCertConfigLoader::_set_curves(SSL_CTX *ctx)
+{
+ QUICConfig::scoped_config params;
+ if (params->server_supported_groups() == nullptr) {
+ return true;
+ }
+
+ if (!SSL_CTX_set1_groups_list(ctx, params->server_supported_groups())) {
+ Error("invalid QUIC groups list for server in records.config");
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+bool
+QUICMultiCertConfigLoader::_setup_session_cache(SSL_CTX *ctx)
+{
+#if TS_HAS_OPENSSL_QUIC
+ SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION);
Review Comment:
Good catch—the TLS 1.3 bounds were being applied twice. Both QUIC context
constructors already establish TLS 1.3 as an invariant when creating their
SSL_CTX, so _setup_session_cache() should not repeat that policy while
configuring an unrelated feature. I removed the duplicate calls, leaving
protocol-version configuration in the context constructors where it applies
consistently regardless of session-cache behavior.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]