[Yum-devel] [PATCH 2/2] big change for having gpgcakeys

Seth Vidal skvidal at fedoraproject.org
Sat Dec 11 05:16:20 UTC 2010


The idea is you define a gpgcakey for repo. This is the key that once you import will let you import everything else automatically.

If you have a cakey defined then when yum goes to import any other key it will look for a .asc detached signature for that key.
if that signature is from the cakey you've already imported then yum will import the new gpgkey w/o prompting you.

this works for signed packages as well as signed repomd.xml files in repos

this also moves all gpg keyrings  into a new per-repo persistent directory in /var/lib/yum/repos/$basearch/$releasever/repoid
so we don't have to worry about a yum clean all removing our gpgkeys like we have in the past.
---
 output.py       |    3 +
 yum/__init__.py |  175 +++++++++++++++++++++++++++++++++++++++++--------------
 yum/config.py   |    1 +
 yum/repos.py    |    9 ++-
 yum/yumRepo.py  |   26 +++++++-
 5 files changed, 163 insertions(+), 51 deletions(-)

diff --git a/output.py b/output.py
index f99ab37..a36e66c 100755
--- a/output.py
+++ b/output.py
@@ -1228,12 +1228,15 @@ Downgrade %5.5s Package(s)
     def setupKeyImportCallbacks(self):
         confirm_func = self._cli_confirm_gpg_key_import
         gpg_import_func = self.getKeyForRepo
+        gpgca_import_func = self.getCAKeyForRepo
         if hasattr(self, 'prerepoconf'):
             self.prerepoconf.confirm_func = confirm_func
             self.prerepoconf.gpg_import_func = gpg_import_func
+            self.prerepoconf.gpgca_import_func = gpgca_import_func
         else:
             self.repos.confirm_func = confirm_func
             self.repos.gpg_import_func = gpg_import_func
