When checking a feed, check that the key is valid specifically for the feed's
[zeroinstall.git] / zeroinstall / injector / trust.py
blob50f372d1ab5035da2c5de7047432c6eb3f0936cc
1 """
2 Records who we trust to sign interfaces.
4 @var trust_db: Singleton trust database instance.
5 """
7 # Copyright (C) 2006, Thomas Leonard
8 # See the README file for details, or visit http://0install.net.
10 import os, sets
12 import basedir
13 from namespaces import config_site, config_prog, XMLNS_TRUST
15 class TrustDB(object):
16 """A database of trusted keys.
17 @ivar keys: maps trusted key fingerprints to a list of domains
18 @type keys: {str: set(str)}
19 @ivar watchers: callbacks invoked by L{notify}
20 @see: L{trust_db} - the singleton instance of this class"""
21 __slots__ = ['keys', 'watchers']
23 def __init__(self):
24 self.keys = None
25 self.watchers = []
27 def is_trusted(self, fingerprint, domain = None):
28 self.ensure_uptodate()
30 domains = self.keys.get(fingerprint, None)
31 if not domains: return False # Unknown key
33 if domain is None:
34 return True # Deprecated
36 return domain in domains or '*' in domains
38 def get_trust_domains(self, fingerprint):
39 """Return the set of domains in which this key is trusted.
40 If the list includes '*' then the key is trusted everywhere.
41 """
42 self.ensure_uptodate()
43 return self.keys.get(fingerprint, sets.Set())
45 def trust_key(self, fingerprint, domain = '*'):
46 """Add key to the list of trusted fingerprints.
47 @param fingerprint: base 16 fingerprint without any spaces
48 @type fingerprint: str
49 @param domain: domain in which key is to be trusted
50 @type domain: str
51 @note: call L{notify} after trusting one or more new keys"""
52 if self.is_trusted(fingerprint, domain): return
54 int(fingerprint, 16) # Ensure fingerprint is valid
56 if fingerprint not in self.keys:
57 self.keys[fingerprint] = sets.Set()
59 #if domain == '*':
60 # warn("Calling trust_key() without a domain is deprecated")
62 self.keys[fingerprint].add(domain)
63 self.save()
65 def untrust_key(self, key):
66 self.ensure_uptodate()
67 del self.keys[key]
68 self.save()
70 def save(self):
71 from xml.dom import minidom
72 import tempfile
74 doc = minidom.Document()
75 root = doc.createElementNS(XMLNS_TRUST, 'trusted-keys')
76 root.setAttribute('xmlns', XMLNS_TRUST)
77 doc.appendChild(root)
79 for fingerprint in self.keys:
80 keyelem = doc.createElementNS(XMLNS_TRUST, 'key')
81 root.appendChild(keyelem)
82 keyelem.setAttribute('fingerprint', fingerprint)
83 for domain in self.keys[fingerprint]:
84 domainelem = doc.createElementNS(XMLNS_TRUST, 'domain')
85 domainelem.setAttribute('value', domain)
86 keyelem.appendChild(domainelem)
88 d = basedir.save_config_path(config_site, config_prog)
89 fd, tmpname = tempfile.mkstemp(dir = d, prefix = 'trust-')
90 tmp = os.fdopen(fd, 'wb')
91 doc.writexml(tmp, indent = "", addindent = " ", newl = "\n")
92 tmp.close()
94 os.rename(tmpname, os.path.join(d, 'trustdb.xml'))
96 def notify(self):
97 """Call all watcher callbacks.
98 This should be called after trusting one or more new keys.
99 @since: 0.25"""
100 for w in self.watchers: w()
102 def ensure_uptodate(self):
103 from xml.dom import minidom
105 # This is a bit inefficient... (could cache things)
106 self.keys = {}
108 trust = basedir.load_first_config(config_site, config_prog, 'trustdb.xml')
109 if trust:
110 keys = minidom.parse(trust).documentElement
111 for key in keys.getElementsByTagNameNS(XMLNS_TRUST, 'key'):
112 domains = sets.Set()
113 self.keys[key.getAttribute('fingerprint')] = domains
114 for domain in key.getElementsByTagNameNS(XMLNS_TRUST, 'domain'):
115 domains.add(domain.getAttribute('value'))
116 else:
117 # Convert old database to XML format
118 trust = basedir.load_first_config(config_site, config_prog, 'trust')
119 if trust:
120 #print "Loading trust from", trust_db
121 for key in file(trust).read().split('\n'):
122 if key:
123 self.keys[key] = sets.Set('*')
125 def domain_from_url(url):
126 """Extract the trust domain for a URL.
127 @param url: the feed's URL
128 @type url: str
129 @return: the trust domain
130 @rtype: str
131 @since: 0.27
132 @raise SafeException: the URL can't be parsed"""
133 import urlparse
134 from zeroinstall import SafeException
135 if url.startswith('/'):
136 raise SafeException("Can't get domain from a local path: '%s'" % url)
137 domain = urlparse.urlparse(url)[1]
138 if domain and domain != '*':
139 return domain
140 raise SafeException("Can't extract domain from URL '%s'" % url)
142 trust_db = TrustDB()