feature(keyringctl): add verify command to check certificate expectation
This command checks certain expectations using sq and hokey, prints the results to stdout and potentially exists non successfully.
This commit is contained in:
parent
94c3b4c8e9
commit
9733fbafd8
11
README.md
11
README.md
@ -18,6 +18,10 @@ from the provided data structure and to install it:
|
||||
* python
|
||||
* sequoia-sq
|
||||
|
||||
Optional:
|
||||
* hopenpgp-tools (verify)
|
||||
* sq-keyring-linter (verify)
|
||||
|
||||
## Usage
|
||||
|
||||
### Build
|
||||
@ -85,6 +89,13 @@ Only inspect a specific main key
|
||||
./keyringctl inspect --main <username_or_fingerprint_or_directory...>
|
||||
```
|
||||
|
||||
### Verify
|
||||
|
||||
Verify certificates against modern expectations and assumptions
|
||||
```bash
|
||||
./keyringctl verify <username_or_fingerprint_or_directory...>
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
To install archlinux-keyring system-wide use the included `Makefile`:
|
||||
|
@ -14,6 +14,7 @@ from .keyring import convert
|
||||
from .keyring import export
|
||||
from .keyring import inspect_keyring
|
||||
from .keyring import list_keyring
|
||||
from .keyring import verify
|
||||
from .util import absolute_path
|
||||
from .util import cwd
|
||||
|
||||
@ -97,8 +98,24 @@ inspect_parser.add_argument(
|
||||
type=absolute_path,
|
||||
)
|
||||
|
||||
verify_parser = subcommands.add_parser(
|
||||
"verify",
|
||||
help="verify certificates against modern expectations",
|
||||
)
|
||||
verify_parser.add_argument(
|
||||
"source",
|
||||
nargs="*",
|
||||
help="username, fingerprint or directories containing certificates",
|
||||
type=absolute_path,
|
||||
)
|
||||
verify_parser.add_argument("--no-lint-hokey", dest="lint_hokey", action="store_false", help="Do not run hokey lint")
|
||||
verify_parser.add_argument(
|
||||
"--no-lint-sq-keyring", dest="lint_sq_keyring", action="store_false", help="Do not run sq-keyring-linter"
|
||||
)
|
||||
verify_parser.set_defaults(lint_hokey=True, lint_sq_keyring=True)
|
||||
|
||||
def main() -> None:
|
||||
|
||||
def main() -> None: # noqa: ignore=C901
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
@ -165,6 +182,14 @@ def main() -> None:
|
||||
),
|
||||
end="",
|
||||
)
|
||||
elif "verify" == args.subcommand:
|
||||
verify(
|
||||
working_dir=working_dir,
|
||||
keyring_root=keyring_root,
|
||||
sources=args.source,
|
||||
lint_hokey=args.lint_hokey,
|
||||
lint_sq_keyring=args.lint_sq_keyring,
|
||||
)
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
@ -9,6 +9,8 @@ from re import escape
|
||||
from re import match
|
||||
from re import sub
|
||||
from shutil import copytree
|
||||
from subprocess import PIPE
|
||||
from subprocess import Popen
|
||||
from tempfile import mkdtemp
|
||||
from tempfile import mkstemp
|
||||
from typing import Dict
|
||||
@ -27,6 +29,7 @@ from .sequoia import packet_split
|
||||
from .types import Fingerprint
|
||||
from .types import Uid
|
||||
from .types import Username
|
||||
from .util import system
|
||||
|
||||
|
||||
def is_pgp_fingerprint(string: str) -> bool:
|
||||
@ -977,3 +980,49 @@ def inspect_keyring(working_dir: Path, keyring_root: Path, sources: Optional[Lis
|
||||
certifications=True,
|
||||
fingerprints=fingerprints,
|
||||
)
|
||||
|
||||
|
||||
def verify(
|
||||
working_dir: Path,
|
||||
keyring_root: Path,
|
||||
sources: Optional[List[Path]],
|
||||
lint_hokey: bool = True,
|
||||
lint_sq_keyring: bool = True,
|
||||
) -> None:
|
||||
"""Verify certificates against modern expectations using sq-keyring-linter and hokey
|
||||
|
||||
Parameters
|
||||
----------
|
||||
working_dir: A directory to use for temporary files
|
||||
keyring_root: The keyring root directory to look up username shorthand sources
|
||||
sources: A list of username, fingerprint or directories from which to read PGP packet information
|
||||
(defaults to `keyring_root`)
|
||||
lint_hokey: Whether to run hokey lint
|
||||
lint_sq_keyring: Whether to run sq-keyring-linter
|
||||
"""
|
||||
|
||||
if not sources:
|
||||
sources = [keyring_root]
|
||||
|
||||
# transform shorthand paths to actual keyring paths
|
||||
transform_username_to_keyring_path(keyring_dir=keyring_root / "packager", paths=sources)
|
||||
transform_fingerprint_to_keyring_path(keyring_root=keyring_root, paths=sources)
|
||||
|
||||
cert_paths: Set[Path] = get_cert_paths(sources)
|
||||
|
||||
for certificate in sorted(cert_paths):
|
||||
print(f"Verify {certificate.name} owned by {certificate.parent.name}")
|
||||
keyring = Path(
|
||||
mkstemp(dir=working_dir, prefix=f"{certificate.parent.name}-{certificate.name}", suffix=".asc")[1]
|
||||
).absolute()
|
||||
export(
|
||||
working_dir=working_dir,
|
||||
keyring_root=keyring_root,
|
||||
sources=[certificate],
|
||||
output=keyring,
|
||||
)
|
||||
if lint_hokey:
|
||||
keyring_fd = Popen(("sq", "dearmor", f"{str(keyring)}"), stdout=PIPE)
|
||||
print(system(["hokey", "lint"], _stdin=keyring_fd.stdout), end="")
|
||||
if lint_sq_keyring:
|
||||
print(system(["sq-keyring-linter", f"{str(keyring)}"]), end="")
|
||||
|
@ -7,13 +7,16 @@ from os import chdir
|
||||
from os import getcwd
|
||||
from pathlib import Path
|
||||
from re import split
|
||||
from subprocess import PIPE
|
||||
from subprocess import STDOUT
|
||||
from subprocess import CalledProcessError
|
||||
from subprocess import check_output
|
||||
from sys import exit
|
||||
from sys import stderr
|
||||
from traceback import print_stack
|
||||
from typing import IO
|
||||
from typing import AnyStr
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
|
||||
@ -77,12 +80,13 @@ def natural_sort_path(_list: Iterable[Path]) -> Iterable[Path]:
|
||||
return sorted(_list, key=alphanum_key)
|
||||
|
||||
|
||||
def system(cmd: List[str], exit_on_error: bool = False) -> str:
|
||||
def system(cmd: List[str], _stdin: Optional[IO[AnyStr]] = None, exit_on_error: bool = False) -> str:
|
||||
"""Execute a command using check_output
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cmd: A list of strings to be fed to check_output
|
||||
_stdin: input fd used for the spawned process
|
||||
exit_on_error: Whether to exit the script when encountering an error (defaults to False)
|
||||
|
||||
Raises
|
||||
@ -95,9 +99,9 @@ def system(cmd: List[str], exit_on_error: bool = False) -> str:
|
||||
"""
|
||||
|
||||
try:
|
||||
return check_output(cmd, stderr=PIPE).decode()
|
||||
return check_output(cmd, stderr=STDOUT, stdin=_stdin).decode()
|
||||
except CalledProcessError as e:
|
||||
stderr.buffer.write(e.stderr)
|
||||
stderr.buffer.write(e.stdout)
|
||||
print_stack()
|
||||
if exit_on_error:
|
||||
exit(e.returncode)
|
||||
|
Loading…
Reference in New Issue
Block a user