Refactoring to support ALF-9510, ALF-8702

ALF-8702: Solr-Repository SSL Communications (see solr/source/solr/instance/HowToSetUpSolr.txt
ALF-9510: Initial checkin

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@30005 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Steven Glover
2011-08-23 18:34:15 +00:00
parent 6f73e4153c
commit f7f23f6eb7
22 changed files with 1109 additions and 269 deletions

View File

@@ -7,9 +7,9 @@
<import resource="classpath:alfresco/cache-context.xml" /> <import resource="classpath:alfresco/cache-context.xml" />
<import resource="classpath:alfresco/st-context.xml"/> <import resource="classpath:alfresco/st-context.xml"/>
<import resource="classpath*:alfresco/extension/mt/mt-context.xml"/> <import resource="classpath*:alfresco/extension/mt/mt-context.xml"/>
<import resource="classpath:alfresco/encryption-context.xml" />
<import resource="classpath:alfresco/core-services-context.xml" /> <import resource="classpath:alfresco/core-services-context.xml" />
<import resource="classpath:alfresco/copy-services-context.xml" /> <import resource="classpath:alfresco/copy-services-context.xml" />
<import resource="classpath:alfresco/encryption-context.xml" />
<import resource="classpath:alfresco/public-services-context.xml" /> <import resource="classpath:alfresco/public-services-context.xml" />
<import resource="classpath:alfresco/model-specific-services-context.xml" /> <import resource="classpath:alfresco/model-specific-services-context.xml" />
<import resource="classpath:alfresco/action-services-context.xml" /> <import resource="classpath:alfresco/action-services-context.xml" />

View File

@@ -168,16 +168,16 @@
</property> </property>
</bean> </bean>
<bean id="encryptionChecker" class="org.alfresco.encryption.EncryptionChecker"> <bean id="keyStoreChecker" class="org.alfresco.encryption.KeyStoreChecker">
<property name="transactionService" ref="transactionService"/> <property name="transactionService" ref="transactionService"/>
<property name="attributeService" ref="attributeService"/> <property name="attributeService" ref="attributeService"/>
<property name="encryptor" ref="encryptor"/> <property name="encryptor" ref="encryptor"/>
<property name="keyAliases"> </bean>
<list>
<value>metadata</value> <bean id="encryptionChecker" class="org.alfresco.encryption.EncryptionChecker">
<value>solr</value> <property name="keyStoreParameters" ref="keyStoreParameters"/>
</list> <property name="keyResourceLoader" ref="springKeyResourceLoader"/>
</property> <property name="keyStoreChecker" ref="keyStoreChecker"/>
</bean> </bean>
<!-- node indexing for ADM --> <!-- node indexing for ADM -->

View File

@@ -1012,14 +1012,22 @@
</property> </property>
</bean> </bean>
<bean id="solrHttpClientFactory" class="org.alfresco.httpclient.HttpClientFactory">
<property name="secureCommsType" value="${solr.secureComms}"/>
<property name="sSLEncryptionParameters" ref="sslEncryptionParameters"/>
<property name="keyResourceLoader" ref="springKeyResourceLoader"/>
<property name="keyStoreParameters" ref="keyStoreParameters"/>
<property name="encryptionParameters" ref="md5EncryptionParameters"/>
</bean>
<bean id="solrAdminClient" class="org.alfresco.repo.solr.SOLRAdminClient" init-method="init" destroy-method="shutdown"> <bean id="solrAdminClient" class="org.alfresco.repo.solr.SOLRAdminClient" init-method="init" destroy-method="shutdown">
<property name="solrHost" value="${solr.solrHost}"/> <property name="solrHost" value="${solr.host}"/>
<property name="solrPort" value="${solr.solrAdminPort}"/> <property name="solrPort" value="${solr.port}"/>
<property name="solrUrl" value="${solr.solrUrl}"/>
<property name="solrUser" value="${solr.solrUser}"/> <property name="solrUser" value="${solr.solrUser}"/>
<property name="solrPassword" value="${solr.solrPassword}"/> <property name="solrPassword" value="${solr.solrPassword}"/>
<property name="solrPingCronExpression" value="${solr.solrPingCronExpression}"/> <property name="solrPingCronExpression" value="${solr.solrPingCronExpression}"/>
<property name="solrConnectTimeout" value="${solr.solrConnectTimeout}"/> <property name="solrConnectTimeout" value="${solr.solrConnectTimeout}"/>
<property name="httpClientFactory" ref="solrHttpClientFactory"/>
</bean> </bean>
<bean id="solr" class="org.alfresco.repo.management.subsystems.ChildApplicationContextFactory" parent="abstractPropertyBackedBean"> <bean id="solr" class="org.alfresco.repo.management.subsystems.ChildApplicationContextFactory" parent="abstractPropertyBackedBean">

View File

@@ -9,33 +9,81 @@
http://code.google.com/p/spring-crypto-utils/schema/crypt http://code.google.com/p/spring-crypto-utils/schema/crypt
http://code.google.com/p/spring-crypto-utils/schema/crypt.xsd"> http://code.google.com/p/spring-crypto-utils/schema/crypt.xsd">
<!-- Beans to initilize encryption -->
<bean id="sslKeyStoreParameters" class="org.alfresco.encryption.KeyStoreParameters">
<property name="location" value="${encryption.ssl.keystore.location}"/>
<property name="type" value="${encryption.ssl.keystore.type}"/>
<property name="provider" value="${encryption.ssl.keystore.provider}"/>
<property name="keyMetaDataFileLocation" value="${encryption.ssl.keystore.keyMetaData.location}"/>
</bean>
<bean id="sslTrustStoreParameters" class="org.alfresco.encryption.KeyStoreParameters">
<property name="location" value="${encryption.ssl.truststore.location}"/>
<property name="type" value="${encryption.ssl.truststore.type}"/>
<property name="provider" value="${encryption.ssl.truststore.provider}"/>
<property name="keyMetaDataFileLocation" value="${encryption.ssl.truststore.keyMetaData.location}"/>
</bean>
<bean id="sslEncryptionParameters" class="org.alfresco.encryption.ssl.SSLEncryptionParameters">
<property name="keyStoreParameters" ref="sslKeyStoreParameters"/>
<property name="trustStoreParameters" ref="sslTrustStoreParameters"/>
</bean>
<bean id="md5EncryptionParameters" class="org.alfresco.httpclient.MD5EncryptionParameters">
<property name="cipherAlgorithm" value="${encryption.cipherAlgorithm}"/>
<property name="messageTimeout" value="${encryption.mac.messageTimeout}"/>
<property name="macAlgorithm" value="${encryption.mac.algorithm}"/>
</bean>
<bean id="springKeyResourceLoader" class="org.alfresco.encryption.SpringKeyResourceLoader"> <bean id="springKeyResourceLoader" class="org.alfresco.encryption.SpringKeyResourceLoader">
</bean> </bean>
<!-- Beans to initilize encryption --> <bean id="keyStoreParameters" class="org.alfresco.encryption.KeyStoreParameters">
<bean id="keyProvider" class="org.alfresco.encryption.KeystoreKeyProvider" init-method="init">
<property name="location" value="${encryption.keystore.location}"/> <property name="location" value="${encryption.keystore.location}"/>
<property name="keyResourceLoader" ref="springKeyResourceLoader"/>
<property name="provider" value="${encryption.keystore.provider}"/> <property name="provider" value="${encryption.keystore.provider}"/>
<property name="type" value="${encryption.keystore.type}"/> <property name="type" value="${encryption.keystore.type}"/>
<property name="passwordsFileLocation" value="${encryption.keystore.passwordsFile.location}"/> <property name="keyMetaDataFileLocation" value="${encryption.keystore.keyMetaData.location}"/>
</bean>
<bean id="keyProvider" class="org.alfresco.encryption.KeystoreKeyProvider" init-method="init">
<property name="keyStoreParameters" ref="keyStoreParameters"/>
<property name="keyResourceLoader" ref="springKeyResourceLoader"/>
</bean> </bean>
<bean id="encryptor" class="org.alfresco.encryption.DefaultEncryptor" init-method="init"> <bean id="encryptor" class="org.alfresco.encryption.DefaultEncryptor" init-method="init">
<property name="keyProvider" ref="keyProvider"/> <property name="keyProvider" ref="keyProvider"/>
<property name="cipherAlgorithm" value="${encryption.cipherAlgorithm}" />
</bean>
<!--
<bean id="reEncryptor" class="org.alfresco.encryption.ReEncryptor" init-method="init">
<property name="keyProvider" ref="keyProvider"/>
<property name="cipherAlgorithm" value="${encryption.cipherAlgorithm}" />
</bean>
-->
<!-- Fallback encryptor - used by the re-encryptor -->
<!--
<bean id="backupKeyStoreParameters" class="org.alfresco.encryption.KeyStoreParameters">
<property name="location" value="${encryption.keystore.fallback.location}"/>
<property name="provider" value="${encryption.keystore.fallback.provider}"/>
<property name="type" value="${encryption.keystore.fallback.type}"/>
<property name="keyMetaDataFileLocation" value="${encryption.keystore.fallback.keyMetaData.location}"/>
</bean>
<bean id="backupKeyProvider" class="org.alfresco.encryption.KeystoreKeyProvider" init-method="init">
<property name="keyStoreParameters" ref="backupKeyStoreParameters"/>
<property name="keyResourceLoader" ref="springKeyResourceLoader"/>
</bean>
<bean id="backupEncryptor" class="org.alfresco.encryption.DefaultEncryptor" init-method="init">
<property name="keyProvider" ref="backupKeyProvider"/>
<property name="cipherAlgorithm" value="${encryption.encryption.cipherAlgorithm}" /> <property name="cipherAlgorithm" value="${encryption.encryption.cipherAlgorithm}" />
</bean> </bean>
<bean id="macUtils" class="org.alfresco.encryption.MACUtils"> <bean id="fallbackEncryptor" class="org.alfresco.encryption.DefaultFallbackEncryptor">
<property name="keyProvider" ref="keyProvider"/> <property name="fallback" ref="backupEncryptor"/>
<property name="macAlgorithm" value="${encryption.macAlgorithm}"/> <property name="main" ref="encryptor" />
</bean> </bean>
-->
<bean id="encryptionUtils" class="org.alfresco.encryption.DefaultEncryptionUtils">
<property name="encryptor" ref="encryptor"/>
<property name="macUtils" ref="macUtils"/>
<property name="messageTimeout" value="${encryption.messageTimeout}"/>
<property name="remoteIP" value="${solr.solrHost}" />
</bean>
</beans> </beans>

Binary file not shown.

View File

@@ -0,0 +1,20 @@
keystore.password=mp6yc0UD9e
aliases=metadata,solr
# The password protecting the keystore entries
keystore.password=mp6yc0UD9e
# The password protecting the alias: metadata
metadata.seed=
metadata.algorithm=PBEWithMD5AndDES
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

View File

@@ -0,0 +1,7 @@
aliases=ssl.alfresco.ca,ssl.repo
# The ssl keystore password
keystore.password=kT9X6oe68t
# The password protecting the ssl repository key
ssl.repo.password=kT9X6oe68t
# The password protecting the ssl Alfresco CA key
ssl.alfresco.ca.password=kT9X6oe68t

View File

@@ -0,0 +1,5 @@
aliases=alfresco.ca
# The ssl truststore password
keystore.password=kT9X6oe68t
# The password protecting the ssl Alfresco CA strust certificate
alfresco.ca.password=kT9X6oe68t

Binary file not shown.

Binary file not shown.

View File

@@ -673,21 +673,44 @@ deployment.filesystem.default.metadatadir=${deployment.filesystem.metadatadir}/d
# #
# Encryption properties # Encryption properties
# #
encryption.encryption.cipherAlgorithm=DESede/CBC/PKCS5Padding #dir.keystore=${dir.root}/keystore
encryption.keystore.location=classpath:alfresco/.keystore dir.keystore=classpath:alfresco/keystore
encryption.keystore.passwordsFile.location=classpath:alfresco/keystore-passwords.properties encryption.cipherAlgorithm=DESede/CBC/PKCS5Padding
encryption.keystore.location=${dir.keystore}/keystore
encryption.keystore.keyMetaData.location=${dir.keystore}/keystore-passwords.properties
encryption.keystore.provider= encryption.keystore.provider=
encryption.keystore.type=JCEKS encryption.keystore.type=JCEKS
encryption.messageTimeout=30000 #encryption.keystore.keyAlgorithm=PBEWithMD5AndDES
encryption.macAlgorithm=HmacSHA1 encryption.keystore.keyAlgorithm=DESede
encryption.keystore.keyAliases=metadata, solr
# SOLR connection details encryption.keystore.fallback.location=${dir.keystore}/fallback-keystore
solr.solrHost=localhost encryption.keystore.fallback.keyMetaData.location=${dir.keystore}/fallback-keystore-passwords.properties
solr.solrAdminPort=8834 encryption.keystore.fallback.provider=
solr.solrUrl=http://${solr.solrHost}:8080/solr encryption.keystore.fallback.type=JCEKS
# mac
encryption.mac.messageTimeout=30000
encryption.mac.algorithm=HmacSHA1
# ssl
encryption.ssl.keystore.location=${dir.keystore}/ssl.keystore
encryption.ssl.keystore.provider=
encryption.ssl.keystore.type=JCEKS
encryption.ssl.keystore.keyMetaData.location=${dir.keystore}/ssl-keystore-passwords.properties
encryption.ssl.truststore.location=${dir.keystore}/ssl.truststore
encryption.ssl.truststore.provider=
encryption.ssl.truststore.type=JCEKS
encryption.ssl.truststore.keyMetaData.location=${dir.keystore}/ssl-truststore-passwords.properties
# SOLR connection details (e.g. for JMX)
solr.host=localhost
solr.port=8443
solr.solrUser=solr solr.solrUser=solr
solr.solrPassword=solr solr.solrPassword=solr
solr.secureComms.enabled=true # none, https
solr.secureComms=https
# ms # ms
solr.solrConnectTimeout=5000 solr.solrConnectTimeout=5000
solr.solrPingCronExpression=0 0/5 * * * ? * solr.solrPingCronExpression=0 0/5 * * * ? *

View File

@@ -29,10 +29,36 @@
</property> </property>
</bean> </bean>
<bean id="search.solrQueryHTTPCLient" class="org.alfresco.repo.search.impl.solr.SolrQueryHTTPClient" > <bean id="search.solrQueryHTTPCLient" class="org.alfresco.repo.search.impl.solr.SolrQueryHTTPClient" init-method="init">
<property name="solrHost">
<value>${solr.host}</value>
</property>
<property name="solrPort">
<value>${solr.port}</value>
</property>
<property name="httpClientFactory" ref="solrHttpClientFactory"/>
<!--
<property name="baseUrl"> <property name="baseUrl">
<value>${solr.base.url}</value> <value>${solr.base.url}</value>
</property> </property>
-->
<!--
<property name="keyStoreLocation">
<value>${encryption.ssl.keystore.location}</value>
</property>
<property name="keyStoreType">
<value>${encryption.ssl.keystore.type}</value>
</property>
<property name="trustStoreLocation">
<value>${encryption.ssl.truststore.location}</value>
</property>
<property name="trustStoreType">
<value>${encryption.ssl.truststore.type}</value>
</property>
<property name="passwordFileLocation">
<value>${solr.ssl.passwordsFile.location}</value>
</property>
-->
<property name="nodeDAO"> <property name="nodeDAO">
<ref bean="nodeDAO" /> <ref bean="nodeDAO" />
</property> </property>

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
/**
*
* @since 4.0
*
*/
public class BootstrapReEncryptor extends AbstractLifecycleBean
{
private boolean enabled;
private ReEncryptor reEncryptor;
private KeyStoreParameters oldKeyStoreParameters;
public void setEnabled(boolean enabled)
{
this.enabled = enabled;
}
public void setReEncryptor(ReEncryptor reEncryptor)
{
this.reEncryptor = reEncryptor;
}
public void setOldKeyStoreParameters(KeyStoreParameters oldKeyStoreParameters)
{
this.oldKeyStoreParameters = oldKeyStoreParameters;
}
public void reEncrypt()
{
reEncryptor.execute(oldKeyStoreParameters);
}
@Override
protected void onBootstrap(ApplicationEvent event)
{
if(enabled)
{
reEncrypt();
}
}
@Override
protected void onShutdown(ApplicationEvent event)
{
}
}

View File

@@ -18,17 +18,6 @@
*/ */
package org.alfresco.encryption; package org.alfresco.encryption;
import java.io.Serializable;
import java.security.InvalidKeyException;
import java.util.List;
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.transaction.TransactionService;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.GUID;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;
@@ -38,6 +27,7 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
* The EncryptionChecker checks the state of the repository's encryption system. * The EncryptionChecker checks the state of the repository's encryption system.
* In particular it checks: * In particular it checks:
* <ul> * <ul>
* <li> that the keystore exists and, if not, creates one.
* <li> that the encryption keys have not been changed. If so, the bootstrap will be halted. * <li> that the encryption keys have not been changed. If so, the bootstrap will be halted.
* </ul> * </ul>
* *
@@ -47,96 +37,31 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
public class EncryptionChecker extends AbstractLifecycleBean public class EncryptionChecker extends AbstractLifecycleBean
{ {
private static Log logger = LogFactory. getLog(EncryptionChecker.class); private static Log logger = LogFactory. getLog(EncryptionChecker.class);
public static String TOP_LEVEL_KEY = "keyCheck";
private static enum KEY_STATUS
{
OK, CHANGED, MISSING;
};
private TransactionService transactionService; private KeyStoreChecker keyStoreChecker;
private AttributeService attributeService; private KeyStoreParameters keyStoreParameters;
private Encryptor encryptor; private KeyResourceLoader keyResourceLoader;
private List<String> keyAliases;
public void setTransactionService(TransactionService transactionService) public void setkeyStoreParameters(KeyStoreParameters keyStoreParameters)
{ {
this.transactionService = transactionService; this.keyStoreParameters = keyStoreParameters;
} }
public void setAttributeService(AttributeService attributeService) public void setKeyStoreChecker(KeyStoreChecker keyStoreChecker)
{ {
this.attributeService = attributeService; this.keyStoreChecker = keyStoreChecker;
} }
public void setKeyAliases(List<String> keyAliases) public void setKeyResourceLoader(KeyResourceLoader keyResourceLoader)
{ {
this.keyAliases = keyAliases; this.keyResourceLoader = keyResourceLoader;
}
public void setEncryptor(Encryptor encryptor)
{
this.encryptor = encryptor;
}
private void removeKey(String keyAlias)
{
attributeService.removeAttributes(TOP_LEVEL_KEY);
logger.info("Removed registered key " + keyAlias);
}
private 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
{
// 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);
return KEY_STATUS.MISSING;
}
} }
@Override @Override
protected void onBootstrap(ApplicationEvent event) protected void onBootstrap(ApplicationEvent event)
{ {
RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper(); AlfrescoKeyStore mainKeyStore = new CachingKeyStore(keyStoreParameters, keyResourceLoader);
final RetryingTransactionCallback<Void> checkKeysCallback = new RetryingTransactionCallback<Void>() keyStoreChecker.checkKeyStore(mainKeyStore);
{
public Void execute() throws Throwable
{
for(String keyAlias : keyAliases)
{
KEY_STATUS keyStatus = checkKey(keyAlias);
if(keyStatus == KEY_STATUS.CHANGED)
{
// Note: this will halt the application bootstrap.
throw new AlfrescoRuntimeException("The key with alias " + keyAlias + " has been changed, re-instate the previous keystore");
}
}
return null;
}
};
retryingTransactionHelper.doInTransaction(checkKeysCallback, false);
} }
@Override @Override
@@ -144,70 +69,4 @@ public class EncryptionChecker extends AbstractLifecycleBean
{ {
} }
public void removeRegisteredKeys()
{
RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper();
final RetryingTransactionCallback<Void> removeKeysCallback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
for(String keyAlias : keyAliases)
{
removeKey(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());
}
}
} }

