/*******************************************************************************
* Copyright (c) 2013, 2014 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.keyczar;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import org.keyczar.Crypter;
import org.keyczar.DefaultKeyType;
import org.keyczar.Encrypter;
import org.keyczar.GenericKeyczar;
import org.keyczar.KeyMetadata;
import org.keyczar.Signer;
import org.keyczar.Verifier;
import org.keyczar.enums.KeyPurpose;
import org.keyczar.exceptions.KeyczarException;
import org.keyczar.interfaces.KeyczarReader;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
/* Script that tries all message lengths, and generates keys both in JS and Java.
#!/bin/sh
set -e
JAVA="java -ea -cp java/server/lib/guava-14.0.1.jar:java/server/lib/keyczar-0.71f-040513.jar:java/server/lib/gson-2.2.4.jar:java/server/lib/log4j-1.2.17.jar:bin"
mkdir -p testwtf
MESSAGE=''
for i in `seq 471`; do
rm -f testwtf/*
echo "Generating keys with Java"
$JAVA co.mitro.keyczar.RoundTripper encrypt testwtf "$MESSAGE"
node ~/keyczarjs/roundtripper.js roundtrip testwtf
$JAVA co.mitro.keyczar.RoundTripper decrypt testwtf "$MESSAGE"
rm -f testwtf/*
echo "Generting keys with JS"
node ~/keyczarjs/roundtripper.js encrypt testwtf "$MESSAGE"
$JAVA co.mitro.keyczar.RoundTripper roundtrip testwtf
node ~/keyczarjs/roundtripper.js decrypt testwtf "$MESSAGE"
echo $i
echo
MESSAGE="${MESSAGE}A"
done
*/
/**
* Verifies interoperability with other implementations.
*
* encrypt mode:
* - Generate private key.
* - Write encrypted message.
*
* Other implementation:
* - Decrypt message with private key.
* - Re-encrypt message with public key.
*
* decrypt mode:
* - Decrypt the re-encrypted message and verify
*/
public class RoundTripper {
private static GenericKeyczar createKey(DefaultKeyType type, KeyPurpose purpose, String outpath) throws KeyczarException {
// Generate a key and write it out
System.out.println("generating " + type + " key into " + outpath);
GenericKeyczar key = Util.createKey(type, purpose);
Util.writeJsonToPath(key, outpath);
return key;
}
private static void encrypt(String keyPath, String message, String outpath) throws KeyczarException, IOException {
Encrypter key = new Encrypter(Util.readJsonFromPath(keyPath));
System.out.println("encrypting message length " + message.length());
String output = key.encrypt(message);
Files.write(output, new File(outpath), Charsets.UTF_8);
}
private static void sign(String keyPath, String message, String outpath) throws KeyczarException, IOException {
Signer key = new Signer(Util.readJsonFromPath(keyPath));
System.out.println("signing message length " + message.length());
String output = key.sign(message);
Files.write(output, new File(outpath), Charsets.UTF_8);
}
public static void verify(String keyPath, String message, String signaturePath) throws KeyczarException {
// Read the key, possibly decrypting using a password
KeyczarReader reader = Util.readJsonFromPath(keyPath);
Verifier key = new Verifier(reader);
String signature = Util.readFile(signaturePath);
System.out.println("verifying signature on message length " + message.length());
if (!key.verify(message, signature)) {
System.err.println("Signature could not be verified!\n");
System.exit(1);
}
}
public static String decrypt(String keyPath, String encryptedPath, String expectedMessage,
DefaultKeyType expectedType, String keyPassword) throws KeyczarException {
// Read the key, possibly decrypting using a password
KeyczarReader reader = Util.readJsonFromPath(keyPath);
if (keyPassword != null) {
reader = new KeyczarPBEReader(reader, keyPassword);
}
KeyMetadata metadata = KeyMetadata.read(reader.getMetadata());
if (metadata.getType() != expectedType) {
throw new RuntimeException("Unexpected key type: " + metadata.getType());
}
Crypter key = new Crypter(reader);
String data = Util.readFile(encryptedPath);
String output = key.decrypt(data);
if (expectedMessage != null) {
if (output.equals(expectedMessage)) {
System.out.println(encryptedPath + " decrypts successfully");
} else {
System.err.println("Decryption does not match?\n" + output);
System.exit(1);
}
}
return output;
}
private static final String MESSAGE = "Hello this is a longish message from Java";
private static final String PASSWORD = "foopassword";
private static void encryptSession(String keyPath, String outPath, String message) throws KeyczarException {
KeyczarReader reader = Util.readJsonFromPath(keyPath);
String output = Util.encryptWithSession(new Encrypter(reader), message);
try {
FileOutputStream out = new FileOutputStream(outPath);
byte[] data = output.getBytes("UTF-8");
out.write(data);
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static String decryptSession(String keyPath, String inPath, String expectedMessage) throws KeyczarException {
KeyczarReader reader = Util.readJsonFromPath(keyPath);
String input = Util.readFile(inPath);
String output = Util.decryptWithSession(new Crypter(reader), input);
if (expectedMessage != null && !output.equals(expectedMessage)) {
System.err.println("Session decryption does not match?\n" + output);
System.exit(1);
}
return output;
}
private static String makeLonger(String inputMessage) {
StringBuilder longMessage = new StringBuilder(inputMessage);
// protect against zero length input
if (longMessage.length() == 0) {
longMessage.append(0x00);
}
while (longMessage.length() < 1000) {
longMessage.append(longMessage);
}
return longMessage.toString();
}
public static void main(String[] args) throws KeyczarException, IOException {
if (args.length != 2 && args.length != 3) {
System.err.println("RoundTripper (mode) (in/out directory) [message to en/decrypt]");
System.exit(1);
}
String mode = args[0];
String dirpath = args[1];
String message = MESSAGE;
if (args.length == 3) {
message = args[2];
}
if (mode.equals("encrypt")) {
GenericKeyczar privateKey = createKey(DefaultKeyType.RSA_PRIV, KeyPurpose.DECRYPT_AND_ENCRYPT, dirpath + "/privatekey.json");
// Export/use the public key
KeyczarReader publicKeyReader = Util.exportPublicKeys(privateKey);
Util.writeJsonToPath(new GenericKeyczar(publicKeyReader), dirpath + "/publickey.json");
encrypt(dirpath + "/publickey.json", message, dirpath + "/publickey_encrypted");
// Write the encrypted key
FileOutputStream output = new FileOutputStream(dirpath + "/privatekey_encrypted.json");
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, "UTF-8"));
JsonWriter.writeEncrypted(privateKey, PASSWORD, writer);
writer.close();
// Create a symmetric key
createKey(DefaultKeyType.AES, KeyPurpose.DECRYPT_AND_ENCRYPT, dirpath + "/symmetric.json");
encrypt(dirpath + "/symmetric.json", message, dirpath + "/symmetric_encrypted");
// Encrypt with session data
encryptSession(dirpath + "/publickey.json", dirpath + "/publickey_session", makeLonger(message));
// Create a signing key
GenericKeyczar privateSignKey = createKey(DefaultKeyType.RSA_PRIV, KeyPurpose.SIGN_AND_VERIFY, dirpath + "/privatekey_sign.json");
// Export the public key; sign with the private key
KeyczarReader publicSignKeyReader = Util.exportPublicKeys(privateSignKey);
Util.writeJsonToPath(new GenericKeyczar(publicSignKeyReader), dirpath + "/publickey_sign.json");
sign(dirpath + "/privatekey_sign.json", message, dirpath + "/privatekey_sign");
} else if (mode.equals("decrypt")) {
decrypt(dirpath + "/privatekey.json", dirpath + "/publickey_reencrypted", message, DefaultKeyType.RSA_PRIV, null);
decrypt(dirpath + "/symmetric.json", dirpath + "/symmetric_reencrypted", message, DefaultKeyType.AES, null);
String output = decryptSession(dirpath + "/privatekey.json", dirpath + "/publickey_session_reencrypted", makeLonger(message));
// verify the session signature
verify(dirpath + "/publickey_sign.json", output, dirpath + "/publickey_session_sign");
} else if (mode.equals("roundtrip")) {
// re-encrypt the message
String output = decrypt(dirpath + "/privatekey.json", dirpath + "/publickey_encrypted", null, DefaultKeyType.RSA_PRIV, null);
encrypt(dirpath + "/publickey.json", output, dirpath + "/publickey_reencrypted");
// Decrypt with the encrypted key
String output2 = decrypt(dirpath + "/privatekey_encrypted.json",
dirpath + "/publickey_encrypted", null, DefaultKeyType.RSA_PRIV, PASSWORD);
if (!output2.equals(output)) {
System.err.println("encrypted private key did not work?");
System.exit(1);
}
output = decrypt(dirpath + "/symmetric.json", dirpath + "/symmetric_encrypted", null, DefaultKeyType.AES, null);
encrypt(dirpath + "/symmetric.json", output, dirpath + "/symmetric_reencrypted");
// Verify the signature
verify(dirpath + "/publickey_sign.json", output, dirpath + "/privatekey_sign");
output = decryptSession(dirpath + "/privatekey.json", dirpath + "/publickey_session", null);
encryptSession(dirpath + "/publickey.json", dirpath + "/publickey_session_reencrypted", output);
// sign the session output
sign(dirpath + "/privatekey_sign.json", output, dirpath + "/publickey_session_sign");
} else {
System.err.println("Mode must be encrypt, decrypt, or roundtrip\n");
System.exit(1);
}
}
}