/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
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
18
"""GPG signing and checking logic."""
19
1996.3.1 by John Arbash Meinel
Demandloading builtins.py drops our load time from 350ms to 291ms
20
import os
21
import sys
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
22
from StringIO import StringIO
1996.3.1 by John Arbash Meinel
Demandloading builtins.py drops our load time from 350ms to 291ms
23
24
from bzrlib.lazy_import import lazy_import
25
lazy_import(globals(), """
1442.1.58 by Robert Collins
gpg signing of content
26
import errno
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
27
import subprocess
28
1912.3.2 by John Arbash Meinel
Adding some logging, because on my machine TTY is not exported by default.
29
from bzrlib import (
6372.1.3 by Vincent Ladeuil
Fix gpg_signing_ket default values handling
30
    config,
1912.3.2 by John Arbash Meinel
Adding some logging, because on my machine TTY is not exported by default.
31
    errors,
32
    trace,
1551.8.11 by Aaron Bentley
Clear terminal before signing
33
    ui,
1912.3.2 by John Arbash Meinel
Adding some logging, because on my machine TTY is not exported by default.
34
    )
6092.2.3 by Jonathan Riddell
improve formatting
35
from bzrlib.i18n import (
36
    gettext, 
37
    ngettext,
38
    )
1996.3.1 by John Arbash Meinel
Demandloading builtins.py drops our load time from 350ms to 291ms
39
""")
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
40
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
41
#verification results
5971.1.1 by Jonathan Riddell
add a verify command
42
SIGNATURE_VALID = 0
43
SIGNATURE_KEY_MISSING = 1
44
SIGNATURE_NOT_VALID = 2
45
SIGNATURE_NOT_SIGNED = 3
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
46
SIGNATURE_EXPIRED = 4
5971.1.1 by Jonathan Riddell
add a verify command
47
48
1442.1.62 by Robert Collins
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.
49
class DisabledGPGStrategy(object):
50
    """A GPG Strategy that makes everything fail."""
51
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
52
    @staticmethod
53
    def verify_signatures_available():
54
        return True
55
1442.1.62 by Robert Collins
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.
56
    def __init__(self, ignored):
57
        """Real strategies take a configuration."""
58
59
    def sign(self, content):
60
        raise errors.SigningFailed('Signing is disabled.')
61
5971.1.31 by Jonathan Riddell
and update tests
62
    def verify(self, content, testament):
