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 java.io.IOException;
import java.io.InputStream;
import java.util.List;
import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManager;
import com.amazonaws.encryptionsdk.exception.BadCiphertextException;
import com.amazonaws.encryptionsdk.internal.MessageCryptoHandler;
import com.amazonaws.encryptionsdk.internal.Utils;
/**
* 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());
}
}