diff --git a/CMakeLists.txt b/CMakeLists.txt index d28c3bd289c..3620c1994dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -570,6 +570,20 @@ check_symbol_exists(SSL_CTX_get0_implemented_groups "openssl/ssl.h" HAVE_SSL_CTX check_symbol_exists(SSL_get_group_id "openssl/ssl.h" HAVE_SSL_GET_GROUP_ID) check_symbol_exists(SSL_get_group_name "openssl/ssl.h" HAVE_SSL_GET_GROUP_NAME) check_symbol_exists(SSL_group_to_name "openssl/ssl.h" HAVE_SSL_GROUP_TO_NAME) +check_symbol_exists(SSL_get_sigalgs "openssl/ssl.h" HAVE_SSL_GET_SIGALGS) +check_symbol_exists(SSL_get_shared_sigalgs "openssl/ssl.h" HAVE_SSL_GET_SHARED_SIGALGS) +check_symbol_exists(SSL_get0_peer_verify_algorithms "openssl/ssl.h" HAVE_SSL_GET0_PEER_VERIFY_ALGORITHMS) +check_symbol_exists(SSL_get_signature_algorithm_used "openssl/ssl.h" HAVE_SSL_GET_SIGNATURE_ALGORITHM_USED) +check_cxx_source_compiles( + "#include + int main() { + SSL *ssl = nullptr; + int digest = 0; + int type = 0; + return SSL_get_signature_nid(ssl, &digest) + SSL_get_signature_type_nid(ssl, &type); + }" + HAVE_SSL_GET_LOCAL_SIGNATURE_NIDS +) check_symbol_exists(SSL_set_max_early_data "openssl/ssl.h" HAVE_SSL_SET_MAX_EARLY_DATA) check_symbol_exists(SSL_read_early_data "openssl/ssl.h" HAVE_SSL_READ_EARLY_DATA) check_symbol_exists(SSL_write_early_data "openssl/ssl.h" HAVE_SSL_WRITE_EARLY_DATA) diff --git a/doc/admin-guide/logging/formatting.en.rst b/doc/admin-guide/logging/formatting.en.rst index 420df43090b..6ca0bcb3f33 100644 --- a/doc/admin-guide/logging/formatting.en.rst +++ b/doc/admin-guide/logging/formatting.en.rst @@ -691,6 +691,9 @@ SSL / Encryption .. _cqssv: .. _cqssc: .. _cqssu: +.. _cqssg: +.. _cqssig: +.. _cqssin: .. _cqssa: .. _cthbr: .. _cthbt: @@ -730,6 +733,15 @@ cqssg Client Request SSL Group used by |TS| to communicate with the client. This field is only applicable when building |TS| against OpenSSL 3.2 or later or a version of BoringSSL that supports querying group names. +cqssig Client Request TLS signature schemes offered by the client, rendered as + dash-separated decimal IANA code points in wire order. + RFC 8701 GREASE values are omitted. This field contains + ``-`` when the client does not send the extension or the + TLS library cannot expose it. +cqssin Client Request TLS signature scheme used by |TS| to authenticate the + handshake, rendered as a decimal IANA code point. Resumed + sessions contain ``-`` because they do not create a new + handshake signature. cqssa Client Request ALPN Protocol ID negotiated with the client. cthbr Client Request TLS handshake bytes received from the client. This is the number of bytes read from the client during the TLS diff --git a/include/iocore/net/TLSBasicSupport.h b/include/iocore/net/TLSBasicSupport.h index 7d9354fa558..49be1cc7938 100644 --- a/include/iocore/net/TLSBasicSupport.h +++ b/include/iocore/net/TLSBasicSupport.h @@ -26,6 +26,7 @@ #include #include +#include #include @@ -49,6 +50,11 @@ class TLSBasicSupport const char *get_tls_cipher_suite() const; const char *get_tls_curve() const; std::string_view get_tls_group() const; + std::string_view get_tls_offered_signature_algorithms() const; + std::string_view get_tls_negotiated_signature_algorithm() const; + void capture_tls_offered_signature_algorithms(); + void capture_tls_offered_signature_algorithms(std::string offered); + void capture_tls_negotiated_signature_algorithm(); ink_hrtime get_tls_handshake_begin_time() const; ink_hrtime get_tls_handshake_end_time() const; bool get_tls_handshake_bytes(uint64_t &bytes_in, uint64_t &bytes_out) const; @@ -83,9 +89,11 @@ class TLSBasicSupport protected: void clear(); - virtual SSL *_get_ssl_object() const = 0; - virtual ssl_curve_id _get_tls_curve() const = 0; - virtual std::string_view _get_tls_group() const = 0; + virtual SSL *_get_ssl_object() const = 0; + virtual ssl_curve_id _get_tls_curve() const = 0; + virtual std::string_view _get_tls_group() const = 0; + virtual std::string _get_tls_offered_signature_algorithms() const = 0; + virtual std::string _get_tls_negotiated_signature_algorithm() const = 0; void _record_tls_handshake_begin_time(); void _record_tls_handshake_end_time(); @@ -103,9 +111,11 @@ class TLSBasicSupport X509_STORE_CTX *_cert_to_verify = nullptr; - ink_hrtime _tls_handshake_begin_time = 0; - ink_hrtime _tls_handshake_end_time = 0; - mutable bool _tls_handshake_bytes_measured = false; - mutable uint64_t _tls_handshake_bytes_in = 0; - mutable uint64_t _tls_handshake_bytes_out = 0; + ink_hrtime _tls_handshake_begin_time = 0; + ink_hrtime _tls_handshake_end_time = 0; + mutable bool _tls_handshake_bytes_measured = false; + mutable uint64_t _tls_handshake_bytes_in = 0; + mutable uint64_t _tls_handshake_bytes_out = 0; + mutable std::optional _tls_offered_signature_algorithms; + mutable std::optional _tls_negotiated_signature_algorithm; }; diff --git a/include/proxy/http/HttpUserAgent.h b/include/proxy/http/HttpUserAgent.h index 95b1120545d..365741deaf4 100644 --- a/include/proxy/http/HttpUserAgent.h +++ b/include/proxy/http/HttpUserAgent.h @@ -54,6 +54,8 @@ struct ClientConnectionInfo { char const *cipher_suite{"-"}; char const *curve{"-"}; std::string security_group{"-"}; + std::string offered_signature_algorithms{"-"}; + std::string negotiated_signature_algorithm{"-"}; int alpn_id{SessionProtocolNameRegistry::INVALID}; @@ -101,6 +103,10 @@ class HttpUserAgent char const *get_client_security_group() const; + char const *get_client_offered_signature_algorithms() const; + + char const *get_client_negotiated_signature_algorithm() const; + int get_client_alpn_id() const; uint64_t get_client_tls_handshake_bytes_rx() const; @@ -193,6 +199,19 @@ HttpUserAgent::set_txn(ProxyTransaction *txn, TransactionMilestones &milestones) m_conn_info.security_group = '-'; } + if (auto offered_signature_algorithms{tbs->get_tls_offered_signature_algorithms()}; !offered_signature_algorithms.empty()) { + m_conn_info.offered_signature_algorithms = offered_signature_algorithms; + } else { + m_conn_info.offered_signature_algorithms = '-'; + } + + if (auto negotiated_signature_algorithm{tbs->get_tls_negotiated_signature_algorithm()}; + !negotiated_signature_algorithm.empty()) { + m_conn_info.negotiated_signature_algorithm = negotiated_signature_algorithm; + } else { + m_conn_info.negotiated_signature_algorithm = '-'; + } + if (!m_conn_info.tcp_reused) { // Copy along the TLS handshake timings milestones[TS_MILESTONE_TLS_HANDSHAKE_START] = tbs->get_tls_handshake_begin_time(); @@ -312,6 +331,18 @@ HttpUserAgent::get_client_security_group() const return m_conn_info.security_group.c_str(); } +inline char const * +HttpUserAgent::get_client_offered_signature_algorithms() const +{ + return m_conn_info.offered_signature_algorithms.c_str(); +} + +inline char const * +HttpUserAgent::get_client_negotiated_signature_algorithm() const +{ + return m_conn_info.negotiated_signature_algorithm.c_str(); +} + inline int HttpUserAgent::get_client_alpn_id() const { diff --git a/include/proxy/logging/LogAccess.h b/include/proxy/logging/LogAccess.h index 35f14ea55c5..4b18efa2a6e 100644 --- a/include/proxy/logging/LogAccess.h +++ b/include/proxy/logging/LogAccess.h @@ -134,51 +134,53 @@ class LogAccess // // client -> proxy fields // - int marshal_client_host_ip(char *); // STR - int marshal_host_interface_ip(char *); // STR - int marshal_client_host_ip_verified(char *); // STR - int marshal_client_host_port(char *); // INT - int marshal_remote_host_ip(char *); // STR - int marshal_remote_host_port(char *); // STR - int marshal_client_auth_user_name(char *); // STR - int marshal_client_req_timestamp_sec(char *); // INT - int marshal_client_req_timestamp_ms(char *); // INT - int marshal_client_req_text(char *); // STR - int marshal_client_req_http_method(char *); // STR - int marshal_client_req_url(char *); // STR - int marshal_client_req_url_canon(char *); // STR - int marshal_client_req_unmapped_url_canon(char *); // STR - int marshal_client_req_unmapped_url_path(char *); // STR - int marshal_client_req_unmapped_url_host(char *); // STR - int marshal_client_req_url_path(char *); // STR - int marshal_client_req_url_scheme(char *); // STR - int marshal_client_req_http_version(char *); // INT - int marshal_client_req_protocol_version(char *); // STR - int marshal_server_req_protocol_version(char *); // STR - int marshal_client_req_squid_len(char *); // INT - int marshal_client_req_squid_len_tls(char *); // INT - int marshal_client_req_header_len(char *); // INT - int marshal_client_req_content_len(char *); // INT - int marshal_client_req_tcp_reused(char *); // INT - int marshal_client_req_is_ssl(char *); // INT - int marshal_client_req_ssl_reused(char *); // INT - int marshal_client_ssl_resumption_type(char *); // INT - int marshal_client_req_is_internal(char *); // INT - int marshal_client_req_mptcp_state(char *); // INT - int marshal_client_security_protocol(char *); // STR - int marshal_client_security_cipher_suite(char *); // STR - int marshal_client_security_curve(char *); // STR - int marshal_client_security_group(char *); // STR - int marshal_client_security_alpn(char *); // STR - int marshal_client_finish_status_code(char *); // INT - int marshal_client_req_id(char *); // INT - int marshal_client_req_uuid(char *); // STR - int marshal_client_rx_error_code(char *); // STR - int marshal_client_tx_error_code(char *); // STR - int marshal_client_tls_handshake_bytes_rx(char *); // INT - int marshal_client_tls_handshake_bytes_tx(char *); // INT - int marshal_client_tls_handshake_bytes(char *); // INT - int marshal_client_req_all_header_fields(char *); // STR + int marshal_client_host_ip(char *); // STR + int marshal_host_interface_ip(char *); // STR + int marshal_client_host_ip_verified(char *); // STR + int marshal_client_host_port(char *); // INT + int marshal_remote_host_ip(char *); // STR + int marshal_remote_host_port(char *); // STR + int marshal_client_auth_user_name(char *); // STR + int marshal_client_req_timestamp_sec(char *); // INT + int marshal_client_req_timestamp_ms(char *); // INT + int marshal_client_req_text(char *); // STR + int marshal_client_req_http_method(char *); // STR + int marshal_client_req_url(char *); // STR + int marshal_client_req_url_canon(char *); // STR + int marshal_client_req_unmapped_url_canon(char *); // STR + int marshal_client_req_unmapped_url_path(char *); // STR + int marshal_client_req_unmapped_url_host(char *); // STR + int marshal_client_req_url_path(char *); // STR + int marshal_client_req_url_scheme(char *); // STR + int marshal_client_req_http_version(char *); // INT + int marshal_client_req_protocol_version(char *); // STR + int marshal_server_req_protocol_version(char *); // STR + int marshal_client_req_squid_len(char *); // INT + int marshal_client_req_squid_len_tls(char *); // INT + int marshal_client_req_header_len(char *); // INT + int marshal_client_req_content_len(char *); // INT + int marshal_client_req_tcp_reused(char *); // INT + int marshal_client_req_is_ssl(char *); // INT + int marshal_client_req_ssl_reused(char *); // INT + int marshal_client_ssl_resumption_type(char *); // INT + int marshal_client_req_is_internal(char *); // INT + int marshal_client_req_mptcp_state(char *); // INT + int marshal_client_security_protocol(char *); // STR + int marshal_client_security_cipher_suite(char *); // STR + int marshal_client_security_curve(char *); // STR + int marshal_client_security_group(char *); // STR + int marshal_client_offered_signature_algorithms(char *); // STR + int marshal_client_negotiated_signature_algorithm(char *); // STR + int marshal_client_security_alpn(char *); // STR + int marshal_client_finish_status_code(char *); // INT + int marshal_client_req_id(char *); // INT + int marshal_client_req_uuid(char *); // STR + int marshal_client_rx_error_code(char *); // STR + int marshal_client_tx_error_code(char *); // STR + int marshal_client_tls_handshake_bytes_rx(char *); // INT + int marshal_client_tls_handshake_bytes_tx(char *); // INT + int marshal_client_tls_handshake_bytes(char *); // INT + int marshal_client_req_all_header_fields(char *); // STR // // proxy -> client fields diff --git a/include/proxy/logging/TransactionLogData.h b/include/proxy/logging/TransactionLogData.h index 908e036e389..b25ab601a0a 100644 --- a/include/proxy/logging/TransactionLogData.h +++ b/include/proxy/logging/TransactionLogData.h @@ -130,6 +130,8 @@ class TransactionLogData const char *get_client_cipher_suite() const; const char *get_client_curve() const; const char *get_client_security_group() const; + const char *get_client_offered_signature_algorithms() const; + const char *get_client_negotiated_signature_algorithm() const; int get_client_alpn_id() const; // ===== SNI ===== diff --git a/include/tscore/ink_config.h.cmake.in b/include/tscore/ink_config.h.cmake.in index 73c8b860fb9..0db6295b952 100644 --- a/include/tscore/ink_config.h.cmake.in +++ b/include/tscore/ink_config.h.cmake.in @@ -183,6 +183,11 @@ const int DEFAULT_STACKSIZE = @DEFAULT_STACK_SIZE@; #cmakedefine01 HAVE_SSL_GET_GROUP_ID #cmakedefine01 HAVE_SSL_GET_GROUP_NAME #cmakedefine01 HAVE_SSL_GROUP_TO_NAME +#cmakedefine01 HAVE_SSL_GET_SIGALGS +#cmakedefine01 HAVE_SSL_GET_SHARED_SIGALGS +#cmakedefine01 HAVE_SSL_GET0_PEER_VERIFY_ALGORITHMS +#cmakedefine01 HAVE_SSL_GET_SIGNATURE_ALGORITHM_USED +#cmakedefine01 HAVE_SSL_GET_LOCAL_SIGNATURE_NIDS #cmakedefine01 HAVE_SSL_ERROR_DESCRIPTION #cmakedefine01 HAVE_OSSL_PARAM_CONSTRUCT_END #cmakedefine01 TS_USE_TLS_SET_CIPHERSUITES diff --git a/src/iocore/net/CMakeLists.txt b/src/iocore/net/CMakeLists.txt index ac3f12d7cc8..78639fe3ff6 100644 --- a/src/iocore/net/CMakeLists.txt +++ b/src/iocore/net/CMakeLists.txt @@ -140,6 +140,7 @@ if(BUILD_TESTING) NetVCTest.cc unit_tests/test_ProxyProtocol.cc unit_tests/test_SSLSNIConfig.cc + unit_tests/test_SSLUtils.cc unit_tests/test_YamlSNIConfig.cc unit_tests/unit_test_main.cc unit_tests/benchmark_TLSCertCompression.cc diff --git a/src/iocore/net/P_QUICNetVConnection.h b/src/iocore/net/P_QUICNetVConnection.h index e6fec2b8ea5..5759413555e 100644 --- a/src/iocore/net/P_QUICNetVConnection.h +++ b/src/iocore/net/P_QUICNetVConnection.h @@ -160,6 +160,8 @@ class QUICNetVConnection : public UnixNetVConnection, SSL *_get_ssl_object() const override; ssl_curve_id _get_tls_curve() const override; std::string_view _get_tls_group() const override; + std::string _get_tls_offered_signature_algorithms() const override; + std::string _get_tls_negotiated_signature_algorithm() const override; int _verify_certificate(X509_STORE_CTX *ctx) override; // TLSSNISupport diff --git a/src/iocore/net/P_SSLNetVConnection.h b/src/iocore/net/P_SSLNetVConnection.h index 3ff284ff8cc..293767593a8 100644 --- a/src/iocore/net/P_SSLNetVConnection.h +++ b/src/iocore/net/P_SSLNetVConnection.h @@ -319,6 +319,8 @@ class SSLNetVConnection : public UnixNetVConnection, } ssl_curve_id _get_tls_curve() const override; std::string_view _get_tls_group() const override; + std::string _get_tls_offered_signature_algorithms() const override; + std::string _get_tls_negotiated_signature_algorithm() const override; int _verify_certificate(X509_STORE_CTX *ctx) override; // TLSSessionResumptionSupport diff --git a/src/iocore/net/P_SSLUtils.h b/src/iocore/net/P_SSLUtils.h index 8e2cbf57884..bca1d86c8b5 100644 --- a/src/iocore/net/P_SSLUtils.h +++ b/src/iocore/net/P_SSLUtils.h @@ -23,6 +23,7 @@ #include "P_SSLCertLookup.h" #include "iocore/net/SSLTypes.h" +#include "swoc/MemSpan.h" #include "tscore/Diags.h" #define OPENSSL_THREAD_DEFINES @@ -31,6 +32,7 @@ #endif #include #include +#include class SSLNetVConnection; @@ -42,6 +44,18 @@ ssl_curve_id SSLGetCurveNID(SSL *ssl); /// Return the TLS Group Name associated with the specified SSL connection. std::string_view SSLGetGroupName(SSL *ssl); +/// Format TLS signature-scheme code points as dash-separated decimal values, excluding GREASE. +std::string SSLFormatSignatureAlgorithms(swoc::MemSpan algorithms); + +/// Return the peer's offered TLS signature schemes in wire order. +std::string SSLGetOfferedSignatureAlgorithms(SSL *ssl); + +/// Return the TLS signature scheme used locally to authenticate the current handshake. +std::string SSLGetNegotiatedSignatureAlgorithm(SSL *ssl); + +/// Capture TLS signature information while the TLS library still exposes the selected scheme. +void SSLHandshakeInfoCallback(const SSL *ssl, int where, int ret); + SSL_SESSION *SSLSessionDup(SSL_SESSION *sess); enum class SSLCertContextType; diff --git a/src/iocore/net/QUICMultiCertConfigLoader.cc b/src/iocore/net/QUICMultiCertConfigLoader.cc index 2e7049643e8..d5e84b77a73 100644 --- a/src/iocore/net/QUICMultiCertConfigLoader.cc +++ b/src/iocore/net/QUICMultiCertConfigLoader.cc @@ -23,6 +23,7 @@ #include "P_SSLCertLookup.h" #include "P_SSLConfig.h" +#include "P_SSLUtils.h" #include "iocore/net/QUICMultiCertConfigLoader.h" #include "iocore/net/quic/QUICConfig.h" #include "mgmt/config/ConfigContextDiags.h" @@ -107,10 +108,9 @@ QUICMultiCertConfigLoader::_set_cipher_suites_for_legacy_versions(SSL_CTX * /* c } bool -QUICMultiCertConfigLoader::_set_info_callback(SSL_CTX * /* ctx ATS_UNUSED */) +QUICMultiCertConfigLoader::_set_info_callback(SSL_CTX *ctx) { - // Disabled for now - // TODO Check if we need this for QUIC + SSL_CTX_set_info_callback(ctx, SSLHandshakeInfoCallback); return true; } diff --git a/src/iocore/net/QUICNetVConnection.cc b/src/iocore/net/QUICNetVConnection.cc index d113ae593e1..81424e189aa 100644 --- a/src/iocore/net/QUICNetVConnection.cc +++ b/src/iocore/net/QUICNetVConnection.cc @@ -831,6 +831,18 @@ QUICNetVConnection::_get_tls_group() const } } +std::string +QUICNetVConnection::_get_tls_offered_signature_algorithms() const +{ + return SSLGetOfferedSignatureAlgorithms(this->_ssl); +} + +std::string +QUICNetVConnection::_get_tls_negotiated_signature_algorithm() const +{ + return SSLGetNegotiatedSignatureAlgorithm(this->_ssl); +} + int QUICNetVConnection::_verify_certificate(X509_STORE_CTX * /* ctx ATS_UNUSED */) { diff --git a/src/iocore/net/SSLNetVConnection.cc b/src/iocore/net/SSLNetVConnection.cc index 2a2ac30836c..52f7cd5865a 100644 --- a/src/iocore/net/SSLNetVConnection.cc +++ b/src/iocore/net/SSLNetVConnection.cc @@ -2186,6 +2186,18 @@ SSLNetVConnection::_get_tls_group() const } } +std::string +SSLNetVConnection::_get_tls_offered_signature_algorithms() const +{ + return SSLGetOfferedSignatureAlgorithms(ssl); +} + +std::string +SSLNetVConnection::_get_tls_negotiated_signature_algorithm() const +{ + return SSLGetNegotiatedSignatureAlgorithm(ssl); +} + int SSLNetVConnection::_verify_certificate(X509_STORE_CTX * /* ctx ATS_UNUSED */) { diff --git a/src/iocore/net/SSLUtils.cc b/src/iocore/net/SSLUtils.cc index e09df82dfe2..6b04cfaf538 100644 --- a/src/iocore/net/SSLUtils.cc +++ b/src/iocore/net/SSLUtils.cc @@ -36,6 +36,7 @@ #include "config/ssl_multicert.h" #include "iocore/net/SSLAPIHooks.h" #include "iocore/net/SSLDiags.h" +#include "iocore/net/TLSBasicSupport.h" #include "iocore/net/TLSSessionResumptionSupport.h" #include "records/RecHttp.h" #include "tscore/MatcherUtils.h" @@ -202,6 +203,30 @@ ssl_verify_client_callback(int preverify_ok, X509_STORE_CTX *ctx) return preverify_ok; } +static std::string +ssl_client_offered_signature_algorithms(TLSSNISupport::ClientHello &client_hello) +{ + uint8_t const *extension = nullptr; + size_t length = 0; + + if (client_hello.getExtension(TLSEXT_TYPE_signature_algorithms, &extension, &length) != 1 || length < 2) { + return {}; + } + + size_t const algorithms_length = static_cast(extension[0]) << 8 | extension[1]; + if (algorithms_length != length - 2 || algorithms_length % 2 != 0) { + return {}; + } + + std::vector algorithms; + algorithms.reserve(algorithms_length / 2); + for (size_t offset = 2; offset < length; offset += 2) { + algorithms.push_back(static_cast(extension[offset]) << 8 | extension[offset + 1]); + } + + return SSLFormatSignatureAlgorithms(algorithms); +} + #if HAVE_SSL_CTX_SET_CLIENT_HELLO_CB // Pausable callback static int @@ -216,6 +241,10 @@ ssl_client_hello_callback(const SSL_CLIENT_HELLO *client_hello) TLSSNISupport::ClientHello ch = {client_hello}; #endif + if (auto *tbs = TLSBasicSupport::getInstance(s); tbs != nullptr) { + tbs->capture_tls_offered_signature_algorithms(ssl_client_offered_signature_algorithms(ch)); + } + TLSSNISupport *snis = TLSSNISupport::getInstance(s); if (snis) { snis->on_client_hello(ch); @@ -258,6 +287,10 @@ ssl_cert_callback(SSL *ssl, [[maybe_unused]] void *arg) bool reenabled; int retval = 1; + if (auto *tbs = TLSBasicSupport::getInstance(ssl); tbs != nullptr) { + tbs->capture_tls_offered_signature_algorithms(); + } + // If we are in tunnel mode, don't select a cert. Pause! if (sslnetvc) { NetVConnection *netvc = reinterpret_cast(sslnetvc); @@ -1025,6 +1058,8 @@ ssl_callback_info(const SSL *ssl, int where, int ret) { Dbg(dbg_ctl_ssl_load, "ssl_callback_info ssl: %p, where: %d, ret: %d, State: %s", ssl, where, ret, SSL_state_string_long(ssl)); + SSLHandshakeInfoCallback(ssl, where, ret); + SSLNetVConnection *netvc = SSLNetVCAccess(ssl); if (!netvc || netvc->ssl != ssl) { @@ -2543,6 +2578,183 @@ SSLGetGroupName([[maybe_unused]] SSL *ssl) #endif // HAVE_SSL_GET0_GROUP_NAME } +namespace +{ +bool +is_grease_signature_algorithm(uint16_t algorithm) +{ + return (algorithm & 0x0f0f) == 0x0a0a; +} + +#if HAVE_SSL_GET_LOCAL_SIGNATURE_NIDS && HAVE_SSL_GET_SHARED_SIGALGS +bool +signature_algorithm_matches_private_key(SSL *ssl, uint16_t algorithm) +{ + EVP_PKEY *private_key = SSL_get_privatekey(ssl); + + if (private_key == nullptr) { + return false; + } + + int const key_type = EVP_PKEY_base_id(private_key); + + switch (algorithm) { + case 0x0804: // rsa_pss_rsae_sha256 + case 0x0805: // rsa_pss_rsae_sha384 + case 0x0806: // rsa_pss_rsae_sha512 + return key_type == EVP_PKEY_RSA; + case 0x0809: // rsa_pss_pss_sha256 + case 0x080a: // rsa_pss_pss_sha384 + case 0x080b: // rsa_pss_pss_sha512 + return key_type == EVP_PKEY_RSA_PSS; + default: + break; + } + +#if defined(TLS1_3_VERSION) + if (SSL_version(ssl) >= TLS1_3_VERSION && key_type == EVP_PKEY_EC) { + int expected_curve = NID_undef; + + switch (algorithm) { + case 0x0403: // ecdsa_secp256r1_sha256 + expected_curve = NID_X9_62_prime256v1; + break; + case 0x0503: // ecdsa_secp384r1_sha384 + expected_curve = NID_secp384r1; + break; + case 0x0603: // ecdsa_secp521r1_sha512 + expected_curve = NID_secp521r1; + break; + case 0x081a: // ecdsa_brainpoolP256r1tls13_sha256 + expected_curve = NID_brainpoolP256r1; + break; + case 0x081b: // ecdsa_brainpoolP384r1tls13_sha384 + expected_curve = NID_brainpoolP384r1; + break; + case 0x081c: // ecdsa_brainpoolP512r1tls13_sha512 + expected_curve = NID_brainpoolP512r1; + break; + default: + break; + } + + if (expected_curve != NID_undef) { + EC_KEY const *ec_key = EVP_PKEY_get0_EC_KEY(private_key); + + if (ec_key != nullptr) { + EC_GROUP const *group = EC_KEY_get0_group(ec_key); + return group != nullptr && EC_GROUP_get_curve_name(group) == expected_curve; + } + } + } +#endif + + return true; +} +#endif +} // namespace + +std::string +SSLFormatSignatureAlgorithms(swoc::MemSpan algorithms) +{ + std::string result; + + for (uint16_t const algorithm : algorithms) { + if (is_grease_signature_algorithm(algorithm)) { + continue; + } + if (!result.empty()) { + result.push_back('-'); + } + result.append(std::to_string(algorithm)); + } + + return result; +} + +std::string +SSLGetOfferedSignatureAlgorithms([[maybe_unused]] SSL *ssl) +{ +#if HAVE_SSL_GET0_PEER_VERIFY_ALGORITHMS + uint16_t const *algorithms = nullptr; + size_t const count = SSL_get0_peer_verify_algorithms(ssl, &algorithms); + + return SSLFormatSignatureAlgorithms({algorithms, count}); +#elif HAVE_SSL_GET_SIGALGS + int const count = SSL_get_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr, nullptr); + + if (count <= 0) { + return {}; + } + + std::vector algorithms; + algorithms.reserve(count); + for (int index = 0; index < count; ++index) { + unsigned char signature = 0; + unsigned char hash = 0; + + if (SSL_get_sigalgs(ssl, index, nullptr, nullptr, nullptr, &signature, &hash) > 0) { + algorithms.push_back(static_cast(hash) << 8 | signature); + } + } + + return SSLFormatSignatureAlgorithms(algorithms); +#else + return {}; +#endif +} + +std::string +SSLGetNegotiatedSignatureAlgorithm([[maybe_unused]] SSL *ssl) +{ +#if HAVE_SSL_GET_SIGNATURE_ALGORITHM_USED + uint16_t const algorithm = SSL_get_signature_algorithm_used(ssl); + + return algorithm == 0 ? "" : std::to_string(algorithm); +#elif HAVE_SSL_GET_LOCAL_SIGNATURE_NIDS && HAVE_SSL_GET_SHARED_SIGALGS + int signature_type = NID_undef; + int hash = NID_undef; + + if (SSL_get_signature_type_nid(ssl, &signature_type) != 1 || SSL_get_signature_nid(ssl, &hash) != 1) { + return {}; + } + + int const count = SSL_get_shared_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr, nullptr); + for (int index = 0; index < count; ++index) { + int shared_signature_type = NID_undef; + int shared_hash = NID_undef; + unsigned char signature = 0; + unsigned char raw_hash = 0; + + if (SSL_get_shared_sigalgs(ssl, index, &shared_signature_type, &shared_hash, nullptr, &signature, &raw_hash) <= 0 || + shared_signature_type != signature_type || shared_hash != hash) { + continue; + } + + uint16_t const algorithm = static_cast(raw_hash) << 8 | signature; + if (signature_algorithm_matches_private_key(ssl, algorithm)) { + return std::to_string(algorithm); + } + } + + return {}; +#else + return {}; +#endif +} + +void +SSLHandshakeInfoCallback(const SSL *ssl, int where, int /* ret */) +{ + if ((where & SSL_CB_HANDSHAKE_DONE) == 0) { + return; + } + + if (auto *tbs = TLSBasicSupport::getInstance(const_cast(ssl)); tbs != nullptr) { + tbs->capture_tls_negotiated_signature_algorithm(); + } +} + SSL_SESSION * SSLSessionDup(SSL_SESSION *sess) { diff --git a/src/iocore/net/TLSBasicSupport.cc b/src/iocore/net/TLSBasicSupport.cc index 897e4067419..2f3da68ab7c 100644 --- a/src/iocore/net/TLSBasicSupport.cc +++ b/src/iocore/net/TLSBasicSupport.cc @@ -75,6 +75,8 @@ TLSBasicSupport::clear() this->_tls_handshake_bytes_measured = false; this->_tls_handshake_bytes_in = 0; this->_tls_handshake_bytes_out = 0; + this->_tls_offered_signature_algorithms.reset(); + this->_tls_negotiated_signature_algorithm.reset(); } // Returns true only on the first call that reads from the BIOs, so callers @@ -170,6 +172,51 @@ TLSBasicSupport::get_tls_group() const return this->_get_tls_group(); } +std::string_view +TLSBasicSupport::get_tls_offered_signature_algorithms() const +{ + if (!this->_tls_offered_signature_algorithms.has_value()) { + this->_tls_offered_signature_algorithms = + this->_get_ssl_object() == nullptr ? "" : this->_get_tls_offered_signature_algorithms(); + } + + return *this->_tls_offered_signature_algorithms; +} + +std::string_view +TLSBasicSupport::get_tls_negotiated_signature_algorithm() const +{ + if (!this->_tls_negotiated_signature_algorithm.has_value()) { + this->_tls_negotiated_signature_algorithm = + this->_get_ssl_object() == nullptr ? "" : this->_get_tls_negotiated_signature_algorithm(); + } + + return *this->_tls_negotiated_signature_algorithm; +} + +void +TLSBasicSupport::capture_tls_offered_signature_algorithms() +{ + std::string offered = this->_get_ssl_object() == nullptr ? "" : this->_get_tls_offered_signature_algorithms(); + + if (!offered.empty() || !this->_tls_offered_signature_algorithms.has_value()) { + this->_tls_offered_signature_algorithms = std::move(offered); + } +} + +void +TLSBasicSupport::capture_tls_offered_signature_algorithms(std::string offered) +{ + this->_tls_offered_signature_algorithms = std::move(offered); +} + +void +TLSBasicSupport::capture_tls_negotiated_signature_algorithm() +{ + this->_tls_negotiated_signature_algorithm = + this->_get_ssl_object() == nullptr ? "" : this->_get_tls_negotiated_signature_algorithm(); +} + ink_hrtime TLSBasicSupport::get_tls_handshake_begin_time() const { diff --git a/src/iocore/net/unit_tests/test_SSLUtils.cc b/src/iocore/net/unit_tests/test_SSLUtils.cc new file mode 100644 index 00000000000..52e2eb1dc03 --- /dev/null +++ b/src/iocore/net/unit_tests/test_SSLUtils.cc @@ -0,0 +1,96 @@ +/** @file + + 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. + */ + +#include "../P_SSLUtils.h" + +#include "iocore/net/TLSBasicSupport.h" + +#include + +#include + +namespace +{ +class TestTLSBasicSupport : public TLSBasicSupport +{ +public: + std::string offered_signature_algorithms; + +protected: + SSL * + _get_ssl_object() const override + { + return reinterpret_cast(const_cast(this)); + } + + ssl_curve_id + _get_tls_curve() const override + { + return 0; + } + + std::string_view + _get_tls_group() const override + { + return {}; + } + + std::string + _get_tls_offered_signature_algorithms() const override + { + return offered_signature_algorithms; + } + + std::string + _get_tls_negotiated_signature_algorithm() const override + { + return {}; + } + + int + _verify_certificate(X509_STORE_CTX *) override + { + return 0; + } +}; +} // namespace + +TEST_CASE("TLS signature algorithms retain wire order and omit GREASE") +{ + std::array const algorithms{0x0403, 0x0a0a, 0x0804, 0x1a1a, 0xffff}; + + CHECK(SSLFormatSignatureAlgorithms(algorithms) == "1027-2052-65535"); +} + +TEST_CASE("TLS signature algorithms containing only GREASE are empty") +{ + std::array const algorithms{0x0a0a, 0x5a5a, 0xfafa}; + + CHECK(SSLFormatSignatureAlgorithms(algorithms).empty()); +} + +TEST_CASE("TLS signature algorithms preserve an earlier non-empty capture") +{ + TestTLSBasicSupport support; + + support.capture_tls_offered_signature_algorithms("1027-2052"); + support.capture_tls_offered_signature_algorithms(); + + CHECK(support.get_tls_offered_signature_algorithms() == "1027-2052"); +} diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc index 96b7a56798e..36199ebe70e 100644 --- a/src/proxy/logging/Log.cc +++ b/src/proxy/logging/Log.cc @@ -600,6 +600,16 @@ Log::init_fields() global_field_list.add(field, false); field_symbol_hash.emplace("cqssg", field); + field = new LogField("client_offered_signature_algorithms", "cqssig", LogField::Type::STRING, + &LogAccess::marshal_client_offered_signature_algorithms, &LogAccess::unmarshal_str); + global_field_list.add(field, false); + field_symbol_hash.emplace("cqssig", field); + + field = new LogField("client_negotiated_signature_algorithm", "cqssin", LogField::Type::STRING, + &LogAccess::marshal_client_negotiated_signature_algorithm, &LogAccess::unmarshal_str); + global_field_list.add(field, false); + field_symbol_hash.emplace("cqssin", field); + field = new LogField("client_sec_alpn", "cqssa", LogField::Type::STRING, &LogAccess::marshal_client_security_alpn, &LogAccess::unmarshal_str); global_field_list.add(field, false); diff --git a/src/proxy/logging/LogAccess.cc b/src/proxy/logging/LogAccess.cc index 6d0d2fe6641..14d7ac7b720 100644 --- a/src/proxy/logging/LogAccess.cc +++ b/src/proxy/logging/LogAccess.cc @@ -2452,6 +2452,34 @@ LogAccess::marshal_client_security_group(char *buf) return round_len; } +int +LogAccess::marshal_client_offered_signature_algorithms(char *buf) +{ + const char *offered_signature_algorithms = m_data->get_client_offered_signature_algorithms(); + const char *value = offered_signature_algorithms ? offered_signature_algorithms : DEFAULT_STR; + int round_len = LogAccess::padded_strlen(value); + + if (buf) { + marshal_str(buf, value, round_len); + } + + return round_len; +} + +int +LogAccess::marshal_client_negotiated_signature_algorithm(char *buf) +{ + const char *negotiated_signature_algorithm = m_data->get_client_negotiated_signature_algorithm(); + const char *value = negotiated_signature_algorithm ? negotiated_signature_algorithm : DEFAULT_STR; + int round_len = LogAccess::padded_strlen(value); + + if (buf) { + marshal_str(buf, value, round_len); + } + + return round_len; +} + int LogAccess::marshal_client_security_alpn(char *buf) { diff --git a/src/proxy/logging/TransactionLogData.cc b/src/proxy/logging/TransactionLogData.cc index fb2cf26b791..e9298a05714 100644 --- a/src/proxy/logging/TransactionLogData.cc +++ b/src/proxy/logging/TransactionLogData.cc @@ -701,6 +701,24 @@ TransactionLogData::get_client_security_group() const return nullptr; } +const char * +TransactionLogData::get_client_offered_signature_algorithms() const +{ + if (likely(m_http_sm != nullptr)) { + return m_http_sm->get_user_agent().get_client_offered_signature_algorithms(); + } + return nullptr; +} + +const char * +TransactionLogData::get_client_negotiated_signature_algorithm() const +{ + if (likely(m_http_sm != nullptr)) { + return m_http_sm->get_user_agent().get_client_negotiated_signature_algorithm(); + } + return nullptr; +} + int TransactionLogData::get_client_alpn_id() const { diff --git a/tests/gold_tests/tls/gold/tls_signature_algorithms.gold b/tests/gold_tests/tls/gold/tls_signature_algorithms.gold new file mode 100644 index 00000000000..f9bd982af5c --- /dev/null +++ b/tests/gold_tests/tls/gold/tls_signature_algorithms.gold @@ -0,0 +1,4 @@ +tls12-full TLSv1.2 1025 1025 0 +tls12-resumed TLSv1.2 1025 - 1 +tls13-full TLSv1.3 2052-1025 2052 0 +tls13-resumed TLSv1.3 2052-1025 - 1 diff --git a/tests/gold_tests/tls/tls_signature_algorithms.test.py b/tests/gold_tests/tls/tls_signature_algorithms.test.py new file mode 100644 index 00000000000..d16c2cb0cb7 --- /dev/null +++ b/tests/gold_tests/tls/tls_signature_algorithms.test.py @@ -0,0 +1,164 @@ +''' +Verify TLS signature-algorithm access log fields. +''' +# 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. + +import os + +Test.Summary = ''' +Verify offered and negotiated TLS signature-algorithm log fields. +''' + +Test.SkipUnless(Condition.HasOpenSSLVersion('1.1.1')) + + +class TestTLSSignatureAlgorithms: + '''Verify TLS signature-algorithm access log fields.''' + + _paths = ('/tls12-full', '/tls12-resumed', '/tls13-full', '/tls13-resumed') + + def __init__(self): + '''Configure the test processes and handshake runs.''' + self._server = self._configure_server() + self._ts = self._configure_traffic_server() + self._tls12_session = os.path.join(Test.RunDirectory, 'tls12.session') + self._tls13_session = os.path.join(Test.RunDirectory, 'tls13.session') + + self._add_handshake_run( + 'Log a full TLS 1.2 handshake', + '/tls12-full', + '-tls1_2', + 'rsa_pkcs1_sha256', + f'-sess_out {self._tls12_session}', + start_processes=True) + self._add_handshake_run( + 'Log a resumed TLS 1.2 handshake', '/tls12-resumed', '-tls1_2', 'rsa_pkcs1_sha256', f'-sess_in {self._tls12_session}') + self._add_handshake_run( + 'Log a full TLS 1.3 handshake', '/tls13-full', '-tls1_3', 'rsa_pss_rsae_sha256:rsa_pkcs1_sha256', + f'-sess_out {self._tls13_session}') + self._add_handshake_run( + 'Log a resumed TLS 1.3 handshake', + '/tls13-resumed', + '-tls1_3', + 'rsa_pss_rsae_sha256:rsa_pkcs1_sha256', + f'-sess_in {self._tls13_session}', + keep_processes_running=False) + + def _configure_server(self) -> 'Process': + '''Configure the origin server. + + :return: The origin server process. + ''' + server = Test.MakeOriginServer('server') + for path in self._paths: + request = { + 'headers': f'GET {path} HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n', + 'timestamp': '1469733493.993', + 'body': '', + } + response = { + 'headers': 'HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n', + 'timestamp': '1469733493.993', + 'body': '', + } + server.addResponse('sessionlog.json', request, response) + return server + + def _configure_traffic_server(self) -> 'Process': + '''Configure Traffic Server. + + :return: The Traffic Server process. + ''' + ts = Test.MakeATSProcess('ts', enable_tls=True) + ts.addSSLfile('ssl/server.pem') + ts.addSSLfile('ssl/server.key') + ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{self._server.Variables.Port}') + ts.Disk.ssl_multicert_yaml.AddLines( + ''' +ssl_multicert: + - dest_ip: "*" + ssl_cert_name: server.pem + ssl_key_name: server.key +'''.split('\n')) + ts.Disk.records_config.update( + { + 'proxy.config.ssl.server.cert.path': ts.Variables.SSLDir, + 'proxy.config.ssl.server.private_key.path': ts.Variables.SSLDir, + 'proxy.config.ssl.server.session_ticket.enable': 1, + 'proxy.config.ssl.server.session_ticket.number': 2, + }) + ts.Disk.logging_yaml.AddLines( + ''' +logging: + formats: + - name: tls_signature_algorithms + format: '% % % % %' + logs: + - filename: tls_signature_algorithms + format: tls_signature_algorithms +'''.split('\n')) + log_path = os.path.join(ts.Variables.LOGDIR, 'tls_signature_algorithms.log') + Test.Disk.File(log_path, exists=True, content='gold/tls_signature_algorithms.gold') + return ts + + def _s_client_command(self, path: str, tls_option: str, sigalgs: str, session_option: str) -> str: + '''Build an OpenSSL client command for a handshake run. + + :param path: The request path. + :param tls_option: The option selecting the TLS version. + :param sigalgs: The signature algorithms to offer. + :param session_option: The option for reading or writing a session. + :return: The OpenSSL client command. + ''' + request = f'GET {path} HTTP/1.1\\r\\nHost: example.com\\r\\nConnection: close\\r\\n\\r\\n' + return ( + f'printf "{request}" | openssl s_client -quiet -connect 127.0.0.1:{self._ts.Variables.ssl_port} ' + f'-servername example.com {tls_option} -sigalgs {sigalgs} {session_option}') + + def _add_handshake_run( + self, + description: str, + path: str, + tls_option: str, + sigalgs: str, + session_option: str, + start_processes: bool = False, + keep_processes_running: bool = True) -> 'TestRun': + '''Add a TLS handshake test run. + + :param description: The test run description. + :param path: The request path. + :param tls_option: The option selecting the TLS version. + :param sigalgs: The signature algorithms to offer. + :param session_option: The option for reading or writing a session. + :param start_processes: Whether this run starts the server processes. + :param keep_processes_running: Whether the server processes remain running afterward. + :return: The configured TestRun. + ''' + tr = Test.AddTestRun(description) + tr.Processes.Default.Command = self._s_client_command(path, tls_option, sigalgs, session_option) + tr.Processes.Default.ReturnCode = 0 + if start_processes: + tr.Processes.Default.StartBefore(self._server) + tr.Processes.Default.StartBefore(self._ts) + if keep_processes_running: + tr.StillRunningAfter = self._server + tr.StillRunningAfter += self._ts + return tr + + +TestTLSSignatureAlgorithms()