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