Merge branch 'feature/cleanup-api' into develop
commit
fd6dbe71b6
6
Makefile
6
Makefile
|
@ -5,13 +5,13 @@ clean:
|
|||
rm -f ./*.pyo
|
||||
|
||||
cleantest: clean
|
||||
mkdir -p gnupg/tests/keys
|
||||
mkdir -p gnupg/tests/tmp_test
|
||||
touch gnupg/tests/placeholder.log
|
||||
rm -rf gnupg/tests/keys
|
||||
rm -rf gnupg/tests/tmp_test
|
||||
rm gnupg/tests/*.log
|
||||
|
||||
test: cleantest
|
||||
python gnupg/tests/test_gnupg.py parsers basic genkey sign
|
||||
python gnupg/tests/test_gnupg.py parsers basic genkey sign listkeys
|
||||
|
||||
install:
|
||||
python setup.py install --record installed-files.txt
|
||||
|
|
226
gnupg/gnupg.py
226
gnupg/gnupg.py
|
@ -107,78 +107,7 @@ from parsers import GenKey, Sign, ListKeys, ListPackets
|
|||
from parsers import _fix_unsafe, _sanitise, _is_allowed, _sanitise_list
|
||||
from util import logger, _conf
|
||||
|
||||
import util
|
||||
|
||||
|
||||
def _copy_data(instream, outstream):
|
||||
"""Copy data from one stream to another.
|
||||
|
||||
:type instream: :class:`io.BytesIO` or :class:`io.StringIO` or file
|
||||
:param instream: A byte stream or open file to read from.
|
||||
:param file outstream: The file descriptor of a tmpfile to write to.
|
||||
"""
|
||||
sent = 0
|
||||
|
||||
try:
|
||||
#assert (util._is_stream(instream)
|
||||
# or isinstance(instream, file)), "instream not stream or file"
|
||||
assert isinstance(outstream, file), "outstream is not a file"
|
||||
except AssertionError as ae:
|
||||
logger.exception(ae)
|
||||
return
|
||||
|
||||
if hasattr(sys.stdin, 'encoding'):
|
||||
enc = sys.stdin.encoding
|
||||
else:
|
||||
enc = 'ascii'
|
||||
|
||||
while True:
|
||||
data = instream.read(1024)
|
||||
if len(data) == 0:
|
||||
break
|
||||
sent += len(data)
|
||||
logger.debug("_copy_data(): sending chunk (%d):\n%s" % (sent, data[:256]))
|
||||
try:
|
||||
outstream.write(data)
|
||||
except UnicodeError:
|
||||
try:
|
||||
outstream.write(data.encode(enc))
|
||||
except IOError:
|
||||
logger.exception('_copy_data(): Error sending data: Broken pipe')
|
||||
break
|
||||
except IOError:
|
||||
# Can get 'broken pipe' errors even when all data was sent
|
||||
logger.exception('_copy_data(): Error sending data: Broken pipe')
|
||||
break
|
||||
try:
|
||||
outstream.close()
|
||||
except IOError:
|
||||
logger.exception('_copy_data(): Got IOError while closing %s' % outstream)
|
||||
else:
|
||||
logger.debug("_copy_data(): Closed output, %d bytes sent." % sent)
|
||||
|
||||
def _threaded_copy_data(instream, outstream):
|
||||
"""Copy data from one stream to another in a separate thread.
|
||||
|
||||
Wraps ``_copy_data()`` in a :class:`threading.Thread`.
|
||||
|
||||
:type instream: :class:`io.BytesIO` or :class:`io.StringIO`
|
||||
:param instream: A byte stream to read from.
|
||||
:param file outstream: The file descriptor of a tmpfile to write to.
|
||||
"""
|
||||
copy_thread = threading.Thread(target=_copy_data,
|
||||
args=(instream, outstream))
|
||||
copy_thread.setDaemon(True)
|
||||
logger.debug('_threaded_copy_data(): %r, %r, %r', copy_thread,
|
||||
instream, outstream)
|
||||
copy_thread.start()
|
||||
return copy_thread
|
||||
|
||||
def _write_passphrase(stream, passphrase, encoding):
|
||||
passphrase = '%s\n' % passphrase
|
||||
passphrase = passphrase.encode(encoding)
|
||||
stream.write(passphrase)
|
||||
logger.debug("_write_passphrase(): Wrote passphrase.")
|
||||
import util as _util
|
||||
|
||||
|
||||
class GPG(object):
|
||||
|
@ -227,12 +156,12 @@ class GPG(object):
|
|||
gpghome = _conf
|
||||
self.gpghome = _fix_unsafe(gpghome)
|
||||
if self.gpghome:
|
||||
util._create_gpghome(self.gpghome)
|
||||
_util._create_gpghome(self.gpghome)
|
||||
else:
|
||||
message = ("Unsuitable gpg home dir: %s" % gpghome)
|
||||
logger.debug("GPG.__init__(): %s" % message)
|
||||
|
||||
self.gpgbinary = util._find_gpgbinary(gpgbinary)
|
||||
self.gpgbinary = _util._find_gpgbinary(gpgbinary)
|
||||
|
||||
if keyring is not None:
|
||||
raise DeprecationWarning("Option 'keyring' changing to 'secring'")
|
||||
|
@ -242,17 +171,6 @@ class GPG(object):
|
|||
self.secring = os.path.join(self.gpghome, secring)
|
||||
self.pubring = os.path.join(self.gpghome, pubring)
|
||||
|
||||
#for ring in [self.secring, self.pubring]:
|
||||
# if ring and not os.path.isfile(ring):
|
||||
# with open(ring, 'a+') as ringfile:
|
||||
# ringfile.write("")
|
||||
# ringfile.flush()
|
||||
# try:
|
||||
# assert util._has_readwrite(ring), \
|
||||
# ("Need r+w for %s" % ring)
|
||||
# except AssertionError as ae:
|
||||
# logger.debug(ae.message)
|
||||
|
||||
self.options = _sanitise(options) if options else None
|
||||
|
||||
self.encoding = locale.getpreferredencoding()
|
||||
|
@ -261,7 +179,7 @@ class GPG(object):
|
|||
|
||||
try:
|
||||
assert self.gpghome is not None, "Got None for self.gpghome"
|
||||
assert util._has_readwrite(self.gpghome), ("Home dir %s needs r+w"
|
||||
assert _util._has_readwrite(self.gpghome), ("Home dir %s needs r+w"
|
||||
% self.gpghome)
|
||||
assert self.gpgbinary, "Could not find gpgbinary %s" % full
|
||||
assert isinstance(verbose, bool), "'verbose' must be boolean"
|
||||
|
@ -354,7 +272,7 @@ class GPG(object):
|
|||
break
|
||||
logger.debug("chunk: %r" % data[:256])
|
||||
chunks.append(data)
|
||||
if util._py3k:
|
||||
if _util._py3k:
|
||||
# Join using b'' or '', as appropriate
|
||||
result.data = type(data)().join(chunks)
|
||||
else:
|
||||
|
@ -399,8 +317,8 @@ class GPG(object):
|
|||
else:
|
||||
stdin = p.stdin
|
||||
if passphrase:
|
||||
_write_passphrase(stdin, passphrase, self.encoding)
|
||||
writer = _threaded_copy_data(file, stdin)
|
||||
_util._write_passphrase(stdin, passphrase, self.encoding)
|
||||
writer = _util._threaded_copy_data(file, stdin)
|
||||
self._collect_output(p, result, writer, stdin)
|
||||
return result
|
||||
|
||||
|
@ -411,8 +329,8 @@ class GPG(object):
|
|||
"""Create a signature for a message or file."""
|
||||
if isinstance(message, file):
|
||||
result = self._sign_file(message, **kwargs)
|
||||
elif not util._is_stream(message):
|
||||
f = util._make_binary_stream(message, self.encoding)
|
||||
elif not _util._is_stream(message):
|
||||
f = _util._make_binary_stream(message, self.encoding)
|
||||
result = self._sign_file(f, **kwargs)
|
||||
f.close()
|
||||
else:
|
||||
|
@ -448,8 +366,8 @@ class GPG(object):
|
|||
try:
|
||||
stdin = p.stdin
|
||||
if passphrase:
|
||||
_write_passphrase(stdin, passphrase, self.encoding)
|
||||
writer = _threaded_copy_data(file, stdin)
|
||||
_util._write_passphrase(stdin, passphrase, self.encoding)
|
||||
writer = _util._threaded_copy_data(file, stdin)
|
||||
except IOError:
|
||||
logger.exception("_sign_file(): Error writing message")
|
||||
writer = None
|
||||
|
@ -471,7 +389,7 @@ class GPG(object):
|
|||
>>> assert verify
|
||||
|
||||
"""
|
||||
f = util._make_binary_stream(data, self.encoding)
|
||||
f = _util._make_binary_stream(data, self.encoding)
|
||||
result = self.verify_file(f)
|
||||
f.close()
|
||||
return result
|
||||
|
@ -483,7 +401,7 @@ class GPG(object):
|
|||
detached signature should be specified as the ``sig_file``.
|
||||
|
||||
:param file file: A file descriptor object. Its type will be checked
|
||||
with :func:`util._is_file`.
|
||||
with :func:`_util._is_file`.
|
||||
:param str sig_file: A file containing the GPG signature data for
|
||||
``file``. If given, ``file`` is verified via this
|
||||
detached signature.
|
||||
|
@ -496,10 +414,10 @@ class GPG(object):
|
|||
logger.debug("verify_file(): Handling embedded signature")
|
||||
args = ["--verify"]
|
||||
proc = self._open_subprocess(args)
|
||||
writer = _threaded_copy_data(file, proc.stdin)
|
||||
writer = _util._threaded_copy_data(file, proc.stdin)
|
||||
self._collect_output(proc, result, writer, stdin=proc.stdin)
|
||||
else:
|
||||
if not util._is_file(sig_file):
|
||||
if not _util._is_file(sig_file):
|
||||
logger.debug("verify_file(): '%r' is not a file" % sig_file)
|
||||
return result
|
||||
logger.debug('verify_file(): Handling detached verification')
|
||||
|
@ -508,7 +426,7 @@ class GPG(object):
|
|||
sig_fh = open(sig_file)
|
||||
args = ["--verify %s - " % sig_fh.name]
|
||||
proc = self._open_subprocess(args)
|
||||
writer = _threaded_copy_data(file, proc.stdin)
|
||||
writer = _util._threaded_copy_data(file, proc.stdin)
|
||||
self._collect_output(proc, result, stdin=proc.stdin)
|
||||
finally:
|
||||
if sig_fh and not sig_fh.closed:
|
||||
|
@ -568,7 +486,7 @@ class GPG(object):
|
|||
|
||||
result = self._result_map['import'](self)
|
||||
logger.debug('import_keys: %r', key_data[:256])
|
||||
data = util._make_binary_stream(key_data, self.encoding)
|
||||
data = _util._make_binary_stream(key_data, self.encoding)
|
||||
self._handle_io(['--import'], data, result, binary=True)
|
||||
logger.debug('import_keys result: %r', result.__dict__)
|
||||
data.close()
|
||||
|
@ -587,7 +505,7 @@ class GPG(object):
|
|||
safe_keyserver = _fix_unsafe(keyserver)
|
||||
|
||||
result = self._result_map['import'](self)
|
||||
data = util._make_binary_stream("", self.encoding)
|
||||
data = _util._make_binary_stream("", self.encoding)
|
||||
args = ['--keyserver', keyserver, '--recv-keys']
|
||||
|
||||
if keyids:
|
||||
|
@ -606,7 +524,7 @@ class GPG(object):
|
|||
which='key'
|
||||
if secret:
|
||||
which='secret-key'
|
||||
if util._is_list_or_tuple(fingerprints):
|
||||
if _util._is_list_or_tuple(fingerprints):
|
||||
fingerprints = ' '.join(fingerprints)
|
||||
args = ['--batch --delete-%s "%s"' % (which, fingerprints)]
|
||||
result = self._result_map['delete'](self)
|
||||
|
@ -619,7 +537,7 @@ class GPG(object):
|
|||
which=''
|
||||
if secret:
|
||||
which='-secret-key'
|
||||
if util._is_list_or_tuple(keyids):
|
||||
if _util._is_list_or_tuple(keyids):
|
||||
keyids = ' '.join(['"%s"' % k for k in keyids])
|
||||
args = ["--armor --export%s %s" % (which, keyids)]
|
||||
p = self._open_subprocess(args)
|
||||
|
@ -650,7 +568,8 @@ class GPG(object):
|
|||
which='public-keys'
|
||||
if secret:
|
||||
which='secret-keys'
|
||||
args = "--list-%s --fixed-list-mode --fingerprint --with-colons --no-show-photos" % (which,)
|
||||
args = "--list-%s --fixed-list-mode --fingerprint " % (which,)
|
||||
args += "--with-colons --list-options no-show-photos"
|
||||
args = [args]
|
||||
p = self._open_subprocess(args)
|
||||
|
||||
|
@ -689,9 +608,8 @@ class GPG(object):
|
|||
raise NotImplemented("Functionality for '--list-sigs' not implemented.")
|
||||
|
||||
def gen_key(self, input):
|
||||
"""
|
||||
Generate a key; you might use gen_key_input() to create the control
|
||||
input.
|
||||
"""Generate a GnuPG key through batch file key generation. See
|
||||
:meth:`GPG.gen_key_input()` for creating the control input.
|
||||
|
||||
>>> gpg = GPG(gpghome="keys")
|
||||
>>> input = gpg.gen_key_input()
|
||||
|
@ -700,24 +618,77 @@ class GPG(object):
|
|||
>>> result = gpg.gen_key('foo')
|
||||
>>> assert not result
|
||||
|
||||
:param dict input: A dictionary of parameters and values for the new
|
||||
key.
|
||||
:returns: The result mapping with details of the new key, which is a
|
||||
:class:`parsers.GenKey <GenKey>` object.
|
||||
"""
|
||||
args = ["--gen-key --batch"]
|
||||
key = self._result_map['generate'](self)
|
||||
f = util._make_binary_stream(input, self.encoding)
|
||||
f = _util._make_binary_stream(input, self.encoding)
|
||||
self._handle_io(args, f, key, binary=True)
|
||||
f.close()
|
||||
return key
|
||||
|
||||
def gen_key_input(self, **kwargs):
|
||||
"""Generate GnuPG key(s) through batch file key generation.
|
||||
"""Generate a batch file for input to :meth:`GPG.gen_key()`.
|
||||
|
||||
The GnuPG batch file key generation feature allows unattended key
|
||||
generation by creating a file with special syntax and then providing it
|
||||
to: gpg --gen-key --batch <batch file>
|
||||
to: ``gpg --gen-key --batch``:
|
||||
|
||||
Key-Type: RSA
|
||||
Key-Length: 4096
|
||||
Name-Real: Autogenerated Key
|
||||
Name-Email: %s@%s
|
||||
Expire-Date: 2014-04-01
|
||||
%pubring foo.gpg
|
||||
%secring sec.gpg
|
||||
%commit
|
||||
|
||||
Key-Type: DSA
|
||||
Key-Length: 1024
|
||||
Subkey-Type: ELG-E
|
||||
Subkey-Length: 1024
|
||||
Name-Real: Joe Tester
|
||||
Name-Comment: with stupid passphrase
|
||||
Name-Email: joe@foo.bar
|
||||
Expire-Date: 0
|
||||
Passphrase: abc
|
||||
%pubring foo.pub
|
||||
%secring foo.sec
|
||||
%commit
|
||||
|
||||
see http://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html#Unattended-GPG-key-generation
|
||||
for more details.
|
||||
|
||||
>>> gpg = GPG(gpghome="keys")
|
||||
>>> params = {'name_real':'python-gnupg tester', 'name_email':'test@ing'}
|
||||
>>> key_input = gpg.gen_key_input(**params)
|
||||
>>> result = gpg.gen_key(input)
|
||||
>>> assert result
|
||||
|
||||
:param str name_real: The uid name for the generated key.
|
||||
:param str name_email: The uid email for the generated key. (default:
|
||||
$USERNAME@$HOSTNAME)
|
||||
:param str name_comment: The comment in the uid of the generated key.
|
||||
:param str key_type: One of 'RSA', 'DSA', or 'ELG-E'. (default: 'RSA')
|
||||
:param int key_length: The length in bytes of the new key.
|
||||
(default: 4096)
|
||||
:param str subkey_type: If ``key_type`` is 'RSA', an additional subkey
|
||||
can be generated, and it's type must also be 'RSA'. If ``key_type``
|
||||
is 'DSA', then the only subkey type which can be generated is
|
||||
'ELG-E'.
|
||||
:param int subkey_length: The length in bytes of the new subkey.
|
||||
:type expire: int or str
|
||||
:param expire: If an integer, the number of days before the key will
|
||||
expire; if 0, the key will not expire. Otherwise, this can be given
|
||||
as a string in the form <n>w or <n>m or <n>y, i.e. "5m" would mean
|
||||
that the key will expire in five months, "1w" would expire in one
|
||||
week, and "3y" would expire in three years. (default: "1y")
|
||||
:param str passphrase: The passphrase for the new key.
|
||||
"""
|
||||
|
||||
parms = {}
|
||||
for key, val in list(kwargs.items()):
|
||||
key = key.replace('_','-').title()
|
||||
|
@ -726,7 +697,7 @@ class GPG(object):
|
|||
parms.setdefault('Key-Type', 'RSA')
|
||||
parms.setdefault('Key-Length', 4096)
|
||||
parms.setdefault('Name-Real', "Autogenerated Key")
|
||||
parms.setdefault('Name-Comment', "Generated by python-gnupg")
|
||||
parms.setdefault('Expire-Date', _util._next_year())
|
||||
try:
|
||||
logname = os.environ['LOGNAME']
|
||||
except KeyError:
|
||||
|
@ -734,6 +705,7 @@ class GPG(object):
|
|||
hostname = socket.gethostname()
|
||||
parms.setdefault('Name-Email', "%s@%s"
|
||||
% (logname.replace(' ', '_'), hostname))
|
||||
|
||||
out = "Key-Type: %s\n" % parms.pop('Key-Type')
|
||||
for key, val in list(parms.items()):
|
||||
out += "%s: %s\n" % (key, val)
|
||||
|
@ -742,28 +714,6 @@ class GPG(object):
|
|||
out += "%commit\n"
|
||||
return out
|
||||
|
||||
# Key-Type: RSA
|
||||
# Key-Length: 1024
|
||||
# Name-Real: ISdlink Server on %s
|
||||
# Name-Comment: Created by %s
|
||||
# Name-Email: isdlink@%s
|
||||
# Expire-Date: 0
|
||||
# %commit
|
||||
#
|
||||
#
|
||||
# Key-Type: DSA
|
||||
# Key-Length: 1024
|
||||
# Subkey-Type: ELG-E
|
||||
# Subkey-Length: 1024
|
||||
# Name-Real: Joe Tester
|
||||
# Name-Comment: with stupid passphrase
|
||||
# Name-Email: joe@foo.bar
|
||||
# Expire-Date: 0
|
||||
# Passphrase: abc
|
||||
# %pubring foo.pub
|
||||
# %secring foo.sec
|
||||
# %commit
|
||||
|
||||
#
|
||||
# ENCRYPTION
|
||||
#
|
||||
|
@ -805,7 +755,7 @@ class GPG(object):
|
|||
args = ['--symmetric']
|
||||
else:
|
||||
args = ['--encrypt']
|
||||
if not util._is_list_or_tuple(recipients):
|
||||
if not _util._is_list_or_tuple(recipients):
|
||||
recipients = (recipients,)
|
||||
for recipient in recipients:
|
||||
args.append('--recipient "%s"' % recipient)
|
||||
|
@ -861,7 +811,7 @@ class GPG(object):
|
|||
>>> assert result.fingerprint == print1
|
||||
|
||||
"""
|
||||
data = util._make_binary_stream(data, self.encoding)
|
||||
data = _util._make_binary_stream(data, self.encoding)
|
||||
result = self.encrypt_file(data, recipients, **kwargs)
|
||||
data.close()
|
||||
return result
|
||||
|
@ -871,7 +821,7 @@ class GPG(object):
|
|||
|
||||
:param message: A string or file-like object to decrypt.
|
||||
"""
|
||||
data = util._make_binary_stream(message, self.encoding)
|
||||
data = _util._make_binary_stream(message, self.encoding)
|
||||
result = self.decrypt_file(data, **kwargs)
|
||||
data.close()
|
||||
return result
|
||||
|
@ -965,7 +915,7 @@ class GPGWrapper(GPG):
|
|||
"""Send keys to a keyserver."""
|
||||
result = self._result_map['list'](self)
|
||||
gnupg.logger.debug('send_keys: %r', keyids)
|
||||
data = gnupg.util._make_binary_stream("", self.encoding)
|
||||
data = gnupg._util._make_binary_stream("", self.encoding)
|
||||
args = ['--keyserver', keyserver, '--send-keys']
|
||||
args.extend(keyids)
|
||||
self._handle_io(args, data, result, binary=True)
|
||||
|
@ -985,7 +935,7 @@ class GPGWrapper(GPG):
|
|||
args.append('--cipher-algo %s' % cipher_algo)
|
||||
else:
|
||||
args = ['--encrypt']
|
||||
if not util._is_list_or_tuple(recipients):
|
||||
if not _util._is_list_or_tuple(recipients):
|
||||
recipients = (recipients,)
|
||||
for recipient in recipients:
|
||||
args.append('--recipient "%s"' % recipient)
|
||||
|
@ -1008,7 +958,7 @@ class GPGWrapper(GPG):
|
|||
args = ["--list-packets"]
|
||||
result = self._result_map['list-packets'](self)
|
||||
self._handle_io(args,
|
||||
util._make_binary_stream(raw_data, self.encoding),
|
||||
_util._make_binary_stream(raw_data, self.encoding),
|
||||
result)
|
||||
return result
|
||||
|
||||
|
|
|
@ -275,7 +275,7 @@ def _is_allowed(input):
|
|||
['--list-keys', '--list-key', '--fixed-list-mode',
|
||||
'--list-secret-keys', '--list-public-keys',
|
||||
'--list-packets', '--with-colons',
|
||||
'--no-show-photos',
|
||||
'--list-options',
|
||||
'--delete-keys', '--delete-secret-keys',
|
||||
'--encrypt', '--encrypt-files',
|
||||
'--decrypt', '--decrypt-files',
|
||||
|
|
|
@ -11,7 +11,6 @@ import argparse
|
|||
import doctest
|
||||
import logging
|
||||
from functools import wraps
|
||||
import inspect
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
|
@ -30,24 +29,24 @@ from gnupg import parsers
|
|||
from gnupg import util
|
||||
|
||||
__author__ = gnupg.__author__
|
||||
__date__ = gnupg.__date__
|
||||
__version__ = gnupg.__version__
|
||||
|
||||
REPO_DIR = os.getcwd()
|
||||
HOME_DIR = os.path.join(REPO_DIR, 'keys')
|
||||
|
||||
tempfile.tempdir = os.path.join(REPO_DIR, 'tmp_test')
|
||||
logger = logging.getLogger('gnupg')
|
||||
_here = os.path.join(os.path.join(util._repo, 'gnupg'), 'tests')
|
||||
_files = os.path.join(_here, 'files')
|
||||
|
||||
tempfile.tempdir = os.path.join(_here, 'tmp_test')
|
||||
if not os.path.isdir(tempfile.gettempdir()):
|
||||
os.mkdir(tempfile.gettempdir())
|
||||
|
||||
HOME_DIR = tempfile.tempdir
|
||||
|
||||
@wraps(tempfile.TemporaryFile)
|
||||
def _make_tempfile(*args, **kwargs):
|
||||
return tempfile.TemporaryFile(dir=tempfile.gettempdir(),
|
||||
*args, **kwargs)
|
||||
|
||||
logger = logging.getLogger('gnupg')
|
||||
_here = os.path.join(os.path.join(util._repo, 'gnupg'), 'tests')
|
||||
_files = os.path.join(_here, 'files')
|
||||
|
||||
KEYS_TO_IMPORT = """-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1.4.9 (MingW32)
|
||||
|
@ -118,11 +117,10 @@ def compare_keys(k1, k2):
|
|||
|
||||
|
||||
class ResultStringIO(io.StringIO):
|
||||
def __init__(self):
|
||||
super(self, io.StringIO).__init__()
|
||||
|
||||
def __init__(self, init_string):
|
||||
super(ResultStringIO, self).__init__(init_string)
|
||||
def write(self, data):
|
||||
super(self, io.StringIO).write(unicode(data))
|
||||
super(ResultStringIO, self).write(unicode(data))
|
||||
|
||||
|
||||
class GPGTestCase(unittest.TestCase):
|
||||
|
@ -216,7 +214,7 @@ class GPGTestCase(unittest.TestCase):
|
|||
cmd = self.gpg._make_args(None, False)
|
||||
expected = ['/usr/bin/gpg',
|
||||
'--status-fd 2 --no-tty',
|
||||
'--homedir "%s"' % os.path.join(os.getcwd(), 'keys'),
|
||||
'--homedir "%s"' % HOME_DIR,
|
||||
'--no-default-keyring --keyring %s' % self.pubring,
|
||||
'--secret-keyring %s' % self.secring]
|
||||
self.assertListEqual(cmd, expected)
|
||||
|
@ -244,16 +242,19 @@ class GPGTestCase(unittest.TestCase):
|
|||
"Empty list expected...got instead: %s"
|
||||
% str(private_keys))
|
||||
|
||||
|
||||
def test_copy_data(self):
|
||||
"""
|
||||
XXX implement me
|
||||
XXX add me to a test suite
|
||||
|
||||
Test that _copy_data() is able to duplicate byte streams.
|
||||
"""
|
||||
instream = io.BytesIO("This is a string of bytes mapped in memory.")
|
||||
outstream = str("And this one is just a string.")
|
||||
def test_copy_data_bytesio(self):
|
||||
"""Test that _copy_data() is able to duplicate byte streams."""
|
||||
message = "This is a BytesIO string string in memory."
|
||||
instream = io.BytesIO(message)
|
||||
self.assertEqual(unicode(message), instream.getvalue())
|
||||
outstream = ResultStringIO(u'result:')
|
||||
copied = outstream
|
||||
util._copy_data(instream, outstream)
|
||||
self.assertTrue(outstream.readable())
|
||||
self.assertTrue(outstream.closed)
|
||||
self.assertFalse(instream.closed)
|
||||
self.assertTrue(copied.closed)
|
||||
#self.assertEqual(instream.getvalue()[6:], outstream.getvalue())
|
||||
|
||||
def generate_key_input(self, real_name, email_domain, key_length=None,
|
||||
key_type=None, subkey_type=None, passphrase=None):
|
||||
|
@ -386,9 +387,9 @@ class GPGTestCase(unittest.TestCase):
|
|||
|
||||
def test_key_generation_with_empty_value(self):
|
||||
"""Test that key generation handles empty values."""
|
||||
params = {'name_comment': ' '}
|
||||
params = {'name_real': ' '}
|
||||
batch = self.gpg.gen_key_input(**params)
|
||||
self.assertTrue('\nName-Comment: Generated by python-gnupg\n' in batch)
|
||||
self.assertTrue('\nName-Real: Autogenerated Key\n' in batch)
|
||||
|
||||
def test_key_generation_override_default_value(self):
|
||||
"""Test that overriding a default value in gen_key_input() works."""
|
||||
|
@ -399,8 +400,9 @@ class GPGTestCase(unittest.TestCase):
|
|||
|
||||
def test_list_keys_after_generation(self):
|
||||
"""Test that after key generation, the generated key is available."""
|
||||
self.test_list_keys_initial()
|
||||
self.do_key_generation()
|
||||
self.test_list_keys_initial_public()
|
||||
self.test_list_keys_initial_secret()
|
||||
self.generate_key("Johannes Trithemius", 'iusedcarrierpidgeons@inste.ad')
|
||||
public_keys = self.gpg.list_keys()
|
||||
self.assertTrue(is_list_with_len(public_keys, 1),
|
||||
"1-element list expected")
|
||||
|
@ -670,7 +672,8 @@ class GPGTestCase(unittest.TestCase):
|
|||
|
||||
suites = { 'parsers': set(['test_parsers_fix_unsafe',
|
||||
'test_parsers_is_hex_valid',
|
||||
'test_parsers_is_hex_invalid',]),
|
||||
'test_parsers_is_hex_invalid',
|
||||
'test_copy_data_bytesio',]),
|
||||
'basic': set(['test_gpghome_creation',
|
||||
'test_gpg_binary',
|
||||
'test_gpg_binary_not_abs',
|
||||
|
@ -768,11 +771,6 @@ if __name__ == "__main__":
|
|||
suite_names = list()
|
||||
for name, methodset in suites.items():
|
||||
suite_names.append(name)
|
||||
this_file = inspect.getfile(inspect.currentframe()).split('.', 1)[0]
|
||||
#mod = getattr(this_file, '__dict__', None)
|
||||
#func = getattr(gnupg.__module__, '__setattr__', None)
|
||||
#if func is not None:
|
||||
# func(name, list(methodset))
|
||||
setattr(GPGTestCase, name, list(methodset))
|
||||
|
||||
parser = argparse.ArgumentParser(description="Unittests for python-gnupg")
|
||||
|
|
134
gnupg/util.py
134
gnupg/util.py
|
@ -30,6 +30,11 @@ from datetime import datetime
|
|||
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
import threading
|
||||
|
||||
try:
|
||||
from io import StringIO
|
||||
|
@ -62,6 +67,54 @@ _ugpg = os.path.join(_user, '.gnupg') ## $HOME/.gnupg
|
|||
_conf = os.path.join(os.path.join(_user, '.config'),
|
||||
'python-gnupg') ## $HOME/.config/python-gnupg
|
||||
|
||||
|
||||
def _copy_data(instream, outstream):
|
||||
"""Copy data from one stream to another.
|
||||
|
||||
:type instream: :class:`io.BytesIO` or :class:`io.StringIO` or file
|
||||
:param instream: A byte stream or open file to read from.
|
||||
:param file outstream: The file descriptor of a tmpfile to write to.
|
||||
"""
|
||||
sent = 0
|
||||
|
||||
#try:
|
||||
# #assert (util._is_stream(instream)
|
||||
# # or isinstance(instream, file)), "instream not stream or file"
|
||||
# assert isinstance(outstream, file), "outstream is not a file"
|
||||
#except AssertionError as ae:
|
||||
# logger.exception(ae)
|
||||
# return
|
||||
|
||||
if hasattr(sys.stdin, 'encoding'):
|
||||
enc = sys.stdin.encoding
|
||||
else:
|
||||
enc = 'ascii'
|
||||
|
||||
while True:
|
||||
data = instream.read(1024)
|
||||
if len(data) == 0:
|
||||
break
|
||||
sent += len(data)
|
||||
logger.debug("_copy_data(): sending chunk (%d):\n%s" % (sent, data[:256]))
|
||||
try:
|
||||
outstream.write(data)
|
||||
except UnicodeError:
|
||||
try:
|
||||
outstream.write(data.encode(enc))
|
||||
except IOError:
|
||||
logger.exception('_copy_data(): Error sending data: Broken pipe')
|
||||
break
|
||||
except IOError:
|
||||
# Can get 'broken pipe' errors even when all data was sent
|
||||
logger.exception('_copy_data(): Error sending data: Broken pipe')
|
||||
break
|
||||
try:
|
||||
outstream.close()
|
||||
except IOError:
|
||||
logger.exception('_copy_data(): Got IOError while closing %s' % outstream)
|
||||
else:
|
||||
logger.debug("_copy_data(): Closed output, %d bytes sent." % sent)
|
||||
|
||||
def _create_gpghome(gpghome):
|
||||
"""Create the specified GnuPG home directory, if necessary.
|
||||
|
||||
|
@ -181,15 +234,78 @@ def _make_binary_stream(s, encoding):
|
|||
rv = StringIO(s)
|
||||
return rv
|
||||
|
||||
## xxx unused function?
|
||||
def _today():
|
||||
"""Get the current date.
|
||||
def _make_passphrase(length=None, save=False, file=None):
|
||||
"""Create a passphrase and write it to a file that only the user can read.
|
||||
|
||||
This is not very secure, and should not be relied upon for actual key
|
||||
passphrases.
|
||||
|
||||
:param int length: The length in bytes of the string to generate.
|
||||
|
||||
:param file file: The file to save the generated passphrase in. If not
|
||||
given, defaults to 'passphrase-<the real user id>-<seconds since
|
||||
epoch>' in the top-level directory.
|
||||
"""
|
||||
if not length:
|
||||
length = 40
|
||||
|
||||
passphrase = _make_random_string(length)
|
||||
|
||||
if save:
|
||||
ruid, euid, suid = os.getresuid()
|
||||
gid = os.getgid()
|
||||
now = time.mktime(time.gmtime())
|
||||
|
||||
if not file:
|
||||
filename = str('passphrase-%s-%s' % uid, now)
|
||||
file = os.path.join(_repo, filename)
|
||||
|
||||
with open(file, 'a') as fh:
|
||||
fh.write(passphrase)
|
||||
fh.flush()
|
||||
fh.close()
|
||||
os.chmod(file, 0600)
|
||||
os.chown(file, ruid, gid)
|
||||
|
||||
logger.warn("Generated passphrase saved to %s" % file)
|
||||
return passphrase
|
||||
|
||||
def _make_random_string(length):
|
||||
"""Returns a random lowercase, uppercase, alphanumerical string.
|
||||
|
||||
:param int length: The length in bytes of the string to generate.
|
||||
"""
|
||||
chars = string.ascii_lowercase + string.ascii_uppercase + string.digits
|
||||
return ''.join(random.choice(chars) for x in range(length))
|
||||
|
||||
def _next_year():
|
||||
"""Get the date of today plus one year.
|
||||
|
||||
:rtype: str
|
||||
:returns: The date, in the format '%Y-%m-%d'.
|
||||
:returns: The date of this day next year, in the format '%Y-%m-%d'.
|
||||
"""
|
||||
now_string = datetime.now().__str__()
|
||||
return now_string.split(' ', 1)[0]
|
||||
now = datetime.now().__str__()
|
||||
date = now.split(' ', 1)[0]
|
||||
year, month, day = date.split('-', 2)
|
||||
next_year = str(int(year)+1)
|
||||
return '-'.join((next_year, month, day))
|
||||
|
||||
def _threaded_copy_data(instream, outstream):
|
||||
"""Copy data from one stream to another in a separate thread.
|
||||
|
||||
Wraps ``_copy_data()`` in a :class:`threading.Thread`.
|
||||
|
||||
:type instream: :class:`io.BytesIO` or :class:`io.StringIO`
|
||||
:param instream: A byte stream to read from.
|
||||
:param file outstream: The file descriptor of a tmpfile to write to.
|
||||
"""
|
||||
copy_thread = threading.Thread(target=_copy_data,
|
||||
args=(instream, outstream))
|
||||
copy_thread.setDaemon(True)
|
||||
logger.debug('_threaded_copy_data(): %r, %r, %r', copy_thread,
|
||||
instream, outstream)
|
||||
copy_thread.start()
|
||||
return copy_thread
|
||||
|
||||
def _which(executable, flags=os.X_OK):
|
||||
"""Borrowed from Twisted's :mod:twisted.python.proutils .
|
||||
|
@ -228,3 +344,9 @@ def _which(executable, flags=os.X_OK):
|
|||
if os.access(pext, flags):
|
||||
result.append(pext)
|
||||
return result
|
||||
|
||||
def _write_passphrase(stream, passphrase, encoding):
|
||||
passphrase = '%s\n' % passphrase
|
||||
passphrase = passphrase.encode(encoding)
|
||||
stream.write(passphrase)
|
||||
logger.debug("_write_passphrase(): Wrote passphrase.")
|
||||
|
|
Loading…
Reference in New Issue