diff --git a/config/alfresco-global.properties.sample b/config/alfresco-global.properties.sample index 6c99a6e2f0..ae0cbdce3c 100644 --- a/config/alfresco-global.properties.sample +++ b/config/alfresco-global.properties.sample @@ -6,6 +6,7 @@ # Sample custom content and index data location # #dir.root=/srv/alfresco/alf_data +#dir.keystore=${dir.root}/keystore # # Sample database connection properties diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index caeeef3de8..8442086ddb 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -168,18 +168,17 @@ - - - - - - + + + + + @@ -811,5 +810,4 @@ - diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 490a75af21..17fa24849a 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -1019,11 +1019,15 @@ + + + + diff --git a/config/alfresco/encryption-context.xml b/config/alfresco/encryption-context.xml index 8433ba0743..57d675a510 100644 --- a/config/alfresco/encryption-context.xml +++ b/config/alfresco/encryption-context.xml @@ -46,6 +46,13 @@ + + + + + + + @@ -55,35 +62,27 @@ - - - - diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml index 59129e25e2..206e2ebe66 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml @@ -763,13 +763,25 @@ + + select aspects.node_id as node_id, aspects.qname_id as qname_id - from alf_node_aspects aspects diff --git a/config/alfresco/keystore-passwords.properties b/config/alfresco/keystore-passwords.properties deleted file mode 100644 index 251d17c0d7..0000000000 --- a/config/alfresco/keystore-passwords.properties +++ /dev/null @@ -1,6 +0,0 @@ -# The password protecting the keystore entries -keystore=mp6yc0UD9e -# The password protecting the alias: metadata -metadata=oKIWzVdEdA -# The password protecting the alias: solr -solr=TxHTtOnrwQ \ No newline at end of file diff --git a/config/alfresco/keystore/keystore-passwords.properties b/config/alfresco/keystore/keystore-passwords.properties index 2e51c4f163..06a5dd76ec 100644 --- a/config/alfresco/keystore/keystore-passwords.properties +++ b/config/alfresco/keystore/keystore-passwords.properties @@ -1,20 +1,11 @@ -keystore.password=mp6yc0UD9e - -aliases=metadata,solr +aliases=metadata # The password protecting the keystore entries keystore.password=mp6yc0UD9e # The password protecting the alias: metadata -metadata.seed= -metadata.algorithm=PBEWithMD5AndDES +metadata.keyData= +metadata.algorithm=DESede metadata.password=oKIWzVdEdA # The password protecting the alias: solr -solr.seed= -solr.algorithm=PBEWithMD5AndDES -solr.password=TxHTtOnrwQ - -# The password protecting the keystore entries -#keystore=mp6yc0UD9e -# The password protecting the alias: metadata -#metadata=oKIWzVdEdA -# The password protecting the alias: solr -#solr=TxHTtOnrwQ \ No newline at end of file +#solr.keyData= +#solr.algorithm=DESede +#solr.password=TxHTtOnrwQ \ No newline at end of file diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index d5c3dc3834..5827192643 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -681,22 +681,29 @@ deployment.filesystem.default.metadatadir=${deployment.filesystem.metadatadir}/d # Encryption properties # #dir.keystore=${dir.root}/keystore +# default keystores location dir.keystore=classpath:alfresco/keystore + +encryption.keySpec.class=org.alfresco.encryption.DESEDEKeyGenerator +encryption.keyAlgorithm=DESede encryption.cipherAlgorithm=DESede/CBC/PKCS5Padding + +# secret key keystore configuration encryption.keystore.location=${dir.keystore}/keystore encryption.keystore.keyMetaData.location=${dir.keystore}/keystore-passwords.properties encryption.keystore.provider= encryption.keystore.type=JCEKS -#encryption.keystore.keyAlgorithm=PBEWithMD5AndDES -encryption.keystore.keyAlgorithm=DESede -encryption.keystore.keyAliases=metadata, solr -encryption.keystore.fallback.location=${dir.keystore}/fallback-keystore -encryption.keystore.fallback.keyMetaData.location=${dir.keystore}/fallback-keystore-passwords.properties -encryption.keystore.fallback.provider= -encryption.keystore.fallback.type=JCEKS +# backup keystore (if configured) +encryption.keystore.backup.location= +encryption.keystore.backup.keyMetaData.location= +encryption.keystore.backup.provider= +encryption.keystore.backup.type= -# mac +# Should encryptable properties be re-encrypted with new encryption keys on botstrap? +encryption.bootstrap.reencrypt=false + +# mac/md5 encryption encryption.mac.messageTimeout=30000 encryption.mac.algorithm=HmacSHA1 @@ -712,7 +719,8 @@ encryption.ssl.truststore.keyMetaData.location=${dir.keystore}/ssl-truststore-pa # SOLR connection details (e.g. for JMX) solr.host=localhost -solr.port=8443 +solr.port=8080 +solr.port.ssl=8443 solr.solrUser=solr solr.solrPassword=solr # none, https diff --git a/config/alfresco/subsystems/Search/solr/solr-search-context.xml b/config/alfresco/subsystems/Search/solr/solr-search-context.xml index 48cb523e66..0fcbc6cbe4 100644 --- a/config/alfresco/subsystems/Search/solr/solr-search-context.xml +++ b/config/alfresco/subsystems/Search/solr/solr-search-context.xml @@ -30,35 +30,7 @@ - - ${solr.host} - - - ${solr.port} - - - diff --git a/config/alfresco/subsystems/Search/solr/solr-search.properties b/config/alfresco/subsystems/Search/solr/solr-search.properties index defb170688..4cbde289fc 100644 --- a/config/alfresco/subsystems/Search/solr/solr-search.properties +++ b/config/alfresco/subsystems/Search/solr/solr-search.properties @@ -1 +1,3 @@ -solr.base.url=http://localhost:8080/solr \ No newline at end of file +solr.host=localhost +solr.port=8080 +solr.port.ssl=8443 \ No newline at end of file diff --git a/source/java/org/alfresco/encryption/BootstrapReEncryptor.java b/source/java/org/alfresco/encryption/BootstrapReEncryptor.java index 7cab37f5a2..a2e4f3a9e8 100644 --- a/source/java/org/alfresco/encryption/BootstrapReEncryptor.java +++ b/source/java/org/alfresco/encryption/BootstrapReEncryptor.java @@ -18,6 +18,9 @@ */ package org.alfresco.encryption; +import org.alfresco.error.AlfrescoRuntimeException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; import org.springframework.extensions.surf.util.AbstractLifecycleBean; @@ -28,9 +31,10 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean; */ public class BootstrapReEncryptor extends AbstractLifecycleBean { + private static Log logger = LogFactory.getLog(BootstrapReEncryptor.class); + private boolean enabled; private ReEncryptor reEncryptor; - private KeyStoreParameters oldKeyStoreParameters; public void setEnabled(boolean enabled) { @@ -42,14 +46,20 @@ public class BootstrapReEncryptor extends AbstractLifecycleBean this.reEncryptor = reEncryptor; } - public void setOldKeyStoreParameters(KeyStoreParameters oldKeyStoreParameters) + public int reEncrypt() { - this.oldKeyStoreParameters = oldKeyStoreParameters; - } - - public void reEncrypt() - { - reEncryptor.execute(oldKeyStoreParameters); + try + { + return reEncryptor.reEncrypt(); + } + catch(MissingKeyException e) + { + throw new AlfrescoRuntimeException("Bootstrap re-encryption failed", e); + } + catch(MissingKeyStoreException e) + { + throw new AlfrescoRuntimeException("Bootstrap re-encryption failed", e); + } } @Override @@ -57,7 +67,15 @@ public class BootstrapReEncryptor extends AbstractLifecycleBean { if(enabled) { - reEncrypt(); + if(logger.isDebugEnabled()) + { + logger.debug("Re-encrypting encryptable properties..."); + } + int propertiesReEncrypted = reEncrypt(); + if(logger.isDebugEnabled()) + { + logger.debug("...done, re-encrypted " + propertiesReEncrypted + " properties."); + } } } diff --git a/source/java/org/alfresco/encryption/EncryptionChecker.java b/source/java/org/alfresco/encryption/EncryptionChecker.java index 31e60f812d..adf2debe27 100644 --- a/source/java/org/alfresco/encryption/EncryptionChecker.java +++ b/source/java/org/alfresco/encryption/EncryptionChecker.java @@ -60,7 +60,7 @@ public class EncryptionChecker extends AbstractLifecycleBean @Override protected void onBootstrap(ApplicationEvent event) { - AlfrescoKeyStore mainKeyStore = new CachingKeyStore(keyStoreParameters, keyResourceLoader); + AlfrescoKeyStore mainKeyStore = new AlfrescoKeyStoreImpl(keyStoreParameters, keyResourceLoader); keyStoreChecker.checkKeyStore(mainKeyStore); } diff --git a/source/java/org/alfresco/encryption/EncryptionKeysRegistryImpl.java b/source/java/org/alfresco/encryption/EncryptionKeysRegistryImpl.java new file mode 100644 index 0000000000..d4e9847d85 --- /dev/null +++ b/source/java/org/alfresco/encryption/EncryptionKeysRegistryImpl.java @@ -0,0 +1,204 @@ +/* + * 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 java.io.Serializable; +import java.security.InvalidKeyException; +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.attributes.AttributeService; +import org.alfresco.service.cmr.attributes.AttributeService.AttributeQueryCallback; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.EqualsHelper; +import org.alfresco.util.GUID; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * + * @since 4.0 + * + */ +// TODO caching? This will probably not be used extensively. +public class EncryptionKeysRegistryImpl implements EncryptionKeysRegistry +{ + public static String TOP_LEVEL_KEY = "keyCheck"; + private static final Log logger = LogFactory.getLog(EncryptionKeysRegistryImpl.class); + + private TransactionService transactionService; + private AttributeService attributeService; + private Encryptor encryptor; + + public void setAttributeService(AttributeService attributeService) + { + this.attributeService = attributeService; + } + + public void setEncryptor(Encryptor encryptor) + { + this.encryptor = encryptor; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void registerKey(String keyAlias) + { + // register the key by creating an attribute that stores a guid and its encrypted value + String guid = GUID.generate(); + 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() + { + final List registeredKeys = new ArrayList(); + + if(attributeService.exists(TOP_LEVEL_KEY)) + { + attributeService.getAttributes(new AttributeQueryCallback() + { + public boolean handleAttribute(Long id, Serializable value, + Serializable[] keys) + { + registeredKeys.add((String)value); + return true; + } + + }, + 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 + { + keyAliasesUnchanged.add(keyAlias); + } + } + + return new KeysReport(keyAliasesChanged, keyAliasesUnchanged); + } + + public KEY_STATUS checkKey(String keyAlias) + { + if(attributeService.exists(TOP_LEVEL_KEY, keyAlias)) + { + try + { + // 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); + Serializable storedGUID = encryptor.unsealObject(keyAlias, keyCheck.getEncrypted()); + return EqualsHelper.nullSafeEquals(storedGUID, keyCheck.getGuid()) ? KEY_STATUS.OK : KEY_STATUS.CHANGED; + } + catch(InvalidKeyException e) + { + // key exception indicates that the key has changed - it can't decrypt the + // previously-encrypted data + return KEY_STATUS.CHANGED; + } + } + else + { + return KEY_STATUS.MISSING; + } + } + + // note that this removes _all_ keys in the keystore. Use with care. + public void removeRegisteredKeys(final AlfrescoKeyStore keyStore) + { + RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper(); + final RetryingTransactionCallback removeKeysCallback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + for(String keyAlias : keyStore.getKeyAliases()) + { + attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias); + } + + return null; + } + }; + retryingTransactionHelper.doInTransaction(removeKeysCallback, false); + } + + /** + * A KeyCheck object stores a well-known guid and it's encrypted value. + * + * @since 4.0 + * + */ + private static class KeyCheck implements Serializable + { + private static final long serialVersionUID = 4514315444977162903L; + + private String guid; + private Serializable encrypted; + + public KeyCheck(String guid, Serializable encrypted) + { + super(); + this.guid = guid; + this.encrypted = encrypted; + } + + public String getGuid() + { + return guid; + } + + public Serializable getEncrypted() + { + return encrypted; + } + + public boolean equals(Object other) + { + if(this == other) + { + return true; + } + + if(!(other instanceof KeyCheck)) + { + return false; + } + KeyCheck keyCheck = (KeyCheck)other; + return EqualsHelper.nullSafeEquals(keyCheck.getGuid(), getGuid()) && + EqualsHelper.nullSafeEquals(keyCheck.getEncrypted(), getEncrypted()); + } + } +} diff --git a/source/java/org/alfresco/encryption/EncryptionTests.java b/source/java/org/alfresco/encryption/EncryptionTests.java new file mode 100644 index 0000000000..146b82cf22 --- /dev/null +++ b/source/java/org/alfresco/encryption/EncryptionTests.java @@ -0,0 +1,303 @@ +/* + * 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 java.io.File; +import java.io.Serializable; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESedeKeySpec; +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.dictionary.DictionaryBootstrap; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.node.encryption.MetadataEncryptor; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +public class EncryptionTests extends TestCase +{ + private static final String TEST_MODEL = "org/alfresco/encryption/reencryption_model.xml"; +// private static final String TEST_BUNDLE = "org/alfresco/encryption/encryptiontest_model"; + + private static int NUM_PROPERTIES = 500; + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private static QName NODE_TYPE = QName.createQName("http://www.alfresco.org/test/reencryption_test/1.0", "base"); + private static QName PROP = QName.createQName("http://www.alfresco.org/test/reencryption_test/1.0", "prop1"); + + private NodeRef rootNodeRef; + + private TransactionService transactionService; + private DictionaryService dictionaryService; + private NodeService nodeService; + private MetadataEncryptor metadataEncryptor; + private ReEncryptor reEncryptor; + private String cipherAlgorithm = "DESede/CBC/PKCS5Padding"; + private KeyStoreParameters keyStoreParameters; + private KeyStoreParameters backupKeyStoreParameters; + private KeyResourceLoader keyResourceLoader; + private EncryptionKeysRegistryImpl encryptionKeysRegistry; + private KeyStoreChecker keyStoreChecker; + + private AuthenticationComponent authenticationComponent; + private DictionaryDAO dictionaryDAO; + private TenantService tenantService; + + private String keyAlgorithm; + private Map newKeys = new HashMap(); + private List before = new ArrayList(); + private List after = new ArrayList(); + + private UserTransaction tx; + + public void setUp() throws Exception + { + dictionaryService = (DictionaryService)ctx.getBean("dictionaryService"); + nodeService = (NodeService)ctx.getBean("nodeService"); + transactionService = (TransactionService)ctx.getBean("transactionService"); + tenantService = (TenantService)ctx.getBean("tenantService"); + dictionaryDAO = (DictionaryDAO)ctx.getBean("dictionaryDAO"); + metadataEncryptor = (MetadataEncryptor)ctx.getBean("metadataEncryptor"); + authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); + keyResourceLoader = (KeyResourceLoader)ctx.getBean("springKeyResourceLoader"); + reEncryptor = (ReEncryptor)ctx.getBean("reEncryptor"); + backupKeyStoreParameters = (KeyStoreParameters)ctx.getBean("backupKeyStoreParameters"); + keyStoreChecker = (KeyStoreChecker)ctx.getBean("keyStoreChecker"); + keyStoreParameters = (KeyStoreParameters)ctx.getBean("keyStoreParameters"); + encryptionKeysRegistry = (EncryptionKeysRegistryImpl)ctx.getBean("encryptionKeysRegistry"); + + // reencrypt in one txn (since we don't commit the model, the qnames won't be available across transactions) + reEncryptor.setSplitTxns(false); + + this.authenticationComponent.setSystemUserAsCurrentUser(); + + tx = transactionService.getUserTransaction(); + tx.begin(); + + StoreRef storeRef = nodeService.createStore( + StoreRef.PROTOCOL_WORKSPACE, + "ReEncryptor_" + System.currentTimeMillis()); + rootNodeRef = nodeService.getRootNode(storeRef); + + keyAlgorithm = "DESede"; + newKeys.put(KeyProvider.ALIAS_METADATA, generateSecretKey(keyAlgorithm)); + + // Load models + DictionaryBootstrap bootstrap = new DictionaryBootstrap(); + List bootstrapModels = new ArrayList(); + 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(); + } + + protected KeyProvider getKeyProvider(KeyStoreParameters keyStoreParameters) + { + KeyProvider backupKeyProvider = new KeystoreKeyProvider(keyStoreParameters, keyResourceLoader); + return backupKeyProvider; + } + + public void setBackupKeyStoreParameters(KeyStoreParameters backupKeyStoreParameters) + { + this.backupKeyStoreParameters = backupKeyStoreParameters; + } + + @Override + protected void tearDown() throws Exception + { + authenticationComponent.clearCurrentSecurityContext(); + tx.rollback(); + super.tearDown(); + } + + protected KeyProvider getKeyProvider(final Map keys) + { + KeyProvider keyProvider = new KeyProvider() + { + @Override + public Key getKey(String keyAlias) + { + return keys.get(keyAlias); + } + }; + return keyProvider; + } + + protected Encryptor getEncryptor(KeyProvider keyProvider) + { + DefaultEncryptor encryptor = new DefaultEncryptor(); + encryptor.setCipherAlgorithm(cipherAlgorithm); + encryptor.setCipherProvider(null); + encryptor.setKeyProvider(keyProvider); + + return encryptor; + } + + protected MetadataEncryptor getMetadataEncryptor(Encryptor encryptor) + { + MetadataEncryptor metadataEncryptor = new MetadataEncryptor(); + metadataEncryptor.setDictionaryService(dictionaryService); + metadataEncryptor.setEncryptor(encryptor); + + return metadataEncryptor; + } + + protected void createEncryptedProperties(List nodes, MetadataEncryptor metadataEncryptor) + { + for(int i = 0; i < NUM_PROPERTIES; i++) + { + NodeRef nodeRef = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("assoc1"), NODE_TYPE).getChildRef(); + nodes.add(nodeRef); + + Map props = new HashMap(); + props.put(PROP, nodeRef.toString()); + props = metadataEncryptor.encrypt(props); + nodeService.setProperties(nodeRef, props); + } + } + + public byte[] generateKeyData() throws NoSuchAlgorithmException + { + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + random.setSeed(System.currentTimeMillis()); + byte bytes[] = new byte[DESedeKeySpec.DES_EDE_KEY_LEN]; + random.nextBytes(bytes); + return bytes; + } + + protected Key generateSecretKey(String keyAlgorithm) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException + { + DESedeKeySpec keySpec = new DESedeKeySpec(generateKeyData()); + SecretKeyFactory kf = SecretKeyFactory.getInstance(keyAlgorithm); + SecretKey secretKey = kf.generateSecret(keySpec); + return secretKey; + } + + public void testReEncrypt() + { + try + { + // Create encrypted properties using the configured encryptor and key provider + createEncryptedProperties(before, metadataEncryptor); + + // Create encrypted properties using the new encryptor and key provider + KeyProvider newKeyProvider = getKeyProvider(newKeys); + Encryptor newEncryptor = getEncryptor(newKeyProvider); + MetadataEncryptor newMetadataEncryptor = getMetadataEncryptor(newEncryptor); + createEncryptedProperties(after, newMetadataEncryptor); + + // re-encrypt + long start = System.currentTimeMillis(); + reEncryptor.reEncrypt(newKeyProvider); + System.out.println("Re-encrypted " + NUM_PROPERTIES*2 + " properties in " + (System.currentTimeMillis() - start) + "ms"); + + // check that the nodes have been re-encrypted properly i.e. check that the properties + // decrypted using the new keys match the expected values. + for(NodeRef nodeRef : before) + { + Map props = nodeService.getProperties(nodeRef); + props = newMetadataEncryptor.decrypt(props); + assertNotNull("", props.get(PROP)); + assertEquals("", nodeRef.toString(), props.get(PROP)); + } + + for(NodeRef nodeRef : after) + { + Map props = nodeService.getProperties(nodeRef); + props = newMetadataEncryptor.decrypt(props); + assertNotNull("", props.get(PROP)); + assertEquals("", nodeRef.toString(), props.get(PROP)); + } + } + catch(AlfrescoRuntimeException e) + { + if(e.getCause() instanceof InvalidKeyException) + { + e.printStackTrace(); + fail(); + } + } + } + + public void testBootstrapReEncrypt() + { + try + { + reEncryptor.reEncrypt(); + fail("Should have caught missing backup key store"); + } + catch(MissingKeyException e) + { + fail(""); + } + catch(MissingKeyStoreException e) + { + + } + } + + public void testKeyStoreCreation() + { + String keyStoreLocation = System.getProperty("user.dir") + File.separator + "encryption-tests.keystore"; + File keyStoreFile = new File(keyStoreLocation); + if(keyStoreFile.exists()) + { + assertTrue("", keyStoreFile.delete()); + } + + 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")); + } +} \ 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 7e2617d866..d7d2043a6d 100644 --- a/source/java/org/alfresco/encryption/EncryptorTest.java +++ b/source/java/org/alfresco/encryption/EncryptorTest.java @@ -85,15 +85,15 @@ 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 666dd1189a..b18b86e60d 100644 --- a/source/java/org/alfresco/encryption/KeyStoreChecker.java +++ b/source/java/org/alfresco/encryption/KeyStoreChecker.java @@ -18,56 +18,36 @@ */ package org.alfresco.encryption; -import java.io.FileOutputStream; -import java.io.Serializable; -import java.security.InvalidKeyException; -import java.security.Key; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.attributes.AttributeService; -import org.alfresco.service.cmr.attributes.AttributeService.AttributeQueryCallback; import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.EqualsHelper; -import org.alfresco.util.GUID; 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). + * * @since 4.0 * */ public class KeyStoreChecker { - public static String TOP_LEVEL_KEY = "keyCheck"; private static final Log logger = LogFactory.getLog(KeyStoreChecker.class); - private static enum KEY_STATUS - { - OK, CHANGED, MISSING; - }; - private AttributeService attributeService; - private Encryptor encryptor; private TransactionService transactionService; + private EncryptionKeysRegistryImpl encryptionKeysRegistry; public KeyStoreChecker() { } - - public void setAttributeService(AttributeService attributeService) + + public void setEncryptionKeysRegistry(EncryptionKeysRegistryImpl encryptionKeysRegistry) { - this.attributeService = attributeService; - } - - public void setEncryptor(Encryptor encryptor) - { - this.encryptor = encryptor; + this.encryptionKeysRegistry = encryptionKeysRegistry; } public void setTransactionService(TransactionService transactionService) @@ -75,100 +55,38 @@ public class KeyStoreChecker this.transactionService = transactionService; } - public KeyStoreChecker(AttributeService attributeService, Encryptor encryptor, TransactionService transactionService) + public KeyStoreChecker(EncryptionKeysRegistryImpl encryptionKeysRegistry) { - this.attributeService = attributeService; - this.transactionService = transactionService; - this.encryptor = encryptor; + this.encryptionKeysRegistry = encryptionKeysRegistry; } - - private KEY_STATUS checkKey(AlfrescoKeyStore keyStore, String keyAlias) - { - if(attributeService.exists(TOP_LEVEL_KEY, keyStore.getLocation(), keyAlias)) - { - try - { - // 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, keyStore.getLocation(), keyAlias); - Serializable storedGUID = encryptor.unsealObject(keyAlias, keyCheck.getEncrypted()); - return EqualsHelper.nullSafeEquals(storedGUID, keyCheck.getGuid()) ? KEY_STATUS.OK : KEY_STATUS.CHANGED; - } - catch(InvalidKeyException e) - { - // key exception indicates that the key has changed - it can't decrypt the - // previously-encrypted data - return KEY_STATUS.CHANGED; - } - } - else - { - return KEY_STATUS.MISSING; - } - } - private void registerKey(AlfrescoKeyStore keyStore, String keyAlias) + protected void createKeyStore(AlfrescoKeyStore keyStore) { - // register the key by creating an attribute that stores a guid and its encrypted value - String guid = GUID.generate(); - Serializable encrypted = encryptor.sealObject(keyAlias, null, guid); - KeyCheck keyCheck = new KeyCheck(guid, encrypted); - attributeService.createAttribute(keyCheck, TOP_LEVEL_KEY, keyStore.getLocation(), keyAlias); - logger.info("Registered key " + keyAlias); + keyStore.create(); + + // Register the key store keys + for(String keyAlias : keyStore.getKeyAliases()) + { + encryptionKeysRegistry.registerKey(keyAlias); + } } - protected KeysReport getKeysReport(AlfrescoKeyStore keyStore) - { - final List registeredKeys = new ArrayList(); - - if(attributeService.exists(TOP_LEVEL_KEY, keyStore.getLocation())) - { - attributeService.getAttributes(new AttributeQueryCallback() - { - public boolean handleAttribute(Long id, Serializable value, - Serializable[] keys) - { - registeredKeys.add((String)value); - return true; - } - - }, - TOP_LEVEL_KEY, keyStore.getLocation()); - } - - List keyAliasesChanged = new ArrayList(); - List keyAliasesUnchanged = new ArrayList(); - - for(String keyAlias : registeredKeys) - { - KEY_STATUS keyStatus = checkKey(keyStore, keyAlias); - if(keyStatus == KEY_STATUS.CHANGED) - { - keyAliasesChanged.add(keyAlias); - } - else - { - keyAliasesUnchanged.add(keyAlias); - } - } - - return new KeysReport(keyAliasesChanged, keyAliasesUnchanged); - } - 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 = getKeysReport(keyStore); + 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 - if(keysReport.getKeysChanged().size() > 0) + // 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"); @@ -182,9 +100,7 @@ public class KeyStoreChecker // Note: this will halt the application bootstrap. throw new AlfrescoRuntimeException("Keys have already been registered, re-instate the previous keystore"); } - - // no keys found, create a new keystore - // TODO + if(logger.isDebugEnabled()) { logger.debug("Keystore not found, creating..."); @@ -197,157 +113,4 @@ public class KeyStoreChecker }; retryingTransactionHelper.doInTransaction(checkKeysCallback, false); } - - protected void createKeyStore(AlfrescoKeyStore keyStore) - { - new CreatingKeyStore(keyStore).create(); - } - - public static class KeysReport - { - private List keysChanged; - private List keysUnchanged; - - public KeysReport(List keysChanged, List keysUnchanged) - { - super(); - this.keysChanged = keysChanged; - this.keysUnchanged = keysUnchanged; - } - - public List getKeysChanged() - { - return keysChanged; - } - - public List getKeysUnchanged() - { - return keysUnchanged; - } - } - - /** - * A KeyCheck object stores a well-known guid and it's encrypted value. - * - * @since 4.0 - * - */ - private static class KeyCheck implements Serializable - { - private static final long serialVersionUID = 4514315444977162903L; - - private String guid; - private Serializable encrypted; - - public KeyCheck(String guid, Serializable encrypted) - { - super(); - this.guid = guid; - this.encrypted = encrypted; - } - - public String getGuid() - { - return guid; - } - - public Serializable getEncrypted() - { - return encrypted; - } - - public boolean equals(Object other) - { - if(this == other) - { - return true; - } - - if(!(other instanceof KeyCheck)) - { - return false; - } - KeyCheck keyCheck = (KeyCheck)other; - return EqualsHelper.nullSafeEquals(keyCheck.getGuid(), getGuid()) && - EqualsHelper.nullSafeEquals(keyCheck.getEncrypted(), getEncrypted()); - } - } - - public void removeRegisteredKeys(final AlfrescoKeyStore keyStore) - { - RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper(); - final RetryingTransactionCallback removeKeysCallback = new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - attributeService.removeAttributes(TOP_LEVEL_KEY, keyStore.getLocation()); - - return null; - } - }; - retryingTransactionHelper.doInTransaction(removeKeysCallback, false); - } - - private class CreatingKeyStore extends CachingKeyStore - { - CreatingKeyStore(AlfrescoKeyStore keyStore) - { - super(keyStore.getkeyStoreParameters(), keyStore.getKeyResourceLoader()); - } - - public void create() - { - KeyInfoManager keyInfoManager = null; - - try - { - keyInfoManager = getKeyInfoManager(); - - ks.load(null, null); - String keyStorePassword = keyInfoManager.getKeyStorePassword(); - if(keyStorePassword == null) - { - throw new AlfrescoRuntimeException("Key store password is null for keystore at location " + getLocation() - + ", key store meta data location" + getKeyMetaDataFileLocation()); - } - - // Add keys from the passwords file to the keystore - for(Map.Entry keyEntry : keyInfoManager.getKeyInfo().entrySet()) - { - KeyInformation keyInfo = keyInfoManager.getKeyInformation(keyEntry.getKey()); - String keyPassword = keyInfo.getPassword(); - if(keyPassword == null) - { - throw new AlfrescoRuntimeException("No password found for encryption key " + keyEntry.getKey()); - } - Key key = generateSecretKey(keyEntry.getValue()); - ks.setKeyEntry(keyInfo.getAlias(), key, keyInfo.getPassword().toCharArray(), null); - } - - ks.store(new FileOutputStream(getLocation()), keyStorePassword.toCharArray()); - - // Register the key store keys - for(Map.Entry keyEntry : keyInfoManager.getKeyInfo().entrySet()) - { - registerKey(this, keyEntry.getKey()); - } - } - catch(Throwable e) - { - throw new AlfrescoRuntimeException( - "Failed to create keystore: \n" + - " Location: " + getLocation() + "\n" + - " Provider: " + getProvider() + "\n" + - " Type: " + getType(), - e); - } - finally - { - if(keyInfoManager != null) - { - keyInfoManager.clear(); - } - } - } - } } diff --git a/source/java/org/alfresco/encryption/MissingKeyException.java b/source/java/org/alfresco/encryption/MissingKeyException.java new file mode 100644 index 0000000000..04bd268336 --- /dev/null +++ b/source/java/org/alfresco/encryption/MissingKeyException.java @@ -0,0 +1,41 @@ +/* + * 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 new file mode 100644 index 0000000000..bbe586ce31 --- /dev/null +++ b/source/java/org/alfresco/encryption/MissingKeyStoreException.java @@ -0,0 +1,35 @@ +/* + * 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 5d023d85a5..f3ca6f3d51 100644 --- a/source/java/org/alfresco/encryption/ReEncryptor.java +++ b/source/java/org/alfresco/encryption/ReEncryptor.java @@ -19,26 +19,30 @@ package org.alfresco.encryption; import java.io.Serializable; +import java.security.Key; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Map; import javax.crypto.SealedObject; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.batch.BatchProcessWorkProvider; import org.alfresco.repo.batch.BatchProcessor; import org.alfresco.repo.dictionary.DictionaryDAO; -import org.alfresco.repo.dictionary.NamespaceDAO; import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.domain.node.NodePropertyEntity; import org.alfresco.repo.domain.node.NodePropertyKey; +import org.alfresco.repo.domain.node.NodePropertyValue; import org.alfresco.repo.domain.qname.QNameDAO; import org.alfresco.repo.node.encryption.MetadataEncryptor; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; @@ -65,17 +69,22 @@ public class ReEncryptor implements ApplicationContextAware private static Log logger = LogFactory.getLog(ReEncryptor.class); private NodeDAO nodeDAO; - private NamespaceDAO namespaceDAO; private DictionaryDAO dictionaryDAO; private DictionaryService dictionaryService; + private TransactionService transactionService; private QNameDAO qnameDAO; - private MetadataEncryptor metadataEncryptor; + private KeyStoreParameters backupKeyStoreParameters; + private KeyProvider keyProvider; + private KeyResourceLoader keyResourceLoader; private ApplicationContext applicationContext; - - private TransactionService transactionService; private RetryingTransactionHelper transactionHelper; + private String cipherAlgorithm; + + // TODO propertize + private int chunkSize = 50; + private boolean splitTxns = true; /** * Set the transaction provider so that each execution can be performed within a transaction @@ -86,40 +95,93 @@ public class ReEncryptor implements ApplicationContextAware this.transactionHelper = transactionService.getRetryingTransactionHelper(); this.transactionHelper.setForceWritable(true); } - -// protected MetadataEncryptor getMetadataEncryptor(EncryptionParameters encryptionParameters) -// { -// DefaultEncryptor encryptor = new DefaultEncryptor(); -// encryptor.setCipherAlgorithm(encryptionParameters.getCipherAlgorithm()); -// encryptor.setCipherProvider(null); -// KeystoreKeyProvider keyProvider = new KeystoreKeyProvider(); -// keyProvider.setLocation(encryptionParameters.getKeyStoreLocation()); -// keyProvider.setPasswordsFileLocation(encryptionParameters.getPasswordFileLocation()); -// keyProvider.setType(encryptionParameters.getKeyStoreType()); -// keyProvider.setKeyResourceLoader(new SpringKeyResourceLoader()); -// keyProvider.setProvider(encryptionParameters.getKeyStoreProvider() -// ); -// -// encryptor.setKeyProvider(keyProvider); -// -// MetadataEncryptor metadataEncryptor = new MetadataEncryptor(); -// metadataEncryptor.setEncryptor(encryptor); -// metadataEncryptor.setDictionaryService(dictionaryService); -// -// return metadataEncryptor; -// } - public void setMetadataEncryptor(MetadataEncryptor metadataEncryptor) + public void setSplitTxns(boolean splitTxns) { - this.metadataEncryptor = metadataEncryptor; + this.splitTxns = splitTxns; } - public void init() + public void setNodeDAO(NodeDAO nodeDAO) { + this.nodeDAO = nodeDAO; } - public void reencrypt(final KeyStoreParameters newEncryptionParameters, final List properties) + public void setDictionaryDAO(DictionaryDAO dictionaryDAO) { + this.dictionaryDAO = dictionaryDAO; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + public void setCipherAlgorithm(String cipherAlgorithm) + { + this.cipherAlgorithm = cipherAlgorithm; + } + + public void setBackupKeyStoreParameters(KeyStoreParameters backupKeyStoreParameters) + { + this.backupKeyStoreParameters = backupKeyStoreParameters; + } + + protected KeyProvider getKeyProvider(KeyStoreParameters keyStoreParameters) + { + KeyProvider keyProvider = new KeystoreKeyProvider(keyStoreParameters, keyResourceLoader); + return keyProvider; + } + + public void setKeyProvider(KeyProvider keyProvider) + { + this.keyProvider = keyProvider; + } + + public void setKeyResourceLoader(KeyResourceLoader keyResourceLoader) + { + this.keyResourceLoader = keyResourceLoader; + } + + public MetadataEncryptor getMetadataEncryptor(KeyProvider backupKeyProvider, KeyProvider newKeyProvider) + { + DefaultEncryptor backupEncryptor = new DefaultEncryptor(); + backupEncryptor.setCipherProvider(null); // TODO parameterize + backupEncryptor.setCipherAlgorithm(cipherAlgorithm); + backupEncryptor.setKeyProvider(backupKeyProvider); + + DefaultEncryptor encryptor = new DefaultEncryptor(); + encryptor.setCipherProvider(null); // TODO parameterize + encryptor.setCipherAlgorithm(cipherAlgorithm); + encryptor.setKeyProvider(newKeyProvider); + + DefaultFallbackEncryptor fallbackEncryptor = new DefaultFallbackEncryptor(encryptor, backupEncryptor); + MetadataEncryptor metadataEncryptor = new MetadataEncryptor(); + metadataEncryptor.setEncryptor(fallbackEncryptor); + metadataEncryptor.setDictionaryService(dictionaryService); + return metadataEncryptor; + } + + protected KeyProvider getKeyProvider(final Map keys) + { + KeyProvider keyProvider = new KeyProvider() + { + @Override + public Key getKey(String keyAlias) + { + return keys.get(keyAlias); + } + }; + return keyProvider; + } + + protected void reencrypt(final MetadataEncryptor metadataEncryptor, final List properties) + { + final Iterator it = properties.iterator(); BatchProcessor.BatchProcessWorker worker = new BatchProcessor.BatchProcessWorker() { public String getIdentifier(NodePropertyEntity entity) @@ -135,22 +197,22 @@ public class ReEncryptor implements ApplicationContextAware { } - public void process(NodePropertyEntity entity) throws Throwable + public void process(final NodePropertyEntity entity) throws Throwable { - Object value = entity.getValue(); + NodePropertyValue nodePropValue = entity.getValue(); + // TODO check that we have the correct type i.e. can be cast to Serializable + Serializable value = nodePropValue.getSerializableValue(); if(value instanceof SealedObject) { SealedObject sealed = (SealedObject)value; - NodePropertyKey nodeKey = entity.getKey(); - QName propertyQName = qnameDAO.getQName(nodeKey.getQnameId()).getSecond(); + NodePropertyKey propertyKey = entity.getKey(); + QName propertyQName = qnameDAO.getQName(propertyKey.getQnameId()).getSecond(); - // metadataEncryptor uses a fallback encryptor; decryption will try the - // default (new) keys first (which will fail for properties created before the - // change in keys), followed by the backup keys. + // decrypt... Serializable decrypted = metadataEncryptor.decrypt(propertyQName, sealed); - // Re-encrypt. The new keys will be used. + // ...and then re-encrypt. The new keys will be used. Serializable resealed = metadataEncryptor.encrypt(propertyQName, decrypted); // TODO update resealed using batch update? @@ -159,15 +221,16 @@ public class ReEncryptor implements ApplicationContextAware } else { - // TODO + NodePropertyKey nodeKey = entity.getKey(); + QName propertyQName = qnameDAO.getQName(nodeKey.getQnameId()).getSecond(); + logger.warn("Encountered an encrypted property that is not a SealedObject, for node id " + + entity.getNodeId() + ", property " + propertyQName); } } }; BatchProcessWorkProvider provider = new BatchProcessWorkProvider() { - private int start = 0; - @Override public int getTotalEstimatedWorkSize() { @@ -177,46 +240,87 @@ public class ReEncryptor implements ApplicationContextAware @Override public Collection getNextWork() { - int end = start + 20; - if(end > properties.size()) + int count = 0; + List sublist = new ArrayList(chunkSize); + while(it.hasNext() && count < chunkSize) { - end = properties.size(); + sublist.add(it.next()); + count++; } - List sublist = properties.subList(start, end); - start += 20; + return sublist; } }; - // Migrate using 2 threads, 20 authorities per transaction. Log every 100 entries. - // TODO, propertize these numbers + // TODO, "propertize" these numbers new BatchProcessor( I18NUtil.getMessage(""), transactionHelper, provider, 2, 20, applicationContext, - logger, 100).process(worker, true); + logger, 20).process(worker, splitTxns); } - public void execute(KeyStoreParameters newEncryptionParameters) + /** + * Re-encrypt using the configured backup keystore to decrypt and the main keystore to encrypt + */ + public int reEncrypt() throws MissingKeyException, MissingKeyStoreException { - // Proceed only if fallback is available i.e. the systems has both old and new keys - if(metadataEncryptor.isFallbackAvailable()) + if(!backupKeyStoreParameters.isDefined()) { - QName model = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, namespaceDAO); - - // get properties that are encrypted - Collection propertyDefs = dictionaryDAO.getProperties(model, DataTypeDefinition.ENCRYPTED); - List properties = nodeDAO.getProperties(propertyDefs); - - // reencrypt these properties - reencrypt(newEncryptionParameters, properties); + throw new MissingKeyStoreException("Backup key store is not defined"); } - else + KeyProvider backupKeyProvider = getKeyProvider(backupKeyStoreParameters); + if(backupKeyProvider.getKey(KeyProvider.ALIAS_METADATA) == null) { - // TODO + throw new MissingKeyException("Unable to find the metadata key in backup key store. Does the backup key store exist?"); } + MetadataEncryptor metadataEncryptor = getMetadataEncryptor(backupKeyProvider, keyProvider); + return reEncrypt(metadataEncryptor); + } + + /** + * 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. + * + * Note: it is the responsibility of the end user to ensure that the keystore configured by newKeyStoreParameters is + * placed in the repository keystore directory. This can be done while the repository is running and it will be picked + * up automatically the next time the repository restarts. + */ + public int reEncrypt(KeyStoreParameters parameters) + { + KeyProvider newKeyProvider = getKeyProvider(parameters); + return reEncrypt(newKeyProvider); + } + + public int reEncrypt(KeyProvider newKeyProvider) + { + MetadataEncryptor metadataEncryptor = getMetadataEncryptor(keyProvider, newKeyProvider); + return reEncrypt(metadataEncryptor); + } + + protected int reEncrypt(MetadataEncryptor metadataEncryptor) + { + // get properties that are encrypted + Collection propertyDefs = dictionaryDAO.getPropertiesOfDataType(DataTypeDefinition.ENCRYPTED); + // TODO use callback mechanism + List properties = nodeDAO.selectProperties(propertyDefs); + + if(logger.isDebugEnabled()) + { + logger.debug("Found " + properties.size() + " properties to re-encrypt..."); + } + + // reencrypt these properties + reencrypt(metadataEncryptor, properties); + + if(logger.isDebugEnabled()) + { + logger.debug("...done re-encrypting."); + } + + return properties.size(); } @Override diff --git a/source/java/org/alfresco/encryption/keystore-parameters.properties b/source/java/org/alfresco/encryption/keystore-parameters.properties new file mode 100644 index 0000000000..7a200acec8 --- /dev/null +++ b/source/java/org/alfresco/encryption/keystore-parameters.properties @@ -0,0 +1,5 @@ +keystore.password=mp6yc0UD9e +aliases=test +test.password=abc +test.keyData=lhfDmkonev3wRI3RnxFuVcUOh2bSnB2Y +test.algorithm=DESede \ No newline at end of file diff --git a/source/java/org/alfresco/encryption/reencryption_model.xml b/source/java/org/alfresco/encryption/reencryption_model.xml new file mode 100644 index 0000000000..4850508c10 --- /dev/null +++ b/source/java/org/alfresco/encryption/reencryption_model.xml @@ -0,0 +1,48 @@ + + + Alfresco Content Model + Alfresco + 2011-08-24 + 1.0 + + + + + + + + + + + + + + + + + + Base + The Base Type + + + + + d:encrypted + false + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/domain/node/NodeDAO.java b/source/java/org/alfresco/repo/domain/node/NodeDAO.java index 4d3a17e765..39f21ee90d 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeDAO.java +++ b/source/java/org/alfresco/repo/domain/node/NodeDAO.java @@ -671,5 +671,11 @@ public interface NodeDAO extends NodeBulkLoader */ public void setNodeDefiningAclId(Long nodeId, long id); - public List getProperties(Collection propertyDefs); + /** + * Used by the re-encryptor to re-encrypt encryptable properties with a new encryption key. + * + * @param propertyDefs + * @return + */ + public List selectProperties(Collection propertyDefs); } 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 61f2f72fba..b8d384dc7f 100644 --- a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java @@ -91,7 +91,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl private static final String SELECT_NODES_BY_UUIDS = "alfresco.node.select_NodesByUuids"; private static final String SELECT_NODES_BY_IDS = "alfresco.node.select_NodesByIds"; private static final String SELECT_NODE_PROPERTIES = "alfresco.node.select_NodeProperties"; - private static final String SELECT_PROPERTIES_BY_TYPE = "alfresco.node.select_PropertiesByType"; + private static final String SELECT_PROPERTIES_BY_TYPES = "alfresco.node.select_PropertiesByTypes"; private static final String SELECT_NODE_ASPECTS = "alfresco.node.select_NodeAspects"; private static final String INSERT_NODE_PROPERTY = "alfresco.node.insert.insert_NodeProperty"; private static final String UPDATE_PRIMARY_CHILDREN_SHARED_ACL = "alfresco.node.update.update_PrimaryChildrenSharedAcl"; @@ -514,7 +514,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl default: prop.setQnameIds(new ArrayList(qnameIds)); } - + List rows = (List) template.selectList(SELECT_NODE_PROPERTIES, prop); Map> results = makePersistentPropertiesMap(rows); Map props = results.get(nodeId); @@ -1521,20 +1521,25 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl } } - // TODO - use a callback approach - public List getProperties(Collection propertyDefs) + @Override + public List selectProperties(Collection propertyDefs) { Set qnames = new HashSet(); for(PropertyDefinition propDef : propertyDefs) { qnames.add(propDef.getName()); } - + + // TODO use callback approach final List props = new ArrayList(); // qnames of properties that are encrypted Set qnameIds = qnameDAO.convertQNamesToIds(qnames, false); - template.select(SELECT_PROPERTIES_BY_TYPE, qnameIds, new ResultHandler() + // TODO - use a callback approach + + IdsEntity param = new IdsEntity(); + param.setIds(new ArrayList(qnameIds)); + template.select(SELECT_PROPERTIES_BY_TYPES, param, new ResultHandler() { @Override public void handleResult(ResultContext context) @@ -1542,7 +1547,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl props.add((NodePropertyEntity)context.getResultObject()); } }); - + return props; } diff --git a/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java b/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java index 9540a238ab..377286be8d 100644 --- a/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java +++ b/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java @@ -1,7 +1,6 @@ package org.alfresco.repo.node.encryption; import java.io.Serializable; -import java.security.InvalidKeyException; import java.security.KeyException; import java.util.HashMap; import java.util.HashSet; @@ -11,7 +10,6 @@ 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; @@ -223,23 +221,4 @@ public class MetadataEncryptor // Done return outbound; } - - public Serializable reencrypt(QName propertyQName, Serializable sealed) throws InvalidKeyException - { - // metadataEncryptor uses a fallback encryptor; decryption will try the - // default (new) keys first (which will fail for properties created before the - // change in keys), followed by the backup keys. - Serializable decrypted = decrypt(propertyQName, sealed); - - // Re-encrypt. The new keys will be used. - Serializable resealed = encrypt(propertyQName, decrypted); - - return resealed; - } - - public boolean isFallbackAvailable() - { - return false; -// return encryptor.isFallbackAvailable(); - } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java index 953a8c90e4..6498fe57da 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java @@ -44,8 +44,11 @@ import org.alfresco.service.cmr.security.PermissionService; import org.apache.commons.codec.net.URLCodec; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.params.HttpClientParams; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONArray; @@ -69,8 +72,6 @@ public class SolrQueryHTTPClient private Map storeMappings; - private String solrHost; - private int solrPort; private String baseUrl; private HttpClient httpClient; @@ -83,14 +84,13 @@ public class SolrQueryHTTPClient public void init() { StringBuilder sb = new StringBuilder(); -// sb.append("http://"); -// sb.append(solrHost); -// sb.append(":"); -// sb.append(solrPort); sb.append("/solr"); this.baseUrl = sb.toString(); - httpClient = httpClientFactory.getHttpClient(solrHost, solrPort); + httpClient = httpClientFactory.getHttpClient(); + HttpClientParams params = httpClient.getParams(); + params.setBooleanParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, true); + httpClient.getState().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), new UsernamePasswordCredentials("admin", "admin")); } public void setHttpClientFactory(HttpClientFactory httpClientFactory) @@ -118,46 +118,6 @@ public class SolrQueryHTTPClient this.storeMappings = storeMappings; } - public void setSolrHost(String solrHost) - { - this.solrHost = solrHost; - } - - public void setSolrPort(int solrPort) - { - this.solrPort = solrPort; - } - -// public void setBaseUrl(String baseUrl) -// { -// this.baseUrl = baseUrl; -// } - -// public void setKeyStoreLocation(String keyStoreLocation) -// { -// this.keyStoreLocation = keyStoreLocation; -// } -// -// public void setTrustStoreLocation(String trustStoreLocation) -// { -// this.trustStoreLocation = trustStoreLocation; -// } -// -// public void setKeyStoreType(String keyStoreType) -// { -// this.keyStoreType = keyStoreType; -// } -// -// public void setTrustStoreType(String trustStoreType) -// { -// this.trustStoreType = trustStoreType; -// } -// -// public void setPasswordFileLocation(String passwordFileLocation) -// { -// this.passwordFileLocation = passwordFileLocation; -// } - public ResultSet executeQuery(SearchParameters searchParameters, String language) { try diff --git a/source/java/org/alfresco/repo/solr/SOLRAdminClient.java b/source/java/org/alfresco/repo/solr/SOLRAdminClient.java index bc52ba86a0..a6a93aa17f 100644 --- a/source/java/org/alfresco/repo/solr/SOLRAdminClient.java +++ b/source/java/org/alfresco/repo/solr/SOLRAdminClient.java @@ -61,6 +61,7 @@ public class SOLRAdminClient implements ApplicationEventPublisherAware { private String solrHost; private int solrPort; + private int solrSSLPort; private String solrUrl; private String solrUser; private String solrPassword; @@ -87,6 +88,11 @@ public class SOLRAdminClient implements ApplicationEventPublisherAware this.solrPort = Integer.parseInt(solrPort); } + public void setSolrsslPort(int solrSSLPort) + { + this.solrSSLPort = solrSSLPort; + } + public void setSolrUser(String solrUser) { this.solrUser = solrUser; @@ -118,32 +124,14 @@ public class SOLRAdminClient implements ApplicationEventPublisherAware this.httpClientFactory = httpClientFactory; } -// protected HttpClient getHttpClient() -// { -// return httpClientFactory.getHttpClient(solrHost, solrPort); -//// HttpClient httpClient = new HttpClient(); -//// -//// HttpClientParams params = httpClient.getParams(); -//// params.setBooleanParameter("http.tcp.nodelay", true); -//// params.setBooleanParameter("http.connection.stalecheck", false); -//// -//// ProtocolSocketFactory socketFactory = new AuthSSLProtocolSocketFactory( -//// keyResourceLoader, encryptionParameters); -//// Protocol myhttps = new Protocol("https", socketFactory, 8843); -//// httpClient.getHostConfiguration().setHost(solrHost, 8080, myhttps); -//// -//// return httpClient; -// } - public void init() { ParameterCheck.mandatory("solrHost", solrHost); ParameterCheck.mandatory("solrPort", solrPort); + ParameterCheck.mandatory("solrUser", solrUser); ParameterCheck.mandatory("solrPassword", solrPassword); ParameterCheck.mandatory("solrPingCronExpression", solrPingCronExpression); - ParameterCheck.mandatory("solrPort", solrPort); ParameterCheck.mandatory("solrConnectTimeout", solrConnectTimeout); - ParameterCheck.mandatory("solrUser", solrUser); try { @@ -151,10 +139,10 @@ public class SOLRAdminClient implements ApplicationEventPublisherAware sb.append(httpClientFactory.isSSL() ? "https://" : "http://"); sb.append(solrHost); sb.append(":"); - sb.append(solrPort); + sb.append(httpClientFactory.isSSL() ? solrSSLPort: solrPort); sb.append("/solr"); this.solrUrl = sb.toString(); - HttpClient httpClient = httpClientFactory.getHttpClient(solrHost, solrPort); + HttpClient httpClient = httpClientFactory.getHttpClient(); server = new CommonsHttpSolrServer(solrUrl, httpClient); // TODO remove credentials because we're using SSL?