dotnet/aspnetcore · error · Exception
Signature verification failed
Error message
Signature verification failed: {result.stderr.decode('utf-8')} What it means
Raised by fetch_release_file when gpgv exits non-zero verifying Release.gpg against Release using the supplied keyring. The exception text embeds gpgv's stderr so the operator can see whether the failure was a missing key, an expired key, a BAD signature, or a malformed file.
Solutions
- Install or update the matching archive-keyring package (debian-archive-keyring, debian-ports-archive-keyring, or ubuntu-keyring depending on the mirror) and pass its path via --keyring.
- Read the embedded stderr in the exception — 'No public key' means keyring mismatch; 'BAD signature' means tampering or truncation; 'signature verification failed' on gpgv 2.4+ usually means the keyring path is wrong.
- Re-download Release and Release.gpg from a canonical mirror to rule out truncation/staleness.
- If signature checking is genuinely not needed in your environment, drop --force-check-gpg (or pass --skipsigcheck to build-rootfs.sh), but treat this as an environment-level decision, not a workaround.
Example fix
# before: wrong keyring for the mirror python3 install-debs.py --mirror http://deb.debian.org/debian --suite trixie --arch amd64 \ --rootfsdir rootfs --force-check-gpg --keyring ubuntu-keyring.gpg libc6 # after: matching keyring apt-get install -y debian-archive-keyring python3 install-debs.py --mirror http://deb.debian.org/debian --suite trixie --arch amd64 \ --rootfsdir rootfs --force-check-gpg \ --keyring /usr/share/keyrings/debian-archive-keyring.gpg libc6
Defensive patterns
Strategy: validation
Validate before calling
# Pre-flight: ensure the keyring contains the suite's signing key, and that gpgv works.
required=$(curl -fsS "$MIRROR/dists/$SUITE/Release.gpg" | gpgv --keyring "$KEYRING" --status-fd 1 /dev/stdin <(curl -fsS "$MIRROR/dists/$SUITE/Release") 2>&1)
printf '%s\n' "$required" | grep -q GOODSIG || { echo 'keyring missing signing key'; exit 1; } Try / catch
try:
asyncio.run(fetch_release_file(session, mirror, suite, keyring))
except Exception as e:
msg = str(e)
if 'No public key' in msg: print('Install/upgrade the matching archive-keyring package.')
elif 'BAD signature' in msg: print('Tampered or truncated Release; switch mirror.')
raise Prevention
- Install the matching keyring package for your mirror and suite (debian-archive-keyring, debian-ports-archive-keyring, ubuntu-keyring).
- Pass the explicit keyring path via --keyring; do not rely on gpg's default keyboxd on GnuPG 2.4+.
- Keep the keyring package current so signing-key rollovers do not bite.
When it happens
Trigger: subprocess.run(['gpgv', '--keyring', keyring, release_gpg_file.name, release_file.name]) returns with result.returncode != 0. Causes include the signing key not present in the keyring, an expired or revoked signing key, a tampered Release file, or a truncated download of either Release or Release.gpg (the script downloads both via download_file but does not checksum them, so truncation is possible).
Common situations: Missing or wrong --keyring (e.g. ubuntu keyring used against a debian-ports mirror); archive signing key rolled over and the installed keyring package is older than the rollover; signature file fetched from a stale proxy while Release is fresh; gpgv unavailable or a non-functional keyboxd setup on GnuPG 2.4+ (the comment in code says gpgv was chosen specifically to dodge this); truncated Release.gpg from a flaky mirror.
Related errors
- SHA256 mismatch for : expected , got
- SHA256 mismatch for : expected , got
- Could not find checksum for
- Could not find 'data.tar.*' in
- Failed to download after attempts.
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/86b3ba2daf045fd9.
Report an issue: GitHub.
Appendix: source
Thrown at eng/common/cross/install-debs.py:135
release_gpg_url = f"{mirror}/dists/{suite}/Release.gpg"
with tempfile.NamedTemporaryFile() as release_file, tempfile.NamedTemporaryFile() as release_gpg_file:
await download_file(session, release_url, release_file.name)
await download_file(session, release_gpg_url, release_gpg_file.name)
print("Verifying signature of Release with Release.gpg.")
# Use gpgv rather than gpg for verification. gpgv verifies a detached
# signature against a fixed keyring without involving gpg-agent or
# keyboxd, which makes it robust on hosts running GnuPG 2.4+ (e.g. Azure
# Linux) where "gpg --keyring" routes through keyboxd and can fail.
verify_command = ["gpgv"]
if keyring:
verify_command += ["--keyring", keyring]
verify_command += [release_gpg_file.name, release_file.name]
result = subprocess.run(verify_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
raise Exception(f"Signature verification failed: {result.stderr.decode('utf-8')}")
print("Signature verified successfully.")
with open(release_file.name) as f:
return f.read()
def parse_release_file(content, path):
"""Parses the Release file and returns sha256 checksum of the specified path."""
# data looks like this:
# <checksum> <size> <path>
matches = re.findall(r'^ (\S*) +(\S*) +(\S*)$', content, re.MULTILINE)
for entry in matches:
# the file has both md5 and sha256 checksums, we want sha256 which has a length of 64
if entry[2] == path and len(entry[0]) == 64:
return entry[0]
View on GitHub (pinned to 3600ca084e)