Last active
February 18, 2025 20:53
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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