Conversation
There was a problem hiding this comment.
Pull request overview
Adds new access-log fields to expose TLS signature-scheme information (client-offered list and ATS-selected scheme), improving visibility for certificate selection / client-compatibility analysis across TLS 1.2/1.3 and resumed sessions.
Changes:
- Introduces two new logging fields (
cqssig,cqssin) wired throughTransactionLogData/LogAccess/Logand documented in the admin guide. - Captures and formats signature-scheme data (GREASE-filtered, dash-separated decimal code points) for both TCP-TLS and QUIC paths.
- Adds a unit test for GREASE filtering/formatting and a new gold test validating the access-log output for full vs resumed TLS 1.2/1.3 handshakes.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/gold_tests/tls/tls_signature_algorithms.test.py | New gold test driving OpenSSL handshakes and validating new log fields. |
| tests/gold_tests/tls/gold/tls_signature_algorithms.gold | Expected access-log output for the new fields across handshake modes/versions. |
| src/proxy/logging/TransactionLogData.cc | Exposes new TLS signature data from HttpUserAgent to logging. |
| src/proxy/logging/LogAccess.cc | Adds marshalling for the new signature-algorithm log fields. |
| src/proxy/logging/Log.cc | Registers cqssig / cqssin logging field symbols. |
| src/iocore/net/unit_tests/test_SSLUtils.cc | Unit tests for GREASE omission and wire-order formatting. |
| src/iocore/net/TLSBasicSupport.cc | Adds capture/cache plumbing for offered + negotiated signature scheme values. |
| src/iocore/net/SSLUtils.cc | Implements extraction/formatting and hooks capture into TLS callbacks. |
| src/iocore/net/SSLNetVConnection.cc | Provides SSLNetVConnection implementations for the new TLSBasicSupport virtuals. |
| src/iocore/net/QUICNetVConnection.cc | Provides QUICNetVConnection implementations for the new TLSBasicSupport virtuals. |
| src/iocore/net/QUICMultiCertConfigLoader.cc | Enables handshake info callback for QUIC to capture negotiated scheme. |
| src/iocore/net/P_SSLUtils.h | Declares new signature-algorithm helper APIs. |
| src/iocore/net/P_SSLNetVConnection.h | Declares new TLSBasicSupport virtual overrides for SSLNetVConnection. |
| src/iocore/net/P_QUICNetVConnection.h | Declares new TLSBasicSupport virtual overrides for QUICNetVConnection. |
| src/iocore/net/CMakeLists.txt | Adds the new SSLUtils unit test to the build. |
| include/tscore/ink_config.h.cmake.in | Adds feature-detection macros for TLS signature API availability. |
| include/proxy/logging/TransactionLogData.h | Declares new TransactionLogData accessors for signature fields. |
| include/proxy/logging/LogAccess.h | Declares new marshal functions for signature fields. |
| include/proxy/http/HttpUserAgent.h | Stores signature-scheme strings in ClientConnectionInfo and exposes getters. |
| include/iocore/net/TLSBasicSupport.h | Adds APIs/virtuals and optional caches for signature scheme capture. |
| doc/admin-guide/logging/formatting.en.rst | Documents cqssig/cqssin. |
| CMakeLists.txt | Adds configure-time checks for OpenSSL/BoringSSL signature API symbols. |
f685eb6 to
ee1564d
Compare
ee1564d to
8cfd649
Compare
b104445 to
52ea149
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
src/iocore/net/SSLUtils.cc:231
ssl_client_offered_signature_algorithms()runs on every TLS handshake (ClientHello) but currently allocates and populates astd::vector<uint16_t>just to immediately re-walk it for formatting. Consider formatting directly while iterating the extension bytes (including GREASE filtering) to avoid the extra allocation and pass over the data. This keeps per-handshake overhead lower on busy TLS termination nodes.
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<size_t>(extension[0]) << 8 | extension[1];
if (algorithms_length != length - 2 || algorithms_length % 2 != 0) {
return {};
}
std::vector<uint16_t> algorithms;
algorithms.reserve(algorithms_length / 2);
for (size_t offset = 2; offset < length; offset += 2) {
algorithms.push_back(static_cast<uint16_t>(extension[offset]) << 8 | extension[offset + 1]);
}
return SSLFormatSignatureAlgorithms(algorithms);
}
tests/gold_tests/tls/tls_signature_algorithms.test.py:118
- Using the HTTP request string as the
printfformat (printf \"{request}\") can be fragile: any%characters in the request (now or in future edits) would be treated as format directives, and escape handling for\\r/\\ncan vary across shells. Prefer passing the request as an argument toprintf(so it’s not a format string) and using an explicit format (e.g., one that interprets backslash escapes) to make the command more robust/portable.
def _s_client_command(self, path: str, tls_option: str, sigalgs: str, session_option: str) -> str:
tests/gold_tests/tls/tls_signature_algorithms.test.py:129
- Using the HTTP request string as the
printfformat (printf \"{request}\") can be fragile: any%characters in the request (now or in future edits) would be treated as format directives, and escape handling for\\r/\\ncan vary across shells. Prefer passing the request as an argument toprintf(so it’s not a format string) and using an explicit format (e.g., one that interprets backslash escapes) to make the command more robust/portable.
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}')
cmcfarlen
left a comment
There was a problem hiding this comment.
Review
Two new log fields (cqssig offered list, cqssin used algorithm) captured from the ClientHello, the cert callback, and a handshake-done info callback.
What's right
- The GREASE filter is correct, which is the easiest thing here to get wrong.
(a & 0x0f0f) == 0x0a0a && (a >> 12) == ((a >> 4) & 0x0f)matches exactly RFC 8701's0xNANAfamily — I checked0x0a0a/0x1a1a/0xfafa(filtered) against0x1a2aand0xffff(retained), and the unit tests pin all of those, including the non-obvious reserved-but-not-GREASE case. - Wire order is preserved, and the TLS 1.2 code-point reconstruction
hash << 8 | signaturematches theSignatureAndHashAlgorithmencoding, soSSL_get_sigalgs's raw out-params compose correctly. - Resumption semantics are coherent: no CertificateVerify → empty →
-, documented, and the gold file exercises full and resumed at both 1.2 and 1.3.
Main finding: on OpenSSL, cqssin is a heuristic, and that's the path nearly everyone runs
SSL_get_signature_algorithm_used and SSL_get0_peer_verify_algorithms are BoringSSL APIs. I checked the OpenSSL 3.6 headers — both are absent; only SSL_get_sigalgs, SSL_get_shared_sigalgs, SSL_get_signature_nid and SSL_get_signature_type_nid exist. So every OpenSSL build takes the #elif branch, which reconstructs the code point by scanning shared sigalgs for a (signature-type NID, hash NID) match and disambiguating via signature_algorithm_matches_private_key().
The logic is coherent — the RSA vs RSA-PSS ambiguity is genuinely why the key-type check has to exist — but it is inference rather than the value the library actually used, and it returns the first match. Two consequences I'd like addressed:
- The docs promise "the TLS signature scheme used by |TS|" with no hint that on the common build it is derived. An operator comparing an OpenSSL box against a BoringSSL box could see different values for identical handshakes. A sentence in
formatting.en.rstand a comment above the#elifwould cover it. - The exact path is the one CI's OpenSSL jobs never execute, so the primary implementation is only covered wherever BoringSSL/quiche builds run.
Introduces the tree's only deprecated EC_KEY usage
signature_algorithm_matches_private_key() uses EVP_PKEY_get0_EC_KEY, EC_KEY_get0_group and EC_GROUP_get_curve_name, and that block compiles precisely on the OpenSSL path. Grepping src/, include/ and plugins/, these appear nowhere else in the tree. It builds today only because of OPENSSL_API_COMPAT=10002, and it lands right after #13476 ("Resolve OpenSSL 4.0 build issues"). EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, ...) gets the same answer without the deprecated handle.
Test coverage gaps
tests/gold_tests/tls/ssl/server.pemisrsaEncryption, so the entire EC branch — including the six-entry ECDSA/brainpool table — is never executed by any test, unit or auto. That branch is where the reconstruction is most likely to be wrong, so an ECDSA cert case, or a unit test oversignature_algorithm_matches_private_key, would be worth more than the four existing gold lines.- Log-flush flake risk: the gold compares a four-line log with no
max_secs_per_buffer/flush record and no wait run, relying on the shutdown flush and on the four entries landing in request order. There's a run of recent commits doing nothing but fixing that exact class of flake (stale_response,jax_fingerprint,slice_prefetch,pqsi-pqsplog order, plus "Harden timing-sensitive AuTests"). Cheap to harden now.
Undocumented behavior change: QUIC info callback
QUICMultiCertConfigLoader::_set_info_callback went from an explicit no-op (// Disabled for now / TODO Check if we need this for QUIC) to installing SSLHandshakeInfoCallback. That's a reasonable prerequisite, but it enables an info callback on QUIC contexts where there was none, isn't mentioned in the description, and no test covers QUIC for these fields. Worth a line in the description at minimum.
Smaller items
- The field symbols break the established shape. The existing TLS fields are five characters —
cqssl,cqssv,cqssc,cqssu,cqssg,cqssa— and these add six-charactercqssig/cqssin, wherecqssinreads like "SSL in" rather than "signature negotiated". Log symbols are permanent once shipped, so one round of bikeshedding now seems worth it. - Two more
std::strings per transaction inClientConnectionInfo. A realistic offered list (8 schemes, roughly 40 characters) exceeds SSO, so that's an extra heap allocation per TLS transaction. Consistent with howsecurity_groupalready works, so not a blocker — just noting this list is longer than its neighbors. - The two
capture_tls_offered_signature_algorithmsoverloads differ subtly: the argument-taking one overwrites, the no-arg one preserves a non-empty earlier value. Tested, but distinct names would document themselves. m_conn_info.offered_signature_algorithms = '-';assigns achar. It matches the adjacentsecurity_groupline so it's fine, though"-"reads better.
Requested changes
Requesting changes on three items: the doc caveat plus code comment for the derived cqssin, replacing the deprecated EC_KEY calls, and either an ECDSA test case or a unit test over the reconstruction. The rest is discretionary.
52ea149 to
c39fc35
Compare
c39fc35 to
7397e16
Compare
|
Updated cqssin documentation to explain its derived behavior. OpenSSL 3 now uses provider-based curve lookup, the fallback requires a unique compatible scheme, and the AuTest includes a P-256 ECDSA TLS 1.3 handshake. |
7397e16 to
76ae3f1
Compare
Certificate selection decisions require visibility into the signature algorithms clients offer, but access logs currently expose only TLS cipher and group details. This makes it difficult to identify clients that depend on a particular certificate signature type. This patch adds offered and negotiated signature-algorithm log fields with precise GREASE filtering and a defensive guard for resumed handshakes. It handles OpenSSL fallback ambiguity, documents derivation semantics, and covers capture precedence, certificate disambiguation, and TLS 1.2 and TLS 1.3 full and resumed handshakes. Co-authored-by: GPT-6 Astra Medium
76ae3f1 to
ca54c63
Compare
Certificate selection decisions require visibility into the signature
algorithms clients offer, but access logs currently expose only TLS
cipher and group details. This makes it difficult to identify clients
that depend on a particular certificate signature type.
This patch adds offered and negotiated signature-algorithm log fields
with precise GREASE filtering and a defensive guard for resumed
handshakes. It handles OpenSSL fallback ambiguity, documents derivation
semantics, and covers capture precedence, certificate disambiguation,
and TLS 1.2 and TLS 1.3 full and resumed handshakes.
Co-authored-by: GPT-6 Astra Medium