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,18 @@ mod _ssl {
178185 #[ pyattr]
179186 const HAS_PSK : bool = true ;
180187
188+ // Encoding constants for Certificate.public_bytes()
189+ #[ pyattr]
190+ pub ( crate ) const ENCODING_PEM : i32 = sys:: X509_FILETYPE_PEM ;
191+ #[ pyattr]
192+ pub ( crate ) const ENCODING_DER : i32 = sys:: X509_FILETYPE_ASN1 ;
193+ #[ pyattr]
194+ const ENCODING_PEM_AUX : i32 = sys:: X509_FILETYPE_PEM + 0x100 ;
195+
196+ // OpenSSL error codes for unexpected EOF detection
197+ const ERR_LIB_SSL : i32 = 20 ;
198+ const SSL_R_UNEXPECTED_EOF_WHILE_READING : i32 = 294 ;
199+
181200 // the openssl version from the API headers
182201
183202 #[ pyattr( name = "OPENSSL_VERSION" ) ]
@@ -349,32 +368,6 @@ mod _ssl {
349368 fn _nid2obj ( nid : Nid ) -> Option < Asn1Object > {
350369 unsafe { ptr2obj ( sys:: OBJ_nid2obj ( nid. as_raw ( ) ) ) }
351370 }
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- }
378371
379372 type PyNid = ( libc:: c_int , String , String , Option < String > ) ;
380373 fn obj2py ( obj : & Asn1ObjectRef , vm : & VirtualMachine ) -> PyResult < PyNid > {
@@ -387,7 +380,12 @@ mod _ssl {
387380 . long_name ( )
388381 . map_err ( |_| vm. new_value_error ( "NID has no long name" . to_owned ( ) ) ) ?
389382 . to_owned ( ) ;
390- Ok ( ( nid. as_raw ( ) , short_name, long_name, obj2txt ( obj, true ) ) )
383+ Ok ( (
384+ nid. as_raw ( ) ,
385+ short_name,
386+ long_name,
387+ cert:: obj2txt ( obj, true ) ,
388+ ) )
391389 }
392390
393391 #[ derive( FromArgs ) ]
@@ -1219,46 +1217,54 @@ mod _ssl {
12191217 }
12201218
12211219 #[ pymethod]
1222- fn get_unverified_chain ( & self , vm : & VirtualMachine ) -> Option < PyObjectRef > {
1220+ fn get_unverified_chain ( & self , vm : & VirtualMachine ) -> PyResult < Option < PyListRef > > {
12231221 let stream = self . stream . read ( ) ;
1224- let chain = stream. ssl ( ) . peer_cert_chain ( ) ?;
1222+ let Some ( chain) = stream. ssl ( ) . peer_cert_chain ( ) else {
1223+ return Ok ( None ) ;
1224+ } ;
12251225
1226+ // Return Certificate objects
12261227 let certs: Vec < PyObjectRef > = chain
12271228 . iter ( )
1228- . filter_map ( |cert| cert. to_der ( ) . ok ( ) . map ( |der| vm. ctx . new_bytes ( der) . into ( ) ) )
1229- . collect ( ) ;
1230-
1231- Some ( vm. ctx . new_list ( certs) . into ( ) )
1229+ . map ( |cert| unsafe {
1230+ sys:: X509_up_ref ( cert. as_ptr ( ) ) ;
1231+ let owned = X509 :: from_ptr ( cert. as_ptr ( ) ) ;
1232+ cert_to_certificate ( vm, owned)
1233+ } )
1234+ . collect :: < PyResult < _ > > ( ) ?;
1235+ Ok ( Some ( vm. ctx . new_list ( certs) ) )
12321236 }
12331237
12341238 #[ pymethod]
1235- fn get_verified_chain ( & self , vm : & VirtualMachine ) -> Option < PyListRef > {
1239+ fn get_verified_chain ( & self , vm : & VirtualMachine ) -> PyResult < Option < PyListRef > > {
12361240 let stream = self . stream . read ( ) ;
12371241 unsafe {
12381242 let chain = sys:: SSL_get0_verified_chain ( stream. ssl ( ) . as_ptr ( ) ) ;
12391243 if chain. is_null ( ) {
1240- return None ;
1244+ return Ok ( None ) ;
12411245 }
12421246
12431247 let num_certs = sys:: OPENSSL_sk_num ( chain as * const _ ) ;
1244- let mut certs = Vec :: new ( ) ;
12451248
1249+ let mut certs = Vec :: with_capacity ( num_certs as usize ) ;
1250+ // Return Certificate objects
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 ( ) ) ;
1254- }
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+ let cert_obj = cert_to_certificate ( vm, owned_cert) ?;
1260+ certs. push ( cert_obj) ;
12551261 }
12561262
1257- if certs. is_empty ( ) {
1263+ Ok ( if certs. is_empty ( ) {
12581264 None
12591265 } else {
12601266 Some ( vm. ctx . new_list ( certs) )
1261- }
1267+ } )
12621268 }
12631269 }
12641270
@@ -1978,7 +1984,10 @@ mod _ssl {
19781984 }
19791985
19801986 #[ track_caller]
1981- fn convert_openssl_error ( vm : & VirtualMachine , err : ErrorStack ) -> PyBaseExceptionRef {
1987+ pub ( crate ) fn convert_openssl_error (
1988+ vm : & VirtualMachine ,
1989+ err : ErrorStack ,
1990+ ) -> PyBaseExceptionRef {
19821991 let cls = PySslError :: class ( & vm. ctx ) . to_owned ( ) ;
19831992 match err. errors ( ) . last ( ) {
19841993 Some ( e) => {
@@ -2047,18 +2056,49 @@ mod _ssl {
20472056 ) ,
20482057 ssl:: ErrorCode :: SYSCALL => match e. io_error ( ) {
20492058 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- ) ,
2059+ // When no I/O error and OpenSSL error queue is empty,
2060+ // this is an EOF in violation of protocol -> SSLEOFError
2061+ // Need to set args[0] = SSL_ERROR_EOF for suppress_ragged_eofs check
2062+ None => {
2063+ return vm. new_exception (
2064+ PySslEOFError :: class ( & vm. ctx ) . to_owned ( ) ,
2065+ vec ! [
2066+ vm. ctx. new_int( SSL_ERROR_EOF ) . into( ) ,
2067+ vm. ctx
2068+ . new_str( "EOF occurred in violation of protocol" )
2069+ . into( ) ,
2070+ ] ,
2071+ ) ;
2072+ }
20542073 } ,
2055- ssl:: ErrorCode :: SSL => match e. ssl_error ( ) {
2056- Some ( e) => return convert_openssl_error ( vm, e. clone ( ) ) ,
2057- None => (
2074+ ssl:: ErrorCode :: SSL => {
2075+ // Check for OpenSSL 3.0 SSL_R_UNEXPECTED_EOF_WHILE_READING
2076+ if let Some ( ssl_err) = e. ssl_error ( ) {
2077+ // In OpenSSL 3.0+, unexpected EOF is reported as SSL_ERROR_SSL
2078+ // with this specific reason code instead of SSL_ERROR_SYSCALL
2079+ unsafe {
2080+ let err_code = sys:: ERR_peek_last_error ( ) ;
2081+ let reason = sys:: ERR_GET_REASON ( err_code) ;
2082+ let lib = sys:: ERR_GET_LIB ( err_code) ;
2083+ if lib == ERR_LIB_SSL && reason == SSL_R_UNEXPECTED_EOF_WHILE_READING {
2084+ return vm. new_exception (
2085+ vm. class ( "_ssl" , "SSLEOFError" ) ,
2086+ vec ! [
2087+ vm. ctx. new_int( SSL_ERROR_EOF ) . into( ) ,
2088+ vm. ctx
2089+ . new_str( "EOF occurred in violation of protocol" )
2090+ . into( ) ,
2091+ ] ,
2092+ ) ;
2093+ }
2094+ }
2095+ return convert_openssl_error ( vm, ssl_err. clone ( ) ) ;
2096+ }
2097+ (
20582098 PySslError :: class ( & vm. ctx ) . to_owned ( ) ,
20592099 "A failure in the SSL library occurred" ,
2060- ) ,
2061- } ,
2100+ )
2101+ }
20622102 _ => (
20632103 PySslError :: class ( & vm. ctx ) . to_owned ( ) ,
20642104 "A failure in the SSL library occurred" ,
@@ -2106,93 +2146,6 @@ mod _ssl {
21062146 ( cipher. name ( ) , cipher. version ( ) , cipher. bits ( ) . secret )
21072147 }
21082148
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-
21962149 impl Read for SocketStream {
21972150 fn read ( & mut self , buf : & mut [ u8 ] ) -> std:: io:: Result < usize > {
21982151 let mut socket: & PySocket = & self . 0 ;
0 commit comments