diff --git a/Makefile b/Makefile index ec9f02e7f..1c60b7a64 100644 --- a/Makefile +++ b/Makefile @@ -11,4 +11,5 @@ duvet_report: report \ --spec-pattern "compliance/**/*.toml" \ --source-pattern "src/**/*.java" \ + --source-pattern "compliance_exceptions/*.txt" \ --html specification_compliance_report.html diff --git a/compliance_exceptions/s3-kms-keyring.txt b/compliance_exceptions/s3-kms-keyring.txt new file mode 100644 index 000000000..29ea6f49b --- /dev/null +++ b/compliance_exceptions/s3-kms-keyring.txt @@ -0,0 +1,15 @@ +// The KMS Keyring in S3EC Java v3 does not validate KMS Key IDs. +// Passing an invalid key ID will eventually lead to KMS Request failures. + +//= specification/s3-encryption/materials/s3-kms-keyring.md#initialization +//= type=exception +//# The KmsKeyring MAY validate that the AWS KMS key identifier is not null or empty. +//= specification/s3-encryption/materials/s3-kms-keyring.md#initialization +//= type=exception +//# If the KmsKeyring validates that the AWS KMS key identifier is not null or empty, then it MUST throw an exception. +//= specification/s3-encryption/materials/s3-kms-keyring.md#initialization +//= type=exception +//# The KmsKeyring MAY validate that the AWS KMS key identifier is [a valid AWS KMS Key identifier](../../framework/aws-kms/aws-kms-key-arn.md#a-valid-aws-kms-identifier). +//= specification/s3-encryption/materials/s3-kms-keyring.md#initialization +//= type=exception +//# If the KmsKeyring validates that the AWS KMS key identifier is not a valid AWS KMS Key identifier, then it MUST throw an exception. diff --git a/specification b/specification index 85d4c1769..280a89401 160000 --- a/specification +++ b/specification @@ -1 +1 @@ -Subproject commit 85d4c176901e29fb795ec551abd11fc7df076f94 +Subproject commit 280a894019cd1b4efc6b16cfb233bf1ec21bc508 diff --git a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java index e1c275bd5..5377f8e96 100644 --- a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java @@ -212,7 +212,7 @@ public static Consumer withAdditionalCo //= specification/s3-encryption/client.md#optional-api-operations //= type=implication - //# ReEncryptInstructionFile MAY be implemented by the S3EC. + //# - ReEncryptInstructionFile MAY be implemented by the S3EC. /** * Re-encrypts an instruction file with a new keyring while preserving the original encrypted object in S3. * This enables: @@ -252,7 +252,7 @@ public ReEncryptInstructionFileResponse reEncryptInstructionFile(ReEncryptInstru //Decrypt the data key using the current keyring //= specification/s3-encryption/client.md#optional-api-operations //= type=implication - //# ReEncryptInstructionFile MUST decrypt the instruction file's encrypted data key for the given object using the client's CMM. + //# - ReEncryptInstructionFile MUST decrypt the instruction file's encrypted data key for the given object using the client's CMM. DecryptionMaterials decryptedMaterials = this._cryptoMaterialsManager.decryptMaterials( DecryptMaterialsRequest.builder() .algorithmSuite(algorithmSuite) @@ -273,7 +273,7 @@ public ReEncryptInstructionFileResponse reEncryptInstructionFile(ReEncryptInstru //Re-encrypt the data key with the new keyring while preserving other cryptographic parameters //= specification/s3-encryption/client.md#optional-api-operations //= type=implication - //# ReEncryptInstructionFile MUST re-encrypt the plaintext data key with a provided keyring. + //# - ReEncryptInstructionFile MUST re-encrypt the plaintext data key with a provided keyring. RawKeyring newKeyring = reEncryptInstructionFileRequest.newKeyring(); EncryptionMaterials encryptedMaterials = newKeyring.onEncrypt(encryptionMaterials); @@ -317,7 +317,7 @@ private void enforceRotation(EncryptionMaterials newEncryptionMaterials, GetObje //= specification/s3-encryption/client.md#required-api-operations //= type=implication - //# PutObject MUST be implemented by the S3EC. + //# - PutObject MUST be implemented by the S3EC. /** * See {@link S3EncryptionClient#putObject(PutObjectRequest, RequestBody)}. *

