CLIENT-CERT enabled EJB3 in JBoss5.1

Version 6

    After have struggled a bit to setup this stuff, learning a lot of interesting things, so here I am writing a  clean, pure description of how set up a CLIENT-CERT enable EJB3 application in JBoss5.1 which uses a DatabaseCertLoginModule. The goal of this document is more a cookbook, than a reference one so please forgive lacks of explaination.

     

    The EJB class

    Skipping the details of the implementaiton lets see only the top part of the class

     

    import org.jboss.security.annotation.SecurityDomain;
    import org.jboss.wsf.spi.annotation.WebContext;
    import javax.annotation.security.RolesAllowed;
    import javax.ejb.Stateless;
    import javax.jws.WebService;
    
    @WebService
    @Stateless(name="dpws")
    @SecurityDomain("discoveryDomain")
    @RolesAllowed("OPERATOR")
    @WebContext(contextRoot="/discovery", urlPattern="/dpws" ,authMethod="CLIENT-CERT", transportGuarantee="CONFIDENTIAL", secureWSDLAccess=false)
    public class MyDiscovery implements MyDiscoveryRemote, MyDiscoveryLocal{
         
         @WebMethod(operationName="getNames")
         public String[] getNames() {
            return {"Fabio", "Marco"};
        }
    }

     

    Few notes:

      • the authMethod (CLIENT-CERT) requires the client to send a certificate for the authentication
      • the transportGuarantee (CONFIDENTIAL) requires JBoss to open an SSL connection
      • the secureWSDLAccess (false) allow anyone to see the WSDL

    For other options see the annotations documentation

     

    • When packaging the EJB do not forget to add to the META-INF directory the jboss.xml file
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 3.2//EN" "http://www.jboss.org/j2ee/dtd/jboss_3_2.dtd">
    <jboss>
        <security-domain>java:/jaas/discoveryDomain</security-domain>
    </jboss>
    

     

    Note that the security-domain defers to the previous EJB's @SecurityDomains.

     

    Client's and Server's Certificates

    Open a terminal windows, create a temporary folder, say "Certificates", and executes the following commands

     

    Create the server keystore

    keytool -genkey -alias serverkeys -keyalg RSA -keystore server.keystore -storepass psw1 -keypass psw2



    Export the server certificate

     keytool -export -alias serverkeys -keystore server.keystore -storepass psw1 -file server.cer

     

    Create the client keystore

     keytool -genkey -alias clientkeys -keyalg RSA -keystore client.keystore -storepass pswA -keypass pswB

     

    Export the client certificate

     keytool -export -alias clientkeys -keystore client.keystore -storepass pswA -file client.cer

     

    Import the client certificate in the server.truststore

     keytool -import -v -keystore server.truststore  -storepass psw1 -file client.cer

     

    Import the serevr certificate in the server.truststore

     keytool -import -v -keystore client.truststore  -storepass pswA -file server.cer

     

    Anyway in a real world you just need only two steps:

    1. a certify signed by a trusted Certification Authority (CA) for your client
    2. import the CA certificate into your server.truststore
    3. import the server certificate in your client.truststore

    JBoss setup

    As should be known, ${jboss.server.home.dir} indicates the $JBOSS_HOME/server/YourInstance directory.

     

    • Copy the server.keystore and server.truststore files in ${jboss.server.home.dir}/conf

     

    • Enable the JBoss's SSL connector editing the ${jboss.server.home.dir}/deploy/jbossweb.sar/server.xml and adding the following configuration

     

    <!-- SSL/TLS Connector configuration using the admin devl guide keystore-->
          <Connector protocol="HTTP/1.1" SSLEnabled="true" 
               port="8443" address="${jboss.bind.address}"
               scheme="https" secure="true" clientAuth="false" 
               keystoreFile="${jboss.server.home.dir}/conf/server.keystore"
                    keystorePass="psw1" 
               truststoreFile="${jboss.server.home.dir}/conf/server.truststore"
                    truststorePass="psw2"
               sslProtocol = "TLS" />
    

     

    • In the ${jboss.server.home.dir}/deploy folder create a discovery-service.xml file (or eventually edit one *-service.xml file you already use) and add

     

    <!-- Configures the Discovery SecurityDomain -->
    
    <mbean code="org.jboss.security.plugins.JaasSecurityDomain" name="jboss.security:service=SecurityDomain">
         <constructor>
              <arg type="java.lang.String" value="discoveryDomain" />
         </constructor>
         <attribute name="KeyStoreURL">file:${jboss.server.home.dir}/conf/server.keystore</attribute>
         <attribute name="KeyStorePass">pws1</attribute>
         <attribute name="TrustStoreURL">file:${jboss.server.home.dir}/conf/server.truststore</attribute>
         <attribute name="TrustStorePass">pws2</attribute>
         <depends>jboss.security:service=JaasSecurityManager</depends>
    </mbean>
    
    <mbean code="org.jboss.security.auth.login.DynamicLoginConfig" name="jboss:service=DynamicLoginConfig">
         <attribute name="AuthConfig">
              file:${jboss.server.home.dir}/deploy/discovery-security-config.xml
         </attribute>
         <depends optional-attribute-name="LoginConfigService">
              jboss.security:service=XMLLoginConfig
         </depends>
         <depends optional-attribute-name="SecurityManagerService">          jboss.security:service=JaasSecurityManager
         </depends>
    </mbean>
    

     

         Notice that

      • the first MBean defines the domain name (discoveryDomain) and the location of the key/truststore
      • the second MBean defines the location of a specific policy configuraration file (so do not have to touch the ${jboss.server.home.dir}/conf/login-config.xml)

     

    • creates ${jboss.server.home.dir}/deploy/discovery-security-config.xml file

     

    <?xml version="1.0" encoding="UTF-8"?>
    <policy>
    
         <application-policy name="discoveryDomain">
    
              <authentication>
                   <login-module code="org.jboss.security.auth.spi.DatabaseCertLoginModule" flag = "required">
    
                        <module-option name="password-stacking">useFirstPass</module-option>
                        <module-option name="securityDomain">java:/jaas/discoveryDomain</module-option>
    
                        <module-option name="dsJndiName">jdbc/securityDS</module-option>
                        <module-option name="rolesQuery">select role, role_group from roles where principal_id=?</module-option>
    
                        <module-option name="verifier">ndg.common.jboss.security.CertVerifier</module-option>
    
                   </login-module>
    
              </authentication>
    
         </application-policy>
    
    </policy>
    
    

    Notice that the policy defines a custom verifier, CertVerifier, which allow the application to customize the authenticationl; an empty, always trusting implementation, looks like

     

     

    import java.security.KeyStore;
    import java.security.cert.X509Certificate;
    import org.jboss.security.auth.certs.X509CertificateVerifier;
    
    public class CertVerifier implements X509CertificateVerifier {
        public boolean verify(X509Certificate arg0, String arg1, KeyStore arg2, KeyStore arg3) {
            System.out.println("CIAO!!!!!");
            return true;
        }
    }
    

    Notes that this clas should be packed in a separate jar file and deployed possibly in creates ${jboss.server.home.dir}/lib

     

     

    • As should be clear the policy uses a DatabaseCertLoginModule class so we have to define a table in a given database using the roleTable.sql script

     

    CREATE TABLE roles( role_group character varying NOT NULL DEFAULT 'Roles'::character varying,
    "role" character varying NOT NULL,
    principal_id character varying NOT NULL)
    

    Notice that:

      • The role_group column has 'Roles' as DEFAULT. This value MUST NOT be changed nor overrided.
      • You need to insert by hand a row with your client role/PrincipalID. Such row will look like

     

    "Roles";"OPERATOR";"CN=Maurizio Nagni, OU=BADC, O=STFC, L=Harwell, ST=Oxfordshire, C=UK"
    
    

     

     

    • At the end you need a configuration file for the datasource similar to a discovery-ds.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <datasources>
         <local-tx-datasource>
         <jndi-name>jdbc/securityDS</jndi-name>
         [.......]
         <local-tx-datasource>
    <datasources>
    
    

     

    The Client setup

    Once you deploy all the necessary machinery in JBoss you should verify that your WSDL is visible but none of the services is available. Now you can generate a WebService Client using any tool you like most (I used the Eclipse) to write a quick and dirty test like

     

    import java.rmi.RemoteException;
    import javax.security.auth.login.LoginContext;
    import javax.xml.rpc.ServiceException;
    
    public class Client {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            MyDiscoveryService svc = new MyDiscoveryServiceLocator();
            MyDiscovery ws;
            try {
                ws = svc.getMyDiscoveryPort();
                String[] listNames = ws.getNames(); //my webService method
                for (String name : listNames)
                    System.out.println(name);
            } catch (ServiceException e) {
                e.printStackTrace();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
    

     

     

    Now you can run the Client class passing to the JVM the appropriate parameters, that is

     

    -Djavax.net.ssl.keyStore=/myPathTo/client.keystore
    -Djavax.net.ssl.keyStorePassword=pswA-Djavax.net.ssl.trustStore=/myPathTo/client.truststore
    -Djavax.net.ssl.trustStorePassword=pswB

     

    and verify that the client has been authenticated using its certificate and that the client name has been correctly associated to the role specified in the Roles table.