/* 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 save certificate sign requests to disk. * * 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_mutex.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 SERIAL_RAND_BITS 64 #define DEFAULT_SERIAL_SUFFIX "pem" #define DEFAULT_TRANSACTION_SUFFIX "cert" #define DB_type 0 #define DB_exp_date 1 #define DB_rev_date 2 #define DB_serial 3 /* index - unique */ #define DB_file 4 #define DB_name 5 /* index - unique when active and not disabled */ #define DB_NUMBER 6 #define DB_TYPE_REV 'R' #define DB_TYPE_EXP 'E' #define DB_TYPE_VAL 'V' module AP_MODULE_DECLARE_DATA ca_disk_module; typedef struct { const char *csr_path; const char *serial_path; const char *serial_path_suffix; const char *transaction_path; const char *transaction_path_suffix; const char *serial_file; const char *index_file; const char *chain_file; int index_unique; int csr_path_set :1; int serial_path_set :1; int transaction_path_set :1; int serial_file_set :1; int index_file_set :1; int index_unique_set :1; int chain_set :1; } ca_config_rec; struct ap_ca_instance_t { void *placeholder; }; typedef struct { int nid; char *oid; char *name1; char *name2; } niddef_t; #define NEW_NIDS 1 static niddef_t scep_oid_def[NEW_NIDS] = { { -1, "2.16.840.1.113733.1.9.7", "transactionID", "transactionID" } }; static unsigned long index_serial_hash(const OPENSSL_CSTRING *a) { const char *n; n = a[DB_serial]; while (*n == '0') n++; return (lh_strhash(n)); } static int index_serial_cmp(const OPENSSL_CSTRING *a, const OPENSSL_CSTRING *b) { const char *aa, *bb; for (aa = a[DB_serial]; *aa == '0'; aa++) ; for (bb = b[DB_serial]; *bb == '0'; bb++) ; return (strcmp(aa, bb)); } static int index_name_qual(char **a) { return (a[0][0] == 'V'); } static unsigned long index_name_hash(const OPENSSL_CSTRING *a) { return (lh_strhash(a[DB_name])); } int index_name_cmp(const OPENSSL_CSTRING *a, const OPENSSL_CSTRING *b) { return (strcmp(a[DB_name], b[DB_name])); } static IMPLEMENT_LHASH_HASH_FN(index_serial, OPENSSL_CSTRING) static IMPLEMENT_LHASH_COMP_FN(index_serial, OPENSSL_CSTRING) static IMPLEMENT_LHASH_HASH_FN(index_name, OPENSSL_CSTRING) static IMPLEMENT_LHASH_COMP_FN(index_name, OPENSSL_CSTRING) apr_global_mutex_t *ca_disk_mutex; /* Lock around shared memory segment access */ static const char *ca_disk_mutex_type = "ca_disk_mutex_type"; 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, "Disk: ", 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_disk: " "%s (%s)", message, err); } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, "mod_ca_disk: " "%s", message); } BIO_free(mem); } 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_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_BIO_cleanup(void *data) { BIO_free((BIO *) data); return APR_SUCCESS; } static apr_status_t ca_ASN1_STRING_cleanup(void *data) { ASN1_PRINTABLE_free((ASN1_STRING *) data); return APR_SUCCESS; } static apr_status_t ca_ASN1_TIME_cleanup(void *data) { ASN1_TIME_free((ASN1_TIME *) 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_BIGNUM_cleanup(void *data) { BN_free((BIGNUM *) data); return APR_SUCCESS; } static apr_status_t ca_TXT_DB_cleanup(void *data) { TXT_DB_free((TXT_DB *) data); return APR_SUCCESS; } 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); if (s) { apr_pool_cleanup_register(pool, s, ca_ASN1_STRING_cleanup, apr_pool_cleanup_null); } } return s; } static ASN1_TIME *parse_ASN1_TIME(apr_pool_t *pool, ca_asn1_t *time) { ASN1_TIME *t = NULL; if (time) { d2i_ASN1_TIME(&t, &time->val, time->len); if (t) { apr_pool_cleanup_register(pool, t, ca_ASN1_TIME_cleanup, apr_pool_cleanup_null); } } return t; } static ASN1_INTEGER *parse_ASN1_INTEGER(apr_pool_t *pool, ca_asn1_t *integer) { ASN1_INTEGER *i = NULL; if (integer) { d2i_ASN1_INTEGER(&i, &integer->val, integer->len); if (i) { apr_pool_cleanup_register(pool, i, ca_ASN1_INTEGER_cleanup, apr_pool_cleanup_null); } } return i; } static X509_NAME *parse_X509_NAME(apr_pool_t *pool, ca_asn1_t *name) { X509_NAME *n = NULL; if (name) { d2i_X509_NAME(&n, &name->val, name->len); if (n) { apr_pool_cleanup_register(pool, n, ca_X509_NAME_cleanup, apr_pool_cleanup_null); } } return n; } /* Prevent anything untowards in a filename; by replacing * any non alpanums by a _ construct. */ 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); 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; } static int ca_sign_disk(request_rec *r, apr_hash_t *params, const unsigned char **buffer, apr_size_t *len) { X509_REQ *creq = NULL; const unsigned char *tmp = *buffer; int idx; ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_disk_module); /* path to csr directory defined? */ if (!conf->csr_path) { return DECLINED; } /* read in the certificate request */ 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); /* handle the transaction_id if present */ idx = X509_REQ_get_attr_by_NID(creq, OBJ_sn2nid("transactionID"), -1); if (idx == -1) { /* Note: In the absence of a transactionID, it would be nice to use either * the CN, or the subjectAltName, or the subject for this, or perhaps a hash * of some kind. All in good time. */ log_message(r, APR_SUCCESS, "mod_ca frontend did not supply a transaction ID, it is required"); return HTTP_BAD_REQUEST; } else { apr_off_t offset; char buf[HUGE_STRING_LEN]; BIO *out; char *tname, *fname; apr_status_t status; apr_file_t *temp; ASN1_PRINTABLESTRING *str; const char *transaction_id; X509_ATTRIBUTE *attr = X509_REQ_get_attr(creq, idx); if (X509_ATTRIBUTE_count(attr) != 1) { log_message(r, APR_SUCCESS, "the transaction ID must have a single value"); return HTTP_BAD_REQUEST; } str = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_PRINTABLESTRING, NULL); if (!str) { log_message(r, APR_SUCCESS, "the transaction ID must be a printable string"); return HTTP_BAD_REQUEST; } transaction_id = apr_pstrndup(r->pool, (const char *) str->data, str->length); /* create a bio for our CSR */ out = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, out, ca_BIO_cleanup, apr_pool_cleanup_null); if (!X509_REQ_print(out, creq)) { log_message(r, APR_SUCCESS, "CSR summary could not generated"); return HTTP_INTERNAL_SERVER_ERROR; } if (!PEM_write_bio_X509_REQ(out, creq)) { log_message(r, APR_SUCCESS, "CSR could not be PEM encoded"); return HTTP_INTERNAL_SERVER_ERROR; } /* try write away the CSR as a PEM file */ status = apr_filepath_merge(&fname, conf->csr_path, apr_pstrcat(r->pool, safe_filename(r->pool, transaction_id), ".csr", NULL), APR_FILEPATH_SECUREROOT | APR_FILEPATH_NOTRELATIVE, r->pool); if (APR_SUCCESS != status) { log_message(r, status, "The CSR path must be a valid path"); return HTTP_INTERNAL_SERVER_ERROR; } status = apr_filepath_merge(&tname, conf->csr_path, "csr.XXXXXX", APR_FILEPATH_SECUREROOT | APR_FILEPATH_NOTRELATIVE, r->pool); if (APR_SUCCESS != status) { log_message(r, status, "The CSR path must be a valid path"); return HTTP_INTERNAL_SERVER_ERROR; } status = apr_file_mktemp(&temp, tname, APR_CREATE | APR_WRITE | APR_EXCL, r->pool); if (APR_SUCCESS != status) { log_message(r, status, "Could not create the CSR temporary file"); return HTTP_INTERNAL_SERVER_ERROR; } /* write away the CSR */ while ((offset = BIO_read(out, buf, sizeof(buf))) > 0) { status = apr_file_write_full(temp, buf, offset, NULL); if (APR_SUCCESS != status) { log_message(r, status, "Could not write to the CSR temporary file"); apr_file_close(temp); apr_file_remove(tname, r->pool); return HTTP_INTERNAL_SERVER_ERROR; } } status = apr_file_close(temp); if (APR_SUCCESS != status) { log_message(r, status, "Could not write to the CSR temporary file"); apr_file_remove(tname, r->pool); return HTTP_INTERNAL_SERVER_ERROR; } /* rename the file into place */ status = apr_file_rename(tname, fname, r->pool); if (APR_SUCCESS != status) { log_message(r, status, "Could not rename the CSR temporary file"); apr_file_remove(tname, r->pool); return HTTP_INTERNAL_SERVER_ERROR; } } return DONE; } static int ca_certstore_disk(request_rec *r, apr_hash_t *params, const unsigned char *buffer, apr_size_t len) { const unsigned char *tmp = buffer; char *path = NULL, *link = NULL; BIO *out; char *tname; apr_file_t *temp; apr_off_t offset; char buf[HUGE_STRING_LEN]; X509 *cert = NULL; PKCS7 *p7 = NULL; apr_status_t status; ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_disk_module); /* certificate path defined? */ if (!conf->serial_path && !conf->transaction_path) { return DECLINED; } /* read in the certificates */ if (!d2i_PKCS7(&p7, &tmp, len)) { log_message(r, APR_SUCCESS, "could not DER decode the PKCS7 certificate to be stored"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, cert, ca_PKCS7_cleanup, apr_pool_cleanup_null); /* grab the first certificate */ if (OBJ_obj2nid(p7->type) == NID_pkcs7_signed) { STACK_OF(X509) *certs = p7->d.sign->cert; if (sk_X509_num(certs)) { cert = sk_X509_value(certs, 0); } else { log_message(r, APR_SUCCESS, "PKCS7 contained zero certificates, nothing to store"); return HTTP_BAD_REQUEST; } } else { log_message(r, APR_SUCCESS, "PKCS7 was not signedData, nothing to store"); return HTTP_BAD_REQUEST; } /* extract the serial if requested */ if (conf->serial_path) { const char *key; BIGNUM *bn = NULL; ASN1_INTEGER *si = X509_get_serialNumber(cert); if (!si) { log_message(r, APR_SUCCESS, "certificate had no serial number, could not be stored"); return HTTP_BAD_REQUEST; } bn = ASN1_INTEGER_to_BN(si, NULL); if (BN_is_zero(bn)) { key = apr_pstrcat(r->pool, "00.", conf->serial_path_suffix, NULL); } else { char *tmp = BN_bn2hex(bn); key = apr_pstrcat(r->pool, tmp, ".", conf->serial_path_suffix, NULL); OPENSSL_free(tmp); } BN_free(bn); /* set up the path */ status = apr_filepath_merge(&path, conf->serial_path, key, APR_FILEPATH_SECUREROOT | APR_FILEPATH_NOTRELATIVE, r->pool); if (APR_SUCCESS != status) { log_message(r, status, apr_psprintf(r->pool, "The CADiskCertificateBySerialPath could not be merged with: %s", key)); return HTTP_INTERNAL_SERVER_ERROR; } } /* extract the transactionID if requested */ if (conf->transaction_path) { const char *key; ca_asn1_t *transaction_id; transaction_id = params ? apr_hash_get(params, "transactionID", APR_HASH_KEY_STRING) : NULL; if (transaction_id) { 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 HAVE_ASN1_STRING_GET0_DATA kid = (const char *) ASN1_STRING_get0_data(s); #else kid = (const char *) ASN1_STRING_data(s); #endif key = apr_pstrcat(r->pool, apr_pstrndup(r->pool, safe_filename(r->pool,kid), ASN1_STRING_length(s)), ".", conf->transaction_path_suffix, NULL); /* set up the path */ status = apr_filepath_merge(path ? &link : &path, conf->transaction_path, key, APR_FILEPATH_SECUREROOT | APR_FILEPATH_NOTRELATIVE, r->pool); if (APR_SUCCESS != status) { log_message(r, status, apr_psprintf(r->pool, "The CADiskCertificateByTransactionPath could not be merged with: %s", key)); return HTTP_INTERNAL_SERVER_ERROR; } } } /* no path isolated? not for us */ if (!path) { return DECLINED; } /* create a bio for our CSR */ out = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, out, ca_BIO_cleanup, apr_pool_cleanup_null); if (!X509_print(out, cert)) { log_message(r, APR_SUCCESS, "Certificate summary could not generated"); return HTTP_INTERNAL_SERVER_ERROR; } if (!PEM_write_bio_X509(out, cert)) { log_message(r, APR_SUCCESS, "Certificate could not be PEM encoded"); return HTTP_INTERNAL_SERVER_ERROR; } /* try write away the certificate as a PEM file */ tname = apr_pstrcat(r->pool, path, ".XXXXXX", NULL); status = apr_file_mktemp(&temp, tname, APR_CREATE | APR_WRITE | APR_EXCL, r->pool); if (APR_SUCCESS != status) { log_message(r, status, "Could not create the certificate temporary file"); return HTTP_INTERNAL_SERVER_ERROR; } /* write away the buffer */ while ((offset = BIO_read(out, buf, sizeof(buf))) > 0) { status = apr_file_write_full(temp, buf, offset, NULL); if (APR_SUCCESS != status) { log_message(r, status, "Could not write to the certificate temporary file"); apr_file_close(temp); apr_file_remove(tname, r->pool); return HTTP_INTERNAL_SERVER_ERROR; } } status = apr_file_close(temp); if (APR_SUCCESS != status) { log_message(r, status, "Could not write to the certificate temporary file"); apr_file_remove(tname, r->pool); return HTTP_INTERNAL_SERVER_ERROR; } /* rename the file into place */ status = apr_file_rename(tname, path, r->pool); if (APR_SUCCESS != status) { log_message(r, status, "Could not rename the certificate temporary file"); apr_file_remove(tname, r->pool); return HTTP_INTERNAL_SERVER_ERROR; } /* do we have a link? create it now */ if (link) { #if HAVE_APR_FILE_LINK status = apr_file_link(path, link); #else status = APR_ENOTIMPL; #endif if (APR_SUCCESS != status) { log_message(r, status, "Could not link the certificate file to the CADiskCertificateByTransactionPath"); apr_file_remove(path, r->pool); return HTTP_INTERNAL_SERVER_ERROR; } } return OK; } static int ca_getcert_disk(request_rec *r, apr_hash_t *search, const unsigned char **buffer, apr_size_t *len) { ca_asn1_t *serial; ca_asn1_t *transaction_id; ca_asn1_t *issuer; ca_asn1_t *subject; const char *path, *key; apr_status_t status = APR_SUCCESS; char *fname; BIO *in; X509 *cert; X509_NAME *name; PKCS7 *p7; X509 *xs, *next; const unsigned char *tmp, *end; unsigned char *tmp2; apr_size_t size; STACK_OF(X509) *chain; int rv, i; ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_disk_module); /* certificate path defined? */ if (!conf->serial_path && !conf->transaction_path) { return DECLINED; } /* identify the path and the key, serial takes priority over transaction ID */ serial = apr_hash_get(search, "serial", APR_HASH_KEY_STRING); if (serial && conf->serial_path) { BIGNUM *bn = NULL; ASN1_INTEGER *i = parse_ASN1_INTEGER(r->pool, serial); if (i) { bn = ASN1_INTEGER_to_BN(i, NULL); if (BN_is_zero(bn)) { key = apr_pstrcat(r->pool, "00.", conf->serial_path_suffix, NULL); } else { char *tmp = BN_bn2hex(bn); key = apr_pstrcat(r->pool, tmp, ".", conf->serial_path_suffix, NULL); OPENSSL_free(tmp); } path = conf->serial_path; BN_free(bn); } else { log_message(r, status, "The serial number could not be parsed"); return HTTP_BAD_REQUEST; } } else { transaction_id = apr_hash_get(search, "transactionID", APR_HASH_KEY_STRING); if (transaction_id && conf->transaction_path) { ASN1_STRING *s = parse_ASN1_STRING(r->pool, transaction_id); if (s) { path = conf->transaction_path; #if HAVE_ASN1_STRING_GET0_DATA key = apr_pstrcat(r->pool, apr_pstrndup(r->pool, (const char *) ASN1_STRING_get0_data(s), ASN1_STRING_length(s)), ".", conf->transaction_path_suffix, NULL); #else key = apr_pstrcat(r->pool, apr_pstrndup(r->pool, (const char *) ASN1_STRING_data(s), ASN1_STRING_length(s)), ".", conf->transaction_path_suffix, NULL); #endif } else { log_message(r, status, "The transactionID could not be parsed"); return HTTP_BAD_REQUEST; } } else { return DECLINED; } } /* does the file exist? */ status = apr_filepath_merge(&fname, path, safe_filename(r->pool, key), APR_FILEPATH_SECUREROOT | APR_FILEPATH_NOTRELATIVE, r->pool); if (APR_SUCCESS != status) { log_message(r, status, "The certificate was not found"); return HTTP_NOT_FOUND; } 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, status, "The certificate was not found"); return HTTP_NOT_FOUND; } cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL); if (!cert) { log_message(r, status, "The certificate could not be parsed"); return HTTP_NOT_FOUND; } issuer = apr_hash_get(search, "issuer", APR_HASH_KEY_STRING); if (!issuer) { log_message(r, status, "The issuer was not specified"); return HTTP_BAD_REQUEST; } name = parse_X509_NAME(r->pool, issuer); if (X509_NAME_cmp(name, X509_get_issuer_name(cert))) { char *buf; int len; BIO *log = BIO_new(BIO_s_mem()); BIO_puts(log, "('"); X509_NAME_print_ex(log, name, 0, XN_FLAG_ONELINE); BIO_puts(log, "' != '"); X509_NAME_print_ex(log, X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE); BIO_puts(log, "')"); len = BIO_ctrl_pending(log); buf = apr_palloc(r->pool, len); BIO_write(log, buf, len); BIO_free(log); log_message(r, status, apr_psprintf(r->pool, "The certificate did not match the issuer: %.*s", len, buf)); return HTTP_BAD_REQUEST; } subject = apr_hash_get(search, "subject", APR_HASH_KEY_STRING); if (subject) { name = parse_X509_NAME(r->pool, subject); if (X509_NAME_cmp(name, X509_get_subject_name(cert))) { char *buf; int len; BIO *log = BIO_new(BIO_s_mem()); BIO_puts(log, "('"); X509_NAME_print_ex(log, name, 0, XN_FLAG_ONELINE); BIO_puts(log, "' != '"); X509_NAME_print_ex(log, X509_get_subject_name(cert), 0, XN_FLAG_ONELINE); BIO_puts(log, "')"); len = BIO_ctrl_pending(log); buf = apr_palloc(r->pool, len); BIO_write(log, buf, len); BIO_free(log); log_message(r, status, apr_psprintf(r->pool, "The certificate did not match the expected subject: %.*s", len, buf)); return HTTP_BAD_REQUEST; } } /* 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; } /* add the certificate chain */ 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; } 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 = cert; i = chain ? sk_X509_num(chain) : 0; while (i) { next = X509_find_by_subject(chain, X509_get_issuer_name(xs)); if (next) { 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; } if (!X509_NAME_cmp(X509_get_subject_name(xs), X509_get_issuer_name(xs))) { break; } 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 certificate"); 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 PKCS7"); return HTTP_INTERNAL_SERVER_ERROR; } return OK; } static int ca_makeserial_disk(request_rec *r, apr_hash_t *params, const unsigned char **serial, apr_size_t *len) { apr_status_t status; apr_file_t *tfile; ASN1_INTEGER *ai; BIGNUM *bn = NULL; TXT_DB *db = NULL; ASN1_TIME *tm = NULL; X509_NAME *subject = NULL; char *serial_file_new; ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_disk_module); /* serial file defined? */ if (!conf->serial_file) { return DECLINED; } 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; } /* get the not after time and subject, if set */ tm = parse_ASN1_TIME(r->pool, apr_hash_get(params, "notAfter", APR_HASH_KEY_STRING)); subject = parse_X509_NAME(r->pool, apr_hash_get(params, "subject", APR_HASH_KEY_STRING)); /* must we open the openssl database? */ if (conf->index_file && tm && subject) { 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); 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)); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } if (conf->index_unique && !TXT_DB_create_index(db, DB_name, index_name_qual, LHASH_HASH_FN(index_name), LHASH_COMP_FN(index_name))) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "Could not hash the index file (%ld,%ld,%ld)", db->error, db->arg1, db->arg2)); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } } /* create the new file, at the temporary name */ serial_file_new = apr_pstrcat(r->pool, conf->serial_file, ".XXXXXX", NULL); status = apr_file_mktemp(&tfile, serial_file_new, APR_FOPEN_WRITE | APR_FOPEN_EXCL, r->pool); if (APR_SUCCESS != status) { log_message(r, status, "Could not open the serial number index file"); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } /* try open the existing file */ { BIO *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, conf->serial_file) <= 0) { /* on read error, we create a serial file */ bn = BN_new(); apr_pool_cleanup_register(r->pool, bn, ca_BIGNUM_cleanup, apr_pool_cleanup_null); } else { char buffer[1024]; ai = ASN1_INTEGER_new(); if (!ai || !a2i_ASN1_INTEGER(in, ai, buffer, sizeof(buffer))) { log_message(r, APR_SUCCESS, "Could not parse the serial number"); apr_file_close(tfile); apr_file_remove(serial_file_new, r->pool); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, ai, ca_ASN1_INTEGER_cleanup, apr_pool_cleanup_null); bn = ASN1_INTEGER_to_BN(ai, NULL); if (!bn) { log_message(r, APR_SUCCESS, "Could not convert integer to BIGNUM"); apr_file_close(tfile); apr_file_remove(serial_file_new, r->pool); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, bn, ca_BIGNUM_cleanup, apr_pool_cleanup_null); /* increment the serial */ BN_add_word(bn, 1); } } /* now save the serial back again */ ai = BN_to_ASN1_INTEGER(bn, NULL); if (!ai) { log_message(r, APR_SUCCESS, "Could not convert BIGNUM to integer"); apr_file_close(tfile); apr_file_remove(serial_file_new, r->pool); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } else { unsigned char *tmp2; char *buf; apr_size_t size; BIO *out = BIO_new(BIO_s_mem()); i2a_ASN1_INTEGER(out, ai); BIO_puts(out, "\n"); size = BIO_ctrl_pending(out); buf = apr_palloc(r->pool, size); BIO_read(out, buf, size); BIO_free(out); if (db) { OPENSSL_STRING *row = (char **) OPENSSL_malloc(sizeof(char *)*(DB_NUMBER+1)); if (!row) { log_message(r, APR_SUCCESS, "Could not allocate new row into index file"); apr_file_close(tfile); apr_file_remove(serial_file_new, r->pool); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } row[DB_type] = (char *) OPENSSL_malloc(2); row[DB_exp_date] = (char *) OPENSSL_malloc(tm->length+1); if (BN_is_zero(bn)) { row[DB_serial] = BUF_strdup("00"); } else { row[DB_serial] = BN_bn2hex(bn); } row[DB_rev_date] = NULL; row[DB_file] = (char *) OPENSSL_malloc(8); row[DB_name] = X509_NAME_oneline(subject, NULL, 0); if ((!row[DB_type]) || (!row[DB_exp_date]) || (!row[DB_serial]) || (!row[DB_file]) || (!row[DB_name])) { log_message(r, APR_SUCCESS, "Could not allocate new row into index file"); apr_file_close(tfile); apr_file_remove(serial_file_new, r->pool); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } row[DB_type][0] = 'V'; row[DB_type][1] = '\0'; BUF_strlcpy(row[DB_file], "unknown", 8); memcpy(row[DB_exp_date], tm->data, tm->length); row[DB_exp_date][tm->length] = '\0'; if (!TXT_DB_insert(db, row)) { log_message(r, APR_SUCCESS, db->error == DB_ERROR_INDEX_CLASH ? conf->index_unique ? "Index file clash: serial / subject already used" : "Index file clash: serial already used" : "Could not insert new row into index file"); apr_file_close(tfile); apr_file_remove(serial_file_new, r->pool); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } } status = apr_file_write_full(tfile, buf, size, &size); if (APR_SUCCESS != status) { log_message(r, status, "Could not write the serial number index file"); apr_file_close(tfile); apr_file_remove(serial_file_new, r->pool); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } status = apr_file_close(tfile); if (APR_SUCCESS != status) { log_message(r, status, "Could not close the serial number index file"); apr_file_remove(serial_file_new, r->pool); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } status = apr_file_rename(serial_file_new, conf->serial_file, r->pool); if (APR_SUCCESS != status) { log_message(r, status, "Could not rename the serial number index file"); apr_file_remove(serial_file_new, r->pool); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } if (conf->index_file) { char *tname; BIO *out = BIO_new(BIO_s_file()); tname = apr_pstrcat(r->pool, conf->index_file, ".XXXXXX", NULL); if (BIO_write_filename(out, tname) <= 0) { log_message(r, APR_SUCCESS, "Index file could not be created"); BIO_free(out); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } if (db && !TXT_DB_write(out, db)) { log_message(r, APR_SUCCESS, "Index could not generated"); BIO_free(out); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } BIO_free(out); /* rename the file into place */ status = apr_file_rename(tname, conf->index_file, r->pool); if (APR_SUCCESS != status) { log_message(r, status, "Could not rename the index temporary file"); apr_file_remove(tname, r->pool); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } } /* write out the serial number */ *len = i2d_ASN1_INTEGER(ai, NULL); if (*len <= 0) { log_message(r, APR_SUCCESS, "could not DER encode the serial number"); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } *serial = tmp2 = apr_palloc(r->pool, *len); if (!i2d_ASN1_INTEGER(ai, &tmp2)) { log_message(r, APR_SUCCESS, "could not DER encode the serial number"); apr_global_mutex_unlock(ca_disk_mutex); return HTTP_INTERNAL_SERVER_ERROR; } apr_global_mutex_unlock(ca_disk_mutex); 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)); 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->csr_path = (add->csr_path_set == 0) ? base->csr_path : add->csr_path; new->csr_path_set = add->csr_path_set || base->csr_path_set; new->serial_path = (add->serial_path_set == 0) ? base->serial_path : add->serial_path; new->serial_path_suffix = (add->serial_path_set == 0) ? base->serial_path_suffix : add->serial_path_suffix; new->serial_path_set = add->serial_path_set || base->serial_path_set; new->transaction_path = (add->transaction_path_set == 0) ? base->transaction_path : add->transaction_path; new->transaction_path_suffix = (add->transaction_path_set == 0) ? base->transaction_path_suffix : add->transaction_path_suffix; new->transaction_path_set = add->transaction_path_set || base->transaction_path_set; new->serial_file = (add->serial_file_set == 0) ? base->serial_file : add->serial_file; new->serial_file_set = add->serial_file_set || base->serial_file_set; new->index_file = (add->index_file_set == 0) ? base->index_file : add->index_file; new->index_file_set = add->index_file_set || base->index_file_set; new->index_unique = (add->index_unique_set == 0) ? base->index_unique : add->index_unique; new->index_unique_set = add->index_unique_set || base->index_unique_set; return new; } static const char *set_csr_path(cmd_parms *cmd, void *dconf, const char *arg) { ca_config_rec *conf = dconf; arg = ap_server_root_relative(cmd->pool, arg); conf->csr_path = arg; conf->csr_path_set = 1; return NULL; } static const char *set_serial_path(cmd_parms *cmd, void *dconf, const char *path, const char *suffix) { ca_config_rec *conf = dconf; path = ap_server_root_relative(cmd->pool, path); conf->serial_path = path; conf->serial_path_suffix = suffix ? suffix : DEFAULT_SERIAL_SUFFIX; conf->serial_path_set = 1; return NULL; } static const char *set_transaction_path(cmd_parms *cmd, void *dconf, const char *path, const char *suffix) { ca_config_rec *conf = dconf; path = ap_server_root_relative(cmd->pool, path); conf->transaction_path = path; conf->transaction_path_suffix = suffix ? suffix : DEFAULT_TRANSACTION_SUFFIX; conf->transaction_path_set = 1; return NULL; } static const char *set_serial_file(cmd_parms *cmd, void *dconf, const char *arg) { ca_config_rec *conf = dconf; arg = ap_server_root_relative(cmd->pool, arg); conf->serial_file = arg; conf->serial_file_set = 1; return NULL; } static const char *set_index_file(cmd_parms *cmd, void *dconf, const char *arg) { ca_config_rec *conf = dconf; arg = ap_server_root_relative(cmd->pool, arg); conf->index_file = arg; conf->index_file_set = 1; return NULL; } static const char *set_index_unique(cmd_parms *cmd, void *dconf, int flag) { ca_config_rec *conf = dconf; conf->index_unique = flag; conf->index_unique_set = 1; return NULL; } static const command_rec ca_cmds[] = { AP_INIT_TAKE1("CADiskCertificateSignRequestPath", set_csr_path, NULL, RSRC_CONF | ACCESS_CONF, "Set to the path where certificate sign requests should be stored."), AP_INIT_TAKE12("CADiskCertificateByTransactionPath", set_transaction_path, NULL, RSRC_CONF | ACCESS_CONF, "Set to the path for certificates keyed by transaction, followed by optional suffix (defaults to 'cert')."), AP_INIT_TAKE12("CADiskCertificateBySerialPath", set_serial_path, NULL, RSRC_CONF | ACCESS_CONF, "Set to the path for certificates keyed by serial number, followed by optional suffix (defaults to 'pem')."), AP_INIT_TAKE1("CADiskSerialFile", set_serial_file, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of the serial file, if any."), AP_INIT_TAKE1("CADiskIndexFile", set_index_file, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of the index file, if any."), AP_INIT_FLAG("CADiskIndexUnique", set_index_unique, NULL, RSRC_CONF | ACCESS_CONF, "If enabled, the certificate subject must be unique."), { NULL } }; static apr_status_t ca_cleanup(void *data) { ERR_free_strings(); EVP_cleanup(); return APR_SUCCESS; } static int ca_pre_disk_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) { ap_mutex_register(pconf, ca_disk_mutex_type, NULL, APR_LOCK_DEFAULT, 0); OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); apr_pool_cleanup_register(pconf, NULL, ca_cleanup, apr_pool_cleanup_null); /* define the new object definitions needed for SCEP */ int i; for (i = 0; i < NEW_NIDS; i++) { if (scep_oid_def[i].nid == -1) { scep_oid_def[i].nid = OBJ_create(scep_oid_def[i].oid, scep_oid_def[i].name1, scep_oid_def[i].name2); } } return APR_SUCCESS; } static int ca_disk_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { apr_status_t rs; /* if the mutex already exists, as it would after a restart, reuse */ if (ca_disk_mutex) { return OK; } /* Create global mutex */ rs = ap_global_mutex_create(&ca_disk_mutex, NULL, ca_disk_mutex_type, NULL, s, pconf, 0); if (APR_SUCCESS != rs) { return HTTP_INTERNAL_SERVER_ERROR; } return OK; } /* * This routine gets called when a child inits. We use it to attach * to the shared memory segment, and reinitialize the mutex. */ static void ca_disk_child_init(apr_pool_t *p, server_rec *s) { apr_status_t rs; /* * Re-open the mutex for the child. Note we're reusing * the mutex pointer global here. */ rs = apr_global_mutex_child_init(&ca_disk_mutex, apr_global_mutex_lockfile(ca_disk_mutex), p); if (APR_SUCCESS != rs) { ap_log_error( APLOG_MARK, APLOG_CRIT, rs, s, "Failed to reopen mutex %s in child", ca_disk_mutex_type); /* There's really nothing else we can do here, since This * routine doesn't return a status. If this ever goes wrong, * it will turn Apache into a fork bomb. Let's hope it never * will. */ exit(1); /* Ugly, but what else? */ } } static void register_hooks(apr_pool_t *p) { ap_hook_pre_config(ca_pre_disk_config, NULL, NULL, APR_HOOK_MIDDLE); 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_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); ap_hook_ca_makeserial(ca_makeserial_disk, NULL, NULL, APR_HOOK_MIDDLE); } AP_DECLARE_MODULE(ca_disk) = { 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 */ };