Hello community, here is the log from the commit of package python-python-gnupg for openSUSE:Factory checked in at 2019-01-25 22:44:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-python-gnupg (Old) and /work/SRC/openSUSE:Factory/.python-python-gnupg.new.28833 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-python-gnupg" Fri Jan 25 22:44:11 2019 rev:5 rq:668267 version:0.4.4 Changes: -------- --- /work/SRC/openSUSE:Factory/python-python-gnupg/python-python-gnupg.changes 2018-12-24 11:42:35.473388325 +0100 +++ /work/SRC/openSUSE:Factory/.python-python-gnupg.new.28833/python-python-gnupg.changes 2019-01-25 22:44:12.807165479 +0100 @@ -1,0 +2,30 @@ +Thu Jan 24 10:07:27 UTC 2019 - Tomáš Chvátal <tchva...@suse.com> + +- Enable tests + +------------------------------------------------------------------- +Thu Jan 24 09:31:19 UTC 2019 - Karol Babioch <kbabi...@suse.de> + +- Update to 0.4.4: + * Changed how any return value from the ``on_data`` callable is processed. In + earlier versions, the return value was ignored. In this version, if the + return value is ``False``, the data received from ``gpg`` is not buffered. + Otherwise (if the value is ``None`` or ``True``, for example), the data is + buffered as normal. This functionality can be used to do your own + buffering, or to prevent buffering altogether. The ``on_data`` callable is + also called once with an empty byte-string to signal the end of data from + ``gpg``. + * Added an additional attribute ``check_fingerprint_collisions`` to + ``GPG`` instances, which defaults to ``False``. It seems that ``gpg`` is + happy to have duplicate keys and fingerprints in a keyring, so we can't be + too strict. A user can set this attribute of an instance to ``True`` to + trigger a check for collisions. + * With GnuPG 2.2.7 or later, provide the fingerprint of a signing key for a + failed signature verification, if available. + * For verification where multiple signatures are involved, a mapping of + signature_ids to fingerprint, keyid, username, creation date, creation + timestamp and expiry timestamp is provided. + * Added a check to disallow certain control characters ('\r', '\n', NUL) in + passphrases. + +------------------------------------------------------------------- Old: ---- python-gnupg-0.4.3.tar.gz New: ---- python-gnupg-0.4.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-python-gnupg.spec ++++++ --- /var/tmp/diff_new_pack.lOdi8C/_old 2019-01-25 22:44:13.263164918 +0100 +++ /var/tmp/diff_new_pack.lOdi8C/_new 2019-01-25 22:44:13.267164912 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-python-gnupg # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,9 +18,8 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define oldpython python -%bcond_with test Name: python-python-gnupg -Version: 0.4.3 +Version: 0.4.4 Release: 0 Summary: A wrapper for the GNU Privacy Guard (GPG or GnuPG) License: BSD-3-Clause @@ -29,20 +28,16 @@ Source: https://files.pythonhosted.org/packages/source/p/python-gnupg/python-gnupg-%{version}.tar.gz BuildRequires: %{python_module setuptools} BuildRequires: fdupes +BuildRequires: gpg2 BuildRequires: python-rpm-macros Requires: gpg2 +Obsoletes: python-gnupg < %{version} +Provides: python-gnupg = %{version} BuildArch: noarch -%if %{with test} -BuildRequires: gpg2 -%endif %ifpython2 Obsoletes: %{oldpython}-gnupg < %{version} Provides: %{oldpython}-gnupg = %{version} %endif -%ifpython3 -Obsoletes: python3-gnupg < %{version} -Provides: python3-gnupg = %{version} -%endif %python_subpackages %description @@ -59,10 +54,9 @@ %python_install %python_expand %fdupes %{buildroot}%{$python_sitelib} -%if %{with test} %check +export NO_EXTERNAL_TESTS=true %python_exec test_gnupg.py -%endif %files %{python_files} %license LICENSE.txt ++++++ python-gnupg-0.4.3.tar.gz -> python-gnupg-0.4.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.3/PKG-INFO new/python-gnupg-0.4.4/PKG-INFO --- old/python-gnupg-0.4.3/PKG-INFO 2018-06-13 16:42:22.000000000 +0200 +++ new/python-gnupg-0.4.4/PKG-INFO 2019-01-24 09:48:36.000000000 +0100 @@ -1,12 +1,12 @@ Metadata-Version: 1.1 Name: python-gnupg -Version: 0.4.3 +Version: 0.4.4 Summary: A wrapper for the Gnu Privacy Guard (GPG or GnuPG) Home-page: http://gnupg.readthedocs.io/en/latest/ Author: Vinay Sajip Author-email: vinay_sa...@red-dove.com -License: Copyright (C) 2008-2018 by Vinay Sajip. All Rights Reserved. See LICENSE.txt for license. -Download-URL: https://pypi.io/packages/source/p/python-gnupg/python-gnupg-0.4.3.tar.gz +License: Copyright (C) 2008-2019 by Vinay Sajip. All Rights Reserved. See LICENSE.txt for license. +Download-URL: https://pypi.io/packages/source/p/python-gnupg/python-gnupg-0.4.4.tar.gz Description: This module allows easy access to GnuPG's key management, encryption and signature functionality from Python programs. It is intended for use with Python 2.4 or greater. Platform: No particular restrictions Classifier: Development Status :: 5 - Production/Stable @@ -24,5 +24,6 @@ Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development :: Libraries :: Python Modules diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.3/README.rst new/python-gnupg-0.4.4/README.rst --- old/python-gnupg-0.4.3/README.rst 2018-06-13 13:50:26.000000000 +0200 +++ new/python-gnupg-0.4.4/README.rst 2019-01-23 20:36:19.000000000 +0100 @@ -63,12 +63,44 @@ .. note:: GCnn refers to an issue nn on Google Code. -0.4.4 (future) +0.4.5 (future) -------------- Released: Not yet. +0.4.4 +----- + +Released: 2019-01-24 + +* Fixed #108: Changed how any return value from the ``on_data`` callable is + processed. In earlier versions, the return value was ignored. In this version, + if the return value is ``False``, the data received from ``gpg`` is not + buffered. Otherwise (if the value is ``None`` or ``True``, for example), the + data is buffered as normal. This functionality can be used to do your own + buffering, or to prevent buffering altogether. + + The ``on_data`` callable is also called once with an empty byte-string to + signal the end of data from ``gpg``. + +* Fixed #97: Added an additional attribute ``check_fingerprint_collisions`` to + ``GPG`` instances, which defaults to ``False``. It seems that ``gpg`` is happy + to have duplicate keys and fingerprints in a keyring, so we can't be too + strict. A user can set this attribute of an instance to ``True`` to trigger a + check for collisions. + +* Fixed #111: With GnuPG 2.2.7 or later, provide the fingerprint of a signing + key for a failed signature verification, if available. + +* Fixed #21: For verification where multiple signatures are involved, a + mapping of signature_ids to fingerprint, keyid, username, creation date, + creation timestamp and expiry timestamp is provided. + +* Added a check to disallow certain control characters ('\r', '\n', NUL) in + passphrases. + + 0.4.3 ----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.3/gnupg.py new/python-gnupg-0.4.4/gnupg.py --- old/python-gnupg-0.4.3/gnupg.py 2018-06-13 13:11:43.000000000 +0200 +++ new/python-gnupg-0.4.4/gnupg.py 2019-01-24 09:43:25.000000000 +0100 @@ -27,14 +27,14 @@ and so does not work on Windows). Renamed to gnupg.py to avoid confusion with the previous versions. -Modifications Copyright (C) 2008-2018 Vinay Sajip. All rights reserved. +Modifications Copyright (C) 2008-2019 Vinay Sajip. All rights reserved. A unittest harness (test_gnupg.py) has also been added. """ -__version__ = "0.4.3" +__version__ = "0.4.4" __author__ = "Vinay Sajip" -__date__ = "$13-Jun-2018 12:11:43$" +__date__ = "$24-Jan-2019 08:43:25$" try: from io import StringIO @@ -244,6 +244,7 @@ self.sig_timestamp = None self.trust_text = None self.trust_level = None + self.sig_info = {} def __nonzero__(self): return self.valid @@ -251,41 +252,72 @@ __bool__ = __nonzero__ def handle_status(self, key, value): + + def update_sig_info(**kwargs): + sig_id = self.signature_id + if sig_id: + info = self.sig_info[sig_id] + info.update(kwargs) + if key in self.TRUST_LEVELS: self.trust_text = key self.trust_level = self.TRUST_LEVELS[key] + update_sig_info(trust_level=self.trust_level, + trust_text=self.trust_text) elif key in ("WARNING", "ERROR"): logger.warning('potential problem: %s: %s', key, value) elif key == "BADSIG": # pragma: no cover self.valid = False self.status = 'signature bad' self.key_id, self.username = value.split(None, 1) + update_sig_info(keyid=self.key_id, username=self.username, + status=self.status) elif key == "ERRSIG": # pragma: no cover self.valid = False + parts = value.split() (self.key_id, algo, hash_algo, cls, - self.timestamp) = value.split()[:5] + self.timestamp) = parts[:5] + # Since GnuPG 2.2.7, a fingerprint is tacked on + if len(parts) >= 7: + self.fingerprint = parts[6] self.status = 'signature error' + update_sig_info(keyid=self.key_id, timestamp=self.timestamp, + fingerprint=self.fingerprint, status=self.status) elif key == "EXPSIG": # pragma: no cover self.valid = False self.status = 'signature expired' self.key_id, self.username = value.split(None, 1) + update_sig_info(keyid=self.key_id, username=self.username, + status=self.status) elif key == "GOODSIG": self.valid = True self.status = 'signature good' self.key_id, self.username = value.split(None, 1) + update_sig_info(keyid=self.key_id, username=self.username, + status=self.status) elif key == "VALIDSIG": + fingerprint, creation_date, sig_ts, expire_ts = value.split()[:4] (self.fingerprint, self.creation_date, self.sig_timestamp, - self.expire_timestamp) = value.split()[:4] + self.expire_timestamp) = (fingerprint, creation_date, sig_ts, + expire_ts) # may be different if signature is made with a subkey self.pubkey_fingerprint = value.split()[-1] self.status = 'signature valid' + update_sig_info(fingerprint=fingerprint, creation_date=creation_date, + timestamp=sig_ts, expiry=expire_ts, + pubkey_fingerprint=self.pubkey_fingerprint, + status=self.status) elif key == "SIG_ID": + sig_id, creation_date, timestamp = value.split() + self.sig_info[sig_id] = {'creation_date': creation_date, + 'timestamp': timestamp} (self.signature_id, - self.creation_date, self.timestamp) = value.split() + self.creation_date, self.timestamp) = (sig_id, creation_date, + timestamp) elif key == "DECRYPTION_FAILED": # pragma: no cover self.valid = False self.key_id = value @@ -303,6 +335,7 @@ else: self.key_status = 'signing key was revoked' self.status = self.key_status + update_sig_info(status=self.status, keyid=self.key_id) elif key in ("UNEXPECTED", "FAILURE"): # pragma: no cover self.valid = False self.key_id = value @@ -524,7 +557,7 @@ def fpr(self, args): fp = args[9] - if fp in self.key_map: # pragma: no cover + if fp in self.key_map and self.gpg.check_fingerprint_collisions: # pragma: no cover raise ValueError('Unexpected fingerprint collision: %s' % fp) if not self.in_subkey: self.curkey['fingerprint'] = fp @@ -823,6 +856,10 @@ dot = '.'.encode('ascii') self.version = tuple([int(s) for s in m.groups()[0].split(dot)]) + # See issue #97. It seems gpg allow duplicate keys in keyrings, so we + # can't be too strict. + self.check_fingerprint_collisions = False + def make_args(self, args, passphrase): """ Make a list of command line elements for GPG. The value of ``args`` @@ -921,11 +958,15 @@ while True: data = stream.read(1024) if len(data) == 0: + if on_data: + on_data(data) break logger.debug("chunk: %r" % data[:256]) - chunks.append(data) + append = True if on_data: - on_data(data) + append = on_data(data) != False + if append: + chunks.append(data) if _py3k: # Join using b'' or '', as appropriate result.data = type(data)().join(chunks) @@ -996,9 +1037,20 @@ args.extend(['--yes']) args.extend(['--output', no_quote(output)]) + def is_valid_passphrase(self, passphrase): + """ + Confirm that the passphrase doesn't contain newline-type characters - + it is passed in a pipe to gpg, and so not checking could lead to + spoofing attacks by passing arbitrary text after passphrase and newline. + """ + return ('\n' not in passphrase and '\r' not in passphrase and + '\x00' not in passphrase) + def sign_file(self, file, keyid=None, passphrase=None, clearsign=True, detach=False, binary=False, output=None, extra_args=None): """sign file""" + if passphrase and not self.is_valid_passphrase(passphrase): + raise ValueError('Invalid passphrase') logger.debug("sign_file: %s", file) if binary: # pragma: no cover args = ['-s'] @@ -1118,7 +1170,7 @@ >>> gpg = GPG(gpgbinary=GPGBINARY, gnupghome="keys") >>> os.chmod('keys', 0x1C0) >>> result = gpg.recv_keys('pgp.mit.edu', '92905378') - >>> assert result + >>> if 'NO_EXTERNAL_TESTS' not in os.environ: assert result """ result = self.result_map['import'](self) @@ -1159,6 +1211,8 @@ via pinentry, you should specify expect_passphrase=False. (It's only checked for GnuPG >= 2.1). """ + if passphrase and not self.is_valid_passphrase(passphrase): + raise ValueError('Invalid passphrase') which='key' if secret: # pragma: no cover if (self.version >= (2, 1) and passphrase is None and @@ -1196,7 +1250,8 @@ via pinentry, you should specify expect_passphrase=False. (It's only checked for GnuPG >= 2.1). """ - + if passphrase and not self.is_valid_passphrase(passphrase): + raise ValueError('Invalid passphrase') which='' if secret: which='-secret-key' @@ -1277,7 +1332,8 @@ if sigs: which = 'sigs' - else: which='keys' + else: + which = 'keys' if secret: which='secret-keys' args = ['--list-%s' % which, @@ -1321,7 +1377,7 @@ >>> gpg = GPG(gpgbinary=GPGBINARY, gnupghome='keys') >>> os.chmod('keys', 0x1C0) >>> result = gpg.search_keys('<vinay_sa...@hotmail.com>') - >>> assert result, 'Failed using default keyserver' + >>> if 'NO_EXTERNAL_TESTS' not in os.environ: assert result, 'Failed using default keyserver' >>> #keyserver = 'keyserver.ubuntu.com' >>> #result = gpg.search_keys('<vinay_sa...@hotmail.com>', keyserver) >>> #assert result, 'Failed using keyserver.ubuntu.com' @@ -1427,6 +1483,8 @@ always_trust=False, passphrase=None, armor=True, output=None, symmetric=False, extra_args=None): "Encrypt the message read from the file-like object 'file'" + if passphrase and not self.is_valid_passphrase(passphrase): + raise ValueError('Invalid passphrase') args = ['--encrypt'] if symmetric: # can't be False or None - could be True or a cipher algo value @@ -1515,6 +1573,8 @@ def decrypt_file(self, file, always_trust=False, passphrase=None, output=None, extra_args=None): + if passphrase and not self.is_valid_passphrase(passphrase): + raise ValueError('Invalid passphrase') args = ["--decrypt"] if output: # write the output to a file with the specified name self.set_output_without_confirmation(args, output) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.3/setup.py new/python-gnupg-0.4.4/setup.py --- old/python-gnupg-0.4.3/setup.py 2018-03-28 16:21:20.000000000 +0200 +++ new/python-gnupg-0.4.4/setup.py 2019-01-24 09:25:30.000000000 +0100 @@ -7,7 +7,7 @@ long_description = "This module allows easy access to GnuPG's key \ management, encryption and signature functionality from Python programs. \ It is intended for use with Python 2.4 or greater.", - license="""Copyright (C) 2008-2018 by Vinay Sajip. All Rights Reserved. See LICENSE.txt for license.""", + license="""Copyright (C) 2008-2019 by Vinay Sajip. All Rights Reserved. See LICENSE.txt for license.""", version=version, author="Vinay Sajip", author_email="vinay_sa...@red-dove.com", @@ -33,6 +33,7 @@ "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", "Operating System :: OS Independent", "Topic :: Software Development :: Libraries :: Python Modules" ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.3/test_gnupg.py new/python-gnupg-0.4.4/test_gnupg.py --- old/python-gnupg-0.4.3/test_gnupg.py 2018-06-13 13:12:12.000000000 +0200 +++ new/python-gnupg-0.4.4/test_gnupg.py 2019-01-24 09:43:59.000000000 +0100 @@ -28,7 +28,7 @@ import gnupg __author__ = "Vinay Sajip" -__date__ = "$13-Jun-2018 12:12:12$" +__date__ = "$24-Jan-2019 08:43:59$" ALL_TESTS = True @@ -173,11 +173,14 @@ class GPGTestCase(unittest.TestCase): def setUp(self): - hd = os.path.join(os.getcwd(), 'keys') - if os.path.exists(hd): - self.assertTrue(os.path.isdir(hd), - "Not a directory: %s" % hd) - shutil.rmtree(hd, ignore_errors=True) + if 'STATIC_TEST_HOMEDIR' not in os.environ: + hd = tempfile.mkdtemp(prefix='keys-') + else: + hd = os.path.join(os.getcwd(), 'keys') + if os.path.exists(hd): + self.assertTrue(os.path.isdir(hd), + "Not a directory: %s" % hd) + shutil.rmtree(hd, ignore_errors=True) prepare_homedir(hd) self.homedir = hd self.gpg = gpg = gnupg.GPG(gnupghome=hd, gpgbinary=GPGBINARY) @@ -193,6 +196,10 @@ data_file.write(os.urandom(5120 * 1024)) data_file.close() + def tearDown(self): + if 'STATIC_TEST_HOMEDIR' not in os.environ: + shutil.rmtree(self.homedir, ignore_errors=True) + def test_environment(self): "Test the environment by ensuring that setup worked" hd = self.homedir @@ -373,7 +380,7 @@ # and the keyring file name has changed. pkn = 'pubring.kbx' skn = None - hd = os.path.join(os.getcwd(), 'keys') + hd = self.homedir if os.name == 'posix': pkn = os.path.join(hd, pkn) if skn: @@ -489,6 +496,9 @@ data = data.encode(gpg.encoding) edata = str(gpg.encrypt(data, barbara)) self.assertNotEqual(data, edata, "Data must have changed") + self.assertRaises(ValueError, gpg.decrypt, edata, passphrase="bbr\x00own") + self.assertRaises(ValueError, gpg.decrypt, edata, passphrase="bbr\rown") + self.assertRaises(ValueError, gpg.decrypt, edata, passphrase="bbr\nown") ddata = gpg.decrypt(edata, passphrase="bbrown") if data != ddata.data: # pragma: no cover logger.debug("was: %r", data) @@ -503,6 +513,12 @@ logger.debug("test_encryption_and_decryption ends") # Test symmetric encryption data = "chippy was here" + self.assertRaises(ValueError, gpg.encrypt, data, None, + passphrase='bbr\x00own', symmetric=True) + self.assertRaises(ValueError, gpg.encrypt, data, None, + passphrase='bbr\rown', symmetric=True) + self.assertRaises(ValueError, gpg.encrypt, data, None, + passphrase='bbr\nown', symmetric=True) edata = str(gpg.encrypt(data, None, passphrase='bbrown', symmetric=True)) ddata = gpg.decrypt(edata, passphrase='bbrown') self.assertEqual(data, str(ddata)) @@ -603,6 +619,9 @@ else: data = unicode('Hello, André', self.gpg.encoding) data = data.encode(self.gpg.encoding) + self.assertRaises(ValueError, self.gpg.sign, data, keyid=key.fingerprint, passphrase="bbr\x00own") + self.assertRaises(ValueError, self.gpg.sign, data, keyid=key.fingerprint, passphrase="bbr\rown") + self.assertRaises(ValueError, self.gpg.sign, data, keyid=key.fingerprint, passphrase="bbr\nown") sig = self.gpg.sign(data, keyid=key.fingerprint, passphrase='bbrown') self.assertFalse(sig, "Bad passphrase should fail") sig = self.gpg.sign(data, keyid=key.fingerprint, passphrase='aable') @@ -836,12 +855,13 @@ #@skipIf(os.name == 'nt', 'Test not suitable for Windows') def test_search_keys(self): "Test that searching for keys works" - r = self.gpg.search_keys('<vinay_sa...@hotmail.com>') - self.assertTrue(r) - self.assertTrue('Vinay Sajip <vinay_sa...@hotmail.com>' in r[0]['uids']) - r = self.gpg.search_keys('92905378') - self.assertTrue(r) - self.assertTrue('Vinay Sajip <vinay_sa...@hotmail.com>' in r[0]['uids']) + if 'NO_EXTERNAL_TESTS' not in os.environ: + r = self.gpg.search_keys('<vinay_sa...@hotmail.com>') + self.assertTrue(r) + self.assertTrue('Vinay Sajip <vinay_sa...@hotmail.com>' in r[0]['uids']) + r = self.gpg.search_keys('92905378') + self.assertTrue(r) + self.assertTrue('Vinay Sajip <vinay_sa...@hotmail.com>' in r[0]['uids']) def test_quote_with_shell(self): "Test shell quoting with a real shell"