/* Licensed to Stichting The Commons Conservancy (TCC) under one or more * contributor license agreements. See the AUTHORS file distributed with * this work for additional information regarding copyright ownership. * TCC licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Provider to sign and issue digital certificates based on an OpenSSL based * provider implementation. * * Author: Graham Leggett * */ #include #include #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_protocol.h" #include "http_request.h" #include "util_script.h" #include "mod_ca.h" #undef PACKAGE_BUGREPORT #undef PACKAGE_NAME #undef PACKAGE_STRING #undef PACKAGE_TARNAME #undef PACKAGE_VERSION #include "config.h" #define DEFAULT_CA_DAYS 365*1 #define DEFAULT_CA_DIGEST "SHA256" #define SERIAL_RAND_BITS 64 module AP_MODULE_DECLARE_DATA ca_provider_module; #if HAVE_OSSL_STORE_OPEN_EX #include #include #include #include #include #include #include #include #include #include #include #ifndef sk_EVP_PKEY_new_null DEFINE_STACK_OF(EVP_PKEY) #endif static OSSL_LIB_CTX *libctx = NULL; typedef struct { const char *uri; const char *pq; } ca_uri; typedef struct { unsigned int key_set:1; unsigned int passphrase_set:1; unsigned int digest_set:1; unsigned int cert_set:1; unsigned int days_set:1; unsigned int ext_set:1; unsigned int chains_set:1; unsigned int cas_set:1; unsigned int nextcas_set:1; X509 *signer; X509_NAME *signer_name; unsigned char *signer_der; int signer_der_len; X509 *signer_ca; unsigned char *signer_ca_der; int signer_ca_der_len; apr_time_t signer_ca_expires; int signer_set; X509 *next_signer; unsigned char *next_signer_der; int next_signer_der_len; apr_time_t next_signer_expires; int next_signer_set; ca_uri *key; ca_uri *cert; apr_array_header_t *chains; apr_array_header_t *cas; apr_array_header_t *nextcas; const char *passphrase; const char *digest; const char *digest_pq; int days; apr_hash_t *ext; } ca_config_rec; typedef struct { unsigned int device_set:1; const char *device; } ca_server_rec; typedef struct { const char *key; const char *value; } ca_kv; struct ap_ca_instance_t { void *placeholder; }; static ca_asn1_t *make_ASN1_TIME(apr_pool_t *pool, ASN1_TIME *time) { ca_asn1_t *buf = apr_palloc(pool, sizeof(ca_asn1_t)); unsigned char *tmp; buf->len = i2d_ASN1_TIME(time, NULL); buf->val = tmp = apr_palloc(pool, buf->len); i2d_ASN1_TIME(time, &tmp); return buf; } static void log_detail(request_rec *r, apr_status_t status, const char *message, const char *detail) { int len; BIO *mem = BIO_new(BIO_s_mem()); char *err = apr_palloc(r->pool, HUGE_STRING_LEN); ERR_print_errors(mem); len = BIO_gets(mem, err, HUGE_STRING_LEN - 1); if (len > -1) { err[len] = 0; } apr_table_setn(r->notes, "error-notes", apr_pstrcat(r->pool, "Provider signing: ", ap_escape_html( r->pool, message), NULL)); /* Allow "error-notes" string to be printed by ap_send_error_response() */ apr_table_setn(r->notes, "verbose-error-to", "*"); if (len > 0) { if (detail) { ap_log_rerror( APLOG_MARK, APLOG_ERR, status, r, "mod_ca_provider: " "%s (%s): %s", message, err, detail); } else { ap_log_rerror( APLOG_MARK, APLOG_ERR, status, r, "mod_ca_provider: " "%s (%s)", message, err); } } else { if (detail) { ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, "mod_ca_provider: " "%s: %s", message, detail); } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, "mod_ca_provider: " "%s", message); } } BIO_free(mem); } static void log_message(request_rec *r, apr_status_t status, const char *message) { log_detail(r, status, message, NULL); } static apr_status_t ca_BIO_cleanup(void *data) { BIO_free((BIO *) data); return APR_SUCCESS; } static apr_status_t ca_OSSL_PROVIDER_cleanup(void *data) { OSSL_PROVIDER_unload((OSSL_PROVIDER *) data); return APR_SUCCESS; } static apr_status_t ca_UI_METHOD_cleanup(void *data) { UI_destroy_method((UI_METHOD *) data); return APR_SUCCESS; } static apr_status_t ca_OSSL_STORE_CTX_cleanup(void *data) { OSSL_STORE_close((OSSL_STORE_CTX *) data); return APR_SUCCESS; } static apr_status_t ca_sk_EVP_PKEY_cleanup(void *data) { sk_EVP_PKEY_pop_free((STACK_OF(EVP_PKEY) *) data, EVP_PKEY_free); return APR_SUCCESS; } static apr_status_t ca_sk_X509_cleanup(void *data) { sk_X509_pop_free((STACK_OF(X509) *) data, X509_free); return APR_SUCCESS; } static apr_status_t ca_PKCS7_cleanup(void *data) { PKCS7_free((PKCS7 *) data); return APR_SUCCESS; } static apr_status_t ca_X509_cleanup(void *data) { X509_free((X509 *) data); return APR_SUCCESS; } static apr_status_t ca_X509_EXTENSION_cleanup(void *data) { X509_EXTENSION_free((X509_EXTENSION *) data); return APR_SUCCESS; } static apr_status_t ca_X509_REQ_cleanup(void *data) { X509_REQ_free((X509_REQ *) data); return APR_SUCCESS; } static apr_status_t ca_ASN1_INTEGER_cleanup(void *data) { ASN1_INTEGER_free((ASN1_INTEGER *) data); return APR_SUCCESS; } static apr_status_t ca_ASN1_GENERALIZEDTIME_cleanup(void *data) { ASN1_GENERALIZEDTIME_free((ASN1_GENERALIZEDTIME *) data); return APR_SUCCESS; } static apr_time_t ASN1_TIME_to_gmtime(ASN1_TIME *time) { if (time) { struct tm ts; memset(&ts, 0, sizeof(ts)); switch (time->type) { case V_ASN1_UTCTIME: { sscanf((const char *) time->data, "%02d%02d%02d%02d%02d%02dZ", &ts.tm_year, &ts.tm_mon, &ts.tm_mday, &ts.tm_hour, &ts.tm_min, &ts.tm_sec); ts.tm_mon -= 1; break; } case V_ASN1_GENERALIZEDTIME: { sscanf((const char *) time->data, "%04d%02d%02d%02d%02d%02dZ", &ts.tm_year, &ts.tm_mon, &ts.tm_mday, &ts.tm_hour, &ts.tm_min, &ts.tm_sec); ts.tm_year -= 1900; ts.tm_mon -= 1; break; } } return (apr_time_t) timegm(&ts); } return 0; } static int ca_pass_cb(UI *ui, UI_STRING *uis) { request_rec *r = UI_get0_user_data(ui); ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_provider_module); if (!conf->passphrase) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "Passphrase expected for '%s' but none was provided", conf->key->uri)); return 0; } UI_set_result(ui, uis, conf->passphrase); return 1; } static EVP_PKEY *get_signing_key_and_cert(request_rec *r) { ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_provider_module); OSSL_STORE_CTX *store = NULL; UI_METHOD *ui_method; EVP_PKEY *key = NULL; X509 *cert = NULL; STACK_OF(EVP_PKEY) *keys; STACK_OF(X509) *intermediates; X509 *x; unsigned char *buf; apr_size_t nkeys = 0; apr_size_t ncerts = 0; int len; keys = sk_EVP_PKEY_new_null(); apr_pool_cleanup_register(r->pool, keys, ca_sk_EVP_PKEY_cleanup, apr_pool_cleanup_null); intermediates = sk_X509_new_null(); apr_pool_cleanup_register(r->pool, intermediates, ca_sk_X509_cleanup, apr_pool_cleanup_null); ui_method = UI_create_method("passphrase"); UI_method_set_reader(ui_method, ca_pass_cb); apr_pool_cleanup_register(r->pool, ui_method, ca_UI_METHOD_cleanup, apr_pool_cleanup_null); store = OSSL_STORE_open_ex(conf->key->uri, libctx, conf->key->pq, ui_method, conf, NULL, NULL, NULL); if (!store) { log_detail(r, APR_SUCCESS, "Could not open key store", conf->key->uri); return NULL; } apr_pool_cleanup_register(r->pool, store, ca_OSSL_STORE_CTX_cleanup, apr_pool_cleanup_null); /* get all keys */ OSSL_STORE_expect(store, OSSL_STORE_INFO_PKEY); while (!OSSL_STORE_eof(store)) { int type; OSSL_STORE_INFO *info = OSSL_STORE_load(store); if (!info) { break; } type = OSSL_STORE_INFO_get_type(info); if (type == OSSL_STORE_INFO_PKEY) { EVP_PKEY *k = OSSL_STORE_INFO_get1_PKEY(info); sk_EVP_PKEY_push(keys, k); nkeys++; } OSSL_STORE_INFO_free(info); } /* get all ca certs */ store = OSSL_STORE_open_ex(conf->cert->uri, libctx, conf->cert->pq, ui_method, conf, NULL, NULL, NULL); if (!store) { log_detail(r, APR_SUCCESS, "Could not open certificate store", conf->cert->uri); return NULL; } apr_pool_cleanup_register(r->pool, store, ca_OSSL_STORE_CTX_cleanup, apr_pool_cleanup_null); OSSL_STORE_expect(store, OSSL_STORE_INFO_CERT); while (!OSSL_STORE_eof(store)) { int type; OSSL_STORE_INFO *info = OSSL_STORE_load(store); if (!info) { break; } type = OSSL_STORE_INFO_get_type(info); if (type == OSSL_STORE_INFO_CERT) { X509 *c = OSSL_STORE_INFO_get1_CERT(info); int is_ca = X509_check_ca(c); if (is_ca) { sk_X509_push(intermediates, c); ncerts++; } else { X509_free(c); } } OSSL_STORE_INFO_free(info); } /* for each ca cert that has a matching key, decide on best * cert and key - best cert is the most recent issued. */ while ((x = sk_X509_pop(intermediates))) { EVP_PKEY *k = NULL; int i, n, found = 0; /* no key, skip */ n = sk_EVP_PKEY_num(keys); for (i = 0; i < n; ++i) { k = sk_EVP_PKEY_value(keys, i); if (X509_check_private_key(x, k)) { found = 1; break; } } if (!found) { X509_free(x); continue; } /* no best candidate yet? we're in first place */ if (!cert) { EVP_PKEY_up_ref(k); cert = x; /* don't dup, we're returning this */ key = k; continue; } /* were we issued after the previous best? */ if (ASN1_TIME_compare(X509_get0_notBefore(cert), X509_get0_notBefore(x)) < 0) { X509_free(cert); EVP_PKEY_free(key); EVP_PKEY_up_ref(k); cert = x; /* don't dup, we're returning this */ key = k; continue; } X509_free(x); } /* found no certs with keys? bail out */ if (!cert || !key) { log_detail(r, APR_SUCCESS, "No certs found matching keys", apr_psprintf(r->pool, "%" APR_SIZE_T_FMT " keys at '%s', %" APR_SIZE_T_FMT " non-leaf certs at '%s'", nkeys, conf->key->uri, ncerts, conf->cert->uri)); return NULL; } /* save away the signer certificate */ conf->signer = cert; conf->signer_name = X509_get_subject_name(cert); if (!conf->signer_name) { log_message(r, APR_SUCCESS, "signer certificate did not have a subject name"); return NULL; } len = i2d_X509(cert, NULL); if (len <= 0) { log_message(r, APR_SUCCESS, "could not DER encode the signed X509"); return NULL; } conf->signer_der_len = len; conf->signer_der = buf = apr_palloc(r->pool, len); if (!i2d_X509(cert, &buf)) { log_message(r, APR_SUCCESS, "could not DER encode the signed X509"); return NULL; } apr_pool_cleanup_register(r->pool, cert, ca_X509_cleanup, apr_pool_cleanup_null); return key; } int ca_sign_provider(request_rec *r, apr_hash_t *params, const unsigned char **buffer, apr_size_t *len) { EVP_MD *digest; X509V3_CTX ext_ctx; X509 *cert = NULL; X509_REQ *creq = NULL; EVP_PKEY *pktmp = NULL, *key = NULL; X509_NAME *subject = NULL; ASN1_INTEGER *sno = NULL; ASN1_GENERALIZEDTIME *t = NULL; STACK_OF(X509_EXTENSION) *exts; apr_hash_index_t *iter; PKCS7 *p7; BIO *audit = NULL; const unsigned char *tmp; unsigned char *tmp2; const unsigned char *end; X509 *xs, *next; STACK_OF(X509) *chain; apr_size_t size; apr_time_t time; int rv, i; ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_provider_module); /* key and cert defined? */ if (!conf->key || !conf->cert) { return DECLINED; } /* read in the certificate */ tmp = *buffer; if (!d2i_X509_REQ(&creq, &tmp, *len)) { log_message(r, APR_SUCCESS, "could not DER decode the certificate to be signed"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, creq, ca_X509_REQ_cleanup, apr_pool_cleanup_null); /* search for all ca certificates and keys */ key = get_signing_key_and_cert(r); if (!key) { /* error handled above */ return HTTP_INTERNAL_SERVER_ERROR; } /* create the cert to sign */ cert = X509_new(); if (!cert) { log_message(r, APR_SUCCESS, "X509_new failed"); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, cert, ca_X509_cleanup, apr_pool_cleanup_null); /* set v3 certificate */ X509_set_version(cert, 2); subject = X509_REQ_get_subject_name(creq); if (!subject) { log_message(r, APR_SUCCESS, "request had no subject"); return HTTP_BAD_REQUEST; } X509_set_subject_name(cert, subject); exts = X509_REQ_get_extensions(creq); if (exts) { int idx = -1, crit = -1; GENERAL_NAMES *gens = X509V3_get_d2i(exts, NID_subject_alt_name, &crit, &idx); while (gens) { X509_EXTENSION *san = X509V3_EXT_i2d(NID_subject_alt_name, crit, gens); X509_add_ext(cert, san, -1); gens = X509V3_get_d2i(exts, NID_subject_alt_name, &crit, &idx); } } pktmp = X509_REQ_get_pubkey(creq); if (!pktmp) { log_message(r, APR_SUCCESS, "request had no public key"); return HTTP_BAD_REQUEST; } X509_set_pubkey(cert, pktmp); if (!X509_set_issuer_name(cert, conf->signer_name)) { log_message(r, APR_SUCCESS, "could not set the issuer name"); return HTTP_INTERNAL_SERVER_ERROR; } /* read in the time */ rv = ap_run_ca_gettime(r, &time, NULL, NULL, NULL); if (rv == DECLINED) { log_message(r, APR_SUCCESS, "No module configured to generate the time (ca_get_time)"); return HTTP_INTERNAL_SERVER_ERROR; } if (rv != OK) { return rv; } t = ASN1_GENERALIZEDTIME_adj(NULL, (time_t) apr_time_sec(time), 0, 0); if (!t) { log_message(r, APR_SUCCESS, "Could not create a generalized time"); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, t, ca_ASN1_GENERALIZEDTIME_cleanup, apr_pool_cleanup_null); X509_set_notBefore(cert, X509_gmtime_adj(t, (long) 60 * 60 * 24 * -1)); X509_set_notAfter(cert, X509_gmtime_adj(t, (long) 60 * 60 * 24 * conf->days)); apr_hash_set(params, "notBefore", APR_HASH_KEY_STRING, make_ASN1_TIME(r->pool, X509_get_notBefore(cert))); apr_hash_set(params, "notAfter", APR_HASH_KEY_STRING, make_ASN1_TIME(r->pool, X509_get_notAfter(cert))); /* read in the serial number */ rv = ap_run_ca_makeserial(r, params, buffer, len); if (rv == DECLINED) { log_message(r, APR_SUCCESS, "No module configured to generate the serial number (ca_make_serial)"); return HTTP_INTERNAL_SERVER_ERROR; } if (rv != OK) { return rv; } if (!d2i_ASN1_INTEGER(&sno, buffer, *len)) { log_message(r, APR_SUCCESS, "could not DER decode the serial number (ca_make_serial)"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, sno, ca_ASN1_INTEGER_cleanup, apr_pool_cleanup_null); if (!X509_set_serialNumber(cert, sno)) { log_message(r, APR_SUCCESS, "could not assign serial number"); return HTTP_INTERNAL_SERVER_ERROR; } X509V3_set_ctx(&ext_ctx, conf->signer, cert, NULL, NULL, 0); for (iter = apr_hash_first(r->pool, conf->ext); iter; iter = apr_hash_next(iter)) { const void *vname; void *vval; const char *name, *val; apr_hash_this(iter, &vname, NULL, &vval); name = vname; val = vval; X509_EXTENSION *extension = X509V3_EXT_conf(NULL, &ext_ctx, (char *) name, (char *) val); if (!extension) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "extension '%s' could not be set to '%s'", name, val)); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, extension, ca_X509_EXTENSION_cleanup, apr_pool_cleanup_null); X509_add_ext(cert, extension, -1); } digest = EVP_MD_fetch(libctx, conf->digest, conf->digest_pq); if (!digest) { log_detail(r, APR_SUCCESS, "Digest could not be fetched", apr_pstrcat(r->pool, conf->digest, conf->digest_pq ? " " : NULL, conf->digest_pq, NULL)); return HTTP_INTERNAL_SERVER_ERROR; } if (!X509_sign(cert, key, digest)) { log_message(r, APR_SUCCESS, "could not sign the request"); return HTTP_INTERNAL_SERVER_ERROR; } /* create a new PKCS#7 */ p7 = PKCS7_new(); if (!p7) { log_message(r, APR_SUCCESS, "could not create a PKCS7 response"); return HTTP_INTERNAL_SERVER_ERROR; } else { apr_pool_cleanup_register(r->pool, p7, ca_PKCS7_cleanup, apr_pool_cleanup_null); } PKCS7_set_type(p7, NID_pkcs7_signed); /* workaround to avoid :BAD OBJECT encoding in i2d_PKCS7 - https://github.com/openssl/openssl/issues/8618 */ p7->d.sign->contents->type=OBJ_nid2obj(NID_pkcs7_data); /* add the generated certificate */ if (!PKCS7_add_certificate(p7, cert)) { log_message(r, APR_SUCCESS, "could not add the signed certificate to the PKCS7 response"); return HTTP_INTERNAL_SERVER_ERROR; } /* print the subject, if necessary */ else if (APLOGrdebug(r)) { audit = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, audit, ca_BIO_cleanup, apr_pool_cleanup_null); BIO_puts(audit, "["); X509_NAME_print_ex(audit, X509_get_subject_name(cert), 0, XN_FLAG_RFC2253); BIO_puts(audit, "]"); } /* add the signer certificate */ if (X509_NAME_cmp(X509_get_subject_name(conf->signer), X509_get_issuer_name(conf->signer))) { if (!PKCS7_add_certificate(p7, conf->signer)) { log_message(r, APR_SUCCESS, "could not add the signer certificate to the PKCS7 response"); return HTTP_INTERNAL_SERVER_ERROR; } else if (APLOGrdebug(r)) { BIO_puts(audit, ", ["); X509_NAME_print_ex(audit, X509_get_subject_name(conf->signer), 0, XN_FLAG_RFC2253); BIO_puts(audit, "]"); } } /* add the certificate chain */ tmp = NULL; size = 0; rv = ap_run_ca_getchain(r, &tmp, &size, NULL); if (rv > OK) { return rv; } if (tmp) { chain = sk_X509_new_null(); apr_pool_cleanup_register(r->pool, chain, ca_sk_X509_cleanup, apr_pool_cleanup_null); end = tmp + size; while (tmp < end) { X509 *cert = NULL; if (!(cert = d2i_X509(NULL, &tmp, end - tmp))) { log_message(r, APR_SUCCESS, "could not DER decode the CA certificate"); return HTTP_BAD_REQUEST; } sk_X509_push(chain, cert); } xs = conf->signer; i = chain ? sk_X509_num(chain) : 0; while (i) { next = X509_find_by_subject(chain, X509_get_issuer_name(xs)); if (next) { if (!X509_NAME_cmp(X509_get_subject_name(next), X509_get_issuer_name(next))) { break; } if (!PKCS7_add_certificate(p7, next)) { log_message(r, APR_SUCCESS, "could not add a certificate in the chain to the PKCS7 response"); return HTTP_INTERNAL_SERVER_ERROR; } else if (APLOGrdebug(r)) { BIO_puts(audit, ", ["); X509_NAME_print_ex(audit, X509_get_subject_name(next), 0, XN_FLAG_RFC2253); BIO_puts(audit, "]"); } xs = next; } else { break; } i--; } } /* write out the certificate */ *len = i2d_PKCS7(p7, NULL); if (*len <= 0) { log_message(r, APR_SUCCESS, "could not DER encode the signed PKCS7"); return HTTP_INTERNAL_SERVER_ERROR; } *buffer = tmp2 = apr_palloc(r->pool, *len); if (!i2d_PKCS7(p7, &tmp2)) { log_message(r, APR_SUCCESS, "could not DER encode the signed PKCS7"); return HTTP_INTERNAL_SERVER_ERROR; } if (audit) { unsigned char *buf; int n = BIO_get_mem_data(audit, &buf); ap_log_rerror(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, r, "mod_ca_provider: Successfully signed certificate and chain: %.*s", n, buf); } return OK; } int ca_getca_provider(request_rec *r, const unsigned char **cacert, apr_size_t *len, apr_time_t *validity) { ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_provider_module); BIO *out; unsigned char *der; ca_uri *uris = (ca_uri *)conf->cas->elts; ASN1_TIME *nextupdate; apr_time_t expires = 0; int blen, i; /* chain defined? */ if (!conf->cas->nelts) { return DECLINED; } /* * We try hard to find a chain, depending on the detail * provided. */ out = BIO_new(BIO_s_mem()); for (i = 0; i < conf->cas->nelts; i++) { OSSL_STORE_CTX *store = NULL; store = OSSL_STORE_open_ex(uris[i].uri, libctx, uris[i].pq, NULL, NULL, NULL, NULL, NULL); if (!store) { BIO_free(out); log_detail(r, APR_SUCCESS, "Could not open certificate authority store", uris[i].uri); return HTTP_INTERNAL_SERVER_ERROR; } OSSL_STORE_expect(store, OSSL_STORE_INFO_CERT); while (!OSSL_STORE_eof(store)) { int type; OSSL_STORE_INFO *info = OSSL_STORE_load(store); if (!info) { break; } type = OSSL_STORE_INFO_get_type(info); if (type == OSSL_STORE_INFO_CERT) { X509 *x = OSSL_STORE_INFO_get1_CERT(info); int is_ca = X509_check_ca(x); if (is_ca) { i2d_X509_bio(out, x); nextupdate = X509_get_notAfter(x); if (nextupdate) { apr_time_t e = ASN1_TIME_to_gmtime(nextupdate); if (!expires || expires > e) { expires = e; } } } X509_free(x); } OSSL_STORE_INFO_free(info); } OSSL_STORE_close(store); } blen = BIO_ctrl_pending(out); *len = blen; *cacert = der = apr_palloc(r->pool, blen); BIO_read(out, der, blen); BIO_free(out); if (validity) { *validity = expires; } return OK; } int ca_getnextca_provider(request_rec *r, const unsigned char **cacert, apr_size_t *len, apr_time_t *validity) { ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_provider_module); BIO *out; unsigned char *der; ca_uri *uris = (ca_uri *)conf->nextcas->elts; ASN1_TIME *nextupdate; apr_time_t expires = 0; int blen, i; /* chain defined? */ if (!conf->nextcas->nelts) { return DECLINED; } /* * We try hard to find a chain, depending on the detail * provided. */ out = BIO_new(BIO_s_mem()); for (i = 0; i < conf->nextcas->nelts; i++) { OSSL_STORE_CTX *store = NULL; store = OSSL_STORE_open_ex(uris[i].uri, libctx, uris[i].pq, NULL, NULL, NULL, NULL, NULL); if (!store) { BIO_free(out); log_detail(r, APR_SUCCESS, "Could not open next certificate authority store", uris[i].uri); return HTTP_INTERNAL_SERVER_ERROR; } OSSL_STORE_expect(store, OSSL_STORE_INFO_CERT); while (!OSSL_STORE_eof(store)) { int type; OSSL_STORE_INFO *info = OSSL_STORE_load(store); if (!info) { break; } type = OSSL_STORE_INFO_get_type(info); if (type == OSSL_STORE_INFO_CERT) { X509 *x = OSSL_STORE_INFO_get1_CERT(info); int is_ca = X509_check_ca(x); if (is_ca) { i2d_X509_bio(out, x); nextupdate = X509_get_notAfter(x); if (nextupdate) { apr_time_t e = ASN1_TIME_to_gmtime(nextupdate); if (!expires || expires > e) { expires = e; } } } X509_free(x); } OSSL_STORE_INFO_free(info); } OSSL_STORE_close(store); } blen = BIO_ctrl_pending(out); *len = blen; *cacert = der = apr_palloc(r->pool, blen); BIO_read(out, der, blen); BIO_free(out); if (validity) { *validity = expires; } return OK; } int ca_getchain_provider(request_rec *r, const unsigned char **chain, apr_size_t *len, apr_time_t *validity) { ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_provider_module); BIO *out; unsigned char *der; ca_uri *uris = (ca_uri *)conf->chains->elts; ASN1_TIME *nextupdate; apr_time_t expires = 0; int blen, i; /* chain defined? */ if (!conf->chains->nelts) { return DECLINED; } /* * We try hard to find a chain, depending on the detail * provided. */ out = BIO_new(BIO_s_mem()); for (i = 0; i < conf->chains->nelts; i++) { OSSL_STORE_CTX *store = NULL; store = OSSL_STORE_open_ex(uris[i].uri, libctx, uris[i].pq, NULL, NULL, NULL, NULL, NULL); if (!store) { BIO_free(out); log_detail(r, APR_SUCCESS, "Could not open certificate chain store", uris[i].uri); return HTTP_INTERNAL_SERVER_ERROR; } OSSL_STORE_expect(store, OSSL_STORE_INFO_CERT); while (!OSSL_STORE_eof(store)) { int type; OSSL_STORE_INFO *info = OSSL_STORE_load(store); if (!info) { break; } type = OSSL_STORE_INFO_get_type(info); if (type == OSSL_STORE_INFO_CERT) { X509 *x = OSSL_STORE_INFO_get1_CERT(info); int is_ca = X509_check_ca(x); if (is_ca) { i2d_X509_bio(out, x); nextupdate = X509_get_notAfter(x); if (nextupdate) { apr_time_t e = ASN1_TIME_to_gmtime(nextupdate); if (!expires || expires > e) { expires = e; } } } X509_free(x); } OSSL_STORE_INFO_free(info); } OSSL_STORE_close(store); } blen = BIO_ctrl_pending(out); *len = blen; *chain = der = apr_palloc(r->pool, blen); BIO_read(out, der, blen); BIO_free(out); if (validity) { *validity = expires; } return OK; } static void *create_ca_server_config(apr_pool_t *p, server_rec *s) { ca_server_rec *conf = apr_pcalloc(p, sizeof(ca_server_rec)); return conf; } static void *merge_ca_server_config(apr_pool_t *p, void *basev, void *addv) { ca_server_rec *new = (ca_server_rec *) apr_pcalloc(p, sizeof(ca_config_rec)); ca_server_rec *add = (ca_server_rec *) addv; ca_server_rec *base = (ca_server_rec *) basev; new->device = (add->device_set == 0) ? base->device : add->device; new->device_set = add->device_set || base->device_set; return new; } static void *create_ca_dir_config(apr_pool_t *p, char *d) { ca_config_rec *conf = apr_pcalloc(p, sizeof(ca_config_rec)); conf->chains = apr_array_make(p, 10, sizeof(ca_uri)); conf->cas = apr_array_make(p, 1, sizeof(ca_uri)); conf->nextcas = apr_array_make(p, 1, sizeof(ca_uri)); conf->digest = DEFAULT_CA_DIGEST; conf->days = DEFAULT_CA_DAYS; conf->ext = apr_hash_make(p); return conf; } static void *merge_ca_dir_config(apr_pool_t *p, void *basev, void *addv) { ca_config_rec *new = (ca_config_rec *) apr_pcalloc(p, sizeof(ca_config_rec)); ca_config_rec *add = (ca_config_rec *) addv; ca_config_rec *base = (ca_config_rec *) basev; new->key = (add->key_set == 0) ? base->key : add->key; new->key_set = add->key_set || base->key_set; new->passphrase = (add->passphrase_set == 0) ? base->passphrase : add->passphrase; new->passphrase_set = add->passphrase_set || base->passphrase_set; new->digest = (add->digest_set == 0) ? base->digest : add->digest; new->digest_pq = (add->digest_set == 0) ? base->digest_pq : add->digest_pq; new->digest_set = add->digest_set || base->digest_set; new->cert = (add->cert_set == 0) ? base->cert : add->cert; new->cert_set = add->cert_set || base->cert_set; new->chains = (add->chains_set == 0) ? base->chains : add->chains; new->chains_set = add->chains_set || base->chains_set; new->cas = (add->cas_set == 0) ? base->cas : add->cas; new->cas_set = add->cas_set || base->cas_set; new->nextcas = (add->nextcas_set == 0) ? base->nextcas : add->nextcas; new->nextcas_set = add->nextcas_set || base->nextcas_set; new->days = (add->days_set == 0) ? base->days : add->days; new->days_set = add->days_set || base->days_set; new->ext = (add->ext_set == 0) ? base->ext : apr_hash_overlay(p, add->ext, base->ext); new->ext_set = add->ext_set || base->ext_set; return new; } static const char *set_ca_key(cmd_parms *cmd, void *dconf, const char *arg, const char *pq) { ca_config_rec *conf = dconf; conf->key = apr_palloc(cmd->pool, sizeof(ca_uri)); conf->key->uri = arg; conf->key->pq = pq; conf->key_set = 1; return NULL; } static const char *set_ca_passphrase(cmd_parms *cmd, void *dconf, const char *arg) { ca_config_rec *conf = dconf; conf->passphrase = arg; conf->passphrase_set = 1; return NULL; } static const char *set_ca_digest(cmd_parms *cmd, void *dconf, const char *arg, const char *pq) { ca_config_rec *conf = dconf; conf->digest = arg; conf->digest_pq = pq; conf->digest_set = 1; return NULL; } static const char *set_ca_cert(cmd_parms *cmd, void *dconf, const char *arg, const char *pq) { ca_config_rec *conf = dconf; conf->cert = apr_palloc(cmd->pool, sizeof(ca_uri)); conf->cert->uri = arg; conf->cert->pq = pq; conf->cert_set = 1; return NULL; } static const char *set_ca_chain(cmd_parms *cmd, void *dconf, const char *arg, const char *pq) { ca_config_rec *conf = dconf; ca_uri *chain = apr_array_push(conf->chains); chain->uri = arg; chain->pq = pq; conf->chains_set = 1; return NULL; } static const char *set_ca(cmd_parms *cmd, void *dconf, const char *arg, const char *pq) { ca_config_rec *conf = dconf; ca_uri *ca = apr_array_push(conf->cas); ca->uri = arg; ca->pq = pq; conf->cas_set = 1; return NULL; } static const char *set_ca_next(cmd_parms *cmd, void *dconf, const char *arg, const char *pq) { ca_config_rec *conf = dconf; ca_uri *nextca = apr_array_push(conf->nextcas); nextca->uri = arg; nextca->pq = pq; conf->nextcas_set = 1; return NULL; } static const char *set_ca_days(cmd_parms *cmd, void *dconf, const char *arg) { ca_config_rec *conf = dconf; char *end = NULL; apr_int64_t days = apr_strtoi64(arg, &end, 10); if ((end && *end) || days < 1 || days > APR_INT32_MAX) { return "CAEngineDays argument must be a positive integer representing the days for the certificate to be signed for"; } conf->days = (int) days; conf->days_set = 1; return NULL; } static const char *set_ca_extension(cmd_parms *cmd, void *dconf, const char *name, const char *val) { ca_config_rec *conf = dconf; apr_hash_set(conf->ext, name, APR_HASH_KEY_STRING, val); conf->ext_set = 1; return NULL; } const char *set_provider_device(cmd_parms *cmd, void *dcfg, const char *arg) { server_rec *s = cmd->server; ca_server_rec *conf = ap_get_module_config(s->module_config, &ca_provider_module); OSSL_PARAM_BLD *bld; OSSL_PARAM *params; OSSL_PROVIDER *prov = NULL; const char *err; const char *name = NULL; if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) { return err; } bld = OSSL_PARAM_BLD_new(); if (!bld) { return "CAProvider: could not create new OSSL_PARAM_BLD."; } while (*arg) { char *word, *val; word = ap_getword_conf(cmd->pool, &arg); if (!name) { name = word; continue; } val = strchr(word, '='); if (!val) { return "Invalid CAProvider parameter. Parameter must be " "in the form 'key=value'."; } else { *val++ = '\0'; } OSSL_PARAM_BLD_push_utf8_string(bld, word, val, 0); } params = OSSL_PARAM_BLD_to_param(bld); if (!params) { return "CAProvider: could not OSSL_PARAM_BLD_to_param."; } if (name) { prov = OSSL_PROVIDER_load_ex(libctx, name, params); if (prov) { apr_pool_cleanup_register(cmd->pool, prov, ca_OSSL_PROVIDER_cleanup, apr_pool_cleanup_null); } else { int len; BIO *mem = BIO_new(BIO_s_mem()); char *err = apr_palloc(cmd->pool, HUGE_STRING_LEN); ERR_print_errors(mem); len = BIO_gets(mem, err, HUGE_STRING_LEN - 1); if (len > -1) { err[len] = 0; } return apr_pstrcat(cmd->pool, "CAProvider: could not load '", name, "': ", err, NULL); } } else { return "CAProvider: no name specified."; } conf->device_set = 1; return NULL; } static const command_rec ca_cmds[] = { AP_INIT_TAKE12("CAProviderCertificate", set_ca_cert, NULL, RSRC_CONF | ACCESS_CONF, "URI of signer certificate, followed by optional property query string"), AP_INIT_TAKE12("CAProviderKey", set_ca_key, NULL, RSRC_CONF | ACCESS_CONF, "URI of the signing key, followed by optional property query string"), AP_INIT_TAKE1("CAProviderPassphrase", set_ca_passphrase, NULL, RSRC_CONF | ACCESS_CONF, "Filename of the passphrase to unlock the URI."), AP_INIT_TAKE12("CAProviderDigest", set_ca_digest, NULL, RSRC_CONF | ACCESS_CONF, "Name of the signing digest, followed by optional property query string"), AP_INIT_TAKE12("CAProviderChain", set_ca_chain, NULL, RSRC_CONF | ACCESS_CONF, "URI of signer certificate chain, followed by optional property query string. Can be specified more than once."), AP_INIT_TAKE12("CAProviderCA", set_ca, NULL, RSRC_CONF | ACCESS_CONF, "URI of the CA certificate, followed by optional property query string. Can be specified more than once."), AP_INIT_TAKE12("CAProviderNextCA", set_ca_next, NULL, RSRC_CONF | ACCESS_CONF, "URI of the next CA certificate to follow this one, followed by optional property query string. Can be specified more than once."), AP_INIT_TAKE1("CAProviderDays", set_ca_days, NULL, RSRC_CONF | ACCESS_CONF, "Set to the number of days the certificate must be signed for."), AP_INIT_TAKE2("CAProviderExtension", set_ca_extension, NULL, RSRC_CONF | ACCESS_CONF, "Certificate extension to add to the certificate when signed."), AP_INIT_RAW_ARGS("CAProvider", set_provider_device, NULL, RSRC_CONF, "Name of the crypto provider to use along with optional key=value parameters."), { NULL } }; static apr_status_t ca_cleanup(void *data) { OSSL_LIB_CTX_free(libctx); return APR_SUCCESS; } static int ca_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) { libctx = OSSL_LIB_CTX_new(); apr_pool_cleanup_register(pconf, NULL, ca_cleanup, ca_cleanup); return APR_SUCCESS; } static void register_hooks(apr_pool_t *p) { ap_hook_pre_config(ca_pre_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ca_sign(ca_sign_provider, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ca_getca(ca_getca_provider, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ca_getnextca(ca_getnextca_provider, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ca_getchain(ca_getchain_provider, NULL, NULL, APR_HOOK_MIDDLE); } AP_DECLARE_MODULE(ca_provider) = { STANDARD20_MODULE_STUFF, create_ca_dir_config, /* dir config creater */ merge_ca_dir_config, /* dir merger --- default is to override */ create_ca_server_config, /* server config */ merge_ca_server_config, /* merge server config */ ca_cmds, /* command apr_table_t */ register_hooks /* register hooks */ }; #else static int ca_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "mod_ca_provider is not supported on this platform."); return APR_SUCCESS; } static void register_hooks(apr_pool_t *p) { ap_hook_pre_config(ca_pre_config, NULL, NULL, APR_HOOK_MIDDLE); } AP_DECLARE_MODULE(ca_provider) = { STANDARD20_MODULE_STUFF, NULL, /* dir config creater */ NULL, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ NULL, /* command apr_table_t */ register_hooks /* register hooks */ }; #endif