Update year to 2009 in various places
[zeroinstall/zeroinstall-rsl.git] / zeroinstall / injector / trust.py
blob6c5265598a644fde4378d39a9ff195f3579d6991
1 """
2 Records who we trust to sign interfaces.
4 Trust is divided up into domains, so that it is possible to trust a key
5 in some cases and not others.
7 @var trust_db: Singleton trust database instance.
8 """
10 # Copyright (C) 2009, Thomas Leonard
11 # See the README file for details, or visit http://0install.net.
13 import os
15 from zeroinstall.support import basedir
16 from namespaces import config_site, config_prog, XMLNS_TRUST
18 class TrustDB(object):
19 """A database of trusted keys.
20 @ivar keys: maps trusted key fingerprints to a set of domains for which where it is trusted
21 @type keys: {str: set(str)}
22 @ivar watchers: callbacks invoked by L{notify}
23 @see: L{trust_db} - the singleton instance of this class"""
24 __slots__ = ['keys', 'watchers']
26 def __init__(self):
27 self.keys = None
28 self.watchers = []
30 def is_trusted(self, fingerprint, domain = None):
31 self.ensure_uptodate()
33 domains = self.keys.get(fingerprint, None)
34 if not domains: return False # Unknown key
36 if domain is None:
37 return True # Deprecated
39 return domain in domains or '*' in domains
41 def get_trust_domains(self, fingerprint):
42 """Return the set of domains in which this key is trusted.
43 If the list includes '*' then the key is trusted everywhere.
44 @since: 0.27
45 """
46 self.ensure_uptodate()
47 return self.keys.get(fingerprint, set())
49 def get_keys_for_domain(self, domain):
50 """Return the set of keys trusted for this domain.
51 @since: 0.27"""
52 self.ensure_uptodate()
53 return set([fp for fp in self.keys
54 if domain in self.keys[fp]])
56 def trust_key(self, fingerprint, domain = '*'):
57 """Add key to the list of trusted fingerprints.
58 @param fingerprint: base 16 fingerprint without any spaces
59 @type fingerprint: str
60 @param domain: domain in which key is to be trusted
61 @type domain: str
62 @note: call L{notify} after trusting one or more new keys"""
63 if self.is_trusted(fingerprint, domain): return
65 int(fingerprint, 16) # Ensure fingerprint is valid
67 if fingerprint not in self.keys:
68 self.keys[fingerprint] = set()
70 #if domain == '*':
71 # warn("Calling trust_key() without a domain is deprecated")
73 self.keys[fingerprint].add(domain)
74 self.save()
76 def untrust_key(self, key, domain = '*'):
77 self.ensure_uptodate()
78 self.keys[key].remove(domain)
80 if not self.keys[key]:
81 # No more domains for this key
82 del self.keys[key]
84 self.save()
86 def save(self):
87 from xml.dom import minidom
88 import tempfile
90 doc = minidom.Document()
91 root = doc.createElementNS(XMLNS_TRUST, 'trusted-keys')
92 root.setAttribute('xmlns', XMLNS_TRUST)
93 doc.appendChild(root)
95 for fingerprint in self.keys:
96 keyelem = doc.createElementNS(XMLNS_TRUST, 'key')
97 root.appendChild(keyelem)
98 keyelem.setAttribute('fingerprint', fingerprint)
99 for domain in self.keys[fingerprint]:
100 domainelem = doc.createElementNS(XMLNS_TRUST, 'domain')
101 domainelem.setAttribute('value', domain)
102 keyelem.appendChild(domainelem)
104 d = basedir.save_config_path(config_site, config_prog)
105 fd, tmpname = tempfile.mkstemp(dir = d, prefix = 'trust-')
106 tmp = os.fdopen(fd, 'wb')
107 doc.writexml(tmp, indent = "", addindent = " ", newl = "\n")
108 tmp.close()
110 os.rename(tmpname, os.path.join(d, 'trustdb.xml'))
112 def notify(self):
113 """Call all watcher callbacks.
114 This should be called after trusting or untrusting one or more new keys.
115 @since: 0.25"""
116 for w in self.watchers: w()
118 def ensure_uptodate(self):
119 from xml.dom import minidom
121 # This is a bit inefficient... (could cache things)
122 self.keys = {}
124 trust = basedir.load_first_config(config_site, config_prog, 'trustdb.xml')
125 if trust:
126 keys = minidom.parse(trust).documentElement
127 for key in keys.getElementsByTagNameNS(XMLNS_TRUST, 'key'):
128 domains = set()
129 self.keys[key.getAttribute('fingerprint')] = domains
130 for domain in key.getElementsByTagNameNS(XMLNS_TRUST, 'domain'):
131 domains.add(domain.getAttribute('value'))
132 else:
133 # Convert old database to XML format
134 trust = basedir.load_first_config(config_site, config_prog, 'trust')
135 if trust:
136 #print "Loading trust from", trust_db
137 for key in file(trust).read().split('\n'):
138 if key:
139 self.keys[key] = set(['*'])
140 else:
141 # No trust database found.
142 # Trust Thomas Leonard's key for 0install.net by default.
143 # Avoids distracting confirmation box on first run when we check
144 # for updates to the GUI.
145 self.keys['92429807C9853C0744A68B9AAE07828059A53CC1'] = set(['0install.net'])
147 def domain_from_url(url):
148 """Extract the trust domain for a URL.
149 @param url: the feed's URL
150 @type url: str
151 @return: the trust domain
152 @rtype: str
153 @since: 0.27
154 @raise SafeException: the URL can't be parsed"""
155 import urlparse
156 from zeroinstall import SafeException
157 if url.startswith('/'):
158 raise SafeException("Can't get domain from a local path: '%s'" % url)
159 domain = urlparse.urlparse(url)[1]
160 if domain and domain != '*':
161 return domain
162 raise SafeException("Can't extract domain from URL '%s'" % url)
164 trust_db = TrustDB()