2 A dialog box for confirming GPG keys.
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
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):
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)
27 if hasattr(content
, 'set_padding'):
28 content
.set_padding(8, 4)
30 content
.set_border_width(8)
31 page
.pack_start(frame
, expand
, True, 0)
35 for x
in range(4, len(fp
), 4):
36 s
+= ' ' + fp
[x
:x
+ 4]
40 label
= gtk
.Label(text
)
41 label
.set_alignment(0, 0.5)
42 label
.set_selectable(True)
45 def make_hints_area(closed
, key_info_fetcher
):
48 for node
in parent
.childNodes
:
49 if node
.nodeType
== node
.TEXT_NODE
:
50 text
= text
+ node
.data
53 hints
= gtk
.VBox(False, 4)
57 infos
= set(key_info_fetcher
.info
) - shown
59 hints
.add(make_hint(info
.getAttribute("vote"), text(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
)
70 def update_when_ready():
71 while key_info_fetcher
.blocker
:
72 yield key_info_fetcher
.blocker
, closed
74 # The dialog box was closed. Stop updating.
85 def make_hint(vote
, hint_text
):
86 hint_icon
= gtk
.Image()
88 hint_icon
.set_from_stock(gtk
.STOCK_YES
, gtk
.ICON_SIZE_BUTTON
)
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)
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.
107 def __init__(self
, pending
, valid_sigs
, parent
):
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
)
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
)}
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
166 for toggle
in trust_checkbox
.values():
167 if toggle
.get_active():
170 self
.set_response_sensitive(gtk
.RESPONSE_OK
, trust_any
)
173 for sig
in valid_sigs
:
174 if hasattr(sig
, 'get_details'):
176 details
= sig
.get_details()
178 if item
[0] == 'uid' and len(item
) > 9:
183 page
= gtk
.VBox(False, 4)
184 page
.set_border_width(8)
186 frame(page
, _('Fingerprint'), pretty_fp(sig
.fingerprint
))
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
)
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'))
205 trust_checkbox
[sig
].set_active(True)
211 if len(valid_sigs
) == 1:
212 for box
in trust_checkbox
.values():
215 def response(box
, resp
):
216 if resp
== gtk
.RESPONSE_HELP
:
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
):
225 self
.trust_keys(to_trust
, domain
)
227 self
.connect('response', response
)
229 def trust_keys(self
, agreed_sigs
, domain
):
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
):
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"""
248 for note
in valid_sigs
[sig
].info
:
249 if note
.getAttribute("vote") == "good":
252 unknown
= [sig
for sig
in to_trust
if is_unknown(sig
)]
255 if len(unknown
) == 1:
256 msg
= _('WARNING: you are confirming a key which was not known to the key server. Are you sure?')
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
,
264 box
.set_position(gtk
.WIN_POS_CENTER
)
267 return response
== gtk
.RESPONSE_OK
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.""")),
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 \
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!""")))