CVE-2020-15720 (published 2020-07-14T14:15Z)

In Dogtag PKI through 10.8.3, the pki.client.PKIConnection class did not enable python-requests certificate validation. Since the verify parameter was hard-coded in all request functions, it was not possible to override the setting. As a result, tools making use of this class, such as the pki-server command, may have been vulnerable to Person-in-the-Middle attacks in certain non-localhost use cases. This is fixed in 10.9.0-b1.

Severity: MEDIUM
CVSS v2 base score: 4.0 (AV:N/AC:H/Au:N/C:P/I:P/A:N)
CVSS v3 base score: 6.8, MEDIUM (CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N)

Weakness information:
cwe_id: CWE-295
cwe_name: Improper Certificate Validation
description: The product does not validate, or incorrectly validates, a certificate.
extended_description: When a certificate is invalid or malicious, it might allow an attacker to spoof a trusted entity by interfering in the communication path between the host and client. The product might connect to a malicious host while believing it is a trusted host, or the product might be deceived into accepting spoofed data that appears to originate from a trusted host.
         

Fix information:
repo_url: https://github.com/dogtagpki/pki
commit_details: repo_url: https://github.com/dogtagpki/pki
author: Alexander Scheel
author_date: 2020-06-15 10:19:40-04:00
msg: 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>
repository: repo_url: https://github.com/dogtagpki/pki
repo_name: dogtagpki/pki
description: The Dogtag Certificate System is an enterprise-class Certificate Authority (CA) which supports all aspects of certificate lifecycle management, including key archival, OCSP and smartcard management.
repo_language: Java
owner: dogtagpki
file_changes: filename: client.py
change_type: ModificationType.MODIFY
diff: @@ -25,11 +25,13 @@ from __future__ import print_function
 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:
@@ -55,8 +57,41 @@ def catch_insecure_warning(func):
 
 
 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(
@@ -67,6 +102,23 @@ class SSLContextAdapter(adapters.HTTPAdapter):
         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
@@ -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.
@@ -103,6 +155,9 @@ class PKIConnection:
         :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.
         """
 
@@ -123,7 +178,7 @@ class PKIConnection:
             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
 

programming_language: Python