/* 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. */ /* * Parse and return responses to OCSP requests. * * 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_OCSP_SIZE 128*1024 #define DEFAULT_FRESHNESS 2 #define DEFAULT_FRESHNESS_MAX 3600*24 module AP_MODULE_DECLARE_DATA ocsp_module; static const char *crl_reasons[] = { "unspecified", "keyCompromise", "CACompromise", "affiliationChanged", "superseded", "cessationOfOperation", "certificateHold", "removeFromCRL", }; typedef struct { X509 *signer; int signer_set; EVP_PKEY *key; int key_set; STACK_OF(X509) *others; int others_set; apr_off_t size; int size_set; const char *location; int location_set; apr_time_t next_update; int next_update_set; int no_certificates; int no_certificates_set; int identify_by_key_id; int identify_by_key_id_set; int *reason; int reason_set; ASN1_TIME *revocation_time; int revocation_time_set; ASN1_GENERALIZEDTIME *invalidity_date; int invalidity_date_set; ASN1_OBJECT *hold_instruction; int hold_instruction_set; int freshness; int freshness_max; int freshness_set; } ocsp_config_rec; static apr_status_t ocsp_BIO_cleanup(void *data) { BIO_free((BIO *) data); return APR_SUCCESS; } static apr_status_t ocsp_OCSP_REQUEST_cleanup(void *data) { OCSP_REQUEST_free((OCSP_REQUEST *) data); return APR_SUCCESS; } static apr_status_t ocsp_OCSP_BASICRESP_cleanup(void *data) { OCSP_BASICRESP_free((OCSP_BASICRESP *) data); return APR_SUCCESS; } static apr_status_t ocsp_OCSP_RESPONSE_cleanup(void *data) { OCSP_RESPONSE_free((OCSP_RESPONSE *) data); return APR_SUCCESS; } static apr_status_t ocsp_OCSP_CERTID_cleanup(void *data) { OCSP_CERTID_free((OCSP_CERTID *) data); return APR_SUCCESS; } static apr_status_t ocsp_X509_cleanup(void *data) { X509_free((X509 *) data); return APR_SUCCESS; } static apr_status_t ocsp_ASN1_TIME_cleanup(void *data) { ASN1_TIME_free((ASN1_TIME *) data); return APR_SUCCESS; } static apr_status_t ocsp_ASN1_GENERALIZEDTIME_cleanup(void *data) { ASN1_GENERALIZEDTIME_free((ASN1_GENERALIZEDTIME *) data); return APR_SUCCESS; } static apr_status_t ocsp_ASN1_ENUMERATED_cleanup(void *data) { ASN1_ENUMERATED_free((ASN1_ENUMERATED *) data); return APR_SUCCESS; } static apr_status_t ocsp_ASN1_OBJECT_cleanup(void *data) { ASN1_OBJECT_free((ASN1_OBJECT *) data); return APR_SUCCESS; } static void *create_ocsp_dir_config(apr_pool_t *p, char *d) { ocsp_config_rec *conf = apr_pcalloc(p, sizeof(ocsp_config_rec)); conf->size = DEFAULT_OCSP_SIZE; conf->freshness = DEFAULT_FRESHNESS; conf->freshness_max = DEFAULT_FRESHNESS_MAX; return conf; } static void *merge_ocsp_dir_config(apr_pool_t *p, void *basev, void *addv) { ocsp_config_rec *new = (ocsp_config_rec *) apr_pcalloc(p, sizeof(ocsp_config_rec)); ocsp_config_rec *add = (ocsp_config_rec *) addv; ocsp_config_rec *base = (ocsp_config_rec *) basev; new->signer = (add->signer_set == 0) ? base->signer : add->signer; new->signer_set = add->signer_set || base->signer_set; new->key = (add->key_set == 0) ? base->key : add->key; new->key_set = add->key_set || base->key_set; new->others = (add->others_set == 0) ? base->others : add->others; new->others_set = add->others_set || base->others_set; new->size = (add->size_set == 0) ? base->size : add->size; new->size_set = add->size_set || base->size_set; new->location = (add->location_set == 0) ? base->location : add->location; new->location_set = add->location_set || base->location_set; new->next_update = (add->next_update_set == 0) ? base->next_update : add->next_update; new->next_update_set = add->next_update_set || base->next_update_set; new->no_certificates = (add->no_certificates_set == 0) ? base->no_certificates : add->no_certificates; new->no_certificates_set = add->no_certificates_set || base->no_certificates_set; new->identify_by_key_id = (add->identify_by_key_id_set == 0) ? base->identify_by_key_id : add->identify_by_key_id; new->identify_by_key_id_set = add->identify_by_key_id_set || base->identify_by_key_id_set; new->reason = (add->reason_set == 0) ? base->reason : add->reason; new->reason_set = add->reason_set || base->reason_set; new->revocation_time = (add->revocation_time_set == 0) ? base->revocation_time : add->revocation_time; new->revocation_time_set = add->revocation_time_set || base->revocation_time_set; new->invalidity_date = (add->invalidity_date_set == 0) ? base->invalidity_date : add->invalidity_date; new->invalidity_date_set = add->invalidity_date_set || base->invalidity_date_set; new->hold_instruction = (add->hold_instruction_set == 0) ? base->hold_instruction : add->hold_instruction; new->hold_instruction_set = add->hold_instruction_set || base->hold_instruction_set; new->freshness = (add->freshness_set == 0) ? base->freshness : add->freshness; new->freshness_max = (add->freshness_set == 0) ? base->freshness_max : add->freshness_max; new->freshness_set = add->freshness_set || base->freshness_set; return new; } static apr_status_t ra_certificate_cleanup(void *data) { ocsp_config_rec *conf = data; X509_free(conf->signer); conf->signer = NULL; return APR_SUCCESS; } static const char *set_ra_certificate(cmd_parms *cmd, void *dconf, const char *arg) { ocsp_config_rec *conf = dconf; BIO *in; /* set_signing_certificate() will be called twice. Don't bother * going through all of the initialization on the first call * because it will just be thrown away.*/ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) { return NULL; } 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->signer = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL); if (!conf->signer) { BIO_free(in); return apr_psprintf(cmd->pool, "Could not parse certificate from: %s", arg); } conf->signer_set = 1; apr_pool_cleanup_register(cmd->pool, conf, ra_certificate_cleanup, apr_pool_cleanup_null); BIO_free(in); return NULL; } static apr_status_t ra_key_cleanup(void *data) { ocsp_config_rec *conf = data; EVP_PKEY_free(conf->key); conf->key = NULL; return APR_SUCCESS; } static const char *set_ra_key(cmd_parms *cmd, void *dconf, const char *arg) { ocsp_config_rec *conf = dconf; BIO *in; /* set_signing_key() will be called twice. Don't bother * going through all of the initialization on the first call * because it will just be thrown away.*/ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) { return NULL; } 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, ra_key_cleanup, apr_pool_cleanup_null); BIO_free(in); return NULL; } static apr_status_t ra_others_cleanup(void *data) { ocsp_config_rec *conf = data; sk_X509_free(conf->others); conf->others = NULL; return APR_SUCCESS; } static const char *set_ra_others(cmd_parms *cmd, void *dconf, const char *arg) { ocsp_config_rec *conf = dconf; BIO *in; STACK_OF(X509_INFO) *xis; X509_INFO *xi; int i; /* set_signing_certificate() will be called twice. Don't bother * going through all of the initialization on the first call * because it will just be thrown away.*/ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) { return NULL; } 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); } if (!conf->others) { conf->others = sk_X509_new_null(); apr_pool_cleanup_register(cmd->pool, conf, ra_others_cleanup, apr_pool_cleanup_null); } xis = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL); if (!xis) { BIO_free(in); return apr_psprintf(cmd->pool, "Could not parse certificate(s) from: %s", arg); } for (i = 0; i < sk_X509_INFO_num(xis); i++) { xi = sk_X509_INFO_value(xis, i); if (xi->x509) { if (!sk_X509_push(conf->others, xi->x509)) { sk_X509_INFO_pop_free(xis, X509_INFO_free); BIO_free(in); return apr_psprintf(cmd->pool, "Could not push certificate(s) from: %s", arg); } xi->x509 = NULL; } } conf->others_set = 1; sk_X509_INFO_pop_free(xis, X509_INFO_free); BIO_free(in); return NULL; } static const char *set_ocsp_size(cmd_parms *cmd, void *dconf, const char *arg) { ocsp_config_rec *conf = dconf; if (apr_strtoff(&conf->size, arg, NULL, 10) != APR_SUCCESS || conf->size < 4096) { return "OcspSize argument must be an integer representing the max size of an OCSP request, at least 4096"; } conf->size_set = 1; return NULL; } static const char *set_location(cmd_parms *cmd, void *dconf, const char *arg) { ocsp_config_rec *conf = dconf; conf->location = arg; conf->location_set = 1; return NULL; } static const char *set_ocsp_next_update(cmd_parms *cmd, void *dconf, const char *arg) { ocsp_config_rec *conf = dconf; apr_off_t next; if (apr_strtoff(&next, arg, NULL, 10) != APR_SUCCESS || next < 0) { return "OcspNextUpdate argument must be a positive integer representing the number of seconds until the next update, or zero to disable"; } conf->next_update = next; conf->next_update_set = 1; return NULL; } static const char *set_ocsp_no_certificates(cmd_parms *cmd, void *dconf, int flag) { ocsp_config_rec *conf = dconf; conf->no_certificates = flag; conf->no_certificates_set = 1; return NULL; } static const char *set_ocsp_identify_by_key_id(cmd_parms *cmd, void *dconf, int flag) { ocsp_config_rec *conf = dconf; conf->identify_by_key_id = flag; conf->identify_by_key_id_set = 1; return NULL; } static const char *set_ocsp_override_reason(cmd_parms *cmd, void *dconf, const char *arg) { ocsp_config_rec *conf = dconf; int i; for (i = 0; i < (sizeof(crl_reasons) / sizeof(const char *)); i++) { if (!strcasecmp(arg, crl_reasons[i])) { conf->reason = apr_palloc(cmd->pool, sizeof(int)); *conf->reason = i; break; } } if (!conf->reason) { return apr_psprintf(cmd->pool, "Unrecognised override reason '%s'", arg); } conf->reason_set = 1; return NULL; } static const char *set_ocsp_override_revocation_time(cmd_parms *cmd, void *dconf, const char *arg) { ocsp_config_rec *conf = dconf; conf->revocation_time = ASN1_TIME_new(); if (!ASN1_TIME_set_string(conf->revocation_time, arg)) { return apr_psprintf(cmd->pool, "Override revocation time '%s' could not be parsed, expected YYYYMMDDHHMMSSZ", arg); } conf->revocation_time_set = 1; apr_pool_cleanup_register(cmd->pool, conf->revocation_time, ocsp_ASN1_TIME_cleanup, apr_pool_cleanup_null); return NULL; } static const char *set_ocsp_override_invalidity_date(cmd_parms *cmd, void *dconf, const char *arg) { ocsp_config_rec *conf = dconf; conf->invalidity_date = ASN1_GENERALIZEDTIME_new(); if (!ASN1_GENERALIZEDTIME_set_string(conf->invalidity_date, arg)) { return apr_psprintf(cmd->pool, "Override invalidity date '%s' could not be parsed, expected YYYYMMDDHHMMSSZ", arg); } conf->invalidity_date_set = 1; apr_pool_cleanup_register(cmd->pool, conf->invalidity_date, ocsp_ASN1_GENERALIZEDTIME_cleanup, apr_pool_cleanup_null); return NULL; } static const char *set_ocsp_override_hold_instruction(cmd_parms *cmd, void *dconf, const char *arg) { ocsp_config_rec *conf = dconf; conf->hold_instruction = OBJ_txt2obj(arg, 0); if (!conf->hold_instruction) { return apr_psprintf(cmd->pool, "Override hold instruction '%s' could not be parsed, expected valid OID (such as: holdInstructionCallIssuer, holdInstructionReject)", arg); } conf->hold_instruction_set = 1; apr_pool_cleanup_register(cmd->pool, conf->hold_instruction, ocsp_ASN1_OBJECT_cleanup, apr_pool_cleanup_null); return NULL; } static const char *set_ocsp_freshness(cmd_parms *cmd, void *dconf, const char *arg, const char *max) { ocsp_config_rec *conf = dconf; conf->freshness = atoi(arg); if (max) { conf->freshness_max = atoi(max); } conf->freshness_set = 1; if (conf->freshness < 0 || conf->freshness_max < 0) { return "OCSPFreshness must specify a positive integer (or integers)"; } return NULL; } static const command_rec ocsp_cmds[] = { AP_INIT_TAKE1("OcspSigningCertificate", set_ra_certificate, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of the signing certificate."), AP_INIT_TAKE1("OcspSigningKey", set_ra_key, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of the signing key."), AP_INIT_TAKE1("OcspOtherCertificates", set_ra_others, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of a file containing other certificates to add to the response."), AP_INIT_TAKE1("OcspSize", set_ocsp_size, NULL, RSRC_CONF | ACCESS_CONF, "Set to the maximum size of the OCSP request from the client."), AP_INIT_TAKE1("OcspLocation", set_location, NULL, RSRC_CONF | ACCESS_CONF, "Set to the location of the ocsp service."), AP_INIT_TAKE1("OcspNextUpdate", set_ocsp_next_update, NULL, RSRC_CONF | ACCESS_CONF, "Set to the number of seconds until the next update. Defaults to zero (to disable)."), AP_INIT_FLAG("OcspNoCertificates", set_ocsp_no_certificates, NULL, RSRC_CONF | ACCESS_CONF, "Set to 'on' to suppress the sending of certificates in the response. Defaults to 'off'."), AP_INIT_FLAG("OcspIdentifyByKeyID", set_ocsp_identify_by_key_id, NULL, RSRC_CONF | ACCESS_CONF, "Set to 'on' to identify the signer certificate by key ID. Defaults to 'off' for subject name."), AP_INIT_TAKE1("OcspOverrideReason", set_ocsp_override_reason, NULL, RSRC_CONF | ACCESS_CONF, "Mark all certificates as revoked, giving this reason."), AP_INIT_TAKE1("OcspOverrideRevocationTime", set_ocsp_override_revocation_time, NULL, RSRC_CONF | ACCESS_CONF, "If all certificates are revoked, add this revocation time, formatted as per http://tools.ietf.org/html/rfc2459#section-4.1.2.5.2 (YYYYMMDDHHMMSSZ)"), AP_INIT_TAKE1("OcspOverrideInvalidityDate", set_ocsp_override_invalidity_date, NULL, RSRC_CONF | ACCESS_CONF, "If all certificates are revoked, add this invalidity date, formatted as per http://tools.ietf.org/html/rfc2459#section-4.1.2.5.2 (YYYYMMDDHHMMSSZ)"), AP_INIT_TAKE1("OcspOverrideHoldInstruction", set_ocsp_override_hold_instruction, NULL, RSRC_CONF | ACCESS_CONF, "If all certificates are revoked, add this hold instruction, formatted as an OID (expected: holdInstructionCallIssuer, holdInstructionReject)"), AP_INIT_TAKE12("OcspFreshness", set_ocsp_freshness, NULL, RSRC_CONF | ACCESS_CONF, "The offset to the next update will be divided by this factor when added as a max-age, set to zero to disable. Defaults to \"2\". An optional maximum value can be specified, defaults to one day."), { NULL } }; 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, "OCSP response could not be returned: ", 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, "%s (%s)", message, err); } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, "%s", message); } BIO_free(mem); } static ca_asn1_t *make_ASN1_INTEGER(apr_pool_t *pool, ASN1_INTEGER *integer) { ca_asn1_t *buf = apr_palloc(pool, sizeof(ca_asn1_t)); unsigned char *tmp; buf->len = i2d_ASN1_INTEGER(integer, NULL); buf->val = tmp = apr_palloc(pool, buf->len); i2d_ASN1_INTEGER(integer, &tmp); return buf; } 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 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, ocsp_ASN1_TIME_cleanup, apr_pool_cleanup_null); } } return t; } static ASN1_GENERALIZEDTIME *parse_ASN1_GENERALIZEDTIME(apr_pool_t *pool, ca_asn1_t *generalizedtime) { ASN1_GENERALIZEDTIME *g = NULL; if (generalizedtime) { d2i_ASN1_GENERALIZEDTIME(&g, &generalizedtime->val, generalizedtime->len); if (g) { apr_pool_cleanup_register(pool, g, ocsp_ASN1_GENERALIZEDTIME_cleanup, apr_pool_cleanup_null); } } return g; } static ASN1_INTEGER *parse_ASN1_ENUMERATED(apr_pool_t *pool, ca_asn1_t *enumerated) { ASN1_ENUMERATED *e = NULL; if (enumerated) { d2i_ASN1_ENUMERATED(&e, &enumerated->val, enumerated->len); if (e) { apr_pool_cleanup_register(pool, e, ocsp_ASN1_ENUMERATED_cleanup, apr_pool_cleanup_null); } } return e; } static ASN1_OBJECT *parse_ASN1_OBJECT(apr_pool_t *pool, ca_asn1_t *object) { ASN1_OBJECT *o = NULL; if (object) { d2i_ASN1_OBJECT(&o, &object->val, object->len); if (o) { apr_pool_cleanup_register(pool, o, ocsp_ASN1_OBJECT_cleanup, apr_pool_cleanup_null); } } return o; } static int process_ocsp(request_rec *r, ocsp_config_rec *conf, OCSP_REQUEST *req, int get) { apr_size_t len; const unsigned char *buf; unsigned char *tmp; OCSP_RESPONSE *resp = NULL; X509 *ca = NULL; apr_status_t status; apr_time_t validity, lowest = apr_time_now() + apr_time_from_sec(3600*24*365); int rv, count, i; apr_bucket_brigade *bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_bucket *e; /* sanity check, no signer or key, no ocsp */ if (!conf->signer) { log_message(r, APR_SUCCESS, "No signing certificate configured"); return HTTP_INTERNAL_SERVER_ERROR; } if (!conf->key) { log_message(r, APR_SUCCESS, "No signing key configured"); return HTTP_INTERNAL_SERVER_ERROR; } /* get the CA certificate */ rv = ap_run_ca_getca(r, &buf, &len, &validity); lowest = lowest < validity ? lowest : validity; if (rv == DECLINED) { log_message(r, APR_SUCCESS, "No module configured to get the CA certificate (getca)"); return HTTP_INTERNAL_SERVER_ERROR; } if (rv != OK) { return rv; } if (!d2i_X509(&ca, &buf, len)) { log_message(r, APR_SUCCESS, "could not DER decode the CA certificate"); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, ca, ocsp_X509_cleanup, apr_pool_cleanup_null); count = OCSP_request_onereq_count(req); if (count <= 0) { ap_log_rerror( APLOG_MARK, APLOG_WARNING, 0, r, "OCSP query contains no requests, sending MALFORMEDREQUEST"); resp = OCSP_response_create(OCSP_RESPONSE_STATUS_MALFORMEDREQUEST, NULL); apr_pool_cleanup_register(r->pool, resp, ocsp_OCSP_RESPONSE_cleanup, apr_pool_cleanup_null); } else { OCSP_BASICRESP *bresp; ASN1_TIME *thisupdate, *nextupdate = NULL; ca_asn1_t *thisupdate_asn1, *nextupdate_asn1; /* create the basic response to be returned */ bresp = OCSP_BASICRESP_new(); apr_pool_cleanup_register(r->pool, bresp, ocsp_OCSP_BASICRESP_cleanup, apr_pool_cleanup_null); /* calculate default values for thisupdate and nextupdate */ thisupdate = X509_gmtime_adj(NULL, 0); if (conf->next_update > 0) { nextupdate = X509_gmtime_adj(NULL, conf->next_update); } /* extract this update */ thisupdate_asn1 = make_ASN1_TIME(r->pool, thisupdate); /* extract next update */ nextupdate_asn1 = make_ASN1_TIME(r->pool, nextupdate); for (i = 0; i < count; i++) { OCSP_ONEREQ *breq; OCSP_CERTID *cid, *caid; ASN1_OBJECT *oid; const EVP_MD *digest; ASN1_INTEGER *serial; apr_hash_t *certstatus; breq = OCSP_request_onereq_get0(req, i); cid = OCSP_onereq_get0_id(breq); OCSP_id_get0_info(NULL, &oid, NULL, NULL, cid); digest = EVP_get_digestbyobj(oid); if (!digest) { resp = OCSP_response_create(OCSP_RESPONSE_STATUS_INTERNALERROR, NULL); break; } caid = OCSP_cert_to_id(digest, NULL, ca); if (caid) { apr_pool_cleanup_register(r->pool, caid, ocsp_OCSP_CERTID_cleanup, apr_pool_cleanup_null); } /* is this request about our CA? */ if (OCSP_id_issuer_cmp(caid, cid)) { /* no it isn't, skip this one */ OCSP_basic_add1_status(bresp, cid, V_OCSP_CERTSTATUS_UNKNOWN, 0, NULL, thisupdate, nextupdate); continue; } /* Have we declared our entire CA revoked? * * If so, insert our revocation reason override for all certificates, * as well as optional revocation time, invalidity date and hold * instruction if set. */ if (conf->reason) { OCSP_SINGLERESP *sresp; if (!conf->revocation_time) { ap_log_rerror( APLOG_MARK, APLOG_WARNING, 0, r, "OCSP revocation time should be set with OcspOverrideRevocationTime when overriding"); } sresp = OCSP_basic_add1_status(bresp, cid, V_OCSP_CERTSTATUS_REVOKED, *conf->reason, conf->revocation_time, thisupdate, nextupdate); if (conf->invalidity_date) { OCSP_SINGLERESP_add1_ext_i2d(sresp, NID_invalidity_date, conf->invalidity_date, 0, 0); } if (conf->hold_instruction) { if (*conf->reason != 6) { ap_log_rerror( APLOG_MARK, APLOG_WARNING, 0, r, "OCSP override hold instruction should only be specified when OCSP override reason is 'certificateHold'"); } OCSP_SINGLERESP_add1_ext_i2d(sresp, NID_hold_instruction_code, conf->hold_instruction, 0, 0); } continue; } /* Otherwise, look for our certificate */ certstatus = apr_hash_make(r->pool); /* extract the serial number of the cert */ OCSP_id_get0_info(NULL, NULL, NULL, &serial, cid); if (!serial) { ap_log_rerror( APLOG_MARK, APLOG_WARNING, 0, r, "OCSP query lacked serialNumber, sending MALFORMEDREQUEST"); resp = OCSP_response_create( OCSP_RESPONSE_STATUS_MALFORMEDREQUEST, NULL); apr_pool_cleanup_register(r->pool, resp, ocsp_OCSP_RESPONSE_cleanup, apr_pool_cleanup_null); break; } apr_hash_set(certstatus, CA_SERIAL_NUMBER, APR_HASH_KEY_STRING, make_ASN1_INTEGER(r->pool, serial)); /* extract this update */ apr_hash_set(certstatus, CA_THIS_UPDATE, APR_HASH_KEY_STRING, thisupdate_asn1); /* extract next update */ apr_hash_set(certstatus, CA_NEXT_UPDATE, APR_HASH_KEY_STRING, nextupdate_asn1); /* get the OCSP basic response */ rv = ap_run_ca_getcertstatus(r, certstatus, &validity); lowest = lowest < validity ? lowest : validity; if (rv == DECLINED) { log_message(r, APR_SUCCESS, "No module configured to get the certificate status (getcertstatus)"); return HTTP_INTERNAL_SERVER_ERROR; } if (rv != OK) { return rv; } else { ASN1_TIME *thisupdate, *nextupdate; int *status = apr_hash_get(certstatus, CA_CERT_STATUS, APR_HASH_KEY_STRING); if (!status) { log_message(r, APR_SUCCESS, "No module returned the certificate status (getcertstatus)"); return HTTP_INTERNAL_SERVER_ERROR; } thisupdate = parse_ASN1_TIME(r->pool, apr_hash_get(certstatus, CA_THIS_UPDATE, APR_HASH_KEY_STRING)); nextupdate = parse_ASN1_TIME(r->pool, apr_hash_get(certstatus, CA_NEXT_UPDATE, APR_HASH_KEY_STRING)); switch (*status) { case CA_CERT_STATUS_UNKNOWN: { OCSP_basic_add1_status(bresp, cid, V_OCSP_CERTSTATUS_UNKNOWN, 0, NULL, thisupdate, nextupdate); break; } case CA_CERT_STATUS_GOOD: { OCSP_basic_add1_status(bresp, cid, V_OCSP_CERTSTATUS_GOOD, 0, NULL, thisupdate, nextupdate); break; } case CA_CERT_STATUS_REVOKED: { OCSP_SINGLERESP *sresp; ASN1_ENUMERATED *reason = parse_ASN1_ENUMERATED(r->pool, apr_hash_get(certstatus, CA_REVOCATION_REASON, APR_HASH_KEY_STRING)); ASN1_TIME *revokedtime = parse_ASN1_TIME(r->pool, apr_hash_get(certstatus, CA_REVOCATION_TIME, APR_HASH_KEY_STRING)); ASN1_OBJECT *holdinstruction = parse_ASN1_OBJECT(r->pool, apr_hash_get(certstatus, CA_HOLD_INSTRUCTION_CODE, APR_HASH_KEY_STRING)); ASN1_GENERALIZEDTIME *invaliditydate = parse_ASN1_GENERALIZEDTIME(r->pool, apr_hash_get(certstatus, CA_INVALIDITY_DATE, APR_HASH_KEY_STRING)); sresp = OCSP_basic_add1_status(bresp, cid, V_OCSP_CERTSTATUS_REVOKED, reason ? ASN1_ENUMERATED_get(reason) : 0, revokedtime, thisupdate, nextupdate); if (invaliditydate) { OCSP_SINGLERESP_add1_ext_i2d(sresp, NID_invalidity_date, invaliditydate, 0, 0); } if (holdinstruction) { OCSP_SINGLERESP_add1_ext_i2d(sresp, NID_hold_instruction_code, holdinstruction, 0, 0); } break; } } } } if (!resp) { /* we're ready, copy the nonce */ OCSP_copy_nonce(bresp, req); /* sign the final response */ if (!OCSP_basic_sign(bresp, conf->signer, conf->key, NULL, conf->others, (conf->no_certificates ? OCSP_NOCERTS : 0) | (conf->identify_by_key_id ? OCSP_RESPID_KEY : 0))) { log_message(r, APR_SUCCESS, "OCSP response could not be signed"); return HTTP_INTERNAL_SERVER_ERROR; } else { resp = OCSP_response_create(OCSP_RESPONSE_STATUS_SUCCESSFUL, bresp); apr_pool_cleanup_register(r->pool, resp, ocsp_OCSP_RESPONSE_cleanup, apr_pool_cleanup_null); } } } /* serialise the response */ len = i2d_OCSP_RESPONSE(resp, NULL); buf = tmp = apr_palloc(r->pool, len); i2d_OCSP_RESPONSE(resp, &tmp); e = apr_bucket_pool_create((const char *) buf, len, r->pool, r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, e); /* content type */ ap_set_content_type(r, "application/ocsp-response"); if (get) { apr_sha1_ctx_t sha1; apr_byte_t digest[APR_SHA1_DIGESTSIZE]; char *etag; apr_sha1_update_binary(&sha1, buf, len); apr_sha1_final(digest, &sha1); etag = apr_palloc(r->pool, 31); apr_base64_encode_binary(etag + 1, digest, sizeof(digest)); etag[0] = '\"'; etag[29] = '\"'; etag[30] = 0; apr_table_setn(r->headers_out, "ETag", etag); /* handle freshness lifetime for caching */ if (!apr_table_get(r->headers_out, "Cache-Control")) { apr_off_t delta = apr_time_sec(lowest - apr_time_now()); delta = delta > 0 ? conf->freshness ? delta / conf->freshness : 0 : 0; delta = delta < conf->freshness_max ? delta : conf->freshness_max; apr_table_setn(r->headers_out, "Cache-Control", apr_psprintf(r->pool, "max-age=%" APR_OFF_T_FMT, delta)); } if ((rv = ap_meets_conditions(r)) != OK) { r->status = rv; apr_brigade_cleanup(bb); } else { ap_set_content_length(r, len); } } else { ap_set_content_length(r, len); } e = apr_bucket_eos_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, e); status = ap_pass_brigade(r->output_filters, bb); if (status == APR_SUCCESS || r->status != HTTP_OK || r->connection->aborted) { return OK; } else { /* no way to know what type of error occurred */ ap_log_rerror( APLOG_MARK, APLOG_DEBUG, status, r, "ocsp_handler: ap_pass_brigade returned %i", status); return HTTP_INTERNAL_SERVER_ERROR; } /* ready to leave */ return OK; } static int get_ocsp(request_rec *r, ocsp_config_rec *conf) { const char *basename; const unsigned char *buf; int rv; apr_size_t len; OCSP_REQUEST *req; /* discard the request body on GET */ if ((rv = ap_discard_request_body(r)) != OK) { return rv; } /* identify the basename, it will be our request */ basename = strrchr(r->uri, '/'); if (!basename || !basename[0] || !basename[1]) { log_message(r, APR_SUCCESS, "OCSP request missing from the URL"); return HTTP_BAD_REQUEST; } /* decode the base64 to obtain the request */ len = apr_base64_decode_len(basename); if (len <= 0) { log_message(r, APR_SUCCESS, "OCSP request could not be base64 decoded"); return HTTP_BAD_REQUEST; } buf = apr_palloc(r->pool, len); apr_base64_decode_binary((unsigned char *) buf, basename); /* parse the request */ req = d2i_OCSP_REQUEST(NULL, &buf, len); if (!req) { log_message(r, APR_SUCCESS, "OCSP request could not be parsed"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, req, ocsp_OCSP_REQUEST_cleanup, apr_pool_cleanup_null); return process_ocsp(r, conf, req, 1); } static int post_ocsp(request_rec *r, ocsp_config_rec *conf) { apr_bucket_brigade *bb; int rv, seen_eos; apr_size_t total = 0; const char *type; OCSP_REQUEST *req; BIO *in = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, in, ocsp_BIO_cleanup, apr_pool_cleanup_null); /* is this an ocsp request? */ type = apr_table_get(r->headers_in, "Content-Type"); if (!type || strcmp(type, "application/ocsp-request")) { return HTTP_UNSUPPORTED_MEDIA_TYPE; } bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); seen_eos = 0; do { apr_bucket *bucket; rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN); if (rv != APR_SUCCESS) { apr_brigade_destroy(bb); return HTTP_BAD_REQUEST; } for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb); bucket = APR_BUCKET_NEXT(bucket)) { const char *data; apr_size_t len; if (APR_BUCKET_IS_EOS(bucket)) { seen_eos = 1; break; } /* These are metadata buckets. */ if (bucket->length == 0) { continue; } rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); if (rv != APR_SUCCESS) { return HTTP_BAD_REQUEST; } if (!BIO_write(in, data, (int) len)) { return HTTP_BAD_REQUEST; } total += len; if (total > conf->size) { return HTTP_REQUEST_ENTITY_TOO_LARGE; } } apr_brigade_cleanup(bb); } while (!seen_eos); /* parse the request */ req = d2i_OCSP_REQUEST_bio(in, NULL); if (!req) { log_message(r, APR_SUCCESS, "OCSP request could not be parsed"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, req, ocsp_OCSP_REQUEST_cleanup, apr_pool_cleanup_null); return process_ocsp(r, conf, req, 0); } static int options_wadl(request_rec *r, ocsp_config_rec *conf) { int rv; /* discard the request body */ if ((rv = ap_discard_request_body(r)) != OK) { return rv; } ap_set_content_type(r, "application/vnd.sun.wadl+xml"); ap_rprintf(r, "\n" "\n" " \n" " \n" " \n" " \n" " \n" " The body of the request is expected to contain an ASN.1 DER encoded\n" " OCSP Request message.\n" " \n" " \n" " \n" " \n" " On a configuration error, 500 Internal Server Error will be returned,\n" " and the server error log will contain full details of the\n" " error.\n" " \n" " \n" " \n" " \n" " For requests with incomplete, unparseable or missing information,\n" " 400 Bad Request is returned.\n" " \n" " \n" " \n" " \n" " After a successful lookup of the certificate status, 200 OK will be returned\n" " with the body containing the ASN.1 DER-encoded OCSP response.\n" " \n" " \n" " \n" " \n" " \n" "\n", conf->location ? conf->location : apr_pstrcat(r->pool, ap_http_scheme(r), "://", r->server->server_hostname, r->uri, NULL)); return OK; } static int ocsp_handler(request_rec *r) { ocsp_config_rec *conf = ap_get_module_config(r->per_dir_config, &ocsp_module); if (!conf) { return DECLINED; } if (strcmp(r->handler, "ocsp")) { return DECLINED; } /* GET and POST handle OCSP, while OPTIONS returns WADL for the service */ ap_allow_methods(r, 1, "GET", "POST", "OPTIONS", NULL); if (!strcmp(r->method, "GET")) { return get_ocsp(r, conf); } else if (!strcmp(r->method, "POST")) { return post_ocsp(r, conf); } else if (!strcmp(r->method, "OPTIONS")) { return options_wadl(r, conf); } else { return HTTP_METHOD_NOT_ALLOWED; } } static apr_status_t ocsp_cleanup(void *data) { ERR_free_strings(); EVP_cleanup(); return APR_SUCCESS; } static int ocsp_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, ocsp_cleanup, apr_pool_cleanup_null); return APR_SUCCESS; } static void register_hooks(apr_pool_t *p) { ap_hook_pre_config(ocsp_pre_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(ocsp_handler, NULL, NULL, APR_HOOK_MIDDLE); } AP_DECLARE_MODULE(ocsp) = { STANDARD20_MODULE_STUFF, create_ocsp_dir_config, /* dir config creater */ merge_ocsp_dir_config, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ ocsp_cmds, /* command apr_table_t */ register_hooks /* register hooks */ };