From f7f23f6eb72fb3ff292c4196dd04b51da51f24a9 Mon Sep 17 00:00:00 2001 From: Steven Glover Date: Tue, 23 Aug 2011 18:34:15 +0000 Subject: [PATCH] Refactoring to support ALF-9510, ALF-8702 ALF-8702: Solr-Repository SSL Communications (see solr/source/solr/instance/HowToSetUpSolr.txt ALF-9510: Initial checkin git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@30005 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/application-context-core.xml | 2 +- config/alfresco/bootstrap-context.xml | 14 +- config/alfresco/core-services-context.xml | 14 +- config/alfresco/encryption-context.xml | 80 +++- config/alfresco/keystore/keystore | Bin 0 -> 1254 bytes .../keystore/keystore-passwords.properties | 20 + .../ssl-keystore-passwords.properties | 7 + .../ssl-truststore-passwords.properties | 5 + config/alfresco/keystore/ssl.keystore | Bin 0 -> 2568 bytes config/alfresco/keystore/ssl.truststore | Bin 0 -> 651 bytes config/alfresco/repository.properties | 43 ++- .../Search/solr/solr-search-context.xml | 28 +- .../encryption/BootstrapReEncryptor.java | 68 ++++ .../encryption/EncryptionChecker.java | 169 +-------- .../alfresco/encryption/KeyStoreChecker.java | 353 ++++++++++++++++++ .../encryption/KeyStoreKeyProviderTest.java | 172 ++++++--- .../org/alfresco/encryption/ReEncryptor.java | 228 +++++++++++ .../alfresco/repo/domain/node/NodeDAO.java | 3 + .../repo/domain/node/ibatis/NodeDAOImpl.java | 27 ++ .../node/encryption/MetadataEncryptor.java | 23 +- .../search/impl/solr/SolrQueryHTTPClient.java | 77 +++- .../alfresco/repo/solr/SOLRAdminClient.java | 45 ++- 22 files changed, 1109 insertions(+), 269 deletions(-) create mode 100644 config/alfresco/keystore/keystore create mode 100644 config/alfresco/keystore/keystore-passwords.properties create mode 100644 config/alfresco/keystore/ssl-keystore-passwords.properties create mode 100644 config/alfresco/keystore/ssl-truststore-passwords.properties create mode 100644 config/alfresco/keystore/ssl.keystore create mode 100644 config/alfresco/keystore/ssl.truststore create mode 100644 source/java/org/alfresco/encryption/BootstrapReEncryptor.java create mode 100644 source/java/org/alfresco/encryption/KeyStoreChecker.java create mode 100644 source/java/org/alfresco/encryption/ReEncryptor.java diff --git a/config/alfresco/application-context-core.xml b/config/alfresco/application-context-core.xml index 9230844827..329f0c2fab 100644 --- a/config/alfresco/application-context-core.xml +++ b/config/alfresco/application-context-core.xml @@ -7,9 +7,9 @@ + - diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 4429404dd3..caeeef3de8 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -168,16 +168,16 @@ - + - - - metadata - solr - - + + + + + + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 83c7b07908..a71fa35755 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -1012,14 +1012,22 @@ + + + + + + + + - - - + + + diff --git a/config/alfresco/encryption-context.xml b/config/alfresco/encryption-context.xml index bbe5f12799..8433ba0743 100644 --- a/config/alfresco/encryption-context.xml +++ b/config/alfresco/encryption-context.xml @@ -9,33 +9,81 @@ 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/keystore b/config/alfresco/keystore/keystore new file mode 100644 index 0000000000000000000000000000000000000000..7e966eebae5433614e59d7d350981be1add2c621 GIT binary patch literal 1254 zcmX?i?%X*B1_mZ5W@g~XO)W`GNi0cZU|=+uyYa`PHE$VMi;EbHlk;=+ic9nKl8Y(} zO7iszit@`cQ&Nlcf>RT7Qd9hsvQm>v-13XOQ!4|C@=Ji6{Gzkrry`#l><$K+UQxs# znUz?USOL?Itj*5McG=ryDXA#|iA9OI#U%_((N56}0zg5Ky{ReA z`FSO&c_oDmbqq{C44egERgO97B@9A7APe+!67$magG-7s^U|$-7}$$}&I3w=)H7EU zloo-^Tk~1`2OCGo1CYB4fIjCp;AP=pS5Aw%-N{guQEH^W^G zGA`K^em?L$Xi0v?^1_Ct`=sU^zGjqpT<3(u1>-eaX6t_n{P^$B>KTs0et%MUKeH@Y zlO1F9I_AgAA3S&e-iU2i7B=}9Cc=2(*!Pa*Uu)y|JPXM22Bnx)^qpIpb^sT{NW#a<_@di%}#`RkSxhgsY+Wzx-=^7O$98^y=z z`$TgNO%OOx_ix|i8-@k-OTLNlWfaHwBu3VBxX%l@#iCroAQs@{8lG8_;p<}Rn3ob# zlv$9I>f#!VQvw*=%nU5W`8h?v@HOOIU>7`65}4hu%G;|B*%zQBFn4*4>~|TlPa7H5 zDF5Hxm2mo5gM)VKO}`1|AN_SV?p3z z^y~KhjZcG4s_e^sBD*j@y<_HrjUG!%8B^;v@EpyQN?6>Z{c29LHp?~T`_JcX_4z1v zURCyy+twEw9hTZA%l_NGHD+=%Q^1_e<=eQHO?sY>X_7T1>)> z2D~gB-m)KgvL?p5Ffod-G%+<;u%G0g|2#qQuH}y1J_@g9hWBdnnEzb3Ci_XQirl$- zhVxUGZkOyVG^_1Y7QY*8753Dk%1z@i{IMWYO(&NlIG)f@kKeA zZ)KN1dS9P2{rdJvmUmjdUYFu|$iH;kjKEJR-%qFTNOwr8u6~>vVzB4=f-i9@kCt%F zQ0i+ry1pou_3Z+Y_a@@I#H8OdTyV%bW-%$o?aXq+$(NctJM%-8j6Z0xO_%XmtGr{b zhGUywq0xebS3cA1E+*SF*~rb~Z{5nA`+WBEyKBzf`8>gCckb_^f-S+@xIFqbw{);; zTYi5Qmp8*N+p9_1y+ZPEL~X=WBZ*({IXNd~wIoD|?({q6aK^B5);uA8pE=7agQvcY zn^kI4zPCB_;!<7LLlbA7ST}d`{N?ZFWUXQ}LV-S$0v4-=2~{473hJ)eI= z+}@A(N-ed2*fYMnvqL7f+pptoh1I3a3Huwron_g*)vKjq?xHiSE{dhxb%|aUO3UYd zmiVi_vhbY7U0W+5?dLktk1B7fN=@UbX?@JKC2Q)LGY2BQxmTFai<;rg_56Xd(#r>!)-YZBuMuk{d*kWPh%Gbk&6mz*sK2t~_%%Bt zvuC_c%XU95wT*iHOZmF{KDDFLH-)rrO>s&}ohf@-Z)V?RmunU8(zsu1Hz&W#*}wAs zquWaI!Invaks=xjVr)9KuYY-iE>kf*=`oVJ_dq%#_r;jMT&wLwN&PkRX?^ zm}5>_QEG8=zCv(*T1k0gQL2JZNs6AKh=CAD0kbeST!FKrft)z6p`n3=p@pH5fr+7E z6p(8K>@;Dx(}IAxyEwBXzo^on zaXxb3FtRc*H}*0ZG*U6-GVD3+=MZ0K*lk2#kN z7Nj*4?pn5YMZgn{-GQrDZSNH|fBx+E-1jSrd7cY?oTxIPE~rN1-ebQ7?~Aj9lnGm|71%IXV|q`^#4%ru@IXge7SStzSLg_YIIax-_6rBGCs(p)yd}sF1q+Z x6>lQ6e9=ozgw((6J)_ADUl}`^+(=Ju5^Iv#S1d8Tpq;9c8?d-(K8Kh{7yxok!^Hpq literal 0 HcmV?d00001 diff --git a/config/alfresco/keystore/ssl.truststore b/config/alfresco/keystore/ssl.truststore new file mode 100644 index 0000000000000000000000000000000000000000..1f127e5a64a88a835736e63b000604ffeaa21b30 GIT binary patch literal 651 zcmX?i?%X*B1_mY|W(3pRi8*OSsm00pddZ1EF~e&g0wyxBM(CLuSOS%~8#FOF8#FPl zVd7+Xs+0QX{`?)n2E1&XT5TR}-+37sSy>qj(hRu`IN6v(S=fY`+?`+?4q>KHZ$n`N zL68i)Fqdy)W=d*aMrvY;p}c`CNRUfd%n@q0LU4XsNqJ&Xs)A2Rik_i}fe=UmvoJSY zfwQB5oH(zcp@D^=xq*eTg|T@QkZT0wnnAgAb4TNRKs{6z*EKcSXPxjopE(S8eYVHGlr>_uTg@ig}(3ew?T>p)ROK zyx(3`n#H&iJ6gs5!rdb2x11htB=21{BZunqR6GS zffli5Q#t9xnDuy*+Ts}00=GM$^ZZW literal 0 HcmV?d00001 diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 74e86dfc93..b68743d0fc 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -673,21 +673,44 @@ deployment.filesystem.default.metadatadir=${deployment.filesystem.metadatadir}/d # # Encryption properties # -encryption.encryption.cipherAlgorithm=DESede/CBC/PKCS5Padding -encryption.keystore.location=classpath:alfresco/.keystore -encryption.keystore.passwordsFile.location=classpath:alfresco/keystore-passwords.properties +#dir.keystore=${dir.root}/keystore +dir.keystore=classpath:alfresco/keystore +encryption.cipherAlgorithm=DESede/CBC/PKCS5Padding +encryption.keystore.location=${dir.keystore}/keystore +encryption.keystore.keyMetaData.location=${dir.keystore}/keystore-passwords.properties encryption.keystore.provider= encryption.keystore.type=JCEKS -encryption.messageTimeout=30000 -encryption.macAlgorithm=HmacSHA1 +#encryption.keystore.keyAlgorithm=PBEWithMD5AndDES +encryption.keystore.keyAlgorithm=DESede +encryption.keystore.keyAliases=metadata, solr -# SOLR connection details -solr.solrHost=localhost -solr.solrAdminPort=8834 -solr.solrUrl=http://${solr.solrHost}:8080/solr +encryption.keystore.fallback.location=${dir.keystore}/fallback-keystore +encryption.keystore.fallback.keyMetaData.location=${dir.keystore}/fallback-keystore-passwords.properties +encryption.keystore.fallback.provider= +encryption.keystore.fallback.type=JCEKS + +# mac +encryption.mac.messageTimeout=30000 +encryption.mac.algorithm=HmacSHA1 + +# ssl +encryption.ssl.keystore.location=${dir.keystore}/ssl.keystore +encryption.ssl.keystore.provider= +encryption.ssl.keystore.type=JCEKS +encryption.ssl.keystore.keyMetaData.location=${dir.keystore}/ssl-keystore-passwords.properties +encryption.ssl.truststore.location=${dir.keystore}/ssl.truststore +encryption.ssl.truststore.provider= +encryption.ssl.truststore.type=JCEKS +encryption.ssl.truststore.keyMetaData.location=${dir.keystore}/ssl-truststore-passwords.properties + +# SOLR connection details (e.g. for JMX) +solr.host=localhost +solr.port=8443 solr.solrUser=solr solr.solrPassword=solr -solr.secureComms.enabled=true +# none, https +solr.secureComms=https + # ms solr.solrConnectTimeout=5000 solr.solrPingCronExpression=0 0/5 * * * ? * diff --git a/config/alfresco/subsystems/Search/solr/solr-search-context.xml b/config/alfresco/subsystems/Search/solr/solr-search-context.xml index f17b4aa2af..48cb523e66 100644 --- a/config/alfresco/subsystems/Search/solr/solr-search-context.xml +++ b/config/alfresco/subsystems/Search/solr/solr-search-context.xml @@ -29,10 +29,36 @@ - + + + ${solr.host} + + + ${solr.port} + + + + diff --git a/source/java/org/alfresco/encryption/BootstrapReEncryptor.java b/source/java/org/alfresco/encryption/BootstrapReEncryptor.java new file mode 100644 index 0000000000..7cab37f5a2 --- /dev/null +++ b/source/java/org/alfresco/encryption/BootstrapReEncryptor.java @@ -0,0 +1,68 @@ +/* + * 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 org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +/** + * + * @since 4.0 + * + */ +public class BootstrapReEncryptor extends AbstractLifecycleBean +{ + private boolean enabled; + private ReEncryptor reEncryptor; + private KeyStoreParameters oldKeyStoreParameters; + + public void setEnabled(boolean enabled) + { + this.enabled = enabled; + } + + public void setReEncryptor(ReEncryptor reEncryptor) + { + this.reEncryptor = reEncryptor; + } + + public void setOldKeyStoreParameters(KeyStoreParameters oldKeyStoreParameters) + { + this.oldKeyStoreParameters = oldKeyStoreParameters; + } + + public void reEncrypt() + { + reEncryptor.execute(oldKeyStoreParameters); + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + if(enabled) + { + reEncrypt(); + } + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/encryption/EncryptionChecker.java b/source/java/org/alfresco/encryption/EncryptionChecker.java index 0ea16d8025..31e60f812d 100644 --- a/source/java/org/alfresco/encryption/EncryptionChecker.java +++ b/source/java/org/alfresco/encryption/EncryptionChecker.java @@ -18,17 +18,6 @@ */ 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; @@ -38,6 +27,7 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean; * The EncryptionChecker checks the state of the repository's encryption system. * In particular it checks: *
    + *
  • that the keystore exists and, if not, creates one. *
  • that the encryption keys have not been changed. If so, the bootstrap will be halted. *
