commit:     42847dc3ba230bce06ebca836b0bc90ac5521304
Author:     Sam James <sam <AT> gentoo <DOT> org>
AuthorDate: Mon Jan  5 04:56:50 2026 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Mon Jan  5 21:12:01 2026 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=42847dc3

gpkg: provide error summaries if verification fails

Do a pass over the error output to see if we can produce a short summary
of the problem for the user, then share the raw GnuPG output marked clearly
as such below.

For example, if /etc/portage/gnupg doesn't exist..

Before:
```
 * Binary package is not usable:
 *    gpg: keyblock resource '/etc/portage/gnupg/pubring.kbx': No such file or 
directory
 *    [GNUPG:] ERROR add_keyblock_resource 33587281
 *    [GNUPG:] PLAINTEXT 74 0
 *    [GNUPG:] NEWSIG
 *    gpg: Signature made Thu 09 Oct 2025 15:22:49 BST
 *    gpg:                using RSA key 534E4209AB49EEE1C19D96162C44695DB9F6043D
 *    [GNUPG:] ERROR keydb_search 33554445
 *    [GNUPG:] ERROR keydb_search 33554445
 *    [GNUPG:] ERRSIG 2C44695DB9F6043D 1 10 01 1760019769 9 
534E4209AB49EEE1C19D96162C44695DB9F6043D
 *    [GNUPG:] NO_PUBKEY 2C44695DB9F6043D
 *    gpg: Can't check signature: No public key
 *    [GNUPG:] FAILURE gpg-exit 33554433
 *    gpg: can't create `/etc/portage/gnupg/random_seed': No such file or 
directory
!!! Invalid binary package: 
'/var/cache/binhost/gentoo/sys-libs/glibc/glibc-2.41-r6-4.gpkg.tar', GnuPG 
verification failed
```

After:
```
 * Binary package is not usable (verification failed):
 *  Summary:
 *      binpkg signed with at least one unknown key (try running 
PORTAGE_TRUST_HELPER=getuto?)
 *
 *  Raw GnuPG output:
 *      gpg: keyblock resource '/etc/portage/gnupg/pubring.kbx': No such file 
or directory
 *      [GNUPG:] ERROR add_keyblock_resource 33587281
 *      [GNUPG:] PLAINTEXT 74 0
 *      [GNUPG:] NEWSIG
 *      gpg: Signature made Thu 09 Oct 2025 15:22:49 BST
 *      gpg:                using RSA key 
534E4209AB49EEE1C19D96162C44695DB9F6043D
 *      [GNUPG:] ERROR keydb_search 33554445
 *      [GNUPG:] ERROR keydb_search 33554445
 *      [GNUPG:] ERRSIG 2C44695DB9F6043D 1 10 01 1760019769 9 
534E4209AB49EEE1C19D96162C44695DB9F6043D
 *      [GNUPG:] NO_PUBKEY 2C44695DB9F6043D
 *      gpg: Can't check signature: No public key
 *      [GNUPG:] FAILURE gpg-exit 33554433
 *      gpg: can't create `/etc/portage/gnupg/random_seed': No such file or 
