You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ex...@apache.org on 2022/03/23 18:31:14 UTC

[nifi] branch main updated: NIFI-1468 Added tests to handle invalid cipher streams missing Salt/IV

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

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 772adbc  NIFI-1468 Added tests to handle invalid cipher streams missing Salt/IV
772adbc is described below

commit 772adbc70957cc47256c8f7ea3668b2187e12923
Author: Emilio Setiadarma <em...@gmail.com>
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 <ex...@apache.org>
---
 .../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 a3931eb..1b6110b 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 238fba3..96c7435 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