[rs-commit] r540 - in /mod_ca/trunk: ChangeLog mod_ca_disk.c
rs-commit at redwax.eu
rs-commit at redwax.eu
Fri Mar 6 14:49:59 CET 2026
Author: minfrin at redwax.eu
Date: Fri Mar 6 14:49:58 2026
New Revision: 540
Log:
Add ca_reqauthz hook to mod_ca_disk to verify the case
where the renewal or reissue was signed by a
previously issued certificate.
Modified:
mod_ca/trunk/ChangeLog
mod_ca/trunk/mod_ca_disk.c
Modified: mod_ca/trunk/ChangeLog
==============================================================================
--- mod_ca/trunk/ChangeLog (original)
+++ mod_ca/trunk/ChangeLog Fri Mar 6 14:49:58 2026
@@ -1,5 +1,9 @@
Changes with v1.0.0
+
+ *) Add ca_reqauthz hook to mod_ca_disk to verify the case
+ where the renewal or reissue was signed by a
+ previously issued certificate. [Graham Leggett]
*) Add constants for proof of possession. [Graham Leggett]
Modified: mod_ca/trunk/mod_ca_disk.c
==============================================================================
--- mod_ca/trunk/mod_ca_disk.c (original)
+++ mod_ca/trunk/mod_ca_disk.c Fri Mar 6 14:49:58 2026
@@ -207,6 +207,12 @@
return APR_SUCCESS;
}
+static apr_status_t ca_X509_cleanup(void *data)
+{
+ X509_free((X509 *) data);
+ return APR_SUCCESS;
+}
+
static apr_status_t ca_BIO_cleanup(void *data)
{
BIO_free((BIO *) data);
@@ -237,17 +243,36 @@
return APR_SUCCESS;
}
+static apr_status_t ca_OPENSSL_cleanup(void *data)
+{
+ OPENSSL_free(data);
+ return APR_SUCCESS;
+}
+
static apr_status_t ca_TXT_DB_cleanup(void *data)
{
TXT_DB_free((TXT_DB *) data);
return APR_SUCCESS;
}
+static ca_asn1_t *make_X509(apr_pool_t *pool, X509 *cert)
+{
+ ca_asn1_t *buf = apr_palloc(pool, sizeof(ca_asn1_t));
+ unsigned char *tmp;
+
+ buf->len = i2d_X509(cert, NULL);
+ buf->val = tmp = apr_palloc(pool, buf->len);
+ i2d_X509(cert, &tmp);
+
+ return buf;
+}
+
static ASN1_STRING *parse_ASN1_STRING(apr_pool_t *pool, ca_asn1_t *string)
{
ASN1_STRING *s = NULL;
if (string) {
- d2i_ASN1_PRINTABLE(&s, &string->val, string->len);
+ const unsigned char *val = string->val;
+ d2i_ASN1_PRINTABLE(&s, &val, string->len);
if (s) {
apr_pool_cleanup_register(pool, s, ca_ASN1_STRING_cleanup,
apr_pool_cleanup_null);
@@ -260,7 +285,8 @@
{
ASN1_TIME *t = NULL;
if (time) {
- d2i_ASN1_TIME(&t, &time->val, time->len);
+ const unsigned char *val = time->val;
+ d2i_ASN1_TIME(&t, &val, time->len);
if (t) {
apr_pool_cleanup_register(pool, t, ca_ASN1_TIME_cleanup,
apr_pool_cleanup_null);
@@ -273,7 +299,8 @@
{
ASN1_INTEGER *i = NULL;
if (integer) {
- d2i_ASN1_INTEGER(&i, &integer->val, integer->len);
+ const unsigned char *val = integer->val;
+ d2i_ASN1_INTEGER(&i, &val, integer->len);
if (i) {
apr_pool_cleanup_register(pool, i, ca_ASN1_INTEGER_cleanup,
apr_pool_cleanup_null);
@@ -286,13 +313,28 @@
{
X509_NAME *n = NULL;
if (name) {
- d2i_X509_NAME(&n, &name->val, name->len);
+ const unsigned char *val = name->val;
+ d2i_X509_NAME(&n, &val, name->len);
if (n) {
apr_pool_cleanup_register(pool, n, ca_X509_NAME_cleanup,
apr_pool_cleanup_null);
}
}
return n;
+}
+
+static X509 *parse_X509(apr_pool_t *pool, ca_asn1_t *cert)
+{
+ X509 *x = NULL;
+ if (cert) {
+ const unsigned char *val = cert->val;
+ d2i_X509(&x, &val, cert->len);
+ if (x) {
+ apr_pool_cleanup_register(pool, x, ca_X509_cleanup,
+ apr_pool_cleanup_null);
+ }
+ }
+ return x;
}
/* Prevent anything untowards in a filename; by replacing
@@ -300,31 +342,31 @@
*/
static const char * safe_filename(apr_pool_t *pool, const char * fname)
{
- const char hex[16] = "0123456789ABCDEF";
- char * out = NULL, * o;
-
- if (fname == NULL)
- return "_";
-
- if (!*fname)
- return "_00";
-
- // worst case lenght; including trailing \0.
- out = apr_palloc(pool, 1 + strlen(fname) * 3);
+ const char hex[16] = "0123456789ABCDEF";
+ char * out = NULL, * o;
+
+ if (fname == NULL)
+ return "_";
+
+ if (!*fname)
+ return "_00";
+
+ // worst case lenght; including trailing \0.
+ out = apr_palloc(pool, 1 + strlen(fname) * 3);
o = out;
- for(const char *p = fname; *p; p++)
- {
- if (apr_isalnum(*p))
- {
- *o++ = *p;
- continue;
- };
- *o++ = '_';
- *o++ = hex[ ( *p >> 4) & 0xF ];
- *o++ = hex[ ( *p >> 0) & 0xF ];
- };
- return out;
+ for(const char *p = fname; *p; p++)
+ {
+ if (apr_isalnum(*p))
+ {
+ *o++ = *p;
+ continue;
+ };
+ *o++ = '_';
+ *o++ = hex[ ( *p >> 4) & 0xF ];
+ *o++ = hex[ ( *p >> 0) & 0xF ];
+ };
+ return out;
}
static int ca_sign_disk(request_rec *r, apr_hash_t *params,
@@ -564,19 +606,19 @@
APR_HASH_KEY_STRING) :
NULL;
if (transaction_id) {
- const char * kid;
+ const char * kid;
ASN1_STRING *s = parse_ASN1_STRING(r->pool, transaction_id);
if (!s) {
log_message(r, status, "The transactionID could not be parsed");
return HTTP_BAD_REQUEST;
}
- if (conf->serial_path)
- ap_log_rerror( APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r,
- "Both serial and transaction path defined; using the latter.");
+ if (conf->serial_path)
+ ap_log_rerror( APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r,
+ "Both serial and transaction path defined; using the latter.");
#if HAVE_ASN1_STRING_GET0_DATA
- kid = (const char *) ASN1_STRING_get0_data(s);
+ kid = (const char *) ASN1_STRING_get0_data(s);
#else
kid = (const char *) ASN1_STRING_data(s);
#endif
@@ -668,11 +710,11 @@
/* do we have a link? create it now */
if (link) {
#if HAVE_APR_FILE_LINK
- status = apr_file_link(path, link);
+ status = apr_file_link(path, link);
#else
status = APR_ENOTIMPL;
#endif
- if (APR_SUCCESS != status) {
+ if (APR_SUCCESS != status) {
log_message(r, status,
"Could not link the certificate file to the CADiskCertificateByTransactionPath");
@@ -1257,6 +1299,222 @@
apr_global_mutex_unlock(ca_disk_mutex);
return OK;
}
+}
+
+static int ca_reqauthz_disk(request_rec *r, apr_hash_t *params,
+ const unsigned char *buffer, apr_size_t len)
+{
+ ca_asn1_t *pop, *dpop;
+ X509 *cert, *dcert;
+ TXT_DB *db = NULL;
+ ASN1_INTEGER *si;
+ BIGNUM *bn = NULL;
+ char *fname;
+ char **result;
+ const char *key;
+ apr_status_t status;
+
+ ca_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &ca_disk_module);
+
+ /* index file defined? */
+ if (!conf->index_file || !conf->serial_path) {
+ return DECLINED;
+ }
+
+ pop = apr_hash_get(params, CA_POP_CERT, APR_HASH_KEY_STRING);
+
+ /* we only care about certificate proofs of possession */
+ if (!pop) {
+ return DECLINED;
+ }
+
+ cert = parse_X509(r->pool, pop);
+
+ if (!cert) {
+ log_message(r, APR_SUCCESS,
+ "could not DER decode the certificate");
+
+ return HTTP_BAD_REQUEST;
+ }
+
+ status = apr_global_mutex_lock(ca_disk_mutex);
+ if (APR_SUCCESS != status) {
+ log_message(r, status,
+ "Could not obtain the mutex for serial number index file");
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* read in the index file */
+ BIO *in = BIO_new(BIO_s_file());
+
+ if (BIO_read_filename(in, conf->index_file) <= 0) {
+ /* on read error, we create a database */
+ BIO_free(in);
+ in = BIO_new(BIO_s_mem());
+ }
+ apr_pool_cleanup_register(r->pool, in, ca_BIO_cleanup,
+ apr_pool_cleanup_null);
+
+ db = TXT_DB_read(in, DB_NUMBER);
+ if (!db) {
+ log_message(r, APR_SUCCESS, "Could not parse the index file");
+
+ apr_global_mutex_unlock(ca_disk_mutex);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ apr_pool_cleanup_register(r->pool, db, ca_TXT_DB_cleanup,
+ apr_pool_cleanup_null);
+
+ apr_global_mutex_unlock(ca_disk_mutex);
+
+ if (!TXT_DB_create_index(db, DB_serial, NULL,
+ LHASH_HASH_FN(index_serial), LHASH_COMP_FN(index_serial))) {
+ log_message(r, APR_SUCCESS,
+ apr_psprintf(r->pool,
+ "Could not hash the index file (%ld,%ld,%ld)",
+ db->error, db->arg1, db->arg2));
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* isolate the serial number */
+ si = X509_get_serialNumber(cert);
+ if (!si) {
+ log_message(r, APR_SUCCESS,
+ "certificate had no serial number, renewal not possible");
+
+ return HTTP_BAD_REQUEST;
+ }
+ bn = ASN1_INTEGER_to_BN(si, NULL);
+ if (!bn) {
+ log_message(r, APR_SUCCESS,
+ "certificate serial number could not be converted to a big number, renewal not possible");
+
+ return HTTP_BAD_REQUEST;
+ }
+ apr_pool_cleanup_register(r->pool, bn, ca_BIGNUM_cleanup,
+ apr_pool_cleanup_null);
+
+ OPENSSL_STRING *row =
+ (char **) apr_pcalloc(r->pool, sizeof(char *)*(DB_NUMBER+1));
+ if (!row) {
+ log_message(r, APR_SUCCESS,
+ "Could not allocate search row for index file");
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ row[DB_serial] = BN_bn2hex(bn);
+ apr_pool_cleanup_register(r->pool, row[DB_serial], ca_OPENSSL_cleanup,
+ apr_pool_cleanup_null);
+
+ result = TXT_DB_get_by_index(db, DB_serial, row);
+
+ if (!result) {
+ log_message(r, APR_SUCCESS,
+ "Signing certificate serial number not found, renewal forbidden");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ /* check certificate validity */
+
+ if (!result[DB_type] || result[DB_type][1]) {
+ /* none of the above */
+ log_message(r, APR_SUCCESS,
+ "Signing certificate has unknown status, renewal forbidden");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ else if (result[DB_type][0] == DB_TYPE_VAL) {
+ /* valid, continue */
+ }
+ else if (result[DB_type][0] == DB_TYPE_REV) {
+ /* revoked */
+ log_message(r, APR_SUCCESS,
+ "Signing certificate revoked, renewal forbidden");
+
+ return HTTP_FORBIDDEN;
+ }
+ else if (result[DB_type][0] == DB_TYPE_EXP) {
+ /* expired */
+ log_message(r, APR_SUCCESS,
+ "Signing certificate expired, renewal forbidden");
+
+ return HTTP_FORBIDDEN;
+ }
+ else {
+ /* none of the above */
+ log_message(r, APR_SUCCESS,
+ "Signing certificate has unknown status, renewal forbidden");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ /* check certificate expiry */
+
+ if (X509_cmp_current_time(X509_get0_notBefore(cert)) > 0) {
+ log_message(r, APR_SUCCESS,
+ "Signing certificate not yet valid, renewal forbidden");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ if (X509_cmp_current_time(X509_get0_notAfter(cert)) < 0) {
+ log_message(r, APR_SUCCESS,
+ "Signing certificate expired, renewal forbidden");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ /* try load the existing certificate from disk */
+
+ key = apr_pstrcat(r->pool, row[DB_serial], ".", conf->serial_path_suffix,
+ NULL);
+
+ status = apr_filepath_merge(&fname, conf->serial_path, key,
+ APR_FILEPATH_SECUREROOT | APR_FILEPATH_NOTRELATIVE, r->pool);
+ if (APR_SUCCESS != status) {
+ log_message(r, APR_SUCCESS,
+ "Signing certificate renewal forbidden");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ in = BIO_new(BIO_s_file());
+ apr_pool_cleanup_register(r->pool, in, ca_BIO_cleanup,
+ apr_pool_cleanup_null);
+
+ if (BIO_read_filename(in, fname) <= 0) {
+ log_message(r, APR_SUCCESS,
+ "Signing certificate not found, renewal forbidden");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ dcert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
+ if (!dcert) {
+ log_message(r, APR_SUCCESS,
+ "Signing certificate could not be parsed, renewal forbidden");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ dpop = make_X509(r->pool, dcert);
+
+ if (!dpop || dpop->len != pop->len || memcmp(dpop->val, pop->val, dpop->len)) {
+ log_message(r, APR_SUCCESS,
+ "Signing certificate did not match, renewal forbidden");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ /* if we got this far, the certs matched, were not revoked or expired */
+
+ return OK;
}
static void *create_ca_dir_config(apr_pool_t *p, char *d)
@@ -1481,6 +1739,7 @@
ap_hook_child_init(ca_disk_child_init, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_post_config(ca_disk_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_ca_reqauthz(ca_reqauthz_disk, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_ca_sign(ca_sign_disk, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_ca_certstore(ca_certstore_disk, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_ca_getcert(ca_getcert_disk, NULL, NULL, APR_HOOK_MIDDLE);
More information about the rs-commit
mailing list