{"record":{"id":"81ae112157195e05","repo":"dotnet/runtime","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/runtime/blob/60108ba66eb7d1d12f595480091b4ad80a24b172/eng/common/cross/install-debs.py#L117-L153","documentation":"With --force-check-gpg, install-debs.py downloads Release and Release.gpg and runs gpgv to verify the detached signature against a keyring. If gpgv exits non-zero (expired/revoked/missing key, malformed signature, wrong keyring, or tampered Release), the build refuses to proceed and surfaces gpgv's stderr so the operator can see the exact OpenPGP failure.","triggerScenarios":"gpgv returns non-zero at install-debs.py:132-135. Caused by: the suite's signing key rotated and your keyring lacks the new one, the keyring path is wrong/empty, the Release.gpg is stale relative to Release, or GnuPG 2.4+ keyboxd routing issues (the comment notes gpgv was chosen specifically to avoid that).","commonSituations":"Forgetting to pass --keyring or pointing it at an outdated file. Distribution rolled its archive key (Debian does this periodically). Behind a proxy that corrupted Release.gpg. Host with a partially-initialized GnuPG config.","solutions":["Install the appropriate archive keyring package (e.g. debian-archive-keyring, ubuntu-archive-keyring, debian-ports-archive-keyring) and point --keyring at it.","Update the keyring package to the latest version so it contains the current signing key.","Read the embedded gpgv stderr in the exception - 'NO_PUBKEY' tells you which key id to import.","As a last resort in a trusted environment, drop --force-check-gpg, but prefer fixing the keyring."],"exampleFix":"# before\npython3 install-debs.py --force-check-gpg --keyring /tmp/old.kbx --suite trixie ...\n# Signature verification failed: NO_PUBKEY ABCD1234...\n\n# after\napt-get install -y debian-archive-keyring\npython3 install-debs.py --force-check-gpg --keyring /usr/share/keyrings/debian-archive-keyring.gpg --suite trixie ...","handlingStrategy":"validation","validationCode":"# Verify the keyring contains the suite's signing key BEFORE the full build.\nimport subprocess\ndef keyring_has_key(keyring: str, key_id: str) -> bool:\n    rc = subprocess.run(['gpg', '--no-default-keyring', '--keyring', keyring, '--list-keys', key_id],\n                        stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)\n    return rc.returncode == 0\n\n# Or test gpgv directly against Release/Release.gpg of the chosen suite.","typeGuard":null,"tryCatchPattern":"try:\n    main()\nexcept Exception as e:\n    if 'Signature verification failed' in str(e):\n        print('Install/update the archive keyring, then re-run with --force-check-gpg.')\n        sys.exit(2)\n    raise","preventionTips":["Install the latest debian-archive-keyring / ubuntu-archive-keyring / debian-ports-archive-keyring.","Pass --keyring explicitly to avoid GnuPG default keybox routing issues (gpgv is used intentionally).","Read the NO_PUBKEY line in the error to import the exact missing key."],"tags":["dotnet-rootfs","debian","gpg","signature","security","python"],"backgroundTag":null,"analyzedSha":"60108ba66eb7d1d12f595480091b4ad80a24b172","analyzedAt":"2026-08-10T18:54:11.478Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}