119
116
return (count, result, all_verifiable)
121
118
def valid_commits_message(self, count):
122
return i18n.gettext("{0} commits with valid signatures").format(
123
count[SIGNATURE_VALID])
119
return gettext(u"{0} commits with valid signatures").format(
120
count[SIGNATURE_VALID])
125
122
def unknown_key_message(self, count):
126
return i18n.ngettext("{0} commit with unknown key",
127
"{0} commits with unknown keys",
123
return ngettext(u"{0} commit with unknown key",
124
u"{0} commits with unknown keys",
128
125
count[SIGNATURE_KEY_MISSING]).format(
129
126
count[SIGNATURE_KEY_MISSING])
131
128
def commit_not_valid_message(self, count):
132
return i18n.ngettext("{0} commit not valid",
133
"{0} commits not valid",
129
return ngettext(u"{0} commit not valid",
130
u"{0} commits not valid",
134
131
count[SIGNATURE_NOT_VALID]).format(
135
132
count[SIGNATURE_NOT_VALID])
137
134
def commit_not_signed_message(self, count):
138
return i18n.ngettext("{0} commit not signed",
139
"{0} commits not signed",
135
return ngettext(u"{0} commit not signed",
136
u"{0} commits not signed",
140
137
count[SIGNATURE_NOT_SIGNED]).format(
141
138
count[SIGNATURE_NOT_SIGNED])
140
def expired_commit_message(self, count):
141
return ngettext(u"{0} commit with key now expired",
142
u"{0} commits with key now expired",
143
count[SIGNATURE_EXPIRED]).format(
144
count[SIGNATURE_EXPIRED])
144
147
def _set_gpg_tty():
145
148
tty = os.environ.get('TTY')
170
186
def _command_line(self):
171
return [self._config.gpg_signing_command(), '--clearsign']
173
def __init__(self, config):
174
self._config = config
177
self.context = gpgme.Context()
178
except ImportError, error:
179
pass # can't use verify()
187
key = self._config_stack.get('gpg_signing_key')
188
if key is None or key == 'default':
189
# 'default' or not setting gpg_signing_key at all means we should
190
# use the user email address
191
key = config.extract_email_address(self._config_stack.get('email'))
192
return [self._config_stack.get('gpg_signing_command'), '--clearsign',
181
195
def sign(self, content):
182
196
if isinstance(content, unicode):
230
244
signature = StringIO(content)
231
245
plain_output = StringIO()
234
247
result = self.context.verify(signature, None, plain_output)
235
248
except gpgme.GpgmeError,error:
236
249
raise errors.SignatureVerificationFailed(error[2])
251
# No result if input is invalid.
252
# test_verify_invalid()
238
253
if len(result) == 0:
239
254
return SIGNATURE_NOT_VALID, None
255
# User has specified a list of acceptable keys, check our result is in
256
# it. test_verify_unacceptable_key()
240
257
fingerprint = result[0].fpr
241
258
if self.acceptable_keys is not None:
242
259
if not fingerprint in self.acceptable_keys:
243
260
return SIGNATURE_KEY_MISSING, fingerprint[-8:]
261
# Check the signature actually matches the testament.
262
# test_verify_bad_testament()
244
263
if testament != plain_output.getvalue():
245
264
return SIGNATURE_NOT_VALID, None
265
# Yay gpgme set the valid bit.
266
# Can't write a test for this one as you can't set a key to be
267
# trusted using gpgme.
246
268
if result[0].summary & gpgme.SIGSUM_VALID:
247
269
key = self.context.get_key(fingerprint)
248
270
name = key.uids[0].name
249
271
email = key.uids[0].email
250
272
return SIGNATURE_VALID, name + " <" + email + ">"
273
# Sigsum_red indicates a problem, unfortunatly I have not been able
274
# to write any tests which actually set this.
251
275
if result[0].summary & gpgme.SIGSUM_RED:
252
276
return SIGNATURE_NOT_VALID, None
277
# GPG does not know this key.
278
# test_verify_unknown_key()
253
279
if result[0].summary & gpgme.SIGSUM_KEY_MISSING:
254
280
return SIGNATURE_KEY_MISSING, fingerprint[-8:]
255
#summary isn't set if sig is valid but key is untrusted
281
# Summary isn't set if sig is valid but key is untrusted but if user
282
# has explicity set the key as acceptable we can validate it.
256
283
if result[0].summary == 0 and self.acceptable_keys is not None:
257
284
if fingerprint in self.acceptable_keys:
285
# test_verify_untrusted_but_accepted()
258
286
return SIGNATURE_VALID, None
260
return SIGNATURE_KEY_MISSING, None
287
# test_verify_valid_but_untrusted()
288
if result[0].summary == 0 and self.acceptable_keys is None:
289
return SIGNATURE_NOT_VALID, None
290
if result[0].summary & gpgme.SIGSUM_KEY_EXPIRED:
291
expires = self.context.get_key(result[0].fpr).subkeys[0].expires
292
if expires > result[0].timestamp:
293
# The expired key was not expired at time of signing.
294
# test_verify_expired_but_valid()
295
return SIGNATURE_EXPIRED, fingerprint[-8:]
297
# I can't work out how to create a test where the signature
298
# was expired at the time of signing.
299
return SIGNATURE_NOT_VALID, None
300
# A signature from a revoked key gets this.
301
# test_verify_revoked_signature()
302
if result[0].summary & gpgme.SIGSUM_SYS_ERROR:
303
return SIGNATURE_NOT_VALID, None
304
# Other error types such as revoked keys should (I think) be caught by
305
# SIGSUM_RED so anything else means something is buggy.
261
306
raise errors.SignatureVerificationFailed("Unknown GnuPG key "\
262
307
"verification result")
264
309
def set_acceptable_keys(self, command_line_input):
265
"""sets the acceptable keys for verifying with this GPGStrategy
310
"""Set the acceptable keys for verifying with this GPGStrategy.
267
312
:param command_line_input: comma separated list of patterns from
271
316
key_patterns = None
272
acceptable_keys_config = self._config.acceptable_keys()
317
acceptable_keys_config = self._config_stack.get('acceptable_keys')
274
319
if isinstance(acceptable_keys_config, unicode):
275
320
acceptable_keys_config = str(acceptable_keys_config)
276
321
except UnicodeEncodeError:
277
raise errors.BzrCommandError('Only ASCII permitted in option names')
322
# gpg Context.keylist(pattern) does not like unicode
323
raise errors.BzrCommandError(
324
gettext('Only ASCII permitted in option names'))
279
326
if acceptable_keys_config is not None:
280
327
key_patterns = acceptable_keys_config
281
if command_line_input is not None: #command line overrides config
328
if command_line_input is not None: # command line overrides config
282
329
key_patterns = command_line_input
283
330
if key_patterns is not None:
284
331
patterns = key_patterns.split(",")
292
339
self.acceptable_keys.append(key.subkeys[0].fpr)
293
340
trace.mutter("Added acceptable key: " + key.subkeys[0].fpr)
294
341
if not found_key:
295
trace.note(i18n.gettext(
296
"No GnuPG key results for pattern: {}"
343
"No GnuPG key results for pattern: {0}"
297
344
).format(pattern))
299
def do_verifications(self, revisions, repository):
346
def do_verifications(self, revisions, repository,
347
process_events_callback=None):
300
348
"""do verifications on a set of revisions
302
350
:param revisions: list of revision ids to verify
303
351
:param repository: repository object
352
:param process_events_callback: method to call for GUI frontends that
353
want to keep their UI refreshed
305
355
:return: count dictionary of results of each type,
306
356
result list for each revision,
309
359
count = {SIGNATURE_VALID: 0,
310
360
SIGNATURE_KEY_MISSING: 0,
311
361
SIGNATURE_NOT_VALID: 0,
312
SIGNATURE_NOT_SIGNED: 0}
362
SIGNATURE_NOT_SIGNED: 0,
363
SIGNATURE_EXPIRED: 0}
314
365
all_verifiable = True
315
366
for rev_id in revisions:
316
367
verification_result, uid =\
317
repository.verify_revision(rev_id,self)
368
repository.verify_revision_signature(rev_id, self)
318
369
result.append([rev_id, verification_result, uid])
319
370
count[verification_result] += 1
320
371
if verification_result != SIGNATURE_VALID:
321
372
all_verifiable = False
373
if process_events_callback is not None:
374
process_events_callback()
322
375
return (count, result, all_verifiable)
324
377
def verbose_valid_message(self, result):
377
430
signers[fingerprint] += 1
379
432
for fingerprint, number in signers.items():
380
result.append( i18n.ngettext("Unknown key {0} signed {1} commit",
381
"Unknown key {0} signed {1} commits",
433
result.append( ngettext(u"Unknown key {0} signed {1} commit",
434
u"Unknown key {0} signed {1} commits",
382
435
number).format(fingerprint, number) )
438
def verbose_expired_key_message(self, result, repo):
439
"""takes a verify result and returns list of expired key info"""
441
fingerprint_to_authors = {}
442
for rev_id, validity, fingerprint in result:
443
if validity == SIGNATURE_EXPIRED:
444
revision = repo.get_revision(rev_id)
445
authors = ', '.join(revision.get_apparent_authors())
446
signers.setdefault(fingerprint, 0)
447
signers[fingerprint] += 1
448
fingerprint_to_authors[fingerprint] = authors
450
for fingerprint, number in signers.items():
452
ngettext(u"{0} commit by author {1} with key {2} now expired",
453
u"{0} commits by author {1} with key {2} now expired",
455
number, fingerprint_to_authors[fingerprint], fingerprint) )
385
458
def valid_commits_message(self, count):
386
459
"""returns message for number of commits"""
387
return i18n.gettext("{0} commits with valid signatures").format(
460
return gettext(u"{0} commits with valid signatures").format(
388
461
count[SIGNATURE_VALID])
390
463
def unknown_key_message(self, count):
391
464
"""returns message for number of commits"""
392
return i18n.ngettext("{0} commit with unknown key",
393
"{0} commits with unknown keys",
394
count[SIGNATURE_KEY_MISSING]).format(
465
return ngettext(u"{0} commit with unknown key",
466
u"{0} commits with unknown keys",
467
count[SIGNATURE_KEY_MISSING]).format(
395
468
count[SIGNATURE_KEY_MISSING])
397
470
def commit_not_valid_message(self, count):
398
471
"""returns message for number of commits"""
399
return i18n.ngettext("{0} commit not valid",
400
"{0} commits not valid",
401
count[SIGNATURE_NOT_VALID]).format(
472
return ngettext(u"{0} commit not valid",
473
u"{0} commits not valid",
474
count[SIGNATURE_NOT_VALID]).format(
402
475
count[SIGNATURE_NOT_VALID])
404
477
def commit_not_signed_message(self, count):
405
478
"""returns message for number of commits"""
406
return i18n.ngettext("{0} commit not signed",
407
"{0} commits not signed",
408
count[SIGNATURE_NOT_SIGNED]).format(
479
return ngettext(u"{0} commit not signed",
480
u"{0} commits not signed",
481
count[SIGNATURE_NOT_SIGNED]).format(
409
482
count[SIGNATURE_NOT_SIGNED])
484
def expired_commit_message(self, count):
485
"""returns message for number of commits"""
486
return ngettext(u"{0} commit with key now expired",
487
u"{0} commits with key now expired",
488
count[SIGNATURE_EXPIRED]).format(
489
count[SIGNATURE_EXPIRED])