From 889ea0e58f1b091848c98f150249e480ece9ab1c Mon Sep 17 00:00:00 2001 From: Steven Glover Date: Tue, 26 Jul 2011 13:53:49 +0000 Subject: [PATCH] ALF-9501 "RINF 38: KeyStore detection sequence, password file and encryption check" - initial checkin git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@29354 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/bootstrap-context.xml | 12 + config/alfresco/encryption-context.xml | 12 +- config/alfresco/keystore-passwords.properties | 6 + config/alfresco/repository.properties | 8 +- .../encryption/EncryptionChecker.java | 213 ++++++++++++++++++ .../alfresco/encryption/EncryptorTest.java | 33 ++- .../encryption/KeyStoreKeyProviderTest.java | 26 ++- .../node/encryption/MetadataEncryptor.java | 28 ++- 8 files changed, 307 insertions(+), 31 deletions(-) create mode 100644 config/alfresco/keystore-passwords.properties create mode 100644 source/java/org/alfresco/encryption/EncryptionChecker.java diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 748086dea8..40c97a0249 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -173,6 +173,18 @@ + + + + + + + metadata + solr + + + + diff --git a/config/alfresco/encryption-context.xml b/config/alfresco/encryption-context.xml index 1821136c03..bbe5f12799 100644 --- a/config/alfresco/encryption-context.xml +++ b/config/alfresco/encryption-context.xml @@ -9,22 +9,16 @@ http://code.google.com/p/spring-crypto-utils/schema/crypt http://code.google.com/p/spring-crypto-utils/schema/crypt.xsd"> - + - + - - - - - - - + diff --git a/config/alfresco/keystore-passwords.properties b/config/alfresco/keystore-passwords.properties new file mode 100644 index 0000000000..251d17c0d7 --- /dev/null +++ b/config/alfresco/keystore-passwords.properties @@ -0,0 +1,6 @@ +# The password protecting the keystore entries +keystore=mp6yc0UD9e +# The password protecting the alias: metadata +metadata=oKIWzVdEdA +# The password protecting the alias: solr +solr=TxHTtOnrwQ \ No newline at end of file diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index b63aca8789..6083e73247 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -673,15 +673,9 @@ deployment.filesystem.default.metadatadir=${deployment.filesystem.metadatadir}/d # encryption.encryption.cipherAlgorithm=DESede/CBC/PKCS5Padding encryption.keystore.location=classpath:alfresco/.keystore -encryption.keystore.passwordsFile.location= +encryption.keystore.passwordsFile.location=classpath:alfresco/keystore-passwords.properties encryption.keystore.provider= encryption.keystore.type=JCEKS -# The password protecting the keystore entries -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 encryption.messageTimeout=30000 encryption.macAlgorithm=HmacSHA1 diff --git a/source/java/org/alfresco/encryption/EncryptionChecker.java b/source/java/org/alfresco/encryption/EncryptionChecker.java new file mode 100644 index 0000000000..0ea16d8025 --- /dev/null +++ b/source/java/org/alfresco/encryption/EncryptionChecker.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.encryption; + +import java.io.Serializable; +import java.security.InvalidKeyException; +import java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.attributes.AttributeService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.EqualsHelper; +import org.alfresco.util.GUID; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +/** + * The EncryptionChecker checks the state of the repository's encryption system. + * In particular it checks: + *
    + *
  • that the encryption keys have not been changed. If so, the bootstrap will be halted. + *
