Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
294 changes: 294 additions & 0 deletions ext/openssl/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,289 @@ static void php_openssl_pkey_free_obj(zend_object *object)
zend_object_std_dtor(&key_object->std);
}

/* OpenSSLSession class */

zend_class_entry *php_openssl_session_ce;

static zend_object_handlers php_openssl_session_object_handlers;

bool php_openssl_is_session_ce(zval *val)
{
return Z_TYPE_P(val) == IS_OBJECT && Z_OBJCE_P(val) == php_openssl_session_ce;
}

SSL_SESSION *php_openssl_session_from_zval(zval *zv)
{
if (!php_openssl_is_session_ce(zv)) {
return NULL;
}
return Z_OPENSSL_SESSION_P(zv)->session;
}

void php_openssl_session_object_init(zval *zv, SSL_SESSION *session)
{
object_init_ex(zv, php_openssl_session_ce);
php_openssl_session_object *obj = Z_OPENSSL_SESSION_P(zv);
obj->session = session;

unsigned int id_len = 0;
const unsigned char *id = SSL_SESSION_get_id(session, &id_len);
zend_update_property_stringl(php_openssl_session_ce, Z_OBJ_P(zv),
ZEND_STRL("id"), (char *)id, id_len);
}

static zend_object *php_openssl_session_create_object(zend_class_entry *class_type)
{
php_openssl_session_object *intern = zend_object_alloc(sizeof(php_openssl_session_object), class_type);

zend_object_std_init(&intern->std, class_type);
object_properties_init(&intern->std, class_type);

return &intern->std;
}

static zend_function *php_openssl_session_get_constructor(zend_object *object)
{
zend_throw_error(NULL,
"Cannot directly construct OpenSSLSession, use OpenSSLSession::import() or TLS session callbacks");
return NULL;
}

static void php_openssl_session_free_obj(zend_object *object)
{
php_openssl_session_object *session_object = php_openssl_session_from_obj(object);

if (session_object->session) {
SSL_SESSION_free(session_object->session);
session_object->session = NULL;
}
zend_object_std_dtor(&session_object->std);
}

#define PHP_OPENSSL_SESSION_CHECK() \
php_openssl_session_object *obj = Z_OPENSSL_SESSION_P(ZEND_THIS); \
if (!obj->session) { \
zend_throw_exception(zend_ce_exception, "Session is not valid", 0); \
RETURN_THROWS(); \
}

PHP_METHOD(OpenSSLSession, export)
{
zend_long format = ENCODING_DER;

ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(format)
ZEND_PARSE_PARAMETERS_END();

PHP_OPENSSL_SESSION_CHECK();

if (format == ENCODING_DER) {
int len = i2d_SSL_SESSION(obj->session, NULL);
if (len <= 0) {
zend_throw_exception(zend_ce_exception, "Failed to export session", 0);
RETURN_THROWS();
}

zend_string *result = zend_string_alloc(len, 0);
unsigned char *p = (unsigned char *)ZSTR_VAL(result);
i2d_SSL_SESSION(obj->session, &p);
ZSTR_VAL(result)[len] = '\0';

RETURN_NEW_STR(result);
}

if (format == ENCODING_PEM) {
BIO *bio = BIO_new(BIO_s_mem());
if (!bio) {
zend_throw_exception(zend_ce_exception, "Failed to create BIO", 0);
RETURN_THROWS();
}

if (!PEM_write_bio_SSL_SESSION(bio, obj->session)) {
BIO_free(bio);
zend_throw_exception(zend_ce_exception, "Failed to export session as PEM", 0);
RETURN_THROWS();
}

char *data;
long len = BIO_get_mem_data(bio, &data);
zend_string *result = zend_string_init(data, len, 0);
BIO_free(bio);

RETURN_NEW_STR(result);
}

zend_argument_value_error(1, "must be OPENSSL_ENCODING_DER or OPENSSL_ENCODING_PEM");
RETURN_THROWS();
}

PHP_METHOD(OpenSSLSession, import)
{
zend_string *data;
zend_long format = ENCODING_DER;

ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_STR(data)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(format)
ZEND_PARSE_PARAMETERS_END();

SSL_SESSION *session = NULL;

if (format == ENCODING_DER) {
const unsigned char *p = (const unsigned char *)ZSTR_VAL(data);
session = d2i_SSL_SESSION(NULL, &p, ZSTR_LEN(data));
} else if (format == ENCODING_PEM) {
BIO *bio = BIO_new_mem_buf(ZSTR_VAL(data), ZSTR_LEN(data));
if (bio) {
session = PEM_read_bio_SSL_SESSION(bio, NULL, NULL, NULL);
BIO_free(bio);
}
} else {
zend_argument_value_error(2, "must be OPENSSL_ENCODING_DER or OPENSSL_ENCODING_PEM");
RETURN_THROWS();
}

if (!session) {
zend_throw_exception(zend_ce_exception, "Failed to import session data", 0);
RETURN_THROWS();
}

php_openssl_session_object_init(return_value, session);
}

PHP_METHOD(OpenSSLSession, isResumable)
{
ZEND_PARSE_PARAMETERS_NONE();
PHP_OPENSSL_SESSION_CHECK();

RETURN_BOOL(SSL_SESSION_is_resumable(obj->session));
}

PHP_METHOD(OpenSSLSession, getTimeout)
{
ZEND_PARSE_PARAMETERS_NONE();
PHP_OPENSSL_SESSION_CHECK();

RETURN_LONG((zend_long)SSL_SESSION_get_timeout(obj->session));
}

PHP_METHOD(OpenSSLSession, getCreatedAt)
{
ZEND_PARSE_PARAMETERS_NONE();
PHP_OPENSSL_SESSION_CHECK();

RETURN_LONG((zend_long)SSL_SESSION_get_time(obj->session));
}