View File

@@ -0,0 +1,353 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.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.
*
* @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;
public KeyStoreChecker()
{
}
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 KeyStoreChecker(AttributeService attributeService, Encryptor encryptor, TransactionService transactionService)
{
this.attributeService = attributeService;
this.transactionService = transactionService;
this.encryptor = encryptor;
}
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)
{
// 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);
}
protected KeysReport getKeysReport(AlfrescoKeyStore keyStore)
{
final List<String> registeredKeys = new ArrayList<String>();
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<String> keyAliasesChanged = new ArrayList<String>();
List<String> keyAliasesUnchanged = new ArrayList<String>();
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)
{
RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper();
final RetryingTransactionCallback<Void> checkKeysCallback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
KeysReport keysReport = getKeysReport(keyStore);
// 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)
{
// 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");
}
// no keys found, create a new keystore
// TODO
if(logger.isDebugEnabled())
{
logger.debug("Keystore not found, creating...");
}
createKeyStore(keyStore);
}
return null;
}
};
retryingTransactionHelper.doInTransaction(checkKeysCallback, false);
}
protected void createKeyStore(AlfrescoKeyStore keyStore)
{
new CreatingKeyStore(keyStore).create();
}
public static class KeysReport
{
private List<String> keysChanged;
private List<String> keysUnchanged;
public KeysReport(List<String> keysChanged, List<String> keysUnchanged)
{
super();
this.keysChanged = keysChanged;
this.keysUnchanged = keysUnchanged;
}
public List<String> getKeysChanged()
{
return keysChanged;
}
public List<String> 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<Void> removeKeysCallback = new RetryingTransactionCallback<Void>()
{
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<String, CachingKeyStore.KeyInformation> 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<String, CachingKeyStore.KeyInformation> 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();
}
}
}
}
}

