/* 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. */ /* * Simple provider to sign and issue digital certificates. * * Author: Graham Leggett * */ #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_simple_module; typedef struct { unsigned int signer_set :1; unsigned int next_signer_set :1; unsigned int key_set :1; unsigned int days_set :1; unsigned int serial_random_set :1; unsigned int serial_subject_set :1; unsigned int time_set :1; unsigned int ext_set :1; unsigned int pkey_ctx_set :1; X509 *signer; X509 *signer_ca; X509 *next_signer; X509_NAME *signer_name; EVP_PKEY *key; EVP_PKEY_CTX *pkey_ctx; apr_hash_t *ext; unsigned char *signer_der; unsigned char *signer_chain_der; unsigned char *signer_ca_der; unsigned char *next_signer_der; apr_time_t signer_expires; apr_time_t signer_chain_expires; apr_time_t signer_ca_expires; apr_time_t next_signer_expires; int signer_der_len; int signer_chain_der_len; int signer_ca_der_len; int next_signer_der_len; int days; int serial_random; int serial_subject; int time; } ca_config_rec; 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, "Simple 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_simple: " "%s (%s)", message, err); } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, "mod_ca_simple: " "%s", message); } BIO_free(mem); } static const char *log_config(cmd_parms *cmd, 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; } BIO_free(mem); if (len > 0) { return apr_psprintf(cmd->pool, "%s (%s)", message, err); } else { return message; } } static apr_status_t ca_BIO_cleanup(void *data) { BIO_free((BIO *) data); return APR_SUCCESS; } static apr_status_t ca_EVP_PKEY_cleanup(void *data) { EVP_PKEY_free((EVP_PKEY *) data); return APR_SUCCESS; } static apr_status_t ca_EVP_PKEY_CTX_cleanup(void *data) { EVP_PKEY_CTX_free((EVP_PKEY_CTX *) 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_X509_NAME_cleanup(void *data) { X509_NAME_free((X509_NAME *) data); return APR_SUCCESS; } static apr_status_t ca_BIGNUM_cleanup(void *data) { BN_free((BIGNUM *) 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; } int ca_sign_simple(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; 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_simple_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; } 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, conf->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_simple: Successfully signed certificate and chain: %.*s", n, buf); } return OK; } int ca_getca_simple(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_simple_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_simple(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_simple_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_simple(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_simple_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 int ca_makeserial_simple_subject(request_rec *r, apr_hash_t *params, const unsigned char **serial, apr_size_t *len) { ca_asn1_t *subject; X509_NAME *s = NULL; ASN1_INTEGER *sno = NULL; unsigned char *tmp2; char buf[HUGE_STRING_LEN]; ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_simple_module); /* extract serial from CSR? */ if (!conf->serial_subject) { return DECLINED; } /* read in the certificate sign request, if any */ subject = params ? apr_hash_get(params, "subject", APR_HASH_KEY_STRING) : NULL; if (subject) { const unsigned char *tmp = subject->val; if (!d2i_X509_NAME(&s, &tmp, subject->len)) { log_message(r, APR_SUCCESS, "could not DER decode the subject, serial number not extracted"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, s, ca_X509_NAME_cleanup, apr_pool_cleanup_null); } else { log_message(r, APR_SUCCESS, "Subject was not available while CASimpleSerialSubject was enabled, serial number not extracted"); return HTTP_BAD_REQUEST; } /* extract serial from subject? */ if (X509_NAME_get_text_by_NID(s, NID_serialNumber, buf, sizeof(buf)) >= 0) { BIGNUM *btmp = NULL; if (!BN_hex2bn(&btmp, buf)) { log_message(r, APR_SUCCESS, "could not parse serial number within the subject"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, btmp, ca_BIGNUM_cleanup, apr_pool_cleanup_null); sno = BN_to_ASN1_INTEGER(btmp, sno); if (!sno) { log_message(r, APR_SUCCESS, "could not create asn1 integer"); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, sno, ca_ASN1_INTEGER_cleanup, apr_pool_cleanup_null); } else { return DECLINED; } /* write out the serial number */ *len = i2d_ASN1_INTEGER(sno, NULL); if (*len <= 0) { log_message(r, APR_SUCCESS, "could not DER encode the serial number"); return HTTP_INTERNAL_SERVER_ERROR; } *serial = tmp2 = apr_palloc(r->pool, *len); if (!i2d_ASN1_INTEGER(sno, &tmp2)) { log_message(r, APR_SUCCESS, "could not DER encode the serial number"); return HTTP_INTERNAL_SERVER_ERROR; } return OK; } static int ca_makeserial_simple_random(request_rec *r, apr_hash_t *params, const unsigned char **serial, apr_size_t *len) { ASN1_INTEGER *sno = NULL; BIGNUM *btmp = NULL; unsigned char *tmp2; ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_simple_module); /* random serial number? */ if (!conf->serial_random) { return DECLINED; } btmp = BN_new(); apr_pool_cleanup_register(r->pool, btmp, ca_BIGNUM_cleanup, apr_pool_cleanup_null); if (!BN_rand(btmp, SERIAL_RAND_BITS, 0, 0)) { log_message(r, APR_SUCCESS, "could not create random serial number"); return HTTP_INTERNAL_SERVER_ERROR; } sno = BN_to_ASN1_INTEGER(btmp, sno); if (!sno) { log_message(r, APR_SUCCESS, "could not create asn1 integer"); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, sno, ca_ASN1_INTEGER_cleanup, apr_pool_cleanup_null); /* write out the serial number */ *len = i2d_ASN1_INTEGER(sno, NULL); if (*len <= 0) { log_message(r, APR_SUCCESS, "could not DER encode the serial number"); return HTTP_INTERNAL_SERVER_ERROR; } *serial = tmp2 = apr_palloc(r->pool, *len); if (!i2d_ASN1_INTEGER(sno, &tmp2)) { log_message(r, APR_SUCCESS, "could not DER encode the serial number"); return HTTP_INTERNAL_SERVER_ERROR; } return OK; } static int ca_gettime_simple(request_rec *r, apr_time_t *time, apr_interval_time_t *as, apr_interval_time_t *ams, apr_interval_time_t *amicro) { ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_simple_module); /* return the time? */ if (!conf->time) { return DECLINED; } if (time) { *time = apr_time_now(); } if (as) { *as = 1; } if (ams) { *ams = 0; } if (amicro) { *amicro = 0; } return OK; } static int ca_makekey_simple(request_rec *r, apr_hash_t *params, const unsigned char **key, apr_size_t *len) { ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_simple_module); EVP_PKEY *pkey = NULL; unsigned char *tmp; /* return a private key? */ if (!conf->pkey_ctx) { return DECLINED; } if (EVP_PKEY_keygen(conf->pkey_ctx, &pkey) <= 0) { log_message(r, APR_SUCCESS, "could not generate a private key"); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, pkey, ca_EVP_PKEY_cleanup, apr_pool_cleanup_null); /* write out the key */ *len = i2d_PrivateKey(pkey, NULL); if (*len <= 0) { log_message(r, APR_SUCCESS, "could not DER encode the private key"); return HTTP_INTERNAL_SERVER_ERROR; } *key = tmp = apr_palloc(r->pool, *len); if (!i2d_PrivateKey(pkey, &tmp)) { log_message(r, APR_SUCCESS, "could not DER encode the private key"); return HTTP_INTERNAL_SERVER_ERROR; } return OK; } 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->serial_random = (add->serial_random_set == 0) ? base->serial_random : add->serial_random; new->serial_random_set = add->serial_random_set || base->serial_random_set; new->serial_subject = (add->serial_subject_set == 0) ? base->serial_subject : add->serial_subject; new->serial_subject_set = add->serial_subject_set || base->serial_subject_set; new->time = (add->time_set == 0) ? base->time : add->time; new->time_set = add->time_set || base->time_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; new->pkey_ctx = (add->pkey_ctx_set == 0) ? base->pkey_ctx : add->pkey_ctx; new->pkey_ctx_set = add->pkey_ctx_set || base->pkey_ctx_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 apr_status_t signing_key_cleanup(void *data) { ca_config_rec *conf = data; EVP_PKEY_free(conf->key); conf->key = NULL; return APR_SUCCESS; } static const char *set_signing_key(cmd_parms *cmd, void *dconf, const char *arg) { ca_config_rec *conf = dconf; BIO *in; 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 key from: %s", arg); } conf->key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL); if (!conf->key) { BIO_free(in); return apr_psprintf(cmd->pool, "Could not parse key from: %s", arg); } conf->key_set = 1; apr_pool_cleanup_register(cmd->pool, conf, signing_key_cleanup, apr_pool_cleanup_null); BIO_free(in); 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 "CASimpleDays 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_serial_random(cmd_parms *cmd, void *dconf, int flag) { ca_config_rec *conf = dconf; conf->serial_random = flag; conf->serial_random_set = 1; return NULL; } static const char *set_ca_serial_subject(cmd_parms *cmd, void *dconf, int flag) { ca_config_rec *conf = dconf; conf->serial_subject = flag; conf->serial_subject_set = 1; return NULL; } static const char *set_ca_time(cmd_parms *cmd, void *dconf, int flag) { ca_config_rec *conf = dconf; conf->time = flag; conf->time_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; } static const char *set_ca_algorithm(cmd_parms *cmd, void *dconf, int argc, char *const argv[]) { ca_config_rec *conf = dconf; const EVP_PKEY_ASN1_METHOD *method; int pkey_id; ERR_clear_error(); if (argc < 1) return log_config(cmd, "CASimpleAlgorithm needs at least one " "argument -- the name of the algoritm"); method = EVP_PKEY_asn1_find_str(NULL, argv[0], -1); if (!method) { return log_config(cmd, apr_psprintf(cmd->pool, "CASimpleAlgorithm '%s' was not found", argv[0])); } EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, method); conf->pkey_ctx = EVP_PKEY_CTX_new_id(pkey_id, NULL); if (!conf->pkey_ctx) { return log_config(cmd, apr_psprintf(cmd->pool, "CASimpleAlgorithm '%s': EVP_PKEY_CTX could not be created for private key ID %d", argv[0], pkey_id)); } apr_pool_cleanup_register(cmd->pool, conf->pkey_ctx, ca_EVP_PKEY_CTX_cleanup, apr_pool_cleanup_null); if (EVP_PKEY_keygen_init(conf->pkey_ctx) <= 0) { return log_config(cmd, apr_psprintf(cmd->pool, "CASimpleAlgorithm '%s': EVP_PKEY keygen could not be initialised", argv[0])); } for(int i = 1; i < argc; i++) { const char *arg = argv[i]; char *val = strchr(arg, '='); if (val) { *(val++) = 0; } else { return log_config(cmd, apr_psprintf(cmd->pool, "CASimpleAlgorithm parameter %d '%s' must be a name=value pair", i, arg)); } if (EVP_PKEY_CTX_ctrl_str(conf->pkey_ctx, arg, val) <= 0) { return log_config(cmd, apr_psprintf(cmd->pool, "CASimpleAlgorithm parameter %d '%s' cannot be set to '%s'", i, arg, val)); } } conf->pkey_ctx_set = 1; return NULL; } static const char *set_ca_paramfile(cmd_parms *cmd, void *dconf, const char *arg) { ca_config_rec *conf = dconf; BIO *pbio; EVP_PKEY *pkey = NULL; pbio = BIO_new_file(arg, "r"); if (!pbio) { return log_config(cmd, apr_psprintf(cmd->pool, "CASimpleParamFile '%s': Can't open parameter file", arg)); } pkey = PEM_read_bio_Parameters(pbio, NULL); BIO_free(pbio); if (!pkey) { return log_config(cmd, apr_psprintf(cmd->pool, "CASimpleParamFile '%s': Error reading parameter file", arg)); } conf->pkey_ctx = EVP_PKEY_CTX_new(pkey, NULL); EVP_PKEY_free(pkey); if (!conf->pkey_ctx) { return log_config(cmd, apr_psprintf(cmd->pool, "CASimpleParamFile '%s': EVP_PKEY_CTX could not be created", arg)); } apr_pool_cleanup_register(cmd->pool, conf->pkey_ctx, ca_EVP_PKEY_CTX_cleanup, apr_pool_cleanup_null); if (EVP_PKEY_keygen_init(conf->pkey_ctx) <= 0) { return log_config(cmd, apr_psprintf(cmd->pool, "CASimpleParamFile '%s': EVP_PKEY keygen could not be initialised", arg)); } conf->pkey_ctx_set = 1; return NULL; } static const command_rec ca_cmds[] = { AP_INIT_TAKE1("CASimpleCertificate", set_signing_certificate, NULL, RSRC_CONF | ACCESS_CONF, "Filename of certificate chain: signing certificate first, CA certificate last."), AP_INIT_TAKE1("CASimpleKey", set_signing_key, NULL, RSRC_CONF | ACCESS_CONF, "Filename of the signing key."), AP_INIT_TAKE1("CASimpleNextCertificate", set_next_signing_certificate, NULL, RSRC_CONF | ACCESS_CONF, "Filename of the next CA certificate to follow this one, if any."), AP_INIT_TAKE1("CASimpleDays", set_ca_days, NULL, RSRC_CONF | ACCESS_CONF, "Set to the number of days the certificate must be signed for."), AP_INIT_FLAG("CASimpleSerialRandom", set_ca_serial_random, NULL, RSRC_CONF | ACCESS_CONF, "When enabled, a random serial number will be allocated."), AP_INIT_FLAG("CASimpleSerialSubject", set_ca_serial_subject, NULL, RSRC_CONF | ACCESS_CONF, "When enabled, the serial number will be allocated from the certificate sign request, if present."), AP_INIT_FLAG("CASimpleTime", set_ca_time, NULL, RSRC_CONF | ACCESS_CONF, "When enabled, the time will be obtained from the system time."), AP_INIT_TAKE2("CASimpleExtension", set_ca_extension, NULL, RSRC_CONF | ACCESS_CONF, "Certificate extension to add to the certificate when signed."), AP_INIT_TAKE_ARGV("CASimpleAlgorithm", set_ca_algorithm, NULL, RSRC_CONF | ACCESS_CONF, "When enabled, private keys will be generated with this algorithm."), AP_INIT_TAKE1("CASimpleParamFile", set_ca_paramfile, NULL, RSRC_CONF | ACCESS_CONF, "When enabled, private keys will be generated with this parameter file."), { NULL } }; static apr_status_t ca_cleanup(void *data) { 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(); apr_pool_cleanup_register(pconf, NULL, ca_cleanup, apr_pool_cleanup_null); 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_simple, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ca_getca(ca_getca_simple, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ca_getnextca(ca_getnextca_simple, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ca_getchain(ca_getchain_simple, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ca_makeserial(ca_makeserial_simple_subject, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ca_makeserial(ca_makeserial_simple_random, NULL, NULL, APR_HOOK_LAST); ap_hook_ca_makekey(ca_makekey_simple, NULL, NULL, APR_HOOK_LAST); ap_hook_ca_gettime(ca_gettime_simple, NULL, NULL, APR_HOOK_LAST); } AP_DECLARE_MODULE(ca_simple) = { STANDARD20_MODULE_STUFF, create_ca_dir_config, /* dir config creater */ merge_ca_dir_config, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ ca_cmds, /* command apr_table_t */ register_hooks /* register hooks */ };