diff --git a/libkeyringctl/keyring.py b/libkeyringctl/keyring.py index 0f22a79..d64986d 100644 --- a/libkeyringctl/keyring.py +++ b/libkeyringctl/keyring.py @@ -5,9 +5,7 @@ from collections.abc import Iterable from itertools import chain from logging import debug from pathlib import Path -from re import escape from re import match -from re import sub from shutil import copytree from tempfile import NamedTemporaryFile from tempfile import mkdtemp @@ -33,6 +31,7 @@ from .types import Username from .util import filter_fingerprints_by_trust from .util import get_cert_paths from .util import get_fingerprint_from_partial +from .util import simplify_ascii from .util import transform_fd_to_tmpfile @@ -161,7 +160,7 @@ def convert_certificate( # noqa: ignore=C901 elif packet.name.endswith("--UserID"): current_packet_mode = "uid" current_packet_fingerprint = None - current_packet_uid = simplify_user_id(Uid(packet_dump_field(packet, "Value"))) + current_packet_uid = Uid(simplify_ascii(packet_dump_field(packet, "Value"))) uids[current_packet_uid] = packet elif packet.name.endswith("--PublicSubkey"): @@ -493,24 +492,6 @@ def persist_uid_revocations( packet_join(packets=[revocation], output=output_file, force=True) -def simplify_user_id(user_id: Uid) -> Uid: - """Simplify the User ID string to contain more filesystem friendly characters - - Parameters - ---------- - user_id: A User ID string (e.g. 'Foobar McFooface ') - - Returns - ------- - The simplified representation of user_id - """ - - user_id_str: str = user_id.replace("@", "_at_") - user_id_str = sub("[<>]", "", user_id_str) - user_id_str = sub("[" + escape(r" !@#$%^&*()_-+=[]{}\|;:,.<>/?") + "]", "_", user_id_str) - return Uid(user_id_str) - - def derive_username_from_fingerprint(keyring_dir: Path, certificate_fingerprint: Fingerprint) -> Optional[Username]: """Attempt to derive the username of a public key fingerprint from a keyring directory diff --git a/libkeyringctl/util.py b/libkeyringctl/util.py index f57c7ce..b128dd7 100644 --- a/libkeyringctl/util.py +++ b/libkeyringctl/util.py @@ -1,5 +1,4 @@ # SPDX-License-Identifier: GPL-3.0-or-later - from collections.abc import Iterable from collections.abc import Iterator from contextlib import contextmanager @@ -7,7 +6,11 @@ from os import chdir from os import environ from os import getcwd from pathlib import Path +from re import escape from re import split +from re import sub +from string import ascii_letters +from string import digits from subprocess import STDOUT from subprocess import CalledProcessError from subprocess import check_output @@ -263,3 +266,51 @@ def filter_fingerprints_by_trust(trusts: Dict[Fingerprint, Trust], trust: Trust) filter(lambda item: trust == item[1], trusts.items()), ) ) + + +simple_printable: str = ascii_letters + digits + "_-.+@" +ascii_mapping: Dict[str, str] = { + "àáâãäæąăǎа": "a", + "ćçĉċč": "c", + "ďđ": "d", + "éèêëęēĕėěɇ": "e", + "ĝğġģ": "g", + "ĥħȟ": "h", + "ìíîïĩīĭįıij": "i", + "ĵɉ": "j", + "ķ": "k", + "ł": "l", + "ńņň": "n", + "òóôõöøŏőðȍǿ": "o", + "śș": "s", + "ß": "ss", + "ț": "t", + "úûüȗűȕù": "u", + "ýÿ": "y", + "źż": "z", +} +ascii_mapping_lookup: Dict[str, str] = {} +for key, value in ascii_mapping.items(): + for c in key: + if c in ascii_mapping_lookup: + raise Exception(f"duplicate ascii mapping: {c}") + ascii_mapping_lookup[c] = value + ascii_mapping_lookup[c.upper()] = value.upper() + + +def simplify_ascii(_str: str) -> str: + """Simplify a string to contain more filesystem and printable friendly characters + + Parameters + ---------- + _str: A string to simplify (e.g. 'Foobar McFooface ') + + Returns + ------- + The simplified representation of _str + """ + _str = _str.strip("<") + _str = _str.strip(">") + _str = "".join([ascii_mapping_lookup.get(char) or char for char in _str]) + _str = sub("[^" + escape(simple_printable) + "]", "_", _str) + return _str diff --git a/libkeyringctl/verify.py b/libkeyringctl/verify.py index 575780d..fc2ac1f 100644 --- a/libkeyringctl/verify.py +++ b/libkeyringctl/verify.py @@ -10,7 +10,6 @@ from typing import Set from libkeyringctl.keyring import export from libkeyringctl.keyring import get_fingerprints_from_paths from libkeyringctl.keyring import is_pgp_fingerprint -from libkeyringctl.keyring import simplify_user_id from libkeyringctl.keyring import transform_fingerprint_to_keyring_path from libkeyringctl.keyring import transform_username_to_keyring_path from libkeyringctl.sequoia import packet_dump_field @@ -20,6 +19,7 @@ from libkeyringctl.types import PacketKind from libkeyringctl.types import Uid from libkeyringctl.util import get_cert_paths from libkeyringctl.util import get_fingerprint_from_partial +from libkeyringctl.util import simplify_ascii from libkeyringctl.util import system @@ -161,7 +161,7 @@ def verify_integrity(certificate: Path, all_fingerprints: Set[Fingerprint]) -> N kind = kinds[0] if kind != "User": raise Exception(f"Unexpected packet in file {str(uid_path)}: {kind}") - uid_value = simplify_user_id(Uid(packet_dump_field(packet=uid_path, field="Value"))) + uid_value = Uid(simplify_ascii(packet_dump_field(packet=uid_path, field="Value"))) if uid_value != uid.name: raise Exception(f"Unexpected uid in file {str(uid_path)}: {uid_value}") elif not uid_path.is_dir(): diff --git a/tests/conftest.py b/tests/conftest.py index 11e255a..51e1d9f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,7 +25,6 @@ from pytest import fixture from libkeyringctl.keyring import convert_certificate from libkeyringctl.keyring import export from libkeyringctl.keyring import get_fingerprints_from_keyring_files -from libkeyringctl.keyring import simplify_user_id from libkeyringctl.sequoia import certify from libkeyringctl.sequoia import key_extract_certificate from libkeyringctl.sequoia import key_generate @@ -35,6 +34,7 @@ from libkeyringctl.types import Fingerprint from libkeyringctl.types import Uid from libkeyringctl.types import Username from libkeyringctl.util import cwd +from libkeyringctl.util import simplify_ascii from libkeyringctl.util import system test_keys: Dict[Username, List[Path]] = defaultdict(list) @@ -133,7 +133,7 @@ def create_uid_certification( certificate: Path = test_certificates[certified][0] fingerprint: Fingerprint = Fingerprint(test_keyring_certificates[certified][0].name) issuer_fingerprint: Fingerprint = Fingerprint(test_keyring_certificates[issuer][0].name) - simplified_uid = simplify_user_id(uid) + simplified_uid = Uid(simplify_ascii(uid)) output: Path = ( working_dir