From 0d3bec96b547d3e2d65fb27f6d25bd8149d0667d Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Sun, 19 Jul 2009 14:28:19 +0100 Subject: [PATCH] Connect to a key-server to lookup trust hints --- zeroinstall/injector/fetch.py | 47 +++++++++++++++++++++++++++++++++++++---- zeroinstall/injector/handler.py | 36 ++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/zeroinstall/injector/fetch.py b/zeroinstall/injector/fetch.py index 5422ddc..4beb59d 100644 --- a/zeroinstall/injector/fetch.py +++ b/zeroinstall/injector/fetch.py @@ -30,6 +30,46 @@ def _get_feed_dir(feed): raise SafeException(_("Invalid URL '%s'") % feed) return os.path.join('feeds', scheme, domain, _escape_slashes(rest)) +class KeyInfoFetcher: + """Fetches information about a GPG key from a key-info server. + @since: 0.42 + Example: + >>> kf = KeyInfoFetcher('https://server', fingerprint) + >>> while True: + print "Info:", kf.collect_info() + if kf.blocker is None: break + print kf.status + yield kf.blocker + """ + def __init__(self, server, fingerprint): + self.fingerprint = fingerprint + self.info = [] + self.blocker = None + + if server is None: return + + self.status = 'Fetching key information from %s...' % server + + dl = download.Download(server + '/key/' + fingerprint) + dl.start() + + @tasks.async + def fetch_key_info(): + tempfile = dl.tempfile + yield dl.downloaded + self.blocker = None + tasks.check(dl.downloaded) + tempfile.seek(0) + from xml.dom import minidom + doc = minidom.parse(tempfile) + if doc.documentElement.localName != 'key-lookup': + raise SafeException('Expected , not <%s>' % doc.documentElement.localName) + self.info += doc.documentElement.childNodes + + self.blocker = fetch_key_info() + +DEFAULT_KEY_LOOKUP_SERVER = 'https://keylookup.appspot.com' + class Fetcher(object): """Downloads and stores various things. @ivar handler: handler to use for user-interaction @@ -42,6 +82,8 @@ class Fetcher(object): def __init__(self, handler): self.handler = handler self.feed_mirror = "http://roscidus.com/0mirror" + self.key_info_server = DEFAULT_KEY_LOOKUP_SERVER + self.key_info = {} @tasks.async def cook(self, required_digest, recipe, stores, force = False, impl_hint = None): @@ -212,7 +254,7 @@ class Fetcher(object): iface = iface_cache.get_interface(pending.url) if not iface_cache.update_interface_if_trusted(iface, pending.sigs, pending.new_xml): - blocker = self.handler.confirm_keys(pending, self.fetch_key_info) + blocker = self.handler.confirm_keys(pending, lambda fingerprint: KeyInfoFetcher('http://localhost:8080', fingerprint)) if blocker: yield blocker tasks.check(blocker) @@ -223,9 +265,6 @@ class Fetcher(object): task.dl = dl return task - def fetch_key_info(self, fingerprint): - return - def download_impl(self, impl, retrieval_method, stores, force = False): """Download an implementation. @param impl: the selected implementation diff --git a/zeroinstall/injector/handler.py b/zeroinstall/injector/handler.py index b3d24fb..c95d4a4 100644 --- a/zeroinstall/injector/handler.py +++ b/zeroinstall/injector/handler.py @@ -128,6 +128,7 @@ class Handler(object): new-style, or L{confirm_trust_keys} for older classes. A class is considered old-style if it overrides confirm_trust_keys and not confirm_import_feed. + @since: 0.42 @arg pending: an object holding details of the updated feed @type pending: L{PendingFeed} @arg fetch_key_info: a function which can be used to fetch information about a key fingerprint @@ -139,7 +140,7 @@ class Handler(object): if hasattr(self.confirm_trust_keys, 'original') or not hasattr(self.confirm_import_feed, 'original'): # new-style class - self.confirm_import_feed(pending, fetch_key_info) + return self.confirm_import_feed(pending, fetch_key_info) else: # old-style class from zeroinstall.injector import iface_cache @@ -149,8 +150,10 @@ class Handler(object): iface = iface_cache.iface_cache.get_interface(pending.url) return self.confirm_trust_keys(iface, pending.sigs, pending.new_xml) + @tasks.async def confirm_import_feed(self, pending, fetch_key_info): """Sub-classes should override this method to interact with the user about new feeds. + @since: 0.42 @see: L{confirm_keys}""" from zeroinstall.injector import trust, gpg valid_sigs = [s for s in pending.sigs if isinstance(s, gpg.ValidSig)] @@ -166,6 +169,36 @@ class Handler(object): for x in valid_sigs: print >>sys.stderr, "-", x + def text(parent): + text = "" + for node in parent.childNodes: + if node.nodeType == node.TEXT_NODE: + text = text + node.data + return text + + kfs = [fetch_key_info(sig.fingerprint) for sig in valid_sigs] + while kfs: + old_kfs = kfs + kfs = [] + for kf in old_kfs: + infos = kf.collect_info() + if infos: + if len(valid_sigs) > 1: + print "%s: " % kf.fingerprint + for info in infos: + print >>sys.stderr, "-", text(info) + if kf.blocker: + kfs.append(kf) + if kfs: + for kf in kfs: print >>sys.stderr, kf.status + blockers = [kf.blocker for kf in kfs] + yield blockers + for b in blockers: + try: + tasks.check(b) + except Exception, ex: + warn("Failed to get key info: %s", ex) + if len(valid_sigs) == 1: print >>sys.stderr, "Do you want to trust this key to sign feeds from '%s'?" % domain else: @@ -187,6 +220,7 @@ class Handler(object): def confirm_trust_keys(self, interface, sigs, iface_xml): """We don't trust any of the signatures yet. Ask the user. When done update the L{trust} database, and then call L{trust.TrustDB.notify}. + @deprecated: see L{confirm_keys} @arg interface: the interface being updated @arg sigs: a list of signatures (from L{gpg.check_stream}) @arg iface_xml: the downloaded data (not yet trusted) -- 2.11.4.GIT