using System;
using System.Collections.Generic;
using Amazon.Runtime;
using Amazon.S3.Model;
using Amazon.Runtime.Internal;
namespace Amazon.Extensions.S3.Encryption.Internal
{
///
/// Custom pipeline handler to encrypt the data as it is being uploaded to S3.
///
public abstract class SetupEncryptionHandler : PipelineHandler
{
///
/// Construct an instance SetupEncryptionHandler.
///
///
public SetupEncryptionHandler(AmazonS3EncryptionClientBase encryptionClient)
{
this.EncryptionClient = encryptionClient;
}
///
/// Gets the EncryptionClient property which is the AmazonS3EncryptionClient that is encrypting the object.
///
public AmazonS3EncryptionClientBase EncryptionClient
{
get;
private set;
}
///
/// Calls pre invoke logic before calling the next handler
/// in the pipeline.
///
/// The execution context which contains both the
/// requests and response context.
public override void InvokeSync(IExecutionContext executionContext)
{
PreInvoke(executionContext);
base.InvokeSync(executionContext);
}
///
/// Encrypts the S3 object being uploaded.
///
/// The execution context, it contains the request and response context.
protected void PreInvoke(IExecutionContext executionContext)
{
ThrowIfRangeGet(executionContext);
var instructions = GenerateInstructions(executionContext);
var putObjectRequest = executionContext.RequestContext.OriginalRequest as PutObjectRequest;
if (putObjectRequest != null)
{
#if BCL
EncryptObject(instructions, putObjectRequest);
#else
EncryptObjectAsync(instructions, putObjectRequest).GetAwaiter().GetResult();
#endif
}
PreInvokeSynchronous(executionContext, instructions);
}
#if BCL
private void EncryptObject(EncryptionInstructions instructions, PutObjectRequest putObjectRequest)
{
ValidateConfigAndMaterials();
if (EncryptionClient.S3CryptoConfig.StorageMode == CryptoStorageMode.ObjectMetadata)
{
GenerateEncryptedObjectRequestUsingMetadata(putObjectRequest, instructions);
}
else
{
var instructionFileRequest = GenerateEncryptedObjectRequestUsingInstructionFile(putObjectRequest, instructions);
EncryptionClient.S3ClientForInstructionFile.PutObject(instructionFileRequest);
}
}
#endif
///
/// Updates the request where the instruction file contains encryption information
/// and the input stream contains the encrypted object contents.
///
/// The request whose contents are to be encrypted.
/// EncryptionInstructions instructions used for creating encrypt stream
protected abstract PutObjectRequest GenerateEncryptedObjectRequestUsingInstructionFile(PutObjectRequest putObjectRequest, EncryptionInstructions instructions);
///
/// Updates the request where the metadata contains encryption information
/// and the input stream contains the encrypted object contents.
///
/// The request whose contents are to be encrypted.
/// EncryptionInstructions instructions used for creating encrypt stream
protected abstract void GenerateEncryptedObjectRequestUsingMetadata(PutObjectRequest putObjectRequest, EncryptionInstructions instructions);
///
/// Generate encryption instructions
///
/// The execution context, it contains the request and response context.
/// EncryptionInstructions to be used for encryption
protected abstract EncryptionInstructions GenerateInstructions(IExecutionContext executionContext);
#if AWS_ASYNC_API
///
/// Calls pre invoke logic before calling the next handler
/// in the pipeline.
///
/// The response type for the current request.
/// The execution context, it contains the request and response context.
/// A task that represents the asynchronous operation.
public override async System.Threading.Tasks.Task InvokeAsync(IExecutionContext executionContext)
{
await PreInvokeAsync(executionContext).ConfigureAwait(false);
return await base.InvokeAsync(executionContext).ConfigureAwait(false);
}
///
/// Encrypts the S3 object being uploaded.
///
/// The execution context, it contains the request and response context.
protected async System.Threading.Tasks.Task PreInvokeAsync(IExecutionContext executionContext)
{
ThrowIfRangeGet(executionContext);
EncryptionInstructions instructions = await GenerateInstructionsAsync(executionContext).ConfigureAwait(false);
var request = executionContext.RequestContext.OriginalRequest;
var putObjectRequest = request as PutObjectRequest;
if (putObjectRequest != null)
{
await EncryptObjectAsync(instructions, putObjectRequest).ConfigureAwait(false);
}
PreInvokeSynchronous(executionContext, instructions);
}
private async System.Threading.Tasks.Task EncryptObjectAsync(EncryptionInstructions instructions, PutObjectRequest putObjectRequest)
{
ValidateConfigAndMaterials();
if (EncryptionClient.S3CryptoConfig.StorageMode == CryptoStorageMode.ObjectMetadata)
{
GenerateEncryptedObjectRequestUsingMetadata(putObjectRequest, instructions);
}
else
{
var instructionFileRequest = GenerateEncryptedObjectRequestUsingInstructionFile(putObjectRequest, instructions);
await EncryptionClient.S3ClientForInstructionFile.PutObjectAsync(instructionFileRequest)
.ConfigureAwait(false);
}
}
///
/// Generate encryption instructions asynchronously
///
/// The execution context, it contains the request and response context.
/// EncryptionInstructions to be used for encryption
protected abstract System.Threading.Tasks.Task GenerateInstructionsAsync(IExecutionContext executionContext);
#elif AWS_APM_API
///
/// Calls pre invoke logic before calling the next handler
/// in the pipeline.
///
/// The execution context which contains both the
/// requests and response context.
/// IAsyncResult which represent an async operation.
public override IAsyncResult InvokeAsync(IAsyncExecutionContext executionContext)
{
IExecutionContext syncExecutionContext = ExecutionContext.CreateFromAsyncContext(executionContext);
ThrowIfRangeGet(syncExecutionContext);
if (NeedToGenerateKMSInstructions(syncExecutionContext))
throw new NotSupportedException("The AWS SDK for .NET Framework 3.5 version of " +
EncryptionClient.GetType().Name + " does not support KMS key wrapping via the async programming model. " +
"Please use the synchronous version instead.");
var instructions = GenerateInstructions(syncExecutionContext);
var putObjectRequest = syncExecutionContext.RequestContext.OriginalRequest as PutObjectRequest;
if (putObjectRequest != null)
{
EncryptObject(instructions, putObjectRequest);
}
PreInvokeSynchronous(syncExecutionContext, instructions);
return base.InvokeAsync(executionContext);
}
#endif
protected bool NeedToGenerateKMSInstructions(IExecutionContext executionContext)
{
return EncryptionClient.EncryptionMaterials.KMSKeyID != null &&
NeedToGenerateInstructions(executionContext);
}
internal static bool NeedToGenerateInstructions(IExecutionContext executionContext)
{
var request = executionContext.RequestContext.OriginalRequest;
var putObjectRequest = request as PutObjectRequest;
var initiateMultiPartUploadRequest = request as InitiateMultipartUploadRequest;
return putObjectRequest != null || initiateMultiPartUploadRequest != null;
}
internal void PreInvokeSynchronous(IExecutionContext executionContext, EncryptionInstructions instructions)
{
var request = executionContext.RequestContext.OriginalRequest;
var useKMSKeyWrapping = this.EncryptionClient.EncryptionMaterials.KMSKeyID != null;
var initiateMultiPartUploadRequest = request as InitiateMultipartUploadRequest;
if (initiateMultiPartUploadRequest != null)
{
GenerateInitiateMultiPartUploadRequest(instructions, initiateMultiPartUploadRequest, useKMSKeyWrapping);
}
var uploadPartRequest = request as UploadPartRequest;
if (uploadPartRequest != null)
{
GenerateEncryptedUploadPartRequest(uploadPartRequest);
}
}
///
/// Updates the request where the input stream contains the encrypted object contents.
///
/// UploadPartRequest whose input stream needs to updated
protected abstract void GenerateEncryptedUploadPartRequest(UploadPartRequest uploadPartRequest);
///
/// Update InitiateMultipartUploadRequest request with given encryption instructions
///
/// EncryptionInstructions which used for encrypting the UploadPartRequest request
/// InitiateMultipartUploadRequest whose encryption context needs to updated
/// If true, KMS mode of encryption is used
protected abstract void GenerateInitiateMultiPartUploadRequest(EncryptionInstructions instructions, InitiateMultipartUploadRequest initiateMultiPartUploadRequest, bool useKmsKeyWrapping);
///
/// Make sure that the storage mode and encryption materials are compatible.
/// The client only supports KMS key wrapping in metadata storage mode.
///
internal void ValidateConfigAndMaterials()
{
var usingKMSKeyWrapping = this.EncryptionClient.EncryptionMaterials.KMSKeyID != null;
var usingMetadataStorageMode = EncryptionClient.S3CryptoConfig.StorageMode == CryptoStorageMode.ObjectMetadata;
if (usingKMSKeyWrapping && !usingMetadataStorageMode)
throw new AmazonClientException($"{EncryptionClient.GetType().Name} only supports KMS key wrapping in metadata storage mode. " +
"Please set StorageMode to CryptoStorageMode.ObjectMetadata or refrain from using KMS EncryptionMaterials.");
}
///
/// Throws an exception if attempting a range GET with an encryption client
///
/// The execution context, it contains the request and response context.
internal void ThrowIfRangeGet(IExecutionContext executionContext)
{
var getObjectRequest = executionContext.RequestContext.OriginalRequest as GetObjectRequest;
if (getObjectRequest != null && getObjectRequest.ByteRange != null)
{
throw new NotSupportedException("Unable to perform range get request: Range get is not supported. " +
$"See {EncryptionUtils.SDKEncryptionDocsUrl}");
}
}
}
}