/** * Builds an instruction object from the contents of an instruction file. * * @param instructionFile A non-null instruction file retrieved from S3 that contains encryption * information * @param materialsProvider The non-null encryption materials provider to be used to encrypt and * decrypt data. * @param cryptoProvider The crypto provider whose encryption implementation will be used to * encrypt and decrypt data. Null is ok and uses the preferred provider from * Security.getProviders(). * @return A non-null instruction object containing encryption information * @deprecated no longer used and will be removed in the future */ @Deprecated public static EncryptionInstruction buildInstructionFromInstructionFile( S3Object instructionFile, EncryptionMaterialsProvider materialsProvider, Provider cryptoProvider) { JSONObject instructionJSON = parseJSONInstruction(instructionFile); try { // Get fields from instruction object String encryptedSymmetricKeyB64 = instructionJSON.getString(Headers.CRYPTO_KEY); String ivB64 = instructionJSON.getString(Headers.CRYPTO_IV); String materialsDescriptionString = instructionJSON.tryGetString(Headers.MATERIALS_DESCRIPTION); Map<String, String> materialsDescription = convertJSONToMap(materialsDescriptionString); // Decode from Base 64 to standard binary bytes byte[] encryptedSymmetricKey = Base64.decode(encryptedSymmetricKeyB64); byte[] iv = Base64.decode(ivB64); if (encryptedSymmetricKey == null || iv == null) { // If necessary encryption info was not found in the instruction file, throw an exception. throw new AmazonClientException( String.format( "Necessary encryption info not found in the instruction file '%s' in bucket '%s'", instructionFile.getKey(), instructionFile.getBucketName())); } EncryptionMaterials materials = retrieveOriginalMaterials(materialsDescription, materialsProvider); // If we're unable to retrieve the original encryption materials, we can't decrypt the object, // so // throw an exception. if (materials == null) { throw new AmazonClientException( String.format( "Unable to retrieve the encryption materials that originally " + "encrypted object corresponding to instruction file '%s' in bucket '%s'.", instructionFile.getKey(), instructionFile.getBucketName())); } // Decrypt the symmetric key and create the symmetric cipher SecretKey symmetricKey = getDecryptedSymmetricKey(encryptedSymmetricKey, materials, cryptoProvider); CipherFactory cipherFactory = new CipherFactory(symmetricKey, Cipher.DECRYPT_MODE, iv, cryptoProvider); return new EncryptionInstruction( materialsDescription, encryptedSymmetricKey, symmetricKey, cipherFactory); } catch (JSONException e) { throw new AmazonClientException( "Unable to parse retrieved instruction file : " + e.getMessage()); } }
/** @see com.amazonaws.http.HttpResponseHandler#handle(com.amazonaws.http.HttpResponse) */ public AmazonWebServiceResponse<S3Object> handle(HttpResponse response) throws Exception { /* * TODO: It'd be nice to set the bucket name and key here, but the * information isn't easy to pull out of the response/request * currently. */ S3Object object = new S3Object(); AmazonWebServiceResponse<S3Object> awsResponse = parseResponseMetadata(response); if (response.getHeaders().get(Headers.REDIRECT_LOCATION) != null) { object.setRedirectLocation(response.getHeaders().get(Headers.REDIRECT_LOCATION)); } ObjectMetadata metadata = object.getObjectMetadata(); populateObjectMetadata(response, metadata); boolean hasServerSideCalculatedChecksum = !ServiceUtils.isMultipartUploadETag(metadata.getETag()); boolean responseContainsEntireObject = response.getHeaders().get("Content-Range") == null; if (hasServerSideCalculatedChecksum && responseContainsEntireObject) { byte[] expectedChecksum = BinaryUtils.fromHex(metadata.getETag()); object.setObjectContent( new S3ObjectInputStream( new ChecksumValidatingInputStream( response.getContent(), expectedChecksum, object.getBucketName() + "/" + object.getKey()), response.getHttpRequest())); } else { object.setObjectContent( new S3ObjectInputStream(response.getContent(), response.getHttpRequest())); } awsResponse.setResult(object); return awsResponse; }
/** * Builds an instruction object from the object metadata. * * @param object A non-null object that contains encryption information in its headers * @param materialsProvider The non-null encryption materials provider to be used to encrypt and * decrypt data. * @param cryptoProvider The crypto provider whose encryption implementation will be used to * encrypt and decrypt data. Null is ok and uses the preferred provider from * Security.getProviders(). * @return A non-null instruction object containing encryption information * @throws AmazonClientException if encryption information is missing in the metadata, or the * encryption materials used to encrypt the object are not available via the materials * Accessor * @deprecated no longer used and will be removed in the future */ @Deprecated public static EncryptionInstruction buildInstructionFromObjectMetadata( S3Object object, EncryptionMaterialsProvider materialsProvider, Provider cryptoProvider) { ObjectMetadata metadata = object.getObjectMetadata(); // Get encryption info from metadata. byte[] encryptedSymmetricKeyBytes = getCryptoBytesFromMetadata(Headers.CRYPTO_KEY, metadata); byte[] initVectorBytes = getCryptoBytesFromMetadata(Headers.CRYPTO_IV, metadata); String materialsDescriptionString = getStringFromMetadata(Headers.MATERIALS_DESCRIPTION, metadata); Map<String, String> materialsDescription = convertJSONToMap(materialsDescriptionString); if (encryptedSymmetricKeyBytes == null || initVectorBytes == null) { // If necessary encryption info was not found in the instruction file, throw an exception. throw new AmazonClientException( String.format( "Necessary encryption info not found in the headers of file '%s' in bucket '%s'", object.getKey(), object.getBucketName())); } EncryptionMaterials materials = retrieveOriginalMaterials(materialsDescription, materialsProvider); // If we're unable to retrieve the original encryption materials, we can't decrypt the object, // so // throw an exception. if (materials == null) { throw new AmazonClientException( String.format( "Unable to retrieve the encryption materials that originally " + "encrypted file '%s' in bucket '%s'.", object.getKey(), object.getBucketName())); } // Decrypt the symmetric key and create the symmetric cipher SecretKey symmetricKey = getDecryptedSymmetricKey(encryptedSymmetricKeyBytes, materials, cryptoProvider); CipherFactory cipherFactory = new CipherFactory(symmetricKey, Cipher.DECRYPT_MODE, initVectorBytes, cryptoProvider); return new EncryptionInstruction( materialsDescription, encryptedSymmetricKeyBytes, symmetricKey, cipherFactory); }
/** * Gets an object stored in S3 and downloads it into the specified file. This method includes the * one-time retry mechanism after integrity check failure on the downloaded file. It will also * return immediately after getting null valued S3Object (when getObject request does not meet the * specified constraints). * * @param file The file to store the object's data in. * @param safeS3DownloadTask The implementation of SafeS3DownloadTask interface which allows user * to get access to all the visible variables at the calling site of this method. */ public static S3Object retryableDownloadS3ObjectToFile( File file, RetryableS3DownloadTask retryableS3DownloadTask) { boolean hasRetried = false; boolean needRetry; S3Object s3Object; do { needRetry = false; s3Object = retryableS3DownloadTask.getS3ObjectStream(); if (s3Object == null) return null; try { ServiceUtils.downloadObjectToFile( s3Object, file, retryableS3DownloadTask.needIntegrityCheck()); } catch (AmazonClientException ace) { // Determine whether an immediate retry is needed according to the captured // AmazonClientException. // (There are three cases when downloadObjectToFile() throws AmazonClientException: // 1) SocketException or SSLProtocolException when writing to disk (e.g. when user aborts // the download) // 2) Other IOException when writing to disk // 3) MD5 hashes don't match // The current code will retry the download only when case 2) or 3) happens. if (ace.getCause() instanceof SocketException || ace.getCause() instanceof SSLProtocolException) { throw ace; } else { needRetry = true; if (hasRetried) throw ace; else { log.info( "Retry the download of object " + s3Object.getKey() + " (bucket " + s3Object.getBucketName() + ")", ace); hasRetried = true; } } } finally { try { s3Object.getObjectContent().abort(); } catch (IOException e) { } } } while (needRetry); return s3Object; }
String getBucketName() { return s3obj.getBucketName(); }
/* (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); }