[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