diff --git a/pretty_bad_protocol/_meta.py b/pretty_bad_protocol/_meta.py index ac6ea40..1aedb04 100644 --- a/pretty_bad_protocol/_meta.py +++ b/pretty_bad_protocol/_meta.py @@ -792,20 +792,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) diff --git a/pretty_bad_protocol/_parsers.py b/pretty_bad_protocol/_parsers.py index 6af3d6d..7f8666d 100644 --- a/pretty_bad_protocol/_parsers.py +++ b/pretty_bad_protocol/_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 _util._is_file(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. @@ -333,6 +400,16 @@ 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) + 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 ## shell escaped: @@ -514,6 +591,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/pretty_bad_protocol/gnupg.py b/pretty_bad_protocol/gnupg.py index 7efabcf..295ab77 100644 --- a/pretty_bad_protocol/gnupg.py +++ b/pretty_bad_protocol/gnupg.py @@ -373,10 +373,18 @@ def recv_keys(self, *keyids, **kwargs): >>> 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])