From 99210995463497cf4ee9ec1bee79c5f2bff4e3f8 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 16 Apr 2012 12:31:47 -0700 Subject: [PATCH 1/4] Try to use the OS's CA certificate bundle for SSL verification --- AUTHORS.rst | 1 + requests/models.py | 10 +++++++--- requests/utils.py | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) 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..28beed3c 100644 --- a/requests/models.py +++ b/requests/models.py @@ -27,7 +27,7 @@ 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, CA_BUNDLE_PATH) from .compat import ( urlparse, urlunparse, urljoin, urlsplit, urlencode, str, bytes, SimpleCookie, is_py2) @@ -524,7 +524,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,10 +537,14 @@ 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 operating system's bundle, if it can be found. + if not cert_loc: + cert_loc = CA_BUNDLE_PATH + # Use the awesome certifi list. if not cert_loc: cert_loc = __import__('certifi').where() diff --git a/requests/utils.py b/requests/utils.py index ab6672f9..0ebcf60b 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -24,6 +24,21 @@ from .compat import basestring, bytes, str NETRC_FILES = ('.netrc', '_netrc') +# common paths for the OS's CA certificate bundle +POSSIBLE_CA_BUNDLE_PATHS = [ + # Red Hat, CentOS, Fedora and friends: + '/etc/pki/tls/certs/ca-bundle.crt', + # Ubuntu and friends: + '/etc/ssl/certs/ca-certificates.crt', +] + +def get_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 + +CA_BUNDLE_PATH = get_ca_bundle_path() def dict_to_sequence(d): """Returns an internal sequence dictionary update.""" From 1360e77cb2c5c7956b9c2b6e510040fa73c6d7ee Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 16 Apr 2012 15:42:14 -0700 Subject: [PATCH 2/4] Add a smoke test for https functionality --- tests/test_requests_https.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100755 tests/test_requests_https.py 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() From e1528ce3be5a0798d509ae774ac7c3667b17ef29 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 19 Apr 2012 14:27:26 -0700 Subject: [PATCH 3/4] add the FreeBSD certificate bundle path --- requests/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requests/utils.py b/requests/utils.py index 0ebcf60b..0dd396f3 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -30,6 +30,8 @@ POSSIBLE_CA_BUNDLE_PATHS = [ '/etc/pki/tls/certs/ca-bundle.crt', # Ubuntu and friends: '/etc/ssl/certs/ca-certificates.crt', + # FreeBSD (provided by the ca_root_nss package): + '/usr/local/share/certs/ca-root-nss.crt', ] def get_ca_bundle_path(): From b4eb8663afce7d6c5e06df8625c5307a6126ecb5 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 22 Apr 2012 18:43:59 -0700 Subject: [PATCH 4/4] prefer certifi's bundle to the OS bundle --- requests/models.py | 9 ++++----- requests/utils.py | 18 ++++++++++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/requests/models.py b/requests/models.py index 28beed3c..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, CA_BUNDLE_PATH) + 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) @@ -541,13 +542,11 @@ class Request(object): if not cert_loc and self.config.get('trust_env'): cert_loc = os.environ.get('CURL_CA_BUNDLE') - # Use the operating system's bundle, if it can be found. if not cert_loc: - cert_loc = CA_BUNDLE_PATH + cert_loc = DEFAULT_CA_BUNDLE_PATH - # Use the awesome certifi list. if not cert_loc: - cert_loc = __import__('certifi').where() + 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 0dd396f3..ed9ffb1c 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -21,26 +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: + # Red Hat, CentOS, Fedora and friends (provided by the ca-certificates package): '/etc/pki/tls/certs/ca-bundle.crt', - # Ubuntu and friends: + # 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_ca_bundle_path(): +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 -CA_BUNDLE_PATH = get_ca_bundle_path() +# 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."""