12
votes

I am trying to make a Tomcat web application use client certificate authentication for incoming connections. Everything works fine when using clientAuth=true in server.xml, however due to other applications running on the same server, we cannot use this in the production environment.

Is there a way to form a web.xml document such that it forces client certificate usage for the application in the same way as clientAuth=true? It seems like using the CLIENT-CERT setting also requires you to setup a tomcat user account for each certificate which is to access your system? We need to be able to allow all certificates which are from a specified CA (set in the server truststore) where the subject matches certain rules (checked within the actual application). I was hoping that something like the following would work, but no luck yet!

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Everything</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>
<login-config>
    <auth-method>CLIENT-CERT</auth-method>
</login-config>
2

2 Answers

6
votes

First of all, it sounds like you want clientAuth=want instead of clientAuth=true: that will allow the client to provide a certificate but not absolutely require one.

When you use authentication of any kind, Tomcat (or any servlet container for that matter) must be able to build a Principal object out of it -- one that has a name (usually a username). The container then must decide what roles the user has in order to properly authorize a particular request. So, Tomcat will need to know about the users beforehand in order to make authorization work.

On the other hand, if you don't need any authorization, you could set clientAuth=want and then use a Filter to verify the certificate. There's no need to use CLIENT-CERT authentication if you are already doing your own checking.

3
votes

I was just reseaching solution to above problem and finally found a solution:

  1. Configure tomcat with connector clientAuth="false" attribute(Otherwise all secure connections to server will do mutual, client server, ssl authentification.

  2. Add the following in web.xml(I have just shown here example)

    <security-constraint>
        <web-resource-collection>
            <url-pattern>/LoginTestServlet1</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>manager</role-name>
        </auth-constraint>
        <user-data-constraint>
            <!-- transport-guarantee can be CONFIDENTIAL, INTEGRAL, or NONE -->
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
     <security-constraint>
        <web-resource-collection>
            <url-pattern>/LoginTestServlet2</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>manager</role-name>
        </auth-constraint>
       <!--  <user-data-constraint>
            transport-guarantee can be CONFIDENTIAL, INTEGRAL, or NONE
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>  
        </user-data-constraint> -->
    </security-constraint>
     <login-config>
        <auth-method>CLIENT-CERT</auth-method>
        <realm-name>certificate</realm-name>
      </login-config>
    

    manager

  3. In tomcat users-users.xml add the following(pls note that if trust store has almost identical certificates then you should clearly identify your certificate as follows)

    <role rolename="manager"/>
    

    < user username="[email protected], CN=KS, OU=OFF, O=OFS, L=Bukhara,
    ST=Bukhara, C=UZ" password="" roles="manager"/>

  4. put in a browser(or curl) address line:

    https://yourdomain.com:8443/LoginTest/LoginTestServlet1 or
    https://yourdomain.com:8443/LoginTest/LoginTestServlet2

  5. For this to work you have to add certificate to browser personal certificate list(if you are testing with browser). I have tried with Mozilla Firefox and it will easyly let you do this.(But it only accepts b12 certificate so it is suggested that you should use openssl with java keytool). If everything configured right then you will get prompt from mozilla to choose certificate from existing ones. If you are using curl(it is used for automatic web interfaces testing then use the following commant line to test(I have just given an example here.) Plese note that you should choose certificate which you imported into trust store.

    curl -s -k --cert selfsigned.pem --key key.pem -v --anyauth https://yourdomain.com:8443/LoginTest/LoginTestServlet1 --cacert selfsigned.pem or curl -s -k --cert selfsigned.pem --key key.pem -v --anyauth http://yourdomain.com:8080/LoginTest/LoginTestServlet2 --cacert selfsigned.pem

Note: My connector looks like as follows:

<Connector port="8443"
       maxThreads="150"  scheme="https" secure="true" SSLEnabled="true"
       sslProtocol="TLS" keystoreType="PKCS12"  truststoreType="PKCS12" clientAuth="false"
               keystoreFile="C:/Program Files/glassfish-3.1.2/glassfish/domains/domain1/config/cacerts.pkcs12"
               truststoreFile= "C:/Program Files/glassfish-3.1.2/glassfish/domains/domain1/config/cacerts.pkcs12"
               truststorePass="changeit"
               keystorePass="changeit"
               protocol="org.apache.coyote.http11.Http11Protocol">