Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 48 additions & 7 deletions docs/manual/mod/mod_ssl.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1421,7 +1421,7 @@ SSLCARevocationCheck chain no_crl_for_cert_ok
<directivesynopsis>
<name>SSLVerifyClient</name>
<description>Type of Client Certificate verification</description>
<syntax>SSLVerifyClient <var>level</var></syntax>
<syntax>SSLVerifyClient <var>level</var> [<var>accepted-errors</var>]</syntax>
<default>SSLVerifyClient none</default>
<contextlist><context>server config</context>
<context>virtual host</context>
Expand All @@ -1442,19 +1442,60 @@ before the HTTP response is sent.</p>
The following levels are available for <var>level</var>:</p>
<ul>
<li><strong>none</strong>:
no client Certificate is required at all</li>
no client certificate is required at all. When this level is used,
a second argument for <var>accepted-errors</var> is not permitted.</li>
<li><strong>optional</strong>:
the client <em>may</em> present a valid Certificate</li>
the client <em>may</em> present a valid certificate</li>
<li><strong>require</strong>:
the client <em>has to</em> present a valid Certificate</li>
the client <em>has to</em> present a valid certificate</li>
<li><strong>optional_no_ca</strong>:
the client may present a valid Certificate<br />
the client may present a valid certificate<br />
but it need not to be (successfully) verifiable. This option
cannot be relied upon for client authentication. </li>
cannot be relied upon for client authentication. This is now equivalent to
<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>
</ul>
<p>
The optional second argument <var>accepted-errors</var> 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 <var>accepted-errors</var> weakens client certificate verification and
should not be used in production deployments.
The following shorthand names are available for <var>accepted-errors</var>:</p>
<ul>
<li><strong>self-signed</strong>:
Accepts <code>X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT</code> and
<code>X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN</code>.</li>
<li><strong>untrusted-cert</strong>:
Accepts <code>X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY</code> and
<code>X509_V_ERR_CERT_UNTRUSTED</code>.</li>
<li><strong>invalid-signature</strong>:
Accepts <code>X509_V_ERR_CERT_SIGNATURE_FAILURE</code> and
<code>X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE</code>.</li>
<li><strong>expired-cert</strong>:
Accepts <code>X509_V_ERR_CERT_HAS_EXPIRED</code> and
<code>X509_V_ERR_CERT_NOT_YET_VALID</code>.</li>
<li><strong>purpose-mismatch</strong>:
Accepts <code>X509_V_ERR_INVALID_PURPOSE</code>.</li>
</ul>
<p>
Alternatively, any of the following OpenSSL X509 verification error names can be used directly,
such as <code>X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT</code>,
<code>X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN</code>,
<code>X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY</code>,
<code>X509_V_ERR_CERT_UNTRUSTED</code>,
<code>X509_V_ERR_CERT_SIGNATURE_FAILURE</code>,
<code>X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE</code>,
<code>X509_V_ERR_CERT_HAS_EXPIRED</code>,
<code>X509_V_ERR_CERT_NOT_YET_VALID</code>, or
<code>X509_V_ERR_INVALID_PURPOSE</code>.</p>
<example><title>Example</title>
<highlight language="config">
SSLVerifyClient require
SSLVerifyClient require purpose-mismatch
</highlight>
</example>
<example><title>Example with accepted errors</title>
<highlight language="config">
SSLVerifyClient optional self-signed,untrusted-cert,purpose-mismatch
</highlight>
</example>
</usage>
Expand Down
5 changes: 3 additions & 2 deletions modules/ssl/mod_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand Down
136 changes: 134 additions & 2 deletions modules/ssl/ssl_engine_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
11 changes: 8 additions & 3 deletions modules/ssl/ssl_engine_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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);

Expand Down
41 changes: 37 additions & 4 deletions modules/ssl/ssl_engine_kernel.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -1714,14 +1736,25 @@ 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)
"cannot perform OCSP validation for cert "
"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) {
Expand Down
Loading