Bug 35083 - Certificate validation problems trapping
Summary: Certificate validation problems trapping
Status: RESOLVED WONTFIX
Alias: None
Product: Apache httpd-2
Classification: Unclassified
Component: mod_ssl (show other bugs)
Version: 2.0.54
Hardware: All All
: P3 enhancement with 53 votes (vote)
Target Milestone: ---
Assignee: Apache HTTPD Bugs Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2005-05-26 10:09 UTC by Marc Stern
Modified: 2016-07-07 12:01 UTC (History)
4 users (show)



Attachments
Patch based on SSL_CVERIFY_OPTIONAL_NO_CA (6.38 KB, patch)
2007-01-25 04:26 UTC, Marc Stern
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Marc Stern 2005-05-26 10:09:52 UTC
In case a SSL connection fails because a certificate is expired, or a CRL is
unavailable, etc., the browser receives a SSL error that results in a cryptic
technical error displayed to the user - sometimes only an error number like in
Firefox. In such a situation, the SSL connection could be established, and an
additional module could trap the exact SSL error and redirect to a page with the
specific error message ("Your certificate is expired", "We cannot check the
validity of the certificate - retry later", etc.).

I developed such a module, that I'll submit today: mod_ssl_error

In order to let the module trap the code, we need, when the module is loaded, to
accept to establish the connection in case of a certificate validation error:

In ssl_io_filter_connect( ) - ssl_engine_io.c - we have 2 cases (at line 1147
and 1173) where the connection may break because of certificates
verification/validation problem:  ' return ssl_filter_io_shutdown(filter_ctx, c,
1); '

I would return only if the error trapping module (mod_ssl_error) is not loaded.
If it is loaded, I would accept the certificate (continue the treatment and
return DECLINED), as the error will be trapped later.
So, replace
    return ssl_filter_io_shutdown(filter_ctx, c, 1); 
by
     if ( ! is_ssl_error_loaded ) return ssl_filter_io_shutdown(filter_ctx, c, 1); 

In order to check if the module is loaded, I need a few lines at the beginning
of the function - unless a function exists to check if a module is loaded ?
Currently I coded it in the function:
    BOOL is_ssl_error_loaded = FALSE;
    { /* Check if mod_ssl_error is loaded */
        extern AP_DECLARE_DATA module *ap_top_module;
        module *modp;
        for ( modp = ap_top_module; modp; modp = modp->next )
            if ( strcmp(modp->name, "mod_ssl_error.c") == 0 ) {
                is_ssl_error_loaded = TRUE;
                break;
            }
    }
Comment 1 Paul Querna 2005-05-27 21:05:47 UTC
I don't believe we should write something specific for your module (eg checking
for the exact name of your module).

If we want to provide this, I think we should look at integrating that module
with the core mod_ssl.  The other alternative is to just provide a configuration
directive to not return.  This might be the best way to go.

Something like:

SSLFatalErrors Off

Thoughts?
Comment 2 Marc Stern 2005-05-30 07:36:38 UTC
The best would obviously to integrate it with the core mod_ssl.

The main goal was to avoid a misconfiguration that would accept wrong
certificates; this could be the case with the directive.

If we want to keep the compatibility with the curretn version (SSL error not
remapped), maybe the best would be to put my module in the standard distribution
and check if it is loaded, no ? This ensures that non fatal errors are only
passing when a module is loaded to trap them later.

Maybe I'm too paranoiac ... or experienced ;-)
Comment 3 Marc Stern 2005-06-01 10:21:39 UTC
Addition to trap when no certif is sent (user hits Esc in browser):

