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
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 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
)
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
)}
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
165 for toggle
in trust_checkbox
.values():
166 if toggle
.get_active():
169 self
.set_response_sensitive(gtk
.RESPONSE_OK
, trust_any
)
172 for sig
in valid_sigs
:
173 if hasattr(sig
, 'get_details'):
175 details
= sig
.get_details()
177 if item
[0] == 'uid' and len(item
) > 9:
182 page
= gtk
.VBox(False, 4)
183 page
.set_border_width(8)
185 frame(page
, _('Fingerprint'), pretty_fp(sig
.fingerprint
))
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
)
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'))
204 trust_checkbox
[sig
].set_active(True)
210 if len(valid_sigs
) == 1:
211 for box
in trust_checkbox
.values():
214 def response(box
, resp
):
215 if resp
== gtk
.RESPONSE_HELP
:
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
):
224 self
.trust_keys(to_trust
, domain
)
226 self
.connect('response', response
)
228 def trust_keys(self
, agreed_sigs
, domain
):
231 for sig
in agreed_sigs
:
232 trust
.trust_db
.trust_key(sig
.fingerprint
, domain
)
234 trust
.trust_db
.notify()
235 except Exception, ex
:
236 gtkutils
.show_message_box(self
, str(ex
), gtk
.MESSAGE_ERROR
)
237 if not isinstance(ex
, SafeException
):
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"""
247 for note
in valid_sigs
[sig
].info
:
248 if note
.getAttribute("vote") == "good":
251 unknown
= [sig
for sig
in to_trust
if is_unknown(sig
)]
254 if len(unknown
) == 1:
255 msg
= _('WARNING: you are confirming a key which was not known to the key server. Are you sure?')
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
,
263 box
.set_position(gtk
.WIN_POS_CENTER
)
266 return response
== gtk
.RESPONSE_OK
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.""")),
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 \
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!""")))