1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
|
# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
# SPDX-License-Identifier: GPL-2.0-or-later
"""Run a stateless OpenPGP command-line tool."""
from __future__ import annotations
import subprocess # noqa: S404
import typing
from testsigs.parse import gnupg as p_gnupg
from . import base
if typing.TYPE_CHECKING:
import pathlib
from typing import Final
from testsigs import defs
def _gnupg_list_keys(cfg: defs.Config) -> list[defs.PublicKey]:
"""Run `gpg --list-keys` and partially parse the output."""
list_output: Final = subprocess.check_output( # noqa: S603
[cfg.prog.gnupg, "--batch", "--with-colons", "--list-keys"],
cwd=cfg.path.work,
encoding="UTF-8",
env=cfg.env,
)
print(list_output) # noqa: T201
return p_gnupg.parse_list_keys(list_output)
class GnuPGCli(base.PGPCli):
"""Invoke GnuPG as far as we can in batch mode."""
def generate_keys(self, uid: str) -> defs.KeyFiles:
"""Generate an OpenPGP key to sign with."""
# GnuPG makes it... difficult to generate keys in an unattended manner.
raise NotImplementedError
def import_secret_key(self, keys: defs.KeyFiles, uid: str) -> defs.PublicKey:
"""Import a secret key, return the parts of the public key we care about."""
no_keys: Final = _gnupg_list_keys(self.cfg)
if no_keys:
raise RuntimeError(repr((self.cfg.prog.gnupg, no_keys)))
subprocess.check_call( # noqa: S603
[self.cfg.prog.gnupg, "--import", keys.secret_bin],
cwd=self.cfg.path.work,
env=self.cfg.env,
)
single_key: Final = _gnupg_list_keys(self.cfg)
if len(single_key) != 1 or single_key[0].uid != uid:
raise RuntimeError(repr((self.cfg.prog.gnupg, single_key)))
return single_key[0]
def sign_detached(self, _keys: defs.KeyFiles, datafile: pathlib.Path) -> pathlib.Path:
"""Create an ASCII-armored detached signature, return the path to the new file."""
signature: Final = datafile.with_name(datafile.name + ".asc")
subprocess.check_call( # noqa: S603
[self.cfg.prog.gnupg, "-ab", datafile.name],
cwd=self.cfg.path.work,
env=self.cfg.env,
)
if not signature.is_file():
raise RuntimeError(repr((datafile, signature)))
return signature
def verify_detached(
self,
_keys: defs.KeyFiles,
_pub_key: defs.PublicKey,
datafile: pathlib.Path,
signature: pathlib.Path,
) -> None:
"""Verify an OpenPGP detached signature in a file."""
subprocess.check_call( # noqa: S603
[self.cfg.prog.gnupg, "--verify", signature, datafile],
cwd=self.cfg.path.work,
env=self.cfg.env,
)
|