/* 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 module to handle CRLs and OCSP requests against CRLs. * * Author: Graham Leggett * */ #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" #include #undef PACKAGE_BUGREPORT #undef PACKAGE_NAME #undef PACKAGE_STRING #undef PACKAGE_TARNAME #undef PACKAGE_VERSION #include "config.h" #define DEFAULT_CA_DAYS 365*1 #define SERIAL_RAND_BITS 64 module AP_MODULE_DECLARE_DATA ca_crl_module; typedef struct { X509_CRL *crl; apr_hash_t *crl_index; unsigned char *crl_der; int crl_der_len; apr_time_t crl_expires; int crl_set; } ca_config_rec; struct ap_ca_instance_t { void *placeholder; }; 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, "While reading the CRL: ", 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_crl: " "%s (%s)", message, err); } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, "mod_ca_crl: " "%s", message); } BIO_free(mem); } static ca_asn1_t *make_ASN1_ENUMERATED(apr_pool_t *pool, ASN1_ENUMERATED *enumerated) { ca_asn1_t *buf = apr_palloc(pool, sizeof(ca_asn1_t)); unsigned char *tmp; buf->len = i2d_ASN1_ENUMERATED(enumerated, NULL); buf->val = tmp = apr_palloc(pool, buf->len); i2d_ASN1_ENUMERATED(enumerated, &tmp); return buf; } static ca_asn1_t *make_ASN1_INTEGER(apr_pool_t *pool, const ASN1_INTEGER *integer) { ca_asn1_t *buf = apr_palloc(pool, sizeof(ca_asn1_t)); unsigned char *tmp; buf->len = i2d_ASN1_INTEGER((ASN1_INTEGER *)integer, NULL); buf->val = tmp = apr_palloc(pool, buf->len); i2d_ASN1_INTEGER((ASN1_INTEGER *)integer, &tmp); return buf; } static ca_asn1_t *make_ASN1_GENERALIZEDTIME(apr_pool_t *pool, ASN1_GENERALIZEDTIME *time) { ca_asn1_t *buf = apr_palloc(pool, sizeof(ca_asn1_t)); unsigned char *tmp; buf->len = i2d_ASN1_GENERALIZEDTIME(time, NULL); buf->val = tmp = apr_palloc(pool, buf->len); i2d_ASN1_GENERALIZEDTIME(time, &tmp); return buf; } static ca_asn1_t *make_ASN1_TIME(apr_pool_t *pool, const ASN1_TIME *time) { ca_asn1_t *buf = apr_palloc(pool, sizeof(ca_asn1_t)); unsigned char *tmp; buf->len = i2d_ASN1_TIME((ASN1_TIME *)time, NULL); buf->val = tmp = apr_palloc(pool, buf->len); i2d_ASN1_TIME((ASN1_TIME *)time, &tmp); return buf; } static ca_asn1_t *make_ASN1_OBJECT(apr_pool_t *pool, ASN1_OBJECT *object) { ca_asn1_t *buf = apr_palloc(pool, sizeof(ca_asn1_t)); unsigned char *tmp; buf->len = i2d_ASN1_OBJECT(object, NULL); buf->val = tmp = apr_palloc(pool, buf->len); i2d_ASN1_OBJECT(object, &tmp); return buf; } static apr_time_t ASN1_TIME_to_gmtime(const ASN1_TIME *time) { if (time) { struct tm ts; memset(&ts, 0, sizeof(ts)); switch (time->type) { case V_ASN1_UTCTIME: { sscanf((const char *) time->data, "%02d%02d%02d%02d%02d%02dZ", &ts.tm_year, &ts.tm_mon, &ts.tm_mday, &ts.tm_hour, &ts.tm_min, &ts.tm_sec); ts.tm_mon -= 1; break; } case V_ASN1_GENERALIZEDTIME: { sscanf((const char *) time->data, "%04d%02d%02d%02d%02d%02dZ", &ts.tm_year, &ts.tm_mon, &ts.tm_mday, &ts.tm_hour, &ts.tm_min, &ts.tm_sec); ts.tm_year -= 1900; ts.tm_mon -= 1; break; } } return (apr_time_t) timegm(&ts); } return 0; } static int ca_getcrl_crl(request_rec *r, const unsigned char **crl, apr_size_t *len, apr_time_t *validity) { ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_crl_module); /* key and cert defined? */ if (!conf->crl_der) { return DECLINED; } *crl = conf->crl_der; *len = conf->crl_der_len; if (validity) { *validity = conf->crl_expires; } return OK; } static int ca_getcertstatus_crl(request_rec *r, apr_hash_t *certstatus, apr_time_t *validity) { ca_asn1_t *serial; X509_REVOKED *revoked; ca_config_rec *conf = ap_get_module_config(r->per_dir_config, &ca_crl_module); /* hash defined? */ if (!conf->crl_index) { return DECLINED; } /* retrieve the serial number */ serial = apr_hash_get(certstatus, CA_SERIAL_NUMBER, APR_HASH_KEY_STRING); if (!serial) { log_message(r, APR_SUCCESS, "could not decode the certificate serial number"); return HTTP_BAD_REQUEST; } else { int *status = apr_pcalloc(r->pool, sizeof(int)); const ASN1_TIME *nextupdate, *thisupdate; ASN1_ENUMERATED *reason; ASN1_OBJECT *hold; ASN1_GENERALIZEDTIME *invalidity; /* look up the certificate in the index */ revoked = apr_hash_get(conf->crl_index, serial->val, serial->len); if (!revoked) { *status = CA_CERT_STATUS_GOOD; } else { *status = CA_CERT_STATUS_REVOKED; /* save away the revocation date */ #if HAVE_X509_REVOKED_GET0_REVOCATIONDATE apr_hash_set(certstatus, CA_REVOCATION_TIME, APR_HASH_KEY_STRING, make_ASN1_TIME(r->pool, X509_REVOKED_get0_revocationDate(revoked))); #else apr_hash_set(certstatus, CA_REVOCATION_TIME, APR_HASH_KEY_STRING, make_ASN1_TIME(r->pool, revoked->revocationDate)); #endif /* do we have a revocation reason? */ reason = X509_REVOKED_get_ext_d2i(revoked, NID_crl_reason, NULL, NULL); if (reason) { apr_hash_set(certstatus, CA_REVOCATION_REASON, APR_HASH_KEY_STRING, make_ASN1_ENUMERATED(r->pool, reason)); } /* do we have a hold instruction? */ hold = X509_REVOKED_get_ext_d2i(revoked, NID_hold_instruction_code, NULL, NULL); if (hold) { apr_hash_set(certstatus, CA_HOLD_INSTRUCTION_CODE, APR_HASH_KEY_STRING, make_ASN1_OBJECT(r->pool, hold)); } /* do we have an invalidity date? */ invalidity = X509_REVOKED_get_ext_d2i(revoked, NID_invalidity_date, NULL, NULL); if (invalidity) { apr_hash_set(certstatus, CA_INVALIDITY_DATE, APR_HASH_KEY_STRING, make_ASN1_GENERALIZEDTIME(r->pool, invalidity)); } } /* return the last update */ #if HAVE_X509_CRL_GET0_LASTUPDATE thisupdate = X509_CRL_get0_lastUpdate(conf->crl); #else thisupdate = X509_CRL_get_lastUpdate(conf->crl); #endif if (thisupdate) { apr_hash_set(certstatus, CA_THIS_UPDATE, APR_HASH_KEY_STRING, make_ASN1_TIME(r->pool, thisupdate)); } /* return the next update (if present) */ #if HAVE_X509_CRL_GET0_NEXTUPDATE nextupdate = X509_CRL_get0_nextUpdate(conf->crl); #else nextupdate = X509_CRL_get_nextUpdate(conf->crl); #endif if (nextupdate) { apr_hash_set(certstatus, CA_NEXT_UPDATE, APR_HASH_KEY_STRING, make_ASN1_TIME(r->pool, nextupdate)); if (validity) { *validity = ASN1_TIME_to_gmtime(nextupdate); } } /* return the status */ apr_hash_set(certstatus, CA_CERT_STATUS, APR_HASH_KEY_STRING, status); } return OK; } static void *create_ca_crl_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_crl_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->crl = (add->crl_set == 0) ? base->crl : add->crl; new->crl_index = (add->crl_set == 0) ? base->crl_index : add->crl_index; new->crl_der = (add->crl_set == 0) ? base->crl_der : add->crl_der; new->crl_der_len = (add->crl_set == 0) ? base->crl_der_len : add->crl_der_len; new->crl_expires = (add->crl_set == 0) ? base->crl_expires : add->crl_expires; new->crl_set = add->crl_set || base->crl_set; return new; } static apr_status_t crl_cleanup(void *data) { ca_config_rec *conf = data; X509_CRL_free(conf->crl); conf->crl = NULL; memset(conf->crl_der, 0, conf->crl_der_len); return APR_SUCCESS; } static const char *set_crl(cmd_parms *cmd, void *dconf, const char *arg) { ca_config_rec *conf = dconf; BIO *in, *out; STACK_OF(X509_REVOKED) *revoked; const ASN1_TIME *nextupdate; apr_int64_t i; 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 CRL from: %s", arg); } conf->crl = PEM_read_bio_X509_CRL(in, NULL, NULL, NULL); if (!conf->crl) { BIO_free(in); return apr_psprintf(cmd->pool, "Could not parse CRL from: %s", arg); } out = BIO_new(BIO_s_mem()); i2d_X509_CRL_bio(out, conf->crl); conf->crl_der_len = BIO_ctrl_pending(out); conf->crl_der = apr_palloc(cmd->pool, conf->crl_der_len); BIO_read(out, conf->crl_der, (int) conf->crl_der_len); conf->crl_set = 1; /* index the CRL */ conf->crl_index = apr_hash_make(cmd->pool); revoked = X509_CRL_get_REVOKED(conf->crl); for (i = 0; i < sk_X509_REVOKED_num(revoked); i++) { X509_REVOKED *r = sk_X509_REVOKED_value(revoked, i); if (r) { #if HAVE_X509_REVOKED_GET0_SERIALNUMBER ca_asn1_t *serial = make_ASN1_INTEGER(cmd->pool, X509_REVOKED_get0_serialNumber(r)); #else ca_asn1_t *serial = make_ASN1_INTEGER(cmd->pool, r->serialNumber); #endif if (serial) { apr_hash_set(conf->crl_index, serial->val, serial->len, r); } } } #if HAVE_X509_CRL_GET0_NEXTUPDATE nextupdate = X509_CRL_get0_nextUpdate(conf->crl); #else nextupdate = X509_CRL_get_nextUpdate(conf->crl); #endif if (nextupdate) { conf->crl_expires = ASN1_TIME_to_gmtime(nextupdate); } apr_pool_cleanup_register(cmd->pool, conf, crl_cleanup, apr_pool_cleanup_null); BIO_free(in); BIO_free(out); return NULL; } static const command_rec ca_crl_cmds[] = { AP_INIT_TAKE1("CACRLCertificateRevocationList", set_crl, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of the certificate revocation list."), { 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_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_getcrl(ca_getcrl_crl, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ca_getcertstatus(ca_getcertstatus_crl, NULL, NULL, APR_HOOK_MIDDLE); } module AP_MODULE_DECLARE_DATA ca_crl_module = { STANDARD20_MODULE_STUFF, create_ca_crl_dir_config, /* dir config creater */ merge_ca_crl_dir_config, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ ca_crl_cmds, /* command apr_table_t */ register_hooks /* register hooks */ };