+ orig_verify_mode = filter_ctx->pssl->verify_mode;
+ if ( sslErrorRedirected )
+    filter_ctx->pssl->verify_mode &= ~SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
 if ((n = SSL_accept(filter_ctx->pssl)) <= 0) {

...

 verify_result = SSL_get_verify_result(filter_ctx->pssl);

+ if ( orig_verify_mode != filter_ctx->pssl->verify_mode ) {
+    verify_result = X509_V_ERR_CERT_REJECTED;
+    sslconn->verify_error = X509_verify_cert_error_string(verify_result);
+ }

 if ((verify_result != X509_V_OK) ||


The choice of X509_V_ERR_CERT_REJECTED as error is purely arbitrary, any better
one ?
Comment 4 Marc Stern 2005-06-01 11:15:22 UTC
Sorry, I forgot something.
Here is the correct version:

+ orig_verify_mode = filter_ctx->pssl->verify_mode;
+ if ( sslErrorRedirected )
+    filter_ctx->pssl->verify_mode &= ~SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
 if ((n = SSL_accept(filter_ctx->pssl)) <= 0) {

...

 verify_result = SSL_get_verify_result(filter_ctx->pssl);

+ if ( (orig_verify_mode != filter_ctx->pssl->verify_mode) &&
+      !filter_ctx->pssl->session->peer ) {
+    verify_result = X509_V_ERR_CERT_REJECTED;
+    sslconn->verify_error = X509_verify_cert_error_string(verify_result);
+ }

 if ((verify_result != X509_V_OK) ||
Comment 5 Daniel Risacher 2005-08-17 04:20:52 UTC
(In reply to comment #2)
> The best would obviously to integrate it with the core mod_ssl.
> 

I agree strongly.  The current behavior of mod_ssl is significantly broken for
client certificates.  
Comment 6 Joe Orton 2005-08-17 12:29:16 UTC
I'm not convinced this is necessary.  mod_ssl should be reporting errors at
TLS-level for TLS-level problems like bad certs.  If browsers handle that badly
then fix the browsers.

If you do want custom HTML responses for cert validation problems, it should be
possible already using "SSLVerifyClient optional" with SSLRequire and a custom
403 errordoc.  What about the current code prevents that?  Possibly more
information needs to be exported to get to the "certificate has expired" stuff.
 2.1's SSL_CLIENT_V_REMAIN might be sufficient for that.
Comment 7 Marc Stern 2005-08-31 09:31:46 UTC
Joe,

1. I do not think we could blame the browsers (at least not totally), because
they do not receive enough information to display a error complete mesage.

2. Although Apache strictly follows the standards, its main goal is to be used
by B2B-like clients, AND BROWSERS, no ?
There is no one browser that implements SSL error trapping in another way that
giving a technical error message or number

3. In a lot of environments, the Web admin would like to give more information
to the user about how to solve the connection problem. This is especially true
in governmental sites: most of European countries (and several other ones) are
deploying identity smart card with national certificates that are intended to
bare citizens that do not understand anything about certificates. The error
message to be given is highly specific in this case.

4. Although not stressed in the module description, the module also allows some
reporting of errors in a way governmental applications like a lot: their
application can receive (and so handle/log) any connection failure with the
exact reason. Again, for governmental certificates, this is highly important
because of the OCSP SLA, etc.
This could be achieved in a separate application/script by analysing the logs,
although the needed log level would probably generate to much data for a
production environment.

> If you do want custom HTML responses for cert validation problems, it should
> be possible already using "SSLVerifyClient optional" with SSLRequire and
> a custom 403 errordoc.
> What about the current code prevents that?
> Possibly more information needs to be exported to get to the
> "certificate has expired" stuff.
> 2.1's SSL_CLIENT_V_REMAIN might be sufficient for that.
I tried, but couldn't do that (at least in 2.0.54). Revoked certificates are
accepted and ther's no way to trap it.
If we could trap the errors without modifying med_ssl core, then it's obviously
perfect. My module could thus be reimplemented to use that new
SSL_CLIENT_V_REMAIN, is that correct ?
Comment 8 Joe Orton 2005-08-31 12:33:22 UTC
The specific configuration I'm talking about would be:

  SSLVerifyClient optional_no_ca
  SSLRequire %{SSL_VERIFY_CLIENT} eq "SUCCESS"
  ErrorDocument 403 /bzzt.html

but, I guess for the case where the cert has been revoked by a CRL, or the cert
has expired, this is not sufficient, since the handshake will fail in those cases.

So the minimal enhancement that I think is acceptable is to have more
fine-grained failure modes for SSLVerifyClient.  e.g.

  SSLVerifyCLient optional_revoked
  SSLVerifyClient optional_expired

or something like that.  Maybe ideally it would be possible to combine such
options perhaps, allowing

  SSLVerifyClient optional no_ca revoked expired

or using a separate directive:

  SSLVerifyClient optional
  SSLVerifyIgnoreFailures no_ca revoked expired
  
I'm not sure about the best UI here.

So I think a patch for something like this would be acceptable and is the best
way to implement this feature.  This is critical code and has a bad security
history though so it needs to be done carefully.
Comment 9 Marc Stern 2005-08-31 12:56:43 UTC
>   SSLVerifyClient optional_no_ca
>   SSLRequire %{SSL_VERIFY_CLIENT} eq "SUCCESS"
>   ErrorDocument 403 /bzzt.html
How would the application know about the exact status, in order to display a
relevant error message ?
What is the reason for error 403: authorisation failure, certificate revocation,
certificate expiration ?
Can the relevant info systematically be added to an environment variable ?

If you look the mod_ssl_error, it adds all information to GET data.
Maybe a stupid question, but does the "SSLVerifyClient optional" directive add
any added value to mod_ssl_error ? If not, couldn't we imagine to completely
replace this directive by mod_ssl_error which is more general ?
Comment 10 Joe Orton 2005-08-31 15:08:15 UTC
Yes, put any required info into env vars so that an SSI page or whatever can
retrieve that stuff directly.  The redirect-to-custom-URI-with-QUERY_URI thing
used by mod_ssl_error is just a nasty hack; do it with env vars.
Comment 11 Marc Stern 2005-09-06 10:25:45 UTC
I would like to be sure to not introduce a security risk because of a bad
configuration: suppose we use something like "SSLVerifyClient optional_no_ca"
and trap the error by activating another directive; if the administrator removes
the error trapping, the certificate would be accepted.
This is obviously the administrator's fault, but you know about the security
failure principle.
Shouldn't we replace the check for "SSLVerifyClient optional_no_ca ..." by a
check "is error trapping activated" ? Currently, the check controls if the
module is loaded (thus activated), but we could imagine a general directive
"SSLTrapCertifErrors".

Btw, what was the original intend of the "SSLVerifyClient  optional_no_ca"
directive ? Is it really used ? Because, if we use it as a flag to trap the
error, we cannot use the "old way" anymore, so we break the compatibility.

To recap, does the following seem better:
 - we include the whole handling in mod_ssl (no external module)
 - we add a general directive "SSLTrapCertifErrors"
 - we put all extra info (error, DN, serial, ...) in environment variables

If you prefer a separate module, how to activate it in without either explicitly
patching mod_ssl, or accepting all certrificates error in mod_ssl - which I find
dangerous as explained above ?
Comment 12 Joe Orton 2005-09-06 13:45:36 UTC
 - we include the whole handling in mod_ssl (no external module)

I'm not sure what you mean by that.

 - we add a general directive "SSLTrapCertifErrors"

I am OK with a new directive which allows the admin to relax certain SSL
certificate verification errors such as recovation status, expiry status, etc.

 - we put all extra info (error, DN, serial, ...) in environment variables

Yes.  DN and serial are already available in env vars of course.
Comment 13 Marc Stern 2005-09-06 14:48:49 UTC
>  - we include the whole handling in mod_ssl (no external module)
> I'm not sure what you mean by that.
I mean it becomes part of mod_ssl, it is not a separate module.
To be honest, I designed it as a module to have minimal impact on mod_ssl, in
order to speed up acceptance - bad guess.

>  - we add a general directive "SSLTrapCertifErrors"
> I am OK with a new directive which allows the admin to relax certain SSL
> certificate verification errors such as recovation status, expiry status, etc.
This does not relax anything, it just replaces a SSL error by a HTTP error (or a
custom page).
If needed, "SSLVerifyClient optional_no_ca ..." can still be used.
If it has any utility (?)
Comment 14 Marc Stern 2005-11-24 11:13:44 UTC
While waiting for the best integration as requested, here is how to integrate it
correctly with 2.0.54 (and probably above) - I added some other traps during
renegociation.

mod_ssl.h: add
extern int sslErrorRedirected;

ssl_engine_init.c: add
int sslErrorRedirected = -1;

in ssl_engine_io.c:
at the beginning of ssl_io_filter_connect(): add
if ( sslErrorRedirected == -1 ) { /* Check if mod_ssl_error is loaded */
   extern AP_DECLARE_DATA module *ap_top_module;
   module *modp;
   sslErrorRedirected = FALSE;
   for ( modp = ap_top_module; modp; modp = modp->next )
      if ( strcmp(modp->name, "mod_ssl_error.c") == 0 ) {
         sslErrorRedirected = TRUE;
         break;
      }
}

in ssl_engine_io.c:
in ssl_io_filter_connect(), after
   orig_verify_mode = filter_ctx->pssl->verify_mode;
/* Accept if no certs are sent (user hits Esc) */
if ( sslErrorRedirected ) filter_ctx->pssl->verify_mode &=
~SSL_VERIFY_FAIL_IF_NO_PEER_CERT;

in ssl_engine_io.c:
in ssl_io_filter_connect(), replace
if (ssl_verify_error_is_optional(verify_result) && ...) {
   ...
}
else {
   return ssl_filter_io_shutdown(filter_ctx, c, 1);
}
by
if (ssl_verify_error_is_optional(verify_result) && ...) {
   ...
}
else {
   if ( sslErrorRedirected ) return APR_SUCCESS;
   return ssl_filter_io_shutdown(filter_ctx, c, 1);
}


in ssl_engine_io.c:
in ssl_io_filter_connect(), replace
if ((sc->server->auth.verify_mode == SSL_CVERIFY_REQUIRE) && ... ){
   return ssl_filter_io_shutdown(filter_ctx, c, 1);
}
by if ((sc->server->auth.verify_mode == SSL_CVERIFY_REQUIRE) && ... ){
   if ( sslErrorRedirected ) return APR_SUCCESS;
   return ssl_filter_io_shutdown(filter_ctx, c, 1);
}

in ssl_engine_kernel.c: replace
SSL_do_handshake(ssl);
if (SSL_get_state(ssl) != SSL_ST_OK) {
by
SSL_do_handshake(ssl);
if (!sslErrorRedirected && SSL_get_state(ssl) != SSL_ST_OK) {

in ssl_engine_kernel.c in ssl_hook_Access(): replace
if (do_verify && (SSL_get_verify_result(ssl) != X509_V_OK)) {
   ...
   return HTTP_FORBIDDEN;
by
if (do_verify && (SSL_get_verify_result(ssl) != X509_V_OK)) {
   ...
   if (!sslErrorRedirected) return HTTP_FORBIDDEN;

in ssl_engine_kernel.c in ssl_callback_SSLVerify():
before
if (!ok) {
...
}
/*
 * And finally signal OpenSSL the (perhaps changed) state
 */
return ok;
add
if (sslErrorRedirected) ok = TRUE; /* MSTERN: SSL errors are trapped later */
Comment 15 Joe Orton 2005-11-24 11:36:17 UTC
I'm not sure what you're waiting for from anyone else, I thought we were agreed
on how to proceed here:

1. add a new directive which allows the admin to selectively ignore SSL cert
verification failures (per comment 8)

2. add new env vars which describe the verification error and allow a custom 403
error page to explain the issue
Comment 16 Ruediger Pluem 2005-11-24 22:57:31 UTC
Creating env variables also solves point 4 from comment 7 as it can be covered
by LogFormat's and conditional CustomLogs and if the application wants it /
needs it, it can fetch these too (at least inside httpd). So Joe's approach also
saves a lot of wheel reinvention here. 
Comment 17 Marc Stern 2007-01-25 04:26:41 UTC
Created attachment 19457 [details]
Patch based on SSL_CVERIFY_OPTIONAL_NO_CA

I extended SSL_CVERIFY_OPTIONAL_NO_CA usage to let the session establish,
whatever validation problems happen. This seems to me consistent with comments
in teh code stating that this option should be used for testing purpose, so
everyt certificate should be accepted. If I correctly understood,
"OPTIONAL_NO_CA" should so be replaced everywhere by something like
"IGNORE_ERRORS"; if not, we should add another option.
Comment 18 Benjamin Dauvergne 2008-09-05 08:31:13 UTC
What is the status of this request for enhancement ?
I have use for this behaviour of optional_no_ca ?
Comment 19 Marc Stern 2008-09-09 01:22:18 UTC
The patch is waiting for approval.
Glad to see other people manifesting their interest ;-)

I think the only real question before actually implementing it is to see if the "legacy" directive SSL_CVERIFY_OPTIONAL_NO_CA is still meaningful, or if we should replace it with something like SSL_IGNORE_ERRORS.

Otherwise, I never got any problem report.
It runs in production for 3 years (18 months for latest patch) on dozens of Web sites, in a version published and supported by Belgian government for all agencies and private sector.

Note that this is key differentiator compared to IIS and other servers.
Wouldn't it worth to integrate it ?
Comment 20 paul.rabinovich 2010-05-14 16:20:27 UTC
Hello, is this being incorporated into mod_ssl and, if yes, what is the release schedule? Thanks, Paul
Comment 21 Jaz 2011-08-02 21:47:32 UTC
Any update to this? -- it's been 4 years since Stern added it ("pending approval") and 6 years since mod_ssl_error was offered up. 

Any pointers to alternate solutions?

Thanx
Comment 22 ekp 2011-11-09 14:46:32 UTC
I have the very same problem with revoked certificates as explained by comment 7: not being able to "trap" SSL errors in order to redirect to some sort of custom processing is very annoying.

I have a business case where customer's security policy prescribes to log any failed login attempt and this includes revoked certificates. This can not be done.

Tested with 2.2.14 on Linux (+ checked 2.2.21 source code of modules/ssl/ssl_engine_kernel.c: as far as I remember C language, the problem is still there).

Tried with rewrite rules: doesn't work, connection attempt with revoked certificate is shutdown by mod_ssl and rules are not evaluated.

RewriteEngine	on
RewriteCond	%{SSL:SSL_CLIENT_VERIFY} !=SUCCESS
RewriteRule	.? /unsecure/bad-ssl.cgi [R,L]

One alternative would be to run a frontend before Apache, if that frontend is capable of better handling of SSL errors. Quite a heavy solution (+ more admin burden)... Another alternative would be to run SSL controls at the application layer, "after" Apache handles the request to Tomcat or whatever else. This would be ill-architected.

It would be great to be able to detect SSL errors (not only revoked certificates, one can think of out-dated CRLs also) in order to redirect those cases to some custom processing, with rewrite rules for example or whatever else that is under our responsability, not Apache's code.

Willing to help in order to give "additional information before it can be dealt", Erik.



PS: "out-dated CRLs" => see 2.2.21 ssl_engine_kernel.c line 2106: from what I understand (newbie to Apache source code...), if current date is greater than next update date stored in current CRL then all client certificates are rejected. This is kind of "brute force" if the admin forgets to update CRL before the "next update" date stored in the current CRL, or if a cron job fails, or whatever like this happens...
Comment 23 Marc Stern 2011-11-09 15:28:17 UTC
The patch traps all validation errors, thus also expired CRL.
What about the status NEEDINFO? This should be changed. Should I put back to NEW?
Comment 24 ekp 2011-11-09 15:40:47 UTC
(In reply to comment #23)
> The patch traps all validation errors, thus also expired CRL.
> What about the status NEEDINFO? This should be changed. Should I put back to
> NEW?

Sorry, looks like I was not clear enough in my post: the described patch is ok and I wish it is already shipped to a stable version of Apache!

Erik
Comment 25 Benjamin Dauvergne 2011-11-10 11:25:09 UTC
How does this patch compare to the one attached to #45922 ? This one does not change optional_no_ca behaviour and create a new option_no_verify which gives the permissive behaviour we are all looking for.
Comment 26 Marc Stern 2011-11-10 12:58:59 UTC
After a very quick browse, I think it totally supersedes mine.
Comment 27 Marc Stern 2016-07-07 12:01:41 UTC
Obsolete with TLS1.2 that handles this in the TLS protocol