From: William A. Rowe Jr Date: Fri, 17 Aug 2012 17:30:46 +0000 (+0000) Subject: mod_ssl: Add SSLProxyMachineCertificateChainFile directive uses openssl X-Git-Tag: 2.2.23~38 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=20c67c9e0c0d719099fcd2bc588af663cebe0627;p=thirdparty%2Fapache%2Fhttpd.git mod_ssl: Add SSLProxyMachineCertificateChainFile directive uses openssl to construct a chain for each proxy cert. When a remote server requests a client certificate that is NOT the direct issuer of any available client certificate, the chain for that certificate will be used to trace it to a known CA and that client certificate will be used. Submitted by: druggeri Reviewed by: kbrand, rpluem Backports: 1160863,1162103,1170833,1172010,1175946,1242089 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.2.x@1374374 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index 6941b58c14f..457570e8b59 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,10 @@ Changes with Apache 2.2.23 envvars: Fix insecure handling of LD_LIBRARY_PATH that could lead to the current working directory to be searched for DSOs. [Stefan Fritsch] + *) Added SSLProxyMachineCertificateChainFile directive so the proxy client + can select the proper client certificate when using a chain and the + remote server only lists the root CA as allowed. + *) mpm_event, mpm_worker: Remain active amidst prevalent child process resource shortages. [Jeff Trawick] diff --git a/docs/manual/mod/mod_ssl.xml b/docs/manual/mod/mod_ssl.xml index 697ea883dc6..2a368c5ecc0 100644 --- a/docs/manual/mod/mod_ssl.xml +++ b/docs/manual/mod/mod_ssl.xml @@ -1457,6 +1457,37 @@ SSLProxyMachineCertificateFile /usr/local/apache2/conf/ssl.crt/proxy.pem + +SSLProxyMachineCertificateChainFile +File of concatenated PEM-encoded CA certificates to be used by the proxy for choosing a certificate +SSLProxyMachineCertificateChainFile filename +server config +Not applicable +Available in Apache 2.2.22 and later + + +

+This directive sets the all-in-one file where you keep the certificate chain +for all of the client certs in use. This directive will be needed if the +remote server presents a list of CA certificates that are not direct signers +of one of the configured client certificates. +

+

+This referenced file is simply the concatenation of the various PEM-encoded +certificate files. Upon startup, each client certificate configured will +be examined and a chain of trust will be constructed. +

+Security warning +

If this directive is enabled, all of the certificates in the file will be +trusted as if they were also in +SSLProxyCACertificateFile.

+
+Example +SSLProxyMachineCertificateChainFile /usr/local/apache2/conf/ssl.crt/proxyCA.pem + +
+
+ SSLProxyVerify Type of remote server Certificate verification diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c index 5edb1c82e6c..a5fe388b3ba 100644 --- a/modules/ssl/mod_ssl.c +++ b/modules/ssl/mod_ssl.c @@ -189,6 +189,10 @@ static const command_rec ssl_config_cmds[] = { SSL_CMD_SRV(ProxyMachineCertificatePath, TAKE1, "SSL Proxy: directory containing client certificates " "(`/path/to/dir' - contains PEM encoded certificates)") + SSL_CMD_SRV(ProxyMachineCertificateChainFile, TAKE1, + "SSL Proxy: file containing issuing certificates " + "of the client certificate " + "(`/path/to/file' - PEM encoded certificates)") SSL_CMD_SRV(ProxyCheckPeerExpire, FLAG, "SSL Proxy: check the peers certificate expiration date") SSL_CMD_SRV(ProxyCheckPeerCN, FLAG, diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c index 8d3b99d4fc7..ee1243e47c1 100644 --- a/modules/ssl/ssl_engine_config.c +++ b/modules/ssl/ssl_engine_config.c @@ -141,7 +141,9 @@ static void modssl_ctx_init_proxy(SSLSrvConfigRec *sc, mctx->pkp->cert_file = NULL; mctx->pkp->cert_path = NULL; + mctx->pkp->ca_cert_file = NULL; mctx->pkp->certs = NULL; + mctx->pkp->ca_certs = NULL; } static void modssl_ctx_init_server(SSLSrvConfigRec *sc, @@ -233,6 +235,7 @@ static void modssl_ctx_cfg_merge_proxy(modssl_ctx_t *base, cfgMergeString(pkp->cert_file); cfgMergeString(pkp->cert_path); + cfgMergeString(pkp->ca_cert_file); } static void modssl_ctx_cfg_merge_server(modssl_ctx_t *base, @@ -1478,6 +1481,21 @@ const char *ssl_cmd_SSLProxyMachineCertificatePath(cmd_parms *cmd, return NULL; } +const char *ssl_cmd_SSLProxyMachineCertificateChainFile(cmd_parms *cmd, + void *dcfg, + const char *arg) +{ + SSLSrvConfigRec *sc = mySrvConfig(cmd->server); + const char *err; + + if ((err = ssl_cmd_check_file(cmd, &arg))) { + return err; + } + + sc->proxy->pkp->ca_cert_file = arg; + + return NULL; +} const char *ssl_cmd_SSLUserName(cmd_parms *cmd, void *dcfg, const char *arg) diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c index 34535410b8c..2f0094ad84e 100644 --- a/modules/ssl/ssl_engine_init.c +++ b/modules/ssl/ssl_engine_init.c @@ -975,6 +975,9 @@ static void ssl_init_proxy_certs(server_rec *s, { int n, ncerts = 0; STACK_OF(X509_INFO) *sk; + STACK_OF(X509) *chain; + X509_STORE_CTX *sctx; + X509_STORE *store = SSL_CTX_get_cert_store(mctx->ssl_ctx); modssl_pk_proxy_t *pkp = mctx->pkp; SSL_CTX_set_client_cert_cb(mctx->ssl_ctx, @@ -1020,6 +1023,78 @@ static void ssl_init_proxy_certs(server_rec *s, "loaded %d client certs for SSL proxy", ncerts); pkp->certs = sk; + + if (!pkp->ca_cert_file || !store) { + return; + } + + /* Load all of the CA certs and construct a chain */ + pkp->ca_certs = (STACK_OF(X509) **) apr_pcalloc(p, ncerts * sizeof(sk)); + sctx = X509_STORE_CTX_new(); + + if (!sctx) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, + "SSL proxy client cert initialization failed"); + ssl_log_ssl_error(APLOG_MARK, APLOG_EMERG, s); + ssl_die(); + } + + X509_STORE_load_locations(store, pkp->ca_cert_file, NULL); + + for (n = 0; n < ncerts; n++) { + int i; + + X509_INFO *inf = sk_X509_INFO_value(pkp->certs, n); + X509_NAME *name = X509_get_subject_name(inf->x509); + char *cert_dn = SSL_X509_NAME_to_string(ptemp, name, 0); + X509_STORE_CTX_init(sctx, store, inf->x509, NULL); + + /* Attempt to verify the client cert */ + if (X509_verify_cert(sctx) != 1) { + int err = X509_STORE_CTX_get_error(sctx); + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, + "SSL proxy client cert chain verification failed for %s: %s", + cert_dn, X509_verify_cert_error_string(err)); + } + + /* Clear X509_verify_cert errors */ + ERR_clear_error(); + + /* Obtain a copy of the verified chain */ + chain = X509_STORE_CTX_get1_chain(sctx); + + if (chain != NULL) { + /* Discard end entity cert from the chain */ + X509_free(sk_X509_shift(chain)); + + if ((i = sk_X509_num(chain)) > 0) { + /* Store the chain for later use */ + pkp->ca_certs[n] = chain; + } + else { + /* Discard empty chain */ + sk_X509_pop_free(chain, X509_free); + pkp->ca_certs[n] = NULL; + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "loaded %i intermediate CA%s for cert %i (%s)", + i, i == 1 ? "" : "s", n, cert_dn); + if (i > 0) { + int j; + for (j = 0; j < i; j++) { + X509_NAME *ca_name = X509_get_subject_name(sk_X509_value(chain, j)); + char *ca_dn = SSL_X509_NAME_to_string(ptemp, ca_name, 0); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "%i: %s", j, ca_dn); + } + } + } + + /* get ready for next X509_STORE_CTX_init */ + X509_STORE_CTX_cleanup(sctx); + } + + X509_STORE_CTX_free(sctx); } static void ssl_init_proxy_ctx(server_rec *s, @@ -1302,6 +1377,17 @@ static void ssl_init_ctx_cleanup_proxy(modssl_ctx_t *mctx) ssl_init_ctx_cleanup(mctx); if (mctx->pkp->certs) { + int i = 0; + int ncerts = sk_X509_INFO_num(mctx->pkp->certs); + + if (mctx->pkp->ca_certs) { + for (i = 0; i < ncerts; i++) { + if (mctx->pkp->ca_certs[i] != NULL) { + sk_X509_pop_free(mctx->pkp->ca_certs[i], X509_free); + } + } + } + sk_X509_INFO_pop_free(mctx->pkp->certs, X509_INFO_free); mctx->pkp->certs = NULL; } diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c index 33f97bd9d90..5ea88575237 100644 --- a/modules/ssl/ssl_engine_kernel.c +++ b/modules/ssl/ssl_engine_kernel.c @@ -1651,11 +1651,14 @@ int ssl_callback_proxy_cert(SSL *ssl, MODSSL_CLIENT_CERT_CB_ARG_TYPE **x509, EVP conn_rec *c = (conn_rec *)SSL_get_app_data(ssl); server_rec *s = mySrvFromConn(c); SSLSrvConfigRec *sc = mySrvConfig(s); - X509_NAME *ca_name, *issuer; + X509_NAME *ca_name, *issuer, *ca_issuer; X509_INFO *info; + X509 *ca_cert; STACK_OF(X509_NAME) *ca_list; STACK_OF(X509_INFO) *certs = sc->proxy->pkp->certs; - int i, j; + STACK_OF(X509) *ca_certs; + STACK_OF(X509) **ca_cert_chains; + int i, j, k; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, SSLPROXY_CERT_CB_LOG_FMT "entered", @@ -1685,6 +1688,7 @@ int ssl_callback_proxy_cert(SSL *ssl, MODSSL_CLIENT_CERT_CB_ARG_TYPE **x509, EVP return TRUE; } + ca_cert_chains = sc->proxy->pkp->ca_certs; for (i = 0; i < sk_X509_NAME_num(ca_list); i++) { ca_name = sk_X509_NAME_value(ca_list, i); @@ -1692,6 +1696,7 @@ int ssl_callback_proxy_cert(SSL *ssl, MODSSL_CLIENT_CERT_CB_ARG_TYPE **x509, EVP info = sk_X509_INFO_value(certs, j); issuer = X509_get_issuer_name(info->x509); + /* Search certs (by issuer name) one by one*/ if (X509_NAME_cmp(issuer, ca_name) == 0) { modssl_proxy_info_log(s, info, "found acceptable cert"); @@ -1699,7 +1704,27 @@ int ssl_callback_proxy_cert(SSL *ssl, MODSSL_CLIENT_CERT_CB_ARG_TYPE **x509, EVP return TRUE; } - } + + if (ca_cert_chains) { + /* + * Failed to find direct issuer - search intermediates + * (by issuer name), if provided. + */ + ca_certs = ca_cert_chains[j]; + for (k = 0; k < sk_X509_num(ca_certs); k++) { + ca_cert = sk_X509_value(ca_certs, k); + ca_issuer = X509_get_issuer_name(ca_cert); + + if(X509_NAME_cmp(ca_issuer, ca_name) == 0 ) { + modssl_proxy_info_log(s, info, "found acceptable cert by intermediate CA"); + + modssl_set_cert_info(info, x509, pkey); + + return TRUE; + } + } /* end loop through chained certs */ + } + } /* end loop through available certs */ } ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h index 15deb8f1b38..6aaaae9cfed 100644 --- a/modules/ssl/ssl_private.h +++ b/modules/ssl/ssl_private.h @@ -425,7 +425,11 @@ typedef struct { /** proxy can have any number of cert/key pairs */ const char *cert_file; const char *cert_path; - STACK_OF(X509_INFO) *certs; + const char *ca_cert_file; + STACK_OF(X509_INFO) *certs; /* Contains End Entity certs */ + STACK_OF(X509) **ca_certs; /* Contains ONLY chain certs for + * each item in certs. + * (ptr to array of ptrs) */ } modssl_pk_proxy_t; /** stuff related to authentication that can also be per-dir */ @@ -566,6 +570,7 @@ const char *ssl_cmd_SSLProxyCARevocationPath(cmd_parms *, void *, const char *) const char *ssl_cmd_SSLProxyCARevocationFile(cmd_parms *, void *, const char *); const char *ssl_cmd_SSLProxyMachineCertificatePath(cmd_parms *, void *, const char *); const char *ssl_cmd_SSLProxyMachineCertificateFile(cmd_parms *, void *, const char *); +const char *ssl_cmd_SSLProxyMachineCertificateChainFile(cmd_parms *, void *, const char *); const char *ssl_cmd_SSLProxyCheckPeerExpire(cmd_parms *cmd, void *dcfg, int flag); const char *ssl_cmd_SSLProxyCheckPeerCN(cmd_parms *cmd, void *dcfg, int flag); diff --git a/modules/ssl/ssl_toolkit_compat.h b/modules/ssl/ssl_toolkit_compat.h index 369516b2d51..bd57a17ae6f 100644 --- a/modules/ssl/ssl_toolkit_compat.h +++ b/modules/ssl/ssl_toolkit_compat.h @@ -238,6 +238,7 @@ typedef void (*modssl_popfree_fn)(char *data); #define sk_X509_push sk_push #define sk_X509_pop_free(st, free) sk_pop_free((STACK*)(st), (modssl_popfree_fn)(free)) #define sk_X509_value (X509 *)sk_value +#define sk_X509_shift (X509 *)sk_shift #define sk_X509_INFO_free sk_free #define sk_X509_INFO_pop_free(st, free) sk_pop_free((STACK*)(st), (modssl_popfree_fn)(free)) #define sk_X509_INFO_num sk_num diff --git a/modules/ssl/ssl_util_ssl.c b/modules/ssl/ssl_util_ssl.c index a06b65047a1..0ad9854a204 100644 --- a/modules/ssl/ssl_util_ssl.c +++ b/modules/ssl/ssl_util_ssl.c @@ -382,6 +382,38 @@ BOOL SSL_X509_getCN(apr_pool_t *p, X509 *xs, char **cppCN) return FALSE; } +/* + * convert an X509_NAME to an RFC 2253 formatted string, optionally truncated + * to maxlen characters (specify a maxlen of 0 for no length limit) + */ +char *SSL_X509_NAME_to_string(apr_pool_t *p, X509_NAME *dn, unsigned int maxlen) +{ + char *result = NULL; + BIO *bio; + int len; + + if ((bio = BIO_new(BIO_s_mem())) == NULL) + return NULL; + X509_NAME_print_ex(bio, dn, 0, XN_FLAG_RFC2253); + len = BIO_pending(bio); + if (len > 0) { + result = apr_palloc(p, maxlen ? maxlen+1 : len+1); + if (maxlen && maxlen < len) { + len = BIO_read(bio, result, maxlen); + if (maxlen > 2) { + /* insert trailing ellipsis if there's enough space */ + apr_snprintf(result + maxlen - 3, 4, "..."); + } + } else { + len = BIO_read(bio, result, len); + } + result[len] = NUL; + } + BIO_free(bio); + + return result; +} + /* _________________________________________________________________ ** ** Low-Level CA Certificate Loading diff --git a/modules/ssl/ssl_util_ssl.h b/modules/ssl/ssl_util_ssl.h index 04bcbdc2787..c72ca791a64 100644 --- a/modules/ssl/ssl_util_ssl.h +++ b/modules/ssl/ssl_util_ssl.h @@ -85,6 +85,7 @@ int SSL_X509_STORE_lookup(X509_STORE *, int, X509_NAME *, X509_OBJECT *) char *SSL_make_ciphersuite(apr_pool_t *, SSL *); BOOL SSL_X509_isSGC(X509 *); BOOL SSL_X509_getBC(X509 *, int *, int *); +char *SSL_X509_NAME_to_string(apr_pool_t *, X509_NAME *, unsigned int); BOOL SSL_X509_getCN(apr_pool_t *, X509 *, char **); BOOL SSL_X509_INFO_load_file(apr_pool_t *, STACK_OF(X509_INFO) *, const char *); BOOL SSL_X509_INFO_load_path(apr_pool_t *, STACK_OF(X509_INFO) *, const char *);