Monday, July 27, 2009

Building a Building a Custom Trust Association Interceptor for WebSphere Portal, Part III

Continuing the discussion on a quick and easy way of accomplishing single sign on to WebSphere Portal (see Part 1 and Part 2), we look at how to decrypt incoming parameters.

There is obviously a very easy to exploit security issue if authentication parameters are passed between different applications in clear text. Any user with a HTTP proxy (such as Charles) would be able to listen on requests and determine authentication protocol. The following solution allows the trusted third party to encrypt authentication information being passed between the portal site and the third party.

For the purpose of this exercise, we'll use PGP for decryption. PGP is available for encryption/decryption, but it is no longer distributed as freeware. You need to download PGP Corp's Desktop Trial Software to aquire this functionality now. Don't worry - the software reverts to the freeware functionality after 30 days, so you'll still be able to do basic encryption and decryption.

This examples assumes you already have gone through the process of generating a private key file and a public key file, and placed those somewhere on your server. The code later on will need to reference those key ring files. If you need help creating a key ring, see the PGP documentation for help.

We'll use the Bouncy Castle API to perform the nitty-gritty process of decrypting the incoming information. You'll need to download the appropriate version of the bcprov-jdk*.jar and bcpg-jdk*.jar from the Latest Releases page. Add these files to the lib directory of the application server, so that they are available to the server itself.

Now, the catch to using PGP is that it is a strong encryption algorithm. So strong, in fact, that you need to download a different java security policy to work with it. For the purpose of WebSphere Portal, you would get them off the IBM site (here). For different jdk providers, you would get it from their respective sites (such as Sun, Oracle). Unpack these files into the jre/lib/security directory of your jdk.

Great. Now that we're done with the necessary set up and dependencies, let's look at the code. A lot of this can be found as an example in the Bouncy Castle documentation. Remember to change the constants at the beginning of the class to match your secret pass phrase and the location of your key ring files.



/*
* PGP Decryption Utility
*
*/
package com.security.pgp;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.util.Iterator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;