View File

@@ -18,12 +18,15 @@
*/ */
package org.alfresco.encryption; package org.alfresco.encryption;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.Key; import java.security.Key;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.UnrecoverableKeyException; import java.security.UnrecoverableKeyException;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Properties;
import junit.framework.TestCase; import junit.framework.TestCase;
@@ -55,21 +58,71 @@ public class KeyStoreKeyProviderTest extends TestCase
/* package */ static KeystoreKeyProvider getTestKeyStoreProvider() /* package */ static KeystoreKeyProvider getTestKeyStoreProvider()
{ {
Map<String, String> passwords = new HashMap<String, String>(5); Map<String, String> passwords = new HashMap<String, String>(5);
passwords.put(KeystoreKeyProvider.KEY_KEYSTORE_PASSWORD, "ksPwd2"); passwords.put(AlfrescoKeyStore.KEY_KEYSTORE_PASSWORD, "ksPwd2");
passwords.put(ALIAS_ONE, "aliasPwd1"); passwords.put(ALIAS_ONE, "aliasPwd1");
passwords.put(ALIAS_TWO, "aliasPwd2"); passwords.put(ALIAS_TWO, "aliasPwd2");
KeystoreKeyProvider ks = new KeystoreKeyProvider( KeyStoreParameters encryptionParameters = new KeyStoreParameters("JCEKS", "SunJCE", null, FILE_TWO);
FILE_TWO, KeystoreKeyProvider keyProvider = new KeystoreKeyProvider(encryptionParameters, getKeyStoreLoader(passwords));
getKeyStoreLoader(), // FILE_TWO,
"SunJCE", // getKeyStoreLoader(),
"JCEKS", // "SunJCE",
passwords); // "JCEKS",
return ks; // passwords);
return keyProvider;
} }
protected static KeyResourceLoader getKeyStoreLoader() /* package */ static KeystoreKeyProvider getTestKeyStoreProvider(String keyStoreLocation, Map<String, String> passwords)
{ {
return new SpringKeyResourceLoader(); // Map<String, String> passwords = new HashMap<String, String>(5);
// passwords.put(KeyStoreManager.KEY_KEYSTORE_PASSWORD, "ksPwd2");
// passwords.put(ALIAS_ONE, "aliasPwd1");
// passwords.put(ALIAS_TWO, "aliasPwd2");
KeyStoreParameters encryptionParameters = new KeyStoreParameters("JCEKS", "SunJCE", null, keyStoreLocation);
KeystoreKeyProvider keyProvider = new KeystoreKeyProvider(encryptionParameters, getKeyStoreLoader(passwords));
// FILE_TWO,
// getKeyStoreLoader(),
// "SunJCE",
// "JCEKS",
// passwords);
return keyProvider;
}
private static class TestKeyResourceLoader extends SpringKeyResourceLoader
{
private Properties props;
TestKeyResourceLoader(Map<String, String> passwords)
{
StringBuilder aliases = new StringBuilder();
props = new Properties();
int i = 0;
for(Map.Entry<String, String> password : passwords.entrySet())
{
props.put(password.getKey() + ".password", password.getValue());
aliases.append(password.getKey());
if(i < passwords.size() - 1)
{
aliases.append(",");
i++;
}
}
props.put("aliases", aliases.toString());
}
@Override
public Properties loadKeyMetaData(String keyMetaDataFileLocation)
throws IOException, FileNotFoundException
{
return props;
}
}
protected static KeyResourceLoader getKeyStoreLoader(Map<String, String> passwords)
{
return new TestKeyResourceLoader(passwords);
} }
public void setUp() throws Exception public void setUp() throws Exception
@@ -78,24 +131,28 @@ public class KeyStoreKeyProviderTest extends TestCase
public void testNoKeyStorePasswords() throws Exception public void testNoKeyStorePasswords() throws Exception
{ {
KeystoreKeyProvider keyProvider = new KeystoreKeyProvider( KeystoreKeyProvider keyProvider = getTestKeyStoreProvider(FILE_ONE, Collections.<String,String>emptyMap());
FILE_ONE,
getKeyStoreLoader(), // KeystoreKeyProvider keyProvider = new KeystoreKeyProvider(
"SunJCE", // FILE_ONE,
"JCEKS", // getKeyStoreLoader(),
Collections.<String,String>emptyMap()); // "SunJCE",
// "JCEKS",
// Collections.<String,String>emptyMap());
// This has succeeded because we have not attempted to access it // This has succeeded because we have not attempted to access it
assertNull("Should be no keys available", keyProvider.getKey(ALIAS_ONE)); assertNull("Should be no keys available", keyProvider.getKey(ALIAS_ONE));
} }
public void testKeyStoreWithOnlyAliasPasswords() throws Exception public void testKeyStoreWithOnlyAliasPasswords() throws Exception
{ {
KeystoreKeyProvider keyProvider = new KeystoreKeyProvider( KeystoreKeyProvider keyProvider = getTestKeyStoreProvider(FILE_ONE, Collections.singletonMap(ALIAS_ONE, "aliasPwd1"));
FILE_TWO,
getKeyStoreLoader(), // KeystoreKeyProvider keyProvider = new KeystoreKeyProvider(
"SunJCE", // FILE_TWO,
"JCEKS", // getKeyStoreLoader(),
Collections.singletonMap(ALIAS_ONE, "aliasPwd1")); // "SunJCE",
// "JCEKS",
// Collections.singletonMap(ALIAS_ONE, "aliasPwd1"));
// This has succeeded because we have not attempted to access it // This has succeeded because we have not attempted to access it
assertNotNull("Should be able to key alias with same password", keyProvider.getKey(ALIAS_ONE)); assertNotNull("Should be able to key alias with same password", keyProvider.getKey(ALIAS_ONE));
} }
@@ -104,12 +161,14 @@ public class KeyStoreKeyProviderTest extends TestCase
{ {
try try
{ {
new KeystoreKeyProvider( getTestKeyStoreProvider(FILE_ONE, Collections.singletonMap(ALIAS_ONE, "password_fail"));
FILE_ONE,
getKeyStoreLoader(), // new KeystoreKeyProvider(
"SunJCE", // FILE_ONE,
"JCEKS", // getKeyStoreLoader(),
Collections.singletonMap(ALIAS_ONE, "password_fail")); // "SunJCE",
// "JCEKS",
// Collections.singletonMap(ALIAS_ONE, "password_fail"));
fail("Expect to fail because password is incorrect"); fail("Expect to fail because password is incorrect");
} }
catch (AlfrescoRuntimeException e) catch (AlfrescoRuntimeException e)
@@ -123,12 +182,13 @@ public class KeyStoreKeyProviderTest extends TestCase
{ {
try try
{ {
new KeystoreKeyProvider( getTestKeyStoreProvider(FILE_TWO, Collections.singletonMap(ALIAS_TWO, "password_fail"));
FILE_TWO, // new KeystoreKeyProvider(
getKeyStoreLoader(), // FILE_TWO,
"SunJCE", // getKeyStoreLoader(),
"JCEKS", // "SunJCE",
Collections.singletonMap(ALIAS_TWO, "password_fail")); // "JCEKS",
// Collections.singletonMap(ALIAS_TWO, "password_fail"));
fail("Expect to fail because password is incorrect"); fail("Expect to fail because password is incorrect");
} }
catch (AlfrescoRuntimeException e) catch (AlfrescoRuntimeException e)
@@ -140,12 +200,14 @@ public class KeyStoreKeyProviderTest extends TestCase
public void testAliasWithCorrectPassword_One() throws Exception public void testAliasWithCorrectPassword_One() throws Exception
{ {
KeystoreKeyProvider ks = new KeystoreKeyProvider( KeystoreKeyProvider ks = getTestKeyStoreProvider(FILE_ONE, Collections.singletonMap(ALIAS_ONE, "aliasPwd1"));
FILE_ONE,
getKeyStoreLoader(), // KeystoreKeyProvider ks = new KeystoreKeyProvider(
"SunJCE", // FILE_ONE,
"JCEKS", // getKeyStoreLoader(),
Collections.singletonMap(ALIAS_ONE, "aliasPwd1")); // "SunJCE",
// "JCEKS",
// Collections.singletonMap(ALIAS_ONE, "aliasPwd1"));
Key keyOne = ks.getKey(ALIAS_ONE); Key keyOne = ks.getKey(ALIAS_ONE);
assertNotNull(keyOne); assertNotNull(keyOne);
} }
@@ -155,12 +217,16 @@ public class KeyStoreKeyProviderTest extends TestCase
Map<String, String> passwords = new HashMap<String, String>(5); Map<String, String> passwords = new HashMap<String, String>(5);
passwords.put(ALIAS_ONE, "aliasPwd1"); passwords.put(ALIAS_ONE, "aliasPwd1");
passwords.put(ALIAS_TWO, "aliasPwd2"); passwords.put(ALIAS_TWO, "aliasPwd2");
KeystoreKeyProvider ks = new KeystoreKeyProvider(
FILE_TWO, KeystoreKeyProvider ks = getTestKeyStoreProvider(FILE_TWO, passwords);
getKeyStoreLoader(),
"SunJCE", // KeystoreKeyProvider ks = new KeystoreKeyProvider(
"JCEKS", // FILE_TWO,
passwords); // getKeyStoreLoader(),
// "SunJCE",
// "JCEKS",
// passwords);
assertNotNull(ks.getKey(ALIAS_ONE)); assertNotNull(ks.getKey(ALIAS_ONE));
assertNotNull(ks.getKey(ALIAS_TWO)); assertNotNull(ks.getKey(ALIAS_TWO));
} }
@@ -171,12 +237,14 @@ public class KeyStoreKeyProviderTest extends TestCase
passwords.put(ALIAS_ONE, "aliasPwd1"); passwords.put(ALIAS_ONE, "aliasPwd1");
passwords.put(ALIAS_TWO, "aliasPwd2"); passwords.put(ALIAS_TWO, "aliasPwd2");
passwords.put(ALIAS_THREE, "aliasPwd3"); passwords.put(ALIAS_THREE, "aliasPwd3");
KeystoreKeyProvider ks = new KeystoreKeyProvider( KeystoreKeyProvider ks = getTestKeyStoreProvider(FILE_THREE, passwords);
FILE_THREE,
getKeyStoreLoader(), // KeystoreKeyProvider ks = new KeystoreKeyProvider(
"SunJCE", // FILE_THREE,
"JCEKS", // getKeyStoreLoader(),
passwords); // "SunJCE",
// "JCEKS",
// passwords);
assertNotNull(ks.getKey(ALIAS_ONE)); assertNotNull(ks.getKey(ALIAS_ONE));
assertNotNull(ks.getKey(ALIAS_TWO)); assertNotNull(ks.getKey(ALIAS_TWO));
assertNull(ks.getKey(ALIAS_THREE)); assertNull(ks.getKey(ALIAS_THREE));

View File

@@ -0,0 +1,228 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.crypto.SealedObject;
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.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;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.extensions.surf.util.I18NUtil;
// TODO use Batch code to run in parallel
// TODO lock so that only one encryptor can run at a time
/**
* Re-encrypts encryptable repository properties using a new set of encryption keys.
* Decrypts the repository properties using the default encryptor, falling back to
* a backup decryptor (using the old encryption keys) if necessary, and then re-encrypts
* the properties.
*
* The system can stay running during this operation.
*
* @since 4.0
*/
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 QNameDAO qnameDAO;
private MetadataEncryptor metadataEncryptor;
private ApplicationContext applicationContext;
private TransactionService transactionService;
private RetryingTransactionHelper transactionHelper;
/**
* Set the transaction provider so that each execution can be performed within a transaction
*/
public void setTransactionService(TransactionService transactionService)
{
this.transactionService = transactionService;
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)
{
this.metadataEncryptor = metadataEncryptor;
}
public void init()
{
}
public void reencrypt(final KeyStoreParameters newEncryptionParameters, final List<NodePropertyEntity> properties)
{
BatchProcessor.BatchProcessWorker<NodePropertyEntity> worker = new BatchProcessor.BatchProcessWorker<NodePropertyEntity>()
{
public String getIdentifier(NodePropertyEntity entity)
{
return String.valueOf(entity.getNodeId());
}
public void beforeProcess() throws Throwable
{
}
public void afterProcess() throws Throwable
{
}
public void process(NodePropertyEntity entity) throws Throwable
{
Object value = entity.getValue();
if(value instanceof SealedObject)
{
SealedObject sealed = (SealedObject)value;
NodePropertyKey nodeKey = entity.getKey();
QName propertyQName = qnameDAO.getQName(nodeKey.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.
Serializable decrypted = metadataEncryptor.decrypt(propertyQName, sealed);
// Re-encrypt. The new keys will be used.
Serializable resealed = metadataEncryptor.encrypt(propertyQName, decrypted);
// TODO update resealed using batch update?
// does the node DAO do batch updating?
nodeDAO.setNodeProperties(entity.getNodeId(), Collections.singletonMap(propertyQName, resealed));
}
else
{
// TODO
}
}
};
BatchProcessWorkProvider<NodePropertyEntity> provider = new BatchProcessWorkProvider<NodePropertyEntity>()
{
private int start = 0;
@Override
public int getTotalEstimatedWorkSize()
{
return properties.size();
}
@Override
public Collection<NodePropertyEntity> getNextWork()
{
int end = start + 20;
if(end > properties.size())
{
end = properties.size();
}
List<NodePropertyEntity> 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
new BatchProcessor<NodePropertyEntity>(
I18NUtil.getMessage(""),
transactionHelper,
provider,
2, 20,
applicationContext,
logger, 100).process(worker, true);
}
public void execute(KeyStoreParameters newEncryptionParameters)
{
// Proceed only if fallback is available i.e. the systems has both old and new keys
if(metadataEncryptor.isFallbackAvailable())
{
QName model = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, namespaceDAO);
// get properties that are encrypted
Collection<PropertyDefinition> propertyDefs = dictionaryDAO.getProperties(model, DataTypeDefinition.ENCRYPTED);
List<NodePropertyEntity> properties = nodeDAO.getProperties(propertyDefs);
// reencrypt these properties
reencrypt(newEncryptionParameters, properties);
}
else
{
// TODO
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException
{
this.applicationContext = applicationContext;
}
}

View File

@@ -27,6 +27,7 @@ import java.util.Set;
import org.alfresco.repo.node.NodeBulkLoader; import org.alfresco.repo.node.NodeBulkLoader;
import org.alfresco.service.cmr.dictionary.InvalidTypeException; import org.alfresco.service.cmr.dictionary.InvalidTypeException;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidNodeRefException;
@@ -36,6 +37,7 @@ import org.alfresco.service.cmr.repository.StoreExistsException;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.util.Pair; import org.alfresco.util.Pair;
import org.springframework.dao.ConcurrencyFailureException;
/** /**
* DAO services for <b>alf_node</b> and related tables * DAO services for <b>alf_node</b> and related tables
@@ -671,4 +673,5 @@ public interface NodeDAO extends NodeBulkLoader
*/ */
public void setNodeDefiningAclId(Long nodeId, long id); public void setNodeDefiningAclId(Long nodeId, long id);
public List<NodePropertyEntity> getProperties(Collection<PropertyDefinition> propertyDefs);
} }

View File

@@ -49,6 +49,7 @@ import org.alfresco.repo.domain.node.TransactionEntity;
import org.alfresco.repo.domain.node.TransactionQueryEntity; import org.alfresco.repo.domain.node.TransactionQueryEntity;
import org.alfresco.repo.domain.qname.QNameDAO; import org.alfresco.repo.domain.qname.QNameDAO;
import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
@@ -90,6 +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_UUIDS = "alfresco.node.select_NodesByUuids";
private static final String SELECT_NODES_BY_IDS = "alfresco.node.select_NodesByIds"; 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_NODE_PROPERTIES = "alfresco.node.select_NodeProperties";
private static final String SELECT_PROPERTIES_BY_TYPE = "alfresco.node.select_PropertiesByType";
private static final String SELECT_NODE_ASPECTS = "alfresco.node.select_NodeAspects"; 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 INSERT_NODE_PROPERTY = "alfresco.node.insert.insert_NodeProperty";
private static final String UPDATE_PRIMARY_CHILDREN_SHARED_ACL = "alfresco.node.update.update_PrimaryChildrenSharedAcl"; private static final String UPDATE_PRIMARY_CHILDREN_SHARED_ACL = "alfresco.node.update.update_PrimaryChildrenSharedAcl";
@@ -1482,6 +1484,31 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl
} }
} }
// TODO - use a callback approach
public List<NodePropertyEntity> getProperties(Collection<PropertyDefinition> propertyDefs)
{
Set<QName> qnames = new HashSet<QName>();
for(PropertyDefinition propDef : propertyDefs)
{
qnames.add(propDef.getName());
}
final List<NodePropertyEntity> props = new ArrayList<NodePropertyEntity>();
// qnames of properties that are encrypted
Set<Long> qnameIds = qnameDAO.convertQNamesToIds(qnames, false);
template.select(SELECT_PROPERTIES_BY_TYPE, qnameIds, new ResultHandler()
{
@Override
public void handleResult(ResultContext context)
{
props.add((NodePropertyEntity)context.getResultObject());
}
});
return props;
}
/* /*
* DAO OVERRIDES * DAO OVERRIDES
*/ */

