update to latest gnupg.py
This commit is contained in:
parent
c2fe1c3bdf
commit
651d52560d
|
@ -31,11 +31,10 @@ Modifications Copyright (C) 2008-2014 Vinay Sajip. All rights reserved.
|
||||||
|
|
||||||
A unittest harness (test_gnupg.py) has also been added.
|
A unittest harness (test_gnupg.py) has also been added.
|
||||||
"""
|
"""
|
||||||
import locale
|
|
||||||
|
|
||||||
__version__ = "0.3.4"
|
__version__ = "0.3.8.dev0"
|
||||||
__author__ = "Vinay Sajip"
|
__author__ = "Vinay Sajip"
|
||||||
__date__ = "$05-Jun-2014 09:48:54$"
|
__date__ = "$07-Dec-2014 18:46:17$"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
@ -53,6 +52,13 @@ from subprocess import PIPE
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
STARTUPINFO = None
|
||||||
|
if os.name == 'nt':
|
||||||
|
try:
|
||||||
|
from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW, SW_HIDE
|
||||||
|
except ImportError:
|
||||||
|
STARTUPINFO = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import logging.NullHandler as NullHandler
|
import logging.NullHandler as NullHandler
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -73,6 +79,50 @@ logger = logging.getLogger('gajim.c.gnupg')
|
||||||
if not logger.handlers:
|
if not logger.handlers:
|
||||||
logger.addHandler(NullHandler())
|
logger.addHandler(NullHandler())
|
||||||
|
|
||||||
|
# We use the test below because it works for Jython as well as CPython
|
||||||
|
if os.path.__name__ == 'ntpath':
|
||||||
|
# On Windows, we don't need shell quoting, other than worrying about
|
||||||
|
# paths with spaces in them.
|
||||||
|
def shell_quote(s):
|
||||||
|
return '"%s"' % s
|
||||||
|
else:
|
||||||
|
# Section copied from sarge
|
||||||
|
|
||||||
|
# This regex determines which shell input needs quoting
|
||||||
|
# because it may be unsafe
|
||||||
|
UNSAFE = re.compile(r'[^\w%+,./:=@-]')
|
||||||
|
|
||||||
|
def shell_quote(s):
|
||||||
|
"""
|
||||||
|
Quote text so that it is safe for Posix command shells.
|
||||||
|
|
||||||
|
For example, "*.py" would be converted to "'*.py'". If the text is
|
||||||
|
considered safe it is returned unquoted.
|
||||||
|
|
||||||
|
:param s: The value to quote
|
||||||
|
:type s: str (or unicode on 2.x)
|
||||||
|
:return: A safe version of the input, from the point of view of Posix
|
||||||
|
command shells
|
||||||
|
:rtype: The passed-in type
|
||||||
|
"""
|
||||||
|
if not isinstance(s, string_types):
|
||||||
|
raise TypeError('Expected string type, got %s' % type(s))
|
||||||
|
if not s:
|
||||||
|
result = "''"
|
||||||
|
elif not UNSAFE.search(s):
|
||||||
|
result = s
|
||||||
|
else:
|
||||||
|
result = "'%s'" % s.replace("'", r"'\''")
|
||||||
|
return result
|
||||||
|
|
||||||
|
# end of sarge code
|
||||||
|
|
||||||
|
# Now that we use shell=False, we shouldn't need to quote arguments.
|
||||||
|
# Use no_quote instead of shell_quote to remind us of where quoting
|
||||||
|
# was needed.
|
||||||
|
def no_quote(s):
|
||||||
|
return s
|
||||||
|
|
||||||
def _copy_data(instream, outstream):
|
def _copy_data(instream, outstream):
|
||||||
# Copy one stream to another
|
# Copy one stream to another
|
||||||
sent = 0
|
sent = 0
|
||||||
|
@ -112,11 +162,19 @@ def _write_passphrase(stream, passphrase, encoding):
|
||||||
passphrase = '%s\n' % passphrase
|
passphrase = '%s\n' % passphrase
|
||||||
passphrase = passphrase.encode(encoding)
|
passphrase = passphrase.encode(encoding)
|
||||||
stream.write(passphrase)
|
stream.write(passphrase)
|
||||||
logger.debug("Wrote passphrase: %r", passphrase)
|
logger.debug('Wrote passphrase')
|
||||||
|
|
||||||
def _is_sequence(instance):
|
def _is_sequence(instance):
|
||||||
return isinstance(instance, (list, tuple, set, frozenset))
|
return isinstance(instance, (list, tuple, set, frozenset))
|
||||||
|
|
||||||
|
def _make_memory_stream(s):
|
||||||
|
try:
|
||||||
|
from io import BytesIO
|
||||||
|
rv = BytesIO(s)
|
||||||
|
except ImportError:
|
||||||
|
rv = StringIO(s)
|
||||||
|
return rv
|
||||||
|
|
||||||
def _make_binary_stream(s, encoding):
|
def _make_binary_stream(s, encoding):
|
||||||
if _py3k:
|
if _py3k:
|
||||||
if isinstance(s, str):
|
if isinstance(s, str):
|
||||||
|
@ -124,12 +182,7 @@ def _make_binary_stream(s, encoding):
|
||||||
else:
|
else:
|
||||||
if type(s) is not str:
|
if type(s) is not str:
|
||||||
s = s.encode(encoding)
|
s = s.encode(encoding)
|
||||||
try:
|
return _make_memory_stream(s)
|
||||||
from io import BytesIO
|
|
||||||
rv = BytesIO(s)
|
|
||||||
except ImportError:
|
|
||||||
rv = StringIO(s)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
class Verify(object):
|
class Verify(object):
|
||||||
"Handle status messages for --verify"
|
"Handle status messages for --verify"
|
||||||
|
@ -175,7 +228,8 @@ class Verify(object):
|
||||||
"PLAINTEXT_LENGTH", "POLICY_URL", "DECRYPTION_INFO",
|
"PLAINTEXT_LENGTH", "POLICY_URL", "DECRYPTION_INFO",
|
||||||
"DECRYPTION_OKAY", "INV_SGNR", "FILE_START", "FILE_ERROR",
|
"DECRYPTION_OKAY", "INV_SGNR", "FILE_START", "FILE_ERROR",
|
||||||
"FILE_DONE", "PKA_TRUST_GOOD", "PKA_TRUST_BAD", "BADMDC",
|
"FILE_DONE", "PKA_TRUST_GOOD", "PKA_TRUST_BAD", "BADMDC",
|
||||||
"GOODMDC", "NO_SGNR", "NOTATION_NAME", "NOTATION_DATA"):
|
"GOODMDC", "NO_SGNR", "NOTATION_NAME", "NOTATION_DATA",
|
||||||
|
"PROGRESS"):
|
||||||
pass
|
pass
|
||||||
elif key == "BADSIG":
|
elif key == "BADSIG":
|
||||||
self.valid = False
|
self.valid = False
|
||||||
|
@ -230,6 +284,10 @@ class Verify(object):
|
||||||
else:
|
else:
|
||||||
self.key_status = 'signing key was revoked'
|
self.key_status = 'signing key was revoked'
|
||||||
self.status = self.key_status
|
self.status = self.key_status
|
||||||
|
elif key == "UNEXPECTED":
|
||||||
|
self.valid = False
|
||||||
|
self.key_id = value
|
||||||
|
self.status = 'unexpected data'
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown status message: %r" % key)
|
raise ValueError("Unknown status message: %r" % key)
|
||||||
|
|
||||||
|
@ -298,8 +356,8 @@ class ImportResult(object):
|
||||||
'problem': reason, 'text': self.problem_reason[reason]})
|
'problem': reason, 'text': self.problem_reason[reason]})
|
||||||
elif key == "IMPORT_RES":
|
elif key == "IMPORT_RES":
|
||||||
import_res = value.split()
|
import_res = value.split()
|
||||||
for i in range(len(self.counts)):
|
for i, count in enumerate(self.counts):
|
||||||
setattr(self, self.counts[i], int(import_res[i]))
|
setattr(self, count, int(import_res[i]))
|
||||||
elif key == "KEYEXPIRED":
|
elif key == "KEYEXPIRED":
|
||||||
self.results.append({'fingerprint': None,
|
self.results.append({'fingerprint': None,
|
||||||
'problem': '0', 'text': 'Key expired'})
|
'problem': '0', 'text': 'Key expired'})
|
||||||
|
@ -326,7 +384,53 @@ BASIC_ESCAPES = {
|
||||||
r'\0': '\0',
|
r'\0': '\0',
|
||||||
}
|
}
|
||||||
|
|
||||||
class ListKeys(list):
|
class SendResult(object):
|
||||||
|
def __init__(self, gpg):
|
||||||
|
self.gpg = gpg
|
||||||
|
|
||||||
|
def handle_status(self, key, value):
|
||||||
|
logger.debug('SendResult: %s: %s', key, value)
|
||||||
|
|
||||||
|
class SearchKeys(list):
|
||||||
|
''' Handle status messages for --search-keys.
|
||||||
|
|
||||||
|
Handle pub and uid (relating the latter to the former).
|
||||||
|
|
||||||
|
Don't care about the rest
|
||||||
|
'''
|
||||||
|
|
||||||
|
UID_INDEX = 1
|
||||||
|
FIELDS = 'type keyid algo length date expires'.split()
|
||||||
|
|
||||||
|
def __init__(self, gpg):
|
||||||
|
self.gpg = gpg
|
||||||
|
self.curkey = None
|
||||||
|
self.fingerprints = []
|
||||||
|
self.uids = []
|
||||||
|
|
||||||
|
def get_fields(self, args):
|
||||||
|
result = {}
|
||||||
|
for i, var in enumerate(self.FIELDS):
|
||||||
|
result[var] = args[i]
|
||||||
|
result['uids'] = []
|
||||||
|
return result
|
||||||
|
|
||||||
|
def pub(self, args):
|
||||||
|
self.curkey = curkey = self.get_fields(args)
|
||||||
|
self.append(curkey)
|
||||||
|
|
||||||
|
def uid(self, args):
|
||||||
|
uid = args[self.UID_INDEX]
|
||||||
|
uid = ESCAPE_PATTERN.sub(lambda m: chr(int(m.group(1), 16)), uid)
|
||||||
|
for k, v in BASIC_ESCAPES.items():
|
||||||
|
uid = uid.replace(k, v)
|
||||||
|
self.curkey['uids'].append(uid)
|
||||||
|
self.uids.append(uid)
|
||||||
|
|
||||||
|
def handle_status(self, key, value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ListKeys(SearchKeys):
|
||||||
''' Handle status messages for --list-keys.
|
''' Handle status messages for --list-keys.
|
||||||
|
|
||||||
Handle pub and uid (relating the latter to the former).
|
Handle pub and uid (relating the latter to the former).
|
||||||
|
@ -343,25 +447,17 @@ class ListKeys(list):
|
||||||
grp = reserved for gpgsm
|
grp = reserved for gpgsm
|
||||||
rvk = revocation key
|
rvk = revocation key
|
||||||
'''
|
'''
|
||||||
def __init__(self, gpg):
|
|
||||||
self.gpg = gpg
|
UID_INDEX = 9
|
||||||
self.curkey = None
|
FIELDS = 'type trust length algo keyid date expires dummy ownertrust uid'.split()
|
||||||
self.fingerprints = []
|
|
||||||
self.uids = []
|
|
||||||
|
|
||||||
def key(self, args):
|
def key(self, args):
|
||||||
vars = ("""
|
self.curkey = curkey = self.get_fields(args)
|
||||||
type trust length algo keyid date expires dummy ownertrust uid
|
if curkey['uid']:
|
||||||
""").split()
|
curkey['uids'].append(curkey['uid'])
|
||||||
self.curkey = {}
|
del curkey['uid']
|
||||||
for i in range(len(vars)):
|
curkey['subkeys'] = []
|
||||||
self.curkey[vars[i]] = args[i]
|
self.append(curkey)
|
||||||
self.curkey['uids'] = []
|
|
||||||
if self.curkey['uid']:
|
|
||||||
self.curkey['uids'].append(self.curkey['uid'])
|
|
||||||
del self.curkey['uid']
|
|
||||||
self.curkey['subkeys'] = []
|
|
||||||
self.append(self.curkey)
|
|
||||||
|
|
||||||
pub = sec = key
|
pub = sec = key
|
||||||
|
|
||||||
|
@ -369,22 +465,34 @@ class ListKeys(list):
|
||||||
self.curkey['fingerprint'] = args[9]
|
self.curkey['fingerprint'] = args[9]
|
||||||
self.fingerprints.append(args[9])
|
self.fingerprints.append(args[9])
|
||||||
|
|
||||||
def uid(self, args):
|
|
||||||
uid = args[9]
|
|
||||||
uid = ESCAPE_PATTERN.sub(lambda m: chr(int(m.group(1), 16)), uid)
|
|
||||||
for k, v in BASIC_ESCAPES.items():
|
|
||||||
uid = uid.replace(k, v)
|
|
||||||
self.curkey['uids'].append(uid)
|
|
||||||
self.uids.append(uid)
|
|
||||||
|
|
||||||
def sub(self, args):
|
def sub(self, args):
|
||||||
subkey = [args[4], args[11]]
|
subkey = [args[4], args[11]]
|
||||||
self.curkey['subkeys'].append(subkey)
|
self.curkey['subkeys'].append(subkey)
|
||||||
|
|
||||||
def handle_status(self, key, value):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Crypt(Verify):
|
class ScanKeys(ListKeys):
|
||||||
|
''' Handle status messages for --with-fingerprint.'''
|
||||||
|
|
||||||
|
def sub(self, args):
|
||||||
|
# --with-fingerprint --with-colons somehow outputs fewer colons,
|
||||||
|
# use the last value args[-1] instead of args[11]
|
||||||
|
subkey = [args[4], args[-1]]
|
||||||
|
self.curkey['subkeys'].append(subkey)
|
||||||
|
|
||||||
|
class TextHandler(object):
|
||||||
|
def _as_text(self):
|
||||||
|
return self.data.decode(self.gpg.encoding, self.gpg.decode_errors)
|
||||||
|
|
||||||
|
if _py3k:
|
||||||
|
__str__ = _as_text
|
||||||
|
else:
|
||||||
|
__unicode__ = _as_text
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
|
||||||
|
class Crypt(Verify, TextHandler):
|
||||||
"Handle status messages for --encrypt and --decrypt"
|
"Handle status messages for --encrypt and --decrypt"
|
||||||
def __init__(self, gpg):
|
def __init__(self, gpg):
|
||||||
Verify.__init__(self, gpg)
|
Verify.__init__(self, gpg)
|
||||||
|
@ -398,19 +506,16 @@ class Crypt(Verify):
|
||||||
|
|
||||||
__bool__ = __nonzero__
|
__bool__ = __nonzero__
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.data.decode(self.gpg.encoding, self.gpg.decode_errors)
|
|
||||||
|
|
||||||
def handle_status(self, key, value):
|
def handle_status(self, key, value):
|
||||||
if key in ("ENC_TO", "USERID_HINT", "GOODMDC", "END_DECRYPTION",
|
if key in ("ENC_TO", "USERID_HINT", "GOODMDC", "END_DECRYPTION",
|
||||||
"BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA",
|
"BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA", "PROGRESS",
|
||||||
"CARDCTRL", "BADMDC", "SC_OP_FAILURE", "SC_OP_SUCCESS"):
|
"CARDCTRL", "BADMDC", "SC_OP_FAILURE", "SC_OP_SUCCESS"):
|
||||||
# in the case of ERROR, this is because a more specific error
|
# in the case of ERROR, this is because a more specific error
|
||||||
# message will have come first
|
# message will have come first
|
||||||
pass
|
pass
|
||||||
elif key in ("NEED_PASSPHRASE", "BAD_PASSPHRASE", "GOOD_PASSPHRASE",
|
elif key in ("NEED_PASSPHRASE", "BAD_PASSPHRASE", "GOOD_PASSPHRASE",
|
||||||
"MISSING_PASSPHRASE", "DECRYPTION_FAILED",
|
"MISSING_PASSPHRASE", "DECRYPTION_FAILED",
|
||||||
"KEY_NOT_CREATED"):
|
"KEY_NOT_CREATED", "NEED_PASSPHRASE_PIN"):
|
||||||
self.status = key.replace("_", " ").lower()
|
self.status = key.replace("_", " ").lower()
|
||||||
elif key == "NEED_PASSPHRASE_SYM":
|
elif key == "NEED_PASSPHRASE_SYM":
|
||||||
self.status = 'need symmetric passphrase'
|
self.status = 'need symmetric passphrase'
|
||||||
|
@ -487,31 +592,29 @@ class DeleteResult(object):
|
||||||
__bool__ = __nonzero__
|
__bool__ = __nonzero__
|
||||||
|
|
||||||
|
|
||||||
class Sign(object):
|
class Sign(TextHandler):
|
||||||
"Handle status messages for --sign"
|
"Handle status messages for --sign"
|
||||||
def __init__(self, gpg):
|
def __init__(self, gpg):
|
||||||
self.gpg = gpg
|
self.gpg = gpg
|
||||||
self.type = None
|
self.type = None
|
||||||
self.hash_algo = None
|
self.hash_algo = None
|
||||||
self.fingerprint = None
|
self.fingerprint = None
|
||||||
self.status = ''
|
|
||||||
|
|
||||||
def __nonzero__(self):
|
def __nonzero__(self):
|
||||||
return self.fingerprint is not None
|
return self.fingerprint is not None
|
||||||
|
|
||||||
__bool__ = __nonzero__
|
__bool__ = __nonzero__
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.data.decode(self.gpg.encoding, self.gpg.decode_errors)
|
|
||||||
|
|
||||||
def handle_status(self, key, value):
|
def handle_status(self, key, value):
|
||||||
if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE",
|
if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE",
|
||||||
"GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL", "INV_SGNR",
|
"GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL", "INV_SGNR",
|
||||||
"KEYREVOKED", "NO_SGNR", "MISSING_PASSPHRASE",
|
"NO_SGNR", "MISSING_PASSPHRASE", "NEED_PASSPHRASE_PIN",
|
||||||
"SC_OP_FAILURE", "SC_OP_SUCCESS"):
|
"SC_OP_FAILURE", "SC_OP_SUCCESS", "PROGRESS"):
|
||||||
pass
|
pass
|
||||||
elif key in ("KEYEXPIRED", "SIGEXPIRED"):
|
elif key in ("KEYEXPIRED", "SIGEXPIRED"):
|
||||||
self.status = 'key expired'
|
self.status = 'key expired'
|
||||||
|
elif key == "KEYREVOKED":
|
||||||
|
self.status = 'key revoked'
|
||||||
elif key == "SIG_CREATED":
|
elif key == "SIG_CREATED":
|
||||||
(self.type,
|
(self.type,
|
||||||
algo, self.hash_algo, cls,
|
algo, self.hash_algo, cls,
|
||||||
|
@ -520,7 +623,8 @@ class Sign(object):
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown status message: %r" % key)
|
raise ValueError("Unknown status message: %r" % key)
|
||||||
|
|
||||||
VERSION_RE = re.compile(r'gpg \(GnuPG\) (\d+(\.\d+)*)'.encode('utf-8'), re.I)
|
VERSION_RE = re.compile(r'gpg \(GnuPG\) (\d+(\.\d+)*)'.encode('ascii'), re.I)
|
||||||
|
HEX_DIGITS_RE = re.compile(r'[0-9a-f]+$', re.I)
|
||||||
|
|
||||||
class GPG(object):
|
class GPG(object):
|
||||||
|
|
||||||
|
@ -531,7 +635,10 @@ class GPG(object):
|
||||||
'delete': DeleteResult,
|
'delete': DeleteResult,
|
||||||
'generate': GenKey,
|
'generate': GenKey,
|
||||||
'import': ImportResult,
|
'import': ImportResult,
|
||||||
|
'send': SendResult,
|
||||||
'list': ListKeys,
|
'list': ListKeys,
|
||||||
|
'scan': ScanKeys,
|
||||||
|
'search': SearchKeys,
|
||||||
'sign': Sign,
|
'sign': Sign,
|
||||||
'verify': Verify,
|
'verify': Verify,
|
||||||
}
|
}
|
||||||
|
@ -571,9 +678,11 @@ class GPG(object):
|
||||||
if isinstance(options, str):
|
if isinstance(options, str):
|
||||||
options = [options]
|
options = [options]
|
||||||
self.options = options
|
self.options = options
|
||||||
self.encoding = locale.getpreferredencoding()
|
# Changed in 0.3.7 to use Latin-1 encoding rather than
|
||||||
if self.encoding is None: # This happens on Jython!
|
# locale.getpreferredencoding falling back to sys.stdin.encoding
|
||||||
self.encoding = sys.stdin.encoding
|
# falling back to utf-8, because gpg itself uses latin-1 as the default
|
||||||
|
# encoding.
|
||||||
|
self.encoding = 'latin-1'
|
||||||
if gnupghome and not os.path.isdir(self.gnupghome):
|
if gnupghome and not os.path.isdir(self.gnupghome):
|
||||||
os.makedirs(self.gnupghome,0x1C0)
|
os.makedirs(self.gnupghome,0x1C0)
|
||||||
p = self._open_subprocess(["--version"])
|
p = self._open_subprocess(["--version"])
|
||||||
|
@ -586,7 +695,7 @@ class GPG(object):
|
||||||
if not m:
|
if not m:
|
||||||
self.version = None
|
self.version = None
|
||||||
else:
|
else:
|
||||||
dot = '.'.encode('utf-8')
|
dot = '.'.encode('ascii')
|
||||||
self.version = tuple([int(s) for s in m.groups()[0].split(dot)])
|
self.version = tuple([int(s) for s in m.groups()[0].split(dot)])
|
||||||
|
|
||||||
def make_args(self, args, passphrase):
|
def make_args(self, args, passphrase):
|
||||||
|
@ -595,18 +704,18 @@ class GPG(object):
|
||||||
will be appended. The ``passphrase`` argument needs to be True if
|
will be appended. The ``passphrase`` argument needs to be True if
|
||||||
a passphrase will be sent to GPG, else False.
|
a passphrase will be sent to GPG, else False.
|
||||||
"""
|
"""
|
||||||
cmd = [self.gpgbinary, '--status-fd 2 --no-tty']
|
cmd = [self.gpgbinary, '--status-fd', '2', '--no-tty']
|
||||||
if self.gnupghome:
|
if self.gnupghome:
|
||||||
cmd.append('--homedir "%s"' % self.gnupghome)
|
cmd.extend(['--homedir', no_quote(self.gnupghome)])
|
||||||
if self.keyring:
|
if self.keyring:
|
||||||
cmd.append('--no-default-keyring')
|
cmd.append('--no-default-keyring')
|
||||||
for fn in self.keyring:
|
for fn in self.keyring:
|
||||||
cmd.append('--keyring "%s"' % fn)
|
cmd.extend(['--keyring', no_quote(fn)])
|
||||||
if self.secret_keyring:
|
if self.secret_keyring:
|
||||||
for fn in self.secret_keyring:
|
for fn in self.secret_keyring:
|
||||||
cmd.append('--secret-keyring "%s"' % fn)
|
cmd.extend(['--secret-keyring', no_quote(fn)])
|
||||||
if passphrase:
|
if passphrase:
|
||||||
cmd.append('--batch --passphrase-fd 0')
|
cmd.extend(['--batch', '--passphrase-fd', '0'])
|
||||||
if self.use_agent:
|
if self.use_agent:
|
||||||
cmd.append('--use-agent')
|
cmd.append('--use-agent')
|
||||||
if self.options:
|
if self.options:
|
||||||
|
@ -617,11 +726,19 @@ class GPG(object):
|
||||||
def _open_subprocess(self, args, passphrase=False):
|
def _open_subprocess(self, args, passphrase=False):
|
||||||
# Internal method: open a pipe to a GPG subprocess and return
|
# Internal method: open a pipe to a GPG subprocess and return
|
||||||
# the file objects for communicating with it.
|
# the file objects for communicating with it.
|
||||||
cmd = ' '.join(self.make_args(args, passphrase))
|
cmd = self.make_args(args, passphrase)
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(cmd)
|
pcmd = ' '.join(cmd)
|
||||||
|
print(pcmd)
|
||||||
logger.debug("%s", cmd)
|
logger.debug("%s", cmd)
|
||||||
return Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
if not STARTUPINFO:
|
||||||
|
si = None
|
||||||
|
else:
|
||||||
|
si = STARTUPINFO()
|
||||||
|
si.dwFlags = STARTF_USESHOWWINDOW
|
||||||
|
si.wShowWindow = SW_HIDE
|
||||||
|
return Popen(cmd, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE,
|
||||||
|
startupinfo=si)
|
||||||
|
|
||||||
def _read_response(self, stream, result):
|
def _read_response(self, stream, result):
|
||||||
# Internal method: reads all the stderr output from GPG, taking notice
|
# Internal method: reads all the stderr output from GPG, taking notice
|
||||||
|
@ -723,8 +840,15 @@ class GPG(object):
|
||||||
f.close()
|
f.close()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def set_output_without_confirmation(self, args, output):
|
||||||
|
"If writing to a file which exists, avoid a confirmation message."
|
||||||
|
if os.path.exists(output):
|
||||||
|
# We need to avoid an overwrite confirmation message
|
||||||
|
args.extend(['--batch', '--yes'])
|
||||||
|
args.extend(['--output', output])
|
||||||
|
|
||||||
def sign_file(self, file, keyid=None, passphrase=None, clearsign=True,
|
def sign_file(self, file, keyid=None, passphrase=None, clearsign=True,
|
||||||
detach=False, binary=False):
|
detach=False, binary=False, output=None):
|
||||||
"""sign file"""
|
"""sign file"""
|
||||||
logger.debug("sign_file: %s", file)
|
logger.debug("sign_file: %s", file)
|
||||||
if binary:
|
if binary:
|
||||||
|
@ -738,7 +862,10 @@ class GPG(object):
|
||||||
elif clearsign:
|
elif clearsign:
|
||||||
args.append("--clearsign")
|
args.append("--clearsign")
|
||||||
if keyid:
|
if keyid:
|
||||||
args.append('--default-key "%s"' % keyid)
|
args.extend(['--default-key', no_quote(keyid)])
|
||||||
|
if output: # write the output to a file with the specified name
|
||||||
|
self.set_output_without_confirmation(args, output)
|
||||||
|
|
||||||
result = self.result_map['sign'](self)
|
result = self.result_map['sign'](self)
|
||||||
#We could use _handle_io here except for the fact that if the
|
#We could use _handle_io here except for the fact that if the
|
||||||
#passphrase is bad, gpg bails and you can't write the message.
|
#passphrase is bad, gpg bails and you can't write the message.
|
||||||
|
@ -790,8 +917,8 @@ class GPG(object):
|
||||||
logger.debug('Wrote to temp file: %r', s)
|
logger.debug('Wrote to temp file: %r', s)
|
||||||
os.write(fd, s)
|
os.write(fd, s)
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
args.append(fn)
|
args.append(no_quote(fn))
|
||||||
args.append('"%s"' % data_filename)
|
args.append(no_quote(data_filename))
|
||||||
try:
|
try:
|
||||||
p = self._open_subprocess(args)
|
p = self._open_subprocess(args)
|
||||||
self._collect_output(p, result, stdin=p.stdin)
|
self._collect_output(p, result, stdin=p.stdin)
|
||||||
|
@ -799,6 +926,15 @@ class GPG(object):
|
||||||
os.unlink(fn)
|
os.unlink(fn)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def verify_data(self, sig_filename, data):
|
||||||
|
"Verify the signature in sig_filename against data in memory"
|
||||||
|
logger.debug('verify_data: %r, %r ...', sig_filename, data[:16])
|
||||||
|
result = self.result_map['verify'](self)
|
||||||
|
args = ['--verify', no_quote(sig_filename), '-']
|
||||||
|
stream = _make_memory_stream(data)
|
||||||
|
self._handle_io(args, stream, result, binary=True)
|
||||||
|
return result
|
||||||
|
|
||||||
#
|
#
|
||||||
# KEY MANAGEMENT
|
# KEY MANAGEMENT
|
||||||
#
|
#
|
||||||
|
@ -859,9 +995,6 @@ class GPG(object):
|
||||||
def recv_keys(self, keyserver, *keyids):
|
def recv_keys(self, keyserver, *keyids):
|
||||||
"""Import a key from a keyserver
|
"""Import a key from a keyserver
|
||||||
|
|
||||||
The doctest assertion is skipped in Jython because security permissions
|
|
||||||
may prevent the recv_keys from succeeding.
|
|
||||||
|
|
||||||
>>> import shutil
|
>>> import shutil
|
||||||
>>> shutil.rmtree("keys")
|
>>> shutil.rmtree("keys")
|
||||||
>>> gpg = GPG(gnupghome="keys")
|
>>> gpg = GPG(gnupghome="keys")
|
||||||
|
@ -874,33 +1007,60 @@ class GPG(object):
|
||||||
logger.debug('recv_keys: %r', keyids)
|
logger.debug('recv_keys: %r', keyids)
|
||||||
data = _make_binary_stream("", self.encoding)
|
data = _make_binary_stream("", self.encoding)
|
||||||
#data = ""
|
#data = ""
|
||||||
args = ['--keyserver', keyserver, '--recv-keys']
|
args = ['--keyserver', no_quote(keyserver), '--recv-keys']
|
||||||
args.extend(keyids)
|
args.extend([no_quote(k) for k in keyids])
|
||||||
self._handle_io(args, data, result, binary=True)
|
self._handle_io(args, data, result, binary=True)
|
||||||
logger.debug('recv_keys result: %r', result.__dict__)
|
logger.debug('recv_keys result: %r', result.__dict__)
|
||||||
data.close()
|
data.close()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def send_keys(self, keyserver, *keyids):
|
||||||
|
"""Send a key to a keyserver.
|
||||||
|
|
||||||
|
Note: it's not practical to test this function without sending
|
||||||
|
arbitrary data to live keyservers.
|
||||||
|
"""
|
||||||
|
result = self.result_map['send'](self)
|
||||||
|
logger.debug('send_keys: %r', keyids)
|
||||||
|
data = _make_binary_stream('', self.encoding)
|
||||||
|
#data = ""
|
||||||
|
args = ['--keyserver', no_quote(keyserver), '--send-keys']
|
||||||
|
args.extend([no_quote(k) for k in keyids])
|
||||||
|
self._handle_io(args, data, result, binary=True)
|
||||||
|
logger.debug('send_keys result: %r', result.__dict__)
|
||||||
|
data.close()
|
||||||
|
return result
|
||||||
|
|
||||||
def delete_keys(self, fingerprints, secret=False):
|
def delete_keys(self, fingerprints, secret=False):
|
||||||
which='key'
|
which='key'
|
||||||
if secret:
|
if secret:
|
||||||
which='secret-key'
|
which='secret-key'
|
||||||
if _is_sequence(fingerprints):
|
if _is_sequence(fingerprints):
|
||||||
fingerprints = ' '.join(fingerprints)
|
fingerprints = [no_quote(s) for s in fingerprints]
|
||||||
args = ['--batch --delete-%s "%s"' % (which, fingerprints)]
|
else:
|
||||||
|
fingerprints = [no_quote(fingerprints)]
|
||||||
|
args = ['--batch', '--delete-%s' % which]
|
||||||
|
args.extend(fingerprints)
|
||||||
result = self.result_map['delete'](self)
|
result = self.result_map['delete'](self)
|
||||||
p = self._open_subprocess(args)
|
p = self._open_subprocess(args)
|
||||||
self._collect_output(p, result, stdin=p.stdin)
|
self._collect_output(p, result, stdin=p.stdin)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def export_keys(self, keyids, secret=False):
|
def export_keys(self, keyids, secret=False, armor=True, minimal=False):
|
||||||
"export the indicated keys. 'keyid' is anything gpg accepts"
|
"export the indicated keys. 'keyid' is anything gpg accepts"
|
||||||
which=''
|
which=''
|
||||||
if secret:
|
if secret:
|
||||||
which='-secret-key'
|
which='-secret-key'
|
||||||
if _is_sequence(keyids):
|
if _is_sequence(keyids):
|
||||||
keyids = ' '.join(['"%s"' % k for k in keyids])
|
keyids = [no_quote(k) for k in keyids]
|
||||||
args = ["--armor --export%s %s" % (which, keyids)]
|
else:
|
||||||
|
keyids = [no_quote(keyids)]
|
||||||
|
args = ['--export%s' % which]
|
||||||
|
if armor:
|
||||||
|
args.append('--armor')
|
||||||
|
if minimal:
|
||||||
|
args.extend(['--export-options','export-minimal'])
|
||||||
|
args.extend(keyids)
|
||||||
p = self._open_subprocess(args)
|
p = self._open_subprocess(args)
|
||||||
# gpg --export produces no status-fd output; stdout will be
|
# gpg --export produces no status-fd output; stdout will be
|
||||||
# empty in case of failure
|
# empty in case of failure
|
||||||
|
@ -910,6 +1070,27 @@ class GPG(object):
|
||||||
logger.debug('export_keys result: %r', result.data)
|
logger.debug('export_keys result: %r', result.data)
|
||||||
return result.data.decode(self.encoding, self.decode_errors)
|
return result.data.decode(self.encoding, self.decode_errors)
|
||||||
|
|
||||||
|
def _get_list_output(self, p, kind):
|
||||||
|
# Get the response information
|
||||||
|
result = self.result_map[kind](self)
|
||||||
|
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()
|
||||||
|
for line in lines:
|
||||||
|
if self.verbose:
|
||||||
|
print(line)
|
||||||
|
logger.debug("line: %r", line.rstrip())
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
L = line.strip().split(':')
|
||||||
|
if not L:
|
||||||
|
continue
|
||||||
|
keyword = L[0]
|
||||||
|
if keyword in valid_keywords:
|
||||||
|
getattr(result, keyword)(L)
|
||||||
|
return result
|
||||||
|
|
||||||
def list_keys(self, secret=False):
|
def list_keys(self, secret=False):
|
||||||
""" list the keys currently in the keyring
|
""" list the keys currently in the keyring
|
||||||
|
|
||||||
|
@ -930,25 +1111,58 @@ class GPG(object):
|
||||||
which='keys'
|
which='keys'
|
||||||
if secret:
|
if secret:
|
||||||
which='secret-keys'
|
which='secret-keys'
|
||||||
args = "--list-%s --fixed-list-mode --fingerprint --with-colons" % (which,)
|
args = ["--list-%s" % which, "--fixed-list-mode", "--fingerprint",
|
||||||
args = [args]
|
"--with-colons"]
|
||||||
|
p = self._open_subprocess(args)
|
||||||
|
return self._get_list_output(p, 'list')
|
||||||
|
|
||||||
|
def scan_keys(self, filename):
|
||||||
|
"""
|
||||||
|
List details of an ascii armored or binary key file
|
||||||
|
without first importing it to the local keyring.
|
||||||
|
|
||||||
|
The function achieves this by running:
|
||||||
|
$ gpg --with-fingerprint --with-colons filename
|
||||||
|
"""
|
||||||
|
args = ['--with-fingerprint', '--with-colons']
|
||||||
|
args.append(no_quote(filename))
|
||||||
|
p = self._open_subprocess(args)
|
||||||
|
return self._get_list_output(p, 'scan')
|
||||||
|
|
||||||
|
def search_keys(self, query, keyserver='pgp.mit.edu'):
|
||||||
|
""" search keyserver by query (using --search-keys option)
|
||||||
|
|
||||||
|
>>> import shutil
|
||||||
|
>>> shutil.rmtree('keys')
|
||||||
|
>>> gpg = GPG(gnupghome='keys')
|
||||||
|
>>> os.chmod('keys', 0x1C0)
|
||||||
|
>>> result = gpg.search_keys('<vinay_sajip@hotmail.com>')
|
||||||
|
>>> assert result, 'Failed using default keyserver'
|
||||||
|
>>> keyserver = 'keyserver.ubuntu.com'
|
||||||
|
>>> result = gpg.search_keys('<vinay_sajip@hotmail.com>', keyserver)
|
||||||
|
>>> assert result, 'Failed using keyserver.ubuntu.com'
|
||||||
|
|
||||||
|
"""
|
||||||
|
query = query.strip()
|
||||||
|
if HEX_DIGITS_RE.match(query):
|
||||||
|
query = '0x' + query
|
||||||
|
args = ['--fixed-list-mode', '--fingerprint', '--with-colons',
|
||||||
|
'--keyserver', no_quote(keyserver), '--search-keys',
|
||||||
|
no_quote(query)]
|
||||||
p = self._open_subprocess(args)
|
p = self._open_subprocess(args)
|
||||||
|
|
||||||
# there might be some status thingumy here I should handle... (amk)
|
|
||||||
# ...nope, unless you care about expired sigs or keys (stevegt)
|
|
||||||
|
|
||||||
# Get the response information
|
# Get the response information
|
||||||
result = self.result_map['list'](self)
|
result = self.result_map['search'](self)
|
||||||
self._collect_output(p, result, stdin=p.stdin)
|
self._collect_output(p, result, stdin=p.stdin)
|
||||||
lines = result.data.decode(self.encoding,
|
lines = result.data.decode(self.encoding,
|
||||||
self.decode_errors).splitlines()
|
self.decode_errors).splitlines()
|
||||||
valid_keywords = 'pub uid sec fpr sub'.split()
|
valid_keywords = ['pub', 'uid']
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(line)
|
print(line)
|
||||||
logger.debug("line: %r", line.rstrip())
|
logger.debug('line: %r', line.rstrip())
|
||||||
if not line:
|
if not line: # sometimes get blank lines on Windows
|
||||||
break
|
continue
|
||||||
L = line.strip().split(':')
|
L = line.strip().split(':')
|
||||||
if not L:
|
if not L:
|
||||||
continue
|
continue
|
||||||
|
@ -969,7 +1183,7 @@ class GPG(object):
|
||||||
>>> assert not result
|
>>> assert not result
|
||||||
|
|
||||||
"""
|
"""
|
||||||
args = ["--gen-key --batch"]
|
args = ["--gen-key", "--batch"]
|
||||||
result = self.result_map['generate'](self)
|
result = self.result_map['generate'](self)
|
||||||
f = _make_binary_stream(input, self.encoding)
|
f = _make_binary_stream(input, self.encoding)
|
||||||
self._handle_io(args, f, result, binary=True)
|
self._handle_io(args, f, result, binary=True)
|
||||||
|
@ -986,9 +1200,8 @@ class GPG(object):
|
||||||
if str(val).strip(): # skip empty strings
|
if str(val).strip(): # skip empty strings
|
||||||
parms[key] = val
|
parms[key] = val
|
||||||
parms.setdefault('Key-Type','RSA')
|
parms.setdefault('Key-Type','RSA')
|
||||||
parms.setdefault('Key-Length',1024)
|
parms.setdefault('Key-Length',2048)
|
||||||
parms.setdefault('Name-Real', "Autogenerated Key")
|
parms.setdefault('Name-Real', "Autogenerated Key")
|
||||||
parms.setdefault('Name-Comment', "Generated by gnupg.py")
|
|
||||||
try:
|
try:
|
||||||
logname = os.environ['LOGNAME']
|
logname = os.environ['LOGNAME']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1033,23 +1246,30 @@ class GPG(object):
|
||||||
"Encrypt the message read from the file-like object 'file'"
|
"Encrypt the message read from the file-like object 'file'"
|
||||||
args = ['--encrypt']
|
args = ['--encrypt']
|
||||||
if symmetric:
|
if symmetric:
|
||||||
|
# can't be False or None - could be True or a cipher algo value
|
||||||
|
# such as AES256
|
||||||
args = ['--symmetric']
|
args = ['--symmetric']
|
||||||
|
if symmetric is not True:
|
||||||
|
args.extend(['--cipher-algo', no_quote(symmetric)])
|
||||||
|
# else use the default, currently CAST5
|
||||||
else:
|
else:
|
||||||
args = ['--encrypt']
|
if not recipients:
|
||||||
|
raise ValueError('No recipients specified with asymmetric '
|
||||||
|
'encryption')
|
||||||
if not _is_sequence(recipients):
|
if not _is_sequence(recipients):
|
||||||
recipients = (recipients,)
|
recipients = (recipients,)
|
||||||
for recipient in recipients:
|
for recipient in recipients:
|
||||||
args.append('--recipient "%s"' % recipient)
|
args.extend(['--recipient', no_quote(recipient)])
|
||||||
if armor: # create ascii-armored output - set to False for binary output
|
if armor: # create ascii-armored output - False for binary output
|
||||||
args.append('--armor')
|
args.append('--armor')
|
||||||
if output: # write the output to a file with the specified name
|
if output: # write the output to a file with the specified name
|
||||||
if os.path.exists(output):
|
self.set_output_without_confirmation(args, output)
|
||||||
os.remove(output) # to avoid overwrite confirmation message
|
if sign is True:
|
||||||
args.append('--output "%s"' % output)
|
args.append('--sign')
|
||||||
if sign:
|
elif sign:
|
||||||
args.append('--sign --default-key "%s"' % sign)
|
args.extend(['--sign', '--default-key', no_quote(sign)])
|
||||||
if always_trust:
|
if always_trust:
|
||||||
args.append("--always-trust")
|
args.append('--always-trust')
|
||||||
result = self.result_map['crypt'](self)
|
result = self.result_map['crypt'](self)
|
||||||
self._handle_io(args, file, result, passphrase=passphrase, binary=True)
|
self._handle_io(args, file, result, passphrase=passphrase, binary=True)
|
||||||
logger.debug('encrypt result: %r', result.data)
|
logger.debug('encrypt result: %r', result.data)
|
||||||
|
@ -1111,13 +1331,10 @@ class GPG(object):
|
||||||
output=None):
|
output=None):
|
||||||
args = ["--decrypt"]
|
args = ["--decrypt"]
|
||||||
if output: # write the output to a file with the specified name
|
if output: # write the output to a file with the specified name
|
||||||
if os.path.exists(output):
|
self.set_output_without_confirmation(args, output)
|
||||||
os.remove(output) # to avoid overwrite confirmation message
|
|
||||||
args.append('--output "%s"' % output)
|
|
||||||
if always_trust:
|
if always_trust:
|
||||||
args.append("--always-trust")
|
args.append("--always-trust")
|
||||||
result = self.result_map['crypt'](self)
|
result = self.result_map['crypt'](self)
|
||||||
self._handle_io(args, file, result, passphrase, binary=True)
|
self._handle_io(args, file, result, passphrase, binary=True)
|
||||||
logger.debug('decrypt result: %r', result.data)
|
logger.debug('decrypt result: %r', result.data)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue