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]
</usage>
</directivesynopsis>
+<directivesynopsis>
+<name>SSLProxyMachineCertificateChainFile</name>
+<description>File of concatenated PEM-encoded CA certificates to be used by the proxy for choosing a certificate</description>
+<syntax>SSLProxyMachineCertificateChainFile <em>filename</em></syntax>
+<contextlist><context>server config</context></contextlist>
+<override>Not applicable</override>
+<compatibility>Available in Apache 2.2.22 and later</compatibility>
+
+<usage>
+<p>
+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.
+</p>
+<p>
+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.
+</p>
+<note type="warning"><title>Security warning</title>
+<p>If this directive is enabled, all of the certificates in the file will be
+trusted as if they were also in <directive module="mod_ssl">
+SSLProxyCACertificateFile</directive>.</p>
+</note>
+<example><title>Example</title>
+SSLProxyMachineCertificateChainFile /usr/local/apache2/conf/ssl.crt/proxyCA.pem
+</example>
+</usage>
+</directivesynopsis>
+
<directivesynopsis>
<name>SSLProxyVerify</name>
<description>Type of remote server Certificate verification</description>
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,
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,
cfgMergeString(pkp->cert_file);
cfgMergeString(pkp->cert_path);
+ cfgMergeString(pkp->ca_cert_file);
}
static void modssl_ctx_cfg_merge_server(modssl_ctx_t *base,
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)
{
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,
"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,
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;
}
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",
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);
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");
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,
/** 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 */
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);
#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
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
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 *);