commit:     bbf8790c330358a618bdac44f4a488d99a4b5007
Author:     Michał Górny <mgorny <AT> gentoo <DOT> org>
AuthorDate: Tue Oct 21 11:36:52 2025 +0000
Commit:     Michał Górny <mgorny <AT> gentoo <DOT> org>
CommitDate: Tue Oct 21 11:36:52 2025 +0000
URL:        https://gitweb.gentoo.org/proj/gemato.git/commit/?id=bbf8790c

openpgp: Support list_keys() in all envs

Signed-off-by: Michał Górny <mgorny <AT> gentoo.org>

 gemato/openpgp.py     | 110 +++++++++++++++++++++++++-------------------------
 tests/test_openpgp.py |  23 +++++++++--
 2 files changed, 76 insertions(+), 57 deletions(-)

diff --git a/gemato/openpgp.py b/gemato/openpgp.py
index fede3e5..f148765 100644
--- a/gemato/openpgp.py
+++ b/gemato/openpgp.py
@@ -2,6 +2,8 @@
 # (c) 2017-2024 Michał Górny
 # SPDX-License-Identifier: GPL-2.0-or-later
 
+from __future__ import annotations
+
 import base64
 import dataclasses
 import datetime
@@ -138,6 +140,60 @@ class SystemGPGEnvironment:
         raise NotImplementedError(
             'import_key() is not implemented by this OpenPGP provider')
 
+    def list_keys(self, key_ids: list[str] = []) -> dict[str, list[str]]:
+        """
+        List fingerprints and UIDs of specified keys or all keys in keyring
+
+        Returns a mapping from fingerprint (as a string) to an iterable
+        of UIDs.
+        """
+
+        exitst, out, err = self._spawn_gpg(
+            [GNUPG, '--batch', '--with-colons', '--list-keys', *key_ids],
+            raise_on_error=OpenPGPKeyListingError)
+
+        prev_pub = None
+        fpr = None
+        ret = {}
+
+        for line in out.splitlines():
+            # were we expecting a fingerprint?
+            if prev_pub is not None:
+                if line.startswith(b'fpr:'):
+                    fpr = line.split(b':')[9].decode('ASCII')
+                    if not fpr.endswith(prev_pub):
+                        raise OpenPGPKeyListingError(
+                            f'Incorrect fingerprint {fpr} for key '
+                            f'{prev_pub}')
+                    LOGGER.debug(
+                        f'list_keys(): fingerprint: {fpr}')
+                    ret[fpr] = []
+                    prev_pub = None
+                else:
+                    raise OpenPGPKeyListingError(
+                        f'No fingerprint in GPG output, instead got: '
+                        f'{line}')
+            elif line.startswith(b'pub:'):
+                # wait for the fingerprint
+                prev_pub = line.split(b':')[4].decode('ASCII')
+                LOGGER.debug(f'list_keys(): keyid: {prev_pub}')
+            elif line.startswith(b'uid:'):
+                if fpr is None:
+                    raise OpenPGPKeyListingError(
+                        f'UID without key in GPG output: {line}')
+                uid = line.split(b':')[9]
+                _, addr = email.utils.parseaddr(
+                    uid.decode('utf8', errors='replace'))
+                if '@' in addr:
+                    LOGGER.debug(f'list_keys(): UID: {addr}')
+                    ret[fpr].append(addr)
+                else:
+                    LOGGER.debug(
+                        f'list_keys(): ignoring UID without mail: '
+                        f'{uid!r}')
+
+        return ret
+
     def refresh_keys(self, allow_wkd=True, keyserver=None):
         """
         Update the keys from their assigned keyservers. This should be called
@@ -502,60 +558,6 @@ debug-level guru
                 ownertrust,
                 raise_on_error=OpenPGPKeyImportError)
 
-    def list_keys(self):
-        """
-        List fingerprints and UIDs of all keys in keyring
-
-        Returns a mapping from fingerprint (as a string) to an iterable
-        of UIDs.
-        """
-
-        exitst, out, err = self._spawn_gpg(
-            [GNUPG, '--batch', '--with-colons', '--list-keys'],
-            raise_on_error=OpenPGPKeyListingError)
-
-        prev_pub = None
-        fpr = None
-        ret = {}
-
-        for line in out.splitlines():
-            # were we expecting a fingerprint?
-            if prev_pub is not None:
-                if line.startswith(b'fpr:'):
-                    fpr = line.split(b':')[9].decode('ASCII')
-                    if not fpr.endswith(prev_pub):
-                        raise OpenPGPKeyListingError(
-                            f'Incorrect fingerprint {fpr} for key '
-                            f'{prev_pub}')
-                    LOGGER.debug(
-                        f'list_keys(): fingerprint: {fpr}')
-                    ret[fpr] = []
-                    prev_pub = None
-                else:
-                    raise OpenPGPKeyListingError(
-                        f'No fingerprint in GPG output, instead got: '
-                        f'{line}')
-            elif line.startswith(b'pub:'):
-                # wait for the fingerprint
-                prev_pub = line.split(b':')[4].decode('ASCII')
-                LOGGER.debug(f'list_keys(): keyid: {prev_pub}')
-            elif line.startswith(b'uid:'):
-                if fpr is None:
-                    raise OpenPGPKeyListingError(
-                        f'UID without key in GPG output: {line}')
-                uid = line.split(b':')[9]
-                _, addr = email.utils.parseaddr(
-                    uid.decode('utf8', errors='replace'))
-                if '@' in addr:
-                    LOGGER.debug(f'list_keys(): UID: {addr}')
-                    ret[fpr].append(addr)
-                else:
-                    LOGGER.debug(
-                        f'list_keys(): ignoring UID without mail: '
-                        f'{uid!r}')
-
-        return ret
-
     def refresh_keys_wkd(self):
         """
         Attempt to fetch updated keys using WKD.  Returns true if *all*

