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.
13 from zeroinstall
import _
16 from zeroinstall
.support
import basedir
17 from namespaces
import config_site
, config_prog
, XMLNS_TRUST
19 class TrustDB(object):
20 """A database of trusted keys.
21 @ivar keys: maps trusted key fingerprints to a set of domains for which where it is trusted
22 @type keys: {str: set(str)}
23 @ivar watchers: callbacks invoked by L{notify}
24 @see: L{trust_db} - the singleton instance of this class"""
25 __slots__
= ['keys', 'watchers']
31 def is_trusted(self
, fingerprint
, domain
= None):
32 self
.ensure_uptodate()
34 domains
= self
.keys
.get(fingerprint
, None)
35 if not domains
: return False # Unknown key
38 return True # Deprecated
40 return domain
in domains
or '*' in domains
42 def get_trust_domains(self
, fingerprint
):
43 """Return the set of domains in which this key is trusted.
44 If the list includes '*' then the key is trusted everywhere.
47 self
.ensure_uptodate()
48 return self
.keys
.get(fingerprint
, set())
50 def get_keys_for_domain(self
, domain
):
51 """Return the set of keys trusted for this domain.
53 self
.ensure_uptodate()
54 return set([fp
for fp
in self
.keys
55 if domain
in self
.keys
[fp
]])
57 def trust_key(self
, fingerprint
, domain
= '*'):
58 """Add key to the list of trusted fingerprints.
59 @param fingerprint: base 16 fingerprint without any spaces
60 @type fingerprint: str
61 @param domain: domain in which key is to be trusted
63 @note: call L{notify} after trusting one or more new keys"""
64 if self
.is_trusted(fingerprint
, domain
): return
66 int(fingerprint
, 16) # Ensure fingerprint is valid
68 if fingerprint
not in self
.keys
:
69 self
.keys
[fingerprint
] = set()
72 # warn("Calling trust_key() without a domain is deprecated")
74 self
.keys
[fingerprint
].add(domain
)
77 def untrust_key(self
, key
, domain
= '*'):
78 self
.ensure_uptodate()
79 self
.keys
[key
].remove(domain
)
81 if not self
.keys
[key
]:
82 # No more domains for this key
88 from xml
.dom
import minidom
91 doc
= minidom
.Document()
92 root
= doc
.createElementNS(XMLNS_TRUST
, 'trusted-keys')
93 root
.setAttribute('xmlns', XMLNS_TRUST
)
96 for fingerprint
in self
.keys
:
97 keyelem
= doc
.createElementNS(XMLNS_TRUST
, 'key')
98 root
.appendChild(keyelem
)
99 keyelem
.setAttribute('fingerprint', fingerprint
)
100 for domain
in self
.keys
[fingerprint
]:
101 domainelem
= doc
.createElementNS(XMLNS_TRUST
, 'domain')
102 domainelem
.setAttribute('value', domain
)
103 keyelem
.appendChild(domainelem
)
105 d
= basedir
.save_config_path(config_site
, config_prog
)
106 fd
, tmpname
= tempfile
.mkstemp(dir = d
, prefix
= 'trust-')
107 tmp
= os
.fdopen(fd
, 'wb')
108 doc
.writexml(tmp
, indent
= "", addindent
= " ", newl
= "\n")
111 os
.rename(tmpname
, os
.path
.join(d
, 'trustdb.xml'))
114 """Call all watcher callbacks.
115 This should be called after trusting or untrusting one or more new keys.
117 for w
in self
.watchers
: w()
119 def ensure_uptodate(self
):
120 from xml
.dom
import minidom
122 # This is a bit inefficient... (could cache things)
125 trust
= basedir
.load_first_config(config_site
, config_prog
, 'trustdb.xml')
127 keys
= minidom
.parse(trust
).documentElement
128 for key
in keys
.getElementsByTagNameNS(XMLNS_TRUST
, 'key'):
130 self
.keys
[key
.getAttribute('fingerprint')] = domains
131 for domain
in key
.getElementsByTagNameNS(XMLNS_TRUST
, 'domain'):
132 domains
.add(domain
.getAttribute('value'))
134 # Convert old database to XML format
135 trust
= basedir
.load_first_config(config_site
, config_prog
, 'trust')
137 #print "Loading trust from", trust_db
138 for key
in file(trust
).read().split('\n'):
140 self
.keys
[key
] = set(['*'])
142 # No trust database found.
143 # Trust Thomas Leonard's key for 0install.net by default.
144 # Avoids distracting confirmation box on first run when we check
145 # for updates to the GUI.
146 self
.keys
['92429807C9853C0744A68B9AAE07828059A53CC1'] = set(['0install.net'])
148 def domain_from_url(url
):
149 """Extract the trust domain for a URL.
150 @param url: the feed's URL
152 @return: the trust domain
155 @raise SafeException: the URL can't be parsed"""
157 from zeroinstall
import SafeException
158 if url
.startswith('/'):
159 raise SafeException(_("Can't get domain from a local path: '%s'") % url
)
160 domain
= urlparse
.urlparse(url
)[1]
161 if domain
and domain
!= '*':
163 raise SafeException(_("Can't extract domain from URL '%s'") % url
)