Skip to content
Open
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
114 changes: 108 additions & 6 deletions ext/openssl/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1666,6 +1666,84 @@ PHP_FUNCTION(openssl_csr_export)
}
/* }}} */

/* {{{ parse_time_range */
/* convert an array of either integers or strings to a pair of time_t values
* representing the notBefore and notAfter times for a certificate.
* If the array values are strings, they must either be a valid numeric string
* representing the unix timestamp, or they must be an ASN.1 timestamp.
*/
static int parse_time_range(zval *validity, time_t *from_time, time_t *to_time) {
zval *tmp;
zend_long lval;
double dval;
ASN1_TIME *t;
time_t from = -1;
time_t to = -1;

if ((tmp = zend_hash_index_find(Z_ARRVAL_P(validity), 2)) != NULL) {
php_error_docref(NULL, E_WARNING, "Too many timestamps");
return FAILURE;
}
if ((tmp = zend_hash_index_find(Z_ARRVAL_P(validity), 1)) == NULL) {
php_error_docref(NULL, E_WARNING, "Too few timestamps");
return FAILURE;
}
if ((tmp = zend_hash_index_find(Z_ARRVAL_P(validity), 0)) != NULL &&
((Z_TYPE_P(tmp) == IS_LONG) || (Z_TYPE_P(tmp) == IS_STRING))) {
if (Z_TYPE_P(tmp) == IS_LONG) {
from = Z_LVAL_P(tmp);
} else if (Z_TYPE_P(tmp) == IS_STRING) {
switch (is_numeric_string(Z_STRVAL_P(tmp), Z_STRLEN_P(tmp), &lval, &dval, 0)) {
case IS_LONG:
from = lval;
break;
case IS_DOUBLE:
from = (int) dval;
break;
default:
t = ASN1_UTCTIME_new();
if (ASN1_TIME_set_string(t, Z_STRVAL_P(tmp))) {
from = php_openssl_asn1_time_to_time_t(t);
}
ASN1_UTCTIME_free(t);
}
}
}
if (from == -1) {
php_error_docref(NULL, E_WARNING, "Invalid certificate start timestamp");
return FAILURE;
}
if ((tmp = zend_hash_index_find(Z_ARRVAL_P(validity), 1)) != NULL &&
((Z_TYPE_P(tmp) == IS_LONG) || (Z_TYPE_P(tmp) == IS_STRING))) {
if (Z_TYPE_P(tmp) == IS_LONG) {
to = Z_LVAL_P(tmp);
} else {
switch (is_numeric_string(Z_STRVAL_P(tmp), Z_STRLEN_P(tmp), &lval, &dval, 0)) {
case IS_LONG:
to = lval;
break;
case IS_DOUBLE:
to = (int) dval;
break;
default:
t = ASN1_UTCTIME_new();
if (ASN1_TIME_set_string(t, Z_STRVAL_P(tmp))) {
to = php_openssl_asn1_time_to_time_t(t);
}
ASN1_UTCTIME_free(t);
}
}
}
if (to == -1) {
php_error_docref(NULL, E_WARNING, "Invalid certificate end timestamp");
return FAILURE;
}
*from_time = from;
*to_time = to;
return SUCCESS;
}
/* }}} */

/* {{{ Signs a cert with another CERT */
PHP_FUNCTION(openssl_csr_sign)
{
Expand All @@ -1677,20 +1755,22 @@ PHP_FUNCTION(openssl_csr_sign)
zend_object *cert_obj;
zend_string *cert_str;
zval *zpkey, *args = NULL;
zend_long num_days;
zend_long num_days = -1;
zval *validity;
zend_long serial = Z_L(0);
zend_string *serial_hex = NULL;
X509 *cert = NULL, *new_cert = NULL;
EVP_PKEY * key = NULL, *priv_key = NULL;
int i;
bool new_cert_used = false;
struct php_x509_request req;
time_t from_time = -1, to_time = -1;

ZEND_PARSE_PARAMETERS_START(4, 7)
Z_PARAM_OBJ_OF_CLASS_OR_STR(csr_obj, php_openssl_request_ce, csr_str)
Z_PARAM_OBJ_OF_CLASS_OR_STR_OR_NULL(cert_obj, php_openssl_certificate_ce, cert_str)
Z_PARAM_ZVAL(zpkey)
Z_PARAM_LONG(num_days)
Z_PARAM_ZVAL(validity)
Z_PARAM_OPTIONAL
Z_PARAM_ARRAY_OR_NULL(args)
Z_PARAM_LONG(serial)
Expand Down Expand Up @@ -1728,8 +1808,25 @@ PHP_FUNCTION(openssl_csr_sign)
goto cleanup;
}

