Skip to content

Commit 3a71706

Browse files
committed
Add a second argument to SSLVerifyClient to allow for specific TLS verification errors to be ignored.
1 parent a990599 commit 3a71706

6 files changed

Lines changed: 256 additions & 19 deletions

File tree

docs/manual/mod/mod_ssl.xml

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,7 +1421,7 @@ SSLCARevocationCheck chain no_crl_for_cert_ok
14211421
<directivesynopsis>
14221422
<name>SSLVerifyClient</name>
14231423
<description>Type of Client Certificate verification</description>
1424-
<syntax>SSLVerifyClient <var>level</var></syntax>
1424+
<syntax>SSLVerifyClient <var>level</var> [<var>accepted-errors</var>]</syntax>
14251425
<default>SSLVerifyClient none</default>
14261426
<contextlist><context>server config</context>
14271427
<context>virtual host</context>
@@ -1442,19 +1442,60 @@ before the HTTP response is sent.</p>
14421442
The following levels are available for <var>level</var>:</p>
14431443
<ul>
14441444
<li><strong>none</strong>:
1445-
no client Certificate is required at all</li>
1445+
no client certificate is required at all. When this level is used,
1446+
a second argument for <var>accepted-errors</var> is not permitted.</li>
14461447
<li><strong>optional</strong>:
1447-
the client <em>may</em> present a valid Certificate</li>
1448+
the client <em>may</em> present a valid certificate</li>
14481449
<li><strong>require</strong>:
1449-
the client <em>has to</em> present a valid Certificate</li>
1450+
the client <em>has to</em> present a valid certificate</li>
14501451
<li><strong>optional_no_ca</strong>:
1451-
the client may present a valid Certificate<br />
1452+
the client may present a valid certificate<br />
14521453
but it need not to be (successfully) verifiable. This option
1453-
cannot be relied upon for client authentication. </li>
1454+
cannot be relied upon for client authentication. This is now equivalent to
1455+
<code>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</code>.</li>
14541456
</ul>
1457+
<p>
1458+
The optional second argument <var>accepted-errors</var> can be used to specify a
1459+
comma-separated list of verification errors that should be accepted, even if the
1460+
verification level would otherwise reject the certificate.
1461+
Use of <var>accepted-errors</var> weakens client certificate verification and
1462+
should not be used in production deployments.
1463+
The following shorthand names are available for <var>accepted-errors</var>:</p>
1464+
<ul>
1465+
<li><strong>self-signed</strong>:
1466+
Accepts <code>X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT</code> and
1467+
<code>X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN</code>.</li>
1468+
<li><strong>untrusted-cert</strong>:
1469+
Accepts <code>X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY</code> and
1470+
<code>X509_V_ERR_CERT_UNTRUSTED</code>.</li>
1471+
<li><strong>invalid-signature</strong>:
1472+
Accepts <code>X509_V_ERR_CERT_SIGNATURE_FAILURE</code> and
1473+
<code>X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE</code>.</li>
1474+
<li><strong>expired-cert</strong>:
1475+
Accepts <code>X509_V_ERR_CERT_HAS_EXPIRED</code> and
1476+
<code>X509_V_ERR_CERT_NOT_YET_VALID</code>.</li>
1477+
<li><strong>purpose-mismatch</strong>:
1478+
Accepts <code>X509_V_ERR_INVALID_PURPOSE</code>.</li>
1479+
</ul>
1480+
<p>
1481+
Alternatively, any of the following OpenSSL X509 verification error names can be used directly,
1482+
such as <code>X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT</code>,
1483+
<code>X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN</code>,
1484+
<code>X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY</code>,
1485+
<code>X509_V_ERR_CERT_UNTRUSTED</code>,
1486+
<code>X509_V_ERR_CERT_SIGNATURE_FAILURE</code>,
1487+
<code>X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE</code>,
1488+
<code>X509_V_ERR_CERT_HAS_EXPIRED</code>,
1489+
<code>X509_V_ERR_CERT_NOT_YET_VALID</code>, or
1490+
<code>X509_V_ERR_INVALID_PURPOSE</code>.</p>
14551491
<example><title>Example</title>
14561492
<highlight language="config">
1457-
SSLVerifyClient require
1493+
SSLVerifyClient require purpose-mismatch
1494+
</highlight>
1495+
</example>
1496+
<example><title>Example with accepted errors</title>
1497+
<highlight language="config">
1498+
SSLVerifyClient optional self-signed,untrusted-cert,purpose-mismatch
14581499
</highlight>
14591500
</example>
14601501
</usage>

modules/ssl/mod_ssl.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,10 @@ static const command_rec ssl_config_cmds[] = {
149149
"('/path/to/file' - PEM encoded)")
150150
SSL_CMD_SRV(CARevocationCheck, RAW_ARGS,
151151
"SSL CA Certificate Revocation List (CRL) checking mode")
152-
SSL_CMD_ALL(VerifyClient, TAKE1,
152+
SSL_CMD_ALL(VerifyClient, TAKE12,
153153
"SSL Client verify type "
154-
"('none', 'optional', 'require', 'optional_no_ca')")
154+
"('none', 'optional', 'require', 'optional_no_ca' "
155+
"[accepted-errors])")
155156
SSL_CMD_ALL(VerifyDepth, TAKE1,
156157
"SSL Client verify depth "
157158
"('N' - number of intermediate certificates)")

modules/ssl/ssl_engine_config.c

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ static void modssl_ctx_init(modssl_ctx_t *mctx, apr_pool_t *p)
138138
mctx->auth.cipher_suite = NULL;
139139
mctx->auth.verify_depth = UNSET;
140140
mctx->auth.verify_mode = SSL_CVERIFY_UNSET;
141+
mctx->auth.verify_error_mask = 0;
142+
mctx->auth.verify_error_mask_set = FALSE;
141143
mctx->auth.tls13_ciphers = NULL;
142144

