Fix importing OpenPGPv6 keys
Resolves: RHEL-114424
This commit is contained in:
parent
08bd97b467
commit
c7d12da4a0
242
0024-multisig-Do-not-parse-OpenPGP-keys.patch
Normal file
242
0024-multisig-Do-not-parse-OpenPGP-keys.patch
Normal file
@ -0,0 +1,242 @@
|
||||
From 3fd00a0bf41ac2c9342e0ba1d8550ee1ac5f2604 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
|
||||
Date: Fri, 12 Sep 2025 18:12:38 +0200
|
||||
Subject: [PATCH] multisig: Do not parse OpenPGP keys
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
If a key in OpenPGP version 6 format format was defined for
|
||||
a repository, the key was always omitted and never imported to pqrpm
|
||||
key store. As a result, packages signed with that key could not be
|
||||
verified:
|
||||
|
||||
[...]
|
||||
Importing GPG key 0xC9D3B13C:
|
||||
Userid : "rsa4k_user"
|
||||
Fingerprint: CE77 8FBE 1E8D 4DD2 7691 FB33 CA58 1189 C9D3 B13C
|
||||
From : /root/repo/rsa4k.cert
|
||||
Key imported successfully
|
||||
foo 16 MB/s | 16 kB 00:00
|
||||
Import of key(s) didn't help, wrong key(s)?
|
||||
Public key for foo-1.0-1.noarch.rpm is not installed. Failing package is: foo-1.0-1.noarch
|
||||
GPG Keys are configured as: file:///root/repo/rsa4k.cert, file:///root/repo/mldsa87.cert
|
||||
Error: GPG check FAILED
|
||||
|
||||
The cause was that multisig plugin called dnf.crypto.retrieve() to
|
||||
obtain a list of OpenPGP objects from the gpgkey URLs. That DNF
|
||||
function uses dnf.crypto.rawkey2infos() to parse the OpenPGP packets
|
||||
with a gpgme library. But library does not support OpenPGPv6. As
|
||||
a result, multisig got an empty list of OpenPGP objects and dis not
|
||||
import any key in OpenPGPv6 format into pqrm key store.
|
||||
|
||||
Considering that the only available OpenPGPv6 parser is rpm-sequoia
|
||||
library wrapped by pqrpm's librpmio library, and this plugin cannot
|
||||
load them into its name space not to clash with the system librpmio
|
||||
library, an ideal fix would have to develop standalone executable on
|
||||
top of them and parse it output by this plugin. And considering that
|
||||
best code is no code,
|
||||
|
||||
I removed printing any details about the imported keys from this
|
||||
plugin. The only details about the key this plugin now prints is URL
|
||||
or local path to the key file:
|
||||
|
||||
Importing GPG keys from: /root/repos/opgp6/mldsa65.cert
|
||||
Is this ok [y/N]: y
|
||||
Key imported successfully
|
||||
|
||||
Because the user ID and key ID were also required for DNSSEC
|
||||
validation, I had to remove that code. This patch also removes all
|
||||
code that became unreachable because of this fix.
|
||||
|
||||
Another downside is that now the plugin cannot does not check whether
|
||||
a key has already been imported, resulting always asking a using for
|
||||
importing the keys associated with a repository the unverifiable
|
||||
package comes from.
|
||||
|
||||
Resolve: https://issues.redhat.com/browse/RHEL-114424
|
||||
Signed-off-by: Petr Písař <ppisar@redhat.com>
|
||||
---
|
||||
plugins/multisig.py | 115 +++++++++++++++-----------------------------
|
||||
1 file changed, 40 insertions(+), 75 deletions(-)
|
||||
|
||||
diff --git a/plugins/multisig.py b/plugins/multisig.py
|
||||
index 8735a26..f29e41f 100644
|
||||
--- a/plugins/multisig.py
|
||||
+++ b/plugins/multisig.py
|
||||
@@ -1,16 +1,26 @@
|
||||
from __future__ import print_function, absolute_import, unicode_literals
|
||||
import dnf
|
||||
-import dnf.crypto
|
||||
-import dnf.dnssec
|
||||
import dnf.exceptions
|
||||
from dnf.i18n import ucd
|
||||
import dnf.rpm.transaction
|
||||
import dnf.transaction
|
||||
+import dnf.util
|
||||
from dnfpluginscore import _, logger
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
+class MultiSigKey(object):
|
||||
+ def __init__(self, data, url):
|
||||
+ self.id_ = None
|
||||
+ self.fingerprint = None
|
||||
+ self.timestamp = None
|
||||
+ self.raw_key = data
|
||||
+ self.url = url
|
||||
+ self.userid = None
|
||||
+ self.short_id = None
|
||||
+ self.rpm_id = None
|
||||
+
|
||||
class MultiSig(dnf.Plugin):
|
||||
"""
|
||||
This plugin verifies signatures of RPM packages by executing an
|
||||
@@ -168,33 +178,6 @@ class MultiSig(dnf.Plugin):
|
||||
logger.debug(_("Multisig: verification result: {} (code={})").format(msg, result))
|
||||
return result, msg
|
||||
|
||||
- def keyInstalled(self, fingerprint):
|
||||
- '''
|
||||
- Return if the GPG key described by the given fingerprint is installed
|
||||
- in the multisig keyring.
|
||||
-
|
||||
- Return values:
|
||||
- - True key is installed
|
||||
- - False otherwise
|
||||
- Trows: If rpmkeys program could not been executed.
|
||||
-
|
||||
- No effort is made to handle duplicates.
|
||||
- '''
|
||||
- # XXX: rpmkeys expects lowercase
|
||||
- # <https://github.com/rpm-software-management/rpm/issues/3721>
|
||||
- logger.debug(_("Multisig: Checking a presence of key={}").format(fingerprint))
|
||||
- args = (self.rpmkeys_executable,
|
||||
- '--root', self.base.conf.installroot,
|
||||
- '--list', fingerprint.lower())
|
||||
- p = subprocess.run(
|
||||
- args=args,
|
||||
- executable=self.rpmkeys_executable,
|
||||
- cwd='/',
|
||||
- stdin=subprocess.DEVNULL,
|
||||
- stdout=subprocess.DEVNULL,
|
||||
- stderr=subprocess.DEVNULL)
|
||||
- return p.returncode == 0
|
||||
-
|
||||
def importKey(self, key):
|
||||
'''
|
||||
Import given Key object into the multisig keyring.
|
||||
@@ -232,6 +215,28 @@ class MultiSig(dnf.Plugin):
|
||||
returncode, stdout, stderr))
|
||||
return returncode == 0
|
||||
|
||||
+ def retrieve(self, keyurl, repo=None):
|
||||
+ """Retrieve a content of a key file specified by the URL using
|
||||
+ repository's proxy configuration.
|
||||
+
|
||||
+ :param keyurl URL of the key file
|
||||
+ :param repo repository object
|
||||
+ :returns: list of MultiSigKey objects populated from the key file
|
||||
+ """
|
||||
+ if keyurl.startswith('http:'):
|
||||
+ logger.warning(_("retrieving repo key for %s unencrypted from %s"), repo.id, keyurl)
|
||||
+ with dnf.util._urlopen(keyurl, repo=repo) as handle:
|
||||
+ # This is a place for parsing the key file and populating key ID etc.
|
||||
+ keyinfos = [MultiSigKey(handle.read(), keyurl)]
|
||||
+ return keyinfos
|
||||
+
|
||||
+ def log_key_import(self, keyinfo):
|
||||
+ """Print and log details about keys to be imported.
|
||||
+ """
|
||||
+ msg = (_('Importing GPG keys from: %s') %
|
||||
+ (keyinfo.url.replace("file://", "")))
|
||||
+ logger.critical("%s", msg)
|
||||
+
|
||||
def _get_key_for_package(self, po, askcb=None, fullaskcb=None):
|
||||
"""Retrieve a key for a package. If needed, use the given
|
||||
callback to prompt whether the key should be imported.
|
||||
@@ -264,53 +269,17 @@ class MultiSig(dnf.Plugin):
|
||||
user_cb_fail = False
|
||||
self._repo_set_imported_gpg_keys.append(repo.id)
|
||||
for keyurl in keyurls:
|
||||
- keys = dnf.crypto.retrieve(keyurl, repo)
|
||||
+ keys = self.retrieve(keyurl, repo)
|
||||
|
||||
for info in keys:
|
||||
- # Check if key is already installed
|
||||
- if self.keyInstalled(info.fingerprint):
|
||||
- msg = _('GPG key at %s (0x%s) is already installed')
|
||||
- logger.info(msg, keyurl, info.short_id)
|
||||
- continue
|
||||
-
|
||||
- # DNS Extension: create a key object, pass it to the verification class
|
||||
- # and print its result as an advice to the user.
|
||||
- if self.base.conf.gpgkey_dns_verification:
|
||||
- dns_input_key = dnf.dnssec.KeyInfo.from_rpm_key_object(info.userid,
|
||||
- info.raw_key)
|
||||
- dns_result = dnf.dnssec.DNSSECKeyVerification.verify(dns_input_key)
|
||||
- logger.info(dnf.dnssec.nice_user_msg(dns_input_key, dns_result))
|
||||
-
|
||||
# Try installing/updating GPG key
|
||||
info.url = keyurl
|
||||
- if self.base.conf.gpgkey_dns_verification:
|
||||
- dnf.crypto.log_dns_key_import(info, dns_result)
|
||||
- else:
|
||||
- dnf.crypto.log_key_import(info)
|
||||
+ self.log_key_import(info)
|
||||
rc = False
|
||||
if self.base.conf.assumeno:
|
||||
rc = False
|
||||
elif self.base.conf.assumeyes:
|
||||
- # DNS Extension: We assume, that the key is trusted in case it is valid,
|
||||
- # its existence is explicitly denied or in case the domain is not signed
|
||||
- # and therefore there is no way to know for sure (this is mainly for
|
||||
- # backward compatibility)
|
||||
- # FAQ:
|
||||
- # * What is PROVEN_NONEXISTENCE?
|
||||
- # In DNSSEC, your domain does not need to be signed, but this state
|
||||
- # (not signed) has to be proven by the upper domain. e.g. when example.com.
|
||||
- # is not signed, com. servers have to sign the message, that example.com.
|
||||
- # does not have any signing key (KSK to be more precise).
|
||||
- if self.base.conf.gpgkey_dns_verification:
|
||||
- if dns_result in (dnf.dnssec.Validity.VALID,
|
||||
- dnf.dnssec.Validity.PROVEN_NONEXISTENCE):
|
||||
- rc = True
|
||||
- logger.info(dnf.dnssec.any_msg(_("The key has been approved.")))
|
||||
- else:
|
||||
- rc = False
|
||||
- logger.info(dnf.dnssec.any_msg(_("The key has been rejected.")))
|
||||
- else:
|
||||
- rc = True
|
||||
+ rc = True
|
||||
|
||||
# grab the .sig/.asc for the keyurl, if it exists if it
|
||||
# does check the signature on the key if it is signed by
|
||||
@@ -318,11 +287,8 @@ class MultiSig(dnf.Plugin):
|
||||
# rc = True else ask as normal.
|
||||
|
||||
elif fullaskcb:
|
||||
- rc = fullaskcb({"po": po, "userid": info.userid,
|
||||
- "hexkeyid": info.short_id,
|
||||
- "keyurl": keyurl,
|
||||
- "fingerprint": info.fingerprint,
|
||||
- "timestamp": info.timestamp})
|
||||
+ rc = fullaskcb({"po": po,
|
||||
+ "keyurl": keyurl})
|
||||
elif askcb:
|
||||
rc = askcb(po, info.userid, info.short_id)
|
||||
|
||||
@@ -331,8 +297,7 @@ class MultiSig(dnf.Plugin):
|
||||
continue
|
||||
|
||||
# Import the key
|
||||
- # XXX: raw_key of second info erroneously contains first and
|
||||
- # second key. Probably a bug in key parser.
|
||||
+ # XXX: raw_key contains all keys found in the key file.
|
||||
#logger.debug(_("Multisig: Importing a key: {}").format(info.raw_key))
|
||||
result = self.importKey(info)
|
||||
if result == False:
|
||||
--
|
||||
2.51.0
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
|
||||
Name: dnf-plugins-core
|
||||
Version: 4.3.0
|
||||
Release: 22%{?dist}
|
||||
Release: 23%{?dist}
|
||||
Summary: Core Plugins for DNF
|
||||
License: GPLv2+
|
||||
URL: https://github.com/rpm-software-management/dnf-plugins-core
|
||||
@ -59,6 +59,7 @@ Patch20: 0020-needs-restarting-Get-boot-time-from-systemd-UnitsLoa.patch
|
||||
Patch21: 0021-dnf-copr-enable-on-Asahi-Fedora-Linux-Remix-guesses.patch
|
||||
Patch22: 0022-reposync-Avoid-multiple-downloads-of-duplicate-packa.patch
|
||||
Patch23: 0023-multisig-A-new-plugin-for-verifying-extraordinary-RP.patch
|
||||
Patch24: 0024-multisig-Do-not-parse-OpenPGP-keys.patch
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: cmake
|
||||
@ -825,6 +826,9 @@ ln -sf %{_mandir}/man1/%{yum_utils_subpackage_name}.1.gz %{buildroot}%{_mandir}/
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Mon Sep 15 2025 Petr Pisar <ppisar@redhat.com> - 4.3.0-23
|
||||
- Fix importing OpenPGPv6 keys (RHEL-114424)
|
||||
|
||||
* Wed Jun 25 2025 Petr Pisar <ppisar@redhat.com> - 4.3.0-22
|
||||
- Add multisig plugin (RHEL-100157)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user