diff --git a/docs/manual/mod/mod_ssl.xml b/docs/manual/mod/mod_ssl.xml index 43f477eb725..bea84de84c7 100644 --- a/docs/manual/mod/mod_ssl.xml +++ b/docs/manual/mod/mod_ssl.xml @@ -1458,6 +1458,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 6f89b1a0258..beee0263ee3 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 569cb26c4d5..f044ef86352 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 fcaf76310ba..cb1bbfb05d0 100644 --- a/modules/ssl/ssl_private.h +++ b/modules/ssl/ssl_private.h @@ -469,6 +469,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) @@ -781,6 +786,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 @@ -916,6 +922,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; @@ -967,6 +974,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 *);