~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/gpg.py

  • Committer: Jelmer Vernooij
  • Date: 2011-11-08 19:09:55 UTC
  • mfrom: (6248 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6249.
  • Revision ID: jelmer@samba.org-20111108190955-xspn5rb9kpgw78oy
mergeĀ lp:bzr

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
"""GPG signing and checking logic."""
19
19
 
20
 
from __future__ import absolute_import
21
 
 
22
20
import os
23
21
import sys
24
22
from StringIO import StringIO
29
27
import subprocess
30
28
 
31
29
from bzrlib import (
32
 
    config,
33
30
    errors,
34
31
    trace,
35
32
    ui,
40
37
    )
41
38
""")
42
39
 
43
 
from bzrlib.symbol_versioning import (
44
 
    deprecated_in,
45
 
    deprecated_method,
46
 
    )
47
 
 
48
40
#verification results
49
41
SIGNATURE_VALID = 0
50
42
SIGNATURE_KEY_MISSING = 1
53
45
SIGNATURE_EXPIRED = 4
54
46
 
55
47
 
56
 
def bulk_verify_signatures(repository, revids, strategy,
57
 
        process_events_callback=None):
58
 
    """Do verifications on a set of revisions
59
 
 
60
 
    :param repository: repository object
61
 
    :param revids: list of revision ids to verify
62
 
    :param strategy: GPG strategy to use
63
 
    :param process_events_callback: method to call for GUI frontends that
64
 
        want to keep their UI refreshed
65
 
 
66
 
    :return: count dictionary of results of each type,
67
 
             result list for each revision,
68
 
             boolean True if all results are verified successfully
69
 
    """
70
 
    count = {SIGNATURE_VALID: 0,
71
 
             SIGNATURE_KEY_MISSING: 0,
72
 
             SIGNATURE_NOT_VALID: 0,
73
 
             SIGNATURE_NOT_SIGNED: 0,
74
 
             SIGNATURE_EXPIRED: 0}
75
 
    result = []
76
 
    all_verifiable = True
77
 
    total = len(revids)
78
 
    pb = ui.ui_factory.nested_progress_bar()
79
 
    try:
80
 
        for i, (rev_id, verification_result, uid) in enumerate(
81
 
                repository.verify_revision_signatures(
82
 
                    revids, strategy)):
83
 
            pb.update("verifying signatures", i, total)
84
 
            result.append([rev_id, verification_result, uid])
85
 
            count[verification_result] += 1
86
 
            if verification_result != SIGNATURE_VALID:
87
 
                all_verifiable = False
88
 
            if process_events_callback is not None:
89
 
                process_events_callback()
90
 
    finally:
91
 
        pb.finished()
92
 
    return (count, result, all_verifiable)
93
 
 
94
 
 
95
48
class DisabledGPGStrategy(object):
96
49
    """A GPG Strategy that makes everything fail."""
97
50
 
142
95
                else:
143
96
                    self.acceptable_keys.append(pattern)
144
97
 
145
 
    @deprecated_method(deprecated_in((2, 6, 0)))
146
98
    def do_verifications(self, revisions, repository):
147
 
        return bulk_verify_signatures(repository, revisions, self)
 
99
        count = {SIGNATURE_VALID: 0,
 
100
                 SIGNATURE_KEY_MISSING: 0,
 
101
                 SIGNATURE_NOT_VALID: 0,
 
102
                 SIGNATURE_NOT_SIGNED: 0,
 
103
                 SIGNATURE_EXPIRED: 0}
 
104
        result = []
 
105
        all_verifiable = True
 
106
        for rev_id in revisions:
 
107
            verification_result, uid =\
 
108
                                repository.verify_revision(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)
148
114
 
149
 
    @deprecated_method(deprecated_in((2, 6, 0)))
150
115
    def valid_commits_message(self, count):
151
 
        return valid_commits_message(count)
 
116
        return gettext(u"{0} commits with valid signatures").format(
 
117
                                        count[SIGNATURE_VALID])            
152
118
 
153
 
    @deprecated_method(deprecated_in((2, 6, 0)))
154
119
    def unknown_key_message(self, count):
155
 
        return unknown_key_message(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])
156
124
 
157
 
    @deprecated_method(deprecated_in((2, 6, 0)))
158
125
    def commit_not_valid_message(self, count):
159
 
        return commit_not_valid_message(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])
160
130
 
161
 
    @deprecated_method(deprecated_in((2, 6, 0)))
162
131
    def commit_not_signed_message(self, count):
163
 
        return commit_not_signed_message(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])
164
136
 
165
 
    @deprecated_method(deprecated_in((2, 6, 0)))
166
137
    def expired_commit_message(self, count):
167
 
        return expired_commit_message(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])
168
142
 
169
143
 
170
144
def _set_gpg_tty():
185
159
 
186
160
    acceptable_keys = None
187
161
 
188
 
    def __init__(self, config_stack):
189
 
        self._config_stack = config_stack
190
 
        try:
191
 
            import gpgme
192
 
            self.context = gpgme.Context()
193
 
        except ImportError, error:
194
 
            pass # can't use verify()
195
 
 
196
162
    @staticmethod
197
163
    def verify_signatures_available():
198
164
        """
207
173
            return False
208
174
 
209
175
    def _command_line(self):
210
 
        key = self._config_stack.get('gpg_signing_key')
211
 
        if key is None or key == 'default':
212
 
            # 'default' or not setting gpg_signing_key at all means we should
213
 
            # use the user email address
214
 
            key = config.extract_email_address(self._config_stack.get('email'))
215
 
        return [self._config_stack.get('gpg_signing_command'), '--clearsign',
216
 
                '-u', key, '--no-tty']
 
176
        
 
177
        return [self._config.gpg_signing_command(), '--clearsign', '-u',
 
178
                                                self._config.gpg_signing_key()]
 
179
 
 
180
    def __init__(self, config):
 
181
        self._config = config
 
182
        try:
 
183
            import gpgme
 
184
            self.context = gpgme.Context()
 
185
        except ImportError, error:
 
186
            pass # can't use verify()
217
187
 
218
188
    def sign(self, content):
219
189
        if isinstance(content, unicode):
253
223
 
254
224
    def verify(self, content, testament):
255
225
        """Check content has a valid signature.
256
 
 
 
226
        
257
227
        :param content: the commit signature
258
228
        :param testament: the valid testament string for the commit
259
 
 
 
229
        
260
230
        :return: SIGNATURE_VALID or a failed SIGNATURE_ value, key uid if valid
261
231
        """
262
232
        try:
266
236
 
267
237
        signature = StringIO(content)
268
238
        plain_output = StringIO()
 
239
        
269
240
        try:
270
241
            result = self.context.verify(signature, None, plain_output)
271
242
        except gpgme.GpgmeError,error:
275
246
        # test_verify_invalid()
276
247
        if len(result) == 0:
277
248
            return SIGNATURE_NOT_VALID, None
278
 
        # User has specified a list of acceptable keys, check our result is in
279
 
        # it.  test_verify_unacceptable_key()
 
249
        # User has specified a list of acceptable keys, check our result is in it.
 
250
        # test_verify_unacceptable_key()
280
251
        fingerprint = result[0].fpr
281
252
        if self.acceptable_keys is not None:
282
 
            if not fingerprint in self.acceptable_keys:
 
253
            if not fingerprint in self.acceptable_keys:                
283
254
                return SIGNATURE_KEY_MISSING, fingerprint[-8:]
284
255
        # Check the signature actually matches the testament.
285
256
        # test_verify_bad_testament()
286
257
        if testament != plain_output.getvalue():
287
 
            return SIGNATURE_NOT_VALID, None
 
258
            return SIGNATURE_NOT_VALID, None 
288
259
        # Yay gpgme set the valid bit.
289
260
        # Can't write a test for this one as you can't set a key to be
290
261
        # trusted using gpgme.
301
272
        # test_verify_unknown_key()
302
273
        if result[0].summary & gpgme.SIGSUM_KEY_MISSING:
303
274
            return SIGNATURE_KEY_MISSING, fingerprint[-8:]
304
 
        # Summary isn't set if sig is valid but key is untrusted but if user
305
 
        # has explicity set the key as acceptable we can validate it.
 
275
        # Summary isn't set if sig is valid but key is untrusted
 
276
        # but if user has explicity set the key as acceptable we can validate it.
306
277
        if result[0].summary == 0 and self.acceptable_keys is not None:
307
278
            if fingerprint in self.acceptable_keys:
308
279
                # test_verify_untrusted_but_accepted()
309
 
                return SIGNATURE_VALID, None
 
280
                return SIGNATURE_VALID, None 
310
281
        # test_verify_valid_but_untrusted()
311
282
        if result[0].summary == 0 and self.acceptable_keys is None:
312
283
            return SIGNATURE_NOT_VALID, None
330
301
                                                 "verification result")
