chore(keyringctl): increase test coverage and fix trust expectations
This commit is contained in:
parent
7513e71b3f
commit
cd585f4be2
@ -196,7 +196,9 @@ def convert_certificate( # noqa: ignore=C901
|
||||
if signature_type == "CertificationRevocation":
|
||||
revocations[current_packet_uid][issuer].append(packet)
|
||||
elif signature_type.endswith("Certification"):
|
||||
if fingerprint_filter is not None and any([fp.endswith(issuer) for fp in fingerprint_filter]):
|
||||
# TODO: extend fp filter to all certifications
|
||||
# TODO: use contains_fingerprint
|
||||
if fingerprint_filter is None or any([fp.endswith(issuer) for fp in fingerprint_filter]):
|
||||
debug(f"The certification by issuer {issuer} is appended as it is found in the filter.")
|
||||
certifications[current_packet_uid][issuer].append(packet)
|
||||
else:
|
||||
@ -587,7 +589,7 @@ def convert(
|
||||
return target_dir
|
||||
|
||||
|
||||
def export_ownertrust(certs: List[Path], output: Path) -> List[Fingerprint]:
|
||||
def export_ownertrust(certs: List[Path], keyring_root: Path, output: Path) -> List[Fingerprint]:
|
||||
"""Export ownertrust from a set of keys and return the trusted and revoked fingerprints
|
||||
|
||||
The output file format is compatible with `gpg --import-ownertrust` and lists the main fingerprint ID of all
|
||||
@ -598,6 +600,7 @@ def export_ownertrust(certs: List[Path], output: Path) -> List[Fingerprint]:
|
||||
Parameters
|
||||
----------
|
||||
certs: The certificates to trust
|
||||
keyring_root: The keyring root directory to get all accepted fingerprints from
|
||||
output: The file path to write to
|
||||
|
||||
Returns
|
||||
@ -605,7 +608,11 @@ def export_ownertrust(certs: List[Path], output: Path) -> List[Fingerprint]:
|
||||
List of ownertrust fingerprints
|
||||
"""
|
||||
|
||||
main_trusts = certificate_trust_from_paths(sources=certs, main_keys=get_fingerprints_from_paths(sources=certs))
|
||||
main_trusts = certificate_trust_from_paths(
|
||||
sources=certs,
|
||||
main_keys=get_fingerprints_from_paths(sources=certs),
|
||||
all_fingerprints=get_fingerprints_from_paths([keyring_root]),
|
||||
)
|
||||
trusted_certs: List[Fingerprint] = filter_fingerprints_by_trust(main_trusts, Trust.full)
|
||||
|
||||
with open(file=output, mode="w") as trusted_certs_file:
|
||||
@ -616,7 +623,7 @@ def export_ownertrust(certs: List[Path], output: Path) -> List[Fingerprint]:
|
||||
return trusted_certs
|
||||
|
||||
|
||||
def export_revoked(certs: List[Path], main_keys: Set[Fingerprint], output: Path) -> None:
|
||||
def export_revoked(certs: List[Path], keyring_root: Path, main_keys: Set[Fingerprint], output: Path) -> None:
|
||||
"""Export the PGP revoked status from a set of keys
|
||||
|
||||
The output file contains the fingerprints of all self-revoked keys and all keys for which at least two revocations
|
||||
@ -627,11 +634,16 @@ def export_revoked(certs: List[Path], main_keys: Set[Fingerprint], output: Path)
|
||||
Parameters
|
||||
----------
|
||||
certs: A list of directories with keys to check for their revocation status
|
||||
keyring_root: The keyring root directory to get all accepted fingerprints from
|
||||
main_keys: A list of strings representing the fingerprints of (current and/or revoked) main keys
|
||||
output: The file path to write to
|
||||
"""
|
||||
|
||||
certificate_trusts = certificate_trust_from_paths(sources=certs, main_keys=main_keys)
|
||||
certificate_trusts = certificate_trust_from_paths(
|
||||
sources=certs,
|
||||
main_keys=main_keys,
|
||||
all_fingerprints=get_fingerprints_from_paths([keyring_root]),
|
||||
)
|
||||
revoked_certs: List[Fingerprint] = filter_fingerprints_by_trust(certificate_trusts, Trust.revoked)
|
||||
|
||||
with open(file=output, mode="w") as revoked_certs_file:
|
||||
@ -839,10 +851,12 @@ def build(
|
||||
|
||||
trusted_main_keys = export_ownertrust(
|
||||
certs=[keyring_root / "main"],
|
||||
keyring_root=keyring_root,
|
||||
output=target_dir / "archlinux-trusted",
|
||||
)
|
||||
export_revoked(
|
||||
certs=[keyring_root],
|
||||
keyring_root=keyring_root,
|
||||
main_keys=set(trusted_main_keys),
|
||||
output=target_dir / "archlinux-revoked",
|
||||
)
|
||||
@ -877,7 +891,9 @@ def list_keyring(keyring_root: Path, sources: Optional[List[Path]] = None, main_
|
||||
for certificate in sources:
|
||||
username: Username = Username(certificate.parent.name)
|
||||
trust = certificate_trust(
|
||||
certificate=certificate, main_keys=get_fingerprints_from_paths([keyring_root / "main"])
|
||||
certificate=certificate,
|
||||
main_keys=get_fingerprints_from_paths([keyring_root / "main"]),
|
||||
all_fingerprints=get_fingerprints_from_paths([keyring_root]),
|
||||
)
|
||||
trust_label = format_trust_label(trust=trust)
|
||||
print(f"{username:<{username_length}} {certificate.name} {trust_label}")
|
||||
|
@ -4,6 +4,7 @@ from logging import debug
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
|
||||
from .types import Color
|
||||
@ -12,9 +13,12 @@ from .types import Trust
|
||||
from .types import Uid
|
||||
from .util import contains_fingerprint
|
||||
from .util import get_cert_paths
|
||||
from .util import get_fingerprint_from_partial
|
||||
|
||||
|
||||
def certificate_trust_from_paths(sources: Iterable[Path], main_keys: Set[Fingerprint]) -> Dict[Fingerprint, Trust]:
|
||||
def certificate_trust_from_paths(
|
||||
sources: Iterable[Path], main_keys: Set[Fingerprint], all_fingerprints: Set[Fingerprint]
|
||||
) -> Dict[Fingerprint, Trust]:
|
||||
"""Get the trust status of all certificates in a list of paths given by main keys.
|
||||
|
||||
Uses `get_get_certificate_trust` to determine the trust status.
|
||||
@ -23,6 +27,7 @@ def certificate_trust_from_paths(sources: Iterable[Path], main_keys: Set[Fingerp
|
||||
----------
|
||||
sources: Certificates to acquire the trust status from
|
||||
main_keys: Fingerprints of trusted keys used to calculate the trust of the certificates from sources
|
||||
all_fingerprints: Fingerprints of all certificates, packager and main, to look up key-ids to full fingerprints
|
||||
|
||||
Returns
|
||||
-------
|
||||
@ -34,11 +39,15 @@ def certificate_trust_from_paths(sources: Iterable[Path], main_keys: Set[Fingerp
|
||||
|
||||
for certificate in sorted(sources):
|
||||
fingerprint = Fingerprint(certificate.name)
|
||||
certificate_trusts[fingerprint] = certificate_trust(certificate=certificate, main_keys=main_keys)
|
||||
certificate_trusts[fingerprint] = certificate_trust(
|
||||
certificate=certificate, main_keys=main_keys, all_fingerprints=all_fingerprints
|
||||
)
|
||||
return certificate_trusts
|
||||
|
||||
|
||||
def certificate_trust(certificate: Path, main_keys: Set[Fingerprint]) -> Trust: # noqa: ignore=C901
|
||||
def certificate_trust( # noqa: ignore=C901
|
||||
certificate: Path, main_keys: Set[Fingerprint], all_fingerprints: Set[Fingerprint]
|
||||
) -> Trust:
|
||||
"""Get the trust status of a certificates given by main keys.
|
||||
|
||||
main certificates are:
|
||||
@ -67,6 +76,7 @@ def certificate_trust(certificate: Path, main_keys: Set[Fingerprint]) -> Trust:
|
||||
----------
|
||||
certificate: Certificate to acquire the trust status from
|
||||
main_keys: Fingerprints of trusted keys used to calculate the trust of the certificates from sources
|
||||
all_fingerprints: Fingerprints of all certificates, packager and main, to look up key-ids to full fingerprints
|
||||
|
||||
Returns
|
||||
-------
|
||||
@ -78,10 +88,13 @@ def certificate_trust(certificate: Path, main_keys: Set[Fingerprint]) -> Trust:
|
||||
revocations: Set[Fingerprint] = set()
|
||||
# TODO: what about direct key revocations/signatures?
|
||||
for revocation in certificate.glob("revocation/*.asc"):
|
||||
issuer: Fingerprint = Fingerprint(revocation.stem)
|
||||
if fingerprint.endswith(issuer):
|
||||
debug(f"Revoking {fingerprint} due to self-revocation")
|
||||
revocations.add(fingerprint)
|
||||
issuer: Optional[Fingerprint] = get_fingerprint_from_partial(all_fingerprints, Fingerprint(revocation.stem))
|
||||
if not issuer:
|
||||
raise Exception(f"Unknown issuer: {issuer}")
|
||||
if not fingerprint.endswith(issuer):
|
||||
raise Exception(f"Wrong root revocation issuer: {issuer}, expected: {fingerprint}")
|
||||
debug(f"Revoking {fingerprint} due to self-revocation")
|
||||
revocations.add(fingerprint)
|
||||
|
||||
if revocations:
|
||||
return Trust.revoked
|
||||
@ -92,26 +105,28 @@ def certificate_trust(certificate: Path, main_keys: Set[Fingerprint]) -> Trust:
|
||||
return Trust.full
|
||||
|
||||
uid_trust: Dict[Uid, Trust] = {}
|
||||
self_revoked_uids: Set[Uid] = set()
|
||||
uids = certificate / "uid"
|
||||
for uid_path in uids.iterdir():
|
||||
uid: Uid = Uid(uid_path.name)
|
||||
|
||||
# TODO: convert key-id to fingerprint otherwise it may contain duplicates
|
||||
revocations = set()
|
||||
self_revoked = False
|
||||
for revocation in uid_path.glob("revocation/*.asc"):
|
||||
issuer = Fingerprint(revocation.stem)
|
||||
issuer = get_fingerprint_from_partial(all_fingerprints, Fingerprint(revocation.stem))
|
||||
if not issuer:
|
||||
raise Exception(f"Unknown issuer: {issuer}")
|
||||
# self revocation
|
||||
if fingerprint.endswith(issuer):
|
||||
self_revoked = True
|
||||
self_revoked_uids.add(uid)
|
||||
# main key revocation
|
||||
elif contains_fingerprint(fingerprints=main_keys, fingerprint=issuer):
|
||||
revocations.add(issuer)
|
||||
|
||||
# TODO: convert key-id to fingerprint otherwise it may contain duplicates
|
||||
certifications: Set[Fingerprint] = set()
|
||||
for certification in uid_path.glob("certification/*.asc"):
|
||||
issuer = Fingerprint(certification.stem)
|
||||
issuer = get_fingerprint_from_partial(all_fingerprints, Fingerprint(certification.stem))
|
||||
if not issuer:
|
||||
raise Exception(f"Unknown issuer: {issuer}")
|
||||
# only take main key certifications into account
|
||||
if not contains_fingerprint(fingerprints=main_keys, fingerprint=issuer):
|
||||
continue
|
||||
@ -121,7 +136,7 @@ def certificate_trust(certificate: Path, main_keys: Set[Fingerprint]) -> Trust:
|
||||
certifications.add(issuer)
|
||||
|
||||
# self revoked uid
|
||||
if self_revoked:
|
||||
if uid in self_revoked_uids:
|
||||
debug(f"Certificate {fingerprint} with uid {uid} is self-revoked")
|
||||
uid_trust[uid] = Trust.revoked
|
||||
continue
|
||||
@ -152,8 +167,7 @@ def certificate_trust(certificate: Path, main_keys: Set[Fingerprint]) -> Trust:
|
||||
if any(map(lambda t: Trust.full == t, uid_trust.values())):
|
||||
trust = Trust.full
|
||||
# no uid has full trust but at least one is revoked
|
||||
# TODO: only revoked if it contains main key revocations, not just self-revocation
|
||||
elif any(map(lambda t: Trust.revoked == t, uid_trust.values())):
|
||||
elif any(map(lambda e: Trust.revoked == e[1] and e[0] not in self_revoked_uids, uid_trust.items())):
|
||||
trust = Trust.revoked
|
||||
# no uid has full trust or is revoked
|
||||
elif any(map(lambda t: Trust.marginal == t, uid_trust.values())):
|
||||
|
@ -86,7 +86,12 @@ def natural_sort_path(_list: Iterable[Path]) -> Iterable[Path]:
|
||||
return sorted(_list, key=alphanum_key)
|
||||
|
||||
|
||||
def system(cmd: List[str], _stdin: Optional[IO[AnyStr]] = None, exit_on_error: bool = False) -> str:
|
||||
def system(
|
||||
cmd: List[str],
|
||||
_stdin: Optional[IO[AnyStr]] = None,
|
||||
exit_on_error: bool = False,
|
||||
env: Optional[Dict[str, str]] = None,
|
||||
) -> str:
|
||||
"""Execute a command using check_output
|
||||
|
||||
Parameters
|
||||
@ -94,6 +99,7 @@ def system(cmd: List[str], _stdin: Optional[IO[AnyStr]] = None, exit_on_error: b
|
||||
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)
|
||||
env: Optional environment vars for the shell invocation
|
||||
|
||||
Raises
|
||||
------
|
||||
@ -103,9 +109,11 @@ def system(cmd: List[str], _stdin: Optional[IO[AnyStr]] = None, exit_on_error: b
|
||||
-------
|
||||
The output of cmd
|
||||
"""
|
||||
if not env:
|
||||
env = {}
|
||||
|
||||
try:
|
||||
return check_output(cmd, stderr=STDOUT, stdin=_stdin).decode()
|
||||
return check_output(cmd, stderr=STDOUT, stdin=_stdin, env=env).decode()
|
||||
except CalledProcessError as e:
|
||||
stderr.buffer.write(bytes(e.stdout, encoding="utf8"))
|
||||
print_stack()
|
||||
@ -215,6 +223,26 @@ def contains_fingerprint(fingerprints: Iterable[Fingerprint], fingerprint: Finge
|
||||
return any(filter(lambda e: str(e).endswith(fingerprint), fingerprints))
|
||||
|
||||
|
||||
def get_fingerprint_from_partial(
|
||||
fingerprints: Iterable[Fingerprint], fingerprint: Fingerprint
|
||||
) -> Optional[Fingerprint]:
|
||||
"""Returns the full fingerprint looked up from a partial fingerprint like a key-id
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fingerprints: Iteratable structure of fingerprints that should be searched
|
||||
fingerprint: Partial fingerprint to search for
|
||||
|
||||
Returns
|
||||
-------
|
||||
The full fingerprint or None
|
||||
"""
|
||||
|
||||
for fingerprint in filter(lambda e: str(e).endswith(fingerprint), fingerprints):
|
||||
return fingerprint
|
||||
return None
|
||||
|
||||
|
||||
def filter_fingerprints_by_trust(trusts: Dict[Fingerprint, Trust], trust: Trust) -> List[Fingerprint]:
|
||||
"""Filters a dict of Fingerprint to Trust by a passed Trust parameter and returns the matching fingerprints.
|
||||
|
||||
|
@ -2,6 +2,9 @@ from collections import defaultdict
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
from shutil import copytree
|
||||
from subprocess import PIPE
|
||||
from subprocess import Popen
|
||||
from tempfile import NamedTemporaryFile
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
@ -14,6 +17,7 @@ from typing import Set
|
||||
from pytest import fixture
|
||||
|
||||
from libkeyringctl.keyring import convert_certificate
|
||||
from libkeyringctl.keyring import export
|
||||
from libkeyringctl.keyring import simplify_user_id
|
||||
from libkeyringctl.sequoia import certify
|
||||
from libkeyringctl.sequoia import key_extract_certificate
|
||||
@ -24,12 +28,15 @@ from libkeyringctl.types import Fingerprint
|
||||
from libkeyringctl.types import Uid
|
||||
from libkeyringctl.types import Username
|
||||
from libkeyringctl.util import cwd
|
||||
from libkeyringctl.util import system
|
||||
|
||||
test_keys: Dict[Username, List[Path]] = defaultdict(list)
|
||||
test_key_revocation: Dict[Username, List[Path]] = defaultdict(list)
|
||||
test_certificates: Dict[Username, List[Path]] = defaultdict(list)
|
||||
test_certificate_uids: Dict[Username, List[List[Uid]]] = defaultdict(list)
|
||||
test_keyring_certificates: Dict[Username, List[Path]] = defaultdict(list)
|
||||
test_main_fingerprints: Set[Fingerprint] = set()
|
||||
test_all_fingerprints: Set[Fingerprint] = set()
|
||||
|
||||
|
||||
@fixture(autouse=True)
|
||||
@ -37,8 +44,10 @@ def reset_storage() -> None:
|
||||
test_keys.clear()
|
||||
test_key_revocation.clear()
|
||||
test_certificates.clear()
|
||||
test_certificate_uids.clear()
|
||||
test_keyring_certificates.clear()
|
||||
test_main_fingerprints.clear()
|
||||
test_all_fingerprints.clear()
|
||||
|
||||
|
||||
def create_certificate(
|
||||
@ -50,16 +59,14 @@ def create_certificate(
|
||||
def decorator(decorated_func: Callable[..., None]) -> Callable[..., Any]:
|
||||
@wraps(decorated_func)
|
||||
def wrapper(working_dir: Path, *args: Any, **kwargs: Any) -> None:
|
||||
print(username)
|
||||
|
||||
key_directory = working_dir / "secret" / f"{id}"
|
||||
key_directory = working_dir / "secret" / f"{username}"
|
||||
key_directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
key_file: Path = key_directory / f"{username}.asc"
|
||||
key_generate(uids=uids, outfile=key_file)
|
||||
test_keys[username].append(key_file)
|
||||
|
||||
certificate_directory = working_dir / "certificate" / f"{id}"
|
||||
certificate_directory = working_dir / "certificate" / f"{username}"
|
||||
certificate_directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
keyring_root: Path = working_dir / "keyring"
|
||||
@ -68,6 +75,7 @@ def create_certificate(
|
||||
|
||||
key_extract_certificate(key=key_file, output=certificate_file)
|
||||
test_certificates[username].append(certificate_file)
|
||||
test_certificate_uids[username].append(uids)
|
||||
|
||||
key_revocation_packet = key_file.parent / f"{key_file.name}.rev"
|
||||
key_revocation_joined = key_file.parent / f"{key_file.name}.joined.rev"
|
||||
@ -88,8 +96,10 @@ def create_certificate(
|
||||
copytree(src=user_dir, dst=(target_dir / user_dir.name), dirs_exist_ok=True)
|
||||
test_keyring_certificates[username].append(target_dir / user_dir.name / decomposed_path.name)
|
||||
|
||||
certificate_fingerprint: Fingerprint = Fingerprint(decomposed_path.name)
|
||||
if "main" == keyring_type:
|
||||
test_main_fingerprints.add(Fingerprint(decomposed_path.name))
|
||||
test_main_fingerprints.add(certificate_fingerprint)
|
||||
test_all_fingerprints.add(certificate_fingerprint)
|
||||
|
||||
decorated_func(working_dir=working_dir, *args, **kwargs)
|
||||
|
||||
@ -169,6 +179,95 @@ def create_key_revocation(
|
||||
return decorator(func)
|
||||
|
||||
|
||||
def create_signature_revocation(
|
||||
issuer: Username, certified: Username, uid: Uid, func: Optional[Callable[[Any], None]] = None
|
||||
) -> Callable[..., Any]:
|
||||
def decorator(decorated_func: Callable[..., None]) -> Callable[..., Any]:
|
||||
@wraps(decorated_func)
|
||||
def wrapper(working_dir: Path, *args: Any, **kwargs: Any) -> None:
|
||||
|
||||
issuer_key: Path = test_keys[issuer][0]
|
||||
keyring_root: Path = working_dir / "keyring"
|
||||
|
||||
keyring_certificate: Path = test_keyring_certificates[certified][0]
|
||||
certified_fingerprint = keyring_certificate.name
|
||||
|
||||
with NamedTemporaryFile(dir=str(working_dir), prefix=f"{certified}", suffix=".asc") as certificate:
|
||||
certificate_path: Path = Path(certificate.name)
|
||||
export(
|
||||
working_dir=working_dir,
|
||||
keyring_root=keyring_root,
|
||||
sources=[keyring_certificate],
|
||||
output=certificate_path,
|
||||
)
|
||||
|
||||
with TemporaryDirectory(prefix="gnupg") as gnupg_home:
|
||||
env = {"GNUPGHOME": gnupg_home}
|
||||
|
||||
print(
|
||||
system(
|
||||
[
|
||||
"gpg",
|
||||
"--no-auto-check-trustdb",
|
||||
"--import",
|
||||
f"{str(issuer_key)}",
|
||||
f"{str(certificate_path)}",
|
||||
],
|
||||
env=env,
|
||||
)
|
||||
)
|
||||
|
||||
uid_confirmations = ""
|
||||
for cert_uid in test_certificate_uids[certified][0]:
|
||||
if uid == cert_uid:
|
||||
uid_confirmations += "y\n"
|
||||
else:
|
||||
uid_confirmations += "n\n"
|
||||
|
||||
commands = Popen(["echo", "-e", f"{uid_confirmations}y\n0\ny\n\ny\ny\nsave\n"], stdout=PIPE)
|
||||
system(
|
||||
[
|
||||
"gpg",
|
||||
"--no-auto-check-trustdb",
|
||||
"--command-fd",
|
||||
"0",
|
||||
"--expert",
|
||||
"--yes",
|
||||
"--batch",
|
||||
"--edit-key",
|
||||
f"{certified_fingerprint}",
|
||||
"revsig",
|
||||
"save",
|
||||
],
|
||||
_stdin=commands.stdout,
|
||||
env=env,
|
||||
)
|
||||
|
||||
revoked_certificate = system(["gpg", "--armor", "--export", f"{certified_fingerprint}"], env=env)
|
||||
certificate.truncate(0)
|
||||
certificate.seek(0)
|
||||
certificate.write(revoked_certificate.encode())
|
||||
certificate.flush()
|
||||
|
||||
target_dir = keyring_root / "packager"
|
||||
decomposed_path: Path = convert_certificate(
|
||||
working_dir=working_dir,
|
||||
certificate=certificate_path,
|
||||
keyring_dir=target_dir,
|
||||
)
|
||||
user_dir = decomposed_path.parent
|
||||
(target_dir / user_dir.name).mkdir(parents=True, exist_ok=True)
|
||||
copytree(src=user_dir, dst=(target_dir / user_dir.name), dirs_exist_ok=True)
|
||||
|
||||
decorated_func(working_dir=working_dir, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
if not func:
|
||||
return decorator
|
||||
return decorator(func)
|
||||
|
||||
|
||||
@fixture(scope="function")
|
||||
def working_dir() -> Generator[Path, None, None]:
|
||||
with TemporaryDirectory(prefix="arch-keyringctl-test-") as tempdir:
|
||||
|
@ -55,26 +55,22 @@ def test_keyring_split(mkdtemp_mock: Mock, system_mock: Mock, create_subdir: boo
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
"force, output",
|
||||
"output",
|
||||
[
|
||||
(True, None),
|
||||
(False, None),
|
||||
(True, Path("output")),
|
||||
(False, Path("output")),
|
||||
None,
|
||||
Path("output"),
|
||||
],
|
||||
)
|
||||
@patch("libkeyringctl.sequoia.system")
|
||||
def test_keyring_merge(system_mock: Mock, force: bool, output: Optional[Path]) -> None:
|
||||
def test_keyring_merge(system_mock: Mock, output: Optional[Path]) -> None:
|
||||
certificates = [Path("foo"), Path("bar")]
|
||||
system_mock.return_value = "return"
|
||||
|
||||
assert sequoia.keyring_merge(certificates=certificates, output=output, force=force) == "return"
|
||||
assert sequoia.keyring_merge(certificates=certificates, output=output) == "return"
|
||||
|
||||
name, args, kwargs = system_mock.mock_calls[0]
|
||||
for cert in certificates:
|
||||
assert str(cert) in args[0]
|
||||
if force:
|
||||
assert "--force" == args[0][1]
|
||||
if output:
|
||||
assert "--output" in args[0] and str(output) in args[0]
|
||||
|
||||
|
@ -1,22 +1,68 @@
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import patch
|
||||
|
||||
from pytest import mark
|
||||
from pytest import raises
|
||||
|
||||
from libkeyringctl.trust import certificate_trust
|
||||
from libkeyringctl.trust import certificate_trust_from_paths
|
||||
from libkeyringctl.trust import format_trust_label
|
||||
from libkeyringctl.trust import trust_color
|
||||
from libkeyringctl.trust import trust_icon
|
||||
from libkeyringctl.types import Color
|
||||
from libkeyringctl.types import Fingerprint
|
||||
from libkeyringctl.types import Trust
|
||||
from libkeyringctl.types import Uid
|
||||
from libkeyringctl.types import Username
|
||||
|
||||
from .conftest import create_certificate
|
||||
from .conftest import create_key_revocation
|
||||
from .conftest import create_signature_revocation
|
||||
from .conftest import create_uid_certification
|
||||
from .conftest import test_all_fingerprints
|
||||
from .conftest import test_keyring_certificates
|
||||
from .conftest import test_main_fingerprints
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
"sources",
|
||||
[
|
||||
([Path("foobar")]),
|
||||
([Path("foobar"), Path("quxdoo")]),
|
||||
],
|
||||
)
|
||||
@patch("libkeyringctl.trust.certificate_trust")
|
||||
def test_certificate_trust_from_paths(
|
||||
certificate_trust_mock: Mock,
|
||||
sources: List[Path],
|
||||
) -> None:
|
||||
certificate_trust_mock.return_value = Trust.full
|
||||
for source in sources:
|
||||
source.mkdir(parents=True, exist_ok=True)
|
||||
cert = source / "foo.asc"
|
||||
cert.touch()
|
||||
|
||||
trusts = certificate_trust_from_paths(
|
||||
sources=sources, main_keys=test_main_fingerprints, all_fingerprints=test_all_fingerprints
|
||||
)
|
||||
for i, source in enumerate(sources):
|
||||
name, args, kwargs = certificate_trust_mock.mock_calls[i]
|
||||
assert kwargs["certificate"] == source
|
||||
assert kwargs["main_keys"] == test_main_fingerprints
|
||||
assert kwargs["all_fingerprints"] == test_all_fingerprints
|
||||
fingerprint = Fingerprint(source.name)
|
||||
assert Trust.full == trusts[fingerprint]
|
||||
assert len(trusts) == len(sources)
|
||||
|
||||
|
||||
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")], keyring_type="main")
|
||||
def test_certificate_trust_main_key_has_full_trust(working_dir: Path, keyring_dir: Path) -> None:
|
||||
trust = certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
test_all_fingerprints,
|
||||
)
|
||||
assert Trust.full == trust
|
||||
|
||||
@ -27,16 +73,46 @@ def test_certificate_trust_main_key_revoked(working_dir: Path, keyring_dir: Path
|
||||
trust = certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
test_all_fingerprints,
|
||||
)
|
||||
assert Trust.revoked == trust
|
||||
|
||||
|
||||
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_key_revocation(username=Username("foobar"), keyring_type="main")
|
||||
def test_certificate_trust_main_key_revoked_unknown_fingerprint_lookup(working_dir: Path, keyring_dir: Path) -> None:
|
||||
fingerprint = Fingerprint(test_keyring_certificates[Username("foobar")][0].name)
|
||||
revocation = list((keyring_dir / "main" / "foobar" / fingerprint / "revocation").iterdir())[0]
|
||||
revocation.rename(revocation.parent / "12341234.asc")
|
||||
with raises(Exception):
|
||||
certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
{Fingerprint("12341234")},
|
||||
)
|
||||
|
||||
|
||||
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_key_revocation(username=Username("foobar"), keyring_type="main")
|
||||
def test_certificate_trust_main_key_revoked_unknown_self_revocation(working_dir: Path, keyring_dir: Path) -> None:
|
||||
fingerprint = Fingerprint(test_keyring_certificates[Username("foobar")][0].name)
|
||||
revocation = list((keyring_dir / "main" / "foobar" / fingerprint / "revocation").iterdir())[0]
|
||||
revocation.rename(revocation.parent / "12341234.asc")
|
||||
with raises(Exception):
|
||||
certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
set(),
|
||||
)
|
||||
|
||||
|
||||
@create_certificate(username=Username("main"), uids=[Uid("main <foo@bar.xyz>")])
|
||||
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")])
|
||||
def test_certificate_trust_no_signature_is_unknown(working_dir: Path, keyring_dir: Path) -> None:
|
||||
trust = certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
test_all_fingerprints,
|
||||
)
|
||||
assert Trust.unknown == trust
|
||||
|
||||
@ -48,6 +124,7 @@ def test_certificate_trust_one_signature_is_marginal(working_dir: Path, keyring_
|
||||
trust = certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
test_all_fingerprints,
|
||||
)
|
||||
assert Trust.marginal == trust
|
||||
|
||||
@ -60,6 +137,7 @@ def test_certificate_trust_one_none_main_signature_gives_no_trust(working_dir: P
|
||||
trust = certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
test_all_fingerprints,
|
||||
)
|
||||
assert Trust.unknown == trust
|
||||
|
||||
@ -75,16 +153,176 @@ def test_certificate_trust_three_main_signature_gives_full_trust(working_dir: Pa
|
||||
trust = certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
test_all_fingerprints,
|
||||
)
|
||||
assert Trust.full == trust
|
||||
|
||||
|
||||
@create_certificate(username=Username("main"), uids=[Uid("main <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")])
|
||||
@create_key_revocation(username=Username("foobar"), keyring_type="packager")
|
||||
@create_key_revocation(username=Username("foobar"))
|
||||
def test_certificate_trust_revoked_key(working_dir: Path, keyring_dir: Path) -> None:
|
||||
trust = certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
test_all_fingerprints,
|
||||
)
|
||||
assert Trust.revoked == trust
|
||||
|
||||
|
||||
@create_certificate(username=Username("main"), uids=[Uid("main <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")])
|
||||
@create_uid_certification(issuer=Username("main"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
@create_signature_revocation(issuer=Username("main"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
def test_certificate_trust_one_signature_revoked(working_dir: Path, keyring_dir: Path) -> None:
|
||||
trust = certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
test_all_fingerprints,
|
||||
)
|
||||
assert Trust.revoked == trust
|
||||
|
||||
|
||||
@create_certificate(username=Username("main1"), uids=[Uid("main1 <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("main2"), uids=[Uid("main2 <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("main3"), uids=[Uid("main3 <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")])
|
||||
@create_uid_certification(issuer=Username("main1"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
@create_uid_certification(issuer=Username("main2"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
@create_uid_certification(issuer=Username("main3"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
@create_signature_revocation(issuer=Username("main3"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
def test_certificate_trust_revoked_if_below_full(working_dir: Path, keyring_dir: Path) -> None:
|
||||
trust = certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
test_all_fingerprints,
|
||||
)
|
||||
assert Trust.revoked == trust
|
||||
|
||||
|
||||
@create_certificate(username=Username("main1"), uids=[Uid("main1 <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("main2"), uids=[Uid("main2 <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("main3"), uids=[Uid("main3 <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("main4"), uids=[Uid("main4 <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")])
|
||||
@create_uid_certification(issuer=Username("main1"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
@create_uid_certification(issuer=Username("main2"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
@create_uid_certification(issuer=Username("main3"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
@create_uid_certification(issuer=Username("main4"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
@create_signature_revocation(issuer=Username("main4"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
def test_certificate_trust_full_remains_if_enough_sigs_present(working_dir: Path, keyring_dir: Path) -> None:
|
||||
trust = certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
test_all_fingerprints,
|
||||
)
|
||||
assert Trust.full == trust
|
||||
|
||||
|
||||
@create_certificate(username=Username("main1"), uids=[Uid("main1 <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("main2"), uids=[Uid("main2 <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("main3"), uids=[Uid("main3 <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>"), Uid("old <old@old.old>")])
|
||||
@create_uid_certification(issuer=Username("main1"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
@create_uid_certification(issuer=Username("main2"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
@create_signature_revocation(issuer=Username("foobar"), certified=Username("foobar"), uid=Uid("old <old@old.old>"))
|
||||
def test_certificate_trust_not_revoked_if_only_one_uid_is_self_revoked(working_dir: Path, keyring_dir: Path) -> None:
|
||||
trust = certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
test_all_fingerprints,
|
||||
)
|
||||
assert Trust.marginal == trust
|
||||
|
||||
|
||||
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>"), Uid("old <old@old.old>")])
|
||||
@create_signature_revocation(issuer=Username("foobar"), certified=Username("foobar"), uid=Uid("old <old@old.old>"))
|
||||
def test_certificate_trust_unknown_if_only_contains_self_revoked(working_dir: Path, keyring_dir: Path) -> None:
|
||||
trust = certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
test_all_fingerprints,
|
||||
)
|
||||
assert Trust.unknown == trust
|
||||
|
||||
|
||||
@create_certificate(username=Username("main1"), uids=[Uid("main1 <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>"), Uid("old <old@old.old>")])
|
||||
@create_uid_certification(issuer=Username("main1"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
def test_certificate_trust_missing_signature_fingerprint_lookup(working_dir: Path, keyring_dir: Path) -> None:
|
||||
with raises(Exception):
|
||||
certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
set(),
|
||||
)
|
||||
|
||||
|
||||
@create_certificate(username=Username("foobar"), uids=[Uid("old <old@old.old>")])
|
||||
@create_signature_revocation(issuer=Username("foobar"), certified=Username("foobar"), uid=Uid("old <old@old.old>"))
|
||||
def test_certificate_trust_missing_revocation_fingerprint_lookup(working_dir: Path, keyring_dir: Path) -> None:
|
||||
with raises(Exception):
|
||||
certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
set(),
|
||||
)
|
||||
|
||||
|
||||
@create_certificate(username=Username("main1"), uids=[Uid("main1 <foo@bar.xyz>")], keyring_type="main")
|
||||
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")])
|
||||
@create_certificate(username=Username("packager"), uids=[Uid("packager <packager@bar.xyz>")])
|
||||
@create_uid_certification(issuer=Username("main1"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
@create_uid_certification(issuer=Username("packager"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
@create_signature_revocation(issuer=Username("packager"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
|
||||
def test_certificate_trust_ignore_3rd_party_revocation(working_dir: Path, keyring_dir: Path) -> None:
|
||||
trust = certificate_trust(
|
||||
test_keyring_certificates[Username("foobar")][0],
|
||||
test_main_fingerprints,
|
||||
test_all_fingerprints,
|
||||
)
|
||||
assert Trust.marginal == trust
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
"trust, result",
|
||||
[
|
||||
(Trust.revoked, Color.RED),
|
||||
(Trust.full, Color.GREEN),
|
||||
(Trust.marginal, Color.YELLOW),
|
||||
(Trust.unknown, Color.YELLOW),
|
||||
],
|
||||
)
|
||||
def test_trust_color(trust: Trust, result: Color) -> None:
|
||||
assert trust_color(trust) == result
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
"trust, result",
|
||||
[
|
||||
(Trust.revoked, "✗"),
|
||||
(Trust.full, "✓"),
|
||||
(Trust.marginal, "~"),
|
||||
(Trust.unknown, "~"),
|
||||
(None, "?"),
|
||||
],
|
||||
)
|
||||
def test_trust_icon(trust: Trust, result: str) -> None:
|
||||
assert trust_icon(trust) == result
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
"trust",
|
||||
[
|
||||
Trust.revoked,
|
||||
Trust.full,
|
||||
Trust.marginal,
|
||||
Trust.unknown,
|
||||
],
|
||||
)
|
||||
@patch("libkeyringctl.trust.trust_icon")
|
||||
@patch("libkeyringctl.trust.trust_color")
|
||||
def test_format_trust_label(trust_color_mock: Mock, trust_icon_mock: Mock, trust: Trust) -> None:
|
||||
trust_icon_mock.return_value = "ICON"
|
||||
trust_color_mock.return_value = Color.GREEN
|
||||
assert f"{Color.GREEN.value}ICON {trust.name}{Color.RST.value}" == format_trust_label(trust)
|
||||
|
@ -3,6 +3,7 @@ from os import getcwd
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import patch
|
||||
@ -12,6 +13,7 @@ from pytest import raises
|
||||
|
||||
from libkeyringctl import util
|
||||
from libkeyringctl.types import Fingerprint
|
||||
from libkeyringctl.types import Trust
|
||||
|
||||
|
||||
def test_cwd() -> None:
|
||||
@ -93,7 +95,7 @@ def test_get_cert_paths() -> None:
|
||||
cert2 = cert_dir2 / "cert2.asc"
|
||||
cert2.touch()
|
||||
|
||||
assert util.get_cert_paths(paths=[tmp_dir]) == set([cert_dir1, cert_dir2])
|
||||
assert util.get_cert_paths(paths=[tmp_dir]) == {cert_dir1, cert_dir2}
|
||||
|
||||
|
||||
def test_get_parent_cert_paths() -> None:
|
||||
@ -112,7 +114,7 @@ def test_get_parent_cert_paths() -> None:
|
||||
cert2 = cert_dir2 / "cert2.asc"
|
||||
cert2.touch()
|
||||
|
||||
assert util.get_parent_cert_paths(paths=[cert1, cert2]) == set([cert_dir1])
|
||||
assert util.get_parent_cert_paths(paths=[cert1, cert2]) == {cert_dir1}
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
@ -132,3 +134,50 @@ def test_get_parent_cert_paths() -> None:
|
||||
)
|
||||
def test_contains_fingerprint(fingerprints: List[Fingerprint], fingerprint: Fingerprint, result: bool) -> None:
|
||||
assert util.contains_fingerprint(fingerprints=fingerprints, fingerprint=fingerprint) is result
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
"fingerprints, fingerprint, result",
|
||||
[
|
||||
([Fingerprint("blahfoo"), Fingerprint("blahbar")], Fingerprint("foo"), Fingerprint("blahfoo")),
|
||||
([Fingerprint("blahfoo"), Fingerprint("blahbar")], Fingerprint("blahfoo"), Fingerprint("blahfoo")),
|
||||
(
|
||||
[Fingerprint("bazfoo"), Fingerprint("bazbar")],
|
||||
Fingerprint("baz"),
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get_fingerprint_from_partial(fingerprints: List[Fingerprint], fingerprint: Fingerprint, result: bool) -> None:
|
||||
assert util.get_fingerprint_from_partial(fingerprints=fingerprints, fingerprint=fingerprint) is result
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
"trusts, trust, result",
|
||||
[
|
||||
(
|
||||
{Fingerprint("foo"): Trust.full, Fingerprint("bar"): Trust.marginal},
|
||||
Trust.full,
|
||||
[Fingerprint("foo")],
|
||||
),
|
||||
(
|
||||
{Fingerprint("foo"): Trust.full, Fingerprint("bar"): Trust.full},
|
||||
Trust.full,
|
||||
[Fingerprint("foo"), Fingerprint("bar")],
|
||||
),
|
||||
(
|
||||
{Fingerprint("foo"): Trust.full, Fingerprint("bar"): Trust.marginal},
|
||||
Trust.unknown,
|
||||
[],
|
||||
),
|
||||
(
|
||||
{},
|
||||
Trust.unknown,
|
||||
[],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_filter_fingerprints_by_trust(
|
||||
trusts: Dict[Fingerprint, Trust], trust: Trust, result: List[Fingerprint]
|
||||
) -> None:
|
||||
assert util.filter_fingerprints_by_trust(trusts=trusts, trust=trust) == result
|
||||
|
Loading…
Reference in New Issue
Block a user