From 2c1f5b49e449d34c030080ee46b6b580772c5378 Mon Sep 17 00:00:00 2001 From: Andy LoPresto Date: Tue, 15 Aug 2017 15:33:19 -0400 Subject: [PATCH] NIFI-3116 This closes #2108. Added initial regression test for StringEncryptor to ensure continued functionality during removal of Jasypt. Added external compatibility regression test for StringEncryptor to ensure continued functionality during removal of Jasypt. Documents custom salt lengths and iteration counts for each encryption method. Added (ignored) failing tests for keyed encryption (Jasypt does not support keyed encryption). Changed StringEncryptor to non-final class and added protected default constructor. Added failing test for initialization status. Added utility methods in CipherUtility. Moved PBE cipher providers (and tests) from nifi-standard-processors to nifi-security-utils module. Implemented PBE and keyed encryption/decryption logic. Moved Scrypt unit test back into scrypt package. Resolved test failures in limited strength cryptographic environment. Implemented keyed encryption/decryption and enabled unit tests. Removed Jasypt dependency from production scope (kept in test scope for backward compatibility tests). Signed-off-by: joewitt --- nifi-assembly/NOTICE | 4 - nifi-commons/nifi-security-utils/pom.xml | 3 + .../nifi/security/util/EncryptionMethod.java | 7 + .../util/crypto/BcryptCipherProvider.java | 0 .../util/crypto/CipherProviderFactory.java | 0 .../security/util/crypto/CipherUtility.java | 98 +++- .../util/crypto/KeyedCipherProvider.java | 4 +- .../util/crypto/NiFiLegacyCipherProvider.java | 0 .../crypto/OpenSSLPKCS5CipherProvider.java | 0 .../util/crypto/PBECipherProvider.java | 0 .../util/crypto/PBKDF2CipherProvider.java | 0 .../crypto/RandomIVPBECipherProvider.java | 0 .../util/crypto/ScryptCipherProvider.java | 0 .../security/util/crypto/bcrypt/BCrypt.java | 6 +- .../security/util/crypto/scrypt/Scrypt.java | 0 .../BcryptCipherProviderGroovyTest.groovy | 212 ++++---- .../CipherProviderFactoryGroovyTest.groovy | 10 +- .../NiFiLegacyCipherProviderGroovyTest.groovy | 168 +++--- ...penSSLPKCS5CipherProviderGroovyTest.groovy | 206 ++++---- .../PBKDF2CipherProviderGroovyTest.groovy | 268 +++++----- .../ScryptCipherProviderGroovyTest.groovy | 214 ++++---- .../util}/scrypt/ScryptGroovyTest.groovy | 37 +- .../src/test/resources/openssl_aes.rb | 0 .../src/test/resources/openssl_bcrypt.rb | 0 .../src/test/resources/openssl_pbkdf2.rb | 0 .../src/test/resources/openssl_scrypt.rb | 0 .../src/main/resources/META-INF/NOTICE | 4 - .../nifi-framework-core/pom.xml | 10 +- .../serialization/FlowFromDOMFactory.java | 3 +- .../apache/nifi/encrypt/StringEncryptor.java | 398 +++++++++++++-- .../nifi/encrypt/StringEncryptorTest.groovy | 482 ++++++++++++++++++ .../src/test/resources/logback-test.xml | 1 + .../nifi-standard-processors/pom.xml | 2 +- 33 files changed, 1507 insertions(+), 630 deletions(-) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/main/java/org/apache/nifi/security/util/crypto/BcryptCipherProvider.java (100%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/main/java/org/apache/nifi/security/util/crypto/CipherProviderFactory.java (100%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/main/java/org/apache/nifi/security/util/crypto/NiFiLegacyCipherProvider.java (100%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/main/java/org/apache/nifi/security/util/crypto/OpenSSLPKCS5CipherProvider.java (100%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/main/java/org/apache/nifi/security/util/crypto/PBECipherProvider.java (100%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/main/java/org/apache/nifi/security/util/crypto/PBKDF2CipherProvider.java (100%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/main/java/org/apache/nifi/security/util/crypto/RandomIVPBECipherProvider.java (100%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/main/java/org/apache/nifi/security/util/crypto/ScryptCipherProvider.java (100%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/main/java/org/apache/nifi/security/util/crypto/bcrypt/BCrypt.java (99%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/main/java/org/apache/nifi/security/util/crypto/scrypt/Scrypt.java (100%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/test/groovy/org/apache/nifi/security/util/crypto/BcryptCipherProviderGroovyTest.groovy (78%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/test/groovy/org/apache/nifi/security/util/crypto/CipherProviderFactoryGroovyTest.groovy (92%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/test/groovy/org/apache/nifi/security/util/crypto/NiFiLegacyCipherProviderGroovyTest.groovy (76%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/test/groovy/org/apache/nifi/security/util/crypto/OpenSSLPKCS5CipherProviderGroovyTest.groovy (71%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/test/groovy/org/apache/nifi/security/util/crypto/PBKDF2CipherProviderGroovyTest.groovy (72%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/test/groovy/org/apache/nifi/security/util/crypto/ScryptCipherProviderGroovyTest.groovy (79%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto => nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util}/scrypt/ScryptGroovyTest.groovy (92%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/test/resources/openssl_aes.rb (100%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/test/resources/openssl_bcrypt.rb (100%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/test/resources/openssl_pbkdf2.rb (100%) rename {nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors => nifi-commons/nifi-security-utils}/src/test/resources/openssl_scrypt.rb (100%) create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/encrypt/StringEncryptorTest.groovy diff --git a/nifi-assembly/NOTICE b/nifi-assembly/NOTICE index 83fd65acce..73b6778698 100644 --- a/nifi-assembly/NOTICE +++ b/nifi-assembly/NOTICE @@ -125,10 +125,6 @@ The following binary components are provided under the Apache Software License v CurvesAIP is BSD-licensed software (https://github.com/virtuald/curvesapi/) Copyright (c) 2005, Graph Builder - (ASLv2) Jasypt - The following NOTICE information applies: - Copyright (c) 2007-2010, The JASYPT team (http://www.jasypt.org) - (ASLv2) Apache Commons Codec The following NOTICE information applies: Apache Commons Codec diff --git a/nifi-commons/nifi-security-utils/pom.xml b/nifi-commons/nifi-security-utils/pom.xml index 3f4a088f23..92b5df5571 100644 --- a/nifi-commons/nifi-security-utils/pom.xml +++ b/nifi-commons/nifi-security-utils/pom.xml @@ -68,6 +68,9 @@ src/test/resources/xxe_template.xml + + src/main/java/org/apache/nifi/security/util/crypto/bcrypt/BCrypt.java diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/EncryptionMethod.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/EncryptionMethod.java index a1ef2a4444..028c9154ce 100644 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/EncryptionMethod.java +++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/EncryptionMethod.java @@ -94,6 +94,13 @@ public enum EncryptionMethod { return !algorithm.startsWith("PBE") && !algorithm.startsWith("PGP"); } + /** + * @return true if this algorithm uses its own internal key derivation process from a password + */ + public boolean isPBECipher() { + return algorithm.startsWith("PBE"); + } + @Override public String toString() { final ToStringBuilder builder = new ToStringBuilder(this); diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/BcryptCipherProvider.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/BcryptCipherProvider.java similarity index 100% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/BcryptCipherProvider.java rename to nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/BcryptCipherProvider.java diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/CipherProviderFactory.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/CipherProviderFactory.java similarity index 100% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/CipherProviderFactory.java rename to nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/CipherProviderFactory.java diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/CipherUtility.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/CipherUtility.java index 2bf952b6ac..369b015a06 100644 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/CipherUtility.java +++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/CipherUtility.java @@ -20,6 +20,11 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -29,6 +34,11 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.processor.exception.ProcessException; @@ -41,6 +51,7 @@ public class CipherUtility { private static final Pattern KEY_LENGTH_PATTERN = Pattern.compile("([\\d]+)BIT"); private static final Map MAX_PASSWORD_LENGTH_BY_ALGORITHM; + private static final int DEFAULT_MAX_ALLOWED_KEY_LENGTH = 128; static { Map aMap = new HashMap<>(); @@ -187,6 +198,12 @@ public class CipherUtility { return getValidKeyLengthsForAlgorithm(algorithm).contains(keyLength); } + /** + * Returns a list of valid key lengths in bits for this algorithm. If the algorithm cannot be parsed, an empty list is returned. + * + * @param algorithm the name of the algorithm + * @return a list of valid key lengths + */ public static List getValidKeyLengthsForAlgorithm(String algorithm) { List validKeyLengths = new ArrayList<>(); if (StringUtils.isEmpty(algorithm)) { @@ -309,11 +326,84 @@ public class CipherUtility { if (encryptionMethod == null) { throw new IllegalArgumentException("Cannot evaluate an empty encryption method algorithm"); } + return MAX_PASSWORD_LENGTH_BY_ALGORITHM.getOrDefault(encryptionMethod.getAlgorithm(), -1); + } - if (MAX_PASSWORD_LENGTH_BY_ALGORITHM.containsKey(encryptionMethod.getAlgorithm())) { - return MAX_PASSWORD_LENGTH_BY_ALGORITHM.get(encryptionMethod.getAlgorithm()); - } else { - return -1; + public static boolean isUnlimitedStrengthCryptoSupported() { + try { + return (Cipher.getMaxAllowedKeyLength("AES") > DEFAULT_MAX_ALLOWED_KEY_LENGTH); + } catch (NoSuchAlgorithmException e) { + return false; } } + + public static boolean isPBECipher(String algorithm) { + EncryptionMethod em = EncryptionMethod.forAlgorithm(algorithm); + return em != null && em.isPBECipher(); + } + + public static boolean isKeyedCipher(String algorithm) { + EncryptionMethod em = EncryptionMethod.forAlgorithm(algorithm); + return em != null && em.isKeyedCipher(); + } + + /** + * Initializes a {@link Cipher} object with the given PBE parameters. + * + * @param algorithm the algorithm + * @param provider the JCA provider + * @param password the password + * @param salt the salt + * @param iterationCount the KDF iteration count + * @param encryptMode true to encrypt; false to decrypt + * @return the initialized Cipher + * @throws IllegalArgumentException if any parameter is invalid + */ + public static Cipher initPBECipher(String algorithm, String provider, String password, byte[] salt, int iterationCount, boolean encryptMode) throws IllegalArgumentException { + try { + // Initialize secret key from password + final PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); + final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm, provider); + SecretKey tempKey = factory.generateSecret(pbeKeySpec); + + final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, iterationCount); + Cipher cipher = Cipher.getInstance(algorithm, provider); + cipher.init(encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, tempKey, parameterSpec); + return cipher; + } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) { + throw new IllegalArgumentException("One or more parameters to initialize the PBE cipher were invalid", e); + } + } + + /** + * Returns the KDF iteration count for various PBE algorithms. These values were determined empirically from configured/chosen legacy values from the earlier version of the project. + * Code demonstrating this is available at {@link StringEncryptorTest#testPBEncryptionShouldBeExternallyConsistent}. + * + * @param algorithm the {@link EncryptionMethod#algorithm} + * @return the iteration count. Default is 0. + */ + public static int getIterationCountForAlgorithm(String algorithm) { + int iterationCount = 0; + // DES/RC*/SHA-1/-256 algorithms use custom iteration counts + if (algorithm.matches("DES|RC|SHAA|SHA256")) { + iterationCount = 1000; + } + return iterationCount; + } + + /** + * Returns the salt length for various PBE algorithms. These values were determined empirically from configured/chosen legacy values from the earlier version of the project. + * Code demonstrating this is available at {@link StringEncryptorTest#testPBEncryptionShouldBeExternallyConsistent}. + * + * @param algorithm the {@link EncryptionMethod#algorithm} + * @return the salt length in bytes. Default is 16. + */ + public static int getSaltLengthForAlgorithm(String algorithm) { + int saltLength = 16; + // DES/RC* algorithms use custom iteration counts + if (algorithm.contains("DES") || algorithm.contains("RC")) { + saltLength = 8; + } + return saltLength; + } } \ No newline at end of file diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/KeyedCipherProvider.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/KeyedCipherProvider.java index 719150f98b..17bfaa060c 100644 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/KeyedCipherProvider.java +++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/KeyedCipherProvider.java @@ -42,7 +42,7 @@ public abstract class KeyedCipherProvider implements CipherProvider { * @return the initialized cipher * @throws Exception if there is a problem initializing the cipher */ - abstract Cipher getCipher(EncryptionMethod encryptionMethod, SecretKey key, byte[] iv, boolean encryptMode) throws Exception; + public abstract Cipher getCipher(EncryptionMethod encryptionMethod, SecretKey key, byte[] iv, boolean encryptMode) throws Exception; /** * Returns an initialized cipher for the specified algorithm. The IV will be generated internally (for encryption). If decryption is requested, it will throw an exception. @@ -53,7 +53,7 @@ public abstract class KeyedCipherProvider implements CipherProvider { * @return the initialized cipher * @throws Exception if there is a problem initializing the cipher or if decryption is requested */ - abstract Cipher getCipher(EncryptionMethod encryptionMethod, SecretKey key, boolean encryptMode) throws Exception; + public abstract Cipher getCipher(EncryptionMethod encryptionMethod, SecretKey key, boolean encryptMode) throws Exception; /** * Generates a new random IV of the correct length. diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/NiFiLegacyCipherProvider.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/NiFiLegacyCipherProvider.java similarity index 100% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/NiFiLegacyCipherProvider.java rename to nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/NiFiLegacyCipherProvider.java diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/OpenSSLPKCS5CipherProvider.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/OpenSSLPKCS5CipherProvider.java similarity index 100% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/OpenSSLPKCS5CipherProvider.java rename to nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/OpenSSLPKCS5CipherProvider.java diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/PBECipherProvider.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/PBECipherProvider.java similarity index 100% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/PBECipherProvider.java rename to nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/PBECipherProvider.java diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/PBKDF2CipherProvider.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/PBKDF2CipherProvider.java similarity index 100% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/PBKDF2CipherProvider.java rename to nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/PBKDF2CipherProvider.java diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/RandomIVPBECipherProvider.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/RandomIVPBECipherProvider.java similarity index 100% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/RandomIVPBECipherProvider.java rename to nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/RandomIVPBECipherProvider.java diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/ScryptCipherProvider.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/ScryptCipherProvider.java similarity index 100% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/ScryptCipherProvider.java rename to nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/ScryptCipherProvider.java diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/bcrypt/BCrypt.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/bcrypt/BCrypt.java similarity index 99% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/bcrypt/BCrypt.java rename to nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/bcrypt/BCrypt.java index 323fe8f630..1a42708f96 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/bcrypt/BCrypt.java +++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/bcrypt/BCrypt.java @@ -537,8 +537,8 @@ public class BCrypt { * Initialise the Blowfish key schedule */ private void init_key() { - P = (int[]) P_orig.clone(); - S = (int[]) S_orig.clone(); + P = P_orig.clone(); + S = S_orig.clone(); } /** @@ -690,7 +690,7 @@ public class BCrypt { B = new BCrypt(); hashed = B.crypt_raw(passwordb, saltb, rounds, - (int[]) bf_crypt_ciphertext.clone()); + bf_crypt_ciphertext.clone()); rs.append("$2"); if (minor >= 'a') diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/scrypt/Scrypt.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/scrypt/Scrypt.java similarity index 100% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/security/util/crypto/scrypt/Scrypt.java rename to nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/scrypt/Scrypt.java diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/BcryptCipherProviderGroovyTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/BcryptCipherProviderGroovyTest.groovy similarity index 78% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/BcryptCipherProviderGroovyTest.groovy rename to nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/BcryptCipherProviderGroovyTest.groovy index e5e001fbc6..6bdb680f44 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/BcryptCipherProviderGroovyTest.groovy +++ b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/BcryptCipherProviderGroovyTest.groovy @@ -41,18 +41,18 @@ import static groovy.test.GroovyAssert.shouldFail import static org.junit.Assert.assertTrue @RunWith(JUnit4.class) -public class BcryptCipherProviderGroovyTest { - private static final Logger logger = LoggerFactory.getLogger(BcryptCipherProviderGroovyTest.class); +class BcryptCipherProviderGroovyTest { + private static final Logger logger = LoggerFactory.getLogger(BcryptCipherProviderGroovyTest.class) private static List strongKDFEncryptionMethods - private static final int DEFAULT_KEY_LENGTH = 128; + private static final int DEFAULT_KEY_LENGTH = 128 public static final String MICROBENCHMARK = "microbenchmark" private static ArrayList AES_KEY_LENGTHS @BeforeClass - public static void setUpOnce() throws Exception { - Security.addProvider(new BouncyCastleProvider()); + static void setUpOnce() throws Exception { + Security.addProvider(new BouncyCastleProvider()) strongKDFEncryptionMethods = EncryptionMethod.values().findAll { it.isCompatibleWithStrongKDFs() } @@ -60,7 +60,7 @@ public class BcryptCipherProviderGroovyTest { logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}") } - if (PasswordBasedEncryptor.supportsUnlimitedStrength()) { + if (CipherUtility.isUnlimitedStrengthCryptoSupported()) { AES_KEY_LENGTHS = [128, 192, 256] } else { AES_KEY_LENGTHS = [128] @@ -68,117 +68,117 @@ public class BcryptCipherProviderGroovyTest { } @Before - public void setUp() throws Exception { + void setUp() throws Exception { } @After - public void tearDown() throws Exception { + void tearDown() throws Exception { } @Test - public void testGetCipherShouldBeInternallyConsistent() throws Exception { + void testGetCipherShouldBeInternallyConsistent() throws Exception { // Arrange RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4) - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = cipherProvider.generateSalt() - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : strongKDFEncryptionMethods) { - logger.info("Using algorithm: ${em.getAlgorithm()}"); + logger.info("Using algorithm: ${em.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, true); - byte[] iv = cipher.getIV(); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, true) + byte[] iv = cipher.getIV() logger.info("IV: ${Hex.encodeHexString(iv)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, DEFAULT_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, DEFAULT_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherWithExternalIVShouldBeInternallyConsistent() throws Exception { + void testGetCipherWithExternalIVShouldBeInternallyConsistent() throws Exception { // Arrange RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4) - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = cipherProvider.generateSalt() - final byte[] IV = Hex.decodeHex("01" * 16 as char[]); + final byte[] IV = Hex.decodeHex("01" * 16 as char[]) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : strongKDFEncryptionMethods) { - logger.info("Using algorithm: ${em.getAlgorithm()}"); + logger.info("Using algorithm: ${em.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true) logger.info("IV: ${Hex.encodeHexString(IV)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherWithUnlimitedStrengthShouldBeInternallyConsistent() throws Exception { + void testGetCipherWithUnlimitedStrengthShouldBeInternallyConsistent() throws Exception { // Arrange Assume.assumeTrue("Test is being skipped due to this JVM lacking JCE Unlimited Strength Jurisdiction Policy file.", - PasswordBasedEncryptor.supportsUnlimitedStrength()); + CipherUtility.isUnlimitedStrengthCryptoSupported()) RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4) - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = cipherProvider.generateSalt() final int LONG_KEY_LENGTH = 256 - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : strongKDFEncryptionMethods) { - logger.info("Using algorithm: ${em.getAlgorithm()}"); + logger.info("Using algorithm: ${em.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, LONG_KEY_LENGTH, true); - byte[] iv = cipher.getIV(); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, LONG_KEY_LENGTH, true) + byte[] iv = cipher.getIV() logger.info("IV: ${Hex.encodeHexString(iv)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, LONG_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, LONG_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testHashPWShouldMatchTestVectors() { + void testHashPWShouldMatchTestVectors() { // Arrange final String PASSWORD = 'abcdefghijklmnopqrstuvwxyz' final String SALT = '$2a$10$fVH8e28OQRj9tqiDXs1e1u' @@ -194,16 +194,16 @@ public class BcryptCipherProviderGroovyTest { } @Test - public void testGetCipherShouldSupportExternalCompatibility() throws Exception { + void testGetCipherShouldSupportExternalCompatibility() throws Exception { // Arrange RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4) - final String PLAINTEXT = "This is a plaintext message."; - final String PASSWORD = "thisIsABadPassword"; + final String PLAINTEXT = "This is a plaintext message." + final String PASSWORD = "thisIsABadPassword" // These values can be generated by running `$ ./openssl_bcrypt` in the terminal - final byte[] SALT = Hex.decodeHex("81455b915ce9efd1fc61a08eb0255936" as char[]); - final byte[] IV = Hex.decodeHex("41a51e0150df6a1f72826b36c6371f3f" as char[]); + final byte[] SALT = Hex.decodeHex("81455b915ce9efd1fc61a08eb0255936" as char[]) + final byte[] IV = Hex.decodeHex("41a51e0150df6a1f72826b36c6371f3f" as char[]) // $v2$w2$base64_salt_22__base64_hash_31 final String FULL_HASH = "\$2a\$10\$gUVbkVzp79H8YaCOsCVZNuz/d759nrMKzjuviaS5/WdcKHzqngGKi" @@ -222,8 +222,8 @@ public class BcryptCipherProviderGroovyTest { byte[] cipherBytes = Hex.decodeHex(CIPHER_TEXT as char[]) EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC - logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}"); - logger.info("External cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}") + logger.info("External cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") // Sanity check Cipher rubyCipher = Cipher.getInstance(encryptionMethod.algorithm, "BC") @@ -245,25 +245,25 @@ public class BcryptCipherProviderGroovyTest { assert generatedHash == FULL_HASH // Act - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, FULL_SALT.bytes, IV, DEFAULT_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, FULL_SALT.bytes, IV, DEFAULT_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert PLAINTEXT.equals(recovered); + assert PLAINTEXT.equals(recovered) } @Test - public void testGetCipherShouldHandleFullSalt() throws Exception { + void testGetCipherShouldHandleFullSalt() throws Exception { // Arrange RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4) - final String PLAINTEXT = "This is a plaintext message."; - final String PASSWORD = "thisIsABadPassword"; + final String PLAINTEXT = "This is a plaintext message." + final String PASSWORD = "thisIsABadPassword" // These values can be generated by running `$ ./openssl_bcrypt.rb` in the terminal - final byte[] IV = Hex.decodeHex("41a51e0150df6a1f72826b36c6371f3f" as char[]); + final byte[] IV = Hex.decodeHex("41a51e0150df6a1f72826b36c6371f3f" as char[]) // $v2$w2$base64_salt_22__base64_hash_31 final String FULL_HASH = "\$2a\$10\$gUVbkVzp79H8YaCOsCVZNuz/d759nrMKzjuviaS5/WdcKHzqngGKi" @@ -282,37 +282,37 @@ public class BcryptCipherProviderGroovyTest { byte[] cipherBytes = Hex.decodeHex(CIPHER_TEXT as char[]) EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC - logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}"); - logger.info("External cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}") + logger.info("External cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") // Act - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, FULL_SALT.bytes, IV, DEFAULT_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, FULL_SALT.bytes, IV, DEFAULT_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert PLAINTEXT.equals(recovered); + assert PLAINTEXT.equals(recovered) } @Test - public void testGetCipherShouldHandleUnformedSalt() throws Exception { + void testGetCipherShouldHandleUnformedSalt() throws Exception { // Arrange RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4) - final String PASSWORD = "thisIsABadPassword"; + final String PASSWORD = "thisIsABadPassword" final def INVALID_SALTS = ['$ab$00$acbdefghijklmnopqrstuv', 'bad_salt', '$3a$11$', 'x', '$2a$10$'] EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC - logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}"); + logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}") // Act INVALID_SALTS.each { String salt -> logger.info("Checking salt ${salt}") def msg = shouldFail(IllegalArgumentException) { - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt.bytes, DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt.bytes, DEFAULT_KEY_LENGTH, true) } // Assert @@ -331,20 +331,20 @@ public class BcryptCipherProviderGroovyTest { } @Test - public void testGetCipherShouldRejectEmptySalt() throws Exception { + void testGetCipherShouldRejectEmptySalt() throws Exception { // Arrange RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4) - final String PASSWORD = "thisIsABadPassword"; + final String PASSWORD = "thisIsABadPassword" EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC - logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}"); + logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}") // Two different errors -- one explaining the no-salt method is not supported, and the other for an empty byte[] passed // Act def msg = shouldFail(IllegalArgumentException) { - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, new byte[0], DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, new byte[0], DEFAULT_KEY_LENGTH, true) } logger.expected(msg) @@ -353,29 +353,29 @@ public class BcryptCipherProviderGroovyTest { } @Test - public void testGetCipherForDecryptShouldRequireIV() throws Exception { + void testGetCipherForDecryptShouldRequireIV() throws Exception { // Arrange RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4) - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = cipherProvider.generateSalt() - final byte[] IV = Hex.decodeHex("00" * 16 as char[]); + final byte[] IV = Hex.decodeHex("00" * 16 as char[]) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : strongKDFEncryptionMethods) { - logger.info("Using algorithm: ${em.getAlgorithm()}"); + logger.info("Using algorithm: ${em.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true) logger.info("IV: ${Hex.encodeHexString(IV)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") def msg = shouldFail(IllegalArgumentException) { - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, false); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, false) } // Assert @@ -384,15 +384,15 @@ public class BcryptCipherProviderGroovyTest { } @Test - public void testGetCipherShouldAcceptValidKeyLengths() throws Exception { + void testGetCipherShouldAcceptValidKeyLengths() throws Exception { // Arrange - RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4); + RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4) - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = cipherProvider.generateSalt() - final byte[] IV = Hex.decodeHex("01" * 16 as char[]); + final byte[] IV = Hex.decodeHex("01" * 16 as char[]) - final String PLAINTEXT = "This is a plaintext message."; + final String PLAINTEXT = "This is a plaintext message." // Currently only AES ciphers are compatible with Bcrypt, so redundant to test all algorithms final def VALID_KEY_LENGTHS = AES_KEY_LENGTHS @@ -403,32 +403,32 @@ public class BcryptCipherProviderGroovyTest { logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()} with key length ${keyLength}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true) logger.info("IV: ${Hex.encodeHexString(IV)}") - byte[] cipherBytes = cipher.doFinal(PLAINTEXT.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(PLAINTEXT.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert PLAINTEXT.equals(recovered); + assert PLAINTEXT.equals(recovered) } } @Test - public void testGetCipherShouldNotAcceptInvalidKeyLengths() throws Exception { + void testGetCipherShouldNotAcceptInvalidKeyLengths() throws Exception { // Arrange - RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4); + RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(4) - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = cipherProvider.generateSalt() - final byte[] IV = Hex.decodeHex("00" * 16 as char[]); + final byte[] IV = Hex.decodeHex("00" * 16 as char[]) - final String PLAINTEXT = "This is a plaintext message."; + final String PLAINTEXT = "This is a plaintext message." // Currently only AES ciphers are compatible with Bcrypt, so redundant to test all algorithms final def INVALID_KEY_LENGTHS = [-1, 40, 64, 112, 512] @@ -440,7 +440,7 @@ public class BcryptCipherProviderGroovyTest { // Initialize a cipher for encryption def msg = shouldFail(IllegalArgumentException) { - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true) } // Assert @@ -449,9 +449,9 @@ public class BcryptCipherProviderGroovyTest { } @Test - public void testGenerateSaltShouldUseProvidedWorkFactor() throws Exception { + void testGenerateSaltShouldUseProvidedWorkFactor() throws Exception { // Arrange - RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(11); + RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(11) int workFactor = cipherProvider.getWorkFactor() // Act @@ -466,9 +466,9 @@ public class BcryptCipherProviderGroovyTest { @Ignore("This test can be run on a specific machine to evaluate if the default work factor is sufficient") @Test - public void testDefaultConstructorShouldProvideStrongWorkFactor() { + void testDefaultConstructorShouldProvideStrongWorkFactor() { // Arrange - RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider(); + RandomIVPBECipherProvider cipherProvider = new BcryptCipherProvider() // Values taken from http://wildlyinaccurate.com/bcrypt-choosing-a-work-factor/ and http://security.stackexchange.com/questions/17207/recommended-of-rounds-for-bcrypt diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/CipherProviderFactoryGroovyTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/CipherProviderFactoryGroovyTest.groovy similarity index 92% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/CipherProviderFactoryGroovyTest.groovy rename to nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/CipherProviderFactoryGroovyTest.groovy index 28da9d1095..b64a162ed9 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/CipherProviderFactoryGroovyTest.groovy +++ b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/CipherProviderFactoryGroovyTest.groovy @@ -44,7 +44,7 @@ class CipherProviderFactoryGroovyTest extends GroovyTestCase { ] @BeforeClass - public static void setUpOnce() throws Exception { + static void setUpOnce() throws Exception { Security.addProvider(new BouncyCastleProvider()) logger.metaClass.methodMissing = { String name, args -> @@ -53,15 +53,15 @@ class CipherProviderFactoryGroovyTest extends GroovyTestCase { } @Before - public void setUp() throws Exception { + void setUp() throws Exception { } @After - public void tearDown() throws Exception { + void tearDown() throws Exception { } @Test - public void testGetCipherProviderShouldResolveRegisteredKDFs() { + void testGetCipherProviderShouldResolveRegisteredKDFs() { // Arrange // Act @@ -77,7 +77,7 @@ class CipherProviderFactoryGroovyTest extends GroovyTestCase { @Ignore("Cannot mock enum using Groovy map coercion") @Test - public void testGetCipherProviderShouldHandleUnregisteredKDFs() { + void testGetCipherProviderShouldHandleUnregisteredKDFs() { // Arrange // Can't mock this; see http://stackoverflow.com/questions/5323505/mocking-java-enum-to-add-a-value-to-test-fail-case diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/NiFiLegacyCipherProviderGroovyTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/NiFiLegacyCipherProviderGroovyTest.groovy similarity index 76% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/NiFiLegacyCipherProviderGroovyTest.groovy rename to nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/NiFiLegacyCipherProviderGroovyTest.groovy index 2a3d456cd8..70db6a74d3 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/NiFiLegacyCipherProviderGroovyTest.groovy +++ b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/NiFiLegacyCipherProviderGroovyTest.groovy @@ -40,63 +40,63 @@ import java.security.Security import static org.junit.Assert.fail @RunWith(JUnit4.class) -public class NiFiLegacyCipherProviderGroovyTest { - private static final Logger logger = LoggerFactory.getLogger(NiFiLegacyCipherProviderGroovyTest.class); +class NiFiLegacyCipherProviderGroovyTest { + private static final Logger logger = LoggerFactory.getLogger(NiFiLegacyCipherProviderGroovyTest.class) - private static List pbeEncryptionMethods = new ArrayList<>(); - private static List limitedStrengthPbeEncryptionMethods = new ArrayList<>(); + private static List pbeEncryptionMethods = new ArrayList<>() + private static List limitedStrengthPbeEncryptionMethods = new ArrayList<>() - private static final String PROVIDER_NAME = "BC"; - private static final int ITERATION_COUNT = 1000; + private static final String PROVIDER_NAME = "BC" + private static final int ITERATION_COUNT = 1000 - private static final byte[] SALT_16_BYTES = Hex.decodeHex("aabbccddeeff00112233445566778899".toCharArray()); + private static final byte[] SALT_16_BYTES = Hex.decodeHex("aabbccddeeff00112233445566778899".toCharArray()) @BeforeClass - public static void setUpOnce() throws Exception { - Security.addProvider(new BouncyCastleProvider()); + static void setUpOnce() throws Exception { + Security.addProvider(new BouncyCastleProvider()) pbeEncryptionMethods = EncryptionMethod.values().findAll { it.algorithm.toUpperCase().startsWith("PBE") } limitedStrengthPbeEncryptionMethods = pbeEncryptionMethods.findAll { !it.isUnlimitedStrength() } } @Before - public void setUp() throws Exception { + void setUp() throws Exception { } @After - public void tearDown() throws Exception { + void tearDown() throws Exception { } private static Cipher getLegacyCipher(String password, byte[] salt, String algorithm) { try { - final PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); - final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm, PROVIDER_NAME); - SecretKey tempKey = factory.generateSecret(pbeKeySpec); + final PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()) + final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm, PROVIDER_NAME) + SecretKey tempKey = factory.generateSecret(pbeKeySpec) - final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATION_COUNT); - Cipher cipher = Cipher.getInstance(algorithm, PROVIDER_NAME); - cipher.init(Cipher.ENCRYPT_MODE, tempKey, parameterSpec); - return cipher; + final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATION_COUNT) + Cipher cipher = Cipher.getInstance(algorithm, PROVIDER_NAME) + cipher.init(Cipher.ENCRYPT_MODE, tempKey, parameterSpec) + return cipher } catch (Exception e) { - logger.error("Error generating legacy cipher", e); - fail(e.getMessage()); + logger.error("Error generating legacy cipher", e) + fail(e.getMessage()) } - return null; + return null } @Test - public void testGetCipherShouldBeInternallyConsistent() throws Exception { + void testGetCipherShouldBeInternallyConsistent() throws Exception { // Arrange - NiFiLegacyCipherProvider cipherProvider = new NiFiLegacyCipherProvider(); + NiFiLegacyCipherProvider cipherProvider = new NiFiLegacyCipherProvider() - final String PASSWORD = "shortPassword"; - final String plaintext = "This is a plaintext message."; + final String PASSWORD = "shortPassword" + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod encryptionMethod : limitedStrengthPbeEncryptionMethods) { - logger.info("Using algorithm: {}", encryptionMethod.getAlgorithm()); + logger.info("Using algorithm: {}", encryptionMethod.getAlgorithm()) if (!CipherUtility.passwordLengthIsValidForAlgorithmOnLimitedStrengthCrypto(PASSWORD.length(), encryptionMethod)) { logger.warn("This test is skipped because the password length exceeds the undocumented limit BouncyCastle imposes on a JVM with limited strength crypto policies") @@ -107,64 +107,64 @@ public class NiFiLegacyCipherProviderGroovyTest { logger.info("Generated salt ${Hex.encodeHexString(salt)} (${salt.length})") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt, true) - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length) - cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherWithUnlimitedStrengthShouldBeInternallyConsistent() throws Exception { + void testGetCipherWithUnlimitedStrengthShouldBeInternallyConsistent() throws Exception { // Arrange Assume.assumeTrue("Test is being skipped due to this JVM lacking JCE Unlimited Strength Jurisdiction Policy file.", - PasswordBasedEncryptor.supportsUnlimitedStrength()); + CipherUtility.isUnlimitedStrengthCryptoSupported()) - NiFiLegacyCipherProvider cipherProvider = new NiFiLegacyCipherProvider(); + NiFiLegacyCipherProvider cipherProvider = new NiFiLegacyCipherProvider() - final String PASSWORD = "shortPassword"; - final String plaintext = "This is a plaintext message."; + final String PASSWORD = "shortPassword" + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod encryptionMethod : pbeEncryptionMethods) { - logger.info("Using algorithm: {}", encryptionMethod.getAlgorithm()); + logger.info("Using algorithm: {}", encryptionMethod.getAlgorithm()) byte[] salt = cipherProvider.generateSalt(encryptionMethod) logger.info("Generated salt ${Hex.encodeHexString(salt)} (${salt.length})") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt, true) - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length) - cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherShouldSupportLegacyCode() throws Exception { + void testGetCipherShouldSupportLegacyCode() throws Exception { // Arrange - NiFiLegacyCipherProvider cipherProvider = new NiFiLegacyCipherProvider(); + NiFiLegacyCipherProvider cipherProvider = new NiFiLegacyCipherProvider() - final String PASSWORD = "short"; - final String plaintext = "This is a plaintext message."; + final String PASSWORD = "short" + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod encryptionMethod : limitedStrengthPbeEncryptionMethods) { - logger.info("Using algorithm: {}", encryptionMethod.getAlgorithm()); + logger.info("Using algorithm: {}", encryptionMethod.getAlgorithm()) if (!CipherUtility.passwordLengthIsValidForAlgorithmOnLimitedStrengthCrypto(PASSWORD.length(), encryptionMethod)) { logger.warn("This test is skipped because the password length exceeds the undocumented limit BouncyCastle imposes on a JVM with limited strength crypto policies") @@ -175,33 +175,33 @@ public class NiFiLegacyCipherProviderGroovyTest { logger.info("Generated salt ${Hex.encodeHexString(salt)} (${salt.length})") // Initialize a legacy cipher for encryption - Cipher legacyCipher = getLegacyCipher(PASSWORD, salt, encryptionMethod.getAlgorithm()); + Cipher legacyCipher = getLegacyCipher(PASSWORD, salt, encryptionMethod.getAlgorithm()) - byte[] cipherBytes = legacyCipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length); + byte[] cipherBytes = legacyCipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length) - Cipher providedCipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt, false); - byte[] recoveredBytes = providedCipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + Cipher providedCipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt, false) + byte[] recoveredBytes = providedCipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherWithoutSaltShouldSupportLegacyCode() throws Exception { + void testGetCipherWithoutSaltShouldSupportLegacyCode() throws Exception { // Arrange - NiFiLegacyCipherProvider cipherProvider = new NiFiLegacyCipherProvider(); + NiFiLegacyCipherProvider cipherProvider = new NiFiLegacyCipherProvider() - final String PASSWORD = "short"; - final byte[] SALT = new byte[0]; + final String PASSWORD = "short" + final byte[] SALT = new byte[0] - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : limitedStrengthPbeEncryptionMethods) { - logger.info("Using algorithm: {}", em.getAlgorithm()); + logger.info("Using algorithm: {}", em.getAlgorithm()) if (!CipherUtility.passwordLengthIsValidForAlgorithmOnLimitedStrengthCrypto(PASSWORD.length(), em)) { logger.warn("This test is skipped because the password length exceeds the undocumented limit BouncyCastle imposes on a JVM with limited strength crypto policies") @@ -209,48 +209,48 @@ public class NiFiLegacyCipherProviderGroovyTest { } // Initialize a legacy cipher for encryption - Cipher legacyCipher = getLegacyCipher(PASSWORD, SALT, em.getAlgorithm()); + Cipher legacyCipher = getLegacyCipher(PASSWORD, SALT, em.getAlgorithm()) - byte[] cipherBytes = legacyCipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length); + byte[] cipherBytes = legacyCipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length) - Cipher providedCipher = cipherProvider.getCipher(em, PASSWORD, false); - byte[] recoveredBytes = providedCipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + Cipher providedCipher = cipherProvider.getCipher(em, PASSWORD, false) + byte[] recoveredBytes = providedCipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherShouldIgnoreKeyLength() throws Exception { + void testGetCipherShouldIgnoreKeyLength() throws Exception { // Arrange - NiFiLegacyCipherProvider cipherProvider = new NiFiLegacyCipherProvider(); + NiFiLegacyCipherProvider cipherProvider = new NiFiLegacyCipherProvider() - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = SALT_16_BYTES - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." final def KEY_LENGTHS = [-1, 40, 64, 128, 192, 256] // Initialize a cipher for encryption EncryptionMethod encryptionMethod = EncryptionMethod.MD5_128AES - final Cipher cipher128 = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, true); - byte[] cipherBytes = cipher128.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length); + final Cipher cipher128 = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, true) + byte[] cipherBytes = cipher128.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length) // Act KEY_LENGTHS.each { int keyLength -> logger.info("Decrypting with 'requested' key length: ${keyLength}") - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, keyLength, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, keyLength, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @@ -262,7 +262,7 @@ public class NiFiLegacyCipherProviderGroovyTest { */ @Ignore("Only needed once to determine max supported password lengths") @Test - public void testShouldDetermineDependenceOnUnlimitedStrengthCrypto() throws IOException { + void testShouldDetermineDependenceOnUnlimitedStrengthCrypto() throws IOException { def encryptionMethods = EncryptionMethod.values().findAll { it.algorithm.startsWith("PBE") } boolean unlimitedCryptoSupported = PasswordBasedEncryptor.supportsUnlimitedStrength() @@ -277,7 +277,7 @@ public class NiFiLegacyCipherProviderGroovyTest { String password = "x" * length try { - NiFiLegacyCipherProvider cipherProvider = new NiFiLegacyCipherProvider(); + NiFiLegacyCipherProvider cipherProvider = new NiFiLegacyCipherProvider() Cipher cipher = cipherProvider.getCipher(encryptionMethod, password, true) return false } catch (Exception e) { diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/OpenSSLPKCS5CipherProviderGroovyTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/OpenSSLPKCS5CipherProviderGroovyTest.groovy similarity index 71% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/OpenSSLPKCS5CipherProviderGroovyTest.groovy rename to nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/OpenSSLPKCS5CipherProviderGroovyTest.groovy index 62b7970896..5d25ba2bb3 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/OpenSSLPKCS5CipherProviderGroovyTest.groovy +++ b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/OpenSSLPKCS5CipherProviderGroovyTest.groovy @@ -40,63 +40,63 @@ import static groovy.test.GroovyAssert.shouldFail import static org.junit.Assert.fail @RunWith(JUnit4.class) -public class OpenSSLPKCS5CipherProviderGroovyTest { - private static final Logger logger = LoggerFactory.getLogger(OpenSSLPKCS5CipherProviderGroovyTest.class); +class OpenSSLPKCS5CipherProviderGroovyTest { + private static final Logger logger = LoggerFactory.getLogger(OpenSSLPKCS5CipherProviderGroovyTest.class) - private static List pbeEncryptionMethods = new ArrayList<>(); - private static List limitedStrengthPbeEncryptionMethods = new ArrayList<>(); + private static List pbeEncryptionMethods = new ArrayList<>() + private static List limitedStrengthPbeEncryptionMethods = new ArrayList<>() - private static final String PROVIDER_NAME = "BC"; - private static final int ITERATION_COUNT = 0; + private static final String PROVIDER_NAME = "BC" + private static final int ITERATION_COUNT = 0 @BeforeClass - public static void setUpOnce() throws Exception { - Security.addProvider(new BouncyCastleProvider()); + static void setUpOnce() throws Exception { + Security.addProvider(new BouncyCastleProvider()) pbeEncryptionMethods = EncryptionMethod.values().findAll { it.algorithm.toUpperCase().startsWith("PBE") } limitedStrengthPbeEncryptionMethods = pbeEncryptionMethods.findAll { !it.isUnlimitedStrength() } } @Before - public void setUp() throws Exception { + void setUp() throws Exception { } @After - public void tearDown() throws Exception { + void tearDown() throws Exception { } private static Cipher getLegacyCipher(String password, byte[] salt, String algorithm) { try { - final PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); - final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm, PROVIDER_NAME); - SecretKey tempKey = factory.generateSecret(pbeKeySpec); + final PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()) + final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm, PROVIDER_NAME) + SecretKey tempKey = factory.generateSecret(pbeKeySpec) - final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATION_COUNT); - Cipher cipher = Cipher.getInstance(algorithm, PROVIDER_NAME); - cipher.init(Cipher.ENCRYPT_MODE, tempKey, parameterSpec); - return cipher; + final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATION_COUNT) + Cipher cipher = Cipher.getInstance(algorithm, PROVIDER_NAME) + cipher.init(Cipher.ENCRYPT_MODE, tempKey, parameterSpec) + return cipher } catch (Exception e) { - logger.error("Error generating legacy cipher", e); - fail(e.getMessage()); + logger.error("Error generating legacy cipher", e) + fail(e.getMessage()) } - return null; + return null } @Test - public void testGetCipherShouldBeInternallyConsistent() throws Exception { + void testGetCipherShouldBeInternallyConsistent() throws Exception { // Arrange - OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider(); + OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider() - final String PASSWORD = "short"; - final byte[] SALT = Hex.decodeHex("aabbccddeeff0011".toCharArray()); + final String PASSWORD = "short" + final byte[] SALT = Hex.decodeHex("aabbccddeeff0011".toCharArray()) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : limitedStrengthPbeEncryptionMethods) { - logger.info("Using algorithm: {}", em.getAlgorithm()); + logger.info("Using algorithm: {}", em.getAlgorithm()) if (!CipherUtility.passwordLengthIsValidForAlgorithmOnLimitedStrengthCrypto(PASSWORD.length(), em)) { logger.warn("This test is skipped because the password length exceeds the undocumented limit BouncyCastle imposes on a JVM with limited strength crypto policies") @@ -104,65 +104,65 @@ public class OpenSSLPKCS5CipherProviderGroovyTest { } // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, true); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, true) - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length) - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherWithUnlimitedStrengthShouldBeInternallyConsistent() throws Exception { + void testGetCipherWithUnlimitedStrengthShouldBeInternallyConsistent() throws Exception { // Arrange Assume.assumeTrue("Test is being skipped due to this JVM lacking JCE Unlimited Strength Jurisdiction Policy file.", - PasswordBasedEncryptor.supportsUnlimitedStrength()); + CipherUtility.isUnlimitedStrengthCryptoSupported()) - OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider(); + OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider() - final String PASSWORD = "shortPassword"; - final byte[] SALT = Hex.decodeHex("aabbccddeeff0011".toCharArray()); + final String PASSWORD = "shortPassword" + final byte[] SALT = Hex.decodeHex("aabbccddeeff0011".toCharArray()) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : pbeEncryptionMethods) { - logger.info("Using algorithm: {}", em.getAlgorithm()); + logger.info("Using algorithm: {}", em.getAlgorithm()) // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, true); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, true) - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length) - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherShouldSupportLegacyCode() throws Exception { + void testGetCipherShouldSupportLegacyCode() throws Exception { // Arrange - OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider(); + OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider() - final String PASSWORD = "shortPassword"; - final byte[] SALT = Hex.decodeHex("0011223344556677".toCharArray()); + final String PASSWORD = "shortPassword" + final byte[] SALT = Hex.decodeHex("0011223344556677".toCharArray()) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : limitedStrengthPbeEncryptionMethods) { - logger.info("Using algorithm: {}", em.getAlgorithm()); + logger.info("Using algorithm: {}", em.getAlgorithm()) if (!CipherUtility.passwordLengthIsValidForAlgorithmOnLimitedStrengthCrypto(PASSWORD.length(), em)) { logger.warn("This test is skipped because the password length exceeds the undocumented limit BouncyCastle imposes on a JVM with limited strength crypto policies") @@ -170,33 +170,33 @@ public class OpenSSLPKCS5CipherProviderGroovyTest { } // Initialize a legacy cipher for encryption - Cipher legacyCipher = getLegacyCipher(PASSWORD, SALT, em.getAlgorithm()); + Cipher legacyCipher = getLegacyCipher(PASSWORD, SALT, em.getAlgorithm()) - byte[] cipherBytes = legacyCipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length); + byte[] cipherBytes = legacyCipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length) - Cipher providedCipher = cipherProvider.getCipher(em, PASSWORD, SALT, false); - byte[] recoveredBytes = providedCipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + Cipher providedCipher = cipherProvider.getCipher(em, PASSWORD, SALT, false) + byte[] recoveredBytes = providedCipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherWithoutSaltShouldSupportLegacyCode() throws Exception { + void testGetCipherWithoutSaltShouldSupportLegacyCode() throws Exception { // Arrange - OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider(); + OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider() - final String PASSWORD = "short"; - final byte[] SALT = new byte[0]; + final String PASSWORD = "short" + final byte[] SALT = new byte[0] - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : limitedStrengthPbeEncryptionMethods) { - logger.info("Using algorithm: {}", em.getAlgorithm()); + logger.info("Using algorithm: {}", em.getAlgorithm()) if (!CipherUtility.passwordLengthIsValidForAlgorithmOnLimitedStrengthCrypto(PASSWORD.length(), em)) { logger.warn("This test is skipped because the password length exceeds the undocumented limit BouncyCastle imposes on a JVM with limited strength crypto policies") @@ -204,64 +204,64 @@ public class OpenSSLPKCS5CipherProviderGroovyTest { } // Initialize a legacy cipher for encryption - Cipher legacyCipher = getLegacyCipher(PASSWORD, SALT, em.getAlgorithm()); + Cipher legacyCipher = getLegacyCipher(PASSWORD, SALT, em.getAlgorithm()) - byte[] cipherBytes = legacyCipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length); + byte[] cipherBytes = legacyCipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length) - Cipher providedCipher = cipherProvider.getCipher(em, PASSWORD, false); - byte[] recoveredBytes = providedCipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + Cipher providedCipher = cipherProvider.getCipher(em, PASSWORD, false) + byte[] recoveredBytes = providedCipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherShouldIgnoreKeyLength() throws Exception { + void testGetCipherShouldIgnoreKeyLength() throws Exception { // Arrange - OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider(); + OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider() - final String PASSWORD = "shortPassword"; - final byte[] SALT = Hex.decodeHex("aabbccddeeff0011".toCharArray()); + final String PASSWORD = "shortPassword" + final byte[] SALT = Hex.decodeHex("aabbccddeeff0011".toCharArray()) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." final def KEY_LENGTHS = [-1, 40, 64, 128, 192, 256] // Initialize a cipher for encryption EncryptionMethod encryptionMethod = EncryptionMethod.MD5_128AES - final Cipher cipher128 = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, true); - byte[] cipherBytes = cipher128.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length); + final Cipher cipher128 = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, true) + byte[] cipherBytes = cipher128.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: {} {}", Hex.encodeHexString(cipherBytes), cipherBytes.length) // Act KEY_LENGTHS.each { int keyLength -> logger.info("Decrypting with 'requested' key length: ${keyLength}") - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, keyLength, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, keyLength, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherShouldRequireEncryptionMethod() throws Exception { + void testGetCipherShouldRequireEncryptionMethod() throws Exception { // Arrange - OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider(); + OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider() - final String PASSWORD = "shortPassword"; - final byte[] SALT = Hex.decodeHex("0011223344556677".toCharArray()); + final String PASSWORD = "shortPassword" + final byte[] SALT = Hex.decodeHex("0011223344556677".toCharArray()) // Act - logger.info("Using algorithm: null"); + logger.info("Using algorithm: null") def msg = shouldFail(IllegalArgumentException) { - Cipher providedCipher = cipherProvider.getCipher(null, PASSWORD, SALT, false); + Cipher providedCipher = cipherProvider.getCipher(null, PASSWORD, SALT, false) } // Assert @@ -269,18 +269,18 @@ public class OpenSSLPKCS5CipherProviderGroovyTest { } @Test - public void testGetCipherShouldRequirePassword() throws Exception { + void testGetCipherShouldRequirePassword() throws Exception { // Arrange - OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider(); + OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider() - final byte[] SALT = Hex.decodeHex("0011223344556677".toCharArray()); + final byte[] SALT = Hex.decodeHex("0011223344556677".toCharArray()) EncryptionMethod encryptionMethod = EncryptionMethod.MD5_128AES // Act - logger.info("Using algorithm: ${encryptionMethod}"); + logger.info("Using algorithm: ${encryptionMethod}") def msg = shouldFail(IllegalArgumentException) { - Cipher providedCipher = cipherProvider.getCipher(encryptionMethod, "", SALT, false); + Cipher providedCipher = cipherProvider.getCipher(encryptionMethod, "", SALT, false) } // Assert @@ -288,19 +288,19 @@ public class OpenSSLPKCS5CipherProviderGroovyTest { } @Test - public void testGetCipherShouldValidateSaltLength() throws Exception { + void testGetCipherShouldValidateSaltLength() throws Exception { // Arrange - OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider(); + OpenSSLPKCS5CipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider() - final String PASSWORD = "shortPassword"; - final byte[] SALT = Hex.decodeHex("00112233445566".toCharArray()); + final String PASSWORD = "shortPassword" + final byte[] SALT = Hex.decodeHex("00112233445566".toCharArray()) EncryptionMethod encryptionMethod = EncryptionMethod.MD5_128AES // Act - logger.info("Using algorithm: ${encryptionMethod}"); + logger.info("Using algorithm: ${encryptionMethod}") def msg = shouldFail(IllegalArgumentException) { - Cipher providedCipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, false); + Cipher providedCipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, false) } // Assert @@ -308,7 +308,7 @@ public class OpenSSLPKCS5CipherProviderGroovyTest { } @Test - public void testGenerateSaltShouldProvideValidSalt() throws Exception { + void testGenerateSaltShouldProvideValidSalt() throws Exception { // Arrange PBECipherProvider cipherProvider = new OpenSSLPKCS5CipherProvider() diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/PBKDF2CipherProviderGroovyTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/PBKDF2CipherProviderGroovyTest.groovy similarity index 72% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/PBKDF2CipherProviderGroovyTest.groovy rename to nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/PBKDF2CipherProviderGroovyTest.groovy index 51a81772a5..661b8098f1 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/PBKDF2CipherProviderGroovyTest.groovy +++ b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/PBKDF2CipherProviderGroovyTest.groovy @@ -37,13 +37,13 @@ import static groovy.test.GroovyAssert.shouldFail import static org.junit.Assert.assertTrue @RunWith(JUnit4.class) -public class PBKDF2CipherProviderGroovyTest { - private static final Logger logger = LoggerFactory.getLogger(PBKDF2CipherProviderGroovyTest.class); +class PBKDF2CipherProviderGroovyTest { + private static final Logger logger = LoggerFactory.getLogger(PBKDF2CipherProviderGroovyTest.class) private static List strongKDFEncryptionMethods public static final String MICROBENCHMARK = "microbenchmark" - private static final int DEFAULT_KEY_LENGTH = 128; + private static final int DEFAULT_KEY_LENGTH = 128 private static final int TEST_ITERATION_COUNT = 1000 private final String DEFAULT_PRF = "SHA-512" private final String SALT_HEX = "0123456789ABCDEFFEDCBA9876543210" @@ -51,8 +51,8 @@ public class PBKDF2CipherProviderGroovyTest { private static ArrayList AES_KEY_LENGTHS @BeforeClass - public static void setUpOnce() throws Exception { - Security.addProvider(new BouncyCastleProvider()); + static void setUpOnce() throws Exception { + Security.addProvider(new BouncyCastleProvider()) strongKDFEncryptionMethods = EncryptionMethod.values().findAll { it.isCompatibleWithStrongKDFs() } @@ -60,7 +60,7 @@ public class PBKDF2CipherProviderGroovyTest { logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}") } - if (PasswordBasedEncryptor.supportsUnlimitedStrength()) { + if (CipherUtility.isUnlimitedStrengthCryptoSupported()) { AES_KEY_LENGTHS = [128, 192, 256] } else { AES_KEY_LENGTHS = [128] @@ -68,53 +68,53 @@ public class PBKDF2CipherProviderGroovyTest { } @Before - public void setUp() throws Exception { + void setUp() throws Exception { } @After - public void tearDown() throws Exception { + void tearDown() throws Exception { } @Test - public void testGetCipherShouldBeInternallyConsistent() throws Exception { + void testGetCipherShouldBeInternallyConsistent() throws Exception { // Arrange - RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT); + RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT) - final String PASSWORD = "shortPassword"; - final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]); + final String PASSWORD = "shortPassword" + final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : strongKDFEncryptionMethods) { - logger.info("Using algorithm: ${em.getAlgorithm()}"); + logger.info("Using algorithm: ${em.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, true); - byte[] iv = cipher.getIV(); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, true) + byte[] iv = cipher.getIV() logger.info("IV: ${Hex.encodeHexString(iv)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, DEFAULT_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, DEFAULT_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherShouldRejectInvalidIV() throws Exception { + void testGetCipherShouldRejectInvalidIV() throws Exception { // Arrange RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT) - final String PASSWORD = "shortPassword"; - final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]); + final String PASSWORD = "shortPassword" + final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]) final def INVALID_IVS = (0..15).collect { int length -> new byte[length] } EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC @@ -137,91 +137,91 @@ public class PBKDF2CipherProviderGroovyTest { } @Test - public void testGetCipherWithExternalIVShouldBeInternallyConsistent() throws Exception { + void testGetCipherWithExternalIVShouldBeInternallyConsistent() throws Exception { // Arrange - RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT); + RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT) - final String PASSWORD = "shortPassword"; - final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]); - final byte[] IV = Hex.decodeHex(IV_HEX as char[]); + final String PASSWORD = "shortPassword" + final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]) + final byte[] IV = Hex.decodeHex(IV_HEX as char[]) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : strongKDFEncryptionMethods) { - logger.info("Using algorithm: ${em.getAlgorithm()}"); + logger.info("Using algorithm: ${em.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true) logger.info("IV: ${Hex.encodeHexString(IV)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherWithUnlimitedStrengthShouldBeInternallyConsistent() throws Exception { + void testGetCipherWithUnlimitedStrengthShouldBeInternallyConsistent() throws Exception { // Arrange Assume.assumeTrue("Test is being skipped due to this JVM lacking JCE Unlimited Strength Jurisdiction Policy file.", - PasswordBasedEncryptor.supportsUnlimitedStrength()); + CipherUtility.isUnlimitedStrengthCryptoSupported()) - RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT); + RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT) - final String PASSWORD = "shortPassword"; - final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]); + final String PASSWORD = "shortPassword" + final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]) final int LONG_KEY_LENGTH = 256 - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : strongKDFEncryptionMethods) { - logger.info("Using algorithm: ${em.getAlgorithm()}"); + logger.info("Using algorithm: ${em.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, LONG_KEY_LENGTH, true); - byte[] iv = cipher.getIV(); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, LONG_KEY_LENGTH, true) + byte[] iv = cipher.getIV() logger.info("IV: ${Hex.encodeHexString(iv)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, LONG_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, LONG_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testShouldRejectEmptyPRF() throws Exception { + void testShouldRejectEmptyPRF() throws Exception { // Arrange RandomIVPBECipherProvider cipherProvider - final String PASSWORD = "shortPassword"; - final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]); - final byte[] IV = Hex.decodeHex(IV_HEX as char[]); + final String PASSWORD = "shortPassword" + final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]) + final byte[] IV = Hex.decodeHex(IV_HEX as char[]) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." final EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC String prf = "" // Act logger.info("Using PRF ${prf}") def msg = shouldFail(IllegalArgumentException) { - cipherProvider = new PBKDF2CipherProvider(prf, TEST_ITERATION_COUNT); + cipherProvider = new PBKDF2CipherProvider(prf, TEST_ITERATION_COUNT) } // Assert @@ -229,15 +229,15 @@ public class PBKDF2CipherProviderGroovyTest { } @Test - public void testShouldResolveDefaultPRF() throws Exception { + void testShouldResolveDefaultPRF() throws Exception { // Arrange RandomIVPBECipherProvider cipherProvider - final String PASSWORD = "shortPassword"; - final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]); - final byte[] IV = Hex.decodeHex(IV_HEX as char[]); + final String PASSWORD = "shortPassword" + final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]) + final byte[] IV = Hex.decodeHex(IV_HEX as char[]) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." final EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC final PBKDF2CipherProvider SHA512_PROVIDER = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT) @@ -246,117 +246,117 @@ public class PBKDF2CipherProviderGroovyTest { logger.info("Using ${prf}") // Act - cipherProvider = new PBKDF2CipherProvider(prf, TEST_ITERATION_COUNT); + cipherProvider = new PBKDF2CipherProvider(prf, TEST_ITERATION_COUNT) logger.info("Resolved PRF to ${cipherProvider.getPRFName()}") - logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}"); + logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true) logger.info("IV: ${Hex.encodeHexString(IV)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = SHA512_PROVIDER.getCipher(encryptionMethod, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = SHA512_PROVIDER.getCipher(encryptionMethod, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } @Test - public void testShouldResolveVariousPRFs() throws Exception { + void testShouldResolveVariousPRFs() throws Exception { // Arrange final List PRFS = ["SHA-1", "MD5", "SHA-256", "SHA-384", "SHA-512"] RandomIVPBECipherProvider cipherProvider - final String PASSWORD = "shortPassword"; - final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]); - final byte[] IV = Hex.decodeHex(IV_HEX as char[]); + final String PASSWORD = "shortPassword" + final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]) + final byte[] IV = Hex.decodeHex(IV_HEX as char[]) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." final EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC // Act PRFS.each { String prf -> logger.info("Using ${prf}") - cipherProvider = new PBKDF2CipherProvider(prf, TEST_ITERATION_COUNT); + cipherProvider = new PBKDF2CipherProvider(prf, TEST_ITERATION_COUNT) logger.info("Resolved PRF to ${cipherProvider.getPRFName()}") - logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}"); + logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true) logger.info("IV: ${Hex.encodeHexString(IV)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherShouldSupportExternalCompatibility() throws Exception { + void testGetCipherShouldSupportExternalCompatibility() throws Exception { // Arrange - RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider("SHA-256", TEST_ITERATION_COUNT); + RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider("SHA-256", TEST_ITERATION_COUNT) - final String PLAINTEXT = "This is a plaintext message."; - final String PASSWORD = "thisIsABadPassword"; + final String PLAINTEXT = "This is a plaintext message." + final String PASSWORD = "thisIsABadPassword" // These values can be generated by running `$ ./openssl_pbkdf2.rb` in the terminal - final byte[] SALT = Hex.decodeHex("ae2481bee3d8b5d5b732bf464ea2ff01" as char[]); - final byte[] IV = Hex.decodeHex("26db997dcd18472efd74dabe5ff36853" as char[]); + final byte[] SALT = Hex.decodeHex("ae2481bee3d8b5d5b732bf464ea2ff01" as char[]) + final byte[] IV = Hex.decodeHex("26db997dcd18472efd74dabe5ff36853" as char[]) final String CIPHER_TEXT = "92edbabae06add6275a1d64815755a9ba52afc96e2c1a316d3abbe1826e96f6c" byte[] cipherBytes = Hex.decodeHex(CIPHER_TEXT as char[]) EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC - logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}"); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}") + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") // Act - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert PLAINTEXT.equals(recovered); + assert PLAINTEXT.equals(recovered) } @Test - public void testGetCipherForDecryptShouldRequireIV() throws Exception { + void testGetCipherForDecryptShouldRequireIV() throws Exception { // Arrange - RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT); + RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT) - final String PASSWORD = "shortPassword"; - final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]); - final byte[] IV = Hex.decodeHex(IV_HEX as char[]); + final String PASSWORD = "shortPassword" + final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]) + final byte[] IV = Hex.decodeHex(IV_HEX as char[]) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : strongKDFEncryptionMethods) { - logger.info("Using algorithm: ${em.getAlgorithm()}"); + logger.info("Using algorithm: ${em.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true) logger.info("IV: ${Hex.encodeHexString(IV)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") def msg = shouldFail(IllegalArgumentException) { - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, false); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, false) } // Assert @@ -365,23 +365,23 @@ public class PBKDF2CipherProviderGroovyTest { } @Test - public void testGetCipherShouldRejectInvalidSalt() throws Exception { + void testGetCipherShouldRejectInvalidSalt() throws Exception { // Arrange RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT) - final String PASSWORD = "thisIsABadPassword"; + final String PASSWORD = "thisIsABadPassword" final def INVALID_SALTS = ['pbkdf2', '$3a$11$', 'x', '$2a$10$', '', null] EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC - logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}"); + logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}") // Act INVALID_SALTS.each { String salt -> logger.info("Checking salt ${salt}") def msg = shouldFail(IllegalArgumentException) { - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt?.bytes, DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt?.bytes, DEFAULT_KEY_LENGTH, true) } // Assert @@ -390,15 +390,15 @@ public class PBKDF2CipherProviderGroovyTest { } @Test - public void testGetCipherShouldAcceptValidKeyLengths() throws Exception { + void testGetCipherShouldAcceptValidKeyLengths() throws Exception { // Arrange RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT) - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]) - final byte[] IV = Hex.decodeHex(IV_HEX as char[]); + final byte[] IV = Hex.decodeHex(IV_HEX as char[]) - final String PLAINTEXT = "This is a plaintext message."; + final String PLAINTEXT = "This is a plaintext message." // Currently only AES ciphers are compatible with PBKDF2, so redundant to test all algorithms final def VALID_KEY_LENGTHS = AES_KEY_LENGTHS @@ -409,30 +409,30 @@ public class PBKDF2CipherProviderGroovyTest { logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()} with key length ${keyLength}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true) logger.info("IV: ${Hex.encodeHexString(IV)}") - byte[] cipherBytes = cipher.doFinal(PLAINTEXT.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(PLAINTEXT.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert PLAINTEXT.equals(recovered); + assert PLAINTEXT.equals(recovered) } } @Test - public void testGetCipherShouldNotAcceptInvalidKeyLengths() throws Exception { + void testGetCipherShouldNotAcceptInvalidKeyLengths() throws Exception { // Arrange - RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT); + RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT) - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = Hex.decodeHex(SALT_HEX as char[]) - final byte[] IV = Hex.decodeHex(IV_HEX as char[]); + final byte[] IV = Hex.decodeHex(IV_HEX as char[]) // Currently only AES ciphers are compatible with PBKDF2, so redundant to test all algorithms final def VALID_KEY_LENGTHS = [-1, 40, 64, 112, 512] @@ -444,7 +444,7 @@ public class PBKDF2CipherProviderGroovyTest { // Initialize a cipher for encryption def msg = shouldFail(IllegalArgumentException) { - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true) } // Assert @@ -454,9 +454,9 @@ public class PBKDF2CipherProviderGroovyTest { @Ignore("This test can be run on a specific machine to evaluate if the default iteration count is sufficient") @Test - public void testDefaultConstructorShouldProvideStrongIterationCount() { + void testDefaultConstructorShouldProvideStrongIterationCount() { // Arrange - RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(); + RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider() // Values taken from http://wildlyinaccurate.com/bcrypt-choosing-a-work-factor/ and http://security.stackexchange.com/questions/17207/recommended-of-rounds-for-bcrypt @@ -530,7 +530,7 @@ public class PBKDF2CipherProviderGroovyTest { } @Test - public void testGenerateSaltShouldProvideValidSalt() throws Exception { + void testGenerateSaltShouldProvideValidSalt() throws Exception { // Arrange RandomIVPBECipherProvider cipherProvider = new PBKDF2CipherProvider(DEFAULT_PRF, TEST_ITERATION_COUNT) diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/ScryptCipherProviderGroovyTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/ScryptCipherProviderGroovyTest.groovy similarity index 79% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/ScryptCipherProviderGroovyTest.groovy rename to nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/ScryptCipherProviderGroovyTest.groovy index 8fce9f2ca7..0e469abc75 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/ScryptCipherProviderGroovyTest.groovy +++ b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/crypto/ScryptCipherProviderGroovyTest.groovy @@ -43,20 +43,20 @@ import static groovy.test.GroovyAssert.shouldFail import static org.junit.Assert.assertTrue @RunWith(JUnit4.class) -public class ScryptCipherProviderGroovyTest { - private static final Logger logger = LoggerFactory.getLogger(ScryptCipherProviderGroovyTest.class); +class ScryptCipherProviderGroovyTest { + private static final Logger logger = LoggerFactory.getLogger(ScryptCipherProviderGroovyTest.class) private static List strongKDFEncryptionMethods - private static final int DEFAULT_KEY_LENGTH = 128; + private static final int DEFAULT_KEY_LENGTH = 128 public static final String MICROBENCHMARK = "microbenchmark" private static ArrayList AES_KEY_LENGTHS RandomIVPBECipherProvider cipherProvider @BeforeClass - public static void setUpOnce() throws Exception { - Security.addProvider(new BouncyCastleProvider()); + static void setUpOnce() throws Exception { + Security.addProvider(new BouncyCastleProvider()) strongKDFEncryptionMethods = EncryptionMethod.values().findAll { it.isCompatibleWithStrongKDFs() } @@ -64,7 +64,7 @@ public class ScryptCipherProviderGroovyTest { logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}") } - if (PasswordBasedEncryptor.supportsUnlimitedStrength()) { + if (CipherUtility.isUnlimitedStrengthCryptoSupported()) { AES_KEY_LENGTHS = [128, 192, 256] } else { AES_KEY_LENGTHS = [128] @@ -72,119 +72,119 @@ public class ScryptCipherProviderGroovyTest { } @Before - public void setUp() throws Exception { + void setUp() throws Exception { // Very fast parameters to test for correctness rather than production values cipherProvider = new ScryptCipherProvider(4, 1, 1) } @After - public void tearDown() throws Exception { + void tearDown() throws Exception { } @Test - public void testGetCipherShouldBeInternallyConsistent() throws Exception { + void testGetCipherShouldBeInternallyConsistent() throws Exception { // Arrange - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = cipherProvider.generateSalt() - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : strongKDFEncryptionMethods) { - logger.info("Using algorithm: ${em.getAlgorithm()}"); + logger.info("Using algorithm: ${em.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, true); - byte[] iv = cipher.getIV(); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, true) + byte[] iv = cipher.getIV() logger.info("IV: ${Hex.encodeHexString(iv)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, DEFAULT_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, DEFAULT_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherWithExternalIVShouldBeInternallyConsistent() throws Exception { + void testGetCipherWithExternalIVShouldBeInternallyConsistent() throws Exception { // Arrange - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = cipherProvider.generateSalt() - final byte[] IV = Hex.decodeHex("01" * 16 as char[]); + final byte[] IV = Hex.decodeHex("01" * 16 as char[]) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : strongKDFEncryptionMethods) { - logger.info("Using algorithm: ${em.getAlgorithm()}"); + logger.info("Using algorithm: ${em.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true) logger.info("IV: ${Hex.encodeHexString(IV)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testGetCipherWithUnlimitedStrengthShouldBeInternallyConsistent() throws Exception { + void testGetCipherWithUnlimitedStrengthShouldBeInternallyConsistent() throws Exception { // Arrange Assume.assumeTrue("Test is being skipped due to this JVM lacking JCE Unlimited Strength Jurisdiction Policy file.", - PasswordBasedEncryptor.supportsUnlimitedStrength()); + CipherUtility.isUnlimitedStrengthCryptoSupported()) - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = cipherProvider.generateSalt() final int LONG_KEY_LENGTH = 256 - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : strongKDFEncryptionMethods) { - logger.info("Using algorithm: ${em.getAlgorithm()}"); + logger.info("Using algorithm: ${em.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, LONG_KEY_LENGTH, true); - byte[] iv = cipher.getIV(); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, LONG_KEY_LENGTH, true) + byte[] iv = cipher.getIV() logger.info("IV: ${Hex.encodeHexString(iv)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, LONG_KEY_LENGTH, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, iv, LONG_KEY_LENGTH, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } } @Test - public void testScryptShouldSupportExternalCompatibility() throws Exception { + void testScryptShouldSupportExternalCompatibility() throws Exception { // Arrange // Default values are N=2^14, r=8, p=1, but the provided salt will contain the parameters used cipherProvider = new ScryptCipherProvider() - final String PLAINTEXT = "This is a plaintext message."; + final String PLAINTEXT = "This is a plaintext message." final String PASSWORD = "thisIsABadPassword" final int DK_LEN = 128 @@ -237,46 +237,46 @@ public class ScryptCipherProviderGroovyTest { logger.info("Converted hash from hex ${hashHex} to Base64 ${base64Hash}") assert Hex.encodeHexString(Base64.decodeBase64(base64Hash)) == hashHex - logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}"); - logger.info("External cipher text: ${CIPHER_TEXT} ${cipherBytes.length}"); + logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}") + logger.info("External cipher text: ${CIPHER_TEXT} ${cipherBytes.length}") // Act - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, javaSalt.bytes, IV, DK_LEN, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, javaSalt.bytes, IV, DK_LEN, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert PLAINTEXT.equals(recovered); + assert PLAINTEXT.equals(recovered) } @Test - public void testGetCipherShouldHandleSaltWithoutParameters() throws Exception { + void testGetCipherShouldHandleSaltWithoutParameters() throws Exception { // Arrange // To help Groovy resolve implementation private methods not known at interface level cipherProvider = cipherProvider as ScryptCipherProvider - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = new byte[cipherProvider.defaultSaltLength] new SecureRandom().nextBytes(SALT) final String EXPECTED_FORMATTED_SALT = cipherProvider.formatSaltForScrypt(SALT) logger.info("Expected salt: ${EXPECTED_FORMATTED_SALT}") - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC - logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}"); + logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}") // Act // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, DEFAULT_KEY_LENGTH, true); - byte[] iv = cipher.getIV(); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, DEFAULT_KEY_LENGTH, true) + byte[] iv = cipher.getIV() logger.info("IV: ${Hex.encodeHexString(iv)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") // Manually initialize a cipher for decrypt with the expected salt byte[] parsedSalt = new byte[cipherProvider.defaultSaltLength] @@ -287,31 +287,31 @@ public class ScryptCipherProviderGroovyTest { SecretKey key = new SecretKeySpec(keyBytes, "AES") Cipher manualCipher = Cipher.getInstance(encryptionMethod.algorithm, encryptionMethod.provider) manualCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)) - byte[] recoveredBytes = manualCipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + byte[] recoveredBytes = manualCipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert plaintext.equals(recovered); + assert plaintext.equals(recovered) } @Test - public void testGetCipherShouldNotAcceptInvalidSalts() throws Exception { + void testGetCipherShouldNotAcceptInvalidSalts() throws Exception { // Arrange - final String PASSWORD = "thisIsABadPassword"; + final String PASSWORD = "thisIsABadPassword" final def INVALID_SALTS = ['bad_sal', '$3a$11$', 'x', '$2a$10$', '$400$1$1$abcdefghijklmnopqrstuvwxyz'] final LENGTH_MESSAGE = "The raw salt must be between 8 and 32 bytes" EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC - logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}"); + logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}") // Act INVALID_SALTS.each { String salt -> logger.info("Checking salt ${salt}") def msg = shouldFail(IllegalArgumentException) { - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt.bytes, DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt.bytes, DEFAULT_KEY_LENGTH, true) } logger.expected(msg) @@ -321,20 +321,20 @@ public class ScryptCipherProviderGroovyTest { } @Test - public void testGetCipherShouldHandleUnformattedSalts() throws Exception { + void testGetCipherShouldHandleUnformattedSalts() throws Exception { // Arrange - final String PASSWORD = "thisIsABadPassword"; + final String PASSWORD = "thisIsABadPassword" final def RECOVERABLE_SALTS = ['$ab$00$acbdefghijklmnopqrstuv', '$4$1$1$0123456789abcdef', '$400$1$1$abcdefghijklmnopqrstuv'] EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC - logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}"); + logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}") // Act RECOVERABLE_SALTS.each { String salt -> logger.info("Checking salt ${salt}") - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt.bytes, DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, salt.bytes, DEFAULT_KEY_LENGTH, true) // Assert assert cipher @@ -342,16 +342,16 @@ public class ScryptCipherProviderGroovyTest { } @Test - public void testGetCipherShouldRejectEmptySalt() throws Exception { + void testGetCipherShouldRejectEmptySalt() throws Exception { // Arrange - final String PASSWORD = "thisIsABadPassword"; + final String PASSWORD = "thisIsABadPassword" EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC - logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}"); + logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()}") // Act def msg = shouldFail(IllegalArgumentException) { - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, new byte[0], DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, new byte[0], DEFAULT_KEY_LENGTH, true) } logger.expected(msg) @@ -360,27 +360,27 @@ public class ScryptCipherProviderGroovyTest { } @Test - public void testGetCipherForDecryptShouldRequireIV() throws Exception { + void testGetCipherForDecryptShouldRequireIV() throws Exception { // Arrange - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = cipherProvider.generateSalt() - final byte[] IV = Hex.decodeHex("00" * 16 as char[]); + final byte[] IV = Hex.decodeHex("00" * 16 as char[]) - final String plaintext = "This is a plaintext message."; + final String plaintext = "This is a plaintext message." // Act for (EncryptionMethod em : strongKDFEncryptionMethods) { - logger.info("Using algorithm: ${em.getAlgorithm()}"); + logger.info("Using algorithm: ${em.getAlgorithm()}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true); + Cipher cipher = cipherProvider.getCipher(em, PASSWORD, SALT, IV, DEFAULT_KEY_LENGTH, true) logger.info("IV: ${Hex.encodeHexString(IV)}") - byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") def msg = shouldFail(IllegalArgumentException) { - cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, false); + cipher = cipherProvider.getCipher(em, PASSWORD, SALT, DEFAULT_KEY_LENGTH, false) } logger.expected(msg) @@ -390,13 +390,13 @@ public class ScryptCipherProviderGroovyTest { } @Test - public void testGetCipherShouldAcceptValidKeyLengths() throws Exception { + void testGetCipherShouldAcceptValidKeyLengths() throws Exception { // Arrange - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = cipherProvider.generateSalt() - final byte[] IV = Hex.decodeHex("01" * 16 as char[]); + final byte[] IV = Hex.decodeHex("01" * 16 as char[]) - final String PLAINTEXT = "This is a plaintext message."; + final String PLAINTEXT = "This is a plaintext message." // Currently only AES ciphers are compatible with Bcrypt, so redundant to test all algorithms final def VALID_KEY_LENGTHS = AES_KEY_LENGTHS @@ -407,30 +407,30 @@ public class ScryptCipherProviderGroovyTest { logger.info("Using algorithm: ${encryptionMethod.getAlgorithm()} with key length ${keyLength}") // Initialize a cipher for encryption - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true) logger.info("IV: ${Hex.encodeHexString(IV)}") - byte[] cipherBytes = cipher.doFinal(PLAINTEXT.getBytes("UTF-8")); - logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}"); + byte[] cipherBytes = cipher.doFinal(PLAINTEXT.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") - cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, false); - byte[] recoveredBytes = cipher.doFinal(cipherBytes); - String recovered = new String(recoveredBytes, "UTF-8"); + cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") logger.info("Recovered: ${recovered}") // Assert - assert PLAINTEXT.equals(recovered); + assert PLAINTEXT.equals(recovered) } } @Test - public void testGetCipherShouldNotAcceptInvalidKeyLengths() throws Exception { + void testGetCipherShouldNotAcceptInvalidKeyLengths() throws Exception { // Arrange - final String PASSWORD = "shortPassword"; + final String PASSWORD = "shortPassword" final byte[] SALT = cipherProvider.generateSalt() - final byte[] IV = Hex.decodeHex("00" * 16 as char[]); + final byte[] IV = Hex.decodeHex("00" * 16 as char[]) - final String PLAINTEXT = "This is a plaintext message."; + final String PLAINTEXT = "This is a plaintext message." // Even though Scrypt can derive keys of arbitrary length, it will fail to validate if the underlying cipher does not support it final def INVALID_KEY_LENGTHS = [-1, 40, 64, 112, 512] @@ -443,7 +443,7 @@ public class ScryptCipherProviderGroovyTest { // Initialize a cipher for encryption def msg = shouldFail(IllegalArgumentException) { - Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true); + Cipher cipher = cipherProvider.getCipher(encryptionMethod, PASSWORD, SALT, IV, keyLength, true) } logger.expected(msg) @@ -453,7 +453,7 @@ public class ScryptCipherProviderGroovyTest { } @Test - public void testScryptShouldNotAcceptInvalidPassword() { + void testScryptShouldNotAcceptInvalidPassword() { // Arrange String badPassword = "" byte[] salt = [0x01 as byte] * 16 @@ -470,9 +470,9 @@ public class ScryptCipherProviderGroovyTest { } @Test - public void testGenerateSaltShouldUseProvidedParameters() throws Exception { + void testGenerateSaltShouldUseProvidedParameters() throws Exception { // Arrange - RandomIVPBECipherProvider cipherProvider = new ScryptCipherProvider(8, 2, 2); + RandomIVPBECipherProvider cipherProvider = new ScryptCipherProvider(8, 2, 2) int n = cipherProvider.getN() int r = cipherProvider.getR() int p = cipherProvider.getP() @@ -488,7 +488,7 @@ public class ScryptCipherProviderGroovyTest { } @Test - public void testShouldParseSalt() throws Exception { + void testShouldParseSalt() throws Exception { // Arrange cipherProvider = cipherProvider as ScryptCipherProvider @@ -498,7 +498,7 @@ public class ScryptCipherProviderGroovyTest { final int EXPECTED_P = 36 final String FORMATTED_SALT = "\$s0\$a0824\$9bgFbqbmbtuNATrEMquiSg" - logger.info("Using salt: ${FORMATTED_SALT}"); + logger.info("Using salt: ${FORMATTED_SALT}") byte[] rawSalt = new byte[16] def params = [] @@ -515,7 +515,7 @@ public class ScryptCipherProviderGroovyTest { @Ignore("This test can be run on a specific machine to evaluate if the default parameters are sufficient") @Test - public void testDefaultConstructorShouldProvideStrongParameters() { + void testDefaultConstructorShouldProvideStrongParameters() { // Arrange ScryptCipherProvider testCipherProvider = new ScryptCipherProvider() diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/scrypt/ScryptGroovyTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/scrypt/ScryptGroovyTest.groovy similarity index 92% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/scrypt/ScryptGroovyTest.groovy rename to nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/scrypt/ScryptGroovyTest.groovy index da34c494fb..416d670c52 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/scrypt/ScryptGroovyTest.groovy +++ b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/scrypt/ScryptGroovyTest.groovy @@ -14,9 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.nifi.security.util.crypto.scrypt +package org.apache.nifi.security.util.scrypt import org.apache.commons.codec.binary.Hex +import org.apache.nifi.security.util.crypto.scrypt.Scrypt import org.bouncycastle.jce.provider.BouncyCastleProvider import org.junit.After import org.junit.Assume @@ -35,7 +36,7 @@ import java.security.Security import static groovy.test.GroovyAssert.shouldFail @RunWith(JUnit4.class) -public class ScryptGroovyTest { +class ScryptGroovyTest { private static final Logger logger = LoggerFactory.getLogger(ScryptGroovyTest.class) private static final String PASSWORD = "shortPassword" @@ -50,7 +51,7 @@ public class ScryptGroovyTest { private static final long TWO_GIGABYTES = 2048L * 1024 * 1024 @BeforeClass - public static void setUpOnce() throws Exception { + static void setUpOnce() throws Exception { Security.addProvider(new BouncyCastleProvider()) logger.metaClass.methodMissing = { String name, args -> @@ -59,16 +60,16 @@ public class ScryptGroovyTest { } @Before - public void setUp() throws Exception { + void setUp() throws Exception { } @After - public void tearDown() throws Exception { + void tearDown() throws Exception { } @Test - public void testDeriveScryptKeyShouldBeInternallyConsistent() throws Exception { + void testDeriveScryptKeyShouldBeInternallyConsistent() throws Exception { // Arrange def allKeys = [] final int RUNS = 10 @@ -91,7 +92,7 @@ public class ScryptGroovyTest { * This test ensures that the local implementation of Scrypt is compatible with the reference implementation from the Colin Percival paper. */ @Test - public void testDeriveScryptKeyShouldMatchTestVectors() { + void testDeriveScryptKeyShouldMatchTestVectors() { // Arrange // These values are taken from Colin Percival's scrypt paper: https://www.tarsnap.com/scrypt/scrypt.pdf @@ -144,7 +145,7 @@ public class ScryptGroovyTest { * to the Java options, this overrides any IDE options. To ensure the heap is properly set, using the {@code groovyUnitTest} profile will re-append {@code -Xmx3072m} to the Java options. */ @Test - public void testDeriveScryptKeyShouldMatchExpensiveTestVector() { + void testDeriveScryptKeyShouldMatchExpensiveTestVector() { // Arrange long totalMemory = Runtime.getRuntime().totalMemory() logger.info("Required memory: ${TWO_GIGABYTES} bytes") @@ -194,7 +195,7 @@ public class ScryptGroovyTest { } @Test - public void testDeriveScryptKeyShouldSupportExternalCompatibility() { + void testDeriveScryptKeyShouldSupportExternalCompatibility() { // Arrange // These values can be generated by running `$ ./openssl_scrypt.rb` in the terminal @@ -220,7 +221,7 @@ public class ScryptGroovyTest { } @Test - public void testScryptShouldBeInternallyConsistent() throws Exception { + void testScryptShouldBeInternallyConsistent() throws Exception { // Arrange def allHashes = [] final int RUNS = 10 @@ -240,7 +241,7 @@ public class ScryptGroovyTest { } @Test - public void testScryptShouldGenerateValidSaltIfMissing() { + void testScryptShouldGenerateValidSaltIfMissing() { // Arrange // The generated salt should be byte[16], encoded as 22 Base64 chars @@ -255,7 +256,7 @@ public class ScryptGroovyTest { } @Test - public void testScryptShouldNotAcceptInvalidN() throws Exception { + void testScryptShouldNotAcceptInvalidN() throws Exception { // Arrange final int MAX_N = Integer.MAX_VALUE / 128 / R - 1 @@ -277,7 +278,7 @@ public class ScryptGroovyTest { } @Test - public void testScryptShouldAcceptValidR() throws Exception { + void testScryptShouldAcceptValidR() throws Exception { // Arrange // Use a large p value to allow r to exceed MAX_R without normal N exceeding MAX_N @@ -302,7 +303,7 @@ public class ScryptGroovyTest { } @Test - public void testScryptShouldNotAcceptInvalidP() throws Exception { + void testScryptShouldNotAcceptInvalidP() throws Exception { // Arrange final int MAX_P = Math.ceil(Integer.MAX_VALUE / 128) - 1 @@ -324,7 +325,7 @@ public class ScryptGroovyTest { } @Test - public void testCheckShouldValidateCorrectPassword() throws Exception { + void testCheckShouldValidateCorrectPassword() throws Exception { // Arrange final String PASSWORD = "thisIsABadPassword" final String EXPECTED_HASH = Scrypt.scrypt(PASSWORD, N, R, P, DK_LEN) @@ -339,7 +340,7 @@ public class ScryptGroovyTest { } @Test - public void testCheckShouldNotValidateIncorrectPassword() throws Exception { + void testCheckShouldNotValidateIncorrectPassword() throws Exception { // Arrange final String PASSWORD = "thisIsABadPassword" final String EXPECTED_HASH = Scrypt.scrypt(PASSWORD, N, R, P, DK_LEN) @@ -354,7 +355,7 @@ public class ScryptGroovyTest { } @Test - public void testCheckShouldNotAcceptInvalidPassword() throws Exception { + void testCheckShouldNotAcceptInvalidPassword() throws Exception { // Arrange final String HASH = '$s0$a0801$abcdefghijklmnopqrstuv$abcdefghijklmnopqrstuv' @@ -376,7 +377,7 @@ public class ScryptGroovyTest { } @Test - public void testCheckShouldNotAcceptInvalidHash() throws Exception { + void testCheckShouldNotAcceptInvalidHash() throws Exception { // Arrange final String PASSWORD = "thisIsABadPassword" diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/openssl_aes.rb b/nifi-commons/nifi-security-utils/src/test/resources/openssl_aes.rb similarity index 100% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/openssl_aes.rb rename to nifi-commons/nifi-security-utils/src/test/resources/openssl_aes.rb diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/openssl_bcrypt.rb b/nifi-commons/nifi-security-utils/src/test/resources/openssl_bcrypt.rb similarity index 100% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/openssl_bcrypt.rb rename to nifi-commons/nifi-security-utils/src/test/resources/openssl_bcrypt.rb diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/openssl_pbkdf2.rb b/nifi-commons/nifi-security-utils/src/test/resources/openssl_pbkdf2.rb similarity index 100% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/openssl_pbkdf2.rb rename to nifi-commons/nifi-security-utils/src/test/resources/openssl_pbkdf2.rb diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/openssl_scrypt.rb b/nifi-commons/nifi-security-utils/src/test/resources/openssl_scrypt.rb similarity index 100% rename from nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/openssl_scrypt.rb rename to nifi-commons/nifi-security-utils/src/test/resources/openssl_scrypt.rb diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework-nar/src/main/resources/META-INF/NOTICE b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework-nar/src/main/resources/META-INF/NOTICE index 099d25b672..22c9ba94b1 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework-nar/src/main/resources/META-INF/NOTICE +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework-nar/src/main/resources/META-INF/NOTICE @@ -40,10 +40,6 @@ The following binary components are provided under the Apache Software License v The following NOTICE information applies: Copyright 2006 Envoi Solutions LLC - (ASLv2) Jasypt - The following NOTICE information applies: - Copyright (c) 2007-2010, The JASYPT team (http://www.jasypt.org) - (ASLv2) Apache Commons Codec The following NOTICE information applies: Apache Commons Codec diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml index 4989be6718..edfa5a38e9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml @@ -99,10 +99,6 @@ com.h2database h2 - - org.jasypt - jasypt - org.bouncycastle bcprov-jdk15on @@ -166,7 +162,11 @@ testng test - + + org.jasypt + jasypt + test + org.apache.nifi nifi-mock diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowFromDOMFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowFromDOMFactory.java index f30a71e724..d4550435ce 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowFromDOMFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowFromDOMFactory.java @@ -48,7 +48,6 @@ import org.apache.nifi.web.api.dto.ProcessorConfigDTO; import org.apache.nifi.web.api.dto.ProcessorDTO; import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO; import org.apache.nifi.web.api.dto.ReportingTaskDTO; -import org.jasypt.exceptions.EncryptionOperationNotPossibleException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; @@ -508,7 +507,7 @@ public class FlowFromDOMFactory { if (value != null && value.startsWith(FlowSerializer.ENC_PREFIX) && value.endsWith(FlowSerializer.ENC_SUFFIX)) { try { return encryptor.decrypt(value.substring(FlowSerializer.ENC_PREFIX.length(), value.length() - FlowSerializer.ENC_SUFFIX.length())); - } catch (EncryptionException | EncryptionOperationNotPossibleException e) { + } catch (EncryptionException e) { final String moreDescriptiveMessage = "There was a problem decrypting a sensitive flow configuration value. " + "Check that the nifi.sensitive.props.key value in nifi.properties matches the value used to encrypt the flow.xml.gz file"; logger.error(moreDescriptiveMessage, e); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/encrypt/StringEncryptor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/encrypt/StringEncryptor.java index dc0e7c7bec..2f18a16cd5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/encrypt/StringEncryptor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/encrypt/StringEncryptor.java @@ -16,23 +16,43 @@ */ package org.apache.nifi.encrypt; +import java.nio.charset.StandardCharsets; +import java.security.Provider; +import java.security.SecureRandom; import java.security.Security; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.crypto.Cipher; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.security.kms.CryptoUtils; +import org.apache.nifi.security.util.EncryptionMethod; +import org.apache.nifi.security.util.KeyDerivationFunction; +import org.apache.nifi.security.util.crypto.CipherProvider; +import org.apache.nifi.security.util.crypto.CipherProviderFactory; +import org.apache.nifi.security.util.crypto.CipherUtility; +import org.apache.nifi.security.util.crypto.KeyedCipherProvider; +import org.apache.nifi.security.util.crypto.NiFiLegacyCipherProvider; +import org.apache.nifi.security.util.crypto.PBECipherProvider; import org.apache.nifi.util.NiFiProperties; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.jasypt.encryption.pbe.StandardPBEStringEncryptor; -import org.jasypt.exceptions.EncryptionInitializationException; -import org.jasypt.exceptions.EncryptionOperationNotPossibleException; +import org.bouncycastle.util.encoders.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** *