directory
!!! Invalid binary package: 
'/var/cache/binhost/gentoo/sys-libs/glibc/glibc-2.41-r6-4.gpkg.tar', GnuPG 
verification failed
```

This helps to identify the common issues users have:
* no keyring at all at /etc/portage/gnupg, or
* binpkg isn't signed (local binpkg of their own but verification is
 enabled globally)

Bug: https://bugs.gentoo.org/930730
Bug: https://bugs.gentoo.org/966958
Signed-off-by: Sam James <sam <AT> gentoo.org>

 lib/portage/gpkg.py | 70 +++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 52 insertions(+), 18 deletions(-)

diff --git a/lib/portage/gpkg.py b/lib/portage/gpkg.py
index 67a4d39ff3..4f6a05cc84 100644
--- a/lib/portage/gpkg.py
+++ b/lib/portage/gpkg.py
@@ -1,4 +1,4 @@
-# Copyright 2001-2025 Gentoo Authors
+# Copyright 2001-2026 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 import tarfile
@@ -9,6 +9,7 @@ import subprocess
 import errno
 import pwd
 import grp
+import re
 import shlex
 import stat
 import sys
@@ -541,6 +542,50 @@ class checksum_helper:
     def __del__(self):
         self.finish()
 
+    def show_gpg_error(self, operation, gpg_error_lines):
+        """
+        Interpret GnuPG output to give a pretty error message
+        with a summary if possible.
+        """
+        if operation == checksum_helper.VERIFY:
+            operation_blurb = "verification failed"
+        elif operation == checksum_helper.SIGNING:
+            operation_blurb = "signing failed"
+
+        # Attempt to give a nicer error the sniffing the status output.
+        error_summaries = []
+        portage_trust_helper = self.settings.get("PORTAGE_TRUST_HELPER", "")
+
+        def _match_list(regex: re.Pattern, msgs: list) -> list[re.Match]:
+            return list(filter(lambda s: re.match(regex, s), msgs))
+
+        if _match_list(r"^\[GNUPG:\] NODATA", gpg_error_lines):
+            error_summaries.append("binpkg appears unsigned (missing any 
signature)")
+        if _match_list(r"^\[GNUPG:\] NO_PUBKEY", gpg_error_lines):
+            error_summaries.append(
+                "binpkg signed with at least one unknown key "
+                + f"(try running PORTAGE_TRUST_HELPER={portage_trust_helper}?)"
+            )
+        if _match_list(r"^\[GNUPG:\] TRUST_UNDEFINED", gpg_error_lines):
+            error_summaries.append(
+                f"binpkg signed with a known key of undefined trust "
+                + f"(try running PORTAGE_TRUST_HELPER={portage_trust_helper}?)"
+            )
+
+        # Don't show any summary if it's ambiguous, in case of
+        # a malformed signature.
+        if len(error_summaries) > 1 or not error_summaries:
+            error_summaries = ["(none available)"]
+
+        out = portage.output.EOutput()
+        msg = [f"Binary package is not usable ({operation_blurb}):"]
+        msg.append(" Summary:")
+        msg.extend("\t" + line for line in error_summaries)
+        msg.append("")
+        msg.append(" Raw GnuPG output:")
+        msg.extend("\t" + line for line in gpg_error_lines)
+        [out.eerror(line) for line in msg]
+
     def _check_gpg_status(self, gpg_status: bytes) -> None:
         """
         Check GnuPG status log for extra info.
@@ -559,16 +604,10 @@ class checksum_helper:
                 trust_signature = True
 
         if (not good_signature) or (not trust_signature):
-            msg = ["Binary package is not usable:"]
-            msg.extend(
-                "\t" + line
-                for line in self.gpg_result.decode(
-                    "UTF-8", errors="replace"
-                ).splitlines()
-            )
-            out = portage.output.EOutput()
-            [out.eerror(line) for line in msg]
-
+            gpg_error_lines = self.gpg_result.decode(
+                "UTF-8", errors="replace"
+            ).splitlines()
+            self.show_gpg_error(checksum_helper.VERIFY, gpg_error_lines)
             raise InvalidSignature("GnuPG verification failed")
 
     def update(self, data):
@@ -611,17 +650,12 @@ class checksum_helper:
                 gpg_error_lines = self.gpg_result.decode(
                     "UTF-8", errors="replace"
                 ).splitlines()
-                out = portage.output.EOutput()
 
                 if self.gpg_operation == checksum_helper.SIGNING:
-                    msg = ["Binary package is not usable (signing failed):"]
-                    msg.extend("\t" + line for line in gpg_error_lines)
-                    [out.eerror(line) for line in msg]
+                    self.show_gpg_error(checksum_helper.SIGNING, 
gpg_error_lines)
                     raise GPGException("GnuPG signing failed")
                 elif self.gpg_operation == checksum_helper.VERIFY:
-                    msg = ["Binary package is not usable (verification 
failed):"]
-                    msg.extend("\t" + line for line in gpg_error_lines)
-                    [out.eerror(line) for line in msg]
+                    self.show_gpg_error(checksum_helper.VERIFY, 
gpg_error_lines)
                     raise InvalidSignature("GnuPG verification failed")
 
 

Reply via email to