~bzr-pqm/bzr/bzr.dev

6609.1.1 by Vincent Ladeuil
Fix the failing gpg test on wily.
1
# Copyright (C) 2005, 2006, 2007, 2009, 2011, 2012, 2013, 2016 Canonical Ltd
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
2
#   Authors: Robert Collins <robert.collins@canonical.com>
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
17
6379.6.7 by Jelmer Vernooij
Move importing from future until after doc string, otherwise the doc string will disappear.
18
"""GPG signing and checking logic."""
19
6379.6.3 by Jelmer Vernooij
Use absolute_import.
20
from __future__ import absolute_import
21
1996.3.1 by John Arbash Meinel
Demandloading builtins.py drops our load time from 350ms to 291ms
22
import os
23
import sys
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
24
from StringIO import StringIO
1996.3.1 by John Arbash Meinel
Demandloading builtins.py drops our load time from 350ms to 291ms
25
26
from bzrlib.lazy_import import lazy_import
27
lazy_import(globals(), """
1442.1.58 by Robert Collins
gpg signing of content
28
import errno
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
29
import subprocess
30
1912.3.2 by John Arbash Meinel
Adding some logging, because on my machine TTY is not exported by default.
31
from bzrlib import (
6372.1.3 by Vincent Ladeuil
Fix gpg_signing_ket default values handling
32
    config,
1912.3.2 by John Arbash Meinel
Adding some logging, because on my machine TTY is not exported by default.
33
    errors,
34
    trace,
1551.8.11 by Aaron Bentley
Clear terminal before signing
35
    ui,
1912.3.2 by John Arbash Meinel
Adding some logging, because on my machine TTY is not exported by default.
36
    )
6092.2.3 by Jonathan Riddell
improve formatting
37
from bzrlib.i18n import (
38
    gettext, 
39
    ngettext,
40
    )
1996.3.1 by John Arbash Meinel
Demandloading builtins.py drops our load time from 350ms to 291ms
41
""")
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
42
6491.1.4 by Jelmer Vernooij
Deprecate GPGStrategy.do_verifications.
43
from bzrlib.symbol_versioning import (
44
    deprecated_in,
45
    deprecated_method,
46
    )
47
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
48
#verification results
5971.1.1 by Jonathan Riddell
add a verify command
49
SIGNATURE_VALID = 0
50
SIGNATURE_KEY_MISSING = 1
51
SIGNATURE_NOT_VALID = 2
52
SIGNATURE_NOT_SIGNED = 3
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
53
SIGNATURE_EXPIRED = 4
5971.1.1 by Jonathan Riddell
add a verify command
54
55
6491.1.3 by Jelmer Vernooij
Make 'bzr verify-signatures' show a progress bar.
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:
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
80
        for i, (rev_id, verification_result, uid) in enumerate(
81
                repository.verify_revision_signatures(
82
                    revids, strategy)):
6491.1.3 by Jelmer Vernooij
Make 'bzr verify-signatures' show a progress bar.
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
1442.1.62 by Robert Collins
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.
95
class DisabledGPGStrategy(object):
96
    """A GPG Strategy that makes everything fail."""
97
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
98
    @staticmethod
99
    def verify_signatures_available():
100
        return True
101
1442.1.62 by Robert Collins
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.
102
    def __init__(self, ignored):
103
        """Real strategies take a configuration."""
104
105
    def sign(self, content):
106
        raise errors.SigningFailed('Signing is disabled.')
107
5971.1.31 by Jonathan Riddell
and update tests
108
    def verify(self, content, testament):
