auto importing rpm gpg public keys from keyserver

Andrea Arcangeli andrea at suse.de
Thu Jun 15 12:15:06 PDT 2006


This should look a bit better, this works fine with both console and gui
too (tested). It's still non-intrusive, if you don't set
keyserver=something, it does absolutely nothing (it only tells you, that
you can enable the feature if you want).

I don't understand what the _ is about (I guess it has to do with
translations), so I omitted it (if you can check a fingerprint chances
are you don't need a translation).

I'm quite satisified by how it works, even if it's still the simple
approach of going with max-performance and not doing the whole checking
outside rpm beforehand.

Ideally I'd like to run a first hdrFromFdno as user nobody to make any
rpm bug become irrelevant in terms of gaining root with a malicious
rpm-header payload. However that is orthogonal and it
would slowdown performance too. How likely it is to have bugs in the rpm
header parsing code? Did it ever happen? Anyway it's certainly _a_lot_
more secure this, than to keep signatures off by default. Exploiting a
buffer overflow is a problem in itself, so there are already multiple
layers of bugs to exploit before you can gain root with this patch
applied, certainly it would be nicer to add one more layer by parsing
the header the first time as user nobody but this seems a reasonable
start.

The end of the patch turns signature checking on by default, and returns
false by default in the interface of the Interface (it's always a safer
default to answer no if unsure, either no or raise an exception). In
practice it should be a noop.

If the package isn't signed it should fail in gpg but that's ok.

Index: smart/backends/rpm/pm.py
--- smart/backends/rpm/pm.py.~1~	2006-06-15 03:03:07.000000000 +0200
+++ smart/backends/rpm/pm.py	2006-06-15 21:04:05.000000000 +0200
@@ -34,12 +34,24 @@ import thread
 import errno
 import fcntl
 import time
+import popen2
 
 try:
     ENCODING = locale.getpreferredencoding()
 except locale.Error:
     ENCODING = "C"
 
+def get_rpm_public_key(filepath):
+    f = file(filepath, 'r')
+    ts = rpm.ts()
+    ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
+    hdr = ts.hdrFromFdno(f.fileno())
+    return hdr.sprintf("%|DSAHEADER?{%{DSAHEADER:pgpsig}}:"
+                       "{%|RSAHEADER?{%{RSAHEADER:pgpsig}}:"
+                       "{%|SIGGPG?{%{SIGGPG:pgpsig}}:"
+                       "{%|SIGPGP?{%{SIGPGP:pgpsig}}:"
+                       "{(none)}|}|}|}|").split()[-1]
+
 class RPMPackageManager(PackageManager):
 
     def commit(self, changeset, pkgpaths):
@@ -152,12 +164,53 @@ class RPMPackageManager(PackageManager):
                 info = loader.getInfo(pkg)
                 mode = pkg in upgrading and "u" or "i"
                 path = pkgpaths[pkg][0]
-                fd = os.open(path, os.O_RDONLY)
-                try:
-                    h = ts.hdrFromFdno(fd)
-                except rpm.error, e:
-                    os.close(fd)
-                    raise Error, "%s: %s" % (os.path.basename(path), e)
+                for _pass in xrange(2):
+                    fd = os.open(path, os.O_RDONLY)
+                    try:
+                        h = ts.hdrFromFdno(fd)
+                    except rpm.error, e:
+                        os.close(fd)
+
+                        # in the slow path we trap the case of a gpg public key not in rpmdb
+                        # and we try to fetch it from the keyserver and we ask the user
+                        # if he wants to trust this key to continue the installation
+                        if not _pass and e.args[0] == 'public key not available':
+                            keyserver = sysconf.get('keyserver')
+                            if not keyserver:
+                                iface.error('To enable the keyserver run: '
+                                            '"smart config --set keyserver=pgp.mit.edu"')
+                            else:
+                                iface.info('We are importing an unknown key please wait...')
+                                key = get_rpm_public_key(path)
+                                popen = popen2.Popen4('gpg --keyserver %s --recv-keys %s' % (keyserver, key))
+                                popen_out = popen.fromchild.read()
+                                if popen.wait():
+                                    iface.error('gpg failed to import keyid %s, '
+                                                'please make sure that gpg is installed, '
+                                                'that the keyserver %s is working '
+                                                'and that the package %s has a valid signature.' % (key,
+                                                                                                    keyserver,
+                                                                                                    path))
+                                else:
+                                    popen_out += '\nThe above GPG key has been imported successfully.\n' \
+                                                 'It is required to install this package:\n\n\t' \
+                                                 + os.path.basename(path) + \
+                                                 '\n\nAre you sure that you want to trust this key forever?\n\n' \
+                                                 'You must verify the below fingerprint before answering.\n' \
+                                                 + os.popen('gpg --fingerprint %s' % key).read() + \
+                                                 '\nIf you answer "Yes" all other packages signed with this key ' \
+                                                 'will be installed automatically.'
+                                    if iface.askYesNo(popen_out):
+                                        key_armor = os.popen('gpg --export %s' % key, 'r').read()
+                                        if ts.pgpImportPubkey(key_armor):
+                                            iface.error('rpm failed to import the public key id %s' % key)
+                                        else:
+                                            # try one more time with the pub key on
+                                            continue
+
+                        raise Error, "%s: %s" % (os.path.basename(path), e)
+                    else:
+                        break
                 os.close(fd)
                 ts.addInstall(h, (info, path), mode)
                 packages += 1
Index: smart/backends/rpm/base.py
--- smart/backends/rpm/base.py.~1~	2006-06-08 05:31:35.000000000 +0200
+++ smart/backends/rpm/base.py	2006-06-15 19:56:18.000000000 +0200
@@ -47,7 +47,7 @@ def getTS(new=False):
     if not hasattr(getTS, "ts"):
         getTS.root = sysconf.get("rpm-root", "/")
         getTS.ts = rpm.ts(getTS.root)
-        if not sysconf.get("rpm-check-signatures", False):
+        if not sysconf.get("rpm-check-signatures", True):
             getTS.ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
         dbdir = os.path.join(getTS.root, "var/lib/rpm")
         if not os.path.isdir(dbdir):
@@ -73,7 +73,7 @@ def getTS(new=False):
                 pass
     if new:
         ts = rpm.ts(getTS.root)
-        if not sysconf.get("rpm-check-signatures", False):
+        if not sysconf.get("rpm-check-signatures", True):
             ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
         return ts
     else:
Index: smart/interface.py
--- smart/interface.py.~1~	2006-06-08 05:31:38.000000000 +0200
+++ smart/interface.py	2006-06-15 20:37:32.000000000 +0200
@@ -70,13 +70,13 @@ class Interface(object):
         return self._progress
 
     def askYesNo(self, question, default=False):
-        return True
+        return default
 
     def askContCancel(self, question, default=False):
-        return True
+        return default
 
     def askOkCancel(self, question, default=False):
-        return True
+        return default
 
     def askInput(self, prompt, message=None, widthchars=None, echo=True):
         return ""



More information about the Smart mailing list