Skip to content

2.7.18.15: eMudhra ssl distrust (CVE-2026-115557) + ensurepip pip 20.3.4.post3 / setuptools 44.1.1.post3 - #91

Merged
icanhasmath merged 5 commits into
2.7from
2.7.18.15-security
Oct 28, 2026
Merged

icanhasmath merged 5 commits into
2.7from
2.7.18.15-security

Conversation

@icanhasmath

@icanhasmath icanhasmath commented Oct 28, 2026 •

Copy link
Copy Markdown

2.7.18.15 security release on top of 2.7.18.14 (release tag 2.7.18.14 = 2.7 @ 2cb6e5d).

Tracking: Linear PROD-1555 (eMudhra/emSign distrust, alongside 3.9.25.1 #88 and 3.10.22.1 #87).

Changes

1. ssl: distrust the compromised eMudhra intermediate CA keys (CVE-2026-115557, pythongh-160040) — 6690fba

Backport of upstream's SPKI denylist (3.11 af52b41). _ssl.c gets a verify callback that hashes each certificate's SubjectPublicKeyInfo with SHA-256 and rejects the 8 eMudhra intermediate keys with X509_V_ERR_CERT_REJECTED. Because it matches on the key, it also catches cross-signs. The SSLContext.verify_mode setter installs the callback, so it covers every CERT_OPTIONAL/CERT_REQUIRED path, including create_default_context(), urllib2 and httplib.

Differences from the 3.x patch on 2.7:

  • The header is Modules/_ssl_distrusted_spki.h, because 2.7 has no Modules/_ssl/ directory. It is wired into the setup.py depends and PCbuild/_ssl.vcxproj(.filters).
  • The code is C89-style and does not use Py_ARRAY_LENGTH. X509_V_ERR_UNSPECIFIED is guarded for OpenSSL older than 1.0.2.
  • 2.7 has no MemoryBIO and no SSLCertVerificationError, so the test does the handshake against ThreadedEchoServer and asserts "certificate verify failed".
  • Tools/msi/msi.py now ships Lib/test/distrusted.zip. The zip is ZipCrypto-encrypted, which 2.7's zipfile can read.

2. ensurepip: pip 20.3.4.post4 + setuptools 44.1.1.post3 — cf00e34, ff19c1d

These replace pip 19.2.3 and setuptools 41.2.0.

  • pip: the ActiveState/pip 20.3.4.post4 release wheel (sha256 53eba762…e7c5). Its vendored certifi is 2026.10.23.post1 (ActiveState's Python 2.7 build of certifi 2026.10.23), which removes emSign ECC Root CA - G3, emSign Root CA - C1 and emSign ECC Root CA - C3; emSign Root CA - G1 is the only emSign root left. (Originally 20.3.4.post3 with certifi 2026.10.19.post1, which still had C1 and ECC C3.) pip's connections go through the ssl module, so the SPKI denylist above still blocks the 8 compromised intermediates for pip.
  • setuptools: ActiveState/setuptools v44.1.1.post3, with fixes for CVE-2022-40897, CVE-2024-6345, CVE-2025-47273 and CVE-2026-59890. The release has no wheel asset, so I built the wheel from the tag (e2a1db3) under Python 2.7, sha256 fc42d2f9…f046a5. Its contents match PyPI's 44.1.1 wheel except for the three fix files.
  • _run_pip: now calls pip._internal.cli.main.main. pip 20 keeps pip._internal.main() only as a shim that prints a deprecation warning on every call.

3. Release — d573c89

PY_VERSION 2.7.18.15 and Misc/NEWS.d/2.7.18.15.rst.

Testing (Linux x86_64, built against system OpenSSL 3.0.13)

  • test_ssl.test_distrusted_spki: fails on pristine 2.7.18.14 with "SSLError not raised", and passes with this change. Every other test_ssl result is identical between the two builds. The 27 pre-existing test_ssl failures come from building 2.7 against OpenSSL 3.0, and appear on the base build as well.

  • test_ensurepip: passes.

  • Install check: make altinstall followed by python2.7 -m ensurepip --default-pip installs pip 20.3.4.post3 and setuptools 44.1.1.post3 without a deprecation warning. On that install, urllib2 HTTPS to pypi.org and pip download six both pass through the new verify callback. Building a setuptools egg and sdist works, and ensurepip._uninstall works.

  • Full suite (regrtest -j6 -w), compared against the pristine 2.7.18.14 build: no new failures. Both builds fail the same 4 tests: test_ftplib, test_socket, test_ssl and test_urllib2_localnet. These come from the local environment and OpenSSL 3.0. The base build also hit a flaky test_threading_local.

  • Linux, against production OpenSSL 1.1.1w (the as-builds openssl/1.11.0.23 Linux artifact). I built the new and base trees the same way and ran regrtest -j6 -w -uall,-largefile,-cpu.

    • Both builds pass 364 tests and fail the same 3 modules: test_ftplib, test_socket and test_ssl.
    • The only individual failure that differs is test_distrusted_spki. It fails on 2.7.18.14 and passes on 2.7.18.15.
    • The other test_ssl failure, NetworkedTests.test_context_setget, needs the network, and the test_ftplib failures are about the local source address. Both happen in this environment regardless of the change.
  • Windows x64: not built here, because there's no MSVC on this box. It needs a build against the as-builds 1.1.1w Windows artifact.

After merge: tag 2.7.18.15 (unprefixed, like 2.7.18.14) on the merge commit, add the platform inventory version, and add CVE-scanner override rows.

Update (ff19c1d): after the pip 20.3.4.post4 release, I re-tested on Linux against OpenSSL 1.1.1w. test_ensurepip passes. After make altinstall, python2.7 -m ensurepip --default-pip installs pip 20.3.4.post4 and setuptools 44.1.1.post3. The vendored certifi reports 2026.10.23.post1, with emSign Root CA - G1 as the only emSign root, and pip download six works. The bundled wheel is byte-identical to the release asset.

icanhasmath and others added 3 commits October 27, 2026 22:49
…VE-2026-115557)

Backport of the upstream ssl SPKI denylist (python/cpython pythonGH-160042,
3.11 commit af52b41 via 7dd8b47) to 2.7.18.x.

The ssl module now rejects TLS certificate chains that contain any of a
set of compromised eMudhra intermediate CA keys.  This is a last line of
defense while root-store updates propagate to the OS, container images
and bundled trust stores.  The denylist matches on the SHA-256 digest of
the DER SubjectPublicKeyInfo, so it also covers cross-signed certificates
that reuse one of the distrusted keys.  A matching certificate fails
verification with X509_V_ERR_CERT_REJECTED.

2.7.18.x adaptations:
- The table lives in Modules/_ssl_distrusted_spki.h (2.7 has no
  Modules/_ssl/ directory); it is listed in the setup.py depends for _ssl
  and in PCbuild/_ssl.vcxproj(.filters).
- _ssl.c is C89-style: declarations at block start, no Py_ARRAY_LENGTH.
- The verify callback is installed by the SSLContext.verify_mode setter
  (2.7's set_verify_mode), which every CERT_OPTIONAL/CERT_REQUIRED path
  goes through, including create_default_context() and wrap_socket().
- X509_V_ERR_UNSPECIFIED is guarded; OpenSSL < 1.0.2 falls back to
  X509_V_ERR_APPLICATION_VERIFICATION for the internal-error case.
- 2.7 has no MemoryBIO and no SSLCertVerificationError, so the test runs
  the handshake against ThreadedEchoServer and asserts "certificate
  verify failed".  It uses tempfile.mkdtemp() in place of
  TemporaryDirectory.  distrusted.zip uses ZipCrypto, which 2.7's
  zipfile can read.
- Tools/msi/msi.py ships Lib/test/distrusted.zip.

Verified: test_distrusted_spki fails on 2.7.18.14 ("SSLError not raised")
and passes with this change; the rest of test_ssl is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ensurepip now bundles the ActiveState security releases of the last
Python 2.7-compatible pip and setuptools lines:

- pip 20.3.4.post3 (https://github.com/ActiveState/pip/releases/tag/20.3.4.post3,
  sha256 e1e5a1543954a109fed9bc1c3af01f0cde6c46f79d3389c2afb884b64fcf2949).
  Its vendored certifi is 2026.10.19.post1, which drops the eMudhra/emSign
  roots (CVE-2026-115557), and it carries the earlier ActiveState
  backports for the 20.3.4 line.
- setuptools 44.1.1.post3 (https://github.com/ActiveState/setuptools/releases/tag/v44.1.1.post3):
  CVE-2022-40897, CVE-2024-6345, CVE-2025-47273 and CVE-2026-59890.  The
  release has no wheel asset, so this wheel was built from tag
  v44.1.1.post3 (e2a1db3) with bootstrap.py + bdist_wheel under Python
  2.7 (sha256 fc42d2f957b1172962a59fad9367200cb7385dd1ac8b3a7a36c0251779f046a5).
  Its files match PyPI's setuptools 44.1.1 wheel except for the three
  fix files (command/egg_info.py, package_index.py, unicode_utils.py),
  and its entry point is easy_install-2.7 rather than easy_install-3.8.

These replace pip 19.2.3 and setuptools 41.2.0.

pip 20 keeps pip._internal.main() only as a deprecated shim that warns
on every call, so _run_pip() now calls pip._internal.cli.main.main(),
as upstream 3.x ensurepip did once it bundled pip >= 19.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bump PY_VERSION to 2.7.18.15 and add release notes for the security
changes in this release: the eMudhra SPKI distrust in ssl
(CVE-2026-115557, pythongh-160040), and the ensurepip upgrade to pip
20.3.4.post3 (certifi 2026.10.19.post1) and setuptools 44.1.1.post3
(CVE-2022-40897, CVE-2024-6345, CVE-2025-47273, CVE-2026-59890).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@ezequielp-activestate ezequielp-activestate left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers. Approving. One wording correction and two small suggestions below.

What I checked (Linux x86_64, CPython built from this branch and from 2.7 in a Debian 10 container, OpenSSL 1.1.1d)

ssl distrust (CVE-2026-115557)

  • The 8 SPKI SHA-256 digests in Modules/_ssl_distrusted_spki.h are byte-for-byte the same as upstream's Modules/_ssl/distrusted_spki.h (3.11 af52b41) and the 3.10.22.1 PR (#87). Upstream has no later fix to the list or the callback on any branch.

  • _ssl.c matches upstream's callback apart from the C89 and X509_V_ERR_UNSPECIFIED adjustments. Upstream's other hunk, which keeps the context callback in newPySSLSocket for post-handshake auth, has nothing to apply to on 2.7: that code is commented out there.

  • Enforcement, using the test fixture chain (leaf ← EM OV TLS CA - G3C ← emSign Root TLS CA - G3) against a local TLS server:

    client 2.7 this branch
    ssl.create_default_context(cafile=root) connects certificate verify failed
    ssl.wrap_socket(cert_reqs=CERT_REQUIRED, ca_certs=root) connects certificate verify failed
    httplib.HTTPSConnection(context=…) connects certificate verify failed
    SSLContext with CERT_OPTIONAL connects certificate verify failed
  • test_ssl: 101 tests OK on this branch, 100 OK on 2.7, both with 14 skipped. Copied onto 2.7, the new test_distrusted_spki fails with "SSLError not raised".

  • test_ensurepip, test_httplib, test_urllib2 and test_urllib2_localnet pass on both builds.

ensurepip

  • pip-20.3.4.post3-py2.py3-none-any.whl is byte-identical to the asset on the ActiveState/pip 20.3.4.post3 release (sha256 e1e5a154…2949).
  • setuptools-44.1.1.post3-py2.py3-none-any.whl (fc42d2f9…f046a5): every file in it matches the v44.1.1.post3 tag (e2a1db3). Against PyPI's 44.1.1 wheel only command/egg_info.py, package_index.py and unicode_utils.py differ.
  • _PIP_VERSION and _SETUPTOOLS_VERSION match the wheels, and the old 19.2.3 and 41.2.0 wheels are gone.
  • After make altinstall, python2.7 -m ensurepip --default-pip installs pip 20.3.4.post3 and setuptools 44.1.1.post3. pip --version, python -m ensurepip --version and pip download six all work. Lib/test/distrusted.zip is installed.
  • PY_VERSION is 2.7.18.15, and the release notes describe both changes.

🟡 The certifi claim in the release notes and PR body is not accurate

Misc/NEWS.d/2.7.18.15.rst says pip's vendored certifi "no longer trusts the eMudhra/emSign roots". The bundled pip/_vendor/certifi/cacert.pem (certifi 2026.10.19.post1) still has emSign Root CA - G1, emSign Root CA - C1 and emSign ECC Root CA - C3. Only emSign ECC Root CA - G3 was removed. That matches upstream certifi 2026.10.19, and the pip wheels in #87 and #88 have the same three roots, so it isn't a packaging mistake. The sentence should say only ECC G3 was removed.

Upstream certifi 2026.10.23 has since also dropped emSign Root CA - C1 and emSign ECC Root CA - C3, leaving only G1. That would be worth picking up in the next pip .postN on all three lines. The SPKI denylist already blocks the 8 compromised intermediates for pip, because pip's connections go through this ssl module, so this isn't urgent.

⚪ Minor

  • The ActiveState/setuptools v44.1.1.post3 release has no wheel asset, so the bundled wheel can't be checked against a published file, only against the tag's source, as above. Attaching the wheel to that release would make it traceable.

Not assessed

  • Windows (MSVC build, PCbuild and msi.py changes).
  • Builds against OpenSSL 1.0.2 or 3.x. I built against Debian's 1.1.1d only, and wired _ssl through Modules/Setup.local because this container's setup.py didn't find OpenSSL on either branch.
  • The full regression suite.

pip 20.3.4.post3 vendors certifi 2026.10.19.post1, which removes only the
emSign ECC Root CA - G3 root, as upstream certifi 2026.10.19 does.  emSign
Root CA - G1, emSign Root CA - C1 and emSign ECC Root CA - C3 are still in
pip/_vendor/certifi/cacert.pem.  The note said that pip no longer trusts
the eMudhra/emSign roots.  pip's TLS goes through the ssl module, so the
SPKI denylist still blocks the compromised intermediates for it.

The ensurepip commit message (cf00e34) makes the same claim; it is
left as is because the 2.7.18.15.rc1 tag already points past it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@icanhasmath

Copy link
Copy Markdown
Author

Thanks. You're right about certifi. I checked pip/_vendor/certifi/cacert.pem in the bundled wheel: emSign Root CA - G1, emSign Root CA - C1 and emSign ECC Root CA - C3 are still there, and only emSign ECC Root CA - G3 is gone.

  • 28706dd: Misc/NEWS.d/2.7.18.15.rst now says certifi 2026.10.19.post1 removes only ECC G3, names the three roots that remain, and notes that the SPKI denylist still covers pip, because its TLS goes through ssl.
  • PR description: the pip bullet is corrected the same way.
  • Not rewritten: the ensurepip commit message (cf00e34) makes the same claim. I left it, because the published 2.7.18.15.rc1 tag points at d573c89; the new commit's message records the correction.
  • certifi 2026.10.23 (drops C1 and ECC C3 as well): agreed, that belongs in the next pip .postN across all three lines.
  • setuptools wheel: I'll leave attaching it to the v44.1.1.post3 release to the setuptools release owner.

@ezequielp-activestate ezequielp-activestate left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of 28706dd: still approved. The only change since my review is the 2.7.18.15 release note, which now says correctly that pip's vendored certifi 2026.10.19.post1 removes only emSign ECC Root CA - G3, and that G1, C1 and ECC C3 remain trusted. It also adds that pip's TLS goes through ssl, so the eMudhra SPKI denylist in this release still covers pip's connections. That holds: pip 20.3.4 verifies through ssl contexts, which the denylist hooks.

Leaving the ensurepip commit message as is, with the published rc1 tag pointing at d573c89, is reasonable. Picking up certifi 2026.10.23 in the next pip post-release covers the remaining two roots.

@icanhasmath

Copy link
Copy Markdown
Author

Follow-up on the setuptools point: the wheel is now attached to ActiveState/setuptools v44.1.1.post3. It's the same file bundled here (sha256 fc42d2f9…f046a5; the downloaded asset hashes the same). The release notes now say how it was built.

Also: pip releases with certifi 2026.10.23 are in review as ActiveState/pip#10–#14. Once they're released, this PR's ensurepip will move from pip 20.3.4.post3 to 20.3.4.post4.

ensurepip now bundles pip 20.3.4.post4, the ActiveState/pip release asset
(sha256 53eba762...e7c5), in place of 20.3.4.post3.  Its vendored certifi
moves from 2026.10.19.post1 to 2026.10.23.post1, ActiveState's Python 2.7
build of certifi 2026.10.23.  That release also removes the emSign Root
CA - C1 and emSign ECC Root CA - C3 roots, so emSign Root CA - G1 is the
only emSign root left (follow-up to GHSA-6q48-6c7w-9gw2).  The
2.7.18.15 release notes say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@icanhasmath

Copy link
Copy Markdown
Author

Bundled pip is now 20.3.4.post4 (ff19c1d). Its vendored certifi is 2026.10.23.post1, so emSign Root CA - G1 is the only emSign root left. The bundled wheel is byte-identical to the release asset.

  • The release notes now describe this.
  • I re-ran test_ensurepip, ensurepip after make altinstall, and pip download against OpenSSL 1.1.1w; all pass.
  • Pre-release 2.7.18.15.rc2 is cut for Windows testing.

@ezequielp-activestate ezequielp-activestate left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of ff19c1d: still approved. The only change since my last review is the bundled pip, now 20.3.4.post4. The wheel in Lib/ensurepip/_bundled/ is byte-identical to the asset on the ActiveState pip 20.3.4.post4 release (sha256 53eba762…), the post3 wheel is removed, and _PIP_VERSION matches. In a Python 2.7 venv, python -m ensurepip from this tree installs pip 20.3.4.post4 and setuptools 44.1.1.post3, and pip's vendored certifi is 2026.10.23.post1 with emSign Root CA - G1 as its only emSign root. That matches the corrected release note.

@icanhasmath
icanhasmath merged commit 4ab1ebd into 2.7 Oct 28, 2026
icanhasmath added a commit that referenced this pull request Oct 28, 2026
Brings in the last two #91 commits: the corrected certifi note in the
2.7.18.15 release notes (28706dd) and the bundled pip 20.3.4.post4
(ff19c1d).
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.

2 participants