From c8a0f6e616a293efd60efd0f16f66e359813bc04 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Wed, 10 Jun 2026 14:31:19 -0700 Subject: [PATCH] Pre-import Python release manager keys before sig verification Auto-update fails the first time a release manager rotates or a brand new release goes out before the key has propagated to the keyservers we poll. Today's 3.13.14 release tripped this: Thomas Wouters' key (7169605F62C751356D054A26A821E680E5FA6305) is on github.com/Yhg1s.gpg but not yet on keyserver.ubuntu.com, keys.openpgp.org, or pgp.mit.edu. python.org publishes the canonical per-RM key URLs at https://www.python.org/downloads/metadata/pgp/. Pull each of them directly at the start of create_pyversions so we never depend on keyserver freshness to verify a brand-new release. The existing keyserver fallback path stays in place for any future keys not on this list. Also fixes a stale literal-{url} formatting in the failure exception. --- relenv/pyversions.py | 48 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/relenv/pyversions.py b/relenv/pyversions.py index 51c9632b..af3344b2 100644 --- a/relenv/pyversions.py +++ b/relenv/pyversions.py @@ -24,7 +24,7 @@ import time from typing import TYPE_CHECKING, Any -from relenv.common import Version, check_url, download_url, fetch_url_content +from relenv.common import Version, check_url, download_url, fetch_url, fetch_url_content if TYPE_CHECKING: import argparse @@ -48,6 +48,23 @@ "pgp.mit.edu", ] +# Python release-manager public keys, per +# https://www.python.org/downloads/metadata/pgp/. Keyservers have been +# unreliable for newer keys, so we import these directly from their +# canonical homes before attempting signature verification. +RELEASE_MANAGER_KEY_URLS = ( + # Thomas Wouters (3.12, 3.13) + "https://github.com/Yhg1s.gpg", + # Pablo Galindo Salgado (3.10, 3.11) + "https://keybase.io/pablogsal/pgp_keys.asc", + # Steve Dower (Windows binaries) + "https://keybase.io/stevedower/pgp_keys.asc", + # Ɓukasz Langa (3.8, 3.9) + "https://keybase.io/ambv/pgp_keys.asc", + # Ned Deily (3.6, 3.7 source; macOS binaries) + "https://keybase.io/nad/pgp_keys.asc", +) + ARCHIVE = "https://www.python.org/ftp/python/{version}/Python-{version}.{ext}" @@ -78,6 +95,31 @@ def _receive_key(keyid: str, server: str) -> bool: return False +def import_release_manager_keys() -> None: + """Import Python release manager public keys from their canonical homes. + + Keyservers regularly fail to serve newly-issued release manager keys, so + we pull each key listed at python.org/downloads/metadata/pgp/ directly + from the URL the release managers themselves publish. + """ + import io + + for url in RELEASE_MANAGER_KEY_URLS: + buf = io.BytesIO() + try: + fetch_url(url, buf) + except Exception as exc: + print(f"Could not fetch release manager key from {url}: {exc}") + continue + proc = subprocess.run( + ["gpg", "--batch", "--import"], + input=buf.getvalue(), + capture_output=True, + ) + if proc.returncode != 0: + print(f"gpg --import failed for {url}: {proc.stderr.decode().strip()}") + + def _get_keyid(proc: subprocess.CompletedProcess[bytes]) -> str | None: try: err = proc.stderr.decode() @@ -1013,6 +1055,8 @@ def create_pyversions(path: pathlib.Path) -> None: versions = detect_python_versions() cwd = os.getcwd() + import_release_manager_keys() + if path.exists(): all_data = json.loads(path.read_text()) # Handle both old format (flat dict) and new format (nested) @@ -1053,7 +1097,7 @@ def create_pyversions(path: pathlib.Path) -> None: print(f"Version {version} has digest {digest(download_path)}") pydata[str(version)] = digest(download_path) else: - raise Exception("Signature failed to verify: {url}") + raise Exception(f"Signature failed to verify: {url}") # Write in new structured format all_data = {"python": pydata, "dependencies": dependencies}