* @@ -47,96 +37,31 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean; 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) + private KeyStoreChecker keyStoreChecker; + private KeyStoreParameters keyStoreParameters; + private KeyResourceLoader keyResourceLoader; + + public void setkeyStoreParameters(KeyStoreParameters keyStoreParameters) { - this.transactionService = transactionService; + this.keyStoreParameters = keyStoreParameters; } - public void setAttributeService(AttributeService attributeService) + public void setKeyStoreChecker(KeyStoreChecker keyStoreChecker) { - this.attributeService = attributeService; + this.keyStoreChecker = keyStoreChecker; } - - public void setKeyAliases(List keyAliases) + + public void setKeyResourceLoader(KeyResourceLoader keyResourceLoader) { - 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; - } + this.keyResourceLoader = keyResourceLoader; } @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); + AlfrescoKeyStore mainKeyStore = new CachingKeyStore(keyStoreParameters, keyResourceLoader); + keyStoreChecker.checkKeyStore(mainKeyStore); } @Override @@ -144,70 +69,4 @@ public class EncryptionChecker extends AbstractLifecycleBean { } - - 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/KeyStoreChecker.java b/source/java/org/alfresco/encryption/KeyStoreChecker.java new file mode 100644 index 0000000000..666dd1189a --- /dev/null +++ b/source/java/org/alfresco/encryption/KeyStoreChecker.java @@ -0,0 +1,353 @@ +/* + * 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.FileOutputStream; +import java.io.Serializable; +import java.security.InvalidKeyException; +import java.security.Key; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +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.cmr.attributes.AttributeService.AttributeQueryCallback; +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; + +/** + * Validates an Alfresco keystore by ensuring that it's registered keys have not changed. + * + * @since 4.0 + * + */ +public class KeyStoreChecker +{ + public static String TOP_LEVEL_KEY = "keyCheck"; + private static final Log logger = LogFactory.getLog(KeyStoreChecker.class); + private static enum KEY_STATUS + { + OK, CHANGED, MISSING; + }; + + private AttributeService attributeService; + private Encryptor encryptor; + private TransactionService transactionService; + + public KeyStoreChecker() + { + } + + public void setAttributeService(AttributeService attributeService) + { + this.attributeService = attributeService; + } + + public void setEncryptor(Encryptor encryptor) + { + this.encryptor = encryptor; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public KeyStoreChecker(AttributeService attributeService, Encryptor encryptor, TransactionService transactionService) + { + this.attributeService = attributeService; + this.transactionService = transactionService; + this.encryptor = encryptor; + } + + private KEY_STATUS checkKey(AlfrescoKeyStore keyStore, String keyAlias) + { + if(attributeService.exists(TOP_LEVEL_KEY, keyStore.getLocation(), 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, keyStore.getLocation(), 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 + { + return KEY_STATUS.MISSING; + } + } + + private void registerKey(AlfrescoKeyStore keyStore, String keyAlias) + { + // 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, keyStore.getLocation(), keyAlias); + logger.info("Registered key " + keyAlias); + } + + protected KeysReport getKeysReport(AlfrescoKeyStore keyStore) + { + final List registeredKeys = new ArrayList(); + + if(attributeService.exists(TOP_LEVEL_KEY, keyStore.getLocation())) + { + attributeService.getAttributes(new AttributeQueryCallback() + { + public boolean handleAttribute(Long id, Serializable value, + Serializable[] keys) + { + registeredKeys.add((String)value); + return true; + } + + }, + TOP_LEVEL_KEY, keyStore.getLocation()); + } + + List keyAliasesChanged = new ArrayList(); + List keyAliasesUnchanged = new ArrayList(); + + for(String keyAlias : registeredKeys) + { + KEY_STATUS keyStatus = checkKey(keyStore, keyAlias); + if(keyStatus == KEY_STATUS.CHANGED) + { + keyAliasesChanged.add(keyAlias); + } + else + { + keyAliasesUnchanged.add(keyAlias); + } + } + + return new KeysReport(keyAliasesChanged, keyAliasesUnchanged); + } + + public void checkKeyStore(final AlfrescoKeyStore keyStore) + { + RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper(); + final RetryingTransactionCallback checkKeysCallback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + KeysReport keysReport = getKeysReport(keyStore); + + // Check for the existence of a key store first + if(keyStore.exists()) + { + // The keystore exists - check whether any keys have been changed + if(keysReport.getKeysChanged().size() > 0) + { + // Note: this will halt the application bootstrap. + throw new AlfrescoRuntimeException("The keys with aliases " + keysReport.getKeysChanged() + " have been changed, re-instate the previous keystore"); + } + } + else + { + // keystore not found, check whether any keys have been registered + if(keysReport.getKeysChanged().size() + keysReport.getKeysUnchanged().size() > 0) + { + // Note: this will halt the application bootstrap. + throw new AlfrescoRuntimeException("Keys have already been registered, re-instate the previous keystore"); + } + + // no keys found, create a new keystore + // TODO + if(logger.isDebugEnabled()) + { + logger.debug("Keystore not found, creating..."); + } + createKeyStore(keyStore); + } + + return null; + } + }; + retryingTransactionHelper.doInTransaction(checkKeysCallback, false); + } + + protected void createKeyStore(AlfrescoKeyStore keyStore) + { + new CreatingKeyStore(keyStore).create(); + } + + public static class KeysReport + { + private List keysChanged; + private List keysUnchanged; + + public KeysReport(List keysChanged, List keysUnchanged) + { + super(); + this.keysChanged = keysChanged; + this.keysUnchanged = keysUnchanged; + } + + public List getKeysChanged() + { + return keysChanged; + } + + public List getKeysUnchanged() + { + return keysUnchanged; + } + } + + /** + * 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()); + } + } + + public void removeRegisteredKeys(final AlfrescoKeyStore keyStore) + { + RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper(); + final RetryingTransactionCallback removeKeysCallback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + attributeService.removeAttributes(TOP_LEVEL_KEY, keyStore.getLocation()); + + return null; + } + }; + retryingTransactionHelper.doInTransaction(removeKeysCallback, false); + } + + private class CreatingKeyStore extends CachingKeyStore + { + CreatingKeyStore(AlfrescoKeyStore keyStore) + { + super(keyStore.getkeyStoreParameters(), keyStore.getKeyResourceLoader()); + } + + public void create() + { + KeyInfoManager keyInfoManager = null; + + try + { + keyInfoManager = getKeyInfoManager(); + + ks.load(null, null); + String keyStorePassword = keyInfoManager.getKeyStorePassword(); + if(keyStorePassword == null) + { + throw new AlfrescoRuntimeException("Key store password is null for keystore at location " + getLocation() + + ", key store meta data location" + getKeyMetaDataFileLocation()); + } + + // Add keys from the passwords file to the keystore + for(Map.Entry keyEntry : keyInfoManager.getKeyInfo().entrySet()) + { + KeyInformation keyInfo = keyInfoManager.getKeyInformation(keyEntry.getKey()); + String keyPassword = keyInfo.getPassword(); + if(keyPassword == null) + { + throw new AlfrescoRuntimeException("No password found for encryption key " + keyEntry.getKey()); + } + Key key = generateSecretKey(keyEntry.getValue()); + ks.setKeyEntry(keyInfo.getAlias(), key, keyInfo.getPassword().toCharArray(), null); + } + + ks.store(new FileOutputStream(getLocation()), keyStorePassword.toCharArray()); + + // Register the key store keys + for(Map.Entry keyEntry : keyInfoManager.getKeyInfo().entrySet()) + { + registerKey(this, keyEntry.getKey()); + } + } + catch(Throwable e) + { + throw new AlfrescoRuntimeException( + "Failed to create keystore: \n" + + " Location: " + getLocation() + "\n" + + " Provider: " + getProvider() + "\n" + + " Type: " + getType(), + e); + } + finally + { + if(keyInfoManager != null) + { + keyInfoManager.clear(); + } + } + } + } +} diff --git a/source/java/org/alfresco/encryption/KeyStoreKeyProviderTest.java b/source/java/org/alfresco/encryption/KeyStoreKeyProviderTest.java index be451e34d9..a764bdd4ad 100644 --- a/source/java/org/alfresco/encryption/KeyStoreKeyProviderTest.java +++ b/source/java/org/alfresco/encryption/KeyStoreKeyProviderTest.java @@ -18,12 +18,15 @@ */ package org.alfresco.encryption; +import java.io.FileNotFoundException; +import java.io.IOException; import java.security.Key; import java.security.KeyStore; import java.security.UnrecoverableKeyException; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Properties; import junit.framework.TestCase; @@ -55,21 +58,71 @@ public class KeyStoreKeyProviderTest extends TestCase /* package */ static KeystoreKeyProvider getTestKeyStoreProvider() { Map passwords = new HashMap(5); - passwords.put(KeystoreKeyProvider.KEY_KEYSTORE_PASSWORD, "ksPwd2"); + passwords.put(AlfrescoKeyStore.KEY_KEYSTORE_PASSWORD, "ksPwd2"); passwords.put(ALIAS_ONE, "aliasPwd1"); passwords.put(ALIAS_TWO, "aliasPwd2"); - KeystoreKeyProvider ks = new KeystoreKeyProvider( - FILE_TWO, - getKeyStoreLoader(), - "SunJCE", - "JCEKS", - passwords); - return ks; + KeyStoreParameters encryptionParameters = new KeyStoreParameters("JCEKS", "SunJCE", null, FILE_TWO); + KeystoreKeyProvider keyProvider = new KeystoreKeyProvider(encryptionParameters, getKeyStoreLoader(passwords)); +// FILE_TWO, +// getKeyStoreLoader(), +// "SunJCE", +// "JCEKS", +// passwords); + return keyProvider; + } + + /* package */ static KeystoreKeyProvider getTestKeyStoreProvider(String keyStoreLocation, Map passwords) + { +// Map passwords = new HashMap(5); +// passwords.put(KeyStoreManager.KEY_KEYSTORE_PASSWORD, "ksPwd2"); +// passwords.put(ALIAS_ONE, "aliasPwd1"); +// passwords.put(ALIAS_TWO, "aliasPwd2"); + KeyStoreParameters encryptionParameters = new KeyStoreParameters("JCEKS", "SunJCE", null, keyStoreLocation); + KeystoreKeyProvider keyProvider = new KeystoreKeyProvider(encryptionParameters, getKeyStoreLoader(passwords)); +// FILE_TWO, +// getKeyStoreLoader(), +// "SunJCE", +// "JCEKS", +// passwords); + return keyProvider; } - protected static KeyResourceLoader getKeyStoreLoader() + private static class TestKeyResourceLoader extends SpringKeyResourceLoader { - return new SpringKeyResourceLoader(); + private Properties props; + + TestKeyResourceLoader(Map passwords) + { + StringBuilder aliases = new StringBuilder(); + props = new Properties(); + + int i = 0; + for(Map.Entry password : passwords.entrySet()) + { + props.put(password.getKey() + ".password", password.getValue()); + + aliases.append(password.getKey()); + if(i < passwords.size() - 1) + { + aliases.append(","); + i++; + } + } + + props.put("aliases", aliases.toString()); + } + + @Override + public Properties loadKeyMetaData(String keyMetaDataFileLocation) + throws IOException, FileNotFoundException + { + return props; + } + } + + protected static KeyResourceLoader getKeyStoreLoader(Map passwords) + { + return new TestKeyResourceLoader(passwords); } public void setUp() throws Exception @@ -78,24 +131,28 @@ public class KeyStoreKeyProviderTest extends TestCase public void testNoKeyStorePasswords() throws Exception { - KeystoreKeyProvider keyProvider = new KeystoreKeyProvider( - FILE_ONE, - getKeyStoreLoader(), - "SunJCE", - "JCEKS", - Collections.emptyMap()); + KeystoreKeyProvider keyProvider = getTestKeyStoreProvider(FILE_ONE, Collections.emptyMap()); + +// KeystoreKeyProvider keyProvider = new KeystoreKeyProvider( +// FILE_ONE, +// getKeyStoreLoader(), +// "SunJCE", +// "JCEKS", +// Collections.emptyMap()); // This has succeeded because we have not attempted to access it assertNull("Should be no keys available", keyProvider.getKey(ALIAS_ONE)); } public void testKeyStoreWithOnlyAliasPasswords() throws Exception { - KeystoreKeyProvider keyProvider = new KeystoreKeyProvider( - FILE_TWO, - getKeyStoreLoader(), - "SunJCE", - "JCEKS", - Collections.singletonMap(ALIAS_ONE, "aliasPwd1")); + KeystoreKeyProvider keyProvider = getTestKeyStoreProvider(FILE_ONE, Collections.singletonMap(ALIAS_ONE, "aliasPwd1")); + +// KeystoreKeyProvider keyProvider = new KeystoreKeyProvider( +// FILE_TWO, +// getKeyStoreLoader(), +// "SunJCE", +// "JCEKS", +// Collections.singletonMap(ALIAS_ONE, "aliasPwd1")); // This has succeeded because we have not attempted to access it assertNotNull("Should be able to key alias with same password", keyProvider.getKey(ALIAS_ONE)); } @@ -104,12 +161,14 @@ public class KeyStoreKeyProviderTest extends TestCase { try { - new KeystoreKeyProvider( - FILE_ONE, - getKeyStoreLoader(), - "SunJCE", - "JCEKS", - Collections.singletonMap(ALIAS_ONE, "password_fail")); + getTestKeyStoreProvider(FILE_ONE, Collections.singletonMap(ALIAS_ONE, "password_fail")); + +// new KeystoreKeyProvider( +// FILE_ONE, +// getKeyStoreLoader(), +// "SunJCE", +// "JCEKS", +// Collections.singletonMap(ALIAS_ONE, "password_fail")); fail("Expect to fail because password is incorrect"); } catch (AlfrescoRuntimeException e) @@ -123,12 +182,13 @@ public class KeyStoreKeyProviderTest extends TestCase { try { - new KeystoreKeyProvider( - FILE_TWO, - getKeyStoreLoader(), - "SunJCE", - "JCEKS", - Collections.singletonMap(ALIAS_TWO, "password_fail")); + getTestKeyStoreProvider(FILE_TWO, Collections.singletonMap(ALIAS_TWO, "password_fail")); +// new KeystoreKeyProvider( +// FILE_TWO, +// getKeyStoreLoader(), +// "SunJCE", +// "JCEKS", +// Collections.singletonMap(ALIAS_TWO, "password_fail")); fail("Expect to fail because password is incorrect"); } catch (AlfrescoRuntimeException e) @@ -140,12 +200,14 @@ public class KeyStoreKeyProviderTest extends TestCase public void testAliasWithCorrectPassword_One() throws Exception { - KeystoreKeyProvider ks = new KeystoreKeyProvider( - FILE_ONE, - getKeyStoreLoader(), - "SunJCE", - "JCEKS", - Collections.singletonMap(ALIAS_ONE, "aliasPwd1")); + KeystoreKeyProvider ks = getTestKeyStoreProvider(FILE_ONE, Collections.singletonMap(ALIAS_ONE, "aliasPwd1")); + +// KeystoreKeyProvider ks = new KeystoreKeyProvider( +// FILE_ONE, +// getKeyStoreLoader(), +// "SunJCE", +// "JCEKS", +// Collections.singletonMap(ALIAS_ONE, "aliasPwd1")); Key keyOne = ks.getKey(ALIAS_ONE); assertNotNull(keyOne); } @@ -155,12 +217,16 @@ public class KeyStoreKeyProviderTest extends TestCase Map passwords = new HashMap(5); passwords.put(ALIAS_ONE, "aliasPwd1"); passwords.put(ALIAS_TWO, "aliasPwd2"); - KeystoreKeyProvider ks = new KeystoreKeyProvider( - FILE_TWO, - getKeyStoreLoader(), - "SunJCE", - "JCEKS", - passwords); + + KeystoreKeyProvider ks = getTestKeyStoreProvider(FILE_TWO, passwords); + +// KeystoreKeyProvider ks = new KeystoreKeyProvider( +// FILE_TWO, +// getKeyStoreLoader(), +// "SunJCE", +// "JCEKS", +// passwords); + assertNotNull(ks.getKey(ALIAS_ONE)); assertNotNull(ks.getKey(ALIAS_TWO)); } @@ -171,12 +237,14 @@ public class KeyStoreKeyProviderTest extends TestCase passwords.put(ALIAS_ONE, "aliasPwd1"); passwords.put(ALIAS_TWO, "aliasPwd2"); passwords.put(ALIAS_THREE, "aliasPwd3"); - KeystoreKeyProvider ks = new KeystoreKeyProvider( - FILE_THREE, - getKeyStoreLoader(), - "SunJCE", - "JCEKS", - passwords); + KeystoreKeyProvider ks = getTestKeyStoreProvider(FILE_THREE, passwords); + +// KeystoreKeyProvider ks = new KeystoreKeyProvider( +// FILE_THREE, +// getKeyStoreLoader(), +// "SunJCE", +// "JCEKS", +// passwords); assertNotNull(ks.getKey(ALIAS_ONE)); assertNotNull(ks.getKey(ALIAS_TWO)); assertNull(ks.getKey(ALIAS_THREE)); diff --git a/source/java/org/alfresco/encryption/ReEncryptor.java b/source/java/org/alfresco/encryption/ReEncryptor.java new file mode 100644 index 0000000000..5d023d85a5 --- /dev/null +++ b/source/java/org/alfresco/encryption/ReEncryptor.java @@ -0,0 +1,228 @@ +/* + * 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.util.Collection; +import java.util.Collections; +import java.util.List; + +import javax.crypto.SealedObject; + +import org.alfresco.repo.batch.BatchProcessWorkProvider; +import org.alfresco.repo.batch.BatchProcessor; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.dictionary.NamespaceDAO; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.node.NodePropertyEntity; +import org.alfresco.repo.domain.node.NodePropertyKey; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.node.encryption.MetadataEncryptor; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +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.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.extensions.surf.util.I18NUtil; + +// TODO use Batch code to run in parallel +// TODO lock so that only one encryptor can run at a time +/** + * Re-encrypts encryptable repository properties using a new set of encryption keys. + * Decrypts the repository properties using the default encryptor, falling back to + * a backup decryptor (using the old encryption keys) if necessary, and then re-encrypts + * the properties. + * + * The system can stay running during this operation. + * + * @since 4.0 + */ +public class ReEncryptor implements ApplicationContextAware +{ + private static Log logger = LogFactory.getLog(ReEncryptor.class); + + private NodeDAO nodeDAO; + private NamespaceDAO namespaceDAO; + private DictionaryDAO dictionaryDAO; + private DictionaryService dictionaryService; + private QNameDAO qnameDAO; + + private MetadataEncryptor metadataEncryptor; + + private ApplicationContext applicationContext; + + private TransactionService transactionService; + private RetryingTransactionHelper transactionHelper; + + /** + * Set the transaction provider so that each execution can be performed within a transaction + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + this.transactionHelper = transactionService.getRetryingTransactionHelper(); + this.transactionHelper.setForceWritable(true); + } + +// protected MetadataEncryptor getMetadataEncryptor(EncryptionParameters encryptionParameters) +// { +// DefaultEncryptor encryptor = new DefaultEncryptor(); +// encryptor.setCipherAlgorithm(encryptionParameters.getCipherAlgorithm()); +// encryptor.setCipherProvider(null); +// KeystoreKeyProvider keyProvider = new KeystoreKeyProvider(); +// keyProvider.setLocation(encryptionParameters.getKeyStoreLocation()); +// keyProvider.setPasswordsFileLocation(encryptionParameters.getPasswordFileLocation()); +// keyProvider.setType(encryptionParameters.getKeyStoreType()); +// keyProvider.setKeyResourceLoader(new SpringKeyResourceLoader()); +// keyProvider.setProvider(encryptionParameters.getKeyStoreProvider() +// ); +// +// encryptor.setKeyProvider(keyProvider); +// +// MetadataEncryptor metadataEncryptor = new MetadataEncryptor(); +// metadataEncryptor.setEncryptor(encryptor); +// metadataEncryptor.setDictionaryService(dictionaryService); +// +// return metadataEncryptor; +// } + + public void setMetadataEncryptor(MetadataEncryptor metadataEncryptor) + { + this.metadataEncryptor = metadataEncryptor; + } + + public void init() + { + } + + public void reencrypt(final KeyStoreParameters newEncryptionParameters, final List properties) + { + BatchProcessor.BatchProcessWorker worker = new BatchProcessor.BatchProcessWorker() + { + public String getIdentifier(NodePropertyEntity entity) + { + return String.valueOf(entity.getNodeId()); + } + + public void beforeProcess() throws Throwable + { + } + + public void afterProcess() throws Throwable + { + } + + public void process(NodePropertyEntity entity) throws Throwable + { + Object value = entity.getValue(); + if(value instanceof SealedObject) + { + SealedObject sealed = (SealedObject)value; + + NodePropertyKey nodeKey = entity.getKey(); + QName propertyQName = qnameDAO.getQName(nodeKey.getQnameId()).getSecond(); + + // metadataEncryptor uses a fallback encryptor; decryption will try the + // default (new) keys first (which will fail for properties created before the + // change in keys), followed by the backup keys. + Serializable decrypted = metadataEncryptor.decrypt(propertyQName, sealed); + + // Re-encrypt. The new keys will be used. + Serializable resealed = metadataEncryptor.encrypt(propertyQName, decrypted); + + // TODO update resealed using batch update? + // does the node DAO do batch updating? + nodeDAO.setNodeProperties(entity.getNodeId(), Collections.singletonMap(propertyQName, resealed)); + } + else + { + // TODO + } + } + }; + + BatchProcessWorkProvider provider = new BatchProcessWorkProvider() + { + private int start = 0; + + @Override + public int getTotalEstimatedWorkSize() + { + return properties.size(); + } + + @Override + public Collection getNextWork() + { + int end = start + 20; + if(end > properties.size()) + { + end = properties.size(); + } + List sublist = properties.subList(start, end); + start += 20; + return sublist; + } + }; + + // Migrate using 2 threads, 20 authorities per transaction. Log every 100 entries. + // TODO, propertize these numbers + new BatchProcessor( + I18NUtil.getMessage(""), + transactionHelper, + provider, + 2, 20, + applicationContext, + logger, 100).process(worker, true); + } + + public void execute(KeyStoreParameters newEncryptionParameters) + { + // Proceed only if fallback is available i.e. the systems has both old and new keys + if(metadataEncryptor.isFallbackAvailable()) + { + QName model = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, namespaceDAO); + + // get properties that are encrypted + Collection propertyDefs = dictionaryDAO.getProperties(model, DataTypeDefinition.ENCRYPTED); + List properties = nodeDAO.getProperties(propertyDefs); + + // reencrypt these properties + reencrypt(newEncryptionParameters, properties); + } + else + { + // TODO + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException + { + this.applicationContext = applicationContext; + } +} diff --git a/source/java/org/alfresco/repo/domain/node/NodeDAO.java b/source/java/org/alfresco/repo/domain/node/NodeDAO.java index 229d143fe7..0d1721e748 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeDAO.java +++ b/source/java/org/alfresco/repo/domain/node/NodeDAO.java @@ -27,6 +27,7 @@ import java.util.Set; import org.alfresco.repo.node.NodeBulkLoader; import org.alfresco.service.cmr.dictionary.InvalidTypeException; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.InvalidNodeRefException; @@ -36,6 +37,7 @@ import org.alfresco.service.cmr.repository.StoreExistsException; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; +import org.springframework.dao.ConcurrencyFailureException; /** * DAO services for alf_node and related tables @@ -671,4 +673,5 @@ public interface NodeDAO extends NodeBulkLoader */ public void setNodeDefiningAclId(Long nodeId, long id); + public List getProperties(Collection propertyDefs); } diff --git a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java index b4ff691417..485aa14c9b 100644 --- a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java @@ -49,6 +49,7 @@ import org.alfresco.repo.domain.node.TransactionEntity; import org.alfresco.repo.domain.node.TransactionQueryEntity; import org.alfresco.repo.domain.qname.QNameDAO; import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; @@ -90,6 +91,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl private static final String SELECT_NODES_BY_UUIDS = "alfresco.node.select_NodesByUuids"; private static final String SELECT_NODES_BY_IDS = "alfresco.node.select_NodesByIds"; private static final String SELECT_NODE_PROPERTIES = "alfresco.node.select_NodeProperties"; + private static final String SELECT_PROPERTIES_BY_TYPE = "alfresco.node.select_PropertiesByType"; private static final String SELECT_NODE_ASPECTS = "alfresco.node.select_NodeAspects"; private static final String INSERT_NODE_PROPERTY = "alfresco.node.insert.insert_NodeProperty"; private static final String UPDATE_PRIMARY_CHILDREN_SHARED_ACL = "alfresco.node.update.update_PrimaryChildrenSharedAcl"; @@ -1482,6 +1484,31 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl } } + // TODO - use a callback approach + public List getProperties(Collection propertyDefs) + { + Set qnames = new HashSet(); + for(PropertyDefinition propDef : propertyDefs) + { + qnames.add(propDef.getName()); + } + + final List props = new ArrayList(); + + // qnames of properties that are encrypted + Set qnameIds = qnameDAO.convertQNamesToIds(qnames, false); + template.select(SELECT_PROPERTIES_BY_TYPE, qnameIds, new ResultHandler() + { + @Override + public void handleResult(ResultContext context) + { + props.add((NodePropertyEntity)context.getResultObject()); + } + }); + + return props; + } + /* * DAO OVERRIDES */ diff --git a/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java b/source/java/org/alfresco/repo/node/encryption/MetadataEncryptor.java index 4d88913ea2..9540a238ab 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.InvalidKeyException; import java.security.KeyException; import java.util.HashMap; import java.util.HashSet; @@ -10,6 +11,7 @@ import java.util.Set; import javax.crypto.SealedObject; import org.alfresco.encryption.Encryptor; +import org.alfresco.encryption.FallbackEncryptor; import org.alfresco.encryption.KeyProvider; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.security.authentication.AuthenticationException; @@ -221,4 +223,23 @@ public class MetadataEncryptor // Done return outbound; } -} + + public Serializable reencrypt(QName propertyQName, Serializable sealed) throws InvalidKeyException + { + // metadataEncryptor uses a fallback encryptor; decryption will try the + // default (new) keys first (which will fail for properties created before the + // change in keys), followed by the backup keys. + Serializable decrypted = decrypt(propertyQName, sealed); + + // Re-encrypt. The new keys will be used. + Serializable resealed = encrypt(propertyQName, decrypted); + + return resealed; + } + + public boolean isFallbackAvailable() + { + return false; +// return encryptor.isFallbackAvailable(); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java index 1c5b9f6f85..953a8c90e4 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java @@ -29,7 +29,7 @@ import java.util.Map; import javax.servlet.http.HttpServletResponse; import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; +import org.alfresco.httpclient.HttpClientFactory; import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.search.impl.lucene.LuceneQueryParserException; import org.alfresco.repo.search.impl.lucene.SolrJSONResultSet; @@ -44,12 +44,8 @@ import org.alfresco.service.cmr.security.PermissionService; import org.apache.commons.codec.net.URLCodec; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpException; -import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; -import org.apache.commons.httpclient.UsernamePasswordCredentials; -import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; import org.apache.commons.httpclient.methods.PostMethod; -import org.apache.commons.httpclient.params.HttpClientParams; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONArray; @@ -73,21 +69,35 @@ public class SolrQueryHTTPClient private Map storeMappings; + private String solrHost; + private int solrPort; private String baseUrl; private HttpClient httpClient; - + private HttpClientFactory httpClientFactory; + public SolrQueryHTTPClient() { - MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager(); - httpClient = new HttpClient(connectionManager); - HttpClientParams params = httpClient.getParams(); - params.setBooleanParameter("http.tcp.nodelay", true); - params.setBooleanParameter("http.connection.stalecheck", false); - params.setBooleanParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, true); - httpClient.getState().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), new UsernamePasswordCredentials("admin", "admin")); } + public void init() + { + StringBuilder sb = new StringBuilder(); +// sb.append("http://"); +// sb.append(solrHost); +// sb.append(":"); +// sb.append(solrPort); + sb.append("/solr"); + this.baseUrl = sb.toString(); + + httpClient = httpClientFactory.getHttpClient(solrHost, solrPort); + } + + public void setHttpClientFactory(HttpClientFactory httpClientFactory) + { + this.httpClientFactory = httpClientFactory; + } + public void setNodeDAO(NodeDAO nodeDAO) { this.nodeDAO = nodeDAO; @@ -108,12 +118,47 @@ public class SolrQueryHTTPClient this.storeMappings = storeMappings; } - public void setBaseUrl(String baseUrl) + public void setSolrHost(String solrHost) { - this.baseUrl = baseUrl; + this.solrHost = solrHost; } + + public void setSolrPort(int solrPort) + { + this.solrPort = solrPort; + } + +// public void setBaseUrl(String baseUrl) +// { +// this.baseUrl = baseUrl; +// } - public ResultSet executeQuery(SearchParameters searchParameters, String language) +// public void setKeyStoreLocation(String keyStoreLocation) +// { +// this.keyStoreLocation = keyStoreLocation; +// } +// +// public void setTrustStoreLocation(String trustStoreLocation) +// { +// this.trustStoreLocation = trustStoreLocation; +// } +// +// public void setKeyStoreType(String keyStoreType) +// { +// this.keyStoreType = keyStoreType; +// } +// +// public void setTrustStoreType(String trustStoreType) +// { +// this.trustStoreType = trustStoreType; +// } +// +// public void setPasswordFileLocation(String passwordFileLocation) +// { +// this.passwordFileLocation = passwordFileLocation; +// } + + public ResultSet executeQuery(SearchParameters searchParameters, String language) { try { diff --git a/source/java/org/alfresco/repo/solr/SOLRAdminClient.java b/source/java/org/alfresco/repo/solr/SOLRAdminClient.java index 74106da37f..bc52ba86a0 100644 --- a/source/java/org/alfresco/repo/solr/SOLRAdminClient.java +++ b/source/java/org/alfresco/repo/solr/SOLRAdminClient.java @@ -29,8 +29,10 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.httpclient.HttpClientFactory; import org.alfresco.util.ParameterCheck; import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.auth.AuthScope; import org.apache.solr.client.solrj.SolrServerException; @@ -68,6 +70,8 @@ public class SOLRAdminClient implements ApplicationEventPublisherAware private ApplicationEventPublisher applicationEventPublisher; private SolrTracker solrTracker; + + private HttpClientFactory httpClientFactory; public SOLRAdminClient() { @@ -83,11 +87,6 @@ public class SOLRAdminClient implements ApplicationEventPublisherAware this.solrPort = Integer.parseInt(solrPort); } - public void setSolrUrl(String url) - { - this.solrUrl = url; - } - public void setSolrUser(String solrUser) { this.solrUser = solrUser; @@ -114,7 +113,29 @@ public class SOLRAdminClient implements ApplicationEventPublisherAware this.solrPingCronExpression = solrPingCronExpression; } - public void init() + public void setHttpClientFactory(HttpClientFactory httpClientFactory) + { + this.httpClientFactory = httpClientFactory; + } + +// protected HttpClient getHttpClient() +// { +// return httpClientFactory.getHttpClient(solrHost, solrPort); +//// HttpClient httpClient = new HttpClient(); +//// +//// HttpClientParams params = httpClient.getParams(); +//// params.setBooleanParameter("http.tcp.nodelay", true); +//// params.setBooleanParameter("http.connection.stalecheck", false); +//// +//// ProtocolSocketFactory socketFactory = new AuthSSLProtocolSocketFactory( +//// keyResourceLoader, encryptionParameters); +//// Protocol myhttps = new Protocol("https", socketFactory, 8843); +//// httpClient.getHostConfiguration().setHost(solrHost, 8080, myhttps); +//// +//// return httpClient; +// } + + public void init() { ParameterCheck.mandatory("solrHost", solrHost); ParameterCheck.mandatory("solrPort", solrPort); @@ -126,7 +147,17 @@ public class SOLRAdminClient implements ApplicationEventPublisherAware try { - server = new CommonsHttpSolrServer(solrUrl); + StringBuilder sb = new StringBuilder(); + sb.append(httpClientFactory.isSSL() ? "https://" : "http://"); + sb.append(solrHost); + sb.append(":"); + sb.append(solrPort); + sb.append("/solr"); + this.solrUrl = sb.toString(); + HttpClient httpClient = httpClientFactory.getHttpClient(solrHost, solrPort); + + server = new CommonsHttpSolrServer(solrUrl, httpClient); + // TODO remove credentials because we're using SSL? Credentials defaultcreds = new UsernamePasswordCredentials(solrUser, solrPassword); server.getHttpClient().getState().setCredentials(new AuthScope(solrHost, solrPort, AuthScope.ANY_REALM), defaultcreds);