Merge lp:~jelmer/brz/gpg into lp:brz

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merge reported by: The Breezy Bot
Merged at revision: not available
Proposed branch: lp:~jelmer/brz/gpg
Merge into: lp:brz
Diff against target: 782 lines (+138/-241)
11 files modified
breezy/config.py (+0/-17)
breezy/errors.py (+0/-24)
breezy/gpg.py (+98/-81)
breezy/help_topics/en/configuration.txt (+0/-10)
breezy/tests/blackbox/test_log.py (+5/-2)
breezy/tests/features.py (+3/-1)
breezy/tests/test_commit.py (+1/-5)
breezy/tests/test_config.py (+0/-2)
breezy/tests/test_gpg.py (+24/-97)
breezy/tests/test_merge_directive.py (+0/-2)
doc/en/release-notes/brz-3.0.txt (+7/-0)
To merge this branch: bzr merge lp:~jelmer/brz/gpg
Reviewer Review Type Date Requested Status
Martin Packman Approve
Review via email: mp+326815@code.launchpad.net

This proposal supersedes a proposal from 2017-07-04.

Commit message

Switch to using python-gpg (part of gpgme) rather than deprecated python-gpgme.

Description of the change

Switch to using python-gpg (part of gpgme) rather than deprecated python-gpgme.

