Updated exception handling to Python 2.6 syntax
[zeroinstall.git] / zeroinstall / gtkui / trust_box.py
blobff8c01566ecfeb7361f1417a7e7214dcc29a60f9
1 """
2 A dialog box for confirming GPG keys.
3 """
5 # Copyright (C) 2009, Thomas Leonard
6 # -*- coding: utf-8 -*-
7 # See the README file for details, or visit http://0install.net.
8 from zeroinstall import _, translation
10 import gtk
11 from zeroinstall.injector.model import SafeException
12 from zeroinstall.injector import gpg, trust
13 from zeroinstall.support import tasks
14 from zeroinstall.gtkui import help_box, gtkutils
16 def frame(page, title, content, expand = False):
17 frame = gtk.Frame()
18 label = gtk.Label()
19 label.set_markup('<b>%s</b>' % title)
20 frame.set_label_widget(label)
21 frame.set_shadow_type(gtk.SHADOW_NONE)
22 if type(content) in (str, unicode):
23 content = gtk.Label(content)
24 content.set_alignment(0, 0.5)
25 content.set_selectable(True)
26 frame.add(content)
27 if hasattr(content, 'set_padding'):
28 content.set_padding(8, 4)
29 else:
30 content.set_border_width(8)
31 page.pack_start(frame, expand, True, 0)
33 def pretty_fp(fp):
34 s = fp[0:4]
35 for x in range(4, len(fp), 4):
36 s += ' ' + fp[x:x + 4]
37 return s
39 def left(text):
40 label = gtk.Label(text)
41 label.set_alignment(0, 0.5)
42 label.set_selectable(True)
43 return label
45 def make_hints_area(closed, key_info_fetcher):
46 def text(parent):
47 text = ""
48 for node in parent.childNodes:
49 if node.nodeType == node.TEXT_NODE:
50 text = text + node.data
51 return text
53 hints = gtk.VBox(False, 4)
55 shown = set()
56 def add_hints():
57 infos = set(key_info_fetcher.info) - shown
58 for info in infos:
59 hints.add(make_hint(info.getAttribute("vote"), text(info)))
60 shown.add(info)
62 if not(key_info_fetcher.blocker or shown):
63 hints.add(make_hint("bad", _('Warning: Nothing known about this key!')))
65 if key_info_fetcher.blocker:
66 status = left(key_info_fetcher.status)
67 hints.add(status)
69 @tasks.async
70 def update_when_ready():
71 while key_info_fetcher.blocker:
72 yield key_info_fetcher.blocker, closed
73 if closed.happened:
74 # The dialog box was closed. Stop updating.
75 return
76 add_hints()
77 status.destroy()
78 update_when_ready()
79 else:
80 add_hints()
82 hints.show()
83 return hints
85 def make_hint(vote, hint_text):
86 hint_icon = gtk.Image()
87 if vote == "good":
88 hint_icon.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_BUTTON)
89 else:
90 hint_icon.set_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_BUTTON)
91 hint = left(hint_text)
92 hint.set_line_wrap(True)
93 hint_hbox = gtk.HBox(False, 4)
94 hint_hbox.pack_start(hint_icon, False, True, 0)
95 hint_hbox.pack_start(hint, True, True, 0)
96 hint_icon.set_alignment(0, 0)
97 hint_hbox.show_all()
98 return hint_hbox
100 class TrustBox(gtk.Dialog):
101 """Display a dialog box asking the user to confirm that one of the
102 keys is trusted for this domain.
104 parent = None
105 closed = None
107 def __init__(self, pending, valid_sigs, parent):
108 """@since: 0.42"""
109 assert valid_sigs
111 gtk.Dialog.__init__(self)
112 self.set_has_separator(False)
113 self.set_position(gtk.WIN_POS_CENTER)
114 self.set_transient_for(parent)
116 self.closed = tasks.Blocker(_("confirming keys with user"))
118 domain = trust.domain_from_url(pending.url)
119 assert domain
121 def destroy(box):
122 self.closed.trigger()
124 self.connect('destroy', destroy)
126 self.set_title(_('Confirm trust'))
128 vbox = gtk.VBox(False, 4)
129 vbox.set_border_width(4)
130 self.vbox.pack_start(vbox, True, True, 0)
132 notebook = gtk.Notebook()
134 if len(valid_sigs) == 1:
135 notebook.set_show_tabs(False)
137 label = left(_('Checking: %s') % pending.url)
138 label.set_padding(4, 4)
139 vbox.pack_start(label, False, True, 0)
141 currently_trusted_keys = trust.trust_db.get_keys_for_domain(domain)
142 if currently_trusted_keys:
143 keys = [gpg.load_key(fingerprint) for fingerprint in currently_trusted_keys]
144 descriptions = [_("%(key_name)s\n(fingerprint: %(key_fingerprint)s)") % {'key_name': key.name, 'key_fingerprint': pretty_fp(key.fingerprint)}
145 for key in keys]
146 else:
147 descriptions = [_('None')]
148 frame(vbox, _('Keys already approved for "%s"') % domain, '\n'.join(descriptions))
150 label = left(translation.ngettext('This key signed the feed:', 'These keys signed the feed:', len(valid_sigs)))
152 label.set_padding(4, 4)
153 vbox.pack_start(label, False, True, 0)
155 vbox.pack_start(notebook, True, True, 0)
157 self.add_button(gtk.STOCK_HELP, gtk.RESPONSE_HELP)
158 self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
159 self.add_button(gtk.STOCK_ADD, gtk.RESPONSE_OK)
160 self.set_default_response(gtk.RESPONSE_OK)
162 trust_checkbox = {} # Sig -> CheckButton
163 def ok_sensitive():
164 trust_any = False
165 for toggle in trust_checkbox.values():
166 if toggle.get_active():
167 trust_any = True
168 break
169 self.set_response_sensitive(gtk.RESPONSE_OK, trust_any)
171 first = True
172 for sig in valid_sigs:
173 if hasattr(sig, 'get_details'):
174 name = '<unknown>'
175 details = sig.get_details()
176 for item in details:
177 if item[0] == 'uid' and len(item) > 9:
178 name = item[9]
179 break
180 else:
181 name = None
182 page = gtk.VBox(False, 4)
183 page.set_border_width(8)
185 frame(page, _('Fingerprint'), pretty_fp(sig.fingerprint))
187 if name is not None:
188 frame(page, _('Claimed identity'), name)
190 frame(page, _('Unreliable hints database says'), make_hints_area(self.closed, valid_sigs[sig]))
192 already_trusted = trust.trust_db.get_trust_domains(sig.fingerprint)
193 if already_trusted:
194 frame(page, _('You already trust this key for these domains'),
195 '\n'.join(already_trusted))
197 trust_checkbox[sig] = gtk.CheckButton(_('_Trust this key'))
198 page.pack_start(trust_checkbox[sig], False, True, 0)
199 trust_checkbox[sig].connect('toggled', lambda t: ok_sensitive())
201 notebook.append_page(page, gtk.Label(name or 'Signature'))
203 if first:
204 trust_checkbox[sig].set_active(True)
205 first = False
207 ok_sensitive()
208 self.vbox.show_all()
210 if len(valid_sigs) == 1:
211 for box in trust_checkbox.values():
212 box.hide()
214 def response(box, resp):
215 if resp == gtk.RESPONSE_HELP:
216 trust_help.display()
217 return
218 if resp == gtk.RESPONSE_OK:
219 to_trust = [sig for sig in trust_checkbox if trust_checkbox[sig].get_active()]
221 if not self._confirm_unknown_keys(to_trust, valid_sigs):
222 return
224 self.trust_keys(to_trust, domain)
225 self.destroy()
226 self.connect('response', response)
228 def trust_keys(self, agreed_sigs, domain):
229 assert domain
230 try:
231 for sig in agreed_sigs:
232 trust.trust_db.trust_key(sig.fingerprint, domain)
234 trust.trust_db.notify()
235 except Exception as ex:
236 gtkutils.show_message_box(self, str(ex), gtk.MESSAGE_ERROR)
237 if not isinstance(ex, SafeException):
238 raise
240 def _confirm_unknown_keys(self, to_trust, valid_sigs):
241 """Check the key-info server's results for these keys. If we don't know any of them,
242 ask for extra confirmation from the user.
243 @param to_trust: the signatures the user wants to trust
244 @return: True to continue"""
246 def is_unknown(sig):
247 for note in valid_sigs[sig].info:
248 if note.getAttribute("vote") == "good":
249 return False
250 return True
251 unknown = [sig for sig in to_trust if is_unknown(sig)]
253 if unknown:
254 if len(unknown) == 1:
255 msg = _('WARNING: you are confirming a key which was not known to the key server. Are you sure?')
256 else:
257 msg = _('WARNING: you are confirming keys which were not known to the key server. Are you sure?')
259 box = gtk.MessageDialog(self,
260 gtk.DIALOG_DESTROY_WITH_PARENT,
261 gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL,
262 msg)
263 box.set_position(gtk.WIN_POS_CENTER)
264 response = box.run()
265 box.destroy()
266 return response == gtk.RESPONSE_OK
268 return True
270 trust_help = help_box.HelpBox(_("Trust Help"),
271 (_('Overview'), '\n' +
272 _("""When you run a program, it typically has access to all your files and can generally do \
273 anything that you're allowed to do (delete files, send emails, etc). So it's important \
274 to make sure that you don't run anything malicious.""")),
276 (_('Digital signatures'), '\n' +
277 _("""Each software author creates a 'key-pair'; a 'public key' and a 'private key'. Without going \
278 into the maths, only something encrypted with the private key will decrypt with the public key.
280 So, when a programmer releases some software, they encrypt it with their private key (which no-one \
281 else has). When you download it, the injector checks that it decrypts using their public key, thus \
282 proving that it came from them and hasn't been tampered with.""")),
284 (_('Trust'), '\n' +
285 _("""After the injector has checked that the software hasn't been modified since it was signed with \
286 the private key, you still have the following problems:
288 1. Does the public key you have really belong to the author?
289 2. Even if the software really did come from that person, do you trust them?""")),
291 (_('Key fingerprints'), '\n' +
292 _("""To confirm (1), you should compare the public key you have with the genuine one. To make this \
293 easier, the injector displays a 'fingerprint' for the key. Look in mailing list postings or some \
294 other source to check that the fingerprint is right (a different key will have a different \
295 fingerprint).
297 You're trying to protect against the situation where an attacker breaks into a web site \
298 and puts up malicious software, signed with the attacker's private key, and puts up the \
299 attacker's public key too. If you've downloaded this software before, you \
300 should be suspicious that you're being asked to confirm another key!""")),
302 (_('Reputation'), '\n' +
303 _("""In general, most problems seem to come from malicous and otherwise-unknown people \
304 replacing software with modified versions, or creating new programs intended only to \
305 cause damage. So, check your programs are signed by a key with a good reputation!""")))