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:
Derek Hulley
2011-06-22 05:28:49 +00:00
parent bae3c6b16c
commit d9a3f5d504
3 changed files with 159 additions and 35 deletions

View File

@@ -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")

View File

@@ -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;

View File

@@ -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