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