/* See LICENSE for licensing and NOTICE for copyright. */
package org.cryptacular.bean;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.KeyStore;
import javax.crypto.SecretKey;
import org.cryptacular.CiphertextHeader;
import org.cryptacular.CryptoException;
import org.cryptacular.EncodingException;
import org.cryptacular.StreamException;
import org.cryptacular.generator.Nonce;
/**
* Base class for all cipher beans. The base class assumes all ciphertext output will contain a prepended {@link
* CiphertextHeader} containing metadata that facilitates decryption.
*
* @author Middleware Services
*/
public abstract class AbstractCipherBean implements CipherBean
{
/** Keystore containing symmetric key(s). */
private KeyStore keyStore;
/** Keystore entry for alias of current key. */
private String keyAlias;
/** Password on private key entry. */
private String keyPassword;
/** Nonce generator. */
private Nonce nonce;
/** Creates a new instance. */
public AbstractCipherBean() {}
/**
* Creates a new instance by specifying all properties.
*
* @param keyStore Key store containing encryption key.
* @param keyAlias Name of encryption key entry in key store.
* @param keyPassword Password used to decrypt key entry in keystore.
* @param nonce Nonce/IV generator.
*/
public AbstractCipherBean(final KeyStore keyStore, final String keyAlias, final String keyPassword, final Nonce nonce)
{
setKeyStore(keyStore);
setKeyAlias(keyAlias);
setKeyPassword(keyPassword);
setNonce(nonce);
}
/** @return Keystore that contains the {@link SecretKey}. */
public KeyStore getKeyStore()
{
return keyStore;
}
/**
* Sets the keystore containing encryption/decryption key(s). The keystore must contain a {@link SecretKey} entry
* whose alias is given by {@link #setKeyAlias(String)}, which will be used at the encryption key. It may contain
* additional symmetric keys to support, for example, key rollover where some existing ciphertexts have headers
* specifying a different key. In general all keys used for outstanding ciphertexts should be contained in the
* keystore.
*
* @param keyStore Keystore containing encryption key(s).
*/
public void setKeyStore(final KeyStore keyStore)
{
this.keyStore = keyStore;
}
/** @return Alias that specifies the {@link KeyStore} entry containing the {@link SecretKey}. */
public String getKeyAlias()
{
return keyAlias;
}
/**
* Sets the keystore entry alias used to locate the current encryption key.
*
* @param keyAlias Alias of {@link SecretKey} used for encryption.
*/
public void setKeyAlias(final String keyAlias)
{
this.keyAlias = keyAlias;
}
/**
* Sets the password used to access the encryption key.
*
* @param keyPassword Encryption key password.
*/
public void setKeyPassword(final String keyPassword)
{
this.keyPassword = keyPassword;
}
/** @return Nonce/IV generation strategy. */
public Nonce getNonce()
{
return nonce;
}
/**
* Sets the nonce/IV generation strategy.
*
* @param nonce Nonce generator.
*/
public void setNonce(final Nonce nonce)
{
this.nonce = nonce;
}
@Override
public byte[] encrypt(final byte[] input) throws CryptoException
{
return process(new CiphertextHeader(nonce.generate(), keyAlias), true, input);
}
@Override
public void encrypt(final InputStream input, final OutputStream output) throws CryptoException, StreamException
{
final CiphertextHeader header = new CiphertextHeader(nonce.generate(), keyAlias);
try {
output.write(header.encode());
} catch (IOException e) {
throw new StreamException(e);
}
process(header, true, input, output);
}
@Override
public byte[] decrypt(final byte[] input) throws CryptoException, EncodingException
{
final CiphertextHeader header = CiphertextHeader.decode(input);
if (header.getKeyName() == null) {
throw new CryptoException("Ciphertext header does not contain required key");
}
return process(header, false, input);
}
@Override
public void decrypt(final InputStream input, final OutputStream output)
throws CryptoException, EncodingException, StreamException
{
final CiphertextHeader header = CiphertextHeader.decode(input);
if (header.getKeyName() == null) {
throw new CryptoException("Ciphertext header does not contain required key");
}
process(header, false, input, output);
}
/**
* Looks up secret key entry in the {@link #keyStore}.
*
* @param alias Name of secret key entry.
*
* @return Secret key.
*/
protected SecretKey lookupKey(final String alias)
{
final Key key;
try {
key = keyStore.getKey(alias, keyPassword.toCharArray());
} catch (Exception e) {
throw new CryptoException("Error accessing keystore entry " + alias, e);
}
if (key instanceof SecretKey) {
return (SecretKey) key;
}
throw new CryptoException(alias + " is not a secret key");
}
/**
* Processes the given data under the action of the cipher.
*
* @param header Ciphertext header.
* @param mode True for encryption; false for decryption.
* @param input Data to process by cipher.
*
* @return Ciphertext data under encryption, plaintext data under decryption.
*/
protected abstract byte[] process(CiphertextHeader header, boolean mode, byte[] input);
/**
* Processes the given data under the action of the cipher.
*
* @param header Ciphertext header.
* @param mode True for encryption; false for decryption.
* @param input Stream containing input data.
* @param output Stream that receives output of cipher.
*/
protected abstract void process(CiphertextHeader header, boolean mode, InputStream input, OutputStream output);
}