/*******************************************************************************
* Copyright (c) 2013 Lectorius, Inc.
* Authors:
* Vijay Pandurangan (vijayp@mitro.co)
* Evan Jones (ej@mitro.co)
* Adam Hilss (ahilss@mitro.co)
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can contact the authors at inbound@mitro.co.
*******************************************************************************/
package co.mitro.core.crypto;
import java.io.File;
import java.io.IOException;
import org.keyczar.Crypter;
import org.keyczar.DefaultKeyType;
import org.keyczar.Encrypter;
import org.keyczar.GenericKeyczar;
import org.keyczar.Signer;
import org.keyczar.Verifier;
import org.keyczar.enums.KeyPurpose;
import org.keyczar.exceptions.KeyczarException;
import org.keyczar.interfaces.KeyczarReader;
import co.mitro.core.crypto.KeyInterfaces.CryptoError;
import co.mitro.core.crypto.KeyInterfaces.KeyFactory;
import co.mitro.core.crypto.KeyInterfaces.PrivateKeyInterface;
import co.mitro.core.crypto.KeyInterfaces.PublicKeyInterface;
import co.mitro.keyczar.JsonWriter;
import co.mitro.keyczar.KeyczarJsonReader;
import co.mitro.keyczar.KeyczarPBEReader;
import co.mitro.keyczar.Util;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import com.google.gson.Gson;
/**
* Implements KeyFactory using Keyczar. Each key is actually a pair for encryption and signatures.
*/
public class KeyczarKeyFactory implements KeyFactory {
// Gson object is thread-safe, assuming custom serializers are thread safe
private static final Gson gson = new Gson();
public static abstract class KeyczarBaseKey {
protected final GenericKeyczar encryptionKey;
protected final GenericKeyczar signingKey;
public KeyczarBaseKey(KeyczarReader encryptionReader, KeyczarReader signingReader)
throws KeyczarException {
this.encryptionKey = new GenericKeyczar(encryptionReader);
this.signingKey = new GenericKeyczar(signingReader);
}
@Override
public String toString() {
SerializedKey key = new SerializedKey();
key.encryption = JsonWriter.toString(encryptionKey);
key.signing = JsonWriter.toString(signingKey);
return gson.toJson(key);
}
}
public static final class KeyczarPublicKey extends KeyczarBaseKey implements PublicKeyInterface {
private final Encrypter encrypter;
private final Verifier verifier;
public KeyczarPublicKey(KeyczarReader encryptionReader, KeyczarReader signingReader)
throws KeyczarException {
super(encryptionReader, signingReader);
encrypter = new Encrypter(encryptionReader);
verifier = new Verifier(signingReader);
}
@Override
public boolean verify(String message, String signature) throws CryptoError {
try {
return verifier.verify(message, signature);
} catch (KeyczarException e) {
throw new CryptoError(e);
} catch (ArrayIndexOutOfBoundsException e) {
throw new CryptoError(e);
}
}
@Override
public String encrypt(String plaintext) throws CryptoError {
try {
return Util.encryptWithSession(encrypter, plaintext);
} catch (KeyczarException e) {
throw new CryptoError(e);
}
}
}
/** Size of keys for RSA. Keyczar defaults to 4096 which is too slow in JavaScript. */
public static final int RSA_KEY_SIZE = 2048;
private static final class SerializedKey {
public String encryption;
public String signing;
}
private static final class SerializedReader {
public final KeyczarJsonReader encryptionReader;
public final KeyczarJsonReader signingReader;
public SerializedReader(SerializedKey key) {
encryptionReader = new KeyczarJsonReader(key.encryption);
signingReader = new KeyczarJsonReader(key.signing);
}
public static SerializedReader loadJson(String serializedKey) {
SerializedKey key = gson.fromJson(serializedKey, SerializedKey.class);
return new SerializedReader(key);
}
}
@Override
public PublicKeyInterface loadPublicKey(String serializedKey) throws CryptoError {
try {
SerializedReader key = SerializedReader.loadJson(serializedKey);
return new KeyczarPublicKey(key.encryptionReader, key.signingReader);
} catch (KeyczarException e) {
throw new CryptoError(e);
}
}
@Override
public PrivateKeyInterface loadPrivateKey(String serializedKey) throws CryptoError {
try {
SerializedReader key = SerializedReader.loadJson(serializedKey);
return new KeyczarPrivateKey(key.encryptionReader, key.signingReader);
} catch (KeyczarException e) {
throw new CryptoError(e);
}
}
@Override
public PrivateKeyInterface loadEncryptedPrivateKey(String serializedKey, String password)
throws CryptoError {
try {
SerializedReader key = SerializedReader.loadJson(serializedKey);
KeyczarPBEReader encryptedEncryption = new KeyczarPBEReader(key.encryptionReader, password);
KeyczarPBEReader encryptedSigning = new KeyczarPBEReader(key.signingReader, password);
return new KeyczarPrivateKey(encryptedEncryption, encryptedSigning);
} catch (KeyczarException e) {
throw new CryptoError(e);
}
}
@Override
public PrivateKeyInterface generate() throws CryptoError {
try {
KeyczarReader encryptionKey = Util.generateKeyczarReader(
DefaultKeyType.RSA_PRIV, KeyPurpose.DECRYPT_AND_ENCRYPT, RSA_KEY_SIZE);
KeyczarReader signingKey = Util.generateKeyczarReader(
DefaultKeyType.RSA_PRIV, KeyPurpose.SIGN_AND_VERIFY, RSA_KEY_SIZE);
return new KeyczarPrivateKey(encryptionKey, signingKey);
} catch (KeyczarException e) {
throw new KeyInterfaces.CryptoError(e);
}
}
private static final class KeyczarPrivateKey
extends KeyczarBaseKey implements PrivateKeyInterface {
private final Crypter crypter;
private final Signer signer;
public KeyczarPrivateKey(KeyczarReader encryptionReader, KeyczarReader signingReader)
throws KeyczarException {
super(encryptionReader, signingReader);
crypter = new Crypter(encryptionReader);
signer = new Signer(signingReader);
}
@Override
public boolean verify(String message, String signature) throws CryptoError {
try {
return signer.verify(message, signature);
} catch (KeyczarException e) {
throw new CryptoError(e);
}
}
@Override
public String sign(String message) throws CryptoError {
try {
return signer.sign(message);
} catch (KeyczarException e) {
throw new CryptoError(e);
}
}
@Override
public PublicKeyInterface exportPublicKey() throws CryptoError {
try {
KeyczarReader publicEncrypt = Util.exportPublicKeys(encryptionKey);
KeyczarReader publicSigning = Util.exportPublicKeys(signingKey);
return new KeyczarPublicKey(publicEncrypt, publicSigning);
} catch (KeyczarException e) {
throw new CryptoError(e);
}
}
@Override
public String exportEncrypted(String password) throws CryptoError {
SerializedKey key = new SerializedKey();
StringBuilder out = new StringBuilder();
JsonWriter.writeEncrypted(encryptionKey, password, out);
key.encryption = out.toString();
out = new StringBuilder();
JsonWriter.writeEncrypted(signingKey, password, out);
key.signing = out.toString();
return gson.toJson(key);
}
@Override
public String encrypt(String plaintext) throws CryptoError {
try {
return Util.encryptWithSession(crypter, plaintext);
} catch (KeyczarException e) {
throw new CryptoError(e);
}
}
@Override
public String decrypt(String message) throws CryptoError {
try {
return Util.decryptWithSession(crypter, message);
} catch (KeyczarException e) {
throw new CryptoError(e);
}
}
}
/** Test program to load a key from disk and decrypt a file. */
public static void main(String[] arguments) throws IOException, CryptoError {
String privateKeyPath = arguments[0];
String password = arguments[1];
String encryptedPath = arguments[2];
String privateKeyData = Files.toString(new File(privateKeyPath), Charsets.UTF_8);
KeyczarKeyFactory keyFactory = new KeyczarKeyFactory();
PrivateKeyInterface key = keyFactory.loadEncryptedPrivateKey(privateKeyData, password);
String encryptedData = Files.toString(new File(encryptedPath), Charsets.UTF_8);
System.out.println(key.decrypt(encryptedData));
}
}