Client: cpp
matchName(), which backs DefaultClientAccessManager, honoured a wildcard in
the leftmost label of a certificate name even when no label followed it, so
a dNSName or common name of "*", "f*" or "*." matched every single-label
host name. A wildcard stands in for the leftmost label of a longer name
(RFC 6125 6.4.3); a pattern whose wildcard label has no label after it no
longer matches.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Client: cpp
ASN1_STRING_to_UTF8() returns a negative length and leaves its output
pointer untouched for a commonName whose string type it cannot convert.
The fallback loop now scopes that pointer to each iteration and consults
the length before the name is used or released.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Client: cpp,c_glib
OpenSSL 4.0 removes SSLv3_method(), TLSv1_method(), TLSv1_1_method(),
TLSv1_2_method(), ERR_remove_state() and ASN1_STRING_data(), and returns
const pointers from X509_get_subject_name(), X509_NAME_get_entry() and
X509_NAME_ENTRY_get_data(). Neither binding compiles against it.
The per-version methods are replaced by TLS_method() with the protocol
window pinned through SSL_CTX_set_min_proto_version() and
SSL_CTX_set_max_proto_version(), which is OpenSSL's own documented
migration and reproduces the old methods exactly: TLSv1_2_method() meant
TLS 1.2 only, and min == max == TLS1_2_VERSION means the same. No enum
value is dropped, so SSLv3, TLSv1_0, TLSv1_1, TLSv1_2 and LATEST keep
working on 4.0 rather than starting to throw "Unknown protocol".
The replacement is selected from 1.1.0 onwards rather than only on 4.0,
because no CI configuration builds against 4.0: gating it there would ship
a code path that nothing ever compiles, let alone runs. At >= 1.1.0 the
existing SSL matrix tests cover it on the OpenSSL 3.x that CI does have.
The same reasoning applies to the const qualifiers, whose consumers have
all taken const since 1.1.0. LibreSSL is excluded throughout: it reports
OPENSSL_VERSION_NUMBER as 0x20000000L but keeps the pre-1.1.0 signatures,
so it stays on the path it compiles today.
SSLTLS is untouched. SSLv23_method() is not removed in 4.0 -- it is an
unconditional #define for TLS_method() -- so that arm needed no change,
and the protocol floor for it still comes from the SSL_CTX_set_options()
call below, not from a version window.
SSLv3 keeps SSLv3_method() below 4.0, which is the only release that
removes it, rather than becoming a TLS_method() window like the others:
SSL_CTX_set_min_proto_version() consults the security level and would
refuse SSL3_VERSION at the default one, where the method function does not,
and the context is built before TSSLSocketFactory::ciphers() lowers it. On
4.0 the protocol is gone outright and the arm is compiled out.
ERR_remove_state() has been an empty stub since 1.1.0, so in c_glib it is
gated to where it still does something: before 1.1.0, and on LibreSSL,
which implements it and has never shipped OPENSSL_thread_stop(). The C++
binding gets the matching guard so LibreSSL reaches the ERR_remove_state()
arm instead of losing per-thread cleanup on both paths.
Tests:
- SecurityTest gains explicit_protocol_version_window, which asserts the
min/max window per enum value instead of merely that a context was
created -- the latter passes on a remapping that silently leaves the
floor to the library default. Proven two-state: with the window not
pinned it fails 8 assertions, LATEST among them, reporting floor 0.
It also asserts that an unavailable SSLv3 is reported rather than
quietly satisfied with another version.
- Both matrix tests skip SSLv3 on 4.0. OPENSSL_NO_SSL3 is not defined
there, so without this they try SSLv3 and time out.
- testtransportsslsocket gains the same two cases for c_glib.
- TSSLSocketMatchNameTest built its subject name through
X509_get_subject_name(), which is const as of 4.0, then mutated it; it
now builds a separate X509_NAME. It also had no SIGPIPE handler while
deliberately provoking rejected handshakes, so it died with status 141
in 6 of 20 runs before this change and 11 of 20 after -- the allocation
pattern shifts the race. It is ignored now, as SecurityTest already
does, and 25 of 25 runs pass.
Verified by building and running lib/cpp and lib/c_glib against three
libraries: the distribution OpenSSL 3.0.2, a from-source 4.0.2, and a
from-source 3.0.2 configured with enable-ssl3 so that the one otherwise
uncompiled arm is covered. All 16 matrix cells pass identically on the
first two, and all 25 on the third -- cell for cell what master produces
there. Unpatched master fails to compile against 4.0.2 with 7 errors in
C++ and 5 in c_glib.
Co-Authored-By: Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
Co-Authored-By: Jaipaul Cheernam <jaipaul.cheernam@est.tech>
Co-Authored-By: Brad Smith <brad@comstyle.com>
Co-Authored-By: loqs <7595223+loqs@users.noreply.github.com>
Co-Authored-By: Finn R. Gärtner <65015656+FinnRG@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Client: cpp
TSSLSocket::authorize() walks the subjectAltName entries and then falls
through to commonName whenever no entry answered ALLOW. DefaultClientAccess
Manager::verify returns SKIP for a name that does not match, not DENY, so
"the certificate names other hosts" and "the certificate names no hosts"
arrive at that fallthrough as the same state, and commonName gets to answer
for a certificate that already had its say.
RFC 6125 6.4.4 and RFC 9525 6.3 put it the other way round: once a dNSName
is present it is the identity, and commonName is for certificates that
carry none. authorize() now records whether a dNSName was seen at all,
separately from whether one matched, and consults commonName only when none
was.
The test drives real handshakes against certificates built in the test
rather than fixtures, because what is under test is the relationship
between two fields of one certificate. Three cases: a matching
subjectAltName is accepted, a non-matching one is refused even though the
commonName would have matched, and a certificate with no subjectAltName is
still served by its commonName. The middle one fails against the unmodified
library.
lib/cpp/README.md documented the old rule -- "if all subjectAltName extensions
were skipped, the common name field would be checked" -- and got two details of
it wrong besides: the common name reaches the application through (b), not (c),
and "data" is the common name, not an IP address. That paragraph is rewritten,
and the change is written up under 0.25.0 in Breaking Changes.
Two things fell out of writing it, both fixed here:
- TSSLSocketMatchNameTest was in Makefile.am but not in CMakeLists.txt, so
ctest never ran it. It is wired into both now.
- TSSLSocket::open() performs no handshake -- it is deferred to the first
I/O through checkHandshake() -- so a test that opens and closes proves
nothing about the certificate. The first version of this test did exactly
that and passed against unfixed code. The client now reads a byte, and
the comment on connectAccepts() says why.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Client: cpp
Wildcards in certificate names must appear only in the leftmost
label per RFC 6125 §6.4.3. Add the check to matchName() and
cover it with a dedicated Boost.Test suite.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>