From e837451aa715a6d75b8ccf4a8c493b9e65792ee1 Mon Sep 17 00:00:00 2001 From: Daniel Wilcox Date: Sat, 15 Oct 2016 18:23:17 -0400 Subject: [PATCH 1/4] Initial commit to add keyserver options. --- gnupg/_meta.py | 16 +++++++++++++--- gnupg/_parsers.py | 5 +++++ gnupg/gnupg.py | 11 ++++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/gnupg/_meta.py b/gnupg/_meta.py index 32ab287..00562db 100644 --- a/gnupg/_meta.py +++ b/gnupg/_meta.py @@ -744,20 +744,30 @@ def _handle_io(self, args, file, result, passphrase=False, binary=False): self._collect_output(p, result, writer, stdin) return result - def _recv_keys(self, keyids, keyserver=None): + def _recv_keys(self, keyids, keyserver=None, keyserver_certs=None): """Import keys from a keyserver. :param str keyids: A space-delimited string containing the keyids to request. :param str keyserver: The keyserver to request the ``keyids`` from; defaults to `gnupg.GPG.keyserver`. + :param str keyserver_certs: A file passed as the CA cert file for the + keyserver. """ if not keyserver: keyserver = self.keyserver args = ['--keyserver {0}'.format(keyserver), '--recv-keys {0}'.format(keyids)] - log.info('Requesting keys from %s: %s' % (keyserver, keyids)) + if keyserver_certs: + args.insert(0, '--keyserver-options ca-cert-file={0}'.format( + keyserver_certs) + ) + log.info('Requesting keys from %s (using CA file %s): %s' % ( + keyserver,keyserver_certs, keyids) + ) + print('%s args: %s' % (self.binary, ' '.join(args))) + print('make_args: %s' % (self._make_args(args, False))) result = self._result_map['import'](self) proc = self._open_subprocess(args) @@ -1029,7 +1039,7 @@ def _encrypt(self, data, recipients, log.info("Encrypted output written successfully.") return result - + def _add_recipient_string(self, args, hidden_recipients, recipient): if isinstance(hidden_recipients, (list, tuple)): if [s for s in hidden_recipients if recipient in str(s)]: diff --git a/gnupg/_parsers.py b/gnupg/_parsers.py index 9de57d2..b8f24d5 100644 --- a/gnupg/_parsers.py +++ b/gnupg/_parsers.py @@ -328,6 +328,10 @@ def _check_option(arg, value): checked += (v + " ") else: log.debug("Dropping keyserver: %s" % v) continue + elif flag in ['--keyserver-options']: + print('found keyserver options: %s' % v) + checked += (v + " ") + continue ## the rest are strings, filenames, etc, and should be ## shell escaped: @@ -496,6 +500,7 @@ def _get_options_group(group=None): #: These have their own parsers and don't really fit into a group other_options = frozenset(['--debug-level', '--keyserver', + '--keyserver-options', ]) #: These should have a directory for an argument diff --git a/gnupg/gnupg.py b/gnupg/gnupg.py index 215233e..f854921 100644 --- a/gnupg/gnupg.py +++ b/gnupg/gnupg.py @@ -369,13 +369,22 @@ def recv_keys(self, *keyids, **kwargs): """Import keys from a keyserver. >>> gpg = gnupg.GPG(homedir="doctests") - >>> key = gpg.recv_keys('hkp://pgp.mit.edu', '3FF0DB166A7476EA') + >>> key = gpg.recv_keys('3FF0DB166A7476EA', + keyserver='hkp://pgp.mit.edu') >>> assert key + >>> ssl_keyserver = 'hkps://hkps.pool.sks-keyservers.net' + >>> ca_cert = '/home/user/hkps.pool.sks-keyservers.netCA.pem' + >>> gpg.recv_keys('6F682D87', + keyserver=ssl_keyserver, + keyserver_certs=ca_cert) + :param str keyids: Each ``keyids`` argument should be a string containing a keyid to request. :param str keyserver: The keyserver to request the ``keyids`` from; defaults to `gnupg.GPG.keyserver`. + :param str keyserver_certs: A file passed as the CA cert file for the + keyserver. """ if keyids: keys = ' '.join([key for key in keyids]) From 62e5c4fd3f5b2b7b827344e83d5f32971e9b6dc0 Mon Sep 17 00:00:00 2001 From: Daniel Wilcox Date: Mon, 17 Oct 2016 02:17:17 -0400 Subject: [PATCH 2/4] Added validation for keyserver-options. --- gnupg/_parsers.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/gnupg/_parsers.py b/gnupg/_parsers.py index b8f24d5..244cbce 100644 --- a/gnupg/_parsers.py +++ b/gnupg/_parsers.py @@ -29,6 +29,12 @@ except ImportError: from ordereddict import OrderedDict +try: + import urlparse +except ImportError: + from urllib import parse as urlparse + +import os import re from . import _util @@ -74,6 +80,67 @@ def _check_keyserver(location): return keyserver return None + +def _check_keyserver_option(ks_option): + """Check that the provided keyserver option is valid and safe. + + :param str ks_option: A valid argument to --keyserver-option. + :rtype: :obj:`str` or :obj:`None` + :returns: A string of the keyserver option or None. + """ + def _is_valid_file(option_value): + """Verify option value is a file.""" + return os.path.isfile(option_value) + + def _is_valid_integer(option_value): + """Verify option value is an integer.""" + return str.isdigit(option_value) + + def _is_valid_http_proxy(option_value): + """Verify option value looks like a proxy URL.""" + if not (option_value.startswith('http://') or + option_value.startswith('https://')): + proxy = 'http://{0}'.format(option_value) + else: + proxy = option_value + parsed_url = urlparse.urlparse(proxy) + if parsed_url.scheme and parsed_url.hostname: + return True + else: + return False + + boolean_options = { + 'auto-key-retrieve', + 'check-cert', + 'honor-keyserver-url', + 'honor-pka-record', + 'keep-temp-files', + 'include-disabled', + 'include-revoked', + 'include-subkeys', + 'use-temp-files', + } + no_prefixed_options = set(['no{}'.format(opt) for opt in boolean_options]) + options_with_validators = { + 'ca-cert-file': _is_valid_file, + 'http-proxy': _is_valid_http_proxy, + 'max-cert-size': is_valid_integer, + 'timeout': _is_valid_integer, + } + valid_simple_options = (set(['debug', 'verbose']) | + boolean_options | + no_prefixed_options) + if ks_option in valid_simple_options: + return ks_option + opt, opt_arg = ks_option.split('=', 1) + if opt in options_with_validators: + arg_ok = options_with_validators[opt](opt_arg) + if arg_ok: + return ks_option + log.debug('Dropping invalid keyserver option: {}'.format(ks_option)) + return None + + def _check_preferences(prefs, pref_type=None): """Check cipher, digest, and compression preference settings. @@ -330,7 +397,13 @@ def _check_option(arg, value): continue elif flag in ['--keyserver-options']: print('found keyserver options: %s' % v) - checked += (v + " ") + keyserver_option = _check_keyserver_option(v) + if keyserver_option: + log.debug('Setting keyserver option: %s' % + keyserver_option) + checked += (keyserver_option + " ") + else: + log.debug('Dropping keyserver option: %s' % v) continue ## the rest are strings, filenames, etc, and should be From 6dbbb283ab32d95aadc43ee75c2881d511fd4002 Mon Sep 17 00:00:00 2001 From: Daniel Wilcox Date: Wed, 29 Nov 2017 23:03:49 +0000 Subject: [PATCH 3/4] Fix silly function naming error. --- gnupg/_parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnupg/_parsers.py b/gnupg/_parsers.py index 244cbce..f34d133 100644 --- a/gnupg/_parsers.py +++ b/gnupg/_parsers.py @@ -124,7 +124,7 @@ def _is_valid_http_proxy(option_value): options_with_validators = { 'ca-cert-file': _is_valid_file, 'http-proxy': _is_valid_http_proxy, - 'max-cert-size': is_valid_integer, + 'max-cert-size': _is_valid_integer, 'timeout': _is_valid_integer, } valid_simple_options = (set(['debug', 'verbose']) | From 2838ee0762062d816e8316102bdff45f6af18312 Mon Sep 17 00:00:00 2001 From: Daniel Wilcox Date: Tue, 23 Oct 2018 13:48:19 +0000 Subject: [PATCH 4/4] Update per PR comments for pytest fix + using internal util. --- gnupg/_parsers.py | 2 +- gnupg/gnupg.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gnupg/_parsers.py b/gnupg/_parsers.py index f34d133..6906882 100644 --- a/gnupg/_parsers.py +++ b/gnupg/_parsers.py @@ -90,7 +90,7 @@ def _check_keyserver_option(ks_option): """ def _is_valid_file(option_value): """Verify option value is a file.""" - return os.path.isfile(option_value) + return _util._is_file(option_value) def _is_valid_integer(option_value): """Verify option value is an integer.""" diff --git a/gnupg/gnupg.py b/gnupg/gnupg.py index f854921..715496b 100644 --- a/gnupg/gnupg.py +++ b/gnupg/gnupg.py @@ -376,8 +376,8 @@ def recv_keys(self, *keyids, **kwargs): >>> ssl_keyserver = 'hkps://hkps.pool.sks-keyservers.net' >>> ca_cert = '/home/user/hkps.pool.sks-keyservers.netCA.pem' >>> gpg.recv_keys('6F682D87', - keyserver=ssl_keyserver, - keyserver_certs=ca_cert) + >>> keyserver=ssl_keyserver, + >>> keyserver_certs=ca_cert) :param str keyids: Each ``keyids`` argument should be a string containing a keyid to request.