@@ -357,7 +357,7 @@ public PutObjectResponse putObject(PutObjectRequest putObjectRequest, RequestBod try { //= specification/s3-encryption/client.md#required-api-operations //= type=implication - //# PutObject MUST encrypt its input data before it is uploaded to S3. + //# - PutObject MUST encrypt its input data before it is uploaded to S3. CompletableFuture futurePut = pipeline.putObject(putObjectRequest, AsyncRequestBody.fromInputStream( requestBody.contentStreamProvider().newStream(), @@ -384,7 +384,7 @@ public PutObjectResponse putObject(PutObjectRequest putObjectRequest, RequestBod //= specification/s3-encryption/client.md#required-api-operations //= type=implication - //# GetObject MUST be implemented by the S3EC. + //# - GetObject MUST be implemented by the S3EC. /** * See {@link S3EncryptionClient#getObject(GetObjectRequest, ResponseTransformer)} *

@@ -408,7 +408,7 @@ public T getObject(GetObjectRequest getObjectRequest, //= specification/s3-encryption/client.md#required-api-operations //= type=implication - //# GetObject MUST decrypt data received from the S3 server and return it as plaintext. + //# - GetObject MUST decrypt data received from the S3 server and return it as plaintext. GetEncryptedObjectPipeline pipeline = GetEncryptedObjectPipeline.builder() .s3AsyncClient(_wrappedAsyncClient) .cryptoMaterialsManager(_cryptoMaterialsManager) @@ -518,7 +518,7 @@ private T onAbort(UploadObjectObserver observer, T t) { //= specification/s3-encryption/client.md#required-api-operations //= type=implication - //# DeleteObject MUST be implemented by the S3EC. + //# - DeleteObject MUST be implemented by the S3EC. /** * See {@link S3Client#deleteObject(DeleteObjectRequest)}. *

@@ -538,11 +538,11 @@ public DeleteObjectResponse deleteObject(DeleteObjectRequest deleteObjectRequest try { //= specification/s3-encryption/client.md#required-api-operations //= type=implementation - //# DeleteObject MUST delete the given object key. + //# - DeleteObject MUST delete the given object key. DeleteObjectResponse deleteObjectResponse = _wrappedAsyncClient.deleteObject(actualRequest).join(); //= specification/s3-encryption/client.md#required-api-operations //= type=implementation - //# DeleteObject MUST delete the associated instruction file using the default instruction file suffix. + //# - DeleteObject MUST delete the associated instruction file using the default instruction file suffix. String instructionObjectKey = deleteObjectRequest.key() + DEFAULT_INSTRUCTION_FILE_SUFFIX; _wrappedAsyncClient.deleteObject(builder -> builder .overrideConfiguration(API_NAME_INTERCEPTOR) @@ -559,7 +559,7 @@ public DeleteObjectResponse deleteObject(DeleteObjectRequest deleteObjectRequest //= specification/s3-encryption/client.md#required-api-operations //= type=implication - //# DeleteObjects MUST be implemented by the S3EC. + //# - DeleteObjects MUST be implemented by the S3EC. /** * See {@link S3Client#deleteObjects(DeleteObjectsRequest)}. *

@@ -578,11 +578,11 @@ public DeleteObjectsResponse deleteObjects(DeleteObjectsRequest deleteObjectsReq try { //= specification/s3-encryption/client.md#required-api-operations //= type=implementation - //# DeleteObjects MUST delete each of the given objects. + //# - DeleteObjects MUST delete each of the given objects. DeleteObjectsResponse deleteObjectsResponse = _wrappedAsyncClient.deleteObjects(actualRequest).join(); //= specification/s3-encryption/client.md#required-api-operations //= type=implementation - //# DeleteObjects MUST delete each of the corresponding instruction files using the default instruction file suffix. + //# - DeleteObjects MUST delete each of the corresponding instruction files using the default instruction file suffix. List deleteObjects = instructionFileKeysToDelete(deleteObjectsRequest); _wrappedAsyncClient.deleteObjects(DeleteObjectsRequest.builder() .overrideConfiguration(API_NAME_INTERCEPTOR) @@ -599,7 +599,7 @@ public DeleteObjectsResponse deleteObjects(DeleteObjectsRequest deleteObjectsReq //= specification/s3-encryption/client.md#optional-api-operations //= type=implication - //# CreateMultipartUpload MAY be implemented by the S3EC. + //# - CreateMultipartUpload MAY be implemented by the S3EC. /** * See {@link S3Client#createMultipartUpload(CreateMultipartUploadRequest)} *

@@ -623,7 +623,7 @@ public CreateMultipartUploadResponse createMultipartUpload(CreateMultipartUpload //= specification/s3-encryption/client.md#optional-api-operations //= type=implication - //# UploadPart MAY be implemented by the S3EC. + //# - UploadPart MAY be implemented by the S3EC. /** * See {@link S3Client#uploadPart(UploadPartRequest, RequestBody)} * @@ -649,7 +649,7 @@ public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requ //= specification/s3-encryption/client.md#optional-api-operations //= type=implication - //# CompleteMultipartUpload MAY be implemented by the S3EC. + //# - CompleteMultipartUpload MAY be implemented by the S3EC. /** * See {@link S3Client#completeMultipartUpload(CompleteMultipartUploadRequest)} * @param request the request instance @@ -669,7 +669,7 @@ public CompleteMultipartUploadResponse completeMultipartUpload(CompleteMultipart //= specification/s3-encryption/client.md#optional-api-operations //= type=implication - //# AbortMultipartUpload MAY be implemented by the S3EC. + //# - AbortMultipartUpload MAY be implemented by the S3EC. /** * See {@link S3Client#abortMultipartUpload(AbortMultipartUploadRequest)} * @param request the request instance @@ -763,7 +763,7 @@ private Builder() { @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Pass mutability into wrapping client") public Builder wrappedClient(S3Client _wrappedClient) { //= specification/s3-encryption/client.md#wrapped-s3-client-s - //= type=exception + //= type=implementation //# The S3EC MUST NOT support use of S3EC as the provided S3 client during its initialization; it MUST throw an exception in this case. if (_wrappedClient instanceof S3EncryptionClient) { throw new S3EncryptionClientException("Cannot use S3EncryptionClient as wrapped client"); @@ -785,7 +785,7 @@ public Builder wrappedClient(S3Client _wrappedClient) { @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Pass mutability into wrapping client") public Builder wrappedAsyncClient(S3AsyncClient _wrappedAsyncClient) { //= specification/s3-encryption/client.md#wrapped-s3-client-s - //= type=exception + //= type=implementation //# The S3EC MUST NOT support use of S3EC as the provided S3 client during its initialization; it MUST throw an exception in this case. if (_wrappedAsyncClient instanceof S3AsyncEncryptionClient) { throw new S3EncryptionClientException("Cannot use S3AsyncEncryptionClient as wrapped client"); @@ -885,7 +885,7 @@ private void checkKeyOptions() { } //= specification/s3-encryption/client.md#cryptographic-materials - //= type=exception + //= type=implementation //# If both a CMM and a Keyring are provided, the S3EC MUST throw an exception. throw new S3EncryptionClientException("Only one may be set of: crypto materials manager, keyring, AES key, RSA key pair, KMS key id"); } diff --git a/src/main/java/software/amazon/encryption/s3/internal/GetEncryptedObjectPipeline.java b/src/main/java/software/amazon/encryption/s3/internal/GetEncryptedObjectPipeline.java index b002b42b8..2e4898608 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/GetEncryptedObjectPipeline.java +++ b/src/main/java/software/amazon/encryption/s3/internal/GetEncryptedObjectPipeline.java @@ -80,7 +80,7 @@ private DecryptionMaterials prepareMaterialsFromRequest(final GetObjectRequest g //# When enabled, the S3EC MUST be able to decrypt objects encrypted with all content encryption algorithms (both legacy and fully supported). if (!_enableLegacyUnauthenticatedModes && algorithmSuite.isLegacy()) { //= specification/s3-encryption/client.md#enable-legacy-unauthenticated-modes - //= type=exception + //= type=implementation //# When disabled, the S3EC MUST NOT decrypt objects encrypted using legacy content encryption algorithms; it MUST throw an exception when attempting to decrypt an object encrypted with a legacy content encryption algorithm. throw new S3EncryptionClientException("Enable legacy unauthenticated modes to use legacy content decryption: " + algorithmSuite.cipherName()); } diff --git a/src/main/java/software/amazon/encryption/s3/internal/MultipartUploadObjectPipeline.java b/src/main/java/software/amazon/encryption/s3/internal/MultipartUploadObjectPipeline.java index ccec2e064..be5f6e24d 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/MultipartUploadObjectPipeline.java +++ b/src/main/java/software/amazon/encryption/s3/internal/MultipartUploadObjectPipeline.java @@ -76,7 +76,7 @@ public CreateMultipartUploadResponse createMultipartUpload(CreateMultipartUpload //= specification/s3-encryption/client.md#optional-api-operations //= type=implication - //# If implemented, CreateMultipartUpload MUST initiate a multipart upload. + //# - If implemented, CreateMultipartUpload MUST initiate a multipart upload. CreateMultipartUploadResponse response = _s3AsyncClient.createMultipartUpload(request).join(); MultipartUploadMaterials mpuMaterials = MultipartUploadMaterials.builder() @@ -138,11 +138,11 @@ public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requ final UploadPartResponse response; //= specification/s3-encryption/client.md#optional-api-operations //= type=implication - //# Each part MUST be encrypted in sequence. + //# - Each part MUST be encrypted in sequence. materials.beginPartUpload(actualRequest.partNumber(), partContentLength); //= specification/s3-encryption/client.md#optional-api-operations //= type=implication - //# Each part MUST be encrypted using the same cipher instance for each part. + //# - Each part MUST be encrypted using the same cipher instance for each part. Cipher cipher = materials.getCipher(materials.getIv()); ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); @@ -150,7 +150,7 @@ public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requ try { //= specification/s3-encryption/client.md#optional-api-operations //= type=implication - //# UploadPart MUST encrypt each part. + //# - UploadPart MUST encrypt each part. final AsyncRequestBody cipherAsyncRequestBody = new CipherAsyncRequestBody( AsyncRequestBody.fromInputStream( requestBody.contentStreamProvider().newStream(), @@ -170,8 +170,6 @@ public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requ } // Ensures parts are not retried to avoid corrupting ciphertext AsyncRequestBody noRetryBody = new NoRetriesAsyncRequestBody(cipherAsyncRequestBody); - //= specification/s3-encryption/client.md#optional-api-operations - //= type=implication response = _s3AsyncClient.uploadPart(actualRequest, noRetryBody).join(); } finally { materials.endPartUpload(); @@ -202,7 +200,7 @@ public CompleteMultipartUploadResponse completeMultipartUpload(CompleteMultipart //= specification/s3-encryption/client.md#optional-api-operations //= type=implication - //# CompleteMultipartUpload MUST complete the multipart upload. + //# - CompleteMultipartUpload MUST complete the multipart upload. CompleteMultipartUploadResponse response = _s3AsyncClient.completeMultipartUpload(actualRequest).join(); _multipartUploadMaterials.remove(uploadId); @@ -216,7 +214,7 @@ public AbortMultipartUploadResponse abortMultipartUpload(AbortMultipartUploadReq .build(); //= specification/s3-encryption/client.md#optional-api-operations //= type=implication - //# AbortMultipartUpload MUST abort the multipart upload. + //# - AbortMultipartUpload MUST abort the multipart upload. return _s3AsyncClient.abortMultipartUpload(actualRequest).join(); } diff --git a/src/main/java/software/amazon/encryption/s3/materials/Keyring.java b/src/main/java/software/amazon/encryption/s3/materials/Keyring.java index e6526108e..5859be184 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/Keyring.java +++ b/src/main/java/software/amazon/encryption/s3/materials/Keyring.java @@ -4,11 +4,38 @@ import java.util.List; +//= specification/s3-encryption/materials/keyrings.md#interface +//= type=implication +//# The Keyring interface and its operations SHOULD adhere to the naming conventions of the implementation language. /** * Keyring defines the interface for wrapping data keys. A {@link CryptographicMaterialsManager} will use * keyrings to encrypt and decrypt data keys. */ public interface Keyring { + //= specification/s3-encryption/materials/keyrings.md#interface + //= type=implication + //# The Keyring interface and its operations SHOULD adhere to the naming conventions of the implementation language. + //= specification/s3-encryption/materials/keyrings.md#interface + //= type=implication + //# The Keyring interface MUST include the OnEncrypt operation. + //= specification/s3-encryption/materials/keyrings.md#interface + //= type=implication + //# The OnEncrypt operation MUST accept an instance of EncryptionMaterials as input. + //= specification/s3-encryption/materials/keyrings.md#interface + //= type=implication + //# The OnEncrypt operation MUST return an instance of EncryptionMaterials as output. EncryptionMaterials onEncrypt(final EncryptionMaterials materials); + //= specification/s3-encryption/materials/keyrings.md#interface + //= type=implication + //# The Keyring interface and its operations SHOULD adhere to the naming conventions of the implementation language. + //= specification/s3-encryption/materials/keyrings.md#interface + //= type=implication + //# The Keyring interface MUST include the OnDecrypt operation. + //= specification/s3-encryption/materials/keyrings.md#interface + //= type=implication + //# The OnDecrypt operation MUST accept an instance of DecryptionMaterials and a collection of EncryptedDataKey instances as input. + //= specification/s3-encryption/materials/keyrings.md#interface + //= type=implication + //# The OnDecrypt operation MUST return an instance of DecryptionMaterials as output. DecryptionMaterials onDecrypt(final DecryptionMaterials materials, final List encryptedDataKeys); } diff --git a/src/main/java/software/amazon/encryption/s3/materials/KmsKeyring.java b/src/main/java/software/amazon/encryption/s3/materials/KmsKeyring.java index 6088811b3..bd3a90c0a 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/KmsKeyring.java +++ b/src/main/java/software/amazon/encryption/s3/materials/KmsKeyring.java @@ -29,6 +29,9 @@ import java.util.Objects; import java.util.Optional; +//= specification/s3-encryption/materials/s3-kms-keyring.md#interface +//= type=implication +//# The KmsKeyring MUST implement the [Keyring interface](keyring-interface.md#interface) and include the behavior described in the [S3 Keyring](s3-keyring.md). /** * This keyring can wrap keys with the active keywrap algorithm and * unwrap with the active and legacy algorithms for KMS keys. @@ -41,10 +44,19 @@ public class KmsKeyring extends S3Keyring { private final KmsClient _kmsClient; private final String _wrappingKeyId; + //= specification/s3-encryption/materials/s3-kms-keyring.md#supported-wrapping-algorithm-modes + //= type=implication + //# The KmsKeyring MUST NOT support encryption using KmsV1 mode. private final DecryptDataKeyStrategy _kmsStrategy = new DecryptDataKeyStrategy() { + //= specification/s3-encryption/materials/s3-kms-keyring.md#decryptdatakey + //= type=implication + //# If the Key Provider Info of the Encrypted Data Key is "kms", the KmsKeyring MUST attempt to decrypt using KmsV1 mode. private static final String KEY_PROVIDER_INFO = "kms"; + //= specification/s3-encryption/materials/s3-kms-keyring.md#supported-wrapping-algorithm-modes + //= type=implication + //# The KmsV1 mode MUST be only enabled when legacy wrapping algorithms are enabled. @Override public boolean isLegacy() { return true; @@ -55,25 +67,58 @@ public String keyProviderInfo() { return KEY_PROVIDER_INFO; } + //= specification/s3-encryption/materials/s3-kms-keyring.md#supported-wrapping-algorithm-modes + //= type=implication + //# The KmsKeyring MUST support decryption using KmsV1 mode. @Override public byte[] decryptDataKey(DecryptionMaterials materials, byte[] encryptedDataKey) { + //= specification/s3-encryption/materials/s3-kms-keyring.md#kmsv1 + //= type=implication + //# To attempt to decrypt a particular [encrypted data key](../structures.md#encrypted-data-key), the + //# KmsKeyring MUST call [AWS KMS Decrypt](https://docs.aws.amazon.com/kms/latest/APIReference/API_Decrypt.html) with the configured AWS KMS client. DecryptRequest request = DecryptRequest.builder() + //= specification/s3-encryption/materials/s3-kms-keyring.md#kmsv1 + //= type=implication + //# - `KeyId` MUST be the configured AWS KMS key identifier. .keyId(_wrappingKeyId) + //= specification/s3-encryption/materials/s3-kms-keyring.md#kmsv1 + //= type=implication + //# - `EncryptionContext` MUST be the [encryption context](../structures.md#encryption-context) + //# included in the input [decryption materials](../structures.md#decryption-materials). .encryptionContext(materials.encryptionContext()) + //= specification/s3-encryption/materials/s3-kms-keyring.md#kmsv1 + //= type=implication + //# - `CiphertextBlob` MUST be the [encrypted data key ciphertext](../structures.md#ciphertext). .ciphertextBlob(SdkBytes.fromByteArray(encryptedDataKey)) + //= specification/s3-encryption/materials/s3-kms-keyring.md#kmsv1 + //= type=implication + //# - A custom API Name or User Agent string SHOULD be provided in order to provide metrics on KMS + //# calls associated with the S3 Encryption Client. .overrideConfiguration(builder -> builder.addApiName(API_NAME)) .build(); + //= specification/s3-encryption/materials/s3-kms-keyring.md#kmsv1 + //= type=implication + //# If the KmsKeyring fails to successfully decrypt the [encrypted data key](../structures.md#encrypted-data-key), then it MUST throw an exception. DecryptResponse response = _kmsClient.decrypt(request); + //= specification/s3-encryption/materials/s3-kms-keyring.md#kmsv1 + //= type=implication + //# The KmsKeyring MUST immediately return the plaintext as a collection of bytes. return response.plaintext().asByteArray(); } }; private final DataKeyStrategy _kmsContextStrategy = new DataKeyStrategy() { + //= specification/s3-encryption/materials/s3-kms-keyring.md#decryptdatakey + //= type=implication + //# If the Key Provider Info of the Encrypted Data Key is "kms+context", the KmsKeyring MUST attempt to decrypt using Kms+Context mode. private static final String KEY_PROVIDER_INFO = "kms+context"; private static final String ENCRYPTION_CONTEXT_ALGORITHM_KEY = "aws:x-amz-cek-alg"; + //= specification/s3-encryption/materials/s3-kms-keyring.md#supported-wrapping-algorithm-modes + //= type=implication + //# The Kms+Context mode MUST be enabled as a fully-supported (non-legacy) wrapping algorithm. @Override public boolean isLegacy() { return false; @@ -149,20 +194,51 @@ public EncryptionMaterials generateDataKey(EncryptionMaterials materials) { .build(); } + //= specification/s3-encryption/materials/s3-kms-keyring.md#encryptdatakey + //= type=implication + //# The KmsKeyring MUST implement the EncryptDataKey method. + //= specification/s3-encryption/materials/s3-kms-keyring.md#supported-wrapping-algorithm-modes + //= type=implication + //# The KmsKeyring MUST support encryption using Kms+Context mode. @Override public byte[] encryptDataKey(SecureRandom secureRandom, EncryptionMaterials materials) { HashMap encryptionContext = new HashMap<>(materials.encryptionContext()); EncryptRequest request = EncryptRequest.builder() + //= specification/s3-encryption/materials/s3-kms-keyring.md#encryptdatakey + //= type=implication + //# - `KeyId` MUST be the configured AWS KMS key identifier. .keyId(_wrappingKeyId) + //= specification/s3-encryption/materials/s3-kms-keyring.md#encryptdatakey + //= type=implication + //# - `EncryptionContext` MUST be the [encryption context](../structures.md#encryption-context) included + //# in the input [encryption materials](../structures.md#encryption-materials). .encryptionContext(encryptionContext) + //= specification/s3-encryption/materials/s3-kms-keyring.md#encryptdatakey + //= type=implication + //# - `PlaintextDataKey` MUST be the plaintext data key in the [encryption materials](../structures.md#encryption-materials). .plaintext(SdkBytes.fromByteArray(materials.plaintextDataKey())) + //= specification/s3-encryption/materials/s3-kms-keyring.md#encryptdatakey + //= type=implication + //# - A custom API Name or User Agent string SHOULD be provided in order to provide metrics on KMS calls associated with the S3 Encryption Client. .overrideConfiguration(builder -> builder.addApiName(API_NAME)) .build(); + //= specification/s3-encryption/materials/s3-kms-keyring.md#encryptdatakey + //= type=implication + //# The keyring MUST call [AWS KMS Encrypt](https://docs.aws.amazon.com/kms/latest/APIReference/API_Encrypt.html) using the configured AWS KMS client. + //= specification/s3-encryption/materials/s3-kms-keyring.md#encryptdatakey + //= type=implication + //# If the call to [AWS KMS Encrypt](https://docs.aws.amazon.com/kms/latest/APIReference/API_Encrypt.html) does not succeed, OnEncrypt MUST fail. EncryptResponse response = _kmsClient.encrypt(request); + //= specification/s3-encryption/materials/s3-kms-keyring.md#encryptdatakey + //= type=implication + //# If the call to AWS KMS Encrypt is successful, OnEncrypt MUST return the `CiphertextBlob` as a collection of bytes. return response.ciphertextBlob().asByteArray(); } + //= specification/s3-encryption/materials/s3-kms-keyring.md#supported-wrapping-algorithm-modes + //= type=implication + //# The KmsKeyring MUST support decryption using Kms+Context mode. @Override public byte[] decryptDataKey(DecryptionMaterials materials, byte[] encryptedDataKey) { Map requestEncryptionContext = new HashMap<>(); @@ -177,23 +253,56 @@ public byte[] decryptDataKey(DecryptionMaterials materials, byte[] encryptedData } } + + //= specification/s3-encryption/materials/s3-kms-keyring.md#kms-context + //= type=implication + //# When decrypting using Kms+Context mode, the KmsKeyring MUST validate the provided (request) encryption context with the stored (materials) encryption context. // We are validating the encryption context to match S3EC V2 behavior // Refer to KMSMaterialsHandler in the V2 client for details Map materialsEncryptionContextCopy = new HashMap<>(materials.encryptionContext()); + //= specification/s3-encryption/materials/s3-kms-keyring.md#kms-context + //= type=implication + //# The stored encryption context with the two reserved keys removed MUST match the provided encryption context. materialsEncryptionContextCopy.remove(KEY_ID_CONTEXT_KEY); materialsEncryptionContextCopy.remove(ENCRYPTION_CONTEXT_ALGORITHM_KEY); if (!materialsEncryptionContextCopy.equals(requestEncryptionContext)) { + //= specification/s3-encryption/materials/s3-kms-keyring.md#kms-context + //= type=implication + //# If the stored encryption context with the two reserved keys removed does not match the provided encryption context, the KmsKeyring MUST throw an exception. throw new S3EncryptionClientException("Provided encryption context does not match information retrieved from S3"); } + //= specification/s3-encryption/materials/s3-kms-keyring.md#kms-context + //= type=implication + //# To attempt to decrypt a particular [encrypted data key](../structures.md#encrypted-data-key), the KmsKeyring + //# MUST call [AWS KMS Decrypt](https://docs.aws.amazon.com/kms/latest/APIReference/API_Decrypt.html) with the configured AWS KMS client. DecryptRequest request = DecryptRequest.builder() + //= specification/s3-encryption/materials/s3-kms-keyring.md#kms-context + //= type=implication + //# - `KeyId` MUST be the configured AWS KMS key identifier. .keyId(_wrappingKeyId) + //= specification/s3-encryption/materials/s3-kms-keyring.md#kms-context + //= type=implication + //# - `EncryptionContext` MUST be the [encryption context](../structures.md#encryption-context) + //# included in the input [decryption materials](../structures.md#decryption-materials). .encryptionContext(materials.encryptionContext()) + //= specification/s3-encryption/materials/s3-kms-keyring.md#kms-context + //= type=implication + //# - `CiphertextBlob` MUST be the [encrypted data key ciphertext](../structures.md#ciphertext). .ciphertextBlob(SdkBytes.fromByteArray(encryptedDataKey)) + //= specification/s3-encryption/materials/s3-kms-keyring.md#kms-context + //= type=implication + //# - A custom API Name or User Agent string SHOULD be provided in order to provide metrics on KMS calls associated with the S3 Encryption Client. .overrideConfiguration(builder -> builder.addApiName(API_NAME)) .build(); + //= specification/s3-encryption/materials/s3-kms-keyring.md#kms-context + //= type=implication + //# If the KmsKeyring fails to successfully decrypt the [encrypted data key](../structures.md#encrypted-data-key), then it MUST throw an exception. DecryptResponse response = _kmsClient.decrypt(request); + //= specification/s3-encryption/materials/s3-kms-keyring.md#kms-context + //= type=implication + //# The KmsKeyring MUST immediately return the plaintext as a collection of bytes. return response.plaintext().asByteArray(); } @@ -207,6 +316,9 @@ public KmsKeyring(Builder builder) { _kmsClient = builder._kmsClient; _wrappingKeyId = builder._wrappingKeyId; + //= specification/s3-encryption/materials/s3-kms-keyring.md#decryptdatakey + //= type=implication + //# The KmsKeyring MUST determine whether to decrypt using KmsV1 mode or Kms+Context mode. decryptDataKeyStrategies.put(_kmsStrategy.keyProviderInfo(), _kmsStrategy); decryptDataKeyStrategies.put(_kmsContextStrategy.keyProviderInfo(), _kmsContextStrategy); } @@ -243,6 +355,9 @@ protected Builder builder() { return this; } + //= specification/s3-encryption/materials/s3-kms-keyring.md#initialization + //= type=implication + //# On initialization, the caller MAY provide an AWS KMS SDK client instance. /** * Note that this does NOT create a defensive clone of KmsClient. Any modifications made to the wrapped * client will be reflected in this Builder. @@ -253,12 +368,18 @@ public Builder kmsClient(KmsClient kmsClient) { return this; } + //= specification/s3-encryption/materials/s3-kms-keyring.md#initialization + //= type=implication + //# On initialization, the caller MUST provide an AWS KMS key identifier. public Builder wrappingKeyId(String wrappingKeyId) { _wrappingKeyId = wrappingKeyId; return this; } public KmsKeyring build() { + //= specification/s3-encryption/materials/s3-kms-keyring.md#initialization + //= type=implication + //# If the caller does not provide an AWS KMS SDK client instance or provides a null value, the KmsKeyring MUST create a default KMS client instance. if (_kmsClient == null) { _kmsClient = KmsClient.create(); } diff --git a/src/main/java/software/amazon/encryption/s3/materials/S3Keyring.java b/src/main/java/software/amazon/encryption/s3/materials/S3Keyring.java index 822f09e8c..993768cb6 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/S3Keyring.java +++ b/src/main/java/software/amazon/encryption/s3/materials/S3Keyring.java @@ -13,6 +13,15 @@ import java.util.List; import java.util.Map; +//= specification/s3-encryption/materials/s3-keyring.md#overview +//= type=implication +//# The S3EC SHOULD implement an S3 Keyring to consolidate validation and other functionality common to all S3 Keyrings. +//= specification/s3-encryption/materials/s3-keyring.md#overview +//= type=implication +//# If implemented, the S3 Keyring MUST implement the Keyring interface. +//= specification/s3-encryption/materials/s3-keyring.md#overview +//= type=implication +//# If implemented, the S3 Keyring MUST NOT be able to be instantiated as a Keyring instance. /** * This serves as the base class for all the keyrings in the S3 encryption client. * Shared functionality is all performed here. @@ -60,16 +69,24 @@ public EncryptionMaterials onEncrypt(EncryptionMaterials materials) { // Allow encrypt strategy to modify the materials if necessary materials = encryptStrategy.modifyMaterials(materials); + //= specification/s3-encryption/materials/s3-keyring.md#onencrypt + //= type=implication + //# If the Plaintext Data Key in the input EncryptionMaterials is null, the S3 Keyring MUST call the GenerateDataKey method using the materials. if (materials.plaintextDataKey() == null) { materials = generateDataKeyStrategy().generateDataKey(materials); } - // Return materials if they already have an encrypted data key. + //= specification/s3-encryption/materials/s3-keyring.md#onencrypt + //= type=implication + //# If the materials returned from GenerateDataKey contain an EncryptedDataKey, the S3 Keyring MUST return the materials. if (!materials.encryptedDataKeys().isEmpty()) { return materials; } try { + //= specification/s3-encryption/materials/s3-keyring.md#onencrypt + //= type=implication + //# If the materials returned from GenerateDataKey do not contain an EncryptedDataKey, the S3 Keyring MUST call the EncryptDataKey method using the materials. byte[] encryptedDataKeyCiphertext = encryptStrategy.encryptDataKey(_secureRandom, materials); EncryptedDataKey encryptedDataKey = EncryptedDataKey.builder() .keyProviderId(S3Keyring.KEY_PROVIDER_ID) @@ -88,20 +105,35 @@ public EncryptionMaterials onEncrypt(EncryptionMaterials materials) { } } + //= specification/s3-encryption/materials/s3-keyring.md#abstract-methods + //= type=implication + //# - The S3 Keyring MUST define an abstract method GenerateDataKey. abstract protected GenerateDataKeyStrategy generateDataKeyStrategy(); + //= specification/s3-encryption/materials/s3-keyring.md#abstract-methods + //= type=implication + //# - The S3 Keyring MUST define an abstract method EncryptDataKey. abstract protected EncryptDataKeyStrategy encryptDataKeyStrategy(); @Override public DecryptionMaterials onDecrypt(final DecryptionMaterials materials, List encryptedDataKeys) { + //= specification/s3-encryption/materials/s3-keyring.md#ondecrypt + //= type=implementation + //# If the input DecryptionMaterials contains a Plaintext Data Key, the S3 Keyring MUST throw an exception. if (materials.plaintextDataKey() != null) { throw new S3EncryptionClientException("Decryption materials already contains a plaintext data key."); } + //= specification/s3-encryption/materials/s3-keyring.md#ondecrypt + //= type=implementation + //# If the input collection of EncryptedDataKey instances contains any number of EDKs other than 1, the S3 Keyring MUST throw an exception. if (encryptedDataKeys.size() != 1) { throw new S3EncryptionClientException("Only one encrypted data key is supported, found: " + encryptedDataKeys.size()); } + //= specification/s3-encryption/materials/s3-keyring.md#ondecrypt + //= type=implementation + //# If the input DecryptionMaterials contains a Plaintext Data Key, the S3 Keyring MUST throw an exception. EncryptedDataKey encryptedDataKey = encryptedDataKeys.get(0); final String keyProviderId = encryptedDataKey.keyProviderId(); if (!KEY_PROVIDER_ID.equals(keyProviderId)) { @@ -120,12 +152,15 @@ public DecryptionMaterials onDecrypt(final DecryptionMaterials materials, List decryptDataKeyStrategies(); abstract public static class Builder> {