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.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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue