diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 2c09b89e31200..211863bbc1f65 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -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) { @@ -1677,7 +1755,8 @@ 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; @@ -1685,12 +1764,13 @@ PHP_FUNCTION(openssl_csr_sign) 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) @@ -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; } @@ -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); + 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(); diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 94902a4acf0da..56e3d17bb8e04 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -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 diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h index 796582c185bb6..0fbc6993a3352 100644 --- a/ext/openssl/openssl_arginfo.h +++ b/ext/openssl/openssl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 8233a8abc8ab7145d905d0fa51478edfe1e55a06 */ + * Stub hash: a927069eb0fc1bdae7b75387705d9391a44fea6e */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_x509_export_to_file, 0, 2, _IS_BOOL, 0) ZEND_ARG_OBJ_TYPE_MASK(0, certificate, OpenSSLCertificate, MAY_BE_STRING, NULL) @@ -87,7 +87,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_openssl_csr_sign, 0, 4, Open ZEND_ARG_OBJ_TYPE_MASK(0, csr, OpenSSLCertificateSigningRequest, MAY_BE_STRING, NULL) ZEND_ARG_OBJ_TYPE_MASK(0, ca_certificate, OpenSSLCertificate, MAY_BE_STRING|MAY_BE_NULL, NULL) ZEND_ARG_INFO(0, private_key) - ZEND_ARG_TYPE_INFO(0, days, IS_LONG, 0) + ZEND_ARG_TYPE_MASK(0, validity, MAY_BE_LONG|MAY_BE_ARRAY, NULL) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, serial, IS_LONG, 0, "0") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, serial_hex, IS_STRING, 1, "null")