Skip to content

Commit f4ced33

Browse files
committed
Add initial implementation and use of OpenSSLSession class
1 parent 03b27dd commit f4ced33

16 files changed

+511
-74
lines changed

ext/openssl/openssl.c

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
171456
static 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();

ext/openssl/openssl.stub.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,36 @@ final class OpenSSLAsymmetricKey
434434
{
435435
}
436436

437+
/**
438+
* @strict-properties
439+
*/
440+
final class OpenSSLSession
441+
{
442+
public readonly string $id;
443+
444+
public function export(int $format = OPENSSL_ENCODING_PEM): string {}
445+
446+
public static function import(string $data, int $format = OPENSSL_ENCODING_PEM): OpenSSLSession {}
447+
448+
public function isResumable(): bool {}
449+
450+
public function getTimeout(): int {}
451+
452+
public function getCreatedAt(): int {}
453+
454+
public function getProtocol(): ?string {}
455+
456+
public function getCipher(): ?string {}
457+
458+
public function hasTicket(): bool {}
459+
460+
public function getTicketLifetimeHint(): ?int {}
461+
462+
public function __serialize(): array {}
463+
464+
public function __unserialize(array $data): void {}
465+
}
466+
437467
function openssl_x509_export_to_file(OpenSSLCertificate|string $certificate, string $output_filename, bool $no_text = true): bool {}
438468

439469
/** @param string $output */

0 commit comments

Comments
 (0)