From 4caf14b6a977ed6e7eca644a75f38403fc150533 Mon Sep 17 00:00:00 2001 From: Ahmad Vazirna Date: Fri, 12 Jan 2024 22:55:29 +0200 Subject: [PATCH] Refactor getKey to IEncryptionKeyProvider --- .../util/BasicEncryptionKeyProvider.java | 65 +++++- .../org/commcare/util/EncryptionHelper.java | 198 ++++-------------- .../commcare/util/IEncryptionKeyProvider.java | 22 +- .../java/org/javarosa/core/model/User.java | 33 ++- .../xpath/expr/XPathDecryptStringFunc.java | 2 +- .../xpath/expr/XPathEncryptStringFunc.java | 2 +- 6 files changed, 126 insertions(+), 196 deletions(-) diff --git a/src/main/java/org/commcare/util/BasicEncryptionKeyProvider.java b/src/main/java/org/commcare/util/BasicEncryptionKeyProvider.java index 7af63c78c..80957c3c9 100644 --- a/src/main/java/org/commcare/util/BasicEncryptionKeyProvider.java +++ b/src/main/java/org/commcare/util/BasicEncryptionKeyProvider.java @@ -1,14 +1,73 @@ package org.commcare.util; +import org.commcare.core.encryption.CryptUtil; + +import java.security.Key; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import javax.crypto.spec.SecretKeySpec; + import static org.commcare.util.EncryptionHelper.CC_KEY_ALGORITHM_AES; import static org.commcare.util.EncryptionHelper.CC_KEY_ALGORITHM_RSA; public class BasicEncryptionKeyProvider implements IEncryptionKeyProvider { @Override - public EncryptionKeyAndTransformation retrieveKeyFromKeyStore(String keyAlias, - EncryptionHelper.CryptographicOperation operation) { - return null; + public EncryptionKeyAndTransformation getKey(String algorithm, String base64encodedKey, + EncryptionHelper.CryptographicOperation cryptographicOperation) + throws EncryptionException { + byte[] keyBytes; + try { + keyBytes = Base64.decode(base64encodedKey); + } catch (Base64DecoderException e) { + throw new EncryptionException("Encryption key base 64 encoding is invalid", e); + } + Key key = null; + if (algorithm.equals(getAESKeyAlgorithmRepresentation())) { + final int AES_KEY_LENGTH_BIT = 256; + + if (8 * keyBytes.length != AES_KEY_LENGTH_BIT) { + throw new EncryptionException("Key should be " + AES_KEY_LENGTH_BIT + + " bits long, not " + 8 * keyBytes.length); + } + key = new SecretKeySpec(keyBytes, getAESKeyAlgorithmRepresentation()); + } else if (algorithm.equals(getRSAKeyAlgorithmRepresentation())) { + // RSA is only used for Android 5.0 - 5.1.1 + + if (8 * keyBytes.length < CryptUtil.RSA_MIN_KEY_LENGTH_BIT) { + throw new EncryptionException("Key should be " + CryptUtil.RSA_MIN_KEY_LENGTH_BIT + + " bits long, not " + keyBytes.length); + } + KeyFactory keyFactory; + try { + keyFactory = KeyFactory.getInstance(getRSAKeyAlgorithmRepresentation()); + } catch (NoSuchAlgorithmException e) { + throw new EncryptionException("There is no Provider for the RSA algorithm", e); + } + + try { + if (cryptographicOperation == EncryptionHelper.CryptographicOperation.Encryption) { + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + key = keyFactory.generatePublic(keySpec); + } else { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + key = keyFactory.generatePrivate(keySpec); + } + } catch (InvalidKeySpecException e) { + throw new EncryptionException("Invalid key Spec: "+ e.getMessage()); + } + } + + if (key == null) { + throw new EncryptionException("Encryption key conversion failed"); + } else { + return new EncryptionKeyAndTransformation(key, + getTransformationString(key.getAlgorithm())); + } } @Override diff --git a/src/main/java/org/commcare/util/EncryptionHelper.java b/src/main/java/org/commcare/util/EncryptionHelper.java index 9728506d0..f490984f6 100644 --- a/src/main/java/org/commcare/util/EncryptionHelper.java +++ b/src/main/java/org/commcare/util/EncryptionHelper.java @@ -1,28 +1,17 @@ package org.commcare.util; -import org.commcare.core.encryption.CryptUtil; -import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; -import java.security.Key; -import java.security.KeyFactory; -import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableEntryException; -import java.security.cert.CertificateException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.GCMParameterSpec; -import javax.crypto.spec.SecretKeySpec; public class EncryptionHelper { @@ -48,62 +37,37 @@ public void reloadEncryptionKeyProvider() { encryptionKeyProvider = EncryptionKeyServiceProvider.getInstance().serviceImpl(); } - /** - * Encrypts a message using a key stored in the platform KeyStore. The key is retrieved using - * its alias which is established during key generation. - * - * @param message a UTF-8 encoded message to be encrypted - * @param keyAlias key alias of the Key stored in the KeyStore, depending on the algorithm, - * it can be a SecretKey (for AES) or PublicKey (for RSA) to be used to - * encrypt the message - * @return A base64 encoded payload containing the IV and AES or RSA encrypted ciphertext, - * which can be decoded by this utility's decrypt method and the same key - * @throws KeyStoreException if the keystore has not been initialized - * @throws NoSuchAlgorithmException if the appropriate data integrity algorithm could not be - * found - * @throws UnrecoverableEntryException if an entry in the keystore cannot be retrieved - */ - public String encryptWithKeyStore(String message, String keyAlias) - throws UnrecoverableEntryException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { - EncryptionKeyAndTransformation keyAndTransformation = encryptionKeyProvider.retrieveKeyFromKeyStore(keyAlias, CryptographicOperation.Encryption); - try { - return encrypt(message, keyAndTransformation); - } catch (EncryptionException e) { - throw new RuntimeException("Error encountered while encrypting the data: ", e); - } - } - public String encryptWithBase64EncodedKey(String algorithm, String message, String key) - throws EncryptionException { - EncryptionKeyAndTransformation keyAndTransformation; - try { - keyAndTransformation = getKey(algorithm, key, CryptographicOperation.Encryption); - } catch (InvalidKeySpecException e) { - throw new EncryptionException("Invalid Key specifications", e); - } - return encrypt(message, keyAndTransformation); - } + + + + + /** * Encrypts a message using the AES or RAS algorithms and produces a base64 encoded payload - * containing the ciphertext, and, when applicable, a random IV which was used to encrypt - * the input. + * containing the ciphertext, and when applicable, a random IV which was used to encrypt + * the message. * - * @param message a UTF-8 encoded message to be encrypted - * @param keyAndTransform depending on the algorithm, a SecretKey or PublicKey, and - * cryptographic transformation to be used to encrypt the message - * @return A base64 encoded payload containing the IV and AES or RSA encrypted ciphertext, - * which can be decoded by this utility's decrypt method and the same key + * @param algorithm the algorithm to be used to encrypt/decrypt. This is only relevant when + * not using a KeyStore + * @param message a UTF-8 encoded message to be encrypted + * @param keyOrAlias depending on the IEncryptionKeyProvider implementation this can be a + * SecretKey/PublicKey or an alias of a key stored in the platform Keystore + * @return A base64 encoded payload containing the IV and AES or RSA encrypted ciphertext, + * which can be decoded by this utility's decrypt method and the same key */ - private String encrypt(String message, EncryptionKeyAndTransformation keyAndTransform) + public String encrypt(String algorithm, String message, String keyOrAlias) throws EncryptionException { final int MIN_IV_LENGTH_BYTE = 1; final int MAX_IV_LENGTH_BYTE = 255; + EncryptionKeyAndTransformation keyAndTransformation = + encryptionKeyProvider.getKey(algorithm, keyOrAlias, CryptographicOperation.Encryption); try { - Cipher cipher = Cipher.getInstance(keyAndTransform.getTransformation()); - cipher.init(Cipher.ENCRYPT_MODE, keyAndTransform.getKey()); + Cipher cipher = Cipher.getInstance(keyAndTransformation.getTransformation()); + cipher.init(Cipher.ENCRYPT_MODE, keyAndTransformation.getKey()); byte[] encryptedMessage = cipher.doFinal(message.getBytes(Charset.forName("UTF-8"))); byte[] iv = cipher.getIV(); int ivSize = (iv == null ? 0 : iv.length); @@ -127,117 +91,25 @@ private String encrypt(String message, EncryptionKeyAndTransformation keyAndTran } } - /** - * Converts a Base64 encoded key into a SecretKey, PrivateKey or PublicKey, depending on the - * algorithm and cryptographic operation - * - * @param algorithm the algorithm to be used to encrypt/decrypt - * @param base64encodedKey key in String format - * @param cryptographicOperation Cryptographic operation where the key is to be used, relevant - * to the RSA algorithm - * @return Secret key, Public key or Private Key to be used - */ - private EncryptionKeyAndTransformation getKey(String algorithm, - String base64encodedKey, - EncryptionHelper.CryptographicOperation cryptographicOperation) - throws EncryptionException, InvalidKeySpecException { - byte[] keyBytes; - try { - keyBytes = Base64.decode(base64encodedKey); - } catch (Base64DecoderException e) { - throw new EncryptionException("Encryption key base 64 encoding is invalid", e); - } - Key key = null; - if (algorithm.equals(encryptionKeyProvider.getAESKeyAlgorithmRepresentation())) { - final int AES_KEY_LENGTH_BIT = 256; - - if (8 * keyBytes.length != AES_KEY_LENGTH_BIT) { - throw new EncryptionException("Key should be " + AES_KEY_LENGTH_BIT + - " bits long, not " + 8 * keyBytes.length); - } - key = new SecretKeySpec(keyBytes, encryptionKeyProvider.getAESKeyAlgorithmRepresentation()); - } else if (algorithm.equals(encryptionKeyProvider.getRSAKeyAlgorithmRepresentation())) { - // RSA is only used for Android 5.0 - 5.1.1 - - if (8 * keyBytes.length < CryptUtil.RSA_MIN_KEY_LENGTH_BIT) { - throw new EncryptionException("Key should be " + CryptUtil.RSA_MIN_KEY_LENGTH_BIT + - " bits long, not " + keyBytes.length); - } - KeyFactory keyFactory; - try { - keyFactory = KeyFactory.getInstance(encryptionKeyProvider.getRSAKeyAlgorithmRepresentation()); - } catch (NoSuchAlgorithmException e) { - throw new EncryptionException("There is no Provider for the RSA algorithm", e); - } - - if (cryptographicOperation == EncryptionHelper.CryptographicOperation.Encryption) { - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); - key = keyFactory.generatePublic(keySpec); - } else { - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); - key = keyFactory.generatePrivate(keySpec); - } - } - - if (key == null) { - throw new EncryptionException("Encryption key conversion failed"); - } else { - return new EncryptionKeyAndTransformation(key, - encryptionKeyProvider.getTransformationString(key.getAlgorithm())); - } - } - - /** - * Decrypts a base64 payload containing an IV and AES or RSA encrypted ciphertext using a key - * stored in the platform KeyStore. The key is retrieved using its alias which is established - * during key generation. - * - * @param message a UTF-8 encoded message to be decrypted - * @param keyAlias key alias of the Key stored in the KeyStore, depending on the algorithm, - * it can be a SecretKey (for AES) or PublicKey (for RSA) to be used to - * decrypt the message - * @return Decrypted message of the provided ciphertext, - * @throws KeyStoreException if the keystore has not been initialized - * @throws NoSuchAlgorithmException if the appropriate data integrity algorithm could not be - * found - * @throws UnrecoverableEntryException if an entry in the keystore cannot be retrieved - */ - public String decryptWithKeyStore(String message, String keyAlias) - throws UnrecoverableEntryException, KeyStoreException, NoSuchAlgorithmException, - CertificateException, IOException { - EncryptionKeyAndTransformation keyAndTransformation = - encryptionKeyProvider.retrieveKeyFromKeyStore(keyAlias, CryptographicOperation.Decryption); - try { - return decrypt(message, keyAndTransformation); - } catch (EncryptionException e) { - throw new RuntimeException("Error encountered while decrypting the data ", e); - } - } - - public String decryptWithBase64EncodedKey(String algorithm, String message, String key) - throws EncryptionException { - EncryptionKeyAndTransformation keyAndTransformation; - try { - keyAndTransformation = getKey(algorithm, key, CryptographicOperation.Decryption); - } catch (InvalidKeySpecException e) { - throw new EncryptionException("Invalid Key specifications", e); - } - return decrypt(message, keyAndTransformation); - } - /** * Decrypts a base64 payload containing an IV and AES or RSA encrypted ciphertext using the - * provided key + * provided key or an alias of a key stored in the platform Keystore * - * @param message a message to be decrypted - * @param keyAndTransform depending on the algorithm, a Secret key or Private key and its - * respective cryptographic transformation to be used for decryption + * @param message a UTF-8 encoded message to be decrypted + * @param keyOrKeyAlias depending on the IEncryptionKeyProvider implementation this can be a + * SecretKey/PublicKey or an alias of a key stored in the platform Keystore * @return Decrypted message for the given encrypted message + * @throws EncryptionException in case the keystore has not been initialized, the appropriate + * data integrity algorithm could not be found or an entry in the + * keystore cannot be retrieved */ - private String decrypt(String message, EncryptionKeyAndTransformation keyAndTransform) + public String decrypt(String algorithm, String message, String keyOrKeyAlias) throws EncryptionException { final int TAG_LENGTH_BIT = 128; + EncryptionKeyAndTransformation keyAndTransformation = + encryptionKeyProvider.getKey(algorithm, keyOrKeyAlias, CryptographicOperation.Decryption); + try { byte[] messageBytes = Base64.decode(message); ByteBuffer bb = ByteBuffer.wrap(messageBytes); @@ -248,11 +120,13 @@ private String decrypt(String message, EncryptionKeyAndTransformation keyAndTran byte[] cipherText = new byte[bb.remaining()]; bb.get(cipherText); - Cipher cipher = Cipher.getInstance(keyAndTransform.getTransformation()); - if (keyAndTransform.getKey().getAlgorithm().equals(encryptionKeyProvider.getAESKeyAlgorithmRepresentation())) { - cipher.init(Cipher.DECRYPT_MODE, keyAndTransform.getKey(), new GCMParameterSpec(TAG_LENGTH_BIT, iv)); + Cipher cipher = Cipher.getInstance(keyAndTransformation.getTransformation()); + if (keyAndTransformation.getKey().getAlgorithm().equals( + encryptionKeyProvider.getAESKeyAlgorithmRepresentation())) { + cipher.init(Cipher.DECRYPT_MODE, keyAndTransformation.getKey(), + new GCMParameterSpec(TAG_LENGTH_BIT, iv)); } else { - cipher.init(Cipher.DECRYPT_MODE, keyAndTransform.getKey()); + cipher.init(Cipher.DECRYPT_MODE, keyAndTransformation.getKey()); } byte[] plainText = cipher.doFinal(cipherText); return new String(plainText, Charset.forName("UTF-8")); diff --git a/src/main/java/org/commcare/util/IEncryptionKeyProvider.java b/src/main/java/org/commcare/util/IEncryptionKeyProvider.java index e4858aef7..9381b4ec3 100644 --- a/src/main/java/org/commcare/util/IEncryptionKeyProvider.java +++ b/src/main/java/org/commcare/util/IEncryptionKeyProvider.java @@ -1,11 +1,5 @@ package org.commcare.util; -import java.io.IOException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableEntryException; -import java.security.cert.CertificateException; - /** * Service interface for Encryption Key providers for KeyStores * @@ -14,9 +8,19 @@ public interface IEncryptionKeyProvider { - EncryptionKeyAndTransformation retrieveKeyFromKeyStore(String keyAlias, - EncryptionHelper.CryptographicOperation operation) - throws KeyStoreException, UnrecoverableEntryException, NoSuchAlgorithmException, CertificateException, IOException; + /** + * Converts a Base64 encoded key into a SecretKey, PrivateKey or PublicKey, depending on the + * algorithm and cryptographic operation + * + * @param algorithm the algorithm to be used to encrypt/decrypt the message, only + * applicable when not using the platform keystore + * @param keyOrKeyAlias key or key alias in String format + * @param cryptographicOperation Cryptographic operation where the key is to be used, relevant + * to the RSA algorithm + * @return Secret key, Public key or Private Key to be used + */ + EncryptionKeyAndTransformation getKey(String algorithm, String keyOrKeyAlias, + EncryptionHelper.CryptographicOperation cryptographicOperation) throws EncryptionException; void generateCryptographicKeyInKeyStore(String keyAlias); diff --git a/src/main/java/org/javarosa/core/model/User.java b/src/main/java/org/javarosa/core/model/User.java index bc2c7fde5..f5a6de6a3 100644 --- a/src/main/java/org/javarosa/core/model/User.java +++ b/src/main/java/org/javarosa/core/model/User.java @@ -1,5 +1,6 @@ package org.javarosa.core.model; +import org.commcare.util.EncryptionException; import org.commcare.util.EncryptionHelper; import org.javarosa.core.model.instance.FormInstance; import org.javarosa.core.model.instance.TreeReference; @@ -15,10 +16,6 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableEntryException; -import java.security.cert.CertificateException; import java.util.Hashtable; import static org.commcare.util.EncryptionHelper.CC_IN_MEMORY_ENCRYPTION_KEY_ALIAS; @@ -106,10 +103,9 @@ public String getUsername() { return this.plaintextUsername; } else { try { - return encryptionHelper.decryptWithKeyStore(this.encryptedUsername, CC_IN_MEMORY_ENCRYPTION_KEY_ALIAS); - } catch (UnrecoverableEntryException | KeyStoreException | NoSuchAlgorithmException | - CertificateException | IOException e) { - throw new RuntimeException("Error encountered while retrieving key from keyStore: ", e); + return encryptionHelper.decrypt(null, this.encryptedUsername, CC_IN_MEMORY_ENCRYPTION_KEY_ALIAS); + } catch (EncryptionException e) { + throw new RuntimeException("Error encountered while decrypting the username", e); } } } @@ -145,10 +141,9 @@ public void setUsername(String username) { this.plaintextUsername = username; } else { try { - this.encryptedUsername = encryptionHelper.encryptWithKeyStore(username, CC_IN_MEMORY_ENCRYPTION_KEY_ALIAS); - } catch (UnrecoverableEntryException | KeyStoreException | NoSuchAlgorithmException | - CertificateException | IOException e) { - throw new RuntimeException("Error encountered while retrieving key from keyStore: ", e); + this.encryptedUsername = encryptionHelper.encrypt(null, username, CC_IN_MEMORY_ENCRYPTION_KEY_ALIAS); + } catch (EncryptionException e) { + throw new RuntimeException("Error encountered while encrypting the username", e); } } } @@ -218,10 +213,9 @@ public void setCachedPwd(String password) { this.plaintextCachedPwd = password; } else { try { - this.encryptedCachedPwd = encryptionHelper.encryptWithKeyStore(password, CC_IN_MEMORY_ENCRYPTION_KEY_ALIAS); - } catch (UnrecoverableEntryException | KeyStoreException | NoSuchAlgorithmException | - CertificateException | IOException e) { - throw new RuntimeException("Error encountered while retrieving key from keyStore: ", e); + this.encryptedCachedPwd = encryptionHelper.encrypt(null, password, CC_IN_MEMORY_ENCRYPTION_KEY_ALIAS); + } catch (EncryptionException e) { + throw new RuntimeException("Error encountered while encrypting the password", e); } } } @@ -231,10 +225,9 @@ public String getCachedPwd() { return this.plaintextCachedPwd; } else { try { - return encryptionHelper.decryptWithKeyStore(this.encryptedCachedPwd, CC_IN_MEMORY_ENCRYPTION_KEY_ALIAS); - } catch (UnrecoverableEntryException | KeyStoreException | NoSuchAlgorithmException | - CertificateException | IOException e) { - throw new RuntimeException("Error encountered while retrieving key from keyStore: ", e); + return encryptionHelper.decrypt(null, this.encryptedCachedPwd, CC_IN_MEMORY_ENCRYPTION_KEY_ALIAS); + } catch (EncryptionException e) { + throw new RuntimeException("Error encountered while decrypting the username", e); } } } diff --git a/src/main/java/org/javarosa/xpath/expr/XPathDecryptStringFunc.java b/src/main/java/org/javarosa/xpath/expr/XPathDecryptStringFunc.java index 54e053019..cbd5a1aeb 100644 --- a/src/main/java/org/javarosa/xpath/expr/XPathDecryptStringFunc.java +++ b/src/main/java/org/javarosa/xpath/expr/XPathDecryptStringFunc.java @@ -49,7 +49,7 @@ private String decryptString(Object o1, Object o2, Object o3) { } try { - return encryptionHelper.decryptWithBase64EncodedKey(algorithm, message, key); + return encryptionHelper.decrypt(algorithm, message, key); } catch (EncryptionException e) { throw new XPathException(e); } diff --git a/src/main/java/org/javarosa/xpath/expr/XPathEncryptStringFunc.java b/src/main/java/org/javarosa/xpath/expr/XPathEncryptStringFunc.java index 12e1f681e..6d1ce5fe3 100644 --- a/src/main/java/org/javarosa/xpath/expr/XPathEncryptStringFunc.java +++ b/src/main/java/org/javarosa/xpath/expr/XPathEncryptStringFunc.java @@ -50,7 +50,7 @@ private String encryptString(Object o1, Object o2, Object o3) { } try { - return encryptionHelper.encryptWithBase64EncodedKey(algorithm, message, key); + return encryptionHelper.encrypt(algorithm, message, key); } catch (EncryptionException e) { throw new XPathException(e); }