org.ow2.proactive.authentication.crypto.Credentials.java Source code

Java tutorial

Introduction

Here is the source code for org.ow2.proactive.authentication.crypto.Credentials.java

Source

/*
 * ProActive Parallel Suite(TM):
 * The Open Source library for parallel and distributed
 * Workflows & Scheduling, Orchestration, Cloud Automation
 * and Big Data Analysis on Enterprise Grids & Clouds.
 *
 * Copyright (c) 2007 - 2017 ActiveEon
 * Contact: contact@activeeon.com
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation: version 3 of
 * the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * If needed, contact us to obtain a release under GPL Version 2 or 3
 * or a different license than the AGPL.
 */
package org.ow2.proactive.authentication.crypto;

import java.io.*;
import java.security.*;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.interfaces.DHPublicKey;

import org.apache.commons.codec.binary.Base64;
import org.objectweb.proactive.annotation.PublicAPI;
import org.objectweb.proactive.core.util.converter.ByteToObjectConverter;
import org.objectweb.proactive.core.util.converter.ObjectToByteConverter;

/**
 * Encapsulates encrypted Scheduler credentials
 * <p>
 * Stores encapsulated Scheduler credentials as well as metadata used to
 * determine which method should be used for decryption: key generation
 * algorithm, key size, and cipher parameters.
 * <p>
 * The credentials are encrypted with a symmetric AES key.
 * The AES key is encrypted using an asymmetric public key:
 * the corresponding private key is required to decrypt the secret AES
 * key, and then decrypt the data.
 * <p>
 * Extensive documentation for these parameters can be found in the
 * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/security/jce/JCERefGuide.html">Java Cryptography Extension Reference Guide</a>.
 * 
 * @see org.ow2.proactive.authentication.crypto.KeyPairUtil
 * @author The ProActive Team
 * @since ProActive Scheduling 1.1
 */
@PublicAPI
public class Credentials implements Serializable {

    // fix for #2456 : Credential Data and TaskLogs contain serialVersionUID based on scheduler server version
    private static final long serialVersionUID = 1L;

    /** Default credentials location */
    private static final String DEFAULT_CREDS = System.getProperty("user.home") + File.separator + ".proactive"
            + File.separator + "security" + File.separator + "creds.enc";

    /** Java properly describing the path to the encrypted credentials on the local drive */
    public static final String credentialsPathProperty = "pa.common.auth.credentials";

    /** Default pubkey location */
    private static final String DEFAULT_PUBKEY = System.getProperty("user.home") + File.separator + ".proactive"
            + File.separator + "security" + File.separator + "pub.key";

    /** Java property describing the path to the public key on the local drive */
    public static final String pubkeyPathProperty = "pa.common.auth.pubkey";

    static {
        File home = new File(DEFAULT_CREDS).getParentFile();
        if (!home.isDirectory()) {
            home.mkdirs();
        }
        home = new File(DEFAULT_PUBKEY).getParentFile();
        if (!home.isDirectory()) {
            home.mkdirs();
        }
    }

    /** key generation algorithm */
    private String algorithm;

    /** key size */
    private int size;

    /** cipher initialization parameters */
    private String cipher;

    /** encrypted data with AES cipher */
    private byte[] data;

    /** AES key encrypted with RSA */
    private byte[] aes;

    /**
     * Default constructor
     * <p>
     * Constructor is kept private, use {@link org.ow2.proactive.authentication.crypto.Credentials#getCredentials()} or
     * {@link org.ow2.proactive.authentication.crypto.Credentials#createCredentials(CredData, String)} to get instances
     *
     * @param algo Key generation algorithm
     * @param size Key size in bits
     * @param cipher Cipher parameters
     * @param aes Encrypted AES key
     * @param data raw encrypted credentials
     */
    private Credentials(String algo, int size, String cipher, byte[] aes, byte[] data) {
        this.algorithm = algo;
        this.size = size;
        this.cipher = cipher;
        this.aes = aes;
        this.data = data;
    }

    /**
     * Write the contents of a Credentials object to the disk
     * <p>
     * Use the current value of the {@link org.ow2.proactive.authentication.crypto.Credentials#credentialsPathProperty}
     * property to determine the file to which the data will be written
     * <p>
     * Credentials are written to disk in base64 encoded form.
     * <p>
     * See {@link org.ow2.proactive.authentication.crypto.Credentials#getCredentials()} for the inverse operation
     * 
     * @param path file path where the credentials will be written on the disk
     * @throws KeyException Unable to locate or open file, IO error
     */
    public void writeToDisk(String path) throws KeyException {
        File f = new File(path);
        FileOutputStream fs;
        try {
            fs = new FileOutputStream(f);
            fs.write(getBase64());
            fs.close();
        } catch (Exception e) {
            throw new KeyException("Could not write credentials to " + path, e);
        }
    }

