org.openanzo.security.keystore.SecretKeyStore.java Source code

Java tutorial

Introduction

Here is the source code for org.openanzo.security.keystore.SecretKeyStore.java

Source

/*******************************************************************************
 * Copyright (c) 2008 Cambridge Semantics Incorporated.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Created by:  Jordi Albornoz Mulligan ( <a href="mailto:jordi@cambridgesemantics.com">jordi@cambridgesemantics.com </a>)
 * 
 * Contributors:
 *     Cambridge Semantics Incorporated - initial API and implementation
 *******************************************************************************/

package org.openanzo.security.keystore;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.util.Dictionary;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.ExceptionConstants;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A component used for encrypting and decrypting data based on a secret key held by the component. Encryption is done with a symmetric cypher with a secret key
 * saved to a keystore.
 * 
 * Be careful to only encrypt and decrypt using corresponding methods. See {@link ISecretKeystore} for more information.
 * 
 * @author Jordi Albornoz Mulligan <jordi@cambridgesemantics.com>
 * @author Joe Betz <jpbetz@cambridgesemantics.com>
 */
public class SecretKeyStore implements ISecretKeystore {
    private static final Logger log = LoggerFactory.getLogger(SecretKeyStore.class);

    /** Prefix for loading a resource URL */
    public static final String resourcePrefix = "resource:";

    private static final String KEY_NAME = "service-container-key";

    private static final String KEY_STORE_ENCODING = "JCEKS";

    private static final String STRING_ENCODING = "UTF-8";

    private File dataDirectory = null;

    private String algorithm;

    private SecretKey skey;

    private BundleContext bundleContext = null;

    @SuppressWarnings("unchecked")
    private Dictionary configurationProperties;

    /**
     * Create a secret key store
     * 
     * @param configurationProperties
     *            configuration properties for secret key store
     * @param dataDirectory
     *            directory for the keystore
     */
    public SecretKeyStore(Dictionary<? extends Object, ? extends Object> configurationProperties,
            File dataDirectory) {
        this.configurationProperties = configurationProperties;
        this.dataDirectory = dataDirectory;
    }

    public void start() throws AnzoException {
        String filelocation = KeyStoreDictionary.getKeyFileLocation(configurationProperties);
        String keyPassword = KeyStoreDictionary.getKeyPassword(configurationProperties);
        String algorithm = KeyStoreDictionary.getAlgorithm(configurationProperties);
        String keystoreType = KeyStoreDictionary.getKeystoreType(configurationProperties);
        if (StringUtils.isEmpty(algorithm)) {
            algorithm = "AES";
        }
        if (StringUtils.isEmpty(keystoreType)) {
            keystoreType = KEY_STORE_ENCODING;
        }
        if (StringUtils.isEmpty(keyPassword)) {
            log.info("Using default key password since no '{}' specified in configuration.",
                    KeyStoreDictionary.KEY_PASSWORD);
            keyPassword = "secret";
        }
        if (StringUtils.isEmpty(filelocation)) {
            throw new AnzoException(ExceptionConstants.OSGI.MISSING_COMPONENT_PARAMETER,
                    KeyStoreDictionary.KEY_FILE_LOCATION);
        }
        log.info("Using keyLocation ('{}') and algorithm ('{}').", filelocation, algorithm);

        this.algorithm = algorithm; // The loadKey method may need to use this.algorithm so we set it here.

        InputStream inputStream = null;
        try {
            File keyStoreFile = null;
            if (filelocation.startsWith(resourcePrefix) && bundleContext != null) { // handle resource:/ file locations
                String resourceLocation = filelocation.substring(resourcePrefix.length());
                URL resourceUrl = bundleContext.getBundle().getResource(resourceLocation);
                if (resourceUrl == null) {
                    throw new AnzoException(ExceptionConstants.IO.READ_ERROR, resourceLocation);
                }
                inputStream = resourceUrl.openStream();
            } else {
                keyStoreFile = (filelocation.startsWith(".") && dataDirectory != null)
                        ? new File(dataDirectory, filelocation)
                        : new File(filelocation);
                if (keyStoreFile.exists()) {
                    inputStream = new FileInputStream(keyStoreFile);
                } else {
                    log.warn("Could not find keystore at '{}'. Creating a new keystore at that location.",
                            keyStoreFile.getAbsolutePath());
                }
            }
            SecretKey key = loadKey(inputStream, keyPassword, keyStoreFile, keystoreType);
            initialize(key, algorithm);
        } catch (IOException e) {
            throw new AnzoException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR, e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("error closing keystore input stream", e);
                }
            }
        }
    }

    public void stop() throws AnzoException {
    }

    /**
     * Initialize the encoder based on the given configuration. This method is typically only used by unit tests.
     * 
     * @param key
     * @param algorithm
     * @throws AnzoException
     */
    public void initialize(SecretKey key, String algorithm) throws AnzoException {
        this.algorithm = algorithm;
        this.skey = key;
    }

    /* (non-Javadoc)
     * @see org.openanzo.server.security.SecretKeyEncoder#encryptAndBase64EncodeString(java.lang.String)
     */
    public String encryptAndBase64EncodeString(String plaintext) throws AnzoException {
        byte[] plainbytes;
        try {
            plainbytes = plaintext.getBytes(STRING_ENCODING);
        } catch (UnsupportedEncodingException e) {
            throw new AnzoException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR, e);
        }
        return encryptAndBase64EncodeBytes(plainbytes);
    }

    /* (non-Javadoc)
     * @see org.openanzo.server.security.SecretKeyEncoder#encryptAndBase64EncodeBytes(byte[])
     */
    public String encryptAndBase64EncodeBytes(byte plaintext[]) throws AnzoException {
        byte[] cypherbytes = encryptBytes(plaintext);
        byte[] encodedCypherbytes = Base64.encodeBase64(cypherbytes);
        String encodedEncryptedStr;
        try {
            encodedEncryptedStr = new String(encodedCypherbytes, STRING_ENCODING); // base64 is ASCII so we use an ASCII compatible encoding to create the String.
        } catch (UnsupportedEncodingException e) {
            throw new AnzoException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR, e);
        }
        return encodedEncryptedStr;
    }

    /* (non-Javadoc)
     * @see org.openanzo.server.security.SecretKeyEncoder#encryptString(java.lang.String)
     */
    public byte[] encryptString(String plaintext) throws AnzoException {
        byte[] plainbytes;
        try {
            plainbytes = plaintext.getBytes(STRING_ENCODING);
        } catch (UnsupportedEncodingException e) {
            throw new AnzoException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR, e);
        }
        return encryptBytes(plainbytes);
    }

    /* (non-Javadoc)
     * @see org.openanzo.server.security.SecretKeyEncoder#encryptBytes(byte[])
     */
    public byte[] encryptBytes(byte plaintext[]) throws AnzoException {
        byte[] cyphertext;
        try {
            Cipher cipher = Cipher.getInstance(algorithm);
            cipher.init(Cipher.ENCRYPT_MODE, skey);
            cyphertext = cipher.doFinal(plaintext);
        } catch (GeneralSecurityException e) {
            throw new AnzoException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR, e);
        }
        return cyphertext;
    }

    /* (non-Javadoc)
     * @see org.openanzo.server.security.SecretKeyEncoder#decryptAndBase64DecodeString(java.lang.String)
     */
    public String decryptAndBase64DecodeString(String base64encodedCyphertext) throws AnzoException {
        byte[] cypherbytes;
        try {
            byte[] base64encodedCypherbytes = base64encodedCyphertext.getBytes(STRING_ENCODING);
            cypherbytes = Base64.decodeBase64(base64encodedCypherbytes);
        } catch (UnsupportedEncodingException e) {
            throw new AnzoException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR, e);
        }
        return decryptString(cypherbytes);
    }

    /* (non-Javadoc)
     * @see org.openanzo.server.security.SecretKeyEncoder#decryptAndBase64DecodeBytes(java.lang.String)
     */
    public byte[] decryptAndBase64DecodeBytes(String base64encodedCyphertext) throws AnzoException {
        byte[] plaintext;
        try {
            byte[] base64encodedCypherbytes = base64encodedCyphertext.getBytes(STRING_ENCODING);
            byte[] cypherbytes = Base64.decodeBase64(base64encodedCypherbytes);
            plaintext = decryptBytes(cypherbytes);
        } catch (UnsupportedEncodingException e) {
            throw new AnzoException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR, e);
        }
        return plaintext;
    }

    /* (non-Javadoc)
     * @see org.openanzo.server.security.SecretKeyEncoder#decryptString(byte[])
     */
    public String decryptString(byte cyphertext[]) throws AnzoException {
        String plaintext;
        try {
            byte[] plainbytes = decryptBytes(cyphertext);
            plaintext = new String(plainbytes, STRING_ENCODING);
        } catch (UnsupportedEncodingException e) {
            throw new AnzoException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR, e);
        }
        return plaintext;
    }

    /* (non-Javadoc)
     * @see org.openanzo.server.security.SecretKeyEncoder#decryptBytes(byte[])
     */
    public byte[] decryptBytes(byte cyphertext[]) throws AnzoException {
        byte[] plaintext;
        try {
            Cipher cipher = Cipher.getInstance(algorithm);
            cipher.init(Cipher.DECRYPT_MODE, skey);
            plaintext = cipher.doFinal(cyphertext);
        } catch (GeneralSecurityException e) {
            throw new AnzoException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR, e);
        }
        return plaintext;
    }

    /**
     * Loads the secret key to use for encryption and decryption. It will read the key from the keystore if it exists. Otherwise it will create a new randomly
     * generated key and save it in a keystore at the given file. It will use the algorithm defined in the <code>algorithm</code> member.
     * 
     * @param keyStoreStream
     *            stream from which to read the keystore which holds the secret key. If null, a new keystore is created.
     * @param password
     *            password used to protect the and integrity-check the secret key.
     * @param keyStoreDestination
     *            File path to which to save the keystore in case it is newly created or a new key was added. If null, then nothing is written out.
     * @return the loaded or newly generated secret key.
     * @throws AnzoException
     */
    private SecretKey loadKey(InputStream keyStoreStream, String password, File keyStoreDestination,
            String keystoreType) throws AnzoException {

        try {
            KeyStore keyStore = KeyStore.getInstance(keystoreType);
            keyStore.load(keyStoreStream, password.toCharArray());

            Key key = null;
            if (keyStore.containsAlias(KEY_NAME)) {
                key = keyStore.getKey(KEY_NAME, password.toCharArray());
            } else {
                log.warn("Could not find key '{}' within keystore. Generating a new key.", KEY_NAME);
                KeyGenerator kgen = KeyGenerator.getInstance(algorithm);
                key = kgen.generateKey();
                keyStore.setKeyEntry(KEY_NAME, key, password.toCharArray(), new Certificate[0]);
                if (keyStoreDestination != null) {
                    log.warn("Storing new key in the keystore.");
                    OutputStream outputStream = null;
                    try {
                        outputStream = FileUtils.openOutputStream(keyStoreDestination);
                        keyStore.store(outputStream, password.toCharArray());
                    } finally {
                        if (outputStream != null) {
                            outputStream.close();
                        }
                    }

                }
            }

            if (!(key instanceof SecretKey))
                throw new AnzoException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR,
                        "key must be of type SecretKey: " + key);
            return (SecretKey) key;
        } catch (GeneralSecurityException e) {
            throw new AnzoException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR, e);
        } catch (IOException e) {
            throw new AnzoException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR, e);
        }

    }

}