diff --git a/requests/adapters.py b/requests/adapters.py index 2475879c..373fe5d0 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -64,7 +64,9 @@ class BaseAdapter(object): data before giving up, as a float, or a :ref:`(connect timeout, read timeout) ` tuple. :type timeout: float or tuple - :param verify: (optional) Whether to verify SSL certificates. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use :param cert: (optional) Any user-provided SSL certificate to be trusted. :param proxies: (optional) The proxies dictionary to apply to the request. """ @@ -202,7 +204,9 @@ class HTTPAdapter(BaseAdapter): :param conn: The urllib3 connection object associated with the cert. :param url: The requested URL. - :param verify: Whether we should actually verify the certificate. + :param verify: Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use :param cert: The SSL certificate to verify. """ if url.lower().startswith('https') and verify: @@ -216,8 +220,9 @@ class HTTPAdapter(BaseAdapter): if not cert_loc: cert_loc = DEFAULT_CA_BUNDLE_PATH - if not cert_loc: - raise Exception("Could not find a suitable SSL CA certificate bundle.") + if not cert_loc or not os.path.exists(cert_loc): + raise IOError("Could not find a suitable TLS CA certificate bundle, " + "invalid path: {0}".format(cert_loc)) conn.cert_reqs = 'CERT_REQUIRED' @@ -236,6 +241,12 @@ class HTTPAdapter(BaseAdapter): conn.key_file = cert[1] else: conn.cert_file = cert + if conn.cert_file and not os.path.exists(conn.cert_file): + raise IOError("Could not find the TLS certificate file, " + "invalid path: {0}".format(conn.cert_file)) + if conn.key_file and not os.path.exists(conn.key_file): + raise IOError("Could not find the TLS key file, " + "invalid path: {0}".format(conn.key_file)) def build_response(self, req, resp): """Builds a :class:`Response ` object from a urllib3 @@ -381,7 +392,9 @@ class HTTPAdapter(BaseAdapter): data before giving up, as a float, or a :ref:`(connect timeout, read timeout) ` tuple. :type timeout: float or tuple - :param verify: (optional) Whether to verify SSL certificates. + :param verify: (optional) Either a boolean, in which case it controls whether + we verify the server's TLS certificate, or a string, in which case it + must be a path to a CA bundle to use :param cert: (optional) Any user-provided SSL certificate to be trusted. :param proxies: (optional) The proxies dictionary to apply to the request. :rtype: requests.Response diff --git a/requests/api.py b/requests/api.py index 856f0b3a..687ed447 100644 --- a/requests/api.py +++ b/requests/api.py @@ -36,7 +36,9 @@ def request(method, url, **kwargs): :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. :type allow_redirects: bool :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. - :param verify: (optional) whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. Defaults to ``True``. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. :param stream: (optional) if ``False``, the response content will be immediately downloaded. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. :return: :class:`Response ` object diff --git a/requests/sessions.py b/requests/sessions.py index bc67334a..06e65570 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -460,8 +460,9 @@ class Session(SessionRedirectMixin): hostname to the URL of the proxy. :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. - :param verify: (optional) whether the SSL cert will be verified. - A CA_BUNDLE path can also be provided. Defaults to ``True``. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. :rtype: requests.Response diff --git a/tests/test_requests.py b/tests/test_requests.py index 829ab3eb..ab1e6a9c 100755 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -769,6 +769,22 @@ class TestRequests: def test_pyopenssl_redirect(self, httpbin_secure, httpbin_ca_bundle): requests.get(httpbin_secure('status', '301'), verify=httpbin_ca_bundle) + def test_invalid_ca_certificate_path(self, httpbin_secure): + INVALID_PATH = '/garbage' + with pytest.raises(IOError) as e: + requests.get(httpbin_secure(), verify=INVALID_PATH) + assert str(e.value) == 'Could not find a suitable TLS CA certificate bundle, invalid path: {0}'.format(INVALID_PATH) + + def test_invalid_ssl_certificate_files(self, httpbin_secure): + INVALID_PATH = '/garbage' + with pytest.raises(IOError) as e: + requests.get(httpbin_secure(), cert=INVALID_PATH) + assert str(e.value) == 'Could not find the TLS certificate file, invalid path: {0}'.format(INVALID_PATH) + + with pytest.raises(IOError) as e: + requests.get(httpbin_secure(), cert=('.', INVALID_PATH)) + assert str(e.value) == 'Could not find the TLS key file, invalid path: {0}'.format(INVALID_PATH) + def test_https_warnings(self, httpbin_secure, httpbin_ca_bundle): """warnings are emitted with requests.get""" if HAS_MODERN_SSL or HAS_PYOPENSSL: