org.jasig.portlet.courses.dao.xml.SecureRequestCredentials.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.portlet.courses.dao.xml.SecureRequestCredentials.java

Source

/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.jasig.portlet.courses.dao.xml;

import java.io.IOException;
import java.io.Serializable;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SealedObject;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import org.apache.commons.lang.RandomStringUtils;

/**
 * Holds a username and password in a secure manner. The intent is not to protect from in memory attacks as calling {@link #getPassword()}
 * returns the plain-text password. The intent is to prevent accidental exposure via serialization, toString or other similar approach.
 * <br/>
 * Decryption will also only work within the same JVM as the object was created as the password used is random and scoped to the life of this class.
 * 
 * @author Eric Dalquist
 */
public final class SecureRequestCredentials implements Serializable {
    private static final long serialVersionUID = 1L;

    private static final String PASSWORD_ALGORITHM = "PBEWithMD5AndDES";
    private static final int ITERATIONS = 20;
    private static final SecretKey SECRET_KEY;

    static {
        final SecretKeyFactory secretKeyFactory;
        try {
            secretKeyFactory = SecretKeyFactory.getInstance(PASSWORD_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new Error("Failed to create SecretKeyFactory for algorithm '" + PASSWORD_ALGORITHM + "'. "
                    + SecureRequestCredentials.class.getSimpleName() + " will not work", e);
        }

        //Generate a random 256 character password
        final char[] keyPassPhrase = RandomStringUtils.randomAscii(256).toCharArray();

        final PBEKeySpec pbeKeySpec = new PBEKeySpec(keyPassPhrase);
        try {
            SECRET_KEY = secretKeyFactory.generateSecret(pbeKeySpec);
        } catch (InvalidKeySpecException e) {
            throw new Error("Failed to create SecretKey for PBEKeySpec '" + pbeKeySpec + "'. "
                    + SecureRequestCredentials.class.getSimpleName() + " will not work", e);
        }
    }

    private final String username;
    private final SealedObject sealedPassword;

    /**
     * @param username Username to store
     * @param password Password to store, the array will be filled with empty strings before this constructor returns
     */
    public SecureRequestCredentials(String username, char[] password) {
        try {
            final Cipher cipher = getCipher(username, Cipher.ENCRYPT_MODE);

            final SealedObject sp;
            try {
                sp = new SealedObject(password, cipher);
            } catch (IllegalBlockSizeException e) {
                throw new Error("Failed to create SealedObject. " + SecureRequestCredentials.class.getSimpleName()
                        + " will not work", e);
            } catch (IOException e) {
                throw new Error("Failed to create SealedObject. " + SecureRequestCredentials.class.getSimpleName()
                        + " will not work", e);
            }

            this.username = username;
            this.sealedPassword = sp;
        } finally {
            //Nuke the password array in memory as soon as we're done with it
            Arrays.fill(password, ' ');
        }
    }

    /**
     * @return The username
     */
    public String getUsername() {
        return username;
    }

    /**
     * @return The password, will fail if attempting this on an object deserialized from another server.
     */
    public char[] getPassword() {
        final Cipher cipher = getCipher(username, Cipher.DECRYPT_MODE);

        try {
            return (char[]) this.sealedPassword.getObject(cipher);
        } catch (IllegalBlockSizeException e) {
            throw new Error("Failed to decrypt SealedObject. " + SecureRequestCredentials.class.getSimpleName()
                    + " will not work", e);
        } catch (BadPaddingException e) {
            throw new Error("Failed to decrypt SealedObject. " + SecureRequestCredentials.class.getSimpleName()
                    + " will not work", e);
        } catch (IOException e) {
            throw new Error("Failed to decrypt SealedObject. " + SecureRequestCredentials.class.getSimpleName()
                    + " will not work", e);
        } catch (ClassNotFoundException e) {
            throw new Error("Failed to decrypt SealedObject. " + SecureRequestCredentials.class.getSimpleName()
                    + " will not work", e);
        }
    }

    private static Cipher getCipher(String username, int opmode) {
        final Cipher cipher;
        try {
            cipher = Cipher.getInstance(PASSWORD_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new Error("Failed to create Cipher for algorithm '" + PASSWORD_ALGORITHM + "'. "
                    + SecureRequestCredentials.class.getSimpleName() + " will not work", e);
        } catch (NoSuchPaddingException e) {
            throw new Error("Failed to create Cipher for algorithm '" + PASSWORD_ALGORITHM + "'. "
                    + SecureRequestCredentials.class.getSimpleName() + " will not work", e);
        }

        final byte[] salt = getSalt(username);
        final PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, ITERATIONS);
        try {
            cipher.init(opmode, SECRET_KEY, pbeParamSpec);
        } catch (InvalidKeyException e) {
            throw new Error("Failed to init Cipher for SecretKey '" + SECRET_KEY + "'. "
                    + SecureRequestCredentials.class.getSimpleName() + " will not work", e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new Error("Failed to init Cipher for PBEParameterSpec '" + pbeParamSpec + "'. "
                    + SecureRequestCredentials.class.getSimpleName() + " will not work", e);
        }

        return cipher;
    }

    /**
     * Uses the first 8 bytes of the username as a salt
     */
    private static byte[] getSalt(String username) {
        final byte[] usernameBytes = username.getBytes();
        if (usernameBytes.length == 8) {
            return usernameBytes;
        }

        final byte[] salt = new byte[8];
        System.arraycopy(usernameBytes, 0, salt, 0, Math.min(usernameBytes.length, salt.length));
        return salt;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Arrays.hashCode(getPassword());
        result = prime * result + ((username == null) ? 0 : username.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        SecureRequestCredentials other = (SecureRequestCredentials) obj;
        if (!Arrays.equals(getPassword(), other.getPassword()))
            return false;
        if (username == null) {
            if (other.username != null)
                return false;
        } else if (!username.equals(other.username))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "SecureRequestCredentials [username=" + username + "]";
    }
}