/* 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 SCEP service. * * Author: Graham Leggett * */ #include #include #include #include #include #include #include #include #include #include #include #include #if OPENSSL_VERSION_NUMBER < 0x1010001fL #include #endif #include #include #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_protocol.h" #include "http_request.h" #include "util_script.h" #include "ap_expr.h" #include "mod_ca.h" #undef PACKAGE_BUGREPORT #undef PACKAGE_NAME #undef PACKAGE_STRING #undef PACKAGE_TARNAME #undef PACKAGE_VERSION #include "config.h" #include "openssl_setter_compat.h" #define DEFAULT_SCEP_SIZE 128*1024 #define DEFAULT_FRESHNESS 2 #define DEFAULT_FRESHNESS_MAX 3600*24 #define DN_UNLIMITED (-1) module AP_MODULE_DECLARE_DATA scep_module; EVP_PKEY *pknull; const EVP_MD *mdnull; typedef struct { const char *name; /* raw name of the object, NULL matches all */ int nid; /* name element from the request */ const ap_expr_info_t *expr; /* if present, expression to be assigned to each name */ int limit; /* if present, take up to the limit number of names */ } name_rec; typedef struct { apr_off_t size; int size_set; const char *location; int location_set; X509 *signer; int signer_set; X509 *next_signer; int next_signer_set; EVP_PKEY *key; int key_set; apr_array_header_t *subject; apr_array_header_t *subjectaltname; int freshness; int freshness_max; const char *crl_url; int crl_url_set :1; int freshness_set :1; int subject_set :1; int subjectaltname_set :1; } scep_config_rec; typedef struct { int nid; char *oid; char *name1; char *name2; } niddef_t; #define NEW_NIDS 8 static niddef_t scep_oid_def[NEW_NIDS] = { { 0, "2.16.840.1.113733.1.9.2", "messageType", "messageType" }, { 0, "2.16.840.1.113733.1.9.3", "pkiStatus", "pkiStatus" }, { 0, "2.16.840.1.113733.1.9.4", "failInfo", "failInfo" }, { 0, "2.16.840.1.113733.1.9.5", "senderNonce", "senderNonce" }, { 0, "2.16.840.1.113733.1.9.6", "recipientNonce", "recipientNonce" }, { 0, "2.16.840.1.113733.1.9.7", "transactionID", "transactionID" }, { 0, "2.16.840.1.113733.1.9.8", "extensionReq", "extensionReq" }, { 0, "1.3.6.1.4.1.4263.5.5", "proxyAuthenticator", "proxyAuthenticator" }, }; typedef struct { X509 *encrypt_cert; PKCS7 *certs; const char *transactionId; int messageType; int pkiStatus; int failInfo; unsigned char *senderNonce; /* 16 bytes */ int senderNonceLength; unsigned char *recipientNonce; /* 16 bytes */ int recipientNonceLength; ASN1_OCTET_STRING *proxyAuthenticator; apr_time_t validity; } scep_t; /** * 3.1.1.2. messageType * * The messageType attribute specifies the type of operation performed * by the transaction. This attribute MUST be included in all PKI * messages. The following message types are defined: * * o PKCSReq (19) -- PKCS#10 [RFC2986] certificate request * * o CertRep (3) -- Response to certificate or CRL request * * o GetCertInitial (20) -- Certificate polling in manual enrollment * * o GetCert (21) -- Retrieve a certificate * * o GetCRL (22) -- Retrieve a CRL * * Undefined message types are treated as an error. * */ #define SCEP_MESSAGETYPE_PKCSREQ 19 #define SCEP_MESSAGETYPE_CERTREP 3 #define SCEP_MESSAGETYPE_GETCERTINITIAL 20 #define SCEP_MESSAGETYPE_GETCERT 21 #define SCEP_MESSAGETYPE_GETCRL 22 /** * 3.1.1.3. pkiStatus * * All response messages MUST include transaction status information, * which is defined as pkiStatus attribute: * * o SUCCESS (0) -- request granted * * o FAILURE (2) -- request rejected. When pkiStatus is FAILURE, the * failInfo attribute, as defined in Section 3.1.1.4, MUST also be * present. * * o PENDING (3) -- request pending for manual approval * * Undefined pkiStatus attributes are treated as an error. * */ #define SCEP_PKISTATUS_SUCCESS 0 #define SCEP_PKISTATUS_FAILURE 2 #define SCEP_PKISTATUS_PENDING 3 /** * 3.1.1.4. failInfo * * The failInfo attribute MUST contain one of the following failure * reasons: * * o badAlg (0) -- Unrecognized or unsupported algorithm identifier * * o badMessageCheck (1) -- integrity check failed * * o badRequest (2) -- transaction not permitted or supported * * o badTime (3) -- The signingTime attribute from the PKCS#7 * authenticatedAttributes was not sufficiently close to the system * time (see Section 3.1.1.6). * o badCertId (4) -- No certificate could be identified matching the * provided criteria * */ #define SCEP_FAILINFO_BADALG 0 #define SCEP_FAILINFO_BADMESSAGECHECK 1 #define SCEP_FAILINFO_BADREQUEST 2 #define SCEP_FAILINFO_BADTIME 3 #define SCEP_FAILINFO_CERTID 4 /** * 3.2.3.1. IssuerAndSubject * * Similar to the IssuerAndSerial defined in PKCS#7 [RFC2315] Section * 6.7, we need to define an IssuerAndSubject ASN.1 type (Figure 7). * * The ASN.1 definition of the issuerAndSubject type is as follows: * issuerAndSubject ::= SEQUENCE { * issuer Name, * subject Name * } */ typedef struct pkcs7_issuer_and_subject_st { X509_NAME *issuer; X509_NAME *subject; } PKCS7_ISSUER_AND_SUBJECT; DECLARE_ASN1_FUNCTIONS(PKCS7_ISSUER_AND_SUBJECT) DECLARE_ASN1_ENCODE_FUNCTIONS(PKCS7_ISSUER_AND_SUBJECT, PKCS7_ISSUER_AND_SUBJECT, PKCS7_ISSUER_AND_SUBJECT) ASN1_SEQUENCE(PKCS7_ISSUER_AND_SUBJECT) = { ASN1_SIMPLE(PKCS7_ISSUER_AND_SUBJECT, issuer, X509_NAME), ASN1_SIMPLE(PKCS7_ISSUER_AND_SUBJECT, subject, X509_NAME) } ASN1_SEQUENCE_END(PKCS7_ISSUER_AND_SUBJECT) IMPLEMENT_ASN1_FUNCTIONS(PKCS7_ISSUER_AND_SUBJECT) PKCS7_ISSUER_AND_SUBJECT *d2i_PKCS7_ISSUER_AND_SUBJECT_bio(BIO *bp, PKCS7_ISSUER_AND_SUBJECT **ias) { return ASN1_item_d2i_bio(ASN1_ITEM_rptr(PKCS7_ISSUER_AND_SUBJECT), bp, ias); } PKCS7_ISSUER_AND_SERIAL *d2i_PKCS7_ISSUER_AND_SERIAL_bio(BIO *bp, PKCS7_ISSUER_AND_SERIAL **ias) { return ASN1_item_d2i_bio(ASN1_ITEM_rptr(PKCS7_ISSUER_AND_SERIAL), bp, ias); } static void *create_scep_dir_config(apr_pool_t *p, char *d) { scep_config_rec *conf = apr_pcalloc(p, sizeof(scep_config_rec)); conf->size = DEFAULT_SCEP_SIZE; 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_scep_dir_config(apr_pool_t *p, void *basev, void *addv) { scep_config_rec *new = (scep_config_rec *) apr_pcalloc(p, sizeof(scep_config_rec)); scep_config_rec *add = (scep_config_rec *) addv; scep_config_rec *base = (scep_config_rec *) basev; new->size = (add->size_set == 0) ? base->size : add->size; new->size_set = add->size_set || base->size_set; new->location = (add->location_set == 0) ? base->location : add->location; new->location_set = add->location_set || base->location_set; new->signer = (add->signer_set == 0) ? base->signer : add->signer; new->signer_set = add->signer_set || base->signer_set; new->key = (add->key_set == 0) ? base->key : add->key; new->key_set = add->key_set || base->key_set; new->next_signer = (add->next_signer_set == 0) ? base->next_signer : add->next_signer; new->next_signer_set = add->next_signer_set || base->next_signer_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; new->crl_url = (add->crl_url_set == 0) ? base->crl_url : add->crl_url; new->crl_url_set = add->crl_url_set || base->crl_url_set; return new; } static apr_status_t ra_certificate_cleanup(void *data) { scep_config_rec *conf = data; X509_free(conf->signer); conf->signer = NULL; return APR_SUCCESS; } static const char *set_ra_certificate(cmd_parms *cmd, void *dconf, const char *arg) { scep_config_rec *conf = dconf; BIO *in; /* set_ra_certificate() will be called twice. Don't bother * going through all of the initialization on the first call * because it will just be thrown away.*/ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) { return NULL; } arg = ap_server_root_relative(cmd->pool, arg); in = BIO_new(BIO_s_file()); if (BIO_read_filename(in, arg) <= 0) { return apr_psprintf(cmd->pool, "Could not load certificate from: %s", arg); } conf->signer = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL); if (!conf->signer) { BIO_free(in); return apr_psprintf(cmd->pool, "Could not parse certificate from: %s", arg); } conf->signer_set = 1; apr_pool_cleanup_register(cmd->pool, conf, ra_certificate_cleanup, apr_pool_cleanup_null); BIO_free(in); return NULL; } static apr_status_t ra_next_certificate_cleanup(void *data) { scep_config_rec *conf = data; X509_free(conf->signer); conf->signer = NULL; return APR_SUCCESS; } static const char *set_ra_next_certificate(cmd_parms *cmd, void *dconf, const char *arg) { scep_config_rec *conf = dconf; BIO *in; /* set_ra_next_certificate() will be called twice. Don't bother * going through all of the initialization on the first call * because it will just be thrown away.*/ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) { return NULL; } arg = ap_server_root_relative(cmd->pool, arg); in = BIO_new(BIO_s_file()); if (BIO_read_filename(in, arg) <= 0) { return apr_psprintf(cmd->pool, "Could not load certificate from: %s", arg); } conf->next_signer = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL); if (!conf->next_signer) { BIO_free(in); return apr_psprintf(cmd->pool, "Could not parse certificate from: %s", arg); } conf->next_signer_set = 1; apr_pool_cleanup_register(cmd->pool, conf, ra_next_certificate_cleanup, apr_pool_cleanup_null); BIO_free(in); return NULL; } static apr_status_t ra_key_cleanup(void *data) { scep_config_rec *conf = data; EVP_PKEY_free(conf->key); conf->key = NULL; return APR_SUCCESS; } static const char *set_ra_key(cmd_parms *cmd, void *dconf, const char *arg) { scep_config_rec *conf = dconf; BIO *in; /* set_signing_key() will be called twice. Don't bother * going through all of the initialization on the first call * because it will just be thrown away.*/ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) { return NULL; } arg = ap_server_root_relative(cmd->pool, arg); in = BIO_new(BIO_s_file()); if (BIO_read_filename(in, arg) <= 0) { return apr_psprintf(cmd->pool, "Could not load key from: %s", arg); } conf->key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL); if (!conf->key) { BIO_free(in); return apr_psprintf(cmd->pool, "Could not parse key from: %s", arg); } conf->key_set = 1; apr_pool_cleanup_register(cmd->pool, conf, ra_key_cleanup, apr_pool_cleanup_null); BIO_free(in); return NULL; } static const char *set_scep_size(cmd_parms *cmd, void *dconf, const char *arg) { scep_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_location(cmd_parms *cmd, void *dconf, const char *arg) { scep_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) { scep_config_rec *conf = dconf; name_rec *name = apr_array_push(conf->subject); 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; } 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); } } else { name->nid = DN_UNLIMITED; if (!name->limit) name->limit = DN_UNLIMITED; }; conf->subject_set = 1; return NULL; } static const char *set_subject_set(cmd_parms *cmd, void *dconf, const char *arg1, const char *arg2) { scep_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) { scep_config_rec *conf = dconf; name_rec *name = apr_array_push(conf->subjectaltname); 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; } 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 = DN_UNLIMITED; /* wildcard */ if (!name->limit) name->limit = DN_UNLIMITED; } conf->subjectaltname_set = 1; return NULL; } static const char *set_subjectaltname_set(cmd_parms *cmd, void *dconf, const char *arg1, const char *arg2) { scep_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_scep_freshness(cmd_parms *cmd, void *dconf, const char *arg, const char *max) { scep_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 "ScepFreshness must specify a positive integer (or integers)"; } return NULL; } static const char *set_crl_url(cmd_parms *cmd, void *dconf, const char *arg) { scep_config_rec *conf = dconf; conf->crl_url = arg; conf->crl_url_set = 1; return NULL; } static const command_rec scep_cmds[] = { AP_INIT_TAKE1("ScepRACertificate", set_ra_certificate, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of the signing certificate."), AP_INIT_TAKE1( "ScepRAKey", set_ra_key, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of the signing key."), AP_INIT_TAKE1( "ScepRANextCertificate", set_ra_next_certificate, NULL, RSRC_CONF | ACCESS_CONF, "Set to the name of the next RA signing certificate."), AP_INIT_TAKE1("ScepSize", set_scep_size, NULL, RSRC_CONF | ACCESS_CONF, "Set to the maximum size of the SCEP request from the client."), AP_INIT_TAKE1("ScepLocation", set_location, NULL, RSRC_CONF | ACCESS_CONF, "Set to the location of the scep service."), AP_INIT_TAKE12("ScepSubjectRequest", 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("ScepSubjectSet", set_subject_set, NULL, RSRC_CONF | ACCESS_CONF, "Specify subject attribute and value that will be included in the certificate."), AP_INIT_TAKE12("ScepSubjectAltNameRequest", 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("ScepSubjectAltNameSet", set_subjectaltname_set, NULL, RSRC_CONF | ACCESS_CONF, "Specify subjectAltName attribute and value that will be included in the certificate."), AP_INIT_TAKE12("ScepFreshness", set_scep_freshness, NULL, RSRC_CONF | ACCESS_CONF, "The age of the certificates will be divided by this factor when added as a max-age, set to zero to disable. Defaults to \"2\". An optional maximum value can be specified, defaults to one day."), AP_INIT_TAKE1("ScepCRLURL", set_crl_url, NULL, RSRC_CONF | ACCESS_CONF, "If set, attempts at GetCRL will be redirected to this URL. GetCRL will be rejected with \"Bad Request\" otherwise."), { NULL } }; static apr_status_t scep_BIO_cleanup(void *data) { BIO_free((BIO *) data); return APR_SUCCESS; } static apr_status_t scep_EVP_PKEY_cleanup(void *data) { EVP_PKEY_free((EVP_PKEY *) data); return APR_SUCCESS; } static apr_status_t scep_X509_cleanup(void *data) { X509_free((X509 *) data); return APR_SUCCESS; } static apr_status_t scep_X509_REQ_cleanup(void *data) { X509_REQ_free((X509_REQ *) data); return APR_SUCCESS; } static apr_status_t scep_X509_STORE_cleanup(void *data) { X509_STORE_free((X509_STORE *) data); return APR_SUCCESS; } static apr_status_t scep_X509_STORE_CTX_cleanup(void *data) { X509_STORE_CTX_free((X509_STORE_CTX *) data); return APR_SUCCESS; } static apr_status_t scep_PKCS7_cleanup(void *data) { PKCS7_free((PKCS7 *) data); return APR_SUCCESS; } static apr_status_t scep_PKCS7_ISSUER_AND_SUBJECT_cleanup(void *data) { PKCS7_ISSUER_AND_SUBJECT_free((PKCS7_ISSUER_AND_SUBJECT *) data); return APR_SUCCESS; } static apr_status_t scep_PKCS7_ISSUER_AND_SERIAL_cleanup(void *data) { PKCS7_ISSUER_AND_SERIAL_free((PKCS7_ISSUER_AND_SERIAL *) data); return APR_SUCCESS; } static void strip_whitespace(char *s1) { char *s2 = s1; while (*s1) { switch (*s1) { case '\n': case '\r': case ' ': { break; } default: { if (s1 != s2) { *(s2++) = *s1; } else { s2++; } break; } } s1++; } } static void log_request(request_rec *r, X509_REQ *req, const char * msg) { BIO * debug = BIO_new(BIO_s_mem()); char buf[HUGE_STRING_LEN]; int len; apr_pool_cleanup_register(r->pool, debug, scep_BIO_cleanup, apr_pool_cleanup_null); X509_REQ_print_ex(debug, req, 0, XN_FLAG_ONELINE); while ((len = BIO_gets(debug, buf, sizeof(buf))) > 0) { /* Remove any LF/CR - as the logging subsystem will do this */ while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) len --; ap_log_rerror( APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "%s: %.*s", msg, len, buf); } } static void make_sender_nonce(request_rec *r, scep_t *rscep) { rscep->senderNonceLength = 16; rscep->senderNonce = apr_palloc(r->pool, rscep->senderNonceLength); apr_generate_random_bytes(rscep->senderNonce, rscep->senderNonceLength); } 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 ca_asn1_t *make_ASN1_INTEGER(apr_pool_t *pool, ASN1_INTEGER *integer) { ca_asn1_t *buf = apr_palloc(pool, sizeof(ca_asn1_t)); unsigned char *tmp; buf->len = i2d_ASN1_INTEGER(integer, NULL); buf->val = tmp = apr_palloc(pool, buf->len); i2d_ASN1_INTEGER(integer, &tmp); return buf; } static ca_asn1_t *make_ASN1_PRINTABLESTRING(apr_pool_t *pool, ASN1_STRING *string) { ca_asn1_t *buf = apr_palloc(pool, sizeof(ca_asn1_t)); unsigned char *tmp; buf->len = i2d_ASN1_PRINTABLE(string, NULL); buf->val = tmp = apr_palloc(pool, buf->len); i2d_ASN1_PRINTABLESTRING(string, &tmp); return buf; } static STACK_OF(X509_ATTRIBUTE) *make_scep_attributes(request_rec *r, scep_t *scep) { STACK_OF(X509_ATTRIBUTE) *sattrs = sk_X509_ATTRIBUTE_new_null(); ASN1_STRING *asn1_string = NULL; X509_ATTRIBUTE *xa; if (scep->transactionId) { asn1_string = ASN1_STRING_new(); ASN1_STRING_set(asn1_string, scep->transactionId, strlen(scep->transactionId)); xa = X509_ATTRIBUTE_create(OBJ_sn2nid("transactionID"), V_ASN1_PRINTABLESTRING, asn1_string); sk_X509_ATTRIBUTE_push(sattrs, xa); } if (scep->messageType) { const char *buffer = apr_psprintf(r->pool, "%d", scep->messageType); asn1_string = ASN1_STRING_new(); ASN1_STRING_set(asn1_string, buffer, strlen(buffer)); xa = X509_ATTRIBUTE_create(OBJ_sn2nid("messageType"), V_ASN1_PRINTABLESTRING, asn1_string); sk_X509_ATTRIBUTE_push(sattrs, xa); } if (scep->pkiStatus >= 0) { const char *buffer = apr_psprintf(r->pool, "%d", scep->pkiStatus); asn1_string = ASN1_STRING_new(); ASN1_STRING_set(asn1_string, buffer, strlen(buffer)); xa = X509_ATTRIBUTE_create(OBJ_sn2nid("pkiStatus"), V_ASN1_PRINTABLESTRING, asn1_string); sk_X509_ATTRIBUTE_push(sattrs, xa); } if (scep->failInfo >= 0) { const char *buffer = apr_psprintf(r->pool, "%d", scep->failInfo); asn1_string = ASN1_STRING_new(); ASN1_STRING_set(asn1_string, buffer, strlen(buffer)); xa = X509_ATTRIBUTE_create(OBJ_sn2nid("failInfo"), V_ASN1_PRINTABLESTRING, asn1_string); sk_X509_ATTRIBUTE_push(sattrs, xa); } if (scep->senderNonce) { asn1_string = ASN1_STRING_new(); ASN1_STRING_set(asn1_string, scep->senderNonce, scep->senderNonceLength); xa = X509_ATTRIBUTE_create(OBJ_sn2nid("senderNonce"), V_ASN1_OCTET_STRING, asn1_string); sk_X509_ATTRIBUTE_push(sattrs, xa); } if (scep->recipientNonce) { asn1_string = ASN1_STRING_new(); ASN1_STRING_set(asn1_string, scep->recipientNonce, scep->recipientNonceLength); xa = X509_ATTRIBUTE_create(OBJ_sn2nid("recipientNonce"), V_ASN1_OCTET_STRING, asn1_string); sk_X509_ATTRIBUTE_push(sattrs, xa); } return sattrs; } static scep_t *parse_scep_attributes(request_rec *r, STACK_OF(X509_ATTRIBUTE) *sattrs) { ASN1_TYPE *asn1_type; X509_ATTRIBUTE *attr; int i; scep_t *scep = apr_pcalloc(r->pool, sizeof(scep_t)); ASN1_OBJECT *transactionId = OBJ_nid2obj(OBJ_sn2nid("transactionID")); ASN1_OBJECT *messageType = OBJ_nid2obj(OBJ_sn2nid("messageType")); ASN1_OBJECT *pkiStatus = OBJ_nid2obj(OBJ_sn2nid("pkiStatus")); ASN1_OBJECT *failInfo = OBJ_nid2obj(OBJ_sn2nid("failInfo")); ASN1_OBJECT *senderNonce = OBJ_nid2obj(OBJ_sn2nid("senderNonce")); ASN1_OBJECT *recipientNonce = OBJ_nid2obj(OBJ_sn2nid("recipientNonce")); ASN1_OBJECT *proxyAuthenticator = OBJ_nid2obj( OBJ_sn2nid("proxyAuthenticator")); /* scan all attributes for the one we are looking for */ for (i = 0; i < sk_X509_ATTRIBUTE_num(sattrs); i++) { ASN1_OBJECT *attr_obj; attr = sk_X509_ATTRIBUTE_value(sattrs, i); /* duplicate the signature algorithm */ #if OPENSSL_VERSION_NUMBER >= 0x010100000L attr_obj = X509_ATTRIBUTE_get0_object(attr); asn1_type = X509_ATTRIBUTE_get0_type(attr, 0); #else asn1_type = sk_ASN1_TYPE_value(attr->value.set, 0); attr_obj = attr->object; #endif if (!OBJ_cmp(attr_obj, transactionId)) { switch (ASN1_TYPE_get(asn1_type)) { case V_ASN1_PRINTABLESTRING: { scep->transactionId = apr_pstrndup(r->pool, #if OPENSSL_VERSION_NUMBER > 0x1010001fL (const char *) ASN1_STRING_get0_data( #else (const char *) ASN1_STRING_data( #endif asn1_type->value.asn1_string), ASN1_STRING_length(asn1_type->value.asn1_string)); break; } } } else if (!OBJ_cmp(attr_obj, messageType)) { switch (ASN1_TYPE_get(asn1_type)) { case V_ASN1_PRINTABLESTRING: { scep->messageType = atoi( apr_pstrndup(r->pool, #if OPENSSL_VERSION_NUMBER > 0x1010001fL (const char *) ASN1_STRING_get0_data( #else (const char *) ASN1_STRING_data( #endif asn1_type->value.asn1_string), ASN1_STRING_length( asn1_type->value.asn1_string))); break; } } } else if (!OBJ_cmp(attr_obj, pkiStatus)) { switch (ASN1_TYPE_get(asn1_type)) { case V_ASN1_PRINTABLESTRING: { scep->pkiStatus = atoi( apr_pstrndup(r->pool, #if OPENSSL_VERSION_NUMBER > 0x1010001fL (const char *) ASN1_STRING_get0_data( #else (const char *) ASN1_STRING_data( #endif asn1_type->value.asn1_string), ASN1_STRING_length( asn1_type->value.asn1_string))); break; } } } else if (!OBJ_cmp(attr_obj, failInfo)) { switch (ASN1_TYPE_get(asn1_type)) { case V_ASN1_PRINTABLESTRING: { scep->failInfo = atoi( apr_pstrndup(r->pool, #if OPENSSL_VERSION_NUMBER > 0x1010001fL (const char *) ASN1_STRING_get0_data( #else (const char *) ASN1_STRING_data( #endif asn1_type->value.asn1_string), ASN1_STRING_length( asn1_type->value.asn1_string))); break; } } } else if (!OBJ_cmp(attr_obj, senderNonce)) { switch (ASN1_TYPE_get(asn1_type)) { case V_ASN1_OCTET_STRING: { scep->senderNonceLength = ASN1_STRING_length( asn1_type->value.octet_string); scep->senderNonce = apr_pmemdup(r->pool, #if OPENSSL_VERSION_NUMBER > 0x1010001fL ASN1_STRING_get0_data(asn1_type->value.octet_string), #else ASN1_STRING_data(asn1_type->value.octet_string), #endif scep->senderNonceLength); break; } } } else if (!OBJ_cmp(attr_obj, recipientNonce)) { switch (ASN1_TYPE_get(asn1_type)) { case V_ASN1_OCTET_STRING: { scep->recipientNonceLength = ASN1_STRING_length( asn1_type->value.octet_string); scep->recipientNonce = apr_pmemdup(r->pool, #if OPENSSL_VERSION_NUMBER > 0x1010001fL ASN1_STRING_get0_data(asn1_type->value.octet_string), #else ASN1_STRING_data(asn1_type->value.octet_string), #endif scep->recipientNonceLength); break; } } } else if (!OBJ_cmp(attr_obj, proxyAuthenticator)) { switch (ASN1_TYPE_get(asn1_type)) { case V_ASN1_OCTET_STRING: { scep->proxyAuthenticator = asn1_type->value.octet_string; break; } } } } return scep; } 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 scep 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 apr_status_t sanity_check(request_rec *r, X509 *next_cert, X509* signer, const char * msg) { X509_STORE_CTX *ctx; X509_STORE *store; ctx = X509_STORE_CTX_new(); if (!ctx) { log_message(r, APR_SUCCESS, "could not create a X509_STORE_CTX"); return !APR_SUCCESS; } apr_pool_cleanup_register(r->pool, ctx, scep_X509_STORE_CTX_cleanup, apr_pool_cleanup_null); store = X509_STORE_new(); if (!store) { log_message(r, APR_SUCCESS, "could not create a X509_STORE"); return !APR_SUCCESS; } apr_pool_cleanup_register(r->pool, store, scep_X509_STORE_cleanup, apr_pool_cleanup_null); if (!X509_STORE_add_cert(store, next_cert)) { log_message(r, APR_SUCCESS, "could not add the next CA certificate to the X509_STORE"); return !APR_SUCCESS; } if (!X509_STORE_CTX_init(ctx, store, signer, NULL)) { log_message(r, APR_SUCCESS, "could not initialise the X509_STORE_CTX"); return !APR_SUCCESS; } if (!X509_verify_cert(ctx)) { int err = X509_STORE_CTX_get_error(ctx); int depth = X509_STORE_CTX_get_error_depth(ctx); log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "could not verify the %s RA certificate against the %s CA certificate at depth %d in the chain: %s", msg, msg, depth, X509_verify_cert_error_string(err))); return !APR_SUCCESS; } return APR_SUCCESS; } /** * 5.2.1. GetCACert * * The OPERATION MUST be set to "GetCACert". * * The MESSAGE MAY be omitted, or it MAY be a string that represents the * certification authority issuer identifier. A CA Administrator * defined string allows for multiple CAs supported by one SCEP server. * * 5.2.1.1. GetCACert Response * * The response for GetCACert is different between the case where the CA * directly communicates with the requester during the enrollment, and * the case where a RA exists and the requester communicates with the RA * during the enrollment. * * 5.2.1.1.1. CA Certificate Only Response * * The response will have a Content-Type of "application/ * x-x509-ca-cert". * * The body of this response consists of an X.509 CA certificate, as * defined in Section 4.1.1.1. * "Content-Type:application/x-x509-ca-cert\n\n" * * 5.2.1.1.2. CA and RA Certificates Response * * The response will have a Content-Type of "application/ * x-x509-ca-ra-cert". * * The body of this response consists of a degenerate certificates-only * PKCS#7 Signed-data (Section 3.3) containing both CA and RA * certificates, as defined in Section 4.1.1.2. * "Content-Type:application/x-x509-ca-ra-cert\n\n" * */ static int get_ca_cert(request_rec *r, scep_config_rec *conf, const char *message) { char buf[HUGE_STRING_LEN]; int rv; apr_size_t len; apr_off_t offset; PKCS7 *p7 = NULL; PKCS7_SIGNED *p7s=NULL; BIO *b; X509 *cert = NULL; const unsigned char *der, *tmp; apr_bucket_brigade *bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_bucket *e; apr_status_t status; apr_sha1_ctx_t sha1; apr_byte_t digest[APR_SHA1_DIGESTSIZE]; char *etag; apr_time_t validity; /* get the CA certificate */ rv = ap_run_ca_getca(r, &der, &len, &validity); if (rv == DECLINED) { log_message(r, APR_SUCCESS, "No module configured to get the CA certificate"); return HTTP_INTERNAL_SERVER_ERROR; } if (rv != OK) { return rv; } tmp = der; if (!d2i_X509(&cert, &tmp, len)) { log_message(r, APR_SUCCESS, "could not DER decode the CA certificate"); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, cert, scep_X509_cleanup, apr_pool_cleanup_null); if (sanity_check(r,cert, conf->signer,"") != APR_SUCCESS) return HTTP_INTERNAL_SERVER_ERROR; ap_set_content_type(r, "application/x-x509-ca-ra-cert"); /* RFC 8894, 3.4: For SCEP, the content field of the ContentInfo value of * a degenerate certificates-only SignedData MUST be omitted. */ p7 = PKCS7_new(); if (!p7) { log_message(r, APR_SUCCESS, "could not create a PKCS7 degenerate response"); return HTTP_INTERNAL_SERVER_ERROR; } else { apr_pool_cleanup_register(r->pool, p7, scep_PKCS7_cleanup, apr_pool_cleanup_null); }; p7s = PKCS7_SIGNED_new(); if (!p7s) { log_message(r, APR_SUCCESS, "could not create a PKCS7 signed degenerate response"); return HTTP_INTERNAL_SERVER_ERROR; } PKCS7_set_type(p7, NID_pkcs7_signed); p7->d.sign = p7s; if (!(p7s->cert = sk_X509_new_null()) || !(p7s->crl = sk_X509_CRL_new_null())) { log_message(r, APR_SUCCESS, "could not create a PKCS7 signed crt/crl stacks"); return HTTP_INTERNAL_SERVER_ERROR; } ASN1_INTEGER_set(p7s->version,1); p7s->contents->type=OBJ_nid2obj(NID_pkcs7_data); sk_X509_push(p7s->cert, cert); sk_X509_push(p7s->cert, conf->signer); // cleanup of p7->p7s affect ->signer; see equivalent // in PKCS7_add_certificate() X509_up_ref(conf->signer); b = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, b, scep_BIO_cleanup, apr_pool_cleanup_null); i2d_PKCS7_bio(b, p7); apr_sha1_init(&sha1); len = 0; while ((offset = BIO_read(b, buf, sizeof(buf))) > 0) { apr_sha1_update(&sha1, buf, offset); apr_brigade_write(bb, NULL, NULL, buf, offset); len += offset; } if (len == 0) { log_message(r, APR_SUCCESS, "Failed to create PKCS7 response"); return HTTP_INTERNAL_SERVER_ERROR; } apr_sha1_final(digest, &sha1); etag = apr_palloc(r->pool, 31); apr_base64_encode_binary(etag + 1, digest, sizeof(digest)); etag[0] = '\"'; etag[29] = '\"'; etag[30] = 0; apr_table_setn(r->headers_out, "ETag", etag); /* handle freshness lifetime for caching */ if (!apr_table_get(r->headers_out, "Cache-Control")) { apr_off_t delta = apr_time_sec(validity - apr_time_now()); delta = delta > 0 ? conf->freshness ? delta / conf->freshness : 0 : 0; delta = delta < conf->freshness_max ? delta : conf->freshness_max; apr_table_setn(r->headers_out, "Cache-Control", apr_psprintf(r->pool, "max-age=%" APR_OFF_T_FMT, delta)); } if ((rv = ap_meets_conditions(r)) != OK) { r->status = rv; apr_brigade_cleanup(bb); } else { apr_brigade_length(bb, 1, &offset); len = offset; 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, "scep_handler: ap_pass_brigade returned %i", status); return HTTP_INTERNAL_SERVER_ERROR; } /* ready to leave */ return OK; } /** * 5.2.6. GetNextCACert * * The OPERATION MUST be set to "GetNextCACert". * * The MESSAGE MAY be ommitted, or it MAY be a string that represents * the certification authority issuer identifier, if such has been set * by the CA Administrator. * * 5.2.6.1. GetNextCACert Response * * The response will have a Content-Type of "application/ * x-x509-next-ca-cert". * * The body of this response consists of a SignedData PKCS#7 [RFC2315], * as defined in Section 4.6.1. (This is similar to the GetCert * response but does not include any of the attributes defined in * Section 3.1.1.) * "Content-Type:application/x-x509-next-ca-cert\n\n" * */ static int get_next_ca_cert(request_rec *r, scep_config_rec *conf, const char *message) { char buf[HUGE_STRING_LEN]; int rv; apr_size_t len; apr_off_t offset; PKCS7 *p7 = NULL; PKCS7_SIGNER_INFO *si; BIO *b; X509 *next_cert = NULL; const unsigned char *der, *tmp; apr_bucket_brigade *bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_bucket *e; apr_status_t status; apr_sha1_ctx_t sha1; apr_byte_t digest[APR_SHA1_DIGESTSIZE]; char *etag; apr_time_t validity; /* get the current CA certificate validity */ rv = ap_run_ca_getca(r, &der, &len, &validity); if (rv == DECLINED) { log_message(r, APR_SUCCESS, "No CA certificate is currently available"); return HTTP_NOT_FOUND; } if (rv != OK) { return rv; } /* get the next CA certificate */ rv = ap_run_ca_getnextca(r, &der, &len, NULL); if (rv == DECLINED) { log_message(r, APR_SUCCESS, "No next CA certificate is currently available"); return HTTP_NOT_FOUND; } if (rv != OK) { return rv; } ap_set_content_type(r, "application/x-x509-next-ca-cert"); /* create a new signed data PKCS#7 */ p7 = PKCS7_new(); if (!p7) { log_message(r, APR_SUCCESS, "could not create a PKCS7 degenerate response"); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, p7, scep_PKCS7_cleanup, apr_pool_cleanup_null); PKCS7_set_type(p7, NID_pkcs7_signed); PKCS7_content_new(p7, NID_pkcs7_data); tmp = der; if (!d2i_X509(&next_cert, &tmp, len)) { log_message(r, APR_SUCCESS, "could not DER decode the next CA certificate"); return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(r->pool, next_cert, scep_X509_cleanup, apr_pool_cleanup_null); if (!PKCS7_add_certificate(p7, next_cert)) { log_message(r, APR_SUCCESS, "could not add the next CA certificate to the degenerate PKCS7 response"); return HTTP_INTERNAL_SERVER_ERROR; } if (!PKCS7_add_certificate(p7, conf->next_signer)) { log_message(r, APR_SUCCESS, "could not add the next RA certificate to the degenerate PKCS7 response"); return HTTP_INTERNAL_SERVER_ERROR; } si = PKCS7_add_signature(p7, conf->signer, conf->key, EVP_sha256()); if (!si) { log_message(r, APR_SUCCESS, "could not add the signature to the signed PKCS7 response"); return HTTP_BAD_REQUEST; } if (sanity_check(r, next_cert, conf->next_signer,"next") != APR_SUCCESS) return HTTP_INTERNAL_SERVER_ERROR; b = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, b, scep_BIO_cleanup, apr_pool_cleanup_null); i2d_PKCS7_bio(b, p7); apr_sha1_init(&sha1); while ((offset = BIO_read(b, buf, sizeof(buf))) > 0) { apr_sha1_update(&sha1, buf, offset); apr_brigade_write(bb, NULL, NULL, buf, offset); } apr_sha1_final(digest, &sha1); etag = apr_palloc(r->pool, 31); apr_base64_encode_binary(etag + 1, digest, sizeof(digest)); etag[0] = '\"'; etag[29] = '\"'; etag[30] = 0; apr_table_setn(r->headers_out, "ETag", etag); /* handle freshness lifetime for caching based on the expiry date of the * current CA certificate. */ if (!apr_table_get(r->headers_out, "Cache-Control")) { apr_off_t delta = apr_time_sec(validity - apr_time_now()); delta = delta > 0 ? conf->freshness ? delta / conf->freshness : 0 : 0; delta = delta < conf->freshness_max ? delta : conf->freshness_max; apr_table_setn(r->headers_out, "Cache-Control", apr_psprintf(r->pool, "max-age=%" APR_OFF_T_FMT, delta)); } if ((rv = ap_meets_conditions(r)) != OK) { r->status = rv; apr_brigade_cleanup(bb); } else { apr_brigade_length(bb, 1, &offset); len = offset; 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, "scep_handler: ap_pass_brigade returned %i", status); return HTTP_INTERNAL_SERVER_ERROR; } /* ready to leave */ return OK; } /** * 3.5.2. CA Capabilities Response Format * * * +--------------------+----------------------------------------------+ * | Keyword | Description | * +--------------------+----------------------------------------------+ * | "AES" | CA supports the AES128-CBC encryption | * | | algorithm. | * | | | * | "DES3" | CA supports the triple DES-CBC encryption | * | | algorithm. | * | | | * | "GetNextCACert" | CA supports the GetNextCACert | * | | message. | * | | | * | "POSTPKIOperation" | CA supports PKIOPeration messages sent | * | | via HTTP POST. | * | | | * | "Renewal" | CA supports the Renewal CA operation. | * | | | * | "SHA-1" | CA supports the SHA-1 hashing algorithm. | * | | | * | "SHA-256" | CA supports the SHA-256 hashing algorithm. | * | | | * | "SHA-512" | CA supports the SHA-512 hashing algorithm. | * | | | * | "SCEPStandard" | CA supports all mandatory-to-implement | * | | sections of the SCEP standard. This keyword | * | | implies "AES", | * | | "POSTPKIOperation", and "SHA-256", as well | * | | as the provisions of | * | | Section 2.9. | * +--------------------+----------------------------------------------+ * * */ static int get_ca_caps(request_rec *r, scep_config_rec *conf, const char *message) { ap_set_content_type(r, "text/plain"); ap_rputs(apr_psprintf(r->pool, "AES\n" "%s" "POSTPKIOperation\n" #if 0 "Renewal\n" #endif "SHA-1\n" "SHA-256\n" "SHA-512\n" "SCEPStandard\n", conf->next_signer ? "GetNextCACert\n" : ""), r); return OK; } static int scep_send_signed_response(request_rec *r, BIO *inbio, scep_t *rscep) { unsigned char buf[HUGE_STRING_LEN]; unsigned char *data; int len; /* len must be signed */ apr_off_t offset; PKCS7 *p7 = NULL; PKCS7_SIGNER_INFO *si; STACK_OF(X509_ATTRIBUTE) *sattrs; BIO *p7bio, *b; apr_bucket_brigade *bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_bucket *e; apr_status_t status; scep_config_rec *conf = ap_get_module_config(r->per_dir_config, &scep_module); /* create a new signed data PKCS#7 */ p7 = PKCS7_new(); if (!p7) { log_message(r, APR_SUCCESS, "could not create a PKCS7 signed response"); return HTTP_BAD_REQUEST; } else { apr_pool_cleanup_register(r->pool, p7, scep_PKCS7_cleanup, apr_pool_cleanup_null); } PKCS7_set_type(p7, NID_pkcs7_signed); PKCS7_content_new(p7, NID_pkcs7_data); si = PKCS7_add_signature(p7, conf->signer, conf->key, EVP_sha256()); if (!si) { log_message(r, APR_SUCCESS, "could not add the signature to the signed PKCS7 response"); return HTTP_BAD_REQUEST; } p7bio = PKCS7_dataInit(p7, NULL); if (!p7bio) { log_message(r, APR_SUCCESS, "could not PKCS7_dataInit in the signed PKCS7 response"); return HTTP_BAD_REQUEST; } else { apr_pool_cleanup_register(r->pool, p7bio, scep_BIO_cleanup, apr_pool_cleanup_null); } /* write the inbio into the signed envelope, if present */ if (inbio) { BIO_set_flags(inbio, BIO_FLAGS_MEM_RDONLY); len = BIO_get_mem_data(inbio, &data); if (len > 0) { if (len != BIO_write(p7bio, data, len)) { log_message(r, APR_SUCCESS, "could not write BIO into the signed PKCS7 response"); return HTTP_INTERNAL_SERVER_ERROR; } } } /* handle the return attributes */ sattrs = make_scep_attributes(r, rscep); PKCS7_set_signed_attributes(si, sattrs); /* add content type to signed attributes */ PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, OBJ_nid2obj(NID_pkcs7_data)); if (!PKCS7_dataFinal(p7, p7bio)) { log_message(r, APR_SUCCESS, "could not PKCS7_dataFinal in the signed PKCS7 response"); return HTTP_BAD_REQUEST; } /** * We're done, write it away. */ ap_set_content_type(r, "application/x-pki-message"); b = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, b, scep_BIO_cleanup, apr_pool_cleanup_null); i2d_PKCS7_bio(b, p7); if (!BIO_flush(b)) { log_message(r, APR_SUCCESS, "could not BIO_flush the signed PKCS7 response"); return HTTP_BAD_REQUEST; } while ((offset = BIO_read(b, buf, sizeof(buf))) > 0) { apr_brigade_write(bb, NULL, NULL, (const char *) buf, offset); } apr_brigade_length(bb, 1, &offset); len = offset; 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, "scep_handler: ap_pass_brigade returned %i", status); return HTTP_INTERNAL_SERVER_ERROR; } /* ready to leave */ return OK; } static int scep_send_encrypted_response(request_rec *r, BIO *inbio, scep_t *rscep) { PKCS7 *p7e = NULL; STACK_OF(X509) *certs; BIO *ebio = NULL; /* If there is no messageData to be transmitted, the entire * pkcsPKIEnvelope MUST be omitted. */ if (!inbio) { return scep_send_signed_response(r, NULL, rscep); } /* perform encryption */ if (!(certs = sk_X509_new(NULL))) { log_message(r, APR_SUCCESS, "could not create a certificate stack"); return HTTP_INTERNAL_SERVER_ERROR; } if (sk_X509_push(certs, rscep->encrypt_cert) <= 0) { log_message(r, APR_SUCCESS, "could not add a certificate to the stack"); return HTTP_INTERNAL_SERVER_ERROR; } p7e = PKCS7_encrypt(certs, inbio, EVP_aes_128_cbc(), PKCS7_BINARY); if (!p7e) { log_message(r, APR_SUCCESS, "could not encrypt PKCS7 payload"); return HTTP_BAD_REQUEST; } /* wrap up the encrypted payload */ ebio = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, ebio, scep_BIO_cleanup, apr_pool_cleanup_null); if (i2d_PKCS7_bio(ebio, p7e) <= 0) { log_message(r, APR_SUCCESS, "could not encode PKCS7 payload"); return HTTP_INTERNAL_SERVER_ERROR; } if (!BIO_flush(ebio)) { log_message(r, APR_SUCCESS, "could not flush PKCS7 payload"); return HTTP_INTERNAL_SERVER_ERROR; } return scep_send_signed_response(r, ebio, rscep); } static int scep_transform_subject(request_rec *r, X509_NAME *subject, X509_NAME *reqsubject) { int i, j; scep_config_rec *conf = ap_get_module_config(r->per_dir_config, &scep_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 < X509_NAME_entry_count(reqsubject); j++) { X509_NAME_ENTRY *tne = X509_NAME_get_entry(reqsubject, j); int nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(tne)); if (name->nid == DN_UNLIMITED || name->nid == nid) { if (name->limit != DN_UNLIMITED && 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; } if (!X509_NAME_add_entry(subject, tne, -1, 0)) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "Subject name '%s' could not be inserted into certificate.", name->name)); return HTTP_INTERNAL_SERVER_ERROR; } count--; } } } } return OK; } static int scep_transform_subjectaltname(request_rec *r, X509_REQ *req, X509_REQ *creq) { int i, j; STACK_OF(X509_EXTENSION) *exts; GENERAL_NAMES *gens = NULL, *sans = NULL; scep_config_rec *conf = ap_get_module_config(r->per_dir_config, &scep_module); exts = X509_REQ_get_extensions(req); // test XXX X509_REQ_add_extensions(creq, exts); if (exts) { int idx = -1; gens = X509V3_get_d2i(exts, NID_subject_alt_name, NULL, &idx); } 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; gens && j < sk_GENERAL_NAME_num(gens); j++) { GENERAL_NAME *gen = sk_GENERAL_NAME_value(gens, j); if (name->nid == DN_UNLIMITED || name->nid == gen->type) { if (name->limit != DN_UNLIMITED && count <= 0) { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "SubjectAltName element '%s' cannot be inserted into certificate more than %d times.", name->name, name->limit)); return HTTP_BAD_REQUEST; } if (!sans) { sans = GENERAL_NAMES_new(); } sk_GENERAL_NAME_push(sans, gen); count--; } } } } /* if we have a subjectAltName, add it to the request */ if (sans) { STACK_OF(X509_EXTENSION) *cexts = NULL; int critical = !X509_NAME_entry_count(X509_REQ_get_subject_name(creq)); X509_EXTENSION *san = X509V3_EXT_i2d(NID_subject_alt_name, critical, sans); X509v3_add_ext(&cexts, san, -1); X509_REQ_add_extensions(creq, cexts); } return OK; } /** * MessageType: PKCSReq */ static int scep_messagetype_pkcsreq(request_rec *r, X509_REQ *req, scep_t *scep) { char buf[HUGE_STRING_LEN]; X509_REQ *creq = NULL; EVP_PKEY *pktmp = NULL; X509_NAME *subject = NULL, *reqsubject = NULL; PKCS7 *certs = NULL; BIO *inbio, *debug; scep_t *rscep; int rv, idx; apr_size_t len; unsigned char *tmp; const unsigned char *buffer; apr_hash_t *params = apr_hash_make(r->pool); /* print the request, if necessary */ if (APLOGrdebug(r)) log_request(r, req, "Certificate Request"); /** * Create a CSR for signing. */ creq = X509_REQ_new(); if (!creq) { log_message(r, APR_SUCCESS, "X509_REQ_new failed"); return HTTP_INTERNAL_SERVER_ERROR; } X509_REQ_set_version(creq, 0L); apr_pool_cleanup_register(r->pool, creq, scep_X509_REQ_cleanup, apr_pool_cleanup_null); /* get the subjects */ subject = X509_REQ_get_subject_name(creq); reqsubject = X509_REQ_get_subject_name(req); /* transform the subjects as per the configuration */ rv = scep_transform_subject(r, subject, reqsubject); if (rv != OK) { return rv; } /* transform the subjectAltNames as per the configuration */ rv = scep_transform_subjectaltname(r, req, creq); if (rv != OK) { return rv; } /* print the subject, if necessary */ if (APLOGrdebug(r)) { int len; debug = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, debug, scep_BIO_cleanup, apr_pool_cleanup_null); X509_NAME_print_ex(debug, reqsubject, 0, XN_FLAG_ONELINE); while ((len = BIO_gets(debug, buf, sizeof(buf))) > 0) { ap_log_rerror( APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, "Requested Certificate Subject: %.*s", len, buf); } 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); } } /* 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, scep_EVP_PKEY_cleanup, apr_pool_cleanup_null); if (0 >= X509_REQ_verify(req, pktmp)) { log_message(r, APR_SUCCESS, "certificate request signature could not be verified"); return HTTP_BAD_REQUEST; } X509_REQ_set_pubkey(creq, pktmp); /* sign the X509_REQ with a dummy signature to work around serialisation bugs in openssl */ X509_REQ_sign(creq, pknull, mdnull); /* handle the challenge */ 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)); } /* 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)); } /* handle the transaction ID */ if (scep->transactionId) { ASN1_STRING *asn1_string = ASN1_STRING_new(); ASN1_STRING_set(asn1_string, scep->transactionId, strlen(scep->transactionId)); apr_hash_set(params, "transactionID", APR_HASH_KEY_STRING, make_ASN1_PRINTABLESTRING(r->pool, asn1_string)); X509_REQ_add1_attr(creq, X509_ATTRIBUTE_create(OBJ_sn2nid("transactionID"), V_ASN1_PRINTABLESTRING, asn1_string)); } else { log_message(r, APR_SUCCESS, "no transactionID included in request"); return HTTP_BAD_REQUEST; } /* 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 to be signed"); return HTTP_INTERNAL_SERVER_ERROR; } buffer = tmp = apr_palloc(r->pool, len); if (!i2d_X509_REQ(creq, &tmp)) { log_message(r, APR_SUCCESS, "could not DER encode the certificate request to be signed"); return HTTP_INTERNAL_SERVER_ERROR; } /* do the authz */ rv = ap_run_ca_reqauthz(r, params, buffer, len); if (rv > OK) { return rv; } if (APLOGrdebug(r)) log_request(r, creq, "Request to Sign"); /* do the signing */ rv = ap_run_ca_sign(r, params, &buffer, &len); if (rv == DECLINED) { log_message(r, APR_SUCCESS, "No module configured to sign the certificate (ca_sign)"); return HTTP_INTERNAL_SERVER_ERROR; } if (rv != OK && rv != DONE) { return rv; } else if (rv == OK) { /* do the store */ rv = ap_run_ca_certstore(r, params, buffer, len); if (rv > OK) { return rv; } /* read in the certificate */ if (!d2i_PKCS7(&certs, &buffer, len)) { log_message(r, APR_SUCCESS, "could not DER decode the signed certificate (certstore)"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, certs, scep_PKCS7_cleanup, apr_pool_cleanup_null); /* we have the cert, send the appropriate reply */ rscep = apr_pcalloc(r->pool, sizeof(scep_t)); rscep->messageType = SCEP_MESSAGETYPE_CERTREP; rscep->transactionId = scep->transactionId; make_sender_nonce(r, rscep); rscep->recipientNonce = scep->senderNonce; rscep->recipientNonceLength = scep->senderNonceLength; rscep->pkiStatus = SCEP_PKISTATUS_SUCCESS; rscep->failInfo = -1; rscep->encrypt_cert = scep->encrypt_cert; /* cert to encrypt with */ rscep->certs = certs; /* payload to send back */ } else if (rv == DONE) { rscep = apr_pcalloc(r->pool, sizeof(scep_t)); rscep->messageType = SCEP_MESSAGETYPE_CERTREP; rscep->transactionId = scep->transactionId; make_sender_nonce(r, rscep); rscep->recipientNonce = scep->senderNonce; rscep->recipientNonceLength = scep->senderNonceLength; rscep->pkiStatus = SCEP_PKISTATUS_PENDING; rscep->encrypt_cert = NULL; rscep->certs = NULL; } else { rscep = apr_pcalloc(r->pool, sizeof(scep_t)); rscep->messageType = SCEP_MESSAGETYPE_CERTREP; rscep->transactionId = scep->transactionId; make_sender_nonce(r, rscep); rscep->recipientNonce = scep->senderNonce; rscep->recipientNonceLength = scep->senderNonceLength; rscep->pkiStatus = SCEP_PKISTATUS_FAILURE; rscep->encrypt_cert = NULL; rscep->certs = NULL; rscep->failInfo = SCEP_FAILINFO_BADREQUEST; } /** * If we've got this far, return a successful response. */ if (rscep->pkiStatus == SCEP_PKISTATUS_SUCCESS && rscep->certs) { /* now, create an envelope to put the signed data in */ inbio = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, inbio, scep_BIO_cleanup, apr_pool_cleanup_null); i2d_PKCS7_bio(inbio, certs); if (!BIO_flush(inbio)) { log_message(r, APR_SUCCESS, "could not flush the BIO for the PKCS7 response"); return HTTP_INTERNAL_SERVER_ERROR; } return scep_send_encrypted_response(r, inbio, rscep); } return scep_send_signed_response(r, NULL, rscep); } /** * MessageType: GetCertInitial */ static int scep_messagetype_getcertinitial(request_rec *r, PKCS7_ISSUER_AND_SUBJECT *ias, scep_t *scep) { apr_hash_t *search = apr_hash_make(r->pool); int rv; const unsigned char *buffer; apr_size_t len; PKCS7 *certs = NULL; BIO *inbio; scep_t *rscep; /* handle the issuer */ if (ias->issuer) { apr_hash_set(search, "issuer", APR_HASH_KEY_STRING, make_X509_NAME(r->pool, ias->issuer)); } /* transform the subjects as per the configuration */ if (ias->subject) { X509_NAME *subject; subject = X509_NAME_new(); rv = scep_transform_subject(r, ias->subject, subject); if (rv != OK) { return rv; } apr_hash_set(search, "subject", APR_HASH_KEY_STRING, make_X509_NAME(r->pool, subject)); } /* handle the transaction ID */ if (scep->transactionId) { ASN1_STRING *asn1_string = ASN1_STRING_new(); ASN1_STRING_set(asn1_string, scep->transactionId, strlen(scep->transactionId)); apr_hash_set(search, "transactionID", APR_HASH_KEY_STRING, make_ASN1_PRINTABLESTRING(r->pool, asn1_string)); ASN1_STRING_free(asn1_string); } else { log_message(r, APR_SUCCESS, "no transactionID included in request"); return HTTP_BAD_REQUEST; } /* certificate ready? */ rv = ap_run_ca_getcert(r, search, &buffer, &len); if (rv == DECLINED) { log_message(r, APR_SUCCESS, "No module configured to get the certificate (ca_getcert)"); return HTTP_INTERNAL_SERVER_ERROR; } else if (rv == OK) { /* read in the certificate */ if (!d2i_PKCS7(&certs, &buffer, len)) { log_message(r, APR_SUCCESS, "could not DER decode the signed certificate (certstore)"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, certs, scep_PKCS7_cleanup, apr_pool_cleanup_null); /* we have the cert, send the appropriate reply */ rscep = apr_pcalloc(r->pool, sizeof(scep_t)); rscep->messageType = SCEP_MESSAGETYPE_CERTREP; rscep->transactionId = scep->transactionId; make_sender_nonce(r, rscep); rscep->recipientNonce = scep->senderNonce; rscep->recipientNonceLength = scep->senderNonceLength; rscep->pkiStatus = SCEP_PKISTATUS_SUCCESS; rscep->failInfo = -1; rscep->encrypt_cert = scep->encrypt_cert; /* cert to encrypt with */ rscep->certs = certs; /* payload to send back */ } else if (rv == HTTP_NOT_FOUND) { rscep = apr_pcalloc(r->pool, sizeof(scep_t)); rscep->messageType = SCEP_MESSAGETYPE_CERTREP; rscep->transactionId = scep->transactionId; make_sender_nonce(r, rscep); rscep->recipientNonce = scep->senderNonce; rscep->recipientNonceLength = scep->senderNonceLength; rscep->pkiStatus = SCEP_PKISTATUS_PENDING; rscep->encrypt_cert = NULL; rscep->certs = NULL; } else { rscep = apr_pcalloc(r->pool, sizeof(scep_t)); rscep->messageType = SCEP_MESSAGETYPE_CERTREP; rscep->transactionId = scep->transactionId; make_sender_nonce(r, rscep); rscep->recipientNonce = scep->senderNonce; rscep->recipientNonceLength = scep->senderNonceLength; rscep->pkiStatus = SCEP_PKISTATUS_FAILURE; rscep->encrypt_cert = NULL; rscep->certs = NULL; rscep->failInfo = SCEP_FAILINFO_BADREQUEST; } /** * If we've got this far, return a successful response. */ if (rscep->pkiStatus == SCEP_PKISTATUS_SUCCESS && rscep->certs) { /* now, create an envelope to put the signed data in */ inbio = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, inbio, scep_BIO_cleanup, apr_pool_cleanup_null); i2d_PKCS7_bio(inbio, certs); if (!BIO_flush(inbio)) { log_message(r, APR_SUCCESS, "could not flush the BIO for the PKCS7 response"); return HTTP_INTERNAL_SERVER_ERROR; } return scep_send_encrypted_response(r, inbio, rscep); } return scep_send_signed_response(r, NULL, rscep); } /** * MessageType: GetCert */ static int scep_messagetype_getcert(request_rec *r, PKCS7_ISSUER_AND_SERIAL *ias, scep_t *scep) { apr_hash_t *search = apr_hash_make(r->pool); int rv; const unsigned char *buffer; apr_size_t len; PKCS7 *certs = NULL; BIO *inbio; scep_t *rscep; /* handle the issuer */ if (ias->issuer) { apr_hash_set(search, "issuer", APR_HASH_KEY_STRING, make_X509_NAME(r->pool, ias->issuer)); } /* handle the serial */ if (ias->serial) { apr_hash_set(search, "serial", APR_HASH_KEY_STRING, make_ASN1_INTEGER(r->pool, ias->serial)); } /* certificate ready? */ rv = ap_run_ca_getcert(r, search, &buffer, &len); if (rv == DECLINED) { log_message(r, APR_SUCCESS, "No module configured to get the certificate (ca_getcert)"); return HTTP_INTERNAL_SERVER_ERROR; } else if (rv == OK) { /* read in the certificate */ if (!d2i_PKCS7(&certs, &buffer, len)) { log_message(r, APR_SUCCESS, "could not DER decode the signed certificate (certstore)"); return HTTP_BAD_REQUEST; } apr_pool_cleanup_register(r->pool, certs, scep_PKCS7_cleanup, apr_pool_cleanup_null); /* we have the cert, send the appropriate reply */ rscep = apr_pcalloc(r->pool, sizeof(scep_t)); rscep->messageType = SCEP_MESSAGETYPE_CERTREP; rscep->transactionId = scep->transactionId; make_sender_nonce(r, rscep); rscep->recipientNonce = scep->senderNonce; rscep->recipientNonceLength = scep->senderNonceLength; rscep->pkiStatus = SCEP_PKISTATUS_SUCCESS; rscep->failInfo = -1; rscep->encrypt_cert = scep->encrypt_cert; /* cert to encrypt with */ rscep->certs = certs; /* payload to send back */ } else if (rv == HTTP_NOT_FOUND) { rscep = apr_pcalloc(r->pool, sizeof(scep_t)); rscep->messageType = SCEP_MESSAGETYPE_CERTREP; rscep->transactionId = scep->transactionId; make_sender_nonce(r, rscep); rscep->recipientNonce = scep->senderNonce; rscep->recipientNonceLength = scep->senderNonceLength; rscep->pkiStatus = SCEP_PKISTATUS_FAILURE; rscep->encrypt_cert = NULL; rscep->certs = NULL; rscep->failInfo = SCEP_FAILINFO_CERTID; } else { rscep = apr_pcalloc(r->pool, sizeof(scep_t)); rscep->messageType = SCEP_MESSAGETYPE_CERTREP; rscep->transactionId = scep->transactionId; make_sender_nonce(r, rscep); rscep->recipientNonce = scep->senderNonce; rscep->recipientNonceLength = scep->senderNonceLength; rscep->pkiStatus = SCEP_PKISTATUS_FAILURE; rscep->encrypt_cert = NULL; rscep->certs = NULL; rscep->failInfo = SCEP_FAILINFO_BADREQUEST; } /** * If we've got this far, return a successful response. */ if (rscep->pkiStatus == SCEP_PKISTATUS_SUCCESS && rscep->certs) { /* now, create an envelope to put the signed data in */ inbio = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, inbio, scep_BIO_cleanup, apr_pool_cleanup_null); i2d_PKCS7_bio(inbio, certs); if (!BIO_flush(inbio)) { log_message(r, APR_SUCCESS, "could not flush the BIO for the PKCS7 response"); return HTTP_INTERNAL_SERVER_ERROR; } return scep_send_encrypted_response(r, inbio, rscep); } return scep_send_signed_response(r, NULL, rscep); } /** * MessageType: GetCRL * * The draft of the SCEP protocol expiring March 10, 2012 says the following: * * "The server SHOULD NOT support the GetCRL method..." * * If configured, we redirect the client to the real CRL distribution point * for this CA, otherwise we reject with a Bad Request. */ static int scep_messagetype_getcrl(request_rec *r, scep_t *scep) { scep_config_rec *conf = ap_get_module_config(r->per_dir_config, &scep_module); if (!conf->crl_url) { log_message(r, APR_SUCCESS, "CRLs cannot be requested from this service"); return HTTP_BAD_REQUEST; } else { apr_table_setn(r->headers_out, "Location", conf->crl_url); return HTTP_MOVED_PERMANENTLY; } } /** * PKIOperation * * Two options: * - "message" was specified, un-base64 encode it, and contents is a PKCS7 binary message * - Incoming MIME type is not application/x-www-form-urlencoded, body is a PKCS7 binary message */ static int get_pki_operation(request_rec *r, scep_config_rec *conf, const char *message, const char *ct) { PKCS7 *p7 = NULL, *p7e = NULL; X509 *x509; STACK_OF(PKCS7_SIGNER_INFO) *sinfo; STACK_OF(X509_ATTRIBUTE) *sattrs; PKCS7_SIGNER_INFO *si; PKCS7_ISSUER_AND_SERIAL *ias; scep_t *scep; BIO *outbio; apr_size_t size = 0; /* * If the incoming body has a non zero content length, and is not an HTML * form (application/x-www-form-urlencoded), interpret the body as a * PKCS7 message. * * "At a minimum, all SCEP implementations compliant with this * specification MUST support [..] communication of binary data via HTTP * POST..." */ if (!ct || strcmp(ct, "application/x-www-form-urlencoded")) { int seen_eos = 0; BIO *b = BIO_new(BIO_s_mem()); apr_pool_cleanup_register(r->pool, b, scep_BIO_cleanup, apr_pool_cleanup_null); apr_bucket_brigade *bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); do { apr_bucket *bucket = NULL, *last = NULL; int rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN); if (rv != APR_SUCCESS) { apr_brigade_destroy(bb); return (rv == AP_FILTER_ERROR) ? rv : HTTP_BAD_REQUEST; } for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb); last = bucket, bucket = APR_BUCKET_NEXT(bucket)) { const char *data; apr_size_t len; if (last) { apr_bucket_delete(last); } if (APR_BUCKET_IS_EOS(bucket)) { seen_eos = 1; break; } if (bucket->length == 0) { continue; } rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); if (rv != APR_SUCCESS) { apr_brigade_destroy(bb); return HTTP_BAD_REQUEST; } BIO_write(b, data, len); size += len; } apr_brigade_cleanup(bb); } while (!seen_eos); if (size) { p7 = d2i_PKCS7_bio(b, NULL); apr_pool_cleanup_register(r->pool, p7, scep_PKCS7_cleanup, apr_pool_cleanup_null); } } /* * Otherwise legacy behaviour. If message has been set, base64 decode the * message and use the PKCS7 that results. * * "For historical reasons implementations MAY support communications of * binary data via HTTP GET". */ if (!p7 && message) { unsigned char *buffer; char *str; int len; BIO *b; buffer = apr_palloc(r->pool, strlen(message)); str = apr_pstrdup(r->pool, message); strip_whitespace(str); len = apr_base64_decode_binary(buffer, str); b = BIO_new(BIO_s_mem()); BIO_write(b, buffer, len); p7 = d2i_PKCS7_bio(b, NULL); apr_pool_cleanup_register(r->pool, p7, scep_PKCS7_cleanup, apr_pool_cleanup_null); memset(buffer, 0, len); BIO_free(b); } /* * No body, no message, we have nothing to work with. */ if (!size && !message) { log_message(r, APR_SUCCESS, "PKIOperation failed: No content body, and no 'message' parameter"); goto err_bad_request; } if (!p7) { log_message(r, APR_SUCCESS, "PKIOperation failed: PKCS7 message could not be read"); goto err_bad_request; } if (!PKCS7_type_is_signed(p7)) { log_message(r, APR_SUCCESS, "PKIOperation failed: PKCS7 message was not signed"); goto err_bad_request; } /** * Extract the signer certificate. */ sinfo = PKCS7_get_signer_info(p7); if (!sinfo) { log_message(r, APR_SUCCESS, "PKIOperation failed: no signer info was found"); goto err_bad_request; } if (sk_PKCS7_SIGNER_INFO_num(sinfo) != 1) { log_message(r, APR_SUCCESS, "PKIOperation failed: more than one signer was found"); goto err_bad_request; } si = sk_PKCS7_SIGNER_INFO_value(sinfo, 0); ias = si->issuer_and_serial; x509 = X509_find_by_issuer_and_serial(p7->d.sign->cert, ias->issuer, ias->serial); /** * Extract the enveloped PKCS7. */ { BIO *b = PKCS7_dataInit(p7, NULL); if (!b) { log_message(r, APR_SUCCESS, "PKIOperation failed: enveloped PKCS7 could not be extracted (bio could not be created)"); BIO_free(b); goto err_bad_request; } p7e = d2i_PKCS7_bio(b, NULL); if (!p7e) { log_message(r, APR_SUCCESS, "PKIOperation failed: enveloped PKCS7 could not be extracted (bio could not be read)"); BIO_free(b); goto err_bad_request; } else { apr_pool_cleanup_register(r->pool, p7e, scep_PKCS7_cleanup, apr_pool_cleanup_null); } if (PKCS7_signatureVerify(b, p7, si, x509) <= 0) { log_message(r, APR_SUCCESS, "PKIOperation failed: signature verification failed"); BIO_free(b); goto err_bad_request; } BIO_free(b); } /** * Get the attributes of the SCEP request. */ sattrs = PKCS7_get_signed_attributes(si); if ((sattrs == NULL) || (sk_X509_ATTRIBUTE_num(sattrs) == 0)) { log_message(r, APR_SUCCESS, "PKIOperation failed: unable to get signed attributes"); goto err_bad_request; } scep = parse_scep_attributes(r, sattrs); scep->encrypt_cert = x509; /** * Decrypt the internal envelope. */ outbio = BIO_new(BIO_s_mem()); if (!PKCS7_decrypt(p7e, conf->key, conf->signer, outbio, 0)) { log_message(r, APR_SUCCESS, "PKIOperation failed: unable to decrypt PKCS7 envelope"); goto err_bad_request; } /** * Handle each messageType. */ switch (scep->messageType) { case SCEP_MESSAGETYPE_PKCSREQ: { X509_REQ *req = d2i_X509_REQ_bio(outbio, NULL); if (!req) { log_message(r, APR_SUCCESS, "PKIOperation failed: unable to parse certificate request"); goto err_bad_request; } else { apr_pool_cleanup_register(r->pool, req, scep_X509_REQ_cleanup, apr_pool_cleanup_null); } return scep_messagetype_pkcsreq(r, req, scep); } case SCEP_MESSAGETYPE_CERTREP: { log_message(r, APR_SUCCESS, "PKIOperation failed: message type CertRep unexpected"); goto err_bad_request; break; } case SCEP_MESSAGETYPE_GETCERTINITIAL: { PKCS7_ISSUER_AND_SUBJECT *ias = d2i_PKCS7_ISSUER_AND_SUBJECT_bio(outbio, NULL); if (!ias) { log_message(r, APR_SUCCESS, "PKIOperation failed: unable to parse issuer and subject"); goto err_bad_request; } else { apr_pool_cleanup_register(r->pool, ias, scep_PKCS7_ISSUER_AND_SUBJECT_cleanup, apr_pool_cleanup_null); } return scep_messagetype_getcertinitial(r, ias, scep); } case SCEP_MESSAGETYPE_GETCERT: { PKCS7_ISSUER_AND_SERIAL *ias = d2i_PKCS7_ISSUER_AND_SERIAL_bio(outbio, NULL); if (!ias) { log_message(r, APR_SUCCESS, "PKIOperation failed: unable to parse issuer and serial"); goto err_bad_request; } else { apr_pool_cleanup_register(r->pool, ias, scep_PKCS7_ISSUER_AND_SERIAL_cleanup, apr_pool_cleanup_null); } return scep_messagetype_getcert(r, ias, scep); } case SCEP_MESSAGETYPE_GETCRL: { return scep_messagetype_getcrl(r, scep); } default: { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "PKIOperation failed: message type %d was not recognised", scep->messageType)); goto err_bad_request; } } err_bad_request: return HTTP_BAD_REQUEST; } static int options_wadl(request_rec *r, scep_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 body of the request is expected to contain an ASN.1 DER encoded\n" " PKCS7 request message.\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 lookup of the certificate status, 200 OK will be returned\n" " with the body containing the ASN.1 DER-encoded OCSP response.\n" " \n" " \n" " \n" " \n" " \n" " \n" " The SCEP operation, one of 'GetCACert', 'PKCSReq', 'GetCertInitial',\n" " 'GetCert', 'GetCRL' or 'GetNextCACert'.\n" " \n" " \n" " The base64 encoded message relevant to the operation.\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 lookup of the certificate status, 200 OK will be returned\n" " with the body containing the ASN.1 DER-encoded OCSP response.\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 int scep_operation(request_rec *r, scep_config_rec *conf, const char *operation, const char *message, const char *ct) { if (!operation) { log_message(r, APR_SUCCESS, "no 'operation' specified"); return HTTP_BAD_REQUEST; } else if (!strcmp(operation, "GetCACert")) { return get_ca_cert(r, conf, message); } else if (!strcmp(operation, "GetNextCACert")) { return get_next_ca_cert(r, conf, message); } else if (!strcmp(operation, "GetCACaps")) { return get_ca_caps(r, conf, message); } else if (!strcmp(operation, "PKIOperation")) { return get_pki_operation(r, conf, message, ct); } return HTTP_BAD_REQUEST; } static int scep_handler(request_rec *r) { const char *operation, *message; apr_table_t *args; scep_config_rec *conf = ap_get_module_config(r->per_dir_config, &scep_module); if (!conf) { return DECLINED; } if (strcmp(r->handler, "scep")) { return DECLINED; } if (!conf->signer) { log_message(r, APR_SUCCESS, "No RA signer certificate is available"); return HTTP_INTERNAL_SERVER_ERROR; } if (!conf->key) { log_message(r, APR_SUCCESS, "No RA signer key is available"); return HTTP_INTERNAL_SERVER_ERROR; } /* A GET/POST to handle SCEP, OPTIONS should return the WADL */ ap_allow_methods(r, 1, "GET", "POST", "OPTIONS", NULL); if (!strcmp(r->method, "GET")) { int rv; /* discard the request body */ if ((rv = ap_discard_request_body(r)) != OK) { return rv; } ap_args_to_table(r, &args); operation = apr_table_get(args, "operation"); message = apr_table_get(args, "message"); return scep_operation(r, conf, operation, message, NULL); } if (!strcmp(r->method, "POST")) { apr_status_t rv; apr_array_header_t *pairs = NULL; apr_off_t len; apr_size_t size; char *buffer; const char *ct; ap_args_to_table(r, &args); operation = apr_table_get(args, "operation"); message = apr_table_get(args, "message"); /* 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)) { rv = ap_parse_form_data(r, NULL, &pairs, -1, conf->size); if (rv != OK) { return rv; } while (pairs && !apr_is_empty_array(pairs)) { ap_form_pair_t *pair = (ap_form_pair_t *) apr_array_pop(pairs); apr_brigade_length(pair->value, 1, &len); size = (apr_size_t) len; buffer = apr_palloc(r->pool, size + 1); apr_brigade_flatten(pair->value, buffer, &size); buffer[len] = 0; if (!strcmp(pair->name, "operation")) { operation = buffer; } else if (!strcmp(pair->name, "message")) { message = buffer; } else { log_message(r, APR_SUCCESS, apr_psprintf(r->pool, "POST variable '%s' was not recognised", pair->name)); return HTTP_BAD_REQUEST; } } } return scep_operation(r, conf, operation, message, ct); } else if (!strcmp(r->method, "OPTIONS")) { return options_wadl(r, conf); } else { return HTTP_METHOD_NOT_ALLOWED; } } static apr_status_t scep_cleanup(void *data) { EVP_PKEY_free(pknull); pknull = NULL; ERR_free_strings(); EVP_cleanup(); return APR_SUCCESS; } static int scep_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, scep_cleanup, apr_pool_cleanup_null); /* define the new object definitions needed for SCEP */ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_CONFIG) { int i; for (i = 0; i < NEW_NIDS; i++) { scep_oid_def[i].nid = OBJ_create(scep_oid_def[i].oid, scep_oid_def[i].name1, scep_oid_def[i].name2); } } /* 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(scep_pre_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(scep_handler, NULL, NULL, APR_HOOK_MIDDLE); #ifdef HAS_OPENSSL_PR10563_WORK_AROUND ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, "Workaround for OpenSSL/#10563 active; which manipulates openssl-private internals."); #endif } AP_DECLARE_MODULE(scep) = { STANDARD20_MODULE_STUFF, create_scep_dir_config, /* dir config creater */ merge_scep_dir_config, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ scep_cmds, /* command apr_table_t */ register_hooks /* register hooks */ };