Added the ability to remove keys from the trusted list, using the Preferences
[zeroinstall.git] / zeroinstall / injector / trust.py
blob0fd1e0c42b7a0009ff7732717c99962b42cb5539
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 @since: 0.27
42 """
43 self.ensure_uptodate()
44 return self.keys.get(fingerprint, sets.Set())
46 def get_keys_for_domain(self, domain):
47 """Return the set of keys trusted for this domain.
48 @since: 0.27"""
49 self.ensure_uptodate()
50 return sets.Set([fp for fp in self.keys
51 if domain in self.keys[fp]])
53 def trust_key(self, fingerprint, domain = '*'):
54 """Add key to the list of trusted fingerprints.
55 @param fingerprint: base 16 fingerprint without any spaces
56 @type fingerprint: str
57 @param domain: domain in which key is to be trusted
58 @type domain: str
59 @note: call L{notify} after trusting one or more new keys"""
60 if self.is_trusted(fingerprint, domain): return
62 int(fingerprint, 16) # Ensure fingerprint is valid
64 if fingerprint not in self.keys:
65 self.keys[fingerprint] = sets.Set()
67 #if domain == '*':
68 # warn("Calling trust_key() without a domain is deprecated")
70 self.keys[fingerprint].add(domain)
71 self.save()
73 def untrust_key(self, key, domain = '*'):
74 self.ensure_uptodate()
75 self.keys[key].remove(domain)
77 if not self.keys[key]:
78 # No more domains for this key
79 del self.keys[key]
81 self.save()
83 def save(self):
84 from xml.dom import minidom
85 import tempfile
87 doc = minidom.Document()
88 root = doc.createElementNS(XMLNS_TRUST, 'trusted-keys')
89 root.setAttribute('xmlns', XMLNS_TRUST)
90 doc.appendChild(root)
92 for fingerprint in self.keys:
93 keyelem = doc.createElementNS(XMLNS_TRUST, 'key')
94 root.appendChild(keyelem)
95 keyelem.setAttribute('fingerprint', fingerprint)
96 for domain in self.keys[fingerprint]:
97 domainelem = doc.createElementNS(XMLNS_TRUST, 'domain')
98 domainelem.setAttribute('value', domain)
99 keyelem.appendChild(domainelem)
101 d = basedir.save_config_path(config_site, config_prog)
102 fd, tmpname = tempfile.mkstemp(dir = d, prefix = 'trust-')
103 tmp = os.fdopen(fd, 'wb')
104 doc.writexml(tmp, indent = "", addindent = " ", newl = "\n")
105 tmp.close()
107 os.rename(tmpname, os.path.join(d, 'trustdb.xml'))
109 def notify(self):
110 """Call all watcher callbacks.
111 This should be called after trusting or untrusting one or more new keys.
112 @since: 0.25"""
113 for w in self.watchers: w()
115 def ensure_uptodate(self):
116 from xml.dom import minidom
118 # This is a bit inefficient... (could cache things)
119 self.keys = {}
121 trust = basedir.load_first_config(config_site, config_prog, 'trustdb.xml')
122 if trust:
123 keys = minidom.parse(trust).documentElement
124 for key in keys.getElementsByTagNameNS(XMLNS_TRUST, 'key'):
125 domains = sets.Set()
126 self.keys[key.getAttribute('fingerprint')] = domains
127 for domain in key.getElementsByTagNameNS(XMLNS_TRUST, 'domain'):
128 domains.add(domain.getAttribute('value'))
129 else:
130 # Convert old database to XML format
131 trust = basedir.load_first_config(config_site, config_prog, 'trust')
132 if trust:
133 #print "Loading trust from", trust_db
134 for key in file(trust).read().split('\n'):
135 if key:
136 self.keys[key] = sets.Set('*')
138 def domain_from_url(url):
139 """Extract the trust domain for a URL.
140 @param url: the feed's URL
141 @type url: str
142 @return: the trust domain
143 @rtype: str
144 @since: 0.27
145 @raise SafeException: the URL can't be parsed"""
146 import urlparse
147 from zeroinstall import SafeException
148 if url.startswith('/'):
149 raise SafeException("Can't get domain from a local path: '%s'" % url)
150 domain = urlparse.urlparse(url)[1]
151 if domain and domain != '*':
152 return domain
153 raise SafeException("Can't extract domain from URL '%s'" % url)
155 trust_db = TrustDB()