~bzr-pqm/bzr/bzr.dev

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