Add multisig plugin
Resolves: RHEL-100157
This commit is contained in:
parent
39b64834f8
commit
08bd97b467
617
0023-multisig-A-new-plugin-for-verifying-extraordinary-RP.patch
Normal file
617
0023-multisig-A-new-plugin-for-verifying-extraordinary-RP.patch
Normal file
@ -0,0 +1,617 @@
|
||||
From 37c4cd7014f4b3c5db64a0a900761e53caf645be Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
|
||||
Date: Tue, 25 Mar 2025 12:34:34 +0100
|
||||
Subject: [PATCH] multisig: A new plugin for verifying extraordinary RPM
|
||||
signatures
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
This plugin executes a dedicated rpmkeys(8) tool of pqrpm for
|
||||
verifying RPM packages bound into a transaction.
|
||||
|
||||
The dedicated tool is supposed to understand signatures in RPM version
|
||||
6 format. One feature of that format is that you can have multiple
|
||||
signatures on a single RPM package.
|
||||
|
||||
This plugin takes public keys from a default key store of pqrpm, which
|
||||
is separate from the native RPM database. A reason is that the native
|
||||
database might reject storing OpenPGP keys with an unsupported key
|
||||
schema. A downside is that "dnf --installroot" places the keyring to
|
||||
a directory which is ununowned in the installroot.
|
||||
|
||||
XXX: This plugin does not report which keys required for the
|
||||
verification are missing (NOKEY case in _process_rpm_output()).
|
||||
Implementing that would require changing too many function interfaces.
|
||||
Classical single-signature verification does not do that either.
|
||||
|
||||
XXX: The plugin is packaged into a package which requires pqrpm which
|
||||
will only be available in RHEL 9. Next RHEL version will support the
|
||||
extraordinary signatures natively. Thus this patch only makes sense in
|
||||
RHEL 9.
|
||||
|
||||
Signed-off-by: Petr Písař <ppisar@redhat.com>
|
||||
---
|
||||
dnf-plugins-core.spec | 19 ++
|
||||
doc/CMakeLists.txt | 5 +
|
||||
doc/conf.py | 3 +
|
||||
doc/index.rst | 1 +
|
||||
doc/multisig.rst | 62 +++++++
|
||||
plugins/CMakeLists.txt | 3 +
|
||||
plugins/multisig.py | 402 +++++++++++++++++++++++++++++++++++++++++
|
||||
7 files changed, 495 insertions(+)
|
||||
create mode 100644 doc/multisig.rst
|
||||
create mode 100644 plugins/multisig.py
|
||||
|
||||
diff --git a/dnf-plugins-core.spec b/dnf-plugins-core.spec
|
||||
index f2d1bc4..cb3b1b8 100644
|
||||
--- a/dnf-plugins-core.spec
|
||||
+++ b/dnf-plugins-core.spec
|
||||
@@ -301,6 +301,18 @@ Obsoletes: python-dnf-plugins-extras-migrate < %{dnf_plugins_extra}
|
||||
Migrate Plugin for DNF, Python 2 version. Migrates history, group and yumdb data from yum to dnf.
|
||||
%endif
|
||||
|
||||
+%if %{with python3}
|
||||
+%package -n python3-dnf-plugin-multisig
|
||||
+Summary: Multisig Plugin for DNF
|
||||
+Requires: pqrpm
|
||||
+Requires: python3-%{name} = %{version}-%{release}
|
||||
+Provides: dnf-plugin-multisig = %{version}-%{release}
|
||||
+
|
||||
+%description -n python3-dnf-plugin-multisig
|
||||
+Multisig Plugin for DNF, Python 3 version. The plugin verifies multiple RPMv6
|
||||
+signatures on RPMv4 packages by using an external rpmkeys program.
|
||||
+%endif
|
||||
+
|
||||
%if %{with python2}
|
||||
%package -n python2-dnf-plugin-post-transaction-actions
|
||||
Summary: Post transaction actions Plugin for DNF
|
||||
@@ -716,6 +728,13 @@ ln -sf %{_mandir}/man1/%{yum_utils_subpackage_name}.1.gz %{buildroot}%{_mandir}/
|
||||
%exclude %{_mandir}/man8/dnf-migrate.*
|
||||
%endif
|
||||
|
||||
+%if %{with python3}
|
||||
+%files -n python3-dnf-plugin-multisig
|
||||
+%{python3_sitelib}/dnf-plugins/multisig.*
|
||||
+%{python3_sitelib}/dnf-plugins/__pycache__/multisig.*
|
||||
+%{_mandir}/man8/dnf*-multisig.*
|
||||
+%endif
|
||||
+
|
||||
%if %{with python2}
|
||||
%files -n python2-dnf-plugin-post-transaction-actions
|
||||
%config(noreplace) %{_sysconfdir}/dnf/plugins/post-transaction-actions.conf
|
||||
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
|
||||
index 79472a5..297506a 100644
|
||||
--- a/doc/CMakeLists.txt
|
||||
+++ b/doc/CMakeLists.txt
|
||||
@@ -47,6 +47,11 @@ INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/dnf-migrate.8
|
||||
DESTINATION share/man/man8)
|
||||
endif()
|
||||
|
||||
+if (${PYTHON_VERSION_MAJOR} STREQUAL "3")
|
||||
+INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/dnf4-multisig.8
|
||||
+ DESTINATION share/man/man8)
|
||||
+endif()
|
||||
+
|
||||
if (${WITHOUT_LOCAL} STREQUAL "0")
|
||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/dnf-local.8
|
||||
DESTINATION share/man/man8)
|
||||
diff --git a/doc/conf.py b/doc/conf.py
|
||||
index 327ac07..2845d18 100644
|
||||
--- a/doc/conf.py
|
||||
+++ b/doc/conf.py
|
||||
@@ -300,6 +300,9 @@ man_pages = [
|
||||
if sys.version_info[0] < 3:
|
||||
man_pages.append(('migrate', 'dnf-migrate', u'DNF migrate Plugin', AUTHORS, 8))
|
||||
|
||||
+if sys.version_info[0] == 3:
|
||||
+ man_pages.append(('multisig', 'dnf4-multisig', u'DNF multisig Plugin', AUTHORS, 8))
|
||||
+
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
diff --git a/doc/index.rst b/doc/index.rst
|
||||
index 251a24e..32984a5 100644
|
||||
--- a/doc/index.rst
|
||||
+++ b/doc/index.rst
|
||||
@@ -37,6 +37,7 @@ This documents core plugins of DNF:
|
||||
leaves
|
||||
local
|
||||
migrate
|
||||
+ multisig
|
||||
modulesync
|
||||
needs_restarting
|
||||
post-transaction-actions
|
||||
diff --git a/doc/multisig.rst b/doc/multisig.rst
|
||||
new file mode 100644
|
||||
index 0000000..21b9436
|
||||
--- /dev/null
|
||||
+++ b/doc/multisig.rst
|
||||
@@ -0,0 +1,62 @@
|
||||
+===================
|
||||
+DNF multisig Plugin
|
||||
+===================
|
||||
+
|
||||
+-----------
|
||||
+Description
|
||||
+-----------
|
||||
+
|
||||
+This plugin verifies extraordinary RPMv6 signatures when installing,
|
||||
+reinstalling, upgrading, or downgrading packages from a repository. If the
|
||||
+verification fails, the RPM operation will be aborted.
|
||||
+
|
||||
+The verification is achieved by executing a dedicated rpmkeys(8) tool which is
|
||||
+supposed to understand package signatures in RPM version 6 format. One feature
|
||||
+of that format is that you can have multiple signatures on a single RPM
|
||||
+package. If the package has no RPMv6 signature a signature in version 4 format
|
||||
+will be verified instead. If there is no signature, the verification will be
|
||||
+handled as failed.
|
||||
+
|
||||
+The dedicated rpmkeys(8) tool trusts public keys in a key store separate from
|
||||
+the native RPM database because the native database might reject storing
|
||||
+OpenPGP keys with an unsupported key schema which is foreseen to be used in
|
||||
+RPMv6 signatures.
|
||||
+
|
||||
+Public keys missing from the separate key store are attempted to be imported
|
||||
+from URLs listed in ``gpgkey`` configuration field of a repository the package
|
||||
+belongs to. Before importing, a user is asked for a confirmation with the
|
||||
+import, unless DNF was invoked with ``--assumeyes`` or ``--assumeno`` options.
|
||||
+
|
||||
+The key store can be inspected with ``/usr/lib/pqrpm/bin/rpmkeys -D --list``
|
||||
+command. A key can be deleted from the key store with
|
||||
+``/usr/lib/pqrpm/bin/rpmkeys -D --erase KEY_ID`` command. The ``KEY_ID`` is
|
||||
+the first word in an output of the ``--list`` command.
|
||||
+
|
||||
+Users who do not wish to verify the extraordinary RPMv6 signatures should
|
||||
+uninstall this plugin.
|
||||
+
|
||||
+-------------
|
||||
+Configuration
|
||||
+-------------
|
||||
+
|
||||
+Hard-coded path to the rpmkeys(8) tool is ``/usr/lib/pqrpm/bin/rpmkeys``.
|
||||
+
|
||||
+This plugin respects ``gpgkey`` and ``gpgcheck`` fields in a repository
|
||||
+configuration. See dnf.conf(5) for more details.
|
||||
+
|
||||
+-----
|
||||
+Files
|
||||
+-----
|
||||
+
|
||||
+``/usr/lib/pqrpm/lib/sysimage/rpm``
|
||||
+ A location of the key store defined by ``/usr/lib/pqrpm/bin/rpmkeys``
|
||||
+ tool.
|
||||
+
|
||||
+--------
|
||||
+See Also
|
||||
+--------
|
||||
+
|
||||
+* :manpage:`dnf.conf(5)`
|
||||
+* :manpage:`dnf(8)`
|
||||
+* :manpage:`rpmkeys(8)`
|
||||
+
|
||||
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
|
||||
index d004e5e..a84a786 100644
|
||||
--- a/plugins/CMakeLists.txt
|
||||
+++ b/plugins/CMakeLists.txt
|
||||
@@ -14,6 +14,9 @@ endif()
|
||||
if (${PYTHON_VERSION_MAJOR} STREQUAL "2")
|
||||
INSTALL (FILES migrate.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
|
||||
endif()
|
||||
+if (${PYTHON_VERSION_MAJOR} STREQUAL "3")
|
||||
+INSTALL (FILES multisig.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
|
||||
+endif()
|
||||
INSTALL (FILES needs_restarting.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
|
||||
INSTALL (FILES post-transaction-actions.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
|
||||
INSTALL (FILES repoclosure.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
|
||||
diff --git a/plugins/multisig.py b/plugins/multisig.py
|
||||
new file mode 100644
|
||||
index 0000000..8735a26
|
||||
--- /dev/null
|
||||
+++ b/plugins/multisig.py
|
||||
@@ -0,0 +1,402 @@
|
||||
+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
|
||||
+from dnfpluginscore import _, logger
|
||||
+import os
|
||||
+import subprocess
|
||||
+import sys
|
||||
+
|
||||
+class MultiSig(dnf.Plugin):
|
||||
+ """
|
||||
+ This plugin verifies signatures of RPM packages by executing an
|
||||
+ extraordinary "rpmkeys" tool. That tool can, for example, support multiple
|
||||
+ RPM v6 signatures, or signature schemata uknown to the ordinary,
|
||||
+ system-wide rpmkeys tool.
|
||||
+
|
||||
+ This verification is perfmored in addition to the standard verification
|
||||
+ performed by DNF.
|
||||
+ """
|
||||
+
|
||||
+ name = "multisig"
|
||||
+
|
||||
+ def __init__(self, base, cli):
|
||||
+ super(MultiSig, self).__init__(base, cli)
|
||||
+ # Path to the rpmkeys executable
|
||||
+ self.rpmkeys_executable = "/usr/lib/pqrpm/bin/rpmkeys"
|
||||
+ # List of repositories whose keys we have tried importing so far
|
||||
+ # during a run of this plugin.
|
||||
+ self._repo_set_imported_gpg_keys = [];
|
||||
+
|
||||
+ def pre_transaction(self):
|
||||
+ inbound_packages = []
|
||||
+ for ts_item in self.base.transaction:
|
||||
+ if ts_item.action in dnf.transaction.FORWARD_ACTIONS:
|
||||
+ inbound_packages.append(ts_item.pkg);
|
||||
+ self.gpgsigcheck(inbound_packages)
|
||||
+
|
||||
+ def _process_rpm_output(self, data):
|
||||
+ # No signatures or digests = corrupt package.
|
||||
+ # There is at least one line for -: and another (empty) entry after the
|
||||
+ # last newline.
|
||||
+ if len(data) < 3 or data[0] != b'-:' or data[-1]:
|
||||
+ return 2
|
||||
+ seen_sig, missing_key, not_trusted, not_signed = False, False, False, False
|
||||
+ for i in data[1:-1]:
|
||||
+ if b': BAD' in i:
|
||||
+ return 2
|
||||
+ elif i.endswith(b': NOKEY'):
|
||||
+ missing_key = True
|
||||
+ elif i.endswith(b': NOTTRUSTED'):
|
||||
+ not_trusted = True
|
||||
+ elif i.endswith(b': NOTFOUND'):
|
||||
+ not_signed = True
|
||||
+ elif not i.endswith(b': OK'):
|
||||
+ return 2
|
||||
+ if not_trusted:
|
||||
+ return 3
|
||||
+ elif missing_key:
|
||||
+ return 1
|
||||
+ elif not_signed:
|
||||
+ return 4
|
||||
+ # we still check return code, so this is safe
|
||||
+ return 0
|
||||
+
|
||||
+ def _verifyPackageUsingRpmkeys(self, package, installroot):
|
||||
+ # "--define=_pkgverify_level signature" enforces signature checking;
|
||||
+ # "--define=_pkgverify_flags 0x0" ensures that all signatures are checked.
|
||||
+ args = (self.rpmkeys_executable,
|
||||
+ '--checksig', '--root', installroot, '--verbose',
|
||||
+ '--define=_pkgverify_level signature', '--define=_pkgverify_flags 0x0',
|
||||
+ '-')
|
||||
+ env = dict(os.environ)
|
||||
+ env['LC_ALL'] = 'C'
|
||||
+ with subprocess.Popen(
|
||||
+ args=args,
|
||||
+ executable=self.rpmkeys_executable,
|
||||
+ env=env,
|
||||
+ stdout=subprocess.PIPE,
|
||||
+ cwd='/',
|
||||
+ stdin=package) as p:
|
||||
+ data = p.communicate()[0]
|
||||
+ returncode = p.returncode
|
||||
+ if type(returncode) is not int:
|
||||
+ raise AssertionError('Popen set return code to non-int')
|
||||
+ # rpmkeys can return something other than 0 or 1 in the case of a
|
||||
+ # fatal error (OOM, abort() called, SIGSEGV, etc)
|
||||
+ if returncode >= 2 or returncode < 0:
|
||||
+ return 2
|
||||
+ ret = self._process_rpm_output(data.split(b'\n'))
|
||||
+ if ret:
|
||||
+ return ret
|
||||
+ return 2 if returncode else 0
|
||||
+
|
||||
+ def _checkSig(self, installroot, package):
|
||||
+ """Takes a transaction set and a package, check it's sigs,
|
||||
+ return 0 if they are all fine
|
||||
+ return 1 if the gpg key can't be found
|
||||
+ return 2 if the header is in someway damaged
|
||||
+ return 3 if the key is not trusted
|
||||
+ return 4 if the pkg is not gpg or pgp signed"""
|
||||
+
|
||||
+ fdno = os.open(package, os.O_RDONLY|os.O_NOCTTY|os.O_CLOEXEC)
|
||||
+ try:
|
||||
+ value = self._verifyPackageUsingRpmkeys(fdno, installroot)
|
||||
+ finally:
|
||||
+ os.close(fdno)
|
||||
+ return value
|
||||
+
|
||||
+ def _sig_check_pkg(self, po):
|
||||
+ """Verify the GPG signature of the given package object.
|
||||
+
|
||||
+ :param po: the package object to verify the signature of
|
||||
+ :return: (result, error_string)
|
||||
+ where result is::
|
||||
+
|
||||
+ 0 = GPG signature verifies ok or verification is not required.
|
||||
+ 1 = GPG verification failed but installation of the right GPG key
|
||||
+ might help.
|
||||
+ 2 = Fatal GPG verification error, give up.
|
||||
+ """
|
||||
+ if po._from_cmdline:
|
||||
+ check = self.base.conf.localpkg_gpgcheck
|
||||
+ hasgpgkey = 0
|
||||
+ else:
|
||||
+ repo = self.base.repos[po.repoid]
|
||||
+ check = repo.gpgcheck
|
||||
+ hasgpgkey = not not repo.gpgkey
|
||||
+
|
||||
+ localfn = os.path.basename(po.localPkg())
|
||||
+ if check:
|
||||
+ logger.debug(_("Multisig: verifying: {}").format(po.localPkg()))
|
||||
+ sigresult = self._checkSig(self.base.conf.installroot, po.localPkg())
|
||||
+ if sigresult == 0:
|
||||
+ result = 0
|
||||
+ msg = _('All signatures for %s successfully verified') % localfn
|
||||
+
|
||||
+ elif sigresult == 1:
|
||||
+ if hasgpgkey:
|
||||
+ result = 1
|
||||
+ else:
|
||||
+ result = 2
|
||||
+ msg = _('Public key for %s is not installed') % localfn
|
||||
+
|
||||
+ elif sigresult == 2:
|
||||
+ result = 2
|
||||
+ msg = _('Problem opening package %s') % localfn
|
||||
+
|
||||
+ elif sigresult == 3:
|
||||
+ if hasgpgkey:
|
||||
+ result = 1
|
||||
+ else:
|
||||
+ result = 2
|
||||
+ result = 1
|
||||
+ msg = _('Public key for %s is not trusted') % localfn
|
||||
+
|
||||
+ elif sigresult == 4:
|
||||
+ result = 2
|
||||
+ msg = _('Package %s is not signed') % localfn
|
||||
+
|
||||
+ else:
|
||||
+ result = 0
|
||||
+ msg = _('Signature verification for %s is disabled') % localfn
|
||||
+
|
||||
+ 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.
|
||||
+
|
||||
+ Return values:
|
||||
+ - True key imported successfully
|
||||
+ - False otherwise
|
||||
+ Trows: If rpmkeys program could not been executed.
|
||||
+
|
||||
+ What happens if a key's raw_string contains multiple public key
|
||||
+ packets, or if the key was already in the keyring is unspecified and
|
||||
+ it depends on rpmkeys behavior. Current rpmkeys implementation
|
||||
+ gracefully ignores (or updates?) existing keys.
|
||||
+ '''
|
||||
+ args = (self.rpmkeys_executable,
|
||||
+ '--root', self.base.conf.installroot,
|
||||
+ '--import', '-')
|
||||
+ env = dict(os.environ)
|
||||
+ env['LC_ALL'] = 'C'
|
||||
+ with subprocess.Popen(
|
||||
+ executable=self.rpmkeys_executable,
|
||||
+ args=args,
|
||||
+ env=env,
|
||||
+ cwd='/',
|
||||
+ # XXX: rpmkeys used to fail reading from a pipe. Fix at
|
||||
+ # <https://github.com/rpm-software-management/rpm/pull/3706>.
|
||||
+ stdin=subprocess.PIPE,
|
||||
+ stdout=subprocess.PIPE,
|
||||
+ stderr=subprocess.PIPE) as p:
|
||||
+ stdout, stderr = p.communicate(input=key.raw_key)
|
||||
+ returncode = p.returncode
|
||||
+ if type(returncode) is not int:
|
||||
+ raise AssertionError('Popen set return code to non-int')
|
||||
+ logger.debug(_("Multisig: Key import result: exitcode={}, stdout={}, stderr={}").format(
|
||||
+ returncode, stdout, stderr))
|
||||
+ return returncode == 0
|
||||
+
|
||||
+ 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.
|
||||
+
|
||||
+ :param po: the package object to retrieve the key of
|
||||
+ :param askcb: Callback function to use to ask permission to
|
||||
+ import a key. The arguments *askcb* should take are the
|
||||
+ package object, the userid of the key, and the keyid
|
||||
+ :param fullaskcb: Callback function to use to ask permission to
|
||||
+ import a key. This differs from *askcb* in that it gets
|
||||
+ passed a dictionary so that we can expand the values passed.
|
||||
+ :raises: :class:`dnf.exceptions.Error` if there are errors
|
||||
+ retrieving the keys
|
||||
+ """
|
||||
+ if po._from_cmdline:
|
||||
+ # raise an exception, because po.repoid is not in self.repos
|
||||
+ msg = _('Unable to retrieve a key for a commandline package: %s')
|
||||
+ raise ValueError(msg % po)
|
||||
+
|
||||
+ repo = self.base.repos[po.repoid]
|
||||
+ key_installed = repo.id in self._repo_set_imported_gpg_keys
|
||||
+ keyurls = [] if key_installed else repo.gpgkey
|
||||
+
|
||||
+ def _prov_key_data(msg):
|
||||
+ msg += _('. Failing package is: %s') % (po) + '\n '
|
||||
+ msg += _('GPG Keys are configured as: %s') % \
|
||||
+ (', '.join(repo.gpgkey))
|
||||
+ return msg
|
||||
+
|
||||
+ user_cb_fail = False
|
||||
+ self._repo_set_imported_gpg_keys.append(repo.id)
|
||||
+ for keyurl in keyurls:
|
||||
+ keys = dnf.crypto.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)
|
||||
+ 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
|
||||
+
|
||||
+ # grab the .sig/.asc for the keyurl, if it exists if it
|
||||
+ # does check the signature on the key if it is signed by
|
||||
+ # one of our ca-keys for this repo or the global one then
|
||||
+ # 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})
|
||||
+ elif askcb:
|
||||
+ rc = askcb(po, info.userid, info.short_id)
|
||||
+
|
||||
+ if not rc:
|
||||
+ user_cb_fail = True
|
||||
+ continue
|
||||
+
|
||||
+ # Import the key
|
||||
+ # XXX: raw_key of second info erroneously contains first and
|
||||
+ # second key. Probably a bug in key parser.
|
||||
+ #logger.debug(_("Multisig: Importing a key: {}").format(info.raw_key))
|
||||
+ result = self.importKey(info)
|
||||
+ if result == False:
|
||||
+ msg = _('Key import failed')
|
||||
+ raise dnf.exceptions.Error(_prov_key_data(msg))
|
||||
+ logger.info(_('Key imported successfully'))
|
||||
+ key_installed = True
|
||||
+
|
||||
+ if not key_installed and user_cb_fail:
|
||||
+ raise dnf.exceptions.Error(_("Didn't install any keys"))
|
||||
+
|
||||
+ if not key_installed:
|
||||
+ msg = _('The GPG keys listed for the "%s" repository are '
|
||||
+ 'already installed but they are not correct for this '
|
||||
+ 'package.\n'
|
||||
+ 'Check that the correct key URLs are configured for '
|
||||
+ 'this repository.') % repo.name
|
||||
+ raise dnf.exceptions.Error(_prov_key_data(msg))
|
||||
+
|
||||
+ # Check if the newly installed keys helped
|
||||
+ result, errmsg = self._sig_check_pkg(po)
|
||||
+ if result != 0:
|
||||
+ if keyurls:
|
||||
+ msg = _("Import of key(s) didn't help, wrong key(s)?")
|
||||
+ logger.info(msg)
|
||||
+ errmsg = ucd(errmsg)
|
||||
+ raise dnf.exceptions.Error(_prov_key_data(errmsg))
|
||||
+
|
||||
+ def gpgsigcheck(self, pkgs):
|
||||
+ """Perform GPG signature verification on the given packages,
|
||||
+ installing keys if possible.
|
||||
+
|
||||
+ :param pkgs: a list of package objects to verify the GPG
|
||||
+ signatures of
|
||||
+ :raises: Will raise :class:`Error` if there's a problem
|
||||
+ """
|
||||
+ error_messages = []
|
||||
+ for po in pkgs:
|
||||
+ result, errmsg = self._sig_check_pkg(po)
|
||||
+
|
||||
+ if result == 0:
|
||||
+ # Verified ok, or verify not req'd
|
||||
+ continue
|
||||
+
|
||||
+ elif result == 1:
|
||||
+ ay = self.base.conf.assumeyes and not self.base.conf.assumeno
|
||||
+ if (not sys.stdin or not sys.stdin.isatty()) and not ay:
|
||||
+ raise dnf.exceptions.Error(_('Refusing to automatically import keys when running ' \
|
||||
+ 'unattended.\nUse "-y" to override.'))
|
||||
+
|
||||
+ # the callback here expects to be able to take options which
|
||||
+ # userconfirm really doesn't... so fake it
|
||||
+ fn = lambda x, y, z: self.base.output.userconfirm()
|
||||
+ try:
|
||||
+ self._get_key_for_package(po, fn)
|
||||
+ except (dnf.exceptions.Error, ValueError) as e:
|
||||
+ error_messages.append(str(e))
|
||||
+
|
||||
+ else:
|
||||
+ # Fatal error
|
||||
+ error_messages.append(errmsg)
|
||||
+
|
||||
+ if error_messages:
|
||||
+ for msg in error_messages:
|
||||
+ logger.critical(msg)
|
||||
+ raise dnf.exceptions.Error(_("GPG check FAILED"))
|
||||
+
|
||||
--
|
||||
2.50.1
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
|
||||
Name: dnf-plugins-core
|
||||
Version: 4.3.0
|
||||
Release: 21%{?dist}
|
||||
Release: 22%{?dist}
|
||||
Summary: Core Plugins for DNF
|
||||
License: GPLv2+
|
||||
URL: https://github.com/rpm-software-management/dnf-plugins-core
|
||||
@ -58,6 +58,7 @@ Patch19: 0019-reposync-Respect-norepopath-with-metadata-path.patch
|
||||
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
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: cmake
|
||||
@ -321,6 +322,18 @@ Obsoletes: python-dnf-plugins-extras-migrate < %{dnf_plugins_extra}
|
||||
Migrate Plugin for DNF, Python 2 version. Migrates history, group and yumdb data from yum to dnf.
|
||||
%endif
|
||||
|
||||
%if %{with python3}
|
||||
%package -n python3-dnf-plugin-multisig
|
||||
Summary: Multisig Plugin for DNF
|
||||
Requires: pqrpm
|
||||
Requires: python3-%{name} = %{version}-%{release}
|
||||
Provides: dnf-plugin-multisig = %{version}-%{release}
|
||||
|
||||
%description -n python3-dnf-plugin-multisig
|
||||
Multisig Plugin for DNF, Python 3 version. The plugin verifies multiple RPMv6
|
||||
signatures on RPMv4 packages by using an external rpmkeys program.
|
||||
%endif
|
||||
|
||||
%if %{with python2}
|
||||
%package -n python2-dnf-plugin-post-transaction-actions
|
||||
Summary: Post transaction actions Plugin for DNF
|
||||
@ -736,6 +749,13 @@ ln -sf %{_mandir}/man1/%{yum_utils_subpackage_name}.1.gz %{buildroot}%{_mandir}/
|
||||
%exclude %{_mandir}/man8/dnf-migrate.*
|
||||
%endif
|
||||
|
||||
%if %{with python3}
|
||||
%files -n python3-dnf-plugin-multisig
|
||||
%{python3_sitelib}/dnf-plugins/multisig.*
|
||||
%{python3_sitelib}/dnf-plugins/__pycache__/multisig.*
|
||||
%{_mandir}/man8/dnf*-multisig.*
|
||||
%endif
|
||||
|
||||
%if %{with python2}
|
||||
%files -n python2-dnf-plugin-post-transaction-actions
|
||||
%config(noreplace) %{_sysconfdir}/dnf/plugins/post-transaction-actions.conf
|
||||
@ -805,6 +825,9 @@ ln -sf %{_mandir}/man1/%{yum_utils_subpackage_name}.1.gz %{buildroot}%{_mandir}/
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Wed Jun 25 2025 Petr Pisar <ppisar@redhat.com> - 4.3.0-22
|
||||
- Add multisig plugin (RHEL-100157)
|
||||
|
||||
* Tue Mar 11 2025 Marek Blaha <mblaha@redhat.com> - 4.3.0-21
|
||||
- reposync: Avoid multiple downloads of duplicate packages (RHEL-64320)
|
||||
- Fix bogus changelog entry date
|
||||
|
||||
Loading…
Reference in New Issue
Block a user