Skip to content

Backport OpenSSL 3 support to openSUSE Python 3.6 - #4

Open
mcepl wants to merge 9 commits into
opensuse_3.6from
opensuse_3.6-openssl_3.2
Open

mcepl wants to merge 9 commits into
opensuse_3.6from
opensuse_3.6-openssl_3.2

Conversation

@mcepl

@mcepl mcepl commented Sep 19, 2026

Copy link
Copy Markdown

Summary

Backport SSL and hashlib compatibility changes for OpenSSL 3, together with supporting CPython runtime fixes and build updates.

Changes

  • Add version-specific SSL error tables for OpenSSL 1.1.1 and 3.x.
  • Avoid the deprecated ERR_func_error_string() API on OpenSSL 3.
  • Handle repeated password-callback invocations and update certificate-loading error handling.
  • Enable and expose OP_IGNORE_UNEXPECTED_EOF to preserve the earlier OpenSSL EOF behavior.
  • Update SSL tests for newer protocol defaults, certificate formatting, and TLS session behavior.
  • Add hash-availability test helpers and update OpenSSL test tooling.
  • Remove the OpenSSL version-header parsing gate that prevents detection of usable OpenSSL 3 hashlib support.
  • Add SSL error headers to extension build dependencies and fall back to system expat when bundled sources are absent.
  • Backport allocator and GC-header alignment fixes, restore PyExc_RecursionErrorInst, and move the hashtable implementation into Python/.
  • Adapt marshal and tracemalloc to the replacement hashtable API.
  • Reformat setup.py and include supporting configure fixes.

Validation

The following suites passed on Linux x86-64 with GCC 16.2.0 and OpenSSL 3.5.3:

  test_ssl
  test_hashlib
  test_tracemalloc
  test_marshal
  test_gc
  test_exceptions

Testing used a clean archive of the branch. A temporary rename of the existing sinpi helper was necessary to avoid a conflict with the host C library. That workaround is not part of this branch.

OpenSSL 3.2, FIPS configurations, Windows builds, and the full CPython test suite have not been validated.

Outstanding review findings

  • A failed copy of a nonzero tracemalloc domain can crash snapshot creation instead of raising MemoryError. Debugger fault injection confirmed this.
  • Windows project files still reference the deleted Modules/hashtable.c.
  • Tracemalloc memory accounting omits the newly separate trace allocations.
  • The GC-header size change needs a binary-extension compatibility assessment before release.

@mcepl
mcepl force-pushed the opensuse_3.6-openssl_3.2 branch from 93882ca to de22aa2 Compare September 20, 2026 20:04
@mcepl
mcepl changed the base branch from opensuse_3.6 to 3.6 September 20, 2026 20:09
@mcepl
mcepl force-pushed the opensuse_3.6-openssl_3.2 branch 4 times, most recently from 98cfa2e to e0d2c7a Compare September 25, 2026 16:42
Backports the upstream CPython changes porting _ssl and _hashlib to
OpenSSL 1.1.1/3.x:
 - Modules/_ssl.c and Modules/_hashopenssl.c changes
 - version-specific error tables _ssl_data_111.h / _ssl_data_300.h
 - rewritten Tools/ssl/make_ssl_data.py, Tools/ssl/multissltests.py
 - OP_IGNORE_UNEXPECTED_EOF, ERR_func_error_string deprecation guard,
   load_verify_locations fixes, OpenSSL 3.0 password-callback fix
 - test and documentation updates plus the accompanying Misc/NEWS.d
   fragments

Patch: python36-OpenSSL-32.patch
Newer compilers (GCC 14, Clang 16) reject implicit function
declarations and implicit int by default, which makes some configure
run tests fail and silently change the detected configuration:

- PTHREAD_SCOPE_SYSTEM check: declare "int main()" (pythongh-99086).
- broken nice() check: include <stdlib.h> and <unistd.h>.

Patch: python36-OpenSSL-32-no-implicit.patch
Introduce Modules/_ssl_compat.h, included by both _ssl.c and
_hashopenssl.c, as the single place for OpenSSL/LibreSSL version
detection and API shims.

- Define PY_OPENSSL_3_API, PY_OPENSSL_1_1_API, PY_OPENSSL_1_1,
  PY_OPENSSL_1_1_1, PY_OPENSSL_3_3 and PY_OPENSSL_PRE_1_1, and refuse
  to build against OpenSSL older than 1.0.2.
- Provide modern (1.1.0/3.0) names as shims over the old API only when
  building against older libraries (OpenSSL_version*, EVP_MD_CTX_new/free,
  ASN1_STRING_get0_data, X509_get0_notBefore/After,
  SSL_get1_peer_certificate, TLS_*method). With OpenSSL 3 nothing maps
  onto deprecated functions.
