Wednesday, October 26, 2011

Serialization, class hierarchy and preserving sessions

If you have a class that implements Serializable, superclass fields do NOT get serialized unless the parent class also explicitly implements Serializable. That bit me the other day with session-scoped objects and preserving sessions across restarts, using Glassfish 3.1.1. Glassfish will complain if any objects in session scope or their properties don't implement serializable, but if those objects extend some parent class, the parent class fields are silently ignored and end up being null on session restore. So the source of the problem wasn't immediately clear, like it would have been if individual session objects or properties within them weren't serializable.

Saturday, October 22, 2011

Quick and dirty SSO with LTPA

If you have WebSphere application server in your environment, it is in fact possible to decode the "LtpaToken" cookie in code for quick-and-dirty SSO with non-WebSphere apps.

The main reason you might want to do this is if you have a portal-like application on WebSphere and want to link to other applications on different non-WebSphere servers.  This is only useful if WebSphere is your main point of entry.

Here's how you do it:

  • Export the LTPA encryption key to a file from WebSphere using the admin console.  You provide a passphrase and a filename.  
  • Find the "com.ibm.websphere.ltpa.3DESKey" value in the exported file.  This is the encrypted key.
  • Base64 decode the above key and decrypt with 3DES, using the passphrase provided.  The decrypted value is the actual key for decrypting LTPA tokens.
  • Take the "LtpaToken" cookie, base64 decode it, and decrypt it with the key.  The legacy LtpaToken cookie (which you can get with "interoperability mode") is encrypted with 3DES; the newer LtpaToken2 cookie uses AES.
  • Convert to String and parse.  The string looks like "values%expiration%signature" where the expiration is a standard UNIX timestamp, which you should use to ensure the token is still valid; and the values somewhere will contain the user DN (e.g., uid=user,ou=company,dc=com).
The Alfresco codebase contains a good example of how to do this in Java

Relevant fragments of code:
    private static final String AES_DECRIPTING_ALGORITHM = "AES/CBC/PKCS5Padding";
   private static final String DES_DECRIPTING_ALGORITHM = "DESede/ECB/PKCS5Padding";

    private byte[] getSecretKey(String ltpa3DESKey, String ltpaPassword) throws Exception
    {
        MessageDigest md = MessageDigest.getInstance("SHA");
        
        md.update(ltpaPassword.getBytes());
        
        byte[] hash3DES = new byte[24];
        
        System.arraycopy(md.digest(), 0, hash3DES, 0, 20);
        
        Arrays.fill(hash3DES, 20, 24, (byte) 0);
        
        final Cipher cipher = Cipher.getInstance(DES_DECRIPTING_ALGORITHM);
        
        final KeySpec keySpec = new DESedeKeySpec(hash3DES);
        
        final Key secretKey = SecretKeyFactory.getInstance("DESede").generateSecret(keySpec);

        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        
        byte[] secret = cipher.doFinal(Base64.decodeBase64(ltpa3DESKey.getBytes()));
        
        return secret;
    }
    
    private byte[] decrypt(byte[] token, byte[] key, String algorithm) throws Exception
    {
        SecretKey sKey = null;

        if (algorithm.indexOf("AES") != -1)
        {
            sKey = new SecretKeySpec(key, 0, 16, "AES");
        }
        else
        {
            DESedeKeySpec kSpec = new DESedeKeySpec(key);
            SecretKeyFactory kFact = SecretKeyFactory.getInstance("DESede");
            sKey = kFact.generateSecret(kSpec);
        }
        Cipher cipher = Cipher.getInstance(algorithm);

        if (algorithm.indexOf("ECB") == -1)
        {
            if (algorithm.indexOf("AES") != -1)
            {
                IvParameterSpec ivs16 = generateIvParameterSpec(key, 16);
                cipher.init(Cipher.DECRYPT_MODE, sKey, ivs16);
            }
            else
            {
                IvParameterSpec ivs8 = generateIvParameterSpec(key, 8);
                cipher.init(Cipher.DECRYPT_MODE, sKey, ivs8);
            }
        }
        else
        {
            cipher.init(Cipher.DECRYPT_MODE, sKey);
        }
        return cipher.doFinal(token);
    }
    
    private IvParameterSpec generateIvParameterSpec(byte key[], int size)
    {
        byte[] row = new byte[size];
        
        for (int i = 0; i < size; i++)
        {
            row[i] = key[i];
        }
        
        return new IvParameterSpec(row);
    }
// How to use it:
byte[] secretKey = getSecretKey(ltpa3DESKey, ltpaPassword);
byte[] ltpaTokenBytes = Base64.decodeBase64(ltpaToken.getBytes());
String token = new String(decrypt(ltpaTokenBytes, secretKey, DES_DECRIPTING_ALGORITHM));
// or
String token = new String(decrypt(ltpaTokenBytes, secretKey, AES_DECRIPTING_ALGORITHM));

Wednesday, October 12, 2011

Bizarre Glassfish JSF/EL performance issue

I found a performance issue with JSF on Glassfish 3.1.1 (and prior) in an unexpected place: a seemingly innocuous line of code like

<h:outputText rendered="#{request.requestURL.indexOf('page') ne -1}" .... />

It turns out that invoking methods through EL expressions (new in EL 2.2) triggers creating a new ExpressionFactory, which in turn calls findResource/getResourceAsStream - a file I/O operation.

These expressions in the "rendered" attribute are particularly bad because they get executed multiple times in various parts of the JSF lifecycle.

See:
http://java.net/jira/browse/JAVASERVERFACES-2223
http://java.net/jira/browse/UEL-19

On the plus side - jvisualvm totally rocks.  I am shocked by how easy it was to get started with for tracking down these issues and it was right there in $JAVA_HOME/bin all along.

Tuesday, October 11, 2011

Infinite loop in Glassfish exception logging

When Glassfish catches a ServletException, it calls getRootCause recursively on each successive unwrapped, nested exception by reflection.  This is fine for wrapped ServletException and JspException, which have well-defined semantics for this method, but creates a big problem if you have an application-defined exception that happens to use the same "getRootCause" name with different semantics.

So I created a Glassfish bug report:
http://java.net/jira/browse/GLASSFISH-17390

Related original bug in Tomcat, since fixed:
https://issues.apache.org/bugzilla/show_bug.cgi?id=39088