Two-factor authentication - client X.509 cert plus username+password
justincranford Apr 5, 2011 5:06 PMI 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!