53
92
return ("-----BEGIN PSEUDO-SIGNED CONTENT-----\n" + content +
54
93
"-----END PSEUDO-SIGNED CONTENT-----\n")
95
def verify(self, content, testament):
96
return SIGNATURE_VALID, None
98
def set_acceptable_keys(self, command_line_input):
99
if command_line_input is not None:
100
patterns = command_line_input.split(",")
101
self.acceptable_keys = []
102
for pattern in patterns:
103
if pattern == "unknown":
106
self.acceptable_keys.append(pattern)
108
def do_verifications(self, revisions, repository):
109
count = {SIGNATURE_VALID: 0,
110
SIGNATURE_KEY_MISSING: 0,
111
SIGNATURE_NOT_VALID: 0,
112
SIGNATURE_NOT_SIGNED: 0,
113
SIGNATURE_EXPIRED: 0}
115
all_verifiable = True
116
for rev_id in revisions:
117
verification_result, uid =\
118
repository.verify_revision(rev_id,self)
119
result.append([rev_id, verification_result, uid])
120
count[verification_result] += 1
121
if verification_result != SIGNATURE_VALID:
122
all_verifiable = False
123
return (count, result, all_verifiable)
125
def valid_commits_message(self, count):
126
return i18n.gettext(u"{0} commits with valid signatures").format(
127
count[SIGNATURE_VALID])
129
def unknown_key_message(self, count):
130
return i18n.ngettext(u"{0} commit with unknown key",
131
u"{0} commits with unknown keys",
132
count[SIGNATURE_KEY_MISSING]).format(
133
count[SIGNATURE_KEY_MISSING])
135
def commit_not_valid_message(self, count):
136
return i18n.ngettext(u"{0} commit not valid",
137
u"{0} commits not valid",
138
count[SIGNATURE_NOT_VALID]).format(
139
count[SIGNATURE_NOT_VALID])
141
def commit_not_signed_message(self, count):
142
return i18n.ngettext(u"{0} commit not signed",
143
u"{0} commits not signed",
144
count[SIGNATURE_NOT_SIGNED]).format(
145
count[SIGNATURE_NOT_SIGNED])
147
def expired_commit_message(self, count):
148
return i18n.ngettext(u"{0} commit with key now expired",
149
u"{0} commits with key now expired",
150
count[SIGNATURE_EXPIRED]).format(
151
count[SIGNATURE_EXPIRED])
57
154
def _set_gpg_tty():
58
155
tty = os.environ.get('TTY')
111
230
raise errors.SigningFailed(self._command_line())
234
def verify(self, content, testament):
235
"""Check content has a valid signature.
237
:param content: the commit signature
238
:param testament: the valid testament string for the commit
240
:return: SIGNATURE_VALID or a failed SIGNATURE_ value, key uid if valid
244
except ImportError, error:
245
raise errors.GpgmeNotInstalled(error)
247
signature = StringIO(content)
248
plain_output = StringIO()
251
result = self.context.verify(signature, None, plain_output)
252
except gpgme.GpgmeError,error:
253
raise errors.SignatureVerificationFailed(error[2])
255
# No result if input is invalid.
256
# test_verify_invalid()
258
return SIGNATURE_NOT_VALID, None
259
# User has specified a list of acceptable keys, check our result is in it.
260
# test_verify_unacceptable_key()
261
fingerprint = result[0].fpr
262
if self.acceptable_keys is not None:
263
if not fingerprint in self.acceptable_keys:
264
return SIGNATURE_KEY_MISSING, fingerprint[-8:]
265
# Check the signature actually matches the testament.
266
# test_verify_bad_testament()
267
if testament != plain_output.getvalue():
268
return SIGNATURE_NOT_VALID, None
269
# Yay gpgme set the valid bit.
270
# Can't write a test for this one as you can't set a key to be
271
# trusted using gpgme.
272
if result[0].summary & gpgme.SIGSUM_VALID:
273
key = self.context.get_key(fingerprint)
274
name = key.uids[0].name
275
email = key.uids[0].email
276
return SIGNATURE_VALID, name + " <" + email + ">"
277
# Sigsum_red indicates a problem, unfortunatly I have not been able
278
# to write any tests which actually set this.
279
if result[0].summary & gpgme.SIGSUM_RED:
280
return SIGNATURE_NOT_VALID, None
281
# GPG does not know this key.
282
# test_verify_unknown_key()
283
if result[0].summary & gpgme.SIGSUM_KEY_MISSING:
284
return SIGNATURE_KEY_MISSING, fingerprint[-8:]
285
# Summary isn't set if sig is valid but key is untrusted
286
# but if user has explicity set the key as acceptable we can validate it.
287
if result[0].summary == 0 and self.acceptable_keys is not None:
288
if fingerprint in self.acceptable_keys:
289
# test_verify_untrusted_but_accepted()
290
return SIGNATURE_VALID, None
291
# test_verify_valid_but_untrusted()
292
if result[0].summary == 0 and self.acceptable_keys is None:
293
return SIGNATURE_NOT_VALID, None
294
if result[0].summary & gpgme.SIGSUM_KEY_EXPIRED:
295
expires = self.context.get_key(result[0].fpr).subkeys[0].expires
296
if expires > result[0].timestamp:
297
# The expired key was not expired at time of signing.
298
# test_verify_expired_but_valid()
299
return SIGNATURE_EXPIRED, fingerprint[-8:]
301
# I can't work out how to create a test where the signature
302
# was expired at the time of signing.
303
return SIGNATURE_NOT_VALID, None
304
# A signature from a revoked key gets this.
305
# test_verify_revoked_signature()
306
if result[0].summary & gpgme.SIGSUM_SYS_ERROR:
307
return SIGNATURE_NOT_VALID, None
308
# Other error types such as revoked keys should (I think) be caught by
309
# SIGSUM_RED so anything else means something is buggy.
310
raise errors.SignatureVerificationFailed("Unknown GnuPG key "\
311
"verification result")
313
def set_acceptable_keys(self, command_line_input):
314
"""sets the acceptable keys for verifying with this GPGStrategy
316
:param command_line_input: comma separated list of patterns from
321
acceptable_keys_config = self._config.acceptable_keys()
323
if isinstance(acceptable_keys_config, unicode):
324
acceptable_keys_config = str(acceptable_keys_config)
325
except UnicodeEncodeError:
326
#gpg Context.keylist(pattern) does not like unicode
327
raise errors.BzrCommandError('Only ASCII permitted in option names')
329
if acceptable_keys_config is not None:
330
key_patterns = acceptable_keys_config
331
if command_line_input is not None: #command line overrides config
332
key_patterns = command_line_input
333
if key_patterns is not None:
334
patterns = key_patterns.split(",")
336
self.acceptable_keys = []
337
for pattern in patterns:
338
result = self.context.keylist(pattern)
342
self.acceptable_keys.append(key.subkeys[0].fpr)
343
trace.mutter("Added acceptable key: " + key.subkeys[0].fpr)
345
trace.note(i18n.gettext(
346
"No GnuPG key results for pattern: {}"
349
def do_verifications(self, revisions, repository,
350
process_events_callback=None):
351
"""do verifications on a set of revisions
353
:param revisions: list of revision ids to verify
354
:param repository: repository object
355
:param process_events_callback: method to call for GUI frontends that
356
want to keep their UI refreshed
358
:return: count dictionary of results of each type,
359
result list for each revision,
360
boolean True if all results are verified successfully
362
count = {SIGNATURE_VALID: 0,
363
SIGNATURE_KEY_MISSING: 0,
364
SIGNATURE_NOT_VALID: 0,
365
SIGNATURE_NOT_SIGNED: 0,
366
SIGNATURE_EXPIRED: 0}
368
all_verifiable = True
369
for rev_id in revisions:
370
verification_result, uid =\
371
repository.verify_revision(rev_id,self)
372
result.append([rev_id, verification_result, uid])
373
count[verification_result] += 1
374
if verification_result != SIGNATURE_VALID:
375
all_verifiable = False
376
if process_events_callback is not None:
377
process_events_callback()
378
return (count, result, all_verifiable)
380
def verbose_valid_message(self, result):
381
"""takes a verify result and returns list of signed commits strings"""
383
for rev_id, validity, uid in result:
384
if validity == SIGNATURE_VALID:
385
signers.setdefault(uid, 0)
388
for uid, number in signers.items():
389
result.append( i18n.ngettext(u"{0} signed {1} commit",
390
u"{0} signed {1} commits",
391
number).format(uid, number) )
395
def verbose_not_valid_message(self, result, repo):
396
"""takes a verify result and returns list of not valid commit info"""
398
for rev_id, validity, empty in result:
399
if validity == SIGNATURE_NOT_VALID:
400
revision = repo.get_revision(rev_id)
401
authors = ', '.join(revision.get_apparent_authors())
402
signers.setdefault(authors, 0)
403
signers[authors] += 1
405
for authors, number in signers.items():
406
result.append( i18n.ngettext(u"{0} commit by author {1}",
407
u"{0} commits by author {1}",
408
number).format(number, authors) )
411
def verbose_not_signed_message(self, result, repo):
412
"""takes a verify result and returns list of not signed commit info"""
414
for rev_id, validity, empty in result:
415
if validity == SIGNATURE_NOT_SIGNED:
416
revision = repo.get_revision(rev_id)
417
authors = ', '.join(revision.get_apparent_authors())
418
signers.setdefault(authors, 0)
419
signers[authors] += 1
421
for authors, number in signers.items():
422
result.append( i18n.ngettext(u"{0} commit by author {1}",
423
u"{0} commits by author {1}",
424
number).format(number, authors) )
427
def verbose_missing_key_message(self, result):
428
"""takes a verify result and returns list of missing key info"""
430
for rev_id, validity, fingerprint in result:
431
if validity == SIGNATURE_KEY_MISSING:
432
signers.setdefault(fingerprint, 0)
433
signers[fingerprint] += 1
435
for fingerprint, number in signers.items():
436
result.append( i18n.ngettext(u"Unknown key {0} signed {1} commit",
437
u"Unknown key {0} signed {1} commits",
438
number).format(fingerprint, number) )
441
def verbose_expired_key_message(self, result, repo):
442
"""takes a verify result and returns list of expired key info"""
444
fingerprint_to_authors = {}
445
for rev_id, validity, fingerprint in result:
446
if validity == SIGNATURE_EXPIRED:
447
revision = repo.get_revision(rev_id)
448
authors = ', '.join(revision.get_apparent_authors())
449
signers.setdefault(fingerprint, 0)
450
signers[fingerprint] += 1
451
fingerprint_to_authors[fingerprint] = authors
453
for fingerprint, number in signers.items():
454
result.append( i18n.ngettext(u"{0} commit by author {1} with "\
455
"key {2} now expired",
456
u"{0} commits by author {1} with key {2} now "\
458
number).format(number,
459
fingerprint_to_authors[fingerprint], fingerprint) )
462
def valid_commits_message(self, count):
463
"""returns message for number of commits"""
464
return i18n.gettext(u"{0} commits with valid signatures").format(
465
count[SIGNATURE_VALID])
467
def unknown_key_message(self, count):
468
"""returns message for number of commits"""
469
return i18n.ngettext(u"{0} commit with unknown key",
470
u"{0} commits with unknown keys",
471
count[SIGNATURE_KEY_MISSING]).format(
472
count[SIGNATURE_KEY_MISSING])
474
def commit_not_valid_message(self, count):
475
"""returns message for number of commits"""
476
return i18n.ngettext(u"{0} commit not valid",
477
u"{0} commits not valid",
478
count[SIGNATURE_NOT_VALID]).format(
479
count[SIGNATURE_NOT_VALID])
481
def commit_not_signed_message(self, count):
482
"""returns message for number of commits"""
483
return i18n.ngettext(u"{0} commit not signed",
484
u"{0} commits not signed",
485
count[SIGNATURE_NOT_SIGNED]).format(
486
count[SIGNATURE_NOT_SIGNED])
488
def expired_commit_message(self, count):
489
"""returns message for number of commits"""
490
return i18n.ngettext(u"{0} commit with key now expired",
491
u"{0} commits with key now expired",
492
count[SIGNATURE_EXPIRED]).format(
493
count[SIGNATURE_EXPIRED])