331
302
 
332
303
    def set_acceptable_keys(self, command_line_input):
333
 
        """Set the acceptable keys for verifying with this GPGStrategy.
334
 
 
 
304
        """sets the acceptable keys for verifying with this GPGStrategy
 
305
        
335
306
        :param command_line_input: comma separated list of patterns from
336
307
                                command line
337
308
        :return: nothing
338
309
        """
339
310
        key_patterns = None
340
 
        acceptable_keys_config = self._config_stack.get('acceptable_keys')
 
311
        acceptable_keys_config = self._config.acceptable_keys()
341
312
        try:
342
313
            if isinstance(acceptable_keys_config, unicode):
343
314
                acceptable_keys_config = str(acceptable_keys_config)
344
315
        except UnicodeEncodeError:
345
 
            # gpg Context.keylist(pattern) does not like unicode
346
 
            raise errors.BzrCommandError(
347
 
                gettext('Only ASCII permitted in option names'))
 
316
            #gpg Context.keylist(pattern) does not like unicode
 
317
            raise errors.BzrCommandError(gettext('Only ASCII permitted in option names'))
348
318
 
349
319
        if acceptable_keys_config is not None:
350
320
            key_patterns = acceptable_keys_config
351
 
        if command_line_input is not None: # command line overrides config
 
321
        if command_line_input is not None: #command line overrides config
352
322
            key_patterns = command_line_input
353
323
        if key_patterns is not None:
354
324
            patterns = key_patterns.split(",")
366
336
                            "No GnuPG key results for pattern: {0}"
367
337
                                ).format(pattern))
368
338
 
369
 
    @deprecated_method(deprecated_in((2, 6, 0)))
370
339
    def do_verifications(self, revisions, repository,
371
340
                            process_events_callback=None):
372
341
        """do verifications on a set of revisions
