Tomcat web application with SSL client certificates

Often repeated topic, tons of stuff you can google, yet either I’m retarded or just plain unlucky – it took ma days to get it all right. What we’re going to do?

We need OpenSSL – for that I used Ubuntu installation running on Sun/Oracle VirtualBox as I run the Tomcat on Windows. Then I used JDK 1.6 and its keytool. Finally unzip downloaded Tomcat 6 somewhere. Generally, you’ll find this all on any Linux. Use Sun’s JDK, not gcj stuff.

The rough idea is this: We need CA – our authority. We’ll create our own one – it will substitute something serious like Verisign – but it’s for free. :-) Then you need server certificate that is signed by this CA and client certificate that is signed with the same CA as well.

You can create another authority that is not the root CA. You can have your own CA1 signed by some CA2 that is signed by root CA (self signed). You can have server certificate signed by CA1 and client certificates signed by CA3, but CA3 must be signed by CA2 (as is CA1). What you need is a certificate of any CA “above” your server certificate imported into your browser (this is not absolutely necessary, but it ensures, that browser trusts the server certificate without asking the user – which is the idea why the most famous root CAs are pre-imported in your browser). And you need CA “above” the client certificate for client authentication + client certificate in your browser. I hope I didn’t make any factual error in this paragraph. :-)

For now we will have a single self-signed root CA and both the server and the client certificates signed by the same CA.

Open SSL

We’ll start with OpenSSL and CA. I don’t use CA.sh helper script, just because I don’t. Don’t ask why, it just wraps openssl command anyway. To warn you in advance, I’m lame with SSL, PKI, certificates, openssl, and the whole stuff – I just somehow made it all work. And I want you to make it too with a single tutorial. Whatever you don’t understand, you have to read some theory behind on your own – I will not explain theory, as I don’t master it either.

We will use default configuration file for openssl, later you can mess with your own. Check this command to learn where the default openssl.conf is:

$ openssl version -d

To go on, I presume you’re already in shell/console/whatever. Create some directory (I’ll use certtest) and go there.

Certificate Authority

First we need the key pair for our authority – this is a critical file (if you were real authority that is). You can create more types of keys, but experiments are for experts (see the word similarity?) so let’s go on with RSA key:

$ openssl genrsa -out ca.key 1024

Now let’s create the root certificate:

$ openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
You are about to be asked to enter information that will be incorporated into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:SK
State or Province Name (full name) [Some-State]:Bratislava
Locality Name (eg, city) []:Bratislava
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Naive
Organizational Unit Name (eg, section) []:CA
Common Name (eg, YOUR name) []:CA Admin
Email Address []:ca@naive.sk

(There is actual naive.sk domain I have nothing to do with, but I used Naive as my “wannabe company” for years and I’ll not change anything on it now. Obviously, don’t mail there.)

Now we have ca.crt which is our root certificate. This will be the file imported into browser later. It contains public key and some other information – and it’s self signed (this is cause by -x509 option). If you copy openssl.cnf to your certtest directory, you can change some values there and questions for Locality, … will have better defaults. Use -config option in any openssl command (openssl command is the first argument after openssl command itself ;-)), check openssl documentation for more options, for instance this for all available options for req command.

That’s it! You are now well concealed certificate authority. Not really, we need a few more things for further steps. Let’s create some directory structure according to default openssl.cnf:

$ mkdir -p demoCA/newcerts

Now we need index and serial file for our CA:

$ touch demoCA/index.txt
$ echo '01' > demoCA/serial

And now we’re really done with setup of our CA with all things openssl needs.

Server certificate

First we need key pair for the server:

$ openssl genrsa -out localhost.key 2048

We have to create so called certificate signing request (CSR) for our web server. Let’s try it:

$ openssl req -new -key localhost.key -out localhost.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:SK
State or Province Name (full name) [Some-State]:Bratislava
Locality Name (eg, city) []:Bratislava
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Other Company
Organizational Unit Name (eg, section) []:IT
Common Name (eg, YOUR name) []:localhost
Email Address []:admin@other.sk

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

YOU MUST use your server name in common name, otherwise someone will complain about it (browser definitely, but there are some servers that refuses to start too). In this case I use localhost, which is good enough for local tests, but not good enough for remote access to your computer. You can use yourhost.yourdomain instead, then maybe you want to use the same pattern in file names.

