com.amazonaws.encryptionsdk.CryptoInputStream Maven / Gradle / Ivy
/*
* Copyright 2016 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.encryptionsdk;
import static com.amazonaws.encryptionsdk.internal.Utils.assertNonNull;
import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManager;
import com.amazonaws.encryptionsdk.exception.BadCiphertextException;
import com.amazonaws.encryptionsdk.internal.MessageCryptoHandler;
import com.amazonaws.encryptionsdk.internal.Utils;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* A CryptoInputStream is a subclass of java.io.InputStream. It performs cryptographic
* transformation of the bytes passing through it.
*
* The CryptoInputStream wraps a provided InputStream object and performs cryptographic
* transformation of the bytes read from the wrapped InputStream. It uses the cryptography handler
* provided during construction to invoke methods that perform the cryptographic transformations.
*
*
In short, reading from the CryptoInputStream returns bytes that are the cryptographic
* transformations of the bytes read from the wrapped InputStream.
*
*
For example, if the cryptography handler provides methods for decryption, the
* CryptoInputStream will read ciphertext bytes from the wrapped InputStream, decrypt, and return
* them as plaintext bytes.
*
*
This class adheres strictly to the semantics, especially the failure semantics, of its
* ancestor class java.io.InputStream. This class overrides all the methods specified in its
* ancestor class.
*
*
To instantiate an instance of this class, please see {@link AwsCrypto}.
*
* @param The type of {@link MasterKey}s used to manipulate the data.
*/
public class CryptoInputStream> extends InputStream {
private static final int MAX_READ_LEN = 4096;
private byte[] outBytes_ = new byte[0];
private int outStart_;
private int outEnd_;
private final InputStream inputStream_;
private final MessageCryptoHandler cryptoHandler_;
private boolean hasFinalCalled_;
private boolean hasProcessBytesCalled_;
/**
* Constructs a CryptoInputStream that wraps the provided InputStream object. It performs
* cryptographic transformation of the bytes read from the wrapped InputStream using the methods
* provided in the provided CryptoHandler implementation.
*
* @param inputStream the inputStream object to be wrapped.
* @param cryptoHandler the cryptoHandler implementation that provides the methods to use in
* performing cryptographic transformation of the bytes read from the inputStream.
*/
CryptoInputStream(final InputStream inputStream, final MessageCryptoHandler cryptoHandler) {
inputStream_ = Utils.assertNonNull(inputStream, "inputStream");
cryptoHandler_ = Utils.assertNonNull(cryptoHandler, "cryptoHandler");
}
/**
* Fill the output bytes by reading from the wrapped InputStream and processing it through the
* crypto handler.
*
* @return the number of bytes processed and returned by the crypto handler.
*/
private int fillOutBytes() throws IOException, BadCiphertextException {
final byte[] inputStreamBytes = new byte[MAX_READ_LEN];
final int readLen = inputStream_.read(inputStreamBytes);
outStart_ = 0;
int processedLen;
if (readLen < 0) {
// Mark end of stream until doFinal returns something.
processedLen = -1;
if (!hasFinalCalled_) {
int outOffset = 0;
int outLen = 0;
// Handle the case where processBytes() was never called before.
// This happens with an empty file where the end of stream is
// reached on the first read attempt. In this case,
// processBytes() must be called so the header bytes are written
// during encryption.
if (!hasProcessBytesCalled_) {
outBytes_ = new byte[cryptoHandler_.estimateOutputSize(0)];
outLen +=
cryptoHandler_
.processBytes(inputStreamBytes, 0, 0, outBytes_, outOffset)
.getBytesWritten();
outOffset += outLen;
} else {
outBytes_ = new byte[cryptoHandler_.estimateFinalOutputSize()];
}
// Get final bytes.
outLen += cryptoHandler_.doFinal(outBytes_, outOffset);
processedLen = outLen;
hasFinalCalled_ = true;
}
} else {
// process the read bytes.
outBytes_ = new byte[cryptoHandler_.estimatePartialOutputSize(readLen)];
processedLen =
cryptoHandler_
.processBytes(inputStreamBytes, 0, readLen, outBytes_, outStart_)
.getBytesWritten();
hasProcessBytesCalled_ = true;
}
outEnd_ = processedLen;
return processedLen;
}
/**
* {@inheritDoc}
*
* @throws BadCiphertextException This is thrown only during decryption if b contains invalid or
* corrupt ciphertext.
*/
@Override
public int read(final byte[] b, final int off, final int len)
throws IllegalArgumentException, IOException, BadCiphertextException {
assertNonNull(b, "b");
if (len < 0 || off < 0) {
throw new IllegalArgumentException(
String.format("Invalid values for offset: %d and length: %d", off, len));
}
if (b.length == 0 || len == 0) {
return 0;
}
// fill the output bytes if there aren't any left to return.
if ((outEnd_ - outStart_) <= 0) {
int newBytesLen = 0;
// Block until a byte is read or end of stream in the underlying
// stream is reached.
while (newBytesLen == 0) {
newBytesLen = fillOutBytes();
}
if (newBytesLen < 0) {
return -1;
}
}
final int copyLen = Math.min((outEnd_ - outStart_), len);
System.arraycopy(outBytes_, outStart_, b, off, copyLen);
outStart_ += copyLen;
return copyLen;
}
/**
* {@inheritDoc}
*
* @throws BadCiphertextException This is thrown only during decryption if b contains invalid or
* corrupt ciphertext.
*/
@Override
public int read(final byte[] b)
throws IllegalArgumentException, IOException, BadCiphertextException {
return read(b, 0, b.length);
}
/**
* {@inheritDoc}
*
* @throws BadCiphertextException if b contains invalid or corrupt ciphertext. This is thrown only
* during decryption.
*/
@Override
public int read() throws IOException, BadCiphertextException {
final byte[] bArray = new byte[1];
int result = 0;
while (result == 0) {
result = read(bArray, 0, 1);
}
if (result > 0) {
return (bArray[0] & 0xFF);
} else {
return result;
}
}
@Override
public void close() throws IOException {
inputStream_.close();
}
/** Returns metadata associated with the performed cryptographic operation. */
@Override
public int available() throws IOException {
return (outBytes_.length + inputStream_.available());
}
/**
* Sets an upper bound on the size of the input data. This method should be called before reading
* any data from the stream. If this method is not called prior to reading any data, performance
* may be reduced (notably, it will not be possible to cache data keys when encrypting).
*
* Among other things, this size is used to enforce limits configured on the {@link
* CachingCryptoMaterialsManager}.
*
*
If the input size set here is exceeded, an exception will be thrown, and the encyption or
* decryption will fail.
*
*
If this method is called multiple times, the smallest bound will be used.
*
* @param size Maximum input size.
*/
public void setMaxInputLength(long size) {
cryptoHandler_.setMaxInputLength(size);
}
/**
* Returns the result of the cryptographic operations including associate metadata.
*
* @throws IOException
* @throws BadCiphertextException
*/
public CryptoResult, K> getCryptoResult()
throws BadCiphertextException, IOException {
while (!cryptoHandler_.getHeaders().isComplete()) {
if (fillOutBytes() == -1) {
throw new BadCiphertextException("No CiphertextHeaders found.");
}
}
//noinspection unchecked
return new CryptoResult<>(
this, (List) cryptoHandler_.getMasterKeys(), cryptoHandler_.getHeaders());
}
}