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