From 29be795e5a22022e4f6f8265506d127b5077455a Mon Sep 17 00:00:00 2001
From: studersi
Date: Fri, 10 Apr 2026 14:04:27 +0200
Subject: [PATCH 1/3] Add SSLVerifyClientEKU directive to control Extended Key
Usage checks for client certificates.
---
docs/manual/mod/mod_ssl.xml | 42 +++++++++++++++++++++++++++++++++
modules/ssl/mod_ssl.c | 3 +++
modules/ssl/ssl_engine_config.c | 37 +++++++++++++++++++++++++++++
modules/ssl/ssl_engine_kernel.c | 19 +++++++++++++++
modules/ssl/ssl_private.h | 8 +++++++
5 files changed, 109 insertions(+)
diff --git a/docs/manual/mod/mod_ssl.xml b/docs/manual/mod/mod_ssl.xml
index f08d83fbf48..9528d8b34df 100644
--- a/docs/manual/mod/mod_ssl.xml
+++ b/docs/manual/mod/mod_ssl.xml
@@ -1460,6 +1460,48 @@ SSLVerifyClient require
+
+SSLVerifyClientEKU
+Whether to enforce Extended Key Usage checks for Client Certificates
+SSLVerifyClientEKU on|off
+SSLVerifyClientEKU on
+server config
+virtual host
+directory
+.htaccess
+AuthConfig
+
+
+
+This directive controls whether mod_ssl enforces X.509 Extended Key Usage
+(EKU) invalid purpose checks during client certificate
+verification. The default value on preserves the standard
+behavior and rejects client certificates whose EKU does not allow client
+authentication.
+
+
+Setting this directive explicitly to on is identical to omitting
+the directive.
+
+
+When set to off, mod_ssl will ignore only the
+invalid purpose verification error for client certificates while
+leaving other verification checks (e.g. chain validation, signature, validity
+period, revocation checks) unchanged.
+
+
+This setting only affects client certificate verification performed by
+SSLVerifyClient.
+
+Example
+
+SSLVerifyClient require
+SSLVerifyClientEKU off
+
+
+
+
+
SSLVerifyDepth
Maximum depth of CA Certificates in Client
diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c
index 8f79d0a8bc0..6e2cffe0afb 100644
--- a/modules/ssl/mod_ssl.c
+++ b/modules/ssl/mod_ssl.c
@@ -152,6 +152,9 @@ static const command_rec ssl_config_cmds[] = {
SSL_CMD_ALL(VerifyClient, TAKE1,
"SSL Client verify type "
"('none', 'optional', 'require', 'optional_no_ca')")
+ SSL_CMD_ALL(VerifyClientEKU, TAKE1,
+ "Whether to enforce client certificate Extended Key Usage "
+ "during SSL client verification ('on' or 'off')")
SSL_CMD_ALL(VerifyDepth, TAKE1,
"SSL Client verify depth "
"('N' - number of intermediate certificates)")
diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c
index a38dd943e4c..fc92dd8e6e8 100644
--- a/modules/ssl/ssl_engine_config.c
+++ b/modules/ssl/ssl_engine_config.c
@@ -138,6 +138,7 @@ static void modssl_ctx_init(modssl_ctx_t *mctx, apr_pool_t *p)
mctx->auth.cipher_suite = NULL;
mctx->auth.verify_depth = UNSET;
mctx->auth.verify_mode = SSL_CVERIFY_UNSET;
+ mctx->auth.verify_client_eku = SSL_VERIFY_EKU_UNSET;
mctx->auth.tls13_ciphers = NULL;
mctx->ocsp_mask = UNSET;
@@ -284,6 +285,7 @@ static void modssl_ctx_cfg_merge(apr_pool_t *p,
cfgMergeString(auth.cipher_suite);
cfgMergeInt(auth.verify_depth);
cfgMerge(auth.verify_mode, SSL_CVERIFY_UNSET);
+ cfgMerge(auth.verify_client_eku, SSL_VERIFY_EKU_UNSET);
cfgMergeString(auth.tls13_ciphers);
cfgMergeInt(ocsp_mask);
@@ -405,6 +407,7 @@ void *ssl_config_perdir_create(apr_pool_t *p, char *dir)
dc->szCipherSuite = NULL;
dc->nVerifyClient = SSL_CVERIFY_UNSET;
+ dc->nVerifyClientEKU = SSL_VERIFY_EKU_UNSET;
dc->nVerifyDepth = UNSET;
dc->szUserName = NULL;
@@ -461,6 +464,7 @@ void *ssl_config_perdir_merge(apr_pool_t *p, void *basev, void *addv)
cfgMergeString(szCipherSuite);
cfgMerge(nVerifyClient, SSL_CVERIFY_UNSET);
+ cfgMerge(nVerifyClientEKU, SSL_VERIFY_EKU_UNSET);
cfgMergeInt(nVerifyDepth);
cfgMergeString(szUserName);
@@ -1321,6 +1325,36 @@ const char *ssl_cmd_SSLVerifyClient(cmd_parms *cmd,
return NULL;
}
+const char *ssl_cmd_SSLVerifyClientEKU(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ ssl_verify_eku_t mode;
+
+ if (strcEQ(arg, "on")) {
+ mode = SSL_VERIFY_EKU_UNSET;
+ }
+ else if (strcEQ(arg, "off")) {
+ mode = SSL_VERIFY_EKU_OFF;
+ }
+ else {
+ return apr_pstrcat(cmd->temp_pool, cmd->cmd->name,
+ ": Invalid argument '", arg,
+ "' (expected 'on' or 'off')", NULL);
+ }
+
+ if (cmd->path) {
+ dc->nVerifyClientEKU = mode;
+ }
+ else {
+ sc->server->auth.verify_client_eku = mode;
+ }
+
+ return NULL;
+}
+
static const char *ssl_cmd_verify_depth_parse(cmd_parms *parms,
const char *arg,
int *depth)
@@ -2622,6 +2656,9 @@ static void modssl_auth_ctx_dump(modssl_auth_ctx_t *auth, apr_pool_t *p, int pro
}
#endif
DMP_VERIFY(proxy? "SSLProxyVerify" : "SSLVerifyClient", auth->verify_mode);
+ if (!proxy) {
+ DMP_ON_OFF("SSLVerifyClientEKU", auth->verify_client_eku);
+ }
DMP_LONG( proxy? "SSLProxyVerify" : "SSLVerifyDepth", auth->verify_depth);
DMP_STRING(proxy? "SSLProxyCACertificateFile" : "SSLCACertificateFile", auth->ca_cert_file);
DMP_STRING(proxy? "SSLProxyCACertificatePath" : "SSLCACertificatePath", auth->ca_cert_path);
diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c
index cb88f0112c6..6ce4f7253f0 100644
--- a/modules/ssl/ssl_engine_kernel.c
+++ b/modules/ssl/ssl_engine_kernel.c
@@ -1630,6 +1630,7 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
int errdepth = X509_STORE_CTX_get_error_depth(ctx);
int depth = UNSET;
int verify = SSL_CVERIFY_UNSET;
+ ssl_verify_eku_t verify_eku = SSL_VERIFY_EKU_UNSET;
/*
* Log verification information
@@ -1657,6 +1658,13 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
verify = mctx->auth.verify_mode;
}
+ if (dc && !conn->outgoing) {
+ verify_eku = dc->nVerifyClientEKU;
+ }
+ if (verify_eku == SSL_VERIFY_EKU_UNSET) {
+ verify_eku = mctx->auth.verify_client_eku;
+ }
+
if (verify == SSL_CVERIFY_NONE) {
/*
* SSLProxyVerify is either not configured or set to "none".
@@ -1666,6 +1674,17 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
return TRUE;
}
+ if (!ok && !conn->outgoing
+ && errnum == X509_V_ERR_INVALID_PURPOSE
+ && verify_eku == SSL_VERIFY_EKU_OFF) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, conn,
+ "Certificate Verification: EKU check disabled by "
+ "SSLVerifyClientEKU, accepting invalid purpose");
+ X509_STORE_CTX_set_error(ctx, X509_V_OK);
+ errnum = X509_V_OK;
+ ok = TRUE;
+ }
+
if (ssl_verify_error_is_optional(errnum) &&
(verify == SSL_CVERIFY_OPTIONAL_NO_CA))
{
diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h
index 442b8b17ae4..ea07a1fb3f0 100644
--- a/modules/ssl/ssl_private.h
+++ b/modules/ssl/ssl_private.h
@@ -479,6 +479,11 @@ typedef enum {
SSL_CVERIFY_OPTIONAL_NO_CA = 3
} ssl_verify_t;
+typedef enum {
+ SSL_VERIFY_EKU_UNSET = UNSET,
+ SSL_VERIFY_EKU_OFF = 0
+} ssl_verify_eku_t;
+
#define SSL_VERIFY_PEER_STRICT \
(SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
@@ -791,6 +796,7 @@ typedef struct {
/** for client or downstream server authentication */
int verify_depth;
ssl_verify_t verify_mode;
+ ssl_verify_eku_t verify_client_eku;
/** TLSv1.3 has its separate cipher list, separate from the
settings for older TLS protocol versions. Since which one takes
@@ -926,6 +932,7 @@ struct SSLDirConfigRec {
ssl_opt_t nOptionsDel;
const char *szCipherSuite;
ssl_verify_t nVerifyClient;
+ ssl_verify_eku_t nVerifyClientEKU;
int nVerifyDepth;
const char *szUserName;
apr_size_t nRenegBufferSize;
@@ -977,6 +984,7 @@ const char *ssl_cmd_SSLClientHelloVars(cmd_parms *, void *, int flag);
const char *ssl_cmd_SSLCompression(cmd_parms *, void *, int flag);
const char *ssl_cmd_SSLSessionTickets(cmd_parms *, void *, int flag);
const char *ssl_cmd_SSLVerifyClient(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLVerifyClientEKU(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLVerifyDepth(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLSessionCache(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLSessionCacheTimeout(cmd_parms *, void *, const char *);
From a9905991523d8e4fe16403edc73fc562ec4917d7 Mon Sep 17 00:00:00 2001
From: studersi
Date: Fri, 1 May 2026 20:50:39 +0200
Subject: [PATCH 2/3] Revert "Add SSLVerifyClientEKU directive to control
Extended Key Usage checks for client certificates."
This reverts commit 7634be350583d0f51c39a70a0bd3a992067a01a3.
---
docs/manual/mod/mod_ssl.xml | 42 ---------------------------------
modules/ssl/mod_ssl.c | 3 ---
modules/ssl/ssl_engine_config.c | 37 -----------------------------
modules/ssl/ssl_engine_kernel.c | 19 ---------------
modules/ssl/ssl_private.h | 8 -------
5 files changed, 109 deletions(-)
diff --git a/docs/manual/mod/mod_ssl.xml b/docs/manual/mod/mod_ssl.xml
index 9528d8b34df..f08d83fbf48 100644
--- a/docs/manual/mod/mod_ssl.xml
+++ b/docs/manual/mod/mod_ssl.xml
@@ -1460,48 +1460,6 @@ SSLVerifyClient require
-
-SSLVerifyClientEKU
-Whether to enforce Extended Key Usage checks for Client Certificates
-SSLVerifyClientEKU on|off
-SSLVerifyClientEKU on
-server config
-virtual host
-directory
-.htaccess
-AuthConfig
-
-
-
-This directive controls whether mod_ssl enforces X.509 Extended Key Usage
-(EKU) invalid purpose checks during client certificate
-verification. The default value on preserves the standard
-behavior and rejects client certificates whose EKU does not allow client
-authentication.
-
-
-Setting this directive explicitly to on is identical to omitting
-the directive.
-
-
-When set to off, mod_ssl will ignore only the
-invalid purpose verification error for client certificates while
-leaving other verification checks (e.g. chain validation, signature, validity
-period, revocation checks) unchanged.
-
-
-This setting only affects client certificate verification performed by
-SSLVerifyClient.
-
-Example
-
-SSLVerifyClient require
-SSLVerifyClientEKU off
-
-
-
-
-
SSLVerifyDepth
Maximum depth of CA Certificates in Client
diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c
index 6e2cffe0afb..8f79d0a8bc0 100644
--- a/modules/ssl/mod_ssl.c
+++ b/modules/ssl/mod_ssl.c
@@ -152,9 +152,6 @@ static const command_rec ssl_config_cmds[] = {
SSL_CMD_ALL(VerifyClient, TAKE1,
"SSL Client verify type "
"('none', 'optional', 'require', 'optional_no_ca')")
- SSL_CMD_ALL(VerifyClientEKU, TAKE1,
- "Whether to enforce client certificate Extended Key Usage "
- "during SSL client verification ('on' or 'off')")
SSL_CMD_ALL(VerifyDepth, TAKE1,
"SSL Client verify depth "
"('N' - number of intermediate certificates)")
diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c
index fc92dd8e6e8..a38dd943e4c 100644
--- a/modules/ssl/ssl_engine_config.c
+++ b/modules/ssl/ssl_engine_config.c
@@ -138,7 +138,6 @@ static void modssl_ctx_init(modssl_ctx_t *mctx, apr_pool_t *p)
mctx->auth.cipher_suite = NULL;
mctx->auth.verify_depth = UNSET;
mctx->auth.verify_mode = SSL_CVERIFY_UNSET;
- mctx->auth.verify_client_eku = SSL_VERIFY_EKU_UNSET;
mctx->auth.tls13_ciphers = NULL;
mctx->ocsp_mask = UNSET;
@@ -285,7 +284,6 @@ static void modssl_ctx_cfg_merge(apr_pool_t *p,
cfgMergeString(auth.cipher_suite);
cfgMergeInt(auth.verify_depth);
cfgMerge(auth.verify_mode, SSL_CVERIFY_UNSET);
- cfgMerge(auth.verify_client_eku, SSL_VERIFY_EKU_UNSET);
cfgMergeString(auth.tls13_ciphers);
cfgMergeInt(ocsp_mask);
@@ -407,7 +405,6 @@ void *ssl_config_perdir_create(apr_pool_t *p, char *dir)
dc->szCipherSuite = NULL;
dc->nVerifyClient = SSL_CVERIFY_UNSET;
- dc->nVerifyClientEKU = SSL_VERIFY_EKU_UNSET;
dc->nVerifyDepth = UNSET;
dc->szUserName = NULL;
@@ -464,7 +461,6 @@ void *ssl_config_perdir_merge(apr_pool_t *p, void *basev, void *addv)
cfgMergeString(szCipherSuite);
cfgMerge(nVerifyClient, SSL_CVERIFY_UNSET);
- cfgMerge(nVerifyClientEKU, SSL_VERIFY_EKU_UNSET);
cfgMergeInt(nVerifyDepth);
cfgMergeString(szUserName);
@@ -1325,36 +1321,6 @@ const char *ssl_cmd_SSLVerifyClient(cmd_parms *cmd,
return NULL;
}
-const char *ssl_cmd_SSLVerifyClientEKU(cmd_parms *cmd,
- void *dcfg,
- const char *arg)
-{
- SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
- SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
- ssl_verify_eku_t mode;
-
- if (strcEQ(arg, "on")) {
- mode = SSL_VERIFY_EKU_UNSET;
- }
- else if (strcEQ(arg, "off")) {
- mode = SSL_VERIFY_EKU_OFF;
- }
- else {
- return apr_pstrcat(cmd->temp_pool, cmd->cmd->name,
- ": Invalid argument '", arg,
- "' (expected 'on' or 'off')", NULL);
- }
-
- if (cmd->path) {
- dc->nVerifyClientEKU = mode;
- }
- else {
- sc->server->auth.verify_client_eku = mode;
- }
-
- return NULL;
-}
-
static const char *ssl_cmd_verify_depth_parse(cmd_parms *parms,
const char *arg,
int *depth)
@@ -2656,9 +2622,6 @@ static void modssl_auth_ctx_dump(modssl_auth_ctx_t *auth, apr_pool_t *p, int pro
}
#endif
DMP_VERIFY(proxy? "SSLProxyVerify" : "SSLVerifyClient", auth->verify_mode);
- if (!proxy) {
- DMP_ON_OFF("SSLVerifyClientEKU", auth->verify_client_eku);
- }
DMP_LONG( proxy? "SSLProxyVerify" : "SSLVerifyDepth", auth->verify_depth);
DMP_STRING(proxy? "SSLProxyCACertificateFile" : "SSLCACertificateFile", auth->ca_cert_file);
DMP_STRING(proxy? "SSLProxyCACertificatePath" : "SSLCACertificatePath", auth->ca_cert_path);
diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c
index 6ce4f7253f0..cb88f0112c6 100644
--- a/modules/ssl/ssl_engine_kernel.c
+++ b/modules/ssl/ssl_engine_kernel.c
@@ -1630,7 +1630,6 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
int errdepth = X509_STORE_CTX_get_error_depth(ctx);
int depth = UNSET;
int verify = SSL_CVERIFY_UNSET;
- ssl_verify_eku_t verify_eku = SSL_VERIFY_EKU_UNSET;
/*
* Log verification information
@@ -1658,13 +1657,6 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
verify = mctx->auth.verify_mode;
}
- if (dc && !conn->outgoing) {
- verify_eku = dc->nVerifyClientEKU;
- }
- if (verify_eku == SSL_VERIFY_EKU_UNSET) {
- verify_eku = mctx->auth.verify_client_eku;
- }
-
if (verify == SSL_CVERIFY_NONE) {
/*
* SSLProxyVerify is either not configured or set to "none".
@@ -1674,17 +1666,6 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
return TRUE;
}
- if (!ok && !conn->outgoing
- && errnum == X509_V_ERR_INVALID_PURPOSE
- && verify_eku == SSL_VERIFY_EKU_OFF) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, conn,
- "Certificate Verification: EKU check disabled by "
- "SSLVerifyClientEKU, accepting invalid purpose");
- X509_STORE_CTX_set_error(ctx, X509_V_OK);
- errnum = X509_V_OK;
- ok = TRUE;
- }
-
if (ssl_verify_error_is_optional(errnum) &&
(verify == SSL_CVERIFY_OPTIONAL_NO_CA))
{
diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h
index ea07a1fb3f0..442b8b17ae4 100644
--- a/modules/ssl/ssl_private.h
+++ b/modules/ssl/ssl_private.h
@@ -479,11 +479,6 @@ typedef enum {
SSL_CVERIFY_OPTIONAL_NO_CA = 3
} ssl_verify_t;
-typedef enum {
- SSL_VERIFY_EKU_UNSET = UNSET,
- SSL_VERIFY_EKU_OFF = 0
-} ssl_verify_eku_t;
-
#define SSL_VERIFY_PEER_STRICT \
(SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
@@ -796,7 +791,6 @@ typedef struct {
/** for client or downstream server authentication */
int verify_depth;
ssl_verify_t verify_mode;
- ssl_verify_eku_t verify_client_eku;
/** TLSv1.3 has its separate cipher list, separate from the
settings for older TLS protocol versions. Since which one takes
@@ -932,7 +926,6 @@ struct SSLDirConfigRec {
ssl_opt_t nOptionsDel;
const char *szCipherSuite;
ssl_verify_t nVerifyClient;
- ssl_verify_eku_t nVerifyClientEKU;
int nVerifyDepth;
const char *szUserName;
apr_size_t nRenegBufferSize;
@@ -984,7 +977,6 @@ const char *ssl_cmd_SSLClientHelloVars(cmd_parms *, void *, int flag);
const char *ssl_cmd_SSLCompression(cmd_parms *, void *, int flag);
const char *ssl_cmd_SSLSessionTickets(cmd_parms *, void *, int flag);
const char *ssl_cmd_SSLVerifyClient(cmd_parms *, void *, const char *);
-const char *ssl_cmd_SSLVerifyClientEKU(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLVerifyDepth(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLSessionCache(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLSessionCacheTimeout(cmd_parms *, void *, const char *);
From 3a71706adefc2e444e20abb8e915879f6c5dbce5 Mon Sep 17 00:00:00 2001
From: studersi
Date: Fri, 1 May 2026 22:37:19 +0200
Subject: [PATCH 3/3] Add a second argument to SSLVerifyClient to allow for
specific TLS verification errors to be ignored.
---
docs/manual/mod/mod_ssl.xml | 55 +++++++++++--
modules/ssl/mod_ssl.c | 5 +-
modules/ssl/ssl_engine_config.c | 136 +++++++++++++++++++++++++++++++-
modules/ssl/ssl_engine_io.c | 11 ++-
modules/ssl/ssl_engine_kernel.c | 41 +++++++++-
modules/ssl/ssl_private.h | 27 ++++++-
6 files changed, 256 insertions(+), 19 deletions(-)
diff --git a/docs/manual/mod/mod_ssl.xml b/docs/manual/mod/mod_ssl.xml
index f08d83fbf48..96a982c1d3d 100644
--- a/docs/manual/mod/mod_ssl.xml
+++ b/docs/manual/mod/mod_ssl.xml
@@ -1421,7 +1421,7 @@ SSLCARevocationCheck chain no_crl_for_cert_ok
SSLVerifyClient
Type of Client Certificate verification
-SSLVerifyClient level
+SSLVerifyClient level [accepted-errors]
SSLVerifyClient none
server config
virtual host
@@ -1442,19 +1442,60 @@ before the HTTP response is sent.
The following levels are available for level:
- none:
- no client Certificate is required at all
+ no client certificate is required at all. When this level is used,
+ a second argument for accepted-errors is not permitted.
- optional:
- the client may present a valid Certificate
+ the client may present a valid certificate
- require:
- the client has to present a valid Certificate
+ the client has to present a valid certificate
- optional_no_ca:
- the client may present a valid Certificate
+ the client may present a valid certificate
but it need not to be (successfully) verifiable. This option
- cannot be relied upon for client authentication.
+ cannot be relied upon for client authentication. This is now equivalent to
+ SSLVerifyClient optional X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,X509_V_ERR_CERT_UNTRUSTED,X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE,X509_V_ERR_CERT_HAS_EXPIRED.
+
+The optional second argument accepted-errors can be used to specify a
+comma-separated list of verification errors that should be accepted, even if the
+verification level would otherwise reject the certificate.
+Use of accepted-errors weakens client certificate verification and
+should not be used in production deployments.
+The following shorthand names are available for accepted-errors:
+
+- self-signed:
+ Accepts
X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT and
+ X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN.
+- untrusted-cert:
+ Accepts
X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY and
+ X509_V_ERR_CERT_UNTRUSTED.
+- invalid-signature:
+ Accepts
X509_V_ERR_CERT_SIGNATURE_FAILURE and
+ X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE.
+- expired-cert:
+ Accepts
X509_V_ERR_CERT_HAS_EXPIRED and
+ X509_V_ERR_CERT_NOT_YET_VALID.
+- purpose-mismatch:
+ Accepts
X509_V_ERR_INVALID_PURPOSE.
+
+
+Alternatively, any of the following OpenSSL X509 verification error names can be used directly,
+such as X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
+X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
+X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
+X509_V_ERR_CERT_UNTRUSTED,
+X509_V_ERR_CERT_SIGNATURE_FAILURE,
+X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE,
+X509_V_ERR_CERT_HAS_EXPIRED,
+X509_V_ERR_CERT_NOT_YET_VALID, or
+X509_V_ERR_INVALID_PURPOSE.
Example
-SSLVerifyClient require
+ SSLVerifyClient require purpose-mismatch
+
+
+Example with accepted errors
+
+ SSLVerifyClient optional self-signed,untrusted-cert,purpose-mismatch
diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c
index 8f79d0a8bc0..b6f206f1992 100644
--- a/modules/ssl/mod_ssl.c
+++ b/modules/ssl/mod_ssl.c
@@ -149,9 +149,10 @@ static const command_rec ssl_config_cmds[] = {
"('/path/to/file' - PEM encoded)")
SSL_CMD_SRV(CARevocationCheck, RAW_ARGS,
"SSL CA Certificate Revocation List (CRL) checking mode")
- SSL_CMD_ALL(VerifyClient, TAKE1,
+ SSL_CMD_ALL(VerifyClient, TAKE12,
"SSL Client verify type "
- "('none', 'optional', 'require', 'optional_no_ca')")
+ "('none', 'optional', 'require', 'optional_no_ca' "
+ "[accepted-errors])")
SSL_CMD_ALL(VerifyDepth, TAKE1,
"SSL Client verify depth "
"('N' - number of intermediate certificates)")
diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c
index a38dd943e4c..9344349549d 100644
--- a/modules/ssl/ssl_engine_config.c
+++ b/modules/ssl/ssl_engine_config.c
@@ -138,6 +138,8 @@ static void modssl_ctx_init(modssl_ctx_t *mctx, apr_pool_t *p)
mctx->auth.cipher_suite = NULL;
mctx->auth.verify_depth = UNSET;
mctx->auth.verify_mode = SSL_CVERIFY_UNSET;
+ mctx->auth.verify_error_mask = 0;
+ mctx->auth.verify_error_mask_set = FALSE;
mctx->auth.tls13_ciphers = NULL;
mctx->ocsp_mask = UNSET;
@@ -284,6 +286,14 @@ static void modssl_ctx_cfg_merge(apr_pool_t *p,
cfgMergeString(auth.cipher_suite);
cfgMergeInt(auth.verify_depth);
cfgMerge(auth.verify_mode, SSL_CVERIFY_UNSET);
+ if (add->auth.verify_error_mask_set) {
+ mrg->auth.verify_error_mask = add->auth.verify_error_mask;
+ mrg->auth.verify_error_mask_set = TRUE;
+ }
+ else {
+ mrg->auth.verify_error_mask = base->auth.verify_error_mask;
+ mrg->auth.verify_error_mask_set = base->auth.verify_error_mask_set;
+ }
cfgMergeString(auth.tls13_ciphers);
cfgMergeInt(ocsp_mask);
@@ -405,6 +415,8 @@ void *ssl_config_perdir_create(apr_pool_t *p, char *dir)
dc->szCipherSuite = NULL;
dc->nVerifyClient = SSL_CVERIFY_UNSET;
+ dc->nVerifyClientErrorMask = 0;
+ dc->nVerifyClientErrorMaskSet = FALSE;
dc->nVerifyDepth = UNSET;
dc->szUserName = NULL;
@@ -461,6 +473,14 @@ void *ssl_config_perdir_merge(apr_pool_t *p, void *basev, void *addv)
cfgMergeString(szCipherSuite);
cfgMerge(nVerifyClient, SSL_CVERIFY_UNSET);
+ if (add->nVerifyClientErrorMaskSet) {
+ mrg->nVerifyClientErrorMask = add->nVerifyClientErrorMask;
+ mrg->nVerifyClientErrorMaskSet = TRUE;
+ }
+ else {
+ mrg->nVerifyClientErrorMask = base->nVerifyClientErrorMask;
+ mrg->nVerifyClientErrorMaskSet = base->nVerifyClientErrorMaskSet;
+ }
cfgMergeInt(nVerifyDepth);
cfgMergeString(szUserName);
@@ -1298,24 +1318,136 @@ static const char *ssl_cmd_verify_parse(cmd_parms *parms,
return NULL;
}
+#define SSL_VERIFY_CLIENT_OPTIONAL_NO_CA_ERRORS \
+ (ACCEPT_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT \
+ | ACCEPT_X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN \
+ | ACCEPT_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY \
+ | ACCEPT_X509_V_ERR_CERT_UNTRUSTED \
+ | ACCEPT_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE \
+ | ACCEPT_X509_V_ERR_CERT_HAS_EXPIRED)
+
+static const char *ssl_cmd_verify_error_mask_add(cmd_parms *parms,
+ const char *token,
+ unsigned int *mask)
+{
+ if (strcEQ(token, "self-signed")) {
+ *mask |= ACCEPT_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
+ | ACCEPT_X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN;
+ }
+ else if (strcEQ(token, "untrusted-cert")) {
+ *mask |= ACCEPT_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
+ | ACCEPT_X509_V_ERR_CERT_UNTRUSTED;
+ }
+ else if (strcEQ(token, "invalid-signature")) {
+ *mask |= ACCEPT_X509_V_ERR_CERT_SIGNATURE_FAILURE
+ | ACCEPT_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE;
+ }
+ else if (strcEQ(token, "expired-cert")) {
+ *mask |= ACCEPT_X509_V_ERR_CERT_HAS_EXPIRED;
+ }
+ else if (strcEQ(token, "purpose-mismatch") || strcEQ(token, "X509_V_ERR_INVALID_PURPOSE")) {
+ *mask |= ACCEPT_X509_V_ERR_INVALID_PURPOSE;
+ }
+ else if (strcEQ(token, "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT")) {
+ *mask |= ACCEPT_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT;
+ }
+ else if (strcEQ(token, "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN")) {
+ *mask |= ACCEPT_X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN;
+ }
+ else if (strcEQ(token, "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY")) {
+ *mask |= ACCEPT_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY;
+ }
+ else if (strcEQ(token, "X509_V_ERR_CERT_UNTRUSTED")) {
+ *mask |= ACCEPT_X509_V_ERR_CERT_UNTRUSTED;
+ }
+ else if (strcEQ(token, "X509_V_ERR_CERT_SIGNATURE_FAILURE")) {
+ *mask |= ACCEPT_X509_V_ERR_CERT_SIGNATURE_FAILURE;
+ }
+ else if (strcEQ(token, "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE")) {
+ *mask |= ACCEPT_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE;
+ }
+ else if (strcEQ(token, "X509_V_ERR_CERT_HAS_EXPIRED")) {
+ *mask |= ACCEPT_X509_V_ERR_CERT_HAS_EXPIRED;
+ }
+ else if (strcEQ(token, "X509_V_ERR_CERT_NOT_YET_VALID")) {
+ *mask |= ACCEPT_X509_V_ERR_CERT_NOT_YET_VALID;
+ }
+ else {
+ return apr_pstrcat(parms->temp_pool, parms->cmd->name,
+ ": Invalid accepted-errors value '", token, "'",
+ NULL);
+ }
+
+ return NULL;
+}
+
+static const char *ssl_cmd_verify_error_mask_parse(cmd_parms *parms,
+ const char *arg,
+ unsigned int *mask)
+{
+ const char *token;
+ char *list;
+ const char *list_cursor;
+ const char *err;
+
+ *mask = 0;
+ list = apr_pstrdup(parms->temp_pool, arg);
+ list_cursor = list;
+
+ while (*list_cursor) {
+ token = ap_getword(parms->temp_pool, &list_cursor, ',');
+ if (!*token) {
+ return apr_pstrcat(parms->temp_pool, parms->cmd->name,
+ ": Invalid accepted-errors list",
+ NULL);
+ }
+ if ((err = ssl_cmd_verify_error_mask_add(parms, token, mask))) {
+ return err;
+ }
+ }
+
+ return NULL;
+}
+
const char *ssl_cmd_SSLVerifyClient(cmd_parms *cmd,
void *dcfg,
- const char *arg)
+ const char *arg1,
+ const char *arg2)
{
SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
ssl_verify_t mode = SSL_CVERIFY_NONE;
+ unsigned int error_mask = 0;
const char *err;
- if ((err = ssl_cmd_verify_parse(cmd, arg, &mode))) {
+ if ((err = ssl_cmd_verify_parse(cmd, arg1, &mode))) {
return err;
}
+ if (arg2 != NULL) {
+ if (mode == SSL_CVERIFY_NONE) {
+ return apr_pstrcat(cmd->temp_pool, cmd->cmd->name,
+ ": accepted-errors is not allowed when level is 'none'",
+ NULL);
+ }
+
+ if ((err = ssl_cmd_verify_error_mask_parse(cmd, arg2, &error_mask))) {
+ return err;
+ }
+ }
+ else if (mode == SSL_CVERIFY_OPTIONAL_NO_CA) {
+ error_mask = SSL_VERIFY_CLIENT_OPTIONAL_NO_CA_ERRORS;
+ }
+
if (cmd->path) {
dc->nVerifyClient = mode;
+ dc->nVerifyClientErrorMask = error_mask;
+ dc->nVerifyClientErrorMaskSet = TRUE;
}
else {
sc->server->auth.verify_mode = mode;
+ sc->server->auth.verify_error_mask = error_mask;
+ sc->server->auth.verify_error_mask_set = TRUE;
}
return NULL;
diff --git a/modules/ssl/ssl_engine_io.c b/modules/ssl/ssl_engine_io.c
index 2156ab40a49..826df6e4eea 100644
--- a/modules/ssl/ssl_engine_io.c
+++ b/modules/ssl/ssl_engine_io.c
@@ -1482,8 +1482,13 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx)
if ((verify_result != X509_V_OK) ||
sslconn->verify_error)
{
- if (ssl_verify_error_is_optional(verify_result) &&
- (sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
+ unsigned int verify_error_mask = sc->server->auth.verify_error_mask;
+
+ if (sslconn->dc && sslconn->dc->nVerifyClient != SSL_CVERIFY_UNSET) {
+ verify_error_mask = sslconn->dc->nVerifyClientErrorMask;
+ }
+
+ if (ssl_verify_error_is_accepted(verify_result, verify_error_mask))
{
/* leaving this log message as an error for the moment,
* according to the mod_ssl docs:
@@ -1496,7 +1501,7 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx)
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02009)
"SSL client authentication failed, "
"accepting certificate based on "
- "\"SSLVerifyClient optional_no_ca\" "
+ "\"SSLVerifyClient accepted-errors\" "
"configuration");
ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server);
diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c
index cb88f0112c6..44d6a74c280 100644
--- a/modules/ssl/ssl_engine_kernel.c
+++ b/modules/ssl/ssl_engine_kernel.c
@@ -1630,6 +1630,7 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
int errdepth = X509_STORE_CTX_get_error_depth(ctx);
int depth = UNSET;
int verify = SSL_CVERIFY_UNSET;
+ unsigned int verify_error_mask = 0;
/*
* Log verification information
@@ -1653,8 +1654,19 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
verify = dc->nVerifyClient;
}
}
- if (!dc || (verify == SSL_CVERIFY_UNSET)) {
- verify = mctx->auth.verify_mode;
+ if (conn->outgoing) {
+ if (!dc || (verify == SSL_CVERIFY_UNSET)) {
+ verify = mctx->auth.verify_mode;
+ }
+ }
+ else {
+ if (!dc || (verify == SSL_CVERIFY_UNSET)) {
+ verify = mctx->auth.verify_mode;
+ verify_error_mask = mctx->auth.verify_error_mask;
+ }
+ else {
+ verify_error_mask = dc->nVerifyClientErrorMask;
+ }
}
if (verify == SSL_CVERIFY_NONE) {
@@ -1666,7 +1678,7 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
return TRUE;
}
- if (ssl_verify_error_is_optional(errnum) &&
+ if (conn->outgoing && ssl_verify_error_is_optional(errnum) &&
(verify == SSL_CVERIFY_OPTIONAL_NO_CA))
{
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, conn, APLOGNO(02037)
@@ -1677,6 +1689,16 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
sslconn->verify_info = "GENEROUS";
ok = TRUE;
}
+ else if (!conn->outgoing && ssl_verify_error_is_accepted(errnum, verify_error_mask))
+ {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, conn, APLOGNO(02037)
+ "Certificate Verification: Verifiable Issuer is "
+ "configured as optional, therefore we're accepting "
+ "the certificate");
+
+ sslconn->verify_info = "GENEROUS";
+ ok = TRUE;
+ }
/*
* Expired certificates vs. "expired" CRLs: by default, OpenSSL
@@ -1714,7 +1736,8 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
/* If there was an optional verification error, it's not
* possible to perform OCSP validation since the issuer may be
* missing/untrusted. Fail in that case. */
- if (ssl_verify_error_is_optional(errnum)) {
+ if (conn->outgoing
+ && ssl_verify_error_is_optional(errnum)) {
X509_STORE_CTX_set_error(ctx, X509_V_ERR_APPLICATION_VERIFICATION);
errnum = X509_V_ERR_APPLICATION_VERIFICATION;
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn, APLOGNO(02038)
@@ -1722,6 +1745,16 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
"if issuer has not been verified "
"(optional_no_ca configured)");
ok = FALSE;
+ }
+ else if (!conn->outgoing
+ && ssl_verify_error_is_accepted(errnum, verify_error_mask)) {
+ X509_STORE_CTX_set_error(ctx, X509_V_ERR_APPLICATION_VERIFICATION);
+ errnum = X509_V_ERR_APPLICATION_VERIFICATION;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn, APLOGNO(02038)
+ "cannot perform OCSP validation for cert "
+ "if issuer has not been verified "
+ "(accepted-errors configured)");
+ ok = FALSE;
} else {
ok = modssl_verify_ocsp(ctx, sc, s, conn, conn->pool);
if (!ok) {
diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h
index 442b8b17ae4..63d7cfd6223 100644
--- a/modules/ssl/ssl_private.h
+++ b/modules/ssl/ssl_private.h
@@ -490,6 +490,27 @@ typedef enum {
|| (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE) \
|| (errnum == X509_V_ERR_CERT_HAS_EXPIRED))
+#define ACCEPT_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT (1U<<0)
+#define ACCEPT_X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN (1U<<1)
+#define ACCEPT_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (1U<<2)
+#define ACCEPT_X509_V_ERR_CERT_UNTRUSTED (1U<<3)
+#define ACCEPT_X509_V_ERR_CERT_SIGNATURE_FAILURE (1U<<4)
+#define ACCEPT_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE (1U<<5)
+#define ACCEPT_X509_V_ERR_INVALID_PURPOSE (1U<<6)
+#define ACCEPT_X509_V_ERR_CERT_HAS_EXPIRED (1U<<7)
+#define ACCEPT_X509_V_ERR_CERT_NOT_YET_VALID (1U<<8)
+
+#define ssl_verify_error_is_accepted(errnum, accepted_errors) \
+ ((errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && ((accepted_errors) & ACCEPT_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)) \
+ || (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN && ((accepted_errors) & ACCEPT_X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN)) \
+ || (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY && ((accepted_errors) & ACCEPT_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY)) \
+ || (errnum == X509_V_ERR_CERT_UNTRUSTED && ((accepted_errors) & ACCEPT_X509_V_ERR_CERT_UNTRUSTED)) \
+ || (errnum == X509_V_ERR_CERT_SIGNATURE_FAILURE && ((accepted_errors) & ACCEPT_X509_V_ERR_CERT_SIGNATURE_FAILURE)) \
+ || (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE && ((accepted_errors) & ACCEPT_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE)) \
+ || (errnum == X509_V_ERR_INVALID_PURPOSE && ((accepted_errors) & ACCEPT_X509_V_ERR_INVALID_PURPOSE)) \
+ || (errnum == X509_V_ERR_CERT_HAS_EXPIRED && ((accepted_errors) & ACCEPT_X509_V_ERR_CERT_HAS_EXPIRED)) \
+ || (errnum == X509_V_ERR_CERT_NOT_YET_VALID && ((accepted_errors) & ACCEPT_X509_V_ERR_CERT_NOT_YET_VALID)))
+
/**
* CRL checking mask (mode | flags)
*/
@@ -791,6 +812,8 @@ typedef struct {
/** for client or downstream server authentication */
int verify_depth;
ssl_verify_t verify_mode;
+ unsigned int verify_error_mask;
+ BOOL verify_error_mask_set;
/** TLSv1.3 has its separate cipher list, separate from the
settings for older TLS protocol versions. Since which one takes
@@ -926,6 +949,8 @@ struct SSLDirConfigRec {
ssl_opt_t nOptionsDel;
const char *szCipherSuite;
ssl_verify_t nVerifyClient;
+ unsigned int nVerifyClientErrorMask;
+ BOOL nVerifyClientErrorMaskSet;
int nVerifyDepth;
const char *szUserName;
apr_size_t nRenegBufferSize;
@@ -976,7 +1001,7 @@ const char *ssl_cmd_SSLHonorCipherOrder(cmd_parms *cmd, void *dcfg, int flag);
const char *ssl_cmd_SSLClientHelloVars(cmd_parms *, void *, int flag);
const char *ssl_cmd_SSLCompression(cmd_parms *, void *, int flag);
const char *ssl_cmd_SSLSessionTickets(cmd_parms *, void *, int flag);
-const char *ssl_cmd_SSLVerifyClient(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLVerifyClient(cmd_parms *, void *, const char *, const char *);
const char *ssl_cmd_SSLVerifyDepth(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLSessionCache(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLSessionCacheTimeout(cmd_parms *, void *, const char *);