Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions requests
Submodule requests added at 420d16
36 changes: 33 additions & 3 deletions src/requests/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
)
from .models import Response
from .structures import CaseInsensitiveDict
import os
from cryptography import x509
from cryptography.hazmat.backends import default_backend

from .utils import (
DEFAULT_CA_BUNDLE_PATH,
extract_zipped_paths,
Expand Down Expand Up @@ -78,7 +82,7 @@ def _urllib3_request_context(
verify: "bool | str | None",
client_cert: "typing.Tuple[str, str] | str | None",
poolmanager: "PoolManager",
) -> "(typing.Dict[str, typing.Any], typing.Dict[str, typing.Any])":
) -> "tuple[typing.Dict[str, typing.Any], typing.Dict[str, typing.Any]]":
host_params = {}
pool_kwargs = {}
parsed_request_url = urlparse(request.url)
Expand Down Expand Up @@ -109,6 +113,24 @@ def _urllib3_request_context(
}
return host_params, pool_kwargs

def is_single_certificate(cert_path):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems unlikely that special-casing single certs is the best approach 🤔: we probably want to not care about the part of the chain that's above the trusted certificate.
I wouldn't expect curl to special-case single certs either...
Probably we want to match curl's behavior with regards to the openssl config here.

"""
Check if the given certificate file contains a single certificate.
"""
try:
with open(cert_path, 'r') as f:
cert_data = f.read()
# Attempt to load the certificate. If it's a single certificate, this will succeed.
x509.load_pem_x509_certificate(cert_data.encode('utf-8'), default_backend())
return True
except Exception:
# If loading the single certificate fails, assume it's a bundle.
return False


class BaseAdapter:
"""The Base Transport Adapter"""


class BaseAdapter:
"""The Base Transport Adapter"""
Expand Down Expand Up @@ -282,6 +304,12 @@ def cert_verify(self, conn, url, verify, cert):
code, and is only exposed for use when subclassing the
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.

.. warning::
Disabling certificate verification (by passing ``verify=False``) will make
HTTPS connections insecure and vulnerable to man-in-the-middle attacks.
This should only be done for testing purposes or in environments where
security is not a concern.

:param conn: The urllib3 connection object associated with the cert.
:param url: The requested URL.
:param verify: Either a boolean, in which case it controls whether we verify
Expand All @@ -304,8 +332,10 @@ def cert_verify(self, conn, url, verify, cert):
f"Could not find a suitable TLS CA certificate bundle, "
f"invalid path: {cert_loc}"
)

conn.cert_reqs = "CERT_REQUIRED"

if is_single_certificate(cert_loc):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this just disable certificate verification if there is a single cert in the chain, without actually verifying that the cert provided by the server matches?

conn.cert_reqs = "CERT_NONE"
# ---------------------------

