Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python310 for openSUSE:Factory checked in at 2026-11-09 17:45:30 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python310 (Old) and /work/SRC/openSUSE:Factory/.python310.new.3759290 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python310" Mon Nov 9 17:45:30 2026 rev:86 rq:1396358 version:3.10.22 Changes: -------- --- /work/SRC/openSUSE:Factory/python310/python310.changes 2026-10-22 15:54:22.599365740 +0200 +++ /work/SRC/openSUSE:Factory/.python310.new.3759290/python310.changes 2026-11-09 17:45:34.026426245 +0100 @@ -1,0 +2,7 @@ +Fri Nov 6 19:38:08 UTC 2026 - Matej Cepl <[email protected]> + +- CVE-2026-115557: Distrust compromised eMudhra + intermediate CA keys (bsc#1287357) + CVE-2026-115557-distrust-emudhra-ca-keys.patch + +------------------------------------------------------------------- New: ---- CVE-2026-115557-distrust-emudhra-ca-keys.patch ----------(New B)---------- New: intermediate CA keys (bsc#1287357) CVE-2026-115557-distrust-emudhra-ca-keys.patch ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python310.spec ++++++ --- /var/tmp/diff_new_pack.40TU0c/_old 2026-11-09 17:45:35.827501323 +0100 +++ /var/tmp/diff_new_pack.40TU0c/_new 2026-11-09 17:45:35.832501531 +0100 @@ -230,6 +230,9 @@ # PATCH-FIX-UPSTREAM bsc1263083-http-cookies-atob-utf8.patch bsc#1263083 [email protected] # Use decodeURIComponent() for UTF-8 support in js_output() Patch63: bsc1263083-http-cookies-atob-utf8.patch +# PATCH-FIX-UPSTREAM CVE-2026-115557-distrust-emudhra-ca-keys.patch bsc#1287357 Matej Cepl <[email protected]> +# Distrust compromised eMudhra intermediate CA keys +Patch64: CVE-2026-115557-distrust-emudhra-ca-keys.patch ### END OF PATCHES BuildRequires: autoconf-archive BuildRequires: automake @@ -550,6 +553,9 @@ # Don't fail on warnings when building documentation sed -i -e '/^SPHINXERRORHANDLING/s/-W//' Doc/Makefile +# Prepare testing zip archive from the patched version (`base64` is part of coreutils) +base64 -d Lib/test/distrusted.zip.b64 >Lib/test/distrusted.zip + %build %if %{with doc} TODAY_DATE=`date -r %{SOURCE0} "+%%B %%d, %%Y"` ++++++ CVE-2026-115557-distrust-emudhra-ca-keys.patch ++++++ Index: Python-3.10.22/Lib/test/distrusted.zip.b64 =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ Python-3.10.22/Lib/test/distrusted.zip.b64 2026-11-06 20:40:03.092785125 +0100 @@ -0,0 +1,89 @@ +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 +LRGEGqnrM5dKHV27nUAUbU7aBbjeiUznr9p8UyYWnaolSEsH1dpbLEzzfWeMW5NDH/6Xn5ZR8BvY +GqnJ+1n1GSUien+mR5wuikLk4OI+z49mehRlBLjGY1spJGW6K3o42ouRTUlaDMFwVwVGgp1fIjqA +xP7v4xuiMtMehjyAdN9QcN3oL956twPuB6lNlGmndhr7jtlf8WqAtHVHgdgYbOhi0Ams2VL9UEsH +CKv9ww4lAgAAKgMAAFBLAQIeAwoAAAAAABpjVl0AAAAAAAAAAAAAAAALAAAAAAAAAAAAEADtQQAA +AABkaXN0cnVzdGVkL1BLAQIeAxQACQAIABpjVl2GodrkzAsAAHUVAAAhAAAAAAAAAAEAAACkgSkA +AABkaXN0cnVzdGVkL2Rpc3RydXN0ZWRfa2V5Y2VydC5wZW1QSwECHgMUAAkACAAaY1ZdoZp39n8D +AADZBAAAJgAAAAAAAAABAAAApIFEDAAAZGlzdHJ1c3RlZC9kaXN0cnVzdGVkX2ludGVybWVkaWF0 +ZS5wZW1QSwECHgMUAAkACAAaY1Zdq/3DDiUCAAAqAwAAHgAAAAAAAAABAAAApIEXEAAAZGlzdHJ1 +c3RlZC9kaXN0cnVzdGVkX3Jvb3QucGVtUEsFBgAAAAAEAAQAKAEAAIgSAAAAAA== Index: Python-3.10.22/Lib/test/test_ssl.py =================================================================== --- Python-3.10.22.orig/Lib/test/test_ssl.py 2026-10-01 02:48:01.000000000 +0200 +++ Python-3.10.22/Lib/test/test_ssl.py 2026-11-06 20:40:03.093368174 +0100 @@ -9,12 +9,15 @@ from test.support import socket_helper from test.support import threading_helper from test.support import warnings_helper +import base64 import contextlib import re import socket import select import struct import time +import tempfile +import zipfile import datetime import gc import http.client @@ -131,6 +134,7 @@ SIGNED_CERTFILE2_HOSTNAME = 'fakehostname' SIGNED_CERTFILE_ECC = data_file("keycertecc.pem") SIGNED_CERTFILE_ECC_HOSTNAME = 'localhost-ecc' +DISTRUSTED_SPKI_ZIP_B64 = data_file("distrusted.zip.b64") # Same certificate as pycacert.pem, but without extra text in file SIGNING_CA = data_file("capath", "ceff1710.0") @@ -2103,6 +2107,26 @@ c_in.write(s_out.read()) client.unwrap() + def test_distrusted_spki(self): + client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + with tempfile.TemporaryDirectory() as tmpdir: + zip_path = os.path.join(tmpdir, "distrusted.zip") + with open(DISTRUSTED_SPKI_ZIP_B64, "rb") as f: + with open(zip_path, "wb") as zf: + zf.write(base64.b64decode(f.read())) + with zipfile.ZipFile(zip_path) as z: + z.extractall(tmpdir, pwd=b"Brave Sir Robin") + certdir = os.path.join(tmpdir, "distrusted") + client_context.load_verify_locations( + os.path.join(certdir, "distrusted_root.pem")) + server_context.load_cert_chain( + os.path.join(certdir, "distrusted_keycert.pem")) + + with self.assertRaises(ssl.SSLCertVerificationError) as cm: + connected_bio_pair(client_context, server_context, "localhost") + self.assertEqual(cm.exception.verify_code, 28) + def test_sni_callback_context_released_and_callback_raises(self): # Variant of the test below without a HelloRetryRequest: the callback # switches the connection to another context, drops the last Index: Python-3.10.22/Misc/NEWS.d/next/Security/2026-10-21-16-55-07.gh-issue-160040.9PjFjC.rst =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ Python-3.10.22/Misc/NEWS.d/next/Security/2026-10-21-16-55-07.gh-issue-160040.9PjFjC.rst 2026-11-06 20:40:03.093758700 +0100 @@ -0,0 +1,12 @@ +.. cve: CVE-2026-115557 + +:mod:`ssl` now rejects TLS certificate chains that contain any of a set of +compromised eMudhra intermediate CA keys. This is an extraordinary measure and +a last line of defense: removing trust is primarily the job of the platform root +store, and the built-in denylist only blocks connections while that update has +not yet reached the OS, a container image, or a bundled trust store in various +Python packages. Entries are added to the denylist only under a very high +threshold. +The denylist matches on the public key (the SHA-256 digest of the :rfc:`5280` +``SubjectPublicKeyInfo``), so it also covers the cross-signed certificates that +reuse one of the distrusted keys. Index: Python-3.10.22/Modules/_ssl.c =================================================================== --- Python-3.10.22.orig/Modules/_ssl.c 2026-10-01 02:48:01.000000000 +0200 +++ Python-3.10.22/Modules/_ssl.c 2026-11-06 20:40:03.094162180 +0100 @@ -54,6 +54,7 @@ /* Include OpenSSL header files */ #include "openssl/rsa.h" #include "openssl/crypto.h" +#include "openssl/evp.h" #include "openssl/x509.h" #include "openssl/x509v3.h" #include "openssl/pem.h" @@ -880,6 +881,7 @@ int (*verify_cb)(int, X509_STORE_CTX *) = NULL; verify_cb = SSL_get_verify_callback(self->ssl); mode |= SSL_VERIFY_POST_HANDSHAKE; + /* Keep ctx verify callback (distrusted SPKI check) */ SSL_set_verify(self->ssl, mode, verify_cb); } } else { @@ -2985,11 +2987,70 @@ * _SSLContext objects */ +#include "_ssl/distrusted_spki.h" + +/* Returns 1 if the SHA-256 of the cert's SPKI is on the distrusted list, + 0 if not (okay), -1 on an internal error. */ +static int +_cert_has_distrusted_spki(X509 *cert) +{ + unsigned char *spki = NULL; + int spki_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &spki); + if (spki_len <= 0 || spki == NULL) { + return -1; + } + + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int digest_len = 0; + int ok = EVP_Digest(spki, (size_t)spki_len, digest, &digest_len, + EVP_sha256(), NULL); + OPENSSL_free(spki); + assert(sizeof(_distrusted_spki[0]) == SHA256_DIGEST_LENGTH); + if (!ok + || digest_len != SHA256_DIGEST_LENGTH + || sizeof(_distrusted_spki[0]) != SHA256_DIGEST_LENGTH) { + return -1; + } + + for (size_t i = 0; i < Py_ARRAY_LENGTH(_distrusted_spki); i++) { + if (memcmp(digest, _distrusted_spki[i], SHA256_DIGEST_LENGTH) == 0) { + return 1; + } + } + return 0; +} + +/* Verify callback, called once per cert. Reject distrusted SPKIs. */ +static int +_ssl_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + /* Don't override an error OpenSSL already reported for this certificate. */ + if (!preverify_ok) { + return preverify_ok; + } + X509 *cert = X509_STORE_CTX_get_current_cert(x509_ctx); + if (cert == NULL) { + return preverify_ok; + } + switch (_cert_has_distrusted_spki(cert)) { + case 0: + /* okay: not on the distrusted list */ + return preverify_ok; + case 1: + /* rejected: public key is on the distrusted list */ + X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CERT_REJECTED); + return 0; + default: + /* internal error */ + X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_UNSPECIFIED); + return 0; + } +} + static int _set_verify_mode(PySSLContext *self, enum py_ssl_cert_requirements n) { int mode; - int (*verify_cb)(int, X509_STORE_CTX *) = NULL; switch(n) { case PY_SSL_CERT_NONE: @@ -3010,9 +3071,7 @@ /* bpo-37428: newPySSLSocket() sets SSL_VERIFY_POST_HANDSHAKE flag for * server sockets and SSL_set_post_handshake_auth() for client. */ - /* keep current verify cb */ - verify_cb = SSL_CTX_get_verify_callback(self->ctx); - SSL_CTX_set_verify(self->ctx, mode, verify_cb); + SSL_CTX_set_verify(self->ctx, mode, _ssl_verify_callback); return 0; } Index: Python-3.10.22/Modules/_ssl/distrusted_spki.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ Python-3.10.22/Modules/_ssl/distrusted_spki.h 2026-11-06 20:40:03.094488144 +0100 @@ -0,0 +1,75 @@ +/* Denylist of distrusted SubjectPublicKeyInfo (SPKI). + * + * Verification rejects any chain with a cert whose SPKI SHA-256 digest is in + * the _distrusted_spki table. We match the key, not a cert fingerprint, so + * cross-signs that reuse the key are blocked too. + * + * This is an extraordinary measure and last line of defense. Removing trust is + * primarily the job of the platform root store. This list only blocks keys while + * that update is still propagating to the OS, container images, or root CAs + * bundled in Python packages. The threshold for adding an entry is very high. + * + * See https://github.com/python/cpython/issues/160040 for the eMudhra discussion. + * + * To block another key, add the SHA-256 digest bytes of its SPKI: + * + * $ openssl x509 -in cert.pem -noout -pubkey | openssl pkey -pubin -outform DER | openssl dgst -sha256 -binary | xxd -i + */ +#ifndef Py_SSL_DISTRUSTED_SPKI_H +#define Py_SSL_DISTRUSTED_SPKI_H + +#include "openssl/sha.h" + +static const unsigned char _distrusted_spki[][SHA256_DIGEST_LENGTH] = { + /* Compromised eMudhra intermediates, https://emudhra.we-have-a-problem.com/ */ + /* CN=EM OV TLS CA - G3B,O=eMudhra BV,C=NL */ + { + 0x43, 0xbf, 0xfa, 0x19, 0xd6, 0xef, 0x65, 0x14, 0x51, 0xf6, 0x84, 0x1e, + 0x01, 0x99, 0x31, 0xe9, 0x6f, 0x6f, 0x2e, 0xae, 0xff, 0x28, 0xab, 0x4b, + 0x6f, 0x1c, 0xe4, 0x57, 0x6d, 0xe2, 0x71, 0x7b, + }, + /* CN=EM DV TLS CA - G3C,O=eMudhra Inc,C=US */ + { + 0x49, 0xdd, 0x35, 0x3d, 0xc2, 0xb9, 0x19, 0xa3, 0x77, 0x06, 0x45, 0x11, + 0x7d, 0x0e, 0x60, 0xc1, 0x61, 0x15, 0xe7, 0xd1, 0xbc, 0x25, 0x9e, 0xef, + 0xe7, 0x34, 0xb0, 0x0e, 0xa4, 0x34, 0xfe, 0x5f, + }, + /* CN=EM EV TLS CA - G3C,O=eMudhra Inc,C=US */ + { + 0x5c, 0x06, 0x60, 0xa2, 0xb1, 0x64, 0x91, 0x0d, 0x8f, 0xf5, 0x89, 0xc1, + 0x8e, 0x14, 0x55, 0x2e, 0x9e, 0x0e, 0xc2, 0x37, 0xdc, 0xef, 0xe2, 0xcb, + 0x73, 0x6d, 0x41, 0xa4, 0x81, 0x1f, 0x81, 0xa0, + }, + /* CN=EM OV TLS CA - G3C,O=eMudhra Inc,C=US */ + { + 0x63, 0xec, 0xef, 0xee, 0x43, 0x1d, 0x49, 0x52, 0x3b, 0xc1, 0x13, 0x6c, + 0x4e, 0x72, 0xd0, 0x20, 0x6a, 0x47, 0x70, 0x86, 0xca, 0xf6, 0x06, 0xcd, + 0x5c, 0xf0, 0x25, 0x68, 0x6c, 0x10, 0x9c, 0xf8, + }, + /* CN=EM OV CS CA - G3C,O=eMudhra Inc,C=US */ + { + 0x92, 0xc0, 0x63, 0xd6, 0x7e, 0x8f, 0x14, 0xdb, 0x71, 0xd2, 0x88, 0xe1, + 0x12, 0x66, 0xff, 0xda, 0xf6, 0x9c, 0xbf, 0x11, 0x5f, 0x7b, 0xab, 0x14, + 0xf3, 0xbc, 0x2f, 0x68, 0xd2, 0x5e, 0x0e, 0x30, + }, + /* CN=EM EV TLS CA - G3B,O=eMudhra BV,C=NL */ + { + 0xe0, 0x25, 0x4c, 0x7f, 0xcd, 0xc2, 0x65, 0xe2, 0xf0, 0xf8, 0xa4, 0x28, + 0xda, 0xbb, 0x75, 0xab, 0x4a, 0xe0, 0x39, 0xb4, 0x87, 0x2d, 0x62, 0xf5, + 0xee, 0x57, 0xf0, 0x87, 0x7d, 0x5a, 0x8a, 0x41, + }, + /* CN=EM DV TLS CA - G3B,O=eMudhra BV,C=NL */ + { + 0xeb, 0xed, 0x64, 0x75, 0xc2, 0xc2, 0xd8, 0x0f, 0xcb, 0x19, 0x9f, 0x3f, + 0xe8, 0xdd, 0x6e, 0x6b, 0x3c, 0xe6, 0x0b, 0x77, 0xeb, 0x15, 0xd4, 0xe4, + 0x2c, 0x3d, 0xec, 0x0c, 0x6f, 0x5b, 0x51, 0x05, + }, + /* CN=EM OV CS CA - G3B,O=eMudhra BV,C=NL */ + { + 0xec, 0x4f, 0xfa, 0x1a, 0xd0, 0xac, 0x88, 0x08, 0x13, 0x14, 0xb7, 0xaa, + 0xff, 0xef, 0x6a, 0x91, 0xf9, 0x7c, 0xd5, 0x72, 0x91, 0xbb, 0x5b, 0xa6, + 0x86, 0xc5, 0x41, 0x5f, 0x5e, 0xb4, 0xf6, 0xb6, + }, +}; + +#endif /* !Py_SSL_DISTRUSTED_SPKI_H */ ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.40TU0c/_old 2026-11-09 17:45:36.035509994 +0100 +++ /var/tmp/diff_new_pack.40TU0c/_new 2026-11-09 17:45:36.041510244 +0100 @@ -1,6 +1,6 @@ -mtime: 1792174715 -commit: a7ee42b1813f51faee6a656ff7a10dd201bb468219fa308d9741430d54f8844f +mtime: 1794237305 +commit: fde74c86307c5936d62ae926ec7306121f0156bb8b65965c2688f89a8231b7dd url: https://src.opensuse.org/python-interpreters/python310 -revision: a7ee42b1813f51faee6a656ff7a10dd201bb468219fa308d9741430d54f8844f +revision: fde74c86307c5936d62ae926ec7306121f0156bb8b65965c2688f89a8231b7dd projectscmsync: https://src.opensuse.org/python-interpreters/_ObsPrj ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-11-09 16:15:05.000000000 +0100 @@ -0,0 +1,7 @@ +.osc +*.obscpio +*.osc +_build.* +.pbuild +*.rej +python310*-build/
