There are some situations where you want to authenticate an user against a database or using X509 certificates and then assign roles according to the mapping in a different backend, for example a LDAP server.
JBoss has a long history of supporting the password-stacking=useFirstPass option in its login modules but can it be used with LdapExtLoginModule so it is used for role mapping only? In recent versions of the application server (such as EAP 5.1) the answer is yes, as we will see in the following example.
Description
This example will show how to authenticate to JBoss' jmx-console with a X509 certificate (using BaseCertLoginModule) and getting the user's roles assigned by a LDAP server (using LdapExtLoginModule).
Creating the client certificate
We use keytool for this:
keytool -genkeypair -alias client -keysize 1024 -validity 365 -keystore client.keystore -dname "cn=Marcus,dc=jboss,dc=org"
This will create a JKS keystore containing a X509 certificate valid for one year.
Verify the certificate using keytool also:
[mmoyses@mmoyses temp]$ keytool -list -v -keystore client.keystore Enter keystore password: Keystore type: JKS Keystore provider: SUN Your keystore contains 1 entry Alias name: client Creation date: Nov 24, 2010 Entry type: PrivateKeyEntry Certificate chain length: 1 Certificate[1]: Owner: CN=Marcus, DC=jboss, DC=org Issuer: CN=Marcus, DC=jboss, DC=org Serial number: 4ced116a Valid from: Wed Nov 24 11:21:46 BRST 2010 until: Thu Nov 24 11:21:46 BRST 2011 Certificate fingerprints: MD5: B9:F2:25:97:2D:4D:4F:A9:1B:E9:A3:09:8D:A0:D4:C8 SHA1: 56:31:FE:B6:4B:11:83:2C:D9:C8:15:09:B3:C3:6D:38:83:99:D5:E7 Signature algorithm name: SHA1withDSA Version: 3 ******************************************* *******************************************
Now we need to export this certificate so it can be later added to the server's truststore.
Here is how:
[mmoyses@mmoyses temp]$ keytool -exportcert -alias client -keystore client.keystore -file client.certificate Enter keystore password: Certificate stored in file <client.certificate>
This client.certificate can be verified to see if the certificate was correctly exported:
[mmoyses@mmoyses temp]$ keytool -printcert -v -file client.certificate Owner: CN=Marcus, DC=jboss, DC=org Issuer: CN=Marcus, DC=jboss, DC=org Serial number: 4ced116a Valid from: Wed Nov 24 11:21:46 BRST 2010 until: Thu Nov 24 11:21:46 BRST 2011 Certificate fingerprints: MD5: B9:F2:25:97:2D:4D:4F:A9:1B:E9:A3:09:8D:A0:D4:C8 SHA1: 56:31:FE:B6:4B:11:83:2C:D9:C8:15:09:B3:C3:6D:38:83:99:D5:E7 Signature algorithm name: SHA1withDSA Version: 3
We also need the client certificate in the PKCS12 format so it can imported into the browser. This is how to do it:
keytool -importkeystore -srckeystore client.keystore -destkeystore client.p12 -srcstoretype JKS -deststoretype PKCS12
This will create the file client.p12 which contains the same certificate but in the PKCS12 format. To verify it, execute:
[mmoyses@mmoyses temp]$ openssl pkcs12 -info -in client.p12 Enter Import Password: MAC Iteration 1024 MAC verified OK PKCS7 Data Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 1024 Bag Attributes friendlyName: client localKeyID: 54 69 6D 65 20 31 32 39 30 36 30 35 35 31 39 38 37 36 Key Attributes: <No Attributes> Enter PEM pass phrase: Verifying - Enter PEM pass phrase: -----BEGIN ENCRYPTED PRIVATE KEY----- MIIBljBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIacoVCCQSp9sCAggA MBQGCCqGSIb3DQMHBAjfDdmRO6O0fASCAVBosjD/P8OcLN3UtSGPXmK5FAuWnfeM 72IufOdNoUoL/krl6Rij7qet8f6QTedDPwF479S6Vt1jMwT779NrI+gCmljnyhWr xlotRFgF/qxVVCzITAWVlaqmAp7SfqmRF3+pjCcGY4Ihoz/bTDYj1Rn43w09mjBa Lv9eyGKcHcbN1HtiP6ivLE69QuQrUKTJXrJsxOuzWaKIzcfwkrPqPQfm5DUCDYE2 DaQ7TP71aYyGJPaE7SE6IY7wRrw+X7mhsrNRLyXaFXPKM1M+sbPux0PS7KhEZycU s+KygcPAGwjD2JQSgCbwrJshL3VKMJduMJq0qfJ37bR99Akf7XcXfUjUzL/w8+AI 6UKxhHgUCfrX1UCZl941g8VJraYIoU5Dtd3rqTSvQarFnHQ9lgolbzQeXyYxsYHM GbU//AMVttNOJlR9Hv4/xMHwYOjxnBsuWT4= -----END ENCRYPTED PRIVATE KEY----- PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 1024 Certificate bag Bag Attributes friendlyName: CN=Marcus,DC=jboss,DC=org localKeyID: 54 69 6D 65 20 31 32 39 30 36 30 35 35 31 39 38 37 36 subject=/DC=org/DC=jboss/CN=Marcus issuer=/DC=org/DC=jboss/CN=Marcus -----BEGIN CERTIFICATE----- MIICszCCAnGgAwIBAgIETO0RajALBgcqhkjOOAQDBQAwPTETMBEGCgmSJomT8ixk ARkWA29yZzEVMBMGCgmSJomT8ixkARkWBWpib3NzMQ8wDQYDVQQDEwZNYXJjdXMw HhcNMTAxMTI0MTMyMTQ2WhcNMTExMTI0MTMyMTQ2WjA9MRMwEQYKCZImiZPyLGQB GRYDb3JnMRUwEwYKCZImiZPyLGQBGRYFamJvc3MxDzANBgNVBAMTBk1hcmN1czCC AbcwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADD Hj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gE exAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/Ii Axmd0UgBxwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4 V7l5lK+7+jrqgvlXTAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozI puE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4Vrl nwaSi2ZegHtVJWQBTDv+z0kqA4GEAAKBgHaY1mg76jjXTkDD7kSPurgfaK6lJdzW sUPDAsxAOgCUtfh/oZDnkKKqixRu5KI74Hx7gHRtFRSBzwOx+jWrJ5HG2ZVJR7Io HXbRmEihLMoXIze5n9AzJCNJsfm1GWGsGxu/WiNdI5mJPCfECTPb53nk3JkxSBId UXpuIOhQzkPfMAsGByqGSM44BAMFAAMvADAsAhQVNsju4jwV6PT/7Nf4c4USOWe5 SAIUSuFYIJEOduiagbA6KwG2L9PcZ8k= -----END CERTIFICATE-----
Import this certificate into your browser. I'm not going into the details of that.
Configuring JBoss
I. Setting up a SSL connector
a) Creating the server certificate
Again we use keytool for this:
keytool -genkeypair -alias jboss -keysize 1024 -validity 365 -keystore jboss.keystore -dname "cn=JBoss Server,dc=jboss,dc=org"
This will create a JKS keystore containing a X509 certificate valid for one year.
Verifying the certificate:
[mmoyses@mmoyses temp]$ keytool -list -v -keystore jboss.keystore Enter keystore password: Keystore type: JKS Keystore provider: SUN Your keystore contains 1 entry Alias name: jboss Creation date: Nov 24, 2010 Entry type: PrivateKeyEntry Certificate chain length: 1 Certificate[1]: Owner: CN=JBoss Server, DC=jboss, DC=org Issuer: CN=JBoss Server, DC=jboss, DC=org Serial number: 4ced102b Valid from: Wed Nov 24 11:16:27 BRST 2010 until: Thu Nov 24 11:16:27 BRST 2011 Certificate fingerprints: MD5: 07:D5:49:FF:5C:B0:CB:BF:CD:CD:8C:50:98:CE:8F:74 SHA1: 60:E2:32:8E:F0:7D:0F:F6:A0:E2:65:F9:C1:79:C6:C8:76:AA:1A:8C Signature algorithm name: SHA1withDSA Version: 3 ******************************************* *******************************************
b) Creating the server truststore
For self signed certificates like the ones showed here we need to import each one into the server's truststore so they are accepted at the socket level. Here is how to import the certificate:
[mmoyses@mmoyses temp]$ keytool -importcert -alias "CN=Marcus, DC=jboss, DC=org" -file client.certificate -keystore clients.truststore Enter keystore password: Re-enter new password: Owner: CN=Marcus, DC=jboss, DC=org Issuer: CN=Marcus, DC=jboss, DC=org Serial number: 4ced116a Valid from: Wed Nov 24 11:21:46 BRST 2010 until: Thu Nov 24 11:21:46 BRST 2011 Certificate fingerprints: MD5: B9:F2:25:97:2D:4D:4F:A9:1B:E9:A3:09:8D:A0:D4:C8 SHA1: 56:31:FE:B6:4B:11:83:2C:D9:C8:15:09:B3:C3:6D:38:83:99:D5:E7 Signature algorithm name: SHA1withDSA Version: 3 Trust this certificate? [no]: yes Certificate was added to keystore
Notice the certificate was imported using an alias equals to the DN of the certificate. This is required by BaseCertLoginModule.
You can verifiy the truststore in the same way:
[mmoyses@mmoyses temp]$ keytool -list -v -keystore clients.truststore Enter keystore password: Keystore type: JKS Keystore provider: SUN Your keystore contains 1 entry Alias name: cn=marcus, dc=jboss, dc=org Creation date: Nov 24, 2010 Entry type: trustedCertEntry Owner: CN=Marcus, DC=jboss, DC=org Issuer: CN=Marcus, DC=jboss, DC=org Serial number: 4ced116a Valid from: Wed Nov 24 11:21:46 BRST 2010 until: Thu Nov 24 11:21:46 BRST 2011 Certificate fingerprints: MD5: B9:F2:25:97:2D:4D:4F:A9:1B:E9:A3:09:8D:A0:D4:C8 SHA1: 56:31:FE:B6:4B:11:83:2C:D9:C8:15:09:B3:C3:6D:38:83:99:D5:E7 Signature algorithm name: SHA1withDSA Version: 3 ******************************************* *******************************************
Notice that the certificate is now a trustedCertEntry and not a privateKeyEntry as it used to be in the original keystore.
c) Setting up the connector
Modify deploy/jbossweb.sar/server.xml and add a SSL connector using the keystore and truststore we created in earlier steps:
<Connector protocol="HTTP/1.1" SSLEnabled="true" port="8443" address="${jboss.bind.address}" scheme="https" secure="true" clientAuth="true" keystoreFile="/home/mmoyses/certificate/temp/jboss.keystore" keystorePass="changeit" truststoreFile="/home/mmoyses/certificate/temp/clients.truststore" trustStorePass="changeit" sslProtocol = "TLS" />
Notice the attribute clientAuth="true". This is required for CLIENT-CERT authentication.
II. Setting up the jmx-console web application to require client certificates
Just modify depoy/jmx-console.war/WEB-INF/web.xml and change:
<auth-method>BASIC</auth-method>
to
<auth-method>CLIENT-CERT</auth-method>
III. Setting up the security domain
a) Configuring the login modules
Modify conf/login-config.xml to authenticate using BaseCertLoginModule and LdapExtLoginModule:
<application-policy name="jmx-console"> <authentication> <login-module code="org.jboss.security.auth.spi.BaseCertLoginModule" flag="required"> <module-option name="password-stacking">useFirstPass</module-option> <module-option name="securityDomain">java:/jaas/clients</module-option> </login-module> <login-module code="org.jboss.security.auth.spi.LdapExtLoginModule" flag="required"> <module-option name="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</module-option> <module-option name="java.naming.provider.url">ldap://localhost/</module-option> <module-option name="java.naming.security.authentication">simple</module-option> <module-option name="bindDN">cn=Root,dc=jboss,dc=org</module-option> <module-option name="bindCredential">secret</module-option> <module-option name="baseCtxDN">dc=jboss,dc=org</module-option> <module-option name="rolesCtxDN">ou=Roles,dc=jboss,dc=org</module-option> <module-option name="baseFilter">(seeAlso={0})</module-option> <module-option name="roleFilter">(member={1})</module-option> <module-option name="roleAttributeID">cn</module-option> <module-option name="roleRecursion">0</module-option> <module-option name="password-stacking">useFirstPass</module-option> </login-module> </authentication> </application-policy>
b) Deploy the JaasSecurityDomain bean
BaseCertLoginModule requires a JaasSecurityDomain bean (the securityDomain option in the login module) to validate the certificates. We will use the same truststore created for the SSL connector for this. That's why we need to add the trusted certificates using the DN as the alias name of the entry.
To deploy the bean create a file named clients-jboss-beans.xml inside the deploy/ directory with the following contents:
<?xml version="1.0" encoding="UTF-8"?> <!-- JaasSecurityDomain Microcontainer Beans --> <deployment xmlns="urn:jboss:bean-deployer:2.0"> <bean name="JaasSecurityDomain:clients" class="org.jboss.security.plugins.JaasSecurityDomain"> <constructor> <parameter>clients</parameter> </constructor> <property name="keyStoreURL">/home/mmoyses/certificate/temp/clients.truststore</property> <property name="keyStorePass">changeit</property> <annotation>@org.jboss.aop.microcontainer.aspects.jmx.JMX(name="jboss.security:service=JaasSecurityDomain,domain=clients",exposedInterface=org.jboss.security.plugins.JaasSecurityDomainMBean.class)</annotation> </bean> </deployment>
This will create the java:/jaas/clients security domain needed by BaseCertLoginModule.
Example LDAP server ldif
dn: dc=jboss,dc=org objectclass: top objectclass: dcObject objectclass: organization dc: jboss o: example dn: cn=Root,dc=jboss,dc=org objectclass: organizationalRole cn: Root dn: ou=Roles,dc=jboss,dc=org objectClass: top objectClass: organizationalUnit ou: Roles dn: cn=JBossAdmin,ou=Roles,dc=jboss,dc=org objectClass: top objectClass: groupOfNames cn: JBossAdmin description: the JBossAdmin role member: cn=Marcus,dc=jboss,dc=org dn: cn=HttpInvoker,ou=Roles,dc=jboss,dc=org objectClass: groupOfNames objectClass: top cn: HttpInvoker description: the HttpInvoker role member: cn=Marcus,dc=jboss,dc=org dn: cn=Marcus,dc=jboss,dc=org objectclass: person cn: Marcus sn: Moyses userPassword: password seeAlso: CN=Marcus, DC=jboss, DC=org
Notice I used the seeAlso attribute to set the DN of the user (and also the DN of the user certificate) and thus mapped the baseFilter option of LdapExtLoginModule to use this attribute. AD users should definitely use the distinguishedName attribute in this place.
With everything set up correctly we can now login to jmx-console using our certificate.
This is an example log showing the successful authentication and role mapping:
2010-11-24 15:07:53,465 TRACE [org.jboss.security.plugins.auth.JaasSecurityManagerBase.jmx-console] (http-127.0.0.1-8443-1) Begin isValid, principal:CN=Marcus, DC=jboss, DC=org, cache info: null 2010-11-24 15:07:53,465 TRACE [org.jboss.security.plugins.auth.JaasSecurityManagerBase.jmx-console] (http-127.0.0.1-8443-1) defaultLogin, principal=CN=Marcus, DC=jboss, DC=org 2010-11-24 15:07:53,465 TRACE [org.jboss.security.auth.login.XMLLoginConfigImpl] (http-127.0.0.1-8443-1) Begin getAppConfigurationEntry(jmx-console), size=11 2010-11-24 15:07:53,465 TRACE [org.jboss.security.auth.login.XMLLoginConfigImpl] (http-127.0.0.1-8443-1) End getAppConfigurationEntry(jmx-console), authInfo=AppConfigurationEntry[]: [0] LoginModule Class: org.jboss.security.auth.spi.BaseCertLoginModule ControlFlag: LoginModuleControlFlag: required Options: name=securityDomain, value=java:/jaas/clients name=password-stacking, value=useFirstPass [1] LoginModule Class: org.jboss.security.auth.spi.LdapExtLoginModule ControlFlag: LoginModuleControlFlag: required Options: name=baseFilter, value=(seeAlso={0}) name=java.naming.security.authentication, value=simple name=java.naming.factory.initial, value=com.sun.jndi.ldap.LdapCtxFactory name=roleFilter, value=(member={1}) name=bindCredential, value=**** name=bindDN, value=cn=Root,dc=jboss,dc=org name=java.naming.provider.url, value=ldap://localhost/ name=rolesCtxDN, value=ou=Roles,dc=jboss,dc=org name=roleRecursion, value=0 name=baseCtxDN, value=dc=jboss,dc=org name=roleAttributeID, value=cn name=password-stacking, value=useFirstPass 2010-11-24 15:07:53,469 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) initialize 2010-11-24 15:07:53,470 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) Security domain: jmx-console 2010-11-24 15:07:53,470 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) securityDomain=java:/jaas/clients 2010-11-24 15:07:53,470 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) found domain: org.jboss.security.plugins.JaasSecurityDomain 2010-11-24 15:07:53,470 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) exit: initialize(Subject, CallbackHandler, Map, Map) 2010-11-24 15:07:53,470 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) enter: login() 2010-11-24 15:07:53,470 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) login 2010-11-24 15:07:53,470 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) enter: getAliasAndCert() 2010-11-24 15:07:53,471 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) exit: getAliasAndCert() 2010-11-24 15:07:53,471 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) enter: validateCredentail(String, X509Certificate) 2010-11-24 15:07:53,471 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) Supplied Credential: 4ced116a CN=Marcus, DC=jboss, DC=org Existing Credential: 4ced116a CN=Marcus, DC=jboss, DC=org 2010-11-24 15:07:53,471 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) The supplied certificate matched the certificate in the keystore. 2010-11-24 15:07:53,471 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) exit: validateCredentail(String, X509Certificate) 2010-11-24 15:07:53,471 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) User 'CN=Marcus, DC=jboss, DC=org' authenticated, loginOk=true 2010-11-24 15:07:53,471 DEBUG [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) exit: login() 2010-11-24 15:07:53,476 TRACE [org.jboss.security.auth.spi.LdapExtLoginModule] (http-127.0.0.1-8443-1) initialize 2010-11-24 15:07:53,476 TRACE [org.jboss.security.auth.spi.LdapExtLoginModule] (http-127.0.0.1-8443-1) Security domain: jmx-console 2010-11-24 15:07:53,476 TRACE [org.jboss.security.auth.spi.LdapExtLoginModule] (http-127.0.0.1-8443-1) login 2010-11-24 15:07:53,486 TRACE [org.jboss.security.auth.spi.BaseCertLoginModule] (http-127.0.0.1-8443-1) commit, loginOk=true 2010-11-24 15:07:53,486 TRACE [org.jboss.security.auth.spi.LdapExtLoginModule] (http-127.0.0.1-8443-1) commit, loginOk=true 2010-11-24 15:07:53,487 TRACE [org.jboss.security.auth.spi.LdapExtLoginModule] (http-127.0.0.1-8443-1) Logging into LDAP server, env={java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory, java.naming.security.principal=cn=Root,dc=jboss,dc=org, roleRecursion=0, password-stacking=useFirstPass, baseCtxDN=dc=jboss,dc=org, roleAttributeID=cn, roleFilter=(member={1}), rolesCtxDN=ou=Roles,dc=jboss,dc=org, baseFilter=(seeAlso={0}), jboss.security.security_domain=jmx-console, java.naming.provider.url=ldap://localhost/, bindDN=cn=Root,dc=jboss,dc=org, java.naming.security.authentication=simple, bindCredential=secret, java.naming.security.credentials=***} 2010-11-24 15:07:53,518 TRACE [org.jboss.security.auth.spi.LdapExtLoginModule] (http-127.0.0.1-8443-1) Assign user to role JBossAdmin 2010-11-24 15:07:53,519 TRACE [org.jboss.security.auth.spi.LdapExtLoginModule] (http-127.0.0.1-8443-1) Assign user to role HttpInvoker
Comments