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.
10 # Copyright (C) 2009, Thomas Leonard
11 # See the README file for details, or visit http://0install.net.
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']
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
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.
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.
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
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()
71 # warn("Calling trust_key() without a domain is deprecated")
73 self
.keys
[fingerprint
].add(domain
)
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
87 from xml
.dom
import minidom
90 doc
= minidom
.Document()
91 root
= doc
.createElementNS(XMLNS_TRUST
, 'trusted-keys')
92 root
.setAttribute('xmlns', XMLNS_TRUST
)
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")
110 os
.rename(tmpname
, os
.path
.join(d
, 'trustdb.xml'))
113 """Call all watcher callbacks.
114 This should be called after trusting or untrusting one or more new keys.
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)
124 trust
= basedir
.load_first_config(config_site
, config_prog
, 'trustdb.xml')
126 keys
= minidom
.parse(trust
).documentElement
127 for key
in keys
.getElementsByTagNameNS(XMLNS_TRUST
, 'key'):
129 self
.keys
[key
.getAttribute('fingerprint')] = domains
130 for domain
in key
.getElementsByTagNameNS(XMLNS_TRUST
, 'domain'):
131 domains
.add(domain
.getAttribute('value'))
133 # Convert old database to XML format
134 trust
= basedir
.load_first_config(config_site
, config_prog
, 'trust')
136 #print "Loading trust from", trust_db
137 for key
in file(trust
).read().split('\n'):
139 self
.keys
[key
] = set(['*'])
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
151 @return: the trust domain
154 @raise SafeException: the URL can't be parsed"""
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
!= '*':
162 raise SafeException("Can't extract domain from URL '%s'" % url
)