28
28
# the PATH to point to the installed tools and set up other environment variables
29
29
# needed by the tools.
30
30
import argparse
31
+ import certifi
31
32
import contextlib
32
33
import copy
33
34
import datetime
@@ -464,11 +465,15 @@ def parse_platform_arg(platform_str: str) -> str:
464
465
465
466
DL_CERT_DICT = {'dl.espressif.com' : DIGICERT_ROOT_G2_CERT , 'github.com' : DIGICERT_ROOT_CA_CERT }
466
467
468
+ def get_certifi_path () -> str :
469
+ cert_path = certifi .where ()
470
+ if os .path .exists (cert_path ):
471
+ return cert_path
467
472
468
473
def create_esp_idf_ssl_context (url : str ) -> ssl .SSLContext :
469
474
"""
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
+
472
477
This function detects the SSL backend (LibreSSL vs OpenSSL) and creates
473
478
an appropriate SSL context with version-specific optimizations. It also
474
479
handles custom DigiCert certificates for known domains.
@@ -481,98 +486,92 @@ def create_esp_idf_ssl_context(url: str) -> ssl.SSLContext:
481
486
"""
482
487
ssl_version = ssl .OPENSSL_VERSION
483
488
ssl_version_info = ssl .OPENSSL_VERSION_INFO
484
-
489
+
485
490
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
+
488
495
if "LibreSSL" in ssl_version :
489
- # macOS LibreSSL - more conservative settings
490
- ctx = ssl .create_default_context ()
491
496
ctx .set_ciphers ('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS' )
492
497
ctx .check_hostname = True
493
498
ctx .verify_mode = ssl .CERT_REQUIRED
494
499
info ("LibreSSL-compatible configuration activated" )
495
-
500
+
496
501
elif ssl_version_info >= (3 , 0 , 0 ):
497
- # OpenSSL 3.x - use modern features
498
- ctx = ssl .create_default_context ()
499
502
ctx .minimum_version = ssl .TLSVersion .TLSv1_2
500
503
if hasattr (ssl , 'TLSVersion' ) and hasattr (ssl .TLSVersion , 'TLSv1_3' ):
501
504
ctx .maximum_version = ssl .TLSVersion .TLSv1_3
502
505
info ("OpenSSL 3.x modern configuration activated" )
503
-
506
+
504
507
elif ssl_version_info >= (1 , 1 , 1 ):
505
- # OpenSSL 1.1.1+ - proven configuration
506
- ctx = ssl .create_default_context ()
507
508
ctx .minimum_version = ssl .TLSVersion .TLSv1_2
508
509
info ("OpenSSL 1.1.1+ standard configuration activated" )
509
-
510
+
510
511
else :
511
- # Legacy OpenSSL - basic functionality
512
512
warn ("Outdated OpenSSL version detected, using legacy mode" )
513
- ctx = ssl .create_default_context ()
514
513
ctx .options |= ssl .OP_NO_SSLv2 | ssl .OP_NO_SSLv3
515
-
514
+
516
515
# ESP-IDF DigiCert Certificate Handling
517
516
parsed_url = urllib .parse .urlparse (url )
518
517
domain = parsed_url .netloc .lower ()
519
-
518
+
520
519
if domain in DL_CERT_DICT :
521
520
cert_data = DL_CERT_DICT [domain ]
522
521
ctx .load_verify_locations (cadata = cert_data )
523
- # Disable hostname checking for custom certificates
524
522
ctx .check_hostname = False
525
523
ctx .verify_mode = ssl .CERT_REQUIRED
526
524
info (f"✓ Custom DigiCert certificate loaded for { domain } " )
527
-
528
- return ctx
529
525
526
+ return ctx
530
527
531
528
def get_ssl_fallback_contexts (url : str ) -> List [Tuple [str , ssl .SSLContext ]]:
532
529
"""
533
530
Creates fallback SSL contexts for different scenarios.
534
-
531
+ Incorporates certifi CA bundle if available.
532
+
535
533
This function provides multiple SSL context configurations that are tried
536
534
in order when downloading fails. This approach maximizes compatibility
537
535
across different systems and SSL configurations.
538
-
536
+
539
537
Args:
540
538
url: The URL to create contexts for
541
-
539
+
542
540
Returns:
543
541
List of tuples containing (config_name, ssl_context) pairs
544
542
"""
545
543
contexts = []
546
-
547
- # 1. Primary context with backend detection
544
+
545
+ # 1. Primary context with backend detection and certifi support
548
546
try :
549
547
primary_ctx = create_esp_idf_ssl_context (url )
550
548
contexts .append (('esp_idf_optimized' , primary_ctx ))
551
549
except Exception as e :
552
550
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
555
553
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 ()
557
556
parsed_url = urllib .parse .urlparse (url )
558
557
domain = parsed_url .netloc .lower ()
559
-
558
+
560
559
if domain in DL_CERT_DICT :
561
560
cert_data = DL_CERT_DICT [domain ]
562
561
standard_ctx .load_verify_locations (cadata = cert_data )
563
562
standard_ctx .check_hostname = False
564
-
563
+
565
564
contexts .append (('standard_with_custom_cert' , standard_ctx ))
566
565
except Exception as e :
567
566
warn (f"Standard SSL context with custom cert failed: { e } " )
568
-
569
- # 3. System default without modifications
567
+
568
+ # 3. System default
570
569
try :
571
570
system_ctx = ssl .create_default_context ()
572
571
contexts .append (('system_default' , system_ctx ))
573
572
except Exception as e :
574
573
warn (f"System default SSL context failed: { e } " )
575
-
574
+
576
575
# 4. Unverified as last resort
577
576
try :
578
577
unverified_ctx = ssl .create_default_context ()
@@ -581,7 +580,7 @@ def get_ssl_fallback_contexts(url: str) -> List[Tuple[str, ssl.SSLContext]]:
581
580
contexts .append (('unverified' , unverified_ctx ))
582
581
except Exception as e :
583
582
warn (f"Unverified SSL context failed: { e } " )
584
-
583
+
585
584
return contexts
586
585
587
586
0 commit comments