+            self.repos.gpgca_import_func = gpgca_import_func
 
     def interrupt_callback(self, cbobj):
         '''Handle CTRL-C's during downloads
diff --git a/yum/__init__.py b/yum/__init__.py
index 92fa0d0..67cf5b3 100644
--- a/yum/__init__.py
+++ b/yum/__init__.py
@@ -85,6 +85,7 @@ from yum.rpmtrans import RPMTransaction,SimpleCliCallBack
 from yum.i18n import to_unicode, to_str
 
 import string
+import StringIO
 
 from weakref import proxy as weakref
 
@@ -135,6 +136,7 @@ class _YumPreRepoConf:
         self.interrupt_callback = None
         self.confirm_func = None
         self.gpg_import_func = None
+        self.gpgca_import_func = None
         self.cachedir = None
         self.cache = None
 
@@ -415,7 +417,13 @@ class YumBase(depsolve.Depsolve):
             else:
                 thisrepo.repo_config_age = repo_age
                 thisrepo.repofile = repofn
-                
+                # repos are ver/arch specific so add $basearch/$releasever
+                self.conf._repos_persistdir = os.path.normpath('%s/repos/%s/%s/'
+                     % (self.conf.persistdir,  self.yumvar.get('basearch', '$basearch'),
+                        self.yumvar.get('releasever', '$releasever')))
+                thisrepo.base_persistdir = self.conf._repos_persistdir
+
+
             if thisrepo.id in self.repo_setopts:
                 for opt in self.repo_setopts[thisrepo.id].items:
                     if not hasattr(thisrepo, opt):
@@ -575,6 +583,7 @@ class YumBase(depsolve.Depsolve):
             self.repos.setInterruptCallback(prerepoconf.interrupt_callback)
             self.repos.confirm_func = prerepoconf.confirm_func
             self.repos.gpg_import_func = prerepoconf.gpg_import_func
+            self.repos.gpgca_import_func = prerepoconf.gpgca_import_func
             if prerepoconf.cachedir is not None:
                 self.repos.setCacheDir(prerepoconf.cachedir)
             if prerepoconf.cache is not None:
@@ -4309,15 +4318,16 @@ class YumBase(depsolve.Depsolve):
         self.conf.obsoletes = old_conf_obs
         return done
 
-    def _retrievePublicKey(self, keyurl, repo=None):
+    def _retrievePublicKey(self, keyurl, repo=None, getSig=True):
         """
         Retrieve a key file
         @param keyurl: url to the key to retrieve
         Returns a list of dicts with all the keyinfo
         """
         key_installed = False
-
-        self.logger.info(_('Retrieving GPG key from %s') % keyurl)
+        
+        msg = _('Retrieving key from %s') % keyurl
+        self.verbose_logger.log(logginglevels.INFO_2, msg)
        
         # Go get the GPG key from the given URL
         try:
@@ -4336,6 +4346,33 @@ class YumBase(depsolve.Depsolve):
         except urlgrabber.grabber.URLGrabError, e:
             raise Errors.YumBaseError(_('GPG key retrieval failed: ') +
                                       to_unicode(str(e)))
+                                      
+        # check for a .asc file accompanying it - that's our gpg sig on the key
+        # suck it down and do the check
+        sigfile = None
+        valid_sig = False
+        if getSig and repo and repo.gpgcakey:
+            self.getCAKeyForRepo(repo, callback=repo.confirm_func)
+            try:
+                url = misc.to_utf8(keyurl + '.asc')
+                opts = repo._default_grabopts()
+                text = repo.id + '/gpgkeysig'
+                sigfile = urlgrabber.urlopen(url, **opts)
+
+            except urlgrabber.grabber.URLGrabError, e:
+                sigfile = None
+
+            if sigfile:
+                if not misc.valid_detached_sig(sigfile, 
+                                    StringIO.StringIO(rawkey), repo.gpgcadir):
+                    #if we decide we want to check, even though the sig failed
+                    # here is where we would do that
+                    raise Errors.YumBaseError(_('GPG key signature on key %s does not match CA Key for repo: %s') % (url, repo.id))
+                else:
+                    msg = _('GPG key signature verified against CA Key(s)')
+                    self.verbose_logger.log(logginglevels.INFO_2, msg)
+                    valid_sig = True
+            
         # Parse the key
         try:
             keys_info = misc.getgpgkeyinfo(rawkey, multiple=True)
@@ -4352,29 +4389,31 @@ class YumBase(depsolve.Depsolve):
                       _('GPG key parsing failed: key does not have value %s') + info
                 thiskey[info] = keyinfo[info]
             thiskey['hexkeyid'] = misc.keyIdToRPMVer(keyinfo['keyid']).upper()
+            thiskey['valid_sig'] = valid_sig
+            thiskey['has_sig'] = bool(sigfile)
             keys.append(thiskey)
         
         return keys
 
-    def _getKeyImportMessage(self, info, keyurl):
+    def _getKeyImportMessage(self, info, keyurl, keytype='GPG'):
         msg = None
         if keyurl.startswith("file:"):
             fname = keyurl[len("file:"):]
             pkgs = self.rpmdb.searchFiles(fname)
             if pkgs:
                 pkgs = sorted(pkgs)[-1]
-                msg = (_('Importing GPG key 0x%s:\n'
+                msg = (_('Importing %s key 0x%s:\n'
                          ' Userid : %s\n'
                          ' Package: %s (%s)\n'
                          ' From   : %s') %
-                       (info['hexkeyid'], to_unicode(info['userid']),
+                       (keytype, info['hexkeyid'], to_unicode(info['userid']),
                         pkgs, pkgs.ui_from_repo,
                         keyurl.replace("file://","")))
         if msg is None:
-            msg = (_('Importing GPG key 0x%s:\n'
+            msg = (_('Importing %s key 0x%s:\n'
                      ' Userid: "%s"\n'
                      ' From  : %s') %
-                   (info['hexkeyid'], to_unicode(info['userid']),
+                   (keytype, info['hexkeyid'], to_unicode(info['userid']),
                     keyurl.replace("file://","")))
         self.logger.critical("%s", msg)
 
@@ -4405,24 +4444,34 @@ class YumBase(depsolve.Depsolve):
                     self.logger.info(_('GPG key at %s (0x%s) is already installed') % (
                         keyurl, info['hexkeyid']))
                     continue
-
-                # Try installing/updating GPG key
-                self._getKeyImportMessage(info, keyurl)
-                rc = False
-                if self.conf.assumeyes:
-                    rc = True
-                elif fullaskcb:
-                    rc = fullaskcb({"po": po, "userid": info['userid'],
-                                    "hexkeyid": info['hexkeyid'], 
-                                    "keyurl": keyurl,
-                                    "fingerprint": info['fingerprint'],
-                                    "timestamp": info['timestamp']})
-                elif askcb:
-                    rc = askcb(po, info['userid'], info['hexkeyid'])
-
-                if not rc:
-                    raise Errors.YumBaseError, _("Not installing key")
                 
+                if repo.gpgcakey and info['has_sig'] and info['valid_sig']:
+                    key_installed = True
+                else:
+                    # Try installing/updating GPG key
+                    self._getKeyImportMessage(info, keyurl)
+                    rc = False
+                    if self.conf.assumeyes:
+                        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['hexkeyid'], 
+                                        "keyurl": keyurl,
+                                        "fingerprint": info['fingerprint'],
+                                        "timestamp": info['timestamp']})
+                    elif askcb:
+                        rc = askcb(po, info['userid'], info['hexkeyid'])
+
+                    if not rc:
+                        raise Errors.YumBaseError, _("Not installing key")
+                    
                 # Import the key
                 ts = self.rpmdb.readOnlyTS()
                 result = ts.pgpImportPubkey(misc.procgpgkey(info['raw_key']))
@@ -4446,43 +4495,55 @@ class YumBase(depsolve.Depsolve):
             self.logger.info(_("Import of key(s) didn't help, wrong key(s)?"))
             raise Errors.YumBaseError, errmsg
     
-    def getKeyForRepo(self, repo, callback=None):
+    def _getAnyKeyForRepo(self, repo, destdir, keyurl_list, is_cakey=False, callback=None):
         """
         Retrieve a key for a repository If needed, prompt for if the key should
         be imported using callback
         
         @param repo: Repository object to retrieve the key of.
+        @param destdir: destination of the gpg pub ring
+        @param keyurl_list: list of urls for gpg keys
+        @param is_cakey: bool - are we pulling in a ca key or not
         @param callback: Callback function to use for asking for verification
                           of a key. Takes a dictionary of key info.
         """
-        keyurls = repo.gpgkey
+
         key_installed = False
-        for keyurl in keyurls:
-            keys = self._retrievePublicKey(keyurl, repo)
+        for keyurl in keyurl_list:
+            keys = self._retrievePublicKey(keyurl, repo, getSig=not is_cakey)
             for info in keys:
                 # Check if key is already installed
-                if info['keyid'] in misc.return_keyids_from_pubring(repo.gpgdir):
+                if hex(int(info['keyid']))[2:-1].upper() in misc.return_keyids_from_pubring(destdir):
                     self.logger.info(_('GPG key at %s (0x%s) is already imported') % (
                         keyurl, info['hexkeyid']))
+                    key_installed = True
                     continue
 
                 # Try installing/updating GPG key
-                self._getKeyImportMessage(info, keyurl)
-                rc = False
-                if self.conf.assumeyes:
-                    rc = True
-                elif callback:
-                    rc = callback({"repo": repo, "userid": info['userid'],
-                                    "hexkeyid": info['hexkeyid'], "keyurl": keyurl,
-                                    "fingerprint": info['fingerprint'],
-                                    "timestamp": info['timestamp']})
-
-
-                if not rc:
-                    raise Errors.YumBaseError, _("Not installing key for repo %s") % repo
+                if is_cakey:
+                    keytype = 'CA'
+                else:
+                    keytype = 'GPG'
+
+                if repo.gpgcakey and info['has_sig'] and info['valid_sig']:
+                    key_installed = True
+                else:
+                    self._getKeyImportMessage(info, keyurl, keytype)
+                    rc = False
+                    if self.conf.assumeyes:
+                        rc = True
+                    elif callback:
+                        rc = callback({"repo": repo, "userid": info['userid'],
+                                        "hexkeyid": info['hexkeyid'], "keyurl": keyurl,
+                                        "fingerprint": info['fingerprint'],
+                                        "timestamp": info['timestamp']})
+
+
+                    if not rc:
+                        raise Errors.YumBaseError, _("Not installing key for repo %s") % repo
                 
                 # Import the key
-                result = misc.import_key_to_pubring(info['raw_key'], info['hexkeyid'], gpgdir=repo.gpgdir)
+                result = misc.import_key_to_pubring(info['raw_key'], info['hexkeyid'], gpgdir=destdir)
                 if not result:
                     raise Errors.YumBaseError, _('Key import failed')
                 self.logger.info(_('Key imported successfully'))
@@ -4495,6 +4556,29 @@ class YumBase(depsolve.Depsolve):
                   'Check that the correct key URLs are configured for ' \
                   'this repository.') % (repo.name)
 
+    def getKeyForRepo(self, repo, callback=None):
+        """
+        Retrieve a key for a repository If needed, prompt for if the key should
+        be imported using callback
+        
+        @param repo: Repository object to retrieve the key of.
+        @param callback: Callback function to use for asking for verification
+                          of a key. Takes a dictionary of key info.
+        """
+        self._getAnyKeyForRepo(repo, repo.gpgdir, repo.gpgkey, is_cakey=False, callback=callback)
+
+    def getCAKeyForRepo(self, repo, callback=None):
+        """
+        Retrieve a key for a repository If needed, prompt for if the key should
+        be imported using callback
+        
+        @param repo: Repository object to retrieve the key of.
+        @param callback: Callback function to use for asking for verification
+                          of a key. Takes a dictionary of key info.
+        """
+
+        self._getAnyKeyForRepo(repo, repo.gpgcadir, repo.gpgcakey, is_cakey=True, callback=callback)
+
     def _limit_installonly_pkgs(self):
         """ Limit packages based on conf.installonly_limit, if any of the
             packages being installed have a provide in conf.installonlypkgs.
@@ -4772,6 +4856,7 @@ class YumBase(depsolve.Depsolve):
         newrepo.gpgcheck = self.conf.gpgcheck
         newrepo.repo_gpgcheck = self.conf.repo_gpgcheck
         newrepo.basecachedir = self.conf.cachedir
+        newrepo.base_persistdir = self.conf._repos_persistdir
 
         for key in kwargs.keys():
             if not hasattr(newrepo, key): continue # skip the ones which aren't vars
diff --git a/yum/config.py b/yum/config.py
index 14eb992..97e5e3d 100644
--- a/yum/config.py
+++ b/yum/config.py
@@ -794,6 +794,7 @@ class RepoConf(BaseConfig):
     metalink   = UrlOption()
     mediaid = Option()
     gpgkey = UrlListOption()
+    gpgcakey = UrlListOption()
     exclude = ListOption() 
     includepkgs = ListOption() 
 
diff --git a/yum/repos.py b/yum/repos.py
index 4b74ac6..f11ed52 100644
--- a/yum/repos.py
+++ b/yum/repos.py
@@ -32,9 +32,12 @@ class _wrap_ayum_getKeyForRepo:
         we have a seperate class).
         A "better" fix might be to explicitly pass the YumBase instance to
         the callback ... API change! """
-    def __init__(self, ayum):
+    def __init__(self, ayum, ca=False):
         self.ayum = weakref(ayum)
+        self.ca = ca
     def __call__(self, repo, callback=None):
+        if self.ca:
+            return self.ayum.getCAKeyForRepo(repo, callback)
         return self.ayum.getKeyForRepo(repo, callback)
 
 class RepoStorage:
@@ -57,6 +60,7 @@ class RepoStorage:
         # even quasi-useful
         # defaults to what is probably sane-ish
         self.gpg_import_func = _wrap_ayum_getKeyForRepo(ayum)
+        self.gpgca_import_func = _wrap_ayum_getKeyForRepo(ayum, ca=True)
         self.confirm_func = None
 
         # This allow listEnabled() to be O(1) most of the time.
@@ -77,7 +81,8 @@ class RepoStorage:
 
         for repo in repos:
             repo.setup(self.ayum.conf.cache, self.ayum.mediagrabber,
-                   gpg_import_func = self.gpg_import_func, confirm_func=self.confirm_func)
+                   gpg_import_func = self.gpg_import_func, confirm_func=self.confirm_func,
+                   gpgca_import_func = self.gpgca_import_func)
             # if we come back from setup NOT enabled then mark as disabled
             # so nothing else touches us
             if not repo.enabled:
diff --git a/yum/yumRepo.py b/yum/yumRepo.py
index b0e23c6..4926e9d 100644
--- a/yum/yumRepo.py
+++ b/yum/yumRepo.py
@@ -255,6 +255,7 @@ class YumRepository(Repository, config.RepoConf):
                                  # config is very, very old
         # throw in some stubs for things that will be set by the config class
         self.basecachedir = ""
+        self.base_persistdir = ""
         self.cost = 1000
         self.copy_local = 0
         # holder for stuff we've grabbed
@@ -273,6 +274,7 @@ class YumRepository(Repository, config.RepoConf):
 
         # callbacks for gpg key importing and confirmation
         self.gpg_import_func = None
+        self.gpgca_import_func = None
         self.confirm_func = None
 
         #  The reason we want to turn this off are things like repoids
@@ -368,7 +370,8 @@ class YumRepository(Repository, config.RepoConf):
         # we exclude all vars which start with _ or are in this list:
         excluded_vars = ('mediafunc', 'sack', 'metalink_data', 'grab', 
                          'grabfunc', 'repoXML', 'cfg', 'retrieved',
-                        'mirrorlistparsed', 'gpg_import_func', 'failure_obj',
+                        'mirrorlistparsed', 'gpg_import_func', 
+                        'gpgca_import_func', 'failure_obj',
                         'callback', 'confirm_func', 'groups_added', 
                         'interrupt_callback', 'id', 'mirror_failure_obj',
                         'repo_config_age', 'groupsfilename', 'copy_local', 
@@ -541,12 +544,15 @@ class YumRepository(Repository, config.RepoConf):
         """make the necessary dirs, if possible, raise on failure"""
 
         cachedir = os.path.join(self.basecachedir, self.id)
+        persistdir = os.path.join(self.base_persistdir, self.id)
         pkgdir = os.path.join(cachedir, 'packages')
         hdrdir = os.path.join(cachedir, 'headers')
         self.setAttribute('_dir_setup_cachedir', cachedir)
         self.setAttribute('_dir_setup_pkgdir', pkgdir)
         self.setAttribute('_dir_setup_hdrdir', hdrdir)
-        self.setAttribute('_dir_setup_gpgdir', self.cachedir + '/gpgdir')
+        self.setAttribute('_dir_setup_persistdir', persistdir)
+        self.setAttribute('_dir_setup_gpgdir', persistdir + '/gpgdir')
+        self.setAttribute('_dir_setup_gpgcadir', persistdir + '/gpgcadir')
 
         cookie = self.cachedir + '/' + self.metadata_cookie_fn
         self.setAttribute('_dir_setup_metadata_cookie', cookie)
@@ -554,6 +560,14 @@ class YumRepository(Repository, config.RepoConf):
         for dir in [self.cachedir, self.pkgdir]:
             self._dirSetupMkdir_p(dir)
 
+        # persistdir is really root-only but try the make anyway and just
+        # catch the exception
+        for dir in [self.persistdir]:
+            try:
+                self._dirSetupMkdir_p(dir)
+            except Errors.RepoError, e:
+                pass
+                
         # if we're using a cachedir that's not the system one, copy over these
         # basic items from the system one
         self._preload_md_from_system_cache('repomd.xml')
@@ -583,12 +597,16 @@ class YumRepository(Repository, config.RepoConf):
             self._dirSetupMkdir_p(val)
         return ret
     cachedir = property(lambda self: self._dirGetAttr('cachedir'))
+    persistdir = property(lambda self: self._dirGetAttr('persistdir'))
+
     pkgdir   = property(lambda self: self._dirGetAttr('pkgdir'),
                         lambda self, x: self._dirSetAttr('pkgdir', x))
     hdrdir   = property(lambda self: self._dirGetAttr('hdrdir'),
                         lambda self, x: self._dirSetAttr('hdrdir', x))
     gpgdir   = property(lambda self: self._dirGetAttr('gpgdir'),
                         lambda self, x: self._dirSetAttr('gpgdir', x))
+    gpgcadir   = property(lambda self: self._dirGetAttr('gpgcadir'),  
+                        lambda self, x: self._dirSetAttr('gpgcadir', x))
     metadata_cookie = property(lambda self: self._dirGetAttr('metadata_cookie'))
 
     def baseurlSetup(self):
@@ -947,11 +965,12 @@ class YumRepository(Repository, config.RepoConf):
             fo.close()
             del fo
 
-    def setup(self, cache, mediafunc = None, gpg_import_func=None, confirm_func=None):
+    def setup(self, cache, mediafunc = None, gpg_import_func=None, confirm_func=None, gpgca_import_func=None):
         try:
             self.cache = cache
             self.mediafunc = mediafunc
             self.gpg_import_func = gpg_import_func
+            self.gpgca_import_func = gpgca_import_func
             self.confirm_func = confirm_func
         except Errors.RepoError, e:
             raise
@@ -1451,7 +1470,6 @@ class YumRepository(Repository, config.RepoConf):
                                        size=102400)
             except URLGrabError, e:
                 raise URLGrabError(-1, 'Error finding signature for repomd.xml for %s: %s' % (self, e))
-
             valid = misc.valid_detached_sig(result, filepath, self.gpgdir)
             if not valid and self.gpg_import_func:
                 try:
-- 
1.7.2.1



More information about the Yum-devel mailing list