5971.1.33 by Jonathan Riddell
rename errors.VerifyFailed to errors.SignatureVerificationFailed
63
        raise errors.SignatureVerificationFailed('Signature verification is \
64
disabled.')
5971.1.6 by Jonathan Riddell
fix methods for dummy gpg strategies
65
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
66
    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
67
        pass
68
1442.1.62 by Robert Collins
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.
69
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
70
class LoopbackGPGStrategy(object):
5971.1.85 by Jonathan Riddell
use unicode strings for UI
71
    """A GPG Strategy that acts like 'cat' - data is just passed through.
5971.1.86 by Jonathan Riddell
doc string formatting
72
    Used in tests.
73
    """
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
74
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
75
    @staticmethod
76
    def verify_signatures_available():
77
        return True
78
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
79
    def __init__(self, ignored):
80
        """Real strategies take a configuration."""
81
82
    def sign(self, content):
1551.12.15 by Aaron Bentley
add header/trailer to fake clearsigned texts
83
        return ("-----BEGIN PSEUDO-SIGNED CONTENT-----\n" + content +
1551.12.52 by Aaron Bentley
speling fix
84
                "-----END PSEUDO-SIGNED CONTENT-----\n")
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
85
5971.1.31 by Jonathan Riddell
and update tests
86
    def verify(self, content, testament):
5971.1.22 by Jonathan Riddell
fix tests
87
        return SIGNATURE_VALID, None
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
88
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
89
    def set_acceptable_keys(self, command_line_input):
90
        if command_line_input is not None:
91
            patterns = command_line_input.split(",")
92
            self.acceptable_keys = []
93
            for pattern in patterns:
94
                if pattern == "unknown":
95
                    pass
96
                else:
97
                    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
98
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
99
    def do_verifications(self, revisions, repository):
100
        count = {SIGNATURE_VALID: 0,
101
                 SIGNATURE_KEY_MISSING: 0,
102
                 SIGNATURE_NOT_VALID: 0,
6043.2.10 by Jonathan Riddell
fix test test_verify_commits_acceptable_key
103
                 SIGNATURE_NOT_SIGNED: 0,
104
                 SIGNATURE_EXPIRED: 0}
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
105
        result = []
106
        all_verifiable = True
107
        for rev_id in revisions:
108
            verification_result, uid =\
6257.3.1 by Jelmer Vernooij
Support verifying remote signatures.
109
                repository.verify_revision_signature(rev_id,self)
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
110
            result.append([rev_id, verification_result, uid])
111
            count[verification_result] += 1
112
            if verification_result != SIGNATURE_VALID:
113
                all_verifiable = False
114
        return (count, result, all_verifiable)
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
115
116
    def valid_commits_message(self, count):
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
117
        return gettext(u"{0} commits with valid signatures").format(
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
118
                                        count[SIGNATURE_VALID])
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
119
120
    def unknown_key_message(self, count):
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
121
        return ngettext(u"{0} commit with unknown key",
5971.1.85 by Jonathan Riddell
use unicode strings for UI
122
                             u"{0} commits with unknown keys",
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
123
                             count[SIGNATURE_KEY_MISSING]).format(
124
                                        count[SIGNATURE_KEY_MISSING])
125
126
    def commit_not_valid_message(self, count):
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
127
        return ngettext(u"{0} commit not valid",
5971.1.85 by Jonathan Riddell
use unicode strings for UI
128
                             u"{0} commits not valid",
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
129
                             count[SIGNATURE_NOT_VALID]).format(
130
                                            count[SIGNATURE_NOT_VALID])
131
132
    def commit_not_signed_message(self, count):
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
133
        return ngettext(u"{0} commit not signed",
5971.1.85 by Jonathan Riddell
use unicode strings for UI
134
                             u"{0} commits not signed",
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
135
                             count[SIGNATURE_NOT_SIGNED]).format(
136
                                        count[SIGNATURE_NOT_SIGNED])
137
6043.3.3 by Jonathan Riddell
add expired_commit_message() to LoopbackGPGStrategy
138
    def expired_commit_message(self, count):
6092.2.7 by Jonathan Riddell
i18n.gettext() -> gettext()
139
        return ngettext(u"{0} commit with key now expired",
140
                        u"{0} commits with key now expired",
141
                        count[SIGNATURE_EXPIRED]).format(
6043.3.3 by Jonathan Riddell
add expired_commit_message() to LoopbackGPGStrategy
142
                                        count[SIGNATURE_EXPIRED])
143
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
144
1912.3.1 by John Arbash Meinel
updating gpg.py to set GPG_TTY in the environment.
145
def _set_gpg_tty():
146
    tty = os.environ.get('TTY')
147
    if tty is not None:
148
        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.
149
        trace.mutter('setting GPG_TTY=%s', tty)
150
    else:
151
        # This is not quite worthy of a warning, because some people
152
        # don't need GPG_TTY to be set. But it is worthy of a big mark
153
        # in ~/.bzr.log, so that people can debug it if it happens to them
154
        trace.mutter('** Env var TTY empty, cannot set GPG_TTY.'
155
                     '  Is TTY exported?')
1912.3.1 by John Arbash Meinel
updating gpg.py to set GPG_TTY in the environment.
156
157
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
158
class GPGStrategy(object):
159
    """GPG Signing and checking facilities."""
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
160
5971.1.11 by Jonathan Riddell
add set_acceptable_keys() so user can specify which gpg keys can be used for verification
161
    acceptable_keys = None
162
6351.3.9 by Vincent Ladeuil
Move __init__ at the beginning of the class since that's the current idiom.
163
    def __init__(self, config_stack):
164
        self._config_stack = config_stack
165
        try:
166
            import gpgme
167
            self.context = gpgme.Context()
168
        except ImportError, error:
169
            pass # can't use verify()
170
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
171
    @staticmethod
172
    def verify_signatures_available():
5971.1.82 by Jonathan Riddell
method doc
173
        """
5971.1.86 by Jonathan Riddell
doc string formatting
174
        check if this strategy can verify signatures
175
5971.1.82 by Jonathan Riddell
method doc
176
        :return: boolean if this strategy can verify signatures
177
        """
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
178
        try:
179
            import gpgme
180
            return True
181
        except ImportError, error:
182
            return False
183
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
184
    def _command_line(self):
6372.3.1 by Jelmer Vernooij
Clumsily fix gpg_signing_key.
185
        key = self._config_stack.get('gpg_signing_key')
6372.1.3 by Vincent Ladeuil
Fix gpg_signing_ket default values handling
186
        if key is None or key == 'default':
6372.1.4 by Vincent Ladeuil
Add a comment.
187
            # 'default' or not setting gpg_signing_key at all means we should
188
            # use the user email address
6372.1.3 by Vincent Ladeuil
Fix gpg_signing_ket default values handling
189
            key = config.extract_email_address(self._config_stack.get('email'))
6351.3.2 by Jelmer Vernooij
Convert some gpg options to config stacks.
190
        return [self._config_stack.get('gpg_signing_command'), '--clearsign',
6372.3.1 by Jelmer Vernooij
Clumsily fix gpg_signing_key.
191
                '-u', key]
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
192
1442.1.58 by Robert Collins
gpg signing of content
193
    def sign(self, content):
2273.1.1 by John Arbash Meinel
``GPGStrategy.sign()`` will now raise ``BzrBadParameterUnicode`` if
194
        if isinstance(content, unicode):
195
            raise errors.BzrBadParameterUnicode('content')
1551.8.11 by Aaron Bentley
Clear terminal before signing
196
        ui.ui_factory.clear_term()
1963.1.8 by John Arbash Meinel
Don't use preexec_fn on win32
197
198
        preexec_fn = _set_gpg_tty
199
        if sys.platform == 'win32':
200
            # Win32 doesn't support preexec_fn, but wouldn't support TTY anyway.
201
            preexec_fn = None
1442.1.58 by Robert Collins
gpg signing of content
202
        try:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
203
            process = subprocess.Popen(self._command_line(),
204
                                       stdin=subprocess.PIPE,
1912.3.1 by John Arbash Meinel
updating gpg.py to set GPG_TTY in the environment.
205
                                       stdout=subprocess.PIPE,
1963.1.8 by John Arbash Meinel
Don't use preexec_fn on win32
206
                                       preexec_fn=preexec_fn)
1442.1.58 by Robert Collins
gpg signing of content
207
            try:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
208
                result = process.communicate(content)[0]
1442.1.58 by Robert Collins
gpg signing of content
209
                if process.returncode is None:
210
                    process.wait()
211
                if process.returncode != 0:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
212
                    raise errors.SigningFailed(self._command_line())
1442.1.58 by Robert Collins
gpg signing of content
213
                return result
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
214
            except OSError, e:
1442.1.58 by Robert Collins
gpg signing of content
215
                if e.errno == errno.EPIPE:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
216
                    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.
217
                else:
218
                    raise
1442.1.58 by Robert Collins
gpg signing of content
219
        except ValueError:
220
            # bad subprocess parameters, should never happen.
221
            raise
222
        except OSError, e:
223
            if e.errno == errno.ENOENT:
224
                # gpg is not installed
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
225
                raise errors.SigningFailed(self._command_line())
1442.1.58 by Robert Collins
gpg signing of content
226
            else:
227
                raise
5971.1.1 by Jonathan Riddell
add a verify command
228
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
229
    def verify(self, content, testament):
5971.1.7 by Jonathan Riddell
add method docs
230
        """Check content has a valid signature.
231
        
232
        :param content: the commit signature
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
233
        :param testament: the valid testament string for the commit
5971.1.7 by Jonathan Riddell
add method docs
234
        
5971.1.18 by Jonathan Riddell
add email to verbose output
235
        :return: SIGNATURE_VALID or a failed SIGNATURE_ value, key uid if valid
5971.1.7 by Jonathan Riddell
add method docs
236
        """
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
237
        try:
238
            import gpgme
5971.1.41 by Jonathan Riddell
fix calling GpgmeNotInstalled
239
        except ImportError, error:
240
            raise errors.GpgmeNotInstalled(error)
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
241
5971.1.1 by Jonathan Riddell
add a verify command
242
        signature = StringIO(content)
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
243
        plain_output = StringIO()
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
244
        try:
5971.1.61 by Jonathan Riddell
make gpgme context global to class
245
            result = self.context.verify(signature, None, plain_output)
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
246
        except gpgme.GpgmeError,error:
5971.1.33 by Jonathan Riddell
rename errors.VerifyFailed to errors.SignatureVerificationFailed
247
            raise errors.SignatureVerificationFailed(error[2])
5971.1.1 by Jonathan Riddell
add a verify command
248
6043.2.15 by Jonathan Riddell
turn comments into sentences
249
        # No result if input is invalid.
250
        # test_verify_invalid()
5971.1.9 by Jonathan Riddell
add some tests
251
        if len(result) == 0:
5971.1.17 by Jonathan Riddell
add verbose option
252
            return SIGNATURE_NOT_VALID, None
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
253
        # User has specified a list of acceptable keys, check our result is in
254
        # it.  test_verify_unacceptable_key()
5971.1.13 by Jonathan Riddell
return missing if not in acceptable keys
255
        fingerprint = result[0].fpr
256
        if self.acceptable_keys is not None:
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
257
            if not fingerprint in self.acceptable_keys:
5971.1.27 by Jonathan Riddell
verbose info for unknown keys
258
                return SIGNATURE_KEY_MISSING, fingerprint[-8:]
6043.2.15 by Jonathan Riddell
turn comments into sentences
259
        # Check the signature actually matches the testament.
260
        # test_verify_bad_testament()
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
261
        if testament != plain_output.getvalue():
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
262
            return SIGNATURE_NOT_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
263
        # Yay gpgme set the valid bit.
264
        # Can't write a test for this one as you can't set a key to be
265
        # trusted using gpgme.
5971.1.1 by Jonathan Riddell
add a verify command
266
        if result[0].summary & gpgme.SIGSUM_VALID:
5971.1.61 by Jonathan Riddell
make gpgme context global to class
267
            key = self.context.get_key(fingerprint)
5971.1.17 by Jonathan Riddell
add verbose option
268
            name = key.uids[0].name
5971.1.18 by Jonathan Riddell
add email to verbose output
269
            email = key.uids[0].email
270
            return SIGNATURE_VALID, name + " <" + email + ">"
6043.2.15 by Jonathan Riddell
turn comments into sentences
271
        # Sigsum_red indicates a problem, unfortunatly I have not been able
272
        # to write any tests which actually set this.
5971.1.1 by Jonathan Riddell
add a verify command
273
        if result[0].summary & gpgme.SIGSUM_RED:
5971.1.17 by Jonathan Riddell
add verbose option
274
            return SIGNATURE_NOT_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
275
        # GPG does not know this key.
276
        # test_verify_unknown_key()
5971.1.1 by Jonathan Riddell
add a verify command
277
        if result[0].summary & gpgme.SIGSUM_KEY_MISSING:
5971.1.27 by Jonathan Riddell
verbose info for unknown keys
278
            return SIGNATURE_KEY_MISSING, fingerprint[-8:]
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
279
        # Summary isn't set if sig is valid but key is untrusted but if user
280
        # has explicity set the key as acceptable we can validate it.
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
281
        if result[0].summary == 0 and self.acceptable_keys is not None:
282
            if fingerprint in self.acceptable_keys:
6043.2.15 by Jonathan Riddell
turn comments into sentences
283
                # test_verify_untrusted_but_accepted()
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
284
                return SIGNATURE_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
285
        # test_verify_valid_but_untrusted()
6043.2.7 by Jonathan Riddell
some reordering of verification, improve names of tests
286
        if result[0].summary == 0 and self.acceptable_keys is None:
287
            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
288
        if result[0].summary & gpgme.SIGSUM_KEY_EXPIRED:
6043.2.8 by Jonathan Riddell
add test for unknown key
289
            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
290
            if expires > result[0].timestamp:
6043.2.15 by Jonathan Riddell
turn comments into sentences
291
                # The expired key was not expired at time of signing.
292
                # test_verify_expired_but_valid()
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
293
                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
294
            else:
6043.2.15 by Jonathan Riddell
turn comments into sentences
295
                # I can't work out how to create a test where the signature
296
                # 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
297
                return SIGNATURE_NOT_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
298
        # A signature from a revoked key gets this.
299
        # test_verify_revoked_signature()
6043.2.5 by Jonathan Riddell
catch a revoked key and add test for it
300
        if result[0].summary & gpgme.SIGSUM_SYS_ERROR:
301
            return SIGNATURE_NOT_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
302
        # Other error types such as revoked keys should (I think) be caught by
303
        # SIGSUM_RED so anything else means something is buggy.
5971.1.42 by Jonathan Riddell
fix string formatting
304
        raise errors.SignatureVerificationFailed("Unknown GnuPG key "\
305
                                                 "verification result")
5971.1.11 by Jonathan Riddell
add set_acceptable_keys() so user can specify which gpg keys can be used for verification
306
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
307
    def set_acceptable_keys(self, command_line_input):
6351.3.2 by Jelmer Vernooij
Convert some gpg options to config stacks.
308
        """Set the acceptable keys for verifying with this GPGStrategy.
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
309
        
310
        :param command_line_input: comma separated list of patterns from
311
                                command line
312
        :return: nothing
313
        """
314
        key_patterns = None
6351.3.2 by Jelmer Vernooij
Convert some gpg options to config stacks.
315
        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
316
        try:
317
            if isinstance(acceptable_keys_config, unicode):
318
                acceptable_keys_config = str(acceptable_keys_config)
319
        except UnicodeEncodeError:
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
320
            # gpg Context.keylist(pattern) does not like unicode
321
            raise errors.BzrCommandError(
322
                gettext('Only ASCII permitted in option names'))
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
323
324
        if acceptable_keys_config is not None:
325
            key_patterns = acceptable_keys_config
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
326
        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
327
            key_patterns = command_line_input
328
        if key_patterns is not None:
329
            patterns = key_patterns.split(",")
330
331
            self.acceptable_keys = []
332
            for pattern in patterns:
333
                result = self.context.keylist(pattern)
334
                found_key = False
335
                for key in result:
336
                    found_key = True
337
                    self.acceptable_keys.append(key.subkeys[0].fpr)
338
                    trace.mutter("Added acceptable key: " + key.subkeys[0].fpr)
339
                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
340
                    trace.note(gettext(
6123.1.15 by Jelmer Vernooij
fix format string
341
                            "No GnuPG key results for pattern: {0}"
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
342
                                ).format(pattern))
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
343
5971.1.83 by Jonathan Riddell
add a process_events_callback when verifying revisions so qbzr can keep the UI responsive
344
    def do_verifications(self, revisions, repository,
345
                            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
346
        """do verifications on a set of revisions
347
        
348
        :param revisions: list of revision ids to verify
349
        :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
350
        :param process_events_callback: method to call for GUI frontends that
351
                                                want to keep their UI refreshed
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
352
        
353
        :return: count dictionary of results of each type,
354
                 result list for each revision,
355
                 boolean True if all results are verified successfully
356
        """
357
        count = {SIGNATURE_VALID: 0,
358
                 SIGNATURE_KEY_MISSING: 0,
359
                 SIGNATURE_NOT_VALID: 0,
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
360
                 SIGNATURE_NOT_SIGNED: 0,
361
                 SIGNATURE_EXPIRED: 0}
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
362
        result = []
363
        all_verifiable = True
364
        for rev_id in revisions:
365
            verification_result, uid =\
6257.3.1 by Jelmer Vernooij
Support verifying remote signatures.
366
                repository.verify_revision_signature(rev_id, self)
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
367
            result.append([rev_id, verification_result, uid])
368
            count[verification_result] += 1
369
            if verification_result != SIGNATURE_VALID:
370
                all_verifiable = False
5971.1.83 by Jonathan Riddell
add a process_events_callback when verifying revisions so qbzr can keep the UI responsive
371
            if process_events_callback is not None:
372
                process_events_callback()
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
373
        return (count, result, all_verifiable)
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
374
375
    def verbose_valid_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
376
        """takes a verify result and returns list of signed commits strings"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
377
        signers = {}
378
        for rev_id, validity, uid in result:
379
            if validity == SIGNATURE_VALID:
380
                signers.setdefault(uid, 0)
381
                signers[uid] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
382
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
383
        for uid, number in signers.items():
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
384
             result.append( ngettext(u"{0} signed {1} commit",
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
385
                             u"{0} signed {1} commits",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
386
                             number).format(uid, number) )
387
        return result
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
388
389
390
    def verbose_not_valid_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
391
        """takes a verify result and returns list of not valid commit info"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
392
        signers = {}
393
        for rev_id, validity, empty in result:
394
            if validity == SIGNATURE_NOT_VALID:
395
                revision = repo.get_revision(rev_id)
396
                authors = ', '.join(revision.get_apparent_authors())
397
                signers.setdefault(authors, 0)
398
                signers[authors] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
399
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
400
        for authors, number in signers.items():
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
401
            result.append( ngettext(u"{0} commit by author {1}",
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
402
                                 u"{0} commits by author {1}",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
403
                                 number).format(number, authors) )
404
        return result
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
405
406
    def verbose_not_signed_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
407
        """takes a verify result and returns list of not signed commit info"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
408
        signers = {}
409
        for rev_id, validity, empty in result:
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
410
            if validity == SIGNATURE_NOT_SIGNED:
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
411
                revision = repo.get_revision(rev_id)
412
                authors = ', '.join(revision.get_apparent_authors())
413
                signers.setdefault(authors, 0)
414
                signers[authors] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
415
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
416
        for authors, number in signers.items():
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
417
            result.append( ngettext(u"{0} commit by author {1}",
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
418
                                 u"{0} commits by author {1}",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
419
                                 number).format(number, authors) )
420
        return result
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
421
422
    def verbose_missing_key_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
423
        """takes a verify result and returns list of missing key info"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
424
        signers = {}
425
        for rev_id, validity, fingerprint in result:
426
            if validity == SIGNATURE_KEY_MISSING:
427
                signers.setdefault(fingerprint, 0)
428
                signers[fingerprint] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
429
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
430
        for fingerprint, number in signers.items():
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
431
            result.append( ngettext(u"Unknown key {0} signed {1} commit",
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
432
                                 u"Unknown key {0} signed {1} commits",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
433
                                 number).format(fingerprint, number) )
434
        return result
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
435
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
436
    def verbose_expired_key_message(self, result, repo):
437
        """takes a verify result and returns list of expired key info"""
438
        signers = {}
439
        fingerprint_to_authors = {}
440
        for rev_id, validity, fingerprint in result:
441
            if validity == SIGNATURE_EXPIRED:
442
                revision = repo.get_revision(rev_id)
443
                authors = ', '.join(revision.get_apparent_authors())
444
                signers.setdefault(fingerprint, 0)
445
                signers[fingerprint] += 1
446
                fingerprint_to_authors[fingerprint] = authors
447
        result = []
448
        for fingerprint, number in signers.items():
6372.1.1 by Vincent Ladeuil
Remove spurious spaces.
449
            result.append(
450
                ngettext(u"{0} commit by author {1} with key {2} now expired",
451
                         u"{0} commits by author {1} with key {2} now expired",
452
                         number).format(
453
                    number, fingerprint_to_authors[fingerprint], fingerprint) )
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
454
        return result
455
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
456
    def valid_commits_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
457
        """returns message for number of commits"""
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
458
        return gettext(u"{0} commits with valid signatures").format(
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
459
                                        count[SIGNATURE_VALID])
460
461
    def unknown_key_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
462
        """returns message for number of commits"""
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
463
        return ngettext(u"{0} commit with unknown key",
464
                        u"{0} commits with unknown keys",
465
                        count[SIGNATURE_KEY_MISSING]).format(
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
466
                                        count[SIGNATURE_KEY_MISSING])
467
468
    def commit_not_valid_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
469
        """returns message for number of commits"""
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
470
        return ngettext(u"{0} commit not valid",
471
                        u"{0} commits not valid",
472
                        count[SIGNATURE_NOT_VALID]).format(
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
473
                                            count[SIGNATURE_NOT_VALID])
474
475
    def commit_not_signed_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
476
        """returns message for number of commits"""
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
477
        return ngettext(u"{0} commit not signed",
478
                        u"{0} commits not signed",
479
                        count[SIGNATURE_NOT_SIGNED]).format(
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
480
                                        count[SIGNATURE_NOT_SIGNED])
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
481
482
    def expired_commit_message(self, count):
483
        """returns message for number of commits"""
6092.2.7 by Jonathan Riddell
i18n.gettext() -> gettext()
484
        return ngettext(u"{0} commit with key now expired",
485
                        u"{0} commits with key now expired",
486
                        count[SIGNATURE_EXPIRED]).format(
487
                                    count[SIGNATURE_EXPIRED])