using System; using System.Collections.Generic; using System.Globalization; 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; using Amazon.Runtime.SharedInterfaces; using ThirdParty.Json.LitJson; namespace Amazon.S3.Encryption.Internal { /// /// Custom the pipeline handler to decrypt objects. /// public class SetupDecryptionHandler : PipelineHandler { private const string KMSKeyIDMetadataMessage = "Unable to determine the KMS key ID from the object metadata. "; /// /// Construct instance of SetupDecryptionHandler. /// /// public SetupDecryptionHandler(AmazonS3EncryptionClient encryptionClient) { this.EncryptionClient = encryptionClient; } /// /// Gets the EncryptionClient property which is the AmazonS3EncryptionClient that is decrypting the object. /// public AmazonS3EncryptionClient EncryptionClient { get; private set; } /// /// Calls the post invoke logic after calling the next handler /// in the pipeline. /// /// The execution context which contains both the /// requests and response context. public override void InvokeSync(IExecutionContext executionContext) { base.InvokeSync(executionContext); PostInvoke(executionContext); } /// /// Decrypt the object being downloaded. /// /// protected void PostInvoke(IExecutionContext executionContext) { byte[] encryptedKMSEnvelopeKey; Dictionary encryptionContext; byte[] decryptedEnvelopeKeyKMS = null; if (KMSEnvelopeKeyIsPresent(executionContext, out encryptedKMSEnvelopeKey, out encryptionContext)) decryptedEnvelopeKeyKMS = EncryptionClient.KMSClient.Decrypt(encryptedKMSEnvelopeKey, encryptionContext); PostInvokeSynchronous(executionContext, decryptedEnvelopeKeyKMS); } #if AWS_ASYNC_API /// /// Calls the and post invoke logic after 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) { var response = await base.InvokeAsync(executionContext).ConfigureAwait(false); await PostInvokeAsync(executionContext).ConfigureAwait(false); return response; } /// /// Decrypt the object being downloaded. /// /// protected async System.Threading.Tasks.Task PostInvokeAsync(IExecutionContext executionContext) { byte[] encryptedKMSEnvelopeKey; Dictionary encryptionContext; byte[] decryptedEnvelopeKeyKMS = null; if (KMSEnvelopeKeyIsPresent(executionContext, out encryptedKMSEnvelopeKey, out encryptionContext)) decryptedEnvelopeKeyKMS = await EncryptionClient.KMSClient.DecryptAsync( encryptedKMSEnvelopeKey, encryptionContext).ConfigureAwait(false); PostInvokeSynchronous(executionContext, decryptedEnvelopeKeyKMS); } #elif AWS_APM_API /// /// Calls the PostInvoke methods after calling the next handler /// in the pipeline. /// /// The execution context, it contains the /// request and response context. protected override void InvokeAsyncCallback(IAsyncExecutionContext executionContext) { IExecutionContext syncExecutionContext = ExecutionContext.CreateFromAsyncContext(executionContext); // Process the response if an exception hasn't occured if (executionContext.ResponseContext.AsyncResult.Exception == null) { byte[] encryptedKMSEnvelopeKey; Dictionary encryptionContext; if (KMSEnvelopeKeyIsPresent(syncExecutionContext, out encryptedKMSEnvelopeKey, out encryptionContext)) 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."); PostInvokeSynchronous(syncExecutionContext, null); } base.InvokeAsyncCallback(executionContext); } #endif private static bool KMSEnvelopeKeyIsPresent(IExecutionContext executionContext, out byte[] encryptedKMSEnvelopeKey, out Dictionary encryptionContext) { var response = executionContext.ResponseContext.Response; var getObjectResponse = response as GetObjectResponse; encryptedKMSEnvelopeKey = null; encryptionContext = null; if (getObjectResponse != null) { var metadata = getObjectResponse.Metadata; EncryptionUtils.EnsureSupportedAlgorithms(metadata); var base64EncodedEncryptedKMSEnvelopeKey = metadata[EncryptionUtils.XAmzKeyV2]; if (base64EncodedEncryptedKMSEnvelopeKey != null) { encryptedKMSEnvelopeKey = Convert.FromBase64String(base64EncodedEncryptedKMSEnvelopeKey); string kmsKeyIDFromMetadata = GetKMSKeyIDFromMetadata(metadata); encryptionContext = new Dictionary { { EncryptionUtils.KMSCmkIDKey, kmsKeyIDFromMetadata } }; return true; } } return false; } private static string GetKMSKeyIDFromMetadata(MetadataCollection metadata) { var materialDescriptionJsonString = metadata[EncryptionUtils.XAmzMatDesc]; if (materialDescriptionJsonString == null) { throw new InvalidDataException(KMSKeyIDMetadataMessage + "The key '" + EncryptionUtils.XAmzMatDesc + "' is missing."); } else { JsonData materialDescriptionJsonData; try { materialDescriptionJsonData = JsonMapper.ToObject(materialDescriptionJsonString); } catch (JsonException e) { throw new InvalidDataException(KMSKeyIDMetadataMessage + "The key '" + EncryptionUtils.XAmzMatDesc + "' does not contain valid JSON.", e); } JsonData kmsKeyIDJsonData; try { kmsKeyIDJsonData = materialDescriptionJsonData[EncryptionUtils.KMSCmkIDKey]; } catch (JsonException e) { throw new InvalidDataException(KMSKeyIDMetadataMessage + "The key '" + EncryptionUtils.KMSCmkIDKey + "' is does not contain valid JSON.", e); } if (kmsKeyIDJsonData == null) { throw new InvalidDataException(KMSKeyIDMetadataMessage + "The key '" + kmsKeyIDJsonData + "' is missing from the material description."); } return kmsKeyIDJsonData.ToString(); } } /// /// Decrypt the object being downloaded. /// /// /// protected void PostInvokeSynchronous(IExecutionContext executionContext, byte[] decryptedEnvelopeKeyKMS) { var request = executionContext.RequestContext.Request; var response = executionContext.ResponseContext.Response; var initiateMultiPartUploadRequest = request.OriginalRequest as InitiateMultipartUploadRequest; var initiateMultiPartResponse = response as InitiateMultipartUploadResponse; if (initiateMultiPartResponse != null) { byte[] encryptedEnvelopeKey = initiateMultiPartUploadRequest.EncryptedEnvelopeKey; byte[] envelopeKey = initiateMultiPartUploadRequest.EnvelopeKey; byte[] iv = initiateMultiPartUploadRequest.IV; UploadPartEncryptionContext contextForEncryption = new UploadPartEncryptionContext(); contextForEncryption.StorageMode = initiateMultiPartUploadRequest.StorageMode; contextForEncryption.EncryptedEnvelopeKey = encryptedEnvelopeKey; contextForEncryption.EnvelopeKey = envelopeKey; contextForEncryption.NextIV = iv; contextForEncryption.FirstIV = iv; contextForEncryption.PartNumber = 0; //Add context for encryption of next part this.EncryptionClient.CurrentMultiPartUploadKeys.Add(initiateMultiPartResponse.UploadId, contextForEncryption); } var uploadPartRequest = request.OriginalRequest as UploadPartRequest; var uploadPartResponse = response as UploadPartResponse; if (uploadPartResponse != null) { string uploadID = uploadPartRequest.UploadId; UploadPartEncryptionContext encryptedUploadedContext = null; if (!this.EncryptionClient.CurrentMultiPartUploadKeys.TryGetValue(uploadID, out encryptedUploadedContext)) throw new AmazonS3Exception("encryption context for multi part upload not found"); if (uploadPartRequest.IsLastPart == false) { object stream = null; if (!((Amazon.Runtime.Internal.IAmazonWebServiceRequest)uploadPartRequest).RequestState.TryGetValue(AmazonS3EncryptionClient.S3CryptoStream, out stream)) throw new AmazonS3Exception("cannot retrieve S3 crypto stream from request state, hence cannot get Initialization vector for next uploadPart "); var encryptionStream = stream as AESEncryptionUploadPartStream; encryptedUploadedContext.NextIV = encryptionStream.InitializationVector; } } var getObjectResponse = response as GetObjectResponse; if (getObjectResponse != null) { if (EncryptionUtils.IsEncryptionInfoInMetadata(getObjectResponse) == true) { DecryptObjectUsingMetadata(getObjectResponse, decryptedEnvelopeKeyKMS); } else { GetObjectResponse instructionFileResponse = null; try { GetObjectRequest instructionFileRequest = EncryptionUtils.GetInstructionFileRequest(getObjectResponse); instructionFileResponse = this.EncryptionClient.S3ClientForInstructionFile.GetObject(instructionFileRequest); } catch (AmazonServiceException ace) { throw new AmazonServiceException(string.Format(CultureInfo.InvariantCulture, "Unable to decrypt data for object {0} in bucket {1}", getObjectResponse.Key, getObjectResponse.BucketName), ace); } DecryptObjectUsingInstructionFile(getObjectResponse, instructionFileResponse); } } var completeMultiPartUploadRequest = request.OriginalRequest as CompleteMultipartUploadRequest; var completeMultipartUploadResponse = response as CompleteMultipartUploadResponse; if (completeMultipartUploadResponse != null) { UploadPartEncryptionContext context = this.EncryptionClient.CurrentMultiPartUploadKeys[completeMultiPartUploadRequest.UploadId]; if (context.StorageMode == CryptoStorageMode.InstructionFile) { byte[] envelopeKey = context.EnvelopeKey; byte[] iv = context.FirstIV; byte[] encryptedEnvelopeKey = context.EncryptedEnvelopeKey; EncryptionInstructions instructions = new EncryptionInstructions(EncryptionClient.EncryptionMaterials.MaterialsDescription, envelopeKey, encryptedEnvelopeKey, iv); PutObjectRequest instructionFileRequest = EncryptionUtils.CreateInstructionFileRequest(completeMultiPartUploadRequest, instructions); this.EncryptionClient.S3ClientForInstructionFile.PutObject(instructionFileRequest); } //Clear Context data since encryption is completed this.EncryptionClient.CurrentMultiPartUploadKeys.Clear(); } } /// /// Updates object where the object input stream contains the decrypted contents. /// /// /// The getObject response of InstructionFile. /// /// /// The getObject response whose contents are to be decrypted. /// private void DecryptObjectUsingInstructionFile(GetObjectResponse response, GetObjectResponse instructionFileResponse) { // Create an instruction object from the instruction file response EncryptionInstructions instructions = EncryptionUtils.BuildInstructionsUsingInstructionFile( instructionFileResponse, this.EncryptionClient.EncryptionMaterials); // Decrypt the object with the instructions EncryptionUtils.DecryptObjectUsingInstructions(response, instructions); } /// /// Updates object where the object input stream contains the decrypted contents. /// /// /// The getObject response whose contents are to be decrypted. /// /// /// The decrypted envelope key to be use if KMS key wrapping is being used. Or null if non-KMS key wrapping is being used. /// private void DecryptObjectUsingMetadata(GetObjectResponse objectResponse, byte[] decryptedEnvelopeKeyKMS) { // Create an instruction object from the object metadata EncryptionInstructions instructions = EncryptionUtils.BuildInstructionsFromObjectMetadata( objectResponse, this.EncryptionClient.EncryptionMaterials, decryptedEnvelopeKeyKMS); // Decrypt the object with the instruction EncryptionUtils.DecryptObjectUsingInstructions(objectResponse, instructions); } } }