Skip to content

Requests - SSL Certification

Requests - SSL/TLS Certificate Verification

Section titled “Requests - SSL/TLS Certificate Verification”

Modern web communication relies heavily on HTTPS (HTTP Secure) to protect data in transit. This security is provided by SSL/TLS (Secure Sockets Layer/Transport Layer Security) certificates. When you use the Requests library to interact with HTTPS URLs, it plays a crucial role in ensuring this security by verifying the server’s SSL certificate by default.

This verification process checks two main things: 1) If the certificate was issued by a trusted Certificate Authority (CA). 2) If the hostname in the URL matches the hostname in the certificate. This helps prevent ‘man-in-the-middle’ attacks.

By default, Requests will attempt to verify the SSL certificate for any HTTPS request. It uses a set of trusted root CAs that are bundled with the certifi package, which is often installed as a dependency of Requests.

If the certificate is valid and trusted, the request proceeds smoothly:

import requests
# Example: Fetching data from a secure API endpoint
URL = 'https://jsonplaceholder.typicode.com/users/1'
try:
response = requests.get(URL)
response.raise_for_status() # Raise an exception for bad status codes (4XX or 5XX)
print('Successfully fetched data:')
print(response.json()) # Assuming the response is JSON
except requests.exceptions.SSLError as e:
print(f'SSL Error occurred while accessing {URL}: {e}')
except requests.exceptions.RequestException as e:
print(f'Request failed for {URL}: {e}')
Successfully fetched data:
{
'id': 1,
'name': 'Leanne Graham',
'username': 'Bret',
'email': 'Sincere@april.biz',
'address': { # ... other address details ...
},
'phone': '1-770-736-8031 x56442',
'website': 'hildegard.org',
'company': { # ... other company details ...
}
# ... (rest of the JSON data, truncated for brevity)
}

If requests cannot verify the certificate (e.g., it’s self-signed and not in the trust store, expired, or there’s a hostname mismatch), it will raise an requests.exceptions.SSLError.

Disabling SSL Verification (Use with Extreme Caution)

Section titled “Disabling SSL Verification (Use with Extreme Caution)”

In some specific scenarios, like testing against a development server with a self-signed certificate, you might need to disable SSL verification. You can do this by passing verify=False to the request method.

Warning: Disabling SSL verification makes your connection insecure and exposes your application to security risks, including man-in-the-middle attacks. This should only be done in controlled, trusted environments and never in production for external services.

import requests
import warnings
# It's generally better to import the specific warning from urllib3 directly
from urllib3.exceptions import InsecureRequestWarning
# For demonstration, let's try a site known for having a self-signed certificate
URL_SELF_SIGNED = 'https://self-signed.badssl.com/'
# Suppress only the InsecureRequestWarning for this example.
# In a real application, address the SSL issue or accept the risk explicitly.
warnings.simplefilter('ignore', InsecureRequestWarning)
try:
print(f'Attempting request to {URL_SELF_SIGNED} with verify=False')
# This request would fail with SSLError if verify=True (default)
response = requests.get(URL_SELF_SIGNED, verify=False, timeout=5)
print(f'Request to {URL_SELF_SIGNED} successful with verify=False (Status: {response.status_code})')
print('Note: An InsecureRequestWarning was suppressed for this demonstration.')
# If you need to see the warning, comment out the warnings.simplefilter line above.
except requests.exceptions.SSLError as e:
print(f'SSL Error (this should not happen with verify=False): {e}')
except requests.exceptions.RequestException as e:
print(f'Request failed for {URL_SELF_SIGNED}: {e}')
# Example of what happens if you try without verify=False (and without handling the warning)
# print('\nAttempting request with default verification (expected to fail for self-signed.badssl.com):')
# try:
# response_fail = requests.get(URL_SELF_SIGNED, timeout=5)
# except requests.exceptions.SSLError as e:
# print(f'As expected, SSL Error: {e}')

When verify=False is used, Requests, via urllib3, issues an InsecureRequestWarning. It is strongly advised to address the underlying SSL certificate issue rather than routinely disabling verification or suppressing this warning in production environments.

Expected Output (with warning suppressed):

Section titled “Expected Output (with warning suppressed):”
Attempting request to https://self-signed.badssl.com/ with verify=False
Request to https://self-signed.badssl.com/ successful with verify=False (Status: 200)
Note: An InsecureRequestWarning was suppressed for this demonstration.

Sometimes, you need to communicate with a server whose certificate is signed by a private or internal Certificate Authority (CA) not included in public trust stores (e.g., in corporate environments). In such cases, you can tell Requests to trust this CA by providing a path to its certificate bundle file (a file containing one or more CA certificates in PEM format).

You pass the path to this CA bundle file to the verify parameter.

import requests
# This is a conceptual example. You would replace the URL and path with your actual values.
# url_internal_service = 'https://internal.example.com/api/data'
# path_to_custom_ca_bundle = '/path/to/your/company-ca-bundle.pem'
# print(f"Conceptual request to {url_internal_service} using custom CA:")
# print(f"response = requests.get(url_internal_service, verify='{path_to_custom_ca_bundle}')")
# To make this runnable, we'll just show the concept:
print('Conceptual example for using a custom CA bundle:')
print("response = requests.get('https://your-internal-service.com/data', verify='/path/to/custom_ca.pem')")
# Example using httpbin.org, which doesn't require a custom CA, but shows syntax:
# If you had a local CA cert for 'httpbin.org' (which you wouldn't normally):
# try:
# response = requests.get('https://httpbin.org/get', verify='/path/to/a/valid/ca_for_httpbin.pem')
# print('Request to httpbin.org with (hypothetical) custom CA successful.')
# except requests.exceptions.SSLError as e:
# print(f'SSL Error with custom CA for httpbin.org (as expected if path is invalid/not needed): {e}')

If the server’s certificate is successfully validated against the provided CA bundle, the request will proceed. Otherwise, an SSLError will typically be raised, indicating that the certificate could not be verified with the custom CA(s).

For more advanced scenarios, such as using client-side SSL certificates for mutual TLS authentication, Requests allows you to pass a path to your client certificate and private key using the cert parameter (e.g., cert=('/path/to/client.crt', '/path/to/client.key')). Consult the official Requests documentation for more details.

If you encounter SSL errors (requests.exceptions.SSLError):

  • Outdated CA Bundle: Ensure your certifi package (which Requests often uses for CAs) is up to date: pip install --upgrade certifi requests.
  • Self-Signed Certificate: If it’s a server you control (e.g., development), either install its CA into your trust store, provide its CA to verify, or (least secure, for trusted local dev only) use verify=False.
  • Hostname Mismatch: The hostname in the URL must match the Common Name (CN) or a Subject Alternative Name (SAN) in the server’s certificate.
  • Intermediate Certificate Missing: The server might not be sending the complete certificate chain. This is a server configuration issue.
  • System Clock: Ensure your system’s clock is accurately synchronized. SSL certificates have validity periods.
  • Proxies/Firewalls: Corporate proxies or firewalls might intercept and re-sign SSL traffic with their own certificates. You might need to trust the proxy’s CA certificate.

You can find the path to the CA bundle certifi uses by running: python -m certifi in your terminal. This can be helpful for diagnostics.

Understanding and correctly configuring SSL/TLS verification is crucial for building secure applications. Always prioritize security.