diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml
index 1161671c92..0553bef9dc 100644
--- a/config/alfresco/bootstrap-context.xml
+++ b/config/alfresco/bootstrap-context.xml
@@ -169,8 +169,7 @@
-
-
+
diff --git a/config/alfresco/encryption-context.xml b/config/alfresco/encryption-context.xml
index 219741adec..cc79bf6580 100644
--- a/config/alfresco/encryption-context.xml
+++ b/config/alfresco/encryption-context.xml
@@ -11,8 +11,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -20,7 +37,7 @@
-
+
@@ -36,45 +53,24 @@
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -82,38 +78,39 @@
-
-
+
+
+
+
+
+
+
+
+
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml
index b2a06e01ca..c311d4d8e6 100644
--- a/config/alfresco/node-services-context.xml
+++ b/config/alfresco/node-services-context.xml
@@ -247,13 +247,16 @@
-
+
+
+
diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties
index a713b1eaba..2bde7abfe0 100644
--- a/config/alfresco/repository.properties
+++ b/config/alfresco/repository.properties
@@ -731,7 +731,9 @@ encryption.ssl.truststore.provider=
encryption.ssl.truststore.type=JCEKS
encryption.ssl.truststore.keyMetaData.location=${dir.keystore}/ssl-truststore-passwords.properties
-encryption.reencryptor.chunkSize=50
+# Re-encryptor properties
+encryption.reencryptor.chunkSize=100
+encryption.reencryptor.numThreads=2
# SOLR connection details (e.g. for JMX)
solr.host=localhost
diff --git a/source/java/org/alfresco/encryption/BootstrapReEncryptor.java b/source/java/org/alfresco/encryption/BootstrapReEncryptor.java
index 94efad30e7..c5fa98e691 100644
--- a/source/java/org/alfresco/encryption/BootstrapReEncryptor.java
+++ b/source/java/org/alfresco/encryption/BootstrapReEncryptor.java
@@ -52,7 +52,7 @@ public class BootstrapReEncryptor extends AbstractLifecycleBean
{
return reEncryptor.bootstrapReEncrypt();
}
- catch(MissingKeyStoreException e)
+ catch(MissingKeyException e)
{
throw new AlfrescoRuntimeException("Bootstrap re-encryption failed", e);
}
diff --git a/source/java/org/alfresco/encryption/EncryptionChecker.java b/source/java/org/alfresco/encryption/EncryptionChecker.java
index adf2debe27..046927124e 100644
--- a/source/java/org/alfresco/encryption/EncryptionChecker.java
+++ b/source/java/org/alfresco/encryption/EncryptionChecker.java
@@ -18,6 +18,9 @@
*/
package org.alfresco.encryption;
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.transaction.TransactionService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
@@ -37,31 +40,39 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
public class EncryptionChecker extends AbstractLifecycleBean
{
private static Log logger = LogFactory. getLog(EncryptionChecker.class);
-
+ private TransactionService transactionService;
private KeyStoreChecker keyStoreChecker;
- private KeyStoreParameters keyStoreParameters;
- private KeyResourceLoader keyResourceLoader;
-
- public void setkeyStoreParameters(KeyStoreParameters keyStoreParameters)
- {
- this.keyStoreParameters = keyStoreParameters;
- }
public void setKeyStoreChecker(KeyStoreChecker keyStoreChecker)
{
this.keyStoreChecker = keyStoreChecker;
}
- public void setKeyResourceLoader(KeyResourceLoader keyResourceLoader)
+ public void setTransactionService(TransactionService transactionService)
{
- this.keyResourceLoader = keyResourceLoader;
+ this.transactionService = transactionService;
}
@Override
protected void onBootstrap(ApplicationEvent event)
{
- AlfrescoKeyStore mainKeyStore = new AlfrescoKeyStoreImpl(keyStoreParameters, keyResourceLoader);
- keyStoreChecker.checkKeyStore(mainKeyStore);
+ transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ try
+ {
+ keyStoreChecker.validateKeyStores();
+ }
+ catch(Throwable e)
+ {
+ // Just throw as a runtime exception
+ throw new AlfrescoRuntimeException("Keystores are invalid", e);
+ }
+
+ return null;
+ }
+ });
}
@Override
diff --git a/source/java/org/alfresco/encryption/EncryptionKeysRegistryImpl.java b/source/java/org/alfresco/encryption/EncryptionKeysRegistryImpl.java
index d4e9847d85..45f2fcb1e1 100644
--- a/source/java/org/alfresco/encryption/EncryptionKeysRegistryImpl.java
+++ b/source/java/org/alfresco/encryption/EncryptionKeysRegistryImpl.java
@@ -20,8 +20,10 @@ package org.alfresco.encryption;
import java.io.Serializable;
import java.security.InvalidKeyException;
+import java.security.Key;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
@@ -34,6 +36,8 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
+ * Registered Encryption Keys are stored in the AttributeService directly under a top level key defined by
+ * TOP_LEVEL_KEY (which means that all key aliases must be unique across however many keystores are being used).
*
* @since 4.0
*
@@ -46,16 +50,22 @@ public class EncryptionKeysRegistryImpl implements EncryptionKeysRegistry
private TransactionService transactionService;
private AttributeService attributeService;
- private Encryptor encryptor;
+ private String cipherAlgorithm;
+ private String cipherProvider;
public void setAttributeService(AttributeService attributeService)
{
this.attributeService = attributeService;
}
- public void setEncryptor(Encryptor encryptor)
+ public void setCipherAlgorithm(String cipherAlgorithm)
{
- this.encryptor = encryptor;
+ this.cipherAlgorithm = cipherAlgorithm;
+ }
+
+ public void setCipherProvider(String cipherProvider)
+ {
+ this.cipherProvider = cipherProvider;
}
public void setTransactionService(TransactionService transactionService)
@@ -63,55 +73,87 @@ public class EncryptionKeysRegistryImpl implements EncryptionKeysRegistry
this.transactionService = transactionService;
}
- public void registerKey(String keyAlias)
+ protected Encryptor getEncryptor(final KeyMap keys)
{
+ DefaultEncryptor encryptor = new DefaultEncryptor();
+ encryptor.setCipherAlgorithm(cipherAlgorithm);
+ encryptor.setCipherProvider(cipherProvider);
+ encryptor.setKeyProvider(new KeyProvider()
+ {
+ @Override
+ public Key getKey(String keyAlias)
+ {
+ return keys.getCachedKey(keyAlias).getKey();
+ }
+ });
+ return encryptor;
+ }
+
+ public void init()
+ {
+ }
+
+ public void registerKey(String keyAlias, Key key)
+ {
+ if(isKeyRegistered(keyAlias))
+ {
+ throw new IllegalArgumentException("Key " + keyAlias + " is already registered");
+ }
+
// register the key by creating an attribute that stores a guid and its encrypted value
String guid = GUID.generate();
+
+ KeyMap keys = new KeyMap();
+ keys.setKey(keyAlias, key);
+ Encryptor encryptor = getEncryptor(keys);
Serializable encrypted = encryptor.sealObject(keyAlias, null, guid);
KeyCheck keyCheck = new KeyCheck(guid, encrypted);
attributeService.createAttribute(keyCheck, TOP_LEVEL_KEY, keyAlias);
logger.info("Registered key " + keyAlias);
}
- public KeysReport getKeysReport()
+ public void unregisterKey(String keyAlias)
+ {
+ attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias);
+ }
+
+ public boolean isKeyRegistered(String keyAlias)
+ {
+ return (attributeService.getAttribute(TOP_LEVEL_KEY, keyAlias) != null);
+ }
+
+ public List getRegisteredKeys(final Set keyStoreKeys)
{
final List registeredKeys = new ArrayList();
- if(attributeService.exists(TOP_LEVEL_KEY))
+ attributeService.getAttributes(new AttributeQueryCallback()
{
- attributeService.getAttributes(new AttributeQueryCallback()
+ public boolean handleAttribute(Long id, Serializable value,
+ Serializable[] keys)
{
- public boolean handleAttribute(Long id, Serializable value,
- Serializable[] keys)
+ if(value instanceof KeyCheck)
{
- registeredKeys.add((String)value);
- return true;
+ // Add as a registered key if the keystore contains the key
+ String keyAlias = (String)keys[1];
+ if(keyStoreKeys.contains(keyAlias))
+ {
+ registeredKeys.add(keyAlias);
+ }
}
-
- },
- TOP_LEVEL_KEY);
- }
-
- List keyAliasesChanged = new ArrayList();
- List keyAliasesUnchanged = new ArrayList();
-
- for(String keyAlias : registeredKeys)
- {
- KEY_STATUS keyStatus = checkKey(keyAlias);
- if(keyStatus == KEY_STATUS.CHANGED)
- {
- keyAliasesChanged.add(keyAlias);
+ else
+ {
+ logger.warn("Unexpected value class in keys registry: " + value.getClass());
+ }
+ return true;
}
- else
- {
- keyAliasesUnchanged.add(keyAlias);
- }
- }
- return new KeysReport(keyAliasesChanged, keyAliasesUnchanged);
+ },
+ TOP_LEVEL_KEY);
+
+ return registeredKeys;
}
-
- public KEY_STATUS checkKey(String keyAlias)
+
+ public KEY_STATUS checkKey(String keyAlias, Key key)
{
if(attributeService.exists(TOP_LEVEL_KEY, keyAlias))
{
@@ -120,6 +162,10 @@ public class EncryptionKeysRegistryImpl implements EncryptionKeysRegistry
// check that the key has not changed by decrypting the encrypted guid attribute
// comparing against the guid
KeyCheck keyCheck = (KeyCheck)attributeService.getAttribute(TOP_LEVEL_KEY, keyAlias);
+
+ KeyMap keys = new KeyMap();
+ keys.setKey(keyAlias, key);
+ Encryptor encryptor = getEncryptor(keys);
Serializable storedGUID = encryptor.unsealObject(keyAlias, keyCheck.getEncrypted());
return EqualsHelper.nullSafeEquals(storedGUID, keyCheck.getGuid()) ? KEY_STATUS.OK : KEY_STATUS.CHANGED;
}
@@ -137,14 +183,14 @@ public class EncryptionKeysRegistryImpl implements EncryptionKeysRegistry
}
// note that this removes _all_ keys in the keystore. Use with care.
- public void removeRegisteredKeys(final AlfrescoKeyStore keyStore)
+ public void removeRegisteredKeys(final Set keys)
{
RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper();
final RetryingTransactionCallback removeKeysCallback = new RetryingTransactionCallback()
{
public Void execute() throws Throwable
{
- for(String keyAlias : keyStore.getKeyAliases())
+ for(String keyAlias : keys)
{
attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias);
}
@@ -161,7 +207,7 @@ public class EncryptionKeysRegistryImpl implements EncryptionKeysRegistry
* @since 4.0
*
*/
- private static class KeyCheck implements Serializable
+ public static class KeyCheck implements Serializable
{
private static final long serialVersionUID = 4514315444977162903L;
@@ -201,4 +247,5 @@ public class EncryptionKeysRegistryImpl implements EncryptionKeysRegistry
EqualsHelper.nullSafeEquals(keyCheck.getEncrypted(), getEncrypted());
}
}
+
}
diff --git a/source/java/org/alfresco/encryption/EncryptionTests.java b/source/java/org/alfresco/encryption/EncryptionTests.java
index 38a28edf49..f92270ef19 100644
--- a/source/java/org/alfresco/encryption/EncryptionTests.java
+++ b/source/java/org/alfresco/encryption/EncryptionTests.java
@@ -18,8 +18,8 @@
*/
package org.alfresco.encryption;
-import java.io.File;
import java.io.Serializable;
+import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
@@ -51,6 +51,7 @@ 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
@@ -72,7 +73,8 @@ public class EncryptionTests extends TestCase
private ReEncryptor reEncryptor;
private String cipherAlgorithm = "DESede/CBC/PKCS5Padding";
private KeyStoreParameters backupKeyStoreParameters;
- private AlfrescoKeyStoreImpl backupKeyStore;
+ private AlfrescoKeyStoreImpl mainKeyStore;
+ //private AlfrescoKeyStoreImpl backupKeyStore;
private KeyResourceLoader keyResourceLoader;
private EncryptionKeysRegistryImpl encryptionKeysRegistry;
private KeyStoreChecker keyStoreChecker;
@@ -84,7 +86,7 @@ public class EncryptionTests extends TestCase
private TenantService tenantService;
private String keyAlgorithm;
- private Map newKeys = new HashMap();
+ private KeyMap newKeys = new KeyMap();
private List before = new ArrayList();
private List after = new ArrayList();
@@ -104,8 +106,9 @@ public class EncryptionTests extends TestCase
backupKeyStoreParameters = (KeyStoreParameters)ctx.getBean("backupKeyStoreParameters");
keyStoreChecker = (KeyStoreChecker)ctx.getBean("keyStoreChecker");
encryptionKeysRegistry = (EncryptionKeysRegistryImpl)ctx.getBean("encryptionKeysRegistry");
- backupKeyStore = (AlfrescoKeyStoreImpl)ctx.getBean("backupKeyStore");
- mainEncryptor = (DefaultEncryptor)ctx.getBean("encryptor");
+ //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)
@@ -122,8 +125,8 @@ public class EncryptionTests extends TestCase
rootNodeRef = nodeService.getRootNode(storeRef);
keyAlgorithm = "DESede";
- newKeys.put(KeyProvider.ALIAS_METADATA, generateSecretKey(keyAlgorithm));
-
+ newKeys.setKey(KeyProvider.ALIAS_METADATA, generateSecretKey(keyAlgorithm));
+
// Load models
DictionaryBootstrap bootstrap = new DictionaryBootstrap();
List bootstrapModels = new ArrayList();
@@ -156,46 +159,19 @@ public class EncryptionTests extends TestCase
super.tearDown();
}
- protected KeyProvider getKeyProvider(final Map keys)
+ protected KeyProvider getKeyProvider(final KeyMap keys)
{
KeyProvider keyProvider = new KeyProvider()
{
@Override
public Key getKey(String keyAlias)
{
- return keys.get(keyAlias);
- }
-
- @Override
- public void refresh()
- {
- // nothing to do
+ return keys.getCachedKey(keyAlias).getKey();
}
};
return keyProvider;
}
- protected Encryptor getFallbackEncryptor(KeyProvider keyProvider)
- {
- DefaultEncryptor encryptor = new DefaultEncryptor();
- encryptor.setCipherAlgorithm(cipherAlgorithm);
- encryptor.setCipherProvider(null);
- encryptor.setKeyProvider(keyProvider);
-
- DefaultFallbackEncryptor fallbackEncryptor = new DefaultFallbackEncryptor(encryptor, mainEncryptor);
-
- return fallbackEncryptor;
- }
-
- protected MetadataEncryptor getMetadataEncryptor(Encryptor encryptor)
- {
- MetadataEncryptor metadataEncryptor = new MetadataEncryptor();
- metadataEncryptor.setDictionaryService(dictionaryService);
- metadataEncryptor.setEncryptor(encryptor);
-
- return metadataEncryptor;
- }
-
protected void createEncryptedProperties(List nodes)
{
for(int i = 0; i < NUM_PROPERTIES; i++)
@@ -269,7 +245,7 @@ public class EncryptionTests extends TestCase
assertEquals("", nodeRef.toString(), props.get(PROP));
}
}
- catch(MissingKeyStoreException e)
+ catch(MissingKeyException e)
{
fail(e.getMessage());
}
@@ -294,36 +270,142 @@ public class EncryptionTests extends TestCase
{
// ensure that the backup key store is not available
backupKeyStoreParameters.setLocation("");
- backupKeyStore.reload();
+ //backupKeyStore.reload();
+ mainKeyStore.reload();
reEncryptor.bootstrapReEncrypt();
fail("Should have caught missing backup key store");
}
- catch(MissingKeyStoreException e)
+ catch(MissingKeyException e)
{
- System.out.println("Successfully caught missing key store exception");
+ 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 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 testKeyStoreCreation()
+ public void testChangeKeys() throws Throwable
{
- String keyStoreLocation = System.getProperty("user.dir") + File.separator + "encryption-tests.keystore";
- File keyStoreFile = new File(keyStoreLocation);
- if(keyStoreFile.exists())
+ testChangeKeysImpl(false);
+ }
+
+ public void testChangeKeysCachedCiphers() throws Throwable
+ {
+ testChangeKeysImpl(true);
+ }
+
+ public void testFailedEncryptionWithCachedCiphers() throws Throwable
+ {
+ Pair 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()
{
- assertTrue("", keyStoreFile.delete());
+ @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
}
- KeyStoreParameters keyStoreParameters = new KeyStoreParameters();
- keyStoreParameters.setLocation(keyStoreLocation);
- keyStoreParameters.setKeyMetaDataFileLocation("classpath:org/alfresco/encryption/keystore-parameters.properties");
- keyStoreParameters.setType("JCEKS");
- AlfrescoKeyStore keyStore = new AlfrescoKeyStoreImpl(keyStoreParameters, keyResourceLoader);
-
- encryptionKeysRegistry.removeRegisteredKeys(keyStore);
-
- keyStoreChecker.checkKeyStore(keyStore);
-
- assertNotNull("", keyStore.getKey("test"));
+ 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");
+ }
}
}
\ No newline at end of file
diff --git a/source/java/org/alfresco/encryption/EncryptorTest.java b/source/java/org/alfresco/encryption/EncryptorTest.java
index d7d2043a6d..1fe52d2a7e 100644
--- a/source/java/org/alfresco/encryption/EncryptorTest.java
+++ b/source/java/org/alfresco/encryption/EncryptorTest.java
@@ -85,15 +85,14 @@ public class EncryptorTest extends TestCase
Serializable testObject = " This is a string, but will be serialized ";
Serializable sealedObject = encryptor.sealObject("mykey2", null, testObject);
-// try
-// {
+ try
+ {
Object output = encryptor.unsealObject("mykey2", sealedObject);
assertEquals("Encryption round trip failed. ", testObject, output);
-// }
-// catch(KeyException e)
-// {
-// throw new AlfrescoRuntimeException("", e)
-//; }
-
+ }
+ catch(KeyException e)
+ {
+ throw new AlfrescoRuntimeException("", e);
+ }
}
}
diff --git a/source/java/org/alfresco/encryption/KeyStoreChecker.java b/source/java/org/alfresco/encryption/KeyStoreChecker.java
index b18b86e60d..873634a6a2 100644
--- a/source/java/org/alfresco/encryption/KeyStoreChecker.java
+++ b/source/java/org/alfresco/encryption/KeyStoreChecker.java
@@ -18,18 +18,11 @@
*/
package org.alfresco.encryption;
-import org.alfresco.error.AlfrescoRuntimeException;
-import org.alfresco.repo.transaction.RetryingTransactionHelper;
-import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
-import org.alfresco.service.transaction.TransactionService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
- * Validates an Alfresco keystore by ensuring that it's registered keys have not changed.
- *
- * The keys are registered using the AttributeService and are stored in one level under TOP_LEVEL_KEY
- * (so key aliases must be unique across however many keystores are being used).
+ * Checks the repository key stores.
*
* @since 4.0
*
@@ -37,80 +30,24 @@ import org.apache.commons.logging.LogFactory;
public class KeyStoreChecker
{
private static final Log logger = LogFactory.getLog(KeyStoreChecker.class);
-
- private TransactionService transactionService;
- private EncryptionKeysRegistryImpl encryptionKeysRegistry;
+
+ private AlfrescoKeyStore mainKeyStore;
public KeyStoreChecker()
{
}
-
- public void setEncryptionKeysRegistry(EncryptionKeysRegistryImpl encryptionKeysRegistry)
+
+ public void setMainKeyStore(AlfrescoKeyStore mainKeyStore)
{
- this.encryptionKeysRegistry = encryptionKeysRegistry;
+ this.mainKeyStore = mainKeyStore;
}
- public void setTransactionService(TransactionService transactionService)
+ public void validateKeyStores() throws InvalidKeystoreException, MissingKeyException
{
- this.transactionService = transactionService;
- }
-
- public KeyStoreChecker(EncryptionKeysRegistryImpl encryptionKeysRegistry)
- {
- this.encryptionKeysRegistry = encryptionKeysRegistry;
- }
-
- protected void createKeyStore(AlfrescoKeyStore keyStore)
- {
- keyStore.create();
-
- // Register the key store keys
- for(String keyAlias : keyStore.getKeyAliases())
+ mainKeyStore.validateKeys();
+ if(!mainKeyStore.exists())
{
- encryptionKeysRegistry.registerKey(keyAlias);
+ mainKeyStore.create();
}
}
-
- public void checkKeyStore(final AlfrescoKeyStore keyStore)
- {
- // TODO check that, if keystore exists, keys are registered
- RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper();
- final RetryingTransactionCallback checkKeysCallback = new RetryingTransactionCallback()
- {
- public Void execute() throws Throwable
- {
- KeysReport keysReport = encryptionKeysRegistry.getKeysReport();
-
- // Check for the existence of a key store first
- if(keyStore.exists())
- {
- // The keystore exists - check whether any keys have been changed
- // find out which registered keys have changed
- if(keysReport.getKeysChanged().size() > 0)
- {
- // Note: this will halt the application bootstrap.
- throw new AlfrescoRuntimeException("The keys with aliases " + keysReport.getKeysChanged() + " have been changed, re-instate the previous keystore");
- }
- }
- else
- {
- // keystore not found, check whether any keys have been registered
- if(keysReport.getKeysChanged().size() + keysReport.getKeysUnchanged().size() > 0)
- {
- // Note: this will halt the application bootstrap.
- throw new AlfrescoRuntimeException("Keys have already been registered, re-instate the previous keystore");
- }
-
- if(logger.isDebugEnabled())
- {
- logger.debug("Keystore not found, creating...");
- }
- createKeyStore(keyStore);
- }
-
- return null;
- }
- };
- retryingTransactionHelper.doInTransaction(checkKeysCallback, false);
- }
}
diff --git a/source/java/org/alfresco/encryption/KeyStoreTests.java b/source/java/org/alfresco/encryption/KeyStoreTests.java
new file mode 100644
index 0000000000..760e58a313
--- /dev/null
+++ b/source/java/org/alfresco/encryption/KeyStoreTests.java
@@ -0,0 +1,419 @@
+/*
+ * 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 .
+ */
+package org.alfresco.encryption;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESedeKeySpec;
+import javax.transaction.NotSupportedException;
+import javax.transaction.SystemException;
+import javax.transaction.UserTransaction;
+
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.alfresco.util.GUID;
+import org.apache.commons.codec.binary.Base64;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+
+/**
+ *
+ * @since 4.0
+ *
+ */
+public class KeyStoreTests
+{
+ private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
+
+ private TransactionService transactionService;
+ private KeyStoreChecker keyStoreChecker;
+ private EncryptionKeysRegistry encryptionKeysRegistry;
+ private UserTransaction txn = null;
+ private KeyResourceLoader keyResourceLoader;
+ private List toDelete;
+ private DefaultEncryptor backupEncryptor;
+
+ @Before
+ public void setup() throws SystemException, NotSupportedException
+ {
+ transactionService = (TransactionService)ctx.getBean("transactionService");
+ keyStoreChecker = (KeyStoreChecker)ctx.getBean("keyStoreChecker");
+ encryptionKeysRegistry = (EncryptionKeysRegistry)ctx.getBean("encryptionKeysRegistry");
+ keyResourceLoader = (KeyResourceLoader)ctx.getBean("springKeyResourceLoader");
+ backupEncryptor = (DefaultEncryptor)ctx.getBean("backupEncryptor");
+
+ toDelete = new ArrayList(10);
+
+ AuthenticationUtil.setRunAsUserSystem();
+ UserTransaction txn = transactionService.getUserTransaction();
+ txn.begin();
+ }
+
+ @After
+ public void teardown() throws IllegalStateException, SecurityException, SystemException
+ {
+ if(txn != null)
+ {
+ txn.rollback();
+ }
+
+ for(String guid : toDelete)
+ {
+ File file = new File(guid);
+ if(file.exists())
+ {
+ file.delete();
+ }
+ }
+ }
+
+ public String generateEncodedKey()
+ {
+ try
+ {
+ return Base64.encodeBase64String(generateKeyData());
+ }
+ catch(Throwable e)
+ {
+ fail("Unexpected exception: " + e.getMessage());
+ return null;
+ }
+ }
+
+ 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 String generateKeystoreName()
+ {
+ String guid = GUID.generate();
+ toDelete.add(guid);
+ return guid;
+ }
+
+ protected Key generateSecretKey(String keyAlgorithm)
+ {
+ try
+ {
+ DESedeKeySpec keySpec = new DESedeKeySpec(generateKeyData());
+ SecretKeyFactory kf = SecretKeyFactory.getInstance(keyAlgorithm);
+ SecretKey secretKey = kf.generateSecret(keySpec);
+ return secretKey;
+ }
+ catch(Throwable e)
+ {
+ fail("Unexpected exception: " + e.getMessage());
+ return null;
+ }
+ }
+
+ protected TestAlfrescoKeyStore getKeyStore(String name, String type, final Map passwords, final Map encodedKeyData,
+ String keyStoreLocation, String backupKeyStoreLocation)
+ {
+ KeyResourceLoader testKeyResourceLoader = new KeyResourceLoader()
+ {
+ @Override
+ public InputStream getKeyStore(String keyStoreLocation)
+ throws FileNotFoundException
+ {
+ return keyResourceLoader.getKeyStore(keyStoreLocation);
+ }
+
+ @Override
+ public Properties loadKeyMetaData(String keyMetaDataFileLocation)
+ throws IOException, FileNotFoundException
+ {
+ Properties p = new Properties();
+ p.put("keystore.password", "password");
+ StringBuilder aliases = new StringBuilder();
+ for(String keyAlias : passwords.keySet())
+ {
+ p.put(keyAlias + ".password", passwords.get(keyAlias));
+ if(encodedKeyData != null && encodedKeyData.get(keyAlias) != null)
+ {
+ p.put(keyAlias + ".keyData", encodedKeyData.get(keyAlias));
+ }
+ p.put(keyAlias + ".algorithm", "DESede");
+ aliases.append(keyAlias);
+ aliases.append(",");
+ }
+ if(aliases.length() > 0)
+ {
+ // remove trailing comma
+ aliases.delete(aliases.length() - 1, aliases.length());
+ }
+ p.put("aliases", aliases.toString());
+ return p;
+ }
+ };
+
+ KeyStoreParameters keyStoreParameters = new KeyStoreParameters(name, type, null, "", keyStoreLocation);
+ KeyStoreParameters backupKeyStoreParameters = new KeyStoreParameters(name + ".backup", type, null, "", backupKeyStoreLocation);
+ TestAlfrescoKeyStore keyStore = new TestAlfrescoKeyStore();
+ keyStore.setKeyStoreParameters(keyStoreParameters);
+ keyStore.setBackupKeyStoreParameters(backupKeyStoreParameters);
+ keyStore.setKeyResourceLoader(testKeyResourceLoader);
+ keyStore.setValidateKeyChanges(true);
+ keyStore.setEncryptionKeysRegistry(encryptionKeysRegistry);
+ return keyStore;
+ }
+
+ @Test
+ public void test1()
+ {
+ // missing keystore, missing backup keystore, no registered keys -> create key store with metadata key and register key
+
+ TestAlfrescoKeyStore missingMainKeyStore = getKeyStore("main", "JCEKS", Collections.singletonMap(KeyProvider.ALIAS_METADATA, "metadata"),
+ Collections.singletonMap(KeyProvider.ALIAS_METADATA, generateEncodedKey()), generateKeystoreName(), generateKeystoreName());
+
+ encryptionKeysRegistry.unregisterKey(KeyProvider.ALIAS_METADATA);
+ keyStoreChecker.setMainKeyStore(missingMainKeyStore);
+
+ try
+ {
+ keyStoreChecker.validateKeyStores();
+ }
+ catch(InvalidKeystoreException e)
+ {
+ fail("Unexpected exception: " + e.getMessage());
+ }
+ catch (MissingKeyException e)
+ {
+ fail("Unexpected exception : " + e.getMessage());
+ }
+
+ assertTrue("", encryptionKeysRegistry.getRegisteredKeys(missingMainKeyStore.getKeyAliases()).contains(KeyProvider.ALIAS_METADATA));
+ assertTrue("", missingMainKeyStore.exists());
+ assertTrue("", missingMainKeyStore.getKey(KeyProvider.ALIAS_METADATA) != null);
+ }
+
+ @Test
+ public void test2()
+ {
+ // missing main keystore, missing backup keystore, metadata registered key -> error, re-instate the keystore
+ TestAlfrescoKeyStore missingMainKeyStore = getKeyStore("main", "JCEKS", Collections.singletonMap(KeyProvider.ALIAS_METADATA, "metadata"),
+ null, generateKeystoreName(), generateKeystoreName());
+
+ assertTrue("", encryptionKeysRegistry.isKeyRegistered("metadata"));
+
+ keyStoreChecker.setMainKeyStore(missingMainKeyStore);
+
+ try
+ {
+ keyStoreChecker.validateKeyStores();
+ fail("Should have caught missing main keystore");
+ }
+ catch(InvalidKeystoreException e)
+ {
+ fail("Unexpected exception : " + e.getMessage());
+ }
+ catch (MissingKeyException e)
+ {
+ // ok, expected
+ }
+ }
+
+ @Test
+ public void test3()
+ {
+ // main keystore exists, no registered metadata key -> register key
+
+ // create main keystore
+ TestAlfrescoKeyStore mainKeyStore = getKeyStore("main", "JCEKS", Collections.singletonMap(KeyProvider.ALIAS_METADATA, "metadata"),
+ null, generateKeystoreName(), generateKeystoreName());
+ createAndPopulateKeyStore(mainKeyStore);
+
+ // de-register metadata key
+ encryptionKeysRegistry.unregisterKey(KeyProvider.ALIAS_METADATA);
+
+ // check keys
+ keyStoreChecker.setMainKeyStore(mainKeyStore);
+
+ try
+ {
+ keyStoreChecker.validateKeyStores();
+ }
+ catch(InvalidKeystoreException e)
+ {
+ fail("Unexpected exception: " + e.getMessage());
+ }
+ catch (MissingKeyException e)
+ {
+ fail("Unexpected exception : " + e.getMessage());
+ }
+
+ assertTrue("", encryptionKeysRegistry.isKeyRegistered(KeyProvider.ALIAS_METADATA));
+ }
+
+ @Test
+ public void test4()
+ {
+ // create keystore, change key -> check for exception InvalidKey
+
+ // Firstly, create main keystore to register a well-known key
+
+ encryptionKeysRegistry.unregisterKey(KeyProvider.ALIAS_METADATA);
+
+ TestAlfrescoKeyStore keyStore = getKeyStore("main", "JCEKS", Collections.singletonMap(KeyProvider.ALIAS_METADATA, "metadata"),
+ null, generateKeystoreName(), generateKeystoreName());
+ createAndPopulateKeyStore(keyStore);
+
+ keyStoreChecker.setMainKeyStore(keyStore);
+
+ // check keys
+ try
+ {
+ // should register the metadata key
+ keyStoreChecker.validateKeyStores();
+ }
+ catch(InvalidKeystoreException e)
+ {
+ fail("Unexpected exception: " + e.getMessage());
+ }
+ catch (MissingKeyException e)
+ {
+ fail("Unexpected exception : " + e.getMessage());
+ }
+
+ // check that the metadata key has been registered
+ assertTrue("", encryptionKeysRegistry.isKeyRegistered("metadata"));
+
+ // a changed main keystore with a different metadata key
+// TestAlfrescoKeyStore changedMainKeyStore = getKeyStore("main", "JCEKS", Collections.singletonMap(KeyProvider.ALIAS_METADATA, "metadata"),
+// null, generateKeystoreName(), generateKeystoreName());
+// createAndPopulateKeyStore(changedMainKeyStore);
+ keyStore.changeKey(KeyProvider.ALIAS_METADATA, generateSecretKey("DESede"));
+
+// keyStoreChecker.setMainKeyStore(changedMainKeyStore);
+ try
+ {
+ keyStoreChecker.validateKeyStores();
+ fail("Expected key store checker to detect changed metadata key");
+ }
+ catch(InvalidKeystoreException e)
+ {
+ // ok, expected
+ }
+ catch (MissingKeyException e)
+ {
+ fail("Unexpected exception : " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void test5()
+ {
+ // create main keystore, backup main keystore, change main keystore -> check that backup keystore key is ok, re-register new main key
+ // check that the new main keystore key has been re-registered
+
+ // Firstly, re-install main keystore to register a well-known key
+ encryptionKeysRegistry.unregisterKey(KeyProvider.ALIAS_METADATA);
+
+ TestAlfrescoKeyStore keyStore = getKeyStore("main", "JCEKS", Collections.singletonMap(KeyProvider.ALIAS_METADATA, "metadata"),
+ null, generateKeystoreName(), generateKeystoreName());
+ createAndPopulateKeyStore(keyStore);
+
+ try
+ {
+ keyStoreChecker.setMainKeyStore(keyStore);
+ keyStoreChecker.validateKeyStores();
+ }
+ catch(InvalidKeystoreException e)
+ {
+ fail("Unexpected exception: " + e.getMessage());
+ }
+ catch (MissingKeyException e)
+ {
+ fail("Unexpected exception : " + e.getMessage());
+ }
+
+ keyStore.backup();
+
+ // check that the metadata key has been registered
+ assertTrue("", encryptionKeysRegistry.isKeyRegistered("metadata"));
+
+ // change the metadata key
+ keyStore.changeKey(KeyProvider.ALIAS_METADATA, generateSecretKey("DESede"));
+
+ try
+ {
+ // should detect changed metadata key and re-register it
+ keyStoreChecker.validateKeyStores();
+ }
+ catch(InvalidKeystoreException e)
+ {
+ fail("Unexpected exception: " + e.getMessage());
+ }
+ catch (MissingKeyException e)
+ {
+ fail("Unexpected exception : " + e.getMessage());
+ }
+
+ // check that the new metadata key has been successfully re-registered by encrypting and decrypting some content with it
+ assertTrue("", EncryptionKeysRegistry.KEY_STATUS.OK == encryptionKeysRegistry.checkKey(KeyProvider.ALIAS_METADATA,
+ keyStore.getKey(KeyProvider.ALIAS_METADATA)));
+ }
+
+ private void createAndPopulateKeyStore(TestAlfrescoKeyStore keyStore)
+ {
+ KeyMap keyMap = new KeyMap();
+ keyMap.setKey(KeyProvider.ALIAS_METADATA, generateSecretKey("DESede"));
+ keyStore.create(keyMap, null);
+ }
+
+ private static class TestAlfrescoKeyStore extends AlfrescoKeyStoreImpl
+ {
+ public void create(KeyMap keys, KeyMap backupKeys)
+ {
+ this.keys = (keys != null ? keys : new KeyMap());
+ this.backupKeys = (backupKeys != null ? backupKeys : new KeyMap());
+ super.create();
+ }
+
+ void changeKey(String keyAlias, Key key)
+ {
+ keys.setKey(KeyProvider.ALIAS_METADATA, key);
+ }
+ }
+}
diff --git a/source/java/org/alfresco/encryption/MissingKeyException.java b/source/java/org/alfresco/encryption/MissingKeyException.java
deleted file mode 100644
index 04bd268336..0000000000
--- a/source/java/org/alfresco/encryption/MissingKeyException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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 .
- */
-package org.alfresco.encryption;
-
-/**
- *
- * @since 4.0
- *
- */
-public class MissingKeyException extends Exception
-{
- private static final long serialVersionUID = -7843412242954504581L;
-
- private String keyAlias;
-
- public MissingKeyException(String keyAlias)
- {
- this.keyAlias = keyAlias;
- }
-
- public String getKeyAlias()
- {
- return keyAlias;
- }
-}
diff --git a/source/java/org/alfresco/encryption/MissingKeyStoreException.java b/source/java/org/alfresco/encryption/MissingKeyStoreException.java
deleted file mode 100644
index bbe586ce31..0000000000
--- a/source/java/org/alfresco/encryption/MissingKeyStoreException.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 .
- */
-package org.alfresco.encryption;
-
-/**
- *
- * @since 4.0
- *
- */
-
-public class MissingKeyStoreException extends Exception
-{
- private static final long serialVersionUID = -6484290539637378020L;
-
- public MissingKeyStoreException(String message)
- {
- super(message);
- }
-}
diff --git a/source/java/org/alfresco/encryption/ReEncryptor.java b/source/java/org/alfresco/encryption/ReEncryptor.java
index 9a250c1637..0608fb356b 100644
--- a/source/java/org/alfresco/encryption/ReEncryptor.java
+++ b/source/java/org/alfresco/encryption/ReEncryptor.java
@@ -76,13 +76,12 @@ public class ReEncryptor implements ApplicationContextAware
private QNameDAO qnameDAO;
private MetadataEncryptor metadataEncryptor;
- private KeyProvider backupKeyProvider;
- private KeyProvider keyProvider;
private ApplicationContext applicationContext;
private TransactionService transactionService;
private RetryingTransactionHelper transactionHelper;
+ private int numThreads;
private int chunkSize;
private boolean splitTxns = true;
@@ -113,6 +112,11 @@ public class ReEncryptor implements ApplicationContextAware
{
this.jobLockService = jobLockService;
}
+
+ public void setNumThreads(int numThreads)
+ {
+ this.numThreads = numThreads;
+ }
public void setChunkSize(int chunkSize)
{
@@ -139,16 +143,6 @@ public class ReEncryptor implements ApplicationContextAware
this.qnameDAO = qnameDAO;
}
- public void setBackupKeyProvider(KeyProvider backupKeyProvider)
- {
- this.backupKeyProvider = backupKeyProvider;
- }
-
- public void setKeyProvider(KeyProvider keyProvider)
- {
- this.keyProvider = keyProvider;
- }
-
/**
* Attempts to get the lock. If the lock couldn't be taken, then null is returned.
*
@@ -266,7 +260,7 @@ public class ReEncryptor implements ApplicationContextAware
I18NUtil.getMessage("reencryptor.batchprocessor.name"), // TODO i18n name
transactionHelper,
provider,
- 2, 100,
+ numThreads, chunkSize,
applicationContext,
logger, 100).process(worker, splitTxns);
}
@@ -274,11 +268,11 @@ public class ReEncryptor implements ApplicationContextAware
/**
* Re-encrypt using the configured backup keystore to decrypt and the main keystore to encrypt
*/
- public int bootstrapReEncrypt() throws MissingKeyStoreException
+ public int bootstrapReEncrypt() throws MissingKeyException
{
- if(backupKeyProvider.getKey(KeyProvider.ALIAS_METADATA) == null)
+ if(!metadataEncryptor.backupKeyAvailable(KeyProvider.ALIAS_METADATA))
{
- throw new MissingKeyStoreException("Backup key store is either not present or does not contain a metadata encryption key");
+ throw new MissingKeyException("Backup key store is either not present or does not contain a metadata encryption key");
}
return reEncrypt();
}
@@ -287,31 +281,30 @@ public class ReEncryptor implements ApplicationContextAware
* Re-encrypt by decrypting using the configured keystore and encrypting using a keystore configured using the provided new key store parameters.
* Called from e.g. JMX.
*
+ * Assumes that the main key store has been already been reloaded.
+ *
* Note: it is the responsibility of the end user to ensure that the underlying keystores have been set up appropriately
* i.e. the old key store is backed up to the location defined by the property '${dir.keystore}/backup-keystore' and the new
* key store replaces it. This can be done while the repository is running.
*/
- public int reEncrypt() throws MissingKeyStoreException
+ public int reEncrypt() throws MissingKeyException
{
- // refresh the key providers to pick up changes made
- backupKeyProvider.refresh();
- keyProvider.refresh();
-
- if(keyProvider.getKey(KeyProvider.ALIAS_METADATA) == null)
+ if(!metadataEncryptor.keyAvailable(KeyProvider.ALIAS_METADATA))
{
- throw new MissingKeyStoreException("Main key store is either not present or does not contain a metadata encryption key");
+ throw new MissingKeyException("Main key store is either not present or does not contain a metadata encryption key");
}
- if(backupKeyProvider.getKey(KeyProvider.ALIAS_METADATA) == null)
+ if(!metadataEncryptor.backupKeyAvailable(KeyProvider.ALIAS_METADATA))
{
- throw new MissingKeyStoreException("Backup key store is either not present or does not contain a metadata encryption key");
+ throw new MissingKeyException("Backup key store is either not present or does not contain a metadata encryption key");
}
- int numProps = reEncryptImpl();
+ int numProps = reEncryptImpl();
return numProps;
}
protected int reEncryptImpl()
{
+ // Take out a re-encryptor lock
RetryingTransactionCallback txnWork = new RetryingTransactionCallback()
{
public String execute() throws Exception
diff --git a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java
index 6af46f1fa9..f409bc410c 100644
--- a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java
+++ b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java
@@ -1532,7 +1532,6 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl
qnames.add(propDef.getName());
}
- // TODO use callback approach
// qnames of properties that are encrypted
Set qnameIds = qnameDAO.convertQNamesToIds(qnames, false);
if(qnameIds.size() > 0)
diff --git a/source/java/org/alfresco/repo/node/MetadataEncryptorTests.java b/source/java/org/alfresco/repo/node/MetadataEncryptorTests.java
new file mode 100644
index 0000000000..1baabab05a
--- /dev/null
+++ b/source/java/org/alfresco/repo/node/MetadataEncryptorTests.java
@@ -0,0 +1,201 @@
+package org.alfresco.repo.node;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+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.node.integrity.IntegrityException;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.tenant.TenantService;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.ServiceRegistry;
+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.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.alfresco.util.PropertyMap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.extensions.surf.util.I18NUtil;
+
+public class MetadataEncryptorTests extends TestCase
+{
+ private static final String TEST_MODEL = "org/alfresco/repo/node/encrypted_prop_model.xml";
+ private static QName ENCRYPTED_TYPE_QNAME = QName.createQName("http://www.alfresco.org/test/encryptedPropModel/1.0", "encrypted");
+ private static QName ENCRYPTED_PROP_QNAME = QName.createQName("http://www.alfresco.org/test/encryptedPropModel/1.0", "prop1");
+
+ private static final Log logger = LogFactory.getLog(MetadataEncryptorTests.class);
+
+ private ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext();
+
+ private TransactionService transactionService;
+ private NodeService nodeService;
+ private TenantService tenantService;
+ private DictionaryDAO dictionaryDAO;
+ private MetadataEncryptor metadataEncryptor;
+
+ private StoreRef storeRef;
+ private NodeRef rootNodeRef;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
+ transactionService = serviceRegistry.getTransactionService();
+// txnHelper = transactionService.getRetryingTransactionHelper();
+ metadataEncryptor = (MetadataEncryptor)ctx.getBean("metadataEncryptor");
+ nodeService = serviceRegistry.getNodeService();
+ tenantService = (TenantService)ctx.getBean("tenantService");
+ dictionaryDAO = (DictionaryDAO)ctx.getBean("dictionaryDAO");
+
+ AuthenticationUtil.setRunAsUserSystem();
+
+ DictionaryBootstrap bootstrap = new DictionaryBootstrap();
+ List bootstrapModels = new ArrayList();
+ bootstrapModels.add("alfresco/model/dictionaryModel.xml");
+ bootstrapModels.add(TEST_MODEL);
+// List labels = new ArrayList();
+// labels.add(TEST_BUNDLE);
+ bootstrap.setModels(bootstrapModels);
+// bootstrap.setLabels(labels);
+ bootstrap.setDictionaryDAO(dictionaryDAO);
+ bootstrap.setTenantService(tenantService);
+ bootstrap.bootstrap();
+
+ // create a first store directly
+ RetryingTransactionCallback createStoreWork = new RetryingTransactionCallback()
+ {
+ public NodeRef execute()
+ {
+ StoreRef storeRef = nodeService.createStore(
+ StoreRef.PROTOCOL_WORKSPACE,
+ "Test_" + System.nanoTime());
+ return nodeService.getRootNode(storeRef);
+ }
+ };
+ rootNodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(createStoreWork);
+ }
+
+ /**
+ * Loads the test model required for building the node graphs
+ */
+// public static DictionaryService loadModel(ApplicationContext applicationContext)
+// {
+// DictionaryDAO dictionaryDao = (DictionaryDAO) applicationContext.getBean("dictionaryDAO");
+// // load the system model
+// ClassLoader cl = BaseNodeServiceTest.class.getClassLoader();
+// InputStream modelStream = cl.getResourceAsStream("alfresco/model/contentModel.xml");
+// assertNotNull(modelStream);
+// M2Model model = M2Model.createModel(modelStream);
+// dictionaryDao.putModel(model);
+// // load the test model
+// modelStream = cl.getResourceAsStream("org/alfresco/repo/node/BaseNodeServiceTest_model.xml");
+// assertNotNull(modelStream);
+// model = M2Model.createModel(modelStream);
+// dictionaryDao.putModel(model);
+//
+// DictionaryComponent dictionary = new DictionaryComponent();
+// dictionary.setDictionaryDAO(dictionaryDao);
+// // done
+// return dictionary;
+// }
+
+ public void testWithoutEncryption()
+ {
+ RetryingTransactionCallback encryptionWork = new RetryingTransactionCallback()
+ {
+ public Void execute()
+ {
+ NodeRef nodeRef1 = nodeService.createNode(
+ rootNodeRef,
+ ContentModel.ASSOC_CHILDREN,
+ QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, getName()),
+ ContentModel.TYPE_FOLDER, null).getChildRef();
+
+ try
+ {
+ // Create a node using the thread's locale
+ NodeRef nodeRef2 = nodeService.createNode(
+ nodeRef1,
+ ContentModel.ASSOC_CONTAINS,
+ QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, getName()),
+ ENCRYPTED_TYPE_QNAME,
+ Collections.singletonMap(ENCRYPTED_PROP_QNAME, (Serializable)"hello world")).getChildRef();
+ fail("Should have generated an IllegalArgumentException");
+ }
+ //catch(IntegrityException e)
+ catch(IllegalArgumentException e)
+ {
+ // expected
+ }
+
+ return null;
+ }
+ };
+ transactionService.getRetryingTransactionHelper().doInTransaction(encryptionWork);
+
+ }
+
+ public void testWithEncryption()
+ {
+ RetryingTransactionCallback encryptionWork = new RetryingTransactionCallback()
+ {
+ public Void execute()
+ {
+ NodeRef nodeRef1 = nodeService.createNode(
+ rootNodeRef,
+ ContentModel.ASSOC_CHILDREN,
+ QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, getName()),
+ ContentModel.TYPE_FOLDER, null).getChildRef();
+
+ Map allProperties = new PropertyMap();
+ allProperties.put(ENCRYPTED_PROP_QNAME, "ABC");
+ allProperties = metadataEncryptor.encrypt(allProperties);
+
+ try
+ {
+ NodeRef nodeRef2 = nodeService.createNode(
+ nodeRef1,
+ ContentModel.ASSOC_CONTAINS,
+ QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, getName()),
+ ENCRYPTED_TYPE_QNAME, allProperties).getChildRef();
+ assertNotNull(nodeRef2);
+
+ Serializable encryptedPropertyValue = nodeService.getProperty(nodeRef2, ENCRYPTED_PROP_QNAME);
+ Serializable decryptedPropertyValue = metadataEncryptor.decrypt(ENCRYPTED_PROP_QNAME, encryptedPropertyValue);
+ assertEquals("ABC", decryptedPropertyValue);
+ }
+ //catch(IntegrityException e)
+ catch(Throwable e)
+ {
+ fail();
+ }
+
+ return null;
+ }
+ };
+ transactionService.getRetryingTransactionHelper().doInTransaction(encryptionWork);
+ }
+
+ /**
+ * Clean up the test thread
+ */
+ @Override
+ protected void tearDown()
+ {
+ AuthenticationUtil.clearCurrentSecurityContext();
+ I18NUtil.setLocale(null);
+ }
+}
diff --git a/source/java/org/alfresco/repo/node/encrypted_prop_model.xml b/source/java/org/alfresco/repo/node/encrypted_prop_model.xml
new file mode 100644
index 0000000000..3bb4a4db83
--- /dev/null
+++ b/source/java/org/alfresco/repo/node/encrypted_prop_model.xml
@@ -0,0 +1,47 @@
+
+
+ Alfresco Content Model
+ Alfresco
+ 2005-05-30
+ 1.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Encrypted
+ The Base Type
+ cm:content
+
+
+
+ d:encrypted
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java b/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java
index 377286be8d..fee3f26e09 100644
--- a/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java
+++ b/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java
@@ -9,7 +9,7 @@ import java.util.Set;
import javax.crypto.SealedObject;
-import org.alfresco.encryption.Encryptor;
+import org.alfresco.encryption.FallbackEncryptor;
import org.alfresco.encryption.KeyProvider;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.security.authentication.AuthenticationException;
@@ -35,7 +35,7 @@ import org.alfresco.service.namespace.QName;
public class MetadataEncryptor
{
private DictionaryService dictionaryService;
- private Encryptor encryptor;
+ private FallbackEncryptor encryptor;
/**
* @param dictionaryService service to check if properties need encrypting
@@ -48,7 +48,7 @@ public class MetadataEncryptor
/**
* @param encryptor the class that does the encryption/decryption
*/
- public void setEncryptor(Encryptor encryptor)
+ public void setEncryptor(FallbackEncryptor encryptor)
{
this.encryptor = encryptor;
}
@@ -221,4 +221,14 @@ public class MetadataEncryptor
// Done
return outbound;
}
+
+ public boolean keyAvailable(String keyAlias)
+ {
+ return encryptor.keyAvailable(keyAlias);
+ }
+
+ public boolean backupKeyAvailable(String keyAlias)
+ {
+ return encryptor.backupKeyAvailable(keyAlias);
+ }
}
\ No newline at end of file