[rs-commit] r38 - /mod_csr/trunk/mod_csr.c
rs-commit at redwax.eu
rs-commit at redwax.eu
Sun Apr 7 23:07:51 CEST 2019
Author: minfrin at redwax.eu
Date: Sun Apr 7 23:07:51 2019
New Revision: 38
Log:
Initial import of mod_csr.
Modified:
mod_csr/trunk/mod_csr.c
Modified: mod_csr/trunk/mod_csr.c
==============================================================================
--- mod_csr/trunk/mod_csr.c (original)
+++ mod_csr/trunk/mod_csr.c Sun Apr 7 23:07:51 2019
@@ -0,0 +1,1164 @@
+/* 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 <apr_strings.h>
+
+#include <openssl/asn1t.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/x509v3.h>
+
+#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_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;
+
+typedef struct
+{
+ const char *name; /* raw name of the object, NULL matches all */
+ int nid; /* name element from the request */
+ const char *cgi; /* if present, take the value from the subprocess environment */
+ const char *value; /* if present, use the absolute value */
+ 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 "ScepSize 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);
+
+ 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_cgi(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);
+ }
+ name->cgi = arg2;
+ 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);
+ }
+ name->value = arg2;
+ 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);
+
+ 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_cgi(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);
+ }
+ name->cgi = arg2;
+ 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);
+ }
+ name->value = arg2;
+ 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_TAKE1("CsrSize",
+ set_csr_size, NULL, RSRC_CONF | ACCESS_CONF,
+ "Set to the maximum size of the CSR request from the client."),
+ 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("CsrLocation",
+ set_location, NULL, RSRC_CONF | ACCESS_CONF,
+ "Set to the location of the csr service."),
+ 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("CsrSubjectCGI",
+ set_subject_cgi, NULL, RSRC_CONF | ACCESS_CONF,
+ "Specify CGI variables in the request that will be included in the certificate subject. DN attribute name first, then CGI variable."),
+ AP_INIT_TAKE2("CsrSubjectSet",
+ set_subject_set, NULL, RSRC_CONF | ACCESS_CONF,
+ "Specify subject attribute and value that will be included in the certificate."),
+ 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("CsrSubjectAltNameCGI",
+ set_subjectaltname_cgi, NULL, RSRC_CONF | ACCESS_CONF,
+ "Specify CGI variables in the request that will be included in the certificate subjectAltName. DN attribute name first, then CGI variable."),
+ 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("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."),
+ { 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,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<wadl:application xmlns:wadl=\"http://wadl.dev.java.net/2009/02\"\n"
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ " xsi:schemaLocation=\"http://wadl.dev.java.net/2009/02 file:wadl.xsd\">\n"
+ " <wadl:resources base=\"%s\">\n"
+ " <wadl:resource path=\"/\">\n"
+ " <wadl:method name=\"POST\" id=\"csr\">\n"
+ " <wadl:request>\n"
+ " <wadl:representation mediaType=\"application/x-www-form-urlencoded\">\n"
+ " <wadl:doc>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-'.</wadl:doc>\n"
+ " </wadl:representation>\n"
+ " </wadl:request>\n"
+ " <wadl:response status=\"500\">\n"
+ " <wadl:representation mediaType=\"text/html\">\n"
+ " <wadl:doc>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.</wadl:doc>\n"
+ " </wadl:representation>\n"
+ " </wadl:response>\n"
+ " <wadl:response status=\"400\">\n"
+ " <wadl:representation mediaType=\"text/html\">\n"
+ " <wadl:doc>For requests with incomplete, unparseable or missing information,\n"
+ " 400 Bad Request is returned.</wadl:doc>\n"
+ " </wadl:representation>\n"
+ " </wadl:response>\n"
+ " <wadl:response status=\"200\">\n"
+ " <wadl:representation mediaType=\"application/x-x509-user-cert\">\n"
+ " <wadl:doc>After a successful signing of the certificate, 200 OK will be returned\n"
+ " with the body containing the ASN.1 DER-encoded X509 certificate.</wadl:doc>\n"
+ " </wadl:representation>\n"
+ " </wadl:response>\n"
+ " </wadl:method>\n"
+ " </wadl:resource>\n"
+ " </wadl:resources>\n"
+ "</wadl:application>\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->cgi) {
+ const char *val = (const char *) apr_table_get(r->subprocess_env,
+ name->cgi);
+ if (!val) {
+ log_message(r, APR_SUCCESS,
+ apr_psprintf(r->pool,
+ "CGI name '%s' was not found, and could not be added to the certificate subject as '%s'.",
+ name->cgi, name->name));
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (val) {
+ if (!X509_NAME_add_entry_by_NID(subject, name->nid,
+ MBSTRING_UTF8, (unsigned char *) val, -1, -1, 0)) {
+ log_message(r, APR_SUCCESS,
+ apr_psprintf(r->pool,
+ "CGI name '%s' with value '%s' could not be added to the certificate subject as '%s'.",
+ name->cgi, val, name->name));
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ }
+
+ else if (name->value) {
+ if (!X509_NAME_add_entry_by_NID(subject, name->nid, MBSTRING_UTF8,
+ (unsigned char *) name->value, -1, -1, 0)) {
+ log_message(r, APR_SUCCESS,
+ apr_psprintf(r->pool,
+ "The value '%s' could not be added to the certificate subject as '%s'.",
+ name->value, 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 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->cgi) {
+ char *val = (char *) apr_table_get(r->subprocess_env, name->cgi);
+ if (!val) {
+ log_message(r, APR_SUCCESS,
+ apr_psprintf(r->pool,
+ "CGI name '%s' was not found, and could not be added to the certificate subjectAltName as '%s'.",
+ name->cgi, name->name));
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (val) {
+ GENERAL_NAME *gen = a2i_GENERAL_NAME(NULL, NULL, NULL,
+ name->nid, val, 0);
+ if (!gen) {
+ log_message(r, APR_SUCCESS,
+ apr_psprintf(r->pool,
+ "CGI name '%s' with value '%s' could not be added to the certificate subjectAltName as '%s'.",
+ name->cgi, val, name->name));
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (!sans) {
+ sans = GENERAL_NAMES_new();
+ }
+ sk_GENERAL_NAME_push(sans, gen);
+ }
+
+ }
+
+ else if (name->value) {
+ GENERAL_NAME *gen = a2i_GENERAL_NAME(NULL, NULL, NULL, name->nid,
+ (char *) name->value, 0);
+ if (!gen) {
+ log_message(r, APR_SUCCESS,
+ apr_psprintf(r->pool,
+ "Value '%s' could not be added to the certificate subjectAltName as '%s'.",
+ name->value, 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;
+ X509 *cert = 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);
+
+ /* duplicate the signature algorithm */
+ creq->sig_alg = X509_ALGOR_dup(req->sig_alg);
+
+ /* 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/x-pem-file", 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/x-pem-file");
+ 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_type(r, "application/pkcs7-mime");
+ 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)
+{
+ 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)
+{
+ OpenSSL_add_all_algorithms();
+ ERR_load_crypto_strings();
+
+ apr_pool_cleanup_register(pconf, NULL, csr_cleanup, apr_pool_cleanup_null);
+
+ 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);
+}
+
+module AP_MODULE_DECLARE_DATA csr_module =
+{ 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 */
+};
More information about the rs-commit
mailing list