~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
        """
6589.3.1 by Vincent Ladeuil
Fix command line override handling for acceptable_keys
339
        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
        if acceptable_keys_config is not None:
6589.3.1 by Vincent Ladeuil
Fix command line override handling for acceptable_keys
342
            patterns = acceptable_keys_config
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
343
        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
344
            patterns = command_line_input.split(',')
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
345
6589.3.1 by Vincent Ladeuil
Fix command line override handling for acceptable_keys
346
        if patterns:
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
347
            self.acceptable_keys = []
348
            for pattern in patterns:
349
                result = self.context.keylist(pattern)
350
                found_key = False
351
                for key in result:
352
                    found_key = True
353
                    self.acceptable_keys.append(key.subkeys[0].fpr)
354
                    trace.mutter("Added acceptable key: " + key.subkeys[0].fpr)
355
                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
356
                    trace.note(gettext(
6123.1.15 by Jelmer Vernooij
fix format string
357
                            "No GnuPG key results for pattern: {0}"
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
358
                                ).format(pattern))
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
359
6491.1.4 by Jelmer Vernooij
Deprecate GPGStrategy.do_verifications.
360
    @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
361
    def do_verifications(self, revisions, repository,
362
                            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
363
        """do verifications on a set of revisions
6491.1.1 by Jelmer Vernooij
Various cleanups related to GPG.
364
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
365
        :param revisions: list of revision ids to verify
366
        :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
367
        :param process_events_callback: method to call for GUI frontends that
6491.1.1 by Jelmer Vernooij
Various cleanups related to GPG.
368
            want to keep their UI refreshed
369
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
370
        :return: count dictionary of results of each type,
371
                 result list for each revision,
372
                 boolean True if all results are verified successfully
373
        """
6491.1.3 by Jelmer Vernooij
Make 'bzr verify-signatures' show a progress bar.
374
        return bulk_verify_signatures(repository, revisions, self,
375
            process_events_callback)
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
376
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
377
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
378
    def verbose_valid_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
379
        """takes a verify result and returns list of signed commits strings"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
380
        return verbose_valid_message(result)
381
382
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
383
    def verbose_not_valid_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
384
        """takes a verify result and returns list of not valid commit info"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
385
        return verbose_not_valid_message(result, repo)
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
386
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
387
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
388
    def verbose_not_signed_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
389
        """takes a verify result and returns list of not signed commit info"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
390
        return verbose_not_valid_message(result, repo)
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
391
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
392
    @deprecated_method(deprecated_in((2, 6, 0)))
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
393
    def verbose_missing_key_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
394
        """takes a verify result and returns list of missing key info"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
395
        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
396
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
397
    @deprecated_method(deprecated_in((2, 6, 0)))
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
398
    def verbose_expired_key_message(self, result, repo):
399
        """takes a verify result and returns list of expired key info"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
400
        return verbose_expired_key_message(result, repo)
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
401
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
402
    @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
403
    def valid_commits_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
404
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
405
        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
406
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
407
    @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
408
    def unknown_key_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
409
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
410
        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
411
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
412
    @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
413
    def commit_not_valid_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
414
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
415
        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
416
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
417
    @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
418
    def commit_not_signed_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
419
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
420
        return commit_not_signed_message(count)
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
421
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
422
    @deprecated_method(deprecated_in((2, 6, 0)))
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
423
    def expired_commit_message(self, count):
424
        """returns message for number of commits"""
6491.1.5 by Jelmer Vernooij
Add Repository.verify_revision_signatures.
425
        return expired_commit_message(count)
426
427
428
def valid_commits_message(count):
429
    """returns message for number of commits"""
430
    return gettext(u"{0} commits with valid signatures").format(
431
                                    count[SIGNATURE_VALID])
432
433
434
def unknown_key_message(count):
435
    """returns message for number of commits"""
436
    return ngettext(u"{0} commit with unknown key",
437
                    u"{0} commits with unknown keys",
438
                    count[SIGNATURE_KEY_MISSING]).format(
439
                                    count[SIGNATURE_KEY_MISSING])
440
441
442
def commit_not_valid_message(count):
443
    """returns message for number of commits"""
