File: gnupg.py

package info (click to toggle)
debsigs 0.2.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 420 kB
  • sloc: python: 1,160; perl: 728; makefile: 12; sh: 9
file content (83 lines) | stat: -rw-r--r-- 2,844 bytes parent folder | download | duplicates (2)
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,
        )