- Move the pre-1.1 struct accessor shims out of _ssl.c into the header.
- Replace OPENSSL_VERSION_1_1/OPENSSL_VERSION_3_3 in _ssl.c with the new
  macros.
- Fix _hashopenssl.c: its init guard tested OPENSSL_VERSION_1_1, which
  was never defined there, so OPENSSL_add_all_algorithms_noconf() and
  ERR_load_crypto_strings() ran even with OpenSSL 3. They now only run
  for pre-1.1 libraries.
- Only silence -Wdeprecated-declarations in _ssl.c for pre-3.0 builds,
  so remaining uses of deprecated API are visible with OpenSSL 3.

Patch: python36-OpenSSL-32-ssl-compat-h.patch
Replace deprecated calls on the OpenSSL 3 build path, keeping the old
code for pre-1.1/pre-3.0 builds behind version checks:

- ERR_get_state() is only called for pre-1.1 (error state is
  initialised automatically since 1.1.0).
- ASN1_STRING_data -> ASN1_STRING_get0_data, X509_get_notBefore/After
  -> X509_get0_notBefore/After, SSL_get_peer_certificate ->
  SSL_get1_peer_certificate (shims in _ssl_compat.h for old libraries).
- SSLeay()/SSLeay_version() -> OpenSSL_version_num()/OpenSSL_version().
  Drop the runtime CVE-2014-0198 check around SSL_MODE_RELEASE_BUFFERS:
  all affected versions are below the supported minimum (1.0.2).
- PROTOCOL_TLSv1/TLSv1_1/TLSv1_2/SSLv3: with the 1.1 API use
  TLS_method() and pin min/max protocol version instead of the
  deprecated version-specific methods.
- load_dh_params(): with OpenSSL 3 read parameters through
  PEM_read_bio_Parameters_ex() and install them with
  SSL_CTX_set0_tmp_dh_pkey(); reject non-DH files. Also fix both code
  paths returning None with an exception set when setting the
  parameters failed, and leaking nothing on error.
- set_ecdh_curve(): with OpenSSL 3 use SSL_CTX_set1_groups() instead of
  EC_KEY + SSL_CTX_set_tmp_ecdh().
- RAND_pseudo_bytes(): use RAND_bytes() with the 1.1 API.
- SSLSession.time: use Y2038-safe SSL_SESSION_get_time_ex() on 3.3+.

_ssl.c and _hashopenssl.c now compile against OpenSSL 3.5 with
-DOPENSSL_API_COMPAT=0x30000000L -DOPENSSL_NO_DEPRECATED
-Werror=deprecated-declarations.

Patch: python36-OpenSSL-32-3-only.patch
- Add py_digest_by_name()/PY_EVP_MD_free(). With OpenSSL 3 digests are
  obtained with EVP_MD_fetch() from the default library context, so
  provider properties (e.g. FIPS) apply and no legacy EVP_MD objects
  are used. Legacy aliases such as "RSA-SHA256", which are still
  reported in openssl_md_meth_names, are resolved to their canonical
  name before fetching. Older libraries keep using
  EVP_get_digestbyname(). Fetched digests are released after
  EVP_DigestInit()/PKCS5_PBKDF2_HMAC(), the context holds its own
  reference.
- Check the EVP_MD_CTX_new() result in the named constructors
  (openssl_md5() & co.) and do the digest lookup only once.
- Check the EVP_MD_CTX_copy() result in EVPnew().
- INIT_CONSTRUCTOR_CONSTANTS: fail module init if the name object
  cannot be created, and drop the stray semicolon after while (0).

test_ssl: with OpenSSL 3 load_dh_params() now reads parameters through
the OSSL_DECODER API, which reports errors from that library instead of
PEM/NO_START_LINE; adjust test_lib_reason accordingly.

Patch: python36-OpenSSL-32-3-digests.patch
Version checks:
- All checks for OpenSSL < 1.0.2 are now always true (1.0.2 is the
  enforced minimum), so resolve them: TLSv1.1/1.2 support,
  SSL_CTX_clear_options(), cipher_to_dict()/get_ciphers(),
  CRYPTO_THREADID, and the PBKDF2 availability check.
- The SNI/IP address check tested 0x10200000L, a version that never
  existed (meant 1.0.2), so OpenSSL 1.0.2 and 1.1.x silently used the
  inet_pton() fallback. a2i_IPADDRESS() is available in every supported
  version; use it unconditionally.
