Author: jacopoc Date: Wed Jun 10 09:01:30 2015 New Revision: 1684608 URL: http://svn.apache.org/r1684608 Log: New implementation of the two-way cryptographic services of OFBiz based on Apache Shiro: * two-way encryption is now delegated to Apache Shiro, with stronger initialization vectors * the mechanism is backward compatible * new tools to update the encryption of private keys, useful to upgrade older versions of OFBiz and most of all to replace old keys with new ones (this is critical to implement stronger security practices as requested by PCI) * unit tests
Added: ofbiz/trunk/framework/base/lib/shiro-core-1.2.3.jar (with props) Modified: ofbiz/trunk/LICENSE ofbiz/trunk/build.xml ofbiz/trunk/framework/base/src/org/ofbiz/base/crypto/DesCrypt.java ofbiz/trunk/framework/base/src/org/ofbiz/base/crypto/Main.java ofbiz/trunk/framework/entity/src/org/ofbiz/entity/Delegator.java ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericDelegator.java ofbiz/trunk/framework/entity/src/org/ofbiz/entity/jdbc/SqlJdbcUtil.java ofbiz/trunk/framework/entity/src/org/ofbiz/entity/test/EntityCryptoTestSuite.java ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityCrypto.java ofbiz/trunk/framework/entityext/build.xml ofbiz/trunk/framework/entityext/servicedef/services.xml ofbiz/trunk/framework/entityext/src/org/ofbiz/entityext/data/EntityDataServices.java Modified: ofbiz/trunk/LICENSE URL: http://svn.apache.org/viewvc/ofbiz/trunk/LICENSE?rev=1684608&r1=1684607&r2=1684608&view=diff ============================================================================== --- ofbiz/trunk/LICENSE (original) +++ ofbiz/trunk/LICENSE Wed Jun 10 09:01:30 2015 @@ -38,6 +38,7 @@ framework/base/lib/log4j-slf4j-impl-2.3. framework/base/lib/nekohtml-1.9.16.jar framework/base/lib/resolver-2.9.1.jar framework/base/lib/serializer-2.9.1.jar +framework/base/lib/shiro-core-1.2.3.jar framework/base/lib/ws-commons-java5-1.0.1.jar framework/base/lib/ws-commons-util-1.0.2.jar framework/base/lib/xercesImpl-2.9.1.jar Modified: ofbiz/trunk/build.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/build.xml?rev=1684608&r1=1684607&r2=1684608&view=diff ============================================================================== --- ofbiz/trunk/build.xml (original) +++ ofbiz/trunk/build.xml Wed Jun 10 09:01:30 2015 @@ -1536,6 +1536,8 @@ under the License. <classpath> <path location="framework/base/build/lib/ofbiz-base.jar"/> <path location="framework/base/lib/commons/commons-codec-1.10.jar"/> + <path location="framework/base/lib/shiro-core-1.2.3.jar"/> + <path location="framework/base/lib/slf4j-api-1.6.4.jar"/> </classpath> </java> </target> Added: ofbiz/trunk/framework/base/lib/shiro-core-1.2.3.jar URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/lib/shiro-core-1.2.3.jar?rev=1684608&view=auto ============================================================================== Binary file - no diff available. Propchange: ofbiz/trunk/framework/base/lib/shiro-core-1.2.3.jar ------------------------------------------------------------------------------ svn:mime-type = application/octet-stream Modified: ofbiz/trunk/framework/base/src/org/ofbiz/base/crypto/DesCrypt.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/src/org/ofbiz/base/crypto/DesCrypt.java?rev=1684608&r1=1684607&r2=1684608&view=diff ============================================================================== --- ofbiz/trunk/framework/base/src/org/ofbiz/base/crypto/DesCrypt.java (original) +++ ofbiz/trunk/framework/base/src/org/ofbiz/base/crypto/DesCrypt.java Wed Jun 10 09:01:30 2015 @@ -21,11 +21,11 @@ package org.ofbiz.base.crypto; import java.security.NoSuchAlgorithmException; import java.security.InvalidKeyException; import java.security.InvalidAlgorithmParameterException; +import java.security.Key; import java.security.spec.InvalidKeySpecException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.BadPaddingException; -import javax.crypto.SecretKey; import javax.crypto.NoSuchPaddingException; import javax.crypto.KeyGenerator; import javax.crypto.SecretKeyFactory; @@ -42,14 +42,14 @@ public class DesCrypt { public static final String module = DesCrypt.class.getName(); - public static SecretKey generateKey() throws NoSuchAlgorithmException { + public static Key generateKey() throws NoSuchAlgorithmException { KeyGenerator keyGen = KeyGenerator.getInstance("DESede"); // generate the DES3 key return keyGen.generateKey(); } - public static byte[] encrypt(SecretKey key, byte[] bytes) throws GeneralException { + public static byte[] encrypt(Key key, byte[] bytes) throws GeneralException { Cipher cipher = DesCrypt.getCipher(key, Cipher.ENCRYPT_MODE); byte[] encBytes = null; try { @@ -64,7 +64,7 @@ public class DesCrypt { return encBytes; } - public static byte[] decrypt(SecretKey key, byte[] bytes) throws GeneralException { + public static byte[] decrypt(Key key, byte[] bytes) throws GeneralException { Cipher cipher = DesCrypt.getCipher(key, Cipher.DECRYPT_MODE); byte[] decBytes = null; try { @@ -79,7 +79,7 @@ public class DesCrypt { return decBytes; } - public static SecretKey getDesKey(byte[] rawKey) throws GeneralException { + public static Key getDesKey(byte[] rawKey) throws GeneralException { SecretKeyFactory skf = null; try { skf = SecretKeyFactory.getInstance("DESede"); @@ -97,7 +97,7 @@ public class DesCrypt { } // create the SecretKey Object - SecretKey key = null; + Key key = null; try { key = skf.generateSecret(desedeSpec1); } catch (InvalidKeySpecException e) { @@ -110,7 +110,7 @@ public class DesCrypt { } // return a cipher for a key - DESede/CBC/PKCS5Padding IV = 0 - protected static Cipher getCipher(SecretKey key, int mode) throws GeneralException { + protected static Cipher getCipher(Key key, int mode) throws GeneralException { byte[] zeros = { 0, 0, 0, 0, 0, 0, 0, 0 }; IvParameterSpec iv = new IvParameterSpec(zeros); Modified: ofbiz/trunk/framework/base/src/org/ofbiz/base/crypto/Main.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/src/org/ofbiz/base/crypto/Main.java?rev=1684608&r1=1684607&r2=1684608&view=diff ============================================================================== --- ofbiz/trunk/framework/base/src/org/ofbiz/base/crypto/Main.java (original) +++ ofbiz/trunk/framework/base/src/org/ofbiz/base/crypto/Main.java Wed Jun 10 09:01:30 2015 @@ -19,6 +19,7 @@ package org.ofbiz.base.crypto; import org.apache.commons.codec.binary.Base64; +import org.apache.shiro.crypto.AesCipherService; public class Main { public static void main(String[] args) throws Exception { @@ -29,6 +30,9 @@ public class Main { String digest = HashCrypt.getDigestHash(args[1]); System.out.println(digest); } else if (args[0].equals("-kek")) { + AesCipherService cs = new AesCipherService(); + System.out.println(Base64.encodeBase64String(cs.generateNewKey().getEncoded())); + } else if (args[0].equals("-kek-old")) { System.out.println(Base64.encodeBase64String(DesCrypt.generateKey().getEncoded())); } } Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/Delegator.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/Delegator.java?rev=1684608&r1=1684607&r2=1684608&view=diff ============================================================================== --- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/Delegator.java (original) +++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/Delegator.java Wed Jun 10 09:01:30 2015 @@ -269,8 +269,11 @@ public interface Delegator { @Deprecated void encryptFields(List<? extends GenericEntity> entities) throws GenericEntityException; + @Deprecated Object decryptFieldValue(String entityName, String encValue) throws EntityCryptoException; + Object decryptFieldValue(String entityName, ModelField.EncryptMethod encryptMethod, String encValue) throws EntityCryptoException; + @Deprecated Object encryptFieldValue(String entityName, Object fieldValue) throws EntityCryptoException; Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericDelegator.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericDelegator.java?rev=1684608&r1=1684607&r2=1684608&view=diff ============================================================================== --- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericDelegator.java (original) +++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericDelegator.java Wed Jun 10 09:01:30 2015 @@ -2070,6 +2070,9 @@ public class GenericDelegator implements if (dcc != null) { dcc.clearAllCaches(); } + if (this.crypto != null) { + this.crypto.clearKeyCache(); + } } /* (non-Javadoc) @@ -2677,13 +2680,22 @@ public class GenericDelegator implements return fieldValue; } + @Override + @Deprecated + public Object decryptFieldValue(String entityName, String encValue) throws EntityCryptoException { + if (UtilValidate.isNotEmpty(encValue)) { + return this.crypto.decrypt(entityName, ModelField.EncryptMethod.TRUE, encValue); + } + return null; + } + /* (non-Javadoc) * @see org.ofbiz.entity.Delegator#encryptFieldValue(java.lang.String, java.lang.Object) */ @Override - public Object decryptFieldValue(String entityName, String encValue) throws EntityCryptoException { + public Object decryptFieldValue(String entityName, ModelField.EncryptMethod encryptMethod, String encValue) throws EntityCryptoException { if (UtilValidate.isNotEmpty(encValue)) { - return this.crypto.decrypt(entityName, encValue); + return this.crypto.decrypt(entityName, encryptMethod, encValue); } return null; } Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/jdbc/SqlJdbcUtil.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/jdbc/SqlJdbcUtil.java?rev=1684608&r1=1684607&r2=1684608&view=diff ============================================================================== --- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/jdbc/SqlJdbcUtil.java (original) +++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/jdbc/SqlJdbcUtil.java Wed Jun 10 09:01:30 2015 @@ -540,7 +540,7 @@ public class SqlJdbcUtil { try { Object jdbcValue = handler.getValue(rs, ind); if (jdbcValue instanceof String && curField.getEncryptMethod().isEncrypted()) { - jdbcValue = entity.getDelegator().decryptFieldValue(encryptionKeyName, (String) jdbcValue); + jdbcValue = entity.getDelegator().decryptFieldValue(encryptionKeyName, curField.getEncryptMethod(), (String) jdbcValue); } entity.dangerousSetNoCheckButFast(curField, jdbcValue); return; @@ -597,7 +597,7 @@ public class SqlJdbcUtil { } else { String value = rs.getString(ind); if (value instanceof String && curField.getEncryptMethod().isEncrypted()) { - value = (String) entity.getDelegator().decryptFieldValue(encryptionKeyName, value); + value = (String) entity.getDelegator().decryptFieldValue(encryptionKeyName, curField.getEncryptMethod(), value); } entity.dangerousSetNoCheckButFast(curField, value); } Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/test/EntityCryptoTestSuite.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/test/EntityCryptoTestSuite.java?rev=1684608&r1=1684607&r2=1684608&view=diff ============================================================================== --- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/test/EntityCryptoTestSuite.java (original) +++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/test/EntityCryptoTestSuite.java Wed Jun 10 09:01:30 2015 @@ -33,6 +33,26 @@ public class EntityCryptoTestSuite exten super(name); } + public void testCrypto() throws Exception { + String nanoTime = "" + System.nanoTime(); + delegator.removeByAnd("TestingCrypto", UtilMisc.toMap("testingCryptoTypeId", "BASIC")); + delegator.create("TestingCrypto", UtilMisc.toMap("testingCryptoId", "1", "testingCryptoTypeId", "BASIC")); + GenericValue entity = EntityQuery.use(delegator).from("TestingCrypto").where("testingCryptoId", "1").queryOne(); + assertNull(entity.getString("unencryptedValue")); + assertNull(entity.getString("encryptedValue")); + entity.setString("unencryptedValue", nanoTime); + entity.setString("encryptedValue", nanoTime); + entity.setString("saltedEncryptedValue", nanoTime); + assertEquals(nanoTime, entity.getString("unencryptedValue")); + assertEquals(nanoTime, entity.getString("encryptedValue")); + assertEquals(nanoTime, entity.getString("saltedEncryptedValue")); + entity.store(); + entity.refresh(); + assertEquals(nanoTime, entity.getString("unencryptedValue")); + assertEquals(nanoTime, entity.getString("encryptedValue")); + assertEquals(nanoTime, entity.getString("saltedEncryptedValue")); + } + public void testCryptoEncryption() throws Exception { // clear out all values delegator.removeByAnd("TestingCrypto", UtilMisc.toMap("testingCryptoTypeId", "BASIC")); Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityCrypto.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityCrypto.java?rev=1684608&r1=1684607&r2=1684608&view=diff ============================================================================== --- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityCrypto.java (original) +++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/EntityCrypto.java Wed Jun 10 09:01:30 2015 @@ -25,9 +25,14 @@ import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import javax.crypto.SecretKey; +import java.security.Key; import org.apache.commons.codec.binary.Base64; +import org.apache.shiro.crypto.AesCipherService; +import org.apache.shiro.crypto.OperationMode; +import org.apache.shiro.crypto.hash.HashRequest; +import org.apache.shiro.crypto.hash.HashService; +import org.apache.shiro.crypto.hash.DefaultHashService; import org.ofbiz.base.crypto.DesCrypt; import org.ofbiz.base.crypto.HashCrypt; import org.ofbiz.base.util.Debug; @@ -47,24 +52,25 @@ public final class EntityCrypto { public static final String module = EntityCrypto.class.getName(); protected final Delegator delegator; - protected final ConcurrentMap<String, SecretKey> keyMap = new ConcurrentHashMap<String, SecretKey>(); + protected final ConcurrentMap<String, byte[]> keyMap = new ConcurrentHashMap<String, byte[]>(); protected final StorageHandler[] handlers; public EntityCrypto(Delegator delegator, String kekText) throws EntityCryptoException { this.delegator = delegator; - SecretKey kek; - try { - kek = UtilValidate.isNotEmpty(kekText) ? DesCrypt.getDesKey(Base64.decodeBase64(kekText)) : null; - } catch (GeneralException e) { - throw new EntityCryptoException(e); - } + byte[] kek; + kek = UtilValidate.isNotEmpty(kekText) ? Base64.decodeBase64(kekText) : null; handlers = new StorageHandler[] { + new ShiroStorageHandler(kek), new SaltedBase64StorageHandler(kek), NormalHashStorageHandler, OldFunnyHashStorageHandler, }; } + public void clearKeyCache() { + keyMap.clear(); + } + /** Encrypts an Object into an encrypted hex encoded String */ @Deprecated public String encrypt(String keyName, Object obj) throws EntityCryptoException { @@ -74,11 +80,11 @@ public final class EntityCrypto { /** Encrypts an Object into an encrypted hex encoded String */ public String encrypt(String keyName, EncryptMethod encryptMethod, Object obj) throws EntityCryptoException { try { - SecretKey key = this.findKey(keyName, handlers[0]); + byte[] key = this.findKey(keyName, handlers[0]); if (key == null) { EntityCryptoException caught = null; try { - this.createKey(keyName, handlers[0]); + this.createKey(keyName, handlers[0], encryptMethod); } catch (EntityCryptoException e) { // either a database read error, or a duplicate key insert // if the latter, try to fetch the value created by the @@ -89,7 +95,7 @@ public final class EntityCrypto { key = this.findKey(keyName, handlers[0]); } catch (EntityCryptoException e) { // this is bad, couldn't lookup the value, some bad juju - // is occuring; rethrow the original exception if available + // is occurring; rethrow the original exception if available throw caught != null ? caught : e; } if (key == null) { @@ -115,15 +121,15 @@ public final class EntityCrypto { */ /** Decrypts a hex encoded String into an Object */ - public Object decrypt(String keyName, String encryptedString) throws EntityCryptoException { + public Object decrypt(String keyName, EncryptMethod encryptMethod, String encryptedString) throws EntityCryptoException { try { - return doDecrypt(keyName, encryptedString, handlers[0]); + return doDecrypt(keyName, encryptMethod, encryptedString, handlers[0]); } catch (GeneralException e) { Debug.logInfo("Decrypt with DES key from standard key name hash failed, trying old/funny variety of key name hash", module); for (int i = 1; i < handlers.length; i++) { try { // try using the old/bad hex encoding approach; this is another path the code may take, ie if there is an exception thrown in decrypt - return doDecrypt(keyName, encryptedString, handlers[i]); + return doDecrypt(keyName, encryptMethod, encryptedString, handlers[i]); } catch (GeneralException e1) { // NOTE: this throws the original exception back, not the new one if it fails using the other approach //throw new EntityCryptoException(e); @@ -133,12 +139,12 @@ public final class EntityCrypto { } } - protected Object doDecrypt(String keyName, String encryptedString, StorageHandler handler) throws GeneralException { - SecretKey key = this.findKey(keyName, handler); + protected Object doDecrypt(String keyName, EncryptMethod encryptMethod, String encryptedString, StorageHandler handler) throws GeneralException { + byte[] key = this.findKey(keyName, handler); if (key == null) { throw new EntityCryptoException("key(" + keyName + ") not found in database"); } - byte[] decryptedBytes = handler.decryptValue(key, encryptedString); + byte[] decryptedBytes = handler.decryptValue(key, encryptMethod, encryptedString); try { return UtilObject.getObjectException(decryptedBytes); } catch (ClassNotFoundException e) { @@ -148,7 +154,7 @@ public final class EntityCrypto { } } - protected SecretKey findKey(String originalKeyName, StorageHandler handler) throws EntityCryptoException { + protected byte[] findKey(String originalKeyName, StorageHandler handler) throws EntityCryptoException { String hashedKeyName = handler.getHashedKeyName(originalKeyName); String keyMapName = handler.getKeyMapPrefix(hashedKeyName) + hashedKeyName; if (keyMap.containsKey(keyMapName)) { @@ -170,8 +176,7 @@ public final class EntityCrypto { } try { byte[] keyBytes = handler.decodeKeyBytes(keyValue.getString("keyText")); - SecretKey key = DesCrypt.getDesKey(keyBytes); - keyMap.putIfAbsent(keyMapName, key); + keyMap.putIfAbsent(keyMapName, keyBytes); // Do not remove the next line, it's there to handle the // case of multiple threads trying to find the same key // both threads will do the findOne call, only one will @@ -183,17 +188,12 @@ public final class EntityCrypto { } } - protected void createKey(String originalKeyName, StorageHandler handler) throws EntityCryptoException { + protected void createKey(String originalKeyName, StorageHandler handler, EncryptMethod encryptMethod) throws EntityCryptoException { String hashedKeyName = handler.getHashedKeyName(originalKeyName); - SecretKey key = null; - try { - key = DesCrypt.generateKey(); - } catch (NoSuchAlgorithmException e) { - throw new EntityCryptoException(e); - } + Key key = handler.generateNewKey(); final GenericValue newValue = delegator.makeValue("EntityKeyStore"); try { - newValue.set("keyText", handler.encodeKey(key)); + newValue.set("keyText", handler.encodeKey(key.getEncoded())); } catch (GeneralException e) { throw new EntityCryptoException(e); } @@ -212,35 +212,115 @@ public final class EntityCrypto { } protected abstract static class StorageHandler { + protected abstract Key generateNewKey() throws EntityCryptoException; + protected abstract String getHashedKeyName(String originalKeyName); protected abstract String getKeyMapPrefix(String hashedKeyName); protected abstract byte[] decodeKeyBytes(String keyText) throws GeneralException; - protected abstract String encodeKey(SecretKey key) throws GeneralException; + protected abstract String encodeKey(byte[] key) throws GeneralException; + + protected abstract byte[] decryptValue(byte[] key, EncryptMethod encryptMethod, String encryptedString) throws GeneralException; + protected abstract String encryptValue(EncryptMethod encryptMethod, byte[] key, byte[] objBytes) throws GeneralException; + } + + protected static final class ShiroStorageHandler extends StorageHandler { + private final HashService hashService; + private final AesCipherService cipherService; + private final AesCipherService saltedCipherService; + private final byte[] kek; + + protected ShiroStorageHandler(byte[] kek) { + hashService = new DefaultHashService(); + cipherService = new AesCipherService(); + cipherService.setMode(OperationMode.ECB); + saltedCipherService = new AesCipherService(); + this.kek = kek; + } + + @Override + protected Key generateNewKey() { + return saltedCipherService.generateNewKey(); + } + + @Override + protected String getHashedKeyName(String originalKeyName) { + HashRequest hashRequest = new HashRequest.Builder().setSource(originalKeyName).build(); + return hashService.computeHash(hashRequest).toBase64(); + } + + @Override + protected String getKeyMapPrefix(String hashedKeyName) { + return "{shiro}"; + } + + @Override + protected byte[] decodeKeyBytes(String keyText) throws GeneralException { + byte[] keyBytes = Base64.decodeBase64(keyText); + if (kek != null) { + keyBytes = saltedCipherService.decrypt(keyBytes, kek).getBytes(); + } + return keyBytes; + } - protected abstract byte[] decryptValue(SecretKey key, String encryptedString) throws GeneralException; - protected abstract String encryptValue(EncryptMethod encryptMethod, SecretKey key, byte[] objBytes) throws GeneralException; + @Override + protected String encodeKey(byte[] key) throws GeneralException { + if (kek != null) { + return saltedCipherService.encrypt(key, kek).toBase64(); + } else { + return Base64.encodeBase64String(key); + } + } + + @Override + protected byte[] decryptValue(byte[] key, EncryptMethod encryptMethod, String encryptedString) throws GeneralException { + switch (encryptMethod) { + case SALT: + return saltedCipherService.decrypt(Base64.decodeBase64(encryptedString), key).getBytes(); + default: + return cipherService.decrypt(Base64.decodeBase64(encryptedString), key).getBytes(); + } + } + + @Override + protected String encryptValue(EncryptMethod encryptMethod, byte[] key, byte[] objBytes) throws GeneralException { + switch (encryptMethod) { + case SALT: + return saltedCipherService.encrypt(objBytes, key).toBase64(); + default: + return cipherService.encrypt(objBytes, key).toBase64(); + } + } } protected static abstract class LegacyStorageHandler extends StorageHandler { @Override + protected Key generateNewKey() throws EntityCryptoException { + try { + return DesCrypt.generateKey(); + } catch (NoSuchAlgorithmException e) { + throw new EntityCryptoException(e); + } + } + + @Override protected byte[] decodeKeyBytes(String keyText) throws GeneralException { return StringUtil.fromHexString(keyText); } @Override - protected String encodeKey(SecretKey key) { - return StringUtil.toHexString(key.getEncoded()); + protected String encodeKey(byte[] key) { + return StringUtil.toHexString(key); } @Override - protected byte[] decryptValue(SecretKey key, String encryptedString) throws GeneralException { - return DesCrypt.decrypt(key, StringUtil.fromHexString(encryptedString)); + protected byte[] decryptValue(byte[] key, EncryptMethod encryptMethod, String encryptedString) throws GeneralException { + return DesCrypt.decrypt(DesCrypt.getDesKey(key), StringUtil.fromHexString(encryptedString)); } @Override - protected String encryptValue(EncryptMethod encryptMethod, SecretKey key, byte[] objBytes) throws GeneralException { - return StringUtil.toHexString(DesCrypt.encrypt(key, objBytes)); + protected String encryptValue(EncryptMethod encryptMethod, byte[] key, byte[] objBytes) throws GeneralException { + return StringUtil.toHexString(DesCrypt.encrypt(DesCrypt.getDesKey(key), objBytes)); } }; @@ -269,10 +349,27 @@ public final class EntityCrypto { }; protected static final class SaltedBase64StorageHandler extends StorageHandler { - private final SecretKey kek; + private final Key kek; - protected SaltedBase64StorageHandler(SecretKey kek) { - this.kek = kek; + protected SaltedBase64StorageHandler(byte[] kek) throws EntityCryptoException { + Key key = null; + if (kek != null) { + try { + key = DesCrypt.getDesKey(kek); + } catch (GeneralException e) { + Debug.logInfo("Invalid key-encryption-key specified for SaltedBase64StorageHandler; the key is probably valid for the newer ShiroStorageHandler", module); + } + } + this.kek = key; + } + + @Override + protected Key generateNewKey() throws EntityCryptoException { + try { + return DesCrypt.generateKey(); + } catch (NoSuchAlgorithmException e) { + throw new EntityCryptoException(e); + } } @Override @@ -295,17 +392,16 @@ public final class EntityCrypto { } @Override - protected String encodeKey(SecretKey key) throws GeneralException { - byte[] keyBytes = key.getEncoded(); + protected String encodeKey(byte[] key) throws GeneralException { if (kek != null) { - keyBytes = DesCrypt.encrypt(kek, keyBytes); + key = DesCrypt.encrypt(kek, key); } - return Base64.encodeBase64String(keyBytes); + return Base64.encodeBase64String(key); } @Override - protected byte[] decryptValue(SecretKey key, String encryptedString) throws GeneralException { - byte[] allBytes = DesCrypt.decrypt(key, Base64.decodeBase64(encryptedString)); + protected byte[] decryptValue(byte[] key, EncryptMethod encryptMethod, String encryptedString) throws GeneralException { + byte[] allBytes = DesCrypt.decrypt(DesCrypt.getDesKey(key), Base64.decodeBase64(encryptedString)); int length = allBytes[0]; byte[] objBytes = new byte[allBytes.length - 1 - length]; System.arraycopy(allBytes, 1 + length, objBytes, 0, objBytes.length); @@ -313,7 +409,7 @@ public final class EntityCrypto { } @Override - protected String encryptValue(EncryptMethod encryptMethod, SecretKey key, byte[] objBytes) throws GeneralException { + protected String encryptValue(EncryptMethod encryptMethod, byte[] key, byte[] objBytes) throws GeneralException { byte[] saltBytes; switch (encryptMethod) { case SALT: @@ -330,7 +426,7 @@ public final class EntityCrypto { allBytes[0] = (byte) saltBytes.length; System.arraycopy(saltBytes, 0, allBytes, 1, saltBytes.length); System.arraycopy(objBytes, 0, allBytes, 1 + saltBytes.length, objBytes.length); - String result = Base64.encodeBase64String(DesCrypt.encrypt(key, allBytes)); + String result = Base64.encodeBase64String(DesCrypt.encrypt(DesCrypt.getDesKey(key), allBytes)); return result; } }; Modified: ofbiz/trunk/framework/entityext/build.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entityext/build.xml?rev=1684608&r1=1684607&r2=1684608&view=diff ============================================================================== --- ofbiz/trunk/framework/entityext/build.xml (original) +++ ofbiz/trunk/framework/entityext/build.xml Wed Jun 10 09:01:30 2015 @@ -31,6 +31,7 @@ under the License. <path id="local.class.path"> <fileset dir="../base/lib" includes="*.jar"/> + <fileset dir="../base/lib/commons" includes="*.jar"/> <fileset dir="../base/lib/j2eespecs" includes="*.jar"/> <fileset dir="../base/build/lib" includes="*.jar"/> <fileset dir="../entity/lib" includes="*.jar"/> Modified: ofbiz/trunk/framework/entityext/servicedef/services.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entityext/servicedef/services.xml?rev=1684608&r1=1684607&r2=1684608&view=diff ============================================================================== --- ofbiz/trunk/framework/entityext/servicedef/services.xml (original) +++ ofbiz/trunk/framework/entityext/servicedef/services.xml Wed Jun 10 09:01:30 2015 @@ -153,6 +153,19 @@ under the License. <attribute name="fieldName" type="String" mode="IN" optional="false"/> </service> + <service name="reencryptPrivateKeys" engine="java" auth="true" transaction-timeout="14400" + location="org.ofbiz.entityext.data.EntityDataServices" invoke="reencryptPrivateKeys"> + <description>Re-encrypt the private keys, encrypted in EntityKeyStore with oldKey, using the newKey.</description> + <attribute name="oldKey" type="String" mode="IN" optional="true"/> + <attribute name="newKey" type="String" mode="IN" optional="true"/> + </service> + + <service name="reencryptFields" engine="java" auth="true" transaction-timeout="14400" + location="org.ofbiz.entityext.data.EntityDataServices" invoke="reencryptFields"> + <description>Re-encrypt all the encrypted fields in the data model.</description> + <attribute name="groupName" type="String" mode="IN" optional="true" default-value="org.ofbiz"/> + </service> + <!-- EntitySync Services --> <service name="createEntitySync" default-entity-name="EntitySync" engine="simple" location="component://entityext/script/org/ofbiz/entityext/synchronization/EntitySyncServices.xml" invoke="createEntitySync" auth="true"> Modified: ofbiz/trunk/framework/entityext/src/org/ofbiz/entityext/data/EntityDataServices.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entityext/src/org/ofbiz/entityext/data/EntityDataServices.java?rev=1684608&r1=1684607&r2=1684608&view=diff ============================================================================== --- ofbiz/trunk/framework/entityext/src/org/ofbiz/entityext/data/EntityDataServices.java (original) +++ ofbiz/trunk/framework/entityext/src/org/ofbiz/entityext/data/EntityDataServices.java Wed Jun 10 09:01:30 2015 @@ -31,6 +31,9 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import org.apache.commons.codec.binary.Base64; +import org.apache.shiro.crypto.AesCipherService; +import org.ofbiz.base.crypto.DesCrypt; import org.ofbiz.base.util.Debug; import org.ofbiz.base.util.FileUtil; import org.ofbiz.base.util.GeneralException; @@ -44,6 +47,7 @@ import org.ofbiz.entity.GenericValue; import org.ofbiz.entity.datasource.GenericHelperInfo; import org.ofbiz.entity.jdbc.DatabaseUtil; import org.ofbiz.entity.model.ModelEntity; +import org.ofbiz.entity.model.ModelField; import org.ofbiz.entity.util.EntityListIterator; import org.ofbiz.entity.util.EntityQuery; import org.ofbiz.security.Security; @@ -445,4 +449,95 @@ public class EntityDataServices { return ServiceUtil.returnSuccess(); } + + public static Map<String, Object> reencryptPrivateKeys(DispatchContext dctx, Map<String, Object> context) { + Delegator delegator = dctx.getDelegator(); + Security security = dctx.getSecurity(); + Locale locale = (Locale) context.get("locale"); + + // check permission + GenericValue userLogin = (GenericValue) context.get("userLogin"); + if (!security.hasPermission("ENTITY_MAINT", userLogin)) { + return ServiceUtil.returnError(UtilProperties.getMessage(resource, "EntityExtServicePermissionNotGranted", locale)); + } + String oldKey = (String) context.get("oldKey"); + String newKey = (String) context.get("newKey"); + AesCipherService cipherService = new AesCipherService(); + try { + List<GenericValue> rows = EntityQuery.use(delegator).from("EntityKeyStore").queryList(); + for (GenericValue row: rows) { + byte[] keyBytes = Base64.decodeBase64(row.getString("keyText")); + Debug.logInfo("Processing entry " + row.getString("keyName") + " with key: " + row.getString("keyText"), module); + if (oldKey != null) { + Debug.logInfo("Decrypting with old key: " + oldKey, module); + try { + keyBytes = cipherService.decrypt(keyBytes, Base64.decodeBase64(oldKey)).getBytes(); + } catch(Exception e) { + Debug.logInfo("Failed to decrypt with Shiro cipher; trying with old cipher", module); + try { + keyBytes = DesCrypt.decrypt(DesCrypt.getDesKey(Base64.decodeBase64(oldKey)), keyBytes); + } catch(Exception e1) { + Debug.logError(e1, module); + return ServiceUtil.returnError(e1.getMessage()); + } + } + } + String newKeyText; + if (newKey != null) { + Debug.logInfo("Encrypting with new key: " + oldKey, module); + newKeyText = cipherService.encrypt(keyBytes, Base64.decodeBase64(newKey)).toBase64(); + } else { + newKeyText = Base64.encodeBase64String(keyBytes); + } + Debug.logInfo("Storing new encrypted value: " + newKeyText, module); + row.setString("keyText", newKeyText); + row.store(); + } + } catch(GenericEntityException gee) { + Debug.logError(gee, module); + return ServiceUtil.returnError(gee.getMessage()); + } + delegator.clearAllCaches(); + return ServiceUtil.returnSuccess(); + } + + public static Map<String, Object> reencryptFields(DispatchContext dctx, Map<String, Object> context) { + Delegator delegator = dctx.getDelegator(); + Security security = dctx.getSecurity(); + Locale locale = (Locale) context.get("locale"); + + // check permission + GenericValue userLogin = (GenericValue) context.get("userLogin"); + if (!security.hasPermission("ENTITY_MAINT", userLogin)) { + return ServiceUtil.returnError(UtilProperties.getMessage(resource, "EntityExtServicePermissionNotGranted", locale)); + } + + String groupName = (String) context.get("groupName"); + + Map<String, ModelEntity> modelEntities; + try { + modelEntities = delegator.getModelEntityMapByGroup(groupName); + } catch (GenericEntityException e) { + Debug.logError(e, "Error getting list of entities in group: " + e.toString(), module); + return ServiceUtil.returnError(UtilProperties.getMessage(resource, "EntityExtErrorGettingListOfEntityInGroup", UtilMisc.toMap("errorString", e.toString()), locale)); + } + + for (ModelEntity modelEntity: modelEntities.values()) { + List<ModelField> fields = modelEntity.getFieldsUnmodifiable(); + for (ModelField field: fields) { + if (field.getEncryptMethod().isEncrypted()) { + try { + List<GenericValue> rows = EntityQuery.use(delegator).from(modelEntity.getEntityName()).select(field.getName()).queryList(); + for (GenericValue row: rows) { + row.setString(field.getName(), row.getString(field.getName())); + row.store(); + } + } catch(GenericEntityException gee) { + return ServiceUtil.returnError(gee.getMessage()); + } + } + } + } + return ServiceUtil.returnSuccess(); + } }