Fixed a bug that caused crypto handle expiration times to not take effect.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / hs / provider / CryptoShibHandle.java
index 701eaa8..9d12371 100644 (file)
@@ -2,37 +2,34 @@
  * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
  * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
  * provided that the following conditions are met: Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the
- * above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other
- * materials provided with the distribution, if any, must include the following acknowledgment: "This product includes
- * software developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu> Internet2
- * Project. Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
- * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2,
- * nor the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
- * products derived from this software without specific prior written permission. For written permission, please
- * contact shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2,
- * UCAID, or the University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name,
- * without prior written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS
- * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
- * NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS
- * WITH LICENSEE. IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED
- * INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
- * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
+ * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
+ * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu> Internet2 Project.
+ * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
+ * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
+ * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
+ * products derived from this software without specific prior written permission. For written permission, please contact
+ * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
+ * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
+ * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
+ * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
+ * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
+ * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 package edu.internet2.middleware.shibboleth.hs.provider;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutput;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
 import java.io.StreamCorruptedException;
 import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
@@ -60,8 +57,6 @@ import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
-import sun.misc.BASE64Decoder;
-import sun.misc.BASE64Encoder;
 import edu.internet2.middleware.shibboleth.common.AuthNPrincipal;
 import edu.internet2.middleware.shibboleth.common.IdentityProvider;
 import edu.internet2.middleware.shibboleth.common.InvalidNameIdentifierException;
@@ -69,55 +64,74 @@ import edu.internet2.middleware.shibboleth.common.NameIdentifierMapping;
 import edu.internet2.middleware.shibboleth.common.NameIdentifierMappingException;
 import edu.internet2.middleware.shibboleth.common.ServiceProvider;
 import edu.internet2.middleware.shibboleth.common.ShibResource;
-import edu.internet2.middleware.shibboleth.hs.HSNameIdentifierMapping;
+import edu.internet2.middleware.shibboleth.utils.Base32;
 
 /**
  * {@link HSNameIdentifierMapping}implementation that uses symmetric encryption to store principal data inside
  * Shibboleth Attribute Query Handles.
  * 
  * @author Walter Hoehn
+ * @author Derek Morr
  */
-public class CryptoShibHandle extends AQHNameIdentifierMapping implements HSNameIdentifierMapping {
+public class CryptoShibHandle extends AQHNameIdentifierMapping implements NameIdentifierMapping {
 
-       private static Logger   log             = Logger.getLogger(CryptoShibHandle.class.getName());
-       protected SecretKey             secret;
-       private SecureRandom    random  = new SecureRandom();
+       private static Logger log = Logger.getLogger(CryptoShibHandle.class.getName());
+       protected SecretKey secret;
+       private SecureRandom random = new SecureRandom();
+       private String cipherAlgorithm = "DESede/CBC/PKCS5Padding";
+       private String macAlgorithm = "HmacSHA1";
+       private String storeType = "JCEKS";
 
        public CryptoShibHandle(Element config) throws NameIdentifierMappingException {
+
                super(config);
                try {
 
-                       String keyStorePath = getElementConfigData(config, "KeyStorePath");
-                       String keyStorePassword = getElementConfigData(config, "KeyStorePassword");
-                       String keyStoreKeyAlias = getElementConfigData(config, "KeyStoreKeyAlias");
-                       String keyStoreKeyPassword = getElementConfigData(config, "KeyStoreKeyPassword");
+                       String keyStorePath = getElementConfigData(config, "KeyStorePath", true);
+                       String keyStorePassword = getElementConfigData(config, "KeyStorePassword", true);
+                       String keyStoreKeyAlias = getElementConfigData(config, "KeyStoreKeyAlias", true);
+                       String keyStoreKeyPassword = getElementConfigData(config, "KeyStoreKeyPassword", true);
+
+                       String rawStoreType = getElementConfigData(config, "KeyStoreType", false);
+                       if (rawStoreType != null && !rawStoreType.equals("")) {
+                               storeType = rawStoreType;
+                       }
+                       String rawCipherAlgorithm = getElementConfigData(config, "Cipher", false);
+                       if (rawCipherAlgorithm != null && !rawCipherAlgorithm.equals("")) {
+                               cipherAlgorithm = rawCipherAlgorithm;
+                       }
+                       String rawMacAlgorithm = getElementConfigData(config, "MAC", false);
+                       if (rawMacAlgorithm != null && !rawMacAlgorithm.equals("")) {
+                               macAlgorithm = rawMacAlgorithm;
+                       }
 
-                       KeyStore keyStore = KeyStore.getInstance("JCEKS");
+                       KeyStore keyStore = KeyStore.getInstance(storeType);
                        keyStore.load(new ShibResource(keyStorePath, this.getClass()).getInputStream(), keyStorePassword
                                        .toCharArray());
                        secret = (SecretKey) keyStore.getKey(keyStoreKeyAlias, keyStoreKeyPassword.toCharArray());
 
-                       //Before we finish initilization, make sure that things are
-                       // working
+                       // Before we finish initilization, make sure that things are working
                        testEncryption();
 
                        if (usingDefaultSecret()) {
-                               log
-                                               .warn("You are running Crypto AQH Name Mapping with the default secret key.  This is UNSAFE!  Please change "
-                                                               + "this configuration and restart the origin.");
+                               log.warn("You are running Crypto AQH Name Mapping with the "
+                                               + "default secret key.  This is UNSAFE!  Please change "
+                                               + "this configuration and restart the origin.");
                        }
                } catch (StreamCorruptedException e) {
                        if (System.getProperty("java.version").startsWith("1.4.2")) {
-                               log.error("There is a bug in Java 1.4.2 that prevents JCEKS keystores from being loaded properly.  "
+                               log.error("There is a bug in some versions of Java 1.4.2.x that "
+                                               + "prevent JCEKS keystores from being loaded properly.  "
                                                + "You probably need to upgrade or downgrade your JVM in order to make this work.");
                        }
-                       log.error("An error occurred while loading the java keystore.  Unable to initialize Crypto Name Mapping: "
-                                       + e);
+                       log.error("An error occurred while loading the java keystore.  Unable to initialize "
+                                       + "Crypto Name Mapping: " + e);
                        throw new NameIdentifierMappingException(
-                                       "An error occurred while loading the java keystore.  Unable to initialize Crypto Name Mapping.");
+                                       "An error occurred while loading the java keystore.  Unable to initialize Crypto "
+                                                       + "Name Mapping.");
                } catch (KeyStoreException e) {
-                       log.error("An error occurred while loading the java keystore.  Unable to initialize Crypto Name Mapping: "
-                                       + e);
+                       log.error("An error occurred while loading the java keystore.  Unable to initialize Crypto "
+                                       + "Name Mapping: " + e);
                        throw new NameIdentifierMappingException(
                                        "An error occurred while loading the java keystore.  Unable to initialize Crypto Name Mapping.");
                } catch (CertificateException e) {
@@ -125,76 +139,95 @@ public class CryptoShibHandle extends AQHNameIdentifierMapping implements HSName
                        throw new NameIdentifierMappingException(
                                        "The java keystore contained corrupted data.  Unable to initialize Crypto Name Mapping.");
                } catch (NoSuchAlgorithmException e) {
-                       log
-                                       .error("Appropriate JCE provider not found in the java environment. Unable to initialize Crypto Name Mapping: "
-                                                       + e);
+                       log.error("Appropriate JCE provider not found in the java environment. Unable "
+                                       + "to initialize Crypto Name Mapping: " + e);
                        throw new NameIdentifierMappingException(
                                        "Appropriate JCE provider not found in the java environment. Unable to initialize Crypto Name Mapping.");
                } catch (IOException e) {
-                       log.error("An error accessing while loading the java keystore.  Unable to initialize Crypto Name Mapping: "
-                                       + e);
+                       log.error("An error accessing while loading the java keystore.  Unable to initialize Crypto Name "
+                                       + "Mapping: " + e);
                        throw new NameIdentifierMappingException(
                                        "An error occurred while accessing the java keystore.  Unable to initialize Crypto Name Mapping.");
                } catch (UnrecoverableKeyException e) {
-                       log
-                                       .error("Secret could not be loaded from the java keystore.  Verify that the alias and password are correct: "
-                                                       + e);
+                       log.error("Secret could not be loaded from the java keystore.  Verify that the alias and "
+                                       + "password are correct: " + e);
                        throw new NameIdentifierMappingException(
                                        "Secret could not be loaded from the java keystore.  Verify that the alias and password are correct. ");
                }
        }
 
+       /**
+        * Decode an encrypted handle back into a principal
+        */
        public AuthNPrincipal getPrincipal(SAMLNameIdentifier nameId, ServiceProvider sProv, IdentityProvider idProv)
                        throws NameIdentifierMappingException, InvalidNameIdentifierException {
 
                verifyQualifier(nameId, idProv);
-               
+
                try {
-                       //Separate the IV and handle
-                       byte[] in = new BASE64Decoder().decodeBuffer(nameId.getName());
-                       if (in.length < 9) {
+                       byte[] in = Base32.decode(nameId.getName());
+
+                       Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+                       int ivSize = cipher.getBlockSize();
+                       byte[] iv = new byte[ivSize];
+
+                       Mac mac = Mac.getInstance(macAlgorithm);
+                       mac.init(secret);
+                       int macSize = mac.getMacLength();
+
+                       if (in.length < ivSize) {
                                log.debug("Attribute Query Handle is malformed (not enough bytes).");
                                throw new NameIdentifierMappingException("Attribute Query Handle is malformed (not enough bytes).");
                        }
-                       byte[] iv = new byte[8];
-                       System.arraycopy(in, 0, iv, 0, 8);
-                       byte[] encryptedHandle = new byte[in.length - iv.length];
-                       System.arraycopy(in, 8, encryptedHandle, 0, in.length - iv.length);
 
-                       Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
+                       // extract the IV, setup the cipher and extract the encrypted handle
+                       System.arraycopy(in, 0, iv, 0, ivSize);
                        IvParameterSpec ivSpec = new IvParameterSpec(iv);
                        cipher.init(Cipher.DECRYPT_MODE, secret, ivSpec);
 
-                       byte[] objectArray = cipher.doFinal(encryptedHandle);
-                       GZIPInputStream zipBytesIn = new GZIPInputStream(new ByteArrayInputStream(objectArray));
-
-                       ObjectInputStream objectStream = new ObjectInputStream(zipBytesIn);
+                       byte[] encryptedHandle = new byte[in.length - iv.length];
+                       System.arraycopy(in, ivSize, encryptedHandle, 0, in.length - iv.length);
+
+                       // decrypt the rest of the data and setup the streams
+                       byte[] decryptedBytes = cipher.doFinal(encryptedHandle);
+                       ByteArrayInputStream byteStream = new ByteArrayInputStream(decryptedBytes);
+                       GZIPInputStream compressedData = new GZIPInputStream(byteStream);
+                       DataInputStream dataStream = new DataInputStream(compressedData);
+
+                       // extract the components
+                       byte[] decodedMac = new byte[macSize];
+                       int bytesRead = dataStream.read(decodedMac);
+                       if (bytesRead != macSize) {
+                               log.error("Error parsing handle: Unable to extract HMAC.");
+                               throw new NameIdentifierMappingException("Error parsing handle: Unable to extract HMAC.");
+                       }
+                       long decodedExpirationTime = dataStream.readLong();
+                       String decodedPrincipal = dataStream.readUTF();
 
-                       HMACHandleEntry handleEntry = (HMACHandleEntry) objectStream.readObject();
-                       objectStream.close();
+                       HMACHandleEntry macHandleEntry = createHMACHandleEntry(new AuthNPrincipal(decodedPrincipal));
+                       macHandleEntry.setExpirationTime(decodedExpirationTime);
+                       byte[] generatedMac = macHandleEntry.getMAC(mac);
 
-                       if (handleEntry.isExpired()) {
+                       if (macHandleEntry.isExpired()) {
                                log.debug("Attribute Query Handle is expired.");
                                throw new InvalidNameIdentifierException("Attribute Query Handle is expired.", errorCodes);
                        }
 
-                       Mac mac = Mac.getInstance("HmacSHA1");
-                       mac.init(secret);
-                       if (!handleEntry.isValid(mac)) {
+                       if (!Arrays.equals(decodedMac, generatedMac)) {
                                log.warn("Attribute Query Handle failed integrity check.");
                                throw new NameIdentifierMappingException("Attribute Query Handle failed integrity check.");
                        }
 
                        log.debug("Attribute Query Handle recognized.");
-                       return handleEntry.principal;
+                       return macHandleEntry.principal;
 
                } catch (NoSuchAlgorithmException e) {
                        log.error("Appropriate JCE provider not found in the java environment.  Could not load Algorithm: " + e);
                        throw new NameIdentifierMappingException(
                                        "Appropriate JCE provider not found in the java environment.  Could not load Algorithm.");
                } catch (NoSuchPaddingException e) {
-                       log.error("Appropriate JCE provider not found in the java environment.  Could not load Padding method: "
-                                       + e);
+                       log.error("Appropriate JCE provider not found in the java environment.  Could not load Padding "
+                                       + "method: " + e);
                        throw new NameIdentifierMappingException(
                                        "Appropriate JCE provider not found in the java environment.  Could not load Padding method.");
                } catch (InvalidKeyException e) {
@@ -203,17 +236,20 @@ public class CryptoShibHandle extends AQHNameIdentifierMapping implements HSName
                } catch (GeneralSecurityException e) {
                        log.warn("Unable to decrypt the supplied Attribute Query Handle: " + e);
                        throw new NameIdentifierMappingException("Unable to decrypt the supplied Attribute Query Handle.");
-               } catch (ClassNotFoundException e) {
-                       log.warn("The supplied Attribute Query Handle does not represent a serialized AuthNPrincipal: " + e);
-                       throw new NameIdentifierMappingException(
-                                       "The supplied Attribute Query Handle does not represent a serialized AuthNPrincipal.");
                } catch (IOException e) {
-                       log.warn("The AuthNPrincipal could not be de-serialized from the supplied Attribute Query Handle: " + e);
-                       throw new NameIdentifierMappingException(
-                                       "The AuthNPrincipal could not be de-serialized from the supplied Attribute Query Handle.");
+                       log.warn("IO error while decoding handle.");
+                       throw new NameIdentifierMappingException("IO error while decoding handle.");
                }
        }
 
+       /**
+        * Encodes a principal into a cryptographic handle Format of encoded handle: [IV][HMAC][TTL][principal] where: [IV] =
+        * the Initialization Vector; byte-array [HMAC] = the HMAC; byte array [exprTime] = expiration time of the handle; 8
+        * bytes; Big-endian [principal] = the principal; a UTF-8-encoded string The [HMAC][exprTime][princLen][principal]
+        * byte stream is GZIPped. The IV is pre-pended to this byte stream, and the result is Base32-encoded. We don't need
+        * to encode the IV or MAC's lengths. They can be obtained from Cipher.getBlockSize() and Mac.getMacLength(),
+        * respectively.
+        */
        public SAMLNameIdentifier getNameIdentifierName(AuthNPrincipal principal, ServiceProvider sProv,
                        IdentityProvider idProv) throws NameIdentifierMappingException {
 
@@ -223,33 +259,36 @@ public class CryptoShibHandle extends AQHNameIdentifierMapping implements HSName
                                throw new IllegalArgumentException("A principal must be supplied for Attribute Query Handle creation.");
                        }
 
-                       HandleEntry handleEntry = createHandleEntry(principal);
-
-                       Mac mac = Mac.getInstance("HmacSHA1");
+                       Mac mac = Mac.getInstance(macAlgorithm);
                        mac.init(secret);
-                       HMACHandleEntry macHandleEntry = new HMACHandleEntry(handleEntry, mac);
-
-                       ByteArrayOutputStream outStream = new ByteArrayOutputStream();
-                       ByteArrayOutputStream encStream = new ByteArrayOutputStream();
+                       HMACHandleEntry macHandleEntry = createHMACHandleEntry(principal);
 
-                       Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
-                       byte[] iv = new byte[8];
+                       Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+                       byte[] iv = new byte[cipher.getBlockSize()];
                        random.nextBytes(iv);
                        IvParameterSpec ivSpec = new IvParameterSpec(iv);
                        cipher.init(Cipher.ENCRYPT_MODE, secret, ivSpec);
 
-                       //Handle contains 8 byte IV, followed by cipher text
-                       outStream.write(cipher.getIV());
+                       ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+                       GZIPOutputStream compressedStream = new GZIPOutputStream(byteStream);
+                       DataOutputStream dataStream = new DataOutputStream(compressedStream);
+
+                       dataStream.write(macHandleEntry.getMAC(mac));
+                       dataStream.writeLong(macHandleEntry.getExpirationTime());
+                       dataStream.writeUTF(principal.getName());
 
-                       ObjectOutput objectStream = new ObjectOutputStream(new GZIPOutputStream(encStream));
-                       objectStream.writeObject(macHandleEntry);
-                       objectStream.close();
+                       dataStream.flush();
+                       compressedStream.flush();
+                       compressedStream.finish();
+                       byteStream.flush();
 
-                       outStream.write(cipher.doFinal(encStream.toByteArray()));
-                       encStream.close();
+                       byte[] encryptedData = cipher.doFinal(byteStream.toByteArray());
 
-                       String handle = new BASE64Encoder().encode(outStream.toByteArray());
-                       outStream.close();
+                       byte[] handleBytes = new byte[iv.length + encryptedData.length];
+                       System.arraycopy(iv, 0, handleBytes, 0, iv.length);
+                       System.arraycopy(encryptedData, 0, handleBytes, iv.length, encryptedData.length);
+
+                       String handle = Base32.encode(handleBytes);
 
                        try {
                                return new SAMLNameIdentifier(handle.replaceAll(System.getProperty("line.separator"), ""), idProv
@@ -266,19 +305,25 @@ public class CryptoShibHandle extends AQHNameIdentifierMapping implements HSName
                        throw new NameIdentifierMappingException(
                                        "Appropriate JCE provider not found in the java environment.  Could not load Cipher.");
                } catch (IOException e) {
-                       log.error("Could not serialize Principal for handle creation: " + e);
-                       throw new NameIdentifierMappingException(
-                                       "Could not serialize Principal for Attribute Query Handle creation.");
+                       log.warn("IO error while decoding handle.");
+                       throw new NameIdentifierMappingException("IO error while decoding handle.");
                }
+
        }
 
-       private String getElementConfigData(Element e, String itemName) throws NameIdentifierMappingException {
+       private String getElementConfigData(Element e, String itemName, boolean required)
+                       throws NameIdentifierMappingException {
 
                NodeList itemElements = e.getElementsByTagNameNS(NameIdentifierMapping.mappingNamespace, itemName);
 
                if (itemElements.getLength() < 1) {
-                       log.error(itemName + " not specified.");
-                       throw new NameIdentifierMappingException("Crypto Name Mapping requires a <" + itemName + "> specification.");
+                       if (required) {
+                               log.error(itemName + " not specified.");
+                               throw new NameIdentifierMappingException("Crypto Name Mapping requires a <" + itemName
+                                               + "> specification.");
+                       } else {
+                               return null;
+                       }
                }
 
                if (itemElements.getLength() > 1) {
@@ -292,7 +337,8 @@ public class CryptoShibHandle extends AQHNameIdentifierMapping implements HSName
                }
                if (item == null || item.equals("")) {
                        log.error(itemName + " not specified.");
-                       throw new NameIdentifierMappingException("Crypto Name Mapping requires a <" + itemName + "> specification.");
+                       throw new NameIdentifierMappingException("Crypto Name Mapping requires a valid <" + itemName
+                                       + "> specification.");
                }
                return item;
        }
@@ -301,11 +347,14 @@ public class CryptoShibHandle extends AQHNameIdentifierMapping implements HSName
 
                String decrypted;
                try {
-                       Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
-                       cipher.init(Cipher.ENCRYPT_MODE, secret);
+                       Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+                       byte[] iv = new byte[cipher.getBlockSize()];
+                       random.nextBytes(iv);
+                       IvParameterSpec ivSpec = new IvParameterSpec(iv);
+                       cipher.init(Cipher.ENCRYPT_MODE, secret, ivSpec);
                        byte[] cipherText = cipher.doFinal("test".getBytes());
-                       cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
-                       cipher.init(Cipher.DECRYPT_MODE, secret);
+                       cipher = Cipher.getInstance(cipherAlgorithm);
+                       cipher.init(Cipher.DECRYPT_MODE, secret, ivSpec);
                        decrypted = new String(cipher.doFinal(cipherText));
                } catch (Exception e) {
                        log.error("Round trip encryption/decryption test unsuccessful: " + e);
@@ -319,7 +368,7 @@ public class CryptoShibHandle extends AQHNameIdentifierMapping implements HSName
 
                byte[] code;
                try {
-                       Mac mac = Mac.getInstance("HmacSHA1");
+                       Mac mac = Mac.getInstance(macAlgorithm);
                        mac.init(secret);
                        mac.update("foo".getBytes());
                        code = mac.doFinal();
@@ -336,6 +385,7 @@ public class CryptoShibHandle extends AQHNameIdentifierMapping implements HSName
        }
 
        private boolean usingDefaultSecret() {
+
                byte[] defaultKey = new byte[]{(byte) 0xC7, (byte) 0x49, (byte) 0x80, (byte) 0xD3, (byte) 0x02, (byte) 0x4A,
                                (byte) 0x61, (byte) 0xEF, (byte) 0x25, (byte) 0x5D, (byte) 0xE3, (byte) 0x2F, (byte) 0x57, (byte) 0x51,
                                (byte) 0x20, (byte) 0x15, (byte) 0xC7, (byte) 0x49, (byte) 0x80, (byte) 0xD3, (byte) 0x02, (byte) 0x4A,
@@ -344,35 +394,55 @@ public class CryptoShibHandle extends AQHNameIdentifierMapping implements HSName
                return Arrays.equals(defaultKey, encodedKey);
        }
 
+       protected HMACHandleEntry createHMACHandleEntry(AuthNPrincipal principal) {
+
+               return new HMACHandleEntry(principal, handleTTL);
+       }
+
 }
 
 /**
  * <code>HandleEntry</code> extension class that performs message authentication.
  */
 
-class HMACHandleEntry extends HandleEntry implements Serializable {
+class HMACHandleEntry extends HandleEntry {
 
-       static final long       serialVersionUID        = 1L;
-       protected byte[]        code;
+       protected HMACHandleEntry(AuthNPrincipal principal, long TTL) {
 
-       protected HMACHandleEntry(AuthNPrincipal principal, long TTL, Mac mac) {
                super(principal, TTL);
-               mac.update(this.principal.getName().getBytes());
-               mac.update(new Long(this.expirationTime).byteValue());
-               code = mac.doFinal();
        }
 
-       protected HMACHandleEntry(HandleEntry handleEntry, Mac mac) {
-               super(handleEntry.principal, handleEntry.expirationTime);
-               mac.update(this.principal.getName().getBytes());
-               mac.update(new Long(this.expirationTime).byteValue());
-               code = mac.doFinal();
+       private static byte[] getLongBytes(long longValue) {
+
+               try {
+                       ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+                       DataOutputStream dataStream = new DataOutputStream(byteStream);
+
+                       dataStream.writeLong(longValue);
+                       dataStream.flush();
+                       byteStream.flush();
+
+                       return byteStream.toByteArray();
+               } catch (IOException ex) {
+                       return null;
+               }
+       }
+
+       public byte[] getMAC(Mac mac) {
+
+               mac.update(principal.getName().getBytes());
+               mac.update(getLongBytes(expirationTime));
+
+               return mac.doFinal();
+       }
+
+       public long getExpirationTime() {
+
+               return expirationTime;
        }
 
-       boolean isValid(Mac mac) {
-               mac.update(this.principal.getName().getBytes());
-               mac.update(new Long(this.expirationTime).byteValue());
-               byte[] validationCode = mac.doFinal();
-               return Arrays.equals(code, validationCode);
+       public void setExpirationTime(long expr) {
+
+               expirationTime = expr;
        }
-}
+}
\ No newline at end of file