Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,29 @@

package software.amazon.encryption.s3;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static software.amazon.encryption.s3.TestUtils.*;

import java.lang.annotation.ElementType;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import com.amazonaws.services.s3.model.KMSEncryptionMaterials;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.opentest4j.TestAbortedException;
import software.amazon.encryption.s3.client.S3ECTestServerClient;
import software.amazon.encryption.s3.model.CommitmentPolicy;
import software.amazon.encryption.s3.model.CreateClientInput;
import software.amazon.encryption.s3.model.CreateClientOutput;
import software.amazon.encryption.s3.model.EncryptionAlgorithm;
import software.amazon.encryption.s3.model.GetObjectInput;
import software.amazon.encryption.s3.model.GetObjectOutput;
import software.amazon.encryption.s3.model.InstructionFileConfig;
import software.amazon.encryption.s3.model.KeyMaterial;
import software.amazon.encryption.s3.model.PutObjectInput;
import software.amazon.encryption.s3.model.S3ECConfig;
import software.amazon.encryption.s3.model.S3EncryptionClientError;

import com.amazonaws.services.s3.AmazonS3Encryption;
import com.amazonaws.services.s3.AmazonS3EncryptionClient;
import com.amazonaws.services.s3.model.CryptoConfiguration;
import com.amazonaws.services.s3.model.CryptoMode;
import com.amazonaws.services.s3.model.CryptoStorageMode;
import software.amazon.encryption.s3.TestUtils.*;
import com.amazonaws.services.s3.model.EncryptionMaterialsProvider;
import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider;

