~bzr-pqm/bzr/bzr.dev

5971.1.36 by Jonathan Riddell
update copyright
1
# Copyright (C) 2005, 2011 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()
6043.2.5 by Jonathan Riddell
catch a revoked key and add test for it
325
        if result[0].summary & gpgme.SIGSUM_SYS_ERROR:
326
            return SIGNATURE_NOT_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
327
        # Other error types such as revoked keys should (I think) be caught by
328
        # SIGSUM_RED so anything else means something is buggy.
5971.1.42 by Jonathan Riddell
fix string formatting
329
        raise errors.SignatureVerificationFailed("Unknown GnuPG key "\
330
                                                 "verification result")
5971.1.11 by Jonathan Riddell
add set_acceptable_keys() so user can specify which gpg keys can be used for verification
331
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
332
    def set_acceptable_keys(self, command_line_input):
6351.3.2 by Jelmer Vernooij
Convert some gpg options to config stacks.
333
        """Set the acceptable keys for verifying with this GPGStrategy.
6491.1.1 by Jelmer Vernooij
Various cleanups related to GPG.
334
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
335
        :param command_line_input: comma separated list of patterns from
336
                                command line
337
        :return: nothing
338
        """
339
        key_patterns = None
6351.3.2 by Jelmer Vernooij
Convert some gpg options to config stacks.
340
        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
341
        try:
342
            if isinstance(acceptable_keys_config, unicode):
343
                acceptable_keys_config = str(acceptable_keys_config)
344
        except UnicodeEncodeError:
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
345
            # gpg Context.keylist(pattern) does not like unicode
346
            raise errors.BzrCommandError(
347
                gettext('Only ASCII permitted in option names'))
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
348
349
        if acceptable_keys_config is not None:
350
            key_patterns = acceptable_keys_config
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
351
        if command_line_input is not None: # command line overrides config
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
352
            key_patterns = command_line_input
353
        if key_patterns is not None:
354
            patterns = key_patterns.split(",")
355
356
            self.acceptable_keys = []
357
            for pattern in patterns:
358
                result = self.context.keylist(pattern)
359
                found_key = False
360
                for key in result:
361
                    found_key = True
362
                    self.acceptable_keys.append(key.subkeys[0].fpr)
363
                    trace.mutter("Added acceptable key: " + key.subkeys[0].fpr)
364
                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
365
                    trace.note(gettext(
6123.1.15 by Jelmer Vernooij
fix format string
366
                            "No GnuPG key results for pattern: {0}"
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
367
                                ).format(pattern))
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
368
6491.1.4 by Jelmer Vernooij
Deprecate GPGStrategy.do_verifications.
369
    @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
370
    def do_verifications(self, revisions, repository,
371
                            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
372
        """do verifications on a set of revisions
6491.1.1 by Jelmer Vernooij
Various cleanups related to GPG.
373
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
374
        :param revisions: list of revision ids to verify
375
        :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
376
        :param process_events_callback: method to call for GUI frontends that
6491.1.1 by Jelmer Vernooij
Various cleanups related to GPG.
377
            want to keep their UI refreshed
378
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
379
        :return: count dictionary of results of each type,
380
                 result list for each revision,
381
                 boolean True if all results are verified successfully
382
        """
6491.1.3 by Jelmer Vernooij
Make 'bzr verify-signatures' show a progress bar.
383
        return bulk_verify_signatures(repository, revisions, self,
384
            process_events_callback)
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
385
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
386
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
387
    def verbose_valid_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
388
        """takes a verify result and returns list of signed commits strings"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
389
        return verbose_valid_message(result)
390
391
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
392
    def verbose_not_valid_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
393
        """takes a verify result and returns list of not valid commit info"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
394
        return verbose_not_valid_message(result, repo)
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
395
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
396
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
397
    def verbose_not_signed_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
398
        """takes a verify result and returns list of not signed commit info"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
399
        return verbose_not_valid_message(result, repo)
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
400
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
401
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
402
    def verbose_missing_key_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
403
        """takes a verify result and returns list of missing key info"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
404
        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
405
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
406
    @deprecated_method(deprecated_in((2, 6, 0)))
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
407
    def verbose_expired_key_message(self, result, repo):
408
        """takes a verify result and returns list of expired key info"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
409
        return verbose_expired_key_message(result, repo)
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
410
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
411
    @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
412
    def valid_commits_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
413
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
414
        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
415
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
416
    @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
417
    def unknown_key_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
418
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
419
        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
420
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
421
    @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
422
    def commit_not_valid_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
423
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
424
        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
425
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
426
    @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
427
    def commit_not_signed_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
428
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
429
        return commit_not_signed_message(count)
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
430
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
431
    @deprecated_method(deprecated_in((2, 6, 0)))
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
432
    def expired_commit_message(self, count):
433
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
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