public class PGPSSOUtil
{
private static final String PROVIDER_SHORT = "BC";
private static final String KEY_PHRASE = "MySecretPhrase123";
private static final String DECRYPTION_KEY_FILE = "C:\\PGP\\PGP60.pkr";
private static final String PRIVATE_KEY_FILE = "C:\\PGP\\PGP60.skr";

private static PGPPublicKey pubKey;
private static PGPPrivateKey privateKey;

static
{
// adds the BouncyCastle Security Provider to the Java Security Providers
// list
Security.addProvider(new BouncyCastleProvider());
}

/**
* Constructor
*
* Initializes PGPPublicKey and PGPPrivateKey objects
*
* @throws Exception
*
**/
public PGPSSOUtil() throws Exception
{
// get the PGPPublicKey object
if (pubKey == null)
{
FileInputStream fis = new FileInputStream(DECRYPTION_KEY_FILE);
pubKey = readPublicKeyFromCol(fis);
fis.close();
}
// get the PGPPrivateKey object
if (privateKey == null)
{
FileInputStream fis = new FileInputStream(PRIVATE_KEY_FILE);
privateKey = findSecretKey(fis, pubKey.getKeyID(), KEY_PHRASE.toCharArray());
fis.close();
}
}

/**
* Given an encypted hex byte array, decrypt the data and return a user id to the caller
*
* @param encryptHex Byte Array, containing the encypted user id
* @return a valid SW1 user id, or whatever else is contained in the ecrypted data
*
**/
public String decryptId(byte[] encryptHex)
{
String id = null;
if (encryptHex == null encryptHex.length == 0)
return id;

try
{
// get the decrypted data
byte[] decryptedData = decrypt(privateKey, encryptHex);
// return a String translation of the Byte Array
if (decryptedData != null)
id = new String(decryptedData);
}
catch (Exception e)
{
e.printStackTrace();
}

return id;
}


/**
* Decrypt the given data using the BouncyCastle PGP provider
*
* @param privKey PGPPrivateKey object to decrypt the data with
* @param input Byte Array to decrypt
* @return Byte Array of decypted data
* @throws Exception
*
**/
private byte[] decrypt(PGPPrivateKey privKey, byte[] input) throws Exception
{
byte[] decrypted = null;
try
{
int bufferSize = 1024;
InputStream in = new ByteArrayInputStream(input);
in = PGPUtil.getDecoderStream(in);
PGPObjectFactory pgpObjectFactory = new PGPObjectFactory(in);
PGPEncryptedDataList enc;
Object pgpObject = pgpObjectFactory.nextObject();
if (pgpObject instanceof PGPEncryptedDataList)
{
enc = (PGPEncryptedDataList) pgpObject;
}
else
{
enc = (PGPEncryptedDataList) pgpObjectFactory.nextObject();
}
Iterator it = enc.getEncryptedDataObjects();
PGPPublicKeyEncryptedData pbe = null;
while (it.hasNext())
{
pbe = (PGPPublicKeyEncryptedData) it.next();
}
if (pbe == null)
{
return decrypted;
}
InputStream clear = pbe.getDataStream(privateKey, PROVIDER_SHORT);
PGPObjectFactory plainFact = new PGPObjectFactory(clear);
PGPCompressedData cData = (PGPCompressedData)
plainFact.nextObject();
InputStream compressedStream = cData.getDataStream();
if (!(compressedStream instanceof BufferedInputStream))
{
compressedStream = new BufferedInputStream(compressedStream, bufferSize);
}
PGPObjectFactory pgpFact = new PGPObjectFactory(compressedStream);
Object message = pgpFact.nextObject();
if (message instanceof PGPLiteralData)
{
PGPLiteralData literalData = (PGPLiteralData) message;
ByteArrayOutputStream fOut = new ByteArrayOutputStream();
BufferedInputStream unc = new
BufferedInputStream(literalData.getInputStream(), bufferSize);
byte[] buffer = new byte[bufferSize];
int bytesRead;
while ((bytesRead = unc.read(buffer)) != -1)
{
fOut.write(buffer, 0, bytesRead);
fOut.flush();
}
decrypted = fOut.toByteArray();
fOut.close();
in.close();
}
}
catch (Exception e)
{
e.printStackTrace();
throw e;
}
return decrypted;
}


/**
* Find the public key using the BouncyCastle Provider.
*
* @param in InputStream A InputStream created from the PGP Public Key file
* @return PGPPublicKey object used for encrypting and decypting data
* @throws Exception
*
**/
private PGPPublicKey readPublicKeyFromCol(InputStream in) throws Exception
{
PGPPublicKeyRing pkRing = null;
// get the Public Key Rings from the given Public Key File
PGPPublicKeyRingCollection pkCol = new PGPPublicKeyRingCollection(in);
Iterator it = pkCol.getKeyRings();
while (it.hasNext()) // loop over the key rings
{
pkRing = (PGPPublicKeyRing) it.next();
Iterator pkIt = pkRing.getPublicKeys();
while (pkIt.hasNext())
{
PGPPublicKey key = (PGPPublicKey) pkIt.next();
// check if it's the encryption key, this is the one we want to decrypt files with
if (key.isEncryptionKey())
{
return key;
}
}
}
return null;
}


/**
* Find the PGP Private Key from the Private Key file, using BouncyCastle
*
* @param keyIn InputStream created from the PrivateKey File location
* @param keyID The Key Id from the Public Key File
* @param pass The Passphrase used to extract the private key
* @return PGPPrivateKey representing the private key to decrypt with
* @throws IOException
* @throws PGPException
* @throws NoSuchProviderException
*
**/
private static PGPPrivateKey findSecretKey(InputStream keyIn, long keyID, char[] pass) throws IOException, PGPException, NoSuchProviderException
{
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
if (pgpSecKey == null)
{
return null;
}
return pgpSecKey.extractPrivateKey(pass, PROVIDER_SHORT);
}


/**
* Converts a Hex String back to a Byte Array. Courtesy Bouncy Castle
*
* @param hexStr Hex String to convert back to a normal byte array
* @return normally encoded byte array
*
**/
public byte[] convertHexStringToByteArray(String hexStr)
{
byte bArray[] = new byte[hexStr.length()/2];
for(int i=0; i<(hexStr.length()/2); i++)
{
byte firstNibble = Byte.parseByte(hexStr.substring(2*i,2*i+1),16);
byte secondNibble = Byte.parseByte(hexStr.substring(2*i+1,2*i+2),16);
int finalByte = (secondNibble) | (firstNibble << 4 );
bArray[i] = (byte) finalByte;
}
return bArray;
}

/**
* Converts a Byte Array to Hex String. Got this online from Bouncy Castle
*
* @param in byte[] array normally encoded
* @return String in Hex format
*
**/
public String convertByteArrayToHexString(byte in[])
{
byte ch = 0x00;
int i = 0;
if (in == null || in.length <= 0)
{
return null;
}
String pseudo[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8","9", "A", "B", "C", "D", "E", "F"};
StringBuffer out = new StringBuffer(in.length * 2);
while (i < in.length)
{
ch = (byte) (in[i] & 0xF0);
ch = (byte) (ch >>> 4);
ch = (byte) (ch & 0x0F);
out.append(pseudo[ (int) ch]);
ch = (byte) (in[i] & 0x0F);
out.append(pseudo[ (int) ch]);
i++;
}
String rslt = new String(out);
return rslt;
}


}


Now we would import this class into the original class developed in Part I and use it in
the negotiateValidateandEstablishTrust method. See Part IV for the changes to the class we originally developed.