mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
o Alfresco key store manages keys and backup keys internally o moved key registration and checking into AlfrescoKeyStoreImpl o encryptor thread cache fix resulting from reload of key stores at runtime o more encryption and key store tests o tidy up + more comments o moved hard-coded values to properties file git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@30405 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
411 lines
14 KiB
Java
411 lines
14 KiB
Java
/*
|
|
* Copyright (C) 2005-2011 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.encryption;
|
|
|
|
import java.io.Serializable;
|
|
import java.security.AlgorithmParameters;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.Key;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.SecureRandom;
|
|
import java.security.spec.InvalidKeySpecException;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import javax.crypto.SecretKey;
|
|
import javax.crypto.SecretKeyFactory;
|
|
import javax.crypto.spec.DESedeKeySpec;
|
|
import javax.transaction.UserTransaction;
|
|
|
|
import junit.framework.TestCase;
|
|
|
|
import org.alfresco.error.AlfrescoRuntimeException;
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.repo.dictionary.DictionaryBootstrap;
|
|
import org.alfresco.repo.dictionary.DictionaryDAO;
|
|
import org.alfresco.repo.node.encryption.MetadataEncryptor;
|
|
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
|
import org.alfresco.repo.tenant.TenantService;
|
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
|
import org.alfresco.service.cmr.repository.NodeRef;
|
|
import org.alfresco.service.cmr.repository.NodeService;
|
|
import org.alfresco.service.cmr.repository.StoreRef;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.service.transaction.TransactionService;
|
|
import org.alfresco.util.ApplicationContextHelper;
|
|
import org.alfresco.util.Pair;
|
|
import org.springframework.context.ApplicationContext;
|
|
|
|
public class EncryptionTests extends TestCase
|
|
{
|
|
private static final String TEST_MODEL = "org/alfresco/encryption/reencryption_model.xml";
|
|
|
|
private static int NUM_PROPERTIES = 500;
|
|
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
|
|
|
private static QName NODE_TYPE = QName.createQName("http://www.alfresco.org/test/reencryption_test/1.0", "base");
|
|
private static QName PROP = QName.createQName("http://www.alfresco.org/test/reencryption_test/1.0", "prop1");
|
|
|
|
private NodeRef rootNodeRef;
|
|
|
|
private TransactionService transactionService;
|
|
private DictionaryService dictionaryService;
|
|
private NodeService nodeService;
|
|
private MetadataEncryptor metadataEncryptor;
|
|
private ReEncryptor reEncryptor;
|
|
private String cipherAlgorithm = "DESede/CBC/PKCS5Padding";
|
|
private KeyStoreParameters backupKeyStoreParameters;
|
|
private AlfrescoKeyStoreImpl mainKeyStore;
|
|
//private AlfrescoKeyStoreImpl backupKeyStore;
|
|
private KeyResourceLoader keyResourceLoader;
|
|
private EncryptionKeysRegistryImpl encryptionKeysRegistry;
|
|
private KeyStoreChecker keyStoreChecker;
|
|
private DefaultEncryptor mainEncryptor;
|
|
private DefaultEncryptor backupEncryptor;
|
|
|
|
private AuthenticationComponent authenticationComponent;
|
|
private DictionaryDAO dictionaryDAO;
|
|
private TenantService tenantService;
|
|
|
|
private String keyAlgorithm;
|
|
private KeyMap newKeys = new KeyMap();
|
|
private List<NodeRef> before = new ArrayList<NodeRef>();
|
|
private List<NodeRef> after = new ArrayList<NodeRef>();
|
|
|
|
private UserTransaction tx;
|
|
|
|
public void setUp() throws Exception
|
|
{
|
|
dictionaryService = (DictionaryService)ctx.getBean("dictionaryService");
|
|
nodeService = (NodeService)ctx.getBean("nodeService");
|
|
transactionService = (TransactionService)ctx.getBean("transactionService");
|
|
tenantService = (TenantService)ctx.getBean("tenantService");
|
|
dictionaryDAO = (DictionaryDAO)ctx.getBean("dictionaryDAO");
|
|
metadataEncryptor = (MetadataEncryptor)ctx.getBean("metadataEncryptor");
|
|
authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent");
|
|
keyResourceLoader = (KeyResourceLoader)ctx.getBean("springKeyResourceLoader");
|
|
reEncryptor = (ReEncryptor)ctx.getBean("reEncryptor");
|
|
backupKeyStoreParameters = (KeyStoreParameters)ctx.getBean("backupKeyStoreParameters");
|
|
keyStoreChecker = (KeyStoreChecker)ctx.getBean("keyStoreChecker");
|
|
encryptionKeysRegistry = (EncryptionKeysRegistryImpl)ctx.getBean("encryptionKeysRegistry");
|
|
//backupKeyStore = (AlfrescoKeyStoreImpl)ctx.getBean("backupKeyStore");
|
|
mainKeyStore = (AlfrescoKeyStoreImpl)ctx.getBean("keyStore");
|
|
mainEncryptor = (DefaultEncryptor)ctx.getBean("mainEncryptor");
|
|
backupEncryptor = (DefaultEncryptor)ctx.getBean("backupEncryptor");
|
|
|
|
// reencrypt in one txn (since we don't commit the model, the qnames won't be available across transactions)
|
|
reEncryptor.setSplitTxns(false);
|
|
|
|
this.authenticationComponent.setSystemUserAsCurrentUser();
|
|
|
|
tx = transactionService.getUserTransaction();
|
|
tx.begin();
|
|
|
|
StoreRef storeRef = nodeService.createStore(
|
|
StoreRef.PROTOCOL_WORKSPACE,
|
|
"ReEncryptor_" + System.currentTimeMillis());
|
|
rootNodeRef = nodeService.getRootNode(storeRef);
|
|
|
|
keyAlgorithm = "DESede";
|
|
newKeys.setKey(KeyProvider.ALIAS_METADATA, generateSecretKey(keyAlgorithm));
|
|
|
|
// Load models
|
|
DictionaryBootstrap bootstrap = new DictionaryBootstrap();
|
|
List<String> bootstrapModels = new ArrayList<String>();
|
|
bootstrapModels.add(TEST_MODEL);
|
|
// List<String> labels = new ArrayList<String>();
|
|
// labels.add(TEST_BUNDLE);
|
|
bootstrap.setModels(bootstrapModels);
|
|
// bootstrap.setLabels(labels);
|
|
bootstrap.setDictionaryDAO(dictionaryDAO);
|
|
bootstrap.setTenantService(tenantService);
|
|
bootstrap.bootstrap();
|
|
}
|
|
|
|
protected KeyProvider getKeyProvider(KeyStoreParameters keyStoreParameters)
|
|
{
|
|
KeyProvider backupKeyProvider = new KeystoreKeyProvider(keyStoreParameters, keyResourceLoader);
|
|
return backupKeyProvider;
|
|
}
|
|
|
|
public void setBackupKeyStoreParameters(KeyStoreParameters backupKeyStoreParameters)
|
|
{
|
|
this.backupKeyStoreParameters = backupKeyStoreParameters;
|
|
}
|
|
|
|
@Override
|
|
protected void tearDown() throws Exception
|
|
{
|
|
authenticationComponent.clearCurrentSecurityContext();
|
|
tx.rollback();
|
|
super.tearDown();
|
|
}
|
|
|
|
protected KeyProvider getKeyProvider(final KeyMap keys)
|
|
{
|
|
KeyProvider keyProvider = new KeyProvider()
|
|
{
|
|
@Override
|
|
public Key getKey(String keyAlias)
|
|
{
|
|
return keys.getCachedKey(keyAlias).getKey();
|
|
}
|
|
};
|
|
return keyProvider;
|
|
}
|
|
|
|
protected void createEncryptedProperties(List<NodeRef> nodes)
|
|
{
|
|
for(int i = 0; i < NUM_PROPERTIES; i++)
|
|
{
|
|
NodeRef nodeRef = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("assoc1"), NODE_TYPE).getChildRef();
|
|
nodes.add(nodeRef);
|
|
|
|
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
|
|
props.put(PROP, nodeRef.toString());
|
|
props = metadataEncryptor.encrypt(props);
|
|
nodeService.setProperties(nodeRef, props);
|
|
}
|
|
}
|
|
|
|
public byte[] generateKeyData() throws NoSuchAlgorithmException
|
|
{
|
|
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
|
random.setSeed(System.currentTimeMillis());
|
|
byte bytes[] = new byte[DESedeKeySpec.DES_EDE_KEY_LEN];
|
|
random.nextBytes(bytes);
|
|
return bytes;
|
|
}
|
|
|
|
protected Key generateSecretKey(String keyAlgorithm) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException
|
|
{
|
|
DESedeKeySpec keySpec = new DESedeKeySpec(generateKeyData());
|
|
SecretKeyFactory kf = SecretKeyFactory.getInstance(keyAlgorithm);
|
|
SecretKey secretKey = kf.generateSecret(keySpec);
|
|
return secretKey;
|
|
}
|
|
|
|
public void testReEncrypt()
|
|
{
|
|
KeyProvider backupKeyProvider = backupEncryptor.getKeyProvider();
|
|
KeyProvider mainKeyProvider = mainEncryptor.getKeyProvider();
|
|
try
|
|
{
|
|
// Create encrypted properties using the configured encryptor and key provider
|
|
createEncryptedProperties(before);
|
|
|
|
// Create encrypted properties using the new encryptor and key provider
|
|
KeyProvider newKeyProvider = getKeyProvider(newKeys);
|
|
|
|
// set backup encryptor key provider to main encryptor key provider and drop in
|
|
// new key provider for main encryptor
|
|
backupEncryptor.setKeyProvider(mainEncryptor.getKeyProvider());
|
|
mainEncryptor.setKeyProvider(newKeyProvider);
|
|
|
|
createEncryptedProperties(after);
|
|
|
|
// re-encrypt
|
|
long start = System.currentTimeMillis();
|
|
System.out.println(reEncryptor.reEncrypt() + " properties re-encrypted");
|
|
System.out.println("Re-encrypted " + NUM_PROPERTIES*2 + " properties in " + (System.currentTimeMillis() - start) + "ms");
|
|
|
|
// check that the nodes have been re-encrypted properly i.e. check that the properties
|
|
// decrypted using the new keys match the expected values.
|
|
for(NodeRef nodeRef : before)
|
|
{
|
|
Map<QName, Serializable> props = nodeService.getProperties(nodeRef);
|
|
props = metadataEncryptor.decrypt(props);
|
|
assertNotNull("", props.get(PROP));
|
|
assertEquals("", nodeRef.toString(), props.get(PROP));
|
|
}
|
|
|
|
for(NodeRef nodeRef : after)
|
|
{
|
|
Map<QName, Serializable> props = nodeService.getProperties(nodeRef);
|
|
props = metadataEncryptor.decrypt(props);
|
|
assertNotNull("", props.get(PROP));
|
|
assertEquals("", nodeRef.toString(), props.get(PROP));
|
|
}
|
|
}
|
|
catch(MissingKeyException e)
|
|
{
|
|
fail(e.getMessage());
|
|
}
|
|
catch(AlfrescoRuntimeException e)
|
|
{
|
|
if(e.getCause() instanceof InvalidKeyException)
|
|
{
|
|
e.printStackTrace();
|
|
fail();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
backupEncryptor.setKeyProvider(backupKeyProvider);
|
|
mainEncryptor.setKeyProvider(mainKeyProvider);
|
|
}
|
|
}
|
|
|
|
public void testBootstrapReEncrypt()
|
|
{
|
|
try
|
|
{
|
|
// ensure that the backup key store is not available
|
|
backupKeyStoreParameters.setLocation("");
|
|
//backupKeyStore.reload();
|
|
mainKeyStore.reload();
|
|
|
|
reEncryptor.bootstrapReEncrypt();
|
|
fail("Should have caught missing backup key store");
|
|
}
|
|
catch(MissingKeyException e)
|
|
{
|
|
System.out.println("Successfully caught missing key exception");
|
|
}
|
|
catch(InvalidKeystoreException e)
|
|
{
|
|
fail("Unexpected exception: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
protected void testChangeKeysImpl(boolean cacheCiphers) throws Throwable
|
|
{
|
|
// on a single thread
|
|
// create an encryptor, encrypt a string, change encryptor keys, decrypt -> should result in Invalid Key
|
|
|
|
Pair<byte[], AlgorithmParameters> pair = null;
|
|
DefaultEncryptor encryptor = null;
|
|
Key secretKey1 = null;
|
|
Key secretKey2 = null;
|
|
String test = "hello world";
|
|
final KeyMap keys = new KeyMap();
|
|
byte[] decrypted = null;
|
|
String test1 = null;
|
|
|
|
secretKey1 = generateSecretKey("DESede");
|
|
keys.setKey("test", secretKey1);
|
|
KeyProvider keyProvider = new KeyProvider()
|
|
{
|
|
@Override
|
|
public Key getKey(String keyAlias)
|
|
{
|
|
return keys.getCachedKey(keyAlias).getKey();
|
|
}
|
|
};
|
|
|
|
encryptor = new DefaultEncryptor();
|
|
encryptor.setCipherAlgorithm("DESede/CBC/PKCS5Padding");
|
|
encryptor.setCipherProvider(null);
|
|
encryptor.setKeyProvider(keyProvider);
|
|
encryptor.setCacheCiphers(cacheCiphers);
|
|
pair = encryptor.encrypt("test", null, test.getBytes("UTF-8"));
|
|
|
|
decrypted = encryptor.decrypt("test", pair.getSecond(), pair.getFirst());
|
|
test1 = new String(decrypted, "UTF-8");
|
|
|
|
assertEquals("Expected encrypt,decrypt to end up with the original value", test, test1);
|
|
System.out.println("1:" + new String(decrypted, "UTF-8"));
|
|
|
|
secretKey2 = generateSecretKey("DESede");
|
|
keys.setKey("test", secretKey2);
|
|
|
|
assertNotNull(encryptor);
|
|
assertNotNull(pair);
|
|
|
|
try
|
|
{
|
|
decrypted = encryptor.decrypt("test", pair.getSecond(), pair.getFirst());
|
|
test1 = new String(decrypted, "UTF-8");
|
|
}
|
|
catch(AlfrescoRuntimeException e)
|
|
{
|
|
// ok - decryption failed expected.
|
|
}
|
|
}
|
|
|
|
public void testChangeKeys() throws Throwable
|
|
{
|
|
testChangeKeysImpl(false);
|
|
}
|
|
|
|
public void testChangeKeysCachedCiphers() throws Throwable
|
|
{
|
|
testChangeKeysImpl(true);
|
|
}
|
|
|
|
public void testFailedEncryptionWithCachedCiphers() throws Throwable
|
|
{
|
|
Pair<byte[], AlgorithmParameters> pair = null;
|
|
DefaultEncryptor encryptor = null;
|
|
Key secretKey1 = null;
|
|
Key secretKey2 = null;
|
|
String test = "hello world";
|
|
final KeyMap keys = new KeyMap();
|
|
byte[] decrypted = null;
|
|
String test1 = null;
|
|
|
|
secretKey1 = generateSecretKey("DESede");
|
|
keys.setKey("test", secretKey1);
|
|
KeyProvider keyProvider = new KeyProvider()
|
|
{
|
|
@Override
|
|
public Key getKey(String keyAlias)
|
|
{
|
|
return keys.getCachedKey(keyAlias).getKey();
|
|
}
|
|
};
|
|
|
|
encryptor = new DefaultEncryptor();
|
|
encryptor.setCipherAlgorithm("DESede/CBC/PKCS5Padding");
|
|
encryptor.setCipherProvider(null);
|
|
encryptor.setKeyProvider(keyProvider);
|
|
encryptor.setCacheCiphers(true);
|
|
pair = encryptor.encrypt("test", null, test.getBytes("UTF-8"));
|
|
|
|
secretKey2 = generateSecretKey("DESede");
|
|
keys.setKey("test", secretKey2);
|
|
|
|
assertNotNull(encryptor);
|
|
assertNotNull(pair);
|
|
|
|
try
|
|
{
|
|
decrypted = encryptor.decrypt("test", pair.getSecond(), pair.getFirst());
|
|
test1 = new String(decrypted, "UTF-8");
|
|
fail("Decryption should have failed");
|
|
}
|
|
catch(AlfrescoRuntimeException e)
|
|
{
|
|
// ok - decryption failed expected - changed key
|
|
}
|
|
|
|
keys.setKey("test", secretKey1);
|
|
try
|
|
{
|
|
decrypted = encryptor.decrypt("test", pair.getSecond(), pair.getFirst());
|
|
test1 = new String(decrypted, "UTF-8");
|
|
}
|
|
catch(AlfrescoRuntimeException e)
|
|
{
|
|
fail("Expected decryption to work ok");
|
|
}
|
|
}
|
|
} |