11// spell-checker:disable
22
3+ mod cert;
4+
35use crate :: vm:: { PyRef , VirtualMachine , builtins:: PyModule } ;
46use openssl_probe:: ProbeResult ;
57
@@ -26,15 +28,12 @@ cfg_if::cfg_if! {
2628}
2729
2830#[ allow( non_upper_case_globals) ]
29- #[ pymodule( with( ossl101, ossl111, windows) ) ]
31+ #[ pymodule( with( cert :: ssl_cert , ossl101, ossl111, windows) ) ]
3032mod _ssl {
3133 use super :: { bio, probe} ;
3234 use crate :: {
33- common:: {
34- ascii,
35- lock:: {
36- PyMappedRwLockReadGuard , PyMutex , PyRwLock , PyRwLockReadGuard , PyRwLockWriteGuard ,
37- } ,
35+ common:: lock:: {
36+ PyMappedRwLockReadGuard , PyMutex , PyRwLock , PyRwLockReadGuard , PyRwLockWriteGuard ,
3837 } ,
3938 socket:: { self , PySocket } ,
4039 vm:: {
@@ -43,7 +42,7 @@ mod _ssl {
4342 PyBaseExceptionRef , PyBytesRef , PyListRef , PyOSError , PyStrRef , PyTypeRef , PyWeak ,
4443 } ,
4544 class_or_notimplemented,
46- convert:: { ToPyException , ToPyObject } ,
45+ convert:: ToPyException ,
4746 exceptions,
4847 function:: {
4948 ArgBytesLike , ArgCallable , ArgMemoryBuffer , ArgStrOrBytesLike , Either , FsPath ,
@@ -60,7 +59,7 @@ mod _ssl {
6059 error:: ErrorStack ,
6160 nid:: Nid ,
6261 ssl:: { self , SslContextBuilder , SslOptions , SslVerifyMode } ,
63- x509:: { self , X509 , X509Ref } ,
62+ x509:: X509 ,
6463 } ;
6564 use openssl_sys as sys;
6665 use rustpython_vm:: ospath:: OsPath ;
@@ -73,6 +72,14 @@ mod _ssl {
7372 time:: Instant ,
7473 } ;
7574
75+ // Import certificate types from parent module
76+ use super :: cert:: { self , cert_to_certificate, cert_to_py} ;
77+
78+ // Re-export PySSLCertificate to make it available in the _ssl module
79+ // It will be automatically exposed to Python via #[pyclass]
80+ #[ allow( unused_imports) ]
81+ use super :: cert:: PySSLCertificate ;
82+
7683 // Constants
7784 #[ pyattr]
7885 use sys:: {
@@ -178,6 +185,15 @@ mod _ssl {
178185 #[ pyattr]
179186 const HAS_PSK : bool = true ;
180187
188+ // Encoding constants for Certificate.public_bytes()
189+ // CPython: cpython/Modules/_ssl.h:62-66
190+ #[ pyattr]
191+ pub ( crate ) const ENCODING_PEM : i32 = sys:: X509_FILETYPE_PEM ;
192+ #[ pyattr]
193+ pub ( crate ) const ENCODING_DER : i32 = sys:: X509_FILETYPE_ASN1 ;
194+ #[ pyattr]
195+ const ENCODING_PEM_AUX : i32 = sys:: X509_FILETYPE_PEM + 0x100 ;
196+
181197 // the openssl version from the API headers
182198
183199 #[ pyattr( name = "OPENSSL_VERSION" ) ]
@@ -349,32 +365,6 @@ mod _ssl {
349365 fn _nid2obj ( nid : Nid ) -> Option < Asn1Object > {
350366 unsafe { ptr2obj ( sys:: OBJ_nid2obj ( nid. as_raw ( ) ) ) }
351367 }
352- fn obj2txt ( obj : & Asn1ObjectRef , no_name : bool ) -> Option < String > {
353- let no_name = i32:: from ( no_name) ;
354- let ptr = obj. as_ptr ( ) ;
355- let b = unsafe {
356- let buflen = sys:: OBJ_obj2txt ( std:: ptr:: null_mut ( ) , 0 , ptr, no_name) ;
357- assert ! ( buflen >= 0 ) ;
358- if buflen == 0 {
359- return None ;
360- }
361- let buflen = buflen as usize ;
362- let mut buf = Vec :: < u8 > :: with_capacity ( buflen + 1 ) ;
363- let ret = sys:: OBJ_obj2txt (
364- buf. as_mut_ptr ( ) as * mut libc:: c_char ,
365- buf. capacity ( ) as _ ,
366- ptr,
367- no_name,
368- ) ;
369- assert ! ( ret >= 0 ) ;
370- // SAFETY: OBJ_obj2txt initialized the buffer successfully
371- buf. set_len ( buflen) ;
372- buf
373- } ;
374- let s = String :: from_utf8 ( b)
375- . unwrap_or_else ( |e| String :: from_utf8_lossy ( e. as_bytes ( ) ) . into_owned ( ) ) ;
376- Some ( s)
377- }
378368
379369 type PyNid = ( libc:: c_int , String , String , Option < String > ) ;
380370 fn obj2py ( obj : & Asn1ObjectRef , vm : & VirtualMachine ) -> PyResult < PyNid > {
@@ -387,7 +377,12 @@ mod _ssl {
387377 . long_name ( )
388378 . map_err ( |_| vm. new_value_error ( "NID has no long name" . to_owned ( ) ) ) ?
389379 . to_owned ( ) ;
390- Ok ( ( nid. as_raw ( ) , short_name, long_name, obj2txt ( obj, true ) ) )
380+ Ok ( (
381+ nid. as_raw ( ) ,
382+ short_name,
383+ long_name,
384+ cert:: obj2txt ( obj, true ) ,
385+ ) )
391386 }
392387
393388 #[ derive( FromArgs ) ]
@@ -1223,9 +1218,18 @@ mod _ssl {
12231218 let stream = self . stream . read ( ) ;
12241219 let chain = stream. ssl ( ) . peer_cert_chain ( ) ?;
12251220
1221+ // Return Certificate objects (CPython: _PySSL_CertificateFromX509Stack)
12261222 let certs: Vec < PyObjectRef > = chain
12271223 . iter ( )
1228- . filter_map ( |cert| cert. to_der ( ) . ok ( ) . map ( |der| vm. ctx . new_bytes ( der) . into ( ) ) )
1224+ . filter_map ( |cert| {
1225+ // Clone the X509 certificate to create an owned copy
1226+ // This is equivalent to CPython's X509_up_ref
1227+ unsafe {
1228+ sys:: X509_up_ref ( cert. as_ptr ( ) ) ;
1229+ let owned_cert = X509 :: from_ptr ( cert. as_ptr ( ) ) ;
1230+ cert_to_certificate ( vm, owned_cert) . ok ( )
1231+ }
1232+ } )
12291233 . collect ( ) ;
12301234
12311235 Some ( vm. ctx . new_list ( certs) . into ( ) )
@@ -1243,14 +1247,17 @@ mod _ssl {
12431247 let num_certs = sys:: OPENSSL_sk_num ( chain as * const _ ) ;
12441248 let mut certs = Vec :: new ( ) ;
12451249
1250+ // Return Certificate objects (CPython: _PySSL_CertificateFromX509Stack)
12461251 for i in 0 ..num_certs {
12471252 let cert_ptr = sys:: OPENSSL_sk_value ( chain as * const _ , i) as * mut sys:: X509 ;
12481253 if cert_ptr. is_null ( ) {
12491254 continue ;
12501255 }
1251- let cert = X509Ref :: from_ptr ( cert_ptr) ;
1252- if let Ok ( der) = cert. to_der ( ) {
1253- certs. push ( vm. ctx . new_bytes ( der) . into ( ) ) ;
1256+ // Clone the X509 certificate to create an owned copy
1257+ sys:: X509_up_ref ( cert_ptr) ;
1258+ let owned_cert = X509 :: from_ptr ( cert_ptr) ;
1259+ if let Ok ( cert_obj) = cert_to_certificate ( vm, owned_cert) {
1260+ certs. push ( cert_obj) ;
12541261 }
12551262 }
12561263
@@ -1978,7 +1985,10 @@ mod _ssl {
19781985 }
19791986
19801987 #[ track_caller]
1981- fn convert_openssl_error ( vm : & VirtualMachine , err : ErrorStack ) -> PyBaseExceptionRef {
1988+ pub ( crate ) fn convert_openssl_error (
1989+ vm : & VirtualMachine ,
1990+ err : ErrorStack ,
1991+ ) -> PyBaseExceptionRef {
19821992 let cls = PySslError :: class ( & vm. ctx ) . to_owned ( ) ;
19831993 match err. errors ( ) . last ( ) {
19841994 Some ( e) => {
@@ -2047,18 +2057,51 @@ mod _ssl {
20472057 ) ,
20482058 ssl:: ErrorCode :: SYSCALL => match e. io_error ( ) {
20492059 Some ( io_err) => return io_err. to_pyexception ( vm) ,
2050- None => (
2051- PySslSyscallError :: class ( & vm. ctx ) . to_owned ( ) ,
2052- "EOF occurred in violation of protocol" ,
2053- ) ,
2060+ // CPython: When no I/O error and OpenSSL error queue is empty,
2061+ // this is an EOF in violation of protocol -> SSLEOFError
2062+ // Need to set args[0] = SSL_ERROR_EOF for suppress_ragged_eofs check
2063+ None => {
2064+ return vm. new_exception (
2065+ PySslSyscallError :: class ( & vm. ctx ) . to_owned ( ) ,
2066+ vec ! [
2067+ vm. ctx. new_int( SSL_ERROR_EOF ) . into( ) ,
2068+ vm. ctx
2069+ . new_str( "EOF occurred in violation of protocol" )
2070+ . into( ) ,
2071+ ] ,
2072+ ) ;
2073+ }
20542074 } ,
2055- ssl:: ErrorCode :: SSL => match e. ssl_error ( ) {
2056- Some ( e) => return convert_openssl_error ( vm, e. clone ( ) ) ,
2057- None => (
2075+ ssl:: ErrorCode :: SSL => {
2076+ // Check for OpenSSL 3.0 SSL_R_UNEXPECTED_EOF_WHILE_READING
2077+ if let Some ( ssl_err) = e. ssl_error ( ) {
2078+ // SSL_R_UNEXPECTED_EOF_WHILE_READING = 294 (0x126)
2079+ // In OpenSSL 3.0+, unexpected EOF is reported as SSL_ERROR_SSL
2080+ // with this specific reason code instead of SSL_ERROR_SYSCALL
2081+ unsafe {
2082+ let err_code = sys:: ERR_peek_last_error ( ) ;
2083+ let reason = sys:: ERR_GET_REASON ( err_code) ;
2084+ let lib = sys:: ERR_GET_LIB ( err_code) ;
2085+ // ERR_LIB_SSL = 20, SSL_R_UNEXPECTED_EOF_WHILE_READING = 294
2086+ if lib == 20 && reason == 294 {
2087+ return vm. new_exception (
2088+ vm. class ( "_ssl" , "SSLEOFError" ) ,
2089+ vec ! [
2090+ vm. ctx. new_int( SSL_ERROR_EOF ) . into( ) ,
2091+ vm. ctx
2092+ . new_str( "EOF occurred in violation of protocol" )
2093+ . into( ) ,
2094+ ] ,
2095+ ) ;
2096+ }
2097+ }
2098+ return convert_openssl_error ( vm, ssl_err. clone ( ) ) ;
2099+ }
2100+ (
20582101 PySslError :: class ( & vm. ctx ) . to_owned ( ) ,
20592102 "A failure in the SSL library occurred" ,
2060- ) ,
2061- } ,
2103+ )
2104+ }
20622105 _ => (
20632106 PySslError :: class ( & vm. ctx ) . to_owned ( ) ,
20642107 "A failure in the SSL library occurred" ,
@@ -2106,93 +2149,6 @@ mod _ssl {
21062149 ( cipher. name ( ) , cipher. version ( ) , cipher. bits ( ) . secret )
21072150 }
21082151
2109- fn cert_to_py ( vm : & VirtualMachine , cert : & X509Ref , binary : bool ) -> PyResult {
2110- let r = if binary {
2111- let b = cert. to_der ( ) . map_err ( |e| convert_openssl_error ( vm, e) ) ?;
2112- vm. ctx . new_bytes ( b) . into ( )
2113- } else {
2114- let dict = vm. ctx . new_dict ( ) ;
2115-
2116- let name_to_py = |name : & x509:: X509NameRef | -> PyResult {
2117- let list = name
2118- . entries ( )
2119- . map ( |entry| {
2120- let txt = obj2txt ( entry. object ( ) , false ) . to_pyobject ( vm) ;
2121- let data = vm. ctx . new_str ( entry. data ( ) . as_utf8 ( ) ?. to_owned ( ) ) ;
2122- Ok ( vm. new_tuple ( ( ( txt, data) , ) ) . into ( ) )
2123- } )
2124- . collect :: < Result < _ , _ > > ( )
2125- . map_err ( |e| convert_openssl_error ( vm, e) ) ?;
2126- Ok ( vm. ctx . new_tuple ( list) . into ( ) )
2127- } ;
2128-
2129- dict. set_item ( "subject" , name_to_py ( cert. subject_name ( ) ) ?, vm) ?;
2130- dict. set_item ( "issuer" , name_to_py ( cert. issuer_name ( ) ) ?, vm) ?;
2131- // X.509 version: OpenSSL uses 0-based (0=v1, 1=v2, 2=v3) but Python uses 1-based (1=v1, 2=v2, 3=v3)
2132- dict. set_item ( "version" , vm. new_pyobj ( cert. version ( ) + 1 ) , vm) ?;
2133-
2134- let serial_num = cert
2135- . serial_number ( )
2136- . to_bn ( )
2137- . and_then ( |bn| bn. to_hex_str ( ) )
2138- . map_err ( |e| convert_openssl_error ( vm, e) ) ?;
2139- dict. set_item (
2140- "serialNumber" ,
2141- vm. ctx . new_str ( serial_num. to_owned ( ) ) . into ( ) ,
2142- vm,
2143- ) ?;
2144-
2145- dict. set_item (
2146- "notBefore" ,
2147- vm. ctx . new_str ( cert. not_before ( ) . to_string ( ) ) . into ( ) ,
2148- vm,
2149- ) ?;
2150- dict. set_item (
2151- "notAfter" ,
2152- vm. ctx . new_str ( cert. not_after ( ) . to_string ( ) ) . into ( ) ,
2153- vm,
2154- ) ?;
2155-
2156- #[ allow( clippy:: manual_map) ]
2157- if let Some ( names) = cert. subject_alt_names ( ) {
2158- let san = names
2159- . iter ( )
2160- . filter_map ( |gen_name| {
2161- if let Some ( email) = gen_name. email ( ) {
2162- Some ( vm. new_tuple ( ( ascii ! ( "email" ) , email) ) . into ( ) )
2163- } else if let Some ( dnsname) = gen_name. dnsname ( ) {
2164- Some ( vm. new_tuple ( ( ascii ! ( "DNS" ) , dnsname) ) . into ( ) )
2165- } else if let Some ( ip) = gen_name. ipaddress ( ) {
2166- Some (
2167- vm. new_tuple ( (
2168- ascii ! ( "IP Address" ) ,
2169- String :: from_utf8_lossy ( ip) . into_owned ( ) ,
2170- ) )
2171- . into ( ) ,
2172- )
2173- } else {
2174- // TODO: convert every type of general name:
2175- // https://github.com/python/cpython/blob/3.6/Modules/_ssl.c#L1092-L1231
2176- None
2177- }
2178- } )
2179- . collect ( ) ;
2180- dict. set_item ( "subjectAltName" , vm. ctx . new_tuple ( san) . into ( ) , vm) ?;
2181- } ;
2182-
2183- dict. into ( )
2184- } ;
2185- Ok ( r)
2186- }
2187-
2188- #[ pyfunction]
2189- fn _test_decode_cert ( path : FsPath , vm : & VirtualMachine ) -> PyResult {
2190- let path = path. to_path_buf ( vm) ?;
2191- let pem = std:: fs:: read ( path) . map_err ( |e| e. to_pyexception ( vm) ) ?;
2192- let x509 = X509 :: from_pem ( & pem) . map_err ( |e| convert_openssl_error ( vm, e) ) ?;
2193- cert_to_py ( vm, & x509, false )
2194- }
2195-
21962152 impl Read for SocketStream {
21972153 fn read ( & mut self , buf : & mut [ u8 ] ) -> std:: io:: Result < usize > {
21982154 let mut socket: & PySocket = & self . 0 ;
0 commit comments