@@ -167,6 +167,291 @@ static void php_openssl_pkey_free_obj(zend_object *object)
167167 zend_object_std_dtor (& key_object -> std );
168168}
169169
170+ /* OpenSSLSession class */
171+
172+ zend_class_entry * php_openssl_session_ce ;
173+
174+ static zend_object_handlers php_openssl_session_object_handlers ;
175+
176+ bool php_openssl_is_session_ce (zval * val )
177+ {
178+ return Z_TYPE_P (val ) == IS_OBJECT && Z_OBJCE_P (val ) == php_openssl_session_ce ;
179+ }
180+
181+ SSL_SESSION * php_openssl_session_from_zval (zval * zv )
182+ {
183+ if (!php_openssl_is_session_ce (zv )) {
184+ return NULL ;
185+ }
186+ return Z_OPENSSL_SESSION_P (zv )-> session ;
187+ }
188+
189+ void php_openssl_session_object_init (zval * zv , SSL_SESSION * session )
190+ {
191+ object_init_ex (zv , php_openssl_session_ce );
192+ php_openssl_session_object * obj = Z_OPENSSL_SESSION_P (zv );
193+ obj -> session = session ;
194+
195+ unsigned int id_len = 0 ;
196+ const unsigned char * id = SSL_SESSION_get_id (session , & id_len );
197+ zend_update_property_stringl (php_openssl_session_ce , Z_OBJ_P (zv ),
198+ ZEND_STRL ("id" ), (char * )id , id_len );
199+ }
200+
201+ static zend_object * php_openssl_session_create_object (zend_class_entry * class_type )
202+ {
203+ php_openssl_session_object * intern = zend_object_alloc (sizeof (php_openssl_session_object ), class_type );
204+
205+ zend_object_std_init (& intern -> std , class_type );
206+ object_properties_init (& intern -> std , class_type );
207+
208+ return & intern -> std ;
209+ }
210+
211+ static zend_function * php_openssl_session_get_constructor (zend_object * object )
212+ {
213+ zend_throw_error (NULL ,
214+ "Cannot directly construct OpenSSLSession, use OpenSSLSession::import() or TLS session callbacks" );
215+ return NULL ;
216+ }
217+
218+ static void php_openssl_session_free_obj (zend_object * object )
219+ {
220+ php_openssl_session_object * session_object = php_openssl_session_from_obj (object );
221+
222+ if (session_object -> session ) {
223+ SSL_SESSION_free (session_object -> session );
224+ session_object -> session = NULL ;
225+ }
226+ zend_object_std_dtor (& session_object -> std );
227+ }
228+
229+ #define PHP_OPENSSL_SESSION_CHECK () \
230+ php_openssl_session_object *obj = Z_OPENSSL_SESSION_P(ZEND_THIS); \
231+ if (!obj->session) { \
232+ zend_throw_exception(zend_ce_exception, "Session is not valid", 0); \
233+ RETURN_THROWS(); \
234+ }
235+
236+ PHP_METHOD (OpenSSLSession , export )
237+ {
238+ zend_long format = ENCODING_DER ;
239+
240+ ZEND_PARSE_PARAMETERS_START (0 , 1 )
241+ Z_PARAM_OPTIONAL
242+ Z_PARAM_LONG (format )
243+ ZEND_PARSE_PARAMETERS_END ();
244+
245+ PHP_OPENSSL_SESSION_CHECK ();
246+
247+ if (format == ENCODING_DER ) {
248+ int len = i2d_SSL_SESSION (obj -> session , NULL );
249+ if (len <= 0 ) {
250+ zend_throw_exception (zend_ce_exception , "Failed to export session" , 0 );
251+ RETURN_THROWS ();
252+ }
253+
254+ zend_string * result = zend_string_alloc (len , 0 );
255+ unsigned char * p = (unsigned char * )ZSTR_VAL (result );
256+ i2d_SSL_SESSION (obj -> session , & p );
257+ ZSTR_VAL (result )[len ] = '\0' ;
258+
259+ RETURN_NEW_STR (result );
260+ }
261+
262+ if (format == ENCODING_PEM ) {
263+ BIO * bio = BIO_new (BIO_s_mem ());
264+ if (!bio ) {
265+ zend_throw_exception (zend_ce_exception , "Failed to create BIO" , 0 );
266+ RETURN_THROWS ();
267+ }
268+
269+ if (!PEM_write_bio_SSL_SESSION (bio , obj -> session )) {
270+ BIO_free (bio );
271+ zend_throw_exception (zend_ce_exception , "Failed to export session as PEM" , 0 );
272+ RETURN_THROWS ();
273+ }
274+
275+ char * data ;
276+ long len = BIO_get_mem_data (bio , & data );
277+ zend_string * result = zend_string_init (data , len , 0 );
278+ BIO_free (bio );
279+
280+ RETURN_NEW_STR (result );
281+ }
282+
283+ zend_argument_value_error (1 , "must be OPENSSL_ENCODING_DER or OPENSSL_ENCODING_PEM" );
284+ RETURN_THROWS ();
285+ }
286+
287+ PHP_METHOD (OpenSSLSession , import )
288+ {
289+ zend_string * data ;
290+ zend_long format = ENCODING_DER ;
291+
292+ ZEND_PARSE_PARAMETERS_START (1 , 2 )
293+ Z_PARAM_STR (data )
294+ Z_PARAM_OPTIONAL
295+ Z_PARAM_LONG (format )
296+ ZEND_PARSE_PARAMETERS_END ();
297+
298+ SSL_SESSION * session = NULL ;
299+
300+ if (format == ENCODING_DER ) {
301+ const unsigned char * p = (const unsigned char * )ZSTR_VAL (data );
302+ session = d2i_SSL_SESSION (NULL , & p , ZSTR_LEN (data ));
303+ } else if (format == ENCODING_PEM ) {
304+ BIO * bio = BIO_new_mem_buf (ZSTR_VAL (data ), ZSTR_LEN (data ));
305+ if (bio ) {
306+ session = PEM_read_bio_SSL_SESSION (bio , NULL , NULL , NULL );
307+ BIO_free (bio );
308+ }
309+ } else {
310+ zend_argument_value_error (2 , "must be OPENSSL_ENCODING_DER or OPENSSL_ENCODING_PEM" );
311+ RETURN_THROWS ();
312+ }
313+
314+ if (!session ) {
315+ zend_throw_exception (zend_ce_exception , "Failed to import session data" , 0 );
316+ RETURN_THROWS ();
317+ }
318+
319+ php_openssl_session_object_init (return_value , session );
320+ }
321+
322+ PHP_METHOD (OpenSSLSession , isResumable )
323+ {
324+ ZEND_PARSE_PARAMETERS_NONE ();
325+ PHP_OPENSSL_SESSION_CHECK ();
326+
327+ RETURN_BOOL (SSL_SESSION_is_resumable (obj -> session ));
328+ }
329+
330+ PHP_METHOD (OpenSSLSession , getTimeout )
331+ {
332+ ZEND_PARSE_PARAMETERS_NONE ();
333+ PHP_OPENSSL_SESSION_CHECK ();
334+ RETURN_LONG ((zend_long )SSL_SESSION_get_timeout (obj -> session ));
335+ }
336+
337+ PHP_METHOD (OpenSSLSession , getCreatedAt )
338+ {
339+ ZEND_PARSE_PARAMETERS_NONE ();
340+ PHP_OPENSSL_SESSION_CHECK ();
341+ #if PHP_OPENSSL_API_VERSION >= 0x30300
342+ RETURN_LONG ((zend_long )SSL_SESSION_get_time_ex (obj -> session ));
343+ #else
344+ RETURN_LONG ((zend_long )SSL_SESSION_get_time (obj -> session ));
345+ #endif
346+ }
347+
348+ PHP_METHOD (OpenSSLSession , getProtocol )
349+ {
350+ ZEND_PARSE_PARAMETERS_NONE ();
351+ PHP_OPENSSL_SESSION_CHECK ();
352+
353+ int version = SSL_SESSION_get_protocol_version (obj -> session );
354+
355+ switch (version ) {
356+ case TLS1_3_VERSION :
357+ RETURN_STRING ("TLSv1.3" );
358+ case TLS1_2_VERSION :
359+ RETURN_STRING ("TLSv1.2" );
360+ case TLS1_1_VERSION :
361+ RETURN_STRING ("TLSv1.1" );
362+ case TLS1_VERSION :
363+ RETURN_STRING ("TLSv1.0" );
364+ default :
365+ RETURN_NULL ();
366+ }
367+ }
368+
369+ PHP_METHOD (OpenSSLSession , getCipher )
370+ {
371+ ZEND_PARSE_PARAMETERS_NONE ();
372+ PHP_OPENSSL_SESSION_CHECK ();
373+
374+ const SSL_CIPHER * cipher = SSL_SESSION_get0_cipher (obj -> session );
375+ if (!cipher ) {
376+ RETURN_NULL ();
377+ }
378+
379+ RETURN_STRING (SSL_CIPHER_get_name (cipher ));
380+ }
381+
382+ PHP_METHOD (OpenSSLSession , hasTicket )
383+ {
384+ ZEND_PARSE_PARAMETERS_NONE ();
385+ PHP_OPENSSL_SESSION_CHECK ();
386+
387+ RETURN_BOOL (SSL_SESSION_has_ticket (obj -> session ));
388+ }
389+
390+ PHP_METHOD (OpenSSLSession , getTicketLifetimeHint )
391+ {
392+ ZEND_PARSE_PARAMETERS_NONE ();
393+ PHP_OPENSSL_SESSION_CHECK ();
394+
395+ if (!SSL_SESSION_has_ticket (obj -> session )) {
396+ RETURN_NULL ();
397+ }
398+
399+ RETURN_LONG ((zend_long )SSL_SESSION_get_ticket_lifetime_hint (obj -> session ));
400+ }
401+
402+ PHP_METHOD (OpenSSLSession , __serialize )
403+ {
404+ ZEND_PARSE_PARAMETERS_NONE ();
405+
406+ PHP_OPENSSL_SESSION_CHECK ();
407+
408+ int len = i2d_SSL_SESSION (obj -> session , NULL );
409+ if (len <= 0 ) {
410+ zend_throw_exception (zend_ce_exception , "Failed to serialize session" , 0 );
411+ RETURN_THROWS ();
412+ }
413+
414+ zend_string * der = zend_string_alloc (len , 0 );
415+ unsigned char * p = (unsigned char * )ZSTR_VAL (der );
416+ i2d_SSL_SESSION (obj -> session , & p );
417+ ZSTR_VAL (der )[len ] = '\0' ;
418+
419+ array_init (return_value );
420+ add_assoc_str (return_value , "der" , der );
421+ }
422+
423+ PHP_METHOD (OpenSSLSession , __unserialize )
424+ {
425+ HashTable * data ;
426+
427+ ZEND_PARSE_PARAMETERS_START (1 , 1 )
428+ Z_PARAM_ARRAY_HT (data )
429+ ZEND_PARSE_PARAMETERS_END ();
430+
431+ zval * der_zv = zend_hash_str_find (data , ZEND_STRL ("der" ));
432+ if (!der_zv || Z_TYPE_P (der_zv ) != IS_STRING ) {
433+ zend_throw_exception (zend_ce_exception , "Invalid serialization data" , 0 );
434+ RETURN_THROWS ();
435+ }
436+
437+ const unsigned char * p = (const unsigned char * )Z_STRVAL_P (der_zv );
438+ SSL_SESSION * session = d2i_SSL_SESSION (NULL , & p , Z_STRLEN_P (der_zv ));
439+
440+ if (!session ) {
441+ zend_throw_exception (zend_ce_exception , "Failed to unserialize session" , 0 );
442+ RETURN_THROWS ();
443+ }
444+
445+ php_openssl_session_object * obj = Z_OPENSSL_SESSION_P (ZEND_THIS );
446+ obj -> session = session ;
447+
448+ /* Populate id property */
449+ unsigned int id_len = 0 ;
450+ const unsigned char * id = SSL_SESSION_get_id (session , & id_len );
451+ zend_update_property_stringl (php_openssl_session_ce , Z_OBJ_P (ZEND_THIS ),
452+ ZEND_STRL ("id" ), (char * )id , id_len );
453+ }
454+
170455#if defined(HAVE_OPENSSL_ARGON2 )
171456static const zend_module_dep openssl_deps [] = {
172457 ZEND_MOD_REQUIRED ("standard" )
@@ -416,6 +701,17 @@ PHP_MINIT_FUNCTION(openssl)
416701 php_openssl_pkey_object_handlers .clone_obj = NULL ;
417702 php_openssl_pkey_object_handlers .compare = zend_objects_not_comparable ;
418703
704+ php_openssl_session_ce = register_class_OpenSSLSession ();
705+ php_openssl_session_ce -> create_object = php_openssl_session_create_object ;
706+ php_openssl_session_ce -> default_object_handlers = & php_openssl_session_object_handlers ;
707+
708+ memcpy (& php_openssl_session_object_handlers , & std_object_handlers , sizeof (zend_object_handlers ));
709+ php_openssl_session_object_handlers .offset = XtOffsetOf (php_openssl_session_object , std );
710+ php_openssl_session_object_handlers .free_obj = php_openssl_session_free_obj ;
711+ php_openssl_session_object_handlers .get_constructor = php_openssl_session_get_constructor ;
712+ php_openssl_session_object_handlers .clone_obj = NULL ;
713+ php_openssl_session_object_handlers .compare = zend_objects_not_comparable ;
714+
419715 register_openssl_symbols (module_number );
420716
421717 php_openssl_backend_init ();
0 commit comments