/* 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 * engine implementation. * * Author: Graham Leggett * */ #include #include #include #include #include #include #include #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" #define DEFAULT_CA_DAYS 365*1 #define SERIAL_RAND_BITS 64 module AP_MODULE_DECLARE_DATA ca_engine_module; static ENGINE *engine = NULL; typedef struct { X509 *signer; X509_NAME *signer_name; unsigned char *signer_der; int signer_der_len; apr_time_t signer_expires; unsigned char *signer_chain_der; int signer_chain_der_len; apr_time_t signer_chain_expires; 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; const char *key; int key_set; int days; int days_set; apr_hash_t *ext; int ext_set; } ca_config_rec; typedef struct { int device_set:1; int pre_set:1; int post_set:1; const char *device; apr_array_header_t *pre; apr_array_header_t *post; } 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_message(request_rec *r, apr_status_t status, const char *message) { 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, "Engine 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) { ap_log_rerror( APLOG_MARK, APLOG_ERR, status, r, "mod_ca_engine: " "%s (%s)", message, err); } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, "mod_ca_engine: " "%s", message); } BIO_free(mem); } static void log_server(server_rec *s, apr_status_t status, const char *message) { int len; BIO *mem = BIO_new(BIO_s_mem()); char err[HUGE_STRING_LEN]; ERR_print_errors(mem); len = BIO_gets(mem, err, HUGE_STRING_LEN - 1); if (len > -1) { err[len] = 0; } if (len > 0) { ap_log_error( APLOG_MARK, APLOG_ERR, status, s, "%s (%s)", message, err); } else { ap_log_error(APLOG_MARK, APLOG_ERR, status, s, "%s", message); } BIO_free(mem); } static apr_status_t ca_BIO_cleanup(void *data) { BIO_free((BIO *) data); return APR_SUCCESS; } static apr_status_t ca_PKCS7_cleanup(void *data) { PKCS7_free((PKCS7 *) data); return APR_SUCCESS; } static apr_status_t ca_sk_X509_cleanup(void *data) { sk_X509_free((STACK_OF(X509) *) 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_status_t signing_key_cleanup(void *data) { EVP_PKEY_free((EVP_PKEY *)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; } int ca_sign_engine(request_rec *r, apr_hash_t *params, const unsigned char **buffer, apr_size_t *len) { 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_engine_module); /* key and cert defined? */ if (!conf->key || !conf->signer_der) { 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); /* 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; } if (!(key = ENGINE_load_private_key(engine, conf->key, NULL, NULL))) { log_message(r, APR_SUCCESS, "could not load the private key from the engine"); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, key, signing_key_cleanup, apr_pool_cleanup_null); 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); } if (!X509_sign(cert, key, EVP_sha256())) { 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 == DECLINED) { log_message(r, APR_SUCCESS, "No module configured to get the CA certificate chain (ca_getchain)"); return HTTP_INTERNAL_SERVER_ERROR; } 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_engine: Successfully signed certificate and chain: %.*s", n, buf); } return OK; } int ca_getca_engine(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_engine_module); /* key and cert defined? */ if (!conf->signer_ca_der) { return DECLINED; } *cacert = conf->signer_ca_der; *len = conf->signer_ca_der_len; if (validity) { *validity = conf->signer_ca_expires; } return OK; } int ca_getnextca_engine(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_engine_module); /* key and cert defined? */ if (!conf->next_signer_der) { return DECLINED; } *cacert = conf->next_signer_der; *len = conf->next_signer_der_len; if (validity) { *validity = conf->next_signer_expires; } return OK; } int ca_getchain_engine(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_engine_module); /* key and cert defined? */ if (!conf->signer_chain_der) { return DECLINED; } *chain = conf->signer_chain_der; *len = conf->signer_chain_der_len; if (validity) { *validity = conf->signer_chain_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)); conf->pre = apr_array_make(p, 10, sizeof(ca_kv)); conf->post = apr_array_make(p, 10, sizeof(ca_kv)); 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; new->pre = (add->pre_set == 0) ? base->pre : add->pre; new->pre_set = add->pre_set || base->pre_set; new->post = (add->post_set == 0) ? base->post : add->post; new->post_set = add->post_set || base->post_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->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->signer = (add->signer_set == 0) ? base->signer : add->signer; new->signer_name = (add->signer_set == 0) ? base->signer_name : add->signer_name; new->signer_der = (add->signer_set == 0) ? base->signer_der : add->signer_der; new->signer_der_len = (add->signer_set == 0) ? base->signer_der_len : add->signer_der_len; new->signer_expires = (add->signer_set == 0) ? base->signer_expires : add->signer_expires; new->signer_chain_der = (add->signer_set == 0) ? base->signer_chain_der : add->signer_chain_der; new->signer_chain_der_len = (add->signer_set == 0) ? base->signer_chain_der_len : add->signer_chain_der_len; new->signer_chain_expires = (add->signer_set == 0) ? base->signer_chain_expires : add->signer_chain_expires; new->signer_ca_der = (add->signer_set == 0) ? base->signer_ca_der : add->signer_ca_der; new->signer_ca_der_len = (add->signer_set == 0) ? base->signer_ca_der_len : add->signer_ca_der_len; new->signer_ca_expires = (add->signer_set == 0) ? base->signer_ca_expires : add->signer_ca_expires; new->signer_set = add->signer_set || base->signer_set; new->next_signer = (add->next_signer_set == 0) ? base->next_signer : add->next_signer; new->next_signer_der = (add->next_signer_set == 0) ? base->next_signer_der : add->next_signer_der; new->next_signer_der_len = (add->next_signer_set == 0) ? base->next_signer_der_len : add->next_signer_der_len; new->next_signer_expires = (add->next_signer_set == 0) ? base->next_signer_expires : add->next_signer_expires; new->next_signer_set = add->next_signer_set || base->next_signer_set; new->key = (add->key_set == 0) ? base->key : add->key; new->key_set = add->key_set || base->key_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_signing_certificate(cmd_parms *cmd, void *dconf, const char *arg) { ca_config_rec *conf = dconf; BIO *in, *out; ASN1_TIME *nextupdate; X509 *cert; int ca_offset = 0, sign_offset = 0, len; apr_time_t expires; out = BIO_new(BIO_s_mem()); arg = ap_server_root_relative(cmd->pool, arg); in = BIO_new(BIO_s_file()); if (BIO_read_filename(in, arg) <= 0) { return apr_psprintf(cmd->pool, "Could not load certificate from: %s", arg); } /* read additional certificates in the chain */ while ((cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL))) { expires = 0; nextupdate = X509_get_notAfter(cert); if (nextupdate) { expires = ASN1_TIME_to_gmtime(nextupdate); } conf->signer_ca = cert; conf->signer_ca_expires = expires; ca_offset = BIO_ctrl_pending(out); i2d_X509_bio(out, cert); if (!sign_offset) { sign_offset = BIO_ctrl_pending(out); } if (!conf->signer) { conf->signer = cert; conf->signer_name = X509_get_subject_name(conf->signer); } if (!conf->signer_expires || conf->signer_expires > expires) { conf->signer_expires = expires; } apr_pool_cleanup_register(cmd->pool, cert, ca_X509_cleanup, apr_pool_cleanup_null); } len = BIO_ctrl_pending(out); conf->signer_der_len = sign_offset; conf->signer_der = apr_palloc(cmd->pool, len); BIO_read(out, conf->signer_der, len); conf->signer_ca_der = conf->signer_der + ca_offset; conf->signer_ca_der_len = len - ca_offset; conf->signer_chain_der = conf->signer_der; conf->signer_chain_der_len = ca_offset; conf->signer_set = 1; BIO_free(in); BIO_free(out); if (!conf->signer) { return apr_psprintf(cmd->pool, "Could not parse certificate from: %s", arg); } return NULL; } static apr_status_t next_signing_certificate_cleanup(void *data) { ca_config_rec *conf = data; X509_free(conf->next_signer); conf->next_signer = NULL; memset(conf->next_signer_der, 0, conf->next_signer_der_len); return APR_SUCCESS; } static const char *set_next_signing_certificate(cmd_parms *cmd, void *dconf, const char *arg) { ca_config_rec *conf = dconf; BIO *in, *out; ASN1_TIME *nextupdate; arg = ap_server_root_relative(cmd->pool, arg); in = BIO_new(BIO_s_file()); if (BIO_read_filename(in, arg) <= 0) { return apr_psprintf(cmd->pool, "Could not load certificate from: %s", arg); } conf->next_signer = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL); if (!conf->next_signer) { BIO_free(in); return apr_psprintf(cmd->pool, "Could not parse certificate from: %s", arg); } /* return the next update (if present) */ nextupdate = X509_get_notAfter(conf->next_signer); if (nextupdate) { conf->next_signer_expires = ASN1_TIME_to_gmtime(nextupdate); } out = BIO_new(BIO_s_mem()); i2d_X509_bio(out, conf->next_signer); conf->next_signer_der_len = BIO_ctrl_pending(out); conf->next_signer_der = apr_palloc(cmd->pool, conf->next_signer_der_len); BIO_read(out, conf->next_signer_der, (int) conf->next_signer_der_len); conf->next_signer_set = 1; apr_pool_cleanup_register(cmd->pool, conf, next_signing_certificate_cleanup, apr_pool_cleanup_null); BIO_free(in); BIO_free(out); return NULL; } static const char *set_signing_key(cmd_parms *cmd, void *dconf, const char *arg) { ca_config_rec *conf = dconf; conf->key = arg; conf->key_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_engine_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_engine_module); const char *err; ENGINE *e; if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) { return err; } if ((e = ENGINE_by_id(arg))) { conf->device = arg; ENGINE_free(e); } else { err = "CAEngineDevice: the engine was not found, must be one of: "; e = ENGINE_get_first(); while (e) { err = apr_pstrcat(cmd->pool, err, ", '", ENGINE_get_id(e), "' (", ENGINE_get_name(e), ")", NULL); /* Iterate; this call implicitly decrements the refcount * on the 'old' e, per the docs in engine.h. */ e = ENGINE_get_next(e); } return err; } conf->device_set = 1; return NULL; } static const char *set_engine_pre_command(cmd_parms *cmd, void *dconf, const char *key, const char *value) { ca_kv *kv; server_rec *s = cmd->server; ca_server_rec *conf = ap_get_module_config(s->module_config, &ca_engine_module); kv = apr_array_push(conf->pre); kv->key = key; kv->value = value; conf->pre_set = 1; return NULL; } static const char *set_engine_post_command(cmd_parms *cmd, void *dconf, const char *key, const char *value) { ca_kv *kv; server_rec *s = cmd->server; ca_server_rec *conf = ap_get_module_config(s->module_config, &ca_engine_module); kv = apr_array_push(conf->post); kv->key = key; kv->value = value; conf->post_set = 1; return NULL; } static const command_rec ca_cmds[] = { AP_INIT_TAKE1("CAEngineCertificate", set_signing_certificate, NULL, RSRC_CONF | ACCESS_CONF, "Filename of certificate chain: signing certificate first, CA certificate last."), AP_INIT_TAKE1("CAEngineKey", set_signing_key, NULL, RSRC_CONF | ACCESS_CONF, "Filename of the signing key."), AP_INIT_TAKE1("CAEngineNextCertificate", set_next_signing_certificate, NULL, RSRC_CONF | ACCESS_CONF, "Filename of the next CA certificate to follow this one, if any."), AP_INIT_TAKE1("CAEngineDays", set_ca_days, NULL, RSRC_CONF | ACCESS_CONF, "Set to the number of days the certificate must be signed for."), AP_INIT_TAKE2("CAEngineExtension", set_ca_extension, NULL, RSRC_CONF | ACCESS_CONF, "Certificate extension to add to the certificate when signed."), AP_INIT_TAKE1("CAEngineDevice", set_engine_device, NULL, RSRC_CONF, "Name of the crypto engine to use."), AP_INIT_TAKE12("CAEnginePreCommand", set_engine_pre_command, NULL, RSRC_CONF, "Commands to process before the engine is initialised."), AP_INIT_TAKE12("CAEnginePostCommand", set_engine_post_command, NULL, RSRC_CONF, "Commands to process after the engine is initialised."), { NULL } }; static apr_status_t ca_cleanup(void *data) { ENGINE_cleanup(); ERR_free_strings(); EVP_cleanup(); return APR_SUCCESS; } static int ca_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) { OPENSSL_load_builtin_modules(); OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); ENGINE_load_builtin_engines(); apr_pool_cleanup_register(pconf, NULL, ca_cleanup, ca_cleanup); return APR_SUCCESS; } static apr_status_t ca_engine_child_cleanup(void *data) { if (engine) { ENGINE_finish(engine); ENGINE_free(engine); engine = NULL; } return APR_SUCCESS; } void ca_engine_init_child(apr_pool_t *p, server_rec *s) { ca_server_rec *conf = ap_get_module_config(s->module_config, &ca_engine_module); if (conf->device_set) { /* first, find the engine */ engine = ENGINE_by_id(conf->device); if (engine) { int i; ca_kv *kv = (ca_kv *)conf->pre->elts; /* preinitialised configuration */ for (i = 0; i < conf->pre->nelts; i++) { if (!ENGINE_ctrl_cmd_string(engine, kv[i].key, kv[i].value, 0)) { log_server(s, APR_SUCCESS, apr_psprintf(p, "Engine '%s' preconfiguration: setting '%s' to '%s' failed", conf->device, kv[i].key, kv[i].value)); ENGINE_free(engine); engine = NULL; return; } } /* initialise the engine */ if (ENGINE_init(engine)) { int i; ca_kv *kv = (ca_kv *)conf->post->elts; /* make sure ENGINE_init() is followed by an ENGINE_finish() */ apr_pool_cleanup_register(p, conf, ca_engine_child_cleanup, ca_engine_child_cleanup); /* post initialised configuration */ for (i = 0; i < conf->post->nelts; i++) { if (!ENGINE_ctrl_cmd_string(engine, kv[i].key, kv[i].value, 0)) { log_server(s, APR_SUCCESS, apr_psprintf(p, "Engine '%s' post configuration: setting '%s' to '%s' failed", conf->device, kv[i].key, kv[i].value)); return; } } if (!ENGINE_set_default(engine, ENGINE_METHOD_ALL)) { log_server(s, APR_SUCCESS, apr_psprintf(p, "Engine '%s' could not be set as default", conf->device)); return; } } else { log_server(s, APR_SUCCESS, apr_psprintf(p, "Engine '%s' could not be initialised", conf->device)); } } else { log_server(s, APR_SUCCESS, apr_psprintf(p, "Engine '%s' could not be found", conf->device)); } } } static void register_hooks(apr_pool_t *p) { ap_hook_pre_config(ca_pre_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_child_init(ca_engine_init_child, NULL,NULL, APR_HOOK_MIDDLE); ap_hook_ca_sign(ca_sign_engine, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ca_getca(ca_getca_engine, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ca_getnextca(ca_getnextca_engine, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ca_getchain(ca_getchain_engine, NULL, NULL, APR_HOOK_MIDDLE); } AP_DECLARE_MODULE(ca_engine) = { 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 */ };