Apache Won't Request My SSL Client Certificate Apache Won't Request My SSL Client Certificate linux linux

Apache Won't Request My SSL Client Certificate


First, you need to configure Apache Httpd to request a client certificate. For this, you need at least to use SSLVerifyClient optional on the location/directory you want to be authenticated with this method.

Secondly, the certificates sent by the client needs to be trusted by Apache Httpd too. (You could in principle use SSLVerifyClient optional_no_ca, let any client cert through at the Apache Httpd SSL/TLS stack, and only then verify the certificate within PHP, but that's quite a bit of work, for which you need be a bit more careful since that's not necessarily easy code; more importantly, this would be quite useless in this context, since you're in a scenario where you're in control of your CA.)

As far as I understand, SSL_CLIENT_VERIFY (a variable that I haven't used much myself) seems only really useful with the SSLVerifyClient optional_no_ca. It might work with SSLVerifyClient optional, but I doubt so. SSLVerifyClient require will reject connections using a client certificate that is not trusted (by one of the CAs in SSLCACertificateFile/SSLCACertificatePath), or if there is no certificate. As far as I know, SSLVerifyClient optional will let the client through without a certificate or with a trusted certificate, but will also reject the connection if the certificate is not trusted.

Here, by rejecting the connection, I mean closing the SSL/TLS connection abruptly with an alert. There is no chance to produce an HTTP(S) error page. All you'll get in the standard browser error, something along the lines of ssl_error_unknown_certificate_.... (You should consider this in terms of usability.)

From then onwards, what you need is to set up your own CA, possibly web-based with in-browser key-generation and within the same website. You wouldn't want SSLVerifyClient require for that, because you would need to let the users who don't have a certificate yet in (use optional instead). This being said, these directives need not apply to the entire host, but can be specific to certain locations/directories.

Integrating your own web-based CA (or more generally, creating your own CA) isn't necessarily easy if you're new to all this. Ready-made tools exist (e.g. OpenCA), or you can build your own using various bits of JavaScript/ActiveX, and you would need the server-side code to handle the SPKAC or PKCS#10 requests (and to issue the actual certificate). (For such a CA to be useful, you'd want the users who apply for a new certificate to provide some proof of ID at the time of application, perhaps a password.)

When this is set up, you should configure SSLCACertificateFile (or ...Path) to point to the CA certificate of your internal CA (whether it's a web-based CA or not, on the same site or not). (Of course, keep the private key of your CA private, perhaps configured within your CA web-based application, but Apache Httpd itself doesn't need to know about it.) Browsers will only suggest certificates issued by those CAs or intermediates (unless you've also configured SSLCADNRequestFile, which would be used to send the list of accepted CAs instead).

Note that these two steps (setting up your CA and setting up your website to use client-certificates) really are independent in fact. The fact that both can be part of the same site can be convenient, but isn't necessary. You could try out the Apache Httpd set up without deploying an entire CA on the site first (I'd recommend that, even if it's just to see what you're getting into). There are a number of tools to create your own little CA that are manageable with a handful of certificates: OpenSSL's CA.pl or TinyCA for example. You could also use these test certificates (localhost and testclient, testclient_r is revoked if you want to use the CRL, probably not necessary at first): all passwords are testtest.

As you've already anticipated (with your MySQL DB), you'll need to manage the certificates you issue and map them to users. SSL_CLIENT_M_SERIAL and SSL_CLIENT_I_DN are not the right variables to use, though. SSL_CLIENT_I_DN is the Issuer DN (i.e. the CA's Subject DN). What you'd be looking for is SSL_CLIENT_S_DN: the client cert Subject DN. SSL_CLIENT_M_SERIAL is the certificate serial number: don't use it, since it's unique per certificate: one user could have multiple certificates with the same Subject DN (e.g. if one expires or is revoked).


Despite all this, I'm not sure whether client-certificates are the best way to achieve your goal (letting the employees in your company log on without password).

  • Firstly, the user should protect their own certificates with a password anyway. What you're really after is some form of Single-Sign On (SSO), I guess.

  • Secondly, depending on the degree of computer-literacy of your users, certificates can actually be quite difficult to manage.

    The fact that the word "certificate", strictly speaking, doesn't include the private key at all, but sometimes implies usage of the private key can be confusing for some. On the one hand, you sometimes hear "Import your certificate into your browser" and "Use your certificate to log in"; on the other hand, you can also hear "send me your certificate". The former implies usage and availability of the private key ("certificate" might just mean .p12 in these expressions). The latter definitely shouldn't involve the private key.

    Browser user interfaces tend to be quite poor or confusing for managing the certificates or logging out. Again, if the certificate isn't recognised, the SSL/TLS connection will not be established, so the web server doesn't get a chance to display an HTML error page of any sort.

Perhaps you could also consider other forms of SSO (e.g. CAS, something SAML-based or Kerberos/SPNEGO.)


I have a similar problem with:

  • CentOS 6.3
  • Apache 2.2.15

After some tries i recognize my problem.

If I set SSLVerifyClient optional or SSLVerifyClient optional_no_ca and I specify also SSLCACertificateFile or SSLCACertificatePath, Apache acquires the client certificate only if it's released from CA found in the CA reference file/path specified in configuration.


You may have a look to the apache doc if not done already.

The general principle is that you create your self-signed cert and check it before trying to use it.

Then it looks like the client connects to your intranet site through http. From there, there are many different ways to switch to https using your ssl cert. The easiest way is to use the apache rewrite module. But in your case, as you are making php/mysql checks, you may redirect your client from http to to https, which is not the simple way.

In any of both cases (apache automatic redirect through mod_rewrite, or redirection by cascading tests (php/javascript/html), you need to set up your 2 vhosts (one for http and one for https) in the proper way, but this assumes some hypothesis.

For example (debian - apache 2.2), here is an automatic redirect, done by Apache (eg 1st case described above) :

cat /etc/apache2/sites-available/test

# VHOST test<VirtualHost *:80>    DocumentRoot /home/www/test    ServerName www.test.dev    # ######################    # Redirect commons    # ######################    RewriteEngine on    # Case of vhosts    RewriteOptions Inherit    # ######################    # Redirect (empty index)    # ######################    # Condition 1 to redirect : request matching with the server    RewriteCond %{HTTP_HOST}   ^www\.test\.dev [NC]    # Condition 2 to redirect : non empty HOST    RewriteCond %{HTTP_HOST}   !^$    # Automatic Empty requests Redirect    RewriteRule ^(.*)/$ /index.php    # ######################    # Redirect to SSL    # ######################    RewriteCond %{HTTP_HOST}   ^www\.test\.dev [NC]    RewriteCond %{HTTP_HOST}   !^$    RewriteCond %{SERVER_PORT} ^80$    RewriteCond %{REQUEST_URI} /    # Redirection    RewriteRule ^/(.*)$ https://%{SERVER_NAME}%{REQUEST_URI} [L,R]</VirtualHost>

The second virtual host for SSL :cat /etc/apache2/sites-available/test-ssl

# VHOST for ssl

DocumentRoot "/home/www/test"ServerName www.test.dev    # SSL    SSLEngine on    SSLCACertificateFile /etc/apache2/ssl/cur_cert/ca.pem    SSLCertificateFile /etc/apache2/ssl/cur_cert/serveur.pem    SSLCertificateKeyFile /etc/apache2/ssl/cur_cert/serveur.key    <Directory "/home/www/test">        Options FollowSymLinks MultiViews        AllowOverride None        Order allow,deny        Allow from 127.0.0.1 192.168.0.0/16    </Directory>    <Directory "/home/www/test/cgi-bin">        Options FollowSymLinks MultiViews        AllowOverride None        Order allow,deny        Allow from 127.0.0.1 192.168.0.0/16        Options +ExecCGI        AddHandler cgi-script .cgi    </Directory></VirtualHost>

Your case might defer slightly from this, eg you will not have the redirect portion in the 1st vhost, but only a simple vhost and the second one for https (ssl). The redirection will be done by php/javascript once you have achieved your mysql checks.

Here is an example abstract from a php class for the way to cascade the switch from http to https, using php, then javascript, then html :

public function Redirect($url){    if (TRUE !== Validator::isValidURL($url))        die ("FATAL ERR: url not valid");    // PHP ABSOLUTE URL REDIRECT (HTTP1.1)    if (!headers_sent()) {        header("Status: 200");        header("Cache-Control: no-cache, must-revalidate"); // required for HTTP/1.1        header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // past Date        header("Pragma: no-cache");        header('Location: '.$url); // note: 302 code return by default with "Location"        flush();        exit();        // if headers are already sent... do javascript redirect... if javascript is disabled, do html redirect.    } else {        // Js redirect        echo '<script type="text/javascript">';        //echo "<!--";        echo 'document.location="'. $url .'";';        //echo "//-->";        echo '</script>';        // HTML redirect if js disabled        echo '<noscript>';        echo '<meta http-equiv="refresh" content="0;url="'.$url.'" />';        echo '</noscript>';        exit();    }    return FALSE;} /* end of method (redirect) */

Hope it helps you to better understand how to proceed and adapt this approach to your specific case.