// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package com.amazonaws.encryptionsdk.internal; import com.amazonaws.encryptionsdk.*; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.BadCiphertextException; import com.amazonaws.encryptionsdk.model.CiphertextFooters; import com.amazonaws.encryptionsdk.model.CiphertextHeaders; import com.amazonaws.encryptionsdk.model.CiphertextType; import com.amazonaws.encryptionsdk.model.ContentType; import com.amazonaws.encryptionsdk.model.DecryptionMaterials; import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequest; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import javax.crypto.Cipher; import javax.crypto.SecretKey; /** * This class implements the CryptoHandler interface by providing methods for the decryption of * ciphertext produced by the methods in {@link EncryptionHandler}. * *
This class reads and parses the values in the ciphertext headers and delegates the decryption
* of the ciphertext to the {@link BlockDecryptionHandler} or {@link FrameDecryptionHandler} based
* on the content type parsed in the ciphertext headers.
*/
public class DecryptionHandler Note the methods in the provided master key are used in decrypting the encrypted data key
* parsed from the ciphertext headers.
*
* @param customerMasterKeyProvider the master key provider to use in picking a master key from
* the key blobs encoded in the provided ciphertext.
* @param commitmentPolicy The commitment policy to enforce during decryption
* @param signaturePolicy The signature policy to enforce during decryption
* @param maxEncryptedDataKeys The maximum number of encrypted data keys to unwrap during
* decryption; zero indicates no maximum
* @throws AwsCryptoException if the master key is null.
*/
@SuppressWarnings("unchecked")
public static Note the methods in the provided master key are used in decrypting the encrypted data key
* parsed from the ciphertext headers.
*
* @param customerMasterKeyProvider the master key provider to use in picking a master key from
* the key blobs encoded in the provided ciphertext.
* @param headers already parsed headers which will not be passed into {@link
* #processBytes(byte[], int, int, byte[], int)}
* @param commitmentPolicy The commitment policy to enforce during decryption
* @param signaturePolicy The signature policy to enforce during decryption
* @param maxEncryptedDataKeys The maximum number of encrypted data keys to unwrap during
* decryption; zero indicates no maximum
* @throws AwsCryptoException if the master key is null.
* @deprecated This version may have to recalculate the number of bytes already parsed, which adds
* a performance penalty. Use {@link #create(CryptoMaterialsManager, ParsedCiphertext,
* CommitmentPolicy, SignaturePolicy, int)} instead, which makes the parsed byte count
* directly available instead.
*/
@SuppressWarnings("unchecked")
@Deprecated
public static Note the methods in the provided master key are used in decrypting the encrypted data key
* parsed from the ciphertext headers.
*
* @param customerMasterKeyProvider the master key provider to use in picking a master key from
* the key blobs encoded in the provided ciphertext.
* @param headers already parsed headers which will not be passed into {@link
* #processBytes(byte[], int, int, byte[], int)}
* @param commitmentPolicy The commitment policy to enforce during decryption
* @param signaturePolicy The signature policy to enforce during decryption
* @param maxEncryptedDataKeys The maximum number of encrypted data keys to unwrap during
* decryption; zero indicates no maximum
* @throws AwsCryptoException if the master key is null.
*/
@SuppressWarnings("unchecked")
public static Note the methods in the provided materials manager are used in decrypting the encrypted data
* key parsed from the ciphertext headers.
*
* @param materialsManager the materials manager to use in decrypting the data key from the key
* blobs encoded in the provided ciphertext.
* @param commitmentPolicy The commitment policy to enforce during decryption
* @param signaturePolicy The signature policy to enforce during decryption
* @param maxEncryptedDataKeys The maximum number of encrypted data keys to unwrap during
* decryption; zero indicates no maximum
* @throws AwsCryptoException if the master key is null.
*/
public static DecryptionHandler> create(
final CryptoMaterialsManager materialsManager,
final CommitmentPolicy commitmentPolicy,
final SignaturePolicy signaturePolicy,
final int maxEncryptedDataKeys)
throws AwsCryptoException {
return new DecryptionHandler(
materialsManager, commitmentPolicy, signaturePolicy, maxEncryptedDataKeys);
}
/**
* Create a decryption handler using the provided materials manager and already parsed {@code
* headers}.
*
* Note the methods in the provided materials manager are used in decrypting the encrypted data
* key parsed from the ciphertext headers.
*
* @param materialsManager the materials manager to use in decrypting the data key from the key
* blobs encoded in the provided ciphertext.
* @param headers already parsed headers which will not be passed into {@link
* #processBytes(byte[], int, int, byte[], int)}
* @param commitmentPolicy The commitment policy to enforce during decryption
* @param signaturePolicy The signature policy to enforce during decryption
* @param maxEncryptedDataKeys The maximum number of encrypted data keys to unwrap during
* decryption; zero indicates no maximum
* @throws AwsCryptoException if the master key is null.
* @deprecated This version may have to recalculate the number of bytes already parsed, which adds
* a performance penalty. Use {@link #create(CryptoMaterialsManager, ParsedCiphertext,
* CommitmentPolicy, SignaturePolicy, int)} instead, which makes the parsed byte count
* directly available instead.
*/
@Deprecated
public static DecryptionHandler> create(
final CryptoMaterialsManager materialsManager,
final CiphertextHeaders headers,
final CommitmentPolicy commitmentPolicy,
final SignaturePolicy signaturePolicy,
final int maxEncryptedDataKeys)
throws AwsCryptoException {
return new DecryptionHandler(
materialsManager, headers, commitmentPolicy, signaturePolicy, maxEncryptedDataKeys);
}
/**
* Create a decryption handler using the provided materials manager and already parsed {@code
* headers}.
*
* Note the methods in the provided materials manager are used in decrypting the encrypted data
* key parsed from the ciphertext headers.
*
* @param materialsManager the materials manager to use in decrypting the data key from the key
* blobs encoded in the provided ciphertext.
* @param headers already parsed headers which will not be passed into {@link
* #processBytes(byte[], int, int, byte[], int)}
* @param commitmentPolicy The commitment policy to enforce during decryption
* @param signaturePolicy The signature policy to enforce during decryption
* @param maxEncryptedDataKeys The maximum number of encrypted data keys to unwrap during
* decryption; zero indicates no maximum
* @throws AwsCryptoException if the master key is null.
*/
public static DecryptionHandler> create(
final CryptoMaterialsManager materialsManager,
final ParsedCiphertext headers,
final CommitmentPolicy commitmentPolicy,
final SignaturePolicy signaturePolicy,
final int maxEncryptedDataKeys)
throws AwsCryptoException {
return new DecryptionHandler(
materialsManager, headers, commitmentPolicy, signaturePolicy, maxEncryptedDataKeys);
}
/**
* Decrypt the ciphertext bytes provided in {@code in} and copy the plaintext bytes to {@code
* out}.
*
* This method consumes and parses the ciphertext headers. The decryption of the actual content
* is delegated to {@link BlockDecryptionHandler} or {@link FrameDecryptionHandler} based on the
* content type parsed in the ciphertext header.
*
* @param in the input byte array.
* @param off the offset into the in array where the data to be decrypted starts.
* @param len the number of bytes to be decrypted.
* @param out the output buffer the decrypted plaintext bytes go into.
* @param outOff the offset into the output byte array the decrypted data starts at.
* @return the number of bytes written to {@code out} and processed.
* @throws BadCiphertextException if the ciphertext header contains invalid entries or if the
* header integrity check fails.
* @throws AwsCryptoException if any of the offset or length arguments are negative or if the
* total bytes to decrypt exceeds the maximum allowed value.
*/
@Override
public ProcessingSummary processBytes(
final byte[] in, final int off, final int len, final byte[] out, final int outOff)
throws BadCiphertextException, AwsCryptoException {
// We should arguably check if we are already complete_ here as other handlers
// like FrameDecryptionHandler and BlockDecryptionHandler do.
// However, adding that now could potentially break customers who have extra trailing
// bytes in their decryption streams.
// The handlers are also inconsistent in general with this check. Even those that
// do raise an exception here if already complete will not complain if
// a single call to processBytes() completes the message and provides extra trailing bytes:
// in that case they will just indicate that they didn't process the extra bytes instead.
if (len < 0 || off < 0) {
throw new AwsCryptoException(
String.format("Invalid values for input offset: %d and length: %d", off, len));
}
if (in.length == 0 || len == 0) {
return ProcessingSummary.ZERO;
}
final long totalBytesToParse = unparsedBytes_.length + (long) len;
// check for integer overflow
if (totalBytesToParse > Integer.MAX_VALUE) {
throw new AwsCryptoException(
"Size of the total bytes to parse and decrypt exceeded allowed maximum:"
+ Integer.MAX_VALUE);
}
checkSizeBound(len);
ciphertextBytesSupplied_ += len;
final byte[] bytesToParse = new byte[(int) totalBytesToParse];
final int leftoverBytes = unparsedBytes_.length;
// If there were previously unparsed bytes, add them as the first
// set of bytes to be parsed in this call.
System.arraycopy(unparsedBytes_, 0, bytesToParse, 0, unparsedBytes_.length);
System.arraycopy(in, off, bytesToParse, unparsedBytes_.length, len);
int totalParsedBytes = 0;
if (!ciphertextHeadersParsed_) {
totalParsedBytes += ciphertextHeaders_.deserialize(bytesToParse, 0, maxEncryptedDataKeys_);
// When ciphertext headers are complete, we have the data
// key and cipher mode to initialize the underlying cipher
if (ciphertextHeaders_.isComplete() == true) {
readHeaderFields(ciphertextHeaders_);
updateTrailingSignature(ciphertextHeaders_);
// reset unparsed bytes as parsing of ciphertext headers is
// complete.
unparsedBytes_ = new byte[0];
} else {
// If there aren't enough bytes to parse ciphertext
// headers, we don't have anymore bytes to continue parsing.
// But first copy the leftover bytes to unparsed bytes.
unparsedBytes_ = Arrays.copyOfRange(bytesToParse, totalParsedBytes, bytesToParse.length);
return new ProcessingSummary(0, len);
}
}
int actualOutLen = 0;
if (!contentCryptoHandler_.isComplete()) {
// if there are bytes to parse further, pass it off to underlying
// content cryptohandler.
if ((bytesToParse.length - totalParsedBytes) > 0) {
final ProcessingSummary contentResult =
contentCryptoHandler_.processBytes(
bytesToParse,
totalParsedBytes,
bytesToParse.length - totalParsedBytes,
out,
outOff);
updateTrailingSignature(bytesToParse, totalParsedBytes, contentResult.getBytesProcessed());
actualOutLen = contentResult.getBytesWritten();
totalParsedBytes += contentResult.getBytesProcessed();
}
if (contentCryptoHandler_.isComplete()) {
actualOutLen += contentCryptoHandler_.doFinal(out, outOff + actualOutLen);
}
}
if (contentCryptoHandler_.isComplete()) {
// If the crypto algorithm contains trailing signature, we will need to verify
// the footer of the message.
if (cryptoAlgo_.getTrailingSignatureLength() > 0) {
totalParsedBytes += ciphertextFooters_.deserialize(bytesToParse, totalParsedBytes);
if (ciphertextFooters_.isComplete()) {
// reset unparsed bytes as parsing of the ciphertext footer is
// complete.
// This isn't strictly necessary since processing any further data
// should be an error.
unparsedBytes_ = new byte[0];
try {
if (!trailingSig_.verify(ciphertextFooters_.getMAuth())) {
throw new BadCiphertextException("Bad trailing signature");
}
} catch (final SignatureException ex) {
throw new BadCiphertextException("Bad trailing signature", ex);
}
complete_ = true;
} else {
// If there aren't enough bytes to parse the ciphertext
// footer, we don't have any more bytes to continue parsing.
// But first copy the leftover bytes to unparsed bytes.
unparsedBytes_ = Arrays.copyOfRange(bytesToParse, totalParsedBytes, bytesToParse.length);
return new ProcessingSummary(actualOutLen, len);
}
} else {
complete_ = true;
}
}
return new ProcessingSummary(actualOutLen, totalParsedBytes - leftoverBytes);
}
/**
* Finish processing of the bytes.
*
* @param out space for any resulting output data.
* @param outOff offset into {@code out} to start copying the data at.
* @return number of bytes written into {@code out}.
* @throws BadCiphertextException if the bytes do not decrypt correctly.
*/
@Override
public int doFinal(final byte[] out, final int outOff) throws BadCiphertextException {
// This is an unfortunate special case we have to support for backwards-compatibility.
if (ciphertextBytesSupplied_ == 0) {
return 0;
}
// check if cryptohandler for content has been created. There are cases
// when it might not have been created such as when doFinal() is called
// before the ciphertext headers are fully received and parsed.
if (contentCryptoHandler_ == null) {
throw new BadCiphertextException("Unable to process entire ciphertext.");
} else {
int result = contentCryptoHandler_.doFinal(out, outOff);
if (!complete_) {
throw new BadCiphertextException("Unable to process entire ciphertext.");
}
return result;
}
}
/**
* Return the size of the output buffer required for a processBytes
plus a
* doFinal
with an input of inLen bytes.
*
* @param inLen the length of the input.
* @return the space required to accommodate a call to processBytes and doFinal with input of size
* {@code inLen} bytes.
*/
@Override
public int estimateOutputSize(final int inLen) {
if (contentCryptoHandler_ != null) {
return contentCryptoHandler_.estimateOutputSize(inLen);
} else {
return (inLen > 0) ? inLen : 0;
}
}
@Override
public int estimatePartialOutputSize(int inLen) {
if (contentCryptoHandler_ != null) {
return contentCryptoHandler_.estimatePartialOutputSize(inLen);
} else {
return (inLen > 0) ? inLen : 0;
}
}
@Override
public int estimateFinalOutputSize() {
if (contentCryptoHandler_ != null) {
return contentCryptoHandler_.estimateFinalOutputSize();
} else {
return 0;
}
}
/**
* Return the encryption context. This value is parsed from the ciphertext.
*
* @return the key-value map containing the encryption client.
*/
@Override
public Map