444
    return ngettext(u"{0} commit not valid",
445
                    u"{0} commits not valid",
446
                    count[SIGNATURE_NOT_VALID]).format(
447
                                        count[SIGNATURE_NOT_VALID])
448
449
450
def commit_not_signed_message(count):
451
    """returns message for number of commits"""
452
    return ngettext(u"{0} commit not signed",
453
                    u"{0} commits not signed",
454
                    count[SIGNATURE_NOT_SIGNED]).format(
455
                                    count[SIGNATURE_NOT_SIGNED])
456
457
458
def expired_commit_message(count):
459
    """returns message for number of commits"""
460
    return ngettext(u"{0} commit with key now expired",
461
                    u"{0} commits with key now expired",
462
                    count[SIGNATURE_EXPIRED]).format(
463
                                count[SIGNATURE_EXPIRED])
464
465
466
def verbose_expired_key_message(result, repo):
467
    """takes a verify result and returns list of expired key info"""
468
    signers = {}
469
    fingerprint_to_authors = {}
470
    for rev_id, validity, fingerprint in result:
471
        if validity == SIGNATURE_EXPIRED:
472
            revision = repo.get_revision(rev_id)
473
            authors = ', '.join(revision.get_apparent_authors())
474
            signers.setdefault(fingerprint, 0)
475
            signers[fingerprint] += 1
476
            fingerprint_to_authors[fingerprint] = authors
477
    result = []
478
    for fingerprint, number in signers.items():
479
        result.append(
480
            ngettext(u"{0} commit by author {1} with key {2} now expired",
481
                     u"{0} commits by author {1} with key {2} now expired",
482
                     number).format(
483
                number, fingerprint_to_authors[fingerprint], fingerprint))
484
    return result
485
486
487
def verbose_valid_message(result):
488
    """takes a verify result and returns list of signed commits strings"""
489
    signers = {}
490
    for rev_id, validity, uid in result:
491
        if validity == SIGNATURE_VALID:
492
            signers.setdefault(uid, 0)
493
            signers[uid] += 1
494
    result = []
495
    for uid, number in signers.items():
496
         result.append(ngettext(u"{0} signed {1} commit",
497
                                u"{0} signed {1} commits",
498
                                number).format(uid, number))
499
    return result
500
501
502
def verbose_not_valid_message(result, repo):
503
    """takes a verify result and returns list of not valid commit info"""
504
    signers = {}
505
    for rev_id, validity, empty in result:
506
        if validity == SIGNATURE_NOT_VALID:
507
            revision = repo.get_revision(rev_id)
508
            authors = ', '.join(revision.get_apparent_authors())
509
            signers.setdefault(authors, 0)
510
            signers[authors] += 1
511
    result = []
512
    for authors, number in signers.items():
513
        result.append(ngettext(u"{0} commit by author {1}",
514
                               u"{0} commits by author {1}",
515
                               number).format(number, authors))
516
    return result
517
518
519
def verbose_not_signed_message(result, repo):
520
    """takes a verify result and returns list of not signed commit info"""
521
    signers = {}
522
    for rev_id, validity, empty in result:
523
        if validity == SIGNATURE_NOT_SIGNED:
524
            revision = repo.get_revision(rev_id)
525
            authors = ', '.join(revision.get_apparent_authors())
526
            signers.setdefault(authors, 0)
527
            signers[authors] += 1
528
    result = []
529
    for authors, number in signers.items():
530
        result.append(ngettext(u"{0} commit by author {1}",
531
                               u"{0} commits by author {1}",
532
                               number).format(number, authors))
533
    return result
534
535
536
def verbose_missing_key_message(result):
537
    """takes a verify result and returns list of missing key info"""
538
    signers = {}
539
    for rev_id, validity, fingerprint in result:
540
        if validity == SIGNATURE_KEY_MISSING:
541
            signers.setdefault(fingerprint, 0)
542
            signers[fingerprint] += 1
543
    result = []
544
    for fingerprint, number in signers.items():
545
        result.append(ngettext(u"Unknown key {0} signed {1} commit",
546
                               u"Unknown key {0} signed {1} commits",
547
                               number).format(fingerprint, number))
548
    return result