2828# the PATH to point to the installed tools and set up other environment variables
2929# needed by the tools.
3030import argparse
31+ import certifi
3132import contextlib
3233import copy
3334import datetime
@@ -464,11 +465,15 @@ def parse_platform_arg(platform_str: str) -> str:
464465
465466DL_CERT_DICT = {'dl.espressif.com' : DIGICERT_ROOT_G2_CERT , 'github.com' : DIGICERT_ROOT_CA_CERT }
466467
468+ def get_certifi_path () -> str :
469+ cert_path = certifi .where ()
470+ if os .path .exists (cert_path ):
471+ return cert_path
467472
468473def create_esp_idf_ssl_context (url : str ) -> ssl .SSLContext :
469474 """
470- Creates ESP-IDF optimized SSL context with OpenSSL version detection.
471-
475+ Creates ESP-IDF optimized SSL context with OpenSSL version detection and certifi support .
476+
472477 This function detects the SSL backend (LibreSSL vs OpenSSL) and creates
473478 an appropriate SSL context with version-specific optimizations. It also
474479 handles custom DigiCert certificates for known domains.
@@ -481,98 +486,92 @@ def create_esp_idf_ssl_context(url: str) -> ssl.SSLContext:
481486 """
482487 ssl_version = ssl .OPENSSL_VERSION
483488 ssl_version_info = ssl .OPENSSL_VERSION_INFO
484-
489+
485490 info (f"SSL Backend: { ssl_version } ({ ssl_version_info } )" )
486-
487- # Create context based on detected SSL backend version
491+
492+ cafile = get_certifi_path ()
493+ ctx = ssl .create_default_context (cafile = cafile ) if cafile else ssl .create_default_context ()
494+
488495 if "LibreSSL" in ssl_version :
489- # macOS LibreSSL - more conservative settings
490- ctx = ssl .create_default_context ()
491496 ctx .set_ciphers ('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS' )
492497 ctx .check_hostname = True
493498 ctx .verify_mode = ssl .CERT_REQUIRED
494499 info ("LibreSSL-compatible configuration activated" )
495-
500+
496501 elif ssl_version_info >= (3 , 0 , 0 ):
497- # OpenSSL 3.x - use modern features
498- ctx = ssl .create_default_context ()
499502 ctx .minimum_version = ssl .TLSVersion .TLSv1_2
500503 if hasattr (ssl , 'TLSVersion' ) and hasattr (ssl .TLSVersion , 'TLSv1_3' ):
501504 ctx .maximum_version = ssl .TLSVersion .TLSv1_3
502505 info ("OpenSSL 3.x modern configuration activated" )
503-
506+
504507 elif ssl_version_info >= (1 , 1 , 1 ):
505- # OpenSSL 1.1.1+ - proven configuration
506- ctx = ssl .create_default_context ()
507508 ctx .minimum_version = ssl .TLSVersion .TLSv1_2
508509 info ("OpenSSL 1.1.1+ standard configuration activated" )
509-
510+
510511 else :
511- # Legacy OpenSSL - basic functionality
512512 warn ("Outdated OpenSSL version detected, using legacy mode" )
513- ctx = ssl .create_default_context ()
514513 ctx .options |= ssl .OP_NO_SSLv2 | ssl .OP_NO_SSLv3
515-
514+
516515 # ESP-IDF DigiCert Certificate Handling
517516 parsed_url = urllib .parse .urlparse (url )
518517 domain = parsed_url .netloc .lower ()
519-
518+
520519 if domain in DL_CERT_DICT :
521520 cert_data = DL_CERT_DICT [domain ]
522521 ctx .load_verify_locations (cadata = cert_data )
523- # Disable hostname checking for custom certificates
524522 ctx .check_hostname = False
525523 ctx .verify_mode = ssl .CERT_REQUIRED
526524 info (f"✓ Custom DigiCert certificate loaded for { domain } " )
527-
528- return ctx
529525
526+ return ctx
530527
531528def get_ssl_fallback_contexts (url : str ) -> List [Tuple [str , ssl .SSLContext ]]:
532529 """
533530 Creates fallback SSL contexts for different scenarios.
534-
531+ Incorporates certifi CA bundle if available.
532+
535533 This function provides multiple SSL context configurations that are tried
536534 in order when downloading fails. This approach maximizes compatibility
537535 across different systems and SSL configurations.
538-
536+
539537 Args:
540538 url: The URL to create contexts for
541-
539+
542540 Returns:
543541 List of tuples containing (config_name, ssl_context) pairs
544542 """
545543 contexts = []
546-
547- # 1. Primary context with backend detection
544+
545+ # 1. Primary context with backend detection and certifi support
548546 try :
549547 primary_ctx = create_esp_idf_ssl_context (url )
550548 contexts .append (('esp_idf_optimized' , primary_ctx ))
551549 except Exception as e :
552550 warn (f"Primary SSL context failed: { e } " )
553-
554- # 2. Standard context with custom certificates
551+
552+ # 2. Standard context with custom certificates and certifi support
555553 try :
556- standard_ctx = ssl .create_default_context ()
554+ cafile = get_certifi_path ()
555+ standard_ctx = ssl .create_default_context (cafile = cafile ) if cafile else ssl .create_default_context ()
557556 parsed_url = urllib .parse .urlparse (url )
558557 domain = parsed_url .netloc .lower ()
559-
558+
560559 if domain in DL_CERT_DICT :
561560 cert_data = DL_CERT_DICT [domain ]
562561 standard_ctx .load_verify_locations (cadata = cert_data )
563562 standard_ctx .check_hostname = False
564-
563+
565564 contexts .append (('standard_with_custom_cert' , standard_ctx ))
566565 except Exception as e :
567566 warn (f"Standard SSL context with custom cert failed: { e } " )
568-
569- # 3. System default without modifications
567+
568+ # 3. System default
570569 try :
571570 system_ctx = ssl .create_default_context ()
572571 contexts .append (('system_default' , system_ctx ))
573572 except Exception as e :
574573 warn (f"System default SSL context failed: { e } " )
575-
574+
576575 # 4. Unverified as last resort
577576 try :
578577 unverified_ctx = ssl .create_default_context ()
@@ -581,7 +580,7 @@ def get_ssl_fallback_contexts(url: str) -> List[Tuple[str, ssl.SSLContext]]:
581580 contexts .append (('unverified' , unverified_ctx ))
582581 except Exception as e :
583582 warn (f"Unverified SSL context failed: { e } " )
584-
583+
585584 return contexts
586585
587586
0 commit comments