    /**
     * Retrieves a public key stored in a local file
     * <p>
     * 
     * @param pubPath path to the public key on the local filesystem
     * @return the key encapsulated in a regular JCE container
     * @throws KeyException the key could not be retrieved or is malformed
     */
    public static PublicKey getPublicKey(String pubPath) throws KeyException {
        byte[] bytes;
        File f = new File(pubPath);
        FileInputStream fin;

        String algo = "", tmp = "";

        // recover public key bytes
        try {
            fin = new FileInputStream(f);
            DataInputStream in = new DataInputStream(fin);
            int read, tot = 0;
            while ((read = in.read()) != '\n') {
                algo += (char) read;
                tot++;
            }
            tot++;
            while ((read = in.read()) != '\n') {
                tmp += (char) read;
                tot++;
            }
            tot++;

            bytes = new byte[(int) f.length() - tot];
            in.readFully(bytes);
            in.close();
        } catch (Exception e) {
            throw new KeyException("Could not retrieve public key from " + pubPath, e);
        }

        // reconstruct public key
        X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(bytes);
        PublicKey pubKey;
        KeyFactory keyFactory;

        try {
            keyFactory = KeyFactory.getInstance(algo);
        } catch (NoSuchAlgorithmException e) {
            throw new KeyException("Cannot initialize key factory", e);
        }

        try {
            pubKey = keyFactory.generatePublic(pubKeySpec);
        } catch (InvalidKeySpecException e) {
            throw new KeyException("Cannot re-generate public key", e);
        }

        return pubKey;
    }

    /**
     * Retrieves a private key stored in a local file
     * <p>
     * Tries to guess the algorithm used for keypair generation which
     * is not included in the file. According to <a href="http://download.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html#AppA">Java Cryptography Specification</a>,
     * the algorithm can be only one of "RSA" or "DSA", so this method will try using both.
     * If the algorithm used to generate the key is neither RSA or DSA
     * (highly unlikely), this method cannot recreate the private key, but {@link #decrypt(String)}
     * maybe will.
     * 
     * @param privPath
     *            path to the private key on the local filesystem
     * @return the key encapsulated in a regular JCE container
     * @throws KeyException
     *             the key could not be retrieved or is malformed, or the algorithm used
     *             for generation is different from the ones used by this method
     */
    public static PrivateKey getPrivateKey(String privPath) throws KeyException {
        return getPrivateKey(privPath, new String[] { "RSA", "DSA" });
    }

    /**
     * Retrieves a private key stored in a local file
     * <p>
     * Tries to guess the algorithm used for keypair generation which
     * is not included in the file. According to <a href="http://download.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html#AppA">Java Cryptography Specification</a>,
     * the algorithm can be only one of "RSA" or "DSA", so we can just try both using the
     * <code>algorithms</code> param. If the algorithm used to generate the key is neither RSA or DSA
     * (highly unlikely), this method cannot recreate the private key, but {@link #decrypt(String)}
     * maybe will.
     * 
     * @param privPath
     *            path to the private key on the local filesystem
     * @param algorithms a list of algorithms to try for creating the PK. Recommanded value:
     *          {"RSA","DSA"}
     * @return the key encapsulated in a regular JCE container
     * @throws KeyException
     *             the key could not be retrieved or is malformed, or the algorithm used for generation
     *             is not one of <code>algorithms</code>
     */
    public static PrivateKey getPrivateKey(String privPath, String[] algorithms) throws KeyException {

        PrivateKey privKey = null;

        for (String algo : algorithms) {
            try {
                KeyFactory keyFactory;
                keyFactory = KeyFactory.getInstance(algo);

                // recover private key bytes
                byte[] bytes;
                try {
                    File pkFile = new File(privPath);
                    DataInputStream pkStream = new DataInputStream(new FileInputStream(pkFile));
                    bytes = new byte[(int) pkFile.length()];
                    pkStream.readFully(bytes);
                    pkStream.close();
                } catch (Exception e) {
                    throw new KeyException("Could not recover private key (algo=" + algo + ")", e);
                }

                // reconstruct private key
                PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(bytes);
                try {
                    privKey = keyFactory.generatePrivate(privKeySpec);
                } catch (InvalidKeySpecException e) {
                    throw new KeyException("Cannot re-generate private key  (algo=" + algo + ")", e);
                }
            } catch (Exception e) {
            }
        }

        if (privKey == null) {
            String str = "Could not generate Private Key (algorithms: ";
            for (String algo : algorithms) {
                str += algo + " ";
            }
            str += ")";
            throw new KeyException(str);
        }

        return privKey;
    }

    /**
     * Retrieves a credentials from disk
     * <p>
     * See {@link org.ow2.proactive.authentication.crypto.Credentials#writeToDisk(String)} for details on how information is
     * stored on disk.
     * 
     * @return the Credentials object represented by the file saved at the file
     *         described by the property {@link org.ow2.proactive.authentication.crypto.Credentials#credentialsPathProperty}
     * @throws KeyException Credentials could not be recovered
     */
    public static Credentials getCredentials() throws KeyException {
        return getCredentials(getCredentialsPath());
    }

    /**
     * Retrieves a credentials from disk
     * <p>
     * See {@link org.ow2.proactive.authentication.crypto.Credentials#writeToDisk(String)} for details on how information is
     * stored on disk.
     * 
     * @param path to the file in which credentials are stored
     * @return the Credentials object represented by the file located at <code>path</code>
     * @throws KeyException Credentials could not be recovered
     */
    public static Credentials getCredentials(String path) throws KeyException {
        File f = new File(path);
        byte[] bytes = new byte[(int) f.length()];
        FileInputStream fin;
        try {
            fin = new FileInputStream(f);
            fin.read(bytes);
            fin.close();
        } catch (Exception e) {
            throw new KeyException("Could not read credentials from " + path, e);
        }
        return getCredentialsBase64(bytes);
    }

    /**
     * Constructs a Credentials given an InputStream
     * 
     * @param is contains the base64 representation of a Credentials upon read
     * @return the Credentials object contained in the InputStream
     * @throws KeyException the Credentials data was read but could not be reconstructed
     * @throws IOException the Credentials data could not be read from the stream
     */
    public static Credentials getCredentials(InputStream is) throws KeyException, IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        int len;
        while (true) {
            len = is.read(buf);
            if (len > 0) {
                out.write(buf, 0, len);
            } else {
                break;
            }
        }
        byte[] bytes = out.toByteArray();
        out.close();
        return Credentials.getCredentialsBase64(bytes);
    }

    /**
     * Creates a Credentials given its base64 encoded representation
     * 
     * @param base64enc the Credentials representation as a base64 encoded byte array,
     *  as returned by {@link Credentials#getBase64()}
     * @return the Credentials object corresponding the <code>base64en</code> representation
     * @throws KeyException
     */
    public static Credentials getCredentialsBase64(byte[] base64enc) throws KeyException {
        String algo = "", cipher = "", tmp = "";
        byte[] data;
        byte[] aes;
        int size;
        byte[] asciiEnc;

        try {
            asciiEnc = Base64.decodeBase64(base64enc);
        } catch (Exception e) {
            throw new KeyException("Unable to decode base64 credentials", e);
        }

        try {
            DataInputStream in = new DataInputStream(new ByteArrayInputStream(asciiEnc));
            int read, tot = 0;
            while ((read = in.read()) != '\n') {
                if (read == -1)
                    throw new KeyException("Failed to parse malformed credentials");
                algo += (char) read;
                tot++;
            }
            tot++;
            while ((read = in.read()) != '\n') {
                if (read == -1)
                    throw new KeyException("Failed to parse malformed credentials");
                tmp += (char) read;
                tot++;
            }
            tot++;
            size = Integer.parseInt(tmp);
            while ((read = in.read()) != '\n') {
                if (read == -1)
                    throw new KeyException("Failed to parse malformed credentials");
                cipher += (char) read;
                tot++;
            }
            tot++;
            aes = new byte[size / 8];
            for (int i = 0; i < size / 8; i++) {
                aes[i] = (byte) in.read();
                tot++;
            }

            data = new byte[asciiEnc.length - tot];
            in.readFully(data);
        } catch (Exception e) {
            throw new KeyException("Could not decode credentials", e);
        }

        return new Credentials(algo, size, cipher, aes, data);
    }

    /**
     * Returns a representation of this credentials as a base64 encoded byte array
     * <p>
     * Prior to base64 encoding, format is the following:
     * <ul>
     * <li>The key generation algorithm, in human readable format, on a single
     * line
     * <li>The key size, in human readable format, on a single line
     * <li>The cipher parameters, in human readable format, on a single line
     * <li>The encrypted AES key, which should be exactly <code>size / 8</code> bytes
     * <li>The encrypted data, which can be of arbitrary length, should occupy the rest of the file
     * </ul>
     * @throws KeyException
     */
    public byte[] getBase64() throws KeyException {
        ByteArrayOutputStream b = new ByteArrayOutputStream();
        try {
            b.write((algorithm + '\n').getBytes());
            b.write(("" + size + '\n').getBytes());
            b.write((cipher + '\n').getBytes());
            b.write(this.aes);
            b.write(this.data);
        } catch (IOException e) {

        }
        byte[] ret;
        try {
            ret = Base64.encodeBase64(b.toByteArray());
        } catch (Exception e) {
            throw new KeyException("Unable to encode credentials to base64", e);
        }
        return ret;
    }

    /**
     * 
     * @return the path to the Credentials to use in this runtime
     */
    public static String getCredentialsPath() {
        String path = System.getProperty(credentialsPathProperty);
        if (path == null) {
            path = DEFAULT_CREDS;
        }
        return path;
    }

    /**
     * 
     * @return the path to the public key to use in this runtime
     */
    public static String getPubKeyPath() {
        String path = System.getProperty(pubkeyPathProperty);
        if (path == null) {
            path = DEFAULT_PUBKEY;
        }
        return path;
    }

    /**
     * Creates new encrypted credentials.
     * <p>
     * See {@link org.ow2.proactive.authentication.crypto.Credentials#createCredentials(CredData, PublicKey)}
     *
     * @param cc the data to be encrypted
     * @param pubPath path to the public key
     * @return the Credentials object containing the encrypted data
     * @throws KeyException key generation or encryption failed
     */
    public static Credentials createCredentials(final CredData cc, final String pubPath) throws KeyException {
        PublicKey pubKey = getPublicKey(pubPath);
        return createCredentials(cc, pubKey);
    }

    /**
     * Creates new encrypted credentials.
     * <p>
     * See {@link org.ow2.proactive.authentication.crypto.Credentials#createCredentials(CredData, PublicKey, String)}
     *
     * @param cc the data to be encrypted
     * @param pubKey the public key
     * @return the Credentials object containing the encrypted data
     * @throws KeyException key generation or encryption failed
     */
    public static Credentials createCredentials(final CredData cc, final PublicKey pubKey) throws KeyException {
        return createCredentials(cc, pubKey, "RSA/ECB/PKCS1Padding");
    }

    /**
     * Creates new encrypted credentials
     * <p>
     * Encrypts the message '<code>credData</code>' using the
     * public key <code>pubKey</code> and <code>cipher</code>
     * and store it in a new Credentials object.
     *
     * @see KeyPairUtil#encrypt(PublicKey, String, byte[])
     * @param cc, the class containing the data to be crypted
     * @param pubKey public key used for encryption
     * @param cipher cipher parameters: combination of transformations
     * @return the Credentials object containing the encrypted data
     * @throws KeyException key generation or encryption failed
     */
    public static Credentials createCredentials(final CredData cc, final PublicKey pubKey, final String cipher)
            throws KeyException {
        // serialize clear credentials to byte array
        byte[] clearCred;
        try {
            clearCred = ObjectToByteConverter.ObjectStream.convert(cc);
        } catch (IOException e1) {
            throw new KeyException(e1.getMessage());
        }

        HybridEncryptionUtil.HybridEncryptedData encryptedData = HybridEncryptionUtil.encrypt(pubKey, cipher,
                clearCred);
        byte[] encAes = encryptedData.getEncryptedSymmetricKey();
        byte[] encData = encryptedData.getEncryptedData();

        int size = keySize(pubKey);
        return new Credentials(pubKey.getAlgorithm(), size, cipher, encAes, encData);
    }

    /**
     * Decrypts the encapsulated credentials
     *
     * @see org.ow2.proactive.authentication.crypto.KeyPairUtil#decrypt(PrivateKey, String, byte[])
     * @param privPath path to the private key file
     * @return the credential data containing the clear data:login, password and key
     * @throws KeyException decryption failure, malformed data
     */
    public CredData decrypt(String privPath) throws KeyException {
        PrivateKey privKey = Credentials.getPrivateKey(privPath, new String[] { algorithm });
        return decrypt(privKey);
    }

    /**
     * Decrypts the encapsulated credentials
     *
     * @see org.ow2.proactive.authentication.crypto.KeyPairUtil#decrypt(PrivateKey, String, byte[])
     * @param privKey the private key
     * @return the credential data containing the clear data:login, password and key
     * @throws KeyException decryption failure, malformed data
     */
    public CredData decrypt(PrivateKey privKey) throws KeyException {
        byte[] decryptedData = HybridEncryptionUtil.decrypt(privKey, this.cipher,
                new HybridEncryptionUtil.HybridEncryptedData(aes, data));

        // deserialize clear credentials and obtain login & password
        try {
            return (CredData) ByteToObjectConverter.ObjectStream.convert(decryptedData);
        } catch (Exception e) {
            throw new KeyException(e.getMessage());
        }

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return "[" + algorithm + " " + size + "b " + cipher + "]";
    }

    /**
     * Creates new encrypted credentials
     * <p>
     * See {@link org.ow2.proactive.authentication.crypto.Credentials#createCredentials(String, String, String, String)}
     * 
     * @param login the login to encrypt
     * @param password the corresponding password to encrypt
     * @param pubPath path to the public key
     * @return the Credentials object containing the encrypted data
     * @throws KeyException key generation or encryption failed
     */
    @Deprecated
    public static Credentials createCredentials(String login, String password, String pubPath) throws KeyException {
        return createCredentials(login, password, pubPath, "RSA/ECB/PKCS1Padding");
    }

    /**
     * Creates new encrypted credentials
     * <p>
     * See {@link org.ow2.proactive.authentication.crypto.Credentials#createCredentials(String, String, String, String)}
     * 
     * @param login the login to encrypt
     * @param password the corresponding password to encrypt
     * @param pubKey the public key
     * @return the Credentials object containing the encrypted data
     * @throws KeyException key generation or encryption failed
     */
    @Deprecated
    public static Credentials createCredentials(String login, String password, PublicKey pubKey)
            throws KeyException {
        return createCredentials(login, password, null, pubKey, "RSA/ECB/PKCS1Padding");
    }

    /**
     * Creates new encrypted credentials
     * <p>
     * Encrypts the message '<code>login</code>:<code>password</code>' using the
     * public key at <code>pubPath</code> and <code>cipher</code>
     * and store it in a new Credentials object.
     * 
     * @see KeyPairUtil#encrypt(PublicKey, String, byte[])
     * @param login the login to encrypt
     * @param password the corresponding password to encrypt
     * @param pubPath path to the public key used for encryption
     * @param cipher cipher parameters: combination of transformations
     * @return the Credentials object containing the encrypted data
     * @throws KeyException key generation or encryption failed
     */
    @Deprecated
    public static Credentials createCredentials(String login, String password, String pubPath, String cipher)
            throws KeyException {
        PublicKey pubKey = getPublicKey(pubPath);
        return createCredentials(login, password, null, pubKey, cipher);
    }

    /**
     * Creates new encrypted credentials
     * <p>
     * Encrypts the message '<code>login</code>:<code>password</code>' using the
     * public key <code>pubKey</code> and <code>cipher</code>
     * and store it in a new Credentials object.
     * 
     * @see KeyPairUtil#encrypt(PublicKey, String, byte[])
     * @param login the login to encrypt
     * @param password the corresponding password to encrypt
     * @param pubKey public key used for encryption
     * @param cipher cipher parameters: combination of transformations
     * @return the Credentials object containing the encrypted data
     * @throws KeyException key generation or encryption failed
     */
    @Deprecated
    public static Credentials createCredentials(String login, String password, byte[] datakey, PublicKey pubKey,
            String cipher) throws KeyException {

        CredData cc = new CredData();
        cc.setLogin(CredData.parseLogin(login));
        cc.setDomain(CredData.parseDomain(login));
        cc.setPassword(password);
        cc.setKey(datakey);

        // serialize clear credentials to byte array
        byte[] clearCred;
        try {
            clearCred = ObjectToByteConverter.ObjectStream.convert(cc);
        } catch (IOException e1) {
            throw new KeyException(e1.getMessage());
        }

        int size = keySize(pubKey);

        HybridEncryptionUtil.HybridEncryptedData encryptedData = HybridEncryptionUtil.encrypt(pubKey, cipher,
                clearCred);
        byte[] encAes = encryptedData.getEncryptedSymmetricKey();
        byte[] encData = encryptedData.getEncryptedData();

        return new Credentials(pubKey.getAlgorithm(), size, cipher, encAes, encData);
    }

    private static int keySize(PublicKey pubKey) {
        int size = -1;
        if (pubKey instanceof RSAPublicKey) {
            size = ((RSAPublicKey) pubKey).getModulus().bitLength();
        } else if (pubKey instanceof DSAPublicKey) {
            size = ((DSAPublicKey) pubKey).getParams().getP().bitLength();
        } else if (pubKey instanceof DHPublicKey) {
            size = ((DHPublicKey) pubKey).getParams().getP().bitLength();
        }
        return size;
    }

}