This is an automated email from the ASF dual-hosted git repository.

joewitt pushed a commit to branch support/nifi-1.16
in repository https://gitbox.apache.org/repos/asf/nifi.git

commit 0d3ad93120734f87cd319cb5f2882b68bbe62583
Author: Emilio Setiadarma <[email protected]>
AuthorDate: Wed Mar 16 10:48:57 2022 -0700

    NIFI-1468 Added tests to handle invalid cipher streams missing Salt/IV
    
    - Updated PasswordBasedEncryptorGroovyTest and KeyedEncryptorGroovyTest
    
    This closes #5877
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../util/crypto/KeyedEncryptorGroovyTest.groovy    |  89 +++++++--
 .../crypto/PasswordBasedEncryptorGroovyTest.groovy | 213 ++++++++++++++++++++-
 2 files changed, 280 insertions(+), 22 deletions(-)

diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/KeyedEncryptorGroovyTest.groovy
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/KeyedEncryptorGroovyTest.groovy
index a3931eb5c6..1b6110bcf1 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/KeyedEncryptorGroovyTest.groovy
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/KeyedEncryptorGroovyTest.groovy
@@ -20,11 +20,11 @@ import org.apache.commons.codec.binary.Hex
 import org.apache.nifi.processor.io.StreamCallback
 import org.apache.nifi.security.util.EncryptionMethod
 import org.apache.nifi.security.util.KeyDerivationFunction
+import org.apache.nifi.stream.io.exception.BytePatternNotFoundException
 import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.After
-import org.junit.Before
 import org.junit.BeforeClass
 import org.junit.Test
+import org.junit.Assert
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
@@ -37,8 +37,6 @@ class KeyedEncryptorGroovyTest {
     private static final Logger logger = 
LoggerFactory.getLogger(KeyedEncryptorGroovyTest.class)
 
     private static final String TEST_RESOURCES_PREFIX = 
"src/test/resources/TestEncryptContent/"
-    private static final File plainFile = new 
File("${TEST_RESOURCES_PREFIX}/plain.txt")
-    private static final File encryptedFile = new 
File("${TEST_RESOURCES_PREFIX}/unsalted_128_raw.asc")
 
     private static final String KEY_HEX = "0123456789ABCDEFFEDCBA9876543210"
     private static final SecretKey KEY = new 
SecretKeySpec(Hex.decodeHex(KEY_HEX as char[]), "AES")
@@ -52,14 +50,6 @@ class KeyedEncryptorGroovyTest {
         }
     }
 
-    @Before
-    void setUp() throws Exception {
-    }
-
-    @After
-    void tearDown() throws Exception {
-    }
-
     @Test
     void testShouldEncryptAndDecrypt() throws Exception {
         // Arrange
@@ -191,4 +181,79 @@ class KeyedEncryptorGroovyTest {
             [l.first(), Integer.valueOf(l.last())]
         }
     }
