Files
custom-debian-iso/gpg/verify.py
ulinja b69fd8cfa9 Reimplementation (#1)
* refactor into separate modules

* update README
2022-08-01 12:23:41 +02:00

129 lines
4.5 KiB
Python

"""Utilities for verifying files using gpg."""
from re import compile
from pathlib import Path
from subprocess import run, PIPE, STDOUT
import gpg.exceptions as ex
def assert_detached_signature_is_valid(
path_to_input_file,
path_to_signature_file
):
"""Verifies the input file using the specified detached gpg signature.
The invoking user's local gpg key store is used for verification, the
public key used to create the signature must be present in the invoking
user's local gpg key store.
Parameters
----------
path_to_input_file : str or pathlike object
Path to the file which should be verified.
path_to_signature_file : str or pathlike object
Path to the file containing a detached gpg signature of the input file.
Raises
------
FileNotFoundError
If either of the input files do not exist.
gpg.exceptions.MissingLocalKeyError
If a key referenced by the signature file could not be found in the
invoking user's local key store.
gpg.exceptions.VerificationFailedError
If the gpg verification detects a bad signature.
"""
if '~' in str(path_to_input_file):
path_to_input_file = Path(path_to_input_file).expanduser()
path_to_input_file = Path(path_to_input_file).resolve()
if '~' in str(path_to_signature_file):
path_to_signature_file = Path(path_to_signature_file).expanduser()
path_to_signature_file = Path(path_to_signature_file).resolve()
if not path_to_input_file.is_file():
raise FileNotFoundError(
f"No such file: '{path_to_input_file}'."
)
if not path_to_signature_file.is_file():
raise FileNotFoundError(
f"No such file: '{path_to_signature_file}'."
)
# execute a gpg verification as a shell command, redirecting stderr to
# stdout
process_result = run(
[
"gpg", "--verify", path_to_signature_file, path_to_input_file,
],
stdout=PIPE,
stderr=STDOUT,
text=True,
)
output_lines = process_result.stdout.split("\n")
if len(output_lines) < 3:
raise ex.UnexpectedOutputException(
f"Unexpected output during gpg verification:\n"
f"{process_result.stdout}"
)
if process_result.returncode == 2:
# a missing local key causes return code 2
# and the following output on the third line:
missing_key_regex = compile(
r"^gpg: Can't check signature: No public key$"
)
# an invalid detached signature file causes return code 2
# and the following output on the first line:
invalid_signature_regex = compile(
r"^gpg: no valid OpenPGP data found.$"
)
if missing_key_regex.match(output_lines[2]):
raise ex.MissingLocalKeyError(
"Failed to verify gpg signature: no matching local key."
)
elif invalid_signature_regex.match(output_lines[0]):
raise ex.InvalidSignatureError(
"Invalid signature file."
)
else:
raise ex.UnexpectedOutputException(
f"Unexpected output during gpg verification:\n"
f"{process_result.stdout}"
)
elif process_result.returncode == 1:
# failed verification causes return code 1
# and the following output on line 3:
verification_failed_regex = compile(
r"^gpg: BAD signature from .*$"
)
if not verification_failed_regex.match(output_lines[2]):
raise ex.UnexpectedOutputException(
f"Unexpected output during gpg verification:\n"
f"{process_result.stdout}"
)
else:
raise ex.VerificationFailedError(
"gpg signature verification failed: BAD SIGNATURE!"
)
elif process_result.returncode == 0:
# successful verification causes return code 0
# and the following output on line 3:
verification_successful_regex = compile(
r"^gpg: Good signature from .*$"
)
if not verification_successful_regex.match(output_lines[2]):
raise ex.UnexpectedOutputException(
f"Unexpected output during gpg verification:\n"
f"{process_result.stdout}"
)
else:
raise ex.UnexpectedOutputException(
f"Unexpected return code during gpg verification:\n"
f"{process_result.returncode}"
f"{process_result.stdout}"
)