Also, switch to using gpgme for signing, while we're at it.

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Changes look good mostly, a couple of comments inline.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'breezy/config.py'
--- breezy/config.py 2017-06-15 01:07:35 +0000
+++ breezy/config.py 2017-07-04 21:30:36 +0000
@@ -27,7 +27,6 @@
27email=Your Name <your@email.address>27email=Your Name <your@email.address>
28check_signatures=require|ignore|check-available(default)28check_signatures=require|ignore|check-available(default)
29create_signatures=always|never|when-required(default)29create_signatures=always|never|when-required(default)
30gpg_signing_command=name-of-program
31log_format=name-of-format30log_format=name-of-format
32validate_signatures_in_log=true|false(default)31validate_signatures_in_log=true|false(default)
33acceptable_keys=pattern1,pattern232acceptable_keys=pattern1,pattern2
@@ -798,10 +797,6 @@
798 else:797 else:
799 return None798 return None
800799
801 def _gpg_signing_command(self):
802 """See Config.gpg_signing_command."""
803 return self._get_user_option('gpg_signing_command')
804
805 def _log_format(self):800 def _log_format(self):
806 """See Config.log_format."""801 """See Config.log_format."""
807 return self._get_user_option('log_format')802 return self._get_user_option('log_format')
@@ -1314,10 +1309,6 @@
1314 def remove_user_option(self, option_name, section_name=None):1309 def remove_user_option(self, option_name, section_name=None):
1315 self._get_branch_data_config().remove_option(option_name, section_name)1310 self._get_branch_data_config().remove_option(option_name, section_name)
13161311
1317 def _gpg_signing_command(self):
1318 """See Config.gpg_signing_command."""
1319 return self._get_safe_value('_gpg_signing_command')
1320
1321 def _post_commit(self):1312 def _post_commit(self):
1322 """See Config.post_commit."""1313 """See Config.post_commit."""
1323 return self._get_safe_value('_post_commit')1314 return self._get_safe_value('_post_commit')
@@ -2706,14 +2697,6 @@
2706 Option('email', override_from_env=['BRZ_EMAIL'], default=default_email,2697 Option('email', override_from_env=['BRZ_EMAIL'], default=default_email,
2707 help='The users identity'))2698 help='The users identity'))
2708option_registry.register(2699option_registry.register(
2709 Option('gpg_signing_command',
2710 default='gpg',
2711 help="""\
2712Program to use use for creating signatures.
2713
2714This should support at least the -u and --clearsign options.
2715"""))
2716option_registry.register(
2717 Option('gpg_signing_key',2700 Option('gpg_signing_key',
2718 default=None,2701 default=None,
2719 help="""\2702 help="""\
27202703
=== modified file 'breezy/errors.py'
--- breezy/errors.py 2017-06-10 18:39:27 +0000
+++ breezy/errors.py 2017-07-04 21:30:36 +0000
@@ -1771,22 +1771,6 @@
1771 self.username = username1771 self.username = username
17721772
17731773
1774class SigningFailed(BzrError):
1775
1776 _fmt = 'Failed to GPG sign data with command "%(command_line)s"'
1777
1778 def __init__(self, command_line):
1779 BzrError.__init__(self, command_line=command_line)
1780
1781
1782class SignatureVerificationFailed(BzrError):
1783
1784 _fmt = 'Failed to verify GPG signature data with error "%(error)s"'
1785
1786 def __init__(self, error):
1787 BzrError.__init__(self, error=error)
1788
1789
1790class DependencyNotPresent(BzrError):1774class DependencyNotPresent(BzrError):
17911775
1792 _fmt = 'Unable to import library "%(library)s": %(error)s'1776 _fmt = 'Unable to import library "%(library)s": %(error)s'
@@ -1795,14 +1779,6 @@
1795 BzrError.__init__(self, library=library, error=error)1779 BzrError.__init__(self, library=library, error=error)
17961780
17971781
1798class GpgmeNotInstalled(DependencyNotPresent):
1799
1800 _fmt = 'python-gpgme is not installed, it is needed to verify signatures'
1801
1802 def __init__(self, error):
1803 DependencyNotPresent.__init__(self, 'gpgme', error)
1804
1805
1806class WorkingTreeNotRevision(BzrError):1782class WorkingTreeNotRevision(BzrError):
18071783
1808 _fmt = ("The working tree for %(basedir)s has changed since"1784 _fmt = ("The working tree for %(basedir)s has changed since"
18091785
=== modified file 'breezy/gpg.py'
--- breezy/gpg.py 2017-06-05 20:48:31 +0000
+++ breezy/gpg.py 2017-07-04 21:30:36 +0000
@@ -29,7 +29,6 @@
2929
30from breezy import (30from breezy import (
31 config,31 config,
32 errors,
33 trace,32 trace,
34 ui,33 ui,
35 )34 )
@@ -39,6 +38,9 @@
39 )38 )
40""")39""")
4140
41from . import (
42 errors,
43 )
42from .sixish import (44from .sixish import (
43 BytesIO,45 BytesIO,
44 )46 )
@@ -51,6 +53,30 @@
51SIGNATURE_EXPIRED = 453SIGNATURE_EXPIRED = 4
5254
5355
56class GpgNotInstalled(errors.DependencyNotPresent):
57
58 _fmt = 'python-gpg is not installed, it is needed to verify signatures'
59
60 def __init__(self, error):
61 errors.DependencyNotPresent.__init__(self, 'gpg', error)
62
63
64class SigningFailed(errors.BzrError):
65
66 _fmt = 'Failed to GPG sign data: "%(error)s"'
67
68 def __init__(self, error):
69 errors.BzrError.__init__(self, error=error)
70
71
72class SignatureVerificationFailed(errors.BzrError):
73
74 _fmt = 'Failed to verify GPG signature data with error "%(error)s"'
75
76 def __init__(self, error):
77 errors.BzrError.__init__(self, error=error)
78
79
54def bulk_verify_signatures(repository, revids, strategy,80def bulk_verify_signatures(repository, revids, strategy,
55 process_events_callback=None):81 process_events_callback=None):
56 """Do verifications on a set of revisions82 """Do verifications on a set of revisions
@@ -101,10 +127,10 @@
101 """Real strategies take a configuration."""127 """Real strategies take a configuration."""
102128
103 def sign(self, content):129 def sign(self, content):
104 raise errors.SigningFailed('Signing is disabled.')130 raise SigningFailed('Signing is disabled.')
105131
106 def verify(self, content, testament):132 def verify(self, content, testament):
107 raise errors.SignatureVerificationFailed('Signature verification is \133 raise SignatureVerificationFailed('Signature verification is \
108disabled.')134disabled.')
109135
110 def set_acceptable_keys(self, command_line_input):136 def set_acceptable_keys(self, command_line_input):
@@ -162,11 +188,32 @@
162 def __init__(self, config_stack):188 def __init__(self, config_stack):
163 self._config_stack = config_stack189 self._config_stack = config_stack
164 try:190 try:
165 import gpgme191 import gpg
166 self.context = gpgme.Context()192 self.context = gpg.Context()
167 except ImportError as error:193 except ImportError as error:
168 pass # can't use verify()194 pass # can't use verify()
169195
196 self.context.signers = self._get_signing_keys()
197
198 def _get_signing_keys(self):
199 import gpg
200 keyname = self._config_stack.get('gpg_signing_key')
201 if keyname:
202 try:
203 return [self.context.get_key(keyname)]
204 except gpg.errors.KeyNotFound:
205 pass
206
207 if keyname is None or keyname == 'default':
208 # 'default' or not setting gpg_signing_key at all means we should
209 # use the user email address
210 keyname = config.extract_email_address(self._config_stack.get('email'))
211 possible_keys = self.context.keylist(keyname, secret=True)
212 try:
213 return [next(possible_keys)]
214 except StopIteration:
215 return []
216
170 @staticmethod217 @staticmethod
171 def verify_signatures_available():218 def verify_signatures_available():
172 """219 """
@@ -175,55 +222,24 @@
175 :return: boolean if this strategy can verify signatures222 :return: boolean if this strategy can verify signatures
176 """223 """
177 try:224 try:
178 import gpgme225 import gpg
179 return True226 return True
180 except ImportError as error:227 except ImportError as error:
181 return False228 return False
182229
183 def _command_line(self):
184 key = self._config_stack.get('gpg_signing_key')
185 if key is None or key == 'default':
186 # 'default' or not setting gpg_signing_key at all means we should
187 # use the user email address
188 key = config.extract_email_address(self._config_stack.get('email'))
189 return [self._config_stack.get('gpg_signing_command'), '--clearsign',
190 '-u', key]
191
192 def sign(self, content):230 def sign(self, content):
231 import gpg
193 if isinstance(content, unicode):232 if isinstance(content, unicode):
194 raise errors.BzrBadParameterUnicode('content')233 raise errors.BzrBadParameterUnicode('content')
195 ui.ui_factory.clear_term()
196234
197 preexec_fn = _set_gpg_tty235 plain_text = gpg.Data(content)
198 if sys.platform == 'win32':
199 # Win32 doesn't support preexec_fn, but wouldn't support TTY anyway.
200 preexec_fn = None
201 try:236 try:
202 process = subprocess.Popen(self._command_line(),237 output, result = self.context.sign(
203 stdin=subprocess.PIPE,238 plain_text, mode=gpg.constants.sig.mode.CLEAR)
204 stdout=subprocess.PIPE,239 except gpg.errors.GPGMEError as error:
205 preexec_fn=preexec_fn)240 raise SigningFailed(str(error))
206 try:241
207 result = process.communicate(content)[0]242 return output
208 if process.returncode is None:
209 process.wait()
210 if process.returncode != 0:
211 raise errors.SigningFailed(self._command_line())
212 return result
213 except OSError as e:
214 if e.errno == errno.EPIPE:
215 raise errors.SigningFailed(self._command_line())
216 else:
217 raise
218 except ValueError:
219 # bad subprocess parameters, should never happen.
220 raise
221 except OSError as e:
222 if e.errno == errno.ENOENT:
223 # gpg is not installed
224 raise errors.SigningFailed(self._command_line())
225 else:
226 raise
227243
228 def verify(self, content, testament):244 def verify(self, content, testament):
229 """Check content has a valid signature.245 """Check content has a valid signature.
@@ -234,74 +250,75 @@
234 :return: SIGNATURE_VALID or a failed SIGNATURE_ value, key uid if valid250 :return: SIGNATURE_VALID or a failed SIGNATURE_ value, key uid if valid
235 """251 """
236 try:252 try:
237 import gpgme253 import gpg
238 except ImportError as error:254 except ImportError as error:
239 raise errors.GpgmeNotInstalled(error)255 raise errors.GpgNotInstalled(error)
240256
241 signature = BytesIO(content)257 signature = gpg.Data(content)
242 plain_output = BytesIO()258 sink = gpg.Data()
243 try:259 try:
244 result = self.context.verify(signature, None, plain_output)260 plain_output, result = self.context.verify(signature)
245 except gpgme.GpgmeError as error:261 except gpg.errors.BadSignatures as error:
246 raise errors.SignatureVerificationFailed(error[2])262 signatures = error.result.signatures
263 fingerprint = signatures[0].fpr
264 if signatures[0].summary & gpg.constants.SIGSUM_KEY_EXPIRED:
265 expires = self.context.get_key(signatures[0].fpr).subkeys[0].expires
266 if expires > signatures[0].timestamp:
267 # The expired key was not expired at time of signing.
268 # test_verify_expired_but_valid()
269 return SIGNATURE_EXPIRED, fingerprint[-8:]
270 else:
271 # I can't work out how to create a test where the signature
272 # was expired at the time of signing.
273 return SIGNATURE_NOT_VALID, None
274
275 # GPG does not know this key.
276 # test_verify_unknown_key()
277 if signatures[0].summary & gpg.constants.SIGSUM_KEY_MISSING:
278 return SIGNATURE_KEY_MISSING, fingerprint[-8:]
279
280 return SIGNATURE_NOT_VALID, None
281 except gpg.errors.GPGMEError as error:
282 raise SignatureVerificationFailed(error[2])
247283
248 # No result if input is invalid.284 # No result if input is invalid.
249 # test_verify_invalid()285 # test_verify_invalid()
250 if len(result) == 0:286 if len(result.signatures) == 0:
251 return SIGNATURE_NOT_VALID, None287 return SIGNATURE_NOT_VALID, None
252 # User has specified a list of acceptable keys, check our result is in288 # User has specified a list of acceptable keys, check our result is in
253 # it. test_verify_unacceptable_key()289 # it. test_verify_unacceptable_key()
254 fingerprint = result[0].fpr290 fingerprint = result.signatures[0].fpr
255 if self.acceptable_keys is not None:291 if self.acceptable_keys is not None:
256 if not fingerprint in self.acceptable_keys:292 if not fingerprint in self.acceptable_keys:
257 return SIGNATURE_KEY_MISSING, fingerprint[-8:]293 return SIGNATURE_KEY_MISSING, fingerprint[-8:]
258 # Check the signature actually matches the testament.294 # Check the signature actually matches the testament.
259 # test_verify_bad_testament()295 # test_verify_bad_testament()
260 if testament != plain_output.getvalue():296 if testament != plain_output:
261 return SIGNATURE_NOT_VALID, None297 return SIGNATURE_NOT_VALID, None
262 # Yay gpgme set the valid bit.298 # Yay gpg set the valid bit.
263 # Can't write a test for this one as you can't set a key to be299 # Can't write a test for this one as you can't set a key to be
264 # trusted using gpgme.300 # trusted using gpg.
265 if result[0].summary & gpgme.SIGSUM_VALID:301 if result.signatures[0].summary & gpg.constants.SIGSUM_VALID:
266 key = self.context.get_key(fingerprint)302 key = self.context.get_key(fingerprint)
267 name = key.uids[0].name303 name = key.uids[0].name
268 email = key.uids[0].email304 email = key.uids[0].email
269 return SIGNATURE_VALID, name + " <" + email + ">"305 return SIGNATURE_VALID, name + " <" + email + ">"
270 # Sigsum_red indicates a problem, unfortunatly I have not been able306 # Sigsum_red indicates a problem, unfortunatly I have not been able
271 # to write any tests which actually set this.307 # to write any tests which actually set this.
272 if result[0].summary & gpgme.SIGSUM_RED:308 if result.signatures[0].summary & gpg.constants.SIGSUM_RED:
273 return SIGNATURE_NOT_VALID, None309 return SIGNATURE_NOT_VALID, None
274 # GPG does not know this key.
275 # test_verify_unknown_key()
276 if result[0].summary & gpgme.SIGSUM_KEY_MISSING:
277 return SIGNATURE_KEY_MISSING, fingerprint[-8:]
278 # Summary isn't set if sig is valid but key is untrusted but if user310 # Summary isn't set if sig is valid but key is untrusted but if user
279 # has explicity set the key as acceptable we can validate it.311 # has explicity set the key as acceptable we can validate it.
280 if result[0].summary == 0 and self.acceptable_keys is not None:312 if result.signatures[0].summary == 0 and self.acceptable_keys is not None:
281 if fingerprint in self.acceptable_keys:313 if fingerprint in self.acceptable_keys:
282 # test_verify_untrusted_but_accepted()314 # test_verify_untrusted_but_accepted()
283 return SIGNATURE_VALID, None315 return SIGNATURE_VALID, None
284 # test_verify_valid_but_untrusted()316 # test_verify_valid_but_untrusted()
285 if result[0].summary == 0 and self.acceptable_keys is None:317 if result.signatures[0].summary == 0 and self.acceptable_keys is None:
286 return SIGNATURE_NOT_VALID, None
287 if result[0].summary & gpgme.SIGSUM_KEY_EXPIRED:
288 expires = self.context.get_key(result[0].fpr).subkeys[0].expires
289 if expires > result[0].timestamp:
290 # The expired key was not expired at time of signing.
291 # test_verify_expired_but_valid()
292 return SIGNATURE_EXPIRED, fingerprint[-8:]
293 else:
294 # I can't work out how to create a test where the signature
295 # was expired at the time of signing.
296 return SIGNATURE_NOT_VALID, None
297 # A signature from a revoked key gets this.
298 # test_verify_revoked_signature()
299 if ((result[0].summary & gpgme.SIGSUM_SYS_ERROR
300 or result[0].status.strerror == 'Certificate revoked')):
301 return SIGNATURE_NOT_VALID, None318 return SIGNATURE_NOT_VALID, None
302 # Other error types such as revoked keys should (I think) be caught by319 # Other error types such as revoked keys should (I think) be caught by
303 # SIGSUM_RED so anything else means something is buggy.320 # SIGSUM_RED so anything else means something is buggy.
304 raise errors.SignatureVerificationFailed(321 raise SignatureVerificationFailed(
305 "Unknown GnuPG key verification result")322 "Unknown GnuPG key verification result")
306323
307 def set_acceptable_keys(self, command_line_input):324 def set_acceptable_keys(self, command_line_input):
308325
=== modified file 'breezy/help_topics/en/configuration.txt'
--- breezy/help_topics/en/configuration.txt 2017-06-12 23:25:04 +0000
+++ breezy/help_topics/en/configuration.txt 2017-07-04 21:30:36 +0000
@@ -498,16 +498,6 @@
498 This section only applies to the branch at this directory and not498 This section only applies to the branch at this directory and not
499 branches below it.499 branches below it.
500500
501gpg_signing_command
502~~~~~~~~~~~~~~~~~~~
503
504(Default: "gpg"). Which program should be used to sign and check revisions.
505For example::
506
507 gpg_signing_command = /usr/bin/gnpg
508
509The specified command must accept the options "--clearsign" and "-u <email>".
510
511bzr_remote_path501bzr_remote_path
512~~~~~~~~~~~~~~~502~~~~~~~~~~~~~~~
513503
514504
=== modified file 'breezy/tests/blackbox/test_log.py'
--- breezy/tests/blackbox/test_log.py 2017-06-22 01:52:28 +0000
+++ breezy/tests/blackbox/test_log.py 2017-07-04 21:30:36 +0000
@@ -17,6 +17,9 @@
1717
18"""Black-box tests for brz log."""18"""Black-box tests for brz log."""
1919
20from __future__ import absolute_import
21
22
20import os23import os
2124
22from breezy import (25from breezy import (
@@ -474,7 +477,7 @@
474class TestLogSignatures(TestLog):477class TestLogSignatures(TestLog):
475478
476 def test_log_with_signatures(self):479 def test_log_with_signatures(self):
477 self.requireFeature(features.gpgme)480 self.requireFeature(features.gpg)
478481
479 tree = self.make_linear_branch(format='dirstate-tags')482 tree = self.make_linear_branch(format='dirstate-tags')
480483
@@ -482,7 +485,7 @@
482 self.assertTrue('signature: no signature' in log)485 self.assertTrue('signature: no signature' in log)
483486
484 def test_log_without_signatures(self):487 def test_log_without_signatures(self):
485 self.requireFeature(features.gpgme)488 self.requireFeature(features.gpg)
486489
487 tree = self.make_linear_branch(format='dirstate-tags')490 tree = self.make_linear_branch(format='dirstate-tags')
488491
489492
=== modified file 'breezy/tests/features.py'
--- breezy/tests/features.py 2017-06-17 12:58:29 +0000
+++ breezy/tests/features.py 2017-07-04 21:30:36 +0000
@@ -17,6 +17,8 @@
17"""A collection of commonly used 'Features' to optionally run tests.17"""A collection of commonly used 'Features' to optionally run tests.
18"""18"""
1919
20from __future__ import absolute_import
21
20import os22import os
21import subprocess23import subprocess
22import stat24import stat
@@ -373,7 +375,7 @@
373not_running_as_root = _NotRunningAsRoot()375not_running_as_root = _NotRunningAsRoot()
374376
375apport = ModuleAvailableFeature('apport')377apport = ModuleAvailableFeature('apport')
376gpgme = ModuleAvailableFeature('gpgme')378gpg = ModuleAvailableFeature('gpg')
377lzma = ModuleAvailableFeature('lzma')379lzma = ModuleAvailableFeature('lzma')
378meliae = ModuleAvailableFeature('meliae.scanner')380meliae = ModuleAvailableFeature('meliae.scanner')
379paramiko = ModuleAvailableFeature('paramiko')381paramiko = ModuleAvailableFeature('paramiko')
380382
=== modified file 'breezy/tests/test_commit.py'
--- breezy/tests/test_commit.py 2017-06-18 22:23:02 +0000
+++ breezy/tests/test_commit.py 2017-07-04 21:30:36 +0000
@@ -33,7 +33,6 @@
33from ..errors import (33from ..errors import (
34 PointlessCommit,34 PointlessCommit,
35 BzrError,35 BzrError,
36 SigningFailed,
37 LockContention,36 LockContention,
38 )37 )
39from . import (38from . import (
@@ -53,7 +52,6 @@
5352
54 def __init__(self):53 def __init__(self):
55 super(MustSignConfig, self).__init__('''54 super(MustSignConfig, self).__init__('''
56gpg_signing_command=cat -
57create_signatures=always55create_signatures=always
58''')56''')
5957
@@ -431,7 +429,6 @@
431 # monkey patch gpg signing mechanism429 # monkey patch gpg signing mechanism
432 breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy430 breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
433 conf = config.MemoryStack('''431 conf = config.MemoryStack('''
434gpg_signing_command=cat -
435create_signatures=always432create_signatures=always
436''')433''')
437 commit.Commit(config_stack=conf).commit(434 commit.Commit(config_stack=conf).commit(
@@ -457,10 +454,9 @@
457 # monkey patch gpg signing mechanism454 # monkey patch gpg signing mechanism
458 breezy.gpg.GPGStrategy = breezy.gpg.DisabledGPGStrategy455 breezy.gpg.GPGStrategy = breezy.gpg.DisabledGPGStrategy
459 conf = config.MemoryStack('''456 conf = config.MemoryStack('''
460gpg_signing_command=cat -
461create_signatures=always457create_signatures=always
462''')458''')
463 self.assertRaises(SigningFailed,459 self.assertRaises(breezy.gpg.SigningFailed,
464 commit.Commit(config_stack=conf).commit,460 commit.Commit(config_stack=conf).commit,
465 message="base",461 message="base",
466 allow_pointless=True,462 allow_pointless=True,
467463
=== modified file 'breezy/tests/test_config.py'
--- breezy/tests/test_config.py 2017-06-12 23:25:04 +0000
+++ breezy/tests/test_config.py 2017-07-04 21:30:36 +0000
@@ -176,7 +176,6 @@
176email=Erik B\u00e5gfors <erik@bagfors.nu>176email=Erik B\u00e5gfors <erik@bagfors.nu>
177editor=vim177editor=vim
178change_editor=vimdiff -of @new_path @old_path178change_editor=vimdiff -of @new_path @old_path
179gpg_signing_command=gnome-gpg
180gpg_signing_key=DD4D5088179gpg_signing_key=DD4D5088
181log_format=short180log_format=short
182validate_signatures_in_log=true181validate_signatures_in_log=true
@@ -232,7 +231,6 @@
232# test trailing / matching with no children231# test trailing / matching with no children
233[/a/]232[/a/]
234check_signatures=check-available233check_signatures=check-available
235gpg_signing_command=false
236gpg_signing_key=default234gpg_signing_key=default
237user_local_option=local235user_local_option=local
238# test trailing / matching236# test trailing / matching
239237
=== modified file 'breezy/tests/test_gpg.py'
--- breezy/tests/test_gpg.py 2017-05-22 00:56:52 +0000
+++ breezy/tests/test_gpg.py 2017-07-04 21:30:36 +0000
@@ -43,90 +43,17 @@
43 if content is None:43 if content is None:
44 content = '''44 content = '''
45gpg_signing_key=amy@example.com45gpg_signing_key=amy@example.com
46gpg_signing_command=false'''46'''
47 super(FakeConfig, self).__init__(content)47 super(FakeConfig, self).__init__(content)
4848
4949
50class TestCommandLine(tests.TestCase):
51
52 def setUp(self):
53 super(TestCommandLine, self).setUp()
54 self.my_gpg = gpg.GPGStrategy(FakeConfig())
55
56 def test_signing_command_line(self):
57 self.assertEqual(['false', '--clearsign', '-u', 'amy@example.com'],
58 self.my_gpg._command_line())
59
60 def test_signing_command_line_from_default(self):
61 # Using 'default' for gpg_signing_key will use the mail part of 'email'
62 my_gpg = gpg.GPGStrategy(FakeConfig('''
63email=Amy <amy@example.com>
64gpg_signing_key=default
65gpg_signing_command=false'''))
66 self.assertEqual(['false', '--clearsign', '-u', 'amy@example.com'],
67 my_gpg._command_line())
68
69 def test_signing_command_line_from_email(self):
70 # Not setting gpg_signing_key will use the mail part of 'email'
71 my_gpg = gpg.GPGStrategy(FakeConfig('''
72email=Amy <amy@example.com>
73gpg_signing_command=false'''))
74 self.assertEqual(['false', '--clearsign', '-u', 'amy@example.com'],
75 my_gpg._command_line())
76
77 def test_checks_return_code(self):
78 # This test needs a unix like platform - one with 'false' to run.
79 # if you have one, please make this work :)
80 self.assertRaises(errors.SigningFailed, self.my_gpg.sign, 'content')
81
82 def assertProduces(self, content):
83 # This needs a 'cat' command or similar to work.
84 if sys.platform == 'win32':
85 # Windows doesn't come with cat, and we don't require it
86 # so lets try using python instead.
87 # But stupid windows and line-ending conversions.
88 # It is too much work to make sys.stdout be in binary mode.
89 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65443
90 self.my_gpg._command_line = lambda:[sys.executable, '-c',
91 'import sys; sys.stdout.write(sys.stdin.read())']
92 new_content = content.replace('\n', '\r\n')
93
94 self.assertEqual(new_content, self.my_gpg.sign(content))
95 else:
96 self.my_gpg._command_line = lambda:['cat', '-']
97 self.assertEqual(content, self.my_gpg.sign(content))
98
99 def test_returns_output(self):
100 content = "some content\nwith newlines\n"
101 self.assertProduces(content)
102
103 def test_clears_progress(self):
104 content = "some content\nwith newlines\n"
105 old_clear_term = ui.ui_factory.clear_term
106 clear_term_called = []
107 def clear_term():
108 old_clear_term()
109 clear_term_called.append(True)
110 ui.ui_factory.clear_term = clear_term
111 try:
112 self.assertProduces(content)
113 finally:
114 ui.ui_factory.clear_term = old_clear_term
115 self.assertEqual([True], clear_term_called)
116
117 def test_aborts_on_unicode(self):
118 """You can't sign Unicode text; it must be encoded first."""
119 self.assertRaises(errors.BzrBadParameterUnicode,
120 self.assertProduces, u'foo')
121
122
123class TestVerify(TestCase):50class TestVerify(TestCase):
12451
125 def import_keys(self):52 def import_keys(self):
126 import gpgme53 import gpg
127 context = gpgme.Context()54 context = gpg.Context()
12855
129 key = BytesIO(b"""-----BEGIN PGP PUBLIC KEY BLOCK-----56 key = gpg.Data(b"""-----BEGIN PGP PUBLIC KEY BLOCK-----
130Version: GnuPG v1.4.11 (GNU/Linux)57Version: GnuPG v1.4.11 (GNU/Linux)
13158
132mQENBE343IgBCADwzPW7kmKb2bjB+UU+1ER/ABMZspvtoZMPusUw7bk6coXHF/0W59mQENBE343IgBCADwzPW7kmKb2bjB+UU+1ER/ABMZspvtoZMPusUw7bk6coXHF/0W
@@ -158,7 +85,7 @@
158-----END PGP PUBLIC KEY BLOCK-----85-----END PGP PUBLIC KEY BLOCK-----
159""")86""")
16087
161 secret_key = BytesIO(b"""-----BEGIN PGP PRIVATE KEY BLOCK-----88 secret_key = gpg.Data(b"""-----BEGIN PGP PRIVATE KEY BLOCK-----
162Version: GnuPG v1.4.11 (GNU/Linux)89Version: GnuPG v1.4.11 (GNU/Linux)
16390
164lQOYBE343IgBCADwzPW7kmKb2bjB+UU+1ER/ABMZspvtoZMPusUw7bk6coXHF/0W91lQOYBE343IgBCADwzPW7kmKb2bjB+UU+1ER/ABMZspvtoZMPusUw7bk6coXHF/0W
@@ -217,7 +144,7 @@
217-----END PGP PRIVATE KEY BLOCK-----144-----END PGP PRIVATE KEY BLOCK-----
218""")145""")
219146
220 revoked_key = BytesIO(b"""-----BEGIN PGP PUBLIC KEY BLOCK-----147 revoked_key = gpg.Data(b"""-----BEGIN PGP PUBLIC KEY BLOCK-----
221Version: GnuPG v1.4.11 (GNU/Linux)148Version: GnuPG v1.4.11 (GNU/Linux)
222149
223mI0ETjlW5gEEAOb/6P+TVM59E897wRtatxys2BhsHCXM4T7xjIiANfDwejDdifqh150mI0ETjlW5gEEAOb/6P+TVM59E897wRtatxys2BhsHCXM4T7xjIiANfDwejDdifqh
@@ -242,7 +169,7 @@
242-----END PGP PUBLIC KEY BLOCK-----169-----END PGP PUBLIC KEY BLOCK-----
243""")170""")
244171
245 expired_key = BytesIO(b"""-----BEGIN PGP PUBLIC KEY BLOCK-----172 expired_key = gpg.Data(b"""-----BEGIN PGP PUBLIC KEY BLOCK-----
246Version: GnuPG v1.4.11 (GNU/Linux)173Version: GnuPG v1.4.11 (GNU/Linux)
247174
248mI0ETjZ6PAEEALkR4GcFQidCCxV7pgQwQd5MZua0YO2l92fVqHX+PhnZ6egCLKdD175mI0ETjZ6PAEEALkR4GcFQidCCxV7pgQwQd5MZua0YO2l92fVqHX+PhnZ6egCLKdD
@@ -263,14 +190,14 @@
263=p0gt190=p0gt
264-----END PGP PUBLIC KEY BLOCK-----191-----END PGP PUBLIC KEY BLOCK-----
265""")192""")
266 context.import_(key)193 context.op_import(key)
267 context.import_(secret_key)194 context.op_import(secret_key)
268 context.import_(revoked_key)195 context.op_import(revoked_key)
269 context.import_(expired_key)196 context.op_import(expired_key)
270197
271 def test_verify_untrusted_but_accepted(self):198 def test_verify_untrusted_but_accepted(self):
272 #untrusted by gpg but listed as acceptable_keys by user199 #untrusted by gpg but listed as acceptable_keys by user
273 self.requireFeature(features.gpgme)200 self.requireFeature(features.gpg)
274 self.import_keys()201 self.import_keys()
275202
276 content = """-----BEGIN PGP SIGNED MESSAGE-----203 content = """-----BEGIN PGP SIGNED MESSAGE-----
@@ -301,7 +228,7 @@
301 plain))228 plain))
302229
303 def test_verify_unacceptable_key(self):230 def test_verify_unacceptable_key(self):
304 self.requireFeature(features.gpgme)231 self.requireFeature(features.gpg)
305 self.import_keys()232 self.import_keys()
306233
307 content = """-----BEGIN PGP SIGNED MESSAGE-----234 content = """-----BEGIN PGP SIGNED MESSAGE-----
@@ -332,7 +259,7 @@
332 my_gpg.verify(content, plain))259 my_gpg.verify(content, plain))
333260
334 def test_verify_valid_but_untrusted(self):261 def test_verify_valid_but_untrusted(self):
335 self.requireFeature(features.gpgme)262 self.requireFeature(features.gpg)
336 self.import_keys()263 self.import_keys()
337264
338 content = """-----BEGIN PGP SIGNED MESSAGE-----265 content = """-----BEGIN PGP SIGNED MESSAGE-----
@@ -362,7 +289,7 @@
362 plain))289 plain))
363290
364 def test_verify_bad_testament(self):291 def test_verify_bad_testament(self):
365 self.requireFeature(features.gpgme)292 self.requireFeature(features.gpg)
366 self.import_keys()293 self.import_keys()
367294
368 content = """-----BEGIN PGP SIGNED MESSAGE-----295 content = """-----BEGIN PGP SIGNED MESSAGE-----
@@ -394,7 +321,7 @@
394321
395322
396 def test_verify_revoked_signature(self):323 def test_verify_revoked_signature(self):
397 self.requireFeature(features.gpgme)324 self.requireFeature(features.gpg)
398 self.import_keys()325 self.import_keys()
399326
400 content = """-----BEGIN PGP SIGNED MESSAGE-----327 content = """-----BEGIN PGP SIGNED MESSAGE-----
@@ -418,7 +345,7 @@
418 plain))345 plain))
419346
420 def test_verify_invalid(self):347 def test_verify_invalid(self):
421 self.requireFeature(features.gpgme)348 self.requireFeature(features.gpg)
422 self.import_keys()349 self.import_keys()
423 content = """-----BEGIN PGP SIGNED MESSAGE-----350 content = """-----BEGIN PGP SIGNED MESSAGE-----
424Hash: SHA1351Hash: SHA1
@@ -443,7 +370,7 @@
443 my_gpg.verify(content, plain))370 my_gpg.verify(content, plain))
444371
445 def test_verify_expired_but_valid(self):372 def test_verify_expired_but_valid(self):
446 self.requireFeature(features.gpgme)373 self.requireFeature(features.gpg)
447 self.import_keys()374 self.import_keys()
448 content = """-----BEGIN PGP SIGNED MESSAGE-----375 content = """-----BEGIN PGP SIGNED MESSAGE-----
449Hash: SHA1376Hash: SHA1
@@ -470,7 +397,7 @@
470 my_gpg.verify(content, plain))397 my_gpg.verify(content, plain))
471398
472 def test_verify_unknown_key(self):399 def test_verify_unknown_key(self):
473 self.requireFeature(features.gpgme)400 self.requireFeature(features.gpg)
474 self.import_keys()401 self.import_keys()
475 content = """-----BEGIN PGP SIGNED MESSAGE-----402 content = """-----BEGIN PGP SIGNED MESSAGE-----
476Hash: SHA1403Hash: SHA1
@@ -494,7 +421,7 @@
494 my_gpg.verify(content, plain))421 my_gpg.verify(content, plain))
495422
496 def test_set_acceptable_keys(self):423 def test_set_acceptable_keys(self):
497 self.requireFeature(features.gpgme)424 self.requireFeature(features.gpg)
498 self.import_keys()425 self.import_keys()
499 my_gpg = gpg.GPGStrategy(FakeConfig())426 my_gpg = gpg.GPGStrategy(FakeConfig())
500 my_gpg.set_acceptable_keys("bazaar@example.com")427 my_gpg.set_acceptable_keys("bazaar@example.com")
@@ -502,7 +429,7 @@
502 [u'B5DEED5FCB15DAE6ECEF919587681B1EE3080E45'])429 [u'B5DEED5FCB15DAE6ECEF919587681B1EE3080E45'])
503430
504 def test_set_acceptable_keys_from_config(self):431 def test_set_acceptable_keys_from_config(self):
505 self.requireFeature(features.gpgme)432 self.requireFeature(features.gpg)
506 self.import_keys()433 self.import_keys()
507 my_gpg = gpg.GPGStrategy(FakeConfig(434 my_gpg = gpg.GPGStrategy(FakeConfig(
508 'acceptable_keys=bazaar@example.com'))435 'acceptable_keys=bazaar@example.com'))
@@ -511,7 +438,7 @@
511 [u'B5DEED5FCB15DAE6ECEF919587681B1EE3080E45'])438 [u'B5DEED5FCB15DAE6ECEF919587681B1EE3080E45'])
512439
513 def test_set_acceptable_keys_unknown(self):440 def test_set_acceptable_keys_unknown(self):
514 self.requireFeature(features.gpgme)441 self.requireFeature(features.gpg)
515 my_gpg = gpg.GPGStrategy(FakeConfig())442 my_gpg = gpg.GPGStrategy(FakeConfig())
516 self.notes = []443 self.notes = []
517 def note(*args):444 def note(*args):
@@ -526,10 +453,10 @@
526class TestDisabled(TestCase):453class TestDisabled(TestCase):
527454
528 def test_sign(self):455 def test_sign(self):
529 self.assertRaises(errors.SigningFailed,456 self.assertRaises(gpg.SigningFailed,
530 gpg.DisabledGPGStrategy(None).sign, 'content')457 gpg.DisabledGPGStrategy(None).sign, 'content')
531458
532 def test_verify(self):459 def test_verify(self):
533 self.assertRaises(errors.SignatureVerificationFailed,460 self.assertRaises(gpg.SignatureVerificationFailed,
534 gpg.DisabledGPGStrategy(None).verify, 'content',461 gpg.DisabledGPGStrategy(None).verify, 'content',
535 'testament')462 'testament')
536463
=== modified file 'breezy/tests/test_merge_directive.py'
--- breezy/tests/test_merge_directive.py 2017-06-10 00:17:06 +0000
+++ breezy/tests/test_merge_directive.py 2017-07-04 21:30:36 +0000
@@ -464,8 +464,6 @@
464 class FakeBranch(object):464 class FakeBranch(object):
465 def get_config_stack(self):465 def get_config_stack(self):
466 return self466 return self
467 def gpg_signing_command(self):
468 return 'loopback'
469 md = self.make_merge_directive('example:', 'sha', time, timezone,467 md = self.make_merge_directive('example:', 'sha', time, timezone,
470 'http://example.com', source_branch="http://example.org",468 'http://example.com', source_branch="http://example.org",
471 patch='booga', patch_type='diff')469 patch='booga', patch_type='diff')
472470
=== modified file 'doc/en/release-notes/brz-3.0.txt'
--- doc/en/release-notes/brz-3.0.txt 2017-06-22 01:52:28 +0000
+++ doc/en/release-notes/brz-3.0.txt 2017-07-04 21:30:36 +0000
@@ -56,6 +56,13 @@
56 This simplifies ``brz init --help``.56 This simplifies ``brz init --help``.
57 (Neil Martinsen-Burrell, #330494)57 (Neil Martinsen-Burrell, #330494)
5858
59 * ``python-gpg`` is now used for checking GPG signatures rather than
60 ``python-gpgme``. (Jelmer Vernooij, #1702308)
61
62 * ``python-gpg`` is now used for signing commits, rather than shelling
63 out to the gnupg command. The ``gpg_signing_command`` option has been
64 removed. (Jelmer Vernooij, #847388)
65
59New Features66New Features
60************67************
6168

Subscribers

People subscribed via source and target branches