Verified Commit 9733fbaf authored by Levente Polyak's avatar Levente Polyak 🚀
Browse files

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.
parent 94c3b4c8
......@@ -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)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment