From afa3a5b67924451fbda86ca88cd44a3a0881e603 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Thu, 29 May 2025 10:10:39 -0700 Subject: [PATCH 1/2] chore: add a test for offset against BlockEncryptionHandler --- .../internal/BlockEncryptionHandlerTest.java | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/test/java/com/amazonaws/encryptionsdk/internal/BlockEncryptionHandlerTest.java b/src/test/java/com/amazonaws/encryptionsdk/internal/BlockEncryptionHandlerTest.java index 880fdc74a..9a5da2da8 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/internal/BlockEncryptionHandlerTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/internal/BlockEncryptionHandlerTest.java @@ -16,13 +16,30 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import com.amazonaws.encryptionsdk.AwsCrypto; import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.CryptoInputStream; +import com.amazonaws.encryptionsdk.CryptoOutputStream; +import com.amazonaws.encryptionsdk.CryptoResult; import com.amazonaws.encryptionsdk.TestUtils; import com.amazonaws.encryptionsdk.model.CipherBlockHeaders; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.junit.Before; import org.junit.Test; +import software.amazon.awssdk.utils.StringUtils; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.AesWrappingAlg; +import software.amazon.cryptography.materialproviders.model.CreateRawAesKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.Collections; public class BlockEncryptionHandlerTest { private final CryptoAlgorithm cryptoAlgorithm_ = TestUtils.DEFAULT_TEST_CRYPTO_ALG; @@ -67,4 +84,90 @@ public void correctIVGenerated() throws Exception { }, headers.getNonce()); } + + /** + * This isn't a unit test, but it reproduces a bug found in the FrameEncryptionHandler where the stream + * would be truncated when the offset is >0. + * For robustness' sake, the same test is included against the BlockEncryptionHandler. + * + * @throws Exception + */ + @Test + public void testStreamTruncation() throws Exception { + // Initialize AES key and keyring + SecureRandom rnd = new SecureRandom(); + byte[] rawKey = new byte[16]; + rnd.nextBytes(rawKey); + SecretKeySpec cryptoKey = new SecretKeySpec(rawKey, "AES"); + MaterialProviders materialProviders = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + CreateRawAesKeyringInput keyringInput = + CreateRawAesKeyringInput.builder() + .wrappingKey(ByteBuffer.wrap(cryptoKey.getEncoded())) + .keyNamespace("Example") + .keyName("RandomKey") + .wrappingAlg(AesWrappingAlg.ALG_AES128_GCM_IV12_TAG16) + .build(); + IKeyring keyring = materialProviders.CreateRawAesKeyring(keyringInput); + // Use unframed (block) encryption + AwsCrypto crypto = AwsCrypto.builder() + .withEncryptionFrameSize(0) + .build(); + + String testDataString = StringUtils.repeat("Hello, World! ", 5_000); + + int startOffset = 100; // The data will start from this offset + byte[] inputDataWithOffset = new byte[10_000]; + // the length of the actual data + int dataLength = inputDataWithOffset.length - startOffset; + // copy some data, starting at the startOffset + // so the first |startOffset| bytes are 0s + System.arraycopy( + testDataString.getBytes(StandardCharsets.UTF_8), + 0, + inputDataWithOffset, + startOffset, + dataLength); + // decryptData (non-streaming) doesn't know about the offset + // it will strip out the original 0s + byte[] expectedOutput = new byte[10_000 - startOffset]; + System.arraycopy( + testDataString.getBytes(StandardCharsets.UTF_8), 0, expectedOutput, 0, dataLength); + + // Encrypt the data + byte[] encryptedData; + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + try (CryptoOutputStream cryptoOutput = + crypto.createEncryptingStream(keyring, os, Collections.emptyMap())) { + cryptoOutput.write(inputDataWithOffset, startOffset, dataLength); + } + encryptedData = os.toByteArray(); + } + + // Check non-streaming decrypt + CryptoResult nonStreamDecrypt = crypto.decryptData(keyring, encryptedData); + assertEquals(dataLength, nonStreamDecrypt.getResult().length); + assertArrayEquals(expectedOutput, nonStreamDecrypt.getResult()); + + // Check streaming decrypt + int decryptedLength = 0; + byte[] decryptedData = new byte[inputDataWithOffset.length]; + try (ByteArrayInputStream is = new ByteArrayInputStream(encryptedData); + CryptoInputStream cryptoInput = crypto.createDecryptingStream(keyring, is)) { + int offset = startOffset; + do { + int bytesRead = cryptoInput.read(decryptedData, offset, decryptedData.length - offset); + if (bytesRead <= 0) { + break; // End of stream + } + offset += bytesRead; + decryptedLength += bytesRead; + } while (true); + } + assertEquals(dataLength, decryptedLength); + // These arrays will be offset, i.e. the first |startOffset| bytes are 0s + assertArrayEquals(inputDataWithOffset, decryptedData); + } } From 698bfb42c0f51f3c5f884f422e281dc3335a97ba Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Thu, 29 May 2025 10:11:56 -0700 Subject: [PATCH 2/2] format --- .../internal/BlockEncryptionHandlerTest.java | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/test/java/com/amazonaws/encryptionsdk/internal/BlockEncryptionHandlerTest.java b/src/test/java/com/amazonaws/encryptionsdk/internal/BlockEncryptionHandlerTest.java index 9a5da2da8..5873f8b8d 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/internal/BlockEncryptionHandlerTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/internal/BlockEncryptionHandlerTest.java @@ -23,6 +23,12 @@ import com.amazonaws.encryptionsdk.CryptoResult; import com.amazonaws.encryptionsdk.TestUtils; import com.amazonaws.encryptionsdk.model.CipherBlockHeaders; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.Collections; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.junit.Before; @@ -34,13 +40,6 @@ import software.amazon.cryptography.materialproviders.model.CreateRawAesKeyringInput; import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.security.SecureRandom; -import java.util.Collections; - public class BlockEncryptionHandlerTest { private final CryptoAlgorithm cryptoAlgorithm_ = TestUtils.DEFAULT_TEST_CRYPTO_ALG; private final byte[] messageId_ = @@ -86,9 +85,9 @@ public void correctIVGenerated() throws Exception { } /** - * This isn't a unit test, but it reproduces a bug found in the FrameEncryptionHandler where the stream - * would be truncated when the offset is >0. - * For robustness' sake, the same test is included against the BlockEncryptionHandler. + * This isn't a unit test, but it reproduces a bug found in the FrameEncryptionHandler where the + * stream would be truncated when the offset is >0. For the sake of robustness, the same test is + * included against the BlockEncryptionHandler. * * @throws Exception */ @@ -100,21 +99,19 @@ public void testStreamTruncation() throws Exception { rnd.nextBytes(rawKey); SecretKeySpec cryptoKey = new SecretKeySpec(rawKey, "AES"); MaterialProviders materialProviders = - MaterialProviders.builder() - .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) - .build(); + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); CreateRawAesKeyringInput keyringInput = - CreateRawAesKeyringInput.builder() - .wrappingKey(ByteBuffer.wrap(cryptoKey.getEncoded())) - .keyNamespace("Example") - .keyName("RandomKey") - .wrappingAlg(AesWrappingAlg.ALG_AES128_GCM_IV12_TAG16) - .build(); + CreateRawAesKeyringInput.builder() + .wrappingKey(ByteBuffer.wrap(cryptoKey.getEncoded())) + .keyNamespace("Example") + .keyName("RandomKey") + .wrappingAlg(AesWrappingAlg.ALG_AES128_GCM_IV12_TAG16) + .build(); IKeyring keyring = materialProviders.CreateRawAesKeyring(keyringInput); // Use unframed (block) encryption - AwsCrypto crypto = AwsCrypto.builder() - .withEncryptionFrameSize(0) - .build(); + AwsCrypto crypto = AwsCrypto.builder().withEncryptionFrameSize(0).build(); String testDataString = StringUtils.repeat("Hello, World! ", 5_000); @@ -125,22 +122,22 @@ public void testStreamTruncation() throws Exception { // copy some data, starting at the startOffset // so the first |startOffset| bytes are 0s System.arraycopy( - testDataString.getBytes(StandardCharsets.UTF_8), - 0, - inputDataWithOffset, - startOffset, - dataLength); + testDataString.getBytes(StandardCharsets.UTF_8), + 0, + inputDataWithOffset, + startOffset, + dataLength); // decryptData (non-streaming) doesn't know about the offset // it will strip out the original 0s byte[] expectedOutput = new byte[10_000 - startOffset]; System.arraycopy( - testDataString.getBytes(StandardCharsets.UTF_8), 0, expectedOutput, 0, dataLength); + testDataString.getBytes(StandardCharsets.UTF_8), 0, expectedOutput, 0, dataLength); // Encrypt the data byte[] encryptedData; try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { try (CryptoOutputStream cryptoOutput = - crypto.createEncryptingStream(keyring, os, Collections.emptyMap())) { + crypto.createEncryptingStream(keyring, os, Collections.emptyMap())) { cryptoOutput.write(inputDataWithOffset, startOffset, dataLength); } encryptedData = os.toByteArray(); @@ -155,7 +152,7 @@ public void testStreamTruncation() throws Exception { int decryptedLength = 0; byte[] decryptedData = new byte[inputDataWithOffset.length]; try (ByteArrayInputStream is = new ByteArrayInputStream(encryptedData); - CryptoInputStream cryptoInput = crypto.createDecryptingStream(keyring, is)) { + CryptoInputStream cryptoInput = crypto.createDecryptingStream(keyring, is)) { int offset = startOffset; do { int bytesRead = cryptoInput.read(decryptedData, offset, decryptedData.length - offset);