The GUI trust box now displays information from the key information server
[zeroinstall.git] / zeroinstall / gtkui / trust_box.py
blob08d61c7888aaa5018f05a540a649835091186d76
1 # Copyright (C) 2009, Thomas Leonard
2 # -*- coding: utf-8 -*-
3 # See the README file for details, or visit http://0install.net.
4 from zeroinstall import _
6 import gtk
7 from zeroinstall.injector.model import SafeException
8 from zeroinstall.injector import gpg, trust
9 from zeroinstall.support import tasks
10 from zeroinstall.gtkui import help_box, gtkutils
12 def frame(page, title, content, expand = False):
13 frame = gtk.Frame()
14 label = gtk.Label()
15 label.set_markup('<b>%s</b>' % title)
16 frame.set_label_widget(label)
17 frame.set_shadow_type(gtk.SHADOW_NONE)
18 if type(content) in (str, unicode):
19 content = gtk.Label(content)
20 content.set_alignment(0, 0.5)
21 content.set_selectable(True)
22 frame.add(content)
23 if hasattr(content, 'set_padding'):
24 content.set_padding(8, 4)
25 else:
26 content.set_border_width(8)
27 page.pack_start(frame, expand, True, 0)
29 def pretty_fp(fp):
30 s = fp[0:4]
31 for x in range(4, len(fp), 4):
32 s += ' ' + fp[x:x + 4]
33 return s
35 def left(text):
36 label = gtk.Label(text)
37 label.set_alignment(0, 0.5)
38 label.set_selectable(True)
39 return label
41 def make_hints_area(closed, key_info_fetcher):
42 def text(parent):
43 text = ""
44 for node in parent.childNodes:
45 if node.nodeType == node.TEXT_NODE:
46 text = text + node.data
47 return text
49 hints = gtk.VBox(False, 4)
51 shown = set()
52 def add_hints():
53 infos = set(key_info_fetcher.info) - shown
54 for info in infos:
55 hints.add(make_hint(info.getAttribute("vote"), text(info)))
56 shown.add(info)
58 if not(key_info_fetcher.blocker or shown):
59 hints.add(make_hint("bad", _('Warning: Nothing known about this key!')))
61 if key_info_fetcher.blocker:
62 status = left(key_info_fetcher.status)
63 hints.add(status)
65 @tasks.async
66 def update_when_ready():
67 while key_info_fetcher.blocker:
68 yield key_info_fetcher.blocker, closed
69 if closed.happened:
70 # The dialog box was closed. Stop updating.
71 return
72 add_hints()
73 status.destroy()
74 update_when_ready()
75 else:
76 add_hints()
78 hints.show()
79 return hints
81 def make_hint(vote, hint_text):
82 hint_icon = gtk.Image()
83 if vote == "good":
84 hint_icon.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_BUTTON)
85 else:
86 hint_icon.set_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_BUTTON)
87 hint = left(hint_text)
88 hint.set_line_wrap(True)
89 hint_hbox = gtk.HBox(False, 4)
90 hint_hbox.pack_start(hint_icon, False, True, 0)
91 hint_hbox.pack_start(hint, True, True, 0)
92 hint_icon.set_alignment(0, 0)
93 hint_hbox.show_all()
94 return hint_hbox
96 class TrustBox(gtk.Dialog):
97 """Display a dialog box asking the user to confirm that one of the
98 keys is trusted for this domain.
99 """
100 parent = None
101 closed = None
103 def __init__(self, pending, valid_sigs, parent):
104 """@since: 0.42"""
105 assert valid_sigs
107 gtk.Dialog.__init__(self)
108 self.set_has_separator(False)
109 self.set_position(gtk.WIN_POS_CENTER)
110 self.set_transient_for(parent)
112 self.closed = tasks.Blocker(_("confirming keys with user"))
114 domain = trust.domain_from_url(pending.url)
115 assert domain
117 def destroy(box):
118 self.closed.trigger()
120 self.connect('destroy', destroy)
122 self.set_title(_('Confirm trust'))
124 vbox = gtk.VBox(False, 4)
125 vbox.set_border_width(4)
126 self.vbox.pack_start(vbox, True, True, 0)
128 notebook = gtk.Notebook()
130 if len(valid_sigs) == 1:
131 notebook.set_show_tabs(False)
133 label = left(_('Checking: %s') % pending.url)
134 label.set_padding(4, 4)
135 vbox.pack_start(label, False, True, 0)
137 currently_trusted_keys = trust.trust_db.get_keys_for_domain(domain)
138 if currently_trusted_keys:
139 keys = [gpg.load_key(fingerprint) for fingerprint in currently_trusted_keys]
140 descriptions = [_("%(key_name)s\n(fingerprint: %(key_fingerprint)s)") % {'key_name': key.name, 'key_fingerprint': pretty_fp(key.fingerprint)}
141 for key in keys]
142 else:
143 descriptions = [_('None')]
144 frame(vbox, _('Keys already approved for "%s"') % domain, '\n'.join(descriptions))
146 if len(valid_sigs) == 1:
147 label = left(_('This key signed the feed:'))
148 else:
149 label = left(_('These keys signed the feed:'))
151 label.set_padding(4, 4)
152 vbox.pack_start(label, False, True, 0)
154 vbox.pack_start(notebook, True, True, 0)
156 self.add_button(gtk.STOCK_HELP, gtk.RESPONSE_HELP)
157 self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
158 self.add_button(gtk.STOCK_ADD, gtk.RESPONSE_OK)
159 self.set_default_response(gtk.RESPONSE_OK)
161 trust_checkbox = {} # Sig -> CheckButton
162 def ok_sensitive():
163 trust_any = False
164 for toggle in trust_checkbox.values():
165 if toggle.get_active():
166 trust_any = True
167 break
168 self.set_response_sensitive(gtk.RESPONSE_OK, trust_any)
170 for sig in valid_sigs:
171 if hasattr(sig, 'get_details'):
172 name = '<unknown>'
173 details = sig.get_details()
174 for item in details:
175 if item[0] in ('pub', 'uid') and \
176 len(item) > 9:
177 name = item[9]
178 break
179 else:
180 name = None
181 page = gtk.VBox(False, 4)
182 page.set_border_width(8)
184 frame(page, _('Fingerprint'), pretty_fp(sig.fingerprint))
186 if name is not None:
187 frame(page, _('Claimed identity'), name)
189 frame(page, _('Unreliable hints database says'), make_hints_area(self.closed, valid_sigs[sig]))
191 already_trusted = trust.trust_db.get_trust_domains(sig.fingerprint)
192 if already_trusted:
193 frame(page, _('You already trust this key for these domains'),
194 '\n'.join(already_trusted))
196 trust_checkbox[sig] = gtk.CheckButton(_('_Trust this key'))
197 page.pack_start(trust_checkbox[sig], False, True, 0)
198 trust_checkbox[sig].connect('toggled', lambda t: ok_sensitive())
200 notebook.append_page(page, gtk.Label(name or 'Signature'))
202 ok_sensitive()
203 self.vbox.show_all()
205 def response(box, resp):
206 if resp == gtk.RESPONSE_HELP:
207 trust_help.display()
208 return
209 if resp == gtk.RESPONSE_OK:
210 self.trust_keys([sig for sig in trust_checkbox if trust_checkbox[sig].get_active()], domain)
211 self.destroy()
212 self.connect('response', response)
214 def trust_keys(self, agreed_sigs, domain):
215 assert domain
216 try:
217 for sig in agreed_sigs:
218 trust.trust_db.trust_key(sig.fingerprint, domain)
220 trust.trust_db.notify()
221 except Exception, ex:
222 gtkutils.show_message_box(self, str(ex), gtk.MESSAGE_ERROR)
223 if not isinstance(ex, SafeException):
224 raise
226 trust_help = help_box.HelpBox(_("Trust Help"),
227 (_('Overview'), '\n' +
228 _("""When you run a program, it typically has access to all your files and can generally do \
229 anything that you're allowed to do (delete files, send emails, etc). So it's important \
230 to make sure that you don't run anything malicious.""")),
232 (_('Digital signatures'), '\n' +
233 _("""Each software author creates a 'key-pair'; a 'public key' and a 'private key'. Without going \
234 into the maths, only something encrypted with the private key will decrypt with the public key.
236 So, when a programmer releases some software, they encrypt it with their private key (which no-one \
237 else has). When you download it, the injector checks that it decrypts using their public key, thus \
238 proving that it came from them and hasn't been tampered with.""")),
240 (_('Trust'), '\n' +
241 _("""After the injector has checked that the software hasn't been modified since it was signed with \
242 the private key, you still have the following problems:
244 1. Does the public key you have really belong to the author?
245 2. Even if the software really did come from that person, do you trust them?""")),
247 (_('Key fingerprints'), '\n' +
248 _("""To confirm (1), you should compare the public key you have with the genuine one. To make this \
249 easier, the injector displays a 'fingerprint' for the key. Look in mailing list postings or some \
250 other source to check that the fingerprint is right (a different key will have a different \
251 fingerprint).
253 You're trying to protect against the situation where an attacker breaks into a web site \
254 and puts up malicious software, signed with the attacker's private key, and puts up the \
255 attacker's public key too. If you've downloaded this software before, you \
256 should be suspicious that you're being asked to confirm another key!""")),
258 (_('Reputation'), '\n' +
259 _("""In general, most problems seem to come from malicous and otherwise-unknown people \
260 replacing software with modified versions, or creating new programs intended only to \
261 cause damage. So, check your programs are signed by a key with a good reputation!""")))