/* 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 form data as * a PKCS12 response. * * Author: Graham Leggett * */ #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" #define DEFAULT_PKCS12_SIZE 128*1024 #define DEFAULT_PKCS12_NICKNAME "certificate" #define DEFAULT_PKCS12_PARAM_CHALLENGE "challenge" #define DEFAULT_PKCS12_MD "SHA256" module AP_MODULE_DECLARE_DATA pkcs12_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_challenge_set:1; int param_nickname_set:1; int location_set:1; int subject_set :1; int subjectaltname_set :1; int iter_set :1; int certpbe_set :1; int keypbe_set :1; int nickname_set :1; int macmd_set :1; const char *param_challenge; const char *param_nickname; const char *location; const EVP_MD *macmd; const ap_expr_info_t *nickname; apr_array_header_t *subject; apr_array_header_t *subjectaltname; apr_off_t size; int iter; int certpbe; int keypbe; } pkcs12_config_rec; static void *create_pkcs12_dir_config(apr_pool_t *p, char *d) { pkcs12_config_rec *conf = apr_pcalloc(p, sizeof(pkcs12_config_rec)); conf->size = DEFAULT_PKCS12_SIZE; conf->param_challenge = DEFAULT_PKCS12_PARAM_CHALLENGE; conf->subject = apr_array_make(p, 10, sizeof(name_rec)); conf->subjectaltname = apr_array_make(p, 10, sizeof(name_rec)); conf->iter = PKCS12_DEFAULT_ITER; conf->certpbe = NID_pbe_WithSHA1And3_Key_TripleDES_CBC; conf->keypbe = NID_pbe_WithSHA1And3_Key_TripleDES_CBC; conf->macmd = EVP_get_digestbyname(DEFAULT_PKCS12_MD); return conf; } static void *merge_pkcs12_dir_config(apr_pool_t *p, void *basev, void *addv) { pkcs12_config_rec *new = (pkcs12_config_rec *) apr_pcalloc(p, sizeof(pkcs12_config_rec)); pkcs12_config_rec *add = (pkcs12_config_rec *) addv; pkcs12_config_rec *base = (pkcs12_config_rec *) basev; new->size = (add->size_set == 0) ? base->size : add->size; new->size_set = add->size_set || base->size_set; new->nickname = (add->nickname_set == 0) ? base->nickname : add->nickname; new->nickname_set = add->nickname_set || base->nickname_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->param_nickname = (add->param_nickname_set == 0) ? base->param_nickname : add->param_nickname; new->param_nickname_set = add->param_nickname_set || base->param_nickname_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->iter = (add->iter_set == 0) ? base->iter : add->iter; new->iter_set = add->iter_set || base->iter_set; new->certpbe = (add->certpbe_set == 0) ? base->certpbe : add->certpbe; new->certpbe_set = add->certpbe_set || base->certpbe_set; new->keypbe = (add->keypbe_set == 0) ? base->keypbe : add->keypbe; new->keypbe_set = add->keypbe_set || base->keypbe_set; new->macmd = (add->macmd_set == 0) ? base->macmd : add->macmd; new->macmd_set = add->macmd_set || base->macmd_set; return new; } static const char *set_pkcs12_size(cmd_parms *cmd, void *dconf, const char *arg) { pkcs12_config_rec *conf = dconf; if (apr_strtoff(&conf->size, arg, NULL, 10) != APR_SUCCESS || conf->size < 4096) { return "Pkcs12Size argument must be an integer representing the max size of a form request, at least 4096"; } conf->size_set = 1; return NULL; } static const char *set_pkcs12_param_challenge(cmd_parms *cmd, void *dconf, const char *arg) { pkcs12_config_rec *conf = dconf; conf->param_challenge = arg; conf->param_challenge_set = 1; return NULL; } static const char *set_pkcs12_param_nickname(cmd_parms *cmd, void *dconf, const char *arg) { pkcs12_config_rec *conf = dconf; conf->param_nickname = arg; conf->param_nickname_set = 1; return NULL; } static const char *set_location(cmd_parms *cmd, void *dconf, const char *arg) { pkcs12_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) { pkcs12_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) { pkcs12_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) { pkcs12_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) { pkcs12_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_pkcs12_iter(cmd_parms *cmd, void *dconf, const char *arg) { pkcs12_config_rec *conf = dconf; if (!strcasecmp(arg, "none")) { conf->iter = -1; } else { conf->iter = atoi(arg); if (conf->iter <= 0) { return "Pkcs12Iterate argument must be a positive integer representing the number of iterations, or 'none' to disable"; } } conf->iter_set = 1; return NULL; } static const char *set_pkcs12_digest(cmd_parms *cmd, void *dconf, const char *arg) { pkcs12_config_rec *conf = dconf; conf->macmd = EVP_get_digestbyname(arg); if (!conf->macmd) { return apr_psprintf(cmd->temp_pool, "Pkcs12Digest '%s' is not recognised", arg); } conf->macmd_set = 1; return NULL; } static const char *lookup_pbe(apr_pool_t *pool, int *ppbe, const char *str) { if (!strcmp(str, "NONE")) { *ppbe = -1; } else { *ppbe = OBJ_txt2nid(str); if (*ppbe == NID_undef) { return apr_psprintf(pool, "PBE algorithm '%s' is unrecognised", str); } } return NULL; } static const char *set_pkcs12_certpbe(cmd_parms *cmd, void *dconf, const char *arg) { pkcs12_config_rec *conf = dconf; conf->certpbe_set = 1; return lookup_pbe(cmd->temp_pool, &conf->certpbe, arg); } static const char *set_pkcs12_keypbe(cmd_parms *cmd, void *dconf, const char *arg) { pkcs12_config_rec *conf = dconf; conf->keypbe_set = 1; return lookup_pbe(cmd->temp_pool, &conf->keypbe, arg); } static const char *set_pkcs12_nickname(cmd_parms *cmd, void *dconf, const char *arg1) { pkcs12_config_rec *conf = dconf; const char *expr_err = NULL; conf->nickname = ap_expr_parse_cmd(cmd, arg1, AP_EXPR_FLAG_STRING_RESULT, &expr_err, NULL); if (expr_err) { return apr_pstrcat(cmd->temp_pool, "Cannot parse expression '", arg1, "': ", expr_err, NULL); } conf->nickname_set = 1; return NULL; } static const command_rec pkcs12_cmds[] = { AP_INIT_TAKE1("Pkcs12Size", set_pkcs12_size, NULL, RSRC_CONF | ACCESS_CONF, "Set to the maximum size of the form request from the client."), AP_INIT_TAKE1("Pkcs12ParamChallenge", set_pkcs12_param_challenge, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of the request variable from the client containing the challenge password."), AP_INIT_TAKE1("Pkcs12ParamNickname", set_pkcs12_param_nickname, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of the request variable from the client containing the certificate nickname. Overrides the Pkcs12Nickname directive."), AP_INIT_TAKE1("Pkcs12Location", set_location, NULL, RSRC_CONF | ACCESS_CONF, "Set to the location of the pkcs12 service."), AP_INIT_TAKE12("Pkcs12SubjectRequest", 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("Pkcs12SubjectSet", set_subject_set, NULL, RSRC_CONF | ACCESS_CONF, "Specify subject attribute and value that will be included in the certificate."), AP_INIT_TAKE12("Pkcs12SubjectAltNameRequest", set_subjectaltname_request, NULL, RSRC_CONF | ACCESS_CONF, "Specify fields in the request subjectAltName that will be copied over to the certificate, with optional limit to the number of fields that may appear."), AP_INIT_TAKE2("Pkcs12SubjectAltNameSet", set_subjectaltname_set, NULL, RSRC_CONF | ACCESS_CONF, "Specify subjectAltName attribute and value that will be included in the certificate."), AP_INIT_TAKE1("Pkcs12Iterate", set_pkcs12_iter, NULL, RSRC_CONF | ACCESS_CONF, "Set to the number of iterations. Defaults to 2048."), AP_INIT_TAKE1("Pkcs12Digest", set_pkcs12_digest, NULL, RSRC_CONF | ACCESS_CONF, "Set to the mac digest used on the PKCS12. Defaults to '" DEFAULT_PKCS12_MD "'."), AP_INIT_TAKE1("Pkcs12CertificatePBE", set_pkcs12_certpbe, NULL, RSRC_CONF | ACCESS_CONF, "Specify the certificate PBE algorithm. Defaults to PBE-SHA1-3DES."), AP_INIT_TAKE1("Pkcs12KeyPBE", set_pkcs12_keypbe, NULL, RSRC_CONF | ACCESS_CONF, "Specify the key PBE algorithm. Defaults to PBE-SHA1-3DES."), AP_INIT_TAKE1("Pkcs12Nickname", set_pkcs12_nickname, NULL, RSRC_CONF | ACCESS_CONF, "Set to an expression that resolves to the nickname of the certificate. Defaults to '" DEFAULT_PKCS12_NICKNAME "'."), { 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 PKCS12 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, pkcs12_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 parameters are expected with the subject\n" " 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)); return OK; } static apr_status_t pkcs12_BIO_cleanup(void *data) { BIO_free((BIO *) data); return APR_SUCCESS; } static apr_status_t pkcs12_EVP_PKEY_cleanup(void *data) { EVP_PKEY_free((EVP_PKEY *) data); return APR_SUCCESS; } static apr_status_t pkcs12_PKCS7_cleanup(void *data) { PKCS7_free((PKCS7 *) data); return APR_SUCCESS; } static apr_status_t pkcs12_PKCS12_cleanup(void *data) { PKCS12_free((PKCS12 *) data); return APR_SUCCESS; } static apr_status_t pkcs12_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 int pkcs12_transform_subject(request_rec *r, apr_array_header_t *pairs, X509_NAME *subject, apr_hash_t *seen) { int i, j; pkcs12_config_rec *conf = ap_get_module_config(r->per_dir_config, &pkcs12_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 pkcs12_transform_subjectaltname(request_rec *r, apr_array_header_t *pairs, X509_REQ *creq, apr_hash_t *seen) { int i, j; GENERAL_NAMES *sans = NULL; pkcs12_config_rec *conf = ap_get_module_config(r->per_dir_config, &pkcs12_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 pkcs12_form_handler(request_rec *r) { apr_status_t rv; apr_array_header_t *pairs = NULL; apr_off_t offset; apr_size_t challenge_size; const char *challenge = NULL; unsigned char *p; const char *nickname = NULL; apr_size_t nickname_size; 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; EVP_PKEY *p8 = NULL; X509_NAME *subject = NULL; PKCS7 *p7 = NULL; PKCS12 *p12 = NULL; X509 *cert = NULL; STACK_OF(X509) *certs = NULL; apr_size_t len; pkcs12_config_rec *conf = ap_get_module_config(r->per_dir_config, &pkcs12_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, pkcs12_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 = pkcs12_transform_subject(r, pairs, subject, seen); if (rv != OK) { return rv; } /* transform the subjectAltNames as per the configuration */ rv = pkcs12_transform_subjectaltname(r, pairs, creq, seen); if (rv != OK) { return rv; } /* find the parameters, and find unrecognised options */ while (pairs && !apr_is_empty_array(pairs)) { ap_form_pair_t *pair = (ap_form_pair_t *) apr_array_pop(pairs); /* handle the subject */ 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_pcalloc(r->pool, challenge_size + 1); apr_brigade_flatten(pair->value, (char *)challenge, &challenge_size); } /* handle the param_nickname */ else if (conf->param_nickname && !strcmp(pair->name, conf->param_nickname)) { apr_brigade_length(pair->value, 1, &offset); nickname_size = (apr_size_t) offset; nickname = apr_pcalloc(r->pool, nickname_size + 1); apr_brigade_flatten(pair->value, (char *)nickname, &nickname_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, pkcs12_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); } } /* handle the nickname */ if (!nickname) { if (conf->nickname) { const char *err = NULL; nickname = ap_expr_str_exec(r, conf->nickname, &err); if (err || !nickname) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "Nickname expression could not be executed: %s", err)); return HTTP_INTERNAL_SERVER_ERROR; } } else { nickname = DEFAULT_PKCS12_NICKNAME; } } /* extract the param_challenge, if present */ if (challenge) { if (!X509_REQ_add1_attr_by_txt(creq, "challengePassword", V_ASN1_UTF8STRING, (const unsigned char *) challenge, challenge_size)) { log_message(r, APR_SUCCESS, "could not add the challenge to the certificate request"); return HTTP_INTERNAL_SERVER_ERROR; } } else { log_message(r, APR_SUCCESS, "no challenge password was submitted with the form"); return HTTP_BAD_REQUEST; } /* handle the subject */ if (subject) { apr_hash_set(params, "subject", APR_HASH_KEY_STRING, make_X509_NAME(r->pool, subject)); } /* sign the X509_REQ with a dummy signature to work around serialisation bugs in openssl */ // X509_REQ_set_pubkey(creq, pknull); // X509_REQ_sign(creq, pknull, mdnull); /* no proof of possession for pkcs12 */ /* 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; } /* create a private key */ rv = ap_run_ca_makekey(r, params, &der, &len); if (rv == DECLINED) { log_message(r, APR_SUCCESS, "No module configured to generate a private key (ca_makekey)"); return HTTP_INTERNAL_SERVER_ERROR; } if (rv != OK) { return rv; } if (!d2i_AutoPrivateKey(&p8, &der, len)) { log_message(r, APR_SUCCESS, "could not DER decode the generated private key (ca_makekey)"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, p8, pkcs12_EVP_PKEY_cleanup, apr_pool_cleanup_null); X509_REQ_set_pubkey(creq, p8); /* sign the certificate request with the key */ if (X509_REQ_sign(creq, p8, EVP_sha256()) <= 0) { log_message(r, APR_SUCCESS, "could not sign the certificate request"); return HTTP_INTERNAL_SERVER_ERROR; } /* write out the certificate request again */ 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 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 PKCS7 certificates"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, p7, pkcs12_PKCS7_cleanup, apr_pool_cleanup_null); /* grab the first certificate */ if (OBJ_obj2nid(p7->type) == NID_pkcs7_signed) { certs = p7->d.sign->cert; if (sk_X509_num(certs)) { cert = sk_X509_shift(certs); } 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; } p12 = PKCS12_create((char *)challenge, (char *)nickname, p8, cert, certs, conf->keypbe, conf->certpbe, conf->iter, -1, 0); if (!p12) { log_message(r, APR_SUCCESS, "could not create a PKCS12 structure"); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, p12, pkcs12_PKCS12_cleanup, apr_pool_cleanup_null); if (conf->iter != -1) { PKCS12_set_mac(p12, challenge, -1, NULL, 0, conf->iter, conf->macmd); } /* write out the certificate */ len = i2d_PKCS12(p12, NULL); if (len <= 0) { log_message(r, APR_SUCCESS, "could not DER encode the PKCS12 structure"); return HTTP_INTERNAL_SERVER_ERROR; } der = p = apr_palloc(r->pool, len); if (!i2d_PKCS12(p12, &p)) { log_message(r, APR_SUCCESS, "could not DER encode the PKCS12 structure"); return HTTP_INTERNAL_SERVER_ERROR; } /* return the PKCS12 response */ 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, "pkcs12_handler: ap_pass_brigade returned %i", status); return HTTP_INTERNAL_SERVER_ERROR; } /* ready to leave */ return OK; } static int pkcs12_handler(request_rec *r) { pkcs12_config_rec *conf = ap_get_module_config(r->per_dir_config, &pkcs12_module); if (!conf) { return DECLINED; } if (strcmp(r->handler, "pkcs12")) { 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 pkcs12_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 pkcs12_cleanup(void *data) { ERR_free_strings(); EVP_cleanup(); return APR_SUCCESS; } static int pkcs12_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, pkcs12_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(pkcs12_pre_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(pkcs12_handler, NULL, NULL, APR_HOOK_MIDDLE); } AP_DECLARE_MODULE(pkcs12) = { STANDARD20_MODULE_STUFF, create_pkcs12_dir_config, /* dir config creater */ merge_pkcs12_dir_config, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ pkcs12_cmds, /* command apr_table_t */ register_hooks /* register hooks */ };