- Select the error-code table and the scrypt/ERR_func_error_string code
  with the _ssl_compat.h macros; LibreSSL explicitly uses _ssl_data.h.
- Regenerate the Argument Clinic output accordingly.

Error handling:
- Remove the no-op SSL_R_CERTIFICATE_VERIFY_FAILED block from the
  SSL_ERROR_SYSCALL branch of PySSL_SetError(): type already defaults to
  SSLError and verification failures are reported as SSL_ERROR_SSL.
  Instead, in the SSL_ERROR_SSL branch append the X509 verification
  reason ("certificate verify failed: unable to get local issuer
  certificate"), the same message format as Python 3.7+.
- _add_ca_certs(), _setSSLError(): keep OpenSSL error codes in unsigned
  long. With OpenSSL 3 system errors have bit 31 set and were mangled by
  the conversion to int before being decoded.

Patch: python36-OpenSSL-32-obsolete-version-check.patch
- Fix the OpenSSL minimum version check. The regex could not parse
  OpenSSL 3's opensslv.h, where OPENSSL_VERSION_NUMBER is an expression
  of OPENSSL_VERSION_MAJOR/MINOR/PATCH, so _hashlib was considered too
  old. The new _detect_openssl_version() understands both formats (and
  LibreSSL). The minimum is now 1.0.2, matching Modules/_ssl_compat.h,
  and it applies to both _ssl and _hashlib (previously _ssl was built
  with any OpenSSL).
- Add _ssl_compat.h and the _ssl_data*.h error tables to the depends
  lists of _ssl and _hashlib.
- pyexpat: fall back to the system libexpat when the bundled
  Modules/expat directory has been removed from the tree.

Patch: python36-OpenSSL-32-setup-py-version-detection.patch
test_hashlib:
- Restore test_refleaks_in_hash___init__ and the pbkdf2_hmac argument
  validation assertions, which had been deleted, and go back to the
  original c_hashlib/py_hashlib module names.
- Drop the imports of HASHXOF and get_fips_mode from _hashlib (they do
  not exist in 3.6, so the ImportError fallback always won and FIPS
  mode was never detected) and the PY_BUILTIN_HASHLIB_HASHES config
  variable (also 3.9+ only). Detect FIPS mode from
  /proc/sys/crypto/fips_enabled instead.
- The "unsupported hash type" message is stable again, as _hashlib no
  longer leaks OpenSSL 3 fetch errors into it.

test_ssl:
- Replace the unconditional "OpenSSL 3 effectively disables TLS < 1.2"
  skip, and the removal of TLSv1/TLSv1.1 from test_echo, with
  seclevel_workaround() (as in 3.10): with OpenSSL 3, lower the
  security level to 0 for connections involving TLS < 1.2. With
  OPENSSL_CONF=/dev/null all legacy protocol tests now run and pass.
- skip_if_openssl_cnf_minprotocol_gt_tls11 now follows .include
  directives and recognises "TLS.MinProtocol", as used by system-wide
  crypto policies (openSUSE, Fedora).
- clean_OpenSSL30_san(): decide by the runtime library version.
- Fix the OPENSSL_VERSION_NUMBER upper bound comment/value (< 4.0) and
  drop the unused PY_SSL_DEFAULT_CIPHERS/sysconfig import.

test_ftplib: disable TLS 1.3 on the dummy TLS server (bpo-32947, as in
3.7). With TLS 1.3 test_check_hostname hung, because the server only
learns about the client rejecting the certificate after the handshake.

test.support.hashlib_helper: drop the unused usedforsecurity parameter.

Patch: python36-OpenSSL-32-tests.patch
- Describe the supported OpenSSL range (1.0.2 - 3.x) and the behaviour
  differences with OpenSSL 3: TLS 1.0/1.1 need a lowered security
  level, version-pinned PROTOCOL_TLSv1* contexts (and that, as before,
  they override a system-wide MinProtocol), OP_IGNORE_UNEXPECTED_EOF
  being on by default, and load_dh_params() errors coming from
  OSSL_DECODER.
- OP_IGNORE_UNEXPECTED_EOF: say that it is enabled by default, explain
  the truncation risk and how to turn it off, and fix the
  "versionadded:: 3.10" which is wrong for this backport.
- Add a NEWS entry summarising the OpenSSL 3 API port.

Patch: python36-OpenSSL-32-documentation.patch
@mcepl
mcepl force-pushed the opensuse_3.6-openssl_3.2 branch from e0d2c7a to b24aa6c Compare September 25, 2026 16:55
@mcepl
mcepl changed the base branch from 3.6 to opensuse_3.6 September 25, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant