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