|
8 | 8 | # |
9 | 9 | # and added OCSP validator on the top. |
10 | 10 | import logging |
| 11 | +import os |
11 | 12 | import ssl |
12 | 13 | import time |
13 | 14 | import weakref |
|
39 | 40 | log = logging.getLogger(__name__) |
40 | 41 |
|
41 | 42 |
|
| 43 | +# Helper utilities (private) |
| 44 | +def _resolve_cafile(kwargs: dict[str, Any]) -> str | None: |
| 45 | + """Resolve CA bundle path from kwargs or standard environment variables. |
| 46 | +
|
| 47 | + Precedence: |
| 48 | + 1) kwargs['ca_certs'] if provided by caller |
| 49 | + 2) REQUESTS_CA_BUNDLE |
| 50 | + 3) SSL_CERT_FILE |
| 51 | + """ |
| 52 | + caf = kwargs.get("ca_certs") |
| 53 | + if caf: |
| 54 | + return caf |
| 55 | + return os.environ.get("REQUESTS_CA_BUNDLE") or os.environ.get("SSL_CERT_FILE") |
| 56 | + |
| 57 | + |
| 58 | +def _ensure_partial_chain_on_context(ctx: PyOpenSSLContext, cafile: str | None) -> None: |
| 59 | + """Load CA bundle (when provided) and enable OpenSSL partial-chain support on ctx.""" |
| 60 | + if cafile: |
| 61 | + try: |
| 62 | + ctx.load_verify_locations(cafile=cafile, capath=None) |
| 63 | + except (ssl.SSLError, OSError, ValueError): |
| 64 | + # Leave context unchanged; handshake/validation surfaces failures |
| 65 | + pass |
| 66 | + try: |
| 67 | + store = ctx._ctx.get_cert_store() |
| 68 | + from OpenSSL import crypto as _crypto |
| 69 | + |
| 70 | + if hasattr(_crypto, "X509StoreFlags") and hasattr( |
| 71 | + _crypto.X509StoreFlags, "PARTIAL_CHAIN" |
| 72 | + ): |
| 73 | + store.set_flags(_crypto.X509StoreFlags.PARTIAL_CHAIN) |
| 74 | + except (AttributeError, ImportError, OpenSSL.SSL.Error, OSError, ValueError): |
| 75 | + # Best-effort; if not available, default chain building applies |
| 76 | + pass |
| 77 | + |
| 78 | + |
| 79 | +def _build_context_with_partial_chain(cafile: str | None) -> PyOpenSSLContext: |
| 80 | + """Create PyOpenSSL context configured for CERT_REQUIRED and partial-chain trust.""" |
| 81 | + ctx = PyOpenSSLContext(ssl_.PROTOCOL_TLS_CLIENT) |
| 82 | + try: |
| 83 | + ctx.verify_mode = ssl.CERT_REQUIRED |
| 84 | + except Exception: |
| 85 | + pass |
| 86 | + _ensure_partial_chain_on_context(ctx, cafile) |
| 87 | + return ctx |
| 88 | + |
| 89 | + |
42 | 90 | # Store a *weak* reference so that the context variable doesn’t prolong the |
43 | 91 | # lifetime of the SessionManager. Once all owning connections are GC-ed the |
44 | 92 | # weakref goes dead and OCSP will fall back to its local manager (but most |
@@ -90,35 +138,6 @@ def reset_current_session_manager(token) -> None: |
90 | 138 | pass |
91 | 139 |
|
92 | 140 |
|
93 | | -def _build_pyopenssl_context_with_ca_and_partial_chain( |
94 | | - cafile: str | None, |
95 | | -) -> PyOpenSSLContext: |
96 | | - ctx = PyOpenSSLContext(ssl_.PROTOCOL_TLS_CLIENT) |
97 | | - try: |
98 | | - # Ensure certificate verification is enabled |
99 | | - ctx.verify_mode = ssl.CERT_REQUIRED |
100 | | - except Exception: |
101 | | - pass |
102 | | - try: |
103 | | - if cafile: |
104 | | - ctx.load_verify_locations(cafile=cafile, capath=None) |
105 | | - except Exception: |
106 | | - pass |
107 | | - # Enable partial-chain verification so intermediates in trust store can |
108 | | - # terminate chains |
109 | | - try: |
110 | | - store = ctx._ctx.get_cert_store() |
111 | | - from OpenSSL import crypto as _crypto |
112 | | - |
113 | | - if hasattr(_crypto, "X509StoreFlags") and hasattr( |
114 | | - _crypto.X509StoreFlags, "PARTIAL_CHAIN" |
115 | | - ): |
116 | | - store.set_flags(_crypto.X509StoreFlags.PARTIAL_CHAIN) |
117 | | - except Exception: |
118 | | - pass |
119 | | - return ctx |
120 | | - |
121 | | - |
122 | 141 | def inject_into_urllib3() -> None: |
123 | 142 | """Monkey-patch urllib3 with PyOpenSSL-backed SSL-support and OCSP.""" |
124 | 143 | log.debug("Injecting ssl_wrap_socket_with_ocsp") |
@@ -155,51 +174,16 @@ def ssl_wrap_socket_with_ocsp(*args: Any, **kwargs: Any) -> WrappedSocket: |
155 | 174 | kwargs["ca_certs"] = certifi.where() |
156 | 175 |
|
157 | 176 | # Ensure PyOpenSSL context with partial-chain is used if no suitable context provided |
158 | | - try: |
159 | | - provided_ctx = kwargs.get("ssl_context", None) |
160 | | - if not isinstance(provided_ctx, PyOpenSSLContext): |
161 | | - cafile_for_ctx = kwargs.get("ca_certs") |
162 | | - if not cafile_for_ctx: |
163 | | - import os as _os |
164 | | - |
165 | | - cafile_for_ctx = _os.environ.get( |
166 | | - "REQUESTS_CA_BUNDLE" |
167 | | - ) or _os.environ.get("SSL_CERT_FILE") |
168 | | - kwargs["ssl_context"] = _build_pyopenssl_context_with_ca_and_partial_chain( |
169 | | - cafile_for_ctx |
170 | | - ) |
171 | | - except Exception: |
172 | | - pass |
| 177 | + provided_ctx = kwargs.get("ssl_context", None) |
| 178 | + if not isinstance(provided_ctx, PyOpenSSLContext): |
| 179 | + cafile_for_ctx = _resolve_cafile(kwargs) |
| 180 | + kwargs["ssl_context"] = _build_context_with_partial_chain(cafile_for_ctx) |
173 | 181 |
|
174 | 182 | # If a PyOpenSSLContext is provided, ensure it trusts the provided CA and |
175 | 183 | # partial-chain is enabled |
176 | | - try: |
177 | | - provided_ctx = kwargs.get("ssl_context", None) |
178 | | - if isinstance(provided_ctx, PyOpenSSLContext): |
179 | | - caf = kwargs.get("ca_certs") |
180 | | - if not caf: |
181 | | - import os as _os |
182 | | - |
183 | | - caf = _os.environ.get("REQUESTS_CA_BUNDLE") or _os.environ.get( |
184 | | - "SSL_CERT_FILE" |
185 | | - ) |
186 | | - try: |
187 | | - if caf: |
188 | | - provided_ctx.load_verify_locations(cafile=caf, capath=None) |
189 | | - except Exception: |
190 | | - pass |
191 | | - try: |
192 | | - store = provided_ctx._ctx.get_cert_store() |
193 | | - from OpenSSL import crypto as _crypto |
194 | | - |
195 | | - if hasattr(_crypto, "X509StoreFlags") and hasattr( |
196 | | - _crypto.X509StoreFlags, "PARTIAL_CHAIN" |
197 | | - ): |
198 | | - store.set_flags(_crypto.X509StoreFlags.PARTIAL_CHAIN) |
199 | | - except Exception: |
200 | | - pass |
201 | | - except Exception: |
202 | | - pass |
| 184 | + provided_ctx = kwargs.get("ssl_context", None) |
| 185 | + if isinstance(provided_ctx, PyOpenSSLContext): |
| 186 | + _ensure_partial_chain_on_context(provided_ctx, _resolve_cafile(kwargs)) |
203 | 187 |
|
204 | 188 | ret = ssl_.ssl_wrap_socket(*args, **kwargs) |
205 | 189 |
|
|
0 commit comments