SSL/TLS fun with client certificate authentication

Today’s adventure was a question about limiting SSL connections.

The customer’s problem statement was along the lines of

I want to make SSL connections, using client certificate auth (i.e. two-way ssl) and have the server accept only one certificate. Can I put the one cert I want in the “trusted CA list” to do that?

I thought the answer was “no, that’s not how you do that” but I wasn’t 100% sure so off I went on a research project…

I checked the relevant RFC (2246 for TLS) and found this in section 7.4.4 which talks about the server asking the client for a certificate:

certificate_authorities
A list of the distinguished names of acceptable certificate authorities. These distinguished names may specify a desired distinguished name for a root CA or for a subordinate CA; thus, this message can be used both to describe known roots and a desired authorization space.

I think that means that the cert the user uses has to be issued by one of the certificate authorities in that list, not that the cert is in that list. So I was pretty sure that the answer to their question was “no”, but I wanted to be absolutely sure, so that means testing.

First up – generating certificates. I used my dead simple CA script to generate the certificate for the host and another certificate I’ll use as the client certificate:

$ ./ca.sh ssltest.oracleateam.com
...
$ ./ca.sh tester
...

I had Apache and mod_ssl already installed (use yum to install mod_ssl and you’ll get both installed and configured).

Then I set Apache to use the ca’s certificate as the trust store and to demand a certificate from the client:

#   Certificate Authority (CA):                                                                                    
#   Set the CA certificate verification path where to find CA                                                      
#   certificates for client authentication or alternatively one                                                    
#   huge file containing all of them (file must be PEM encoded)                                                    
#SSLCACertificateFile /etc/pki/tls/certs/ca-bundle.crt                                                             
SSLCACertificateFile /home/ec2-user/ca/ca.crt

...
SSLVerifyClient require

Then I tested with openssl directly and was able to connect without a problem:

[ec2-user@ssltest ~]$ openssl s_client -CAfile ~/ca/ca.crt -cert ~/ca/tester.crt -key ~/ca/tester.key -connect ssltest.oracleateam.com:443
CONNECTED(00000003)
depth=1 C = US, ST = Massachusetts, L = Boston, O = Oracle, OU = A-Team, CN = My Cert Authority, emailAddress = root@ssltest.oracleateam.com
verify return:1
depth=0 C = US, ST = Massachusetts, L = Boston, O = Oracle, OU = A-Team, CN = ssltest.oracleateam.com, emailAddress = root@ssltest.oracleateam.com
verify return:1
...
Acceptable client certificate CA names
/C=US/ST=Massachusetts/L=Boston/O=Oracle/OU=A-Team/CN=My Cert Authority/emailAddress=root@ssltest.oracleateam.com
...

Then I tried swapping the SSLCACertificateFile to just be “tester”‘s certificate:

SSLCACertificateFile /home/ec2-user/ca/tester.crt

and ran the same command again:

[ec2-user@ssltest ~]$ openssl s_client -CAfile ~/ca/ca.crt -cert ~/ca/tester.crt -key ~/ca/tester.key -connect ssltest.oracleateam.com:443
CONNECTED(00000003)
depth=1 C = US, ST = Massachusetts, L = Boston, O = Oracle, OU = A-Team, CN = My Cert Authority, emailAddress = root@ssltest.oracleateam.com
verify return:1
depth=0 C = US, ST = Massachusetts, L = Boston, O = Oracle, OU = A-Team, CN = ssltest.oracleateam.com, emailAddress = root@ssltest.oracleateam.com
verify return:1
3079509740:error:14094418:SSL routines:SSL3_READ_BYTES:tlsv1 alert unknown ca:s3_pkt.c:1193:SSL alert number 48
3079509740:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:s23_lib.c:184:
...
Acceptable client certificate CA names /C=US/ST=Massachusetts/L=Boston/O=Oracle/OU=A-Team/CN=tester/emailAddress=root@ssltest.oracleateam.com

and the openssl client dropped the connection.

So openssl either doesn’t know what to do when the “Acceptable client certificate CA names” matches the certificate or that’s simply not allowed.

I also tested with a little perl script:

#!/usr/bin/perl                                                                                                                     

use LWP::UserAgent;

$HOME=$ENV{HOME};

$ENV{HTTPS_CA_FILE} = "$HOME/ca/ca.crt";
$ENV{HTTPS_DEBUG} = 1;

$ENV{HTTPS_PKCS12_FILE}     = "$HOME/ca/tester.p12";
$ENV{HTTPS_PKCS12_PASSWORD} = 'ABcd1234';

$ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 1 });
$response = $ua->get("https://ssltest.oracleateam.com/");

if ($response->is_success) {
    print $response->content;
}
else {
    print STDERR $response->status_line, "\n";
}

So how are you supposed to do that?

If you’re using Apache then you just configure Apache to accept only the one certificate you want. This from the sample httpd.conf (actually ssl.conf, but that’s included in httpd.conf):

#   Access Control:                                                                                                
#   With SSLRequire you can do per-directory access control based                                                  
#   on arbitrary complex boolean expressions containing server                                                     
#   variable checks and other lookup directives.  The syntax is a                                                  
#   mixture between C and Perl.  See the mod_ssl documentation                                                     
#   for more details.                                                                                              
#                                                                                                      
#SSLRequire (    %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \                                                                 
#            and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \                                                       
#            and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \                                                 
#            and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \                                                         
#            and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20       ) \                                                
#           or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/                                                         
#

Add Your Comment