373
 
 
 
342
        
374
343
        :param revisions: list of revision ids to verify
375
344
        :param repository: repository object
376
345
        :param process_events_callback: method to call for GUI frontends that
377
 
            want to keep their UI refreshed
378
 
 
 
346
                                                want to keep their UI refreshed
 
347
        
379
348
        :return: count dictionary of results of each type,
380
349
                 result list for each revision,
381
350
                 boolean True if all results are verified successfully
382
351
        """
383
 
        return bulk_verify_signatures(repository, revisions, self,
384
 
            process_events_callback)
 
352
        count = {SIGNATURE_VALID: 0,
 
353
                 SIGNATURE_KEY_MISSING: 0,
 
354
                 SIGNATURE_NOT_VALID: 0,
 
355
                 SIGNATURE_NOT_SIGNED: 0,
 
356
                 SIGNATURE_EXPIRED: 0}
 
357
        result = []
 
358
        all_verifiable = True
 
359
        for rev_id in revisions:
 
360
            verification_result, uid =\
 
361
                                repository.verify_revision(rev_id,self)
 
362
            result.append([rev_id, verification_result, uid])
 
363
            count[verification_result] += 1
 
364
            if verification_result != SIGNATURE_VALID:
 
365
                all_verifiable = False
 
366
            if process_events_callback is not None:
 
367
                process_events_callback()
 
368
        return (count, result, all_verifiable)
385
369
 
386
 
    @deprecated_method(deprecated_in((2, 6, 0)))
387
370
    def verbose_valid_message(self, result):
388
371
        """takes a verify result and returns list of signed commits strings"""
389
 
        return verbose_valid_message(result)
390
 
 
391
 
    @deprecated_method(deprecated_in((2, 6, 0)))
 
372
        signers = {}
 
373
        for rev_id, validity, uid in result:
 
374
            if validity == SIGNATURE_VALID:
 
375
                signers.setdefault(uid, 0)
 
376
                signers[uid] += 1
 
377
        result = []
 
378
        for uid, number in signers.items():
 
379
             result.append( ngettext(u"{0} signed {1} commit", 
 
380
                             u"{0} signed {1} commits",
 
381
                             number).format(uid, number) )
 
382
        return result
 
383
 
 
384
 
392
385
    def verbose_not_valid_message(self, result, repo):
393
386
        """takes a verify result and returns list of not valid commit info"""
394
 
        return verbose_not_valid_message(result, repo)
 
387
        signers = {}
 
388
        for rev_id, validity, empty in result:
 
389
            if validity == SIGNATURE_NOT_VALID:
 
390
                revision = repo.get_revision(rev_id)
 
391
                authors = ', '.join(revision.get_apparent_authors())
 
392
                signers.setdefault(authors, 0)
 
393
                signers[authors] += 1
 
394
        result = []
 
395
        for authors, number in signers.items():
 
396
            result.append( ngettext(u"{0} commit by author {1}", 
 
397
                                 u"{0} commits by author {1}",
 
398
                                 number).format(number, authors) )
 
399
        return result
395
400
 
396
 
    @deprecated_method(deprecated_in((2, 6, 0)))
397
401
    def verbose_not_signed_message(self, result, repo):
398
402
        """takes a verify result and returns list of not signed commit info"""
399
 
        return verbose_not_valid_message(result, repo)
 
403
        signers = {}
 
404
        for rev_id, validity, empty in result:
 
405
            if validity == SIGNATURE_NOT_SIGNED:
 
406
                revision = repo.get_revision(rev_id)
 
407
                authors = ', '.join(revision.get_apparent_authors())
 
408
                signers.setdefault(authors, 0)
 
409
                signers[authors] += 1
 
410
        result = []
 
411
        for authors, number in signers.items():
 
412
            result.append( ngettext(u"{0} commit by author {1}", 
 
413
                                 u"{0} commits by author {1}",
 
414
                                 number).format(number, authors) )
 
415
        return result
400
416
 
401
 
    @deprecated_method(deprecated_in((2, 6, 0)))
402
417
    def verbose_missing_key_message(self, result):
403
418
        """takes a verify result and returns list of missing key info"""
404
 
        return verbose_missing_key_message(result)
 
419
        signers = {}
 
420
        for rev_id, validity, fingerprint in result:
 
421
            if validity == SIGNATURE_KEY_MISSING:
 
422
                signers.setdefault(fingerprint, 0)
 
423
                signers[fingerprint] += 1
 
424
        result = []
 
425
        for fingerprint, number in signers.items():
 
426
            result.append( ngettext(u"Unknown key {0} signed {1} commit", 
 
427
                                 u"Unknown key {0} signed {1} commits",
 
428
                                 number).format(fingerprint, number) )
 
429
        return result
405
430
 
406
 
    @deprecated_method(deprecated_in((2, 6, 0)))
407
431
    def verbose_expired_key_message(self, result, repo):
408
432
        """takes a verify result and returns list of expired key info"""
409
 
        return verbose_expired_key_message(result, repo)
 
433
        signers = {}
 
434
        fingerprint_to_authors = {}
 
435
        for rev_id, validity, fingerprint in result:
 
436
            if validity == SIGNATURE_EXPIRED:
 
437
                revision = repo.get_revision(rev_id)
 
438
                authors = ', '.join(revision.get_apparent_authors())
 
439
                signers.setdefault(fingerprint, 0)
 
440
                signers[fingerprint] += 1
 
441
                fingerprint_to_authors[fingerprint] = authors
 
442
        result = []
 
443
        for fingerprint, number in signers.items():
 
444
            result.append(ngettext(u"{0} commit by author {1} with "\
 
445
                                    "key {2} now expired", 
 
446
                                   u"{0} commits by author {1} with key {2} now "\
 
447
                                    "expired",
 
448
                                    number).format(number,
 
449
                            fingerprint_to_authors[fingerprint], fingerprint) )
 
450
        return result
410
451
 
411
 
    @deprecated_method(deprecated_in((2, 6, 0)))
412
452
    def valid_commits_message(self, count):
413
453
        """returns message for number of commits"""
414
 
        return valid_commits_message(count)
 
454
        return gettext(u"{0} commits with valid signatures").format(
 
455
                                        count[SIGNATURE_VALID])
415
456
 
416
 
    @deprecated_method(deprecated_in((2, 6, 0)))
417
457
    def unknown_key_message(self, count):
418
458
        """returns message for number of commits"""
419
 
        return unknown_key_message(count)
 
459
        return ngettext(u"{0} commit with unknown key",
 
460
                        u"{0} commits with unknown keys",
 
461
                        count[SIGNATURE_KEY_MISSING]).format(
 
462
                                        count[SIGNATURE_KEY_MISSING])
420
463
 
421
 
    @deprecated_method(deprecated_in((2, 6, 0)))
422
464
    def commit_not_valid_message(self, count):
423
465
        """returns message for number of commits"""
424
 
        return commit_not_valid_message(count)
 
466
        return ngettext(u"{0} commit not valid",
 
467
                        u"{0} commits not valid",
 
468
                        count[SIGNATURE_NOT_VALID]).format(
 
469
                                            count[SIGNATURE_NOT_VALID])
425
470
 
426
 
    @deprecated_method(deprecated_in((2, 6, 0)))
427
471
    def commit_not_signed_message(self, count):
428
472
        """returns message for number of commits"""
429
 
        return commit_not_signed_message(count)
 
473
        return ngettext(u"{0} commit not signed",
 
474
                        u"{0} commits not signed",
 
475
                        count[SIGNATURE_NOT_SIGNED]).format(
 
476
                                        count[SIGNATURE_NOT_SIGNED])
430
477
 
431
 
    @deprecated_method(deprecated_in((2, 6, 0)))
432
478
    def expired_commit_message(self, count):
433
479
        """returns message for number of commits"""
434
 
        return expired_commit_message(count)
435
 
 
436
 
 
437
 
def valid_commits_message(count):
438
 
    """returns message for number of commits"""
439
 
    return gettext(u"{0} commits with valid signatures").format(
440
 
                                    count[SIGNATURE_VALID])
441
 
 
442
 
 
443
 
def unknown_key_message(count):
444
 
    """returns message for number of commits"""
445
 
    return ngettext(u"{0} commit with unknown key",
446
 
                    u"{0} commits with unknown keys",
447
 
                    count[SIGNATURE_KEY_MISSING]).format(
448
 
                                    count[SIGNATURE_KEY_MISSING])
449
 
 
450
 
 
451
 
def commit_not_valid_message(count):
452
 
    """returns message for number of commits"""
453
 
    return ngettext(u"{0} commit not valid",
454
 
                    u"{0} commits not valid",
455
 
                    count[SIGNATURE_NOT_VALID]).format(
456
 
                                        count[SIGNATURE_NOT_VALID])
457
 
 
458
 
 
459
 
def commit_not_signed_message(count):
460
 
    """returns message for number of commits"""
461
 
    return ngettext(u"{0} commit not signed",
462
 
                    u"{0} commits not signed",
463
 
                    count[SIGNATURE_NOT_SIGNED]).format(
464
 
                                    count[SIGNATURE_NOT_SIGNED])
465
 
 
466
 
 
467
 
def expired_commit_message(count):
468
 
    """returns message for number of commits"""
469
 
    return ngettext(u"{0} commit with key now expired",
470
 
                    u"{0} commits with key now expired",
471
 
                    count[SIGNATURE_EXPIRED]).format(
472
 
                                count[SIGNATURE_EXPIRED])
473
 
 
474
 
 
475
 
def verbose_expired_key_message(result, repo):
476
 
    """takes a verify result and returns list of expired key info"""
477
 
    signers = {}
478
 
    fingerprint_to_authors = {}
479
 
    for rev_id, validity, fingerprint in result:
480
 
        if validity == SIGNATURE_EXPIRED:
481
 
            revision = repo.get_revision(rev_id)
482
 
            authors = ', '.join(revision.get_apparent_authors())
483
 
            signers.setdefault(fingerprint, 0)
484
 
            signers[fingerprint] += 1
485
 
            fingerprint_to_authors[fingerprint] = authors
486
 
    result = []
487
 
    for fingerprint, number in signers.items():
488
 
        result.append(
489
 
            ngettext(u"{0} commit by author {1} with key {2} now expired",
490
 
                     u"{0} commits by author {1} with key {2} now expired",
491
 
                     number).format(
492
 
                number, fingerprint_to_authors[fingerprint], fingerprint))
493
 
    return result
494
 
 
495
 
 
496
 
def verbose_valid_message(result):
497
 
    """takes a verify result and returns list of signed commits strings"""
498
 
    signers = {}
499
 
    for rev_id, validity, uid in result:
500
 
        if validity == SIGNATURE_VALID:
501
 
            signers.setdefault(uid, 0)
502
 
            signers[uid] += 1
503
 
    result = []
504
 
    for uid, number in signers.items():
505
 
         result.append(ngettext(u"{0} signed {1} commit",
506
 
                                u"{0} signed {1} commits",
507
 
                                number).format(uid, number))
508
 
    return result
509
 
 
510
 
 
511
 
def verbose_not_valid_message(result, repo):
512
 
    """takes a verify result and returns list of not valid commit info"""
513
 
    signers = {}
514
 
    for rev_id, validity, empty in result:
515
 
        if validity == SIGNATURE_NOT_VALID:
516
 
            revision = repo.get_revision(rev_id)
517
 
            authors = ', '.join(revision.get_apparent_authors())
518
 
            signers.setdefault(authors, 0)
519
 
            signers[authors] += 1
520
 
    result = []
521
 
    for authors, number in signers.items():
522
 
        result.append(ngettext(u"{0} commit by author {1}",
523
 
                               u"{0} commits by author {1}",
524
 
                               number).format(number, authors))
525
 
    return result
526
 
 
527
 
 
528
 
def verbose_not_signed_message(result, repo):
529
 
    """takes a verify result and returns list of not signed commit info"""
530
 
    signers = {}
531
 
    for rev_id, validity, empty in result:
532
 
        if validity == SIGNATURE_NOT_SIGNED:
533
 
            revision = repo.get_revision(rev_id)
534
 
            authors = ', '.join(revision.get_apparent_authors())
535
 
            signers.setdefault(authors, 0)
536
 
            signers[authors] += 1
537
 
    result = []
538
 
    for authors, number in signers.items():
539
 
        result.append(ngettext(u"{0} commit by author {1}",
540
 
                               u"{0} commits by author {1}",
541
 
                               number).format(number, authors))
542
 
    return result
543
 
 
544
 
 
545
 
def verbose_missing_key_message(result):
546
 
    """takes a verify result and returns list of missing key info"""
547
 
    signers = {}
548
 
    for rev_id, validity, fingerprint in result:
549
 
        if validity == SIGNATURE_KEY_MISSING:
550
 
            signers.setdefault(fingerprint, 0)
551
 
            signers[fingerprint] += 1
552
 
    result = []
553
 
    for fingerprint, number in signers.items():
554
 
        result.append(ngettext(u"Unknown key {0} signed {1} commit",
555
 
                               u"Unknown key {0} signed {1} commits",
556
 
                               number).format(fingerprint, number))
557
 
    return result
 
480
        return ngettext(u"{0} commit with key now expired",
 
481
                        u"{0} commits with key now expired",
 
482
                        count[SIGNATURE_EXPIRED]).format(
 
483
                                    count[SIGNATURE_EXPIRED])