* An application specific string encryptor that collects configuration from the * application properties, system properties, and/or system environment. *

- * + *

*

* Instance of this class are thread-safe

- * + *

*

* The encryption provider and algorithm is configured using the application * properties: @@ -41,95 +61,298 @@ import org.jasypt.exceptions.EncryptionOperationNotPossibleException; *

  • nifi.sensitive.props.algorithm
  • * *

    - * + *

    *

    * The encryptor's password may be set by configuring the below property: *

      *
    • nifi.sensitive.props.key
    • *
    *

    - * */ -public final class StringEncryptor { +public class StringEncryptor { + private static final Logger logger = LoggerFactory.getLogger(StringEncryptor.class); + + private static final List SUPPORTED_ALGORITHMS = new ArrayList<>(); + private static final List SUPPORTED_PROVIDERS = new ArrayList<>(); + + private final String algorithm; + private final String provider; + private final PBEKeySpec password; + private final SecretKeySpec key; + + private String encoding = "HEX"; + + private CipherProvider cipherProvider; static { Security.addProvider(new BouncyCastleProvider()); + + for (EncryptionMethod em : EncryptionMethod.values()) { + SUPPORTED_ALGORITHMS.add(em.getAlgorithm()); + } + logger.debug("Supported encryption algorithms: " + StringUtils.join(SUPPORTED_ALGORITHMS, "\n")); + + for (Provider provider : Security.getProviders()) { + SUPPORTED_PROVIDERS.add(provider.getName()); + } + logger.debug("Supported providers: " + StringUtils.join(SUPPORTED_PROVIDERS, "\n")); } public static final String NF_SENSITIVE_PROPS_KEY = "nifi.sensitive.props.key"; public static final String NF_SENSITIVE_PROPS_ALGORITHM = "nifi.sensitive.props.algorithm"; public static final String NF_SENSITIVE_PROPS_PROVIDER = "nifi.sensitive.props.provider"; private static final String DEFAULT_SENSITIVE_PROPS_KEY = "nififtw!"; - private static final String TEST_PLAINTEXT = "this is a test"; - private final StandardPBEStringEncryptor encryptor; - private StringEncryptor(final String aglorithm, final String provider, final String key) { - encryptor = new StandardPBEStringEncryptor(); - encryptor.setAlgorithm(aglorithm); - encryptor.setProviderName(provider); - encryptor.setPassword(key); - encryptor.setStringOutputType("hexadecimal"); - encryptor.initialize(); + /** + * This constructor creates an encryptor using Password-Based Encryption (PBE). The key value is the direct value provided in nifi.sensitive.props.key in + * nifi.properties, which is a PASSWORD rather than a KEY, but is named such for backward/legacy logical compatibility throughout the rest of the codebase. + *

    + * For actual raw key provision, see {@link #StringEncryptor(String, String, byte[])}. + * + * @param algorithm the PBE cipher algorithm ({@link EncryptionMethod#algorithm}) + * @param provider the JCA Security provider ({@link EncryptionMethod#provider}) + * @param key the UTF-8 characters from nifi.properties -- nifi.sensitive.props.key + */ + protected StringEncryptor(final String algorithm, final String provider, final String key) { + this.algorithm = algorithm; + this.provider = provider; + this.key = null; + this.password = new PBEKeySpec(key == null + ? DEFAULT_SENSITIVE_PROPS_KEY.toCharArray() + : key.toCharArray()); + initialize(); } /** - * Creates an instance of the nifi sensitive property encryptor. Validates - * that the encryptor is actually working. + * This constructor creates an encryptor using Keyed Encryption. The key value is the raw byte value of a symmetric encryption key + * (usually expressed for human-readability/transmission in hexadecimal or Base64 encoded format). + * + * @param algorithm the PBE cipher algorithm ({@link EncryptionMethod#algorithm}) + * @param provider the JCA Security provider ({@link EncryptionMethod#provider}) + * @param key a raw encryption key in bytes + */ + public StringEncryptor(final String algorithm, final String provider, final byte[] key) { + this.algorithm = algorithm; + this.provider = provider; + this.key = new SecretKeySpec(key, extractKeyTypeFromAlgorithm(algorithm)); + this.password = null; + initialize(); + } + + /** + * A default constructor for mocking during testing. + */ + protected StringEncryptor() { + this.algorithm = null; + this.provider = null; + this.key = null; + this.password = null; + } + + /** + * Extracts the cipher "family" (i.e. "AES", "DES", "RC4") from the full algorithm name. + * + * @param algorithm the algorithm ({@link EncryptionMethod#algorithm}) + * @return the cipher family + * @throws EncryptionException if the algorithm is null/empty or not supported + */ + private String extractKeyTypeFromAlgorithm(String algorithm) throws EncryptionException { + if (StringUtils.isBlank(algorithm)) { + throw new EncryptionException("The algorithm cannot be null or empty"); + } + String parsedCipher = CipherUtility.parseCipherFromAlgorithm(algorithm); + if (parsedCipher.equals(algorithm)) { + throw new EncryptionException("No supported algorithm detected"); + } else { + return parsedCipher; + } + } + + /** + * Creates an instance of the NiFi sensitive property encryptor. * * @param niFiProperties properties * @return encryptor * @throws EncryptionException if any issues arise initializing or - * validating the encryptor + * validating the encryptor + * @see #createEncryptor(String, String, String) + * @deprecated as of NiFi 1.4.0 because the entire {@link NiFiProperties} object is not necessary to generate the encryptor. */ + @Deprecated public static StringEncryptor createEncryptor(final NiFiProperties niFiProperties) throws EncryptionException { - Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + // Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); final String sensitivePropAlgorithmVal = niFiProperties.getProperty(NF_SENSITIVE_PROPS_ALGORITHM); final String sensitivePropProviderVal = niFiProperties.getProperty(NF_SENSITIVE_PROPS_PROVIDER); final String sensitivePropValueNifiPropVar = niFiProperties.getProperty(NF_SENSITIVE_PROPS_KEY, DEFAULT_SENSITIVE_PROPS_KEY); - if (StringUtils.isBlank(sensitivePropAlgorithmVal)) { - throw new EncryptionException(NF_SENSITIVE_PROPS_ALGORITHM + "must bet set"); + return createEncryptor(sensitivePropAlgorithmVal, sensitivePropProviderVal, sensitivePropValueNifiPropVar); + } + + /** + * Creates an instance of the NiFi sensitive property encryptor. + * + * @param algorithm the encryption (and key derivation) algorithm ({@link EncryptionMethod#algorithm}) + * @param provider the JCA Security provider ({@link EncryptionMethod#provider}) + * @param password the UTF-8 characters from nifi.properties -- nifi.sensitive.props.key + * @return the initialized encryptor + */ + public static StringEncryptor createEncryptor(String algorithm, String provider, String password) { + if (StringUtils.isBlank(algorithm)) { + throw new EncryptionException(NF_SENSITIVE_PROPS_ALGORITHM + " must be set"); } - if (StringUtils.isBlank(sensitivePropProviderVal)) { - throw new EncryptionException(NF_SENSITIVE_PROPS_PROVIDER + "must bet set"); + if (StringUtils.isBlank(provider)) { + throw new EncryptionException(NF_SENSITIVE_PROPS_PROVIDER + " must be set"); } - if (StringUtils.isBlank(sensitivePropValueNifiPropVar)) { - throw new EncryptionException(NF_SENSITIVE_PROPS_KEY + "must bet set"); + if (StringUtils.isBlank(password)) { + throw new EncryptionException(NF_SENSITIVE_PROPS_KEY + " must be set"); } - final StringEncryptor nifiEncryptor; - try { - nifiEncryptor = new StringEncryptor(sensitivePropAlgorithmVal, sensitivePropProviderVal, sensitivePropValueNifiPropVar); - //test that we can infact encrypt and decrypt something - if (!nifiEncryptor.decrypt(nifiEncryptor.encrypt(TEST_PLAINTEXT)).equals(TEST_PLAINTEXT)) { - throw new EncryptionException("NiFi property encryptor does appear to be working - decrypt/encrypt return invalid results"); + return new StringEncryptor(algorithm, provider, password); + } + + protected void initialize() { + if (isInitialized()) { + logger.debug("Attempted to initialize an already-initialized StringEncryptor"); + return; + } + + if (paramsAreValid()) { + if (CipherUtility.isPBECipher(algorithm)) { + cipherProvider = CipherProviderFactory.getCipherProvider(KeyDerivationFunction.NIFI_LEGACY); + } else { + cipherProvider = CipherProviderFactory.getCipherProvider(KeyDerivationFunction.NONE); } - - } catch (final EncryptionInitializationException | EncryptionOperationNotPossibleException ex) { - throw new EncryptionException("Cannot initialize sensitive property encryptor", ex); - + } else { + throw new EncryptionException("Cannot initialize the StringEncryptor because some configuration values are invalid"); + } + } + + private boolean paramsAreValid() { + boolean algorithmAndProviderValid = algorithmIsValid(algorithm) && providerIsValid(provider); + boolean secretIsValid = false; + if (CipherUtility.isPBECipher(algorithm)) { + secretIsValid = passwordIsValid(password); + } else if (CipherUtility.isKeyedCipher(algorithm)) { + secretIsValid = keyIsValid(key, algorithm); + } + + return algorithmAndProviderValid && secretIsValid; + } + + private boolean keyIsValid(SecretKeySpec key, String algorithm) { + return key != null && CipherUtility.getValidKeyLengthsForAlgorithm(algorithm).contains(key.getEncoded().length * 8); + } + + private boolean passwordIsValid(PBEKeySpec password) { + try { + return password.getPassword() != null; + } catch (IllegalStateException | NullPointerException e) { + return false; + } + } + + public void setEncoding(String base) { + if ("HEX".equalsIgnoreCase(base)) { + this.encoding = "HEX"; + } else if ("BASE64".equalsIgnoreCase(base)) { + this.encoding = "BASE64"; + } else { + throw new IllegalArgumentException("The encoding base must be 'HEX' or 'BASE64'"); } - return nifiEncryptor; } /** * Encrypts the given clear text. * * @param clearText the message to encrypt - * * @return the cipher text - * * @throws EncryptionException if the encrypt fails */ public String encrypt(String clearText) throws EncryptionException { try { - return encryptor.encrypt(clearText); - } catch (final EncryptionOperationNotPossibleException | EncryptionInitializationException eonpe) { - throw new EncryptionException(eonpe); + if (isInitialized()) { + byte[] rawBytes; + if (CipherUtility.isPBECipher(algorithm)) { + rawBytes = encryptPBE(clearText); + } else { + rawBytes = encryptKeyed(clearText); + } + return encode(rawBytes); + } else { + throw new EncryptionException("The encryptor is not initialized"); + } + } catch (final Exception e) { + throw new EncryptionException(e); + } + } + + private byte[] encryptPBE(String plaintext) { + PBECipherProvider pbecp = (PBECipherProvider) cipherProvider; + final EncryptionMethod encryptionMethod = EncryptionMethod.forAlgorithm(algorithm); + + // Generate salt + byte[] salt; + // NiFi legacy code determined the salt length based on the cipher block size + if (pbecp instanceof NiFiLegacyCipherProvider) { + salt = ((NiFiLegacyCipherProvider) pbecp).generateSalt(encryptionMethod); + } else { + salt = pbecp.generateSalt(); + } + + // Determine necessary key length + int keyLength = CipherUtility.parseKeyLengthFromAlgorithm(algorithm); + + // Generate cipher + try { + Cipher cipher = pbecp.getCipher(encryptionMethod, new String(password.getPassword()), salt, keyLength, true); + + // Write IV if necessary (allows for future use of PBKDF2, Bcrypt, or Scrypt) + // byte[] iv = new byte[0]; + // if (cipherProvider instanceof RandomIVPBECipherProvider) { + // iv = cipher.getIV(); + // } + + // Encrypt the plaintext + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); + + // Combine the output + // byte[] rawBytes = CryptoUtils.concatByteArrays(salt, iv, cipherBytes); + return CryptoUtils.concatByteArrays(salt, cipherBytes); + } catch (Exception e) { + throw new EncryptionException("Could not encrypt sensitive value", e); + } + } + + private byte[] encryptKeyed(String plaintext) { + KeyedCipherProvider keyedcp = (KeyedCipherProvider) cipherProvider; + + // Generate cipher + try { + SecureRandom sr = new SecureRandom(); + byte[] iv = new byte[16]; + sr.nextBytes(iv); + + Cipher cipher = keyedcp.getCipher(EncryptionMethod.forAlgorithm(algorithm), key, iv, true); + + // Encrypt the plaintext + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); + + // Combine the output + return CryptoUtils.concatByteArrays(iv, cipherBytes); + } catch (Exception e) { + throw new EncryptionException("Could not encrypt sensitive value", e); + } + } + + private String encode(byte[] rawBytes) { + if (this.encoding.equalsIgnoreCase("HEX")) { + return Hex.encodeHexString(rawBytes); + } else { + return Base64.toBase64String(rawBytes); } } @@ -137,17 +360,96 @@ public final class StringEncryptor { * Decrypts the given cipher text. * * @param cipherText the message to decrypt - * * @return the clear text - * * @throws EncryptionException if the decrypt fails */ public String decrypt(String cipherText) throws EncryptionException { try { - return encryptor.decrypt(cipherText); - } catch (final EncryptionOperationNotPossibleException | EncryptionInitializationException eonpe) { - throw new EncryptionException(eonpe); + if (isInitialized()) { + byte[] plainBytes; + byte[] cipherBytes = decode(cipherText); + if (CipherUtility.isPBECipher(algorithm)) { + plainBytes = decryptPBE(cipherBytes); + } else { + plainBytes = decryptKeyed(cipherBytes); + } + return new String(plainBytes, StandardCharsets.UTF_8); + } else { + throw new EncryptionException("The encryptor is not initialized"); + } + } catch (final Exception e) { + throw new EncryptionException(e); } } + private byte[] decryptPBE(byte[] cipherBytes) throws DecoderException { + PBECipherProvider pbecp = (PBECipherProvider) cipherProvider; + final EncryptionMethod encryptionMethod = EncryptionMethod.forAlgorithm(algorithm); + + // Extract salt + int saltLength = CipherUtility.getSaltLengthForAlgorithm(algorithm); + byte[] salt = new byte[saltLength]; + System.arraycopy(cipherBytes, 0, salt, 0, saltLength); + + byte[] actualCipherBytes = Arrays.copyOfRange(cipherBytes, saltLength, cipherBytes.length); + + // Determine necessary key length + int keyLength = CipherUtility.parseKeyLengthFromAlgorithm(algorithm); + + // Generate cipher + try { + Cipher cipher = pbecp.getCipher(encryptionMethod, new String(password.getPassword()), salt, keyLength, false); + + // Write IV if necessary (allows for future use of PBKDF2, Bcrypt, or Scrypt) + // byte[] iv = new byte[0]; + // if (cipherProvider instanceof RandomIVPBECipherProvider) { + // iv = cipher.getIV(); + // } + + // Decrypt the plaintext + return cipher.doFinal(actualCipherBytes); + } catch (Exception e) { + throw new EncryptionException("Could not decrypt sensitive value", e); + } + } + + private byte[] decryptKeyed(byte[] cipherBytes) { + KeyedCipherProvider keyedcp = (KeyedCipherProvider) cipherProvider; + + // Generate cipher + try { + int ivLength = 16; + byte[] iv = new byte[ivLength]; + System.arraycopy(cipherBytes, 0, iv, 0, ivLength); + + byte[] actualCipherBytes = Arrays.copyOfRange(cipherBytes, ivLength, cipherBytes.length); + + Cipher cipher = keyedcp.getCipher(EncryptionMethod.forAlgorithm(algorithm), key, iv, false); + + // Encrypt the plaintext + return cipher.doFinal(actualCipherBytes); + } catch (Exception e) { + throw new EncryptionException("Could not decrypt sensitive value", e); + } + } + + private byte[] decode(String encoded) throws DecoderException { + if (this.encoding.equalsIgnoreCase("HEX")) { + return Hex.decodeHex(encoded.toCharArray()); + } else { + return Base64.decode(encoded); + } + } + + public boolean isInitialized() { + return this.cipherProvider != null; + } + + protected static boolean algorithmIsValid(String algorithm) { + return SUPPORTED_ALGORITHMS.contains(algorithm); + } + + protected static boolean providerIsValid(String provider) { + return SUPPORTED_PROVIDERS.contains(provider); + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/encrypt/StringEncryptorTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/encrypt/StringEncryptorTest.groovy new file mode 100644 index 0000000000..37e03023b8 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/encrypt/StringEncryptorTest.groovy @@ -0,0 +1,482 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License") you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.encrypt + +import org.apache.commons.codec.binary.Hex +import org.apache.nifi.properties.StandardNiFiProperties +import org.apache.nifi.security.kms.CryptoUtils +import org.apache.nifi.security.util.EncryptionMethod +import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider +import org.apache.nifi.security.util.crypto.CipherUtility +import org.apache.nifi.security.util.crypto.KeyedCipherProvider +import org.apache.nifi.util.NiFiProperties +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.jasypt.encryption.pbe.StandardPBEStringEncryptor +import org.jasypt.encryption.pbe.config.PBEConfig +import org.jasypt.salt.SaltGenerator +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import javax.crypto.Cipher +import javax.crypto.SecretKey +import javax.crypto.SecretKeyFactory +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.PBEKeySpec +import javax.crypto.spec.PBEParameterSpec +import javax.crypto.spec.SecretKeySpec +import java.security.SecureRandom +import java.security.Security + +import static groovy.test.GroovyAssert.shouldFail + +@RunWith(JUnit4.class) +class StringEncryptorTest { + private static final Logger logger = LoggerFactory.getLogger(StringEncryptorTest.class) + + private static final String KEY_HEX = "0123456789ABCDEFFEDCBA9876543210" + + private static final List keyedEncryptionMethods = EncryptionMethod.values().findAll { + it.keyedCipher + } + private static final List pbeEncryptionMethods = EncryptionMethod.values().findAll { + it.algorithm =~ "PBE" + } + + // Unlimited elements are removed in static initializer + private static final List limitedPbeEncryptionMethods = pbeEncryptionMethods + + private static final SecretKey key = new SecretKeySpec(Hex.decodeHex(KEY_HEX as char[]), "AES") + + private static final String KEY = "nifi.sensitive.props.key" + private static final String ALGORITHM = "nifi.sensitive.props.algorithm" + private static final String PROVIDER = "nifi.sensitive.props.provider" + + private static final String DEFAULT_ALGORITHM = "PBEWITHMD5AND128BITAES-CBC-OPENSSL" + private static final String DEFAULT_PROVIDER = "BC" + private static final String DEFAULT_PASSWORD = "nififtw!" + private static final String OTHER_PASSWORD = "thisIsABadPassword" + private static + final Map RAW_PROPERTIES = [(ALGORITHM): DEFAULT_ALGORITHM, (PROVIDER): DEFAULT_PROVIDER, (KEY): DEFAULT_PASSWORD] + private static final NiFiProperties STANDARD_PROPERTIES = new StandardNiFiProperties(new Properties(RAW_PROPERTIES)) + + private static final byte[] DEFAULT_SALT = new byte[8] + private static final byte[] DEFAULT_IV = new byte[16] + private static final int DEFAULT_ITERATION_COUNT = 0 + + @BeforeClass + static void setUpOnce() throws Exception { + Security.addProvider(new BouncyCastleProvider()) + + limitedPbeEncryptionMethods.removeAll { it.algorithm =~ "SHA.*(CBC)?"} + + logger.metaClass.methodMissing = { String name, args -> + logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}") + } + } + + @Before + void setUp() throws Exception { + } + + @After + void tearDown() throws Exception { + } + + private static boolean isUnlimitedStrengthCryptoAvailable() { + Cipher.getMaxAllowedKeyLength("AES") > 128 + } + + private + static Cipher generatePBECipher(boolean encryptMode, EncryptionMethod em = EncryptionMethod.MD5_128AES, String password = DEFAULT_PASSWORD, byte[] salt = DEFAULT_SALT, int iterationCount = DEFAULT_ITERATION_COUNT) { + // Initialize secret key from password + final PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()) + final SecretKeyFactory factory = SecretKeyFactory.getInstance(em.algorithm, em.provider) + SecretKey tempKey = factory.generateSecret(pbeKeySpec) + + final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, iterationCount) + Cipher cipher = Cipher.getInstance(em.algorithm, em.provider) + cipher.init((encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE) as int, tempKey, parameterSpec) + cipher + } + + private + static Cipher generateKeyedCipher(boolean encryptMode, EncryptionMethod em = EncryptionMethod.MD5_128AES, String keyHex = KEY_HEX, byte[] iv = DEFAULT_IV) { + SecretKey tempKey = new SecretKeySpec(Hex.decodeHex(keyHex as char[]), CipherUtility.parseCipherFromAlgorithm(em.algorithm)) + + IvParameterSpec ivSpec = new IvParameterSpec(iv) + Cipher cipher = Cipher.getInstance(em.algorithm, em.provider) + cipher.init((encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE) as int, tempKey, ivSpec) + cipher + } + + @Test + void testPBEncryptionShouldBeInternallyConsistent() throws Exception { + // Arrange + final String plaintext = "This is a plaintext message." + + // Act + for (EncryptionMethod em : limitedPbeEncryptionMethods) { + logger.info("Using algorithm: ${em.getAlgorithm()}") + NiFiProperties niFiProperties = new StandardNiFiProperties(new Properties(RAW_PROPERTIES + [(ALGORITHM): em.algorithm])) + StringEncryptor encryptor = StringEncryptor.createEncryptor(niFiProperties) + + String cipherText = encryptor.encrypt(plaintext) + logger.info("Cipher text: ${cipherText}") + + String recovered = encryptor.decrypt(cipherText) + logger.info("Recovered: ${recovered}") + + // Assert + assert plaintext == recovered + } + } + + @Test + void testPBEncryptionShouldBeExternallyConsistent() throws Exception { + // Arrange + final String plaintext = "This is a plaintext message." + + for (EncryptionMethod em : pbeEncryptionMethods) { + + // Hard-coded 0x00 * 16 + byte[] salt = new byte[16] + int iterationCount = DEFAULT_ITERATION_COUNT + // DES/RC* algorithms use 8 byte salts and custom iteration counts + if (em.algorithm =~ "DES|RC") { + salt = new byte[8] + iterationCount = 1000 + } else if (em.algorithm =~ "SHAA|SHA256") { + // SHA-1/-256 use 16 byte salts but custom iteration counts + iterationCount = 1000 + } + logger.info("Using algorithm: ${em.getAlgorithm()} with ${salt.length} byte salt and ${iterationCount} iterations") + + // Encrypt the value manually + Cipher cipher = generatePBECipher(true, em, DEFAULT_PASSWORD, salt, iterationCount) + + byte[] cipherBytes = cipher.doFinal(plaintext.bytes) + byte[] saltAndCipherBytes = CryptoUtils.concatByteArrays(salt, cipherBytes) + String cipherTextHex = Hex.encodeHexString(saltAndCipherBytes) + logger.info("Cipher text: ${cipherTextHex}") + + NiFiProperties niFiProperties = new StandardNiFiProperties(new Properties(RAW_PROPERTIES + [(ALGORITHM): em.algorithm])) + StringEncryptor encryptor = StringEncryptor.createEncryptor(niFiProperties) + + // Act + String recovered = encryptor.decrypt(cipherTextHex) + logger.info("Recovered: ${recovered}") + + // Assert + assert plaintext == recovered + } + } + + /** + * This test uses the Jasypt library {@see StandardPBEStringEncryptor} to encrypt raw messages as the legacy (pre-1.4.0) NiFi application did. Then the messages are decrypted with the "new"/current primitive implementation to ensure backward compatibility. This test method only exercises limited strength key sizes (even this is not technically accurate as the SHA KDF is restricted even when using 128-bit AES). + * + * @throws Exception + */ + @Test + void testLimitedPBEncryptionShouldBeConsistentWithLegacyEncryption() throws Exception { + // Arrange + final String plaintext = "This is a plaintext message." + + for (EncryptionMethod em : limitedPbeEncryptionMethods) { + + // Hard-coded 0x00 * 16 + byte[] salt = new byte[16] + // DES/RC* algorithms use 8 byte salts + if (em.algorithm =~ "DES|RC") { + salt = new byte[8] + } + logger.info("Using algorithm: ${em.getAlgorithm()} with ${salt.length} byte salt") + + StandardPBEStringEncryptor legacyEncryptor = new StandardPBEStringEncryptor() + SaltGenerator mockSaltGenerator = [generateSalt: { int l -> + logger.mock("Generating ${l} byte salt") + new byte[l] + }, includePlainSaltInEncryptionResults : { + -> true + }] as SaltGenerator + PBEConfig mockConfig = [getAlgorithm : { -> em.algorithm }, + getPassword : { -> DEFAULT_PASSWORD }, + getKeyObtentionIterations: { -> 1000 }, + getProviderName : { -> em.provider }, + getProvider : { -> new BouncyCastleProvider() }, + getSaltGenerator : { -> mockSaltGenerator } + ] as PBEConfig + legacyEncryptor.setConfig(mockConfig) + legacyEncryptor.setStringOutputType("hexadecimal") + + String cipherText = legacyEncryptor.encrypt(plaintext) + logger.info("Cipher text: ${cipherText}") + + NiFiProperties niFiProperties = new StandardNiFiProperties(new Properties(RAW_PROPERTIES + [(ALGORITHM): em.algorithm])) + StringEncryptor encryptor = StringEncryptor.createEncryptor(niFiProperties) + + // Act + String recovered = encryptor.decrypt(cipherText) + logger.info("Recovered: ${recovered}") + + // Assert + assert plaintext == recovered + } + } + + /** + * This test uses the Jasypt library {@see StandardPBEStringEncryptor} to encrypt raw messages as the legacy (pre-1.4.0) NiFi application did. Then the messages are decrypted with the "new"/current primitive implementation to ensure backward compatibility. This test method exercises all strength key sizes. + * + * @throws Exception + */ + @Test + void testPBEncryptionShouldBeConsistentWithLegacyEncryption() throws Exception { + // Arrange + Assume.assumeTrue("Test is being skipped due to this JVM lacking JCE Unlimited Strength Jurisdiction Policy file.", isUnlimitedStrengthCryptoAvailable()) + + final String plaintext = "This is a plaintext message." + + for (EncryptionMethod em : pbeEncryptionMethods) { + + // Hard-coded 0x00 * 16 + byte[] salt = new byte[16] + // DES/RC* algorithms use 8 byte salts + if (em.algorithm =~ "DES|RC") { + salt = new byte[8] + } + logger.info("Using algorithm: ${em.getAlgorithm()} with ${salt.length} byte salt") + + StandardPBEStringEncryptor legacyEncryptor = new StandardPBEStringEncryptor() + SaltGenerator mockSaltGenerator = [generateSalt: { int l -> + logger.mock("Generating ${l} byte salt") + new byte[l] + }, includePlainSaltInEncryptionResults : { + -> true + }] as SaltGenerator + PBEConfig mockConfig = [getAlgorithm : { -> em.algorithm }, + getPassword : { -> DEFAULT_PASSWORD }, + getKeyObtentionIterations: { -> 1000 }, + getProviderName : { -> em.provider }, + getProvider : { -> new BouncyCastleProvider() }, + getSaltGenerator : { -> mockSaltGenerator } + ] as PBEConfig + legacyEncryptor.setConfig(mockConfig) + legacyEncryptor.setStringOutputType("hexadecimal") + + String cipherText = legacyEncryptor.encrypt(plaintext) + logger.info("Cipher text: ${cipherText}") + + NiFiProperties niFiProperties = new StandardNiFiProperties(new Properties(RAW_PROPERTIES + [(ALGORITHM): em.algorithm])) + StringEncryptor encryptor = StringEncryptor.createEncryptor(niFiProperties) + + // Act + String recovered = encryptor.decrypt(cipherText) + logger.info("Recovered: ${recovered}") + + // Assert + assert plaintext == recovered + } + } + + @Test + void testKeyedEncryptionShouldBeInternallyConsistent() throws Exception { + // Arrange + final String plaintext = "This is a plaintext message." + + // Act + for (EncryptionMethod em : keyedEncryptionMethods) { + logger.info("Using algorithm: ${em.getAlgorithm()}") + StringEncryptor encryptor = new StringEncryptor(em.algorithm, em.provider, Hex.decodeHex(KEY_HEX as char[])) + + String cipherText = encryptor.encrypt(plaintext) + logger.info("Cipher text: ${cipherText}") + + String recovered = encryptor.decrypt(cipherText) + logger.info("Recovered: ${recovered}") + + // Assert + assert plaintext == recovered + } + } + + @Test + void testKeyedEncryptionShouldBeExternallyConsistent() throws Exception { + // Arrange + final String plaintext = "This is a plaintext message." + + for (EncryptionMethod em : keyedEncryptionMethods) { + // IV is actually used for keyed encryption + byte[] iv = Hex.decodeHex(("AA" * 16) as char[]) + logger.info("Using algorithm: ${em.getAlgorithm()} with ${iv.length} byte IV") + + // Encrypt the value manually + Cipher cipher = generateKeyedCipher(true, em, KEY_HEX, iv) + + byte[] cipherBytes = cipher.doFinal(plaintext.bytes) + byte[] ivAndCipherBytes = CryptoUtils.concatByteArrays(iv, cipherBytes) + String cipherTextHex = Hex.encodeHexString(ivAndCipherBytes) + logger.info("Cipher text: ${cipherTextHex}") + + StringEncryptor encryptor = new StringEncryptor(em.algorithm, em.provider, Hex.decodeHex(KEY_HEX.chars)) + + // Act + String recovered = encryptor.decrypt(cipherTextHex) + logger.info("Recovered: ${recovered}") + + // Assert + assert plaintext == recovered + } + } + + @Test + void testGetCipherWithExternalIVShouldBeInternallyConsistent() throws Exception { + // Arrange + KeyedCipherProvider cipherProvider = new AESKeyedCipherProvider() + + final String plaintext = "This is a plaintext message." + + // Act + keyedEncryptionMethods.each { EncryptionMethod em -> + logger.info("Using algorithm: ${em.getAlgorithm()}") + byte[] iv = cipherProvider.generateIV() + logger.info("IV: ${Hex.encodeHexString(iv)}") + + // Initialize a cipher for encryption + Cipher cipher = cipherProvider.getCipher(em, key, iv, true) + + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") + + cipher = cipherProvider.getCipher(em, key, iv, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") + logger.info("Recovered: ${recovered}") + + // Assert + assert plaintext.equals(recovered) + } + } + + @Test + void testGetCipherWithUnlimitedStrengthShouldBeInternallyConsistent() throws Exception { + // Arrange + Assume.assumeTrue("Test is being skipped due to this JVM lacking JCE Unlimited Strength Jurisdiction Policy file.", isUnlimitedStrengthCryptoAvailable()) + + KeyedCipherProvider cipherProvider = new AESKeyedCipherProvider() + final List LONG_KEY_LENGTHS = [192, 256] + + final String plaintext = "This is a plaintext message." + + SecureRandom secureRandom = new SecureRandom() + + // Act + keyedEncryptionMethods.each { EncryptionMethod em -> + // Re-use the same IV for the different length keys to ensure the encryption is different + byte[] iv = cipherProvider.generateIV() + logger.info("IV: ${Hex.encodeHexString(iv)}") + + LONG_KEY_LENGTHS.each { int keyLength -> + logger.info("Using algorithm: ${em.getAlgorithm()} with key length ${keyLength}") + + // Generate a key + byte[] keyBytes = new byte[keyLength / 8] + secureRandom.nextBytes(keyBytes) + SecretKey localKey = new SecretKeySpec(keyBytes, "AES") + logger.info("Key: ${Hex.encodeHexString(keyBytes)} ${keyBytes.length}") + + // Initialize a cipher for encryption + Cipher cipher = cipherProvider.getCipher(em, localKey, iv, true) + + byte[] cipherBytes = cipher.doFinal(plaintext.getBytes("UTF-8")) + logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)} ${cipherBytes.length}") + + cipher = cipherProvider.getCipher(em, localKey, iv, false) + byte[] recoveredBytes = cipher.doFinal(cipherBytes) + String recovered = new String(recoveredBytes, "UTF-8") + logger.info("Recovered: ${recovered}") + + // Assert + assert plaintext.equals(recovered) + } + } + } + + @Test + void testStringEncryptorShouldNotBeFinal() throws Exception { + // Arrange + final String plaintext = "This is a plaintext message." + + StringEncryptor mockEncryptor = [encrypt: { String pt -> pt.reverse() }, + decrypt: { String ct -> ct.reverse() }] as StringEncryptor + + // Act + String cipherText = mockEncryptor.encrypt(plaintext) + logger.info("Encrypted ${plaintext} to ${cipherText}") + String recovered = mockEncryptor.decrypt(cipherText) + logger.info("Decrypted ${cipherText} to ${recovered}") + + // Assert + assert recovered == plaintext + assert cipherText != plaintext + } + + @Test + void testStringEncryptorShouldNotOperateIfNotInitialized() throws Exception { + // Arrange + final String plaintext = "This is a plaintext message." + + StringEncryptor uninitializedEncryptor = new StringEncryptor() + + // Act + def encryptMsg = shouldFail(EncryptionException) { + String cipherText = uninitializedEncryptor.encrypt(plaintext) + logger.info("Encrypted ${plaintext} to ${cipherText}") + } + def decryptMsg = shouldFail(EncryptionException) { + String recovered = uninitializedEncryptor.decrypt(plaintext) + logger.info("Decrypted ${plaintext} to ${recovered}") + } + + // Assert + assert encryptMsg =~ "encryptor is not initialized" + assert decryptMsg =~ "encryptor is not initialized" + } + + @Test + void testStringEncryptorShouldDetermineIfInitialized() throws Exception { + // Arrange + StringEncryptor uninitializedEncryptor = new StringEncryptor() + EncryptionMethod em = EncryptionMethod.MD5_128AES + StringEncryptor initializedEncryptor = new StringEncryptor(em.algorithm, em.provider, DEFAULT_PASSWORD) + + // Act + boolean uninitializedIsInitialized = uninitializedEncryptor.isInitialized() + logger.info("Uninitialized encryptor is initialized: ${uninitializedIsInitialized}") + boolean initializedIsInitialized = initializedEncryptor.isInitialized() + logger.info("Initialized encryptor is initialized: ${initializedIsInitialized}") + + // Assert + assert !uninitializedIsInitialized + assert initializedIsInitialized + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/logback-test.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/logback-test.xml index 5a64b9f016..fd00fef8f9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/logback-test.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/logback-test.xml @@ -30,6 +30,7 @@ + diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml index 3440f68dd2..bcaa99efd1 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml @@ -508,7 +508,7 @@ src/test/resources/TestUpdateRecord/schema/person-with-name-string-fields.avsc src/test/resources/TestUpdateRecord/schema/name-fields-only.avsc src/test/resources/TestUpdateRecord/schema/person-with-name-and-mother.avsc - src/main/java/org/apache/nifi/security/util/crypto/bcrypt/BCrypt.java