PHP_METHOD(OpenSSLSession, getProtocol)
{
ZEND_PARSE_PARAMETERS_NONE();
PHP_OPENSSL_SESSION_CHECK();

int version = SSL_SESSION_get_protocol_version(obj->session);

switch (version) {
case TLS1_3_VERSION:
RETURN_STRING("TLSv1.3");
case TLS1_2_VERSION:
RETURN_STRING("TLSv1.2");
case TLS1_1_VERSION:
RETURN_STRING("TLSv1.1");
case TLS1_VERSION:
RETURN_STRING("TLSv1.0");
default:
RETURN_NULL();
}
}

PHP_METHOD(OpenSSLSession, getCipher)
{
ZEND_PARSE_PARAMETERS_NONE();
PHP_OPENSSL_SESSION_CHECK();

const SSL_CIPHER *cipher = SSL_SESSION_get0_cipher(obj->session);
if (!cipher) {
RETURN_NULL();
}

RETURN_STRING(SSL_CIPHER_get_name(cipher));
}

PHP_METHOD(OpenSSLSession, hasTicket)
{
ZEND_PARSE_PARAMETERS_NONE();
PHP_OPENSSL_SESSION_CHECK();

RETURN_BOOL(SSL_SESSION_has_ticket(obj->session));
}

PHP_METHOD(OpenSSLSession, getTicketLifetimeHint)
{
ZEND_PARSE_PARAMETERS_NONE();
PHP_OPENSSL_SESSION_CHECK();

if (!SSL_SESSION_has_ticket(obj->session)) {
RETURN_NULL();
}

RETURN_LONG((zend_long)SSL_SESSION_get_ticket_lifetime_hint(obj->session));
}

PHP_METHOD(OpenSSLSession, __serialize)
{
ZEND_PARSE_PARAMETERS_NONE();

PHP_OPENSSL_SESSION_CHECK();

int len = i2d_SSL_SESSION(obj->session, NULL);
if (len <= 0) {
zend_throw_exception(zend_ce_exception, "Failed to serialize session", 0);
RETURN_THROWS();
}

zend_string *der = zend_string_alloc(len, 0);
unsigned char *p = (unsigned char *)ZSTR_VAL(der);
i2d_SSL_SESSION(obj->session, &p);
ZSTR_VAL(der)[len] = '\0';

array_init(return_value);
add_assoc_str(return_value, "der", der);
}

PHP_METHOD(OpenSSLSession, __unserialize)
{
HashTable *data;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_HT(data)
ZEND_PARSE_PARAMETERS_END();

zval *der_zv = zend_hash_str_find(data, ZEND_STRL("der"));
if (!der_zv || Z_TYPE_P(der_zv) != IS_STRING) {
zend_throw_exception(zend_ce_exception, "Invalid serialization data", 0);
RETURN_THROWS();
}

const unsigned char *p = (const unsigned char *)Z_STRVAL_P(der_zv);
SSL_SESSION *session = d2i_SSL_SESSION(NULL, &p, Z_STRLEN_P(der_zv));

if (!session) {
zend_throw_exception(zend_ce_exception, "Failed to unserialize session", 0);
RETURN_THROWS();
}

php_openssl_session_object *obj = Z_OPENSSL_SESSION_P(ZEND_THIS);
obj->session = session;

/* Populate id property */
unsigned int id_len = 0;
const unsigned char *id = SSL_SESSION_get_id(session, &id_len);
zend_update_property_stringl(php_openssl_session_ce, Z_OBJ_P(ZEND_THIS),
ZEND_STRL("id"), (char *)id, id_len);
}

#if defined(HAVE_OPENSSL_ARGON2)
static const zend_module_dep openssl_deps[] = {
ZEND_MOD_REQUIRED("standard")
Expand Down Expand Up @@ -416,6 +699,17 @@ PHP_MINIT_FUNCTION(openssl)
php_openssl_pkey_object_handlers.clone_obj = NULL;
php_openssl_pkey_object_handlers.compare = zend_objects_not_comparable;

php_openssl_session_ce = register_class_OpenSSLSession();
php_openssl_session_ce->create_object = php_openssl_session_create_object;
php_openssl_session_ce->default_object_handlers = &php_openssl_session_object_handlers;

memcpy(&php_openssl_session_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
php_openssl_session_object_handlers.offset = XtOffsetOf(php_openssl_session_object, std);
php_openssl_session_object_handlers.free_obj = php_openssl_session_free_obj;
php_openssl_session_object_handlers.get_constructor = php_openssl_session_get_constructor;
php_openssl_session_object_handlers.clone_obj = NULL;
php_openssl_session_object_handlers.compare = zend_objects_not_comparable;

register_openssl_symbols(module_number);

php_openssl_backend_init();
Expand Down
30 changes: 30 additions & 0 deletions ext/openssl/openssl.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,36 @@ final class OpenSSLAsymmetricKey
{
}

/**
* @strict-properties
*/
final class OpenSSLSession
{
public readonly string $id;

public function export(int $format = OPENSSL_ENCODING_PEM): string {}

public static function import(string $data, int $format = OPENSSL_ENCODING_PEM): OpenSSLSession {}

public function isResumable(): bool {}

public function getTimeout(): int {}

public function getCreatedAt(): int {}

public function getProtocol(): ?string {}

public function getCipher(): ?string {}

public function hasTicket(): bool {}

public function getTicketLifetimeHint(): ?int {}

public function __serialize(): array {}

public function __unserialize(array $data): void {}
}

function openssl_x509_export_to_file(OpenSSLCertificate|string $certificate, string $output_filename, bool $no_text = true): bool {}

/** @param string $output */
Expand Down
Loading
Loading