mirror of https://github.com/apache/nifi.git
NIFI-1468 Added tests to handle invalid cipher streams missing Salt/IV
- Updated PasswordBasedEncryptorGroovyTest and KeyedEncryptorGroovyTest This closes #5877 Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
parent
5928d2048e
commit
772adbc709
|
@ -20,11 +20,11 @@ import org.apache.commons.codec.binary.Hex
|
||||||
import org.apache.nifi.processor.io.StreamCallback
|
import org.apache.nifi.processor.io.StreamCallback
|
||||||
import org.apache.nifi.security.util.EncryptionMethod
|
import org.apache.nifi.security.util.EncryptionMethod
|
||||||
import org.apache.nifi.security.util.KeyDerivationFunction
|
import org.apache.nifi.security.util.KeyDerivationFunction
|
||||||
|
import org.apache.nifi.stream.io.exception.BytePatternNotFoundException
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.BeforeClass
|
import org.junit.BeforeClass
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.Assert
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
@ -37,8 +37,6 @@ class KeyedEncryptorGroovyTest {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(KeyedEncryptorGroovyTest.class)
|
private static final Logger logger = LoggerFactory.getLogger(KeyedEncryptorGroovyTest.class)
|
||||||
|
|
||||||
private static final String TEST_RESOURCES_PREFIX = "src/test/resources/TestEncryptContent/"
|
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 String KEY_HEX = "0123456789ABCDEFFEDCBA9876543210"
|
||||||
private static final SecretKey KEY = new SecretKeySpec(Hex.decodeHex(KEY_HEX as char[]), "AES")
|
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
|
@Test
|
||||||
void testShouldEncryptAndDecrypt() throws Exception {
|
void testShouldEncryptAndDecrypt() throws Exception {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
@ -191,4 +181,79 @@ class KeyedEncryptorGroovyTest {
|
||||||
[l.first(), Integer.valueOf(l.last())]
|
[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
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -23,12 +23,12 @@ import org.apache.nifi.security.util.EncryptionMethod
|
||||||
import org.apache.nifi.security.util.KeyDerivationFunction
|
import org.apache.nifi.security.util.KeyDerivationFunction
|
||||||
import org.apache.nifi.stream.io.ByteCountingInputStream
|
import org.apache.nifi.stream.io.ByteCountingInputStream
|
||||||
import org.apache.nifi.stream.io.ByteCountingOutputStream
|
import org.apache.nifi.stream.io.ByteCountingOutputStream
|
||||||
|
import org.apache.nifi.stream.io.exception.BytePatternNotFoundException
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Assume
|
import org.junit.Assume
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.BeforeClass
|
import org.junit.BeforeClass
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.Assert
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
@ -56,14 +56,6 @@ class PasswordBasedEncryptorGroovyTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
|
||||||
void setUp() throws Exception {
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
void tearDown() throws Exception {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testShouldEncryptAndDecrypt() throws Exception {
|
void testShouldEncryptAndDecrypt() throws Exception {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
@ -516,4 +508,205 @@ class PasswordBasedEncryptorGroovyTest {
|
||||||
assert encryptor.flowfileAttributes.get("encryptcontent.kdf_salt") == EXPECTED_KDF_SALT
|
assert encryptor.flowfileAttributes.get("encryptcontent.kdf_salt") == EXPECTED_KDF_SALT
|
||||||
assert (29..54)*.toString().contains(encryptor.flowfileAttributes.get("encryptcontent.kdf_salt_length"))
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue