Red Hat Bugzilla – Bug 1248191
Client-certificate prompting is inconsistent
Last modified: 2017-07-01 19:53:10 EDT
I am using Firefox 38 with the OpenSC PKCS11 module to perform client certificate authentication against an Apache httpd server. The httpd configuration has client-certificate authentication as "optional", which allows the web application to support falling back to alternate authentication methods if the user does not have a certificate or if there is some other certificate related problem.
It seems that Firefox caches the fact that a site asked for a
client-certificate and one was not present for the duration of the
Firefox process. If I start a new Firefox process and access httpd that
is using 'SSLVerifyClient optional', I'll be prompted to provide my
certificate. The first time I access that site without a certificate
present, Firefox remembers it and will never prompt me to provide a
certificate again for that site unless I restart the browser.
I've recorded a video showing this behavior, which is consistent. In
the video, I'll first start a fresh browser process, then will access
the site with no certificate available. The ErrorDocument setting will
redirect me to the form as expected. I then insert my smart card and
try to access the site again. I'm never prompted, and it redirects me
to the form.
I then restart the browser, but this time I insert my smart card before
accessing the site. I'm prompted to unlock my card and select a
certificate, and authentication is successful. I then log out of the
web application and remove my smart card, then access it again. The
fallback to form auth works. If I insert the card again, I'm not
prompted for the certificate and it falls back to the form.
The video showing this behavior is located here:
I am not yet sure if this is a smart card specific issue, or if it would also occur using certificates stored in the NSS database used by Firefox.
The relevent mod_ssl configuration that I'm using is as follows:
ErrorDocument 401 '<html><meta http-equiv="refresh" content="0; URL=/users/login"><body>Certificate authentication did not pass.</body></html>'
ErrorDocument 403 '<html><meta http-equiv="refresh" content="0; URL=/users/login"><body>Certificate authentication did not pass.</body></html>'
I see this problem when using certificates in the NSS database, so it's not a smart card specific problem. I've recorded two videos to demonstrate the problem.
In the first video, I already have a certificate loaded in the NSS database when I start Firefox. I access the site and get prompted for my certificate. In this case, I choose "cancel" and am sure to tell Firefox to not remember this decision. It falls back to form-based auth as I expect it to. I then attempt to access the site again, but I'm never prompted for my certificate this time. I would not expect Firefox to remember that I clicked "cancel". I then restart Firefox and access the site, and it prompts me for my certificate again. This time I select my certificate and click "OK" (again telling it to not remember this decision), which authenticates me successfully. I log out and access the site again, and it lets me in immediately via client certificate auth with no prompt, despite the fact that it should not remember this decision. Here is the video for this:
In the second video, I have no certificates loaded in the NSS database when I start Firefox. I access the site, and it gets redirected to the login form as expected. I then import a client certificate and key and attempt to access the site again. Despite the fact that I have a certificate available now, I'm never prompted for it. If I restart Firefox and access the site again, I'm prompted for my certificate and can log in as expected. Here is the video for this:
The behavior in both of these cases seems wrong.
Nathan, we need to consider the TLS session, with potentially a TLS session cache, and the isolation of TLS from the web application.
Modern browsers combined with HTTP 1.1 keep sockets open to improve performance.
At the same you connected first, a TLS handshake was performed, which included the client cert authentication. Once that TLS session had been established, it was very likely kept open, because there was no need to close it.
The requirement to present a client certificate only happens when a TLS handshake happens. But in your example, there was no need for a new TLS handshake from the browser's perspective.
In addition to the TLS handshake, you have your web application that runs inside the TLS layering. The web application runs after the TLS negotiation has completed.
How does the web application know that authentication has already taken place?
I know how that worked with the apache web server in the past, and I suspect in your environment it works similarly.
If the web application code is aware that TLS client authentication is a possible authentication mechanism, then your web code probably queries the web server for the status. In apache, a set of environment variables were used, that the web code could query.
If the webserver saw that TLS authentication has completed (the client presented a certificate that was accepted according to the webserver configuration), then the webserver set environment variables, that will be effective for the scripts that are executed for this page.
(The webserver might theoretically use other means to signal the presence of completed TLS authentication to the web page scripts, I don't know if any different ways to signal that are nowadays being used, but for simplication, I'll continue to talk about environment variables.)
This means, if you navigate to the page again, and the browser continues to use the existing TLS session with the already completed TLS client authentication, then the webserver will continue to set the environment variables for the page scripts. That means, the next time you load your web page, the web page will still find information that authentication is in place.
Now, what happens when you click "sign out" inside your web application?
I don't know what your web application does. I suspect that it uses some sort of session cookies, or session form values, that are being carried along while you use the web application.
Because your application also supports form authentication, I assume your web scripts can look for either the environment variables that signal completed TLS authentication, or look for valid user/password credentials provided in form values. Once one or the other check succeeds, you probably set a session cookie.
At the time you click "sign out", I guess only the session cookie will be deleted. But when you load again, you'll still see the TLS authentication is present, and will continue to treat the session as "signed in".
In order to get the effect that you expect, you'd have to find a way to influence the TLS session from inside your web script code. I don't know if that's possible. It would require that your server scripting environment offered a mechanism that the web script code can use to signal the server to invalidate the current TLS session and require it to perform a new TLS handshake on the next interaction for this server.
The TLS session also explains the behavior in your second video.
Because your server has already allowed the TLS session to complete without presenting a client certificate, the TLS session is still alive after you sign out from the web application. The TLS session is still used when you load the page again (the socket is still alive and will continue to be used).
You'd have to find a way to force the server or the browser to invalidate the current TLS session. If you had a way to do that, the next time you load the page a new TLS handshake would happen, and the server would again ask the browser for the optional client cert.
It should be technically possible to expose a hook into mod_ssl to flush the session cache, although threading such hooks through to (various) application layers is difficult.
But since the trigger here is client-side, it is the insertion of the smart-card which should trigger the browser to discard the TLS session, wouldn't the correct solution be client-side?
If Firefox sees a smartcard insertion and a certificate becomes available which already has an association with a site to which there is an kept-alive TLS connection, couldn't it terminate the connection and flush the cached session entry? (At least in theory?)