8ba7dc1dc9
Rework the whole trust handling by acquiring the trust status from actual assumptions related to the amount of ownertrust signatures and revocations.
225 lines
7.4 KiB
Python
225 lines
7.4 KiB
Python
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from logging import debug
|
|
from pathlib import Path
|
|
from typing import Dict
|
|
from typing import Iterable
|
|
from typing import Set
|
|
|
|
from .types import Color
|
|
from .types import Fingerprint
|
|
from .types import Trust
|
|
from .types import Uid
|
|
from .util import contains_fingerprint
|
|
from .util import get_cert_paths
|
|
|
|
|
|
def certificate_trust_from_paths(sources: Iterable[Path], main_keys: 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.
|
|
|
|
Parameters
|
|
----------
|
|
sources: Certificates to acquire the trust status from
|
|
main_keys: Fingerprints of trusted keys used to calculate the trust of the certificates from sources
|
|
|
|
Returns
|
|
-------
|
|
A dictionary of fingerprints and their trust level
|
|
"""
|
|
|
|
sources = get_cert_paths(sources)
|
|
certificate_trusts: Dict[Fingerprint, Trust] = {}
|
|
|
|
for certificate in sorted(sources):
|
|
fingerprint = Fingerprint(certificate.name)
|
|
certificate_trusts[fingerprint] = certificate_trust(certificate=certificate, main_keys=main_keys)
|
|
return certificate_trusts
|
|
|
|
|
|
def certificate_trust(certificate: Path, main_keys: Set[Fingerprint]) -> Trust: # noqa: ignore=C901
|
|
"""Get the trust status of a certificates given by main keys.
|
|
|
|
main certificates are:
|
|
revoked if:
|
|
- the certificate has been self-revoked (also applies to 3rd party applied revocation certificates)
|
|
full trust if:
|
|
- the certificate is not self-revoked
|
|
|
|
regular certificates are:
|
|
full trust if:
|
|
- the certificate is not self-revoked and:
|
|
- any uid contains at least 3 non revoked main key signatures
|
|
marginal trust if:
|
|
- the certificate is not self-revoked and:
|
|
- any uid contains at least 1 but less than 3 non revoked main key signatures
|
|
- no uid contains at least 3 non revoked main key signatures
|
|
unknown trust if:
|
|
- the certificate is not self-revoked and:
|
|
- no uid contains any non revoked main key signature
|
|
revoked if:
|
|
- the certificate has been self-revoked, or
|
|
- no uid contains at least 3 non revoked main key signatures and:
|
|
- any uid contains at least 1 revoked main key signature
|
|
|
|
Parameters
|
|
----------
|
|
certificate: Certificate to acquire the trust status from
|
|
main_keys: Fingerprints of trusted keys used to calculate the trust of the certificates from sources
|
|
|
|
Returns
|
|
-------
|
|
Trust level of the certificate
|
|
"""
|
|
|
|
fingerprint: Fingerprint = Fingerprint(certificate.name)
|
|
|
|
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)
|
|
|
|
if revocations:
|
|
return Trust.revoked
|
|
|
|
# main keys are either trusted or revoked
|
|
is_main_certificate = contains_fingerprint(fingerprints=main_keys, fingerprint=fingerprint)
|
|
if is_main_certificate:
|
|
return Trust.full
|
|
|
|
uid_trust: Dict[Uid, Trust] = {}
|
|
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)
|
|
# self revocation
|
|
if fingerprint.endswith(issuer):
|
|
self_revoked = True
|
|
# 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)
|
|
# only take main key certifications into account
|
|
if not contains_fingerprint(fingerprints=main_keys, fingerprint=issuer):
|
|
continue
|
|
# do not care about certifications that are revoked
|
|
if contains_fingerprint(fingerprints=revocations, fingerprint=issuer):
|
|
continue
|
|
certifications.add(issuer)
|
|
|
|
# self revoked uid
|
|
if self_revoked:
|
|
debug(f"Certificate {fingerprint} with uid {uid} is self-revoked")
|
|
uid_trust[uid] = Trust.revoked
|
|
continue
|
|
|
|
# full trust
|
|
if len(certifications) >= 3:
|
|
uid_trust[uid] = Trust.full
|
|
continue
|
|
|
|
# no full trust and contains revocations
|
|
if revocations:
|
|
uid_trust[uid] = Trust.revoked
|
|
continue
|
|
|
|
# marginal trust
|
|
if certifications:
|
|
uid_trust[uid] = Trust.marginal
|
|
continue
|
|
|
|
# no trust
|
|
uid_trust[uid] = Trust.unknown
|
|
|
|
for uid, uid_trust_status in uid_trust.items():
|
|
debug(f"Certificate {fingerprint} with uid {uid} has trust level: {uid_trust_status.name}")
|
|
|
|
trust: Trust
|
|
# any uid has full 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())):
|
|
trust = Trust.revoked
|
|
# no uid has full trust or is revoked
|
|
elif any(map(lambda t: Trust.marginal == t, uid_trust.values())):
|
|
trust = Trust.marginal
|
|
else:
|
|
trust = Trust.unknown
|
|
|
|
debug(f"Certificate {fingerprint} has trust level: {trust.name}")
|
|
return trust
|
|
|
|
|
|
def trust_icon(trust: Trust) -> str:
|
|
"""Returns a single character icon representing the passed trust status
|
|
|
|
Parameters
|
|
----------
|
|
trust: The trust to get an icon for
|
|
|
|
Returns
|
|
-------
|
|
The single character icon representing the passed trust status
|
|
"""
|
|
if trust == Trust.revoked:
|
|
return "✗"
|
|
if trust == Trust.unknown:
|
|
return "~"
|
|
if trust == Trust.marginal:
|
|
return "~"
|
|
if trust == Trust.full:
|
|
return "✓"
|
|
return "?"
|
|
|
|
|
|
def trust_color(trust: Trust) -> Color:
|
|
"""Returns a color representing the passed trust status
|
|
|
|
Parameters
|
|
----------
|
|
trust: The trust to get the color of
|
|
|
|
Returns
|
|
-------
|
|
The color representing the passed trust status
|
|
"""
|
|
color: Color = Color.RED
|
|
if trust == Trust.revoked:
|
|
color = Color.RED
|
|
if trust == Trust.unknown:
|
|
color = Color.YELLOW
|
|
if trust == Trust.marginal:
|
|
color = Color.YELLOW
|
|
if trust == Trust.full:
|
|
color = Color.GREEN
|
|
return color
|
|
|
|
|
|
def format_trust_label(trust: Trust) -> str:
|
|
"""Formats a given trust status to a text label including color and icon.
|
|
|
|
Parameters
|
|
----------
|
|
trust: The trust to get the label for
|
|
|
|
Returns
|
|
-------
|
|
Text label representing the trust status as literal and icon with colors
|
|
"""
|
|
return f"{trust_color(trust).value}{trust_icon(trust)} {trust.name}{Color.RST.value}"
|