Skip to content

Commit 2431336

Browse files
SNOW-2067577 OCSP: stop certificates chain traversal as soon as a trusted one met (#2299)
1 parent 03928bf commit 2431336

File tree

4 files changed

+50
-24
lines changed

4 files changed

+50
-24
lines changed

DESCRIPTION.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne
99
# Release Notes
1010
- v3.15(TBD)
1111
- Bumped up min boto and botocore version to 1.24
12+
- OCSP: terminate certificates chain traversal if a trusted certificate already reached
1213

1314
- v3.14.1(April 21, 2025)
1415
- Added support for Python 3.13.

src/snowflake/connector/ocsp_asn1crypto.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,15 +394,22 @@ def extract_certificate_chain(
394394
from OpenSSL.crypto import FILETYPE_ASN1, dump_certificate
395395

396396
cert_map = OrderedDict()
397-
logger.debug("# of certificates: %s", len(connection.get_peer_cert_chain()))
398-
399-
for cert_openssl in connection.get_peer_cert_chain():
397+
cert_chain = connection.get_peer_cert_chain()
398+
logger.debug("# of certificates: %s", len(cert_chain))
399+
self._lazy_read_ca_bundle()
400+
for cert_openssl in cert_chain:
400401
cert_der = dump_certificate(FILETYPE_ASN1, cert_openssl)
401402
cert = Certificate.load(cert_der)
402403
logger.debug(
403404
"subject: %s, issuer: %s", cert.subject.native, cert.issuer.native
404405
)
405406
cert_map[cert.subject.sha256] = cert
407+
if cert.issuer.sha256 in SnowflakeOCSP.ROOT_CERTIFICATES_DICT:
408+
logger.debug(
409+
"A trusted root certificate found: %s, stopping chain traversal here",
410+
cert.subject.native,
411+
)
412+
break
406413

407414
return self.create_pair_issuer_subject(cert_map)
408415

src/snowflake/connector/tool/dump_ocsp_response.py

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

4+
import logging
5+
import sys
46
import time
5-
from os import path
7+
from argparse import ArgumentParser, Namespace
68
from time import gmtime, strftime
79

810
from asn1crypto import ocsp as asn1crypto_ocsp
911

1012
from snowflake.connector.compat import urlsplit
1113
from snowflake.connector.ocsp_asn1crypto import SnowflakeOCSPAsn1Crypto as SFOCSP
14+
from snowflake.connector.ocsp_snowflake import OCSPTelemetryData
1215
from snowflake.connector.ssl_wrap_socket import _openssl_connect
1316

1417

18+
def _parse_args() -> Namespace:
19+
parser = ArgumentParser(
20+
prog="dump_ocsp_response",
21+
description="Dump OCSP Response for the URLs (an internal tool).",
22+
)
23+
parser.add_argument(
24+
"-o",
25+
"--output-file",
26+
required=False,
27+
help="Dump output file",
28+
type=str,
29+
default=None,
30+
)
31+
parser.add_argument(
32+
"--log-level",
33+
required=False,
34+
help="Log level",
35+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
36+
)
37+
parser.add_argument("--log-file", required=False, help="Log file", default=None)
38+
parser.add_argument("urls", nargs="+", help="URLs to dump OCSP Response for")
39+
return parser.parse_args()
40+
41+
1542
def main() -> None:
1643
"""Internal Tool: OCSP response dumper."""
17-
18-
def help() -> None:
19-
print("Dump OCSP Response for the URL. ")
20-
print(
21-
"""
22-
Usage: {} <url> [<url> ...]
23-
""".format(
24-
path.basename(sys.argv[0])
44+
args = _parse_args()
45+
if args.log_level:
46+
if args.log_file:
47+
logging.basicConfig(
48+
filename=args.log_file, level=getattr(logging, args.log_level.upper())
2549
)
26-
)
27-
sys.exit(2)
28-
29-
import sys
30-
31-
if len(sys.argv) < 2:
32-
help()
33-
34-
urls = sys.argv[1:]
35-
dump_ocsp_response(urls, output_filename=None)
50+
else:
51+
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
52+
dump_ocsp_response(args.urls, output_filename=args.output_file)
3653

3754

3855
def dump_good_status(current_time, single_response) -> None:
@@ -87,7 +104,7 @@ def dump_ocsp_response(urls, output_filename):
87104
for issuer, subject in cert_data:
88105
_, _ = ocsp.create_ocsp_request(issuer, subject)
89106
_, _, _, cert_id, ocsp_response_der = ocsp.validate_by_direct_connection(
90-
issuer, subject
107+
issuer, subject, OCSPTelemetryData()
91108
)
92109
ocsp_response = asn1crypto_ocsp.OCSPResponse.load(ocsp_response_der)
93110
print("------------------------------------------------------------")
@@ -115,7 +132,7 @@ def dump_ocsp_response(urls, output_filename):
115132

116133
if output_filename:
117134
SFOCSP.OCSP_CACHE.write_ocsp_response_cache_file(ocsp, output_filename)
118-
return SFOCSP.OCSP_CACHE.CACHE
135+
return SFOCSP.OCSP_CACHE
119136

120137

121138
if __name__ == "__main__":

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ deps =
8484
pytest-timeout
8585
pytest-xdist
8686
mock
87+
certifi<2025.4.26
8788
skip_install = True
8889
setenv = {[testenv]setenv}
8990
passenv = {[testenv]passenv}

0 commit comments

Comments
 (0)