if (num_days < 0 || num_days > LONG_MAX / 86400) {
php_error_docref(NULL, E_WARNING, "Days must be between 0 and %ld", LONG_MAX / 86400);
/* If 'validity' is an integer, it is the number of days the certificate
* will be valid for, starting from right now.
* If it is an array, it is expected to contain two values, the
* starting time and the ending time for the validity period. Each of
* the values are expected to be either a numeric value representing a
* unix timestamp, or a string containing an ASN.1 timestamp.
*/
if (Z_TYPE_P(validity) == IS_LONG) {
num_days = Z_LVAL_P(validity);
if (num_days < 0 || num_days > LONG_MAX / 86400) {
php_error_docref(NULL, E_WARNING, "Days must be between 0 and %ld", LONG_MAX / 86400);
goto cleanup;
}
} else if (Z_TYPE_P(validity) == IS_ARRAY) {
if (parse_time_range(validity, &from_time, &to_time) != SUCCESS) {
goto cleanup;
}
} else {
php_error_docref(NULL, E_WARNING, "Fourth parameter must be integer or array");
goto cleanup;
}

Expand Down Expand Up @@ -1800,8 +1897,13 @@ PHP_FUNCTION(openssl_csr_sign)
php_openssl_store_errors();
goto cleanup;
}
X509_gmtime_adj(X509_getm_notBefore(new_cert), 0);
X509_gmtime_adj(X509_getm_notAfter(new_cert), 60*60*24*num_days);
if (num_days == -1) {
ASN1_TIME_set(X509_getm_notBefore(new_cert), from_time);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about accepting DateTimeInterface?

Copy link
Contributor Author

@StephenWall StephenWall Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to do that...

} else if (Z_TYPE_P(tmp) == IS_OBJECT && Z_OBJCE_P(tmp) == php_date_get_date_ce) {

but then what? How would I turn that object into a time_t?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe like this?

php_date_obj *datetime = php_date_obj_from_obj(Z_OBJ(tmp));
zval timestamp;
zend_call_method_with_0_params(date_obj, php_date_get_date_ce(), NULL, "gettimestamp", &timestamp);
if (Z_TYPE(timestamp) == IS_LONG) {
    from = Z_LVAL_P(timestamp);
}

ASN1_TIME_set(X509_getm_notAfter(new_cert), to_time);
} else {
X509_gmtime_adj(X509_getm_notBefore(new_cert), 0);
X509_gmtime_adj(X509_getm_notAfter(new_cert), 60*60*24*num_days);
}
i = X509_set_pubkey(new_cert, key);
if (!i) {
php_openssl_store_errors();
Expand Down
2 changes: 1 addition & 1 deletion ext/openssl/openssl.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ function openssl_csr_export(OpenSSLCertificateSigningRequest|string $csr, &$outp
/**
* @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $private_key
*/
function openssl_csr_sign(OpenSSLCertificateSigningRequest|string $csr, OpenSSLCertificate|string|null $ca_certificate, #[\SensitiveParameter] $private_key, int $days, ?array $options = null, int $serial = 0, ?string $serial_hex = null): OpenSSLCertificate|false {}
function openssl_csr_sign(OpenSSLCertificateSigningRequest|string $csr, OpenSSLCertificate|string|null $ca_certificate, #[\SensitiveParameter] $private_key, int|array $validity, ?array $options = null, int $serial = 0, ?string $serial_hex = null): OpenSSLCertificate|false {}

/**
* @param OpenSSLAsymmetricKey|null $private_key
Expand Down
4 changes: 2 additions & 2 deletions ext/openssl/openssl_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.