mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Fixed mass encryption/decryption of properties and related tests
- ALF-8646: RINF 38: Text data encryption - ALF-9055: RINF 38: Support encryption against existing data git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28508 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -73,6 +73,7 @@ import org.alfresco.service.namespace.RegexQNamePattern;
|
|||||||
import org.alfresco.service.transaction.TransactionService;
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
import org.alfresco.util.BaseSpringTest;
|
import org.alfresco.util.BaseSpringTest;
|
||||||
import org.alfresco.util.GUID;
|
import org.alfresco.util.GUID;
|
||||||
|
import org.alfresco.util.TestWithUserUtils;
|
||||||
import org.hibernate.dialect.DB2Dialect;
|
import org.hibernate.dialect.DB2Dialect;
|
||||||
import org.hibernate.dialect.Dialect;
|
import org.hibernate.dialect.Dialect;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
@@ -1743,6 +1744,48 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
|
|||||||
assertTrue("Serialization/deserialization failed", checkPropertyQname instanceof QName);
|
assertTrue("Serialization/deserialization failed", checkPropertyQname instanceof QName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testEncryptionAndDecryptionOfProperties()
|
||||||
|
{
|
||||||
|
QName valueToEncrypt = PROP_QNAME_CONTENT_VALUE;
|
||||||
|
QName value = PROP_QNAME_CONTENT_VALUE;
|
||||||
|
|
||||||
|
// Test single property encryption/decryption
|
||||||
|
Serializable encryptedProperty = metadataEncryptor.encrypt(PROP_QNAME_ENCRYPTED_VALUE, valueToEncrypt);
|
||||||
|
assertTrue("Not a SealedObject", encryptedProperty instanceof SealedObject);
|
||||||
|
Serializable encryptedPropertyAgain = metadataEncryptor.encrypt(PROP_QNAME_ENCRYPTED_VALUE, encryptedProperty);
|
||||||
|
assertTrue("Re-encryption not expected", encryptedProperty == encryptedPropertyAgain);
|
||||||
|
Serializable decryptedProperty = metadataEncryptor.decrypt(PROP_QNAME_ENCRYPTED_VALUE, encryptedProperty);
|
||||||
|
assertEquals("Value not decrypted correctly", valueToEncrypt, decryptedProperty);
|
||||||
|
|
||||||
|
// Test mass property encryption/decryption
|
||||||
|
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(5);
|
||||||
|
properties.put(PROP_QNAME_ENCRYPTED_VALUE, valueToEncrypt);
|
||||||
|
properties.put(PROP_QNAME_QNAME_VALUE, value);
|
||||||
|
Map<QName, Serializable> encryptedProperties = metadataEncryptor.encrypt(properties);
|
||||||
|
assertTrue("Not a SealedObject", encryptedProperties.get(PROP_QNAME_ENCRYPTED_VALUE) instanceof SealedObject);
|
||||||
|
assertTrue("Should not encrypt", encryptedProperties.get(PROP_QNAME_QNAME_VALUE) instanceof QName);
|
||||||
|
Map<QName, Serializable> encryptedPropertiesAgain = metadataEncryptor.encrypt(encryptedProperties);
|
||||||
|
assertTrue("Map should not change", encryptedProperties == encryptedPropertiesAgain);
|
||||||
|
assertTrue(
|
||||||
|
"Re-encryption not expected",
|
||||||
|
encryptedProperties.get(PROP_QNAME_ENCRYPTED_VALUE) == encryptedPropertiesAgain.get(PROP_QNAME_ENCRYPTED_VALUE));
|
||||||
|
assertTrue("Should not encrypt", encryptedProperties.get(PROP_QNAME_QNAME_VALUE) instanceof QName);
|
||||||
|
Map<QName, Serializable> decryptedProperties = metadataEncryptor.decrypt(encryptedProperties);
|
||||||
|
assertEquals("Values not decrypted correctly", valueToEncrypt, decryptedProperties.get(PROP_QNAME_ENCRYPTED_VALUE));
|
||||||
|
assertEquals("Values not decrypted correctly", value, decryptedProperties.get(PROP_QNAME_QNAME_VALUE));
|
||||||
|
|
||||||
|
// Check that nulls are handled
|
||||||
|
Map<QName, Serializable> propertiesNull = new HashMap<QName, Serializable>(5);
|
||||||
|
propertiesNull.put(PROP_QNAME_ENCRYPTED_VALUE, null);
|
||||||
|
propertiesNull.put(PROP_QNAME_QNAME_VALUE, null);
|
||||||
|
Map<QName, Serializable> encryptedPropertiesNull = metadataEncryptor.encrypt(propertiesNull);
|
||||||
|
assertTrue("Map should not change", encryptedPropertiesNull == propertiesNull);
|
||||||
|
assertNull("Null should remain", encryptedPropertiesNull.get(PROP_QNAME_ENCRYPTED_VALUE));
|
||||||
|
Map<QName, Serializable> decryptedPropertiesNull = metadataEncryptor.decrypt(encryptedPropertiesNull);
|
||||||
|
assertTrue("Map should not change", encryptedPropertiesNull == decryptedPropertiesNull);
|
||||||
|
assertNull("Null should remain", decryptedPropertiesNull.get(PROP_QNAME_ENCRYPTED_VALUE));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check that <b>d:encrypted</b> properties work correctly.
|
* Check that <b>d:encrypted</b> properties work correctly.
|
||||||
*/
|
*/
|
||||||
@@ -1759,7 +1802,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
|
|||||||
assertTrue("Properties not encrypted", checkProperty instanceof SealedObject);
|
assertTrue("Properties not encrypted", checkProperty instanceof SealedObject);
|
||||||
|
|
||||||
// create node
|
// create node
|
||||||
NodeRef nodeRef = nodeService.createNode(
|
final NodeRef nodeRef = nodeService.createNode(
|
||||||
rootNodeRef,
|
rootNodeRef,
|
||||||
ASSOC_TYPE_QNAME_TEST_CHILDREN,
|
ASSOC_TYPE_QNAME_TEST_CHILDREN,
|
||||||
QName.createQName("pathA"),
|
QName.createQName("pathA"),
|
||||||
@@ -1774,10 +1817,46 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
|
|||||||
checkProperty = checkProperties.get(PROP_QNAME_ENCRYPTED_VALUE);
|
checkProperty = checkProperties.get(PROP_QNAME_ENCRYPTED_VALUE);
|
||||||
assertTrue("Encrypted property not persisted", checkProperty instanceof SealedObject);
|
assertTrue("Encrypted property not persisted", checkProperty instanceof SealedObject);
|
||||||
|
|
||||||
|
// Decrypt individual property
|
||||||
|
checkProperty = metadataEncryptor.decrypt(PROP_QNAME_ENCRYPTED_VALUE, checkProperty);
|
||||||
|
assertEquals("Bulk property decryption failed", property, checkProperty);
|
||||||
|
|
||||||
|
// Now decrypt en-masse
|
||||||
|
checkProperties = metadataEncryptor.decrypt(checkProperties);
|
||||||
|
checkProperty = checkProperties.get(PROP_QNAME_ENCRYPTED_VALUE);
|
||||||
|
assertEquals("Bulk property decryption failed", property, checkProperty);
|
||||||
|
|
||||||
// Now make sure that the value can be null
|
// Now make sure that the value can be null
|
||||||
nodeService.setProperty(nodeRef, PROP_QNAME_ENCRYPTED_VALUE, null);
|
RetryingTransactionCallback<Void> setNullPropCallback = new RetryingTransactionCallback<Void>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Void execute() throws Throwable
|
||||||
|
{
|
||||||
|
nodeService.setProperty(nodeRef, PROP_QNAME_ENCRYPTED_VALUE, null);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
retryingTransactionHelper.doInTransaction(setNullPropCallback);
|
||||||
|
|
||||||
// Finally, make sure that it fails if we don't encrypt
|
// Finally, make sure that it fails if we don't encrypt
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RetryingTransactionCallback<Void> setUnencryptedPropCallback = new RetryingTransactionCallback<Void>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Void execute() throws Throwable
|
||||||
|
{
|
||||||
|
nodeService.setProperty(nodeRef, PROP_QNAME_ENCRYPTED_VALUE, "No encrypted");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
retryingTransactionHelper.doInTransaction(setUnencryptedPropCallback);
|
||||||
|
fail("Failed to detect unencrypted property"); // This behaviour may change
|
||||||
|
}
|
||||||
|
catch (RuntimeException e)
|
||||||
|
{
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@@ -2,7 +2,9 @@ package org.alfresco.repo.node.encryption;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.crypto.SealedObject;
|
import javax.crypto.SealedObject;
|
||||||
|
|
||||||
@@ -76,11 +78,40 @@ public class MetadataEncryptor
|
|||||||
{
|
{
|
||||||
return inbound;
|
return inbound;
|
||||||
}
|
}
|
||||||
|
if (inbound instanceof SealedObject)
|
||||||
|
{
|
||||||
|
return inbound;
|
||||||
|
}
|
||||||
Serializable outbound = encryptor.sealObject(KeyProvider.ALIAS_METADATA, null, inbound);
|
Serializable outbound = encryptor.sealObject(KeyProvider.ALIAS_METADATA, null, inbound);
|
||||||
// Done
|
// Done
|
||||||
return outbound;
|
return outbound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt a property if the data definition (model-specific) requires it.
|
||||||
|
* <p/>
|
||||||
|
* This method can only be called by the 'system' user.
|
||||||
|
*
|
||||||
|
* @param propertyQName the property qualified name
|
||||||
|
* @param inbound the property to decrypt
|
||||||
|
* @return the decrypted property or the original if it wasn't encrypted
|
||||||
|
*/
|
||||||
|
public Serializable decrypt(QName propertyQName, Serializable inbound)
|
||||||
|
{
|
||||||
|
PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName);
|
||||||
|
if (inbound == null || propertyDef == null || !(propertyDef.getDataType().getName().equals(DataTypeDefinition.ENCRYPTED)))
|
||||||
|
{
|
||||||
|
return inbound;
|
||||||
|
}
|
||||||
|
if (!(inbound instanceof SealedObject))
|
||||||
|
{
|
||||||
|
return inbound;
|
||||||
|
}
|
||||||
|
Serializable outbound = encryptor.unsealObject(KeyProvider.ALIAS_METADATA, inbound);
|
||||||
|
// Done
|
||||||
|
return outbound;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt properties if their data definition (model-specific) requires it.
|
* Encrypt properties if their data definition (model-specific) requires it.
|
||||||
* The values provided can be mixed; values will be encrypted only if required.
|
* The values provided can be mixed; values will be encrypted only if required.
|
||||||
@@ -93,36 +124,35 @@ public class MetadataEncryptor
|
|||||||
*/
|
*/
|
||||||
public Map<QName, Serializable> encrypt(Map<QName, Serializable> inbound)
|
public Map<QName, Serializable> encrypt(Map<QName, Serializable> inbound)
|
||||||
{
|
{
|
||||||
boolean encrypt = false;
|
Set<QName> encryptedProperties = new HashSet<QName>(5);
|
||||||
for (Map.Entry<QName, Serializable> entry : inbound.entrySet())
|
for (Map.Entry<QName, Serializable> entry : inbound.entrySet())
|
||||||
{
|
{
|
||||||
QName key = entry.getKey();
|
QName qname = entry.getKey();
|
||||||
PropertyDefinition propertyDef = dictionaryService.getProperty(key);
|
Serializable value = entry.getValue();
|
||||||
|
PropertyDefinition propertyDef = dictionaryService.getProperty(qname);
|
||||||
if (propertyDef != null && (propertyDef.getDataType().getName().equals(DataTypeDefinition.ENCRYPTED)))
|
if (propertyDef != null && (propertyDef.getDataType().getName().equals(DataTypeDefinition.ENCRYPTED)))
|
||||||
{
|
{
|
||||||
encrypt = true;
|
if (value != null && !(value instanceof SealedObject))
|
||||||
break;
|
{
|
||||||
|
encryptedProperties.add(qname);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!encrypt)
|
if (encryptedProperties.isEmpty())
|
||||||
{
|
{
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
return inbound;
|
return inbound;
|
||||||
}
|
}
|
||||||
// Encrypt, in place, using a copied map
|
// Encrypt, in place, using a copied map
|
||||||
Map<QName, Serializable> outbound = new HashMap<QName, Serializable>(inbound);
|
Map<QName, Serializable> outbound = new HashMap<QName, Serializable>(inbound);
|
||||||
for (Map.Entry<QName, Serializable> entry : inbound.entrySet())
|
for (QName propertyQName : encryptedProperties)
|
||||||
{
|
{
|
||||||
Serializable value = entry.getValue();
|
// We have already checked for nulls and conversions
|
||||||
if (value != null && (value instanceof SealedObject))
|
Serializable value = inbound.get(propertyQName);
|
||||||
{
|
// Have to encrypt the value
|
||||||
// Straight copy, i.e. do nothing
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Have to decrypt the value
|
|
||||||
Serializable encryptedValue = encryptor.sealObject(KeyProvider.ALIAS_METADATA, null, value);
|
Serializable encryptedValue = encryptor.sealObject(KeyProvider.ALIAS_METADATA, null, value);
|
||||||
// Store it back
|
// Store it back
|
||||||
outbound.put(entry.getKey(), encryptedValue);
|
outbound.put(propertyQName, encryptedValue);
|
||||||
}
|
}
|
||||||
// Done
|
// Done
|
||||||
return outbound;
|
return outbound;
|
||||||
@@ -142,35 +172,35 @@ public class MetadataEncryptor
|
|||||||
{
|
{
|
||||||
checkAuthentication();
|
checkAuthentication();
|
||||||
|
|
||||||
boolean decrypt = false;
|
Set<QName> encryptedProperties = new HashSet<QName>(5);
|
||||||
for (Map.Entry<QName, Serializable> entry : inbound.entrySet())
|
for (Map.Entry<QName, Serializable> entry : inbound.entrySet())
|
||||||
{
|
{
|
||||||
|
QName qname = entry.getKey();
|
||||||
Serializable value = entry.getValue();
|
Serializable value = entry.getValue();
|
||||||
if (value != null && (value instanceof SealedObject))
|
PropertyDefinition propertyDef = dictionaryService.getProperty(qname);
|
||||||
|
if (propertyDef != null && (propertyDef.getDataType().getName().equals(DataTypeDefinition.ENCRYPTED)))
|
||||||
{
|
{
|
||||||
decrypt = true;
|
if (value != null && (value instanceof SealedObject))
|
||||||
break;
|
{
|
||||||
|
encryptedProperties.add(qname);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!decrypt)
|
if (encryptedProperties.isEmpty())
|
||||||
{
|
{
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
return inbound;
|
return inbound;
|
||||||
}
|
}
|
||||||
// Decrypt, in place, using a copied map
|
// Decrypt, in place, using a copied map
|
||||||
Map<QName, Serializable> outbound = new HashMap<QName, Serializable>(inbound);
|
Map<QName, Serializable> outbound = new HashMap<QName, Serializable>(inbound);
|
||||||
for (Map.Entry<QName, Serializable> entry : inbound.entrySet())
|
for (QName propertyQName : encryptedProperties)
|
||||||
{
|
{
|
||||||
Serializable value = entry.getValue();
|
// We have already checked for nulls and conversions
|
||||||
if (value != null && (value instanceof SealedObject))
|
Serializable value = inbound.get(propertyQName);
|
||||||
{
|
|
||||||
// Straight copy, i.e. do nothing
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Have to decrypt the value
|
// Have to decrypt the value
|
||||||
Serializable decryptedValue = encryptor.unsealObject(KeyProvider.ALIAS_METADATA, value);
|
Serializable unencryptedValue = encryptor.unsealObject(KeyProvider.ALIAS_METADATA, value);
|
||||||
// Store it back
|
// Store it back
|
||||||
outbound.put(entry.getKey(), decryptedValue);
|
outbound.put(propertyQName, unencryptedValue);
|
||||||
}
|
}
|
||||||
// Done
|
// Done
|
||||||
return outbound;
|
return outbound;
|
||||||
|
@@ -19,6 +19,8 @@
|
|||||||
package org.alfresco.repo.node.integrity;
|
package org.alfresco.repo.node.integrity;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.transaction.UserTransaction;
|
import javax.transaction.UserTransaction;
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ import org.alfresco.model.ContentModel;
|
|||||||
import org.alfresco.repo.dictionary.DictionaryDAO;
|
import org.alfresco.repo.dictionary.DictionaryDAO;
|
||||||
import org.alfresco.repo.dictionary.M2Model;
|
import org.alfresco.repo.dictionary.M2Model;
|
||||||
import org.alfresco.repo.node.BaseNodeServiceTest;
|
import org.alfresco.repo.node.BaseNodeServiceTest;
|
||||||
|
import org.alfresco.repo.node.encryption.MetadataEncryptor;
|
||||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||||
import org.alfresco.service.ServiceRegistry;
|
import org.alfresco.service.ServiceRegistry;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
@@ -97,7 +100,7 @@ public class IntegrityTest extends TestCase
|
|||||||
private ServiceRegistry serviceRegistry;
|
private ServiceRegistry serviceRegistry;
|
||||||
private NodeService nodeService;
|
private NodeService nodeService;
|
||||||
private NodeRef rootNodeRef;
|
private NodeRef rootNodeRef;
|
||||||
private PropertyMap allProperties;
|
private Map<QName, Serializable> allProperties;
|
||||||
private UserTransaction txn;
|
private UserTransaction txn;
|
||||||
private AuthenticationComponent authenticationComponent;
|
private AuthenticationComponent authenticationComponent;
|
||||||
|
|
||||||
@@ -116,6 +119,8 @@ public class IntegrityTest extends TestCase
|
|||||||
integrityChecker.setFailOnViolation(true);
|
integrityChecker.setFailOnViolation(true);
|
||||||
integrityChecker.setTraceOn(true);
|
integrityChecker.setTraceOn(true);
|
||||||
integrityChecker.setMaxErrorsPerTransaction(100); // we want to count the correct number of errors
|
integrityChecker.setMaxErrorsPerTransaction(100); // we want to count the correct number of errors
|
||||||
|
|
||||||
|
MetadataEncryptor encryptor = (MetadataEncryptor) ctx.getBean("metadataEncryptor");
|
||||||
|
|
||||||
serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
|
serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
|
||||||
nodeService = serviceRegistry.getNodeService();
|
nodeService = serviceRegistry.getNodeService();
|
||||||
@@ -141,6 +146,7 @@ public class IntegrityTest extends TestCase
|
|||||||
allProperties.put(TEST_PROP_INT_B, "456");
|
allProperties.put(TEST_PROP_INT_B, "456");
|
||||||
allProperties.put(TEST_PROP_ENCRYPTED_A, "ABC");
|
allProperties.put(TEST_PROP_ENCRYPTED_A, "ABC");
|
||||||
allProperties.put(TEST_PROP_ENCRYPTED_B, "DEF");
|
allProperties.put(TEST_PROP_ENCRYPTED_B, "DEF");
|
||||||
|
allProperties = encryptor.encrypt(allProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tearDown() throws Exception
|
public void tearDown() throws Exception
|
||||||
@@ -166,7 +172,7 @@ public class IntegrityTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* Create a node of the given type, and hanging off the root node
|
* Create a node of the given type, and hanging off the root node
|
||||||
*/
|
*/
|
||||||
private NodeRef createNode(String name, QName type, PropertyMap properties)
|
private NodeRef createNode(String name, QName type, Map<QName, Serializable> properties)
|
||||||
{
|
{
|
||||||
return nodeService.createNode(
|
return nodeService.createNode(
|
||||||
rootNodeRef,
|
rootNodeRef,
|
||||||
@@ -247,8 +253,17 @@ public class IntegrityTest extends TestCase
|
|||||||
|
|
||||||
public void testCreateWithoutEncryption() throws Exception
|
public void testCreateWithoutEncryption() throws Exception
|
||||||
{
|
{
|
||||||
NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_ENCRYPTED_PROPERTIES, allProperties);
|
allProperties.put(TEST_PROP_ENCRYPTED_A, "Not encrypted");
|
||||||
checkIntegrityExpectFailure("Failed to detect unencrypted properties", 2);
|
try
|
||||||
|
{
|
||||||
|
NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_ENCRYPTED_PROPERTIES, allProperties);
|
||||||
|
fail("Current detection of unencrypted properties is done by NodeService.");
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
// checkIntegrityExpectFailure("Failed to detect unencrypted properties", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testCreateWithoutPropertiesForAspect() throws Exception
|
public void testCreateWithoutPropertiesForAspect() throws Exception
|
||||||
|
Reference in New Issue
Block a user