Skip to content

Commit b5ab9cd

Browse files
committed
Support specifying an exact date/time range for validity when signing a CSR
This changes the `days` parameter of `openssl_csr_sign()` to a `validity` parameter, which can be either an integer specifying the number of days the certificate is to be valid for (compatible with current usage), or it can be an array of two integer or string values, representing the notBefore and notAfter times to use for the certificate. If they are integers or numeric strings, they are to be a time_t value. If they are non-numeric strings, they are to be an ASN.1 timestamp (YYMMDDHHMMSSZ or YYYYMMDDHHMMSSZ).
1 parent c669fbf commit b5ab9cd

File tree

3 files changed

+110
-8
lines changed

3 files changed

+110
-8
lines changed

ext/openssl/openssl.c

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,6 +1666,84 @@ PHP_FUNCTION(openssl_csr_export)
16661666
}
16671667
/* }}} */
16681668

1669+
/* {{{ parse_time_range */
1670+
/* convert an array of either integers or strings to a pair of time_t values
1671+
* representing the notBefore and notAfter times for a certificate.
1672+
* If the array values are strings, they must either be a valid numeric string
1673+
* representing the unix timestamp, or they must be an ASN.1 timestamp.
1674+
*/
1675+
static int parse_time_range(zval *validity, time_t *from_time, time_t *to_time) {
1676+
zval *tmp;
1677+
long lval;
1678+
double dval;
1679+
ASN1_TIME *t;
1680+
time_t from = -1;
1681+
time_t to = -1;
1682+
1683+
if ((tmp = zend_hash_index_find(Z_ARRVAL_P(validity), 2)) != NULL) {
1684+
php_error_docref(NULL, E_WARNING, "Too many timestamps");
1685+
return FAILURE;
1686+
}
1687+
if ((tmp = zend_hash_index_find(Z_ARRVAL_P(validity), 1)) == NULL) {
1688+
php_error_docref(NULL, E_WARNING, "Too few timestamps");
1689+
return FAILURE;
1690+
}
1691+
if ((tmp = zend_hash_index_find(Z_ARRVAL_P(validity), 0)) != NULL &&
1692+
((Z_TYPE_P(tmp) == IS_LONG) || (Z_TYPE_P(tmp) == IS_STRING))) {
1693+
if (Z_TYPE_P(tmp) == IS_LONG) {
1694+
from = Z_LVAL_P(tmp);
1695+
} else if (Z_TYPE_P(tmp) == IS_STRING) {
1696+
switch (is_numeric_string(Z_STRVAL_P(tmp), Z_STRLEN_P(tmp), &lval, &dval, 0)) {
1697+
case IS_LONG:
1698+
from = lval;
1699+
break;
1700+
case IS_DOUBLE:
1701+
from = (int) dval;
1702+
break;
1703+
default:
1704+
t = ASN1_UTCTIME_new();
1705+
if (ASN1_TIME_set_string(t, Z_STRVAL_P(tmp))) {
1706+
from = php_openssl_asn1_time_to_time_t(t);
1707+
}
1708+
ASN1_UTCTIME_free(t);
1709+
}
1710+
}
1711+
}
1712+
if (from == -1) {
1713+
php_error_docref(NULL, E_WARNING, "Invalid certificate start timestamp");
1714+
return FAILURE;
1715+
}
1716+
if ((tmp = zend_hash_index_find(Z_ARRVAL_P(validity), 1)) != NULL &&
1717+
((Z_TYPE_P(tmp) == IS_LONG) || (Z_TYPE_P(tmp) == IS_STRING))) {
1718+
if (Z_TYPE_P(tmp) == IS_LONG) {
1719+
to = Z_LVAL_P(tmp);
1720+
} else {
1721+
switch (is_numeric_string(Z_STRVAL_P(tmp), Z_STRLEN_P(tmp), &lval, &dval, 0)) {
1722+
case IS_LONG:
1723+
to = lval;
1724+
break;
1725+
case IS_DOUBLE:
1726+
to = (int) dval;
1727+
break;
1728+
default:
1729+
t = ASN1_UTCTIME_new();
1730+
if (ASN1_TIME_set_string(t, Z_STRVAL_P(tmp))) {
1731+
to = php_openssl_asn1_time_to_time_t(t);
1732+
}
1733+
ASN1_UTCTIME_free(t);
1734+
}
1735+
}
1736+
}
1737+
if (to == -1) {
1738+
php_error_docref(NULL, E_WARNING, "Invalid certificate end timestamp");
1739+
return FAILURE;
1740+
}
1741+
*from_time = from;
1742+
*to_time = to;
1743+
return SUCCESS;
1744+
}
1745+
/* }}} */
1746+
16691747
/* {{{ Signs a cert with another CERT */
16701748
PHP_FUNCTION(openssl_csr_sign)
16711749
{
@@ -1677,20 +1755,22 @@ PHP_FUNCTION(openssl_csr_sign)
16771755
zend_object *cert_obj;
16781756
zend_string *cert_str;
16791757
zval *zpkey, *args = NULL;
1680-
zend_long num_days;
1758+
zend_long num_days = -1;
1759+
zval *validity;
16811760
zend_long serial = Z_L(0);
16821761
zend_string *serial_hex = NULL;
16831762
X509 *cert = NULL, *new_cert = NULL;
16841763
EVP_PKEY * key = NULL, *priv_key = NULL;
16851764
int i;
16861765
bool new_cert_used = false;
16871766
struct php_x509_request req;
1767+
time_t from_time = -1, to_time = -1;
16881768

16891769
ZEND_PARSE_PARAMETERS_START(4, 7)
16901770
Z_PARAM_OBJ_OF_CLASS_OR_STR(csr_obj, php_openssl_request_ce, csr_str)
16911771
Z_PARAM_OBJ_OF_CLASS_OR_STR_OR_NULL(cert_obj, php_openssl_certificate_ce, cert_str)
16921772
Z_PARAM_ZVAL(zpkey)
1693-
Z_PARAM_LONG(num_days)
1773+
Z_PARAM_ZVAL(validity)
16941774
Z_PARAM_OPTIONAL
16951775
Z_PARAM_ARRAY_OR_NULL(args)
16961776
Z_PARAM_LONG(serial)
@@ -1728,8 +1808,25 @@ PHP_FUNCTION(openssl_csr_sign)
17281808
goto cleanup;
17291809
}
17301810

