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

  1. 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.
  2. 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.
  3. Re-download Release and Release.gpg from a canonical mirror to rule out truncation/staleness.
  4. 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

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


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)