diff --git a/config/alfresco/.keystore b/config/alfresco/.keystore index 01bd383454..22d2b69a41 100644 Binary files a/config/alfresco/.keystore and b/config/alfresco/.keystore differ diff --git a/config/alfresco/authentication-services-context.xml b/config/alfresco/authentication-services-context.xml index d479ed536d..453ce4b47b 100644 --- a/config/alfresco/authentication-services-context.xml +++ b/config/alfresco/authentication-services-context.xml @@ -375,7 +375,6 @@ - diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index b5f10c3fe0..f2f1dc9a98 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -90,7 +90,6 @@ - diff --git a/config/alfresco/encryption-context.xml b/config/alfresco/encryption-context.xml index 1aeecdd71d..3c2e93cd09 100644 --- a/config/alfresco/encryption-context.xml +++ b/config/alfresco/encryption-context.xml @@ -17,8 +17,8 @@ - - + + diff --git a/config/alfresco/model-specific-services-context.xml b/config/alfresco/model-specific-services-context.xml index fa96d86121..3f103c2a20 100644 --- a/config/alfresco/model-specific-services-context.xml +++ b/config/alfresco/model-specific-services-context.xml @@ -148,7 +148,6 @@ - diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml index 856d9f5f7c..c7bbe87169 100644 --- a/config/alfresco/node-services-context.xml +++ b/config/alfresco/node-services-context.xml @@ -248,4 +248,10 @@ + + + + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 44b0bac3b6..fa3d6aa0ac 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -640,8 +640,8 @@ 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} \ No newline at end of file +encryption.keystore.password=mp6yc0UD9e +# The password protecting the alias: metadata +encryption.keystore.password.metadata=oKIWzVdEdA +# The password protecting the alias: solr +encryption.keystore.password.solr=TxHTtOnrwQ \ No newline at end of file diff --git a/source/java/org/alfresco/cmis/mapping/CMISMapping.java b/source/java/org/alfresco/cmis/mapping/CMISMapping.java index 9f7c76d779..96f7398157 100644 --- a/source/java/org/alfresco/cmis/mapping/CMISMapping.java +++ b/source/java/org/alfresco/cmis/mapping/CMISMapping.java @@ -144,6 +144,7 @@ public class CMISMapping implements InitializingBean // mapAlfrescoToCmisDataType.put(DataTypeDefinition.ANY, null); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.ENCRYPTED, null); mapAlfrescoToCmisDataType.put(DataTypeDefinition.ASSOC_REF, null); mapAlfrescoToCmisDataType.put(DataTypeDefinition.BOOLEAN, CMISDataTypeEnum.BOOLEAN); mapAlfrescoToCmisDataType.put(DataTypeDefinition.CATEGORY, CMISDataTypeEnum.ID); diff --git a/source/java/org/alfresco/jcr/dictionary/DataTypeMap.java b/source/java/org/alfresco/jcr/dictionary/DataTypeMap.java index 2002f76892..db7c3a3cd7 100644 --- a/source/java/org/alfresco/jcr/dictionary/DataTypeMap.java +++ b/source/java/org/alfresco/jcr/dictionary/DataTypeMap.java @@ -56,6 +56,7 @@ public class DataTypeMap dataTypeToPropertyType.put(DataTypeDefinition.NODE_REF, PropertyType.REFERENCE); dataTypeToPropertyType.put(DataTypeDefinition.PATH, PropertyType.PATH); dataTypeToPropertyType.put(DataTypeDefinition.ANY, PropertyType.UNDEFINED); + dataTypeToPropertyType.put(DataTypeDefinition.ENCRYPTED, PropertyType.UNDEFINED); dataTypeToPropertyType.put(DataTypeDefinition.LOCALE, PropertyType.STRING); } diff --git a/source/java/org/alfresco/repo/dictionary/RepoDictionaryDAOTest.java b/source/java/org/alfresco/repo/dictionary/RepoDictionaryDAOTest.java index 5d0d49bed4..94339e3193 100644 --- a/source/java/org/alfresco/repo/dictionary/RepoDictionaryDAOTest.java +++ b/source/java/org/alfresco/repo/dictionary/RepoDictionaryDAOTest.java @@ -354,31 +354,6 @@ public class RepoDictionaryDAOTest extends TestCase propertyDef.isMandatoryEnforced()); } - public void testEncrypted() - { - // get the properties for the test type - QName testEncryptedQName = QName.createQName(TEST_URL, "encrypted"); - ClassDefinition testEncryptedClassDef = service.getClass(testEncryptedQName); - Map testEncryptedPropertyDefs = testEncryptedClassDef.getProperties(); - - PropertyDefinition propertyDef = null; - - QName testTextEncryptedQName = QName.createQName(TEST_URL, "text_encrypted"); - propertyDef = testEncryptedPropertyDefs.get(testTextEncryptedQName); - assertNotNull("Property not found: " + testTextEncryptedQName, - propertyDef); - assertTrue("Expected property to be encrypted: " + testTextEncryptedQName, - propertyDef.isEncrypted()); - - QName testMLTextEncryptedQName = QName.createQName(TEST_URL, "mltext_encrypted"); - propertyDef = testEncryptedPropertyDefs.get(testMLTextEncryptedQName); - assertNotNull("Property not found: " + testMLTextEncryptedQName, - propertyDef); - assertTrue("Expected property to be encrypted: " + testMLTextEncryptedQName, - propertyDef.isEncrypted()); -// TODO test for encrypted == false? - } - public void testSubClassOf() { QName invalid = QName.createQName(TEST_URL, "invalid"); diff --git a/source/java/org/alfresco/repo/domain/PropertyValue.java b/source/java/org/alfresco/repo/domain/PropertyValue.java index d1d0024cd4..3b7d4a8f27 100644 --- a/source/java/org/alfresco/repo/domain/PropertyValue.java +++ b/source/java/org/alfresco/repo/domain/PropertyValue.java @@ -592,6 +592,7 @@ public class PropertyValue implements Cloneable, Serializable { valueTypesByPropertyType = new HashMap(37); valueTypesByPropertyType.put(DataTypeDefinition.ANY, ValueType.SERIALIZABLE); + valueTypesByPropertyType.put(DataTypeDefinition.ENCRYPTED, ValueType.SERIALIZABLE); valueTypesByPropertyType.put(DataTypeDefinition.BOOLEAN, ValueType.BOOLEAN); valueTypesByPropertyType.put(DataTypeDefinition.INT, ValueType.INTEGER); valueTypesByPropertyType.put(DataTypeDefinition.LONG, ValueType.LONG); diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index 9f1a7d013e..8996c57620 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -52,7 +52,6 @@ import org.alfresco.repo.domain.permissions.AclDAO; import org.alfresco.repo.domain.qname.QNameDAO; import org.alfresco.repo.domain.usage.UsageDAO; import org.alfresco.repo.policy.BehaviourFilter; -import org.alfresco.repo.security.encryption.Encryptor; import org.alfresco.repo.security.permissions.AccessControlListProperties; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; @@ -135,7 +134,6 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO private ContentDataDAO contentDataDAO; private LocaleDAO localeDAO; private UsageDAO usageDAO; - private Encryptor encryptor; /** * Cache for the Store root nodes by StoreRef:
@@ -217,14 +215,6 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO this.dictionaryService = dictionaryService; } - /** - * @param encryptor helper to do symmetric property encryption - */ - public void setEncryptor(Encryptor encryptor) - { - this.encryptor = encryptor; - } - /** * @param policyBehaviourFilter the service to determine the behaviour for cm:auditable and * other inherent capabilities. @@ -370,9 +360,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO PropertyCheck.mandatory(this, "contentDataDAO", contentDataDAO); PropertyCheck.mandatory(this, "localeDAO", localeDAO); PropertyCheck.mandatory(this, "usageDAO", usageDAO); - PropertyCheck.mandatory(this, "encryptor", encryptor); - this.nodePropertyHelper = new NodePropertyHelper(dictionaryService, qnameDAO, localeDAO, contentDataDAO, encryptor); + this.nodePropertyHelper = new NodePropertyHelper(dictionaryService, qnameDAO, localeDAO, contentDataDAO); } /* diff --git a/source/java/org/alfresco/repo/domain/node/NodePropertyHelper.java b/source/java/org/alfresco/repo/domain/node/NodePropertyHelper.java index 9c3715d88e..fb7789e969 100644 --- a/source/java/org/alfresco/repo/domain/node/NodePropertyHelper.java +++ b/source/java/org/alfresco/repo/domain/node/NodePropertyHelper.java @@ -32,7 +32,6 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.domain.contentdata.ContentDataDAO; import org.alfresco.repo.domain.locale.LocaleDAO; import org.alfresco.repo.domain.qname.QNameDAO; -import org.alfresco.repo.security.encryption.Encryptor; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -59,7 +58,6 @@ public class NodePropertyHelper private static final Log logger = LogFactory.getLog(NodePropertyHelper.class); private final DictionaryService dictionaryService; - private final Encryptor encryptor; private final QNameDAO qnameDAO; private final LocaleDAO localeDAO; private final ContentDataDAO contentDataDAO; @@ -71,14 +69,12 @@ public class NodePropertyHelper DictionaryService dictionaryService, QNameDAO qnameDAO, LocaleDAO localeDAO, - ContentDataDAO contentDataDAO, - Encryptor encryptor) + ContentDataDAO contentDataDAO) { this.dictionaryService = dictionaryService; this.qnameDAO = qnameDAO; this.localeDAO = localeDAO; this.contentDataDAO = contentDataDAO; - this.encryptor = encryptor; } public Map convertToPersistentProperties(Map in) @@ -147,19 +143,16 @@ public class NodePropertyHelper // Get or spoof the property datatype QName propertyTypeQName; - boolean isEncrypted; if (propertyDef == null) // property not recognised { // allow it for now - persisting excess properties can be useful sometimes propertyTypeQName = DataTypeDefinition.ANY; - isEncrypted = false; } else { propertyTypeQName = propertyDef.getDataType().getName(); - isEncrypted = propertyDef.isEncrypted(); } - + // A property may appear to be multi-valued if the model definition is loose and // an unexploded collection is passed in. Otherwise, use the model-defined behaviour // strictly. @@ -651,17 +644,14 @@ public class NodePropertyHelper } // get property attributes final QName propertyTypeQName; - boolean isEncrypted; if (propertyDef == null) { // allow this for now propertyTypeQName = DataTypeDefinition.ANY; - isEncrypted = false; } else { propertyTypeQName = propertyDef.getDataType().getName(); - isEncrypted = propertyDef.isEncrypted(); } try { diff --git a/source/java/org/alfresco/repo/domain/node/NodePropertyHelperTest.java b/source/java/org/alfresco/repo/domain/node/NodePropertyHelperTest.java index 6f58c9eb9d..7385b132c6 100644 --- a/source/java/org/alfresco/repo/domain/node/NodePropertyHelperTest.java +++ b/source/java/org/alfresco/repo/domain/node/NodePropertyHelperTest.java @@ -32,7 +32,6 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.contentdata.ContentDataDAO; import org.alfresco.repo.domain.locale.LocaleDAO; import org.alfresco.repo.domain.qname.QNameDAO; -import org.alfresco.repo.security.encryption.Encryptor; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.version.VersionModel; @@ -87,9 +86,8 @@ public class NodePropertyHelperTest extends TestCase QNameDAO qnameDAO = (QNameDAO) ctx.getBean("qnameDAO"); LocaleDAO localeDAO = (LocaleDAO) ctx.getBean("localeDAO"); ContentDataDAO contentDataDAO = (ContentDataDAO) ctx.getBean("contentDataDAO"); - Encryptor encryptor = (Encryptor) ctx.getBean("encryptor"); - helper = new NodePropertyHelper(dictionaryService, qnameDAO, localeDAO, contentDataDAO, encryptor); + helper = new NodePropertyHelper(dictionaryService, qnameDAO, localeDAO, contentDataDAO); transactionService = serviceRegistry.getTransactionService(); txnHelper = transactionService.getRetryingTransactionHelper(); txnHelper.setMinRetryWaitMs(10); diff --git a/source/java/org/alfresco/repo/domain/node/NodePropertyValue.java b/source/java/org/alfresco/repo/domain/node/NodePropertyValue.java index 5d8fbd50ae..7f086c6d98 100644 --- a/source/java/org/alfresco/repo/domain/node/NodePropertyValue.java +++ b/source/java/org/alfresco/repo/domain/node/NodePropertyValue.java @@ -553,7 +553,7 @@ public class NodePropertyValue implements Cloneable, Serializable } else { - throw new IllegalArgumentException("SealedObject value not supported: " + value); + throw new IllegalArgumentException("Encrypted properties must be encrypted by the client."); } } }, @@ -689,6 +689,7 @@ public class NodePropertyValue implements Cloneable, Serializable { valueTypesByPropertyType = new HashMap(37); valueTypesByPropertyType.put(DataTypeDefinition.ANY, ValueType.SERIALIZABLE); + valueTypesByPropertyType.put(DataTypeDefinition.ENCRYPTED, ValueType.SEALED_OBJECT); valueTypesByPropertyType.put(DataTypeDefinition.BOOLEAN, ValueType.BOOLEAN); valueTypesByPropertyType.put(DataTypeDefinition.INT, ValueType.INTEGER); valueTypesByPropertyType.put(DataTypeDefinition.LONG, ValueType.LONG); diff --git a/source/java/org/alfresco/repo/forms/processor/node/MockClassAttributeDefinition.java b/source/java/org/alfresco/repo/forms/processor/node/MockClassAttributeDefinition.java index 9f9190d25b..7a5e463a4d 100644 --- a/source/java/org/alfresco/repo/forms/processor/node/MockClassAttributeDefinition.java +++ b/source/java/org/alfresco/repo/forms/processor/node/MockClassAttributeDefinition.java @@ -54,7 +54,6 @@ public class MockClassAttributeDefinition implements PropertyDefinition, Associa private boolean isProtected = false; private boolean mandatory = false; private boolean multiValued = false; - private boolean isEncrypted = false; private MockClassAttributeDefinition(QName name) { @@ -133,227 +132,165 @@ public class MockClassAttributeDefinition implements PropertyDefinition, Associa when(mock.targetClass.getName()).thenReturn(targetClassName); } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#getConstraints() - */ + @Override public List getConstraints() { return null; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#getContainerClass - * () - */ + @Override public ClassDefinition getContainerClass() { return null; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#getDataType() - */ + @Override public DataTypeDefinition getDataType() { return dataType; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#getDefaultValue() - */ + @Override public String getDefaultValue() { return defaultValue; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#getDescription() - */ + @Override public String getDescription() { return description; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#getIndexTokenisationMode() - */ + @Override public IndexTokenisationMode getIndexTokenisationMode() { return null; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#getModel() - */ + @Override public ModelDefinition getModel() { return null; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#getName() - */ + @Override public QName getName() { return name; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#getTitle() - */ + @Override public String getTitle() { return title; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#isIndexed() - */ + @Override public boolean isIndexed() { return false; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#isIndexedAtomically() - */ + @Override public boolean isIndexedAtomically() { return false; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#isMandatory() - */ + @Override public boolean isMandatory() { return mandatory; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#isMandatoryEnforced() - */ + @Override public boolean isMandatoryEnforced() { return false; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#isMultiValued() - */ + @Override public boolean isMultiValued() { return multiValued; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#isOverride() - */ + @Override public boolean isOverride() { return false; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#isProtected() - */ + @Override public boolean isProtected() { return isProtected; } - /* - * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#isStoredInIndex() - */ + @Override public boolean isStoredInIndex() { return false; } - /* - * @see org.alfresco.service.cmr.dictionary.AssociationDefinition#getSourceClass() - */ + @Override public ClassDefinition getSourceClass() { return null; } - /* - * @see org.alfresco.service.cmr.dictionary.AssociationDefinition#getSourceRoleName() - */ + @Override public QName getSourceRoleName() { return null; } - /* - * @see org.alfresco.service.cmr.dictionary.AssociationDefinition#getTargetClass() - */ + @Override public ClassDefinition getTargetClass() { return targetClass; } - /* - * @see org.alfresco.service.cmr.dictionary.AssociationDefinition#getTargetRoleName() - */ + @Override public QName getTargetRoleName() { return null; } - /* - * @see org.alfresco.service.cmr.dictionary.AssociationDefinition#isChild() - */ + @Override public boolean isChild() { return false; } - /* - * @see org.alfresco.service.cmr.dictionary.AssociationDefinition#isSourceMandatory() - */ + @Override public boolean isSourceMandatory() { return false; } - /* - * @see org.alfresco.service.cmr.dictionary.AssociationDefinition#isSourceMany() - */ + @Override public boolean isSourceMany() { return false; } - /* - * @see org.alfresco.service.cmr.dictionary.AssociationDefinition#isTargetMandatory() - */ + @Override public boolean isTargetMandatory() { return targetMandatory; } - /* - * @see org.alfresco.service.cmr.dictionary.AssociationDefinition#isTargetMandatoryEnforced() - */ + @Override public boolean isTargetMandatoryEnforced() { return false; } - /* - * @see org.alfresco.service.cmr.dictionary.AssociationDefinition#isTargetMany() - */ + @Override public boolean isTargetMany() { return targetMany; } - - @Override - public boolean isEncrypted() - { - return isEncrypted; - } - } diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java index 06966519f9..2a5ae200c5 100644 --- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java @@ -34,6 +34,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; +import javax.crypto.SealedObject; import javax.transaction.UserTransaction; import org.alfresco.model.ContentModel; @@ -41,6 +42,7 @@ import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.dictionary.DictionaryComponent; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.node.encryption.MetadataEncryptor; import org.alfresco.repo.node.integrity.IntegrityChecker; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; @@ -100,6 +102,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest public static final QName TYPE_QNAME_TEST_CONTENT = QName.createQName(NAMESPACE, "content"); public static final QName TYPE_QNAME_TEST_MANY_PROPERTIES = QName.createQName(NAMESPACE, "many-properties"); + public static final QName TYPE_QNAME_TEST_MANY_PROPERTIES_ENCRYPTED = QName.createQName(NAMESPACE, "many-properties-encrypted"); public static final QName TYPE_QNAME_TEST_MANY_ML_PROPERTIES = QName.createQName(NAMESPACE, "many-ml-properties"); public static final QName TYPE_QNAME_EXTENDED_CONTENT = QName.createQName(NAMESPACE, "extendedcontent"); public static final QName ASPECT_QNAME_TEST_TITLED = QName.createQName(NAMESPACE, "titled"); @@ -135,7 +138,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest public static final QName ASSOC_TYPE_QNAME_TEST_CHILDREN = ContentModel.ASSOC_CHILDREN; public static final QName ASSOC_TYPE_QNAME_TEST_CONTAINS = ContentModel.ASSOC_CONTAINS; public static final QName ASSOC_TYPE_QNAME_TEST_NEXT = QName.createQName(NAMESPACE, "next"); - + public static final QName ASPECT_WITH_ASSOCIATIONS = QName.createQName(NAMESPACE, "withAssociations"); public static final QName ASSOC_ASPECT_CHILD_ASSOC = QName.createQName(NAMESPACE, "aspect-child-assoc"); public static final QName ASSOC_ASPECT_NORMAL_ASSOC = QName.createQName(NAMESPACE, "aspect-normal-assoc"); @@ -152,12 +155,16 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest public static final QName PROP_QNAME_ANY_PROP_SINGLE = QName.createQName(NAMESPACE, "anyprop-single"); public static final QName PROP_QNAME_ANY_PROP_MULTIPLE = QName.createQName(NAMESPACE, "anyprop-multiple"); + public static final QName ASPECT_WITH_ENCRYPTED = QName.createQName(NAMESPACE, "withEncrypted"); + public static final QName PROP_QNAME_ENCRYPTED_VALUE = QName.createQName(NAMESPACE, "encryptedValue"); + protected PolicyComponent policyComponent; protected DictionaryService dictionaryService; protected TransactionService transactionService; protected RetryingTransactionHelper retryingTransactionHelper; protected AuthenticationComponent authenticationComponent; protected NodeService nodeService; + protected MetadataEncryptor metadataEncryptor; protected Dialect dialect; /** populated during setup */ protected NodeRef rootNodeRef; @@ -169,6 +176,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest super.onSetUpInTransaction(); dialect = (Dialect) applicationContext.getBean("dialect"); + metadataEncryptor = (MetadataEncryptor) applicationContext.getBean("metadataEncryptor"); transactionService = (TransactionService) applicationContext.getBean("transactionComponent"); retryingTransactionHelper = (RetryingTransactionHelper) applicationContext.getBean("retryingTransactionHelper"); @@ -1707,8 +1715,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest } /** - * Ensures that the type you get out of a d:any property is the type that you - * put in. + * Ensures that the type you get out of a d:any property is the type that you put in. */ public void testSerializableProperties() throws Exception { @@ -1736,6 +1743,43 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest assertTrue("Serialization/deserialization failed", checkPropertyQname instanceof QName); } + /** + * Check that d:encrypted properties work correctly. + */ + public void testEncryptedProperties() throws Exception + { + QName property = PROP_QNAME_CONTENT_VALUE; + + Map properties = new HashMap(17); + properties.put(PROP_QNAME_ENCRYPTED_VALUE, property); + + // We have encrypted properties, so encrypt them + properties = metadataEncryptor.encrypt(properties); + Serializable checkProperty = properties.get(PROP_QNAME_ENCRYPTED_VALUE); + assertTrue("Properties not encrypted", checkProperty instanceof SealedObject); + + // create node + NodeRef nodeRef = nodeService.createNode( + rootNodeRef, + ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName("pathA"), + ContentModel.TYPE_CONTAINER, + properties).getChildRef(); + // persist + setComplete(); + endTransaction(); + + // get the properties back + Map checkProperties = nodeService.getProperties(nodeRef); + checkProperty = checkProperties.get(PROP_QNAME_ENCRYPTED_VALUE); + assertTrue("Encrypted property not persisted", checkProperty instanceof SealedObject); + + // Now make sure that the value can be null + nodeService.setProperty(nodeRef, PROP_QNAME_ENCRYPTED_VALUE, null); + + // Finally, make sure that it fails if we don't encrypt + } + @SuppressWarnings("unchecked") public void testMultiProp() throws Exception { @@ -1932,7 +1976,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest nodeService.setProperty(nodeRef, PROP_QNAME_MULTI_VALUE, "GHI"); Serializable checkProperty = nodeService.getProperty(nodeRef, PROP_QNAME_MULTI_VALUE); assertTrue("Property not converted to a Collection", checkProperty instanceof Collection); - assertTrue("Collection doesn't contain value", ((Collection)checkProperty).contains("GHI")); + assertTrue("Collection doesn't contain value", ((Collection)checkProperty).contains("GHI")); } public void testPropertyLocaleBehaviour() throws Exception diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml b/source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml index f3c43de001..1a205197b4 100644 --- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml +++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml @@ -296,7 +296,8 @@ true
- + + @@ -456,6 +457,16 @@ + + + Aspect with encrypted properties + + + d:encrypted + true + + + diff --git a/source/java/org/alfresco/repo/node/db/EncryptedPropertiesTest.java b/source/java/org/alfresco/repo/node/db/EncryptedPropertiesTest.java deleted file mode 100644 index 9b930025f9..0000000000 --- a/source/java/org/alfresco/repo/node/db/EncryptedPropertiesTest.java +++ /dev/null @@ -1,177 +0,0 @@ -package org.alfresco.repo.node.db; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.dictionary.DictionaryBootstrap; -import org.alfresco.repo.dictionary.DictionaryDAO; -import org.alfresco.service.cmr.repository.MLText; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.BaseSpringTest; - -public class EncryptedPropertiesTest extends BaseSpringTest -{ - private static String NAMESPACE = "http://www.alfresco.org/test/encryptiontest/1.0"; - private static QName PROP1_TYPE = QName.createQName(NAMESPACE, "prop1"); - private static QName PROP2_TYPE = QName.createQName(NAMESPACE, "prop2"); - private static QName ASPECT_PROP1_TYPE = QName.createQName(NAMESPACE, "aspectprop1"); - private static QName ASPECT_PROP2_TYPE = QName.createQName(NAMESPACE, "aspectprop2"); - private static QName ENCRYPTIONTEST_TYPE = QName.createQName(NAMESPACE, "testtype"); - - private DictionaryDAO dictionaryDAO; - private NodeService nodeService; - - private NodeRef rootNodeRef; - - @Override - protected void onSetUpInTransaction() throws Exception - { - super.onSetUpInTransaction(); - - dictionaryDAO = (DictionaryDAO) applicationContext.getBean("dictionaryDAO"); - - nodeService = getNodeService(); - - // Create the test model - createTestModel(); - - // create a first store directly - StoreRef storeRef = nodeService.createStore( - StoreRef.PROTOCOL_WORKSPACE, - "Test_" + System.currentTimeMillis()); - rootNodeRef = nodeService.getRootNode(storeRef); - } - - protected NodeService getNodeService() - { - // Force cascading - DbNodeServiceImpl dbNodeServiceImpl = (DbNodeServiceImpl) applicationContext.getBean("dbNodeServiceImpl"); - - return (NodeService) applicationContext.getBean("dbNodeService"); - } - - private void createTestModel() - { - // register the test model - List bootstrapModels = new ArrayList(); - bootstrapModels.add("org/alfresco/repo/node/db/encrypted_properties_test_model.xml"); - - DictionaryBootstrap bootstrap = new DictionaryBootstrap(); - bootstrap.setModels(bootstrapModels); - bootstrap.setDictionaryDAO(dictionaryDAO); - - bootstrap.bootstrap(); - } - - public void testEncryptedProperties() - { - Map properties = new HashMap(); - properties.put(PROP1_TYPE, "test string value"); - - MLText mlTextProperty = new MLText(); - mlTextProperty.addValue(Locale.ENGLISH, "Very good!"); - mlTextProperty.addValue(Locale.FRENCH, "Très bon!"); - mlTextProperty.addValue(Locale.GERMAN, "Sehr gut!"); - properties.put(PROP2_TYPE, mlTextProperty); - - NodeRef test1 = nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(NAMESPACE, "n1"), - ENCRYPTIONTEST_TYPE, - properties).getChildRef(); - - NodeRef test2 = nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(NAMESPACE, "n2"), - ContentModel.TYPE_CONTENT, - null).getChildRef(); - - properties = new HashMap(); - properties.put(ASPECT_PROP1_TYPE, "test string value"); - - mlTextProperty = new MLText(); - mlTextProperty.addValue(Locale.ENGLISH, "Very good!"); - mlTextProperty.addValue(Locale.FRENCH, "Très bon!"); - mlTextProperty.addValue(Locale.GERMAN, "Sehr gut!"); - properties.put(ASPECT_PROP2_TYPE, mlTextProperty); - nodeService.addAspect(test2, QName.createQName(NAMESPACE, "testaspect"), properties); - - String prop1 = (String)nodeService.getProperty(test1, PROP1_TYPE); - assertEquals("test string value", prop1); - - MLText prop2 = (MLText)nodeService.getProperty(test1, PROP2_TYPE); - assertEquals("Very good!", prop2.getValue(Locale.ENGLISH)); - assertEquals("Très bon!", prop2.getValue(Locale.FRENCH)); - assertEquals("Sehr gut!", prop2.getValue(Locale.GERMAN)); - - String aspectprop1 = (String)nodeService.getProperty(test2, ASPECT_PROP1_TYPE); - assertEquals("test string value", aspectprop1); - - MLText aspectprop2 = (MLText)nodeService.getProperty(test2, ASPECT_PROP2_TYPE); - assertEquals("Very good!", aspectprop2.getValue(Locale.ENGLISH)); - assertEquals("Très bon!", aspectprop2.getValue(Locale.FRENCH)); - assertEquals("Sehr gut!", aspectprop2.getValue(Locale.GERMAN)); - } - -// protected void createNodes() -// { -// // create 1000 nodes with a single (non-encrypted) string property -// for(int i = 0; i < 2000; i++) -// { -// Map properties = new HashMap(); -// properties.put(ContentModel.PROP_NAME, "encryption test name"); -// NodeRef test1 = nodeService.createNode( -// rootNodeRef, -// ContentModel.ASSOC_CHILDREN, -// QName.createQName(NAMESPACE, "n" + i), -// ContentModel.TYPE_CONTENT, -// properties).getChildRef(); -// -// } -// } - -// protected void createEnryptedPropertyNodes() -// { -// // create 1000 nodes with a single encrypted string attribute -// for(int i = 0; i < 2000; i++) -// { -// Map properties = new HashMap(); -// properties.put(PROP1_TYPE, "test string value"); -// NodeRef test1 = nodeService.createNode( -// rootNodeRef, -// ContentModel.ASSOC_CHILDREN, -// QName.createQName(NAMESPACE, "n" + i), -// ENCRYPTIONTEST_TYPE, -// properties).getChildRef(); -// -// } -// } - -// public void testEncryptedPropertiesSpeed() -// { -// // warm up -// createNodes(); -// createEnryptedPropertyNodes(); -// -// // time -// long start = System.currentTimeMillis(); -// createEnryptedPropertyNodes(); -// long end = System.currentTimeMillis(); -// System.out.println("Encrypted property 1000 nodes in " + (end - start) + "ms"); -// -// start = System.currentTimeMillis(); -// createNodes(); -// end = System.currentTimeMillis(); -// System.out.println("Non-encrypted property 1000 nodes in " + (end - start) + "ms"); -// } -} diff --git a/source/java/org/alfresco/repo/node/db/encrypted_properties_test_model.xml b/source/java/org/alfresco/repo/node/db/encrypted_properties_test_model.xml deleted file mode 100644 index b43eef36dd..0000000000 --- a/source/java/org/alfresco/repo/node/db/encrypted_properties_test_model.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - VersionStoreBaseTest model - Alfresco - 2005-05-30 - 1.0 - - - - - - - - - - - - - - Test type - The test type - cm:content - - - - d:text - false - - true - - - d:mltext - false - true - - - - - - - - - - - - Test Aspect - The test aspect - - - - - d:text - false - - true - - - d:mltext - false - true - - - - - - \ No newline at end of file diff --git a/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java b/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java new file mode 100644 index 0000000000..9cfb335880 --- /dev/null +++ b/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java @@ -0,0 +1,178 @@ +package org.alfresco.repo.node.encryption; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.SealedObject; + +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.encryption.Encryptor; +import org.alfresco.repo.security.encryption.KeyProvider; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.namespace.QName; + +/** + * Component to convert encrypt/decrypt properties. + *

+ * This is a helper; it is up to the client how and when encryption and decryption is done, + * but metadata integrity enforcement will expect that encrypted properties are already + * encrypted. + *

+ * This class must always be used + * {@link AuthenticationUtil#runAs(org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork, String) running as 'system'}. + * + * @author Derek Hulley + * @since 4.0 + */ +public class MetadataEncryptor +{ + private DictionaryService dictionaryService; + private Encryptor encryptor; + + /** + * @param dictionaryService service to check if properties need encrypting + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param encryptor the class that does the encryption/decryption + */ + public void setEncryptor(Encryptor encryptor) + { + this.encryptor = encryptor; + } + + /** + * @throws AuthenticationException if the thread is not running as 'system' + */ + private final void checkAuthentication() + { + if (!AuthenticationUtil.isRunAsUserTheSystemUser()) + { + throw new AuthenticationException("Metadata decryption can only be done by the system user."); + } + } + + /** + * Encrypt a properties if the data definition (model-specific) requires it. + *

+ * This method has no specific authentication requirements. + * + * @param propertyQName the property qualified name + * @param inbound the property to encrypt + * @return the encrypted property or the original if encryption is not required + */ + public Serializable encrypt(QName propertyQName, Serializable inbound) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); + if (inbound == null || propertyDef == null || !(propertyDef.getDataType().getName().equals(DataTypeDefinition.ENCRYPTED))) + { + return inbound; + } + Serializable outbound = encryptor.sealObject(KeyProvider.ALIAS_METADATA, null, inbound); + // Done + return outbound; + } + + /** + * Encrypt properties if their data definition (model-specific) requires it. + * The values provided can be mixed; values will be encrypted only if required. + *

+ * This method has no specific authentication requirements. + * + * @param inbound the properties to encrypt + * @return a new map of values if some encryption occured + * otherwise the original inbound map is returned + */ + public Map encrypt(Map inbound) + { + boolean encrypt = false; + for (Map.Entry entry : inbound.entrySet()) + { + QName key = entry.getKey(); + PropertyDefinition propertyDef = dictionaryService.getProperty(key); + if (propertyDef != null && (propertyDef.getDataType().getName().equals(DataTypeDefinition.ENCRYPTED))) + { + encrypt = true; + break; + } + } + if (!encrypt) + { + // Nothing to do + return inbound; + } + // Encrypt, in place, using a copied map + Map outbound = new HashMap(inbound); + for (Map.Entry entry : inbound.entrySet()) + { + Serializable value = entry.getValue(); + if (value != null && (value instanceof SealedObject)) + { + // Straight copy, i.e. do nothing + continue; + } + // Have to decrypt the value + Serializable encryptedValue = encryptor.sealObject(KeyProvider.ALIAS_METADATA, null, value); + // Store it back + outbound.put(entry.getKey(), encryptedValue); + } + // Done + return outbound; + } + + /** + * Decrypt properties if they are decryptable. The values provided can be mixed; + * encrypted values will be sought out and decrypted. + *

+ * This method can only be called by the 'system' user. + * + * @param inbound the properties to decrypt + * @return a new map of values if some decryption occured + * otherwise the original inbound map is returned + */ + public Map decrypt(Map inbound) + { + checkAuthentication(); + + boolean decrypt = false; + for (Map.Entry entry : inbound.entrySet()) + { + Serializable value = entry.getValue(); + if (value != null && (value instanceof SealedObject)) + { + decrypt = true; + break; + } + } + if (!decrypt) + { + // Nothing to do + return inbound; + } + // Decrypt, in place, using a copied map + Map outbound = new HashMap(inbound); + for (Map.Entry entry : inbound.entrySet()) + { + Serializable value = entry.getValue(); + if (value != null && (value instanceof SealedObject)) + { + // Straight copy, i.e. do nothing + continue; + } + // Have to decrypt the value + Serializable decryptedValue = encryptor.unsealObject(KeyProvider.ALIAS_METADATA, value); + // Store it back + outbound.put(entry.getKey(), decryptedValue); + } + // Done + return outbound; + } +} diff --git a/source/java/org/alfresco/repo/node/getchildren/GetChildrenCannedQueryFactory.java b/source/java/org/alfresco/repo/node/getchildren/GetChildrenCannedQueryFactory.java index c4cb434574..3aadf45703 100644 --- a/source/java/org/alfresco/repo/node/getchildren/GetChildrenCannedQueryFactory.java +++ b/source/java/org/alfresco/repo/node/getchildren/GetChildrenCannedQueryFactory.java @@ -38,7 +38,6 @@ import org.alfresco.repo.domain.node.NodePropertyHelper; import org.alfresco.repo.domain.qname.QNameDAO; import org.alfresco.repo.domain.query.CannedQueryDAO; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.encryption.Encryptor; import org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityInterceptor; import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -64,8 +63,6 @@ public class GetChildrenCannedQueryFactory extends AbstractCannedQueryFactory getCannedQuery(CannedQueryParameters parameters) { - NodePropertyHelper nodePropertyHelper = new NodePropertyHelper(dictionaryService, qnameDAO, localeDAO, contentDataDAO, encryptor); + NodePropertyHelper nodePropertyHelper = new NodePropertyHelper(dictionaryService, qnameDAO, localeDAO, contentDataDAO); Method method = null; for (Method m : methodService.getClass().getMethods()) diff --git a/source/java/org/alfresco/repo/node/integrity/IntegrityTest.java b/source/java/org/alfresco/repo/node/integrity/IntegrityTest.java index b02ac2c9f7..8380fdc8dd 100644 --- a/source/java/org/alfresco/repo/node/integrity/IntegrityTest.java +++ b/source/java/org/alfresco/repo/node/integrity/IntegrityTest.java @@ -62,6 +62,7 @@ public class IntegrityTest extends TestCase public static final QName TEST_TYPE_WITHOUT_ANYTHING = QName.createQName(NAMESPACE, "typeWithoutAnything"); public static final QName TEST_TYPE_WITH_ASPECT = QName.createQName(NAMESPACE, "typeWithAspect"); public static final QName TEST_TYPE_WITH_PROPERTIES = QName.createQName(NAMESPACE, "typeWithProperties"); + public static final QName TEST_TYPE_WITH_ENCRYPTED_PROPERTIES = QName.createQName(NAMESPACE, "typeWithEncryptedProperties"); public static final QName TEST_TYPE_WITH_ASSOCS = QName.createQName(NAMESPACE, "typeWithAssocs"); public static final QName TEST_TYPE_WITH_CHILD_ASSOCS = QName.createQName(NAMESPACE, "typeWithChildAssocs"); public static final QName TEST_TYPE_WITH_NON_ENFORCED_CHILD_ASSOCS = QName.createQName(NAMESPACE, "typeWithNonEnforcedChildAssocs"); @@ -82,6 +83,9 @@ public class IntegrityTest extends TestCase public static final QName TEST_PROP_INT_A = QName.createQName(NAMESPACE, "prop-int-a"); public static final QName TEST_PROP_INT_B = QName.createQName(NAMESPACE, "prop-int-b"); public static final QName TEST_PROP_INT_C = QName.createQName(NAMESPACE, "prop-int-c"); + public static final QName TEST_PROP_ENCRYPTED_A = QName.createQName(NAMESPACE, "prop-encrypted-a"); + public static final QName TEST_PROP_ENCRYPTED_B = QName.createQName(NAMESPACE, "prop-encrypted-b"); + public static final QName TEST_PROP_ENCRYPTED_C = QName.createQName(NAMESPACE, "prop-encrypted-c"); public static ApplicationContext ctx; static @@ -135,6 +139,8 @@ public class IntegrityTest extends TestCase allProperties.put(TEST_PROP_TEXT_B, "DEF"); allProperties.put(TEST_PROP_INT_A, "123"); allProperties.put(TEST_PROP_INT_B, "456"); + allProperties.put(TEST_PROP_ENCRYPTED_A, "ABC"); + allProperties.put(TEST_PROP_ENCRYPTED_B, "DEF"); } public void tearDown() throws Exception @@ -239,6 +245,12 @@ public class IntegrityTest extends TestCase checkIntegrityExpectFailure("Failed to detect missing removed properties", 1); } + public void testCreateWithoutEncryption() throws Exception + { + NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_ENCRYPTED_PROPERTIES, allProperties); + checkIntegrityExpectFailure("Failed to detect unencrypted properties", 2); + } + public void testCreateWithoutPropertiesForAspect() throws Exception { NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_ASPECT, null); diff --git a/source/java/org/alfresco/repo/node/integrity/IntegrityTest_model.xml b/source/java/org/alfresco/repo/node/integrity/IntegrityTest_model.xml index c64d330f64..b6883d345a 100644 --- a/source/java/org/alfresco/repo/node/integrity/IntegrityTest_model.xml +++ b/source/java/org/alfresco/repo/node/integrity/IntegrityTest_model.xml @@ -38,6 +38,24 @@ + + + Type With Encrypted Properties + sys:base + + + d:encrypted + true + + + d:encrypted + + + d:encrypted + true + + + Type With Aspect diff --git a/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java b/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java index 3eaf786228..e6ad0cfaf5 100644 --- a/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java +++ b/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java @@ -24,11 +24,14 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.crypto.SealedObject; + import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.Constraint; import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.ConstraintException; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; @@ -76,9 +79,6 @@ public class PropertiesIntegrityEvent extends AbstractIntegrityEvent /** * Checks the properties for the type and aspects of the given node. - * - * @param nodeRef - * @param eventResults */ private void checkAllProperties(NodeRef nodeRef, List eventResults) { @@ -163,6 +163,19 @@ public class PropertiesIntegrityEvent extends AbstractIntegrityEvent continue; } Serializable propertyValue = nodeProperties.get(propertyQName); + // Check for encryption first + if (propertyDef.getDataType().getName().equals(DataTypeDefinition.ENCRYPTED)) + { + if (propertyValue != null && !(propertyValue instanceof SealedObject)) + { + IntegrityRecord result = new IntegrityRecord( + "Property must be encrypted: \n" + + " Node: " + nodeRef + "\n" + + " Type: " + typeQName + "\n" + + " Property: " + propertyQName); + eventResults.add(result); + } + } // check constraints List constraintDefs = propertyDef.getConstraints(); for (ConstraintDefinition constraintDef : constraintDefs) diff --git a/source/java/org/alfresco/repo/security/encryption/AbstractEncryptor.java b/source/java/org/alfresco/repo/security/encryption/AbstractEncryptor.java index 172bf9d56c..afe45ab5a5 100644 --- a/source/java/org/alfresco/repo/security/encryption/AbstractEncryptor.java +++ b/source/java/org/alfresco/repo/security/encryption/AbstractEncryptor.java @@ -176,14 +176,57 @@ public abstract class AbstractEncryptor implements Encryptor } @Override - public SealedObject sealObject(String keyAlias, AlgorithmParameters params, Serializable input) + public Serializable sealObject(String keyAlias, AlgorithmParameters params, Serializable input) { - throw new UnsupportedOperationException(); + if (input == null) + { + return null; + } + Cipher cipher = getCipher(keyAlias, params, Cipher.ENCRYPT_MODE); + if (cipher == null) + { + return input; + } + try + { + return new SealedObject(input, cipher); + } + catch (Exception e) + { + throw new AlfrescoRuntimeException("Failed to seal object", e); + } } @Override - public Serializable unsealObject(String keyAlias, SealedObject input) + public Serializable unsealObject(String keyAlias, Serializable input) { - throw new UnsupportedOperationException(); - } + if (input == null) + { + return input; + } + // Don't unseal it if it is not sealed + if (!(input instanceof SealedObject)) + { + return input; + } + // Get the Key, rather than a Cipher + Key key = keyProvider.getKey(keyAlias); + if (key == null) + { + // The client will be expecting to unseal the object + throw new IllegalStateException("No key matching " + keyAlias + ". Cannot unseal object."); + } + // Unseal it using the key + SealedObject sealedInput = (SealedObject) input; + try + { + Serializable output = (Serializable) sealedInput.getObject(key); + // Done + return output; + } + catch (Exception e) + { + throw new AlfrescoRuntimeException("Failed to unseal object", e); + } + } } diff --git a/source/java/org/alfresco/repo/security/encryption/AbstractKeyProvider.java b/source/java/org/alfresco/repo/security/encryption/AbstractKeyProvider.java index 4ab5f7e640..51ea70d58f 100644 --- a/source/java/org/alfresco/repo/security/encryption/AbstractKeyProvider.java +++ b/source/java/org/alfresco/repo/security/encryption/AbstractKeyProvider.java @@ -1,21 +1,17 @@ package org.alfresco.repo.security.encryption; -import java.security.Key; - -import org.alfresco.util.ParameterCheck; - /** * Basic support for key providers + *

+ * TODO: This class will provide the alias name mapping so that use-cases can be mapped + * to different alias names in the keystore. * * @author Derek Hulley * @since 4.0 */ public abstract class AbstractKeyProvider implements KeyProvider { - @Override - public Key getKey(AlfrescoKeyAlias keyAlias) - { - ParameterCheck.mandatory("keyAlias", keyAlias); - return getKey(keyAlias.name()); - } + /* + * Not a useless class. + */ } diff --git a/source/java/org/alfresco/repo/security/encryption/Encryptor.java b/source/java/org/alfresco/repo/security/encryption/Encryptor.java index ff5d075692..89e8ed1c20 100644 --- a/source/java/org/alfresco/repo/security/encryption/Encryptor.java +++ b/source/java/org/alfresco/repo/security/encryption/Encryptor.java @@ -4,7 +4,6 @@ import java.io.Serializable; import java.security.AlgorithmParameters; import javax.crypto.Cipher; -import javax.crypto.SealedObject; import org.alfresco.util.Pair; @@ -64,23 +63,31 @@ public interface Encryptor Object decryptObject(String keyAlias, AlgorithmParameters params, byte[] input); /** - * Convenience method to seal on object up cryptographically + * Convenience method to seal on object up cryptographically. + *

+ * Note that the original object may be returned directly if there is no key associated with + * the alias. * * @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); + Serializable sealObject(String keyAlias, AlgorithmParameters params, Serializable input); /** - * Convenience method to unseal on object up cryptographically. + * Convenience method to unseal on object sealed up cryptographically. *

- * Note that the algorithm parameters are stored in the sealed object and are - * not therefore required for decryption. + * Note that the algorithm parameters not provided on the assumption that a symmetric key + * algorithm is in use - only the key is required for unsealing. + *

+ * Note that the original object may be returned directly if there is no key associated with + * the alias or if the input object is not a SealedObject. * * @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 + * @throws IllegalStateException if the key alias is not valid and the input is a + * SealedObject */ - Serializable unsealObject(String keyAlias, SealedObject input); + Serializable unsealObject(String keyAlias, Serializable input); } diff --git a/source/java/org/alfresco/repo/security/encryption/EncryptorTest.java b/source/java/org/alfresco/repo/security/encryption/EncryptorTest.java index e60a9f1f4d..9b254064eb 100644 --- a/source/java/org/alfresco/repo/security/encryption/EncryptorTest.java +++ b/source/java/org/alfresco/repo/security/encryption/EncryptorTest.java @@ -1,5 +1,6 @@ package org.alfresco.repo.security.encryption; +import java.io.Serializable; import java.security.AlgorithmParameters; import junit.framework.TestCase; @@ -58,4 +59,13 @@ public class EncryptorTest extends TestCase encryptedPair.getFirst()); assertEquals("Encryption round trip failed. ", testObject, output); } + + public void testSealedObject() + { + Serializable testObject = " This is a string, but will be serialized "; + + Serializable sealedObject = encryptor.sealObject("mykey2", null, testObject); + Object output = encryptor.unsealObject("mykey2", sealedObject); + assertEquals("Encryption round trip failed. ", testObject, output); + } } diff --git a/source/java/org/alfresco/repo/security/encryption/KeyProvider.java b/source/java/org/alfresco/repo/security/encryption/KeyProvider.java index 044d65a9f0..282836a3b1 100644 --- a/source/java/org/alfresco/repo/security/encryption/KeyProvider.java +++ b/source/java/org/alfresco/repo/security/encryption/KeyProvider.java @@ -9,17 +9,15 @@ import java.security.Key; */ public interface KeyProvider { + // TODO: Allow the aliases to be configured i.e. include an alias mapper /** - * Enumeration of key aliases supported internally by Alfresco - * - * @author derekh - * @since 4.0 + * Constant representing the keystore alias for keys to encrypt/decrype node metadata */ - public static enum AlfrescoKeyAlias - { - METADATA, - SOLR - } + public static final String ALIAS_METADATA = "metadata"; + /** + * Constant representing the keystore alias for keys to encrypt/decrype SOLR transfer data + */ + public static final String ALIAS_SOLR = "solr"; /** * Get an encryption key if available. @@ -28,12 +26,4 @@ public interface KeyProvider * @return the encryption key or null 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 null if there is no associated key - */ - public Key getKey(AlfrescoKeyAlias keyAlias); } diff --git a/source/java/org/alfresco/repo/security/encryption/KeyStoreKeyProviderTest.java b/source/java/org/alfresco/repo/security/encryption/KeyStoreKeyProviderTest.java index da03c05c0c..d72d3119df 100644 --- a/source/java/org/alfresco/repo/security/encryption/KeyStoreKeyProviderTest.java +++ b/source/java/org/alfresco/repo/security/encryption/KeyStoreKeyProviderTest.java @@ -28,12 +28,11 @@ public class KeyStoreKeyProviderTest extends TestCase 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. + *

+ * TODO: Allow the required aliases and key types to be specified and generate + * a keystore on the fly */ /* package */ static KeystoreKeyProvider getTestKeyStoreProvider() { @@ -49,6 +48,10 @@ public class KeyStoreKeyProviderTest extends TestCase return ks; } + public void setUp() throws Exception + { + } + public void testNoKeyStorePasswords() throws Exception { KeystoreKeyProvider keyProvider = new KeystoreKeyProvider(