+ * + * @since 4.0 + * + */ +public class EncryptionChecker extends AbstractLifecycleBean +{ + private static Log logger = LogFactory. getLog(EncryptionChecker.class); + public static String TOP_LEVEL_KEY = "keyCheck"; + private static enum KEY_STATUS + { + OK, CHANGED, MISSING; + }; + + private TransactionService transactionService; + private AttributeService attributeService; + private Encryptor encryptor; + private List keyAliases; + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setAttributeService(AttributeService attributeService) + { + this.attributeService = attributeService; + } + + public void setKeyAliases(List keyAliases) + { + this.keyAliases = keyAliases; + } + + public void setEncryptor(Encryptor encryptor) + { + this.encryptor = encryptor; + } + + private void removeKey(String keyAlias) + { + attributeService.removeAttributes(TOP_LEVEL_KEY); + logger.info("Removed registered key " + keyAlias); + } + + private KEY_STATUS checkKey(String keyAlias) + { + if(attributeService.exists(TOP_LEVEL_KEY, keyAlias)) + { + try + { + // check that the key has not changed by decrypting the encrypted guid attribute + // comparing against the guid + KeyCheck keyCheck = (KeyCheck)attributeService.getAttribute(TOP_LEVEL_KEY, keyAlias); + Serializable storedGUID = encryptor.unsealObject(keyAlias, keyCheck.getEncrypted()); + return EqualsHelper.nullSafeEquals(storedGUID, keyCheck.getGuid()) ? KEY_STATUS.OK : KEY_STATUS.CHANGED; + } + catch(InvalidKeyException e) + { + // key exception indicates that the key has changed - it can't decrypt the + // previously-encrypted data + return KEY_STATUS.CHANGED; + } + } + else + { + // register the key by creating an attribute that stores a guid and its encrypted value + String guid = GUID.generate(); + Serializable encrypted = encryptor.sealObject(keyAlias, null, guid); + KeyCheck keyCheck = new KeyCheck(guid, encrypted); + attributeService.createAttribute(keyCheck, TOP_LEVEL_KEY, keyAlias); + logger.info("Registered key " + keyAlias); + return KEY_STATUS.MISSING; + } + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper(); + final RetryingTransactionCallback checkKeysCallback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + for(String keyAlias : keyAliases) + { + KEY_STATUS keyStatus = checkKey(keyAlias); + if(keyStatus == KEY_STATUS.CHANGED) + { + // Note: this will halt the application bootstrap. + throw new AlfrescoRuntimeException("The key with alias " + keyAlias + " has been changed, re-instate the previous keystore"); + } + } + + return null; + } + }; + retryingTransactionHelper.doInTransaction(checkKeysCallback, false); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + + } + + public void removeRegisteredKeys() + { + RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper(); + final RetryingTransactionCallback removeKeysCallback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + for(String keyAlias : keyAliases) + { + removeKey(keyAlias); + } + + return null; + } + }; + retryingTransactionHelper.doInTransaction(removeKeysCallback, false); + } + + /** + * A KeyCheck object stores a well-known guid and it's encrypted value. + * + * @since 4.0 + * + */ + private static class KeyCheck implements Serializable + { + private static final long serialVersionUID = 4514315444977162903L; + + private String guid; + private Serializable encrypted; + + public KeyCheck(String guid, Serializable encrypted) + { + super(); + this.guid = guid; + this.encrypted = encrypted; + } + + public String getGuid() + { + return guid; + } + + public Serializable getEncrypted() + { + return encrypted; + } + + public boolean equals(Object other) + { + if(this == other) + { + return true; + } + + if(!(other instanceof KeyCheck)) + { + return false; + } + KeyCheck keyCheck = (KeyCheck)other; + return EqualsHelper.nullSafeEquals(keyCheck.getGuid(), getGuid()) && + EqualsHelper.nullSafeEquals(keyCheck.getEncrypted(), getEncrypted()); + } + } + +} diff --git a/source/java/org/alfresco/encryption/EncryptorTest.java b/source/java/org/alfresco/encryption/EncryptorTest.java index 9e230ef805..7e2617d866 100644 --- a/source/java/org/alfresco/encryption/EncryptorTest.java +++ b/source/java/org/alfresco/encryption/EncryptorTest.java @@ -1,11 +1,30 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ package org.alfresco.encryption; import java.io.Serializable; import java.security.AlgorithmParameters; +import java.security.KeyException; import junit.framework.TestCase; -import org.alfresco.encryption.DefaultEncryptor; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.util.Pair; import org.bouncycastle.util.Arrays; @@ -66,7 +85,15 @@ public class EncryptorTest extends TestCase 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); + try + { + Object output = encryptor.unsealObject("mykey2", sealedObject); + assertEquals("Encryption round trip failed. ", testObject, output); + } + catch(KeyException e) + { + throw new AlfrescoRuntimeException("", e) +; } + } } diff --git a/source/java/org/alfresco/encryption/KeyStoreKeyProviderTest.java b/source/java/org/alfresco/encryption/KeyStoreKeyProviderTest.java index 575ab39461..be451e34d9 100644 --- a/source/java/org/alfresco/encryption/KeyStoreKeyProviderTest.java +++ b/source/java/org/alfresco/encryption/KeyStoreKeyProviderTest.java @@ -1,7 +1,23 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ package org.alfresco.encryption; -import java.io.FileNotFoundException; -import java.io.InputStream; import java.security.Key; import java.security.KeyStore; import java.security.UnrecoverableKeyException; @@ -11,8 +27,6 @@ import java.util.Map; import junit.framework.TestCase; -import org.alfresco.encryption.KeyStoreLoader; -import org.alfresco.encryption.KeystoreKeyProvider; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.util.ApplicationContextHelper; import org.springframework.context.ApplicationContext; @@ -53,9 +67,9 @@ public class KeyStoreKeyProviderTest extends TestCase return ks; } - protected static KeyStoreLoader getKeyStoreLoader() + protected static KeyResourceLoader getKeyStoreLoader() { - return new SpringKeyStoreLoader(); + return new SpringKeyResourceLoader(); } public void setUp() throws Exception diff --git a/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java b/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java index 911e045ace..4d88913ea2 100644 --- a/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java +++ b/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java @@ -1,6 +1,7 @@ package org.alfresco.repo.node.encryption; import java.io.Serializable; +import java.security.KeyException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -10,6 +11,7 @@ import javax.crypto.SealedObject; import org.alfresco.encryption.Encryptor; import org.alfresco.encryption.KeyProvider; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; @@ -107,9 +109,16 @@ public class MetadataEncryptor { return inbound; } - Serializable outbound = encryptor.unsealObject(KeyProvider.ALIAS_METADATA, inbound); - // Done - return outbound; + try + { + Serializable outbound = encryptor.unsealObject(KeyProvider.ALIAS_METADATA, inbound); + // Done + return outbound; + } + catch(KeyException e) + { + throw new AlfrescoRuntimeException("Invalid metadata decryption key", e); + } } /** @@ -198,9 +207,16 @@ public class MetadataEncryptor // We have already checked for nulls and conversions Serializable value = inbound.get(propertyQName); // Have to decrypt the value - Serializable unencryptedValue = encryptor.unsealObject(KeyProvider.ALIAS_METADATA, value); - // Store it back - outbound.put(propertyQName, unencryptedValue); + try + { + Serializable unencryptedValue = encryptor.unsealObject(KeyProvider.ALIAS_METADATA, value); + // Store it back + outbound.put(propertyQName, unencryptedValue); + } + catch(KeyException e) + { + throw new AlfrescoRuntimeException("Invalid metadata decryption key", e); + } } // Done return outbound;