using System; using System.Collections.Generic; using System.Linq; using System.Text; using Amazon.Runtime; using Amazon.S3.Model; using System.IO; using Amazon.S3.Util; using Amazon.Runtime.Internal; using Amazon.Runtime.Internal.Transform; using Amazon.Runtime.Internal.Util; using Amazon.Util; namespace Amazon.S3.Encryption.Internal { /// /// Custom pipeline handler to encrypt the data as it is being uploaded to S3. /// public class SetupEncryptionHandler : PipelineHandler { /// /// Construct an instance SetupEncryptionHandler. /// /// public SetupEncryptionHandler(AmazonS3EncryptionClient encryptionClient) { this.EncryptionClient = encryptionClient; } /// /// Gets the EncryptionClient property which is the AmazonS3EncryptionClient that is encrypting the object. /// public AmazonS3EncryptionClient 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. /// /// protected void PreInvoke(IExecutionContext executionContext) { EncryptionInstructions instructions = null; if (NeedToGenerateKMSInstructions(executionContext)) instructions = EncryptionUtils.GenerateInstructionsForKMSMaterials( EncryptionClient.KMSClient, EncryptionClient.EncryptionMaterials); PreInvokeSynchronous(executionContext, instructions); } #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. /// /// protected async System.Threading.Tasks.Task PreInvokeAsync(IExecutionContext executionContext) { EncryptionInstructions instructions = null; if (NeedToGenerateKMSInstructions(executionContext)) instructions = await EncryptionUtils.GenerateInstructionsForKMSMaterialsAsync( EncryptionClient.KMSClient, EncryptionClient.EncryptionMaterials).ConfigureAwait(false); PreInvokeSynchronous(executionContext, instructions); } #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); if (NeedToGenerateKMSInstructions(syncExecutionContext)) throw new NotSupportedException("The AWS SDK for .NET Framework 3.5 version of " + typeof(AmazonS3EncryptionClient).Name + " does not support KMS key wrapping via the async programming model. " + "Please use the synchronous version instead."); PreInvokeSynchronous(syncExecutionContext, null); return base.InvokeAsync(executionContext); } #endif private bool NeedToGenerateKMSInstructions(IExecutionContext executionContext) { return EncryptionClient.EncryptionMaterials.KMSKeyID != null && NeedToGenerateInstructions(executionContext); } private 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; } private void PreInvokeSynchronous(IExecutionContext executionContext, EncryptionInstructions instructions) { var request = executionContext.RequestContext.OriginalRequest; var putObjectRequest = request as PutObjectRequest; var initiateMultiPartUploadRequest = request as InitiateMultipartUploadRequest; var uploadPartRequest = request as UploadPartRequest; var useKMSKeyWrapping = this.EncryptionClient.EncryptionMaterials.KMSKeyID != null; if (instructions == null && NeedToGenerateInstructions(executionContext)) instructions = EncryptionUtils.GenerateInstructionsForNonKMSMaterials( EncryptionClient.EncryptionMaterials); if (putObjectRequest != null) { ValidateConfigAndMaterials(); if (EncryptionClient.S3CryptoConfig.StorageMode == CryptoStorageMode.ObjectMetadata) GenerateEncryptedObjectRequestUsingMetadata(putObjectRequest, instructions); else GenerateEncryptedObjectRequestUsingInstructionFile(putObjectRequest, instructions); } if (initiateMultiPartUploadRequest != null) { ValidateConfigAndMaterials(); if (EncryptionClient.S3CryptoConfig.StorageMode == CryptoStorageMode.ObjectMetadata) { EncryptionUtils.UpdateMetadataWithEncryptionInstructions( initiateMultiPartUploadRequest, instructions, useKMSKeyWrapping); } initiateMultiPartUploadRequest.StorageMode = EncryptionClient.S3CryptoConfig.StorageMode; initiateMultiPartUploadRequest.EncryptedEnvelopeKey = instructions.EncryptedEnvelopeKey; initiateMultiPartUploadRequest.EnvelopeKey = instructions.EnvelopeKey; initiateMultiPartUploadRequest.IV = instructions.InitializationVector; } if (uploadPartRequest != null) { GenerateEncryptedUploadPartRequest(uploadPartRequest); } } /// /// Make sure that the storage mode and encryption materials are compatible. /// The client only supports KMS key wrapping in metadata storage mode. /// private void ValidateConfigAndMaterials() { var usingKMSKeyWrapping = this.EncryptionClient.EncryptionMaterials.KMSKeyID != null; var usingMetadataStorageMode = EncryptionClient.S3CryptoConfig.StorageMode == CryptoStorageMode.ObjectMetadata; if (usingKMSKeyWrapping && !usingMetadataStorageMode) throw new AmazonClientException("AmazonS3EncryptionClient only supports KMS key wrapping in metadata storage mode. " + "Please set StorageMode to CryptoStorageMode.ObjectMetadata or refrain from using KMS EncryptionMaterials."); } /// /// 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. /// private void GenerateEncryptedObjectRequestUsingMetadata(PutObjectRequest putObjectRequest, EncryptionInstructions instructions) { EncryptionUtils.AddUnencryptedContentLengthToMetadata(putObjectRequest); // Encrypt the object data with the instruction putObjectRequest.InputStream = EncryptionUtils.EncryptRequestUsingInstruction(putObjectRequest.InputStream, instructions); // Update the metadata EncryptionUtils.UpdateMetadataWithEncryptionInstructions( putObjectRequest, instructions, this.EncryptionClient.EncryptionMaterials.KMSKeyID != null); } /// /// Updates the request where the instruction file contains encryption information /// and the input stream contains the encrypted object contents. /// /// private void GenerateEncryptedObjectRequestUsingInstructionFile(PutObjectRequest putObjectRequest, EncryptionInstructions instructions) { EncryptionUtils.AddUnencryptedContentLengthToMetadata(putObjectRequest); // Encrypt the object data with the instruction putObjectRequest.InputStream = EncryptionUtils.EncryptRequestUsingInstruction(putObjectRequest.InputStream, instructions); // Create request for uploading instruction file PutObjectRequest instructionFileRequest = EncryptionUtils.CreateInstructionFileRequest(putObjectRequest, instructions); this.EncryptionClient.S3ClientForInstructionFile.PutObject(instructionFileRequest); } /// /// Updates the request where the input stream contains the encrypted object contents. /// /// private void GenerateEncryptedUploadPartRequest(UploadPartRequest request) { string uploadID = request.UploadId; UploadPartEncryptionContext contextForEncryption = this.EncryptionClient.CurrentMultiPartUploadKeys[uploadID]; byte[] envelopeKey = contextForEncryption.EnvelopeKey; byte[] IV = contextForEncryption.NextIV; EncryptionInstructions instructions = new EncryptionInstructions(EncryptionClient.EncryptionMaterials.MaterialsDescription, envelopeKey, IV); if (request.IsLastPart == false) { if (contextForEncryption.IsFinalPart == true) 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 in correct sequence"); request.InputStream = EncryptionUtils.EncryptUploadPartRequestUsingInstructions(request.InputStream, instructions); contextForEncryption.PartNumber = request.PartNumber; } else { request.InputStream = EncryptionUtils.EncryptRequestUsingInstruction(request.InputStream, instructions); contextForEncryption.IsFinalPart = true; } ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)request).RequestState.Add(AmazonS3EncryptionClient.S3CryptoStream, request.InputStream); } } }