package com.emc.vipr.transform.encryption;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.Provider;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.vipr.transform.InputTransform;
import com.emc.vipr.transform.TransformConstants;
public class KeyStoreEncryptionFactoryTest {
private static final Logger logger = LoggerFactory.getLogger(
KeyStoreEncryptionFactoryTest.class);
private KeyStore keystore;
private String keystorePassword = "viprviprvipr";
private String keyAlias = "masterkey";
private String keystoreFile = "keystore.jks";
protected Provider provider;
@Before
public void setUp() throws Exception {
// Init keystore
keystore = KeyStore.getInstance("jks");
InputStream in = this.getClass().getClassLoader().getResourceAsStream(keystoreFile);
if(in == null) {
throw new FileNotFoundException(keystoreFile);
}
keystore.load(in, keystorePassword.toCharArray());
logger.debug("Keystore Loaded");
for(Enumeration<String> aliases = keystore.aliases(); aliases.hasMoreElements();) {
logger.debug("Found key: {}", aliases.nextElement());
}
}
@Test
public void testRekey() throws Exception {
KeyStoreEncryptionFactory factory = new KeyStoreEncryptionFactory(keystore,
"oldkey", keystorePassword.toCharArray(), provider);
ByteArrayOutputStream out = new ByteArrayOutputStream();
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("name1", "value1");
metadata.put("name2", "value2");
BasicEncryptionOutputTransform outTransform = factory
.getOutputTransform(out, metadata);
// Get some data to encrypt.
InputStream classin = this.getClass().getClassLoader()
.getResourceAsStream("uncompressed.txt");
ByteArrayOutputStream classByteStream = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int c = 0;
while ((c = classin.read(buffer)) != -1) {
classByteStream.write(buffer, 0, c);
}
byte[] uncompressedData = classByteStream.toByteArray();
classin.close();
OutputStream encryptedStream = outTransform.getEncodedOutputStream();
encryptedStream.write(uncompressedData);
encryptedStream.close();
byte[] encryptedObject = out.toByteArray();
Map<String, String> objectMetadata = outTransform.getEncodedMetadata();
// Now, rekey.
factory.setMasterEncryptionKeyAlias(keyAlias);
Map<String, String> objectMetadata2 = factory.rekey(objectMetadata);
// Verify that the key ID and encrypted object key changed.
assertNotEquals("Master key ID should have changed",
objectMetadata.get(TransformConstants.META_ENCRYPTION_KEY_ID),
objectMetadata2.get(TransformConstants.META_ENCRYPTION_KEY_ID));
assertNotEquals("Encrypted object key should have changed",
objectMetadata.get(TransformConstants.META_ENCRYPTION_OBJECT_KEY),
objectMetadata2.get(TransformConstants.META_ENCRYPTION_OBJECT_KEY));
// Decrypt with updated key
ByteArrayInputStream encodedInput = new ByteArrayInputStream(encryptedObject);
InputTransform inTransform = factory.getInputTransform(
outTransform.getTransformConfig(), encodedInput, objectMetadata2);
InputStream inStream = inTransform.getDecodedInputStream();
ByteArrayOutputStream decodedOut = new ByteArrayOutputStream();
while ((c = inStream.read(buffer)) != -1) {
decodedOut.write(buffer, 0, c);
}
byte[] decodedData = decodedOut.toByteArray();
assertArrayEquals("Decrypted output incorrect", uncompressedData, decodedData);
}
@Test
public void testRekey256() throws Exception {
KeyStoreEncryptionFactory factory = new KeyStoreEncryptionFactory(keystore,
"oldkey", keystorePassword.toCharArray(), provider);
Assume.assumeTrue("256-bit AES is not supported",
KeyStoreEncryptionFactory.getMaxKeySize("AES") > 128);
factory.setEncryptionSettings(TransformConstants.DEFAULT_ENCRYPTION_TRANSFORM,
256, provider);
ByteArrayOutputStream out = new ByteArrayOutputStream();
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("name1", "value1");
metadata.put("name2", "value2");
BasicEncryptionOutputTransform outTransform = factory
.getOutputTransform(out, metadata);
// Get some data to encrypt.
InputStream classin = this.getClass().getClassLoader()
.getResourceAsStream("uncompressed.txt");
ByteArrayOutputStream classByteStream = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int c = 0;
while ((c = classin.read(buffer)) != -1) {
classByteStream.write(buffer, 0, c);
}
byte[] uncompressedData = classByteStream.toByteArray();
classin.close();
OutputStream encryptedStream = outTransform.getEncodedOutputStream();
encryptedStream.write(uncompressedData);
encryptedStream.close();
byte[] encryptedObject = out.toByteArray();
Map<String, String> objectMetadata = outTransform.getEncodedMetadata();
// Now, rekey.
factory.setMasterEncryptionKeyAlias(keyAlias);
Map<String, String> objectMetadata2 = factory.rekey(objectMetadata);
// Verify that the key ID and encrypted object key changed.
assertNotEquals("Master key ID should have changed",
objectMetadata.get(TransformConstants.META_ENCRYPTION_KEY_ID),
objectMetadata2.get(TransformConstants.META_ENCRYPTION_KEY_ID));
assertNotEquals("Encrypted object key should have changed",
objectMetadata.get(TransformConstants.META_ENCRYPTION_OBJECT_KEY),
objectMetadata2.get(TransformConstants.META_ENCRYPTION_OBJECT_KEY));
// Decrypt with updated key
ByteArrayInputStream encodedInput = new ByteArrayInputStream(encryptedObject);
InputTransform inTransform = factory.getInputTransform(
outTransform.getTransformConfig(), encodedInput, objectMetadata2);
InputStream inStream = inTransform.getDecodedInputStream();
ByteArrayOutputStream decodedOut = new ByteArrayOutputStream();
while ((c = inStream.read(buffer)) != -1) {
decodedOut.write(buffer, 0, c);
}
byte[] decodedData = decodedOut.toByteArray();
assertArrayEquals("Decrypted output incorrect", uncompressedData, decodedData);
}
@Test
public void testKeyStoreEncryptionFactory() throws Exception {
// Should fail if key not found.
try {
new KeyStoreEncryptionFactory(keystore,
"NoKey", keystorePassword.toCharArray(), provider);
fail("Should not init with invalid key alias");
} catch(InvalidKeyException e) {
// OK
}
}
@Test
public void testGetOutputTransform() throws Exception {
KeyStoreEncryptionFactory factory = new KeyStoreEncryptionFactory(keystore,
keyAlias, keystorePassword.toCharArray(), provider);
ByteArrayOutputStream out = new ByteArrayOutputStream();
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("name1", "value1");
metadata.put("name2", "value2");
BasicEncryptionOutputTransform outTransform = factory
.getOutputTransform(out, metadata);
// Get some data to encrypt.
InputStream classin = this.getClass().getClassLoader()
.getResourceAsStream("uncompressed.txt");
ByteArrayOutputStream classByteStream = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int c = 0;
while ((c = classin.read(buffer)) != -1) {
classByteStream.write(buffer, 0, c);
}
byte[] uncompressedData = classByteStream.toByteArray();
classin.close();
OutputStream encryptedStream = outTransform.getEncodedOutputStream();
encryptedStream.write(uncompressedData);
// Should not allow this yet.
try {
outTransform.getEncodedMetadata();
fail("Should not be able to get encoded metadata until stream is closed");
} catch (IllegalStateException e) {
// OK.
}
encryptedStream.close();
Map<String, String> objectData = outTransform.getEncodedMetadata();
assertEquals("Uncompressed digest incorrect",
"027e997e6b1dfc97b93eb28dc9a6804096d85873",
objectData.get(TransformConstants.META_ENCRYPTION_UNENC_SHA1));
assertEquals("Uncompressed size incorrect", 2516125,
Long.parseLong(objectData
.get(TransformConstants.META_ENCRYPTION_UNENC_SIZE)));
assertNotNull("Missing IV",
objectData.get(TransformConstants.META_ENCRYPTION_IV));
assertEquals("Incorrect master encryption key ID",
"e3a69d422e6d9008e3cdfcbea674ccd9ab4758c3",
objectData.get(TransformConstants.META_ENCRYPTION_KEY_ID));
assertNotNull("Missing object key",
objectData.get(TransformConstants.META_ENCRYPTION_OBJECT_KEY));
assertNotNull("Missing metadata signature",
objectData.get(TransformConstants.META_ENCRYPTION_META_SIG));
assertEquals("name1 incorrect", "value1", objectData.get("name1"));
assertEquals("name2 incorrect", "value2", objectData.get("name2"));
String transformConfig = outTransform.getTransformConfig();
assertEquals("Transform config string incorrect",
"ENC:AES/CBC/PKCS5Padding", transformConfig);
logger.info("Encoded metadata: " + objectData);
}
@Test
public void testGetInputTransform() throws Exception {
Map<String, String> objectMetadata = new HashMap<String, String>();
objectMetadata.put("x-emc-enc-object-key", "iyuQuDL9qsfZk2XqnRihfw8ejr+OcrsflXvYD1I5o/Bw+wZPkY4Fm6py8ng25K/iw6kO0zbqq5v5Ywkng0pgrUdHLyR6Aq/dD0vKTK46E6sHZKknlM7NSixR8qaieBwwc2QnhOzyFPIVWSgwo9TqhlPRlOjftRLwU6Nt056BGt5Lhrqn3DeQpTrZW8LDjcpTC1UZtVXe3v9pXB4JEz8M4iFjnFprHykmixlR35RWOw4tIVEbsbcXZwt9RhVsDHj8qnkH66S88y4IOuuU4JJeFMywFXLdDs+MlUrYrA/MvfZNs34WKLYcFICKuLoHoGZ/gReJPbKy64lhSM8gTtYf/Q==");
objectMetadata.put("x-emc-enc-key-id", "e3a69d422e6d9008e3cdfcbea674ccd9ab4758c3");
objectMetadata.put("x-emc-enc-unencrypted-size", "2516125");
objectMetadata.put("x-emc-enc-unencrypted-sha1", "027e997e6b1dfc97b93eb28dc9a6804096d85873");
objectMetadata.put("x-emc-enc-metadata-signature", "F5IG2SC20oFpjLCc+5aETIy25tjUSodNlpmkae/1g91gkCYtP6NG6aLMQLHwyu789LmSegPQ/flUwcqdDE8nCI9Y2SuVbQIE5wvyB7RXRNqDIBKOan4xiOS/G5BwzzPFs6uL3I0b5Ya/VrJYhnDiRMAC+6L5kDbEVesHkx77qqCxku/SSMzCJ2K7kX/MYKfJdNQgXsFMAZs1PEcJpW8viQVTEYR8YR7bx37y4/lIHBotmC7HtB0RWAIGDFcHrnASyqpyHCYnwYjiPqItWaZy7WxRVM+qkH7IMtJT2XCuuI6VFmNzu57LN8p5ROBKO4l0hTgfgHMOUbpmQwuanb6p9Q==");
objectMetadata.put("x-emc-iv", "OCoTA8kO0A+ZKkoZKa7VIQ==");
objectMetadata.put("name1", "value1");
objectMetadata.put("name2", "value2");
InputStream encryptedObject = this.getClass().getClassLoader()
.getResourceAsStream("encrypted.txt.keystore.aes128");
KeyStoreEncryptionFactory factory = new KeyStoreEncryptionFactory(keystore,
keyAlias, keystorePassword.toCharArray(), provider);
// Load the transform.
InputTransform inTransform = factory.getInputTransform("ENC:AES/CBC/PKCS5Padding", encryptedObject, objectMetadata);
InputStream inStream = inTransform.getDecodedInputStream();
byte[] buffer = new byte[4096];
int c = 0;
// Decrypt into a buffer
ByteArrayOutputStream decryptedData = new ByteArrayOutputStream();
while ((c = inStream.read(buffer)) != -1) {
decryptedData.write(buffer, 0, c);
}
// Get original data to check.
InputStream originalStream = this.getClass().getClassLoader()
.getResourceAsStream("uncompressed.txt");
ByteArrayOutputStream classByteStream = new ByteArrayOutputStream();
while ((c = originalStream.read(buffer)) != -1) {
classByteStream.write(buffer, 0, c);
}
byte[] originalData = classByteStream.toByteArray();
originalStream.close();
assertArrayEquals("Decrypted data incorrect", originalData, decryptedData.toByteArray());
}
/**
* Test using a certificate that lacks the Subject Key Identifier. We should have
* to compute it manually.
*/
@Test
public void testGetInputTransformNoCertSKI() throws Exception {
KeyStoreEncryptionFactory factory = new KeyStoreEncryptionFactory(keystore,
"masterkey2", keystorePassword.toCharArray(), provider);
assertEquals("Wrong master key", "masterkey2",
factory.getMasterEncryptionKeyAlias());
}
}