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