Thursday, March 29. 2018
Configuring kerberos/spnego login in tomcat


Another quick entry this time. The past week I needed to configure kerberos/spnego login in tomcat. I have never done this setup before and it has some specific things, so I prefer to write an entry and do not lose time if I need this a second time. The kerberos/spnego login can be configured by default in tomcat, you do not need anything extra. The entry will comment step by step the configuration.
Install the last version of tomcat (9.0.6 right now).
unzip apache-tomcat-9.0.6.zip cd apache-tomcat-9.0.6/bin chmod u+x *.sh
Create the kerberos user for the service principal name (SPN), this is always the same and commented in all the previous entries about kerberos. I am going to use a machine called jboss.sample.com, so the user is created as follows.
In the account tab the options Password never expires and This account supports Kerberos AES 256 bit encryption are selected.
Then the keytab is created with all the cartographic algorithms.
ktpass -princ HTTP/jboss.sample.com@SAMPLE.COM -mapuser jboss@SAMPLE.COM -pass XXXXX -crypto ALL -ptype KRB5_NT_PRINCIPAL -out jboss-all.keytab
Follow the instructions in the tomcat site to configure kerberos in the continer. Some files need to be prepared in the conf directory of the installation. First the krb5.ini is configured (I reused a previous vm, my windows server is a 2016 essentials called sampleserver.sample.com).
[libdefaults] default_realm = SAMPLE.COM default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes256-cts aes128-cts-hmac-sha1-96 aes128-cts rc4-hmac default_tgs_enctypes = aes256-cts-hmac-sha1-96 aes256-cts aes128-cts-hmac-sha1-96 aes128-cts rc4-hmac permitted_enctypes = aes256-cts-hmac-sha1-96 aes256-cts aes128-cts-hmac-sha1-96 caes128-cts rc4-hmac forwardable = true dns_lookup_realm = false dns_lookup_kdc = false [realms] SAMPLE.COM = { kdc = sampleserver.sample.com default_domain = SAMPLE.COM } [domain_realm] .SAMPLE.COM = SAMPLE.COM SAMPLE.COM = SAMPLE.COM
Then the jaas.conf is configured to use the previous keytab, which is also copied to the conf directory.
com.sun.security.jgss.krb5.initiate { com.sun.security.auth.module.Krb5LoginModule required doNotPrompt=true principal="HTTP/jboss.sample.com@SAMPLE.COM" useKeyTab=true keyTab="/opt/jboss/apache-tomcat-9.0.6/conf/jboss-all.keytab" storeKey=true debug=true; }; com.sun.security.jgss.krb5.accept { com.sun.security.auth.module.Krb5LoginModule required doNotPrompt=true principal="HTTP/jboss.sample.com@SAMPLE.COM" useKeyTab=true keyTab="/opt/jboss/apache-tomcat-9.0.6/conf/jboss-all.keytab" storeKey=true debug=true; };
Add some options to the tomcat startup, I added them to the beginning of the catalina.sh. Those options are to add debug and the configuration for the previous files.
# krb5 opts JAVA_OPTS="$JAVA_OPTS -Djava.security.krb5.conf=/etc/tomcat/krb5.ini -Djava.security.auth.login.config=/etc/tomcat/jaas.conf -Dsun.security.krb5.debug=true -Dsun.security.spnego.debug=true -Djava.security.egd=file:/dev/./urandom"
Finally an AD realm should be added as the user repository for tomcat (the user will be authenticated using SPNEGO and then the user will be searched in the AD for groups). This point is the one that is very specific for tomcat (at least for me), until I understood that I needed to create a valid realm for the kerberos login I was a bit lost. A JNDI realm is added and configured to search the user and get the roles via the memberOf attribute in the user (no role search, roles will be the complete DN).
<Realm className="org.apache.catalina.realm.JNDIRealm" resourceName="UserDatabase" debug="9" connectionName="cn=Admin,cn=users,dc=sample,dc=com" connectionPassword="XXXXX" connectionURL="ldap://sampleserver.sample.com:389" userRoleName="memberOf" userBase="cn=users,dc=sample,dc=com" userSearch="(&(objectClass=user)(sAMAccountName={0}))" userSubtree="true" useDelegatedCredential="false" />
And that is all. Now you just need to deploy the application that should be configured to use a SPNEGO login configuration. In my case the app is a simple JSP and the web.xml just protects all the application.
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <display-name>hello-spnego-tomcat</display-name> <security-constraint> <display-name>Protect all app</display-name> <web-resource-collection> <web-resource-name>exampleWebApp</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>*</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>SPNEGO</auth-method> <realm-name>SPNEGO</realm-name> </login-config> <security-role> <description>Role required to log in to the Application</description> <role-name>*</role-name> </security-role> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
And the JSP uses this idea to get the GSSCredential which could be used to delegate the token to another web service (calling another web service or, in general, reuse the kerberos token for delegation). In tomcat the credential is inside its specific principal implementation.
This is the video of the single sign-on using the kerberos token.
Today's entry is simple, just a kerberos application using tomcat and the default spnego authentication to provide SSO in a windows environment. I personally had never configured tomcat to use kerberos and it always takes some time to setup new things. A JNDI realm is needed, the user obtained via kerberos is searched in the AD and the principal is created (username, roles and GSS credential are stored inside the principal). The configuration for the web application has no special characteristics. My simple JSP to test in in this war file (I have not even created a maven to test this).
Regards!
Saturday, February 24. 2018
OCSP in the Apache web server


