dotnet/aspnetcore · critical · Exception
Signature verification failed: {result.stderr.decode('utf-8'
Error message
Signature verification failed: {result.stderr.decode('utf-8')} What it means
fetch_release_file downloads Release and Release.gpg, then runs gpgv to verify the detached signature against the (optionally) specified keyring. If gpgv returns non-zero, a plain Exception is raised containing the decoded stderr. This is the GPG signature gate that anchors the whole checksum chain when --force-check-gpg is enabled.
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 294cab2f9b)
Solutions
- Install the correct keyring package for your suite: debian-ports-archive-keyring (Debian ports), debian-archive-keyring (Debian mainstream), or ubuntu-keyring (Ubuntu).
- Pass the correct --keyring path (e.g., /usr/share/keyrings/debian-archive-keyring.gpg).
- Ensure gpgv is installed and on PATH (apt-get install gnupg).
- If you intentionally skip trust, drop --force-check-gpg / pass --skipsigcheck to build-rootfs.sh — but understand you lose integrity verification.
- Update the keyring package if the signing key has rotated since your container image was built.
Example fix
# before python3 install-debs.py --force-check-gpg --keyring /tmp/wrong.gpg \ --suite sid --mirror http://deb.debian.org/debian ... # Signature verification failed: Can't check signature: public key not found # after sudo apt-get install -y debian-archive-keyring python3 install-debs.py --force-check-gpg \ --keyring /usr/share/keyrings/debian-archive-keyring.gpg \ --suite bookworm --mirror http://deb.debian.org/debian ...
Defensive patterns
Strategy: validation
Validate before calling
# Verify gpgv and the keyring are usable before the build
import shutil, subprocess
if not shutil.which('gpgv'):
raise SystemExit('gpgv not installed: apt-get install gnupg')
probe = subprocess.run(['gpgv', '--keyring', keyring, '/dev/null', '/dev/null'],
capture_output=True)
if probe.returncode not in (0, 2):
raise SystemExit(f'Keyring {keyring} unusable: {probe.stderr.decode()}') Try / catch
try:
release_content = await fetch_release_file(session, mirror, suite, keyring)
except Exception as e:
if 'Signature verification failed' in str(e):
if 'No public key' in str(e) or 'public key not found' in str(e):
raise SystemExit('Install the suite\'s keyring package (e.g., debian-archive-keyring) and re-run')
raise Prevention
- Install the correct keyring package for each suite before enabling --force-check-gpg.
- Verify gpgv is on PATH in minimal containers.
- Rotate local keyrings periodically to pick up new signing keys.
- Distinguish 'no public key' (fixable with the right keyring) from genuine tampering.
When it happens
Trigger: Invoked from fetch_and_decompress only when check_sig is True. gpgv fails because: the key for the suite is not in the keyring, the Release file was tampered, the Release.gpg is missing/corrupt, an expired signing key, or gpgv itself is not installed/misconfigured. The non-zero returncode triggers the exception.
Common situations: Wrong or empty --keyring file for the suite (Debian vs Ubuntu vs ports use different keyrings); keyring package not installed (debian-ports-archive-keyring, ubuntu-keyring); the signing key has been rotated/expired since the keyring was last updated; building in a minimal container without gpgv.
Related errors
- SHA256 mismatch for {path}: expected {packages_sha}, got {sh
- Could not find checksum for {path} in Release file.
- SHA256 mismatch for {url}: expected {checksum}, got {sha256}
- Failed to download {url}, Status Code: {response.status}
- Failed to download {url} after {max_retries} attempts.
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/86b3ba2daf045fd9.
Report an issue: GitHub.