Hello community, here is the log from the commit of package python-gnupg for openSUSE:Factory checked in at 2013-12-16 07:08:58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-gnupg (Old) and /work/SRC/openSUSE:Factory/.python-gnupg.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-gnupg" Changes: -------- --- /work/SRC/openSUSE:Factory/python-gnupg/python-gnupg.changes 2013-01-24 10:36:41.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.python-gnupg.new/python-gnupg.changes 2013-12-16 07:09:00.000000000 +0100 @@ -1,0 +2,11 @@ +Wed Dec 11 00:49:21 UTC 2013 - [email protected] + +- Update to version 0.3.5 + + Added shell quoting to guard against shell injection. + + Fixed issues #76, #77, #78, #79 and #80. +- Changes from 0.3.4 + + Addresses #65, #66, #67, #68 and #70. +- Changes from 0.3.3 + + Addresses issues #57, #61, #62 and #63. + +------------------------------------------------------------------- Old: ---- python-gnupg-0.3.2.tar.gz New: ---- python-gnupg-0.3.5.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-gnupg.spec ++++++ --- /var/tmp/diff_new_pack.aW0EOB/_old 2013-12-16 07:09:01.000000000 +0100 +++ /var/tmp/diff_new_pack.aW0EOB/_new 2013-12-16 07:09:01.000000000 +0100 @@ -17,7 +17,7 @@ Name: python-gnupg -Version: 0.3.2 +Version: 0.3.5 Release: 0 Url: http://code.google.com/p/python-gnupg/ Summary: A wrapper for the Gnu Privacy Guard (GPG or GnuPG) ++++++ python-gnupg-0.3.2.tar.gz -> python-gnupg-0.3.5.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.3.2/LICENSE new/python-gnupg-0.3.5/LICENSE --- old/python-gnupg-0.3.2/LICENSE 2012-02-08 10:57:01.000000000 +0100 +++ new/python-gnupg-0.3.5/LICENSE 2013-02-08 15:09:00.000000000 +0100 @@ -1,4 +1,4 @@ -Copyright (c) 2008-2012 by Vinay Sajip. +Copyright (c) 2008-2013 by Vinay Sajip. All rights reserved. Redistribution and use in source and binary forms, with or without diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.3.2/PKG-INFO new/python-gnupg-0.3.5/PKG-INFO --- old/python-gnupg-0.3.2/PKG-INFO 2013-01-17 13:55:28.000000000 +0100 +++ new/python-gnupg-0.3.5/PKG-INFO 2013-08-30 19:11:27.000000000 +0200 @@ -1,12 +1,12 @@ Metadata-Version: 1.0 Name: python-gnupg -Version: 0.3.2 +Version: 0.3.5 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: [email protected] -License: Copyright (C) 2008-2012 by Vinay Sajip. All Rights Reserved. See LICENSE for license. -Download-URL: http://python-gnupg.googlecode.com/files/python-gnupg-0.3.2.tar.gz +License: Copyright (C) 2008-2013 by Vinay Sajip. All Rights Reserved. See LICENSE for license. +Download-URL: http://python-gnupg.googlecode.com/files/python-gnupg-0.3.5.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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.3.2/gnupg.py new/python-gnupg-0.3.5/gnupg.py --- old/python-gnupg-0.3.2/gnupg.py 2013-01-16 23:34:46.000000000 +0100 +++ new/python-gnupg-0.3.5/gnupg.py 2013-08-30 19:10:36.000000000 +0200 @@ -33,9 +33,9 @@ """ import locale -__version__ = "0.3.2" +__version__ = "0.3.5" __author__ = "Vinay Sajip" -__date__ = "$16-Jan-2013 15:21:59$" +__date__ = "$30-Aug-2013 18:10:36$" try: from io import StringIO @@ -62,13 +62,58 @@ try: unicode _py3k = False + string_types = basestring + text_type = unicode except NameError: _py3k = True + string_types = str + text_type = str logger = logging.getLogger(__name__) if not logger.handlers: logger.addHandler(NullHandler()) +# We use the test below because it works for Jython as well as CPython +if os.path.__name__ == 'ntpath': + # On Windows, we don't need shell quoting, other than worrying about + # paths with spaces in them. + def shell_quote(s): + return '"%s"' % s +else: + # Section copied from sarge + + # This regex determines which shell input needs quoting + # because it may be unsafe + UNSAFE = re.compile(r'[^\w%+,./:=@-]') + + def shell_quote(s): + """ + Quote text so that it is safe for Posix command shells. + + For example, "*.py" would be converted to "'*.py'". If the text is + considered safe it is returned unquoted. + + :param s: The value to quote + :type s: str (or unicode on 2.x) + :return: A safe version of the input, from the point of view of Posix + command shells + :rtype: The passed-in type + """ + if not isinstance(s, string_types): + raise TypeError('Expected string type, got %s' % type(s)) + if not s: + result = "''" + elif len(s) >= 2 and (s[0], s[-1]) == ("'", "'"): + result = '"%s"' % s.replace('"', r'\"') + elif not UNSAFE.search(s): + result = s + else: + result = "'%s'" % s.replace("'", "'\"'\"'") + return result + + # end of sarge code + + def _copy_data(instream, outstream): # Copy one stream to another sent = 0 @@ -78,7 +123,7 @@ enc = 'ascii' while True: data = instream.read(1024) - if len(data) == 0: + if not data: break sent += len(data) logger.debug("sending chunk (%d): %r", sent, data[:256]) @@ -111,16 +156,16 @@ logger.debug("Wrote passphrase: %r", passphrase) def _is_sequence(instance): - return isinstance(instance,list) or isinstance(instance,tuple) + return isinstance(instance, (list, tuple, set, frozenset)) def _make_binary_stream(s, encoding): + if _py3k: + if isinstance(s, str): + s = s.encode(encoding) + else: + if type(s) is not str: + s = s.encode(encoding) try: - if _py3k: - if isinstance(s, str): - s = s.encode(encoding) - else: - if type(s) is not str: - s = s.encode(encoding) from io import BytesIO rv = BytesIO(s) except ImportError: @@ -150,6 +195,7 @@ self.fingerprint = self.creation_date = self.timestamp = None self.signature_id = self.key_id = None self.username = None + self.key_status = None self.status = None self.pubkey_fingerprint = None self.expire_timestamp = None @@ -167,13 +213,26 @@ self.trust_text = key self.trust_level = self.TRUST_LEVELS[key] elif key in ("RSA_OR_IDEA", "NODATA", "IMPORT_RES", "PLAINTEXT", - "PLAINTEXT_LENGTH", "POLICY_URL", "DECRYPTION_INFO", - "DECRYPTION_OKAY", "INV_SGNR"): + "PLAINTEXT_LENGTH", "POLICY_URL", "DECRYPTION_INFO", + "DECRYPTION_OKAY", "INV_SGNR", "FILE_START", "FILE_ERROR", + "FILE_DONE", "PKA_TRUST_GOOD", "PKA_TRUST_BAD", "BADMDC", + "GOODMDC", "NO_SGNR"): pass elif key == "BADSIG": self.valid = False self.status = 'signature bad' self.key_id, self.username = value.split(None, 1) + elif key == "ERRSIG": + self.valid = False + (self.key_id, + algo, hash_algo, + cls, + self.timestamp) = value.split()[:5] + self.status = 'signature error' + elif key == "EXPSIG": + self.valid = False + self.status = 'signature expired' + self.key_id, self.username = value.split(None, 1) elif key == "GOODSIG": self.valid = True self.status = 'signature good' @@ -189,13 +248,6 @@ elif key == "SIG_ID": (self.signature_id, self.creation_date, self.timestamp) = value.split() - elif key == "ERRSIG": - self.valid = False - (self.key_id, - algo, hash_algo, - cls, - self.timestamp) = value.split()[:5] - self.status = 'signature error' elif key == "DECRYPTION_FAILED": self.valid = False self.key_id = value @@ -204,17 +256,21 @@ self.valid = False self.key_id = value self.status = 'no public key' - elif key in ("KEYEXPIRED", "SIGEXPIRED"): + elif key in ("KEYEXPIRED", "SIGEXPIRED", "KEYREVOKED"): # 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. + # the relevant flag is EXPKEYSIG or REVKEYSIG. pass elif key in ("EXPKEYSIG", "REVKEYSIG"): # signed with expired or revoked key self.valid = False self.key_id = value.split()[0] - self.status = (('%s %s') % (key[:3], key[3:])).lower() + if key == "EXPKEYSIG": + self.key_status = 'signing key has expired' + else: + self.key_status = 'signing key was revoked' + self.status = self.key_status else: raise ValueError("Unknown status message: %r" % key) @@ -302,6 +358,21 @@ return ', '.join(l) ESCAPE_PATTERN = re.compile(r'\\x([0-9a-f][0-9a-f])', re.I) +BASIC_ESCAPES = { + r'\n': '\n', + r'\r': '\r', + r'\f': '\f', + r'\v': '\v', + r'\b': '\b', + r'\0': '\0', +} + +class SendResult(object): + def __init__(self, gpg): + self.gpg = gpg + + def handle_status(self, key, value): + logger.debug('SendResult: %s: %s', key, value) class ListKeys(list): ''' Handle status messages for --list-keys. @@ -349,6 +420,8 @@ def uid(self, args): uid = args[9] uid = ESCAPE_PATTERN.sub(lambda m: chr(int(m.group(1), 16)), uid) + for k, v in BASIC_ESCAPES.items(): + uid = uid.replace(k, v) self.curkey['uids'].append(uid) self.uids.append(uid) @@ -359,6 +432,40 @@ def handle_status(self, key, value): pass +class SearchKeys(list): + ''' Handle status messages for --search-keys. + + Handle pub and uid (relating the latter to the former). + + Don't care about the rest + ''' + def __init__(self, gpg): + self.gpg = gpg + self.curkey = None + self.fingerprints = [] + self.uids = [] + + def pub(self, args): + vars = (""" + type keyid algo length date expires + """).split() + self.curkey = {} + for i in range(len(vars)): + self.curkey[vars[i]] = args[i] + self.curkey['uids'] = [] + self.append(self.curkey) + + def uid(self, args): + uid = args[1] + uid = ESCAPE_PATTERN.sub(lambda m: chr(int(m.group(1), 16)), uid) + for k, v in BASIC_ESCAPES.items(): + uid = uid.replace(k, v) + self.curkey['uids'].append(uid) + self.uids.append(uid) + + def handle_status(self, key, value): + pass + class Crypt(Verify): "Handle status messages for --encrypt and --decrypt" def __init__(self, gpg): @@ -379,7 +486,7 @@ def handle_status(self, key, value): if key in ("ENC_TO", "USERID_HINT", "GOODMDC", "END_DECRYPTION", "BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA", - "CARDCTRL"): + "CARDCTRL", "BADMDC", "SC_OP_FAILURE", "SC_OP_SUCCESS"): # in the case of ERROR, this is because a more specific error # message will have come first pass @@ -446,7 +553,7 @@ problem_reason = { '1': 'No such key', '2': 'Must delete secret key first', - '3': 'Ambigious specification', + '3': 'Ambiguous specification', } def handle_status(self, key, value): @@ -456,11 +563,18 @@ else: raise ValueError("Unknown status message: %r" % key) + def __nonzero__(self): + return self.status == 'ok' + + __bool__ = __nonzero__ + + class Sign(object): "Handle status messages for --sign" def __init__(self, gpg): self.gpg = gpg self.type = None + self.hash_algo = None self.fingerprint = None def __nonzero__(self): @@ -473,16 +587,19 @@ def handle_status(self, key, value): if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE", - "GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL", "INV_SGNR"): + "GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL", "INV_SGNR", + "KEYEXPIRED", "SIGEXPIRED", "KEYREVOKED", "NO_SGNR", + "MISSING_PASSPHRASE", "SC_OP_FAILURE", "SC_OP_SUCCESS"): pass elif key == "SIG_CREATED": (self.type, - algo, hashalgo, cls, + algo, self.hash_algo, cls, self.timestamp, self.fingerprint ) = value.split() else: raise ValueError("Unknown status message: %r" % key) +VERSION_RE = re.compile(r'gpg \(GnuPG\) (\d+(\.\d+)*)'.encode('utf-8'), re.I) class GPG(object): @@ -493,27 +610,43 @@ 'delete': DeleteResult, 'generate': GenKey, 'import': ImportResult, + 'send': SendResult, 'list': ListKeys, + 'search': SearchKeys, 'sign': Sign, 'verify': Verify, } "Encapsulate access to the gpg executable" def __init__(self, gpgbinary='gpg', gnupghome=None, verbose=False, - use_agent=False, keyring=None, options=None): + use_agent=False, keyring=None, options=None, + secret_keyring=None): """Initialize a GPG process wrapper. Options are: gpgbinary -- full pathname for GPG binary. gnupghome -- full pathname to where we can find the public and private keyrings. Default is whatever gpg defaults to. - keyring -- name of alternative keyring file to use. If specified, - the default keyring is not used. + keyring -- name of alternative keyring file to use, or list of such + keyrings. If specified, the default keyring is not used. options =-- a list of additional options to pass to the GPG binary. + secret_keyring -- name of alternative secret keyring file to use, or + list of such keyrings. """ self.gpgbinary = gpgbinary self.gnupghome = gnupghome + if keyring: + # Allow passing a string or another iterable. Make it uniformly + # a list of keyring filenames + if isinstance(keyring, string_types): + keyring = [keyring] self.keyring = keyring + if secret_keyring: + # Allow passing a string or another iterable. Make it uniformly + # a list of keyring filenames + if isinstance(secret_keyring, string_types): + secret_keyring = [secret_keyring] + self.secret_keyring = secret_keyring self.verbose = verbose self.use_agent = use_agent if isinstance(options, str): @@ -522,6 +655,10 @@ self.encoding = locale.getpreferredencoding() if self.encoding is None: # This happens on Jython! self.encoding = sys.stdin.encoding + if self.encoding is None: + logger.warning('No encoding found via locale.getpreferredencoding ' + 'or sys.stdin.encoding, defaulting to utf-8.') + self.encoding = 'utf-8' if gnupghome and not os.path.isdir(self.gnupghome): os.makedirs(self.gnupghome,0x1C0) p = self._open_subprocess(["--version"]) @@ -530,6 +667,12 @@ if p.returncode != 0: raise ValueError("Error invoking gpg: %s: %s" % (p.returncode, result.stderr)) + m = VERSION_RE.match(result.data) + if not m: + self.version = None + else: + dot = '.'.encode('utf-8') + self.version = tuple([int(s) for s in m.groups()[0].split(dot)]) def make_args(self, args, passphrase): """ @@ -539,9 +682,14 @@ """ cmd = [self.gpgbinary, '--status-fd 2 --no-tty'] if self.gnupghome: - cmd.append('--homedir "%s" ' % self.gnupghome) + cmd.append('--homedir %s' % shell_quote(self.gnupghome)) if self.keyring: - cmd.append('--no-default-keyring --keyring "%s" ' % self.keyring) + cmd.append('--no-default-keyring') + for fn in self.keyring: + cmd.append('--keyring %s' % shell_quote(fn)) + if self.secret_keyring: + for fn in self.secret_keyring: + cmd.append('--secret-keyring %s' % shell_quote(fn)) if passphrase: cmd.append('--batch --passphrase-fd 0') if self.use_agent: @@ -635,7 +783,7 @@ stderr.close() stdout.close() - def _handle_io(self, args, file, result, passphrase=None, binary=False): + def _handle_io(self, args, fileobj, result, passphrase=None, binary=False): "Handle a call to GPG - pass input data, collect output data" # Handle a basic data call - pass data to GPG, handle the output # including status information. Garbage In, Garbage Out :) @@ -646,7 +794,7 @@ stdin = p.stdin if passphrase: _write_passphrase(stdin, passphrase, self.encoding) - writer = _threaded_copy_data(file, stdin) + writer = _threaded_copy_data(fileobj, stdin) self._collect_output(p, result, writer, stdin) return result @@ -675,7 +823,7 @@ elif clearsign: args.append("--clearsign") if keyid: - args.append('--default-key "%s"' % keyid) + args.append('--default-key %s' % shell_quote(keyid)) result = self.result_map['sign'](self) #We could use _handle_io here except for the fact that if the #passphrase is bad, gpg bails and you can't write the message. @@ -727,8 +875,8 @@ logger.debug('Wrote to temp file: %r', s) os.write(fd, s) os.close(fd) - args.append(fn) - args.append('"%s"' % data_filename) + args.append(shell_quote(fn)) + args.append(shell_quote(data_filename)) try: p = self._open_subprocess(args) self._collect_output(p, result, stdin=p.stdin) @@ -799,7 +947,8 @@ >>> import shutil >>> shutil.rmtree("keys") >>> gpg = GPG(gnupghome="keys") - >>> result = gpg.recv_keys('pgp.mit.edu', '3FF0DB166A7476EA') + >>> os.chmod('keys', 0x1C0) + >>> result = gpg.recv_keys('keyserver.ubuntu.com', '92905378') >>> assert result """ @@ -807,20 +956,39 @@ logger.debug('recv_keys: %r', keyids) data = _make_binary_stream("", self.encoding) #data = "" - args = ['--keyserver', keyserver, '--recv-keys'] - args.extend(keyids) + args = ['--keyserver', shell_quote(keyserver), '--recv-keys'] + args.extend([shell_quote(k) for k in keyids]) self._handle_io(args, data, result, binary=True) logger.debug('recv_keys result: %r', result.__dict__) data.close() return result + def send_keys(self, keyserver, *keyids): + """Send a key to a keyserver. + + Note: it's not practical to test this function without sending + arbitrary data to live keyservers. + """ + result = self.result_map['send'](self) + logger.debug('send_keys: %r', keyids) + data = _make_binary_stream('', self.encoding) + #data = "" + args = ['--keyserver', shell_quote(keyserver), '--send-keys'] + args.extend([shell_quote(k) for k in keyids]) + self._handle_io(args, data, result, binary=True) + logger.debug('send_keys result: %r', result.__dict__) + data.close() + return result + def delete_keys(self, fingerprints, secret=False): which='key' if secret: which='secret-key' if _is_sequence(fingerprints): - fingerprints = ' '.join(fingerprints) - args = ['--batch --delete-%s "%s"' % (which, fingerprints)] + fingerprints = ' '.join([shell_quote(s) for s in fingerprints]) + else: + fingerprints = shell_quote(fingerprints) + args = ['--batch --delete-%s %s' % (which, fingerprints)] result = self.result_map['delete'](self) p = self._open_subprocess(args) self._collect_output(p, result, stdin=p.stdin) @@ -832,8 +1000,10 @@ if secret: which='-secret-key' if _is_sequence(keyids): - keyids = ' '.join(['"%s"' % k for k in keyids]) - args = ["--armor --export%s %s" % (which, keyids)] + keyids = ' '.join([shell_quote(k) for k in keyids]) + else: + keyids = shell_quote(keyids) + args = ['--armor --export%s %s' % (which, keyids)] p = self._open_subprocess(args) # gpg --export produces no status-fd output; stdout will be # empty in case of failure @@ -890,6 +1060,46 @@ getattr(result, keyword)(L) return result + def search_keys(self, query, keyserver='pgp.mit.edu'): + """ search keyserver by query (using --search-keys option) + + >>> import shutil + >>> shutil.rmtree('keys') + >>> gpg = GPG(gnupghome='keys') + >>> os.chmod('keys', 0x1C0) + >>> result = gpg.search_keys('<[email protected]>') + >>> assert result + >>> keyserver = 'keyserver.ubuntu.com' + >>> result = gpg.search_keys('<[email protected]>', keyserver) + >>> assert result + + """ + + args = ['--fixed-list-mode', '--fingerprint', '--with-colons', + '--keyserver', shell_quote(keyserver), '--search-keys', + shell_quote(query)] + p = self._open_subprocess(args) + + # Get the response information + result = self.result_map['search'](self) + self._collect_output(p, result, stdin=p.stdin) + lines = result.data.decode(self.encoding, + self.decode_errors).splitlines() + valid_keywords = ['pub', 'uid'] + for line in lines: + if self.verbose: + 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: + continue + keyword = L[0] + if keyword in valid_keywords: + getattr(result, keyword)(L) + return result + def gen_key(self, input): """Generate a key; you might use gen_key_input() to create the control input. @@ -919,9 +1129,8 @@ if str(val).strip(): # skip empty strings parms[key] = val parms.setdefault('Key-Type','RSA') - parms.setdefault('Key-Length',1024) + parms.setdefault('Key-Length',2048) parms.setdefault('Name-Real', "Autogenerated Key") - parms.setdefault('Name-Comment', "Generated by gnupg.py") try: logname = os.environ['LOGNAME'] except KeyError: @@ -966,21 +1175,26 @@ "Encrypt the message read from the file-like object 'file'" args = ['--encrypt'] if symmetric: + # can't be False or None - could be True or a cipher algo value + # such as AES256 args = ['--symmetric'] + if symmetric is not True: + args.extend(['--cipher-algo', shell_quote(symmetric)]) + # else use the default, currently CAST5 else: args = ['--encrypt'] if not _is_sequence(recipients): recipients = (recipients,) for recipient in recipients: - args.append('--recipient "%s"' % recipient) - if armor: # create ascii-armored output - set to False for binary output + args.append('--recipient %s' % shell_quote(recipient)) + if armor: # create ascii-armored output - False for binary output args.append('--armor') if output: # write the output to a file with the specified name if os.path.exists(output): os.remove(output) # to avoid overwrite confirmation message - args.append('--output "%s"' % output) + args.append('--output %s' % shell_quote(output)) if sign: - args.append('--sign --default-key "%s"' % sign) + args.append('--sign --default-key %s' % shell_quote(sign)) if always_trust: args.append("--always-trust") result = self.result_map['crypt'](self) @@ -1046,7 +1260,7 @@ if output: # write the output to a file with the specified name if os.path.exists(output): os.remove(output) # to avoid overwrite confirmation message - args.append('--output "%s"' % output) + args.append('--output %s' % shell_quote(output)) if always_trust: args.append("--always-trust") result = self.result_map['crypt'](self) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.3.2/setup.py new/python-gnupg-0.3.5/setup.py --- old/python-gnupg-0.3.2/setup.py 2012-10-17 09:01:28.000000000 +0200 +++ new/python-gnupg-0.3.5/setup.py 2013-01-23 12:01:38.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-2012 by Vinay Sajip. All Rights Reserved. See LICENSE for license.""", + license="""Copyright (C) 2008-2013 by Vinay Sajip. All Rights Reserved. See LICENSE for license.""", version=version, author="Vinay Sajip", author_email="[email protected]", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.3.2/test_gnupg.py new/python-gnupg-0.3.5/test_gnupg.py --- old/python-gnupg-0.3.2/test_gnupg.py 2013-01-16 17:07:23.000000000 +0100 +++ new/python-gnupg-0.3.5/test_gnupg.py 2013-08-30 19:10:57.000000000 +0200 @@ -16,12 +16,14 @@ import gnupg __author__ = "Vinay Sajip" -__date__ = "$16-Jan-2013 15:23:54$" +__date__ = "$30-Aug-2013 18:10:57$" ALL_TESTS = True logger = logging.getLogger(__name__) +GPGBINARY = os.environ.get('GPGBINARY', 'gpg') + KEYS_TO_IMPORT = """-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.9 (MingW32) @@ -96,7 +98,9 @@ "Not a directory: %s" % hd) shutil.rmtree(hd) self.homedir = hd - self.gpg = gnupg.GPG(gnupghome=hd, gpgbinary='gpg') + self.gpg = gnupg.GPG(gnupghome=hd, gpgbinary=GPGBINARY) + if self.gpg.version and self.gpg.version >= (2,): + self.gpg.options = ['--debug-quick-random'] def test_environment(self): "Test the environment by ensuring that setup worked" @@ -125,6 +129,10 @@ 'Name-Comment': 'A test user', 'Expire-Date': 0, } + if '--debug-quick-random' in (self.gpg.options or []): + # If using the fake RNG, a key isn't regarded as valid + # unless its comment has the text (insecure!) in it. + params['Name-Comment'] = 'A test user (insecure!)' params['Name-Real'] = '%s %s' % (first_name, last_name) params['Name-Email'] = ("%s.%s@%s" % (first_name, last_name, domain)).lower() if passphrase is None: @@ -175,18 +183,33 @@ self.assertEqual(uid, 'urn:uuid:731c22c4-830f-422f-80dc-14a9fdae8c19 ' '(dummy comment) <[email protected]>') + def test_key_generation_with_escapes(self): + "Test that key generation handles escape characters" + cmd = self.gpg.gen_key_input(name_comment='Funny chars: ' + '\\r\\n\\f\\v\\0\\b', + name_real='Test Name', + name_email='[email protected]') + result = self.gpg.gen_key(cmd) + keys = self.gpg.list_keys() + self.assertEqual(len(keys), 1) + key = keys[0] + uids = key['uids'] + self.assertEqual(len(uids), 1) + uid = uids[0] + self.assertEqual(uid, 'Test Name (Funny chars: ' + '\r\n\x0c\x0b\x00\x08) <[email protected]>') + def test_key_generation_with_empty_value(self): "Test that key generation handles empty values" params = { - 'key_type': 'RSA', - 'key_length': 1024, - 'name_comment': ' ', # Not added, so default will appear + 'key_type': ' ', + 'key_length': 2048, } cmd = self.gpg.gen_key_input(**params) - self.assertTrue('\nName-Comment: Generated by gnupg.py\n' in cmd) - params['name_comment'] = 'A' + self.assertTrue('Key-Type: RSA\n' in cmd) + params['key_type'] = 'DSA' cmd = self.gpg.gen_key_input(**params) - self.assertTrue('\nName-Comment: A\n' in cmd) + self.assertTrue('Key-Type: DSA\n' in cmd) def test_list_keys_after_generation(self): "Test that after key generation, the generated key is available" @@ -198,6 +221,15 @@ private_keys = self.gpg.list_keys(secret=True) self.assertTrue(is_list_with_len(private_keys, 1), "1-element list expected") + # Now do the same test, but using keyring and secret_keyring arguments + hd = os.path.join(os.getcwd(), 'keys') + gpg = gnupg.GPG(gnupghome=hd, gpgbinary=GPGBINARY, + keyring=os.path.join(hd, 'pubring.gpg'), + secret_keyring=os.path.join(hd, 'secring.gpg')) + public_keys_2 = gpg.list_keys() + self.assertEqual(public_keys_2, public_keys) + private_keys_2 = gpg.list_keys(secret=True) + self.assertEqual(private_keys_2, private_keys) def test_encryption_and_decryption(self): "Test that encryption and decryption works" @@ -233,6 +265,12 @@ edata = str(gpg.encrypt(data, None, passphrase='bbrown', symmetric=True)) ddata = gpg.decrypt(edata, passphrase='bbrown') self.assertEqual(data, str(ddata)) + # Test symmetric encryption with non-default cipher + data = "chippy was here" + edata = str(gpg.encrypt(data, None, passphrase='bbrown', + symmetric='AES256')) + ddata = gpg.decrypt(edata, passphrase='bbrown') + self.assertEqual(data, str(ddata)) def test_import_and_export(self): "Test that key import and export works" @@ -299,6 +337,7 @@ self.assertFalse(sig, "Bad passphrase should fail") sig = self.gpg.sign(data, keyid=key.fingerprint, passphrase='aable') self.assertTrue(sig, "Good passphrase should succeed") + self.assertTrue(sig.hash_algo) verified = self.gpg.verify(sig.data) if key.fingerprint != verified.fingerprint: logger.debug("key: %r", key.fingerprint) @@ -316,6 +355,7 @@ passphrase='aable') data_file.close() self.assertTrue(sig, "File signing should succeed") + self.assertTrue(sig.hash_algo) try: file = gnupg._make_binary_stream(sig.data, self.gpg.encoding) verified = self.gpg.verify_file(file) @@ -332,6 +372,7 @@ passphrase='aable', detach=True) data_file.close() self.assertTrue(sig, "File signing should succeed") + self.assertTrue(sig.hash_algo) try: file = gnupg._make_binary_stream(sig.data, self.gpg.encoding) verified = self.gpg.verify_file(file, 'random_binary_data') @@ -411,6 +452,15 @@ os.remove(fn) logger.debug("test_file_encryption_and_decryption ends") + def test_search_keys(self): + "Test that searching for keys works" + r = self.gpg.search_keys('<[email protected]>') + self.assertTrue(r) + self.assertTrue('Vinay Sajip <[email protected]>' in r[0]['uids']) + r = self.gpg.search_keys('92905378') + self.assertTrue(r) + self.assertTrue('Vinay Sajip <[email protected]>' in r[0]['uids']) + TEST_GROUPS = { 'sign' : set(['test_signature_verification']), @@ -419,8 +469,10 @@ 'key' : set(['test_deletion', 'test_import_and_export', 'test_list_keys_after_generation', 'test_key_generation_with_invalid_key_type', + 'test_key_generation_with_escapes', 'test_key_generation_with_empty_value', - 'test_key_generation_with_colons']), + 'test_key_generation_with_colons', + 'test_search_keys']), 'import' : set(['test_import_only']), 'basic' : set(['test_environment', 'test_list_keys_initial', 'test_nogpg', 'test_make_args']), -- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
