Python 3: the GUI now works well enough to run programs
[zeroinstall/solver.git] / zeroinstall / gtkui / trust_box.py
blob8a931aec880d23668ef27af297052685142fa79b
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, unicode
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 if hasattr(self, 'set_has_separator'):
113 self.set_has_separator(False)
114 self.set_position(gtk.WIN_POS_CENTER)
115 self.set_transient_for(parent)
117 self.closed = tasks.Blocker(_("confirming keys with user"))
119 domain = trust.domain_from_url(pending.url)
120 assert domain
122 def destroy(box):
123 self.closed.trigger()
125 self.connect('destroy', destroy)
127 self.set_title(_('Confirm trust'))
129 vbox = gtk.VBox(False, 4)
130 vbox.set_border_width(4)
131 self.vbox.pack_start(vbox, True, True, 0)
133 notebook = gtk.Notebook()
135 if len(valid_sigs) == 1:
136 notebook.set_show_tabs(False)
138 label = left(_('Checking: %s') % pending.url)
139 label.set_padding(4, 4)
140 vbox.pack_start(label, False, True, 0)
142 currently_trusted_keys = trust.trust_db.get_keys_for_domain(domain)
143 if currently_trusted_keys:
144 keys = [gpg.load_key(fingerprint) for fingerprint in currently_trusted_keys]
145 descriptions = [_("%(key_name)s\n(fingerprint: %(key_fingerprint)s)") % {'key_name': key.name, 'key_fingerprint': pretty_fp(key.fingerprint)}
146 for key in keys]
147 else:
148 descriptions = [_('None')]
149 frame(vbox, _('Keys already approved for "%s"') % domain, '\n'.join(descriptions))
151 label = left(translation.ngettext('This key signed the feed:', 'These keys signed the feed:', len(valid_sigs)))
153 label.set_padding(4, 4)
154 vbox.pack_start(label, False, True, 0)
156 vbox.pack_start(notebook, True, True, 0)
158 self.add_button(gtk.STOCK_HELP, gtk.RESPONSE_HELP)
159 self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
160 self.add_button(gtk.STOCK_ADD, gtk.RESPONSE_OK)
161 self.set_default_response(gtk.RESPONSE_OK)
163 trust_checkbox = {} # Sig -> CheckButton
164 def ok_sensitive():
165 trust_any = False
166 for toggle in trust_checkbox.values():
167 if toggle.get_active():
168 trust_any = True
169 break
170 self.set_response_sensitive(gtk.RESPONSE_OK, trust_any)
172 first = True
173 for sig in valid_sigs:
174 if hasattr(sig, 'get_details'):
175 name = '<unknown>'
176 details = sig.get_details()
177 for item in details:
178 if item[0] == 'uid' and len(item) > 9:
179 name = item[9]
180 break
181 else:
182 name = None
183 page = gtk.VBox(False, 4)
184 page.set_border_width(8)
186 frame(page, _('Fingerprint'), pretty_fp(sig.fingerprint))
188 if name is not None:
189 frame(page, _('Claimed identity'), name)
191 frame(page, _('Unreliable hints database says'), make_hints_area(self.closed, valid_sigs[sig]))
193 already_trusted = trust.trust_db.get_trust_domains(sig.fingerprint)
194 if already_trusted:
195 frame(page, _('You already trust this key for these domains'),
196 '\n'.join(already_trusted))
198 trust_checkbox[sig] = gtk.CheckButton(_('_Trust this key'))
199 page.pack_start(trust_checkbox[sig], False, True, 0)
200 trust_checkbox[sig].connect('toggled', lambda t: ok_sensitive())
202 notebook.append_page(page, gtk.Label(name or 'Signature'))
204 if first:
205 trust_checkbox[sig].set_active(True)
206 first = False
208 ok_sensitive()
209 self.vbox.show_all()
211 if len(valid_sigs) == 1:
212 for box in trust_checkbox.values():
213 box.hide()
215 def response(box, resp):
216 if resp == gtk.RESPONSE_HELP:
217 trust_help.display()
218 return
219 if resp == gtk.RESPONSE_OK:
220 to_trust = [sig for sig in trust_checkbox if trust_checkbox[sig].get_active()]
222 if not self._confirm_unknown_keys(to_trust, valid_sigs):
223 return
225 self.trust_keys(to_trust, domain)
226 self.destroy()
227 self.connect('response', response)
229 def trust_keys(self, agreed_sigs, domain):
230 assert domain
231 try:
232 for sig in agreed_sigs:
233 trust.trust_db.trust_key(sig.fingerprint, domain)
235 trust.trust_db.notify()
236 except Exception as ex:
237 gtkutils.show_message_box(self, str(ex), gtk.MESSAGE_ERROR)
238 if not isinstance(ex, SafeException):
239 raise
241 def _confirm_unknown_keys(self, to_trust, valid_sigs):
242 """Check the key-info server's results for these keys. If we don't know any of them,
243 ask for extra confirmation from the user.
244 @param to_trust: the signatures the user wants to trust
245 @return: True to continue"""
247 def is_unknown(sig):
248 for note in valid_sigs[sig].info:
249 if note.getAttribute("vote") == "good":
250 return False
251 return True
252 unknown = [sig for sig in to_trust if is_unknown(sig)]
254 if unknown:
255 if len(unknown) == 1:
256 msg = _('WARNING: you are confirming a key which was not known to the key server. Are you sure?')
257 else:
258 msg = _('WARNING: you are confirming keys which were not known to the key server. Are you sure?')
260 box = gtk.MessageDialog(self,
261 gtk.DIALOG_DESTROY_WITH_PARENT,
262 gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL,
263 msg)
264 box.set_position(gtk.WIN_POS_CENTER)
265 response = box.run()
266 box.destroy()
267 return response == gtk.RESPONSE_OK
269 return True
271 trust_help = help_box.HelpBox(_("Trust Help"),
272 (_('Overview'), '\n' +
273 _("""When you run a program, it typically has access to all your files and can generally do \
274 anything that you're allowed to do (delete files, send emails, etc). So it's important \
275 to make sure that you don't run anything malicious.""")),
277 (_('Digital signatures'), '\n' +
278 _("""Each software author creates a 'key-pair'; a 'public key' and a 'private key'. Without going \
279 into the maths, only something encrypted with the private key will decrypt with the public key.
281 So, when a programmer releases some software, they encrypt it with their private key (which no-one \
282 else has). When you download it, the injector checks that it decrypts using their public key, thus \
283 proving that it came from them and hasn't been tampered with.""")),
285 (_('Trust'), '\n' +
286 _("""After the injector has checked that the software hasn't been modified since it was signed with \
287 the private key, you still have the following problems:
289 1. Does the public key you have really belong to the author?
290 2. Even if the software really did come from that person, do you trust them?""")),
292 (_('Key fingerprints'), '\n' +
293 _("""To confirm (1), you should compare the public key you have with the genuine one. To make this \
294 easier, the injector displays a 'fingerprint' for the key. Look in mailing list postings or some \
295 other source to check that the fingerprint is right (a different key will have a different \
296 fingerprint).
298 You're trying to protect against the situation where an attacker breaks into a web site \
299 and puts up malicious software, signed with the attacker's private key, and puts up the \
300 attacker's public key too. If you've downloaded this software before, you \
301 should be suspicious that you're being asked to confirm another key!""")),
303 (_('Reputation'), '\n' +
304 _("""In general, most problems seem to come from malicous and otherwise-unknown people \
305 replacing software with modified versions, or creating new programs intended only to \
306 cause damage. So, check your programs are signed by a key with a good reputation!""")))