Hello community, here is the log from the commit of package python3-gnupg for openSUSE:Factory checked in at 2016-09-21 18:48:58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python3-gnupg (Old) and /work/SRC/openSUSE:Factory/.python3-gnupg.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python3-gnupg" Changes: -------- --- /work/SRC/openSUSE:Factory/python3-gnupg/python3-gnupg.changes 2016-05-25 21:24:18.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python3-gnupg.new/python3-gnupg.changes 2016-09-21 18:49:00.000000000 +0200 @@ -1,0 +2,19 @@ +Sun Sep 18 15:52:25 UTC 2016 - a...@gmx.de + +- update to version 0.3.9: + * Fixed #38: You can now request information about signatures + against keys. Thanks to SunDwarf for the suggestion and patch, + which was used as a basis for this change. + * Fixed #49: When exporting keys, no attempt is made to decode the + output when armor=False is specified. + * Fixed #53: A FAILURE message caused by passing an incorrect + passphrase is handled. + * Handled EXPORTED and EXPORT_RES messages while exporting + keys. Thanks to Marcel Pörner for the patch. + * Fixed #54: Improved error message shown when gpg is not available. + * Fixed #55: Added support for KEY_CONSIDERED while verifying. + * Avoided encoding problems with filenames under Windows. Thanks to + Kévin Bernard-Allies for the patch. + * Fixed #57: Used a better mechanism for comparing keys. + +------------------------------------------------------------------- @@ -6 +24,0 @@ - Old: ---- python-gnupg-0.3.8.tar.gz New: ---- python-gnupg-0.3.9.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python3-gnupg.spec ++++++ --- /var/tmp/diff_new_pack.hS2SdC/_old 2016-09-21 18:49:02.000000000 +0200 +++ /var/tmp/diff_new_pack.hS2SdC/_new 2016-09-21 18:49:02.000000000 +0200 @@ -17,7 +17,7 @@ Name: python3-gnupg -Version: 0.3.8 +Version: 0.3.9 Release: 0 Url: https://bitbucket.org/vinay.sajip/python-gnupg Summary: A wrapper for the Gnu Privacy Guard (GPG or GnuPG) ++++++ python-gnupg-0.3.8.tar.gz -> python-gnupg-0.3.9.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.3.8/PKG-INFO new/python-gnupg-0.3.9/PKG-INFO --- old/python-gnupg-0.3.8/PKG-INFO 2015-09-24 23:03:50.000000000 +0200 +++ new/python-gnupg-0.3.9/PKG-INFO 2016-09-10 09:41:21.000000000 +0200 @@ -1,12 +1,12 @@ -Metadata-Version: 1.0 +Metadata-Version: 1.1 Name: python-gnupg -Version: 0.3.8 +Version: 0.3.9 Summary: A wrapper for the Gnu Privacy Guard (GPG or GnuPG) Home-page: http://packages.python.org/python-gnupg/index.html Author: Vinay Sajip Author-email: vinay_sa...@red-dove.com -License: Copyright (C) 2008-2014 by Vinay Sajip. All Rights Reserved. See LICENSE.txt for license. -Download-URL: https://pypi.python.org/packages/source/p/python-gnupg/python-gnupg-0.3.8.tar.gz +License: Copyright (C) 2008-2016 by Vinay Sajip. All Rights Reserved. See LICENSE.txt for license. +Download-URL: https://pypi.python.org/packages/source/p/python-gnupg/python-gnupg-0.3.9.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 @@ -23,6 +23,5 @@ Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 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.3.8/README.rst new/python-gnupg-0.3.9/README.rst --- old/python-gnupg-0.3.8/README.rst 2015-09-24 19:07:37.000000000 +0200 +++ new/python-gnupg-0.3.9/README.rst 2016-09-10 09:37:53.000000000 +0200 @@ -54,11 +54,39 @@ N.B: GCnn refers to an issue nn on Google Code. -0.3.9 (future) +0.4.0 (future) -------------- Released: Not yet +0.3.9 +----- + +Released: 2016-09-10 + +* Fixed #38: You can now request information about signatures against + keys. Thanks to SunDwarf for the suggestion and patch, which was used + as a basis for this change. + +* Fixed #49: When exporting keys, no attempt is made to decode the output when + armor=False is specified. + +* Fixed #53: A ``FAILURE`` message caused by passing an incorrect passphrase + is handled. + +* Handled ``EXPORTED`` and ``EXPORT_RES`` messages while exporting keys. Thanks + to Marcel Pörner for the patch. + +* Fixed #54: Improved error message shown when gpg is not available. + +* Fixed #55: Added support for ``KEY_CONSIDERED`` while verifying. + +* Avoided encoding problems with filenames under Windows. Thanks to Kévin + Bernard-Allies for the patch. + +* Fixed #57: Used a better mechanism for comparing keys. + + 0.3.8 ----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.3.8/gnupg.py new/python-gnupg-0.3.9/gnupg.py --- old/python-gnupg-0.3.8/gnupg.py 2015-09-24 19:03:56.000000000 +0200 +++ new/python-gnupg-0.3.9/gnupg.py 2016-09-10 09:38:35.000000000 +0200 @@ -27,18 +27,18 @@ and so does not work on Windows). Renamed to gnupg.py to avoid confusion with the previous versions. -Modifications Copyright (C) 2008-2014 Vinay Sajip. All rights reserved. +Modifications Copyright (C) 2008-2016 Vinay Sajip. All rights reserved. A unittest harness (test_gnupg.py) has also been added. """ -__version__ = "0.3.8" +__version__ = "0.3.9" __author__ = "Vinay Sajip" -__date__ = "$24-Sep-2015 18:03:55$" +__date__ = "$10-Sep-2016 08:38:35$" try: from io import StringIO -except ImportError: +except ImportError: # pragma: no cover from cStringIO import StringIO import codecs @@ -53,7 +53,7 @@ import threading STARTUPINFO = None -if os.name == 'nt': +if os.name == 'nt': # pragma: no cover try: from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW, SW_HIDE except ImportError: @@ -80,7 +80,7 @@ logger.addHandler(NullHandler()) # We use the test below because it works for Jython as well as CPython -if os.path.__name__ == 'ntpath': +if os.path.__name__ == 'ntpath': # pragma: no cover # On Windows, we don't need shell quoting, other than worrying about # paths with spaces in them. def shell_quote(s): @@ -105,7 +105,7 @@ command shells :rtype: The passed-in type """ - if not isinstance(s, string_types): + if not isinstance(s, string_types): # pragma: no cover raise TypeError('Expected string type, got %s' % type(s)) if not s: result = "''" @@ -138,7 +138,7 @@ sent = 0 if hasattr(sys.stdin, 'encoding'): enc = sys.stdin.encoding - else: + else: # pragma: no cover enc = 'ascii' while True: # See issue #39: read can fail when e.g. a text stream is provided @@ -151,10 +151,10 @@ if not data: break sent += len(data) - logger.debug("sending chunk (%d): %r", sent, data[:256]) + # logger.debug("sending chunk (%d): %r", sent, data[:256]) try: outstream.write(data) - except UnicodeError: + except UnicodeError: # pragma: no cover outstream.write(data.encode(enc)) except: # Can sometimes get 'broken pipe' errors even when the data has all @@ -163,7 +163,7 @@ break try: outstream.close() - except IOError: + except IOError: # pragma: no cover logger.warning('Exception occurred while closing: ignored', exc_info=1) logger.debug("closed output, %d bytes sent", sent) @@ -187,7 +187,7 @@ try: from io import BytesIO rv = BytesIO(s) - except ImportError: + except ImportError: # pragma: no cover rv = StringIO(s) return rv @@ -245,20 +245,21 @@ "DECRYPTION_OKAY", "INV_SGNR", "FILE_START", "FILE_ERROR", "FILE_DONE", "PKA_TRUST_GOOD", "PKA_TRUST_BAD", "BADMDC", "GOODMDC", "NO_SGNR", "NOTATION_NAME", "NOTATION_DATA", - "PROGRESS", "PINENTRY_LAUNCHED", "NEWSIG"): + "PROGRESS", "PINENTRY_LAUNCHED", "NEWSIG", + "KEY_CONSIDERED"): pass - elif key == "BADSIG": + elif key == "BADSIG": # pragma: no cover self.valid = False self.status = 'signature bad' self.key_id, self.username = value.split(None, 1) - elif key == "ERRSIG": + elif key == "ERRSIG": # pragma: no cover self.valid = False (self.key_id, algo, hash_algo, cls, self.timestamp) = value.split()[:5] self.status = 'signature error' - elif key == "EXPSIG": + elif key == "EXPSIG": # pragma: no cover self.valid = False self.status = 'signature expired' self.key_id, self.username = value.split(None, 1) @@ -277,21 +278,21 @@ elif key == "SIG_ID": (self.signature_id, self.creation_date, self.timestamp) = value.split() - elif key == "DECRYPTION_FAILED": + elif key == "DECRYPTION_FAILED": # pragma: no cover self.valid = False self.key_id = value self.status = 'decryption failed' - elif key == "NO_PUBKEY": + elif key == "NO_PUBKEY": # pragma: no cover self.valid = False self.key_id = value self.status = 'no public key' - elif key in ("KEYEXPIRED", "SIGEXPIRED", "KEYREVOKED"): + elif key in ("KEYEXPIRED", "SIGEXPIRED", "KEYREVOKED"): # pragma: no cover # these are useless in verify, since they are spit out for any # pub/subkeys on the key, not just the one doing the signing. # if we want to check for signatures with expired key, # the relevant flag is EXPKEYSIG or REVKEYSIG. pass - elif key in ("EXPKEYSIG", "REVKEYSIG"): + elif key in ("EXPKEYSIG", "REVKEYSIG"): # pragma: no cover # signed with expired or revoked key self.valid = False self.key_id = value.split()[0] @@ -300,10 +301,14 @@ else: self.key_status = 'signing key was revoked' self.status = self.key_status - elif key == "UNEXPECTED": + elif key in ("UNEXPECTED", "FAILURE"): # pragma: no cover self.valid = False self.key_id = value - self.status = 'unexpected data' + if key == "UNEXPECTED": + self.status = 'unexpected data' + else: + # N.B. there might be other reasons + self.status = 'incorrect passphrase' else: raise ValueError("Unknown status message: %r" % key) @@ -349,7 +354,7 @@ if key == "IMPORTED": # this duplicates info we already see in import_ok & import_problem pass - elif key == "NODATA": + elif key == "NODATA": # pragma: no cover self.results.append({'fingerprint': None, 'problem': '0', 'text': 'No valid data found'}) elif key == "IMPORT_OK": @@ -362,7 +367,7 @@ self.results.append({'fingerprint': fingerprint, 'ok': reason, 'text': reasontext}) self.fingerprints.append(fingerprint) - elif key == "IMPORT_PROBLEM": + elif key == "IMPORT_PROBLEM": # pragma: no cover try: reason, fingerprint = value.split() except: @@ -374,19 +379,19 @@ import_res = value.split() for i, count in enumerate(self.counts): setattr(self, count, int(import_res[i])) - elif key == "KEYEXPIRED": + elif key == "KEYEXPIRED": # pragma: no cover self.results.append({'fingerprint': None, 'problem': '0', 'text': 'Key expired'}) - elif key == "SIGEXPIRED": + elif key == "SIGEXPIRED": # pragma: no cover self.results.append({'fingerprint': None, 'problem': '0', 'text': 'Signature expired'}) - else: + else: # pragma: no cover raise ValueError("Unknown status message: %r" % key) def summary(self): l = [] l.append('%d imported' % self.imported) - if self.not_imported: + if self.not_imported: # pragma: no cover l.append('%d not imported' % self.not_imported) return ', '.join(l) @@ -429,6 +434,7 @@ for i, var in enumerate(self.FIELDS): result[var] = args[i] result['uids'] = [] + result['sigs'] = [] return result def pub(self, args): @@ -443,11 +449,11 @@ self.curkey['uids'].append(uid) self.uids.append(uid) - def handle_status(self, key, value): + def handle_status(self, key, value): # pragma: no cover pass class ListKeys(SearchKeys): - ''' Handle status messages for --list-keys. + ''' Handle status messages for --list-keys, --list-sigs. Handle pub and uid (relating the latter to the former). @@ -455,7 +461,6 @@ crt = X.509 certificate crs = X.509 certificate and private key available - ssb = secret subkey (secondary key) uat = user attribute (same as user id except for field 10). sig = signature rev = revocation signature @@ -465,7 +470,7 @@ ''' UID_INDEX = 9 - FIELDS = 'type trust length algo keyid date expires dummy ownertrust uid'.split() + FIELDS = 'type trust length algo keyid date expires dummy ownertrust uid sig'.split() def __init__(self, gpg): super(ListKeys, self).__init__(gpg) @@ -485,7 +490,7 @@ def fpr(self, args): fp = args[9] - if fp in self.key_map: + if fp in self.key_map: # pragma: no cover raise ValueError('Unexpected fingerprint collision: %s' % fp) if not self.in_subkey: self.curkey['fingerprint'] = fp @@ -500,6 +505,14 @@ self.curkey['subkeys'].append(subkey) self.in_subkey = True + def ssb(self, args): + subkey = [args[4], None] # keyid, type + self.curkey['subkeys'].append(subkey) + self.in_subkey = True + + def sig(self, args): + # keyid, uid, sigclass + self.curkey['sigs'].append((args[4], args[9], args[10])) class ScanKeys(ListKeys): ''' Handle status messages for --with-fingerprint.''' @@ -563,13 +576,13 @@ elif key == "END_ENCRYPTION": self.status = 'encryption ok' self.ok = True - elif key == "INV_RECP": + elif key == "INV_RECP": # pragma: no cover self.status = 'invalid recipient' - elif key == "KEYEXPIRED": + elif key == "KEYEXPIRED": # pragma: no cover self.status = 'key expired' - elif key == "SIG_CREATED": + elif key == "SIG_CREATED": # pragma: no cover self.status = 'sig created' - elif key == "SIGEXPIRED": + elif key == "SIGEXPIRED": # pragma: no cover self.status = 'sig expired' else: Verify.handle_status(self, key, value) @@ -605,7 +618,11 @@ For now, just use an existing class to base it on - if needed, we can override handle_status for more specific message handling. """ - pass + def handle_status(self, key, value): + if key in ("EXPORTED", "EXPORT_RES"): + pass + else: + super(ExportResult, self).handle_status(key, value) class DeleteResult(object): "Handle status messages for --delete-key and --delete-secret-key" @@ -623,10 +640,10 @@ } def handle_status(self, key, value): - if key == "DELETE_PROBLEM": + if key == "DELETE_PROBLEM": # pragma: no cover self.status = self.problem_reason.get(value, "Unknown error: %r" % value) - else: + else: # pragma: no cover raise ValueError("Unknown status message: %r" % key) def __nonzero__(self): @@ -655,16 +672,16 @@ "SC_OP_FAILURE", "SC_OP_SUCCESS", "PROGRESS", "PINENTRY_LAUNCHED"): pass - elif key in ("KEYEXPIRED", "SIGEXPIRED"): + elif key in ("KEYEXPIRED", "SIGEXPIRED"): # pragma: no cover self.status = 'key expired' - elif key == "KEYREVOKED": + elif key == "KEYREVOKED": # pragma: no cover self.status = 'key revoked' elif key == "SIG_CREATED": (self.type, algo, self.hash_algo, cls, self.timestamp, self.fingerprint ) = value.split() - else: + else: # pragma: no cover raise ValueError("Unknown status message: %r" % key) VERSION_RE = re.compile(r'gpg \(GnuPG\) (\d+(\.\d+)*)'.encode('ascii'), re.I) @@ -720,7 +737,7 @@ self.secret_keyring = secret_keyring self.verbose = verbose self.use_agent = use_agent - if isinstance(options, str): + if isinstance(options, str): # pragma: no cover options = [options] self.options = options # Changed in 0.3.7 to use Latin-1 encoding rather than @@ -730,14 +747,19 @@ self.encoding = 'latin-1' if gnupghome and not os.path.isdir(self.gnupghome): os.makedirs(self.gnupghome,0x1C0) - p = self._open_subprocess(["--version"]) + try: + p = self._open_subprocess(["--version"]) + except OSError: + msg = 'Unable to run gpg - it may not be available.' + logger.exception(msg) + raise OSError(msg) result = self.result_map['verify'](self) # any result will do for this self._collect_output(p, result, stdin=p.stdin) - if p.returncode != 0: + if p.returncode != 0: # pragma: no cover raise ValueError("Error invoking gpg: %s: %s" % (p.returncode, result.stderr)) m = VERSION_RE.match(result.data) - if not m: + if not m: # pragma: no cover self.version = None else: dot = '.'.encode('ascii') @@ -761,7 +783,7 @@ cmd.extend(['--secret-keyring', no_quote(fn)]) if passphrase: cmd.extend(['--batch', '--passphrase-fd', '0']) - if self.use_agent: + if self.use_agent: # pragma: no cover cmd.append('--use-agent') if self.options: cmd.extend(self.options) @@ -772,13 +794,13 @@ # Internal method: open a pipe to a GPG subprocess and return # the file objects for communicating with it. cmd = self.make_args(args, passphrase) - if self.verbose: + if self.verbose: # pragma: no cover pcmd = ' '.join(cmd) print(pcmd) logger.debug("%s", cmd) if not STARTUPINFO: si = None - else: + else: # pragma: no cover si = STARTUPINFO() si.dwFlags = STARTF_USESHOWWINDOW si.wShowWindow = SW_HIDE @@ -798,7 +820,7 @@ break lines.append(line) line = line.rstrip() - if self.verbose: + if self.verbose: # pragma: no cover print(line) logger.debug("%s", line) if line[0:9] == '[GNUPG:] ': @@ -855,7 +877,7 @@ if stdin is not None: try: stdin.close() - except IOError: + except IOError: # pragma: no cover pass stderr.close() stdout.close() @@ -865,7 +887,7 @@ # Handle a basic data call - pass data to GPG, handle the output # including status information. Garbage In, Garbage Out :) p = self._open_subprocess(args, passphrase is not None) - if not binary: + if not binary: # pragma: no cover stdin = codecs.getwriter(self.encoding)(p.stdin) else: stdin = p.stdin @@ -890,13 +912,13 @@ if os.path.exists(output): # We need to avoid an overwrite confirmation message args.extend(['--batch', '--yes']) - args.extend(['--output', output]) + args.extend(['--output', no_quote(output)]) def sign_file(self, file, keyid=None, passphrase=None, clearsign=True, detach=False, binary=False, output=None): """sign file""" logger.debug("sign_file: %s", file) - if binary: + if binary: # pragma: no cover args = ['-s'] else: args = ['-sa'] @@ -920,7 +942,7 @@ if passphrase: _write_passphrase(stdin, passphrase, self.encoding) writer = _threaded_copy_data(file, stdin) - except IOError: + except IOError: # pragma: no cover logging.exception("error writing message") writer = None self._collect_output(p, result, writer, stdin) @@ -1078,9 +1100,9 @@ def delete_keys(self, fingerprints, secret=False): which='key' - if secret: + if secret: # pragma: no cover which='secret-key' - if _is_sequence(fingerprints): + if _is_sequence(fingerprints): # pragma: no cover fingerprints = [no_quote(s) for s in fingerprints] else: fingerprints = [no_quote(fingerprints)] @@ -1103,7 +1125,7 @@ args = ['--export%s' % which] if armor: args.append('--armor') - if minimal: + if minimal: # pragma: no cover args.extend(['--export-options','export-minimal']) args.extend(keyids) p = self._open_subprocess(args) @@ -1113,7 +1135,11 @@ result = self.result_map['export'](self) self._collect_output(p, result, stdin=p.stdin) logger.debug('export_keys result: %r', result.data) - return result.data.decode(self.encoding, self.decode_errors) + # Issue #49: Return bytes if armor not specified, else text + result = result.data + if armor: + result = result.decode(self.encoding, self.decode_errors) + return result def _get_list_output(self, p, kind): # Get the response information @@ -1121,22 +1147,22 @@ self._collect_output(p, result, stdin=p.stdin) lines = result.data.decode(self.encoding, self.decode_errors).splitlines() - valid_keywords = 'pub uid sec fpr sub'.split() + valid_keywords = 'pub uid sec fpr sub ssb sig'.split() for line in lines: - if self.verbose: + if self.verbose: # pragma: no cover print(line) logger.debug("line: %r", line.rstrip()) - if not line: + if not line: # pragma: no cover break L = line.strip().split(':') - if not L: + if not L: # pragma: no cover continue keyword = L[0] if keyword in valid_keywords: getattr(result, keyword)(L) return result - def list_keys(self, secret=False, keys=None): + def list_keys(self, secret=False, keys=None, sigs=False): """ list the keys currently in the keyring >>> import shutil @@ -1153,7 +1179,10 @@ """ - which='keys' + if sigs: + which = 'sigs' + else: + which='keys' if secret: which='secret-keys' args = ['--list-%s' % which, '--fixed-list-mode', @@ -1208,13 +1237,13 @@ self.decode_errors).splitlines() valid_keywords = ['pub', 'uid'] for line in lines: - if self.verbose: + if self.verbose: # pragma: no cover print(line) logger.debug('line: %r', line.rstrip()) if not line: # sometimes get blank lines on Windows continue L = line.strip().split(':') - if not L: + if not L: # pragma: no cover continue keyword = L[0] if keyword in valid_keywords: @@ -1312,11 +1341,11 @@ args.append('--armor') if output: # write the output to a file with the specified name self.set_output_without_confirmation(args, output) - if sign is True: + if sign is True: # pragma: no cover args.append('--sign') - elif sign: + elif sign: # pragma: no cover args.extend(['--sign', '--default-key', no_quote(sign)]) - if always_trust: + if always_trust: # pragma: no cover args.append('--always-trust') result = self.result_map['crypt'](self) self._handle_io(args, file, result, passphrase=passphrase, binary=True) @@ -1380,7 +1409,7 @@ args = ["--decrypt"] if output: # write the output to a file with the specified name self.set_output_without_confirmation(args, output) - if always_trust: + if always_trust: # pragma: no cover args.append("--always-trust") result = self.result_map['crypt'](self) self._handle_io(args, file, result, passphrase, binary=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.3.8/setup.py new/python-gnupg-0.3.9/setup.py --- old/python-gnupg-0.3.8/setup.py 2015-09-24 19:18:52.000000000 +0200 +++ new/python-gnupg-0.3.9/setup.py 2016-09-09 00:28:05.000000000 +0200 @@ -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-2014 by Vinay Sajip. All Rights Reserved. See LICENSE.txt for license.""", + license="""Copyright (C) 2008-2016 by Vinay Sajip. All Rights Reserved. See LICENSE.txt for license.""", version=version, author="Vinay Sajip", author_email="vinay_sa...@red-dove.com", @@ -32,7 +32,6 @@ "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", "Operating System :: OS Independent", "Topic :: Software Development :: Libraries :: Python Modules" ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.3.8/test_gnupg.py new/python-gnupg-0.3.9/test_gnupg.py --- old/python-gnupg-0.3.8/test_gnupg.py 2015-09-24 19:05:16.000000000 +0200 +++ new/python-gnupg-0.3.9/test_gnupg.py 2016-09-10 09:38:57.000000000 +0200 @@ -2,12 +2,13 @@ """ A test harness for gnupg.py. -Copyright (C) 2008-2014 Vinay Sajip. All rights reserved. +Copyright (C) 2008-2016 Vinay Sajip. All rights reserved. """ import doctest import logging import os.path import os +import re import shutil import stat import sys @@ -17,7 +18,7 @@ import gnupg __author__ = "Vinay Sajip" -__date__ = "$24-Sep-2015 18:05:15$" +__date__ = "$10-Sep-2016 08:38:57$" ALL_TESTS = True @@ -80,16 +81,71 @@ =sqld -----END PGP PUBLIC KEY BLOCK-----""" +SIGNED_KEYS="""-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2 + +mI0EVcnKUQEEAKWazmfM0kbvDdw7Kos2NARaX67c8iJ3GOBimUvYLj4VR3Mqrm34 +ZdLlS8jCmid+qoisefvGW5uw5Q3gIs0mdEdUpFKlXNiIja/Dg/FHjjJPPCjfzDTh +Q03EYA7QvOnXZXhYPBqK7NitsNXW4lPnIJdanLx7yMuL+2Xb+tF39mwnABEBAAG0 +LUpvc2h1YSBDYWx2ZXJ0IChBIHRlc3QgdXNlcikgPGpjQGV4YW1wbGUuY29tPoi3 +BBMBCAAhBQJVycpRAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJELxvNQ+z +0EB2jcED/0lHKaEkyd6cj0Zckf9luIkZ4Hno/vRCquTI7c3aPjS3qmE8mOvKSBCV ++SamPdRM7DdjkdBrrKy2HtiDqbM+1/CdXuQka2SlJWyLCJe48+KWfBpqlY3N4t53 +JjHRitDB+hC8njWTV5prli6EgsBPAF+ZkO0iZhlsMmWdDWgqDpGRiJwEEAEIAAYF +AlXJym8ACgkQBXzPZYwHT9oiiQQAvPF8ubwRopnXIMDQgSxKyFDM1MI1w/wb4Okd +/MkMeZSmdcHJ6pEymp5bYciCBuLW+jw0vZWza3YloO/HtuppnF6A9a1UvYcp/diI +O5qkQqYPlui1PJl7hQ014ioniMfOcC4X/r6PDbC78Pczje0Yh9AOqNGeCyNyNdlc +pjaHb0m4jQRVycpRAQQAo9JjW75F5wTVVO552cGCZWqZvDyBt9+IkoK9Bc+ggdn5 +6R8QVCihYuaSzcSEN84zHaR3MmGKHraCmCSlfe7w0d41Dlns0P03KMdIZOGrm045 +F8TXdSSPQOv5tA4bz3k2lGD0zB8l4NUWFaZ5fzw2i73FF4O/FwCU8xd/JCKVPkkA +EQEAAYifBBgBCAAJBQJVycpRAhsMAAoJELxvNQ+z0EB2xLYD/i3tKirQlVB+32WP +wggstqDp1BlUBmDb+4Gndpg4l7omJTTyOsF26SbYgXZqAdEd5T/UfpEla0DKiBYh +2/CFYXadkgX/ME+GTetTmD4hHoBNmdXau92buXsIXkwh+JR+RC3cl2U6tWb/MIRd +zvJiok8W8/FT/QrEjIa2etN2d+KR +=nNBX +-----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2 + +mI0EVcnKNgEEANIVlIUyRXWHP/ljdMEA8B5NxecRCKusUIPxeapk2do5UCZgR1q8 +5wOP4K/+W3Uj85ylOOCNTFYKRozAHsPMAmQ38W93DZYqFbG6d7rwMvz4pVe0wUtj +SBINoKnoEDZwx3erxFKOkp/5fF3NoYSIx9a0Ds21ESk0TAuH5Tg934YhABEBAAG0 +MVdpbnN0b24gU21pdGggKEEgdGVzdCB1c2VyKSA8d2luc3RvbkBleGFtcGxlLmNv +bT6ItwQTAQgAIQUCVcnKNgIbAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRAF +fM9ljAdP2h05A/4vmnxV1MwcOhJTHZys5g2/j5UoZG7V7lPGpJaojSAIVzYXZtwT +5A7OY8Nl21kIY6gnZlgbTRpHN8Qq2wRKAyW5o6wQvuN16CW4bmGjoHYRGPqkeM0w +G40W/v88JXrYDNNe/68g4pnPsZ3J0oMLbRvCaDQQHXBuZNJrT1sOxl9Of7iNBFXJ +yjYBBACmHbs0PdOF8NEGc+fEtmdKOSKOkrcvg1wTu1KFFTBFEbseHOCNpx+R6lfO +ZiZmHGdKeJhTherfjHaY5jmvyDWq5TLZXK61quNsWxmY2zJ0SRwrIG/CWi4bMi5t +JNc23vMumkz4X5g7x0Ea7xEWkcYBn0H6sZDAtb8d8mrlWkMekQARAQABiJ8EGAEI +AAkFAlXJyjYCGwwACgkQBXzPZYwHT9pQIwP8D9/VroykSE2J3gy0S6HC287jXqXF +0zWejUAQtWUSSRx4esqfLE8lfae6+LDHO8D0Bf6YUJmu7ATOZP2/TIas7JrNvXWc +NKWl2MHEAGUYq8utCjZ3dKKhaV7UvcY4PyLIpFteNkOz4wFe6C0Mm+1NYwokIFyh +zPBq9eFk7Xx9Wrc= +=HT6N +-----END PGP PUBLIC KEY BLOCK----- +""" + + def is_list_with_len(o, n): return isinstance(o, list) and len(o) == n +BASE64_PATTERN = re.compile(r'^(?:[A-Z0-9+/]{4})*(?:[A-Z0-9+/]{2}==|[A-Z0-9+/]{3}=)?$', re.I) + +def get_key_data(s): + lines = s.split('\n') + result = '' + for line in lines: + m = BASE64_PATTERN.match(line) + if m: + result += line + return result + def compare_keys(k1, k2): "Compare ASCII keys" - k1 = k1.split('\n') - k2 = k2.split('\n') - del k1[1] # remove version lines - del k2[1] - return k1 != k2 + # See issue #57: we need to compare only the actual key data, + # ignoring things like spurious blank lines + return get_key_data(k1) != get_key_data(k2) class GPGTestCase(unittest.TestCase): def setUp(self): @@ -102,12 +158,12 @@ self.gpg = gpg = gnupg.GPG(gnupghome=hd, gpgbinary=GPGBINARY) v = gpg.version if v: - if v >= (2,): + if v >= (2,): # pragma: no cover gpg.options = ['--debug-quick-random'] else: gpg.options = ['--quick-random'] self.test_fn = test_fn = 'random_binary_data' - if not os.path.exists(test_fn): + if not os.path.exists(test_fn): # pragma: no cover data_file = open(test_fn, 'wb') data_file.write(os.urandom(5120 * 1024)) data_file.close() @@ -237,14 +293,36 @@ for _, _, sfp in key_info['subkeys']: self.assertTrue(sfp in public_keys.key_map) self.assertTrue(public_keys.key_map[sfp] is key_info) + + # now test with sigs=True + public_keys_sigs = self.gpg.list_keys(sigs=True) + self.assertTrue(is_list_with_len(public_keys_sigs, 1), + "1-element list expected") + key_info = public_keys_sigs[0] + fp = key_info['fingerprint'] + self.assertTrue(fp in public_keys_sigs.key_map) + self.assertTrue(public_keys_sigs.key_map[fp] is key_info) + self.assertTrue(is_list_with_len(key_info['sigs'], 2)) + for siginfo in key_info['sigs']: + self.assertTrue(len(siginfo), 3) + for _, _, sfp in key_info['subkeys']: + self.assertTrue(sfp in public_keys_sigs.key_map) + self.assertTrue(public_keys_sigs.key_map[sfp] is key_info) + private_keys = self.gpg.list_keys(secret=True) self.assertTrue(is_list_with_len(private_keys, 1), "1-element list expected") + self.assertEqual(len(private_keys.fingerprints), 1) # Now do the same test, but using keyring and secret_keyring arguments + pkn = 'pubring.gpg' + skn = 'secring.gpg' hd = os.path.join(os.getcwd(), 'keys') + if os.name == 'posix': + pkn = os.path.join(hd, pkn) + skn = os.path.join(hd, skn) gpg = gnupg.GPG(gnupghome=hd, gpgbinary=GPGBINARY, - keyring=os.path.join(hd, 'pubring.gpg'), - secret_keyring=os.path.join(hd, 'secring.gpg')) + keyring=pkn, secret_keyring=skn) + logger.debug('Using keyring and secret_keyring arguments') public_keys_2 = gpg.list_keys() self.assertEqual(public_keys_2, public_keys) private_keys_2 = gpg.list_keys(secret=True) @@ -283,6 +361,20 @@ 'Donna Davis <donna.da...@delta.com>']) self.assertEqual(actual, expected) + def test_list_signatures(self): + logger.debug("test_list_signatures begins") + imported = self.gpg.import_keys(SIGNED_KEYS) + keys = self.gpg.list_keys(keys=["18897CA2"]) + self.assertTrue(is_list_with_len(keys, 1), "importing test signed key") + sigs = self.gpg.list_keys(keys=["18897CA2"], sigs=True)[0]['sigs'] + logger.debug("testing self-signature") + self.assertTrue(('BC6F350FB3D04076', 'Joshua Calvert (A test user) <j...@example.com>', '13x') in sigs) + logger.debug("testing subkey self-signature") + self.assertTrue(('BC6F350FB3D04076', 'Joshua Calvert (A test user) <j...@example.com>', '18x') in sigs) + logger.debug("testing other signature") + self.assertTrue(('057CCF658C074FDA', 'Winston Smith (A test user) <wins...@example.com>', '10x') in sigs) + logger.debug("test_list_signatures ends") + def test_scan_keys(self): "Test that external key files can be scanned" expected = set([ @@ -315,7 +407,7 @@ edata = str(gpg.encrypt(data, barbara)) self.assertNotEqual(data, edata, "Data must have changed") ddata = gpg.decrypt(edata, passphrase="bbrown") - if data != ddata.data: + if data != ddata.data: # pragma: no cover logger.debug("was: %r", data) logger.debug("new: %r", ddata.data) self.assertEqual(data, ddata.data, "Round-trip must work") @@ -357,15 +449,18 @@ "Exported key should be public") ascii = ascii.replace("\r", "").strip() match = compare_keys(ascii, KEYS_TO_IMPORT) - if match: + if match: # pragma: no cover logger.debug("was: %r", KEYS_TO_IMPORT) logger.debug("now: %r", ascii) self.assertEqual(0, match, "Keys must match") #Generate a key so we can test exporting private keys key = self.do_key_generation() ascii = gpg.export_keys(key.fingerprint, True) + self.assertTrue(isinstance(ascii, gnupg.text_type)) self.assertTrue(ascii.find("PGP PRIVATE KEY BLOCK") >= 0, "Exported key should be private") + binary = gpg.export_keys(key.fingerprint, True, armor=False) + self.assertFalse(isinstance(binary, gnupg.text_type)) logger.debug("test_import_and_export ends") def test_import_only(self): @@ -384,7 +479,7 @@ "Exported key should be public") ascii = ascii.replace("\r", "").strip() match = compare_keys(ascii, KEYS_TO_IMPORT) - if match: + if match: # pragma: no cover logger.debug("was: %r", KEYS_TO_IMPORT) logger.debug("now: %r", ascii) self.assertEqual(0, match, "Keys must match") @@ -406,7 +501,7 @@ self.assertTrue(sig, "Good passphrase should succeed") self.assertTrue(sig.hash_algo) verified = self.gpg.verify(sig.data) - if key.fingerprint != verified.fingerprint: + if key.fingerprint != verified.fingerprint: # pragma: no cover logger.debug("key: %r", key.fingerprint) logger.debug("ver: %r", verified.fingerprint) self.assertEqual(key.fingerprint, verified.fingerprint, @@ -422,10 +517,11 @@ try: file = gnupg._make_binary_stream(sig.data, self.gpg.encoding) verified = self.gpg.verify_file(file) - except UnicodeDecodeError: #happens in Python 2.6 + except UnicodeDecodeError: # pragma: no cover + # sometimes happens in Python 2.6 from io import BytesIO verified = self.gpg.verify_file(BytesIO(sig.data)) - if key.fingerprint != verified.fingerprint: + if key.fingerprint != verified.fingerprint: # pragma: no cover logger.debug("key: %r", key.fingerprint) logger.debug("ver: %r", verified.fingerprint) self.assertEqual(key.fingerprint, verified.fingerprint, @@ -439,10 +535,11 @@ try: file = gnupg._make_binary_stream(sig.data, self.gpg.encoding) verified = self.gpg.verify_file(file, self.test_fn) - except UnicodeDecodeError: #happens in Python 2.6 + except UnicodeDecodeError: # pragma: no cover + # sometimes happens in Python 2.6 from io import BytesIO verified = self.gpg.verify_file(BytesIO(sig.data)) - if key.fingerprint != verified.fingerprint: + if key.fingerprint != verified.fingerprint: # pragma: no cover logger.debug("key: %r", key.fingerprint) logger.debug("ver: %r", verified.fingerprint) self.assertEqual(key.fingerprint, verified.fingerprint, @@ -458,7 +555,7 @@ verified = self.gpg.verify_data(fn, data) finally: os.unlink(fn) - if key.fingerprint != verified.fingerprint: + if key.fingerprint != verified.fingerprint: # pragma: no cover logger.debug("key: %r", key.fingerprint) logger.debug("ver: %r", verified.fingerprint) self.assertEqual(key.fingerprint, verified.fingerprint, @@ -551,7 +648,7 @@ ddata = dfile.read() dfile.close() data = data.encode(self.gpg.encoding) - if ddata != data: + if ddata != data: # pragma: no cover logger.debug("was: %r", data) logger.debug("new: %r", ddata) self.assertEqual(data, ddata, "Round-trip must work") @@ -631,7 +728,7 @@ finally: shutil.rmtree(workdir) - def disabled_test_signing_with_uid(self): + def disabled_test_signing_with_uid(self): # pragma: no cover "Test that signing with uids works. On hold for now." logger.debug("test_signing_with_uid begins") key = self.generate_key("Andrew", "Able", "alpha.com") @@ -653,6 +750,7 @@ 'test_filenames_with_spaces']), 'key' : set(['test_deletion', 'test_import_and_export', 'test_list_keys_after_generation', + 'test_list_signatures', 'test_key_generation_with_invalid_key_type', 'test_key_generation_with_escapes', 'test_key_generation_with_empty_value', @@ -671,7 +769,7 @@ if not args or args == ['--no-doctests']: result = unittest.TestLoader().loadTestsFromTestCase(GPGTestCase) want_doctests = not args - else: + else: # pragma: no cover tests = set() want_doctests = False for arg in args: @@ -688,7 +786,7 @@ def init_logging(): logging.basicConfig(level=logging.DEBUG, filename="test_gnupg.log", - filemode="w", format="%(asctime)s %(levelname)-5s %(name)-10s %(threadName)-10s %(message)s") + filemode="w", format="%(asctime)s %(levelname)-5s %(name)-10s %(threadName)-10s %(lineno)4d %(message)s") def main(): init_logging()