1731-
if (num_days < 0 || num_days > LONG_MAX / 86400) {
1732-
php_error_docref(NULL, E_WARNING, "Days must be between 0 and %ld", LONG_MAX / 86400);
1811+
/* If 'validity' is an integer, it is the number of days the certificate
1812+
* will be valid for, starting from right now.
1813+
* If it is an array, it is expected to contain two values, the
1814+
* starting time and the ending time for the validity period. Each of
1815+
* the values are expected to be either a numeric value representing a
1816+
* unix timestamp, or a string containing an ASN.1 timestamp.
1817+
*/
1818+
if (Z_TYPE_P(validity) == IS_LONG) {
1819+
num_days = Z_LVAL_P(validity);
1820+
if (num_days < 0 || num_days > LONG_MAX / 86400) {
1821+
php_error_docref(NULL, E_WARNING, "Days must be between 0 and %ld", LONG_MAX / 86400);
1822+
goto cleanup;
1823+
}
1824+
} else if (Z_TYPE_P(validity) == IS_ARRAY) {
1825+
if (parse_time_range(validity, &from_time, &to_time) != SUCCESS) {
1826+
goto cleanup;
1827+
}
1828+
} else {
1829+
php_error_docref(NULL, E_WARNING, "Fourth parameter must be integer or array");
17331830
goto cleanup;
17341831
}
17351832

@@ -1800,8 +1897,13 @@ PHP_FUNCTION(openssl_csr_sign)
18001897
php_openssl_store_errors();
18011898
goto cleanup;
18021899
}
1803-
X509_gmtime_adj(X509_getm_notBefore(new_cert), 0);
1804-
X509_gmtime_adj(X509_getm_notAfter(new_cert), 60*60*24*num_days);
1900+
if (num_days == -1) {
1901+
ASN1_TIME_set(X509_getm_notBefore(new_cert), from_time);
1902+
ASN1_TIME_set(X509_getm_notAfter(new_cert), to_time);
1903+
} else {
1904+
X509_gmtime_adj(X509_getm_notBefore(new_cert), 0);
1905+
X509_gmtime_adj(X509_getm_notAfter(new_cert), 60*60*24*num_days);
1906+
}
18051907
i = X509_set_pubkey(new_cert, key);
18061908
if (!i) {
18071909
php_openssl_store_errors();

ext/openssl/openssl.stub.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ function openssl_csr_export(OpenSSLCertificateSigningRequest|string $csr, &$outp
486486
/**
487487
* @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $private_key
488488
*/
489-
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 {}
489+
function openssl_csr_sign(OpenSSLCertificateSigningRequest|string $csr, OpenSSLCertificate|string|null $ca_certificate, #[\SensitiveParameter] $private_key, int|array $validity, ?array $options = null, int $serial = 0): OpenSSLCertificate|false {}
490490

491491
/**
492492
* @param OpenSSLAsymmetricKey|null $private_key

ext/openssl/openssl_arginfo.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)