/* 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. */ /* * Implement a CSR service. * * Author: Graham Leggett * * This module accepts PKCS7 certificate sign requests and issues * certificates in response. */ #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 "ap_expr.h" #include "mod_ca.h" #undef PACKAGE_BUGREPORT #undef PACKAGE_NAME #undef PACKAGE_STRING #undef PACKAGE_TARNAME #undef PACKAGE_VERSION #include "config.h" #define DEFAULT_CSR_SIZE 128*1024 #define DEFAULT_CSR_PARAM_PKCS10 "pkcs10" #define DEFAULT_FRESHNESS 2 #define DEFAULT_FRESHNESS_MAX 3600*24 module AP_MODULE_DECLARE_DATA csr_module; EVP_PKEY *pknull; const EVP_MD *mdnull; typedef struct { const char *name; /* raw name of the object, NULL matches all */ const ap_expr_info_t *expr; /* if present, expression to be assigned to each name */ int nid; /* name element from the request */ int limit; /* if present, take up to the limit number of names */ } name_rec; typedef struct { int size_set:1; int param_pkcs10_set:1; int param_challenge_set:1; int location_set:1; int freshness_set:1; int subject_set :1; int subjectaltname_set :1; apr_off_t size; const char *param_pkcs10; const char *param_challenge; const char *location; apr_array_header_t *subject; apr_array_header_t *subjectaltname; int freshness; int freshness_max; } csr_config_rec; static void *create_csr_dir_config(apr_pool_t *p, char *d) { csr_config_rec *conf = apr_pcalloc(p, sizeof(csr_config_rec)); conf->size = DEFAULT_CSR_SIZE; conf->param_pkcs10 = DEFAULT_CSR_PARAM_PKCS10; conf->subject = apr_array_make(p, 10, sizeof(name_rec)); conf->subjectaltname = apr_array_make(p, 10, sizeof(name_rec)); conf->freshness = DEFAULT_FRESHNESS; conf->freshness_max = DEFAULT_FRESHNESS_MAX; return conf; } static void *merge_csr_dir_config(apr_pool_t *p, void *basev, void *addv) { csr_config_rec *new = (csr_config_rec *) apr_pcalloc(p, sizeof(csr_config_rec)); csr_config_rec *add = (csr_config_rec *) addv; csr_config_rec *base = (csr_config_rec *) basev; new->size = (add->size_set == 0) ? base->size : add->size; new->size_set = add->size_set || base->size_set; new->param_pkcs10 = (add->param_pkcs10_set == 0) ? base->param_pkcs10 : add->param_pkcs10; new->param_pkcs10_set = add->param_pkcs10_set || base->param_pkcs10_set; new->param_challenge = (add->param_challenge_set == 0) ? base->param_challenge : add->param_challenge; new->param_challenge_set = add->param_challenge_set || base->param_challenge_set; new->location = (add->location_set == 0) ? base->location : add->location; new->location_set = add->location_set || base->location_set; new->subject = (add->subject_set == 0) ? base->subject : add->subject; new->subject_set = add->subject_set || base->subject_set; new->subjectaltname = (add->subjectaltname_set == 0) ? base->subjectaltname : add->subjectaltname; new->subjectaltname_set = add->subjectaltname_set || base->subjectaltname_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 const char *set_csr_size(cmd_parms *cmd, void *dconf, const char *arg) { csr_config_rec *conf = dconf; if (apr_strtoff(&conf->size, arg, NULL, 10) != APR_SUCCESS || conf->size < 4096) { return "CsrSize argument must be an integer representing the max size of a SPKAC request, at least 4096"; } conf->size_set = 1; return NULL; } static const char *set_csr_param_pkcs10(cmd_parms *cmd, void *dconf, const char *arg) { csr_config_rec *conf = dconf; conf->param_pkcs10 = arg; conf->param_pkcs10_set = 1; return NULL; } static const char *set_csr_param_challenge(cmd_parms *cmd, void *dconf, const char *arg) { csr_config_rec *conf = dconf; conf->param_challenge = arg; conf->param_challenge_set = 1; return NULL; } static const char *set_location(cmd_parms *cmd, void *dconf, const char *arg) { csr_config_rec *conf = dconf; conf->location = arg; conf->location_set = 1; return NULL; } static const char *set_subject_request(cmd_parms *cmd, void *dconf, const char *arg1, const char *arg2) { csr_config_rec *conf = dconf; name_rec *name = apr_array_push(conf->subject); name->name = arg1; if (strcmp(arg1, "*")) { name->nid = OBJ_txt2nid(arg1); if (name->nid == NID_undef) { return apr_psprintf(cmd->pool, "Argument '%s' must be a valid subject identifier recognised by openssl", arg1); } } if (arg2) { char *end; name->limit = (int) apr_strtoi64(arg2, &end, 10); if (*end || name->limit < 1) { return apr_psprintf(cmd->pool, "Argument '%s' must be a positive integer", arg2); } } else { name->limit = 1; } conf->subject_set = 1; return NULL; } static const char *set_subject_set(cmd_parms *cmd, void *dconf, const char *arg1, const char *arg2) { csr_config_rec *conf = dconf; name_rec *name = apr_array_push(conf->subject); name->name = arg1; name->nid = OBJ_txt2nid(arg1); if (name->nid == NID_undef) { return apr_psprintf(cmd->pool, "Argument '%s' must be a valid subject identifier recognised by openssl", arg1); } else { const char *expr_err = NULL; name->expr = ap_expr_parse_cmd(cmd, arg2, AP_EXPR_FLAG_STRING_RESULT, &expr_err, NULL); if (expr_err) { return apr_pstrcat(cmd->temp_pool, "Cannot parse expression '", arg2, "': ", expr_err, NULL); } } conf->subject_set = 1; return NULL; } const char *subjectaltnames[] = { "otherName", "rfc822Name", "dNSName", "x400Address", "directoryName", "ediPartyName", "uniformResourceIdentifier", "iPAddress", "registeredID" }; static int type_from_subjectaltname(const char *arg) { char a = arg[0]; if (a == 'o' && !strcmp(arg, "otherName")) { return GEN_OTHERNAME; } else if (a == 'r' && !strcmp(arg, "rfc822Name")) { return GEN_EMAIL; } else if (a == 'd' && !strcmp(arg, "dNSName")) { return GEN_DNS; } else if (a == 'x' && !strcmp(arg, "x400Address")) { return GEN_X400; } else if (a == 'd' && !strcmp(arg, "directoryName")) { return GEN_DIRNAME; } else if (a == 'e' && !strcmp(arg, "ediPartyName")) { return GEN_EDIPARTY; } else if (a == 'u' && !strcmp(arg, "uniformResourceIdentifier")) { return GEN_URI; } else if (a == 'i' && !strcmp(arg, "iPAddress")) { return GEN_IPADD; } else if (a == 'r' && !strcmp(arg, "registeredID")) { return GEN_RID; } return -1; } static const char *set_subjectaltname_request(cmd_parms *cmd, void *dconf, const char *arg1, const char *arg2) { csr_config_rec *conf = dconf; name_rec *name = apr_array_push(conf->subjectaltname); name->name = arg1; if (strcmp(arg1, "*")) { name->nid = type_from_subjectaltname(arg1); if (name->nid < 0) { return apr_psprintf(cmd->pool, "Argument '%s' was not one of otherName, rfc822Name, dNSName, x400Address, directoryName, ediPartyName, uniformResourceIdentifier, iPAddress or registeredID", arg1); } } else { name->nid = -1; } if (arg2) { char *end; name->limit = (int) apr_strtoi64(arg2, &end, 10); if (*end || name->limit < 1) { return apr_psprintf(cmd->pool, "Argument '%s' must be a positive integer", arg2); } } else { name->limit = 1; } conf->subjectaltname_set = 1; return NULL; } static const char *set_subjectaltname_set(cmd_parms *cmd, void *dconf, const char *arg1, const char *arg2) { csr_config_rec *conf = dconf; name_rec *name = apr_array_push(conf->subjectaltname); name->name = arg1; name->nid = type_from_subjectaltname(arg1); if (name->nid < 0) { return apr_psprintf(cmd->pool, "Argument '%s' was not one of otherName, rfc822Name, dNSName, x400Address, directoryName, ediPartyName, uniformResourceIdentifier, iPAddress or registeredID", arg1); } else { const char *expr_err = NULL; name->expr = ap_expr_parse_cmd(cmd, arg2, AP_EXPR_FLAG_STRING_RESULT, &expr_err, NULL); if (expr_err) { return apr_pstrcat(cmd->temp_pool, "Cannot parse expression '", arg2, "': ", expr_err, NULL); } } conf->subjectaltname_set = 1; return NULL; } static const char *set_csr_freshness(cmd_parms *cmd, void *dconf, const char *arg, const char *max) { csr_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 "CsrFreshness must specify a positive integer (or integers)"; } return NULL; } static const command_rec csr_cmds[] = { AP_INIT_TAKE12("CsrFreshness", set_csr_freshness, NULL, RSRC_CONF | ACCESS_CONF, "The age of the certificates 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."), AP_INIT_TAKE1("CsrLocation", set_location, NULL, RSRC_CONF | ACCESS_CONF, "Set to the location of the csr service."), AP_INIT_TAKE1("CsrParamPkcs10", set_csr_param_pkcs10, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of the request variable from the client containing the CSR. Defaults to " DEFAULT_CSR_PARAM_PKCS10), AP_INIT_TAKE1("CsrParamChallenge", set_csr_param_challenge, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of the request variable from the client containing the param_challenge password. If unset, will be taken from the CSR itself."), AP_INIT_TAKE1("CsrSize", set_csr_size, NULL, RSRC_CONF | ACCESS_CONF, "Set to the maximum size of the CSR request from the client."), AP_INIT_TAKE12("CsrSubjectAltNameRequest", set_subjectaltname_request, NULL, RSRC_CONF | ACCESS_CONF, "Specify fields in the certificate request subjectAltName that will be copied over to the certificate, with optional limit to the number of fields that may appear."), AP_INIT_TAKE2("CsrSubjectAltNameSet", set_subjectaltname_set, NULL, RSRC_CONF | ACCESS_CONF, "Specify subjectAltName attribute and value that will be included in the certificate."), AP_INIT_TAKE12("CsrSubjectRequest", set_subject_request, NULL, RSRC_CONF | ACCESS_CONF, "Specify fields in the certificate request subject that will be copied over to the certificate, with optional limit to the number of fields that may appear."), AP_INIT_TAKE2("CsrSubjectSet", set_subject_set, NULL, RSRC_CONF | ACCESS_CONF, "Specify subject attribute and value that will be included in the certificate."), { 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, "The CSR gateway could not generate the certificate: ", 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 int options_wadl(request_rec *r, csr_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 form parameter '%s' is expected to contain the PEM encoded PKCS10 structure\n" " while additional parameters contain the subject elements preceded\n" " by 'subject-' and subject alternate name elements preceded by\n" " 'subjectAltName-'.\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 signing of the certificate, 200 OK will be returned\n" " with the body containing the ASN.1 DER-encoded X509 certificate.\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), conf->param_pkcs10); return OK; } static apr_status_t csr_BIO_cleanup(void *data) { BIO_free((BIO *) data); return APR_SUCCESS; } static apr_status_t csr_EVP_PKEY_cleanup(void *data) { EVP_PKEY_free((EVP_PKEY *) data); return APR_SUCCESS; } static apr_status_t csr_PKCS7_cleanup(void *data) { PKCS7_free((PKCS7 *) data); return APR_SUCCESS; } static apr_status_t csr_X509_REQ_cleanup(void *data) { X509_REQ_free((X509_REQ *) data); return APR_SUCCESS; } static ca_asn1_t *make_X509_NAME(apr_pool_t *pool, X509_NAME *name) { ca_asn1_t *buf = apr_palloc(pool, sizeof(ca_asn1_t)); unsigned char *tmp; buf->len = i2d_X509_NAME(name, NULL); buf->val = tmp = apr_palloc(pool, buf->len); i2d_X509_NAME(name, &tmp); return buf; } static ca_asn1_t *make_X509_REQ(apr_pool_t *pool, X509_REQ *req) { ca_asn1_t *buf = apr_palloc(pool, sizeof(ca_asn1_t)); unsigned char *tmp; buf->len = i2d_X509_REQ(req, NULL); buf->val = tmp = apr_palloc(pool, buf->len); i2d_X509_REQ(req, &tmp); return buf; } static int csr_transform_subject(request_rec *r, apr_array_header_t *pairs, X509_NAME *subject, apr_hash_t *seen) { int i, j; csr_config_rec *conf = ap_get_module_config(r->per_dir_config, &csr_module); for (i = 0; i < conf->subject->nelts; i++) { name_rec *name = ((name_rec *) conf->subject->elts) + i; if (name->expr) { const char *err = NULL; const char *arg = ap_expr_str_exec(r, name->expr, &err); if (err || !arg) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "Expression for '%s' could not be executed, and could not be added to the certificate subject: %s", name->name, err)); return HTTP_INTERNAL_SERVER_ERROR; } if (!X509_NAME_add_entry_by_NID(subject, name->nid, MBSTRING_UTF8, (unsigned char *) arg, -1, -1, 0)) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "Expression with value '%s' could not be added to the certificate subject as '%s'.", arg, name->name)); return HTTP_INTERNAL_SERVER_ERROR; } } else { int count = name->limit; for (j = 0; j < pairs->nelts; j++) { ap_form_pair_t *pair = ((ap_form_pair_t *) pairs->elts) + j; if (!strncmp("subject-", pair->name, 8)) { const char *pname = pair->name + 8; int nid = OBJ_txt2nid(pname); if (nid != NID_undef) { if (!name->nid || name->nid == nid) { apr_off_t offset; apr_size_t size; char *buffer; if (count <= 0) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "Subject name '%s' cannot be inserted into certificate more than %d times%s.", name->name, name->limit, name->nid ? "" : "(count is across all wildcard matches)")); return HTTP_BAD_REQUEST; } apr_brigade_length(pair->value, 1, &offset); size = (apr_size_t) offset; buffer = apr_palloc(r->pool, size + 1); apr_brigade_flatten(pair->value, buffer, &size); buffer[size] = 0; ap_unescape_urlencoded(buffer); if (!X509_NAME_add_entry_by_txt(subject, pname, MBSTRING_UTF8, (unsigned char *) buffer, -1, -1, 0)) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "Subject name '%s' with value '%s' could not be added to the certificate subject.", pname, buffer)); return HTTP_BAD_REQUEST; } count--; apr_hash_set(seen, pair->name, APR_HASH_KEY_STRING, pair->name); } } else { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "Name '%s' was not recognised as a valid NID, and could not be inserted into certificate.", pname)); return HTTP_BAD_REQUEST; } } } } } return OK; } static int csr_transform_subjectaltname(request_rec *r, apr_array_header_t *pairs, X509_REQ *creq, apr_hash_t *seen) { int i, j; GENERAL_NAMES *sans = NULL; csr_config_rec *conf = ap_get_module_config(r->per_dir_config, &csr_module); for (i = 0; i < conf->subjectaltname->nelts; i++) { name_rec *name = ((name_rec *) conf->subjectaltname->elts) + i; if (name->expr) { const char *err = NULL; const char *arg = ap_expr_str_exec(r, name->expr, &err); if (err || !arg) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "Expression for '%s' could not be executed, and could not be added to the certificate subjectAltName: %s", name->name, err)); return HTTP_INTERNAL_SERVER_ERROR; } GENERAL_NAME *gen = a2i_GENERAL_NAME(NULL, NULL, NULL, name->nid, (char *) arg, 0); if (!gen) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "Expression with value '%s' could not be added to the certificate subjectAltName as '%s'.", arg, name->name)); return HTTP_INTERNAL_SERVER_ERROR; } if (!sans) { sans = GENERAL_NAMES_new(); } sk_GENERAL_NAME_push(sans, gen); } else { int count = name->limit; for (j = 0; j < pairs->nelts; j++) { ap_form_pair_t *pair = ((ap_form_pair_t *) pairs->elts) + j; if (!strncmp("subjectAltName-", pair->name, 15)) { const char *pname = pair->name + 15; int type = type_from_subjectaltname(pname); if (type != -1) { if (name->nid == -1 || name->nid == type) { GENERAL_NAME *gen; apr_off_t offset; apr_size_t size; char *buffer; if (count <= 0) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "Subject name '%s' cannot be inserted into certificate more than %d times.", name->name, name->limit)); return HTTP_BAD_REQUEST; } apr_brigade_length(pair->value, 1, &offset); size = (apr_size_t) offset; buffer = apr_palloc(r->pool, size + 1); apr_brigade_flatten(pair->value, buffer, &size); buffer[size] = 0; ap_unescape_urlencoded(buffer); if (!(gen = a2i_GENERAL_NAME(NULL, NULL, NULL, type, buffer, 0))) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "SubjectAltName name '%s' with value '%s' could not be added to the certificate.", pname, buffer)); return HTTP_BAD_REQUEST; } if (!sans) { sans = GENERAL_NAMES_new(); } sk_GENERAL_NAME_push(sans, gen); count--; apr_hash_set(seen, pair->name, APR_HASH_KEY_STRING, pair->name); } } else { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "SubjectAltName '%s' was not recognised as a valid NID, and could not be inserted into certificate.", pname)); return HTTP_BAD_REQUEST; } } } } } /* if we have a subjectAltName, add it to the request */ if (sans) { X509_EXTENSION *san = NULL; STACK_OF(X509_EXTENSION) *cexts = NULL; int critical = !X509_NAME_entry_count(X509_REQ_get_subject_name(creq)); san = X509V3_EXT_i2d(NID_subject_alt_name, critical, sans); X509v3_add_ext(&cexts, san, -1); X509_REQ_add_extensions(creq, cexts); } return OK; } static int csr_form_handler(request_rec *r) { apr_status_t rv; apr_array_header_t *pairs = NULL; apr_off_t offset; apr_size_t size, challenge_size; char *buffer; unsigned char *challenge = NULL; unsigned char *p; const char *ct; const unsigned char *der; apr_hash_t *params = apr_hash_make(r->pool); apr_hash_t *seen = apr_hash_make(r->pool); apr_bucket_brigade *bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_bucket *e; apr_status_t status; X509_REQ *creq = NULL; X509_REQ *req = NULL; EVP_PKEY *pktmp = NULL; X509_NAME *subject = NULL; PKCS7 *p7 = NULL; BIO *out = NULL; BUF_MEM *bptr = NULL; apr_size_t len; int idx; csr_config_rec *conf = ap_get_module_config(r->per_dir_config, &csr_module); /* * Now create an X509_REQ request structure representing a full * certificate request. */ creq = X509_REQ_new(); if (!creq) { log_message(r, APR_SUCCESS, "X509_REQ_new failed"); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, creq, csr_X509_REQ_cleanup, apr_pool_cleanup_null); subject = X509_REQ_get_subject_name(creq); rv = ap_parse_form_data(r, NULL, &pairs, -1, conf->size); if (rv != OK) { return rv; } /* transform the subjects as per the configuration */ rv = csr_transform_subject(r, pairs, subject, seen); if (rv != OK) { return rv; } /* transform the subjectAltNames as per the configuration */ rv = csr_transform_subjectaltname(r, pairs, creq, seen); if (rv != OK) { return rv; } /* find the CSR, and find unrecognised options */ while (pairs && !apr_is_empty_array(pairs)) { ap_form_pair_t *pair = (ap_form_pair_t *) apr_array_pop(pairs); if (!strcmp(pair->name, conf->param_pkcs10)) { BIO *mem = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, mem, csr_BIO_cleanup, apr_pool_cleanup_null); apr_brigade_length(pair->value, 1, &offset); size = (apr_size_t) offset; buffer = apr_palloc(r->pool, size + 1); apr_brigade_flatten(pair->value, buffer, &size); buffer[size] = 0; BIO_write(mem, buffer, size); req = PEM_read_bio_X509_REQ(mem, NULL, NULL, NULL); if (!req) { log_message(r, APR_SUCCESS, "Certificate sign request structure could not be decoded"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, req, csr_X509_REQ_cleanup, apr_pool_cleanup_null); } /* handle the subject */ else if (!strncmp("subject-", pair->name, 8) && apr_hash_get(seen, pair->name, APR_HASH_KEY_STRING)) { continue; } /* handle the subjectAltName */ else if (!strncmp("subjectAltName-", pair->name, 15) && apr_hash_get(seen, pair->name, APR_HASH_KEY_STRING)) { continue; } /* handle the param_challenge */ else if (conf->param_challenge && !strcmp(pair->name, conf->param_challenge)) { apr_brigade_length(pair->value, 1, &offset); challenge_size = (apr_size_t) offset; challenge = apr_palloc(r->pool, challenge_size); apr_brigade_flatten(pair->value, (char *)challenge, &challenge_size); } /* otherwise bail out */ else { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "Parameter name '%s' was not expected by configuration.", pair->name)); return HTTP_BAD_REQUEST; } } /* print the subject, if necessary */ if (APLOGrdebug(r)) { char buf[HUGE_STRING_LEN]; int len; BIO *debug = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, debug, csr_BIO_cleanup, apr_pool_cleanup_null); X509_NAME_print_ex(debug, subject, 0, XN_FLAG_ONELINE); while ((len = BIO_gets(debug, buf, sizeof(buf))) > 0) { ap_log_rerror( APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "Generated Certificate Subject: %.*s", len, buf); } } /* sanity check - CSR structure specified in the request? */ if (!req) { log_message(r, APR_SUCCESS, "no certificate sign request structure was specified"); return HTTP_BAD_REQUEST; } /* do the public key verification, and leave if invalid */ if (!(pktmp = X509_REQ_get_pubkey(req))) { log_message(r, APR_SUCCESS, "error unpacking certificate request public key"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, pktmp, csr_EVP_PKEY_cleanup, apr_pool_cleanup_null); if (0 >= X509_REQ_verify(req, pktmp)) { log_message(r, APR_SUCCESS, "certificate request signature could not be verified"); return HTTP_BAD_REQUEST; } X509_REQ_set_pubkey(creq, pktmp); /* sign the X509_REQ with a dummy signature to work around serialisation bugs in openssl */ X509_REQ_sign(creq, pknull, mdnull); /* extract the param_challenge, if present */ idx = X509_REQ_get_attr_by_NID(req, OBJ_sn2nid("challengePassword"), -1); if (idx > -1) { X509_REQ_add1_attr(creq, X509_REQ_get_attr(req, idx)); } else if (challenge) { if (!X509_REQ_add1_attr_by_txt(creq, "challengePassword", V_ASN1_UTF8STRING, challenge, challenge_size)) { log_message(r, APR_SUCCESS, "could not add the challenge to the certificate request"); return HTTP_INTERNAL_SERVER_ERROR; } } /* handle the subject */ if (subject) { apr_hash_set(params, "subject", APR_HASH_KEY_STRING, make_X509_NAME(r->pool, subject)); } /* handle the proof of possession */ if (req) { apr_hash_set(params, "popCertificateSignRequest", APR_HASH_KEY_STRING, make_X509_REQ(r->pool, req)); } /* write out the certificate */ len = i2d_X509_REQ(creq, NULL); if (len <= 0) { log_message(r, APR_SUCCESS, "could not DER encode the certificate request"); return HTTP_INTERNAL_SERVER_ERROR; } der = p = apr_palloc(r->pool, len); if (!i2d_X509_REQ(creq, &p)) { log_message(r, APR_SUCCESS, "could not DER encode the certificate request"); return HTTP_INTERNAL_SERVER_ERROR; } /* do the authz */ rv = ap_run_ca_reqauthz(r, params, der, len); if (rv > OK) { return rv; } /* do the signing */ rv = ap_run_ca_sign(r, params, &der, &len); if (rv == DECLINED) { log_message(r, APR_SUCCESS, "No module configured to sign the certificate"); return HTTP_INTERNAL_SERVER_ERROR; } if (rv != OK) { return rv; } /* do the store */ rv = ap_run_ca_certstore(r, params, der, len); if (rv > OK) { return rv; } /* if application/x-www-form-urlencoded, try parse the form */ ct = apr_table_get(r->headers_in, "Accept"); if (ct && !strcmp("application/pkcs7-mime", ct)) { /* read in the certificates */ if (!d2i_PKCS7(&p7, &der, len)) { log_message(r, APR_SUCCESS, "could not DER decode the signed certificates"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, p7, csr_PKCS7_cleanup, apr_pool_cleanup_null); /* write out the PEM encoded pkcs7 structure */ out = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, out, csr_BIO_cleanup, apr_pool_cleanup_null); if (!PEM_write_bio_PKCS7(out, p7)) { log_message(r, APR_SUCCESS, "could not PEM encode the PKCS7 certificate response"); return HTTP_INTERNAL_SERVER_ERROR; } BIO_get_mem_ptr(out, &bptr); e = apr_bucket_pool_create(bptr->data, bptr->length, r->pool, r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, e); /* content type */ ap_set_content_type(r, "application/pkcs7-mime"); apr_table_set(r->headers_out, "Content-Disposition", "inline, filename=certificates.p7c"); ap_set_content_length(r, len); } else { e = apr_bucket_pool_create((const char *)der, len, r->pool, r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, e); /* content type */ ap_set_content_type(r, "application/x-x509-user-cert"); 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, "csr_handler: ap_pass_brigade returned %i", status); return HTTP_INTERNAL_SERVER_ERROR; } /* ready to leave */ return OK; } static int csr_handler(request_rec *r) { csr_config_rec *conf = ap_get_module_config(r->per_dir_config, &csr_module); if (!conf) { return DECLINED; } if (strcmp(r->handler, "csr")) { return DECLINED; } /* A POST to handle CSR, OPTIONS should return the WADL */ ap_allow_methods(r, 1, "POST", "OPTIONS", NULL); if (!strcmp(r->method, "POST")) { const char *ct; /* if application/x-www-form-urlencoded, try parse the form */ ct = apr_table_get(r->headers_in, "Content-Type"); if (ct && !strcmp("application/x-www-form-urlencoded", ct)) { return csr_form_handler(r); } return HTTP_UNSUPPORTED_MEDIA_TYPE; } else if (!strcmp(r->method, "OPTIONS")) { return options_wadl(r, conf); } else { return HTTP_METHOD_NOT_ALLOWED; } } static apr_status_t csr_cleanup(void *data) { EVP_PKEY_free(pknull); pknull = NULL; ERR_free_strings(); EVP_cleanup(); return APR_SUCCESS; } static int csr_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) { EVP_PKEY_CTX *ctx; int rv; OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); apr_pool_cleanup_register(pconf, NULL, csr_cleanup, apr_pool_cleanup_null); /* create a once off null key for signing X509_REQ structures where a key is not available */ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); if (!ctx) { ap_log_error(APLOG_MARK,APLOG_CRIT, 0, NULL, "EVP_PKEY_CTX_new_id() returned a NULL context, aborting"); return DONE; } if ((rv = EVP_PKEY_keygen_init(ctx)) <= 0) { ap_log_error(APLOG_MARK,APLOG_CRIT, 0, NULL, "EVP_PKEY_keygen_init() returned %d, aborting", rv); return DONE; } if ((rv = EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048)) <= 0) { ap_log_error(APLOG_MARK,APLOG_CRIT, 0, NULL, "EVP_PKEY_CTX_set_rsa_keygen_bits() returned %d, aborting", rv); return DONE; } /* Generate key */ if ((rv = EVP_PKEY_keygen(ctx, &pknull)) <= 0) { ap_log_error(APLOG_MARK,APLOG_CRIT, 0, NULL, "EVP_PKEY_keygen() returned %d, aborting", rv); return DONE; } mdnull = EVP_sha256(); return APR_SUCCESS; } static void register_hooks(apr_pool_t *p) { ap_hook_pre_config(csr_pre_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(csr_handler, NULL, NULL, APR_HOOK_MIDDLE); #ifdef HAS_OPENSSL_PR10563_WORK_AROUND ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, "Workaround for OpenSSL/#10563 active; which manipulates openssl-private internals."); #endif } AP_DECLARE_MODULE(csr) = { STANDARD20_MODULE_STUFF, create_csr_dir_config, /* dir config creater */ merge_csr_dir_config, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ csr_cmds, /* command apr_table_t */ register_hooks /* register hooks */ };