Skip to content

Commit 0bd6084

Browse files
NO-SNOW: CRL changes in aio
1 parent 2e84182 commit 0bd6084

File tree

4 files changed

+69
-8
lines changed

4 files changed

+69
-8
lines changed

src/snowflake/connector/aio/_connection.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
PARAMETER_TIMEZONE,
4848
QueryStatus,
4949
)
50+
from ..crl import CRLConfig
5051
from ..description import PLATFORM, PYTHON_VERSION, SNOWFLAKE_CONNECTOR_VERSION
5152
from ..errorcode import (
5253
ER_CONNECTION_IS_CLOSED,
@@ -622,6 +623,7 @@ def _init_connection_parameters(
622623

623624
# Placeholder attributes; will be initialized in connect()
624625
self._http_config: AioHttpConfig | None = None
626+
self._crl_config: CRLConfig | None = None
625627
self._session_manager: SessionManager | None = None
626628
self._rest = None
627629
for name, (value, _) in DEFAULT_CONFIGURATION.items():

src/snowflake/connector/aio/_session_manager.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,23 @@
33
import sys
44
from typing import TYPE_CHECKING
55

6+
import certifi
67
from aiohttp import ClientRequest, ClientTimeout
78
from aiohttp.client import _RequestOptions
89
from aiohttp.client_proto import ResponseHandler
910
from aiohttp.connector import Connection
1011
from aiohttp.typedefs import StrOrURL
1112

1213
from .. import OperationalError
14+
from ..crl import CertRevocationCheckMode, CRLValidator
1315
from ..errorcode import ER_OCSP_RESPONSE_CERT_STATUS_REVOKED
14-
from ..ssl_wrap_socket import FEATURE_OCSP_RESPONSE_CACHE_FILE_NAME
16+
from ..ssl_wrap_socket import (
17+
FEATURE_CRL_CONFIG,
18+
FEATURE_OCSP_RESPONSE_CACHE_FILE_NAME,
19+
get_current_session_manager,
20+
load_trusted_certificates,
21+
resolve_cafile,
22+
)
1523
from ._ocsp_asn1crypto import SnowflakeOCSPAsn1Crypto
1624

1725
if TYPE_CHECKING:
@@ -71,6 +79,20 @@ async def connect(
7179
) -> Connection:
7280
connection = await super().connect(req, traces, timeout)
7381
protocol = connection.protocol
82+
83+
logger.debug(
84+
"CRL Check Mode: %s",
85+
FEATURE_CRL_CONFIG.cert_revocation_check_mode.name,
86+
)
87+
if (
88+
FEATURE_CRL_CONFIG.cert_revocation_check_mode
89+
!= CertRevocationCheckMode.ENABLED
90+
):
91+
self.validate_crl(protocol, req)
92+
logger.debug(
93+
"The certificate revocation check was successful. No additional checks will be performed."
94+
)
95+
7496
if (
7597
req.is_ssl()
7698
and protocol is not None
@@ -90,6 +112,24 @@ async def connect(
90112
protocol._snowflake_ocsp_validated = True
91113
return connection
92114

115+
def validate_crl(self, protocol: ResponseHandler, req: ClientRequest):
116+
# Resolve CA file path from environment variables or use certifi default
117+
cafile_for_ctx = resolve_cafile({"ca_certs": certifi.where()})
118+
crl_validator = CRLValidator.from_config(
119+
FEATURE_CRL_CONFIG,
120+
get_current_session_manager(),
121+
trusted_certificates=load_trusted_certificates(cafile_for_ctx),
122+
)
123+
sll_object = protocol.transport.get_extra_info("ssl_object")
124+
if not crl_validator.validate_connection(sll_object):
125+
raise OperationalError(
126+
msg=(
127+
"The certificate is revoked or "
128+
"could not be validated via CRL: hostname={}".format(req.url.host)
129+
),
130+
errno=ER_OCSP_RESPONSE_CERT_STATUS_REVOKED,
131+
)
132+
93133
async def validate_ocsp(
94134
self,
95135
hostname: str,

src/snowflake/connector/crl.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
#!/usr/bin/env python
22
from __future__ import annotations
33

4+
import ssl
45
from collections import defaultdict
56
from dataclasses import dataclass
67
from datetime import datetime, timedelta, timezone
78
from enum import Enum, unique
89
from logging import getLogger
910
from pathlib import Path
11+
from ssl import SSLObject
1012
from typing import Any
1113

1214
from cryptography import x509
@@ -750,7 +752,7 @@ def validate_connection(self, connection: SSLConnection) -> bool:
750752
"""
751753
try:
752754
# Get the peer certificate (the start certificate)
753-
peer_cert = connection.get_peer_certificate(as_cryptography=True)
755+
peer_cert = self._get_peer_certificate(connection)
754756
if peer_cert is None:
755757
logger.warning("No peer certificate found in connection")
756758
return (
@@ -765,6 +767,14 @@ def validate_connection(self, connection: SSLConnection) -> bool:
765767
logger.warning("Failed to validate connection: %s", e)
766768
return self._cert_revocation_check_mode == CertRevocationCheckMode.ADVISORY
767769

770+
@staticmethod
771+
def _get_peer_certificate(connection):
772+
if isinstance(connection, SSLObject):
773+
return x509.load_der_x509_certificate(
774+
connection.getpeercert(binary_form=True), default_backend()
775+
)
776+
return connection.get_peer_certificate(as_cryptography=True)
777+
768778
def _extract_certificate_chain_from_connection(
769779
self, connection
770780
) -> list[x509.Certificate] | None:
@@ -777,8 +787,17 @@ def _extract_certificate_chain_from_connection(
777787
Certificate chain as a list of x509.Certificate objects, or None on error
778788
"""
779789
try:
780-
# Convert OpenSSL certificates to cryptography x509 certificates
781-
cert_chain = connection.get_peer_cert_chain(as_cryptography=True)
790+
if isinstance(connection, SSLObject):
791+
cert_chain = []
792+
for cert in connection._sslobj.get_unverified_chain():
793+
cert_bytes = ssl.PEM_cert_to_DER_cert(cert.public_bytes())
794+
cert_chain.append(
795+
x509.load_der_x509_certificate(cert_bytes, default_backend())
796+
)
797+
else:
798+
# Convert OpenSSL certificates to cryptography x509 certificates
799+
cert_chain = connection.get_peer_cert_chain(as_cryptography=True)
800+
782801
if not cert_chain:
783802
logger.debug("No certificate chain found in connection")
784803
return None

src/snowflake/connector/ssl_wrap_socket.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747

4848

4949
# Helper utilities (private)
50-
def _resolve_cafile(kwargs: dict[str, Any]) -> str | None:
50+
def resolve_cafile(kwargs: dict[str, Any]) -> str | None:
5151
"""Resolve CA bundle path from kwargs or standard environment variables.
5252
5353
Precedence:
@@ -150,7 +150,7 @@ def inject_into_urllib3() -> None:
150150
connection_.ssl_wrap_socket = ssl_wrap_socket_with_cert_revocation_checks
151151

152152

153-
def _load_trusted_certificates(cafile: str | None) -> list[x509.Certificate]:
153+
def load_trusted_certificates(cafile: str | None) -> list[x509.Certificate]:
154154
# Use default SSL context to load the CA file and get the certificates
155155
ctx = ssl.create_default_context()
156156
ctx.load_verify_locations(cafile=cafile)
@@ -177,7 +177,7 @@ def ssl_wrap_socket_with_cert_revocation_checks(
177177

178178
# Ensure PyOpenSSL context with partial-chain is used if none or wrong type provided
179179
provided_ctx = params.get("ssl_context")
180-
cafile_for_ctx = _resolve_cafile(params)
180+
cafile_for_ctx = resolve_cafile(params)
181181
if not isinstance(provided_ctx, PyOpenSSLContext):
182182
params["ssl_context"] = _build_context_with_partial_chain(cafile_for_ctx)
183183
else:
@@ -197,7 +197,7 @@ def ssl_wrap_socket_with_cert_revocation_checks(
197197
crl_validator = CRLValidator.from_config(
198198
FEATURE_CRL_CONFIG,
199199
get_current_session_manager(),
200-
trusted_certificates=_load_trusted_certificates(cafile_for_ctx),
200+
trusted_certificates=load_trusted_certificates(cafile_for_ctx),
201201
)
202202
if not crl_validator.validate_connection(ret.connection):
203203
raise OperationalError(

0 commit comments

Comments
 (0)