All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.services.s3.AmazonS3EncryptionClient Maven / Gradle / Ivy

Go to download

The Amazon Web Services SDK for Java provides Java APIs for building software on AWS' cost-effective, scalable, and reliable infrastructure products. The AWS Java SDK allows developers to code against APIs for all of Amazon's infrastructure web services (Amazon S3, Amazon EC2, Amazon SQS, Amazon Relational Database Service, Amazon AutoScaling, etc).

The newest version!
/*
 * Copyright 2010-2014 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.
 */
package com.amazonaws.services.s3;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.services.s3.internal.crypto.*;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.util.VersionInfoUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import java.io.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * The AmazonS3Encryption class extends the Amazon S3 Client, allowing you to store data securely in S3.
 * 

* The encryption materials specified in the constructor will be used to encrypt and decrypt data. */ public class AmazonS3EncryptionClient extends AmazonS3Client { private EncryptionMaterialsProvider encryptionMaterialsProvider; private CryptoConfiguration cryptoConfig; private static final String USER_AGENT = AmazonS3EncryptionClient.class.getName() + "/" + VersionInfoUtils.getVersion(); /** Shared logger for encryption client events */ private static Log log = LogFactory.getLog(AmazonS3EncryptionClient.class); /** Map of data about in progress encrypted multipart uploads. */ private Map currentMultipartUploadSecretKeys = Collections.synchronizedMap(new HashMap()); /** *

* Constructs a new Amazon S3 Encryption client that will make anonymous * requests to Amazon S3. If {@link #getObject(String, String)} is called, * the object contents will be decrypted with the encryption materials provided. *

*

* Only a subset of the Amazon S3 API will work with anonymous * (i.e. unsigned) requests, but this can prove useful in some situations. * For example: *

    *
  • If an Amazon S3 bucket has {@link Permission#Read} permission for the * {@link GroupGrantee#AllUsers} group, anonymous clients can call * {@link #listObjects(String)} to see what objects are stored in a bucket.
  • *
  • If an object has {@link Permission#Read} permission for the * {@link GroupGrantee#AllUsers} group, anonymous clients can call * {@link #getObject(String, String)} and * {@link #getObjectMetadata(String, String)} to pull object content and * metadata.
  • *
  • If a bucket has {@link Permission#Write} permission for the * {@link GroupGrantee#AllUsers} group, anonymous clients can upload objects * to the bucket.
  • *
*

* * @param encryptionMaterials * The encryption materials to be used to encrypt and decrypt data. */ public AmazonS3EncryptionClient(EncryptionMaterials encryptionMaterials) { this(new StaticEncryptionMaterialsProvider(encryptionMaterials)); } /** *

* Constructs a new Amazon S3 Encryption client that will make anonymous * requests to Amazon S3. If {@link #getObject(String, String)} is called, * the object contents will be decrypted with the encryption materials provided. *

*

* Only a subset of the Amazon S3 API will work with anonymous * (i.e. unsigned) requests, but this can prove useful in some situations. * For example: *

    *
  • If an Amazon S3 bucket has {@link Permission#Read} permission for the * {@link GroupGrantee#AllUsers} group, anonymous clients can call * {@link #listObjects(String)} to see what objects are stored in a bucket.
  • *
  • If an object has {@link Permission#Read} permission for the * {@link GroupGrantee#AllUsers} group, anonymous clients can call * {@link #getObject(String, String)} and * {@link #getObjectMetadata(String, String)} to pull object content and * metadata.
  • *
  • If a bucket has {@link Permission#Write} permission for the * {@link GroupGrantee#AllUsers} group, anonymous clients can upload objects * to the bucket.
  • *
*

* * @param encryptionMaterialsProvider * A provider for the encryption materials to be used to encrypt and decrypt data. */ public AmazonS3EncryptionClient(EncryptionMaterialsProvider encryptionMaterialsProvider) { this((AWSCredentialsProvider)null, encryptionMaterialsProvider, new ClientConfiguration(), new CryptoConfiguration()); } /** *

* Constructs a new Amazon S3 Encryption client that will make anonymous * requests to Amazon S3. If {@link #getObject(String, String)} is called, * the object contents will be decrypted with the encryption materials provided. * The encryption implementation of the provided crypto provider will be * used to encrypt and decrypt data. *

*

* Only a subset of the Amazon S3 API will work with anonymous * (i.e. unsigned) requests, but this can prove useful in some situations. * For example: *

    *
  • If an Amazon S3 bucket has {@link Permission#Read} permission for the * {@link GroupGrantee#AllUsers} group, anonymous clients can call * {@link #listObjects(String)} to see what objects are stored in a bucket.
  • *
  • If an object has {@link Permission#Read} permission for the * {@link GroupGrantee#AllUsers} group, anonymous clients can call * {@link #getObject(String, String)} and * {@link #getObjectMetadata(String, String)} to pull object content and * metadata.
  • *
  • If a bucket has {@link Permission#Write} permission for the * {@link GroupGrantee#AllUsers} group, anonymous clients can upload objects * to the bucket.
  • *
*

* * @param encryptionMaterials * The encryption materials to be used to encrypt and decrypt data. * @param cryptoConfig * The crypto configuration whose parameters will be used to encrypt and decrypt data. */ public AmazonS3EncryptionClient(EncryptionMaterials encryptionMaterials, CryptoConfiguration cryptoConfig) { this(new StaticEncryptionMaterialsProvider(encryptionMaterials), cryptoConfig); } /** *

* Constructs a new Amazon S3 Encryption client that will make anonymous * requests to Amazon S3. If {@link #getObject(String, String)} is called, * the object contents will be decrypted with the encryption materials provided. * The encryption implementation of the provided crypto provider will be * used to encrypt and decrypt data. *

*

* Only a subset of the Amazon S3 API will work with anonymous * (i.e. unsigned) requests, but this can prove useful in some situations. * For example: *

    *
  • If an Amazon S3 bucket has {@link Permission#Read} permission for the * {@link GroupGrantee#AllUsers} group, anonymous clients can call * {@link #listObjects(String)} to see what objects are stored in a bucket.
  • *
  • If an object has {@link Permission#Read} permission for the * {@link GroupGrantee#AllUsers} group, anonymous clients can call * {@link #getObject(String, String)} and * {@link #getObjectMetadata(String, String)} to pull object content and * metadata.
  • *
  • If a bucket has {@link Permission#Write} permission for the * {@link GroupGrantee#AllUsers} group, anonymous clients can upload objects * to the bucket.
  • *
*

* * @param encryptionMaterialsProvider * A provider for the encryption materials to be used to encrypt and decrypt data. * @param cryptoConfig * The crypto configuration whose parameters will be used to encrypt and decrypt data. */ public AmazonS3EncryptionClient(EncryptionMaterialsProvider encryptionMaterialsProvider, CryptoConfiguration cryptoConfig) { this((AWSCredentialsProvider)null, encryptionMaterialsProvider, new ClientConfiguration(), cryptoConfig); } /** *

* Constructs a new Amazon S3 Encryption client using the specified AWS credentials to * access Amazon S3. Object contents will be encrypted and decrypted with the encryption * materials provided. *

* * @param credentials * The AWS credentials to use when making requests to Amazon S3 * with this client. * @param encryptionMaterials * The encryption materials to be used to encrypt and decrypt data. */ public AmazonS3EncryptionClient(AWSCredentials credentials, EncryptionMaterials encryptionMaterials) { this(credentials, new StaticEncryptionMaterialsProvider(encryptionMaterials)); } /** *

* Constructs a new Amazon S3 Encryption client using the specified AWS credentials to * access Amazon S3. Object contents will be encrypted and decrypted with the encryption * materials provided. *

* * @param credentials * The AWS credentials to use when making requests to Amazon S3 * with this client. * @param encryptionMaterialsProvider * A provider for the encryption materials to be used to encrypt and decrypt data. */ public AmazonS3EncryptionClient(AWSCredentials credentials, EncryptionMaterialsProvider encryptionMaterialsProvider) { this(credentials, encryptionMaterialsProvider, new ClientConfiguration(), new CryptoConfiguration()); } /** *

* Constructs a new Amazon S3 Encryption client using the specified AWS credentials to * access Amazon S3. Object contents will be encrypted and decrypted with the encryption * materials provided. *

* * @param credentialsProvider * The AWS credentials provider which will provide credentials * to authenticate requests with AWS services. * @param encryptionMaterialsProvider * A provider for the encryption materials to be used to encrypt and decrypt data. */ public AmazonS3EncryptionClient(AWSCredentialsProvider credentialsProvider, EncryptionMaterialsProvider encryptionMaterialsProvider) { this(credentialsProvider, encryptionMaterialsProvider, new ClientConfiguration(), new CryptoConfiguration()); } /** *

* Constructs a new Amazon S3 Encryption client using the specified AWS credentials to * access Amazon S3. Object contents will be encrypted and decrypted with the encryption * materials provided. The encryption implementation of the provided crypto provider will * be used to encrypt and decrypt data. *

* * @param credentials * The AWS credentials to use when making requests to Amazon S3 * with this client. * @param encryptionMaterials * The encryption materials to be used to encrypt and decrypt data. * @param cryptoConfig * The crypto configuration whose parameters will be used to encrypt and decrypt data. */ public AmazonS3EncryptionClient(AWSCredentials credentials, EncryptionMaterials encryptionMaterials, CryptoConfiguration cryptoConfig) { this(credentials, new StaticEncryptionMaterialsProvider(encryptionMaterials), cryptoConfig); } /** *

* Constructs a new Amazon S3 Encryption client using the specified AWS credentials to * access Amazon S3. Object contents will be encrypted and decrypted with the encryption * materials provided. The encryption implementation of the provided crypto provider will * be used to encrypt and decrypt data. *

* * @param credentials * The AWS credentials to use when making requests to Amazon S3 * with this client. * @param encryptionMaterialsProvider * A provider for the encryption materials to be used to encrypt and decrypt data. * @param cryptoConfig * The crypto configuration whose parameters will be used to encrypt and decrypt data. */ public AmazonS3EncryptionClient(AWSCredentials credentials, EncryptionMaterialsProvider encryptionMaterialsProvider, CryptoConfiguration cryptoConfig) { this(credentials, encryptionMaterialsProvider, new ClientConfiguration(), cryptoConfig); } /** *

* Constructs a new Amazon S3 Encryption client using the specified AWS credentials to * access Amazon S3. Object contents will be encrypted and decrypted with the encryption * materials provided. The encryption implementation of the provided crypto provider will * be used to encrypt and decrypt data. *

* * @param credentialsProvider * The AWS credentials provider which will provide credentials * to authenticate requests with AWS services. * @param encryptionMaterialsProvider * A provider for the encryption materials to be used to encrypt and decrypt data. * @param cryptoConfig * The crypto configuration whose parameters will be used to encrypt and decrypt data. */ public AmazonS3EncryptionClient(AWSCredentialsProvider credentialsProvider, EncryptionMaterialsProvider encryptionMaterialsProvider, CryptoConfiguration cryptoConfig) { this(credentialsProvider, encryptionMaterialsProvider, new ClientConfiguration(), cryptoConfig); } /** *

* Constructs a new Amazon S3 Encryption client using the specified AWS credentials and * client configuration to access Amazon S3. Object contents will be encrypted and decrypted * with the encryption materials provided. The crypto provider and storage mode denoted in * the specified crypto configuration will be used to encrypt and decrypt data. *

* * @param credentials * The AWS credentials to use when making requests to Amazon S3 * with this client. * @param encryptionMaterials * The encryption materials to be used to encrypt and decrypt data. * @param clientConfig * The client configuration options controlling how this client * connects to Amazon S3 (ex: proxy settings, retry counts, etc). * @param cryptoConfig * The crypto configuration whose parameters will be used to encrypt and decrypt data. * @throws IllegalArgumentException * If either of the encryption materials or crypto configuration parameters are null. */ public AmazonS3EncryptionClient(AWSCredentials credentials, EncryptionMaterials encryptionMaterials, ClientConfiguration clientConfig, CryptoConfiguration cryptoConfig) { this(credentials, new StaticEncryptionMaterialsProvider(encryptionMaterials), clientConfig, cryptoConfig); } public AmazonS3EncryptionClient(AWSCredentials credentials, EncryptionMaterialsProvider encryptionMaterialsProvider, ClientConfiguration clientConfig, CryptoConfiguration cryptoConfig) { super(credentials, clientConfig); assertParameterNotNull(encryptionMaterialsProvider, "EncryptionMaterialsProvider parameter must not be null."); assertParameterNotNull(cryptoConfig, "CryptoConfiguration parameter must not be null."); this.encryptionMaterialsProvider = encryptionMaterialsProvider; this.cryptoConfig = cryptoConfig; } public AmazonS3EncryptionClient(AWSCredentialsProvider credentialsProvider, EncryptionMaterialsProvider encryptionMaterialsProvider, ClientConfiguration clientConfig, CryptoConfiguration cryptoConfig) { super(credentialsProvider, clientConfig); assertParameterNotNull(encryptionMaterialsProvider, "EncryptionMaterialsProvider parameter must not be null."); assertParameterNotNull(cryptoConfig, "CryptoConfiguration parameter must not be null."); this.encryptionMaterialsProvider = encryptionMaterialsProvider; this.cryptoConfig = cryptoConfig; } /* (non-Javadoc) * @see com.amazonaws.services.s3.AmazonS3#putObject(com.amazonaws.services.s3.model.PutObjectRequest) */ @Override public PutObjectResult putObject(PutObjectRequest putObjectRequest) throws AmazonClientException, AmazonServiceException { appendUserAgent(putObjectRequest, USER_AGENT); if (this.cryptoConfig.getStorageMode() == CryptoStorageMode.InstructionFile) { return putObjectUsingInstructionFile(putObjectRequest); } else { return putObjectUsingMetadata(putObjectRequest); } } /* (non-Javadoc) * @see com.amazonaws.services.s3.AmazonS3#getObject(com.amazonaws.services.s3.model.GetObjectRequest) */ @Override public S3Object getObject(GetObjectRequest getObjectRequest) throws AmazonClientException, AmazonServiceException { appendUserAgent(getObjectRequest, USER_AGENT); // Adjust the crypto range to retrieve all of the cipher blocks needed to contain the user's desired // range of bytes. long[] desiredRange = getObjectRequest.getRange(); long[] adjustedCryptoRange = EncryptionUtils.getAdjustedCryptoRange(desiredRange); if (adjustedCryptoRange != null) { getObjectRequest.setRange(adjustedCryptoRange[0], adjustedCryptoRange[1]); } // Get the object from S3 S3Object retrievedObject = super.getObject(getObjectRequest); // If the caller has specified constraints, it's possible that super.getObject(...) // would return null, so we simply return null as well. if (retrievedObject == null) return null; S3Object objectToBeReturned; try { // Check if encryption info is in object metadata if (EncryptionUtils.isEncryptionInfoInMetadata(retrievedObject)) { objectToBeReturned = decryptObjectUsingMetadata(retrievedObject); } else { // Check if encrypted info is in an instruction file S3Object instructionFile = null; try { instructionFile = getInstructionFile(getObjectRequest); if (EncryptionUtils.isEncryptionInfoInInstructionFile(instructionFile)) { objectToBeReturned = decryptObjectUsingInstructionFile(retrievedObject, instructionFile); } else { // The object was not encrypted to begin with. Return the object without decrypting it. log.warn(String.format("Unable to detect encryption information for object '%s' in bucket '%s'. " + "Returning object without decryption.", retrievedObject.getKey(), retrievedObject.getBucketName())); objectToBeReturned = retrievedObject; } } finally { if (instructionFile != null) { try { instructionFile.getObjectContent().close();} catch (Exception e) {} } } } } catch (AmazonClientException ace) { // If we're unable to set up the decryption, make sure we close the HTTP connection try {retrievedObject.getObjectContent().close();} catch (Exception e) {} throw ace; } // Adjust the output to the desired range of bytes. return EncryptionUtils.adjustOutputToDesiredRange(objectToBeReturned, desiredRange); } /* (non-Javadoc) * @see com.amazonaws.services.s3.AmazonS3#getObject(com.amazonaws.services.s3.model.GetObjectRequest, java.io.File) */ @Override public ObjectMetadata getObject(GetObjectRequest getObjectRequest, File destinationFile) throws AmazonClientException, AmazonServiceException { assertParameterNotNull(destinationFile, "The destination file parameter must be specified when downloading an object directly to a file"); S3Object s3Object = getObject(getObjectRequest); // getObject can return null if constraints were specified but not met if (s3Object == null) return null; OutputStream outputStream = null; try { outputStream = new BufferedOutputStream(new FileOutputStream(destinationFile)); byte[] buffer = new byte[1024*10]; int bytesRead; while ((bytesRead = s3Object.getObjectContent().read(buffer)) > -1) { outputStream.write(buffer, 0, bytesRead); } } catch (IOException e) { throw new AmazonClientException( "Unable to store object contents to disk: " + e.getMessage(), e); } finally { try {outputStream.close();} catch (Exception e) {} try {s3Object.getObjectContent().close();} catch (Exception e) {} } /* * Unlike the standard Amazon S3 Client, the Amazon S3 Encryption Client does not do an MD5 check * here because the contents stored in S3 and the contents we just retrieved are different. In * S3, the stored contents are encrypted, and locally, the retrieved contents are decrypted. */ return s3Object.getObjectMetadata(); } /* (non-Javadoc) * @see com.amazonaws.services.s3.AmazonS3Client#deleteObject(com.amazonaws.services.s3.model.DeleteObjectRequest) */ @Override public void deleteObject(DeleteObjectRequest deleteObjectRequest) { appendUserAgent(deleteObjectRequest, USER_AGENT); // Delete the object super.deleteObject(deleteObjectRequest); // If it exists, delete the instruction file. DeleteObjectRequest instructionDeleteRequest = EncryptionUtils.createInstructionDeleteObjectRequest(deleteObjectRequest); super.deleteObject(instructionDeleteRequest); } /* (non-Javadoc) * @see com.amazonaws.services.s3.AmazonS3Client#completeMultipartUpload(com.amazonaws.services.s3.model.CompleteMultipartUploadRequest) */ @Override public CompleteMultipartUploadResult completeMultipartUpload( CompleteMultipartUploadRequest completeMultipartUploadRequest) throws AmazonClientException, AmazonServiceException { appendUserAgent(completeMultipartUploadRequest, USER_AGENT); String uploadId = completeMultipartUploadRequest.getUploadId(); EncryptedUploadContext encryptedUploadContext = currentMultipartUploadSecretKeys.get(uploadId); if (encryptedUploadContext.hasFinalPartBeenSeen() == false) { throw new AmazonClientException("Unable to complete an encrypted multipart upload without being told which part was the last. " + "Without knowing which part was the last, the encrypted data in Amazon S3 is incomplete and corrupt."); } CompleteMultipartUploadResult result = super.completeMultipartUpload(completeMultipartUploadRequest); // In InstructionFile mode, we want to write the instruction file only after the whole upload has completed correctly. if (cryptoConfig.getStorageMode() == CryptoStorageMode.InstructionFile) { Cipher symmetricCipher = EncryptionUtils.createSymmetricCipher( encryptedUploadContext.getEnvelopeEncryptionKey(), Cipher.ENCRYPT_MODE, cryptoConfig.getCryptoProvider(), encryptedUploadContext.getFirstInitializationVector()); EncryptionMaterials encryptionMaterials = encryptionMaterialsProvider.getEncryptionMaterials(); // Encrypt the envelope symmetric key byte[] encryptedEnvelopeSymmetricKey = EncryptionUtils.getEncryptedSymmetricKey(encryptedUploadContext.getEnvelopeEncryptionKey(), encryptionMaterials, cryptoConfig.getCryptoProvider()); EncryptionInstruction instruction = new EncryptionInstruction(encryptionMaterials.getMaterialsDescription(), encryptedEnvelopeSymmetricKey, encryptedUploadContext.getEnvelopeEncryptionKey(), symmetricCipher); // Put the instruction file into S3 super.putObject(EncryptionUtils.createInstructionPutRequest(encryptedUploadContext.getBucketName(), encryptedUploadContext.getKey(), instruction)); } currentMultipartUploadSecretKeys.remove(uploadId); return result; } /* (non-Javadoc) * @see com.amazonaws.services.s3.AmazonS3Client#initiateMultipartUpload(com.amazonaws.services.s3.model.InitiateMultipartUploadRequest) */ @Override public InitiateMultipartUploadResult initiateMultipartUpload( InitiateMultipartUploadRequest initiateMultipartUploadRequest) throws AmazonClientException, AmazonServiceException { appendUserAgent(initiateMultipartUploadRequest, USER_AGENT); // Generate a one-time use symmetric key and initialize a cipher to encrypt object data SecretKey envelopeSymmetricKey = EncryptionUtils.generateOneTimeUseSymmetricKey(); Cipher symmetricCipher = EncryptionUtils.createSymmetricCipher(envelopeSymmetricKey, Cipher.ENCRYPT_MODE, cryptoConfig.getCryptoProvider(), null); if (cryptoConfig.getStorageMode() == CryptoStorageMode.ObjectMetadata) { EncryptionMaterials encryptionMaterials = encryptionMaterialsProvider.getEncryptionMaterials(); // Encrypt the envelope symmetric key byte[] encryptedEnvelopeSymmetricKey = EncryptionUtils.getEncryptedSymmetricKey(envelopeSymmetricKey, encryptionMaterials, cryptoConfig.getCryptoProvider()); // Store encryption info in metadata ObjectMetadata metadata = EncryptionUtils.updateMetadataWithEncryptionInfo(initiateMultipartUploadRequest, encryptedEnvelopeSymmetricKey, symmetricCipher, encryptionMaterials.getMaterialsDescription()); // Update the request's metadata to the updated metadata initiateMultipartUploadRequest.setObjectMetadata(metadata); } InitiateMultipartUploadResult result = super.initiateMultipartUpload(initiateMultipartUploadRequest); EncryptedUploadContext encryptedUploadContext = new EncryptedUploadContext(initiateMultipartUploadRequest.getBucketName(), initiateMultipartUploadRequest.getKey(), envelopeSymmetricKey); encryptedUploadContext.setNextInitializationVector(symmetricCipher.getIV()); encryptedUploadContext.setFirstInitializationVector(symmetricCipher.getIV()); currentMultipartUploadSecretKeys.put(result.getUploadId(), encryptedUploadContext); return result; } /** * {@inheritDoc} * *

* NOTE: Because the encryption process requires context from block * N-1 in order to encrypt block N, parts uploaded with the * AmazonS3EncryptionClient (as opposed to the normal AmazonS3Client) must * be uploaded serially, and in order. Otherwise, the previous encryption * context isn't available to use when encrypting the current part. */ @Override public UploadPartResult uploadPart(UploadPartRequest uploadPartRequest) throws AmazonClientException, AmazonServiceException { appendUserAgent(uploadPartRequest, USER_AGENT); boolean isLastPart = uploadPartRequest.isLastPart(); String uploadId = uploadPartRequest.getUploadId(); boolean partSizeMultipleOfCipherBlockSize = uploadPartRequest.getPartSize() % JceEncryptionConstants.SYMMETRIC_CIPHER_BLOCK_SIZE == 0; if (!isLastPart && !partSizeMultipleOfCipherBlockSize) { throw new AmazonClientException("Invalid part size: part sizes for encrypted multipart uploads must be multiples " + "of the cipher block size (" + JceEncryptionConstants.SYMMETRIC_CIPHER_BLOCK_SIZE + ") with the exception of the last part. " + "Otherwise encryption adds extra padding that will corrupt the final object."); } // Generate the envelope symmetric key and initialize a cipher to encrypt the object's data EncryptedUploadContext encryptedUploadContext = currentMultipartUploadSecretKeys.get(uploadId); if (encryptedUploadContext == null) throw new AmazonClientException("No client-side information available on upload ID " + uploadId); SecretKey envelopeSymmetricKey = encryptedUploadContext.getEnvelopeEncryptionKey(); byte[] iv = encryptedUploadContext.getNextInitializationVector(); CipherFactory cipherFactory = new CipherFactory(envelopeSymmetricKey, Cipher.ENCRYPT_MODE, iv, this.cryptoConfig.getCryptoProvider()); // Create encrypted input stream ByteRangeCapturingInputStream encryptedInputStream = EncryptionUtils.getEncryptedInputStream(uploadPartRequest, cipherFactory); uploadPartRequest.setInputStream(encryptedInputStream); // The last part of the multipart upload will contain extra padding from the encryption process, which // changes the if (uploadPartRequest.isLastPart()) { // We only change the size of the last part long cryptoContentLength = EncryptionUtils.calculateCryptoContentLength(cipherFactory.createCipher(), uploadPartRequest); if (cryptoContentLength > 0) uploadPartRequest.setPartSize(cryptoContentLength); if (encryptedUploadContext.hasFinalPartBeenSeen()) { throw new AmazonClientException("This part was specified as the last part in a multipart upload, but a previous part was already marked as the last part. " + "Only the last part of the upload should be marked as the last part, otherwise it will cause the encrypted data to be corrupted."); } encryptedUploadContext.setHasFinalPartBeenSeen(true); } // Treat all encryption requests as input stream upload requests, not as file upload requests. uploadPartRequest.setFile(null); uploadPartRequest.setFileOffset(0); UploadPartResult result = super.uploadPart(uploadPartRequest); encryptedUploadContext.setNextInitializationVector(encryptedInputStream.getBlock()); return result; } @Override public CopyPartResult copyPart(CopyPartRequest copyPartRequest) { String uploadId = copyPartRequest.getUploadId(); EncryptedUploadContext encryptedUploadContext = currentMultipartUploadSecretKeys.get(uploadId); if (!encryptedUploadContext.hasFinalPartBeenSeen()) { encryptedUploadContext.setHasFinalPartBeenSeen(true); } return super.copyPart(copyPartRequest); } /* * Private helper methods */ /** * Puts an encrypted object into S3 and stores encryption info in the object metadata. * * @param putObjectRequest * The request object containing all the parameters to upload a * new object to Amazon S3. * @return * A {@link PutObjectResult} object containing the information * returned by Amazon S3 for the new, created object. * @throws AmazonClientException * If any errors are encountered on the client while making the * request or handling the response. * @throws AmazonServiceException * If any errors occurred in Amazon S3 while processing the * request. */ private PutObjectResult putObjectUsingMetadata(PutObjectRequest putObjectRequest) throws AmazonClientException, AmazonServiceException { // Create instruction EncryptionInstruction instruction = EncryptionUtils.generateInstruction(this.encryptionMaterialsProvider, this.cryptoConfig.getCryptoProvider()); // Encrypt the object data with the instruction PutObjectRequest encryptedObjectRequest = EncryptionUtils.encryptRequestUsingInstruction(putObjectRequest, instruction); // Update the metadata EncryptionUtils.updateMetadataWithEncryptionInstruction( putObjectRequest, instruction ); // Put the encrypted object into S3 return super.putObject(encryptedObjectRequest); } /** * Puts an encrypted object into S3, and puts an instruction file into S3. Encryption info is stored in the instruction file. * * @param putObjectRequest * The request object containing all the parameters to upload a * new object to Amazon S3. * @return * A {@link PutObjectResult} object containing the information * returned by Amazon S3 for the new, created object. * @throws AmazonClientException * If any errors are encountered on the client while making the * request or handling the response. * @throws AmazonServiceException * If any errors occurred in Amazon S3 while processing the * request. */ private PutObjectResult putObjectUsingInstructionFile(PutObjectRequest putObjectRequest) throws AmazonClientException, AmazonServiceException { // Create instruction EncryptionInstruction instruction = EncryptionUtils.generateInstruction(this.encryptionMaterialsProvider, this.cryptoConfig.getCryptoProvider()); // Encrypt the object data with the instruction PutObjectRequest encryptedObjectRequest = EncryptionUtils.encryptRequestUsingInstruction(putObjectRequest, instruction); // Put the encrypted object into S3 PutObjectResult encryptedObjectResult = super.putObject(encryptedObjectRequest); // Put the instruction file into S3 PutObjectRequest instructionRequest = EncryptionUtils.createInstructionPutRequest(putObjectRequest, instruction); super.putObject(instructionRequest); // Return the result of the encrypted object PUT. return encryptedObjectResult; } /** * Decrypts an object using information retrieved from metadata. If decryption is not possible, returns null. * * @param object * The S3Object to be decrypted. * @return * An S3Object with decrypted object contents. If decryption is not possible, returns null. */ private S3Object decryptObjectUsingMetadata(S3Object object) { // Create an instruction object from the object headers EncryptionInstruction instruction = EncryptionUtils.buildInstructionFromObjectMetadata( object, this.encryptionMaterialsProvider, this.cryptoConfig.getCryptoProvider() ); // Decrypt the object file with the instruction return EncryptionUtils.decryptObjectUsingInstruction(object, instruction); } /** * Decrypts an object using information retrieved from an instruction file. * * @param object * The S3Object to be decrypted. * @param instructionFile * The S3Object instruction file to be used to decrypt the object. * @return * An S3Object with decrypted object contents. */ private S3Object decryptObjectUsingInstructionFile(S3Object object, S3Object instructionFile) { // Create an instruction object from the retrieved instruction file EncryptionInstruction instruction = EncryptionUtils.buildInstructionFromInstructionFile(instructionFile, this.encryptionMaterialsProvider, this.cryptoConfig.getCryptoProvider()); // Decrypt the object file with the instruction return EncryptionUtils.decryptObjectUsingInstruction(object, instruction); } /** * Retrieves an instruction file from S3. If no instruction file is found, returns null. * * @param getObjectRequest * A GET request for an object in S3. The parameters from this request will be used * to retrieve the corresponding instruction file. * @return * An instruction file, or null if no instruction file was found. */ private S3Object getInstructionFile(GetObjectRequest getObjectRequest) { try { GetObjectRequest instructionFileRequest = EncryptionUtils.createInstructionGetRequest(getObjectRequest); return super.getObject(instructionFileRequest); } catch (AmazonServiceException e) { // If no instruction file is found, log a debug message, and return null. log.debug("Unable to retrieve instruction file : " + e.getMessage()); return null; } } public X appendUserAgent(X request, String userAgent) { request.getRequestClientOptions().appendUserAgent(userAgent); return request; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy