auto importing rpm gpg public keys from keyserver

Andrea Arcangeli andrea at suse.de
Wed Jun 14 22:16:32 PDT 2006


On Sat, Jun 10, 2006 at 06:21:13PM +0200, Pascal Bleser wrote:
> So, basically, what do we need in smart ?
> - - detecting packages that are signed with keys that are not in RPM's
> keyring:
>   * is it possible to know that beforehand ? from repository metadata,
> possibly ?

I suspect it's more efficient to catch the rpmlib failure, but we could
also get the metadata beforehand and to find all keys that are missing
in rpm keyring and start installing later like you suggest.

>   * can we only notice that when a package is actually being installed ?
>  (= the RPM transaction being made/committed) - if so, how would smart
> best react to it ? how do we notice, parsing output or a return code of
> an rpm-python function ?

I found that the simple way is to add the key with the proper
ts.pgpImportPubkey handler and to re-read the header, all in the same ts
session.

However if we do the whole signature checking beforehand inside smart
like you suggested at the previous point (a reasonable choice to get the
full flexibility, but potentially slower and more complex) then this
question become void: we don't need to interface with rpm anymore, since
we'd be disabling signature checking in rpm permanently, it'd be useless
to do it twice first inside smart and later in rpm.

> - - implement a dialog in every interface (text, text/interactive and gtk)
> that tells the user that the transaction contains packages that are
> either unsigned or signed with a key that's not trusted (i.e. not in the
> RPM keyring)

I think we can probably save one path and abort early on if the package
isn't signed or we can't find the key in the keyserver, simpler and more
secure.

> - - implement key fetching from keyservers

That's easy with gpg.

> What about embedding a URL (or the key itself) in .channel files ?
> Maybe even add a signature on channel files themselves...

As long as it's on the client and not on the server it's ok. There's
already a fingerprint option in the apt-rpm channel type, I don't
actually know how it should be used or how it would compare to what you
are suggesting.

> Novell added signed repositories support in SUSE Linux 10.1 (for yast2
> and RPM-MD repositories):
> http://en.opensuse.org/Secure_Installation_Sources
> 
> Implementing support for that would be interesting as well.

Indeed, this would come next. But I suspect these are quite orthogonal
problems. The signature checking of the rpm has precedence: a malicious
channel metadata might confuse the client and DoS the system, but it
should never be exploitable in terms of
spyware/malware/troyan/virus/worms, while the lack of rpm signature
check is.

> Nevertheless, I personally think signature verification should be turned
> on by default, not off.

Agreed. The current smart SUSE package has it on by default in distro.py
so it can't be turned off unless you edit the file in
/usr/lib/smart/distro.py to turn it off manually. SUSE users should be
fully secure now with smart.

I did a first prototype working implementation (I doubt it will last
more than a few days, but it already works).

Obvious problems:

1) I couldn't find a way to extract the keyid from the rpm using the
   rpmmodule.c so I didn't wait too long before taking the simple way:
   in this model the key management is in a very slow path so it's not a
   problem in practice.
2) I don't know how to open popup windows in the GUI, so it probably
   only works with the console, GUI is *untested*.
3) I couldn't find a way to ask the "this session only" without
   disabling signature checking as a whole (and if I did I would had to
   check the signature beforehand instead of catching the failure in the
   fast path, so the whole code would have to be rewritten doing the
   check beforehand).

I think we have to decide if we want to go with this simpler and more
efficient model to catch the rpm signature check failure, or if we want
to turn off rpm signature checks forever and to rewrite all signature
checking inside smart (slower and more complex but more flexible because
it will solve point 3). Either ways it'll be a lot more secure than the
current default of signature totally disabled ;). If needed removing
keys from rpmdb is easy with rpm -e.

This has to be enabled with "smart config --set keyserver=pgp.mit.edu",
it changes nothing by default so it's quite harmless.

Signed-off-by: Andrea Arcangeli <andrea at suse.de>

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 06:39:50.000000000 +0200
@@ -40,6 +40,17 @@ try:
 except locale.Error:
     ENCODING = "C"
 
+def get_rpm_public_key(filepath):
+    f = os.popen('rpm -qpi %s' % filepath, 'r')
+    import re
+    while 1:
+        line = f.readline()
+        if not line:
+            return
+        m = re.search(r'Signature.*Key ID (\w+)', line)
+        if m:
+            return m.group(1)
+
 class RPMPackageManager(PackageManager):
 
     def commit(self, changeset, pkgpaths):
@@ -152,12 +163,40 @@ 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):
+                    print 'install path'
+                    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:
+                                print 'You can set the keyserver with "smart config --set keyserver=pgp.mit.edu"'
+                            else:
+                                print 'We are importing the unknown key please wait, it may take some time'
+                                key = get_rpm_public_key(path)
+                                if os.system('gpg --keyserver %s --recv-keys %s' % (keyserver, key)):
+                                    print 'gpg failed, please make sure it was installed correctly'
+                                else:
+                                    print 'You can see that GPG imported an unknown KEY successfully.'
+                                    print 'Do you want to trust it forever? (y/N) ',
+                                    if raw_input().lower().strip() == 'y':
+                                        key_armor = os.popen('gpg --export %s' % key, 'r').read()
+                                        if ts.pgpImportPubkey(key_armor):
+                                            print '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



More information about the Smart mailing list