Repository: urllib3/urllib3

Issue:
Default certificates are never loaded since version 1.25
Opening an HTTPS URL with ``urllib3>=1.25`` results in ``SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)')))``, example to reproduce is following:
```
import urllib3

http = urllib3.PoolManager()
http.request('GET', 'https://google.com/')
```

The reason is that default system certificates are never loaded because ``HTTPSConnection`` or ``VerificedHTTPSConnection`` creates ssl context without loading certificates and passes it to ``ssl_wrap_socket`` where default certificates are not loaded too because passed ``ssl_context`` is not ``None``. Related lines of code:
``src/urllib3/connection.py``:
```
        if self.ssl_context is None:
            self.ssl_context = create_urllib3_context(
                ssl_version=resolve_ssl_version(None),
                cert_reqs=resolve_cert_reqs(None),
            )

        self.sock = ssl_wrap_socket(
            sock=conn,
            keyfile=self.key_file,
            certfile=self.cert_file,
            key_password=self.key_password,
            ssl_context=self.ssl_context,
            server_hostname=self.server_hostname
)
```
``src/urllib3/util/ssl_.py``:
```
    elif ssl_context is None and hasattr(context, 'load_default_certs'):
        # try to load OS default certs; works well on Windows (require Python3.4+)
        context.load_default_certs()
```


Discussion:
Yep, this looks incorrect. Unfortunately I can't currently think of a good way to handle this and maintain all our interfaces+contracts without calling `load_default_certs()` inside of `HTTPSConnection`. cc: @urllib3/maintainers 
Basically this boils down to `create_urllib3_context()` not knowing about `ca_cert` or `ca_cert_dir` and so it can't make the call whether to load the default system certs or not. The loading of certs is all done in `ssl_wrap_socket()`. (imo `create_urllib3_context()` should probably be the place to load certs)

Resolving patch:
diff --git a/src/urllib3/connection.py b/src/urllib3/connection.py
--- a/src/urllib3/connection.py
+++ b/src/urllib3/connection.py
@@ -249,12 +249,23 @@ def connect(self):
         conn = self._new_conn()
         self._prepare_conn(conn)
 
+        # Wrap socket using verification with the root certs in
+        # trusted_root_certs
+        default_ssl_context = False
         if self.ssl_context is None:
+            default_ssl_context = True
             self.ssl_context = create_urllib3_context(
-                ssl_version=resolve_ssl_version(None),
-                cert_reqs=resolve_cert_reqs(None),
+                ssl_version=resolve_ssl_version(self.ssl_version),
+                cert_reqs=resolve_cert_reqs(self.cert_reqs),
             )
 
+        # Try to load OS default certs if none are given.
+        # Works well on Windows (requires Python3.4+)
+        context = self.ssl_context
+        if (not self.ca_certs and not self.ca_cert_dir and default_ssl_context
+                and hasattr(context, 'load_default_certs')):
+            context.load_default_certs()
+
         self.sock = ssl_wrap_socket(
             sock=conn,
             keyfile=self.key_file,
@@ -331,7 +342,9 @@ def connect(self):
 
         # Wrap socket using verification with the root certs in
         # trusted_root_certs
+        default_ssl_context = False
         if self.ssl_context is None:
+            default_ssl_context = True
             self.ssl_context = create_urllib3_context(
                 ssl_version=resolve_ssl_version(self.ssl_version),
                 cert_reqs=resolve_cert_reqs(self.cert_reqs),
@@ -339,6 +352,13 @@ def connect(self):
 
         context = self.ssl_context
         context.verify_mode = resolve_cert_reqs(self.cert_reqs)
+
+        # Try to load OS default certs if none are given.
+        # Works well on Windows (requires Python3.4+)
+        if (not self.ca_certs and not self.ca_cert_dir and default_ssl_context
+                and hasattr(context, 'load_default_certs')):
+            context.load_default_certs()
+
         self.sock = ssl_wrap_socket(
             sock=conn,
             keyfile=self.key_file,


Test patch:
diff --git a/test/with_dummyserver/test_socketlevel.py b/test/with_dummyserver/test_socketlevel.py
--- a/test/with_dummyserver/test_socketlevel.py
+++ b/test/with_dummyserver/test_socketlevel.py
@@ -35,6 +35,7 @@ class MimeToolMessage(object):
 import select
 import socket
 import ssl
+import mock
 
 import pytest
 
@@ -1184,6 +1185,83 @@ def socket_handler(listener):
         response = pool.urlopen('GET', '/', retries=1)
         self.assertEqual(response.data, b'Success')
 
+    def test_ssl_load_default_certs_when_empty(self):
+        def socket_handler(listener):
+            sock = listener.accept()[0]
+            ssl_sock = ssl.wrap_socket(sock,
+                                       server_side=True,
+                                       keyfile=DEFAULT_CERTS['keyfile'],
+                                       certfile=DEFAULT_CERTS['certfile'],
+                                       ca_certs=DEFAULT_CA)
+
+            buf = b''
+            while not buf.endswith(b'\r\n\r\n'):
+                buf += ssl_sock.recv(65536)
+
+            ssl_sock.send(b'HTTP/1.1 200 OK\r\n'
+                          b'Content-Type: text/plain\r\n'
+                          b'Content-Length: 5\r\n\r\n'
+                          b'Hello')
+
+            ssl_sock.close()
+            sock.close()
+
+        context = mock.create_autospec(ssl_.SSLContext)
+        context.load_default_certs = mock.Mock()
+        context.options = 0
+
+        with mock.patch("urllib3.util.ssl_.SSLContext", lambda *_, **__: context):
+
+            self._start_server(socket_handler)
+            pool = HTTPSConnectionPool(self.host, self.port)
+            self.addCleanup(pool.close)
+
+            with self.assertRaises(MaxRetryError):
+                pool.request("GET", "/", timeout=0.01)
+
+            context.load_default_certs.assert_called_with()
+
+    def test_ssl_dont_load_default_certs_when_given(self):
+        def socket_handler(listener):
+            sock = listener.accept()[0]
+            ssl_sock = ssl.wrap_socket(sock,
+                                       server_side=True,
+                                       keyfile=DEFAULT_CERTS['keyfile'],
+                                       certfile=DEFAULT_CERTS['certfile'],
+                                       ca_certs=DEFAULT_CA)
+
+            buf = b''
+            while not buf.endswith(b'\r\n\r\n'):
+                buf += ssl_sock.recv(65536)
+
+            ssl_sock.send(b'HTTP/1.1 200 OK\r\n'
+                          b'Content-Type: text/plain\r\n'
+                          b'Content-Length: 5\r\n\r\n'
+                          b'Hello')
+
+            ssl_sock.close()
+            sock.close()
+
+        context = mock.create_autospec(ssl_.SSLContext)
+        context.load_default_certs = mock.Mock()
+        context.options = 0
+
+        with mock.patch("urllib3.util.ssl_.SSLContext", lambda *_, **__: context):
+            for kwargs in [{"ca_certs": "/a"},
+                           {"ca_cert_dir": "/a"},
+                           {"ca_certs": "a", "ca_cert_dir": "a"},
+                           {"ssl_context": context}]:
+
+                self._start_server(socket_handler)
+
+                pool = HTTPSConnectionPool(self.host, self.port, **kwargs)
+                self.addCleanup(pool.close)
+
+                with self.assertRaises(MaxRetryError):
+                    pool.request("GET", "/", timeout=0.01)
+
+                context.load_default_certs.assert_not_called()
+
 
 class TestErrorWrapping(SocketDummyServerTestCase):