/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using Amazon.Extensions.S3.Encryption.Util;
using Amazon.Runtime;
using Amazon.S3.Model;
namespace Amazon.Extensions.S3.Encryption.Internal
{
///
/// Custom pipeline handler to encrypt the data as it is being uploaded to S3 for AmazonS3EncryptionClientV2.
///
public class SetupEncryptionHandlerV2 : SetupEncryptionHandler
{
///
/// Encryption material containing cryptographic configuration information
///
internal EncryptionMaterialsV2 EncryptionMaterials => (EncryptionMaterialsV2)EncryptionClient.EncryptionMaterials;
///
/// Construct an instance SetupEncryptionHandlerV2.
///
///
public SetupEncryptionHandlerV2(AmazonS3EncryptionClientBase encryptionClient) : base(encryptionClient)
{
}
///
protected override EncryptionInstructions GenerateInstructions(IExecutionContext executionContext)
{
EncryptionInstructions instructions = null;
if (NeedToGenerateKMSInstructions(executionContext))
{
instructions = EncryptionUtils.GenerateInstructionsForKMSMaterialsV2(EncryptionClient.KMSClient, EncryptionMaterials);
}
if (instructions == null && NeedToGenerateInstructions(executionContext))
{
instructions = EncryptionUtils.GenerateInstructionsForNonKmsMaterialsV2(EncryptionMaterials);
}
return instructions;
}
///
protected override PutObjectRequest GenerateEncryptedObjectRequestUsingInstructionFile(PutObjectRequest putObjectRequest, EncryptionInstructions instructions)
{
EncryptionUtils.AddUnencryptedContentLengthToMetadata(putObjectRequest);
// Encrypt the object data with the instruction
putObjectRequest.InputStream = EncryptionUtils.EncryptRequestUsingInstructionV2(putObjectRequest.InputStream, instructions, putObjectRequest.CalculateContentMD5Header);
// Create request for uploading instruction file
PutObjectRequest instructionFileRequest = EncryptionUtils.CreateInstructionFileRequestV2(putObjectRequest, instructions);
return instructionFileRequest;
}
#if AWS_ASYNC_API
///
protected override async System.Threading.Tasks.Task GenerateInstructionsAsync(IExecutionContext executionContext)
{
EncryptionInstructions instructions = null;
if (NeedToGenerateKMSInstructions(executionContext))
{
instructions = await EncryptionUtils.GenerateInstructionsForKMSMaterialsV2Async(EncryptionClient.KMSClient, EncryptionMaterials)
.ConfigureAwait(false);
}
if (instructions == null && NeedToGenerateInstructions(executionContext))
{
instructions = EncryptionUtils.GenerateInstructionsForNonKmsMaterialsV2(EncryptionMaterials);
}
return instructions;
}
#endif
///
protected override void GenerateEncryptedObjectRequestUsingMetadata(PutObjectRequest putObjectRequest, EncryptionInstructions instructions)
{
EncryptionUtils.AddUnencryptedContentLengthToMetadata(putObjectRequest);
// Encrypt the object data with the instruction
putObjectRequest.InputStream = EncryptionUtils.EncryptRequestUsingInstructionV2(putObjectRequest.InputStream, instructions, putObjectRequest.CalculateContentMD5Header);
// Update the metadata
EncryptionUtils.UpdateMetadataWithEncryptionInstructionsV2(putObjectRequest, instructions, EncryptionClient);
}
///
protected override void GenerateInitiateMultiPartUploadRequest(EncryptionInstructions instructions, InitiateMultipartUploadRequest initiateMultiPartUploadRequest, bool useKmsKeyWrapping)
{
ValidateConfigAndMaterials();
if (EncryptionClient.S3CryptoConfig.StorageMode == CryptoStorageMode.ObjectMetadata)
{
EncryptionUtils.UpdateMetadataWithEncryptionInstructionsV2(initiateMultiPartUploadRequest, instructions, EncryptionClient);
}
var context = new UploadPartEncryptionContext
{
StorageMode = EncryptionClient.S3CryptoConfig.StorageMode,
EncryptedEnvelopeKey = instructions.EncryptedEnvelopeKey,
EnvelopeKey = instructions.EnvelopeKey,
FirstIV = instructions.InitializationVector,
NextIV = instructions.InitializationVector,
PartNumber = 0,
CekAlgorithm = instructions.CekAlgorithm,
WrapAlgorithm = instructions.WrapAlgorithm,
};
EncryptionClient.AllMultiPartUploadRequestContexts[initiateMultiPartUploadRequest] = context;
}
///
protected override void GenerateEncryptedUploadPartRequest(UploadPartRequest request)
{
string uploadID = request.UploadId;
var contextForEncryption = this.EncryptionClient.CurrentMultiPartUploadKeys[uploadID];
var envelopeKey = contextForEncryption.EnvelopeKey;
var IV = contextForEncryption.NextIV;
var instructions = new EncryptionInstructions(EncryptionMaterials.MaterialsDescription, envelopeKey, IV);
if (request.IsLastPart == false)
{
if (contextForEncryption.IsFinalPart)
throw new AmazonClientException("Last part has already been processed, cannot upload this as the last part");
if (request.PartNumber < contextForEncryption.PartNumber)
throw new AmazonClientException($"Upload Parts must be in correct sequence. Request part number {request.PartNumber} must be >= to {contextForEncryption.PartNumber}");
UpdateRequestInputStream(request, contextForEncryption, instructions);
contextForEncryption.PartNumber = request.PartNumber;
}
else
{
UpdateRequestInputStream(request, contextForEncryption, instructions);
contextForEncryption.IsFinalPart = true;
}
((Amazon.Runtime.Internal.IAmazonWebServiceRequest)request).RequestState.Add(AmazonS3EncryptionClient.S3CryptoStream, request.InputStream);
}
private static void UpdateRequestInputStream(UploadPartRequest request, UploadPartEncryptionContext contextForEncryption, EncryptionInstructions instructions)
{
if (contextForEncryption.CryptoStream == null)
{
request.InputStream = EncryptionUtils.EncryptUploadPartRequestUsingInstructionsV2(request.InputStream, instructions);
}
else
{
request.InputStream = contextForEncryption.CryptoStream;
}
// Clear the buffer filled for retry request
var aesGcmEncryptCachingStream = request.InputStream as AesGcmEncryptCachingStream;
if (aesGcmEncryptCachingStream != null)
{
aesGcmEncryptCachingStream.ClearReadBufferToPosition();
}
}
}
}