View File

@@ -1,6 +1,7 @@
package org.alfresco.repo.node.encryption; package org.alfresco.repo.node.encryption;
import java.io.Serializable; import java.io.Serializable;
import java.security.InvalidKeyException;
import java.security.KeyException; import java.security.KeyException;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -10,6 +11,7 @@ import java.util.Set;
import javax.crypto.SealedObject; import javax.crypto.SealedObject;
import org.alfresco.encryption.Encryptor; import org.alfresco.encryption.Encryptor;
import org.alfresco.encryption.FallbackEncryptor;
import org.alfresco.encryption.KeyProvider; import org.alfresco.encryption.KeyProvider;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationException;
@@ -221,4 +223,23 @@ public class MetadataEncryptor
// Done // Done
return outbound; 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();
}
} }

View File

@@ -29,7 +29,7 @@ import java.util.Map;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel; import org.alfresco.httpclient.HttpClientFactory;
import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.search.impl.lucene.LuceneQueryParserException; import org.alfresco.repo.search.impl.lucene.LuceneQueryParserException;
import org.alfresco.repo.search.impl.lucene.SolrJSONResultSet; import org.alfresco.repo.search.impl.lucene.SolrJSONResultSet;
@@ -44,12 +44,8 @@ import org.alfresco.service.cmr.security.PermissionService;
import org.apache.commons.codec.net.URLCodec; import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
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.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod; 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.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.json.JSONArray; import org.json.JSONArray;
@@ -73,19 +69,33 @@ public class SolrQueryHTTPClient
private Map<String, String> storeMappings; private Map<String, String> storeMappings;
private String solrHost;
private int solrPort;
private String baseUrl; private String baseUrl;
private HttpClient httpClient; private HttpClient httpClient;
private HttpClientFactory httpClientFactory;
public SolrQueryHTTPClient() public SolrQueryHTTPClient()
{ {
MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager(); }
httpClient = new HttpClient(connectionManager);
HttpClientParams params = httpClient.getParams(); public void init()
params.setBooleanParameter("http.tcp.nodelay", true); {
params.setBooleanParameter("http.connection.stalecheck", false); StringBuilder sb = new StringBuilder();
params.setBooleanParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, true); // sb.append("http://");
httpClient.getState().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), new UsernamePasswordCredentials("admin", "admin")); // sb.append(solrHost);
// sb.append(":");
// sb.append(solrPort);
sb.append("/solr");
this.baseUrl = sb.toString();
httpClient = httpClientFactory.getHttpClient(solrHost, solrPort);
}
public void setHttpClientFactory(HttpClientFactory httpClientFactory)
{
this.httpClientFactory = httpClientFactory;
} }
public void setNodeDAO(NodeDAO nodeDAO) public void setNodeDAO(NodeDAO nodeDAO)
@@ -108,11 +118,46 @@ public class SolrQueryHTTPClient
this.storeMappings = storeMappings; this.storeMappings = storeMappings;
} }
public void setBaseUrl(String baseUrl) public void setSolrHost(String solrHost)
{ {
this.baseUrl = baseUrl; 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) public ResultSet executeQuery(SearchParameters searchParameters, String language)
{ {
try try

View File

@@ -29,8 +29,10 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.httpclient.HttpClientFactory;
import org.alfresco.util.ParameterCheck; import org.alfresco.util.ParameterCheck;
import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.SolrServerException;
@@ -69,6 +71,8 @@ public class SOLRAdminClient implements ApplicationEventPublisherAware
private ApplicationEventPublisher applicationEventPublisher; private ApplicationEventPublisher applicationEventPublisher;
private SolrTracker solrTracker; private SolrTracker solrTracker;
private HttpClientFactory httpClientFactory;
public SOLRAdminClient() public SOLRAdminClient()
{ {
} }
@@ -83,11 +87,6 @@ public class SOLRAdminClient implements ApplicationEventPublisherAware
this.solrPort = Integer.parseInt(solrPort); this.solrPort = Integer.parseInt(solrPort);
} }
public void setSolrUrl(String url)
{
this.solrUrl = url;
}
public void setSolrUser(String solrUser) public void setSolrUser(String solrUser)
{ {
this.solrUser = solrUser; this.solrUser = solrUser;
@@ -114,6 +113,28 @@ public class SOLRAdminClient implements ApplicationEventPublisherAware
this.solrPingCronExpression = solrPingCronExpression; this.solrPingCronExpression = solrPingCronExpression;
} }
public void setHttpClientFactory(HttpClientFactory httpClientFactory)
{
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() public void init()
{ {
ParameterCheck.mandatory("solrHost", solrHost); ParameterCheck.mandatory("solrHost", solrHost);
@@ -126,7 +147,17 @@ public class SOLRAdminClient implements ApplicationEventPublisherAware
try try
{ {
server = new CommonsHttpSolrServer(solrUrl); StringBuilder sb = new StringBuilder();
sb.append(httpClientFactory.isSSL() ? "https://" : "http://");
sb.append(solrHost);
sb.append(":");
sb.append(solrPort);
sb.append("/solr");
this.solrUrl = sb.toString();
HttpClient httpClient = httpClientFactory.getHttpClient(solrHost, solrPort);
server = new CommonsHttpSolrServer(solrUrl, httpClient);
// TODO remove credentials because we're using SSL?
Credentials defaultcreds = new UsernamePasswordCredentials(solrUser, solrPassword); Credentials defaultcreds = new UsernamePasswordCredentials(solrUser, solrPassword);
server.getHttpClient().getState().setCredentials(new AuthScope(solrHost, solrPort, AuthScope.ANY_REALM), server.getHttpClient().getState().setCredentials(new AuthScope(solrHost, solrPort, AuthScope.ANY_REALM),
defaultcreds); defaultcreds);