143145
mctx->ocsp_mask = UNSET;
@@ -284,6 +286,14 @@ static void modssl_ctx_cfg_merge(apr_pool_t *p,
284286
cfgMergeString(auth.cipher_suite);
285287
cfgMergeInt(auth.verify_depth);
286288
cfgMerge(auth.verify_mode, SSL_CVERIFY_UNSET);
289+
if (add->auth.verify_error_mask_set) {
290+
mrg->auth.verify_error_mask = add->auth.verify_error_mask;
291+
mrg->auth.verify_error_mask_set = TRUE;
292+
}
293+
else {
294+
mrg->auth.verify_error_mask = base->auth.verify_error_mask;
295+
mrg->auth.verify_error_mask_set = base->auth.verify_error_mask_set;
296+
}
287297
cfgMergeString(auth.tls13_ciphers);
288298

289299
cfgMergeInt(ocsp_mask);
@@ -405,6 +415,8 @@ void *ssl_config_perdir_create(apr_pool_t *p, char *dir)
405415

406416
dc->szCipherSuite = NULL;
407417
dc->nVerifyClient = SSL_CVERIFY_UNSET;
418+
dc->nVerifyClientErrorMask = 0;
419+
dc->nVerifyClientErrorMaskSet = FALSE;
408420
dc->nVerifyDepth = UNSET;
409421

410422
dc->szUserName = NULL;
@@ -461,6 +473,14 @@ void *ssl_config_perdir_merge(apr_pool_t *p, void *basev, void *addv)
461473

462474
cfgMergeString(szCipherSuite);
463475
cfgMerge(nVerifyClient, SSL_CVERIFY_UNSET);
476+
if (add->nVerifyClientErrorMaskSet) {
477+
mrg->nVerifyClientErrorMask = add->nVerifyClientErrorMask;
478+
mrg->nVerifyClientErrorMaskSet = TRUE;
479+
}
480+
else {
481+
mrg->nVerifyClientErrorMask = base->nVerifyClientErrorMask;
482+
mrg->nVerifyClientErrorMaskSet = base->nVerifyClientErrorMaskSet;
483+
}
464484
cfgMergeInt(nVerifyDepth);
465485

466486
cfgMergeString(szUserName);
@@ -1298,24 +1318,136 @@ static const char *ssl_cmd_verify_parse(cmd_parms *parms,
12981318
return NULL;
12991319
}
13001320

1321+
#define SSL_VERIFY_CLIENT_OPTIONAL_NO_CA_ERRORS \
1322+
(ACCEPT_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT \
1323+
| ACCEPT_X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN \
1324+
| ACCEPT_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY \
1325+
| ACCEPT_X509_V_ERR_CERT_UNTRUSTED \
1326+
| ACCEPT_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE \
1327+
| ACCEPT_X509_V_ERR_CERT_HAS_EXPIRED)
1328+
1329+
static const char *ssl_cmd_verify_error_mask_add(cmd_parms *parms,
1330+
const char *token,
1331+
unsigned int *mask)
1332+
{
1333+
if (strcEQ(token, "self-signed")) {
1334+
*mask |= ACCEPT_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
1335+
| ACCEPT_X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN;
1336+
}
1337+
else if (strcEQ(token, "untrusted-cert")) {
1338+
*mask |= ACCEPT_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
1339+
| ACCEPT_X509_V_ERR_CERT_UNTRUSTED;
1340+
}
1341+
else if (strcEQ(token, "invalid-signature")) {
1342+
*mask |= ACCEPT_X509_V_ERR_CERT_SIGNATURE_FAILURE
1343+
| ACCEPT_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE;
1344+
}
1345+
else if (strcEQ(token, "expired-cert")) {
1346+
*mask |= ACCEPT_X509_V_ERR_CERT_HAS_EXPIRED;
1347+
}
1348+
else if (strcEQ(token, "purpose-mismatch") || strcEQ(token, "X509_V_ERR_INVALID_PURPOSE")) {
1349+
*mask |= ACCEPT_X509_V_ERR_INVALID_PURPOSE;
1350+
}
1351+
else if (strcEQ(token, "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT")) {
1352+
*mask |= ACCEPT_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT;
1353+
}
1354+
else if (strcEQ(token, "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN")) {
1355+
*mask |= ACCEPT_X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN;
1356+
}
1357+
else if (strcEQ(token, "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY")) {
1358+
*mask |= ACCEPT_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY;
1359+
}
1360+
else if (strcEQ(token, "X509_V_ERR_CERT_UNTRUSTED")) {
1361+
*mask |= ACCEPT_X509_V_ERR_CERT_UNTRUSTED;
1362+
}
1363+
else if (strcEQ(token, "X509_V_ERR_CERT_SIGNATURE_FAILURE")) {
1364+
*mask |= ACCEPT_X509_V_ERR_CERT_SIGNATURE_FAILURE;
1365+
}
1366+
else if (strcEQ(token, "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE")) {
1367+
*mask |= ACCEPT_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE;
1368+
}
1369+
else if (strcEQ(token, "X509_V_ERR_CERT_HAS_EXPIRED")) {
1370+
*mask |= ACCEPT_X509_V_ERR_CERT_HAS_EXPIRED;
1371+
}
1372+
else if (strcEQ(token, "X509_V_ERR_CERT_NOT_YET_VALID")) {
1373+
*mask |= ACCEPT_X509_V_ERR_CERT_NOT_YET_VALID;
1374+
}
1375+
else {
1376+
return apr_pstrcat(parms->temp_pool, parms->cmd->name,
1377+
": Invalid accepted-errors value '", token, "'",
1378+
NULL);
1379+
}
1380+
1381+
return NULL;
1382+
}
1383+
1384+
static const char *ssl_cmd_verify_error_mask_parse(cmd_parms *parms,
1385+
const char *arg,
1386+
unsigned int *mask)
1387+
{
1388+
const char *token;
1389+
char *list;
1390+
const char *list_cursor;
1391+
const char *err;
1392+
1393+
*mask = 0;
1394+
list = apr_pstrdup(parms->temp_pool, arg);
1395+
list_cursor = list;
1396+
1397+
while (*list_cursor) {
1398+
token = ap_getword(parms->temp_pool, &list_cursor, ',');
1399+
if (!*token) {
1400+
return apr_pstrcat(parms->temp_pool, parms->cmd->name,
1401+
": Invalid accepted-errors list",
1402+
NULL);
1403+
}
1404+
if ((err = ssl_cmd_verify_error_mask_add(parms, token, mask))) {
1405+
return err;
1406+
}
1407+
}
1408+
1409+
return NULL;
1410+
}
1411+
13011412
const char *ssl_cmd_SSLVerifyClient(cmd_parms *cmd,
13021413
void *dcfg,
1303-
const char *arg)
1414+
const char *arg1,
1415+
const char *arg2)
13041416
{
13051417
SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
13061418
SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
13071419
ssl_verify_t mode = SSL_CVERIFY_NONE;
1420+
unsigned int error_mask = 0;
13081421
const char *err;
13091422

1310-
if ((err = ssl_cmd_verify_parse(cmd, arg, &mode))) {
1423+
if ((err = ssl_cmd_verify_parse(cmd, arg1, &mode))) {
13111424
return err;
13121425
}
13131426

1427+
if (arg2 != NULL) {
1428+
if (mode == SSL_CVERIFY_NONE) {
1429+
return apr_pstrcat(cmd->temp_pool, cmd->cmd->name,
1430+
": accepted-errors is not allowed when level is 'none'",
1431+
NULL);
1432+
}
1433+
1434+
if ((err = ssl_cmd_verify_error_mask_parse(cmd, arg2, &error_mask))) {
1435+
return err;
1436+
}
1437+
}
1438+
else if (mode == SSL_CVERIFY_OPTIONAL_NO_CA) {
1439+
error_mask = SSL_VERIFY_CLIENT_OPTIONAL_NO_CA_ERRORS;
1440+
}
1441+
13141442
if (cmd->path) {
13151443
dc->nVerifyClient = mode;
1444+
dc->nVerifyClientErrorMask = error_mask;
1445+
dc->nVerifyClientErrorMaskSet = TRUE;
13161446
}
13171447
else {
13181448
sc->server->auth.verify_mode = mode;
1449+
sc->server->auth.verify_error_mask = error_mask;
1450+
sc->server->auth.verify_error_mask_set = TRUE;
13191451
}
13201452

13211453
return NULL;

modules/ssl/ssl_engine_io.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,8 +1482,13 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx)
14821482
if ((verify_result != X509_V_OK) ||
14831483
sslconn->verify_error)
14841484
{
1485-
if (ssl_verify_error_is_optional(verify_result) &&
1486-
(sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
1485+
unsigned int verify_error_mask = sc->server->auth.verify_error_mask;
1486+
1487+
if (sslconn->dc && sslconn->dc->nVerifyClient != SSL_CVERIFY_UNSET) {
1488+
verify_error_mask = sslconn->dc->nVerifyClientErrorMask;
1489+
}
1490+
1491+
if (ssl_verify_error_is_accepted(verify_result, verify_error_mask))
14871492
{
14881493
/* leaving this log message as an error for the moment,
14891494
* according to the mod_ssl docs:
@@ -1496,7 +1501,7 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx)
14961501
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02009)
14971502
"SSL client authentication failed, "
14981503
"accepting certificate based on "
1499-
"\"SSLVerifyClient optional_no_ca\" "
1504+
"\"SSLVerifyClient accepted-errors\" "
15001505
"configuration");
15011506
ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server);
15021507

modules/ssl/ssl_engine_kernel.c

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,7 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
16301630
int errdepth = X509_STORE_CTX_get_error_depth(ctx);
16311631
int depth = UNSET;
16321632
int verify = SSL_CVERIFY_UNSET;
1633+
unsigned int verify_error_mask = 0;
16331634

16341635
/*
16351636
* Log verification information
@@ -1653,8 +1654,19 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
16531654
verify = dc->nVerifyClient;
16541655
}
16551656
}
1656-
if (!dc || (verify == SSL_CVERIFY_UNSET)) {
1657-
verify = mctx->auth.verify_mode;
1657+
if (conn->outgoing) {
1658+
if (!dc || (verify == SSL_CVERIFY_UNSET)) {
1659+
verify = mctx->auth.verify_mode;
1660+
}
1661+
}
1662+
else {
1663+
if (!dc || (verify == SSL_CVERIFY_UNSET)) {
1664+
verify = mctx->auth.verify_mode;
1665+
verify_error_mask = mctx->auth.verify_error_mask;
1666+
}
1667+
else {
1668+
verify_error_mask = dc->nVerifyClientErrorMask;
1669+
}
16581670
}
16591671

16601672
if (verify == SSL_CVERIFY_NONE) {
@@ -1666,7 +1678,7 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
16661678
return TRUE;
16671679
}
16681680

1669-
if (ssl_verify_error_is_optional(errnum) &&
1681+
if (conn->outgoing && ssl_verify_error_is_optional(errnum) &&
16701682
(verify == SSL_CVERIFY_OPTIONAL_NO_CA))
16711683
{
16721684
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)
16771689
sslconn->verify_info = "GENEROUS";
16781690
ok = TRUE;
16791691
}
1692+
else if (!conn->outgoing && ssl_verify_error_is_accepted(errnum, verify_error_mask))
1693+
{
1694+
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, conn, APLOGNO(02037)
1695+
"Certificate Verification: Verifiable Issuer is "
1696+
"configured as optional, therefore we're accepting "
1697+
"the certificate");
1698+
1699+
sslconn->verify_info = "GENEROUS";
1700+
ok = TRUE;
1701+
}
16801702

16811703
/*
16821704
* Expired certificates vs. "expired" CRLs: by default, OpenSSL
@@ -1714,14 +1736,25 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
17141736
/* If there was an optional verification error, it's not
17151737
* possible to perform OCSP validation since the issuer may be
17161738
* missing/untrusted. Fail in that case. */
1717-
if (ssl_verify_error_is_optional(errnum)) {
1739+
if (conn->outgoing
1740+
&& ssl_verify_error_is_optional(errnum)) {
17181741
X509_STORE_CTX_set_error(ctx, X509_V_ERR_APPLICATION_VERIFICATION);
17191742
errnum = X509_V_ERR_APPLICATION_VERIFICATION;
17201743
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn, APLOGNO(02038)
17211744
"cannot perform OCSP validation for cert "
17221745
"if issuer has not been verified "
17231746
"(optional_no_ca configured)");
17241747
ok = FALSE;
1748+
}
1749+
else if (!conn->outgoing
1750+
&& ssl_verify_error_is_accepted(errnum, verify_error_mask)) {
1751+
X509_STORE_CTX_set_error(ctx, X509_V_ERR_APPLICATION_VERIFICATION);
1752+
errnum = X509_V_ERR_APPLICATION_VERIFICATION;
1753+
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn, APLOGNO(02038)
1754+
"cannot perform OCSP validation for cert "
1755+
"if issuer has not been verified "
1756+
"(accepted-errors configured)");
1757+
ok = FALSE;
17251758
} else {
17261759
ok = modssl_verify_ocsp(ctx, sc, s, conn, conn->pool);
17271760
if (!ok) {

0 commit comments

Comments
 (0)