dotnet/efcore · critical · Exception
Signature verification failed: {result.stderr.decode('utf-8'
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 with the supplied keyring. gpgv is used deliberately (instead of gpg) to avoid gpg-agent/keyboxd issues on GnuPG 2.4+ hosts. A failure means the detached signature does not validate the Release file using the keys in the keyring.
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 dbf9771522)
Solutions
- Install the matching archive keyring package for your distro/suite (e.g. debian-ports-archive-keyring, ubuntu-keyring, debian-archive-keyring) and point --keyring at it.
- Read {result.stderr} from the message - gpgv states 'NO_PUBKEY <keyid>' (missing key), 'BADSIG' (tampered), or 'EXPKEYSIG' (expired) which points to the exact cause.
- Sync the system clock (NTP) so signature time validity is evaluated correctly.
- If you intentionally trust the source, drop --force-check-gpg / pass --skipsigcheck to build-rootfs.sh rather than ignoring the failure.
Example fix
# before python install-debs.py --force-check-gpg --keyring /usr/share/keyrings/debian-archive-keyring.gpg --mirror http://ftp.ports.debian.org/debian-ports ... # after (ports mirror needs the ports keyring) apt-get install debian-ports-archive-keyring python install-debs.py --force-check-gpg --keyring /usr/share/keyrings/debian-ports-archive-keyring.gpg --mirror http://ftp.ports.debian.org/debian-ports ...
Defensive patterns
Strategy: validation
Validate before calling
# Validate the keyring actually contains the suite's signing key before calling
import subprocess, sys
def keyring_has_key(keyring, keyid):
if not keyring or not __import__('os').path.exists(keyring):
raise RuntimeError(f"Keyring missing or not found: {keyring!r}")
out = subprocess.run(['gpgv', '--keyring', keyring, '--list-packets', '/dev/null'],
capture_output=True) # smoke: gpgv accepts the keyring
# Better: dump keys with gpg --no-default-keyring --keyring <keyring> --list-keys
res = subprocess.run(['gpg', '--no-default-keyring', '--keyring', keyring, '--list-keys', '--with-colons'],
capture_output=True, text=True)
if keyid not in res.stdout:
raise RuntimeError(f"Keyring {keyring} lacks key {keyid}; install the right archive-keyring package")
return True Try / catch
# Signature failures are security-critical; do not retry silently
try:
content = await fetch_release_file(session, mirror, suite, keyring)
except Exception as e:
msg = str(e)
if "Signature verification failed" in msg:
if "NO_PUBKEY" in msg:
hint = "Keyring is missing the signing key - install the matching archive-keyring package."
elif "EXPKEYSIG" in msg or "EXP" in msg:
hint = "Signing key expired - update the keyring package."
elif "BADSIG" in msg:
hint = "Release file tampered with - treat as integrity incident."
else:
hint = "Check system clock and keyring path."
raise SystemExit(f"{msg}\nHint: {hint}")
raise Prevention
- Always pair --force-check-gpg with the correct distro-specific --keyring (debian vs debian-ports vs ubuntu).
- Read the gpgv stderr token (NO_PUBKEY / EXPKEYSIG / BADSIG) before guessing.
- Keep NTP enabled; signature time windows are clock-sensitive.
- Install the archive-keyring package from the distro itself rather than hand-rolling keyrings.
When it happens
Trigger: Running with --force-check-gpg and a --keyring that does not contain the signing key for the suite; the Release file has been modified after signing; the signing key is expired/revoked; the keyring path is wrong/empty; system clock is far off so signature validity windows are misjudged.
Common situations: Forgot to install the distro keyring package; passed an Ubuntu keyring against a Debian(-ports) mirror or vice versa; used an outdated keyring whose archive-signing key has rotated/expired; mis-typed the --keyring path; NTP off so gpgv rejects a not-yet-valid or expired signature.
Related errors
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/412afeb8d4adc786.
Report an issue: GitHub.