53
83
return ("-----BEGIN PSEUDO-SIGNED CONTENT-----\n" + content +
54
84
"-----END PSEUDO-SIGNED CONTENT-----\n")
86
def verify(self, content, testament):
87
return SIGNATURE_VALID, None
89
def set_acceptable_keys(self, command_line_input):
90
if command_line_input is not None:
91
patterns = command_line_input.split(",")
92
self.acceptable_keys = []
93
for pattern in patterns:
94
if pattern == "unknown":
97
self.acceptable_keys.append(pattern)
99
def do_verifications(self, revisions, repository):
100
count = {SIGNATURE_VALID: 0,
101
SIGNATURE_KEY_MISSING: 0,
102
SIGNATURE_NOT_VALID: 0,
103
SIGNATURE_NOT_SIGNED: 0,
104
SIGNATURE_EXPIRED: 0}
106
all_verifiable = True
107
for rev_id in revisions:
108
verification_result, uid =\
109
repository.verify_revision_signature(rev_id,self)
110
result.append([rev_id, verification_result, uid])
111
count[verification_result] += 1
112
if verification_result != SIGNATURE_VALID:
113
all_verifiable = False
114
return (count, result, all_verifiable)
116
def valid_commits_message(self, count):
117
return gettext(u"{0} commits with valid signatures").format(
118
count[SIGNATURE_VALID])
120
def unknown_key_message(self, count):
121
return ngettext(u"{0} commit with unknown key",
122
u"{0} commits with unknown keys",
123
count[SIGNATURE_KEY_MISSING]).format(
124
count[SIGNATURE_KEY_MISSING])
126
def commit_not_valid_message(self, count):
127
return ngettext(u"{0} commit not valid",
128
u"{0} commits not valid",
129
count[SIGNATURE_NOT_VALID]).format(
130
count[SIGNATURE_NOT_VALID])
132
def commit_not_signed_message(self, count):
133
return ngettext(u"{0} commit not signed",
134
u"{0} commits not signed",
135
count[SIGNATURE_NOT_SIGNED]).format(
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])
57
145
def _set_gpg_tty():
58
146
tty = os.environ.get('TTY')
111
225
raise errors.SigningFailed(self._command_line())
229
def verify(self, content, testament):
230
"""Check content has a valid signature.
232
:param content: the commit signature
233
:param testament: the valid testament string for the commit
235
:return: SIGNATURE_VALID or a failed SIGNATURE_ value, key uid if valid
239
except ImportError, error:
240
raise errors.GpgmeNotInstalled(error)
242
signature = StringIO(content)
243
plain_output = StringIO()
245
result = self.context.verify(signature, None, plain_output)
246
except gpgme.GpgmeError,error:
247
raise errors.SignatureVerificationFailed(error[2])
249
# No result if input is invalid.
250
# test_verify_invalid()
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()
255
fingerprint = result[0].fpr
256
if self.acceptable_keys is not None:
257
if not fingerprint in self.acceptable_keys:
258
return SIGNATURE_KEY_MISSING, fingerprint[-8:]
259
# Check the signature actually matches the testament.
260
# test_verify_bad_testament()
261
if testament != plain_output.getvalue():
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.
266
if result[0].summary & gpgme.SIGSUM_VALID:
267
key = self.context.get_key(fingerprint)
268
name = key.uids[0].name
269
email = key.uids[0].email
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.
273
if result[0].summary & gpgme.SIGSUM_RED:
274
return SIGNATURE_NOT_VALID, None
275
# GPG does not know this key.
276
# test_verify_unknown_key()
277
if result[0].summary & gpgme.SIGSUM_KEY_MISSING:
278
return SIGNATURE_KEY_MISSING, fingerprint[-8:]
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.
281
if result[0].summary == 0 and self.acceptable_keys is not None:
282
if fingerprint in self.acceptable_keys:
283
# test_verify_untrusted_but_accepted()
284
return SIGNATURE_VALID, 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.
304
raise errors.SignatureVerificationFailed("Unknown GnuPG key "\
305
"verification result")
307
def set_acceptable_keys(self, command_line_input):
308
"""Set the acceptable keys for verifying with this GPGStrategy.
310
:param command_line_input: comma separated list of patterns from
315
acceptable_keys_config = self._config_stack.get('acceptable_keys')
317
if isinstance(acceptable_keys_config, unicode):
318
acceptable_keys_config = str(acceptable_keys_config)
319
except UnicodeEncodeError:
320
# gpg Context.keylist(pattern) does not like unicode
321
raise errors.BzrCommandError(
322
gettext('Only ASCII permitted in option names'))
324
if acceptable_keys_config is not None:
325
key_patterns = acceptable_keys_config
326
if command_line_input is not None: # command line overrides config
327
key_patterns = command_line_input
328
if key_patterns is not None:
329
patterns = key_patterns.split(",")
331
self.acceptable_keys = []
332
for pattern in patterns:
333
result = self.context.keylist(pattern)
337
self.acceptable_keys.append(key.subkeys[0].fpr)
338
trace.mutter("Added acceptable key: " + key.subkeys[0].fpr)
341
"No GnuPG key results for pattern: {0}"
344
def do_verifications(self, revisions, repository,
345
process_events_callback=None):
346
"""do verifications on a set of revisions
348
:param revisions: list of revision ids to verify
349
:param repository: repository object
350
:param process_events_callback: method to call for GUI frontends that
351
want to keep their UI refreshed
353
:return: count dictionary of results of each type,
354
result list for each revision,
355
boolean True if all results are verified successfully
357
count = {SIGNATURE_VALID: 0,
358
SIGNATURE_KEY_MISSING: 0,
359
SIGNATURE_NOT_VALID: 0,
360
SIGNATURE_NOT_SIGNED: 0,
361
SIGNATURE_EXPIRED: 0}
363
all_verifiable = True
364
for rev_id in revisions:
365
verification_result, uid =\
366
repository.verify_revision_signature(rev_id, self)
367
result.append([rev_id, verification_result, uid])
368
count[verification_result] += 1
369
if verification_result != SIGNATURE_VALID:
370
all_verifiable = False
371
if process_events_callback is not None:
372
process_events_callback()
373
return (count, result, all_verifiable)
375
def verbose_valid_message(self, result):
376
"""takes a verify result and returns list of signed commits strings"""
378
for rev_id, validity, uid in result:
379
if validity == SIGNATURE_VALID:
380
signers.setdefault(uid, 0)
383
for uid, number in signers.items():
384
result.append( ngettext(u"{0} signed {1} commit",
385
u"{0} signed {1} commits",
386
number).format(uid, number) )
390
def verbose_not_valid_message(self, result, repo):
391
"""takes a verify result and returns list of not valid commit info"""
393
for rev_id, validity, empty in result:
394
if validity == SIGNATURE_NOT_VALID:
395
revision = repo.get_revision(rev_id)
396
authors = ', '.join(revision.get_apparent_authors())
397
signers.setdefault(authors, 0)
398
signers[authors] += 1
400
for authors, number in signers.items():
401
result.append( ngettext(u"{0} commit by author {1}",
402
u"{0} commits by author {1}",
403
number).format(number, authors) )
406
def verbose_not_signed_message(self, result, repo):
407
"""takes a verify result and returns list of not signed commit info"""
409
for rev_id, validity, empty in result:
410
if validity == SIGNATURE_NOT_SIGNED:
411
revision = repo.get_revision(rev_id)
412
authors = ', '.join(revision.get_apparent_authors())
413
signers.setdefault(authors, 0)
414
signers[authors] += 1
416
for authors, number in signers.items():
417
result.append( ngettext(u"{0} commit by author {1}",
418
u"{0} commits by author {1}",
419
number).format(number, authors) )
422
def verbose_missing_key_message(self, result):
423
"""takes a verify result and returns list of missing key info"""
425
for rev_id, validity, fingerprint in result:
426
if validity == SIGNATURE_KEY_MISSING:
427
signers.setdefault(fingerprint, 0)
428
signers[fingerprint] += 1
430
for fingerprint, number in signers.items():
431
result.append( ngettext(u"Unknown key {0} signed {1} commit",
432
u"Unknown key {0} signed {1} commits",
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) )
456
def valid_commits_message(self, count):
457
"""returns message for number of commits"""
458
return gettext(u"{0} commits with valid signatures").format(
459
count[SIGNATURE_VALID])
461
def unknown_key_message(self, count):
462
"""returns message for number of commits"""
463
return ngettext(u"{0} commit with unknown key",
464
u"{0} commits with unknown keys",
465
count[SIGNATURE_KEY_MISSING]).format(
466
count[SIGNATURE_KEY_MISSING])
468
def commit_not_valid_message(self, count):
469
"""returns message for number of commits"""
470
return ngettext(u"{0} commit not valid",
471
u"{0} commits not valid",
472
count[SIGNATURE_NOT_VALID]).format(
473
count[SIGNATURE_NOT_VALID])
475
def commit_not_signed_message(self, count):
476
"""returns message for number of commits"""
477
return ngettext(u"{0} commit not signed",
478
u"{0} commits not signed",
479
count[SIGNATURE_NOT_SIGNED]).format(
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])