Skip to content
Permalink
Browse files Browse the repository at this point in the history
Enable certificate verification in PKIConnection
To PKIConnection's initialization handler, we introduce a new argument,
cert_paths, which takes a string or iterable; each unit of which is
treated as a capath or cafile depending on whether or not it is a
directory. See ssl.SSLContext.load_verify_locations for more
information. This enables both PKI and IPA to specify independent CA
file locations at the same time and have fallback if this does not work.

Because some users might've already loaded the CA certificate into the
system-wide CA certificate store (if they're running Dogtag in
production), we also inclue the global trust store.

Resolves: rh-bz#1426572

Signed-off-by: Alexander Scheel <ascheel@redhat.com>
  • Loading branch information
cipherboy committed Jun 18, 2020
1 parent e39d597 commit 50c23ec
Showing 1 changed file with 58 additions and 3 deletions.
61 changes: 58 additions & 3 deletions base/common/python/pki/client.py
Expand Up @@ -25,11 +25,13 @@
import functools
import inspect
import logging
import os
import ssl
import warnings

import requests
from requests import adapters
from requests.adapters import DEFAULT_POOLBLOCK, DEFAULT_POOLSIZE, DEFAULT_RETRIES
try:
from requests.packages.urllib3.exceptions import InsecureRequestWarning
except ImportError:
Expand All @@ -55,8 +57,41 @@ def wrapper(self, *args, **kwargs):


class SSLContextAdapter(adapters.HTTPAdapter):
"""Custom SSLContext Adapter for requests
"""
Custom SSLContext Adapter for requests
"""

def __init__(self, pool_connections=DEFAULT_POOLSIZE,
pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES,
pool_block=DEFAULT_POOLBLOCK, verify=True,
cert_paths=None):
self.verify = verify
self.cafiles = []
self.capaths = []

cert_paths = cert_paths or []

if isinstance(cert_paths, str):
cert_paths = [cert_paths]

for path in cert_paths:
path = path and os.path.expanduser(path)

if os.path.isdir(path):
self.capaths.append(path)
elif os.path.exists(path):
self.cafiles.append(path)
else:
logger.warning("cert_path missing; not used for validation: %s",
path)

# adapters.HTTPAdapter.__init__ calls our init_poolmanager, which needs
# our cafiles/capaths variables we set up above.
super(SSLContextAdapter, self).__init__(pool_connections=pool_connections,
pool_maxsize=pool_maxsize,
max_retries=max_retries,
pool_block=pool_block)

def init_poolmanager(self, connections, maxsize,
block=adapters.DEFAULT_POOLBLOCK, **pool_kwargs):
context = ssl.SSLContext(
Expand All @@ -67,6 +102,23 @@ def init_poolmanager(self, connections, maxsize,
if getattr(context, "post_handshake_auth", None) is not None:
context.post_handshake_auth = True

# Load from the system trust store when possible; per documentation
# this call could silently fail and refuse to configure any
# certificates. In this instance, the user should provide a
# certificate manually.
context.set_default_verify_paths()

# Load any specific certificate paths that have been specified during
# adapter initialization.
for cafile in self.cafiles:
context.load_verify_locations(cafile=cafile)
for capath in self.capaths:
context.load_verify_locations(capath=capath)

if self.verify:
# Enable certificate verification
context.verify_mode = ssl.VerifyMode.CERT_REQUIRED # pylint: disable=no-member

pool_kwargs['ssl_context'] = context
return super().init_poolmanager(
connections, maxsize, block, **pool_kwargs
Expand All @@ -81,7 +133,7 @@ class PKIConnection:

def __init__(self, protocol='http', hostname='localhost', port='8080',
subsystem=None, accept='application/json',
trust_env=None, verify=False):
trust_env=None, verify=True, cert_paths=None):
"""
Set the parameters for a python-requests based connection to a
Dogtag subsystem.
Expand All @@ -103,6 +155,9 @@ def __init__(self, protocol='http', hostname='localhost', port='8080',
:param verify: verify TLS/SSL connections and configure CA certs
(default: no)
:type verify: None, bool, str
:param cert_paths: paths to CA certificates / directories in OpenSSL
format. (default: None)
:type cert_paths: None, str, list
:return: PKIConnection object.
"""

Expand All @@ -123,7 +178,7 @@ def __init__(self, protocol='http', hostname='localhost', port='8080',
self.serverURI = self.rootURI

self.session = requests.Session()
self.session.mount("https://", SSLContextAdapter())
self.session.mount("https://", SSLContextAdapter(verify=verify, cert_paths=cert_paths))
self.session.trust_env = trust_env
self.session.verify = verify

Expand Down

0 comments on commit 50c23ec

Please sign in to comment.