diff --git a/AUTHORS.rst b/AUTHORS.rst index 7cc76d6b..50e7e1b5 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -95,3 +95,4 @@ Patches and Suggestions - Michael Kelly - Michael Newman - Jonty Wareing +- Shivaram Lingamneni diff --git a/requests/models.py b/requests/models.py index 26bc518d..60f58d2a 100644 --- a/requests/models.py +++ b/requests/models.py @@ -27,7 +27,8 @@ from .exceptions import ( URLRequired, SSLError, MissingSchema, InvalidSchema, InvalidURL) from .utils import ( get_encoding_from_headers, stream_untransfer, guess_filename, requote_uri, - dict_from_string, stream_decode_response_unicode, get_netrc_auth) + dict_from_string, stream_decode_response_unicode, get_netrc_auth, + DEFAULT_CA_BUNDLE_PATH) from .compat import ( urlparse, urlunparse, urljoin, urlsplit, urlencode, str, bytes, SimpleCookie, is_py2) @@ -524,7 +525,7 @@ class Request(object): conn = connectionpool.connection_from_url(url) except LocationParseError as e: raise InvalidURL(e) - + if url.startswith('https') and self.verify: cert_loc = None @@ -537,13 +538,15 @@ class Request(object): if not cert_loc and self.config.get('trust_env'): cert_loc = os.environ.get('REQUESTS_CA_BUNDLE') - # Curl compatiblity. + # Curl compatibility. if not cert_loc and self.config.get('trust_env'): cert_loc = os.environ.get('CURL_CA_BUNDLE') - # Use the awesome certifi list. if not cert_loc: - cert_loc = __import__('certifi').where() + cert_loc = DEFAULT_CA_BUNDLE_PATH + + if not cert_loc: + raise Exception("Could not find a suitable SSL CA certificate bundle.") conn.cert_reqs = 'CERT_REQUIRED' conn.ca_certs = cert_loc diff --git a/requests/utils.py b/requests/utils.py index ab6672f9..ed9ffb1c 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -21,9 +21,36 @@ from .compat import parse_http_list as _parse_list_header from .compat import quote, cookielib, SimpleCookie, is_py2, urlparse from .compat import basestring, bytes, str +CERTIFI_BUNDLE_PATH = None +try: + # see if requests's own CA certificate bundle is installed + import certifi + CERTIFI_BUNDLE_PATH = certifi.where() +except ImportError: + pass NETRC_FILES = ('.netrc', '_netrc') +# common paths for the OS's CA certificate bundle +POSSIBLE_CA_BUNDLE_PATHS = [ + # Red Hat, CentOS, Fedora and friends (provided by the ca-certificates package): + '/etc/pki/tls/certs/ca-bundle.crt', + # Ubuntu, Debian, and friends (provided by the ca-certificates package): + '/etc/ssl/certs/ca-certificates.crt', + # FreeBSD (provided by the ca_root_nss package): + '/usr/local/share/certs/ca-root-nss.crt', +] + +def get_os_ca_bundle_path(): + """Try to pick an available CA certificate bundle provided by the OS.""" + for path in POSSIBLE_CA_BUNDLE_PATHS: + if os.path.exists(path): + return path + return None + +# if certifi is installed, use its CA bundle; +# otherwise, try and use the OS bundle +DEFAULT_CA_BUNDLE_PATH = CERTIFI_BUNDLE_PATH or get_os_ca_bundle_path() def dict_to_sequence(d): """Returns an internal sequence dictionary update.""" diff --git a/tests/test_requests_https.py b/tests/test_requests_https.py new file mode 100755 index 00000000..c6ea8f35 --- /dev/null +++ b/tests/test_requests_https.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys, os +import json +import unittest + +# Path hack. +sys.path.insert(0, os.path.abspath('..')) +import requests + +class HTTPSTest(unittest.TestCase): + """Smoke test for https functionality.""" + + smoke_url = "https://github.com" + + def perform_smoke_test(self, verify=False): + result = requests.get(self.smoke_url, verify=verify) + self.assertEqual(result.status_code, 200) + + def test_smoke(self): + """Smoke test without verification.""" + self.perform_smoke_test(verify=False) + + def test_smoke_verified(self): + """Smoke test with SSL verification.""" + self.perform_smoke_test(verify=True) + + +if __name__ == '__main__': + unittest.main()