Now we’ll “send this CSR file to our CA” (but we’re mixing it in one directory anyway, what a mess!) and our CA will issue the certificate for us:

$ openssl ca -keyfile ca.key -cert ca.crt -out localhost.crt -infiles localhost.csr
Using configuration from /usr/lib/ssl/openssl.cnf
Check that the request matches the signature
Signature ok
The organizationName field needed to be the same in the
CA certificate (Naive) and the request (Other Company)

Because attributes (Locality, State, …) on the server certificate will not match CA certificate (why should they?) we have to choose other than default matching policy. Check config file to find those policies. Additional option will resolve the situation – but don’t add -policy at the end:

$ openssl ca -keyfile ca.key -cert ca.crt -out localhost.crt -policy policy_anything -infiles localhost.csr
Using configuration from /usr/lib/ssl/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
...too long
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

Now we’re talking! Let’s create client certificate before we leave openssl and move to Java stuff!

Client certificate

What a surprise – first step is creating key-pair for our client. That is you as a person this time.

$ openssl genrsa -out client.key 2048

Now we create another certificate request:

$ openssl req -new -key client.key -out client.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:SK
State or Province Name (full name) [Some-State]:Bratislava
Locality Name (eg, city) []:Bratislava
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Other Company
Organizational Unit Name (eg, section) []:stuff
Common Name (eg, YOUR name) []:Richard Richter
Email Address []:richter@other.sk

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

As you see, common name is real name, it’s obviously someone’s personal certificate. I think you can somehow specify the purpose of the certificate (server, client, …) but we just created both of them the same way. You can study details later (as I need to as well).

Now our CA will sign our request and we get the certificate:

$ openssl ca -keyfile ca.key -cert ca.crt -out client.crt -policy policy_anything -infiles client.csr
...
$ ls -R
.:
ca.crt  ca.key  client.crt  client.csr  client.key  demoCA  localhost.crt  localhost.csr  localhost.key

./demoCA:
index.txt  index.txt.attr  index.txt.attr.old  index.txt.old  newcerts  serial  serial.old

./demoCA/newcerts:
01.pem  02.pem

We will not need certificates in PEM format so you don’t have to care about files under demoCA at all. All our *.crt files are in our certtest directory.

PKCS#12

We will use openssl for one last step now. To use certificates in Java keystores and also for client certificate import into Firefox, we need to create PKCS#12 files that combine certificate with its private key. Obviously these files carry important information. We will use password to protect them. Password is optional when you create PKCS#12 file, but is required in both imports (to JKS and Firefox). Let’s do it:

$ openssl pkcs12 -export -in localhost.crt -inkey localhost.key -out localhost.p12 -name sercer
Enter Export Password: (asdf ;-) no one can see anyway)
Verifying - Enter Export Password: (the same again)

Password is important only for this file. Let’s repeat it with client certificate:

$ openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name client

Aliases sercer and client are important to remember (especially sercer).

Server configration

First we have to have keytool from <JDK1.6>/bin/ directory on the path. Important thing is to have JDK 1.6 – just run keytool and it has to show you the help and in that help you have to see -importkeystore sub-command among others. In dark times before JDK 1.6 people had to use other tools or own programs to import PKCS#12 files into JKS files. JKS stands for Java KeyStore – and it’s the file format that Java uses to – you guessed it – store keys! And certificates too. (Some mostly hi-end products use certutil tool that has different keystore format using *.db suffix. It’s solution used before by Netscape, enterprise editions of some appservers, etc. You can google details if you’re in dire need.)

So you have unzipped Tomcat 6 somewhere, let’s go to the directory and there into conf directory. Let’s start with SSL configuration without client authorization – nicely step by step. Edit server.xml and find section containing:

Connector port="8443"

The whole XML tag is probably commented out so remove <!– and –> to make it active and also add keystore attributes – it will look like this:

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
keystoreFile="d:/work/apache-tomcat-6.0.29/conf/keystore.jks" keystorePass="changeit"
clientAuth="false" sslProtocol="TLS" />

Of course use your path to your tomcat’s conf dir. Now we’re missing just the keystore JKS. Let’s create it (you are in conf dir, right?):