/**
* Exhaustive tests for S3 Encryption Client round-trip operations.
Expand All @@ -59,11 +40,21 @@

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class KC_GCMTests {
private static String sharedObjectKeyBase = "test-kc-gcm-kms";
private static final String sharedObjectKeyBaseMetaDataMode = "test-kc-gcm-kms";
private static final String sharedObjectKeyBaseInsFileMode = "test-kc-gcm-kms-instruction-file";
private static KeyMaterial kmsKeyArn = KeyMaterial.builder()
.kmsKeyId(TestUtils.KMS_KEY_ARN)
.build();
private static List<String> crossLanguageObjects = new ArrayList<>();
private static final List<String> crossLanguageObjectsMetaDataMode = new ArrayList<>();
private static final List<String> crossLanguageObjectsInstructionFiles = new ArrayList<>();
private static KeyPair RSA_KEY_PAIR_1;

@BeforeAll
static void setupKeys() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(2048);
RSA_KEY_PAIR_1 = keyPairGen.generateKeyPair();
}

@Order(1)
@ParameterizedTest(name = "{0}: Improved configured with RequireEncryptAllowDecrypt should encrypt KC-GCM")
Expand All @@ -78,7 +69,34 @@ void improved_configured_with_require_encrypt_allow_decrypt_should_encrypt_kc_gc
.build());
String S3ECId = clientOutput.getClientId();

TestUtils.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
TestUtils.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
}

@Order(2)
@ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should encrypt KC-GCM")
@MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest")
void improved_configured_with_require_encrypt_require_decrypt_should_encrypt_kc_gcm_ins_file(TestUtils.LanguageServerTarget language) {
if (!RAW_SUPPORTED.contains(language.getLanguageName())) {
throw new TestAbortedException("Not encrypting raw keyring with: " + language.getLanguageName());
}

KeyMaterial rsaKey = KeyMaterial.builder()
.rsaKey(ByteBuffer.wrap(RSA_KEY_PAIR_1.getPrivate().getEncoded()))
.build();

S3ECTestServerClient client = TestUtils.testServerClientFor(language);
CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder()
.config(S3ECConfig.builder()
.instructionFileConfig(InstructionFileConfig.builder()
.enableInstructionFilePutObject(true)
.build())
.encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY)
.commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT)
.keyMaterial(rsaKey).build())
.build());
String S3ECId = clientOutput.getClientId();

TestUtils.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBaseInsFileMode + language.getLanguageName()), crossLanguageObjectsInstructionFiles, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
}

@Order(2)
Expand All @@ -94,7 +112,7 @@ void improved_configured_with_require_encrypt_require_decrypt_should_encrypt_kc_
.build());
String S3ECId = clientOutput.getClientId();

TestUtils.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
TestUtils.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
}

@Order(2)
Expand All @@ -110,7 +128,7 @@ void improved_configured_with_the_default_should_encrypt_kc_gcm(TestUtils.Langua
.build());
String S3ECId = clientOutput.getClientId();

TestUtils.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
TestUtils.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
}

@Order(10)
Expand All @@ -127,7 +145,7 @@ void transition_configured_with_the_default_should_decrypt_kc_gcm(TestUtils.Lang
.build());
String S3ECId = clientOutput.getClientId();

TestUtils.Decrypt(client, S3ECId, crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
TestUtils.Decrypt(client, S3ECId, crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
}

@Order(11)
Expand All @@ -144,7 +162,7 @@ void transition_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_kc_g
.build());
String S3ECId = clientOutput.getClientId();

TestUtils.Decrypt(client, S3ECId, crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
TestUtils.Decrypt(client, S3ECId, crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
}

@Order(12)
Expand All @@ -162,7 +180,7 @@ void improved_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_kc_gcm
.build());
String S3ECId = clientOutput.getClientId();

TestUtils.Decrypt(client, S3ECId, crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
TestUtils.Decrypt(client, S3ECId, crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
}

@Order(13)
Expand All @@ -179,7 +197,7 @@ void improved_configured_with_require_encrypt_allow_decrypt_should_decrypt_kc_gc
.build());
String S3ECId = clientOutput.getClientId();

TestUtils.Decrypt(client, S3ECId, crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
TestUtils.Decrypt(client, S3ECId, crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
}

@Order(14)
Expand All @@ -196,7 +214,7 @@ void improved_configured_with_require_encrypt_require_decrypt_should_decrypt_kc_
.build());
String S3ECId = clientOutput.getClientId();

TestUtils.Decrypt(client, S3ECId, crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
TestUtils.Decrypt(client, S3ECId, crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
}

@Order(15)
Expand All @@ -213,7 +231,34 @@ void improved_configured_with_the_default_should_decrypt_kc_gcm(TestUtils.Langua
.build());
String S3ECId = clientOutput.getClientId();

TestUtils.Decrypt(client, S3ECId, crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
TestUtils.Decrypt(client, S3ECId, crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
}

@Order(16)
@ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should encrypt KC-GCM")
@MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest")
void improved_configured_with_require_encrypt_require_decrypt_should_decrypt_kc_gcm_ins_file(final TestUtils.LanguageServerTarget language) {
if (!RAW_SUPPORTED.contains(language.getLanguageName())) {
throw new TestAbortedException("Not encrypting raw keyring with: " + language.getLanguageName());
}

KeyMaterial rsaKey = KeyMaterial.builder()
.rsaKey(ByteBuffer.wrap(RSA_KEY_PAIR_1.getPrivate().getEncoded()))
.build();

S3ECTestServerClient client = TestUtils.testServerClientFor(language);
CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder()
.config(S3ECConfig.builder()
.instructionFileConfig(InstructionFileConfig.builder()
.enableInstructionFilePutObject(true)
.build())
.encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY)
.commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT)
.keyMaterial(rsaKey).build())
.build());
String S3ECId = clientOutput.getClientId();

TestUtils.Decrypt(client, S3ECId, crossLanguageObjectsInstructionFiles, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import software.amazon.awssdk.core.ResponseBytes;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.encryption.s3.TestUtils.LanguageServerTarget;
import software.amazon.encryption.s3.client.S3ECTestServerClient;
import software.amazon.encryption.s3.model.CommitmentPolicy;
import software.amazon.encryption.s3.model.CreateClientInput;
Expand Down Expand Up @@ -598,12 +599,77 @@ public void instructionFileWriteAndRead(LanguageServerTarget encLang, LanguageSe
assertFalse(ptInstFile.asUtf8String().isEmpty());
// Read should be enabled by default
GetObjectOutput output = decClient.getObject(GetObjectInput.builder()
.clientID(decS3ECId)
.bucket(BUCKET)
.key(objectKey)
.build());
.clientID(decS3ECId)
.bucket(BUCKET)
.key(objectKey)
.build());

assertEquals(input, new String(output.getBody().array()));
}
}

@ParameterizedTest(name = "{displayName} for Encrypt: {0}, Decrypt: {1}")
@MethodSource("software.amazon.encryption.s3.TestUtils#crossLanguageClients")
public void instructionFileWriteAndReadWithRSA(LanguageServerTarget encLang, LanguageServerTarget decLang) throws Exception {
// Early validation
if (!RAW_SUPPORTED.contains(encLang.getLanguageName())) {
throw new TestAbortedException("not encrypting raw keyring with: " + encLang.getLanguageName());
}
if (!RAW_SUPPORTED.contains(decLang.getLanguageName())) {
throw new TestAbortedException("not decrypting raw keyring with: " + decLang.getLanguageName());
}

KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(2048);
KeyMaterial rsaKeyMaterial = KeyMaterial.builder()
.rsaKey(ByteBuffer.wrap(keyPairGen.generateKeyPair().getPrivate().getEncoded()))
.build();

S3ECConfig config = S3ECConfig.builder()
.instructionFileConfig(InstructionFileConfig.builder()
.enableInstructionFilePutObject(true)
.build())
.encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF)
.commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT)
.keyMaterial(rsaKeyMaterial)
.build();

// Create clients
S3ECTestServerClient encClient = testServerClientFor(encLang);
S3ECTestServerClient decClient = testServerClientFor(decLang);

String encS3ECId = encClient.createClient(CreateClientInput.builder().config(config).build()).getClientId();
String decS3ECId = decClient.createClient(CreateClientInput.builder().config(config).build()).getClientId();

final String objectKey = appendTestSuffix(String.format("rsa-insfile-write-%s-read-%s",
encLang.getLanguageName(), decLang.getLanguageName()));
final String input = "simple-test-input-rsa";

// Encrypt
encClient.putObject(PutObjectInput.builder()
.clientID(encS3ECId)
.bucket(BUCKET)
.key(objectKey)
.body(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8)))
.build());

// Assert using Java plaintext client that an instruction file exists
ResponseBytes<GetObjectResponse> ptInstFile;
try (S3Client ptS3Client = S3Client.create()) {
ptInstFile = ptS3Client.getObjectAsBytes(builder -> builder
.bucket(BUCKET)
.key(objectKey + ".instruction")
.build());
}
assertTrue(ptInstFile.response().metadata().containsKey("x-amz-crypto-instr-file"));
assertFalse(ptInstFile.asUtf8String().isEmpty());
// Read should be enabled by default
GetObjectOutput output = decClient.getObject(GetObjectInput.builder()
.clientID(decS3ECId)
.bucket(BUCKET)
.key(objectKey)
.build());

assertEquals(input, new String(output.getBody().array()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package software.amazon.encryption.s3;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.net.Socket;
import java.net.URI;
Expand Down
23 changes: 14 additions & 9 deletions test-server/net-v2-v3-server/Controllers/ClientController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ public IActionResult CreateClient([FromBody] ClientRequest request)
{
// Return 501 for not implemented features by the server
if (request.Config.EnableDelayedAuthenticationMode)
return StatusCode(501, new GenericServerError { Message = "EnableDelayedAuthenticationMode not supported" });
return StatusCode(501, new GenericServerError { Message = "[NET-current] EnableDelayedAuthenticationMode not supported" });
if (request.Config.SetBufferSize.HasValue)
return StatusCode(501, new GenericServerError { Message = "SetBufferSize not supported" });
return StatusCode(501, new GenericServerError { Message = "[NET-current] SetBufferSize not supported" });
if (request.Config.KeyMaterial.AesKey != null)
return StatusCode(501, new GenericServerError { Message = "AesKey not supported" });
return StatusCode(501, new GenericServerError { Message = "[NET-current] AesKey not supported" });

try
{
Expand All @@ -36,7 +36,7 @@ public IActionResult CreateClient([FromBody] ClientRequest request)
var kmsKeyId = request.Config.KeyMaterial.KmsKeyId;
encryptionMaterial = new EncryptionMaterialsV2(kmsKeyId, KmsType.KmsContext, encryptionContext);
logger.LogInformation(
"Created EncryptionMaterialsV2: KMS={KmsKeyId}",
"[NET-current] Created EncryptionMaterialsV2: KMS={KmsKeyId}",
kmsKeyId);
}
else if (request.Config.KeyMaterial.RsaKey != null)
Expand All @@ -49,7 +49,7 @@ public IActionResult CreateClient([FromBody] ClientRequest request)
"Created EncryptionMaterialsV2: RSA");
} else
{
return StatusCode(501, new GenericServerError { Message = "Unknown or missing key material!" });
return StatusCode(501, new GenericServerError { Message = "[NET-current] Unknown or missing key material!" });
}

var enableLegacyUnauthenticatedModes = request.Config.EnableLegacyUnauthenticatedModes;
Expand All @@ -59,16 +59,21 @@ public IActionResult CreateClient([FromBody] ClientRequest request)
var enableLegacyMode = enableLegacyUnauthenticatedModes || enableLegacyWrappingAlgorithms;
var securityProfile = enableLegacyMode ? SecurityProfile.V2AndLegacy : SecurityProfile.V2;

logger.LogInformation("Created securityProfile= {securityProfile}", securityProfile.ToString());
logger.LogInformation("[NET-current] Created securityProfile= {securityProfile}", securityProfile.ToString());

var configuration = new AmazonS3CryptoConfigurationV2(securityProfile);
if (request.Config.InstructionFileConfig?.EnableInstructionFilePutObject == true)
{
configuration.StorageMode = CryptoStorageMode.InstructionFile;
logger.LogInformation("[NET-current] Created StorageMode= InstructionFile");
}
// Create S3 encryption client
var encryptionClient = new AmazonS3EncryptionClientV2(configuration, encryptionMaterial);
// Add to cache and return client ID
var clientId = clientCacheService.AddClient(encryptionClient);
var response = new ClientResponse { ClientId = clientId };

logger.LogInformation("Created S3EC client with ID: {clientId}", clientId);
logger.LogInformation("[NET-current] Created S3EC client with ID: {clientId}", clientId);

return new ContentResult
{
Expand All @@ -79,10 +84,10 @@ public IActionResult CreateClient([FromBody] ClientRequest request)
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to create S3EC client");
logger.LogError(ex, "[NET-current] Failed to create S3EC client");
return StatusCode(500, new S3EncryptionClientError
{
Message = $"Failed to create client: {ex.Message}"
Message = $"[NET-current] Failed to create client: {ex.Message}"
});
}
}
Expand Down
Loading
Loading