diff --git a/tests/test_openpgp.py b/tests/test_openpgp.py
index 357cad4..6019019 100644
--- a/tests/test_openpgp.py
+++ b/tests/test_openpgp.py
@@ -24,6 +24,7 @@ from gemato.exceptions import (
     OpenPGPExpiredKeyFailure,
     OpenPGPRevokedKeyFailure,
     OpenPGPKeyImportError,
+    OpenPGPKeyListingError,
     OpenPGPKeyRefreshError,
     OpenPGPRuntimeError,
     OpenPGPUntrustedSigFailure,
@@ -808,12 +809,28 @@ def 
test_recursive_manifest_loader_save_submanifest(tmp_path, privkey_env):
      ('VALID_KEY_NOEMAIL', {KEY_FINGERPRINT: []}),
      ('VALID_KEY_NONUTF', {KEY_FINGERPRINT: ['[email protected]']}),
      ])
-def test_list_keys(openpgp_env_with_refresh, key_var, expected):
+def test_list_keys(openpgp_env, key_var, expected):
     try:
-        openpgp_env_with_refresh.import_key(io.BytesIO(globals()[key_var]))
+        openpgp_env.import_key(io.BytesIO(globals()[key_var]))
+    except OpenPGPNoImplementation as e:
+        pytest.skip(str(e))
+    assert openpgp_env.list_keys() == expected
+    assert openpgp_env.list_keys(list(expected.keys())) == expected
+
+
+def test_list_keys_empty(openpgp_env):
+    try:
+        assert openpgp_env.list_keys() == {}
+    except OpenPGPNoImplementation as e:
+        pytest.skip(str(e))
+
+
+def test_list_keys_error(openpgp_env):
+    try:
+        with pytest.raises(OpenPGPKeyListingError):
+            openpgp_env.list_keys(["zzzzz"])
     except OpenPGPNoImplementation as e:
         pytest.skip(str(e))
-    assert openpgp_env_with_refresh.list_keys() == expected
 
 
 @pytest.fixture(scope='module')

Reply via email to