53
82
return ("-----BEGIN PSEUDO-SIGNED CONTENT-----\n" + content +
54
83
"-----END PSEUDO-SIGNED CONTENT-----\n")
85
def verify(self, content, testament):
86
return SIGNATURE_VALID, None
88
def set_acceptable_keys(self, command_line_input):
89
if command_line_input is not None:
90
patterns = command_line_input.split(",")
91
self.acceptable_keys = []
92
for pattern in patterns:
93
if pattern == "unknown":
96
self.acceptable_keys.append(pattern)
98
def do_verifications(self, revisions, repository):
99
count = {SIGNATURE_VALID: 0,
100
SIGNATURE_KEY_MISSING: 0,
101
SIGNATURE_NOT_VALID: 0,
102
SIGNATURE_NOT_SIGNED: 0,
103
SIGNATURE_EXPIRED: 0}
105
all_verifiable = True
106
for rev_id in revisions:
107
verification_result, uid =\
108
repository.verify_revision_signature(rev_id,self)
109
result.append([rev_id, verification_result, uid])
110
count[verification_result] += 1
111
if verification_result != SIGNATURE_VALID:
112
all_verifiable = False
113
return (count, result, all_verifiable)
115
def valid_commits_message(self, count):
116
return gettext(u"{0} commits with valid signatures").format(
117
count[SIGNATURE_VALID])
119
def unknown_key_message(self, count):
120
return ngettext(u"{0} commit with unknown key",
121
u"{0} commits with unknown keys",
122
count[SIGNATURE_KEY_MISSING]).format(
123
count[SIGNATURE_KEY_MISSING])
125
def commit_not_valid_message(self, count):
126
return ngettext(u"{0} commit not valid",
127
u"{0} commits not valid",
128
count[SIGNATURE_NOT_VALID]).format(
129
count[SIGNATURE_NOT_VALID])
131
def commit_not_signed_message(self, count):
132
return ngettext(u"{0} commit not signed",
133
u"{0} commits not signed",
134
count[SIGNATURE_NOT_SIGNED]).format(
135
count[SIGNATURE_NOT_SIGNED])
137
def expired_commit_message(self, count):
138
return ngettext(u"{0} commit with key now expired",
139
u"{0} commits with key now expired",
140
count[SIGNATURE_EXPIRED]).format(
141
count[SIGNATURE_EXPIRED])
57
144
def _set_gpg_tty():
58
145
tty = os.environ.get('TTY')
111
219
raise errors.SigningFailed(self._command_line())
223
def verify(self, content, testament):
224
"""Check content has a valid signature.
226
:param content: the commit signature
227
:param testament: the valid testament string for the commit
229
:return: SIGNATURE_VALID or a failed SIGNATURE_ value, key uid if valid
233
except ImportError, error:
234
raise errors.GpgmeNotInstalled(error)
236
signature = StringIO(content)
237
plain_output = StringIO()
240
result = self.context.verify(signature, None, plain_output)
241
except gpgme.GpgmeError,error:
242
raise errors.SignatureVerificationFailed(error[2])
244
# No result if input is invalid.
245
# test_verify_invalid()
247
return SIGNATURE_NOT_VALID, None
248
# User has specified a list of acceptable keys, check our result is in it.
249
# test_verify_unacceptable_key()
250
fingerprint = result[0].fpr
251
if self.acceptable_keys is not None:
252
if not fingerprint in self.acceptable_keys:
253
return SIGNATURE_KEY_MISSING, fingerprint[-8:]
254
# Check the signature actually matches the testament.
255
# test_verify_bad_testament()
256
if testament != plain_output.getvalue():
257
return SIGNATURE_NOT_VALID, None
258
# Yay gpgme set the valid bit.
259
# Can't write a test for this one as you can't set a key to be
260
# trusted using gpgme.
261
if result[0].summary & gpgme.SIGSUM_VALID:
262
key = self.context.get_key(fingerprint)
263
name = key.uids[0].name
264
email = key.uids[0].email
265
return SIGNATURE_VALID, name + " <" + email + ">"
266
# Sigsum_red indicates a problem, unfortunatly I have not been able
267
# to write any tests which actually set this.
268
if result[0].summary & gpgme.SIGSUM_RED:
269
return SIGNATURE_NOT_VALID, None
270
# GPG does not know this key.
271
# test_verify_unknown_key()
272
if result[0].summary & gpgme.SIGSUM_KEY_MISSING:
273
return SIGNATURE_KEY_MISSING, fingerprint[-8:]
274
# Summary isn't set if sig is valid but key is untrusted
275
# but if user has explicity set the key as acceptable we can validate it.
276
if result[0].summary == 0 and self.acceptable_keys is not None:
277
if fingerprint in self.acceptable_keys:
278
# test_verify_untrusted_but_accepted()
279
return SIGNATURE_VALID, None
280
# test_verify_valid_but_untrusted()
281
if result[0].summary == 0 and self.acceptable_keys is None:
282
return SIGNATURE_NOT_VALID, None
283
if result[0].summary & gpgme.SIGSUM_KEY_EXPIRED:
284
expires = self.context.get_key(result[0].fpr).subkeys[0].expires
285
if expires > result[0].timestamp:
286
# The expired key was not expired at time of signing.
287
# test_verify_expired_but_valid()
288
return SIGNATURE_EXPIRED, fingerprint[-8:]
290
# I can't work out how to create a test where the signature
291
# was expired at the time of signing.
292
return SIGNATURE_NOT_VALID, None
293
# A signature from a revoked key gets this.
294
# test_verify_revoked_signature()
295
if result[0].summary & gpgme.SIGSUM_SYS_ERROR:
296
return SIGNATURE_NOT_VALID, None
297
# Other error types such as revoked keys should (I think) be caught by
298
# SIGSUM_RED so anything else means something is buggy.
299
raise errors.SignatureVerificationFailed("Unknown GnuPG key "\
300
"verification result")
302
def set_acceptable_keys(self, command_line_input):
303
"""Set the acceptable keys for verifying with this GPGStrategy.
305
:param command_line_input: comma separated list of patterns from
310
acceptable_keys_config = self._config_stack.get('acceptable_keys')
312
if isinstance(acceptable_keys_config, unicode):
313
acceptable_keys_config = str(acceptable_keys_config)
314
except UnicodeEncodeError:
315
#gpg Context.keylist(pattern) does not like unicode
316
raise errors.BzrCommandError(gettext('Only ASCII permitted in option names'))
318
if acceptable_keys_config is not None:
319
key_patterns = acceptable_keys_config
320
if command_line_input is not None: #command line overrides config
321
key_patterns = command_line_input
322
if key_patterns is not None:
323
patterns = key_patterns.split(",")
325
self.acceptable_keys = []
326
for pattern in patterns:
327
result = self.context.keylist(pattern)
331
self.acceptable_keys.append(key.subkeys[0].fpr)
332
trace.mutter("Added acceptable key: " + key.subkeys[0].fpr)
335
"No GnuPG key results for pattern: {0}"
338
def do_verifications(self, revisions, repository,
339
process_events_callback=None):
340
"""do verifications on a set of revisions
342
:param revisions: list of revision ids to verify
343
:param repository: repository object
344
:param process_events_callback: method to call for GUI frontends that
345
want to keep their UI refreshed
347
:return: count dictionary of results of each type,
348
result list for each revision,
349
boolean True if all results are verified successfully
351
count = {SIGNATURE_VALID: 0,
352
SIGNATURE_KEY_MISSING: 0,
353
SIGNATURE_NOT_VALID: 0,
354
SIGNATURE_NOT_SIGNED: 0,
355
SIGNATURE_EXPIRED: 0}
357
all_verifiable = True
358
for rev_id in revisions:
359
verification_result, uid =\
360
repository.verify_revision_signature(rev_id, self)
361
result.append([rev_id, verification_result, uid])
362
count[verification_result] += 1
363
if verification_result != SIGNATURE_VALID:
364
all_verifiable = False
365
if process_events_callback is not None:
366
process_events_callback()
367
return (count, result, all_verifiable)
369
def verbose_valid_message(self, result):
370
"""takes a verify result and returns list of signed commits strings"""
372
for rev_id, validity, uid in result:
373
if validity == SIGNATURE_VALID:
374
signers.setdefault(uid, 0)
377
for uid, number in signers.items():
378
result.append( ngettext(u"{0} signed {1} commit",
379
u"{0} signed {1} commits",
380
number).format(uid, number) )
384
def verbose_not_valid_message(self, result, repo):
385
"""takes a verify result and returns list of not valid commit info"""
387
for rev_id, validity, empty in result:
388
if validity == SIGNATURE_NOT_VALID:
389
revision = repo.get_revision(rev_id)
390
authors = ', '.join(revision.get_apparent_authors())
391
signers.setdefault(authors, 0)
392
signers[authors] += 1
394
for authors, number in signers.items():
395
result.append( ngettext(u"{0} commit by author {1}",
396
u"{0} commits by author {1}",
397
number).format(number, authors) )
400
def verbose_not_signed_message(self, result, repo):
401
"""takes a verify result and returns list of not signed commit info"""
403
for rev_id, validity, empty in result:
404
if validity == SIGNATURE_NOT_SIGNED:
405
revision = repo.get_revision(rev_id)
406
authors = ', '.join(revision.get_apparent_authors())
407
signers.setdefault(authors, 0)
408
signers[authors] += 1
410
for authors, number in signers.items():
411
result.append( ngettext(u"{0} commit by author {1}",
412
u"{0} commits by author {1}",
413
number).format(number, authors) )
416
def verbose_missing_key_message(self, result):
417
"""takes a verify result and returns list of missing key info"""
419
for rev_id, validity, fingerprint in result:
420
if validity == SIGNATURE_KEY_MISSING:
421
signers.setdefault(fingerprint, 0)
422
signers[fingerprint] += 1
424
for fingerprint, number in signers.items():
425
result.append( ngettext(u"Unknown key {0} signed {1} commit",
426
u"Unknown key {0} signed {1} commits",
427
number).format(fingerprint, number) )
430
def verbose_expired_key_message(self, result, repo):
431
"""takes a verify result and returns list of expired key info"""
433
fingerprint_to_authors = {}
434
for rev_id, validity, fingerprint in result:
435
if validity == SIGNATURE_EXPIRED:
436
revision = repo.get_revision(rev_id)
437
authors = ', '.join(revision.get_apparent_authors())
438
signers.setdefault(fingerprint, 0)
439
signers[fingerprint] += 1
440
fingerprint_to_authors[fingerprint] = authors
442
for fingerprint, number in signers.items():
443
result.append(ngettext(u"{0} commit by author {1} with "\
444
"key {2} now expired",
445
u"{0} commits by author {1} with key {2} now "\
447
number).format(number,
448
fingerprint_to_authors[fingerprint], fingerprint) )
451
def valid_commits_message(self, count):
452
"""returns message for number of commits"""
453
return gettext(u"{0} commits with valid signatures").format(
454
count[SIGNATURE_VALID])
456
def unknown_key_message(self, count):
457
"""returns message for number of commits"""
458
return ngettext(u"{0} commit with unknown key",
459
u"{0} commits with unknown keys",
460
count[SIGNATURE_KEY_MISSING]).format(
461
count[SIGNATURE_KEY_MISSING])
463
def commit_not_valid_message(self, count):
464
"""returns message for number of commits"""
465
return ngettext(u"{0} commit not valid",
466
u"{0} commits not valid",
467
count[SIGNATURE_NOT_VALID]).format(
468
count[SIGNATURE_NOT_VALID])
470
def commit_not_signed_message(self, count):
471
"""returns message for number of commits"""
472
return ngettext(u"{0} commit not signed",
473
u"{0} commits not signed",
474
count[SIGNATURE_NOT_SIGNED]).format(
475
count[SIGNATURE_NOT_SIGNED])
477
def expired_commit_message(self, count):
478
"""returns message for number of commits"""
479
return ngettext(u"{0} commit with key now expired",
480
u"{0} commits with key now expired",
481
count[SIGNATURE_EXPIRED]).format(
482
count[SIGNATURE_EXPIRED])