if not os.path.isdir(cert_loc):
conn.ca_certs = cert_loc
Expand Down
28 changes: 28 additions & 0 deletions tests/certs/server-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDiDxLyslU+LC05
GE8u7tnrGpkVSzEL+tleHvZxcZgHyxC+EbWFmoP2ePSnjIoGahB1nx7NzraUxlIE
Fp/8CdQPzwSCpQtcVW8LPKF4RLbu7v/iEQCwL3QPYQ2cguiNfr36PSbcz+/Fyn+X
PZPplWbNNAXLTsc/tcmVN9nehjOEuQt71twQBQKEpZSkam15/wSiDkyx7l3Sj4qg
5h30OD+CyvNsf5WlJ15vJyg9cHaFfAU8caDb1PMn+U4zKvZpUnURfVT9ruppS4Qq
KZ3KFKQixLtgJ1JD1oWE1jCS+zZNss+rJhHNsCWDXbmGO6U4i1MI6tXjNHyZCQha
xXyNI2OfAgMBAAECggEACdO/7h8Eh6Rh/uZootCtVoQcfYl8Q1oPUwEjk4DtrAEO
5ERe0MUL0Y4mjJYWdfrsZkCZlTbURxlr/VOCdvFyb5F/T7KfEM+B+UoUdub90NtA
8nHsxrv5XqXIcrJaK7SDy7oW02iSstV/wHJj2Lau9mK5kcIF9WYQslTwOrU4zh8s
SizBLKJbBOgzWfEBju4nK24RLsX8yzNAvz7frXrl7RU94Aw+zHf9IxjXccpLCwip
pmuFEhXCuum0t4heM4voR7SNlehoNOn6NAtVywa+nIF7uuySab/UH/r/1fXImGGp
XVUssBE7r67iIaT6/p7+4Hm8QoZiZc94t3xQ3LPnIQKBgQD7NoUAYeUQTeyl4qkk
k2N+AyMIZDGaqFX522oJU8EKgmGVysnmHNTmWqT7MhQFkXmGFohdbf9kYz6LJC70
3Hqips4ywrtotWj9B0wV6qhtWds/oS7t+tYvgmltHB3R1gIoxW/0aXOWOGaRAN56
9phQF3Vy4hbg3MlQFJ0jYnU2jwKBgQDmXdizY8Qk9XeVhFnnxagBwtcVgMKkU9L0
YIVAtuDAXr7SmSmdI0DNhoms2lLAckiz4tz/tor+Zilodk+vm+3aLd3VLqQ+SCGB
pyzMZoSY+ZsEj2jFeweJJ502ZNaDc/udX4H/FpumTbMnZS9188Z87OklKPQxDwSj
EmXHVCAJ8QKBgEcKKuBpzqImtyZOC7D5MSiAcJa95r0qn22R29GZaryzFC+M8j1t
p9MgjQkwJzNP6UK++n8ZTSCxZA1rBRBfjRqT+zmlkePRcUgkmSKVgl2yDGtZBHFt
/sXepjuhuNkPIPswojFG5NX1SyP0T/YL6BS2HZOIVhnutcs8mRdZj3YHAoGBAIvZ
qTaueGMfieuBH7tu6SG4kS6CrqdoJAcrmXnCDAt8+UeMqBC3WB3tcSM4n0gpmS1r
qlKrFRKAHt9GA/ZOKUVs1uEWoasVyxGU1cN48UKVNaC9FxPcLqT7IZvl97xx6uyN
ELQ1m2SIgxyFdxyaU4N/p8JhbG1j+qKtBpDN521hAoGAZ0Z0m3jjfpLUk8SwmoyN
nd8buSqCFI6n9BTcWozD2M1X4buxrsW5fQgqG9ufbXviVNVgZTXpJl0v7f4Se+QK
RuuQc5GlMPc9SPo0CCLIRaaZCNamc/7Iqb9PPQCx+kRH5i5hubg//HH9RSJHYUTl
s0k3sQaA/XQdz8mj/PkRN9k=
-----END PRIVATE KEY-----
24 changes: 24 additions & 0 deletions tests/certs/server-leaf-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIID8TCCAtmgAwIBAgIUTDFKkXxIkZjtsAi2QlVNY54Wv+QwDQYJKoZIhvcNAQEL
BQAwgYcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzETMBEGA1UEBwwKTGluY29s
bnRvbjENMAsGA1UECgwEQ0lBVDEOMAwGA1UECwwFQ2xhc3MxDTALBgNVBAMMBHRl
c3QxKDAmBgkqhkiG9w0BCQEWGWRhcnRoY2VsdGljMTk4NUBnbWFpbC5jb20wHhcN
MjUwOTE0MDUxMTE4WhcNMjYwOTE0MDUxMTE4WjCBhzELMAkGA1UEBhMCVVMxCzAJ
BgNVBAgMAk5DMRMwEQYDVQQHDApMaW5jb2xudG9uMQ0wCwYDVQQKDARDSUFUMQ4w
DAYDVQQLDAVDbGFzczENMAsGA1UEAwwEdGVzdDEoMCYGCSqGSIb3DQEJARYZZGFy
dGhjZWx0aWMxOTg1QGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAOIPEvKyVT4sLTkYTy7u2esamRVLMQv62V4e9nFxmAfLEL4RtYWag/Z4
9KeMigZqEHWfHs3OtpTGUgQWn/wJ1A/PBIKlC1xVbws8oXhEtu7u/+IRALAvdA9h
DZyC6I1+vfo9JtzP78XKf5c9k+mVZs00BctOxz+1yZU32d6GM4S5C3vW3BAFAoSl
lKRqbXn/BKIOTLHuXdKPiqDmHfQ4P4LK82x/laUnXm8nKD1wdoV8BTxxoNvU8yf5
TjMq9mlSdRF9VP2u6mlLhCopncoUpCLEu2AnUkPWhYTWMJL7Nk2yz6smEc2wJYNd
uYY7pTiLUwjq1eM0fJkJCFrFfI0jY58CAwEAAaNTMFEwHQYDVR0OBBYEFKUroDmc
DA8Mm/ok7Do7C6OQr0jnMB8GA1UdIwQYMBaAFKUroDmcDA8Mm/ok7Do7C6OQr0jn
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIZTZKNMohtOlicw
iPVjZhHqSq+watwlPNzDAw9iAceLLicbe0I0nJrXC0FbnL1u+zA1mLz7ZbKA0Ft6
gJJCj3EU8c1YRDkxeKG2BpPxjS34zjDFrxsTwF6RZdLCOjHbUOmgSe5+yC7hf2jA
M/Aprt2tnfDRtQ+FiyUTD/Byeuu8I5u9UOyHV2tsQtFrazOEQazLmzaIq80IKVHx
6BF5GmCTSp2TxHoawiA44EgfYx4b4M81iECFqA0pKYOCX/wwhZB5ch2kd+9wT/6Z
0YcVvVUURPVnQiiBzkz6M2hbUlPD8HGBe1idRjuZNr9tEzsXkgzOEg5EG0D1HEnf
13fn8AU=
-----END CERTIFICATE-----
24 changes: 24 additions & 0 deletions tests/certs/trusted-leaf-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIID8TCCAtmgAwIBAgIUTDFKkXxIkZjtsAi2QlVNY54Wv+QwDQYJKoZIhvcNAQEL
BQAwgYcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzETMBEGA1UEBwwKTGluY29s
bnRvbjENMAsGA1UECgwEQ0lBVDEOMAwGA1UECwwFQ2xhc3MxDTALBgNVBAMMBHRl
c3QxKDAmBgkqhkiG9w0BCQEWGWRhcnRoY2VsdGljMTk4NUBnbWFpbC5jb20wHhcN
MjUwOTE0MDUxMTE4WhcNMjYwOTE0MDUxMTE4WjCBhzELMAkGA1UEBhMCVVMxCzAJ
BgNVBAgMAk5DMRMwEQYDVQQHDApMaW5jb2xudG9uMQ0wCwYDVQQKDARDSUFUMQ4w
DAYDVQQLDAVDbGFzczENMAsGA1UEAwwEdGVzdDEoMCYGCSqGSIb3DQEJARYZZGFy
dGhjZWx0aWMxOTg1QGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAOIPEvKyVT4sLTkYTy7u2esamRVLMQv62V4e9nFxmAfLEL4RtYWag/Z4
9KeMigZqEHWfHs3OtpTGUgQWn/wJ1A/PBIKlC1xVbws8oXhEtu7u/+IRALAvdA9h
DZyC6I1+vfo9JtzP78XKf5c9k+mVZs00BctOxz+1yZU32d6GM4S5C3vW3BAFAoSl
lKRqbXn/BKIOTLHuXdKPiqDmHfQ4P4LK82x/laUnXm8nKD1wdoV8BTxxoNvU8yf5
TjMq9mlSdRF9VP2u6mlLhCopncoUpCLEu2AnUkPWhYTWMJL7Nk2yz6smEc2wJYNd
uYY7pTiLUwjq1eM0fJkJCFrFfI0jY58CAwEAAaNTMFEwHQYDVR0OBBYEFKUroDmc
DA8Mm/ok7Do7C6OQr0jnMB8GA1UdIwQYMBaAFKUroDmcDA8Mm/ok7Do7C6OQr0jn
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIZTZKNMohtOlicw
iPVjZhHqSq+watwlPNzDAw9iAceLLicbe0I0nJrXC0FbnL1u+zA1mLz7ZbKA0Ft6
gJJCj3EU8c1YRDkxeKG2BpPxjS34zjDFrxsTwF6RZdLCOjHbUOmgSe5+yC7hf2jA
M/Aprt2tnfDRtQ+FiyUTD/Byeuu8I5u9UOyHV2tsQtFrazOEQazLmzaIq80IKVHx
6BF5GmCTSp2TxHoawiA44EgfYx4b4M81iECFqA0pKYOCX/wwhZB5ch2kd+9wT/6Z
0YcVvVUURPVnQiiBzkz6M2hbUlPD8HGBe1idRjuZNr9tEzsXkgzOEg5EG0D1HEnf
13fn8AU=
-----END CERTIFICATE-----
62 changes: 62 additions & 0 deletions tests/test_for_issue_6978.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import pytest
import requests
from requests.exceptions import SSLError
from http.server import HTTPServer, BaseHTTPRequestHandler
import ssl
import threading
import os

