1# crypto.py 2# Keys and signatures. 3# 4# Copyright (C) 2014 Red Hat, Inc. 5# 6# This copyrighted material is made available to anyone wishing to use, 7# modify, copy, or redistribute it subject to the terms and conditions of 8# the GNU General Public License v.2, or (at your option) any later version. 9# This program is distributed in the hope that it will be useful, but WITHOUT 10# ANY WARRANTY expressed or implied, including the implied warranties of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 12# Public License for more details. You should have received a copy of the 13# GNU General Public License along with this program; if not, write to the 14# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 15# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the 16# source code or documentation are not subject to the GNU General Public 17# License and may only be used or replicated with the express permission of 18# Red Hat, Inc. 19# 20 21from __future__ import print_function 22from __future__ import absolute_import 23from __future__ import unicode_literals 24from dnf.i18n import _ 25import contextlib 26import dnf.pycomp 27import dnf.util 28import dnf.yum.misc 29import io 30import logging 31import os 32import tempfile 33 34try: 35 from gpg import Context 36 from gpg import Data 37except ImportError: 38 import gpgme 39 40 41 class Context(object): 42 def __init__(self): 43 self.__dict__["ctx"] = gpgme.Context() 44 45 def __enter__(self): 46 return self 47 48 def __exit__(self, type, value, tb): 49 pass 50 51 @property 52 def armor(self): 53 return self.ctx.armor 54 55 @armor.setter 56 def armor(self, value): 57 self.ctx.armor = value 58 59 def op_import(self, key_fo): 60 if isinstance(key_fo, basestring): 61 key_fo = io.BytesIO(key_fo) 62 self.ctx.import_(key_fo) 63 64 def op_export(self, pattern, mode, keydata): 65 self.ctx.export(pattern, keydata) 66 67 def __getattr__(self, name): 68 return getattr(self.ctx, name) 69 70 71 class Data(object): 72 def __init__(self): 73 self.__dict__["buf"] = io.BytesIO() 74 75 def __enter__(self): 76 return self 77 78 def __exit__(self, type, value, tb): 79 pass 80 81 def read(self): 82 return self.buf.getvalue() 83 84 def __getattr__(self, name): 85 return getattr(self.buf, name) 86 87 88GPG_HOME_ENV = 'GNUPGHOME' 89logger = logging.getLogger('dnf') 90 91 92def _extract_signing_subkey(key): 93 return dnf.util.first(subkey for subkey in key.subkeys if subkey.can_sign) 94 95 96def _printable_fingerprint(fpr_hex): 97 segments = (fpr_hex[i:i + 4] for i in range(0, len(fpr_hex), 4)) 98 return " ".join(segments) 99 100 101def import_repo_keys(repo): 102 gpgdir = repo._pubring_dir 103 known_keys = keyids_from_pubring(gpgdir) 104 for keyurl in repo.gpgkey: 105 for keyinfo in retrieve(keyurl, repo): 106 keyid = keyinfo.id_ 107 if keyid in known_keys: 108 logger.debug(_('repo %s: 0x%s already imported'), repo.id, keyid) 109 continue 110 if not repo._key_import._confirm(keyinfo): 111 continue 112 dnf.yum.misc.import_key_to_pubring( 113 keyinfo.raw_key, keyinfo.short_id, gpgdir=gpgdir, 114 make_ro_copy=False) 115 logger.debug(_('repo %s: imported key 0x%s.'), repo.id, keyid) 116 117 118def keyids_from_pubring(gpgdir): 119 if not os.path.exists(gpgdir): 120 return [] 121 122 with pubring_dir(gpgdir), Context() as ctx: 123 keyids = [] 124 for k in ctx.keylist(): 125 subkey = _extract_signing_subkey(k) 126 if subkey is not None: 127 keyids.append(subkey.keyid) 128 return keyids 129 130 131def log_key_import(keyinfo): 132 msg = (_('Importing GPG key 0x%s:\n' 133 ' Userid : "%s"\n' 134 ' Fingerprint: %s\n' 135 ' From : %s') % 136 (keyinfo.short_id, keyinfo.userid, 137 _printable_fingerprint(keyinfo.fingerprint), 138 keyinfo.url.replace("file://", ""))) 139 logger.critical("%s", msg) 140 141 142def log_dns_key_import(keyinfo, dns_result): 143 log_key_import(keyinfo) 144 if dns_result == dnf.dnssec.Validity.VALID: 145 logger.critical(_('Verified using DNS record with DNSSEC signature.')) 146 else: 147 logger.critical(_('NOT verified using DNS record.')) 148 149@contextlib.contextmanager 150def pubring_dir(pubring_dir): 151 orig = os.environ.get(GPG_HOME_ENV, None) 152 os.environ[GPG_HOME_ENV] = pubring_dir 153 try: 154 yield 155 finally: 156 if orig is None: 157 del os.environ[GPG_HOME_ENV] 158 else: 159 os.environ[GPG_HOME_ENV] = orig 160 161 162def rawkey2infos(key_fo): 163 pb_dir = tempfile.mkdtemp() 164 keyinfos = [] 165 with pubring_dir(pb_dir), Context() as ctx: 166 ctx.op_import(key_fo) 167 for key in ctx.keylist(): 168 subkey = _extract_signing_subkey(key) 169 if subkey is None: 170 continue 171 keyinfos.append(Key(key, subkey)) 172 ctx.armor = True 173 for info in keyinfos: 174 with Data() as sink: 175 ctx.op_export(info.id_, 0, sink) 176 sink.seek(0, os.SEEK_SET) 177 info.raw_key = sink.read() 178 dnf.util.rm_rf(pb_dir) 179 return keyinfos 180 181 182def retrieve(keyurl, repo=None): 183 if keyurl.startswith('http:'): 184 logger.warning(_("retrieving repo key for %s unencrypted from %s"), repo.id, keyurl) 185 with dnf.util._urlopen(keyurl, repo=repo) as handle: 186 keyinfos = rawkey2infos(handle) 187 for keyinfo in keyinfos: 188 keyinfo.url = keyurl 189 return keyinfos 190 191 192class Key(object): 193 def __init__(self, key, subkey): 194 self.id_ = subkey.keyid 195 self.fingerprint = subkey.fpr 196 self.raw_key = None 197 self.timestamp = subkey.timestamp 198 self.url = None 199 self.userid = key.uids[0].uid 200 201 @property 202 def short_id(self): 203 rj = '0' if dnf.pycomp.PY3 else b'0' 204 return self.id_[-8:].rjust(8, rj) 205 206 @property 207 def rpm_id(self): 208 return self.short_id.lower() 209