{"record":{"id":"86b3ba2daf045fd9","repo":"dotnet/aspnetcore","slug":"signature-verification-failed-result-stderr-deco","errorCode":null,"errorMessage":"Signature verification failed: {result.stderr.decode('utf-8')}","messagePattern":"Signature verification failed: (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"eng/common/cross/install-debs.py","lineNumber":135,"sourceCode":"    release_gpg_url = f\"{mirror}/dists/{suite}/Release.gpg\"\n\n    with tempfile.NamedTemporaryFile() as release_file, tempfile.NamedTemporaryFile() as release_gpg_file:\n        await download_file(session, release_url, release_file.name)\n        await download_file(session, release_gpg_url, release_gpg_file.name)\n\n        print(\"Verifying signature of Release with Release.gpg.\")\n        # Use gpgv rather than gpg for verification. gpgv verifies a detached\n        # signature against a fixed keyring without involving gpg-agent or\n        # keyboxd, which makes it robust on hosts running GnuPG 2.4+ (e.g. Azure\n        # Linux) where \"gpg --keyring\" routes through keyboxd and can fail.\n        verify_command = [\"gpgv\"]\n        if keyring:\n            verify_command += [\"--keyring\", keyring]\n        verify_command += [release_gpg_file.name, release_file.name]\n        result = subprocess.run(verify_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n\n        if result.returncode != 0:\n            raise Exception(f\"Signature verification failed: {result.stderr.decode('utf-8')}\")\n\n        print(\"Signature verified successfully.\")\n\n        with open(release_file.name) as f:\n            return f.read()\n\ndef parse_release_file(content, path):\n    \"\"\"Parses the Release file and returns sha256 checksum of the specified path.\"\"\"\n\n    # data looks like this:\n    # <checksum>  <size>  <path>\n    matches = re.findall(r'^ (\\S*) +(\\S*) +(\\S*)$', content, re.MULTILINE)\n\n    for entry in matches:\n        # the file has both md5 and sha256 checksums, we want sha256 which has a length of 64\n        if entry[2] == path and len(entry[0]) == 64:\n            return entry[0]\n","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/3600ca084e9c8b5f4174fc5e747f4c52d2100806/eng/common/cross/install-debs.py#L117-L153","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"# before: wrong keyring for the mirror\npython3 install-debs.py --mirror http://deb.debian.org/debian --suite trixie --arch amd64 \\\n  --rootfsdir rootfs --force-check-gpg --keyring ubuntu-keyring.gpg libc6\n\n# after: matching keyring\napt-get install -y debian-archive-keyring\npython3 install-debs.py --mirror http://deb.debian.org/debian --suite trixie --arch amd64 \\\n  --rootfsdir rootfs --force-check-gpg \\\n  --keyring /usr/share/keyrings/debian-archive-keyring.gpg libc6","handlingStrategy":"validation","validationCode":"# Pre-flight: ensure the keyring contains the suite's signing key, and that gpgv works.\nrequired=$(curl -fsS \"$MIRROR/dists/$SUITE/Release.gpg\" | gpgv --keyring \"$KEYRING\" --status-fd 1 /dev/stdin <(curl -fsS \"$MIRROR/dists/$SUITE/Release\") 2>&1)\nprintf '%s\\n' \"$required\" | grep -q GOODSIG || { echo 'keyring missing signing key'; exit 1; }","typeGuard":null,"tryCatchPattern":"try:\n    asyncio.run(fetch_release_file(session, mirror, suite, keyring))\nexcept Exception as e:\n    msg = str(e)\n    if 'No public key' in msg: print('Install/upgrade the matching archive-keyring package.')\n    elif 'BAD signature' in msg: print('Tampered or truncated Release; switch mirror.')\n    raise","preventionTips":["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."],"tags":["python","gpg","signature","debian","security","cross-build"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}