+
+    @Test
+    void testDecryptShouldHandleCipherStreamMissingIV() {
+        // Arrange
+        KeyedCipherProvider cipherProvider = 
CipherProviderFactory.getCipherProvider(KeyDerivationFunction.NONE)
+        final String IV_DELIMITER = new String(cipherProvider.IV_DELIMITER, 
StandardCharsets.UTF_8)
+
+        final String PLAINTEXT = "This is a plaintext message."
+        InputStream plainStream = new 
ByteArrayInputStream(PLAINTEXT.getBytes("UTF-8"))
+
+        OutputStream cipherStream = new ByteArrayOutputStream()
+        OutputStream recoveredStream = new ByteArrayOutputStream()
+
+        EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
+
+        // Act
+        KeyedEncryptor encryptor = new KeyedEncryptor(encryptionMethod, KEY)
+
+        StreamCallback encryptionCallback = encryptor.getEncryptionCallback()
+        StreamCallback decryptionCallback = encryptor.getDecryptionCallback()
+
+        encryptionCallback.process(plainStream, cipherStream)
+
+        final byte[] cipherBytes = ((ByteArrayOutputStream) 
cipherStream).toByteArray()
+
+        // Remove IV
+        final String cipherString = new String(cipherBytes, 
StandardCharsets.UTF_8)
+        final byte[] removedIVCipherBytes = 
cipherString.split(IV_DELIMITER)[1].getBytes(StandardCharsets.UTF_8)
+
+        InputStream cipherInputStream = new 
ByteArrayInputStream(removedIVCipherBytes)
+        Exception exception = Assert.assertThrows(Exception.class, () -> {
+            decryptionCallback.process(cipherInputStream, recoveredStream)
+        })
+
+        // Assert
+        assert exception.getCause() instanceof BytePatternNotFoundException
+    }
+
+    @Test
+    void testDecryptShouldHandleCipherStreamMissingIVDelimiter() {
+        // Arrange
+        KeyedCipherProvider cipherProvider = 
CipherProviderFactory.getCipherProvider(KeyDerivationFunction.NONE)
+        final String IV_DELIMITER = new String(cipherProvider.IV_DELIMITER, 
StandardCharsets.UTF_8)
+
+        final String PLAINTEXT = "This is a plaintext message."
+        InputStream plainStream = new 
ByteArrayInputStream(PLAINTEXT.getBytes("UTF-8"))
+
+        OutputStream cipherStream = new ByteArrayOutputStream()
+        OutputStream recoveredStream = new ByteArrayOutputStream()
+
+        EncryptionMethod encryptionMethod = EncryptionMethod.AES_CBC
+
+        // Act
+        KeyedEncryptor encryptor = new KeyedEncryptor(encryptionMethod, KEY)
+
+        StreamCallback encryptionCallback = encryptor.getEncryptionCallback()
+        StreamCallback decryptionCallback = encryptor.getDecryptionCallback()
+
+        encryptionCallback.process(plainStream, cipherStream)
+
+        final byte[] cipherBytes = ((ByteArrayOutputStream) 
cipherStream).toByteArray()
+
+        // Remove IV Delimiter
+        final String cipherString = new String(cipherBytes, 
StandardCharsets.UTF_8)
+        final byte[] removedIVDelimiterCipherBytes = 
cipherString.split(IV_DELIMITER)[1].getBytes(StandardCharsets.UTF_8)
+
+        InputStream cipherInputStream = new 
ByteArrayInputStream(removedIVDelimiterCipherBytes)
+
+        Exception exception = Assert.assertThrows(Exception.class, () -> {
+            decryptionCallback.process(cipherInputStream, recoveredStream)
+        })
+
+        // Assert
+        assert exception.getCause() instanceof BytePatternNotFoundException
+    }
 }
\ No newline at end of file
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/PasswordBasedEncryptorGroovyTest.groovy
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/PasswordBasedEncryptorGroovyTest.groovy
index 238fba3917..96c7435221 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/PasswordBasedEncryptorGroovyTest.groovy
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/PasswordBasedEncryptorGroovyTest.groovy
@@ -23,12 +23,12 @@ import org.apache.nifi.security.util.EncryptionMethod
 import org.apache.nifi.security.util.KeyDerivationFunction
 import org.apache.nifi.stream.io.ByteCountingInputStream
 import org.apache.nifi.stream.io.ByteCountingOutputStream
+import org.apache.nifi.stream.io.exception.BytePatternNotFoundException
 import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.After
 import org.junit.Assume
-import org.junit.Before
 import org.junit.BeforeClass
 import org.junit.Test
+import org.junit.Assert
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
@@ -56,14 +56,6 @@ class PasswordBasedEncryptorGroovyTest {
         }
     }
 
