Hello community, here is the log from the commit of package python-python-gnupg for openSUSE:Factory checked in at 2020-08-05 20:30:03 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-python-gnupg (Old) and /work/SRC/openSUSE:Factory/.python-python-gnupg.new.3592 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-python-gnupg" Wed Aug 5 20:30:03 2020 rev:9 rq:824526 version:0.4.6 Changes: -------- --- /work/SRC/openSUSE:Factory/python-python-gnupg/python-python-gnupg.changes 2019-09-11 10:36:15.411281183 +0200 +++ /work/SRC/openSUSE:Factory/.python-python-gnupg.new.3592/python-python-gnupg.changes 2020-08-05 20:30:15.347125442 +0200 @@ -1,0 +2,9 @@ +Wed Aug 5 13:20:26 UTC 2020 - Marketa Calabkova <mcalabk...@suse.com> + +- Update to 0.4.6 + * Fixed #128: Added ECC support by changing key generation parameters. (The Key-Length + value isn't added if a curve is specified.) + * More bugfixes. + * Support for Python versions 3.5 and under is discontinued, except for Python 2.7. + +------------------------------------------------------------------- Old: ---- python-gnupg-0.4.5.tar.gz New: ---- python-gnupg-0.4.6.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-python-gnupg.spec ++++++ --- /var/tmp/diff_new_pack.8Ef11b/_old 2020-08-05 20:30:17.179126398 +0200 +++ /var/tmp/diff_new_pack.8Ef11b/_new 2020-08-05 20:30:17.179126398 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-python-gnupg # -# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2020 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define oldpython python Name: python-python-gnupg -Version: 0.4.5 +Version: 0.4.6 Release: 0 Summary: A wrapper for the GNU Privacy Guard (GPG or GnuPG) License: BSD-3-Clause ++++++ python-gnupg-0.4.5.tar.gz -> python-gnupg-0.4.6.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.5/MANIFEST.in new/python-gnupg-0.4.6/MANIFEST.in --- old/python-gnupg-0.4.5/MANIFEST.in 1970-01-01 01:00:00.000000000 +0100 +++ new/python-gnupg-0.4.6/MANIFEST.in 2020-04-17 08:50:10.000000000 +0200 @@ -0,0 +1,6 @@ +include LICENSE.txt +include README.rst +include test_gnupg.py +include messages.json +include test_*ring.gpg + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.5/PKG-INFO new/python-gnupg-0.4.6/PKG-INFO --- old/python-gnupg-0.4.5/PKG-INFO 2019-08-12 18:50:07.000000000 +0200 +++ new/python-gnupg-0.4.6/PKG-INFO 2020-04-17 13:30:43.000000000 +0200 @@ -1,12 +1,14 @@ -Metadata-Version: 1.1 +Metadata-Version: 1.2 Name: python-gnupg -Version: 0.4.5 +Version: 0.4.6 Summary: A wrapper for the Gnu Privacy Guard (GPG or GnuPG) Home-page: https://docs.red-dove.com/python-gnupg/ Author: Vinay Sajip Author-email: vinay_sa...@red-dove.com +Maintainer: Vinay Sajip +Maintainer-email: vinay_sa...@red-dove.com 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.5.tar.gz +Download-URL: https://pypi.io/packages/source/p/python-gnupg/python-gnupg-0.4.6.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 @@ -15,15 +17,9 @@ Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 2.4 -Classifier: Programming Language :: Python :: 2.5 -Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3.2 -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: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 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.5/README.rst new/python-gnupg-0.4.6/README.rst --- old/python-gnupg-0.4.5/README.rst 2019-08-12 18:47:20.000000000 +0200 +++ new/python-gnupg-0.4.6/README.rst 2020-04-17 10:33:32.000000000 +0200 @@ -64,12 +64,33 @@ .. note:: GCnn refers to an issue nn on Google Code. -0.4.6 (future) +0.4.7 (future) -------------- Released: Not yet. +0.4.6 +----- + +Released: 2020-04-17 + +* Fixed #122: Updated documentation about gnupghome needing to be an existing + directory. + +* Fixed #123: Handled error conditions from gpg when calling trust_keys(). + +* Fixed #124: Avoided an exception being raised when ImportResult.summary() + was called after a failed recv_keys(). + +* Fixed #128: Added ECC support by changing key generation parameters. (The Key-Length + value isn't added if a curve is specified.) + +* Fixed #130: Provided a mechanism to provide more complete error messages. + +Support for Python versions 3.5 and under is discontinued, except for Python 2.7. + + 0.4.5 ----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.5/gnupg.py new/python-gnupg-0.4.6/gnupg.py --- old/python-gnupg-0.4.5/gnupg.py 2019-08-12 18:47:20.000000000 +0200 +++ new/python-gnupg-0.4.6/gnupg.py 2020-04-17 10:35:35.000000000 +0200 @@ -32,9 +32,9 @@ A unittest harness (test_gnupg.py) has also been added. """ -__version__ = "0.4.5" +__version__ = "0.4.6" __author__ = "Vinay Sajip" -__date__ = "$12-Aug-2019 15:58:03$" +__date__ = "$17-Apr-2020 09:35:35$" try: from io import StringIO @@ -349,17 +349,20 @@ # write to "/etc/foo" as a non-root user, a "permission denied" # error will be sent as a non-status message. message = 'error - %s' % value - parts = value.split() - if parts[-1].isdigit(): - code = int(parts[-1]) - system_error = bool(code & 0x8000) - code = code & 0x7FFF - if system_error: - mapping = self.GPG_SYSTEM_ERROR_CODES + operation, code = value.rsplit(' ', 1) + if code.isdigit(): + code = int(code) & 0xFFFFFF # lose the error source + if self.gpg.error_map and code in self.gpg.error_map: + message = '%s: %s' % (operation, self.gpg.error_map[code]) else: - mapping = self.GPG_ERROR_CODES - if code in mapping: - message = mapping[code] + system_error = bool(code & 0x8000) + code = code & 0x7FFF + if system_error: + mapping = self.GPG_SYSTEM_ERROR_CODES + else: + mapping = self.GPG_ERROR_CODES + if code in mapping: + message = '%s: %s' % (operation, mapping[code]) if not self.status: self.status = message elif key in ("DECRYPTION_INFO", "PLAINTEXT", "PLAINTEXT_LENGTH", @@ -376,11 +379,10 @@ sec_dups not_imported'''.split() def __init__(self, gpg): self.gpg = gpg - self.imported = [] self.results = [] self.fingerprints = [] for result in self.counts: - setattr(self, result, None) + setattr(self, result, 0) def __nonzero__(self): if self.not_imported: return False @@ -450,11 +452,11 @@ logger.debug('message ignored: %s, %s', key, value) def summary(self): - l = [] - l.append('%d imported' % self.imported) + result = [] + result.append('%d imported' % self.imported) if self.not_imported: # pragma: no cover - l.append('%d not imported' % self.not_imported) - return ', '.join(l) + result.append('%d not imported' % self.not_imported) + return ', '.join(result) ESCAPE_PATTERN = re.compile(r'\\x([0-9a-f][0-9a-f])', re.I) BASIC_ESCAPES = { @@ -737,6 +739,10 @@ __bool__ = __nonzero__ +class TrustResult(DeleteResult): + pass + + class Sign(TextHandler): "Handle status messages for --sign" def __init__(self, gpg): @@ -779,6 +785,8 @@ class GPG(object): + error_map = None + decode_errors = 'strict' result_map = { @@ -791,6 +799,7 @@ 'scan': ScanKeys, 'search': SearchKeys, 'sign': Sign, + 'trust': TrustResult, 'verify': Verify, 'export': ExportResult, } @@ -990,7 +999,8 @@ rr.start() stdout = process.stdout - dr = threading.Thread(target=self._read_data, args=(stdout, result, self.on_data)) + dr = threading.Thread(target=self._read_data, args=(stdout, result, + self.on_data)) dr.setDaemon(True) logger.debug('stdout reader: %r', dr) dr.start() @@ -1000,8 +1010,9 @@ if writer is not None: writer.join() process.wait() - if process.returncode != 0: - logger.warning('gpg returned a non-zero error code: %d', process.returncode) + rc = process.returncode + if rc != 0: + logger.warning('gpg returned a non-zero error code: %d', rc) if stdin is not None: try: stdin.close() @@ -1456,7 +1467,8 @@ if str(val).strip(): # skip empty strings parms[key] = val parms.setdefault('Key-Type','RSA') - parms.setdefault('Key-Length',2048) + if 'key_curve' not in kwargs: + parms.setdefault('Key-Length',2048) parms.setdefault('Name-Real', "Autogenerated Key") logname = (os.environ.get('LOGNAME') or os.environ.get('USERNAME') or 'unspecified') @@ -1623,9 +1635,12 @@ logger.debug('writing ownertrust info: %s', s); os.write(fd, s.encode(self.encoding)) os.close(fd) - result = self.result_map['delete'](self) + result = self.result_map['trust'](self) p = self._open_subprocess(['--import-ownertrust', fn]) self._collect_output(p, result, stdin=p.stdin) + if p.returncode != 0: + raise ValueError('gpg returned an error - return code %d' % + p.returncode) finally: os.remove(fn) return result diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.5/messages.json new/python-gnupg-0.4.6/messages.json --- old/python-gnupg-0.4.5/messages.json 1970-01-01 01:00:00.000000000 +0100 +++ new/python-gnupg-0.4.6/messages.json 2020-04-17 08:02:24.000000000 +0200 @@ -0,0 +1,565 @@ +{ + "0000": "Success", + "0001": "General error", + "0002": "Unknown packet", + "0003": "Unknown version in packet", + "0004": "Invalid public key algorithm", + "0005": "Invalid digest algorithm", + "0006": "Bad public key", + "0007": "Bad secret key", + "0008": "Bad signature", + "0009": "No public key", + "000A": "Checksum error", + "000B": "Bad passphrase", + "000C": "Invalid cipher algorithm", + "000D": "Cannot open keyring", + "000E": "Invalid packet", + "000F": "Invalid armor", + "0010": "No user ID", + "0011": "No secret key", + "0012": "Wrong secret key used", + "0013": "Bad session key", + "0014": "Unknown compression algorithm", + "0015": "Number is not prime", + "0016": "Invalid encoding method", + "0017": "Invalid encryption scheme", + "0018": "Invalid signature scheme", + "0019": "Invalid attribute", + "001A": "No value", + "001B": "Not found", + "001C": "Value not found", + "001D": "Syntax error", + "001E": "Bad MPI value", + "001F": "Invalid passphrase", + "0020": "Invalid signature class", + "0021": "Resources exhausted", + "0022": "Invalid keyring", + "0023": "Trust DB error", + "0024": "Bad certificate", + "0025": "Invalid user ID", + "0026": "Unexpected error", + "0027": "Time conflict", + "0028": "Keyserver error", + "0029": "Wrong public key algorithm", + "002A": "Tribute to D. A.", + "002B": "Weak encryption key", + "002C": "Invalid key length", + "002D": "Invalid argument", + "002E": "Syntax error in URI", + "002F": "Invalid URI", + "0030": "Network error", + "0031": "Unknown host", + "0032": "Selftest failed", + "0033": "Data not encrypted", + "0034": "Data not processed", + "0035": "Unusable public key", + "0036": "Unusable secret key", + "0037": "Invalid value", + "0038": "Bad certificate chain", + "0039": "Missing certificate", + "003A": "No data", + "003B": "Bug", + "003C": "Not supported", + "003D": "Invalid operation code", + "003E": "Timeout", + "003F": "Internal error", + "0040": "EOF (gcrypt)", + "0041": "Invalid object", + "0042": "Provided object is too short", + "0043": "Provided object is too large", + "0044": "Missing item in object", + "0045": "Not implemented", + "0046": "Conflicting use", + "0047": "Invalid cipher mode", + "0048": "Invalid flag", + "0049": "Invalid handle", + "004A": "Result truncated", + "004B": "Incomplete line", + "004C": "Invalid response", + "004D": "No agent running", + "004E": "Agent error", + "004F": "Invalid data", + "0050": "Unspecific Assuan server fault", + "0051": "General Assuan error", + "0052": "Invalid session key", + "0053": "Invalid S-expression", + "0054": "Unsupported algorithm", + "0055": "No pinentry", + "0056": "pinentry error", + "0057": "Bad PIN", + "0058": "Invalid name", + "0059": "Bad data", + "005A": "Invalid parameter", + "005B": "Wrong card", + "005C": "No dirmngr", + "005D": "dirmngr error", + "005E": "Certificate revoked", + "005F": "No CRL known", + "0060": "CRL too old", + "0061": "Line too long", + "0062": "Not trusted", + "0063": "Operation cancelled", + "0064": "Bad CA certificate", + "0065": "Certificate expired", + "0066": "Certificate too young", + "0067": "Unsupported certificate", + "0068": "Unknown S-expression", + "0069": "Unsupported protection", + "006A": "Corrupted protection", + "006B": "Ambiguous name", + "006C": "Card error", + "006D": "Card reset required", + "006E": "Card removed", + "006F": "Invalid card", + "0070": "Card not present", + "0071": "No PKCS15 application", + "0072": "Not confirmed", + "0073": "Configuration error", + "0074": "No policy match", + "0075": "Invalid index", + "0076": "Invalid ID", + "0077": "No SmartCard daemon", + "0078": "SmartCard daemon error", + "0079": "Unsupported protocol", + "007A": "Bad PIN method", + "007B": "Card not initialized", + "007C": "Unsupported operation", + "007D": "Wrong key usage", + "007E": "Nothing found", + "007F": "Wrong blob type", + "0080": "Missing value", + "0081": "Hardware problem", + "0082": "PIN blocked", + "0083": "Conditions of use not satisfied", + "0084": "PINs are not synced", + "0085": "Invalid CRL", + "0086": "BER error", + "0087": "Invalid BER", + "0088": "Element not found", + "0089": "Identifier not found", + "008A": "Invalid tag", + "008B": "Invalid length", + "008C": "Invalid key info", + "008D": "Unexpected tag", + "008E": "Not DER encoded", + "008F": "No CMS object", + "0090": "Invalid CMS object", + "0091": "Unknown CMS object", + "0092": "Unsupported CMS object", + "0093": "Unsupported encoding", + "0094": "Unsupported CMS version", + "0095": "Unknown algorithm", + "0096": "Invalid crypto engine", + "0097": "Public key not trusted", + "0098": "Decryption failed", + "0099": "Key expired", + "009A": "Signature expired", + "009B": "Encoding problem", + "009C": "Invalid state", + "009D": "Duplicated value", + "009E": "Missing action", + "009F": "ASN.1 module not found", + "00A0": "Invalid OID string", + "00A1": "Invalid time", + "00A2": "Invalid CRL object", + "00A3": "Unsupported CRL version", + "00A4": "Invalid certificate object", + "00A5": "Unknown name", + "00A6": "A locale function failed", + "00A7": "Not locked", + "00A8": "Protocol violation", + "00A9": "Invalid MAC", + "00AA": "Invalid request", + "00AB": "Unknown extension", + "00AC": "Unknown critical extension", + "00AD": "Locked", + "00AE": "Unknown option", + "00AF": "Unknown command", + "00B0": "Not operational", + "00B1": "No passphrase given", + "00B2": "No PIN given", + "00B3": "Not enabled", + "00B4": "No crypto engine", + "00B5": "Missing key", + "00B6": "Too many objects", + "00B7": "Limit reached", + "00B8": "Not initialized", + "00B9": "Missing issuer certificate", + "00BA": "No keyserver available", + "00BB": "Invalid elliptic curve", + "00BC": "Unknown elliptic curve", + "00BD": "Duplicated key", + "00BE": "Ambiguous result", + "00BF": "No crypto context", + "00C0": "Wrong crypto context", + "00C1": "Bad crypto context", + "00C2": "Conflict in the crypto context", + "00C3": "Broken public key", + "00C4": "Broken secret key", + "00C5": "Invalid MAC algorithm", + "00C6": "Operation fully cancelled", + "00C7": "Operation not yet finished", + "00C8": "Buffer too short", + "00C9": "Invalid length specifier in S-expression", + "00CA": "String too long in S-expression", + "00CB": "Unmatched parentheses in S-expression", + "00CC": "S-expression not canonical", + "00CD": "Bad character in S-expression", + "00CE": "Bad quotation in S-expression", + "00CF": "Zero prefix in S-expression", + "00D0": "Nested display hints in S-expression", + "00D1": "Unmatched display hints", + "00D2": "Unexpected reserved punctuation in S-expression", + "00D3": "Bad hexadecimal character in S-expression", + "00D4": "Odd hexadecimal numbers in S-expression", + "00D5": "Bad octal character in S-expression", + "00D9": "All subkeys are expired or revoked", + "00DA": "Database is corrupted", + "00DB": "Server indicated a failure", + "00DC": "No name", + "00DD": "No key", + "00DE": "Legacy key", + "00DF": "Request too short", + "00E0": "Request too long", + "00E1": "Object is in termination state", + "00E2": "No certificate chain", + "00E3": "Certificate is too large", + "00E4": "Invalid record", + "00E5": "The MAC does not verify", + "00E6": "Unexpected message", + "00E7": "Compression or decompression failed", + "00E8": "A counter would wrap", + "00E9": "Fatal alert message received", + "00EA": "No cipher algorithm", + "00EB": "Missing client certificate", + "00EC": "Close notification received", + "00ED": "Ticket expired", + "00EE": "Bad ticket", + "00EF": "Unknown identity", + "00F0": "Bad certificate message in handshake", + "00F1": "Bad certificate request message in handshake", + "00F2": "Bad certificate verify message in handshake", + "00F3": "Bad change cipher message in handshake", + "00F4": "Bad client hello message in handshake", + "00F5": "Bad server hello message in handshake", + "00F6": "Bad server hello done message in handshake", + "00F7": "Bad finished message in handshake", + "00F8": "Bad server key exchange message in handshake", + "00F9": "Bad client key exchange message in handshake", + "00FA": "Bogus string", + "00FB": "Forbidden", + "00FC": "Key disabled", + "00FD": "Not possible with a card based key", + "00FE": "Invalid lock object", + "00FF": "True", + "0100": "False", + "0101": "General IPC error", + "0102": "IPC accept call failed", + "0103": "IPC connect call failed", + "0104": "Invalid IPC response", + "0105": "Invalid value passed to IPC", + "0106": "Incomplete line passed to IPC", + "0107": "Line passed to IPC too long", + "0108": "Nested IPC commands", + "0109": "No data callback in IPC", + "010A": "No inquire callback in IPC", + "010B": "Not an IPC server", + "010C": "Not an IPC client", + "010D": "Problem starting IPC server", + "010E": "IPC read error", + "010F": "IPC write error", + "0111": "Too much data for IPC layer", + "0112": "Unexpected IPC command", + "0113": "Unknown IPC command", + "0114": "IPC syntax error", + "0115": "IPC call has been cancelled", + "0116": "No input source for IPC", + "0117": "No output source for IPC", + "0118": "IPC parameter error", + "0119": "Unknown IPC inquire", + "012C": "Crypto engine too old", + "012D": "Screen or window too small", + "012E": "Screen or window too large", + "012F": "Required environment variable not set", + "0130": "User ID already exists", + "0131": "Name already exists", + "0132": "Duplicated name", + "0133": "Object is too young", + "0134": "Object is too old", + "0135": "Unknown flag", + "0136": "Invalid execution order", + "0137": "Already fetched", + "0138": "Try again later", + "0139": "Wrong name", + "013A": "Not authenticated", + "013B": "Bad authentication", + "013C": "No Keybox daemon running", + "013D": "Keybox daemon error", + "013E": "Service is not running", + "013F": "Service error", + "029A": "System bug detected", + "02C7": "Unknown DNS error", + "02C8": "Invalid DNS section", + "02C9": "Invalid textual address form", + "02CA": "Missing DNS query packet", + "02CB": "Missing DNS answer packet", + "02CC": "Connection closed in DNS", + "02CD": "Verification failed in DNS", + "02CE": "DNS Timeout", + "02D1": "General LDAP error", + "02D2": "General LDAP attribute error", + "02D3": "General LDAP name error", + "02D4": "General LDAP security error", + "02D5": "General LDAP service error", + "02D6": "General LDAP update error", + "02D7": "Experimental LDAP error code", + "02D8": "Private LDAP error code", + "02D9": "Other general LDAP error", + "02EE": "LDAP connecting failed (X)", + "02EF": "LDAP referral limit exceeded", + "02F0": "LDAP client loop", + "02F2": "No LDAP results returned", + "02F3": "LDAP control not found", + "02F4": "Not supported by LDAP", + "02F5": "LDAP connect error", + "02F6": "Out of memory in LDAP", + "02F7": "Bad parameter to an LDAP routine", + "02F8": "User cancelled LDAP operation", + "02F9": "Bad LDAP search filter", + "02FA": "Unknown LDAP authentication method", + "02FB": "Timeout in LDAP", + "02FC": "LDAP decoding error", + "02FD": "LDAP encoding error", + "02FE": "LDAP local error", + "02FF": "Cannot contact LDAP server", + "0300": "LDAP success", + "0301": "LDAP operations error", + "0302": "LDAP protocol error", + "0303": "Time limit exceeded in LDAP", + "0304": "Size limit exceeded in LDAP", + "0305": "LDAP compare false", + "0306": "LDAP compare true", + "0307": "LDAP authentication method not supported", + "0308": "Strong(er) LDAP authentication required", + "0309": "Partial LDAP results+referral received", + "030A": "LDAP referral", + "030B": "Administrative LDAP limit exceeded", + "030C": "Critical LDAP extension is unavailable", + "030D": "Confidentiality required by LDAP", + "030E": "LDAP SASL bind in progress", + "0310": "No such LDAP attribute", + "0311": "Undefined LDAP attribute type", + "0312": "Inappropriate matching in LDAP", + "0313": "Constraint violation in LDAP", + "0314": "LDAP type or value exists", + "0315": "Invalid syntax in LDAP", + "0320": "No such LDAP object", + "0321": "LDAP alias problem", + "0322": "Invalid DN syntax in LDAP", + "0323": "LDAP entry is a leaf", + "0324": "LDAP alias dereferencing problem", + "032F": "LDAP proxy authorization failure (X)", + "0330": "Inappropriate LDAP authentication", + "0331": "Invalid LDAP credentials", + "0332": "Insufficient access for LDAP", + "0333": "LDAP server is busy", + "0334": "LDAP server is unavailable", + "0335": "LDAP server is unwilling to perform", + "0336": "Loop detected by LDAP", + "0340": "LDAP naming violation", + "0341": "LDAP object class violation", + "0342": "LDAP operation not allowed on non-leaf", + "0343": "LDAP operation not allowed on RDN", + "0344": "Already exists (LDAP)", + "0345": "Cannot modify LDAP object class", + "0346": "LDAP results too large", + "0347": "LDAP operation affects multiple DSAs", + "034C": "Virtual LDAP list view error", + "0350": "Other LDAP error", + "0371": "Resources exhausted in LCUP", + "0372": "Security violation in LCUP", + "0373": "Invalid data in LCUP", + "0374": "Unsupported scheme in LCUP", + "0375": "Reload required in LCUP", + "0376": "LDAP cancelled", + "0377": "No LDAP operation to cancel", + "0378": "Too late to cancel LDAP", + "0379": "Cannot cancel LDAP", + "037A": "LDAP assertion failed", + "037B": "Proxied authorization denied by LDAP", + "0400": "User defined error code 1", + "0401": "User defined error code 2", + "0402": "User defined error code 3", + "0403": "User defined error code 4", + "0404": "User defined error code 5", + "0405": "User defined error code 6", + "0406": "User defined error code 7", + "0407": "User defined error code 8", + "0408": "User defined error code 9", + "0409": "User defined error code 10", + "040A": "User defined error code 11", + "040B": "User defined error code 12", + "040C": "User defined error code 13", + "040D": "User defined error code 14", + "040E": "User defined error code 15", + "040F": "User defined error code 16", + "05DC": "SQL success", + "05DD": "SQL error", + "05DE": "Internal logic error in SQL library", + "05DF": "Access permission denied (SQL)", + "05E0": "SQL abort was requested", + "05E1": "SQL database file is locked", + "05E2": "An SQL table in the database is locked", + "05E3": "SQL library ran out of core", + "05E4": "Attempt to write a readonly SQL database", + "05E5": "SQL operation terminated by interrupt", + "05E6": "I/O error during SQL operation", + "05E7": "SQL database disk image is malformed", + "05E8": "Unknown opcode in SQL file control", + "05E9": "Insertion failed because SQL database is full", + "05EA": "Unable to open the SQL database file", + "05EB": "SQL database lock protocol error", + "05EC": "(internal SQL code: empty)", + "05ED": "SQL database schema changed", + "05EE": "String or blob exceeds size limit (SQL)", + "05EF": "SQL abort due to constraint violation", + "05F0": "Data type mismatch (SQL)", + "05F1": "SQL library used incorrectly", + "05F2": "SQL library uses unsupported OS features", + "05F3": "Authorization denied (SQL)", + "05F4": "(unused SQL code: format)", + "05F5": "SQL bind parameter out of range", + "05F6": "File opened that is not an SQL database file", + "05F7": "Notifications from SQL logger", + "05F8": "Warnings from SQL logger", + "0640": "SQL has another row ready", + "0641": "SQL has finished executing", + "3FFD": "System error w/o errno", + "3FFF": "End of file", + "8000": "Argument list too long", + "8001": "Permission denied", + "8002": "Address already in use", + "8003": "Cannot assign requested address", + "8004": "Advertise error", + "8005": "Address family not supported by protocol", + "8006": "Resource temporarily unavailable", + "8007": "Operation already in progress", + "800A": "Invalid exchange", + "800B": "Bad file descriptor", + "800C": "File descriptor in bad state", + "800D": "Bad message", + "800E": "Invalid request descriptor", + "8010": "Invalid request code", + "8011": "Invalid slot", + "8012": "Bad font file format", + "8013": "Device or resource busy", + "8014": "Operation canceled", + "8015": "No child processes", + "8016": "Channel number out of range", + "8017": "Communication error on send", + "8018": "Software caused connection abort", + "8019": "Connection refused", + "801A": "Connection reset by peer", + "801C": "Resource deadlock avoided", + "801D": "Resource deadlock avoided", + "801E": "Destination address required", + "8020": "Numerical argument out of domain", + "8021": "RFS specific error", + "8022": "Disk quota exceeded", + "8023": "File exists", + "8024": "Bad address", + "8025": "File too large", + "8029": "Host is down", + "802A": "No route to host", + "802B": "Identifier removed", + "802D": "Invalid or incomplete multibyte or wide character", + "802E": "Operation now in progress", + "802F": "Interrupted system call", + "8030": "Invalid argument", + "8031": "Input/output error", + "8032": "Transport endpoint is already connected", + "8033": "Is a directory", + "8034": "Is a named type file", + "8035": "Level 2 halted", + "8036": "Level 2 not synchronized", + "8037": "Level 3 halted", + "8038": "Level 3 reset", + "8039": "Can not access a needed shared library", + "803A": "Accessing a corrupted shared library", + "803B": "Cannot exec a shared library directly", + "803C": "Attempting to link in too many shared libraries", + "803D": ".lib section in a.out corrupted", + "803E": "Link number out of range", + "803F": "Too many levels of symbolic links", + "8040": "Wrong medium type", + "8041": "Too many open files", + "8042": "Too many links", + "8043": "Message too long", + "8044": "Multihop attempted", + "8045": "File name too long", + "8046": "No XENIX semaphores available", + "8048": "Network is down", + "8049": "Network dropped connection on reset", + "804A": "Network is unreachable", + "804B": "Too many open files in system", + "804C": "No anode", + "804D": "No buffer space available", + "804E": "No CSI structure available", + "804F": "No data available", + "8050": "No such device", + "8051": "No such file or directory", + "8052": "Exec format error", + "8053": "No locks available", + "8054": "Link has been severed", + "8055": "No medium found", + "8056": "Cannot allocate memory", + "8057": "No message of desired type", + "8058": "Machine is not on the network", + "8059": "Package not installed", + "805A": "Protocol not available", + "805B": "No space left on device", + "805C": "Out of streams resources", + "805D": "Device not a stream", + "805E": "Function not implemented", + "805F": "Block device required", + "8060": "Transport endpoint is not connected", + "8061": "Not a directory", + "8062": "Directory not empty", + "8063": "Not a XENIX named type file", + "8064": "Socket operation on non-socket", + "8065": "Operation not supported", + "8066": "Inappropriate ioctl for device", + "8067": "Name not unique on network", + "8068": "No such device or address", + "8069": "Operation not supported", + "806A": "Value too large for defined data type", + "806B": "Operation not permitted", + "806C": "Protocol family not supported", + "806D": "Broken pipe", + "8072": "Protocol error", + "8073": "Protocol not supported", + "8074": "Protocol wrong type for socket", + "8075": "Numerical result out of range", + "8076": "Remote address changed", + "8077": "Object is remote", + "8078": "Remote I/O error", + "8079": "Interrupted system call should be restarted", + "807A": "Read-only file system", + "807C": "Cannot send after transport endpoint shutdown", + "807D": "Socket type not supported", + "807E": "Illegal seek", + "807F": "No such process", + "8080": "Srmount error", + "8081": "Stale file handle", + "8082": "Streams pipe error", + "8083": "Timer expired", + "8084": "Connection timed out", + "8085": "Too many references: cannot splice", + "8086": "Text file busy", + "8087": "Structure needs cleaning", + "8088": "Protocol driver not attached", + "8089": "Too many users", + "808A": "Resource temporarily unavailable", + "808B": "Invalid cross-device link", + "808C": "Exchange full" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.5/python_gnupg.egg-info/PKG-INFO new/python-gnupg-0.4.6/python_gnupg.egg-info/PKG-INFO --- old/python-gnupg-0.4.5/python_gnupg.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/python-gnupg-0.4.6/python_gnupg.egg-info/PKG-INFO 2020-04-17 13:30:43.000000000 +0200 @@ -0,0 +1,25 @@ +Metadata-Version: 1.2 +Name: python-gnupg +Version: 0.4.6 +Summary: A wrapper for the Gnu Privacy Guard (GPG or GnuPG) +Home-page: https://docs.red-dove.com/python-gnupg/ +Author: Vinay Sajip +Author-email: vinay_sa...@red-dove.com +Maintainer: Vinay Sajip +Maintainer-email: vinay_sa...@red-dove.com +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.6.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 +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +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.5/python_gnupg.egg-info/SOURCES.txt new/python-gnupg-0.4.6/python_gnupg.egg-info/SOURCES.txt --- old/python-gnupg-0.4.5/python_gnupg.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/python-gnupg-0.4.6/python_gnupg.egg-info/SOURCES.txt 2020-04-17 13:30:43.000000000 +0200 @@ -0,0 +1,14 @@ +LICENSE.txt +MANIFEST.in +README.rst +gnupg.py +messages.json +setup.cfg +setup.py +test_gnupg.py +test_pubring.gpg +test_secring.gpg +python_gnupg.egg-info/PKG-INFO +python_gnupg.egg-info/SOURCES.txt +python_gnupg.egg-info/dependency_links.txt +python_gnupg.egg-info/top_level.txt \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.5/python_gnupg.egg-info/dependency_links.txt new/python-gnupg-0.4.6/python_gnupg.egg-info/dependency_links.txt --- old/python-gnupg-0.4.5/python_gnupg.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/python-gnupg-0.4.6/python_gnupg.egg-info/dependency_links.txt 2020-04-17 13:30:43.000000000 +0200 @@ -0,0 +1 @@ + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.5/python_gnupg.egg-info/top_level.txt new/python-gnupg-0.4.6/python_gnupg.egg-info/top_level.txt --- old/python-gnupg-0.4.5/python_gnupg.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/python-gnupg-0.4.6/python_gnupg.egg-info/top_level.txt 2020-04-17 13:30:43.000000000 +0200 @@ -0,0 +1 @@ +gnupg diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.5/setup.cfg new/python-gnupg-0.4.6/setup.cfg --- old/python-gnupg-0.4.5/setup.cfg 1970-01-01 01:00:00.000000000 +0100 +++ new/python-gnupg-0.4.6/setup.cfg 2020-04-17 13:30:43.000000000 +0200 @@ -0,0 +1,7 @@ +[bdist_wheel] +universal = 1 + +[egg_info] +tag_build = +tag_date = 0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-gnupg-0.4.5/setup.py new/python-gnupg-0.4.6/setup.py --- old/python-gnupg-0.4.5/setup.py 2019-07-16 09:21:35.000000000 +0200 +++ new/python-gnupg-0.4.6/setup.py 2020-04-17 13:29:40.000000000 +0200 @@ -1,4 +1,4 @@ -from distutils.core import setup +from setuptools import setup from gnupg import __version__ as version @@ -24,16 +24,10 @@ "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 2.4", - "Programming Language :: Python :: 2.5", - "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "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.5/test_gnupg.py new/python-gnupg-0.4.6/test_gnupg.py --- old/python-gnupg-0.4.5/test_gnupg.py 2019-08-12 18:47:20.000000000 +0200 +++ new/python-gnupg-0.4.6/test_gnupg.py 2020-04-17 10:36:09.000000000 +0200 @@ -2,9 +2,11 @@ """ A test harness for gnupg.py. -Copyright (C) 2008-2018 Vinay Sajip. All rights reserved. +Copyright (C) 2008-2020 Vinay Sajip. All rights reserved. """ import doctest +import io +import json import logging import os.path import os @@ -28,7 +30,7 @@ import gnupg __author__ = "Vinay Sajip" -__date__ = "$12-Aug-2019 15:58:32$" +__date__ = "$17-Apr-2020 09:36:09$" ALL_TESTS = True @@ -136,6 +138,40 @@ -----END PGP PUBLIC KEY BLOCK----- """ +SECRET_KEY = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQPGBFztd1UBCACiHhlEJIGfXNEiUX4GwamgdLOkJ3mbn5OyV4M/Ie3YvvHxveq/ +TFYbuV63iuDVhNXpDUNmGsTq4vFaMsseLl7eESw8UTa3XklHHjh56kw0AVkJA75A +Xq/VshFobLNxYZdtlOVkKe1a3uJVKs+BqFjhavEjQyhkpWvBY51OzCSc2AN/aQZA +F3AltZ8luIHZPs8zVbgH90WIpze+vzAd9FyXD0wV6gylGSifHj8zIhac80evQgD9 +50De7EPnSdgZSNwnlrhQtAIB5UnTETxXk34/W0Rq+BKn6SuchtaP7hXIHC0+B0C7 +zBzPYKMQ7vXc/hceNwSGtgovhaQPCcv1byFBABEBAAH+BwMCUNdAVY/RMdJg1q5n +FQOyVZl2tvd3krExjGYvhabwijbPz+TrVkPhKqdkp4Hbf3oXV/bcbQhG2dld4Ooc ++xtEpTqYw08bNDuk4NEAvggasUkgssHZccDmHySGfA9U8C7B0Hj8xT4SifnuVNL+ +xp9iv1BS03s+UIEVZ2rGjDQy7/G/U6/ZpLqFg+C113VQs6yz0VMsnnAQOMgN0+gQ +aZb6VNPR7nZ5+/hRlx0DgXu++lei9HTmHRz+ZvbbYjeU9nj10eANhO0lEvlgtyXa +v4Y5ERwk86gbkSRGtN88qVK/+GXK60Q33EoGMlwPZrfFGx+N5QuPEnCjT1vvz7E3 +HhCpe4u5Idusgui+tDkxq8BEz6iTGMO1hcb75MDdIQBhJzeJ7OIxyBfqLReF4+Ut +eNwy0wpN3xuEeYvP4ZIe7hj74WWIuKq2+lesPm4eWRPoaQ5MZXmEwbjr29e++V7D +EkHgCYio6TVwrHA0LRSNfm8VVBV2cdsqFOLLutudHoC8BnjetEetmYaA99u0Pevz +NscYwfaWLNW/d5FGyPUb+GQFYzmQWUfUzpg9hu7U79uA0kOwC+4nK6LEalILtoHn +YO3PvvcCEnpWBlDhCR3n0zkNQCulvQKS/ww5q/MDNqvibKiMJHJ1xP89tEU3lnHl +qgwHVmleqUR+yzdg5lo96Yey5yaDdhK5ZR1TFC4qK4Igcn2+WG109659bJUGpEre +Vktu530JutX38ZoyKdHO0uPs/ft/hgBhNd6MKmh7eejo84Wn6/lxkfMydkfKm5QY +dMHF3Ew+l7aACAs3l95V0YDNzA0FyOFkb/tqxyx8dP+O2NdZQZSvG+yxDav05bCq +kwz+7H7sJnUj1JJtUgPTL9yVH+LyUhL8AU13UKVjBFJ4VL5+KDD9KwPkk6aN7zDW +Qv0g8Cc7A8H0tB5BdXRvZ2VuZXJhdGVkIEtleSA8dXNlcjFAdGVzdD6JATgEEwEI +ACIFAlztd1UCGy8GCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEGP32fXSIJgg +IXgH/3o1rUzbjjz1sMoBwRv4qLmgeqlB2YJSVzLWOn4AcrHbxup5O9nJkqG+YFwH +OFmytuiPDKmA4ZXww8f+2rHXdDuwI5SWnfhuPpV863BulIhtjwiwqD9eIzQ9LX79 +K7hXRJ4I0AkYEbDHOWlLHZCrjul/ZaS10QRVR21EYICha2I8tvxsRMPp0I93XnuB +T+z7ykRxRjpMv6MfhWVcw5B0s7lPedLhcx657HfY49t36/CIZ9/zMKsduX7cTOAh +tO8f06R3yfjxLRD8y89frVP3+tGMvt2yGOd5TT0zht5yYcG6QkiHlfdgXqeE8nsU +2392Xn/RETq6xCj3kG6K3wbWqh0= +=2A5s +-----END PGP PRIVATE KEY BLOCK----- +""" def is_list_with_len(o, n): return isinstance(o, list) and len(o) == n @@ -157,19 +193,30 @@ # ignoring things like spurious blank lines return get_key_data(k1) != get_key_data(k2) -AGENT_CONFIG = b'''allow-loopback-pinentry +AGENT_CONFIG = '''allow-loopback-pinentry log-file socket:///tmp/S.my-gnupg-log verbose debug ipc ''' +ENABLE_TOFU = 'ENABLE_TOFU' in os.environ + +if ENABLE_TOFU: + GPG_CONFIG = 'trust-model tofu+pgp\ntofu-default-policy unknown\n' + + def prepare_homedir(hd): if not os.path.isdir(hd): os.makedirs(hd) os.chmod(hd, 0x1C0) fn = os.path.join(hd, 'gpg-agent.conf') - with open(fn, 'wb') as f: + with open(fn, 'w') as f: f.write(AGENT_CONFIG) + if ENABLE_TOFU: + fn = os.path.join(hd, 'gpg.conf') + with open(fn, 'w') as f: + f.write(GPG_CONFIG) + class GPGTestCase(unittest.TestCase): def setUp(self): @@ -305,8 +352,8 @@ self.assertEqual(uid, 'Test Name (Funny chars: ' '\r\n\x0c\x0b\x00\x08) <test.n...@example.com>') - def test_key_generation_with_empty_value(self): - "Test that key generation handles empty values" + def test_key_generation_input(self): + "Test that key generation input handles empty values, curves etc." params = { 'key_type': ' ', 'key_length': 2048, @@ -316,6 +363,18 @@ params['key_type'] = 'DSA' cmd = self.gpg.gen_key_input(**params) self.assertTrue('Key-Type: DSA\n' in cmd) + params = { + 'key_type': 'ECDSA', + 'key_curve': 'nistp384', + 'subkey_type': 'ECDH', + 'subkey_curve': 'nistp384', + 'name_comment': 'NIST P-384', + } + cmd = self.gpg.gen_key_input(**params) + for s in ('Key-Type: ECDSA', 'Key-Curve: nistp384', 'Subkey-Type: ECDH', + 'Subkey-Curve: nistp384', 'Name-Comment: NIST P-384'): + self.assertTrue('%s\n' %s in cmd) + self.assertFalse('Key-Length: ' in cmd) def test_list_keys_after_generation(self): "Test that after key generation, the generated key is available" @@ -448,6 +507,11 @@ for key in keys: self.assertEqual(key['ownertrust'], expected) self.assertRaises(ValueError, gpg.trust_keys, fingerprints, 'TRUST_FOOBAR') + self.assertRaises(ValueError, gpg.trust_keys, 'NO_SUCH_FINGERPRINT', 'TRUST_NEVER') + # gpg should raise an error for the following - but it doesn't! + # self.assertRaises(ValueError, gpg.trust_keys, + # 'BADF00DBADF00DBADF00DBADF00DBADF00DBADF0', + # 'TRUST_NEVER') def test_list_signatures(self): logger.debug("test_list_signatures begins") @@ -599,6 +663,19 @@ binary = gpg.export_keys(key.fingerprint, True, armor=False, passphrase=passphrase) self.assertFalse(isinstance(binary, gnupg.text_type)) + # import a secret key, and confirm that it's found in the list of + # secret keys. + result = gpg.import_keys(SECRET_KEY) + self.assertEqual(result.summary(), '1 imported') + private_keys = gpg.list_keys(secret=True) + self.assertTrue(is_list_with_len(private_keys, 2)) + found = False + for pk in private_keys: + if pk['keyid'].endswith('D2209820'): + found = True + break + self.assertTrue(found) + self.assertEqual(pk['uids'][0], 'Autogenerated Key <user1@test>') logger.debug("test_import_and_export ends") def test_import_only(self): @@ -846,8 +923,8 @@ encfno, encfname = tempfile.mkstemp() os.close(encfno) cases = ( - ('/dev/null/foo', 'not a directory'), - ('/etc/foo', 'permission denied'), + ('/dev/null/foo', 'encrypt: not a directory'), + ('/etc/foo', 'encrypt: permission denied'), ) key = self.generate_key("Barbara", "Brown", "beta.com") barbara = key.fingerprint @@ -861,6 +938,31 @@ if edata.status: self.assertEqual(edata.status, message) + # now try with custom error map, if available + if os.path.exists('messages.json'): + with open('messages.json') as f: + mdata = json.load(f) + messages = {} + for k, v in mdata.items(): + messages[int(k, 16)] = v + + self.gpg.error_map = messages + + cases = ( + ('/dev/null/foo', 'encrypt: Not a directory'), + ('/etc/foo', 'encrypt: Permission denied'), + ) + + for badout, message in cases: + stream = gnupg._make_binary_stream(data, self.gpg.encoding) + edata = self.gpg.encrypt_file(stream, + barbara, armor=False, output=badout) + # on GnuPG 1.4, you sometimes don't get any FAILURE messages, in + # which case status will not be set + if edata.status: + self.assertEqual(edata.status, message) + + def test_filenames_with_spaces(self): # See Issue #16 "Test that filenames with spaces are correctly handled" logger.debug("test_filename_with_spaces begins") @@ -1011,6 +1113,10 @@ self.assertFalse(result) logger.debug("test_doctest_import_keys ends") + def test_recv_keys_no_server(self): + result = self.gpg.recv_keys('foo.bar.baz', '92905378') + self.assertEqual(result.summary(), '0 imported') + TEST_GROUPS = { 'sign' : set(['test_signature_verification', @@ -1023,14 +1129,14 @@ 'test_list_signatures', 'test_key_generation_with_invalid_key_type', 'test_key_generation_with_escapes', - 'test_key_generation_with_empty_value', + 'test_key_generation_input', 'test_key_generation_with_colons', 'test_search_keys', 'test_scan_keys', 'test_key_trust']), 'import' : set(['test_import_only', 'test_doctest_import_keys']), 'basic' : set(['test_environment', 'test_list_keys_initial', 'test_nogpg', 'test_make_args', 'test_quote_with_shell']), - 'test': set(['test_encryption_and_decryption']), + 'test': set(['test_invalid_outputs']), } def suite(args=None):