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