$  keytool -importkeystore -deststorepass changeit -destkeypass changeit  -destkeystore keystore.jks -srckeystore ../../certtest/localhost.p12  -srcstoretype PKCS12 -alias sercer
Enter source keystore password: (famous asdf password goes here)

Use the alias and password that you provided when you created PKCS#12 file. Of course, we’re working on the server now, so use server’s .p12 file, not the client one. This is the certificate that server sends to the browser (but without private key of course, that is used for server part only). Restart the Tomcat (check console window for any errors, there should be none) and access https://localhost:8443 to find out if it works. Browser should have some trust problems, but when you add exception (uncheck permanent for now!) you should see Tomcat homepage.

SSL is running! Congratulations!

Browser and CA certificate

To trust our server certificate we have to import ca.crt file into the browser. I’ll use Firefox and show no images, sorry. You can find many fancy examples elsewhere anyway. IE shares certificates with other Windows (or rather say MS) programs, but Firefox has its own store.

Select Tools in main menu, and Options down there. Options window will appear. Select Advanced in top row of icons and then Encryption tab under icons. You should have both Protocols check on. Let’s click View Certificates button – Certificate Manager window will open. There you have 5 tabs up there – and we want to add CA certificate, so click Authorities. There are many of them predefined (big one, commercial), let’s import yours – click Import button, browse your ca.crt and add it. On the import dialog (Downloading Certificate) check the first option (Trust this CA to identify web sites), or check everything as you wish. Now OK all those windows.

Try HTTPS connection to localhost:8443 again – if you see the warning, try to close the Firefox and open it again (it’s reluctant to learn new certificates or forget removed ones sometimes).

Now you should have trusted SSL site up & running!

Client certificate

Now change the server configuration to require client authentication:

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
truststoreFile="d:/work/apache-tomcat-6.0.29/conf/cacerts.jks" truststorePass="changeit"
keystoreFile="d:/work/apache-tomcat-6.0.29/conf/keystore.jks" keystorePass="changeit"
clientAuth="true" sslProtocol="TLS" />

Attribute clientAuth=”true” is important and there are new attributes for truststore JKS where we need to import CA certificate. Some servers may require CA certificate for previous steps too (and will refuse to start otherwise), Tomcat is ok just with server certificate for SSL when you don’t use client certificates. But it needs CA for client certificates because otherwise browser can’t know which client certificate to send. (And even if client sent all of them – which is not logical in the first place – server couldn’t verify them anyway.) Creating cacerts.jks is a bit different than than keystore.jks:

$ keytool -import -keystore cacerts.jks -storepass changeit -alias my_ca -file ../../certtest/ca.crt
Owner: EMAILADDRESS=ca@naive.sk, CN=CA Admin, OU=CA, O=Naive, L=Bratislava, ST=Bratislava, C=SK
...too long...
Trust this certificate? [no]:  yes
Certificate was added to keystore

For obvious reasons you can’t have PKCS#12 file with CA private key, only the certificate itself is important here. You have to trust the certificate, otherwise it’s not trusted certificate – which it has to be. Password “changeit” can be changed, just change it in server.xml as well. You can change the password of JKS any time later with keytool as well. BTW: alias (my_ca in our example) can be anything you like.

Restart the Tomcat and try HTTPS access. You will learn that:

SSL peer cannot verify your certificate.
(Error code: ssl_error_bad_cert_alert)

Time to fix it in the browser. Open that Certificate Manager again and this time go to Your Certificates tab, hit Import button, select client.p12 file (it will show you only PKCS#12 files this time) and enter password you entered when you created the file (asdf?). Try to access HTTPS again. It should work (if not, restart Firefox again). In case you selected check box saying you will select the client certificate to be sent to the server, you’ll see dialog to choose one (here you can see alias of the certificate – probably “client” in our example – along with its serial number) – but it should work like a charm automagically too.

If you ask: “Do I need two different JKS files?”

Answer is: “No, you can use just keystore.jks for both purposes.” And I don’t know why to separate them, but it’s probably done for reason. Most servers already have those files separated, certutil uses also two files in most applications/examples – there is probably some nice reason. But technically you don’t need it, at least not in our case. :-) Don’t forget to change it in server.xml too if you decide so. After adding both ca.crt and localhost.p12 into single JKS you can see something like this:

$ keytool -list -keystore keystore.jks
Enter keystore password:  (changeit, of course)

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 2 entries

