Skip to content

Instantly share code, notes, and snippets.

@FabianBartl
Last active February 18, 2025 20:53
Show Gist options
  • Save FabianBartl/c157af5625745aa0fed4d5958b7d7456 to your computer and use it in GitHub Desktop.
Save FabianBartl/c157af5625745aa0fed4d5958b7d7456 to your computer and use it in GitHub Desktop.
Simple Python script to verify a given GPG signature of a file. The signature can be provided as a local file or an url, where the script can download it.
"""
Simple Python script to verify a given GPG signature of a file.
The signature can be provided as a local file or an url, where the script can download it.
Example Usage:
`verify-file-gpg-signature.py --keyserver keyserver.ubuntu.com --sig_file OpenVPN-2.6.13-I001-amd64.msi.asc OpenVPN-2.6.13-I001-amd64.msi`
`verify-file-gpg-signature.py --sig_url https://swupdate.openvpn.net/community/releases/OpenVPN-2.6.13-I001-amd64.msi.asc OpenVPN-2.6.13-I001-amd64.msi`
"""
import re
import os
import argparse
import requests
import subprocess
from pathlib import Path
# required (the gpg4win installer doesn't add its path to the windows environment variable PATH)
GPG_BIN = r"C:\Program Files (x86)\GnuPG\bin\gpg.exe"
def gpg_import_key(*, keyserver: str, keyid: str, env: dict = os.environ) -> str:
global GPG_BIN
command = [GPG_BIN, "--keyserver", keyserver, "--recv-keys", keyid]
process = subprocess.Popen(command, stderr=subprocess.PIPE, encoding="utf-8", errors="replace", env=env)
_, stderr = process.communicate()
return stderr
def gpg_verify_file(*, signature: Path, file: Path, env: dict = os.environ) -> str:
global GPG_BIN
command = [GPG_BIN, "--verify", str(signature), str(file)]
process = subprocess.Popen(command, stderr=subprocess.PIPE, encoding="utf-8", errors="replace", env=env)
_, stderr = process.communicate()
return stderr
def download_signature(url: str) -> Path:
base_path = Path(r".")
filename = url.split("/")[-1] # could fail later if url contains invalid path characters (os dependent)
signature_file = base_path / filename
response = requests.get(url)
with open(signature_file, "wb") as file:
file.write(response.content)
return signature_file
def set_gpg_language(language: str) -> dict:
return dict(os.environ, LANG=language)
def main():
_parser = argparse.ArgumentParser()
_parser.add_argument("file", type=Path)
_parser.add_argument("--sig_file", type=Path, required=False)
_parser.add_argument("--sig_url", type=str, required=False)
_parser.add_argument("--keyserver", type=str, default="keyserver.ubuntu.com", required=False)
_args = _parser.parse_args()
if not _args.sig_file and not _args.sig_url:
print("The ascii signature must be provided as a file or an url.")
env = set_gpg_language("en") # must be set to "en" (english) so that the return can be parsed correctly
trusted_keyserver = _args.keyserver
file_to_verify = _args.file
asc_signature = _args.sig_file
asc_signature_url = _args.sig_url
if asc_signature_url:
print("download signature")
asc_signature = download_signature(asc_signature_url)
print("signature stored at:", str(asc_signature))
print("gpg file verification")
reponse_verification = gpg_verify_file(signature=asc_signature, file=file_to_verify, env=env)
print(reponse_verification)
if "Can't check signature: No public key".lower() in reponse_verification.lower():
print("no public key found")
extracted_keyid = re.findall(r"using RSA key ([\S]+)", reponse_verification)[0]
print("extracted key id:", extracted_keyid)
print("import key by extracted id from:", trusted_keyserver) # just add the found key to the local keyring (maybe ask the user?)
response_import = gpg_import_key(keyserver=trusted_keyserver, keyid=extracted_keyid, env=env)
print(response_import)
print("try gpg file verification again")
reponse_verification = gpg_verify_file(signature=asc_signature, file=file_to_verify, env=env)
print(reponse_verification)
if "Good signature from".lower() in reponse_verification.lower():
print(re.findall(r"(Good signature .*)\n", reponse_verification)[0])
else:
print("failed, see gpg return for more info")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment