package scotty.crypto;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import org.apache.commons.codec.binary.Base64;
/**
* Create, load and save keys.
*
* @author Tobias Zeising tobias.zeising@aditu.de http://www.aditu.de
*/
public class KeyManager {
/**
* Constant for no token set
*/
public static final int NO_TOKEN_SET = -1;
/**
* Length of the RSA Key
*/
public static final int KEY_LENGTH = 2048;
/**
* Private Key
*/
private PrivateKey privateKey = null;
/**
* Public Key
*/
private PublicKey publicKey = null;
/**
* Public Key of the gateway
*/
private PublicKey gatewaysPublicKey = null;
/**
* Current RSA signed AES Password
*/
private byte[] currentToken;
/**
* Current AES Password
*/
private String currentAESPassword;
/**
* Timestamp where current token was created
*/
private long currentTokenTimestamp = NO_TOKEN_SET;
/**
* Max validity of token
*/
private long maxValidityOfToken = 3600;
/**
* List of allowed client public keys.
*/
private List<PublicKey> clientPublicKeys = new ArrayList<PublicKey>();
private static final KeyManager instance = new KeyManager();
private KeyManager() {
}
public static KeyManager getInstance() {
return instance;
}
/**
* Generate new key pair.
*
* @throws CryptoException
* any error
*/
public void generateKeyPair() throws CryptoException {
try {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(KEY_LENGTH);
KeyPair keyPair = kpg.generateKeyPair();
privateKey = keyPair.getPrivate();
publicKey = keyPair.getPublic();
} catch (NoSuchAlgorithmException e) {
throw new CryptoException("Error, can't create keypair: "
+ e.getMessage());
}
}
/**
* Write private key to file (private key will be encrypted with AES first)
*
* @param filename
* private key file
* @param password
* password for AES encryption
* @throws CryptoException
* Exception on AES encryption or IO
*/
public void writePrivateKey(String filename, String password)
throws CryptoException {
if (privateKey == null) {
throw new CryptoException("no private key set");
}
byte[] privateKeyAsByteArray = privateKey.getEncoded();
if (password != null && password.length() != 0)
privateKeyAsByteArray = AESEncryption.encrypt(
privateKey.getEncoded(), password);
writeKey(filename, privateKeyAsByteArray);
}
/**
* Write public key to file.
*
* @param filename
* public key file
* @throws CryptoException
* IO exception
*/
public void writePublicKey(String filename) throws CryptoException {
if (publicKey == null) {
throw new CryptoException("no public key set");
}
writeKey(filename, publicKey.getEncoded());
}
/**
* Writes back given byte array into a given file.
*
* @param filename
* @param key
* @throws CryptoException
*/
private void writeKey(String filename, byte[] key) throws CryptoException {
try {
FileOutputStream out = new FileOutputStream(filename);
out.write(new Base64().encode(key));
out.close();
} catch (IOException e) {
throw new CryptoException("Error writing public key into file: "
+ e.getMessage());
}
}
/**
* Reads a private key from file, decrypts it with password (AES) and saves
* the key in KeyManager.
*
* @param filename
* @param password
* @throws CryptoException
*/
public void readPrivateKey(String filename, String password)
throws CryptoException {
try {
byte[] privateKeyFromFile = readFileBase64Decoded(filename);
if (password != null && password.length() != 0)
privateKeyFromFile = AESEncryption.decrypt(privateKeyFromFile,
password);
this.privateKey = parsePrivateKeyFromByteArray(privateKeyFromFile);
} catch (Exception e) {
throw new CryptoException("Error, can't read private key: "
+ e.getMessage());
}
}
/**
* Read public key and saves it in KeyManager.
*
* @param filename
* @throws CryptoException
*/
public void readPublicKey(String filename) throws CryptoException {
try {
byte[] publicKey = readFileBase64Decoded(filename);
this.publicKey = parsePublicKeyFromByteArray(publicKey);
} catch (Exception e) {
throw new CryptoException("Error, can't read public key: "
+ e.getMessage());
}
}
/**
* Read gateways public key and saves it in KeyManager.
*
* @param filename
* @throws CryptoException
*/
public void readGatewaysPublicKey(String filename) throws CryptoException {
try {
byte[] publicKey = readFileBase64Decoded(filename);
this.gatewaysPublicKey = parsePublicKeyFromByteArray(publicKey);
} catch (Exception e) {
throw new CryptoException("Error, can't read gateways public key: "
+ e.getMessage());
}
}
/**
* Reads the public keys of the clients. One key per line. (Content is
* Base64 coded).
*
* @param filename
* @throws CryptoException
*/
public void readClientPublicKey(String filename) throws CryptoException {
try {
byte[] content = readFile(filename);
BufferedReader r = new BufferedReader(new InputStreamReader(
new ByteArrayInputStream(content)));
String clientPublicKey;
while ((clientPublicKey = r.readLine()) != null) {
PublicKey key = parsePublicKeyFromByteArray(Base64
.decodeBase64(clientPublicKey));
clientPublicKeys.add(key);
}
} catch (Exception e) {
throw new CryptoException("Error, can't read gateways public key: "
+ e.getMessage());
}
}
/**
* Reads a given file and return it as byte array.
*
* @param filename
* @return byte array
* @throws CryptoException
*/
private byte[] readFile(String filename) throws CryptoException {
try {
InputStream fileInputStream = null;
if (filename.startsWith("resources")) {
ClassLoader classLoader = getClass().getClassLoader();
fileInputStream = classLoader.getResourceAsStream(filename
.substring(filename.indexOf(":") + 1));
} else {
fileInputStream = new FileInputStream(new File(filename));
}
byte[] b = inputStreamToByteArray(fileInputStream);
fileInputStream.close();
return b;
} catch (IOException e) {
throw new CryptoException("Error writing public key into file: "
+ e.getMessage());
}
}
/**
* Reads a given file and return it as byte array, decodes the Base64
* content.
*
* @param filename
* @return byte array
* @throws CryptoException
*/
private byte[] readFileBase64Decoded(String filename)
throws CryptoException {
byte[] file = readFile(filename);
return Base64.decodeBase64(file);
}
/**
* Converts a public key from byte array to PublicKey object
*
* @param publicKeyRaw
* @return PublicKey
* @throws InvalidKeySpecException
* @throws NoSuchAlgorithmException
*/
private synchronized PublicKey parsePublicKeyFromByteArray(
byte[] publicKeyRaw) throws InvalidKeySpecException,
NoSuchAlgorithmException {
PublicKey publicKey = null;
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyRaw);
publicKey = keyFactory.generatePublic(publicKeySpec);
return publicKey;
}
/**
* Converts a private key from byte array to PrivateKey object
*
* @param privateKeyRaw
* @return PrivateKey
* @throws InvalidKeySpecException
* @throws NoSuchAlgorithmException
*/
private synchronized PrivateKey parsePrivateKeyFromByteArray(
byte[] privateKeyRaw) throws InvalidKeySpecException,
NoSuchAlgorithmException {
PrivateKey privateKey = null;
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyRaw);
privateKey = keyFactory.generatePrivate(privateKeySpec);
return privateKey;
}
/**
* Reads from InputStream and return content as byte array
*
* @param is
* InputStream
* @return byte array
* @throws IOException
*/
private byte[] inputStreamToByteArray(InputStream is) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[16384];
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
return buffer.toByteArray();
}
/**
* Returns current token. If token is no longer valid, a new one will be
* generated.
*
* @return current token
* @throws CryptoException
*/
public synchronized byte[] getCurrentToken() throws CryptoException {
try {
// valid token available?
long timestamp = (new Date().getTime()) / 1000;
if (currentTokenTimestamp != NO_TOKEN_SET
&& timestamp < currentTokenTimestamp + maxValidityOfToken) {
return currentToken;
}
// generate new token
StringBuilder aesPasswordAndTimestamp = new StringBuilder();
// generate AES key
String randomAesPassword = generateRandomString(16);
aesPasswordAndTimestamp.append(randomAesPassword);
aesPasswordAndTimestamp.append(new String("|")); // separator
aesPasswordAndTimestamp.append(timestamp);
// encrypt token with gateways public key
byte[] encryptedToken = RSAEncryption.encrypt(
aesPasswordAndTimestamp.toString().getBytes(),
getGatewaysPublicKey());
// sign AES key and password
byte[] sign = RSAEncryption.sign(aesPasswordAndTimestamp.toString()
.getBytes(), privateKey);
Base64 base64 = new Base64();
StringBuilder encryptedAesTimestampAndSign = new StringBuilder();
encryptedAesTimestampAndSign.append(new String(base64
.encode(encryptedToken)));
encryptedAesTimestampAndSign.append(new String("|")); // separator
encryptedAesTimestampAndSign
.append(new String(base64.encode(sign)));
this.currentToken = encryptedAesTimestampAndSign.toString()
.getBytes();
this.currentAESPassword = randomAesPassword;
this.currentTokenTimestamp = timestamp;
return currentToken;
} catch (Exception e) {
throw new CryptoException(e);
}
}
/**
* Generates an random string
*
* @param length
* of the string
* @return random string
*/
private static String generateRandomString(int length) {
String allowedChars = "0123456789abcdefghijklmnopqrstuvwxyz";
Random random = new Random();
int max = allowedChars.length();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < length; i++) {
int value = random.nextInt(max);
buffer.append(allowedChars.charAt(value));
}
return buffer.toString();
}
public PrivateKey getPrivateKey() {
return privateKey;
}
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
}
public PublicKey getPublicKey() {
return publicKey;
}
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
}
public PublicKey getGatewaysPublicKey() {
return gatewaysPublicKey;
}
public void setGatewaysPublicKey(PublicKey gatewaysPublicKey) {
this.gatewaysPublicKey = gatewaysPublicKey;
}
public String getCurrentAESPassword() {
return currentAESPassword;
}
public void setCurrentAESPassword(String currentAESPassword) {
this.currentAESPassword = currentAESPassword;
}
public long getCurrentTokenTimestamp() {
return currentTokenTimestamp;
}
public void setCurrentTokenTimestamp(long currentTokenTimestamp) {
this.currentTokenTimestamp = currentTokenTimestamp;
}
public long getMaxValidityOfToken() {
return maxValidityOfToken;
}
public void setMaxValidityOfToken(long maxValidityOfToken) {
this.maxValidityOfToken = maxValidityOfToken;
}
public void setCurrentToken(byte[] currentToken) {
this.currentToken = currentToken;
}
public List<PublicKey> getClientPublicKeys() {
return clientPublicKeys;
}
public void setClientPublicKeys(List<PublicKey> clientPublicKeys) {
this.clientPublicKeys = clientPublicKeys;
}
}