mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Added Encryptor interface for symmetric encryption esp. targeting SealedObject
- This will allow a keystore to be checked in (.keystore) and specified by installer - Algorithm parameters embedded in SealedObject but also supported by other Cipher methods ALF-8646: RINF 38: Text data encryption ALF-8956: RINF 38: Encryption key password specified by installer ALF-9055: RINF 38: Support encryption against existing data git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28438 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
BIN
config/alfresco/.keystore
Normal file
BIN
config/alfresco/.keystore
Normal file
Binary file not shown.
@@ -8,6 +8,7 @@
|
|||||||
<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/core-services-context.xml" />
|
<import resource="classpath:alfresco/core-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" />
|
||||||
|
@@ -375,7 +375,7 @@
|
|||||||
<property name="localeDAO" ref="localeDAO"/>
|
<property name="localeDAO" ref="localeDAO"/>
|
||||||
<property name="contentDataDAO" ref="contentDataDAO"/>
|
<property name="contentDataDAO" ref="contentDataDAO"/>
|
||||||
<property name="cannedQueryDAO" ref="cannedQueryDAO"/>
|
<property name="cannedQueryDAO" ref="cannedQueryDAO"/>
|
||||||
<property name="encryptionEngine" ref="encryptionEngine"/>
|
<property name="encryptor" ref="encryptor"/>
|
||||||
<property name="methodSecurityInterceptor" ref="PersonService_security"/>
|
<property name="methodSecurityInterceptor" ref="PersonService_security"/>
|
||||||
<property name="methodService" ref="personService"/>
|
<property name="methodService" ref="personService"/>
|
||||||
<property name="methodName" value="getPeople"/>
|
<property name="methodName" value="getPeople"/>
|
||||||
|
@@ -1549,35 +1549,19 @@
|
|||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- Import the sys admin params from the sysAdmin subsystem -->
|
<!-- Import the sys admin params from the sysAdmin subsystem -->
|
||||||
<bean id="sysAdminParams" class="org.alfresco.repo.management.subsystems.SubsystemProxyFactory">
|
<bean id="sysAdminParams" class="org.alfresco.repo.management.subsystems.SubsystemProxyFactory">
|
||||||
<property name="sourceApplicationContextFactory">
|
<property name="sourceApplicationContextFactory">
|
||||||
<ref bean="sysAdmin" />
|
<ref bean="sysAdmin" />
|
||||||
</property>
|
</property>
|
||||||
<property name="interfaces">
|
<property name="interfaces">
|
||||||
<list>
|
<list>
|
||||||
<value>org.alfresco.repo.admin.SysAdminParams</value>
|
<value>org.alfresco.repo.admin.SysAdminParams</value>
|
||||||
</list>
|
</list>
|
||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- Replacement for Spring's multicaster, that queues up events until the application is started -->
|
<!-- Replacement for Spring's multicaster, that queues up events until the application is started -->
|
||||||
<bean id="applicationEventMulticaster" class="org.alfresco.repo.management.SafeApplicationEventMulticaster"/>
|
<bean id="applicationEventMulticaster" class="org.alfresco.repo.management.SafeApplicationEventMulticaster"/>
|
||||||
|
|
||||||
<!--
|
|
||||||
Encryption engine (used by node DAO to encrypt and decrypt encryptable node properties)
|
|
||||||
-->
|
|
||||||
<bean id="defaultKeyProvider" class="org.alfresco.repo.security.encryption.KeystoreKeyProvider" init-method="init">
|
|
||||||
<property name="keyStorePassword" value="${encryption.keystore.keystore_password}"/>
|
|
||||||
<property name="secretKeyPassword" value="${encryption.keystore.secret_Key_password}"/>
|
|
||||||
<property name="keyStoreFile" value="${dir.root}/alf_keystore"/>
|
|
||||||
<property name="passwordGenerator" ref="passwordGenerator"/>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="testKeyProvider" class="org.alfresco.repo.security.encryption.TestKeyProvider">
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="encryptionEngine" class="org.alfresco.repo.security.encryption.AESEncryptionEngine" init-method="init">
|
|
||||||
<property name="keyProvider" ref="defaultKeyProvider"/>
|
|
||||||
</bean>
|
|
||||||
</beans>
|
</beans>
|
||||||
|
@@ -90,6 +90,7 @@
|
|||||||
<property name="enableTimestampPropagation" value="${system.enableTimestampPropagation}" />
|
<property name="enableTimestampPropagation" value="${system.enableTimestampPropagation}" />
|
||||||
<property name="transactionService" ref="transactionService" />
|
<property name="transactionService" ref="transactionService" />
|
||||||
<property name="dictionaryService" ref="dictionaryService"/>
|
<property name="dictionaryService" ref="dictionaryService"/>
|
||||||
|
<property name="encryptor" ref="encryptor"/>
|
||||||
<property name="policyBehaviourFilter" ref="policyBehaviourFilter" />
|
<property name="policyBehaviourFilter" ref="policyBehaviourFilter" />
|
||||||
<property name="aclDAO" ref="aclDAO"/>
|
<property name="aclDAO" ref="aclDAO"/>
|
||||||
<property name="accessControlListDAO" ref="admNodeACLDAO"/>
|
<property name="accessControlListDAO" ref="admNodeACLDAO"/>
|
||||||
@@ -103,7 +104,6 @@
|
|||||||
<property name="aspectsCache" ref="node.aspectsCache"/>
|
<property name="aspectsCache" ref="node.aspectsCache"/>
|
||||||
<property name="propertiesCache" ref="node.propertiesCache"/>
|
<property name="propertiesCache" ref="node.propertiesCache"/>
|
||||||
<property name="parentAssocsCache" ref="node.parentAssocsCache"/>
|
<property name="parentAssocsCache" ref="node.parentAssocsCache"/>
|
||||||
<property name="encryptionEngine" ref="encryptionEngine"/>
|
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="lockDAO" class="org.alfresco.repo.domain.locks.ibatis.LockDAOImpl">
|
<bean id="lockDAO" class="org.alfresco.repo.domain.locks.ibatis.LockDAOImpl">
|
||||||
|
31
config/alfresco/encryption-context.xml
Normal file
31
config/alfresco/encryption-context.xml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
|
||||||
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:crypt="http://code.google.com/p/spring-crypto-utils/schema/crypt"
|
||||||
|
xsi:schemaLocation="
|
||||||
|
http://www.springframework.org/schema/beans
|
||||||
|
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
|
http://code.google.com/p/spring-crypto-utils/schema/crypt
|
||||||
|
http://code.google.com/p/spring-crypto-utils/schema/crypt.xsd">
|
||||||
|
|
||||||
|
<!-- Beans to initilize encryption -->
|
||||||
|
<bean id="keyProvider" class="org.alfresco.repo.security.encryption.KeystoreKeyProvider" init-method="init">
|
||||||
|
<property name="location" value="${encryption.keystore.location}"/>
|
||||||
|
<property name="provider" value="${encryption.keystore.provider}"/>
|
||||||
|
<property name="type" value="${encryption.keystore.type}"/>
|
||||||
|
<property name="passwords">
|
||||||
|
<map>
|
||||||
|
<entry key="keystore" value="${encryption.keystore.password}"></entry>
|
||||||
|
<entry key="keystore.METADATA" value="${encryption.keystore.password.METADATA}"></entry>
|
||||||
|
<entry key="keystore.SOLR" value="${encryption.keystore.password.SOLR}"></entry>
|
||||||
|
</map>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="encryptor" class="org.alfresco.repo.security.encryption.DefaultEncryptor" init-method="init">
|
||||||
|
<property name="keyProvider" ref="keyProvider"/>
|
||||||
|
<property name="cipherAlgorithm" value="${encryption.encryption.cipherAlgorithm}" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
</beans>
|
@@ -148,7 +148,7 @@
|
|||||||
<property name="localeDAO" ref="localeDAO"/>
|
<property name="localeDAO" ref="localeDAO"/>
|
||||||
<property name="contentDataDAO" ref="contentDataDAO"/>
|
<property name="contentDataDAO" ref="contentDataDAO"/>
|
||||||
<property name="cannedQueryDAO" ref="cannedQueryDAO"/>
|
<property name="cannedQueryDAO" ref="cannedQueryDAO"/>
|
||||||
<property name="encryptionEngine" ref="encryptionEngine"/>
|
<property name="encryptor" ref="encryptor"/>
|
||||||
<property name="methodSecurityInterceptor" ref="FileFolderService_security"/>
|
<property name="methodSecurityInterceptor" ref="FileFolderService_security"/>
|
||||||
<property name="methodService" ref="fileFolderService"/>
|
<property name="methodService" ref="fileFolderService"/>
|
||||||
<property name="methodName" value="list"/>
|
<property name="methodName" value="list"/>
|
||||||
|
@@ -632,7 +632,16 @@ deployment.filesystem.default.rootdir=./www
|
|||||||
deployment.filesystem.default.name=filesystem
|
deployment.filesystem.default.name=filesystem
|
||||||
deployment.filesystem.default.metadatadir=${deployment.filesystem.metadatadir}/default
|
deployment.filesystem.default.metadatadir=${deployment.filesystem.metadatadir}/default
|
||||||
|
|
||||||
# Encryption engine properties. These passwords protect the key store in which the secret
|
#
|
||||||
# key used for encryption and decryption is stored.
|
# Encryption properties
|
||||||
encryption.keystore.keystorePassword=keystorepassword
|
#
|
||||||
encryption.keystore.secretKeyPassword=secretkeypassword
|
encryption.encryption.cipherAlgorithm=DESede/CBC/PKCS5Padding
|
||||||
|
encryption.keystore.location=classpath:alfresco/.keystore
|
||||||
|
encryption.keystore.provider=
|
||||||
|
encryption.keystore.type=JCEKS
|
||||||
|
# The password protecting the keystore entries
|
||||||
|
encryption.keystore.password=ksPwd1
|
||||||
|
# The password protecting the alias: METADATA
|
||||||
|
encryption.keystore.password.METADATA=${encryption.keystore.password}
|
||||||
|
# The password protecting the alias: SOLR
|
||||||
|
encryption.keystore.password.SOLR=${encryption.keystore.password}
|
@@ -19,7 +19,6 @@
|
|||||||
package org.alfresco.repo.domain.node;
|
package org.alfresco.repo.domain.node;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.sql.Savepoint;
|
import java.sql.Savepoint;
|
||||||
@@ -53,12 +52,12 @@ import org.alfresco.repo.domain.permissions.AclDAO;
|
|||||||
import org.alfresco.repo.domain.qname.QNameDAO;
|
import org.alfresco.repo.domain.qname.QNameDAO;
|
||||||
import org.alfresco.repo.domain.usage.UsageDAO;
|
import org.alfresco.repo.domain.usage.UsageDAO;
|
||||||
import org.alfresco.repo.policy.BehaviourFilter;
|
import org.alfresco.repo.policy.BehaviourFilter;
|
||||||
import org.alfresco.repo.security.encryption.EncryptionEngine;
|
import org.alfresco.repo.security.encryption.Encryptor;
|
||||||
import org.alfresco.repo.security.permissions.AccessControlListProperties;
|
import org.alfresco.repo.security.permissions.AccessControlListProperties;
|
||||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
|
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
|
||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
|
||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||||
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||||
import org.alfresco.repo.transaction.TransactionAwareSingleton;
|
import org.alfresco.repo.transaction.TransactionAwareSingleton;
|
||||||
import org.alfresco.repo.transaction.TransactionListenerAdapter;
|
import org.alfresco.repo.transaction.TransactionListenerAdapter;
|
||||||
import org.alfresco.repo.transaction.TransactionalResourceHelper;
|
import org.alfresco.repo.transaction.TransactionalResourceHelper;
|
||||||
@@ -136,7 +135,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
private ContentDataDAO contentDataDAO;
|
private ContentDataDAO contentDataDAO;
|
||||||
private LocaleDAO localeDAO;
|
private LocaleDAO localeDAO;
|
||||||
private UsageDAO usageDAO;
|
private UsageDAO usageDAO;
|
||||||
private EncryptionEngine encryptionEngine;
|
private Encryptor encryptor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache for the Store root nodes by StoreRef:<br/>
|
* Cache for the Store root nodes by StoreRef:<br/>
|
||||||
@@ -218,9 +217,12 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
this.dictionaryService = dictionaryService;
|
this.dictionaryService = dictionaryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEncryptionEngine(EncryptionEngine encryptionEngine)
|
/**
|
||||||
|
* @param encryptor helper to do symmetric property encryption
|
||||||
|
*/
|
||||||
|
public void setEncryptor(Encryptor encryptor)
|
||||||
{
|
{
|
||||||
this.encryptionEngine = encryptionEngine;
|
this.encryptor = encryptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -368,9 +370,9 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
|||||||
PropertyCheck.mandatory(this, "contentDataDAO", contentDataDAO);
|
PropertyCheck.mandatory(this, "contentDataDAO", contentDataDAO);
|
||||||
PropertyCheck.mandatory(this, "localeDAO", localeDAO);
|
PropertyCheck.mandatory(this, "localeDAO", localeDAO);
|
||||||
PropertyCheck.mandatory(this, "usageDAO", usageDAO);
|
PropertyCheck.mandatory(this, "usageDAO", usageDAO);
|
||||||
// PropertyCheck.mandatory(this, "encryptionEngine", encryptionEngine);
|
PropertyCheck.mandatory(this, "encryptor", encryptor);
|
||||||
|
|
||||||
this.nodePropertyHelper = new NodePropertyHelper(dictionaryService, qnameDAO, localeDAO, contentDataDAO, encryptionEngine);
|
this.nodePropertyHelper = new NodePropertyHelper(dictionaryService, qnameDAO, localeDAO, contentDataDAO, encryptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@@ -19,7 +19,6 @@
|
|||||||
package org.alfresco.repo.domain.node;
|
package org.alfresco.repo.domain.node;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -33,7 +32,7 @@ import org.alfresco.error.AlfrescoRuntimeException;
|
|||||||
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
|
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
|
||||||
import org.alfresco.repo.domain.locale.LocaleDAO;
|
import org.alfresco.repo.domain.locale.LocaleDAO;
|
||||||
import org.alfresco.repo.domain.qname.QNameDAO;
|
import org.alfresco.repo.domain.qname.QNameDAO;
|
||||||
import org.alfresco.repo.security.encryption.EncryptionEngine;
|
import org.alfresco.repo.security.encryption.Encryptor;
|
||||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
||||||
import org.alfresco.service.cmr.dictionary.DictionaryException;
|
import org.alfresco.service.cmr.dictionary.DictionaryException;
|
||||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||||
@@ -60,7 +59,7 @@ public class NodePropertyHelper
|
|||||||
private static final Log logger = LogFactory.getLog(NodePropertyHelper.class);
|
private static final Log logger = LogFactory.getLog(NodePropertyHelper.class);
|
||||||
|
|
||||||
private final DictionaryService dictionaryService;
|
private final DictionaryService dictionaryService;
|
||||||
private final EncryptionEngine encryptionEngine;
|
private final Encryptor encryptor;
|
||||||
private final QNameDAO qnameDAO;
|
private final QNameDAO qnameDAO;
|
||||||
private final LocaleDAO localeDAO;
|
private final LocaleDAO localeDAO;
|
||||||
private final ContentDataDAO contentDataDAO;
|
private final ContentDataDAO contentDataDAO;
|
||||||
@@ -73,13 +72,13 @@ public class NodePropertyHelper
|
|||||||
QNameDAO qnameDAO,
|
QNameDAO qnameDAO,
|
||||||
LocaleDAO localeDAO,
|
LocaleDAO localeDAO,
|
||||||
ContentDataDAO contentDataDAO,
|
ContentDataDAO contentDataDAO,
|
||||||
EncryptionEngine encryptionEngine)
|
Encryptor encryptor)
|
||||||
{
|
{
|
||||||
this.dictionaryService = dictionaryService;
|
this.dictionaryService = dictionaryService;
|
||||||
this.qnameDAO = qnameDAO;
|
this.qnameDAO = qnameDAO;
|
||||||
this.localeDAO = localeDAO;
|
this.localeDAO = localeDAO;
|
||||||
this.contentDataDAO = contentDataDAO;
|
this.contentDataDAO = contentDataDAO;
|
||||||
this.encryptionEngine = encryptionEngine;
|
this.encryptor = encryptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<NodePropertyKey, NodePropertyValue> convertToPersistentProperties(Map<QName, Serializable> in)
|
public Map<NodePropertyKey, NodePropertyValue> convertToPersistentProperties(Map<QName, Serializable> in)
|
||||||
@@ -148,14 +147,17 @@ public class NodePropertyHelper
|
|||||||
|
|
||||||
// Get or spoof the property datatype
|
// Get or spoof the property datatype
|
||||||
QName propertyTypeQName;
|
QName propertyTypeQName;
|
||||||
|
boolean isEncrypted;
|
||||||
if (propertyDef == null) // property not recognised
|
if (propertyDef == null) // property not recognised
|
||||||
{
|
{
|
||||||
// allow it for now - persisting excess properties can be useful sometimes
|
// allow it for now - persisting excess properties can be useful sometimes
|
||||||
propertyTypeQName = DataTypeDefinition.ANY;
|
propertyTypeQName = DataTypeDefinition.ANY;
|
||||||
|
isEncrypted = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
propertyTypeQName = propertyDef.getDataType().getName();
|
propertyTypeQName = propertyDef.getDataType().getName();
|
||||||
|
isEncrypted = propertyDef.isEncrypted();
|
||||||
}
|
}
|
||||||
|
|
||||||
// A property may appear to be multi-valued if the model definition is loose and
|
// A property may appear to be multi-valued if the model definition is loose and
|
||||||
@@ -266,19 +268,8 @@ public class NodePropertyHelper
|
|||||||
// Get the Locale ID for the text
|
// Get the Locale ID for the text
|
||||||
Long mlTextLocaleId = localeDAO.getOrCreateLocalePair(mlTextLocale).getFirst();
|
Long mlTextLocaleId = localeDAO.getOrCreateLocalePair(mlTextLocale).getFirst();
|
||||||
// This is persisted against the current locale, but as a d:text instance
|
// This is persisted against the current locale, but as a d:text instance
|
||||||
Serializable v = null;
|
// This is persisted against the current locale, but as a d:text instance
|
||||||
try
|
NodePropertyValue npValue = new NodePropertyValue(DataTypeDefinition.TEXT, mlTextStr);
|
||||||
{
|
|
||||||
v = propertyDef.isEncrypted() ? encrypt(mlTextStr) : mlTextStr;
|
|
||||||
}
|
|
||||||
catch (UnsupportedEncodingException e)
|
|
||||||
{
|
|
||||||
// TODO check that throwing the exception preserves the original logic
|
|
||||||
throw new TypeConversionException(
|
|
||||||
"The property value could not be decoded as a UTF-8 string " + value.getClass(),
|
|
||||||
e);
|
|
||||||
}
|
|
||||||
NodePropertyValue npValue = new NodePropertyValue(DataTypeDefinition.TEXT, v, propertyDef.isEncrypted());
|
|
||||||
NodePropertyKey npKey = new NodePropertyKey();
|
NodePropertyKey npKey = new NodePropertyKey();
|
||||||
npKey.setListIndex(collectionIndex);
|
npKey.setListIndex(collectionIndex);
|
||||||
npKey.setQnameId(propertyQNameId);
|
npKey.setQnameId(propertyQNameId);
|
||||||
@@ -289,29 +280,6 @@ public class NodePropertyHelper
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(!propertyTypeQName.equals(DataTypeDefinition.ANY) && propertyDef.isEncrypted())
|
|
||||||
{
|
|
||||||
if(propertyTypeQName.equals(DataTypeDefinition.TEXT))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// TODO check type of value
|
|
||||||
value = propertyDef.isEncrypted() ? encrypt((String)value) : value;
|
|
||||||
}
|
|
||||||
catch (UnsupportedEncodingException e)
|
|
||||||
{
|
|
||||||
// TODO check that throwing the exception preserves the original logic
|
|
||||||
throw new TypeConversionException(
|
|
||||||
"The property value could not be decoded as a UTF-8 string " + value.getClass(),
|
|
||||||
e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.warn("Encryption is not supported for type " + propertyTypeQName + ", encryption will not be performed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NodePropertyValue npValue = makeNodePropertyValue(propertyDef, value);
|
NodePropertyValue npValue = makeNodePropertyValue(propertyDef, value);
|
||||||
NodePropertyKey npKey = new NodePropertyKey();
|
NodePropertyKey npKey = new NodePropertyKey();
|
||||||
npKey.setListIndex(collectionIndex);
|
npKey.setListIndex(collectionIndex);
|
||||||
@@ -323,18 +291,6 @@ public class NodePropertyHelper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected byte[] encrypt(String input) throws UnsupportedEncodingException
|
|
||||||
{
|
|
||||||
byte[] bytes = encryptionEngine.encryptString(input);
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String decrypt(byte[] input) throws UnsupportedEncodingException
|
|
||||||
{
|
|
||||||
String s = encryptionEngine.decryptAsString(input);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to convert the <code>Serializable</code> value into a full, persistable {@link NodePropertyValue}.
|
* Helper method to convert the <code>Serializable</code> value into a full, persistable {@link NodePropertyValue}.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -364,8 +320,7 @@ public class NodePropertyHelper
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
NodePropertyValue propertyValue = null;
|
NodePropertyValue propertyValue = null;
|
||||||
boolean isEncrypted = propertyDef==null ? false : propertyDef.isEncrypted();
|
propertyValue = new NodePropertyValue(propertyTypeQName, value);
|
||||||
propertyValue = new NodePropertyValue(propertyTypeQName, value, isEncrypted);
|
|
||||||
|
|
||||||
// done
|
// done
|
||||||
return propertyValue;
|
return propertyValue;
|
||||||
@@ -697,17 +652,17 @@ public class NodePropertyHelper
|
|||||||
}
|
}
|
||||||
// get property attributes
|
// get property attributes
|
||||||
final QName propertyTypeQName;
|
final QName propertyTypeQName;
|
||||||
boolean encrypted;
|
boolean isEncrypted;
|
||||||
if (propertyDef == null)
|
if (propertyDef == null)
|
||||||
{
|
{
|
||||||
// allow this for now
|
// allow this for now
|
||||||
propertyTypeQName = DataTypeDefinition.ANY;
|
propertyTypeQName = DataTypeDefinition.ANY;
|
||||||
encrypted = false;
|
isEncrypted = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
propertyTypeQName = propertyDef.getDataType().getName();
|
propertyTypeQName = propertyDef.getDataType().getName();
|
||||||
encrypted = propertyDef.isEncrypted();
|
isEncrypted = propertyDef.isEncrypted();
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -727,24 +682,6 @@ public class NodePropertyHelper
|
|||||||
ContentData contentData = contentDataDAO.getContentData(contentDataId).getSecond();
|
ContentData contentData = contentDataDAO.getContentData(contentDataId).getSecond();
|
||||||
value = new ContentDataWithId(contentData, contentDataId);
|
value = new ContentDataWithId(contentData, contentDataId);
|
||||||
}
|
}
|
||||||
else if (encrypted)
|
|
||||||
{
|
|
||||||
if (propertyTypeQName.equals(DataTypeDefinition.TEXT) || propertyTypeQName.equals(DataTypeDefinition.MLTEXT))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
value = decrypt((byte[])value);
|
|
||||||
}
|
|
||||||
catch (UnsupportedEncodingException e)
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException("Unexpected exception during decryption", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException("Encryption is not supported for " + propertyDef.getDataType().getName() + " types");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// done
|
// done
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@@ -32,7 +32,7 @@ import org.alfresco.model.ContentModel;
|
|||||||
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
|
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
|
||||||
import org.alfresco.repo.domain.locale.LocaleDAO;
|
import org.alfresco.repo.domain.locale.LocaleDAO;
|
||||||
import org.alfresco.repo.domain.qname.QNameDAO;
|
import org.alfresco.repo.domain.qname.QNameDAO;
|
||||||
import org.alfresco.repo.security.encryption.EncryptionEngine;
|
import org.alfresco.repo.security.encryption.Encryptor;
|
||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||||
import org.alfresco.repo.version.VersionModel;
|
import org.alfresco.repo.version.VersionModel;
|
||||||
@@ -87,9 +87,9 @@ public class NodePropertyHelperTest extends TestCase
|
|||||||
QNameDAO qnameDAO = (QNameDAO) ctx.getBean("qnameDAO");
|
QNameDAO qnameDAO = (QNameDAO) ctx.getBean("qnameDAO");
|
||||||
LocaleDAO localeDAO = (LocaleDAO) ctx.getBean("localeDAO");
|
LocaleDAO localeDAO = (LocaleDAO) ctx.getBean("localeDAO");
|
||||||
ContentDataDAO contentDataDAO = (ContentDataDAO) ctx.getBean("contentDataDAO");
|
ContentDataDAO contentDataDAO = (ContentDataDAO) ctx.getBean("contentDataDAO");
|
||||||
EncryptionEngine encryptionEngine = (EncryptionEngine) ctx.getBean("encryptionEngine");
|
Encryptor encryptor = (Encryptor) ctx.getBean("encryptor");
|
||||||
|
|
||||||
helper = new NodePropertyHelper(dictionaryService, qnameDAO, localeDAO, contentDataDAO, encryptionEngine);
|
helper = new NodePropertyHelper(dictionaryService, qnameDAO, localeDAO, contentDataDAO, encryptor);
|
||||||
transactionService = serviceRegistry.getTransactionService();
|
transactionService = serviceRegistry.getTransactionService();
|
||||||
txnHelper = transactionService.getRetryingTransactionHelper();
|
txnHelper = transactionService.getRetryingTransactionHelper();
|
||||||
txnHelper.setMinRetryWaitMs(10);
|
txnHelper.setMinRetryWaitMs(10);
|
||||||
|
@@ -23,7 +23,6 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -31,11 +30,11 @@ import java.util.HashMap;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.crypto.SealedObject;
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
import org.alfresco.repo.domain.schema.SchemaBootstrap;
|
import org.alfresco.repo.domain.schema.SchemaBootstrap;
|
||||||
import org.alfresco.repo.security.encryption.EncryptionEngine;
|
|
||||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
||||||
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.ContentData;
|
import org.alfresco.service.cmr.repository.ContentData;
|
||||||
@@ -527,6 +526,37 @@ public class NodePropertyValue implements Cloneable, Serializable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
SEALED_OBJECT
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Integer getOrdinalNumber()
|
||||||
|
{
|
||||||
|
return Integer.valueOf(22);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ValueType getPersistedType(Serializable value)
|
||||||
|
{
|
||||||
|
return ValueType.SERIALIZABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Serializable convert(Serializable value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if (value instanceof SealedObject)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("SealedObject value not supported: " + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
;
|
;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -637,6 +667,10 @@ public class NodePropertyValue implements Cloneable, Serializable
|
|||||||
{
|
{
|
||||||
return ValueType.CONTENT;
|
return ValueType.CONTENT;
|
||||||
}
|
}
|
||||||
|
else if (value instanceof SealedObject)
|
||||||
|
{
|
||||||
|
return ValueType.SEALED_OBJECT;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// type is not recognised as belonging to any particular slot
|
// type is not recognised as belonging to any particular slot
|
||||||
@@ -733,8 +767,6 @@ public class NodePropertyValue implements Cloneable, Serializable
|
|||||||
private String stringValue;
|
private String stringValue;
|
||||||
private Serializable serializableValue;
|
private Serializable serializableValue;
|
||||||
|
|
||||||
private boolean encrypted;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* default constructor
|
* default constructor
|
||||||
*/
|
*/
|
||||||
@@ -748,16 +780,13 @@ public class NodePropertyValue implements Cloneable, Serializable
|
|||||||
* @param typeQName the dictionary-defined property type to store the property as
|
* @param typeQName the dictionary-defined property type to store the property as
|
||||||
* @param value the value to store. This will be converted into a format compatible
|
* @param value the value to store. This will be converted into a format compatible
|
||||||
* with the type given
|
* with the type given
|
||||||
* @param isEncrypted true if value should be encrypted when persisted
|
|
||||||
*
|
*
|
||||||
* @throws java.lang.UnsupportedOperationException if the value cannot be converted to the type given
|
* @throws java.lang.UnsupportedOperationException if the value cannot be converted to the type given
|
||||||
*/
|
*/
|
||||||
public NodePropertyValue(QName typeQName, Serializable value, boolean encrypted)
|
public NodePropertyValue(QName typeQName, Serializable value)
|
||||||
{
|
{
|
||||||
ParameterCheck.mandatory("typeQName", typeQName);
|
ParameterCheck.mandatory("typeQName", typeQName);
|
||||||
|
|
||||||
this.encrypted = encrypted;
|
|
||||||
|
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
this.actualType = NodePropertyValue.getActualType(value);
|
this.actualType = NodePropertyValue.getActualType(value);
|
||||||
@@ -765,38 +794,17 @@ public class NodePropertyValue implements Cloneable, Serializable
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ValueType persistedValueType = null;
|
// Convert the value to the type required. This ensures that any type conversion issues
|
||||||
|
// are caught early and prevent the scenario where the data in the DB cannot be given
|
||||||
if(encrypted)
|
// back out because it is unconvertable.
|
||||||
{
|
ValueType valueType = makeValueType(typeQName);
|
||||||
// this constructor doesn't appear to get called for type DataTypeDefinition.MLTEXT because MLTEXT is
|
value = valueType.convert(value);
|
||||||
// split out into strings in NodePropertyHelper
|
|
||||||
if(typeQName.equals(DataTypeDefinition.TEXT))
|
|
||||||
{
|
|
||||||
this.actualType = ValueType.STRING;
|
|
||||||
persistedValueType = ValueType.SERIALIZABLE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException("Can encrypt only TEXT and MLTEXT types, this type is " + typeQName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Convert the value to the type required. This ensures that any type conversion issues
|
|
||||||
// are caught early and prevent the scenario where the data in the DB cannot be given
|
|
||||||
// back out because it is unconvertable.
|
|
||||||
ValueType valueType = makeValueType(typeQName);
|
|
||||||
value = valueType.convert(value);
|
|
||||||
|
|
||||||
this.actualType = NodePropertyValue.getActualType(value);
|
|
||||||
|
|
||||||
// get the persisted type
|
|
||||||
persistedValueType = this.actualType.getPersistedType(value);
|
|
||||||
// convert to the persistent type
|
|
||||||
value = persistedValueType.convert(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this.actualType = NodePropertyValue.getActualType(value);
|
||||||
|
// get the persisted type
|
||||||
|
ValueType persistedValueType = this.actualType.getPersistedType(value);
|
||||||
|
// convert to the persistent type
|
||||||
|
value = persistedValueType.convert(value);
|
||||||
setPersistedValue(persistedValueType, value);
|
setPersistedValue(persistedValueType, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1070,9 +1078,9 @@ public class NodePropertyValue implements Cloneable, Serializable
|
|||||||
// have been converted on the way in.
|
// have been converted on the way in.
|
||||||
ret = (Serializable) persistedValue;
|
ret = (Serializable) persistedValue;
|
||||||
}
|
}
|
||||||
else if(encrypted)
|
else if(persistedValue instanceof SealedObject)
|
||||||
{
|
{
|
||||||
ret = persistedValue;
|
ret = (Serializable) persistedValue;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@@ -29,8 +29,8 @@ import org.alfresco.query.CannedQuery;
|
|||||||
import org.alfresco.query.CannedQueryPageDetails;
|
import org.alfresco.query.CannedQueryPageDetails;
|
||||||
import org.alfresco.query.CannedQueryParameters;
|
import org.alfresco.query.CannedQueryParameters;
|
||||||
import org.alfresco.query.CannedQuerySortDetails;
|
import org.alfresco.query.CannedQuerySortDetails;
|
||||||
import org.alfresco.query.PagingRequest;
|
|
||||||
import org.alfresco.query.CannedQuerySortDetails.SortOrder;
|
import org.alfresco.query.CannedQuerySortDetails.SortOrder;
|
||||||
|
import org.alfresco.query.PagingRequest;
|
||||||
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
|
import org.alfresco.repo.domain.contentdata.ContentDataDAO;
|
||||||
import org.alfresco.repo.domain.locale.LocaleDAO;
|
import org.alfresco.repo.domain.locale.LocaleDAO;
|
||||||
import org.alfresco.repo.domain.node.NodeDAO;
|
import org.alfresco.repo.domain.node.NodeDAO;
|
||||||
@@ -38,7 +38,7 @@ import org.alfresco.repo.domain.node.NodePropertyHelper;
|
|||||||
import org.alfresco.repo.domain.qname.QNameDAO;
|
import org.alfresco.repo.domain.qname.QNameDAO;
|
||||||
import org.alfresco.repo.domain.query.CannedQueryDAO;
|
import org.alfresco.repo.domain.query.CannedQueryDAO;
|
||||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||||
import org.alfresco.repo.security.encryption.EncryptionEngine;
|
import org.alfresco.repo.security.encryption.Encryptor;
|
||||||
import org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityInterceptor;
|
import org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityInterceptor;
|
||||||
import org.alfresco.repo.tenant.TenantService;
|
import org.alfresco.repo.tenant.TenantService;
|
||||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||||
@@ -64,7 +64,7 @@ public class GetChildrenCannedQueryFactory extends AbstractCannedQueryFactory<No
|
|||||||
private CannedQueryDAO cannedQueryDAO;
|
private CannedQueryDAO cannedQueryDAO;
|
||||||
private TenantService tenantService;
|
private TenantService tenantService;
|
||||||
|
|
||||||
private EncryptionEngine encryptionEngine;
|
private Encryptor encryptor;
|
||||||
|
|
||||||
private MethodSecurityInterceptor methodSecurityInterceptor;
|
private MethodSecurityInterceptor methodSecurityInterceptor;
|
||||||
private String methodName;
|
private String methodName;
|
||||||
@@ -100,9 +100,9 @@ public class GetChildrenCannedQueryFactory extends AbstractCannedQueryFactory<No
|
|||||||
this.cannedQueryDAO = cannedQueryDAO;
|
this.cannedQueryDAO = cannedQueryDAO;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEncryptionEngine(EncryptionEngine encryptionEngine)
|
public void setEncryptor(Encryptor encryptor)
|
||||||
{
|
{
|
||||||
this.encryptionEngine = encryptionEngine;
|
this.encryptor = encryptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTenantService(TenantService tenantService)
|
public void setTenantService(TenantService tenantService)
|
||||||
@@ -128,7 +128,7 @@ public class GetChildrenCannedQueryFactory extends AbstractCannedQueryFactory<No
|
|||||||
@Override
|
@Override
|
||||||
public CannedQuery<NodeRef> getCannedQuery(CannedQueryParameters parameters)
|
public CannedQuery<NodeRef> getCannedQuery(CannedQueryParameters parameters)
|
||||||
{
|
{
|
||||||
NodePropertyHelper nodePropertyHelper = new NodePropertyHelper(dictionaryService, qnameDAO, localeDAO, contentDataDAO, encryptionEngine);
|
NodePropertyHelper nodePropertyHelper = new NodePropertyHelper(dictionaryService, qnameDAO, localeDAO, contentDataDAO, encryptor);
|
||||||
|
|
||||||
Method method = null;
|
Method method = null;
|
||||||
for (Method m : methodService.getClass().getMethods())
|
for (Method m : methodService.getClass().getMethods())
|
||||||
|
@@ -49,4 +49,12 @@ public class BasicPasswordGenerator implements PasswordGenerator
|
|||||||
{
|
{
|
||||||
return RandomStringUtils.randomAlphanumeric(passwordLength);
|
return RandomStringUtils.randomAlphanumeric(passwordLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String ... args)
|
||||||
|
{
|
||||||
|
BasicPasswordGenerator pwdGen = new BasicPasswordGenerator();
|
||||||
|
pwdGen.setPasswordLength(10);
|
||||||
|
|
||||||
|
System.out.println("A password: " + pwdGen.generatePassword());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,109 +0,0 @@
|
|||||||
package org.alfresco.repo.security.encryption;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.Key;
|
|
||||||
import java.security.Security;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|
||||||
|
|
||||||
public class AESEncryptionEngine implements EncryptionEngine
|
|
||||||
{
|
|
||||||
private static String ALGORITHM = "AES/ECB/PKCS5Padding";
|
|
||||||
private static final Log logger = LogFactory.getLog(AESEncryptionEngine.class);
|
|
||||||
|
|
||||||
private KeyProvider keyProvider;
|
|
||||||
//private Key key;
|
|
||||||
|
|
||||||
private ThreadLocal<Cipher> cipher;
|
|
||||||
|
|
||||||
public AESEncryptionEngine()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKeyProvider(KeyProvider keyProvider)
|
|
||||||
{
|
|
||||||
this.keyProvider = keyProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init()
|
|
||||||
{
|
|
||||||
this.cipher = new ThreadLocal<Cipher>();
|
|
||||||
// key = keyProvider.getKey();
|
|
||||||
// if(key == null)
|
|
||||||
// {
|
|
||||||
// throw new AlfrescoRuntimeException("Secret key is null.");
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
protected byte[] process(int cipherMode, byte[] input)
|
|
||||||
{
|
|
||||||
Cipher cipher = this.cipher.get();
|
|
||||||
|
|
||||||
if(cipher == null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
cipher = Cipher.getInstance(ALGORITHM);
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
|
||||||
try
|
|
||||||
{
|
|
||||||
cipher = Cipher.getInstance(ALGORITHM);
|
|
||||||
}
|
|
||||||
catch(Exception e1)
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException("Unable to initialise encryption engine", e1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(cipher == null)
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException("Unable to initialise encryption engine");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cipher.set(cipher);
|
|
||||||
|
|
||||||
logger.debug("Initialised thread local cipher");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
cipher.init(cipherMode, keyProvider.getKey());
|
|
||||||
|
|
||||||
// do the encryption/decryption in one go
|
|
||||||
return cipher.doFinal(input);
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException("Unexpected exception during encryption/decryption", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] encrypt(byte[] input)
|
|
||||||
{
|
|
||||||
return process(Cipher.ENCRYPT_MODE, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] decrypt(byte[] input)
|
|
||||||
{
|
|
||||||
return process(Cipher.DECRYPT_MODE, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] encryptString(String input) throws UnsupportedEncodingException
|
|
||||||
{
|
|
||||||
byte[] in = input.getBytes("UTF-8");
|
|
||||||
return encrypt(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String decryptAsString(byte[] input) throws UnsupportedEncodingException
|
|
||||||
{
|
|
||||||
return new String(decrypt(input), "UTF-8").trim();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,189 @@
|
|||||||
|
package org.alfresco.repo.security.encryption;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.security.AlgorithmParameters;
|
||||||
|
import java.security.Key;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SealedObject;
|
||||||
|
|
||||||
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
|
import org.alfresco.util.Pair;
|
||||||
|
import org.alfresco.util.PropertyCheck;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic support for encryption engines.
|
||||||
|
*
|
||||||
|
* @since 4.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractEncryptor implements Encryptor
|
||||||
|
{
|
||||||
|
private static final Log logger = LogFactory.getLog(AbstractEncryptor.class);
|
||||||
|
|
||||||
|
private KeyProvider keyProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs with defaults
|
||||||
|
*/
|
||||||
|
protected AbstractEncryptor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param keyProvider provides encryption keys based on aliases
|
||||||
|
*/
|
||||||
|
public void setKeyProvider(KeyProvider keyProvider)
|
||||||
|
{
|
||||||
|
this.keyProvider = keyProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init()
|
||||||
|
{
|
||||||
|
PropertyCheck.mandatory(this, "keyProvider", keyProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cipher getCipher(String keyAlias, AlgorithmParameters params, int mode)
|
||||||
|
{
|
||||||
|
// Get the encryption key
|
||||||
|
Key key = keyProvider.getKey(keyAlias);
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
// No encryption possible
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Cipher cipher = getCipher(key, params, mode);
|
||||||
|
// Done
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug("Cipher constructed: alias=" + keyAlias + "; mode=" + mode + ": " + cipher);
|
||||||
|
}
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException(
|
||||||
|
"Failed to construct cipher: alias=" + keyAlias + "; mode=" + mode,
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method to be written by implementations to construct <b>and initialize</b>
|
||||||
|
* physical ciphering objects.
|
||||||
|
*
|
||||||
|
* @param keyAlias the key alias
|
||||||
|
* @param params algorithm-specific parameters
|
||||||
|
* @param mode the cipher mode
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected abstract Cipher getCipher(Key key, AlgorithmParameters params, int mode) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Pair<byte[], AlgorithmParameters> encrypt(String keyAlias, AlgorithmParameters params, byte[] input)
|
||||||
|
{
|
||||||
|
Cipher cipher = getCipher(keyAlias, params, Cipher.ENCRYPT_MODE);
|
||||||
|
if (cipher == null)
|
||||||
|
{
|
||||||
|
return new Pair<byte[], AlgorithmParameters>(input, null);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] output = cipher.doFinal(input);
|
||||||
|
params = cipher.getParameters();
|
||||||
|
return new Pair<byte[], AlgorithmParameters>(output, params);
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException("Decryption failed for key alias: " + keyAlias, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public byte[] decrypt(String keyAlias, AlgorithmParameters params, byte[] input)
|
||||||
|
{
|
||||||
|
Cipher cipher = getCipher(keyAlias, params, Cipher.DECRYPT_MODE);
|
||||||
|
if (cipher == null)
|
||||||
|
{
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return cipher.doFinal(input);
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException("Decryption failed for key alias: " + keyAlias, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* <p/>
|
||||||
|
* Serializes and {@link #encrypt(byte[]) encrypts} the input data.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Pair<byte[], AlgorithmParameters> encryptObject(String keyAlias, AlgorithmParameters params, Object input)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
|
||||||
|
ObjectOutputStream oos = new ObjectOutputStream(bos);
|
||||||
|
oos.writeObject(input);
|
||||||
|
byte[] unencrypted = bos.toByteArray();
|
||||||
|
return encrypt(keyAlias, params, unencrypted);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException("Failed to serialize or encrypt object", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* <p/>
|
||||||
|
* {@link #decrypt(byte[]) Decrypts} and deserializes the input data
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object decryptObject(String keyAlias, AlgorithmParameters params, byte[] input)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] unencrypted = decrypt(keyAlias, params, input);
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(unencrypted);
|
||||||
|
ObjectInputStream ois = new ObjectInputStream(bis);
|
||||||
|
Object obj = ois.readObject();
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException("Failed to deserialize or decrypt object", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SealedObject sealObject(String keyAlias, AlgorithmParameters params, Serializable input)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Serializable unsealObject(String keyAlias, SealedObject input)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,101 +1,21 @@
|
|||||||
package org.alfresco.repo.security.encryption;
|
package org.alfresco.repo.security.encryption;
|
||||||
|
|
||||||
import java.security.InvalidParameterException;
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.Security;
|
|
||||||
|
|
||||||
import javax.crypto.KeyGenerator;
|
import org.alfresco.util.ParameterCheck;
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
/**
|
||||||
import org.alfresco.repo.security.authentication.PasswordGenerator;
|
* Basic support for key providers
|
||||||
import org.apache.commons.logging.Log;
|
*
|
||||||
import org.apache.commons.logging.LogFactory;
|
* @author Derek Hulley
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
* @since 4.0
|
||||||
|
*/
|
||||||
public abstract class AbstractKeyProvider /*extends AbstractLifecycleBean*/ implements KeyProvider
|
public abstract class AbstractKeyProvider implements KeyProvider
|
||||||
{
|
{
|
||||||
private static final Log logger = LogFactory.getLog(KeyProvider.class);
|
@Override
|
||||||
|
public Key getKey(AlfrescoKeyAlias keyAlias)
|
||||||
private static int KEY_SIZE = 256; // this requires unlimited strength policy files
|
|
||||||
private static int DEFAULT_KEY_SIZE = 128; // default key size should work if KEY_SIZE doesn't
|
|
||||||
private static String KEY_ALGORITHM = "AES";
|
|
||||||
|
|
||||||
protected PasswordGenerator passwordGenerator;
|
|
||||||
|
|
||||||
private Key key;
|
|
||||||
|
|
||||||
public void setKey(Key key)
|
|
||||||
{
|
{
|
||||||
this.key = key;
|
ParameterCheck.mandatory("keyAlias", keyAlias);
|
||||||
}
|
return getKey(keyAlias.name());
|
||||||
|
|
||||||
|
|
||||||
public PasswordGenerator getPasswordGenerator()
|
|
||||||
{
|
|
||||||
return passwordGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPasswordGenerator(PasswordGenerator passwordGenerator)
|
|
||||||
{
|
|
||||||
this.passwordGenerator = passwordGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Key getKey()
|
|
||||||
{
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected KeyGenerator getKeyGenerator()
|
|
||||||
{
|
|
||||||
KeyGenerator keyGenerator = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
|
|
||||||
}
|
|
||||||
catch(NoSuchAlgorithmException e)
|
|
||||||
{
|
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
|
||||||
try
|
|
||||||
{
|
|
||||||
keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
|
|
||||||
}
|
|
||||||
catch(NoSuchAlgorithmException e1)
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException("Unable to initialise encryption engine, no key generator is available", e1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(keyGenerator == null)
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException("Unable to initialise encryption engine, no key generator is available");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
keyGenerator.init(KEY_SIZE);
|
|
||||||
}
|
|
||||||
catch(InvalidParameterException e)
|
|
||||||
{
|
|
||||||
logger.warn(KEY_SIZE + " bits key size is not supported, trying " + DEFAULT_KEY_SIZE + " bits");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// try a smaller key size
|
|
||||||
keyGenerator.init(DEFAULT_KEY_SIZE);
|
|
||||||
}
|
|
||||||
catch(InvalidParameterException e1)
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException("Unable to initialise encryption engine, no key generator is available", e1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Key generateSecretKey()
|
|
||||||
{
|
|
||||||
KeyGenerator keyGenerator = getKeyGenerator();
|
|
||||||
return keyGenerator.generateKey();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,129 +0,0 @@
|
|||||||
package org.alfresco.repo.security.encryption;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.Security;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.bouncycastle.crypto.BufferedBlockCipher;
|
|
||||||
import org.bouncycastle.crypto.CipherParameters;
|
|
||||||
import org.bouncycastle.crypto.InvalidCipherTextException;
|
|
||||||
import org.bouncycastle.crypto.engines.AESEngine;
|
|
||||||
import org.bouncycastle.crypto.paddings.BlockCipherPadding;
|
|
||||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
|
||||||
import org.bouncycastle.crypto.paddings.ZeroBytePadding;
|
|
||||||
import org.bouncycastle.crypto.params.KeyParameter;
|
|
||||||
|
|
||||||
public class DefaultEncryptionEngine implements EncryptionEngine
|
|
||||||
{
|
|
||||||
private static final Log logger = LogFactory.getLog(EncryptionEngine.class);
|
|
||||||
|
|
||||||
//private String encryptionProvider;
|
|
||||||
private KeyProvider keyProvider;
|
|
||||||
|
|
||||||
private BufferedBlockCipher cipher;
|
|
||||||
private AESEngine engine;
|
|
||||||
|
|
||||||
//private Cipher cipher;
|
|
||||||
//private byte[] key;
|
|
||||||
|
|
||||||
public DefaultEncryptionEngine(/*byte[] key*/)
|
|
||||||
{
|
|
||||||
// TODO check that this hasn't already been done
|
|
||||||
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
|
|
||||||
//
|
|
||||||
// this.key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKeyProvider(KeyProvider keyProvider)
|
|
||||||
{
|
|
||||||
this.keyProvider = keyProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
// public void setEncryptionProvider(String encryptionProvider)
|
|
||||||
// {
|
|
||||||
// this.encryptionProvider = encryptionProvider;
|
|
||||||
// }
|
|
||||||
|
|
||||||
public void init()
|
|
||||||
{
|
|
||||||
//cipher = Cipher.getInstance("AES");
|
|
||||||
this.engine = new AESEngine();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Paddings available (http://www.bouncycastle.org/docs/docs1.6/org/bouncycastle/crypto/paddings/BlockCipherPadding.html):
|
|
||||||
* - ISO10126d2Padding
|
|
||||||
* - ISO7816d4Padding
|
|
||||||
* - PKCS7Padding
|
|
||||||
* - TBCPadding
|
|
||||||
* - X923Padding
|
|
||||||
* - ZeroBytePadding
|
|
||||||
*/
|
|
||||||
BlockCipherPadding blockCipherPadding = new ZeroBytePadding();
|
|
||||||
this.cipher = new PaddedBufferedBlockCipher(engine, blockCipherPadding);
|
|
||||||
|
|
||||||
// logger.debug("Encryption cipher: " + cipher.getProvider().getInfo());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected byte[] process(boolean toEncrypt, byte[] input)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//CipherParameters param = new KeyParameter(keyProvider.getKey());
|
|
||||||
//cipher.init(toEncrypt, param);
|
|
||||||
|
|
||||||
int inputLength = input.length;
|
|
||||||
int maximumOutputLength = cipher.getOutputSize(inputLength);
|
|
||||||
byte[] output = new byte[maximumOutputLength];
|
|
||||||
|
|
||||||
int outputOffset = 0;
|
|
||||||
int outputLength = 0;
|
|
||||||
int bytesProcessed = cipher.processBytes(input, 0, input.length, output, 0);
|
|
||||||
outputOffset += bytesProcessed;
|
|
||||||
outputLength += bytesProcessed;
|
|
||||||
bytesProcessed = cipher.doFinal(output, outputOffset);
|
|
||||||
outputOffset += bytesProcessed;
|
|
||||||
outputLength += bytesProcessed;
|
|
||||||
|
|
||||||
if(outputLength == output.length)
|
|
||||||
{
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
byte[] truncatedOutput = new byte[outputLength];
|
|
||||||
System.arraycopy(output, 0, truncatedOutput, 0, outputLength);
|
|
||||||
return truncatedOutput;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(InvalidCipherTextException ex)
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException("Unexpected encryption error", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] encrypt(byte[] input)
|
|
||||||
{
|
|
||||||
return process(true, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] decrypt(byte[] input)
|
|
||||||
{
|
|
||||||
return process(false, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] encryptString(String input) throws UnsupportedEncodingException
|
|
||||||
{
|
|
||||||
byte[] in = input.getBytes("UTF-8");
|
|
||||||
return encrypt(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String decryptAsString(byte[] input) throws UnsupportedEncodingException
|
|
||||||
{
|
|
||||||
return new String(decrypt(input), "UTF-8").trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,32 +0,0 @@
|
|||||||
package org.alfresco.repo.security.encryption;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
public class DefaultEncryptionEngineTest extends TestCase
|
|
||||||
{
|
|
||||||
private DefaultEncryptionEngine encryptionEngine;
|
|
||||||
|
|
||||||
public void setUp() throws Exception
|
|
||||||
{
|
|
||||||
encryptionEngine = new DefaultEncryptionEngine();
|
|
||||||
encryptionEngine.setKeyProvider(new TestKeyProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testBasic()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
String testString = "Hello World";
|
|
||||||
|
|
||||||
byte[] bytes = encryptionEngine.encryptString(testString);
|
|
||||||
String output = encryptionEngine.decryptAsString(bytes);
|
|
||||||
assertEquals("", testString, output);
|
|
||||||
}
|
|
||||||
catch(UnsupportedEncodingException ex)
|
|
||||||
{
|
|
||||||
fail("Unexpected exception: " + ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,75 @@
|
|||||||
|
package org.alfresco.repo.security.encryption;
|
||||||
|
|
||||||
|
import java.security.AlgorithmParameters;
|
||||||
|
import java.security.Key;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
|
||||||
|
import org.alfresco.util.PropertyCheck;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Derek Hulley
|
||||||
|
* @since 4.0
|
||||||
|
*/
|
||||||
|
public class DefaultEncryptor extends AbstractEncryptor
|
||||||
|
{
|
||||||
|
private String cipherAlgorithm;
|
||||||
|
private String cipherProvider;
|
||||||
|
|
||||||
|
private final ThreadLocal<Cipher> threadCipher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor for IOC
|
||||||
|
*/
|
||||||
|
public DefaultEncryptor()
|
||||||
|
{
|
||||||
|
threadCipher = new ThreadLocal<Cipher>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience constructor for tests
|
||||||
|
*/
|
||||||
|
/* package */ DefaultEncryptor(KeyProvider keyProvider, String cipherAlgorithm, String cipherProvider)
|
||||||
|
{
|
||||||
|
this();
|
||||||
|
setKeyProvider(keyProvider);
|
||||||
|
setCipherAlgorithm(cipherAlgorithm);
|
||||||
|
setCipherProvider(cipherProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCipherAlgorithm(String cipherAlgorithm)
|
||||||
|
{
|
||||||
|
this.cipherAlgorithm = cipherAlgorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCipherProvider(String cipherProvider)
|
||||||
|
{
|
||||||
|
this.cipherProvider = cipherProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init()
|
||||||
|
{
|
||||||
|
super.init();
|
||||||
|
PropertyCheck.mandatory(this, "cipherAlgorithm", cipherAlgorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Cipher getCipher(Key key, AlgorithmParameters params, int mode) throws Exception
|
||||||
|
{
|
||||||
|
Cipher cipher = threadCipher.get();
|
||||||
|
if (cipher == null)
|
||||||
|
{
|
||||||
|
if (cipherProvider == null)
|
||||||
|
{
|
||||||
|
cipher = Cipher.getInstance(cipherAlgorithm);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cipher = Cipher.getInstance(cipherAlgorithm, cipherProvider);
|
||||||
|
}
|
||||||
|
threadCipher.set(cipher);
|
||||||
|
}
|
||||||
|
cipher.init(mode, key, params);
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +0,0 @@
|
|||||||
package org.alfresco.repo.security.encryption;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
|
|
||||||
|
|
||||||
public interface EncryptionEngine
|
|
||||||
{
|
|
||||||
public byte[] encrypt(byte[] input);
|
|
||||||
public byte[] decrypt(byte[] input);
|
|
||||||
public byte[] encryptString(String input) throws UnsupportedEncodingException;
|
|
||||||
public String decryptAsString(byte[] input) throws UnsupportedEncodingException;
|
|
||||||
}
|
|
@@ -0,0 +1,86 @@
|
|||||||
|
package org.alfresco.repo.security.encryption;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.security.AlgorithmParameters;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SealedObject;
|
||||||
|
|
||||||
|
import org.alfresco.util.Pair;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface providing methods to encrypt and decrypt data.
|
||||||
|
*
|
||||||
|
* @since 4.0
|
||||||
|
*/
|
||||||
|
public interface Encryptor
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the basic cipher that must be used for the given use-case
|
||||||
|
*
|
||||||
|
* @param keyAlias the encryption key alias
|
||||||
|
* @param params the parameters for the encryption or decryption
|
||||||
|
* @param mode the encryption mode
|
||||||
|
* @return the cipher to use or <tt>null</tt> if there is no
|
||||||
|
* key associated with the key alias
|
||||||
|
*/
|
||||||
|
Cipher getCipher(String keyAlias, AlgorithmParameters params, int mode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt some bytes
|
||||||
|
*
|
||||||
|
* @param keyAlias the encryption key alias
|
||||||
|
* @param input the data to encrypt
|
||||||
|
* @return the encrypted data and parameters used
|
||||||
|
*/
|
||||||
|
Pair<byte[], AlgorithmParameters> encrypt(String keyAlias, AlgorithmParameters params, byte[] input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt some bytes
|
||||||
|
*
|
||||||
|
* @param keyAlias the encryption key alias
|
||||||
|
* @param input the data to decrypt
|
||||||
|
* @return the unencrypted data
|
||||||
|
*/
|
||||||
|
byte[] decrypt(String keyAlias, AlgorithmParameters params, byte[] input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt an object
|
||||||
|
*
|
||||||
|
* @param keyAlias the encryption key alias
|
||||||
|
* @param input the object to write to bytes
|
||||||
|
* @return the encrypted data and parameters used
|
||||||
|
*/
|
||||||
|
Pair<byte[], AlgorithmParameters> encryptObject(String keyAlias, AlgorithmParameters params, Object input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt data as an object
|
||||||
|
*
|
||||||
|
* @param keyAlias the encryption key alias
|
||||||
|
* @param input the data to decrypt
|
||||||
|
* @return the unencrypted data deserialized
|
||||||
|
*/
|
||||||
|
Object decryptObject(String keyAlias, AlgorithmParameters params, byte[] input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to seal on object up cryptographically
|
||||||
|
*
|
||||||
|
* @param keyAlias the encryption key alias
|
||||||
|
* @param input the object to encrypt and seal
|
||||||
|
* @return the sealed object that can be decrypted with the original key
|
||||||
|
*/
|
||||||
|
SealedObject sealObject(String keyAlias, AlgorithmParameters params, Serializable input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to unseal on object up cryptographically.
|
||||||
|
* <p/>
|
||||||
|
* Note that the algorithm parameters are stored in the sealed object and are
|
||||||
|
* not therefore required for decryption.
|
||||||
|
*
|
||||||
|
* @param keyAlias the encryption key alias
|
||||||
|
* @param input the object to decrypt and unseal
|
||||||
|
* @return the original unsealed object that was encrypted with the original key
|
||||||
|
*/
|
||||||
|
Serializable unsealObject(String keyAlias, SealedObject input);
|
||||||
|
}
|
@@ -0,0 +1,61 @@
|
|||||||
|
package org.alfresco.repo.security.encryption;
|
||||||
|
|
||||||
|
import java.security.AlgorithmParameters;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.alfresco.util.Pair;
|
||||||
|
import org.bouncycastle.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.0
|
||||||
|
*/
|
||||||
|
public class EncryptorTest extends TestCase
|
||||||
|
{
|
||||||
|
private DefaultEncryptor encryptor;
|
||||||
|
|
||||||
|
public void setUp() throws Exception
|
||||||
|
{
|
||||||
|
encryptor = new DefaultEncryptor(
|
||||||
|
KeyStoreKeyProviderTest.getTestKeyStoreProvider(),
|
||||||
|
"DESede/CBC/PKCS5Padding",
|
||||||
|
null);
|
||||||
|
encryptor.init(); // Not currently necessary
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBasicBytes_NoKey()
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[] {11, 12, 13};
|
||||||
|
|
||||||
|
Pair<byte[], AlgorithmParameters> encryptedPair = encryptor.encrypt("fluff", null, bytes);
|
||||||
|
byte[] decrypted = encryptor.decrypt(
|
||||||
|
"fluff",
|
||||||
|
encryptedPair.getSecond(),
|
||||||
|
encryptedPair.getFirst());
|
||||||
|
assertTrue("Encryption round trip failed. ", Arrays.areEqual(bytes, decrypted));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBasicBytes_WithKey()
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[] {11, 12, 13};
|
||||||
|
|
||||||
|
Pair<byte[], AlgorithmParameters> encryptedPair = encryptor.encrypt("mykey1", null, bytes);
|
||||||
|
byte[] decrypted = encryptor.decrypt(
|
||||||
|
"mykey1",
|
||||||
|
encryptedPair.getSecond(),
|
||||||
|
encryptedPair.getFirst());
|
||||||
|
assertTrue("Encryption round trip failed. ", Arrays.areEqual(bytes, decrypted));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBasicObject()
|
||||||
|
{
|
||||||
|
Object testObject = " This is a string, but will be serialized ";
|
||||||
|
|
||||||
|
Pair<byte[], AlgorithmParameters> encryptedPair = encryptor.encryptObject("mykey2", null, testObject);
|
||||||
|
Object output = encryptor.decryptObject(
|
||||||
|
"mykey2",
|
||||||
|
encryptedPair.getSecond(),
|
||||||
|
encryptedPair.getFirst());
|
||||||
|
assertEquals("Encryption round trip failed. ", testObject, output);
|
||||||
|
}
|
||||||
|
}
|
@@ -3,11 +3,37 @@ package org.alfresco.repo.security.encryption;
|
|||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A key provider returns the secret key used to encrypt text and mltext properties in the
|
* A key provider returns the secret keys for different use cases.
|
||||||
* database.
|
|
||||||
*
|
*
|
||||||
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
public interface KeyProvider
|
public interface KeyProvider
|
||||||
{
|
{
|
||||||
public Key getKey();
|
/**
|
||||||
|
* Enumeration of key aliases supported internally by Alfresco
|
||||||
|
*
|
||||||
|
* @author derekh
|
||||||
|
* @since 4.0
|
||||||
|
*/
|
||||||
|
public static enum AlfrescoKeyAlias
|
||||||
|
{
|
||||||
|
METADATA,
|
||||||
|
SOLR
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an encryption key if available.
|
||||||
|
*
|
||||||
|
* @param keyAlias the key alias
|
||||||
|
* @return the encryption key or <tt>null</tt> if there is no associated key
|
||||||
|
*/
|
||||||
|
public Key getKey(String keyAlias);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an encryption key if available, using a convenience constant.
|
||||||
|
*
|
||||||
|
* @param keyAlias the key alias
|
||||||
|
* @return the encryption key or <tt>null</tt> if there is no associated key
|
||||||
|
*/
|
||||||
|
public Key getKey(AlfrescoKeyAlias keyAlias);
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,161 @@
|
|||||||
|
package org.alfresco.repo.security.encryption;
|
||||||
|
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
|
import org.alfresco.util.ApplicationContextHelper;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link KeystoreKeyProvider}
|
||||||
|
*
|
||||||
|
* @author Derek Hulley
|
||||||
|
* @since 4.0
|
||||||
|
*/
|
||||||
|
public class KeyStoreKeyProviderTest extends TestCase
|
||||||
|
{
|
||||||
|
private static final String FILE_ONE = "classpath:alfresco/keystore-tests/ks-test-1.jks";
|
||||||
|
private static final String FILE_TWO = "classpath:alfresco/keystore-tests/ks-test-2.jks";
|
||||||
|
private static final String FILE_THREE = "classpath:alfresco/keystore-tests/ks-test-3.jks";
|
||||||
|
private static final String ALIAS_ONE = "mykey1";
|
||||||
|
private static final String ALIAS_TWO = "mykey2";
|
||||||
|
private static final String ALIAS_THREE = "mykey3";
|
||||||
|
|
||||||
|
public void setUp() throws Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper utility to create a two-alias keystore.
|
||||||
|
*/
|
||||||
|
/* package */ static KeystoreKeyProvider getTestKeyStoreProvider()
|
||||||
|
{
|
||||||
|
Map<String, String> passwords = new HashMap<String, String>(5);
|
||||||
|
passwords.put(KeystoreKeyProvider.KEY_KEYSTORE_PASSWORD, "ksPwd2");
|
||||||
|
passwords.put(ALIAS_ONE, "aliasPwd1");
|
||||||
|
passwords.put(ALIAS_TWO, "aliasPwd2");
|
||||||
|
KeystoreKeyProvider ks = new KeystoreKeyProvider(
|
||||||
|
FILE_TWO,
|
||||||
|
"SunJCE",
|
||||||
|
"JCEKS",
|
||||||
|
passwords);
|
||||||
|
return ks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNoKeyStorePasswords() throws Exception
|
||||||
|
{
|
||||||
|
KeystoreKeyProvider keyProvider = new KeystoreKeyProvider(
|
||||||
|
FILE_ONE,
|
||||||
|
"SunJCE",
|
||||||
|
"JCEKS",
|
||||||
|
Collections.<String,String>emptyMap());
|
||||||
|
// This has succeeded because we have not attempted to access it
|
||||||
|
assertNull("Should be no keys available", keyProvider.getKey(ALIAS_ONE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testKeyStoreWithOnlyAliasPasswords() throws Exception
|
||||||
|
{
|
||||||
|
KeystoreKeyProvider keyProvider = new KeystoreKeyProvider(
|
||||||
|
FILE_TWO,
|
||||||
|
"SunJCE",
|
||||||
|
"JCEKS",
|
||||||
|
Collections.singletonMap(ALIAS_ONE, "aliasPwd1"));
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAliasWithIncorrectPassword_One() throws Exception
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
new KeystoreKeyProvider(
|
||||||
|
FILE_ONE,
|
||||||
|
"SunJCE",
|
||||||
|
"JCEKS",
|
||||||
|
Collections.singletonMap(ALIAS_ONE, "password_fail"));
|
||||||
|
fail("Expect to fail because password is incorrect");
|
||||||
|
}
|
||||||
|
catch (AlfrescoRuntimeException e)
|
||||||
|
{
|
||||||
|
// Expected
|
||||||
|
assertTrue(e.getCause() instanceof UnrecoverableKeyException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAliasWithIncorrectPassword_Two() throws Exception
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
new KeystoreKeyProvider(
|
||||||
|
FILE_TWO,
|
||||||
|
"SunJCE",
|
||||||
|
"JCEKS",
|
||||||
|
Collections.singletonMap(ALIAS_TWO, "password_fail"));
|
||||||
|
fail("Expect to fail because password is incorrect");
|
||||||
|
}
|
||||||
|
catch (AlfrescoRuntimeException e)
|
||||||
|
{
|
||||||
|
// Expected
|
||||||
|
assertTrue(e.getCause() instanceof UnrecoverableKeyException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAliasWithCorrectPassword_One() throws Exception
|
||||||
|
{
|
||||||
|
KeystoreKeyProvider ks = new KeystoreKeyProvider(
|
||||||
|
FILE_ONE,
|
||||||
|
"SunJCE",
|
||||||
|
"JCEKS",
|
||||||
|
Collections.singletonMap(ALIAS_ONE, "aliasPwd1"));
|
||||||
|
Key keyOne = ks.getKey(ALIAS_ONE);
|
||||||
|
assertNotNull(keyOne);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAliasWithCorrectPassword_Two() throws Exception
|
||||||
|
{
|
||||||
|
Map<String, String> passwords = new HashMap<String, String>(5);
|
||||||
|
passwords.put(ALIAS_ONE, "aliasPwd1");
|
||||||
|
passwords.put(ALIAS_TWO, "aliasPwd2");
|
||||||
|
KeystoreKeyProvider ks = new KeystoreKeyProvider(
|
||||||
|
FILE_TWO,
|
||||||
|
"SunJCE",
|
||||||
|
"JCEKS",
|
||||||
|
passwords);
|
||||||
|
assertNotNull(ks.getKey(ALIAS_ONE));
|
||||||
|
assertNotNull(ks.getKey(ALIAS_TWO));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAliasWithCorrectPassword_Three() throws Exception
|
||||||
|
{
|
||||||
|
Map<String, String> passwords = new HashMap<String, String>(5);
|
||||||
|
passwords.put(ALIAS_ONE, "aliasPwd1");
|
||||||
|
passwords.put(ALIAS_TWO, "aliasPwd2");
|
||||||
|
passwords.put(ALIAS_THREE, "aliasPwd3");
|
||||||
|
KeystoreKeyProvider ks = new KeystoreKeyProvider(
|
||||||
|
FILE_THREE,
|
||||||
|
"SunJCE",
|
||||||
|
"JCEKS",
|
||||||
|
passwords);
|
||||||
|
assertNotNull(ks.getKey(ALIAS_ONE));
|
||||||
|
assertNotNull(ks.getKey(ALIAS_TWO));
|
||||||
|
assertNull(ks.getKey(ALIAS_THREE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Do we need spring-crypto when it is V1.0?
|
||||||
|
*/
|
||||||
|
public void DISABLED_testSpringCrypto() throws Throwable
|
||||||
|
{
|
||||||
|
ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(
|
||||||
|
new String[] {"alfresco/keystore-tests/encryption-test-context.xml"});
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
KeyStore ks1 = (KeyStore) ctx.getBean("ks-test-1");
|
||||||
|
}
|
||||||
|
}
|
@@ -1,171 +1,246 @@
|
|||||||
package org.alfresco.repo.security.encryption;
|
package org.alfresco.repo.security.encryption;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.KeyStoreException;
|
import java.util.HashMap;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.util.Map;
|
||||||
import java.security.cert.CertificateException;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||||
import javax.crypto.SecretKey;
|
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
import org.alfresco.util.ParameterCheck;
|
import org.alfresco.util.PropertyCheck;
|
||||||
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.util.ResourceUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Provides the system-wide secret key for symmetric database encryption from a key store
|
* Provides the system-wide secret key for symmetric database encryption from a key store
|
||||||
* in the filesystem.
|
* in the filesystem.
|
||||||
*
|
*
|
||||||
|
* @author Derek Hulley
|
||||||
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
public class KeystoreKeyProvider extends AbstractKeyProvider
|
public class KeystoreKeyProvider extends AbstractKeyProvider
|
||||||
{
|
{
|
||||||
|
public static final String KEY_KEYSTORE_PASSWORD = "keystore";
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(KeyProvider.class);
|
private static final Log logger = LogFactory.getLog(KeyProvider.class);
|
||||||
|
|
||||||
private static String KEY_STORE_TYPE = "JCEKS";
|
// Will be cleared after initialization
|
||||||
private static String SECRET_KEY_ALIAS = "secret";
|
private Map<String, String> passwords;
|
||||||
|
private String location;
|
||||||
|
private String provider;
|
||||||
|
private String type;
|
||||||
|
private Map<String, Key> keys;
|
||||||
|
|
||||||
// key store holding the secret key for encrypting and decrypting repository properties
|
private final ReadLock readLock;
|
||||||
private String keyStoreFile;
|
private final WriteLock writeLock;
|
||||||
|
|
||||||
// key store passwords
|
/**
|
||||||
private char[] keyStorePassword;
|
* Constructs the provider with required defaults
|
||||||
private char[] secretKeyPassword;
|
|
||||||
|
|
||||||
public void setKeyStoreFile(String keyStoreFile)
|
|
||||||
{
|
|
||||||
this.keyStoreFile = keyStoreFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKeyStorePassword(String keyStorePassword)
|
|
||||||
{
|
|
||||||
this.keyStorePassword = keyStorePassword.toCharArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSecretKeyPassword(String secretKeyPassword)
|
|
||||||
{
|
|
||||||
this.secretKeyPassword = secretKeyPassword.toCharArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Key getKey()
|
|
||||||
{
|
|
||||||
return super.getKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void saveKeyStore(KeyStore ks) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException
|
|
||||||
{
|
|
||||||
FileOutputStream fos = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
fos = new FileOutputStream(keyStoreFile);
|
|
||||||
ks.store(fos, keyStorePassword);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if(fos != null)
|
|
||||||
{
|
|
||||||
fos.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Create a new secret key and store it in the keystore ks
|
|
||||||
*/
|
*/
|
||||||
protected void createSecretKey(KeyStore ks) throws Exception
|
public KeystoreKeyProvider()
|
||||||
{
|
{
|
||||||
Key key = generateSecretKey();
|
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||||
if(key == null)
|
readLock = lock.readLock();
|
||||||
{
|
writeLock = lock.writeLock();
|
||||||
throw new AlfrescoRuntimeException("Unable to generate secret key");
|
keys = new HashMap<String, Key>(7);
|
||||||
}
|
|
||||||
|
|
||||||
byte[] encoded = key.getEncoded();
|
|
||||||
|
|
||||||
logger.debug("secret key size = " + (encoded.length * 8) + " bits");
|
|
||||||
|
|
||||||
KeyStore.SecretKeyEntry skEntry = new KeyStore.SecretKeyEntry((SecretKey)key);
|
|
||||||
|
|
||||||
ks.setEntry(SECRET_KEY_ALIAS, skEntry, new KeyStore.PasswordProtection(secretKeyPassword));
|
|
||||||
|
|
||||||
saveKeyStore(ks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void loadKeyStore()
|
/**
|
||||||
|
* Convenience constructor for tests. Note that {@link #init()} is also called.
|
||||||
|
*/
|
||||||
|
/* package */ KeystoreKeyProvider(String location, String provider, String type, Map<String, String> passwords)
|
||||||
{
|
{
|
||||||
InputStream is = null;
|
this();
|
||||||
KeyStore ks = null;
|
setLocation(location);
|
||||||
|
setProvider(provider);
|
||||||
|
setType(type);
|
||||||
|
setPasswords(passwords);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
try
|
public void setLocation(String location)
|
||||||
{
|
{
|
||||||
ks = KeyStore.getInstance(KEY_STORE_TYPE);
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
File f = new File(keyStoreFile);
|
public void setProvider(String provider)
|
||||||
if(!f.exists())
|
{
|
||||||
{
|
this.provider = provider;
|
||||||
// no keystore, create one and save it
|
}
|
||||||
ks.load(null, keyStorePassword);
|
|
||||||
|
|
||||||
// generate a secret key
|
public void setType(String type)
|
||||||
createSecretKey(ks);
|
{
|
||||||
}
|
this.type = type;
|
||||||
else
|
}
|
||||||
{
|
|
||||||
is = new BufferedInputStream(new FileInputStream(keyStoreFile));
|
|
||||||
ks.load(is, keyStorePassword);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(Throwable e)
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException(
|
|
||||||
"Unable to load keystore from " + keyStoreFile, e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (is != null)
|
|
||||||
|
|
||||||
{
|
/**
|
||||||
try
|
* Set the map of passwords to access the keystore.
|
||||||
{
|
* <p/>
|
||||||
is.close();
|
* Where required, <tt>null</tt> values must be inserted into the map to indicate the presence
|
||||||
}
|
* of a key that is not protected by a password. They entry for {@link #KEY_KEYSTORE_PASSWORD}
|
||||||
catch(IOException e)
|
* is required if the keystore is password protected.
|
||||||
{
|
*
|
||||||
}
|
* @param passwords a map of passwords including <tt>null</tt> values
|
||||||
}
|
*/
|
||||||
}
|
public void setPasswords(Map<String, String> passwords)
|
||||||
|
{
|
||||||
try
|
this.passwords = new HashMap<String, String>(passwords);
|
||||||
{
|
|
||||||
Key key = ks.getKey(SECRET_KEY_ALIAS, secretKeyPassword);
|
|
||||||
if(key == null)
|
|
||||||
{
|
|
||||||
createSecretKey(ks);
|
|
||||||
}
|
|
||||||
|
|
||||||
setKey(key);
|
|
||||||
}
|
|
||||||
catch(Throwable e)
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException(
|
|
||||||
"Unable to get secret key from " + keyStoreFile, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init()
|
public void init()
|
||||||
{
|
{
|
||||||
ParameterCheck.mandatory("keyStoreFile", keyStoreFile);
|
writeLock.lock();
|
||||||
ParameterCheck.mandatory("passwordGenerator", passwordGenerator);
|
try
|
||||||
|
{
|
||||||
loadKeyStore();
|
safeInit();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
writeLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes class; must be done in a write lock.
|
||||||
|
*/
|
||||||
|
private void safeInit()
|
||||||
|
{
|
||||||
|
if (!PropertyCheck.isValidPropertyString(location))
|
||||||
|
{
|
||||||
|
location = null;
|
||||||
|
}
|
||||||
|
if (!PropertyCheck.isValidPropertyString(provider))
|
||||||
|
{
|
||||||
|
provider = null;
|
||||||
|
}
|
||||||
|
if (!PropertyCheck.isValidPropertyString(type))
|
||||||
|
{
|
||||||
|
type = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyCheck.mandatory(this, "location", location);
|
||||||
|
// Extract the keystore password
|
||||||
|
String pwdKeyStore = passwords.get(KEY_KEYSTORE_PASSWORD);
|
||||||
|
|
||||||
|
// Make sure we choose the default type, if required
|
||||||
|
if (type == null)
|
||||||
|
{
|
||||||
|
type = KeyStore.getDefaultType();
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyStore ks = null;
|
||||||
|
InputStream is = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (provider == null)
|
||||||
|
{
|
||||||
|
ks = KeyStore.getInstance(type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ks = KeyStore.getInstance(type, provider);
|
||||||
|
}
|
||||||
|
// Load it up
|
||||||
|
File ksFile = ResourceUtils.getFile(location);
|
||||||
|
if (!ksFile.exists())
|
||||||
|
{
|
||||||
|
throw new IOException("Unable to find keystore file: " + ksFile);
|
||||||
|
}
|
||||||
|
is = new FileInputStream(ksFile);
|
||||||
|
ks.load(is, pwdKeyStore == null ? null : pwdKeyStore.toCharArray());
|
||||||
|
// Loaded
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug(
|
||||||
|
"Initialize keystore provider: \n" +
|
||||||
|
" Location: " + location + "\n" +
|
||||||
|
" Provider: " + provider + "\n" +
|
||||||
|
" Type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException(
|
||||||
|
"Failed to initialize keystore provider: \n" +
|
||||||
|
" Location: " + location + "\n" +
|
||||||
|
" Provider: " + provider + "\n" +
|
||||||
|
" Type: " + type,
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
pwdKeyStore = null;
|
||||||
|
passwords.remove(KEY_KEYSTORE_PASSWORD);
|
||||||
|
if (is != null)
|
||||||
|
{
|
||||||
|
try { is.close(); } catch (Throwable e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now get the other keys
|
||||||
|
for (Map.Entry<String, String> element : passwords.entrySet())
|
||||||
|
{
|
||||||
|
String keyAlias = element.getKey();
|
||||||
|
String passwordStr = element.getValue();
|
||||||
|
if (!PropertyCheck.isValidPropertyString(passwordStr))
|
||||||
|
{
|
||||||
|
// Force a failure because the property was not properly initialized
|
||||||
|
PropertyCheck.mandatory(this, "passwords." + keyAlias, null);
|
||||||
|
}
|
||||||
|
// Null is an acceptable value (means no key)
|
||||||
|
Key key = null;
|
||||||
|
// Attempt to key the key
|
||||||
|
try
|
||||||
|
{
|
||||||
|
key = ks.getKey(keyAlias, passwordStr == null ? null : passwordStr.toCharArray());
|
||||||
|
keys.put(keyAlias, key);
|
||||||
|
// Key loaded
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug(
|
||||||
|
"Retrieved key from keystore: \n" +
|
||||||
|
" Location: " + location + "\n" +
|
||||||
|
" Provider: " + provider + "\n" +
|
||||||
|
" Type: " + type + "\n" +
|
||||||
|
" Alias: " + keyAlias + "\n" +
|
||||||
|
" Password?: " + (passwordStr != null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException(
|
||||||
|
"Failed to retrieve key from keystore: \n" +
|
||||||
|
" Location: " + location + "\n" +
|
||||||
|
" Provider: " + provider + "\n" +
|
||||||
|
" Type: " + type + "\n" +
|
||||||
|
" Alias: " + keyAlias + "\n" +
|
||||||
|
" Password?: " + (passwordStr != null),
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clear passwords
|
||||||
|
passwords.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key getKey(String keyAlias)
|
||||||
|
{
|
||||||
|
readLock.lock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return keys.get(keyAlias);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
readLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,26 +0,0 @@
|
|||||||
package org.alfresco.repo.security.encryption;
|
|
||||||
|
|
||||||
import java.security.Key;
|
|
||||||
|
|
||||||
import javax.crypto.KeyGenerator;
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
|
||||||
|
|
||||||
public class TestKeyProvider implements KeyProvider
|
|
||||||
{
|
|
||||||
public Key getKey()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
|
|
||||||
SecretKey key = keyGenerator.generateKey();
|
|
||||||
return key;
|
|
||||||
// return Hex.decode("80000000000000000000000000000000");
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
throw new AlfrescoRuntimeException("Unexpected exception generating secret key", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
|
||||||
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:crypt="http://code.google.com/p/spring-crypto-utils/schema/crypt"
|
||||||
|
xsi:schemaLocation="
|
||||||
|
http://www.springframework.org/schema/beans
|
||||||
|
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
|
http://code.google.com/p/spring-crypto-utils/schema/crypt
|
||||||
|
http://code.google.com/p/spring-crypto-utils/schema/crypt.xsd">
|
||||||
|
|
||||||
|
<crypt:keystore id="ks-test-1" location="classpath:alfresco/keystore-tests/ks-test-1.jks" password="ksPwd1" type="JCEKS"/>
|
||||||
|
<crypt:keystore id="ks-test-2" location="classpath:alfresco/keystore-tests/ks-test-2.jks" password="ksPwd2" type="JCEKS"/>
|
||||||
|
|
||||||
|
</beans>
|
BIN
source/test-resources/alfresco/keystore-tests/ks-test-1.jks
Normal file
BIN
source/test-resources/alfresco/keystore-tests/ks-test-1.jks
Normal file
Binary file not shown.
BIN
source/test-resources/alfresco/keystore-tests/ks-test-2.jks
Normal file
BIN
source/test-resources/alfresco/keystore-tests/ks-test-2.jks
Normal file
Binary file not shown.
BIN
source/test-resources/alfresco/keystore-tests/ks-test-3.jks
Normal file
BIN
source/test-resources/alfresco/keystore-tests/ks-test-3.jks
Normal file
Binary file not shown.
BIN
source/test-resources/alfresco/keystore-tests/ks-test-4.jks
Normal file
BIN
source/test-resources/alfresco/keystore-tests/ks-test-4.jks
Normal file
Binary file not shown.
37
source/test-resources/alfresco/keystore-tests/passwords.txt
Normal file
37
source/test-resources/alfresco/keystore-tests/passwords.txt
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
The keystores contained in this folder are used by tests.
|
||||||
|
There are their details:
|
||||||
|
|
||||||
|
==================
|
||||||
|
== ks-test-1.ks ==
|
||||||
|
==================
|
||||||
|
|
||||||
|
Keystore password: ksPwd1
|
||||||
|
Keystore type: JCEKS
|
||||||
|
Keystore provider: SunJCE
|
||||||
|
|
||||||
|
Your keystore contains 1 entry
|
||||||
|
mykey1: aliasPwd1
|
||||||
|
|
||||||
|
==================
|
||||||
|
== ks-test-2.ks ==
|
||||||
|
==================
|
||||||
|
|
||||||
|
Keystore password: ksPwd2
|
||||||
|
Keystore type: JCEKS
|
||||||
|
Keystore provider: SunJCE
|
||||||
|
|
||||||
|
Your keystore contains 2 entries
|
||||||
|
mykey1: aliasPwd1
|
||||||
|
mykey2: aliasPwd2
|
||||||
|
|
||||||
|
==================
|
||||||
|
== ks-test-3.ks ==
|
||||||
|
==================
|
||||||
|
|
||||||
|
Keystore password: ksPwd3
|
||||||
|
Keystore type: JCEKS
|
||||||
|
Keystore provider: SunJCE
|
||||||
|
|
||||||
|
Your keystore contains 2 entries
|
||||||
|
mykey1: aliasPwd1
|
||||||
|
mykey2: aliasPwd2
|
Reference in New Issue
Block a user