diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 637003f3..bb1655bb 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,7 +8,7 @@ on: jobs: lint: - runs-on: macos-13 + runs-on: macos-15 steps: - name: Checkout code diff --git a/test-server/cpp-v2-server/main.cpp b/test-server/cpp-v2-server/main.cpp index c4f2c240..a2b05810 100644 --- a/test-server/cpp-v2-server/main.cpp +++ b/test-server/cpp-v2-server/main.cpp @@ -57,12 +57,19 @@ MHD_Result handle_create_client(struct MHD_Connection *connection, std::string kms_key_id = request["config"]["keyMaterial"]["kmsKeyId"]; bool legacy1 = request["config"]["enableLegacyWrappingAlgorithms"]; bool legacy2 = request["config"]["enableLegacyUnauthenticatedModes"]; + bool inst_put = false; + if (request["config"].contains("instructionFileConfig") && + request["config"]["instructionFileConfig"].contains("enableInstructionFilePutObject")) { + inst_put = request["config"]["instructionFileConfig"]["enableInstructionFilePutObject"]; + } auto materials = std::make_shared(kms_key_id); CryptoConfigurationV2 config(materials); if (legacy1 || legacy2) config.SetSecurityProfile(SecurityProfile::V2_AND_LEGACY); + if (inst_put) + config.SetStorageMethod(StorageMethod::INSTRUCTION_FILE); auto encryption_client = std::make_shared(config); diff --git a/test-server/cpp-v2-transition-server/main.cpp b/test-server/cpp-v2-transition-server/main.cpp index 58d807ef..1fcedc3c 100644 --- a/test-server/cpp-v2-transition-server/main.cpp +++ b/test-server/cpp-v2-transition-server/main.cpp @@ -82,12 +82,19 @@ MHD_Result handle_create_client(struct MHD_Connection *connection, std::string kms_key_id = request["config"]["keyMaterial"]["kmsKeyId"]; bool legacy1 = request["config"]["enableLegacyWrappingAlgorithms"]; bool legacy2 = request["config"]["enableLegacyUnauthenticatedModes"]; + bool inst_put = false; + if (request["config"].contains("instructionFileConfig") && + request["config"]["instructionFileConfig"].contains("enableInstructionFilePutObject")) { + inst_put = request["config"]["instructionFileConfig"]["enableInstructionFilePutObject"]; + } auto materials = std::make_shared(kms_key_id); CryptoConfigurationV2 config(materials); if (legacy1 || legacy2) config.SetSecurityProfile(SecurityProfile::V2_AND_LEGACY); + if (inst_put) + config.SetStorageMethod(StorageMethod::INSTRUCTION_FILE); auto encryption_client = std::make_shared(config); diff --git a/test-server/cpp-v3-server/main.cpp b/test-server/cpp-v3-server/main.cpp index 59167078..1f74974c 100644 --- a/test-server/cpp-v3-server/main.cpp +++ b/test-server/cpp-v3-server/main.cpp @@ -72,12 +72,19 @@ MHD_Result handle_create_client(struct MHD_Connection *connection, std::string kms_key_id = request["config"]["keyMaterial"]["kmsKeyId"]; bool legacy1 = request["config"]["enableLegacyWrappingAlgorithms"]; bool legacy2 = request["config"]["enableLegacyUnauthenticatedModes"]; + bool inst_put = false; + if (request["config"].contains("instructionFileConfig") && + request["config"]["instructionFileConfig"].contains("enableInstructionFilePutObject")) { + inst_put = request["config"]["instructionFileConfig"]["enableInstructionFilePutObject"]; + } auto materials = std::make_shared(kms_key_id); CryptoConfigurationV3 config(materials); if (legacy1 || legacy2) config.AllowLegacy(); + if (inst_put) + config.SetStorageMethod(StorageMethod::INSTRUCTION_FILE); std::string commitmentPolicy = get_config(request, "commitmentPolicy"); std::string encryptionAlgorithm = get_config(request, "encryptionAlgorithm"); diff --git a/test-server/java-tests/build.gradle.kts b/test-server/java-tests/build.gradle.kts index bc37514f..106f82ef 100644 --- a/test-server/java-tests/build.gradle.kts +++ b/test-server/java-tests/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter:5.13.0") testRuntimeOnly("org.junit.platform:junit-platform-launcher") testImplementation("com.amazonaws:aws-java-sdk:1.12.788") + testImplementation("software.amazon.awssdk:s3:2.37.1") testImplementation("org.bouncycastle:bcprov-jdk15on:1.70") } diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index e8dc4bae..a63e1518 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -6,6 +6,7 @@ package software.amazon.encryption.s3; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static software.amazon.encryption.s3.TestUtils.*; @@ -15,19 +16,26 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Stream; +import com.amazonaws.services.s3.AmazonS3EncryptionClientV2; +import com.amazonaws.services.s3.AmazonS3EncryptionV2; +import com.amazonaws.services.s3.model.CryptoConfigurationV2; 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.opentest4j.TestAbortedException; +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.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; @@ -38,7 +46,6 @@ 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; @@ -416,4 +423,125 @@ public void kmsV1LegacyFailsWhenLegacyDisabled(TestUtils.LanguageServerTarget la } } } + + @ParameterizedTest(name = "{displayName} for Encrypt: Java, Decrypt: {0}") + @MethodSource("software.amazon.encryption.s3.TestUtils#clientsForTest") + public void instructionFileReadV2Format(TestUtils.LanguageServerTarget language) { + if (KMS_INSTRUCTION_FILE_UNSUPPORTED.contains(language.getLanguageName())) { + throw new TestAbortedException(String.format("%s does not support KMS instruction files", language.getLanguageName())); + } + if (INSTRUCTION_FILE_GET_UNSUPPORTED.contains(language.getLanguageName())) { + throw new TestAbortedException(String.format("%s does not support instruction file Gets", language.getLanguageName())); + } + S3ECTestServerClient client = testServerClientFor(language); + final String objectKey = appendTestSuffix("read-instruction-file-v2-" + language); + final String input = "simple-test-input"; + KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(KMS_KEY_ARN) + .build(); + CreateClientOutput output1 = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .enableLegacyWrappingAlgorithms(true) + .keyMaterial(kmsKeyArn) + .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .build()) + .build()); + String s3ECId = output1.getClientId(); + + // Write with instruction file using V2 client + EncryptionMaterialsProvider materialsProvider = new KMSEncryptionMaterialsProvider(KMS_KEY_ARN); + CryptoConfigurationV2 cryptoConfigurationV2 = new CryptoConfigurationV2(); + cryptoConfigurationV2.setStorageMode(CryptoStorageMode.InstructionFile); + AmazonS3EncryptionV2 v2Client = AmazonS3EncryptionClientV2.encryptionBuilder() + .withEncryptionMaterialsProvider(materialsProvider) + .withCryptoConfiguration(cryptoConfigurationV2) + .build(); + v2Client.putObject(BUCKET, objectKey, input); + + // Read should be enabled by default + GetObjectOutput output = client.getObject(GetObjectInput.builder() + .clientID(s3ECId) + .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 instructionFileWriteAndRead(LanguageServerTarget encLang, LanguageServerTarget decLang) { + if (INSTRUCTION_FILE_PUT_UNSUPPORTED.contains(encLang.getLanguageName())) { + throw new TestAbortedException("not testing " + encLang.getLanguageName()); + } + if (INSTRUCTION_FILE_GET_UNSUPPORTED.contains(decLang.getLanguageName())) { + throw new TestAbortedException("not testing " + encLang.getLanguageName()); + } + if (KMS_INSTRUCTION_FILE_UNSUPPORTED.contains(encLang.getLanguageName())) { + throw new TestAbortedException("not testing " + encLang.getLanguageName()); + } + if (KMS_INSTRUCTION_FILE_UNSUPPORTED.contains(decLang.getLanguageName())) { + throw new TestAbortedException("not testing " + encLang.getLanguageName()); + } + if (INSTRUCTION_FILE_ROUNDTRIP_TEMP_UNSUPPORTED.contains(encLang.getLanguageName())) { + throw new TestAbortedException("not testing " + encLang.getLanguageName()); + } + S3ECTestServerClient encClient = testServerClientFor(encLang); + S3ECTestServerClient decClient = testServerClientFor(decLang); + final String objectKey = appendTestSuffix(String.format("write-%s-read-%s-instruction-file", encLang.getLanguageName(), decLang.getLanguageName())); + final String input = "simple-test-input"; + KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(KMS_KEY_ARN) + .build(); + CreateClientOutput encOutput = encClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn) + .instructionFileConfig(InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .build()) + .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) + .build()) + .build()); + String encS3ECId = encOutput.getClientId(); + CreateClientOutput decOutput = decClient.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn) + .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .build()) + .build()); + String decS3ECId = decOutput.getClientId(); + + // Write with instruction file + 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 ptInstFile; + try (S3Client ptS3Client = S3Client.create()) { + ptInstFile = ptS3Client.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".instruction") + .build()); + } + // Check for inst file key + if (!encLang.getLanguageName().contains("Ruby")) { + // Ruby doesn't include it :( + 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())); + } } diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java index 8675061a..1cb6c3ce 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java @@ -22,32 +22,29 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.ObjectMetadata; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.junit.jupiter.params.provider.Arguments; import com.amazonaws.regions.Region; import com.amazonaws.regions.Regions; -import software.amazon.smithy.java.aws.client.restjson.RestJsonClientProtocol; -import software.amazon.smithy.java.client.core.ClientConfig; -import software.amazon.smithy.java.client.core.ClientProtocol; -import software.amazon.smithy.java.client.core.endpoint.EndpointResolver; -import software.amazon.encryption.s3.client.S3ECTestServerClient; 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.PutObjectInput; import software.amazon.encryption.s3.model.PutObjectOutput; -import software.amazon.encryption.s3.model.S3ECConfig; -import software.amazon.encryption.s3.model.S3ECTestServerApiService; import software.amazon.encryption.s3.model.S3EncryptionClientError; +import software.amazon.smithy.java.aws.client.restjson.RestJsonClientProtocol; +import software.amazon.smithy.java.client.core.ClientConfig; +import software.amazon.smithy.java.client.core.ClientProtocol; +import software.amazon.smithy.java.client.core.endpoint.EndpointResolver; +import software.amazon.encryption.s3.client.S3ECTestServerClient; +import software.amazon.encryption.s3.model.S3ECTestServerApiService; import software.amazon.smithy.java.http.api.HttpRequest; import software.amazon.smithy.java.http.api.HttpResponse; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.s3.model.ObjectMetadata; -import com.amazonaws.services.s3.model.GetObjectMetadataRequest; - public class TestUtils { // Version name constants @@ -97,6 +94,25 @@ public class TestUtils { public static final Set ENCRYPTION_CONTEXT_ON_ENCRYPT_UNSUPPORTED = Set.of(NET_V2_CURRENT, NET_V3); + // .NET only supports decrypting instruction files using AES and RSA. + // Python MUST support decrypting KMS instruction files, but does not yet. + public static final Set KMS_INSTRUCTION_FILE_UNSUPPORTED = + Set.of(NET_V2_CURRENT, NET_V2_TRANSITION, NET_V3); + + // Go does not write with instruction files + public static final Set INSTRUCTION_FILE_PUT_UNSUPPORTED = + Set.of(GO_V3_CURRENT, GO_V3_TRANSITION, GO_V4, PYTHON_V3 + // Apparently C++ V2 Current does not work, even though it should + , CPP_V2_CURRENT); + + // Not implemented yet in Python. + public static final Set INSTRUCTION_FILE_GET_UNSUPPORTED = + Set.of(PYTHON_V3); + + // PHP doesn't work but it should, temporarily disable + public static final Set INSTRUCTION_FILE_ROUNDTRIP_TEMP_UNSUPPORTED = + Set.of(PHP_V2_CURRENT, PHP_V2_TRANSITION, PHP_V3); + public static final Set CURRENT_VERSIONS = Set.of( JAVA_V3_CURRENT, @@ -286,7 +302,7 @@ public static List metadataMapToList(Map md) { public static void validateServersRunning() { for (LanguageServerTarget server : serverMap.values()) { if (!serverListening(server.getServerURI())) { - throw new RuntimeException(String.format("Test Server for %s is not running at endpoint: %s", + throw new RuntimeException(String.format("Test Server for %s is not running at endpoint: %s", server.getLanguageName(), server.getServerURI())); } } @@ -430,7 +446,7 @@ public static EncryptionAlgorithm GetEncryptionAlgorithm(String objectKey) return EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF; } } - + throw new RuntimeException("Need to support instruction files!"); } @@ -456,7 +472,7 @@ public static void Encrypt( crossLanguageObjects.add(objectKey); } - + public static void Decrypt( S3ECTestServerClient client, String S3ECId, List crossLanguageObjects, @@ -468,7 +484,7 @@ public static void Decrypt( .bucket(TestUtils.BUCKET) .key(objectKey) .build()); - + // Then: Pass assertEquals(objectKey, new String(output.getBody().array())); assertEquals( @@ -478,7 +494,7 @@ public static void Decrypt( ); } } - + public static void Decrypt_fails( S3ECTestServerClient client, String S3ECId, List crossLanguageObjects, diff --git a/test-server/java-v3-server/build.gradle.kts b/test-server/java-v3-server/build.gradle.kts index ca793e56..baae4947 100644 --- a/test-server/java-v3-server/build.gradle.kts +++ b/test-server/java-v3-server/build.gradle.kts @@ -14,7 +14,8 @@ dependencies { implementation("software.amazon.smithy.java:aws-server-restjson:$smithyJavaVersion") compileOnly("software.amazon.awssdk:aws-sdk-java:2.31.66") - implementation("software.amazon.encryption.s3:amazon-s3-encryption-client-java:3.3.5") + // This MUST stay at 3.5.0 + implementation("software.amazon.encryption.s3:amazon-s3-encryption-client-java:3.5.0") } // Use that application plugin to start the service via the `run` task. diff --git a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java b/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java index d992c435..1415fd01 100644 --- a/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java +++ b/test-server/java-v3-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java @@ -3,6 +3,7 @@ import software.amazon.awssdk.core.traits.Trait; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.encryption.s3.S3EncryptionClient; +import software.amazon.encryption.s3.internal.InstructionFileConfig; import software.amazon.encryption.s3.materials.AesKeyring; import software.amazon.encryption.s3.materials.Keyring; import software.amazon.encryption.s3.materials.KmsKeyring; @@ -54,6 +55,7 @@ private boolean onlyOneNonNull(Object... values) { @Override public CreateClientOutput createClient(CreateClientInput input, RequestContext context) { try { + // Key Material / Keyring Creation KeyMaterial key = input.getConfig().getKeyMaterial(); if (!onlyOneNonNull(key.getAesKey(), key.getKmsKeyId(), key.getRsaKey())) { throw new RuntimeException("KeyMaterial must be only one, non-null input!"); @@ -88,7 +90,17 @@ public CreateClientOutput createClient(CreateClientInput input, RequestContext c } else { throw new RuntimeException("No KeyMaterial found!"); } + + // Client Creation + boolean instFilePut = false; + if (input.getConfig().getInstructionFileConfig() != null) { + instFilePut = input.getConfig().getInstructionFileConfig().isEnableInstructionFilePutObject(); + } S3Client s3Client = S3EncryptionClient.builder() + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(S3Client.create()) + .enableInstructionFilePutObject(instFilePut) + .build()) .keyring(keyring) .build(); UUID uuid = UUID.randomUUID(); diff --git a/test-server/java-v3-transition-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java b/test-server/java-v3-transition-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java index d992c435..8622f1ac 100644 --- a/test-server/java-v3-transition-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java +++ b/test-server/java-v3-transition-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java @@ -2,6 +2,7 @@ import software.amazon.awssdk.core.traits.Trait; import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.encryption.s3.internal.InstructionFileConfig; import software.amazon.encryption.s3.S3EncryptionClient; import software.amazon.encryption.s3.materials.AesKeyring; import software.amazon.encryption.s3.materials.Keyring; @@ -88,7 +89,17 @@ public CreateClientOutput createClient(CreateClientInput input, RequestContext c } else { throw new RuntimeException("No KeyMaterial found!"); } + + // Client Creation + boolean instFilePut = false; + if (input.getConfig().getInstructionFileConfig() != null) { + instFilePut = input.getConfig().getInstructionFileConfig().isEnableInstructionFilePutObject(); + } S3Client s3Client = S3EncryptionClient.builder() + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(S3Client.create()) + .enableInstructionFilePutObject(instFilePut) + .build()) .keyring(keyring) .build(); UUID uuid = UUID.randomUUID(); diff --git a/test-server/java-v4-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java b/test-server/java-v4-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java index d992c435..8622f1ac 100644 --- a/test-server/java-v4-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java +++ b/test-server/java-v4-server/src/main/java/software/amazon/encryption/s3/CreateClientOperationImpl.java @@ -2,6 +2,7 @@ import software.amazon.awssdk.core.traits.Trait; import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.encryption.s3.internal.InstructionFileConfig; import software.amazon.encryption.s3.S3EncryptionClient; import software.amazon.encryption.s3.materials.AesKeyring; import software.amazon.encryption.s3.materials.Keyring; @@ -88,7 +89,17 @@ public CreateClientOutput createClient(CreateClientInput input, RequestContext c } else { throw new RuntimeException("No KeyMaterial found!"); } + + // Client Creation + boolean instFilePut = false; + if (input.getConfig().getInstructionFileConfig() != null) { + instFilePut = input.getConfig().getInstructionFileConfig().isEnableInstructionFilePutObject(); + } S3Client s3Client = S3EncryptionClient.builder() + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(S3Client.create()) + .enableInstructionFilePutObject(instFilePut) + .build()) .keyring(keyring) .build(); UUID uuid = UUID.randomUUID(); diff --git a/test-server/model/client.smithy b/test-server/model/client.smithy index 5514672f..bba19b62 100644 --- a/test-server/model/client.smithy +++ b/test-server/model/client.smithy @@ -40,6 +40,17 @@ enum EncryptionAlgorithm { ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY } +structure InstructionFileConfig { + /// This allows specifying a (non-encrypted) client for languages which + /// support this for instruction files. + /// In general, languages should not require specifying it, + /// so it is best to leave it null until there's a good reason not to. + /// This also requires a way to create non-encrypted clients which we don't have yet. + clientId: String, + enableInstructionFilePutObject: Boolean = false, + disableInstructionFile: Boolean = false +} + structure S3ECConfig { enableLegacyUnauthenticatedModes: Boolean = false, enableDelayedAuthenticationMode: Boolean = false, @@ -48,4 +59,5 @@ structure S3ECConfig { keyMaterial: KeyMaterial, commitmentPolicy: CommitmentPolicy, encryptionAlgorithm: EncryptionAlgorithm, + instructionFileConfig: InstructionFileConfig, } diff --git a/test-server/model/object.smithy b/test-server/model/object.smithy index a4b12d5a..6d793353 100644 --- a/test-server/model/object.smithy +++ b/test-server/model/object.smithy @@ -113,6 +113,11 @@ operation ReEncrypt { @required @notProperty clientID: String + + /// Custom instruction file suffix + @httpHeader("InstructionFileSuffix") + @notProperty + instructionFileSuffix: String } output := for Object { diff --git a/test-server/php-v2-server/src/client.php b/test-server/php-v2-server/src/client.php index 44fe1b39..9c39d540 100644 --- a/test-server/php-v2-server/src/client.php +++ b/test-server/php-v2-server/src/client.php @@ -19,6 +19,11 @@ function handleCreateClient() $legacyAlgorithms = $configData["enableLegacyWrappingAlgorithms"] ?? false; $clientId = Uuid::uuid4()->toString(); $kmsKeyId = $keyMaterial["kmsKeyId"] ?? null; + $instFileConfig = $configData['instructionFileConfig'] ?? null; + $instFilePut = false; + if ($instFileConfig != null) { + $instFilePut = $instFileConfig['enableInstructionFilePutObject'] ?? false; + } if ($configData == []) { return GenericServerError("Invalid config in request body", 400); @@ -55,6 +60,7 @@ function handleCreateClient() ], 'kmsKeyId' => $kmsKeyId, 'legacy' => $legacyAlgorithms, + 'instFilePut' => $instFilePut, 'created' => time() ]; diff --git a/test-server/php-v2-transition-server/src/client.php b/test-server/php-v2-transition-server/src/client.php index 44fe1b39..beda557a 100644 --- a/test-server/php-v2-transition-server/src/client.php +++ b/test-server/php-v2-transition-server/src/client.php @@ -19,6 +19,10 @@ function handleCreateClient() $legacyAlgorithms = $configData["enableLegacyWrappingAlgorithms"] ?? false; $clientId = Uuid::uuid4()->toString(); $kmsKeyId = $keyMaterial["kmsKeyId"] ?? null; + $instFilePut = false; + if ($instFileConfig != null) { + $instFilePut = $instFileConfig['enableInstructionFilePutObject'] ?? false; + } if ($configData == []) { return GenericServerError("Invalid config in request body", 400); @@ -55,6 +59,7 @@ function handleCreateClient() ], 'kmsKeyId' => $kmsKeyId, 'legacy' => $legacyAlgorithms, + 'instFilePut' => $instFilePut, 'created' => time() ]; diff --git a/test-server/php-v3-server/src/client.php b/test-server/php-v3-server/src/client.php index 6c40f590..2c6204bb 100644 --- a/test-server/php-v3-server/src/client.php +++ b/test-server/php-v3-server/src/client.php @@ -19,6 +19,12 @@ function handleCreateClient() $legacyAlgorithms = $configData["enableLegacyWrappingAlgorithms"] ?? false; $clientId = Uuid::uuid4()->toString(); $kmsKeyId = $keyMaterial["kmsKeyId"] ?? null; + $instFileConfig = $configData['instructionFileConfig'] ?? null; + $instFilePut = false; + if ($instFileConfig != null) { + $instFilePut = $instFileConfig['enableInstructionFilePutObject'] ?? false; + } + if (empty($configData)) { return GenericServerError("Invalid config in request body", 400); @@ -55,6 +61,7 @@ function handleCreateClient() ], 'kmsKeyId' => $kmsKeyId, 'legacy' => $legacyAlgorithms, + 'instFilePut' => $instFilePut, 'created' => time() ]; diff --git a/test-server/ruby-v2-server/lib/client_manager.rb b/test-server/ruby-v2-server/lib/client_manager.rb index c6a25610..717003bf 100644 --- a/test-server/ruby-v2-server/lib/client_manager.rb +++ b/test-server/ruby-v2-server/lib/client_manager.rb @@ -16,6 +16,7 @@ def initialize def create_client(config) # Extract configuration kms_key_id = config.dig('keyMaterial', 'kmsKeyId') + inst_file_put = config.dig('instructionFileConfig', 'enableInstructionFilePutObject') raise 'KMS Key ID is required' if kms_key_id.nil? || kms_key_id.empty? @@ -25,6 +26,7 @@ def create_client(config) kms_client: @kms_client, key_wrap_schema: :kms_context, content_encryption_schema: :aes_gcm_no_padding, + envelope_location: inst_file_put ? :instruction_file : :metadata }.tap do |hash| if !config['enableLegacyWrappingAlgorithms'].nil? || !config['enableLegacyUnauthenticatedModes'].nil? legacy_modes = config['enableLegacyWrappingAlgorithms'] || config['enableLegacyUnauthenticatedModes'] diff --git a/test-server/ruby-v3-server/lib/client_manager.rb b/test-server/ruby-v3-server/lib/client_manager.rb index 158b4462..a6fb551f 100644 --- a/test-server/ruby-v3-server/lib/client_manager.rb +++ b/test-server/ruby-v3-server/lib/client_manager.rb @@ -16,7 +16,18 @@ def initialize def create_client(config) # Extract configuration kms_key_id = config.dig('keyMaterial', 'kmsKeyId') - + inst_file_put = config.dig('instructionFileConfig', 'enableInstructionFilePutObject') + content_alg = config.dig('encryptionAlgorithm') + + # translate between canonical AlgSuite and Ruby symbols + if content_alg.nil? || content_alg == 'ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY' + content_alg = :alg_aes_256_gcm_hkdf_sha512_commit_key + elsif content_alg == 'ALG_AES_256_GCM_IV12_TAG16_NO_KDF' + content_alg = :aes_gcm_no_padding + else + raise 'Unknown content encryption algorithm provided: ' + content_alg + end + raise 'KMS Key ID is required' if kms_key_id.nil? || kms_key_id.empty? # Create S3 encryption client configuration @@ -24,7 +35,8 @@ def create_client(config) kms_key_id: kms_key_id, kms_client: @kms_client, key_wrap_schema: :kms_context, - # content_encryption_schema: :aes_gcm_no_padding, + envelope_location: inst_file_put ? :instruction_file : :metadata, + content_encryption_schema: content_alg }.tap do |hash| if !config['commitmentPolicy'].nil? hash[:commitment_policy] = case config['commitmentPolicy']