/** @deprecated no longer used and will be removed in the future */ @Deprecated public static ByteRangeCapturingInputStream getEncryptedInputStream( UploadPartRequest request, CipherFactory cipherFactory) { try { InputStream originalInputStream = request.getInputStream(); if (request.getFile() != null) { originalInputStream = new InputSubstream( new RepeatableFileInputStream(request.getFile()), request.getFileOffset(), request.getPartSize(), request.isLastPart()); } originalInputStream = new RepeatableCipherInputStream(originalInputStream, cipherFactory); if (request.isLastPart() == false) { // We want to prevent the final padding from being sent on the stream... originalInputStream = new InputSubstream(originalInputStream, 0, request.getPartSize(), false); } long partSize = request.getPartSize(); int cipherBlockSize = cipherFactory.createCipher().getBlockSize(); return new ByteRangeCapturingInputStream( originalInputStream, partSize - cipherBlockSize, partSize); } catch (Exception e) { throw new AmazonClientException("Unable to create cipher input stream: " + e.getMessage(), e); } }
private PartETag uploadChunk( ByteBuffer bytes, InitiateMultipartUploadResult initResponse, int index) throws Exception { byte[] md5 = S3Utils.md5(bytes); UploadPartRequest request = new UploadPartRequest(); request.setBucketName(initResponse.getBucketName()); request.setKey(initResponse.getKey()); request.setUploadId(initResponse.getUploadId()); request.setPartNumber(index); request.setPartSize(bytes.limit()); request.setMd5Digest(S3Utils.toBase64(md5)); request.setInputStream(new ByteBufferInputStream(bytes)); UploadPartResult response = s3Client.uploadPart(request); PartETag partETag = response.getPartETag(); if (!response.getPartETag().getETag().equals(S3Utils.toHex(md5))) { throw new Exception("Unable to match MD5 for part " + index); } return partETag; }
/** @deprecated no longer used and will be removed in the future */ @Deprecated public static long calculateCryptoContentLength( Cipher symmetricCipher, UploadPartRequest request) { long plaintextLength; if (request.getFile() != null) { if (request.getPartSize() > 0) plaintextLength = request.getPartSize(); else plaintextLength = request.getFile().length(); } else if (request.getInputStream() != null) { plaintextLength = request.getPartSize(); } else { return -1; } long cipherBlockSize = symmetricCipher.getBlockSize(); long offset = cipherBlockSize - (plaintextLength % cipherBlockSize); return plaintextLength + offset; }
/** * {@inheritDoc} * * <p><b>NOTE:</b> 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 InputStream 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); if (encryptedInputStream instanceof ByteRangeCapturingInputStream) { ByteRangeCapturingInputStream bris = (ByteRangeCapturingInputStream) encryptedInputStream; encryptedUploadContext.setNextInitializationVector(bris.getBlock()); } else { throw new AmazonClientException("Unable to access last block of encrypted data"); } return result; }