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.InputStream;
import java.io.OutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
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 BasicEncryptionTransformFactoryTest {
private static final Logger logger = LoggerFactory
.getLogger(BasicEncryptionTransformFactoryTest.class);
private Properties keyprops;
private KeyPair masterKey;
private KeyPair oldKey;
protected Provider provider;
@Before
public void setUp() throws Exception {
// Load some keys.
keyprops = new Properties();
keyprops.load(this.getClass().getClassLoader()
.getResourceAsStream("keys.properties"));
masterKey = KeyUtils.rsaKeyPairFromBase64(
keyprops.getProperty("masterkey.public"),
keyprops.getProperty("masterkey.private"));
oldKey = KeyUtils.rsaKeyPairFromBase64(
keyprops.getProperty("oldkey.public"),
keyprops.getProperty("oldkey.private"));
}
@Test
public void testRejectSmallKey() throws Exception {
// An RSA key < 1024 bits should be rejected as a master key.
BasicEncryptionTransformFactory factory = new BasicEncryptionTransformFactory();
factory.setCryptoProvider(provider);
KeyPair smallKey = null;
try {
smallKey = KeyUtils.rsaKeyPairFromBase64(
keyprops.getProperty("smallkey.public"),
keyprops.getProperty("smallkey.private"));
} catch (Exception e) {
// Good!
logger.info("Key was properly rejected by JVM: " + e);
return;
}
try {
factory.setMasterEncryptionKey(smallKey);
} catch (Exception e) {
// Good!
logger.info("Key was properly rejected by factory: " + e);
return;
}
fail("RSA key < 1024 bits should have been rejected by factory");
}
@Test
public void testSetMasterEncryptionKey() throws Exception {
BasicEncryptionTransformFactory factory = new BasicEncryptionTransformFactory();
factory.setCryptoProvider(provider);
factory.setMasterEncryptionKey(masterKey);
}
@Test
public void testAddMasterDecryptionKey() throws Exception {
BasicEncryptionTransformFactory factory = new BasicEncryptionTransformFactory();
factory.setCryptoProvider(provider);
factory.setMasterEncryptionKey(masterKey);
factory.addMasterDecryptionKey(oldKey);
}
@Test
public void testGetOutputTransformPush() throws Exception {
BasicEncryptionTransformFactory factory = new BasicEncryptionTransformFactory();
factory.setCryptoProvider(provider);
factory.setMasterEncryptionKey(masterKey);
factory.addMasterDecryptionKey(oldKey);
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",
KeyUtils.getRsaPublicKeyFingerprint((RSAPublicKey) masterKey
.getPublic(), provider),
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 testGetOutputTransformPull() throws Exception {
// 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();
BasicEncryptionTransformFactory factory = new BasicEncryptionTransformFactory();
factory.setCryptoProvider(provider);
factory.setMasterEncryptionKey(masterKey);
factory.addMasterDecryptionKey(oldKey);
ByteArrayInputStream in = new ByteArrayInputStream(uncompressedData);
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("name1", "value1");
metadata.put("name2", "value2");
BasicEncryptionOutputTransform outTransform = factory
.getOutputTransform(in, metadata);
InputStream encryptedStream = outTransform.getEncodedInputStream();
while((c = encryptedStream.read(buffer)) != -1) {
// discard
}
// 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",
KeyUtils.getRsaPublicKeyFingerprint((RSAPublicKey) masterKey
.getPublic(), provider),
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", "holbhxpDq92g0dPRuqmtAt23KaNSm3JjKULdzadKdJyn3SINDSbaHnjmDU/Aa5pNSmq8ij+RWdkBlsrk9g6m/tjQm1gMPbeW+IhWJhInL0Mvsa+olZ+cLkztnKz/yHQ6vj9R0m8OboATweV1TTkRx9RFrr2nEBY7jKHNxd8JOJ/1I3gteuhsLKKE9oF2uS0UTVnCTZ3S6tf0W4P9D0PTW9LXQaA3KkFD+4tEbqZfC4ov88CLRAL72YC6KCF1LDZdqGzvqKf2j+92xIiy99+5LatVJPUebVucM8Equ6lAcETjNEsIwLPSNz2P9/saYI8XdLyZQDsOMg/32BbtVCVs7g==");
objectMetadata.put("x-emc-enc-key-id", "000317457b5645b7b5c4daf4cf6780c05438effd");
objectMetadata.put("x-emc-enc-unencrypted-size", "2516125");
objectMetadata.put("x-emc-enc-unencrypted-sha1", "027e997e6b1dfc97b93eb28dc9a6804096d85873");
objectMetadata.put("x-emc-enc-metadata-signature", "G98fTE0w8HzdzbqXv5x5AKl2/MudwrEIJ3nZciI1H9HQKg+i3Jmvi+/miEOeyMv8+lOVDj6CiBUMBpUsqrx46NODC+0MiA3L2JotW2DUJqfvfaTKtgbbFdSUYHshDDw3zZXcULX/flk5vjTYTICBSjedn9tg+VTA24ivk4IPexmPR7BKN+UmRZv6nPuvV1soGWg69K+5qv47lQf2rC4yO7FUXRJA10+nES1/8UmB3NylCwgI/a7UKu00o8kYABqgzVNbWgB4GjCqOtNcWJGSz8Xku3nWySetFLVs0wcwioZ3KyHIf+6p4XzbHx1ie4t9fhZuAYoOTPqDTu0o0QB/pg==");
objectMetadata.put("x-emc-iv", "omQ2kgZauWkK58/m+Eichg==");
objectMetadata.put("name1", "value1");
objectMetadata.put("name2", "value2");
InputStream encryptedObject = this.getClass().getClassLoader()
.getResourceAsStream("encrypted.txt.aes128");
BasicEncryptionTransformFactory factory = new BasicEncryptionTransformFactory();
factory.setCryptoProvider(provider);
factory.setMasterEncryptionKey(masterKey);
// 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 the rejection of a master KeyPair that's not an RSA key (e.g. a DSA
* key)
*/
@Test
public void testRejectNonRsaMasterKey() throws Exception {
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("DSA");
keyGenerator.initialize(512, new SecureRandom());
KeyPair myKeyPair = keyGenerator.generateKeyPair();
BasicEncryptionTransformFactory factory = new BasicEncryptionTransformFactory();
factory.setCryptoProvider(provider);
try {
factory.setMasterEncryptionKey(myKeyPair);
} catch (Exception e) {
// Good!
logger.info("DSA key was properly rejected by factory: " + e);
return;
}
fail("DSA keys should not be allowed.");
}
/**
* Test encrypting with one key, changing the master encryption key, then
* decrypting. The old key should be found and used as the decryption key.
*/
@Test
public void testRekey() throws Exception {
BasicEncryptionTransformFactory factory = new BasicEncryptionTransformFactory();
factory.setCryptoProvider(provider);
factory.setMasterEncryptionKey(oldKey);
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.setMasterEncryptionKey(masterKey);
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 encrypting with one key, changing the master encryption key, then
* decrypting. The old key should be found and used as the decryption key.
*/
@Test
public void testRekey256() throws Exception {
BasicEncryptionTransformFactory factory = new BasicEncryptionTransformFactory();
factory.setCryptoProvider(provider);
factory.setMasterEncryptionKey(oldKey);
Assume.assumeTrue("256-bit AES is not supported",
BasicEncryptionTransformFactory.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.setMasterEncryptionKey(masterKey);
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);
}
}