3 Replies Latest reply on Nov 17, 2011 11:38 AM by justincranford

    Two-factor authentication - client X.509 cert plus username+password

    justincranford

      I am developing a RESTeasy application in JBoss 6.0 AS Final. I have successfully configured and tested one-factor authentication using either BaseCertLoginModule (client X.509 certificates) or DatabaseServerLoginModule (username & hashed password). I need to configure and test using BOTH login modules. This is two-factor authentication, not one-factor authentication with password-stacking=usePassFirst. I need both login modules to authenticate. This is similar to requiring an RSA token to access a server, and then a username+password to login.

       

      Hopefully someone can point me in the right direction to do this with configuration or perhaps a simple customization. Here are my JBoss config files.

       

      File: login-context.xml

      <policy>

      <application-policy name="MySecurityDomain">

        <authentication>

         <login-module code="org.jboss.security.auth.spi.BaseCertLoginModule" flag="required">

          <module-option name="securityDomain">java:/jaas/MySecurityDomain</module-option>

         </login-module>

         <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">

          <module-option name="dsJndiName">java:/MyDataSource</module-option>

          <module-option name="principalsQuery">select credential from Actor where identity=?</module-option>

          <module-option name="rolesQuery">select r.name,'Roles' from actor a,role r where a.identity=? and a.id=r.actorid</module-option>

          <module-option name="hashAlgorithm">MD5</module-option>

          <module-option name="hashEncoding">base64</module-option>

         </login-module>

         <login-module code="org.jboss.security.ClientLoginModule" flag="required">

         </login-module>

        </authentication>

      </application-policy>

      </policy>

       

      File: server.xml

      <Connector address="${jboss.bind.address}" port="${jboss.web.https.port}" protocol="HTTP/1.1"

      secure="true" SSLEnabled="true" scheme="https" sslProtocol="TLS" clientAuth="true"

      keystoreFile="C:/serverkeystore.jceks" keystorePass="serverKS123456" truststoreType="JCEKS" keyAlias="serverscertandkey"

      truststoreFile="C:/servertruststore.jceks" truststorePass="serverTS123456" truststoreType="JCEKS"/>

       

      File: jboss-service.xml

      <mbean code="org.jboss.security.plugins.JaasSecurityDomain" name="jboss.security:service=SecurityDomain">

        <constructor><arg type="java.lang.String" value="MySecurityDomain"></arg></constructor>

        <attribute name="KeyStoreURL">C:/servertruststore.jceks</attribute>

        <attribute name="KeyStorePass">serverTS123456</attribute>

        <attribute name="KeyStoreType">JCEKS</attribute>

        <depends>jboss.security:service=JaasSecurityManager</depends>

      </mbean>

       

      File: jboss-web.xml

      <jboss-web>

          <security-domain flushOnSessionInvalidation="true">java:/jaas/MySecurityDomain</security-domain>

      </jboss-web>

       

      File: web.xml

      <security-constraint>
        <web-resource-collection>
         <web-resource-name>Resteasy</web-resource-name>
         <url-pattern>/*</url-pattern>
         <http-method>GET</http-method>
         <http-method>POST</http-method>
         <http-method>PUT</http-method>
         <http-method>DELETE</http-method>
        </web-resource-collection>

        <auth-constraint>

         <role-name>AdminRole</role-name>
        </auth-constraint>
        <user-data-constraint>
         <transport-guarantee>INTEGRAL</transport-guarantee>
        </user-data-constraint>
      </security-constraint>

      <login-config>
        <auth-method>CLIENT-CERT</auth-method>
        <realm-name>MySecurityDomain</realm-name>
      </login-config>

      <security-role>

        <role-name>AdminRole</role-name>

      </security-role>

       

       

      FYI, here are the commands I used to create my keystores used in these config files. Like I said, X.509 or BASIC authentication is working on its own, so that should be enough to verify these steps are correct.

       

      #######
      #Server
      #######
      # Generate server certificate and private key in keystore
      keytool -genkey -alias serverscertandkey -keyalg RSA -keysize 2048 -validity 3653 -dname "CN=*.domain.com, OU=ServerOrgUnit, O=ServerOrg, L=Ottawa, ST=Ontario, C=CA" -keystore C:\serverkeystore.jceks -storepass serverKS123456 -storetype JCEKS -keypass serverKS123456

       

      # Export server certificate from keystore (send to the client vendor)
      keytool -export -alias serverscertandkey -file c:\serversCertificate.cer -keystore C:\serverkeystore.jceks -storepass serverKS123456 -storetype JCEKS -keypass serverKS123456

       

      # Import client certificate into truststore (received from the client vendor)
      keytool -import -alias "CN=localhost, OU=ClientOrgUnit, O=ClientOrg, L=Toronto, ST=Ontario, C=CA" -file c:\clientCertificate.cer -keystore C:\servertruststore.jceks -storepass serverTS123456 -storetype JCEKS -keypass serverTS123456

       

      # View contents
      keytool -list -v -keystore C:\serverkeystore.jceks   -storepass serverKS123456 -storetype JCEKS
      keytool -list -v -keystore C:\servertruststore.jceks -storepass serverTS123456 -storetype JCEKS


      #######
      #Client
      #######
      # Generate client certificate and private key in keystore
      keytool -genkey -alias clientcertandkey -keyalg RSA -keysize 2048 -validity 3653 -dname "CN=localhost, OU=ClientOrgUnit, O=ClientOrg, L=Toronto, ST=Ontario, C=CA" -keystore C:\clientkeystore.jceks -storepass clientKS123456 -storetype JCEKS -keypass clientKS123456

       

      # Export client certificate from keystore (send to the server vendor)
      keytool -export -alias clientcertandkey -file c:\clientCertificate.cer -keystore C:\clientkeystore.jceks -storepass clientKS123456 -storetype JCEKS -keypass clientKS123456

       

      # Import server certificate into truststore (received from the server vendor)
      keytool -import -alias serverscert -file c:\serversCertificate.cer -keystore C:\clienttruststore.jceks -storepass clientTS123456 -storetype JCEKS -keypass clientTS123456

       

      # View contents
      keytool -list -v -keystore C:\clientkeystore.jceks   -storepass clientKS123456 -storetype JCEKS
      keytool -list -v -keystore C:\clienttruststore.jceks -storepass clientTS123456 -storetype JCEKS

       

       

       

      FYI, I am using HttpClient 4.0 included in JBoss 6 in my JUnit test class to connect to the server. This is how I point to the keystore and truststore.

       

      # Code snippet to set keystore & truststore system properties in JUnit test for REST client
      System.setProperty("javax.net.ssl.keyStore",           "C:/clientkeystore.jceks");
      System.setProperty("javax.net.ssl.trustStore",         "C:/clienttruststore.jceks");
      System.setProperty("javax.net.ssl.keyStoreType",       "JCEKS");
      System.setProperty("javax.net.ssl.trustStoreType",     "JCEKS");
      System.setProperty("javax.net.ssl.keyStorePassword",   "clientKS123456");
      System.setProperty("javax.net.ssl.trustStorePassword", "clientTS123456");

       

       

       

      Like I said, my client works using either BASIC or CLIENT-CERT authentication (set in web.xml), both only if I remove the other login module from login-context.xml. Putting both in login-context.xml with flag="required" does not work. Only one of the login modules succeeds while the other fails.

       

      I have a few theories, but perhaps there are other valid explanations I cannot think of:

       

      1) If I set web.xml to BASIC authentication and put DatabaseServerLoginModule first in login-context.xml, that one authenticates but then I get an invalid X.509 certificate error in BaseCertLoginModule. The message says something like expected X.509 cert, not java.lang.String.

      2) If I set web.xml to CLIENT-AUTH authentication and put BaseCertLoginModule first in login-context.xml, that one authenticates, but then I get an invalid username/password error in DatabaseServerLoginModule.

       

      What I think is happening is Jboss is doing password stacking. The first module does the callback challenge to the client and authenticates, but the second module uses the credential from the first without doing the required callback. How to make both modules do their callbacks independently? Client X.509 cert is for all users, and user/pass is user specific, so they are completely unrelated authentication methods.

       

      Thank you in advance for your help!

        • 1. Re: Two-factor authentication - client X.509 cert plus username+password
          kenhuangus

          Justin: I would like to follow up on your post and see if you have made any progress on the two factor authenction. We have a similar use case for two factor authentication and we will use DatabaseServerLoginModule and another Customer Login Module that we are goint to write to take in the one time passcode as 2nd factor. Please let me know your experience. Much appreciated. - Ken

          • 2. Re: Two-factor authentication - client X.509 cert plus username+password
            justincranford

            kenhuangus: JBoss 6.0 AS documentation said this should work but I never got it to work. You will find JBoss documentation says a lot of things but they are wrong in practice.

             

            For example, I also could not get one-factor authentication working switching between the two different authentication methods (DIGEST + server HTTPS, versus mutual client/server HTTPS). Actually I did get it to work, but it requires enabling unsafe SSL renegotiation. JBoss 6.0 AS documentation made no mention of that, or how it has been disabled by default (perhaps since JBoss 5.1 AS?), but JBoss would fail mutual authentication and spit out "renegotiation disabled" errors unless I turned on unsafe SSL renegotiation. This was a blocker I never solved.

             

            Another example, I could not get pre-emptive authentication to work. Even though I stuffed my DIGEST user/pass in my header and enabled pre-emptive login, JBoss always return an authentication prompt code. JBoss 6.0 AS documentation says pre-emptive login works, but I tried it and had no luck. I could not get past it so I gave up and lived with it.

             

            Actually one thing I did not try for two-factor authentication was having two web applications with independent authentication methods (i.e. one auth-method per web.xml really sucks). If the first application authenticates and forwards to the second application, and the second application only accepts authenticated connections from the first application, then that might work. Not sure if it requires any glue code, but it was a suggestion I read along the way that I never had time to try.

             

            My view of built-in JBoss authentication is 80/20. The built-in login modules cover perhaps 80% of use cases, but for the remaining 20% you are on your own. Also, that 80% erodes over time like in the case of unsafe SSL renegotiation which invalidated functionality that used to work.

             

            In the end, you will likely have to write your own JAAS module for two-factor authentication. That will get around the limitations of the vanilla JBoss JAAS module implementations.

             

            Good luck!

            • 3. Re: Two-factor authentication - client X.509 cert plus username+password
              justincranford

              kenhuangus: Here is a suggestion on the approach you can take to write your own JAAS module.

               

              Setup you web.xml auth-method to be CLIENT, and all other configs/keystores/truststores, as you normally would for one-factor mutual client/server authentication. Verify this works.

               

              Now switch out BaseCertLoginModule for your own JAAS module which overrides the login method. In your override, call super.login() but then copy the logic from DatabaseServerLoginModule to also do the user/pass validation in the database. Having only one auth-method per web.xml means JBoss authentication only calls one CallbackHandler, so you want that to be CLIENT, but you can hard-coded doing the DIGEST or BASIC auth by picking it out of the ServletRequest/HttpServletRequest header which you put in for pre-emptive authentication.

               

              Your JAAS module should be relatively compact with perhaps just a single override of the login() method. If you get it working please post. Thanks!

               

              Good luck!