-    @Before
-    void setUp() throws Exception {
-    }
-
-    @After
-    void tearDown() throws Exception {
-    }
-
     @Test
     void testShouldEncryptAndDecrypt() throws Exception {
         // Arrange
@@ -516,4 +508,205 @@ class PasswordBasedEncryptorGroovyTest {
         assert encryptor.flowfileAttributes.get("encryptcontent.kdf_salt") == 
EXPECTED_KDF_SALT
         assert 
(29..54)*.toString().contains(encryptor.flowfileAttributes.get("encryptcontent.kdf_salt_length"))
     }
+
+    @Test
+    void testDecryptShouldHandleCipherStreamMissingSalt() throws Exception {
+        // Arrange
+        final int OPENSSL_EVP_HEADER_SIZE = 8
+
+        final String PLAINTEXT = "This is a plaintext message."
+        InputStream plainStream = new 
ByteArrayInputStream(PLAINTEXT.getBytes("UTF-8"))
+
+        def encryptionMethodsAndKdfs = [
+                (KeyDerivationFunction.OPENSSL_EVP_BYTES_TO_KEY): 
EncryptionMethod.MD5_128AES,
+                (KeyDerivationFunction.BCRYPT)                  : 
EncryptionMethod.AES_CBC,
+                (KeyDerivationFunction.SCRYPT)                  : 
EncryptionMethod.AES_CBC,
+                (KeyDerivationFunction.PBKDF2)                  : 
EncryptionMethod.AES_CBC
+        ]
+
+        // Act
+        encryptionMethodsAndKdfs.each { KeyDerivationFunction kdf, 
EncryptionMethod encryptionMethod ->
+            PBECipherProvider cipherProvider = (PBECipherProvider) 
CipherProviderFactory.getCipherProvider(kdf)
+
+            OutputStream cipherStream = new ByteArrayOutputStream()
+            OutputStream recoveredStream = new ByteArrayOutputStream()
+
+            PasswordBasedEncryptor encryptor = new 
PasswordBasedEncryptor(encryptionMethod, PASSWORD.toCharArray(), kdf)
+
+            StreamCallback encryptionCallback = 
encryptor.getEncryptionCallback()
+            StreamCallback decryptionCallback = 
encryptor.getDecryptionCallback()
+
+            encryptionCallback.process(plainStream, cipherStream)
+
+            final byte[] cipherBytes = ((ByteArrayOutputStream) 
cipherStream).toByteArray()
+
+            // reads the salt
+            InputStream saltInputStream = new ByteArrayInputStream(cipherBytes)
+            final byte[] saltBytes = cipherProvider.readSalt(saltInputStream)
+
+            int skipLength = saltBytes.length
+            if (cipherProvider instanceof 
org.apache.nifi.security.util.crypto.OpenSSLPKCS5CipherProvider) {
+                skipLength += OPENSSL_EVP_HEADER_SIZE
+            }
+
+            InputStream cipherInputStream = new 
ByteArrayInputStream(cipherBytes)
+            cipherInputStream.skip(skipLength)
+
+            Exception exception = Assert.assertThrows(Exception.class, () -> {
+                decryptionCallback.process(cipherInputStream, recoveredStream)
+            })
+
+            // Assert
+            if (!(cipherProvider instanceof OpenSSLPKCS5CipherProvider)) {
+                assert exception.getCause() instanceof IllegalArgumentException
+            }
+
+            // This is necessary to run multiple iterations
+            plainStream.reset()
+        }
+    }
+
+    @Test
+    void testDecryptShouldHandleCipherStreamMissingSaltDelimiter() throws 
Exception {
+        // Arrange
+        final String SALT_DELIMITER = "NiFiSALT"
+
+        final String PLAINTEXT = "This is a plaintext message."
+        InputStream plainStream = new 
ByteArrayInputStream(PLAINTEXT.getBytes("UTF-8"))
+
+        def encryptionMethodsAndKdfs = [
+                (KeyDerivationFunction.BCRYPT)                  : 
EncryptionMethod.AES_CBC,
+                (KeyDerivationFunction.SCRYPT)                  : 
EncryptionMethod.AES_CBC,
+                (KeyDerivationFunction.PBKDF2)                  : 
EncryptionMethod.AES_CBC
+        ]
+
+        // Act
+        encryptionMethodsAndKdfs.each { KeyDerivationFunction kdf, 
EncryptionMethod encryptionMethod ->
+            PBECipherProvider cipherProvider = (PBECipherProvider) 
CipherProviderFactory.getCipherProvider(kdf)
+
+            OutputStream cipherStream = new ByteArrayOutputStream()
+            OutputStream recoveredStream = new ByteArrayOutputStream()
+
+            PasswordBasedEncryptor encryptor = new 
PasswordBasedEncryptor(encryptionMethod, PASSWORD.toCharArray(), kdf)
+
+            StreamCallback encryptionCallback = 
encryptor.getEncryptionCallback()
+            StreamCallback decryptionCallback = 
encryptor.getDecryptionCallback()
+
+            encryptionCallback.process(plainStream, cipherStream)
+
+            final byte[] cipherBytes = ((ByteArrayOutputStream) 
cipherStream).toByteArray()
+            final String removedDelimiterCipherString = new 
String(cipherBytes, StandardCharsets.UTF_8).replace(SALT_DELIMITER, "")
+
+            InputStream cipherInputStream = new 
ByteArrayInputStream(removedDelimiterCipherString.getBytes(StandardCharsets.UTF_8))
+
+            Exception exception = Assert.assertThrows(Exception.class, () -> {
+                decryptionCallback.process(cipherInputStream, recoveredStream)
+            })
+
+            // Assert
+            assert exception.getCause() instanceof BytePatternNotFoundException
+
+            // This is necessary to run multiple iterations
+            plainStream.reset()
+        }
+    }
+
+    @Test
+    void testDecryptShouldHandleCipherStreamMissingIV() throws Exception {
+        // Arrange
+        final String SALT_DELIMITER="NiFiSALT"
+        final String IV_DELIMITER = "NiFiIV"
+
+        final String PLAINTEXT = "This is a plaintext message."
+        InputStream plainStream = new 
ByteArrayInputStream(PLAINTEXT.getBytes("UTF-8"))
+
+        def encryptionMethodsAndKdfs = [
+                (KeyDerivationFunction.BCRYPT)                  : 
EncryptionMethod.AES_CBC,
+                (KeyDerivationFunction.SCRYPT)                  : 
EncryptionMethod.AES_CBC,
+                (KeyDerivationFunction.PBKDF2)                  : 
EncryptionMethod.AES_CBC
+        ]
+
+        // Act
+        encryptionMethodsAndKdfs.each { KeyDerivationFunction kdf, 
EncryptionMethod encryptionMethod ->
+            PBECipherProvider cipherProvider = (PBECipherProvider) 
CipherProviderFactory.getCipherProvider(kdf)
+
+            OutputStream cipherStream = new ByteArrayOutputStream()
+            OutputStream recoveredStream = new ByteArrayOutputStream()
+
+            PasswordBasedEncryptor encryptor = new 
PasswordBasedEncryptor(encryptionMethod, PASSWORD.toCharArray(), kdf)
+
+            StreamCallback encryptionCallback = 
encryptor.getEncryptionCallback()
+            StreamCallback decryptionCallback = 
encryptor.getDecryptionCallback()
+
+            encryptionCallback.process(plainStream, cipherStream)
+
+            final byte[] cipherBytes = ((ByteArrayOutputStream) 
cipherStream).toByteArray()
+
+            // remove IV in cipher
+            final String cipherString = new String(cipherBytes, 
StandardCharsets.UTF_8)
+            final StringBuilder sb = new StringBuilder()
+            sb.append(cipherString.split(SALT_DELIMITER)[0])
+            sb.append(SALT_DELIMITER)
+            sb.append(IV_DELIMITER)
+            sb.append(cipherString.split(IV_DELIMITER)[1])
+            final String removedIVCipherString = sb.toString()
+
+            InputStream cipherInputStream = new 
ByteArrayInputStream(removedIVCipherString.getBytes(StandardCharsets.UTF_8))
+
+            Exception exception = Assert.assertThrows(Exception.class, () -> {
+                decryptionCallback.process(cipherInputStream, recoveredStream)
+            })
+
+            // Assert
+            assert exception.getCause() instanceof IllegalArgumentException
+
+            // This is necessary to run multiple iterations
+            plainStream.reset()
+        }
+    }
+
+    @Test
+    void testDecryptShouldHandleCipherStreamMissingIVDelimiter() throws 
Exception {
+        // Arrange
+        final String IV_DELIMITER = "NiFiIV"
+
+        final String PLAINTEXT = "This is a plaintext message."
+        InputStream plainStream = new 
ByteArrayInputStream(PLAINTEXT.getBytes("UTF-8"))
+
+        def encryptionMethodsAndKdfs = [
+                (KeyDerivationFunction.BCRYPT)                  : 
EncryptionMethod.AES_CBC,
+                (KeyDerivationFunction.SCRYPT)                  : 
EncryptionMethod.AES_CBC,
+                (KeyDerivationFunction.PBKDF2)                  : 
EncryptionMethod.AES_CBC
+        ]
+
+        // Act
+        encryptionMethodsAndKdfs.each { KeyDerivationFunction kdf, 
EncryptionMethod encryptionMethod ->
+            PBECipherProvider cipherProvider = (PBECipherProvider) 
CipherProviderFactory.getCipherProvider(kdf)
+
+            OutputStream cipherStream = new ByteArrayOutputStream()
+            OutputStream recoveredStream = new ByteArrayOutputStream()
+
+            PasswordBasedEncryptor encryptor = new 
PasswordBasedEncryptor(encryptionMethod, PASSWORD.toCharArray(), kdf)
+
+            StreamCallback encryptionCallback = 
encryptor.getEncryptionCallback()
+            StreamCallback decryptionCallback = 
encryptor.getDecryptionCallback()
+
+            encryptionCallback.process(plainStream, cipherStream)
+
+            final byte[] cipherBytes = ((ByteArrayOutputStream) 
cipherStream).toByteArray()
+            final String removedDelimiterCipherString = new 
String(cipherBytes, StandardCharsets.UTF_8).replace(IV_DELIMITER, "")
+
+            InputStream cipherInputStream = new 
ByteArrayInputStream(removedDelimiterCipherString.getBytes(StandardCharsets.UTF_8))
+
+            Exception exception = Assert.assertThrows(Exception.class, () -> {
+                decryptionCallback.process(cipherInputStream, recoveredStream)
+            })
+
+            // Assert
+            assert exception.getCause() instanceof BytePatternNotFoundException
+
+            // This is necessary to run multiple iterations
+            plainStream.reset()
+        }
+    }
 }
\ No newline at end of file

Reply via email to