# Create a custom request handler to serve a GET request
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(b"")

# Use a mock server with a self-signed certificate and incomplete chain
@pytest.fixture(scope="session")
def incomplete_cert_server():
"""Starts a mock HTTPS server with an incomplete cert chain for testing."""
# This part of the code would require you to generate a specific
# certificate and key to simulate the bug. For the sake of this example,
# we'll use placeholder files. You would replace these with your actual files.
server_address = ('localhost', 8443)
httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)

# Corrected method to create a server-side SSL context
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(
certfile=os.path.join(os.path.dirname(__file__), "certs", "server-leaf-cert.pem"), # Replace with your server's leaf cert
keyfile=os.path.join(os.path.dirname(__file__), "certs", "server-key.pem") # Replace with your server's key
)
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)

server_thread = threading.Thread(target=httpd.serve_forever)
server_thread.daemon = True
server_thread.start()

yield server_address

httpd.shutdown()

def test_incomplete_chain_connects(incomplete_cert_server):
"""
Tests that requests connects when provided a trusted leaf cert,
even if the server's chain is incomplete.
"""
host, port = incomplete_cert_server
url = f"https://{host}:{port}"

# The path to the trusted leaf certificate you would generate.
trusted_leaf_cert_path = os.path.join(os.path.dirname(__file__), "certs", "trusted-leaf-cert.pem")

try:
# Use your corrected adapters.py to test this request
response = requests.get(url, verify=trusted_leaf_cert_path)
# We expect a successful response, so the test should pass
response.raise_for_status()
assert response.status_code == 200
except SSLError as e:
# If the SSLError is raised, the test fails
pytest.fail(f"SSLError was raised: {e}")