5971.1.33 by Jonathan Riddell
rename errors.VerifyFailed to errors.SignatureVerificationFailed
109
        raise errors.SignatureVerificationFailed('Signature verification is \
110
disabled.')
5971.1.6 by Jonathan Riddell
fix methods for dummy gpg strategies
111
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
112
    def set_acceptable_keys(self, command_line_input):
5971.1.14 by Jonathan Riddell
add test for set_acceptable_keys, accept non-trusted keys if specified as acceptable, import dummy key in tests so it works outside my machine
113
        pass
114
1442.1.62 by Robert Collins
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.
115
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
116
class LoopbackGPGStrategy(object):
5971.1.85 by Jonathan Riddell
use unicode strings for UI
117
    """A GPG Strategy that acts like 'cat' - data is just passed through.
5971.1.86 by Jonathan Riddell
doc string formatting
118
    Used in tests.
119
    """
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
120
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
121
    @staticmethod
122
    def verify_signatures_available():
123
        return True
124
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
125
    def __init__(self, ignored):
126
        """Real strategies take a configuration."""
127
128
    def sign(self, content):
1551.12.15 by Aaron Bentley
add header/trailer to fake clearsigned texts
129
        return ("-----BEGIN PSEUDO-SIGNED CONTENT-----\n" + content +
1551.12.52 by Aaron Bentley
speling fix
130
                "-----END PSEUDO-SIGNED CONTENT-----\n")
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
131
5971.1.31 by Jonathan Riddell
and update tests
132
    def verify(self, content, testament):
5971.1.22 by Jonathan Riddell
fix tests
133
        return SIGNATURE_VALID, None
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
134
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
135
    def set_acceptable_keys(self, command_line_input):
136
        if command_line_input is not None:
137
            patterns = command_line_input.split(",")
138
            self.acceptable_keys = []
139
            for pattern in patterns:
140
                if pattern == "unknown":
141
                    pass
142
                else:
143
                    self.acceptable_keys.append(pattern)
5971.1.14 by Jonathan Riddell
add test for set_acceptable_keys, accept non-trusted keys if specified as acceptable, import dummy key in tests so it works outside my machine
144
6491.1.4 by Jelmer Vernooij
Deprecate GPGStrategy.do_verifications.
145
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
146
    def do_verifications(self, revisions, repository):
6491.1.3 by Jelmer Vernooij
Make 'bzr verify-signatures' show a progress bar.
147
        return bulk_verify_signatures(repository, revisions, self)
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
148
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
149
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
150
    def valid_commits_message(self, count):
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
151
        return valid_commits_message(count)
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
152
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
153
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
154
    def unknown_key_message(self, count):
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
155
        return unknown_key_message(count)
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
156
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
157
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
158
    def commit_not_valid_message(self, count):
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
159
        return commit_not_valid_message(count)
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
160
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
161
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
162
    def commit_not_signed_message(self, count):
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
163
        return commit_not_signed_message(count)
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
164
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
165
    @deprecated_method(deprecated_in((2, 6, 0)))
6043.3.3 by Jonathan Riddell
add expired_commit_message() to LoopbackGPGStrategy
166
    def expired_commit_message(self, count):
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
167
        return expired_commit_message(count)
6043.3.3 by Jonathan Riddell
add expired_commit_message() to LoopbackGPGStrategy
168
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
169
1912.3.1 by John Arbash Meinel
updating gpg.py to set GPG_TTY in the environment.
170
def _set_gpg_tty():
171
    tty = os.environ.get('TTY')
172
    if tty is not None:
173
        os.environ['GPG_TTY'] = tty
1912.3.2 by John Arbash Meinel
Adding some logging, because on my machine TTY is not exported by default.
174
        trace.mutter('setting GPG_TTY=%s', tty)
175
    else:
176
        # This is not quite worthy of a warning, because some people
177
        # don't need GPG_TTY to be set. But it is worthy of a big mark
178
        # in ~/.bzr.log, so that people can debug it if it happens to them
179
        trace.mutter('** Env var TTY empty, cannot set GPG_TTY.'
180
                     '  Is TTY exported?')
1912.3.1 by John Arbash Meinel
updating gpg.py to set GPG_TTY in the environment.
181
182
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
183
class GPGStrategy(object):
184
    """GPG Signing and checking facilities."""
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
185
5971.1.11 by Jonathan Riddell
add set_acceptable_keys() so user can specify which gpg keys can be used for verification
186
    acceptable_keys = None
187
6351.3.9 by Vincent Ladeuil
Move __init__ at the beginning of the class since that's the current idiom.
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
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
196
    @staticmethod
197
    def verify_signatures_available():
5971.1.82 by Jonathan Riddell
method doc
198
        """
5971.1.86 by Jonathan Riddell
doc string formatting
199
        check if this strategy can verify signatures
200
5971.1.82 by Jonathan Riddell
method doc
201
        :return: boolean if this strategy can verify signatures
202
        """
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
203
        try:
204
            import gpgme
205
            return True
206
        except ImportError, error:
207
            return False
208
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
209
    def _command_line(self):
6372.3.1 by Jelmer Vernooij
Clumsily fix gpg_signing_key.
210
        key = self._config_stack.get('gpg_signing_key')
6372.1.3 by Vincent Ladeuil
Fix gpg_signing_ket default values handling
211
        if key is None or key == 'default':
6372.1.4 by Vincent Ladeuil
Add a comment.
212
            # 'default' or not setting gpg_signing_key at all means we should
213
            # use the user email address
6372.1.3 by Vincent Ladeuil
Fix gpg_signing_ket default values handling
214
            key = config.extract_email_address(self._config_stack.get('email'))
6351.3.2 by Jelmer Vernooij
Convert some gpg options to config stacks.
215
        return [self._config_stack.get('gpg_signing_command'), '--clearsign',
6437.64.1 by Jelmer Vernooij
Revert use of --no-tty.
216
                '-u', key]
1442.1.58 by Robert Collins
gpg signing of content
217
218
    def sign(self, content):
2273.1.1 by John Arbash Meinel
``GPGStrategy.sign()`` will now raise ``BzrBadParameterUnicode`` if
219
        if isinstance(content, unicode):
220
            raise errors.BzrBadParameterUnicode('content')
1551.8.11 by Aaron Bentley
Clear terminal before signing
221
        ui.ui_factory.clear_term()
1963.1.8 by John Arbash Meinel
Don't use preexec_fn on win32
222
223
        preexec_fn = _set_gpg_tty
224
        if sys.platform == 'win32':
225
            # Win32 doesn't support preexec_fn, but wouldn't support TTY anyway.
226
            preexec_fn = None
1442.1.58 by Robert Collins
gpg signing of content
227
        try:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
228
            process = subprocess.Popen(self._command_line(),
229
                                       stdin=subprocess.PIPE,
1912.3.1 by John Arbash Meinel
updating gpg.py to set GPG_TTY in the environment.
230
                                       stdout=subprocess.PIPE,
1963.1.8 by John Arbash Meinel
Don't use preexec_fn on win32
231
                                       preexec_fn=preexec_fn)
1442.1.58 by Robert Collins
gpg signing of content
232
            try:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
233
                result = process.communicate(content)[0]
1442.1.58 by Robert Collins
gpg signing of content
234
                if process.returncode is None:
235
                    process.wait()
236
                if process.returncode != 0:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
237
                    raise errors.SigningFailed(self._command_line())
1442.1.58 by Robert Collins
gpg signing of content
238
                return result
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
239
            except OSError, e:
1442.1.58 by Robert Collins
gpg signing of content
240
                if e.errno == errno.EPIPE:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
241
                    raise errors.SigningFailed(self._command_line())
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
242
                else:
243
                    raise
1442.1.58 by Robert Collins
gpg signing of content
244
        except ValueError:
245
            # bad subprocess parameters, should never happen.
246
            raise
247
        except OSError, e:
248
            if e.errno == errno.ENOENT:
249
                # gpg is not installed
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
250
                raise errors.SigningFailed(self._command_line())
1442.1.58 by Robert Collins
gpg signing of content
251
            else:
252
                raise
5971.1.1 by Jonathan Riddell
add a verify command
253
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
254
    def verify(self, content, testament):
5971.1.7 by Jonathan Riddell
add method docs
255
        """Check content has a valid signature.
6491.1.1 by Jelmer Vernooij
Various cleanups related to GPG.
256
5971.1.7 by Jonathan Riddell
add method docs
257
        :param content: the commit signature
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
258
        :param testament: the valid testament string for the commit
6491.1.1 by Jelmer Vernooij
Various cleanups related to GPG.
259
5971.1.18 by Jonathan Riddell
add email to verbose output
260
        :return: SIGNATURE_VALID or a failed SIGNATURE_ value, key uid if valid
5971.1.7 by Jonathan Riddell
add method docs
261
        """
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
262
        try:
263
            import gpgme
5971.1.41 by Jonathan Riddell
fix calling GpgmeNotInstalled
264
        except ImportError, error:
265
            raise errors.GpgmeNotInstalled(error)
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
266
5971.1.1 by Jonathan Riddell
add a verify command
267
        signature = StringIO(content)
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
268
        plain_output = StringIO()
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
269
        try:
5971.1.61 by Jonathan Riddell
make gpgme context global to class
270
            result = self.context.verify(signature, None, plain_output)
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
271
        except gpgme.GpgmeError,error:
5971.1.33 by Jonathan Riddell
rename errors.VerifyFailed to errors.SignatureVerificationFailed
272
            raise errors.SignatureVerificationFailed(error[2])
5971.1.1 by Jonathan Riddell
add a verify command
273
6043.2.15 by Jonathan Riddell
turn comments into sentences
274
        # No result if input is invalid.
275
        # test_verify_invalid()
5971.1.9 by Jonathan Riddell
add some tests
276
        if len(result) == 0:
5971.1.17 by Jonathan Riddell
add verbose option
277
            return SIGNATURE_NOT_VALID, None
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
278
        # User has specified a list of acceptable keys, check our result is in
279
        # it.  test_verify_unacceptable_key()
5971.1.13 by Jonathan Riddell
return missing if not in acceptable keys
280
        fingerprint = result[0].fpr
281
        if self.acceptable_keys is not None:
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
282
            if not fingerprint in self.acceptable_keys:
5971.1.27 by Jonathan Riddell
verbose info for unknown keys
283
                return SIGNATURE_KEY_MISSING, fingerprint[-8:]
6043.2.15 by Jonathan Riddell
turn comments into sentences
284
        # Check the signature actually matches the testament.
285
        # test_verify_bad_testament()
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
286
        if testament != plain_output.getvalue():
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
287
            return SIGNATURE_NOT_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
288
        # Yay gpgme set the valid bit.
289
        # Can't write a test for this one as you can't set a key to be
290
        # trusted using gpgme.
5971.1.1 by Jonathan Riddell
add a verify command
291
        if result[0].summary & gpgme.SIGSUM_VALID:
5971.1.61 by Jonathan Riddell
make gpgme context global to class
292
            key = self.context.get_key(fingerprint)
5971.1.17 by Jonathan Riddell
add verbose option
293
            name = key.uids[0].name
5971.1.18 by Jonathan Riddell
add email to verbose output
294
            email = key.uids[0].email
295
            return SIGNATURE_VALID, name + " <" + email + ">"
6043.2.15 by Jonathan Riddell
turn comments into sentences
296
        # Sigsum_red indicates a problem, unfortunatly I have not been able
297
        # to write any tests which actually set this.
5971.1.1 by Jonathan Riddell
add a verify command
298
        if result[0].summary & gpgme.SIGSUM_RED:
5971.1.17 by Jonathan Riddell
add verbose option
299
            return SIGNATURE_NOT_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
300
        # GPG does not know this key.
301
        # test_verify_unknown_key()
5971.1.1 by Jonathan Riddell
add a verify command
302
        if result[0].summary & gpgme.SIGSUM_KEY_MISSING:
5971.1.27 by Jonathan Riddell
verbose info for unknown keys
303
            return SIGNATURE_KEY_MISSING, fingerprint[-8:]
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
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.
5971.1.14 by Jonathan Riddell
add test for set_acceptable_keys, accept non-trusted keys if specified as acceptable, import dummy key in tests so it works outside my machine
306
        if result[0].summary == 0 and self.acceptable_keys is not None:
307
            if fingerprint in self.acceptable_keys:
6043.2.15 by Jonathan Riddell
turn comments into sentences
308
                # test_verify_untrusted_but_accepted()
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
309
                return SIGNATURE_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
310
        # test_verify_valid_but_untrusted()
6043.2.7 by Jonathan Riddell
some reordering of verification, improve names of tests
311
        if result[0].summary == 0 and self.acceptable_keys is None:
312
            return SIGNATURE_NOT_VALID, None
6043.2.3 by Jonathan Riddell
comment gpgme verify usage so it can be verified by someone who knows what they're talking about, also accept signatures from keys which were valid but have since expired
313
        if result[0].summary & gpgme.SIGSUM_KEY_EXPIRED:
6043.2.8 by Jonathan Riddell
add test for unknown key
314
            expires = self.context.get_key(result[0].fpr).subkeys[0].expires
6043.2.3 by Jonathan Riddell
comment gpgme verify usage so it can be verified by someone who knows what they're talking about, also accept signatures from keys which were valid but have since expired
315
            if expires > result[0].timestamp:
6043.2.15 by Jonathan Riddell
turn comments into sentences
316
                # The expired key was not expired at time of signing.
317
                # test_verify_expired_but_valid()
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
318
                return SIGNATURE_EXPIRED, fingerprint[-8:]
6043.2.3 by Jonathan Riddell
comment gpgme verify usage so it can be verified by someone who knows what they're talking about, also accept signatures from keys which were valid but have since expired
319
            else:
6043.2.15 by Jonathan Riddell
turn comments into sentences
320
                # I can't work out how to create a test where the signature
321
                # was expired at the time of signing.
6043.2.3 by Jonathan Riddell
comment gpgme verify usage so it can be verified by someone who knows what they're talking about, also accept signatures from keys which were valid but have since expired
322
                return SIGNATURE_NOT_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
323
        # A signature from a revoked key gets this.
324
        # test_verify_revoked_signature()
6609.1.1 by Vincent Ladeuil
Fix the failing gpg test on wily.
325
        if ((result[0].summary & gpgme.SIGSUM_SYS_ERROR
326
             or result[0].status.strerror == 'Certificate revoked')):
6043.2.5 by Jonathan Riddell
catch a revoked key and add test for it
327
            return SIGNATURE_NOT_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
328
        # Other error types such as revoked keys should (I think) be caught by
329
        # SIGSUM_RED so anything else means something is buggy.
6609.1.1 by Vincent Ladeuil
Fix the failing gpg test on wily.
330
        raise errors.SignatureVerificationFailed(
331
            "Unknown GnuPG key verification result")
5971.1.11 by Jonathan Riddell
add set_acceptable_keys() so user can specify which gpg keys can be used for verification
332
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
333
    def set_acceptable_keys(self, command_line_input):
6351.3.2 by Jelmer Vernooij
Convert some gpg options to config stacks.
334
        """Set the acceptable keys for verifying with this GPGStrategy.
6491.1.1 by Jelmer Vernooij
Various cleanups related to GPG.
335
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
336
        :param command_line_input: comma separated list of patterns from
337
                                command line
338
        :return: nothing
339
        """
6589.3.1 by Vincent Ladeuil
Fix command line override handling for acceptable_keys
340
        patterns = None
6351.3.2 by Jelmer Vernooij
Convert some gpg options to config stacks.
341
        acceptable_keys_config = self._config_stack.get('acceptable_keys')
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
342
        if acceptable_keys_config is not None:
6589.3.1 by Vincent Ladeuil
Fix command line override handling for acceptable_keys
343
            patterns = acceptable_keys_config
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
344
        if command_line_input is not None: # command line overrides config
6589.3.1 by Vincent Ladeuil
Fix command line override handling for acceptable_keys
345
            patterns = command_line_input.split(',')
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
346
6589.3.1 by Vincent Ladeuil
Fix command line override handling for acceptable_keys
347
        if patterns:
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
348
            self.acceptable_keys = []
349
            for pattern in patterns:
350
                result = self.context.keylist(pattern)
351
                found_key = False
352
                for key in result:
353
                    found_key = True
354
                    self.acceptable_keys.append(key.subkeys[0].fpr)
355
                    trace.mutter("Added acceptable key: " + key.subkeys[0].fpr)
356
                if not found_key:
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
357
                    trace.note(gettext(
6123.1.15 by Jelmer Vernooij
fix format string
358
                            "No GnuPG key results for pattern: {0}"
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
359
                                ).format(pattern))
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
360
6491.1.4 by Jelmer Vernooij
Deprecate GPGStrategy.do_verifications.
361
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.83 by Jonathan Riddell
add a process_events_callback when verifying revisions so qbzr can keep the UI responsive
362
    def do_verifications(self, revisions, repository,
363
                            process_events_callback=None):
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
364
        """do verifications on a set of revisions
6491.1.1 by Jelmer Vernooij
Various cleanups related to GPG.
365
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
366
        :param revisions: list of revision ids to verify
367
        :param repository: repository object
5971.1.83 by Jonathan Riddell
add a process_events_callback when verifying revisions so qbzr can keep the UI responsive
368
        :param process_events_callback: method to call for GUI frontends that
6491.1.1 by Jelmer Vernooij
Various cleanups related to GPG.
369
            want to keep their UI refreshed
370
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
371
        :return: count dictionary of results of each type,
372
                 result list for each revision,
373
                 boolean True if all results are verified successfully
374
        """
6491.1.3 by Jelmer Vernooij
Make 'bzr verify-signatures' show a progress bar.
375
        return bulk_verify_signatures(repository, revisions, self,
376
            process_events_callback)
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
377
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
378
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
379
    def verbose_valid_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
380
        """takes a verify result and returns list of signed commits strings"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
381
        return verbose_valid_message(result)
382
383
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
384
    def verbose_not_valid_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
385
        """takes a verify result and returns list of not valid commit info"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
386
        return verbose_not_valid_message(result, repo)
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
387
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
388
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
389
    def verbose_not_signed_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
390
        """takes a verify result and returns list of not signed commit info"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
391
        return verbose_not_valid_message(result, repo)
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
392
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
393
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
394
    def verbose_missing_key_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
395
        """takes a verify result and returns list of missing key info"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
396
        return verbose_missing_key_message(result)
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
397
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
398
    @deprecated_method(deprecated_in((2, 6, 0)))
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
399
    def verbose_expired_key_message(self, result, repo):
400
        """takes a verify result and returns list of expired key info"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
401
        return verbose_expired_key_message(result, repo)
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
402
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
403
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
404
    def valid_commits_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
405
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
406
        return valid_commits_message(count)
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
407
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
408
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
409
    def unknown_key_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
410
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
411
        return unknown_key_message(count)
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
412
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
413
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
414
    def commit_not_valid_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
415
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
416
        return commit_not_valid_message(count)
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
417
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
418
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
419
    def commit_not_signed_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
420
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
421
        return commit_not_signed_message(count)
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
422
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
423
    @deprecated_method(deprecated_in((2, 6, 0)))
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
424
    def expired_commit_message(self, count):
425
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
426
        return expired_commit_message(count)
427
428
429
def valid_commits_message(count):
430
    """returns message for number of commits"""
431
    return gettext(u"{0} commits with valid signatures").format(
432
                                    count[SIGNATURE_VALID])
433
434
435
def unknown_key_message(count):
436
    """returns message for number of commits"""
437
    return ngettext(u"{0} commit with unknown key",
438
                    u"{0} commits with unknown keys",
439
                    count[SIGNATURE_KEY_MISSING]).format(
440
                                    count[SIGNATURE_KEY_MISSING])
441
442
443
def commit_not_valid_message(count):
444
    """returns message for number of commits"""
445
    return ngettext(u"{0} commit not valid",
446
                    u"{0} commits not valid",
447
                    count[SIGNATURE_NOT_VALID]).format(
448
                                        count[SIGNATURE_NOT_VALID])
449
450
451
def commit_not_signed_message(count):
452
    """returns message for number of commits"""
453
    return ngettext(u"{0} commit not signed",
454
                    u"{0} commits not signed",
455
                    count[SIGNATURE_NOT_SIGNED]).format(
456
                                    count[SIGNATURE_NOT_SIGNED])
457
458
459
def expired_commit_message(count):
460
    """returns message for number of commits"""
461
    return ngettext(u"{0} commit with key now expired",
462
                    u"{0} commits with key now expired",
463
                    count[SIGNATURE_EXPIRED]).format(
464
                                count[SIGNATURE_EXPIRED])
465
466
467
def verbose_expired_key_message(result, repo):
468
    """takes a verify result and returns list of expired key info"""
469
    signers = {}
470
    fingerprint_to_authors = {}
471
    for rev_id, validity, fingerprint in result:
472
        if validity == SIGNATURE_EXPIRED:
473
            revision = repo.get_revision(rev_id)
474
            authors = ', '.join(revision.get_apparent_authors())
475
            signers.setdefault(fingerprint, 0)
476
            signers[fingerprint] += 1
477
            fingerprint_to_authors[fingerprint] = authors
478
    result = []
479
    for fingerprint, number in signers.items():
480
        result.append(
481
            ngettext(u"{0} commit by author {1} with key {2} now expired",
482
                     u"{0} commits by author {1} with key {2} now expired",
483
                     number).format(
484
                number, fingerprint_to_authors[fingerprint], fingerprint))
485
    return result
486
487
488
def verbose_valid_message(result):
489
    """takes a verify result and returns list of signed commits strings"""
490
    signers = {}
491
    for rev_id, validity, uid in result:
492
        if validity == SIGNATURE_VALID:
493
            signers.setdefault(uid, 0)
494
            signers[uid] += 1
495
    result = []
496
    for uid, number in signers.items():
497
         result.append(ngettext(u"{0} signed {1} commit",
498
                                u"{0} signed {1} commits",
499
                                number).format(uid, number))
500
    return result
501
502
503
def verbose_not_valid_message(result, repo):
504
    """takes a verify result and returns list of not valid commit info"""
505
    signers = {}
506
    for rev_id, validity, empty in result:
507
        if validity == SIGNATURE_NOT_VALID:
508
            revision = repo.get_revision(rev_id)
509
            authors = ', '.join(revision.get_apparent_authors())
510
            signers.setdefault(authors, 0)
511
            signers[authors] += 1
512
    result = []
513
    for authors, number in signers.items():
514
        result.append(ngettext(u"{0} commit by author {1}",
515
                               u"{0} commits by author {1}",
516
                               number).format(number, authors))
517
    return result
518
519
520
def verbose_not_signed_message(result, repo):
521
    """takes a verify result and returns list of not signed commit info"""
522
    signers = {}
523
    for rev_id, validity, empty in result:
524
        if validity == SIGNATURE_NOT_SIGNED:
525
            revision = repo.get_revision(rev_id)
526
            authors = ', '.join(revision.get_apparent_authors())
527
            signers.setdefault(authors, 0)
528
            signers[authors] += 1
529
    result = []
530
    for authors, number in signers.items():
531
        result.append(ngettext(u"{0} commit by author {1}",
532
                               u"{0} commits by author {1}",
533
                               number).format(number, authors))
534
    return result
535
536
537
def verbose_missing_key_message(result):
538
    """takes a verify result and returns list of missing key info"""
539
    signers = {}
540
    for rev_id, validity, fingerprint in result:
541
        if validity == SIGNATURE_KEY_MISSING:
542
            signers.setdefault(fingerprint, 0)
543
            signers[fingerprint] += 1
544
    result = []
545
    for fingerprint, number in signers.items():
546
        result.append(ngettext(u"Unknown key {0} signed {1} commit",
547
                               u"Unknown key {0} signed {1} commits",
548
                               number).format(fingerprint, number))
549
    return result