chore(keyringctl): increase test coverage and fix trust expectations

This commit is contained in:
Levente Polyak 2021-11-04 19:26:11 +01:00
parent 7513e71b3f
commit cd585f4be2
No known key found for this signature in database
GPG Key ID: FC1B547C8D8172C8
7 changed files with 481 additions and 41 deletions

View File

@ -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}")

View File

@ -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,8 +88,11 @@ 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):
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)
@ -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())):

View File

@ -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.

View File

@ -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:

View File

@ -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]

View File

@ -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)

View File

@ -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