@@ -46,7 +46,6 @@ mod _ssl {
4646 } ;
4747 use std:: {
4848 collections:: HashMap ,
49- io:: Write ,
5049 sync:: {
5150 Arc ,
5251 atomic:: { AtomicUsize , Ordering } ,
@@ -479,9 +478,8 @@ mod _ssl {
479478 return Err ( vm. new_value_error ( "server_hostname cannot start with a dot" ) ) ;
480479 }
481480
482- if hostname. parse :: < std:: net:: IpAddr > ( ) . is_ok ( ) {
483- return Err ( vm. new_value_error ( "server_hostname cannot be an IP address" ) ) ;
484- }
481+ // IP addresses are allowed as server_hostname
482+ // SNI will not be sent for IP addresses
485483
486484 if hostname. contains ( '\0' ) {
487485 return Err ( vm. new_type_error ( "embedded null character" ) ) ;
@@ -1452,35 +1450,74 @@ mod _ssl {
14521450 /// This uses platform-specific methods:
14531451 /// - Linux: openssl-probe to find certificate files
14541452 /// - macOS: Keychain API
1455- /// - Windows: System certificate store
1453+ /// - Windows: System certificate store (ROOT + CA stores)
14561454 fn load_system_certificates (
14571455 & self ,
14581456 store : & mut rustls:: RootCertStore ,
14591457 vm : & VirtualMachine ,
14601458 ) -> PyResult < ( ) > {
1461- let result = rustls_native_certs:: load_native_certs ( ) ;
1462-
1463- // Load successfully found certificates
1464- for cert in result. certs {
1465- let is_ca = cert:: is_ca_certificate ( cert. as_ref ( ) ) ;
1466- if store. add ( cert) . is_ok ( ) {
1467- * self . x509_cert_count . write ( ) += 1 ;
1468- if is_ca {
1469- * self . ca_cert_count . write ( ) += 1 ;
1459+ #[ cfg( windows) ]
1460+ {
1461+ // Windows: Use schannel to load from both ROOT and CA stores
1462+ use schannel:: cert_store:: CertStore ;
1463+
1464+ let store_names = [ "ROOT" , "CA" ] ;
1465+ let open_fns = [ CertStore :: open_current_user, CertStore :: open_local_machine] ;
1466+
1467+ for store_name in store_names {
1468+ for open_fn in & open_fns {
1469+ if let Ok ( cert_store) = open_fn ( store_name) {
1470+ for cert_ctx in cert_store. certs ( ) {
1471+ let der_bytes = cert_ctx. to_der ( ) ;
1472+ let cert =
1473+ rustls:: pki_types:: CertificateDer :: from ( der_bytes. to_vec ( ) ) ;
1474+ let is_ca = cert:: is_ca_certificate ( cert. as_ref ( ) ) ;
1475+ if store. add ( cert) . is_ok ( ) {
1476+ * self . x509_cert_count . write ( ) += 1 ;
1477+ if is_ca {
1478+ * self . ca_cert_count . write ( ) += 1 ;
1479+ }
1480+ }
1481+ }
1482+ }
14701483 }
14711484 }
1472- }
14731485
1474- // If there were errors but some certs loaded, just continue
1475- // If NO certs loaded and there were errors, report the first error
1476- if * self . x509_cert_count . read ( ) == 0 && !result . errors . is_empty ( ) {
1477- return Err ( vm . new_os_error ( format ! (
1478- "Failed to load native certificates: {}" ,
1479- result . errors [ 0 ]
1480- ) ) ) ;
1486+ if * self . x509_cert_count . read ( ) == 0 {
1487+ return Err ( vm . new_os_error (
1488+ "Failed to load certificates from Windows store" . to_owned ( ) ,
1489+ ) ) ;
1490+ }
1491+
1492+ Ok ( ( ) )
14811493 }
14821494
1483- Ok ( ( ) )
1495+ #[ cfg( not( windows) ) ]
1496+ {
1497+ let result = rustls_native_certs:: load_native_certs ( ) ;
1498+
1499+ // Load successfully found certificates
1500+ for cert in result. certs {
1501+ let is_ca = cert:: is_ca_certificate ( cert. as_ref ( ) ) ;
1502+ if store. add ( cert) . is_ok ( ) {
1503+ * self . x509_cert_count . write ( ) += 1 ;
1504+ if is_ca {
1505+ * self . ca_cert_count . write ( ) += 1 ;
1506+ }
1507+ }
1508+ }
1509+
1510+ // If there were errors but some certs loaded, just continue
1511+ // If NO certs loaded and there were errors, report the first error
1512+ if * self . x509_cert_count . read ( ) == 0 && !result. errors . is_empty ( ) {
1513+ return Err ( vm. new_os_error ( format ! (
1514+ "Failed to load native certificates: {}" ,
1515+ result. errors[ 0 ]
1516+ ) ) ) ;
1517+ }
1518+
1519+ Ok ( ( ) )
1520+ }
14841521 }
14851522
14861523 #[ pymethod]
@@ -1491,17 +1528,28 @@ mod _ssl {
14911528 ) -> PyResult < ( ) > {
14921529 let mut store = self . root_certs . write ( ) ;
14931530
1494- // Create loader (without ca_certs_der - default certs don't go to get_ca_certs())
1495- let mut lazy_ca_certs = Vec :: new ( ) ;
1496- let mut loader = cert:: CertLoader :: new ( & mut store, & mut lazy_ca_certs) ;
1531+ #[ cfg( windows) ]
1532+ {
1533+ // Windows: Load system certificates first, then additionally load from env
1534+ // see: test_load_default_certs_env_windows
1535+ let _ = self . load_system_certificates ( & mut store, vm) ;
14971536
1498- // Try Python os.environ first (allows runtime env changes)
1499- // This checks SSL_CERT_FILE and SSL_CERT_DIR from Python's os.environ
1500- let loaded = self . try_load_from_python_environ ( & mut loader, vm) ?;
1537+ let mut lazy_ca_certs = Vec :: new ( ) ;
1538+ let mut loader = cert:: CertLoader :: new ( & mut store, & mut lazy_ca_certs) ;
1539+ let _ = self . try_load_from_python_environ ( & mut loader, vm) ?;
1540+ }
15011541
1502- // Fallback to system certificates if environment variables didn't provide any
1503- if !loaded {
1504- let _ = self . load_system_certificates ( & mut store, vm) ;
1542+ #[ cfg( not( windows) ) ]
1543+ {
1544+ // Non-Windows: Try env vars first; only fallback to system certs if not set
1545+ // see: test_load_default_certs_env
1546+ let mut lazy_ca_certs = Vec :: new ( ) ;
1547+ let mut loader = cert:: CertLoader :: new ( & mut store, & mut lazy_ca_certs) ;
1548+ let loaded = self . try_load_from_python_environ ( & mut loader, vm) ?;
1549+
1550+ if !loaded {
1551+ let _ = self . load_system_certificates ( & mut store, vm) ;
1552+ }
15051553 }
15061554
15071555 // If no certificates were loaded from system, fallback to webpki-roots (Mozilla CA bundle)
@@ -1892,10 +1940,8 @@ mod _ssl {
18921940 return Err ( vm. new_value_error ( "server_hostname cannot start with a dot" ) ) ;
18931941 }
18941942
1895- // Check if it's a bare IP address (not allowed for SNI)
1896- if hostname. parse :: < std:: net:: IpAddr > ( ) . is_ok ( ) {
1897- return Err ( vm. new_value_error ( "server_hostname cannot be an IP address" ) ) ;
1898- }
1943+ // IP addresses are allowed
1944+ // SNI will not be sent for IP addresses
18991945
19001946 // Check for NULL bytes
19011947 if hostname. contains ( '\0' ) {
@@ -3393,44 +3439,56 @@ mod _ssl {
33933439 . as_mut ( )
33943440 . ok_or_else ( || vm. new_value_error ( "Connection not established" ) ) ?;
33953441
3396- // Unified write logic - no need to match on Client/Server anymore
3397- let mut writer = conn. writer ( ) ;
3398- writer
3399- . write_all ( data_bytes. as_ref ( ) )
3400- . map_err ( |e| vm. new_os_error ( format ! ( "Write failed: {e}" ) ) ) ?;
3442+ let is_bio = self . is_bio_mode ( ) ;
3443+ let data: & [ u8 ] = data_bytes. as_ref ( ) ;
34013444
3402- // Flush to get TLS-encrypted data (writer automatically flushed on drop)
3403- // Send encrypted data to socket
3404- if conn . wants_write ( ) {
3405- let is_bio = self . is_bio_mode ( ) ;
3445+ // Write data in chunks to avoid filling the internal TLS buffer
3446+ // rustls has a limited internal buffer, so we need to flush periodically
3447+ const CHUNK_SIZE : usize = 16384 ; // 16KB chunks (typical TLS record size)
3448+ let mut written = 0 ;
34063449
3407- if is_bio {
3408- // BIO mode: Write ALL pending TLS data to outgoing BIO
3409- // This prevents hangs where Python's ssl_io_loop waits for data
3410- self . write_pending_tls ( conn, vm) ?;
3411- } else {
3412- // Socket mode: Try once and may return SSLWantWriteError
3413- let mut buf = Vec :: new ( ) ;
3414- conn. write_tls ( & mut buf)
3415- . map_err ( |e| vm. new_os_error ( format ! ( "TLS write failed: {e}" ) ) ) ?;
3416-
3417- if !buf. is_empty ( ) {
3418- // Wait for socket to be ready for writing
3419- let timed_out = self . sock_wait_for_io_impl ( SelectKind :: Write , vm) ?;
3420- if timed_out {
3421- return Err ( vm. new_os_error ( "Write operation timed out" ) ) ;
3422- }
3450+ while written < data. len ( ) {
3451+ let chunk_end = std:: cmp:: min ( written + CHUNK_SIZE , data. len ( ) ) ;
3452+ let chunk = & data[ written..chunk_end] ;
3453+
3454+ // Write chunk to TLS layer
3455+ {
3456+ let mut writer = conn. writer ( ) ;
3457+ use std:: io:: Write ;
3458+ writer
3459+ . write_all ( chunk)
3460+ . map_err ( |e| vm. new_os_error ( format ! ( "Write failed: {e}" ) ) ) ?;
3461+ }
3462+
3463+ written = chunk_end;
34233464
3424- // Send encrypted data to socket
3425- // Convert BlockingIOError to SSLWantWriteError
3426- match self . sock_send ( buf, vm) {
3427- Ok ( _) => { }
3428- Err ( e) => {
3429- if is_blocking_io_error ( & e, vm) {
3430- // Non-blocking socket would block - return SSLWantWriteError
3431- return Err ( create_ssl_want_write_error ( vm) ) ;
3465+ // Flush TLS data to socket after each chunk
3466+ if conn. wants_write ( ) {
3467+ if is_bio {
3468+ self . write_pending_tls ( conn, vm) ?;
3469+ } else {
3470+ // Socket mode: flush all pending TLS data
3471+ while conn. wants_write ( ) {
3472+ let mut buf = Vec :: new ( ) ;
3473+ conn. write_tls ( & mut buf)
3474+ . map_err ( |e| vm. new_os_error ( format ! ( "TLS write failed: {e}" ) ) ) ?;
3475+
3476+ if !buf. is_empty ( ) {
3477+ let timed_out =
3478+ self . sock_wait_for_io_impl ( SelectKind :: Write , vm) ?;
3479+ if timed_out {
3480+ return Err ( vm. new_os_error ( "Write operation timed out" ) ) ;
3481+ }
3482+
3483+ match self . sock_send ( buf, vm) {
3484+ Ok ( _) => { }
3485+ Err ( e) => {
3486+ if is_blocking_io_error ( & e, vm) {
3487+ return Err ( create_ssl_want_write_error ( vm) ) ;
3488+ }
3489+ return Err ( e) ;
3490+ }
34323491 }
3433- return Err ( e) ;
34343492 }
34353493 }
34363494 }
@@ -4284,7 +4342,14 @@ mod _ssl {
42844342 ( Some ( "/etc/ssl/cert.pem" ) , Some ( "/etc/ssl/certs" ) )
42854343 } ;
42864344
4287- #[ cfg( not( any( target_os = "macos" , target_os = "linux" ) ) ) ]
4345+ #[ cfg( windows) ]
4346+ let ( default_cafile, default_capath) = {
4347+ // Windows uses certificate store, not file paths
4348+ // Return empty strings to avoid None being passed to os.path.isfile()
4349+ ( Some ( "" ) , Some ( "" ) )
4350+ } ;
4351+
4352+ #[ cfg( not( any( target_os = "macos" , target_os = "linux" , windows) ) ) ]
42884353 let ( default_cafile, default_capath) : ( Option < & str > , Option < & str > ) = ( None , None ) ;
42894354
42904355 let tuple = vm. ctx . new_tuple ( vec ! [
@@ -4397,6 +4462,111 @@ mod _ssl {
43974462 }
43984463 }
43994464
4465+ // Windows-specific certificate store enumeration functions
4466+ #[ cfg( windows) ]
4467+ #[ pyfunction]
4468+ fn enum_certificates ( store_name : PyStrRef , vm : & VirtualMachine ) -> PyResult < Vec < PyObjectRef > > {
4469+ use schannel:: { RawPointer , cert_context:: ValidUses , cert_store:: CertStore } ;
4470+ use windows_sys:: Win32 :: Security :: Cryptography ;
4471+
4472+ // Try both Current User and Local Machine stores
4473+ let open_fns = [ CertStore :: open_current_user, CertStore :: open_local_machine] ;
4474+ let stores = open_fns
4475+ . iter ( )
4476+ . filter_map ( |open| open ( store_name. as_str ( ) ) . ok ( ) )
4477+ . collect :: < Vec < _ > > ( ) ;
4478+
4479+ // If no stores could be opened, raise OSError
4480+ if stores. is_empty ( ) {
4481+ return Err ( vm. new_os_error ( format ! (
4482+ "failed to open certificate store {:?}" ,
4483+ store_name. as_str( )
4484+ ) ) ) ;
4485+ }
4486+
4487+ let certs = stores. iter ( ) . flat_map ( |s| s. certs ( ) ) . map ( |c| {
4488+ let cert = vm. ctx . new_bytes ( c. to_der ( ) . to_owned ( ) ) ;
4489+ let enc_type = unsafe {
4490+ let ptr = c. as_ptr ( ) as * const Cryptography :: CERT_CONTEXT ;
4491+ ( * ptr) . dwCertEncodingType
4492+ } ;
4493+ let enc_type = match enc_type {
4494+ Cryptography :: X509_ASN_ENCODING => vm. new_pyobj ( "x509_asn" ) ,
4495+ Cryptography :: PKCS_7_ASN_ENCODING => vm. new_pyobj ( "pkcs_7_asn" ) ,
4496+ other => vm. new_pyobj ( other) ,
4497+ } ;
4498+ let usage: PyObjectRef = match c. valid_uses ( ) {
4499+ Ok ( ValidUses :: All ) => vm. ctx . new_bool ( true ) . into ( ) ,
4500+ Ok ( ValidUses :: Oids ( oids) ) => {
4501+ match crate :: builtins:: PyFrozenSet :: from_iter (
4502+ vm,
4503+ oids. into_iter ( ) . map ( |oid| vm. ctx . new_str ( oid) . into ( ) ) ,
4504+ ) {
4505+ Ok ( set) => set. into_ref ( & vm. ctx ) . into ( ) ,
4506+ Err ( _) => vm. ctx . new_bool ( true ) . into ( ) ,
4507+ }
4508+ }
4509+ Err ( _) => vm. ctx . new_bool ( true ) . into ( ) ,
4510+ } ;
4511+ Ok ( vm. new_tuple ( ( cert, enc_type, usage) ) . into ( ) )
4512+ } ) ;
4513+ certs. collect :: < PyResult < Vec < _ > > > ( )
4514+ }
4515+
4516+ #[ cfg( windows) ]
4517+ #[ pyfunction]
4518+ fn enum_crls ( store_name : PyStrRef , vm : & VirtualMachine ) -> PyResult < Vec < PyObjectRef > > {
4519+ use windows_sys:: Win32 :: Security :: Cryptography :: {
4520+ CRL_CONTEXT , CertCloseStore , CertEnumCRLsInStore , CertOpenSystemStoreW ,
4521+ X509_ASN_ENCODING ,
4522+ } ;
4523+
4524+ let store_name_wide: Vec < u16 > = store_name
4525+ . as_str ( )
4526+ . encode_utf16 ( )
4527+ . chain ( std:: iter:: once ( 0 ) )
4528+ . collect ( ) ;
4529+
4530+ // Open system store
4531+ let store = unsafe { CertOpenSystemStoreW ( 0 , store_name_wide. as_ptr ( ) ) } ;
4532+
4533+ if store. is_null ( ) {
4534+ return Err ( vm. new_os_error ( format ! (
4535+ "failed to open certificate store {:?}" ,
4536+ store_name. as_str( )
4537+ ) ) ) ;
4538+ }
4539+
4540+ let mut result = Vec :: new ( ) ;
4541+
4542+ let mut crl_context: * const CRL_CONTEXT = std:: ptr:: null ( ) ;
4543+ loop {
4544+ crl_context = unsafe { CertEnumCRLsInStore ( store, crl_context) } ;
4545+ if crl_context. is_null ( ) {
4546+ break ;
4547+ }
4548+
4549+ let crl = unsafe { & * crl_context } ;
4550+ let crl_bytes =
4551+ unsafe { std:: slice:: from_raw_parts ( crl. pbCrlEncoded , crl. cbCrlEncoded as usize ) } ;
4552+
4553+ let enc_type = if crl. dwCertEncodingType == X509_ASN_ENCODING {
4554+ vm. new_pyobj ( "x509_asn" )
4555+ } else {
4556+ vm. new_pyobj ( crl. dwCertEncodingType )
4557+ } ;
4558+
4559+ result. push (
4560+ vm. new_tuple ( ( vm. ctx . new_bytes ( crl_bytes. to_vec ( ) ) , enc_type) )
4561+ . into ( ) ,
4562+ ) ;
4563+ }
4564+
4565+ unsafe { CertCloseStore ( store, 0 ) } ;
4566+
4567+ Ok ( result)
4568+ }
4569+
44004570 // Certificate type for SSL module (pure Rust implementation)
44014571 #[ pyattr]
44024572 #[ pyclass( module = "_ssl" , name = "Certificate" ) ]
0 commit comments