OCSP (Online Certificate Status Protocol) is a recurring topic of the blog. I am certainly not an expert, but I fear that there are very few people that really know about this protocol deeply so, sometimes, I am just the best suited around. There have been two or three times already that I needed to configure the Apache httpd server to deal with OCSP for verifying client certificates (TLS mutual authentication) and, in almost all of them, I have had issues with the setup. In all the occasions we were trying to remove the CRL (Certificate Revocation List) configuration for another one using OCSP. And it's usually not so easy.
The CRL configuration is mainly managed by the option SSLRevocationCheck since version 2.4 which controls how the openssl is called to perform the verification of the client certificate. This way you can configure to check the CRL only over the final client certificate (leaf) or over all the chain of certificates (root and intermediate CAs and the final certificate). Besides there is a very useful flag to let pass a certificate for which the CRL is not found (no_crl_for_cert_ok option). In general the option for configuring CRL is very complete and let you setup almost any situation.
Nevertheless the OCSP configuration is much less rich. The revocation is enabled with the option SSLOCSPEnable (only on or off values) and then there are more options to locate the URL of the responder to call (options SSLOCSPDefaultResponder and SSLOCSPOverrideResponder let you configure a default OCSP responder in case the certificate does not have the proper extension, you can even override that extension and always call the same responder no matter what the certificate says), dealing with signature responses and timeouts. But none of the options controls when the OCSP verification is done, if you enable it all the chain is always validated (no leaf of no_crl_for_cert_ok equivalents here).
This difference in configuration makes sometimes impossible to change from CRL to OCSP. The first time the issue was that the customer just wanted to validate the certificates that had the OCSP extension in it (it's not possible because if you enable OCSP you need a valid responder, if no one is found the certificate is not validated and that's an error). In the next times the problem was that the OCSP responder was only for final certificates, the intermediate CA has no extension to validate it (CRL or OCSP) and, therefore, the CRL was configured to only check leaf but the same cannot be configured using OCSP (the intermediate CA was validated, unknown returned and the certificate was not admitted). So, in summary, if the configuration for revocation is a bit complex (several CAs, intermediates, different responders, certificates with extensions and without them,...) the OCSP is a understatement.
The last weekend I decided to check how difficult was configuring OCSP exactly in the same way than CRL (none, leaf and chain with a flag no_ocsp_for_cert_ok to allow the certificates that has no responder associated to go in). The modifications finally resulted in just a few lines of code so I decided to submit a bugzilla just to know what the apache guys think about it. With the changes the CRL behavior can be more easily mapped to the OCSP one, at least I think that I would have configured all my failed previous attempts. I do not know how many people is interested in OCSP revocation for client certificates (maybe it's a minor issue) but, if somebody is using it, the changes can be interesting.
Let's see. Best regards!
Saturday, January 20. 2018
Certificates and WS-Security with JavaEE integration in Wildfly


This the fourth and last entry about certificate login in Wildfly for a jax-ws application. The first pair of posts were dedicated to TLS mutual authentication (the client certificate presented at TLS level was then used to identify the user inside JavaEE). The previous entry introduced WS-Security, the SOAP protocol is extended to use headers that can add different security features. Using WSS the message is signed and the X509 certificate is included in the request message as a binary token. In this last entry my intention was just using that certificate in the header and login the user with it. But I had problems implementing this last step and that was the reason to separate this topic in a new entry. I am going to explain the steps to finally make it work one by one.
By default wildfly has an interceptor that can (out of the box) log a user in with a username/password token. This interceptor class is SubjectCreatingPolicyInterceptor which obtains the token, gets the username and password inside it and validates the pair against the security domain configured in the endpoint. But the entry's goal is different, we are using a certificate token (X509), and there is no interceptor for that by default. Therefore I decided to develop a CertificateBinaryTokenInterceptor.
My class has a method to obtain the certificate from the message. The WSS4J implementation stores all the validations executed over the request in the message itself. The signature verification can be retrieved from those validations and the certificate is just part of the validation results. The method iterates over the list of verifications and gets the one that is a signature, is validated (signature has been checked) and has a certificate attached.
private X509Certificate getCertificate(SoapMessage message) {
List handlerResults = (List) message.get(WSHandlerConstants.RECV_RESULTS);
if (handlerResults != null) {
for (WSHandlerResult handlerResult : handlerResults) {
List engResults = handlerResult.getResults();
if (engResults != null) {
for (WSSecurityEngineResult engResult : engResults) {
Boolean validated = (Boolean) engResult.get(WSSecurityEngineResult.TAG_VALIDATED_TOKEN);
X509Certificate cert = (X509Certificate) engResult.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE);
Integer action = (Integer) engResult.get(WSSecurityEngineResult.TAG_ACTION);
if (Boolean.TRUE.equals(validated) && cert != null && action != null && WSConstants.SIGN == action) {
return cert;
}
}
}
}
}
return null;
}
The main method uses the certificate to log the user as it is done in the default SubjectCreatingPolicyInterceptor.
@Override
public void handleMessage(SoapMessage message) throws Fault {
Endpoint ep = message.getExchange().get(Endpoint.class);
X509Certificate cert = getCertificate(message);
SecurityContext context = message.get(SecurityContext.class);
if (cert == null || context == null || context.getUserPrincipal() == null) {
// no certificate, do nothing
super.handleMessage(message);
} else {
// certificate found => perform a login with it
// the principal name is going to be as in the users.jks in lowercase
Principal principal = new SimplePrincipal(cert.getSubjectX500Principal().getName().toLowerCase());
Subject subject = new Subject();
if (ep.getSecurityDomainContext().isValid(principal, cert, subject)) {
ep.getSecurityDomainContext().pushSubjectContext(subject, principal, cert);
SecurityContext sc = new DefaultSecurityContext(principal, subject);
message.put(SecurityContext.class, sc);
} else {
throw MESSAGES.authenticationFailed(principal.getName());
}
}
}
Then the interceptor is configured in the Echo service:
@Stateless
@WebService(name = "echo",
targetNamespace = "http://es.rickyepoderi.sample/ws",
serviceName = "echo-service")
@Policy(placement = Policy.Placement.BINDING, uri = "WssX509V3Token10.xml")
@DeclareRoles("Users")
@SOAPBinding(style = SOAPBinding.Style.RPC)
@EndpointConfig(configFile = "WEB-INF/jaxws-endpoint-config.xml", configName = "Custom WS-Security Endpoint")
@InInterceptors(interceptors = {
"es.rickyepoderi.sample.CertificateBinaryTokenInterceptor"
})
public class Echo {
Finally the security context is associated to the application (the same security domain used before) but using the jboss-ejb3.xml. This is now necessary, if the configuration was setup in the web.xml/jboss-web.xml files it would not work, the certificate would be searched at TLS level and now it goes in the WSS SOAP header. The file just assigns the security domain to all EJBs:
<?xml version="1.1" encoding="UTF-8"?>
<jboss:ejb-jar xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:s="urn:security:1.1"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-2_0.xsd http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"
version="3.1"
impl-version="2.0">
<assembly-descriptor>
<s:security>
<ejb-name>*</ejb-name>
<s:security-domain>certificate-sec-domain</s:security-domain>
</s:security>
</assembly-descriptor>
</jboss:ejb-jar>
I was pretty sure about this solution but...
Interceptor for {http://es.rickyepoderi.sample/ws}echo-service has thrown exception, unwinding now: java.lang.IllegalArgumentException: only string password accepted
at org.jboss.as.webservices.security.ElytronSecurityDomainContextImpl.isValid(ElytronSecurityDomainContextImpl.java:71)
at es.rickyepoderi.sample.CertificateBinaryTokenInterceptor.handleMessage(CertificateBinaryTokenInterceptor.java:98)
at es.rickyepoderi.sample.CertificateBinaryTokenInterceptor.handleMessage(CertificateBinaryTokenInterceptor.java:32)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308)
at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:121)
...
It seems that the current integration between webservices and elytron subsystem just admits password logins. Looking the code this restriction is pretty clear. Elytron admits more types of login but, for the moment, the web-services just let you login using a password.
I know that in the previous security subsystem this type of login is admitted, so I just reconfigure the wildfly server to use a previous security-domain with a certificate login module.
/subsystem=security/security-domain=certificate-domain:add(cache-type=default)
/subsystem=security/security-domain=certificate-domain/authentication=classic:add()
/subsystem=security/security-domain=certificate-domain/authentication=classic/login-module=Certificate:add(code=Certificate, flag=required)
/subsystem=security/security-domain=certificate-domain/authentication=classic/login-module=Certificate:write-attribute(name=module-options, value={password-stacking=useFirstPass, securityDomain=certificate-domain})
/subsystem=security/security-domain=certificate-domain/jsse=classic:add(keystore={url="${jboss.server.config.dir}/users.jks", password=Kiosko_00})
Just the certificate module is used (stacking can be used to add roles from other modules). The user will be logged in without any role to the application.
Then I changed the jboss-ejb3.xml to use the new certificate-domain instead of the previous elytron realm. And it simply works. As I remembered, you can make a login with the certificate just passing its X509 representation as the password object. The login module just picks it up and checks if it is in the users.jks key-store. Here it is the video, as you see the current user is again the subject of the certificate.
In this last entry of the certificate security for jax-ws web services series, the application uses a WS-Security X509 token to execute a login inside an interceptor. The solution is very similar to the one that default wildfly uses for username tokens (the documented SubjectCreatingPolicyInterceptor class) but with a certificate. Nevertheless the new elytron susbsystem cannot be used but previous security domain works without any flaw. I think that elytron is still very new and it needs some improvements in these integration areas (in this case between web services and security). Throughout this series client certificate authentication for SOAP web services has been explained in detail (two types, at TLS level and using WS-Security). I hope that the entries are useful, or at least interesting, for you. The last maven project for the application can be download from here.
Certificate regards!
Comments