[Python-modules-commits] [python-gnupg] 06/10: Import python-gnupg_0.3.9.orig.tar.gz
Elena Grandi
valhalla-guest at moszumanska.debian.org
Tue Sep 13 13:59:13 UTC 2016
This is an automated email from the git hooks/post-receive script.
valhalla-guest pushed a commit to branch master
in repository python-gnupg.
commit 53585fde665095b170b0706129af9238b168becf
Author: Elena Grandi <valhalla-d at trueelena.org>
Date: Tue Sep 13 15:11:44 2016 +0200
Import python-gnupg_0.3.9.orig.tar.gz
---
PKG-INFO | 9 ++-
README.rst | 30 +++++++++-
gnupg.py | 173 ++++++++++++++++++++++++++++++++++------------------------
setup.py | 3 +-
test_gnupg.py | 146 +++++++++++++++++++++++++++++++++++++++++--------
5 files changed, 257 insertions(+), 104 deletions(-)
diff --git a/PKG-INFO b/PKG-INFO
index 09fab1f..ee38cd5 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -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_sajip at 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.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: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/README.rst b/README.rst
index 8e0c387..19794db 100644
--- a/README.rst
+++ b/README.rst
@@ -54,11 +54,39 @@ Change log
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 --git a/gnupg.py b/gnupg.py
index cfc6cb9..4515841 100644
--- a/gnupg.py
+++ b/gnupg.py
@@ -27,18 +27,18 @@ Vinay Sajip to make use of the subprocess module (Steve's version uses os.fork()
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 sys
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 @@ 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':
+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 @@ else:
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 @@ def _copy_data(instream, outstream):
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 @@ def _copy_data(instream, outstream):
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 @@ def _copy_data(instream, outstream):
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 @@ def _make_memory_stream(s):
try:
from io import BytesIO
rv = BytesIO(s)
- except ImportError:
+ except ImportError: # pragma: no cover
rv = StringIO(s)
return rv
@@ -245,20 +245,21 @@ class Verify(object):
"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 @@ class Verify(object):
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 @@ class Verify(object):
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 @@ class ImportResult(object):
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 @@ class ImportResult(object):
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 @@ class ImportResult(object):
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 @@ class SearchKeys(list):
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 @@ class SearchKeys(list):
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 @@ class ListKeys(SearchKeys):
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 @@ class ListKeys(SearchKeys):
'''
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 @@ class ListKeys(SearchKeys):
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 @@ class ListKeys(SearchKeys):
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 @@ class Crypt(Verify, TextHandler):
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 @@ class ExportResult(GenKey):
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 @@ class DeleteResult(object):
}
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 @@ class Sign(TextHandler):
"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 @@ class GPG(object):
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 @@ class GPG(object):
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 @@ class GPG(object):
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 @@ class GPG(object):
# 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 @@ class GPG(object):
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 @@ class GPG(object):
if stdin is not None:
try:
stdin.close()
- except IOError:
+ except IOError: # pragma: no cover
pass
stderr.close()
stdout.close()
@@ -865,7 +887,7 @@ class GPG(object):
# 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 @@ class GPG(object):
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 @@ class GPG(object):
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 @@ class GPG(object):
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 @@ class GPG(object):
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 @@ class GPG(object):
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 @@ class GPG(object):
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 @@ class GPG(object):
"""
- 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 @@ class GPG(object):
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 @@ class GPG(object):
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 @@ class GPG(object):
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 --git a/setup.py b/setup.py
index 936f1bb..7d48a17 100644
--- a/setup.py
+++ b/setup.py
@@ -7,7 +7,7 @@ setup(name = "python-gnupg",
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_sajip at red-dove.com",
@@ -32,7 +32,6 @@ It is intended for use with Python 2.4 or greater.",
"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 --git a/test_gnupg.py b/test_gnupg.py
index f7133b6..34c48bb 100644
--- a/test_gnupg.py
+++ b/test_gnupg.py
@@ -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 unittest
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 @@ WdPQjKEfKnr+bW4yubwMUYKyAJ4uiE8Rv/oEED1oM3xeJqa+MJ9V1w==
=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 @@ class GPGTestCase(unittest.TestCase):
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 @@ class GPGTestCase(unittest.TestCase):
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 @@ class GPGTestCase(unittest.TestCase):
'Donna Davis <donna.davis at 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) <jc at example.com>', '13x') in sigs)
+ logger.debug("testing subkey self-signature")
+ self.assertTrue(('BC6F350FB3D04076', 'Joshua Calvert (A test user) <jc at example.com>', '18x') in sigs)
+ logger.debug("testing other signature")
+ self.assertTrue(('057CCF658C074FDA', 'Winston Smith (A test user) <winston at 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 @@ class GPGTestCase(unittest.TestCase):
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 @@ class GPGTestCase(unittest.TestCase):
"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 @@ class GPGTestCase(unittest.TestCase):
"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 @@ class GPGTestCase(unittest.TestCase):
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 @@ class GPGTestCase(unittest.TestCase):
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 @@ class GPGTestCase(unittest.TestCase):
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 @@ class GPGTestCase(unittest.TestCase):
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 @@ class GPGTestCase(unittest.TestCase):
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 @@ class GPGTestCase(unittest.TestCase):
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_GROUPS = {
'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 @@ def suite(args=None):
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 suite(args=None):
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()
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-gnupg.git
More information about the Python-modules-commits
mailing list