my_ca, Aug 23, 2010, trustedCertEntry,
Certificate fingerprint (MD5): BF:64:56:47:2E:B5:80:B6:F6:78:4B:8D:5B:AF:6E:0F
sercer, Aug 23, 2010, PrivateKeyEntry,
Certificate fingerprint (MD5): C2:0B:F0:2F:D9:E8:9E:89:43:CF:E4:AF:38:B3:CB:BB

Now we’re done unless we want to utilize the user information from the certificate in our program. And we want!

Identifying user in an application

Let’s write some uber application – no zip download required! Go to tomcat’s webapps directory and follow me:

$ mkdir -p certreader/WEB-INF

Now you have the whole structure of the application done, let’s create two more files. This goes into certreader/WEB-INF/web.xml (now in Java EE 6 – or Tomcat 7 – you can omit this!):

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>Cert test</display-name>
</web-app>

And this goes into certreader/index.jsp:

<%@page import="java.security.cert.X509Certificate"%>
<%
Object o = request.getAttribute("javax.servlet.request.X509Certificate");
if (o != null) {
X509Certificate certs[] = (X509Certificate[]) o;
X509Certificate cert = certs[0];
%>
<%= cert.getSubjectDN().getName() %>
<%
} else {
%>
Object was null.
<%
}
%>

You may need to restart Tomcat 6, if you did this too slowly (like more than a second or so). After that go to https://localhost:8443/certreader/ – index.jsp will kick in and you should see something like:

EMAILADDRESS=richter@other.sk, CN=Richard Richter, OU=stuff, O=Other Company, L=Bratislava, ST=Bratislava, C=SK

Isn’t this just ****ing beautiful? I can tell you I was stuck for days just to get here and I love it now.

Client authentication – want or true?

Depends. True means you have to provide client certificate (server strictly requires it). But imagine you have an application with some public stuff as well – then go for “want” value. Change your server.xml and restart the Tomcat. My Firefox still shows my certification details, but Chrome (without client certification) just says:

Object was null.

So now you can have different content for different users (and they say one’s life has equal value!). Anyway, sorry for such a long post, but if other guides didn’t help and this one did… I’m happy for you. Now I’m definitely happy for myself. :-)

About these ads

About virgo47
Java Developer by profession in the first place. Gamer and amateur musician. And father too. Naive believer in brighter future. Step by step.

