// 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.CommitmentPolicy; import com.amazonaws.encryptionsdk.CryptoAlgorithm; import com.amazonaws.encryptionsdk.MasterKey; 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.EncryptionMaterials; import com.amazonaws.encryptionsdk.model.KeyBlob; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.Signature; import java.security.SignatureException; import java.security.interfaces.ECPrivateKey; import java.util.List; import java.util.Map; import javax.crypto.Cipher; import javax.crypto.SecretKey; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DERSequence; /** * This class implements the CryptoHandler interface by providing methods for the encryption of * plaintext data. * *
This class creates the ciphertext headers and delegates the encryption of the plaintext to the
* {@link BlockEncryptionHandler} or {@link FrameEncryptionHandler} based on the content type.
*/
public class EncryptionHandler implements MessageCryptoHandler {
private static final CiphertextType CIPHERTEXT_TYPE =
CiphertextType.CUSTOMER_AUTHENTICATED_ENCRYPTED_DATA;
private final EncryptionMaterials encryptionMaterials_;
private final Map It encrypts by performing the following operations:
*
*
*
*
* @param in the input byte array.
* @param off the offset into the in array where the data to be encrypted starts.
* @param len the number of bytes to be encrypted.
* @param out the output buffer the encrypted bytes go into.
* @param outOff the offset into the output byte array the encrypted data starts at.
* @return the number of bytes written to out and processed
* @throws AwsCryptoException if len or offset values are negative.
* @throws BadCiphertextException thrown by the underlying cipher handler.
*/
@Override
public ProcessingSummary processBytes(
final byte[] in, final int off, final int len, final byte[] out, final int outOff)
throws AwsCryptoException, BadCiphertextException {
if (len < 0 || off < 0) {
throw new AwsCryptoException(
String.format("Invalid values for input offset: %d and length: %d", off, len));
}
checkPlaintextSizeLimit(len);
int actualOutLen = 0;
if (firstOperation_ == true) {
System.arraycopy(ciphertextHeaderBytes_, 0, out, outOff, ciphertextHeaderBytes_.length);
actualOutLen += ciphertextHeaderBytes_.length;
firstOperation_ = false;
}
ProcessingSummary contentOut =
contentCryptoHandler_.processBytes(in, off, len, out, outOff + actualOutLen);
actualOutLen += contentOut.getBytesWritten();
updateTrailingSignature(out, outOff, actualOutLen);
plaintextBytes_ += contentOut.getBytesProcessed();
return new ProcessingSummary(actualOutLen, contentOut.getBytesProcessed());
}
/**
* Finish encryption of the plaintext bytes.
*
* @param out space for any resulting output data.
* @param outOff offset into out to start copying the data at.
* @return number of bytes written into out.
* @throws BadCiphertextException thrown by the underlying cipher handler.
*/
@Override
public int doFinal(final byte[] out, final int outOff) throws BadCiphertextException {
if (complete_) {
throw new IllegalStateException("Attempted to call doFinal twice");
}
complete_ = true;
checkPlaintextSizeLimit(0);
int written = contentCryptoHandler_.doFinal(out, outOff);
updateTrailingSignature(out, outOff, written);
if (cryptoAlgo_.getTrailingSignatureLength() > 0) {
try {
CiphertextFooters footer = new CiphertextFooters(signContent());
byte[] fBytes = footer.toByteArray();
System.arraycopy(fBytes, 0, out, outOff + written, fBytes.length);
return written + fBytes.length;
} catch (final SignatureException ex) {
throw new AwsCryptoException(ex);
}
} else {
return written;
}
}
private byte[] signContent() throws SignatureException {
if (trailingDigest_ != null) {
if (!trailingSig_.getAlgorithm().contains("ECDSA")) {
throw new UnsupportedOperationException(
"Signatures calculated in pieces is only supported for ECDSA.");
}
final byte[] digest = trailingDigest_.digest();
return generateEcdsaFixedLengthSignature(digest);
}
return trailingSig_.sign();
}
private byte[] generateEcdsaFixedLengthSignature(final byte[] digest) throws SignatureException {
byte[] signature;
// Unfortunately, we need deterministic lengths some signatures are non-deterministic in length.
// So, retry until we get the right length :-(
do {
trailingSig_.update(digest);
signature = trailingSig_.sign();
if (signature.length != cryptoAlgo_.getTrailingSignatureLength()) {
// Most of the time, a signature of the wrong length can be fixed
// be negating s in the signature relative to the group order.
ASN1Sequence seq = ASN1Sequence.getInstance(signature);
ASN1Integer r = (ASN1Integer) seq.getObjectAt(0);
ASN1Integer s = (ASN1Integer) seq.getObjectAt(1);
ECPrivateKey ecKey = (ECPrivateKey) trailingSignaturePrivateKey_;
s = new ASN1Integer(ecKey.getParams().getOrder().subtract(s.getPositiveValue()));
seq = new DERSequence(new ASN1Encodable[] {r, s});
try {
signature = seq.getEncoded();
} catch (IOException ex) {
throw new SignatureException(ex);
}
}
} while (signature.length != cryptoAlgo_.getTrailingSignatureLength());
return signature;
}
/**
* Return the size of the output buffer required for a {@code processBytes} plus a {@code 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 len bytes of
* input.
*/
@Override
public int estimateOutputSize(final int inLen) {
int outSize = 0;
if (firstOperation_ == true) {
outSize += ciphertextHeaderBytes_.length;
}
outSize += contentCryptoHandler_.estimateOutputSize(inLen);
if (cryptoAlgo_.getTrailingSignatureLength() > 0) {
outSize += 2; // Length field in footer
outSize += cryptoAlgo_.getTrailingSignatureLength();
}
return outSize;
}
@Override
public int estimatePartialOutputSize(int inLen) {
int outSize = 0;
if (firstOperation_ == true) {
outSize += ciphertextHeaderBytes_.length;
}
outSize += contentCryptoHandler_.estimatePartialOutputSize(inLen);
return outSize;
}
@Override
public int estimateFinalOutputSize() {
return estimateOutputSize(0);
}
/**
* Return the encryption context.
*
* @return the key-value map containing encryption context.
*/
@Override
public Map