/* 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. */ /* * Generate and return digital certificates from the provided SPKAC data. * * Author: Graham Leggett * */ #include #include #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 "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_SPKAC_SIZE 128*1024 #define DEFAULT_SPKAC_NAME "key" module AP_MODULE_DECLARE_DATA spkac_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 { apr_off_t size; int size_set; const char *name; int name_set; const char *location; int location_set; apr_array_header_t *subject; apr_array_header_t *subjectaltname; int subject_set :1; int subjectaltname_set :1; } spkac_config_rec; static void *create_spkac_dir_config(apr_pool_t *p, char *d) { spkac_config_rec *conf = apr_pcalloc(p, sizeof(spkac_config_rec)); conf->size = DEFAULT_SPKAC_SIZE; conf->name = DEFAULT_SPKAC_NAME; conf->subject = apr_array_make(p, 10, sizeof(name_rec)); conf->subjectaltname = apr_array_make(p, 10, sizeof(name_rec)); return conf; } static void *merge_spkac_dir_config(apr_pool_t *p, void *basev, void *addv) { spkac_config_rec *new = (spkac_config_rec *) apr_pcalloc(p, sizeof(spkac_config_rec)); spkac_config_rec *add = (spkac_config_rec *) addv; spkac_config_rec *base = (spkac_config_rec *) basev; new->size = (add->size_set == 0) ? base->size : add->size; new->size_set = add->size_set || base->size_set; new->name = (add->name_set == 0) ? base->name : add->name; new->name_set = add->name_set || base->name_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; return new; } static const char *set_spkac_size(cmd_parms *cmd, void *dconf, const char *arg) { spkac_config_rec *conf = dconf; if (apr_strtoff(&conf->size, arg, NULL, 10) != APR_SUCCESS || conf->size < 4096) { return "SpkacSize 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_spkac_name(cmd_parms *cmd, void *dconf, const char *arg) { spkac_config_rec *conf = dconf; conf->name = arg; conf->name_set = 1; return NULL; } static const char *set_location(cmd_parms *cmd, void *dconf, const char *arg) { spkac_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) { spkac_config_rec *conf = dconf; name_rec *name = apr_array_push(conf->subject); if (strcmp(arg1, "*")) { 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); } } 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) { spkac_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; } 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) { spkac_config_rec *conf = dconf; name_rec *name = apr_array_push(conf->subjectaltname); if (strcmp(arg1, "*")) { 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 { 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) { spkac_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 command_rec spkac_cmds[] = { AP_INIT_TAKE1("SpkacSize", set_spkac_size, NULL, RSRC_CONF | ACCESS_CONF, "Set to the maximum size of the SPKAC request from the client."), AP_INIT_TAKE1("SpkacName", set_spkac_name, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of the SPKAC request variable from the client. Defaults to " DEFAULT_SPKAC_NAME), AP_INIT_TAKE1("SpkacLocation", set_location, NULL, RSRC_CONF | ACCESS_CONF, "Set to the location of the spkac service."), AP_INIT_TAKE12("SpkacSubjectRequest", set_subject_request, NULL, RSRC_CONF | ACCESS_CONF, "Specify fields in the request that will be included in the certificate. DN attribute name first, then optionally request variable if not the same."), AP_INIT_TAKE2("SpkacSubjectSet", set_subject_set, NULL, RSRC_CONF | ACCESS_CONF, "Specify DN attribute and value that will be included in the certificate."), AP_INIT_TAKE12("SpkacSubjectAltNameRequest", 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("SpkacSubjectAltNameSet", set_subjectaltname_set, NULL, RSRC_CONF | ACCESS_CONF, "Specify subjectAltName attribute and value that will be included in the certificate."), { NULL } }; static apr_status_t spkac_BIO_cleanup(void *data) { BIO_free((BIO *) data); return APR_SUCCESS; } static apr_status_t spkac_PKCS7_cleanup(void *data) { PKCS7_free((PKCS7 *) data); return APR_SUCCESS; } static apr_status_t spkac_X509_REQ_cleanup(void *data) { X509_REQ_free((X509_REQ *) data); return APR_SUCCESS; } static apr_status_t spkac_NETSCAPE_SPKI_cleanup(void *data) { NETSCAPE_SPKI_free((NETSCAPE_SPKI *) data); return APR_SUCCESS; } static apr_status_t spkac_EVP_PKEY_cleanup(void *data) { EVP_PKEY_free((EVP_PKEY *) data); return APR_SUCCESS; } static ca_asn1_t *make_NETSCAPE_SPKI(apr_pool_t *pool, NETSCAPE_SPKI *spki) { ca_asn1_t *buf = apr_palloc(pool, sizeof(ca_asn1_t)); unsigned char *tmp; buf->len = i2d_NETSCAPE_SPKI(spki, NULL); buf->val = tmp = apr_palloc(pool, buf->len); i2d_NETSCAPE_SPKI(spki, &tmp); return buf; } 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 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 spkac 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 void strip_whitespace(char *s1) { char *s2 = s1; while (*s1) { switch (*s1) { case '\n': case '\r': case ' ': { break; } default: { if (s1 != s2) { *(s2++) = *s1; } else { s2++; } break; } } s1++; } } static int spkac_transform_subject(request_rec *r, apr_array_header_t *pairs, X509_NAME *subject, apr_hash_t *seen) { int i, j; spkac_config_rec *conf = ap_get_module_config(r->per_dir_config, &spkac_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.", 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 (!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 spkac_transform_subjectaltname(request_rec *r, apr_array_header_t *pairs, X509_REQ *creq, apr_hash_t *seen) { int i, j; GENERAL_NAMES *sans = NULL; spkac_config_rec *conf = ap_get_module_config(r->per_dir_config, &spkac_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 post_spkac(request_rec *r, spkac_config_rec *conf) { apr_status_t rv; apr_array_header_t *pairs = NULL; apr_off_t offset; apr_size_t size; char *buffer; unsigned char *p; 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; NETSCAPE_SPKI *spki = NULL; EVP_PKEY *pktmp = NULL; X509_NAME *subject = NULL; X509 *cert = NULL; PKCS7 *p7 = NULL; int j; apr_size_t len; /* * 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, spkac_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 = spkac_transform_subject(r, pairs, subject, seen); if (rv != OK) { return rv; } /* transform the subjectAltNames as per the configuration */ rv = spkac_transform_subjectaltname(r, pairs, creq, seen); if (rv != OK) { return rv; } /* find the SPKAC, 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->name)) { 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; /* hack around a lack of support for ignoring whitespace */ strip_whitespace(buffer); der = p = apr_palloc(r->pool, size); len = apr_base64_decode_binary(p, buffer); spki = d2i_NETSCAPE_SPKI(NULL, &der, len); if (!spki) { log_message(r, APR_SUCCESS, "SPKAC structure could not be decoded"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, spki, spkac_NETSCAPE_SPKI_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; } /* 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, spkac_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 - spki structure specified in the request? */ if (!spki) { log_message(r, APR_SUCCESS, "no SPKAC structure was specified"); return HTTP_BAD_REQUEST; } /* do the public key verification, and leave if invalid */ if (!(pktmp = NETSCAPE_SPKI_get_pubkey(spki))) { log_message(r, APR_SUCCESS, "error unpacking SPKAC public key"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, pktmp, spkac_EVP_PKEY_cleanup, apr_pool_cleanup_null); j = NETSCAPE_SPKI_verify(spki, pktmp); if (j <= 0) { log_message(r, APR_SUCCESS, "signature verification failed on SPKAC public key"); 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 challenge, if present */ if (spki->spkac->challenge) { if (!X509_REQ_add1_attr_by_txt(creq, "challengePassword", ASN1_STRING_type(spki->spkac->challenge), #if HAVE_ASN1_STRING_GET0_DATA ASN1_STRING_get0_data(spki->spkac->challenge), #else ASN1_STRING_data(spki->spkac->challenge), #endif ASN1_STRING_length(spki->spkac->challenge))) { 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 (spki) { apr_hash_set(params, "popSpkac", APR_HASH_KEY_STRING, make_NETSCAPE_SPKI(r->pool, spki)); } /* 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; } /* read in the certificate */ 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, spkac_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)) { unsigned char *buf; cert = sk_X509_value(certs, 0); len = i2d_X509(cert, NULL); if (len <= 0) { log_message(r, APR_SUCCESS, "could not DER encode the signed X509"); return HTTP_INTERNAL_SERVER_ERROR; } der = buf = apr_palloc(r->pool, len); if (!i2d_X509(cert, &buf)) { log_message(r, APR_SUCCESS, "could not DER encode the signed X509"); return HTTP_INTERNAL_SERVER_ERROR; } } else { log_message(r, APR_SUCCESS, "PKCS7 contained zero certificates, nothing to return"); return HTTP_BAD_REQUEST; } } else { log_message(r, APR_SUCCESS, "PKCS7 was not signedData, nothing to return"); return HTTP_BAD_REQUEST; } 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, "spkac_handler: ap_pass_brigade returned %i", status); return HTTP_INTERNAL_SERVER_ERROR; } /* ready to leave */ return OK; } static int get_wadl(request_rec *r, spkac_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 SPKAC 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->name); return OK; } static int spkac_handler(request_rec *r) { spkac_config_rec *conf = ap_get_module_config(r->per_dir_config, &spkac_module); if (!conf) { return DECLINED; } if (strcmp(r->handler, "spkac")) { return DECLINED; } ap_allow_methods(r, 1, "POST", "OPTIONS", NULL); if (!strcmp(r->method, "POST")) { return post_spkac(r, conf); } else if (!strcmp(r->method, "OPTIONS")) { return get_wadl(r, conf); } else { return HTTP_METHOD_NOT_ALLOWED; } } static apr_status_t spkac_cleanup(void *data) { EVP_PKEY_free(pknull); pknull = NULL; ERR_free_strings(); EVP_cleanup(); return APR_SUCCESS; } static int spkac_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, spkac_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(spkac_pre_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(spkac_handler, NULL, NULL, APR_HOOK_MIDDLE); } AP_DECLARE_MODULE(spkac) = { STANDARD20_MODULE_STUFF, create_spkac_dir_config, /* dir config creater */ merge_spkac_dir_config, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ spkac_cmds, /* command apr_table_t */ register_hooks /* register hooks */ };