34 Responses to Tomcat web application with SSL client certificates

  1. Anonymous says:

    Great Post !

  2. nikitadb says:

    Thank you so much for posting this article. I scratch my head for several days to get this thing to work. Now, I can have good sleep. Thank you for sharing your knowledge. With much appreciation.

  3. Pingback: Confluence: Quality Assurance

  4. Markus K. says:

    As Dirty Harry phrased it: “Go ahead, make my day…”. You did, really! Thanks a lot!

  5. jb says:

    Thank you so much !!! Great work !!! *THUMBS-UP*

  6. gt says:

    Thanks a lot

  7. Pingback: Why does .jsp page show only code when the website is hit.

  8. Thanks very much for your guideline, it’s very useful to me as I am working on these kind of things

    to answer the: “And I don’t know why to separate them, but it’s probably done for reason”
    I might have found the reason:
    from: http://www.coderanch.com/t/134383/Security/keyStore-vs-trustStore

    [i]Basically they can be a single store or separate.
    You will store in Keystore normally your private stuff and have a different store of trusted entries.
    The separation is good idea.
    The keystore will be used for encrypting/signing some thing with your private key while the trust stores will be used mostly to authenticate remote servers etc.
    In java I think to trust any entries you will pass -trustcacerts option.[/i]
    [...]
    [i]You always need a truststore that points to a file containing trusted certificates, no matter whether you are implementing the server or the client side of the protocol, with one exception. This file is often has a name like cacerts, and by default it may turn out to be a file named cacerts in your jre security directory. The filenames you gave are not defaults, so their contents are not obvious to me.

    You may or may not need a keystore. The keystore points to a file containing private key material. You need a keystore if 1) you are implementing the server side of the protocol, or 2) you are implementing the client side and you need to authenticate yourself to the server.

    There is one exception to everything stated. If you are using certain anonymous DH ciphersuites, then neither side needs either a truststore or a keystore. The connection is unauthenticated.[/i]

    • virgo47 says:

      Thanks for clarification, this sounds reasonable. When they are not separate one can’t protect them on a separate level either. This mostly isn’t an issue, but I can imagine it could be.

  9. Oleg says:

    Many thanks!

  10. tomcatNewbie says:

    Thank you very much for posting this guide. It works perfectly on Firefox, but doesn’t work in IE. Do you have any idea?

    • virgo47 says:

      If you installed client certificate properly into the Windows (IE uses OS shared store, though I don’t know the details), then it should be OK. I’m sure we used it with IE as clients, so I’m afraid I can’t help you with this one. Guide is about server side, so this seems rather like the client specific problem.

  11. Pingback: Welcome - Triton Services Inc

  12. zhaohua says:

    What a great post! I struggled to make it work on Tomcat 7 with similar posts on Internet without any success. I followed your instruction and the first try worked. Your post made my day. Thank you so much!!!!

  13. deep says:

    HI,

    Thank for your post. I was able to access the WSDL using the browser. If you tell us how to access the https from client side code. What needs to be imported in JVM.

    • virgo47 says:

      No experience here, client side was MS based in our case and out of my scope. I’ve never done any HTTP client stuff in Java actually.

  14. Vijay says:

    Thanks ! Great job.

  15. Kevin says:

    I’m running into one problem, and I think that I can resolve it by importing the correct files into my %JAVA_HOME%\jre\lib\security\cacerts. My only question is, which files do I use to import? keystore.jks for the keystore? And what for the certificate file? Thanks!

    • virgo47 says:

      I never did it this way, so I can’t help you here. Also – I’m no generic Java security expert :-) I merely put together one specific recipe that I tried for a few times. If you don’t do exactly what is in the recipe than you have to study the topic further, I don’t know all the details, sorry.

  16. Confused says:

    Thank you! It has taken me nearly 2 weeks to get to this point and your post had the final piece to the puzzle. Seeing client cert user info displayed by the server was a beautiful thing.

    • virgo47 says:

      I’m glad the post helped. I made it to clarify it a bit to myself as well – writing the instructions and then following them and proving them solid is always a good way how to learn something. Not to mention than when I need it again, I know where to go. :-) When it helps other people too I’m really happy.

  17. Akshay says:

    really very good one…..
    its solved my problem…
    Thank a lot…

  18. akshay1988in says:

    Hi I need a help from u hope u ll replay….
    I done the self signed cerificate as u told.
    But i need to use AEs -128 key encryption insted of RSA.
    Do i get any thoughts from U…?

    • virgo47 says:

      I can’t tell for sure, but it just boils down to whether openssl can do such a certificate and Java can use that. But I can’t test that really. I have no experience with AES, I don’t even know whether it’s the right type of cipher for certificates, sorry.

  19. Anonymous says:

    Great work! Thank you very much! Please keep this site alive!

  20. Patty says:

    Thank you so much! This is such great information.

  21. sldt says:

    Best post ever about ssl client certificates.
    Thanks a lot.

  22. herman vierendeels says:

    as said by others, great post

    some more things that are interessant to know

    force use of certificate for your application:

    your_webapp/WEB-INF/web.xml

    CLIENT-CERT
    Website1 Application

    $TOMCAT_HOME/conf/tomcat-users.xml

    remark: username should be cert.getSubjectX500Principal().toString();

  23. Anonymous says:

    Invaluable. You are a good citizen for posting all of this.

  24. Shibaram says:

    Hi,
    Thank you for this much informative post.
    Your post is very much helpful. I did achieve all the steps successfully and my ssl configuration seems to be working as desired till the last step you have mentioned above.
    But I am trying to implement CAS for SSO functionality which is requiring this SSL configuration.

    I am getting below error (In all cases like either when my client application is on another machine like in a tomcat on windows or on the same tomcat in linux env where my ssl is configured) :

    HTTP Status 500 – javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

    java.lang.RuntimeException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    org.jasig.cas.client.util.CommonUtils.getResponseFromServer(CommonUtils.java:341)
    org.jasig.cas.client.util.CommonUtils.getResponseFromServer(CommonUtils.java:305)

    Any help please ??

  25. Anonymous says:

    You are doing God’s work man. Keep it up!

  26. Pingback: RESTful Spring Security with Authentication Token | Virgo's Naive Stories

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 220 other followers