com.amazonaws.services.s3.internal.crypto.v2.ContentCryptoMaterial Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aws-java-sdk-s3 Show documentation
Show all versions of aws-java-sdk-s3 Show documentation
The AWS Java SDK for Amazon S3 module holds the client classes that are used for communicating with Amazon Simple Storage Service
/*
* Copyright 2013-2024 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.services.s3.internal.crypto.v2;
import static com.amazonaws.services.s3.Headers.AWS_CRYPTO_CEK_ALGORITHM;
import static com.amazonaws.services.s3.Headers.CRYPTO_CEK_ALGORITHM;
import static com.amazonaws.services.s3.model.ExtraMaterialsDescription.NONE;
import static com.amazonaws.util.BinaryUtils.copyAllBytesFrom;
import static com.amazonaws.util.Throwables.failure;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.security.Key;
import java.security.Provider;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.SdkClientException;
import com.amazonaws.services.kms.AWSKMS;
import com.amazonaws.services.kms.model.DecryptRequest;
import com.amazonaws.services.kms.model.DecryptResult;
import com.amazonaws.services.s3.Headers;
import com.amazonaws.services.s3.KeyWrapException;
import com.amazonaws.services.s3.internal.crypto.CipherLite;
import com.amazonaws.services.s3.internal.crypto.ContentCryptoScheme;
import com.amazonaws.services.s3.internal.crypto.CryptoUtils;
import com.amazonaws.services.s3.internal.crypto.JceEncryptionConstants;
import com.amazonaws.services.s3.internal.crypto.keywrap.InternalKeyWrapAlgorithm;
import com.amazonaws.services.s3.internal.crypto.keywrap.KMSKeyWrapperContext;
import com.amazonaws.services.s3.internal.crypto.keywrap.KeyWrapAlgorithmResolver;
import com.amazonaws.services.s3.internal.crypto.keywrap.KeyWrapper;
import com.amazonaws.services.s3.internal.crypto.keywrap.KeyWrapperContext;
import com.amazonaws.services.s3.internal.crypto.keywrap.KeyWrapperFactory;
import com.amazonaws.services.s3.model.CryptoConfigurationV2;
import com.amazonaws.services.s3.model.CryptoKeyWrapAlgorithm;
import com.amazonaws.services.s3.model.CryptoMode;
import com.amazonaws.services.s3.model.CryptoRangeGetMode;
import com.amazonaws.services.s3.model.EncryptionMaterials;
import com.amazonaws.services.s3.model.EncryptionMaterialsAccessor;
import com.amazonaws.services.s3.model.EncryptionMaterialsProvider;
import com.amazonaws.services.s3.model.ExtraMaterialsDescription;
import com.amazonaws.services.s3.model.KMSEncryptionMaterials;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutInstructionFileRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.util.Base64;
import com.amazonaws.util.StringUtils;
import com.amazonaws.util.json.Jackson;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Cryptographic material used for client-side content encrypt/decryption in S3.
* This includes the randomly generated one-time secured CEK
* (content-encryption-key) and the respective key wrapping algorithm, if any,
* and the cryptographic scheme in use.
*/
final class ContentCryptoMaterial {
private final InternalKeyWrapAlgorithm keyWrappingAlgorithm;
private final CipherLite cipherLite;
private final Map kekMaterialsDescription;
private final byte[] encryptedCEK;
ContentCryptoMaterial(Map kekMaterialsDescription,
byte[] encryptedCEK,
InternalKeyWrapAlgorithm keyWrappingAlgorithm,
CipherLite cipherLite) {
this.cipherLite = cipherLite;
this.keyWrappingAlgorithm = keyWrappingAlgorithm;
this.encryptedCEK = encryptedCEK.clone();
this.kekMaterialsDescription = kekMaterialsDescription;
}
/**
* Returns the key wrapping algorithm, or null if the content key is not
* secured via a key wrapping algorithm.
*/
InternalKeyWrapAlgorithm getKeyWrappingAlgorithm() {
return keyWrappingAlgorithm;
}
/**
* Returns the content crypto scheme.
*/
ContentCryptoScheme getContentCryptoScheme() {
return cipherLite.getContentCryptoScheme();
}
/**
* Returns the metadata in the latest format.
*/
ObjectMetadata toObjectMetadata(ObjectMetadata metadata) {
// If we generated a symmetric key to encrypt the data, store it in the
// object metadata.
byte[] encryptedCEK = getEncryptedCEK();
metadata.addUserMetadata(Headers.CRYPTO_KEY_V2,
Base64.encodeAsString(encryptedCEK));
// Put the cipher initialization vector (IV) into the object metadata
byte[] iv = cipherLite.getIV();
metadata.addUserMetadata(Headers.CRYPTO_IV, Base64.encodeAsString(iv));
// Put the materials description into the object metadata as JSON
metadata.addUserMetadata(Headers.MATERIALS_DESCRIPTION,
kekMaterialDescAsJson());
// The CRYPTO_CEK_ALGORITHM, CRYPTO_TAG_LENGTH and
// CRYPTO_KEYWRAP_ALGORITHM were not available in the Encryption Only
// (EO) implementation
ContentCryptoScheme scheme = getContentCryptoScheme();
metadata.addUserMetadata(CRYPTO_CEK_ALGORITHM,
scheme.getCipherAlgorithm());
int tagLen = scheme.getTagLengthInBits();
if (tagLen > 0)
metadata.addUserMetadata(Headers.CRYPTO_TAG_LENGTH,
String.valueOf(tagLen));
InternalKeyWrapAlgorithm keyWrapAlgo = getKeyWrappingAlgorithm();
if (keyWrapAlgo != null)
metadata.addUserMetadata(Headers.CRYPTO_KEYWRAP_ALGORITHM,
keyWrapAlgo.algorithmName());
return metadata;
}
/**
* Returns the json string in the latest format.
*/
String toJsonString() {
Map map = new HashMap<>();
byte[] encryptedCEK = getEncryptedCEK();
map.put(Headers.CRYPTO_KEY_V2, Base64.encodeAsString(encryptedCEK));
byte[] iv = cipherLite.getIV();
map.put(Headers.CRYPTO_IV, Base64.encodeAsString(iv));
map.put(Headers.MATERIALS_DESCRIPTION, kekMaterialDescAsJson());
// The CRYPTO_CEK_ALGORITHM, CRYPTO_TAG_LENGTH and
// CRYPTO_KEYWRAP_ALGORITHM were not available in the Encryption Only
// (EO) implementation
ContentCryptoScheme scheme = getContentCryptoScheme();
map.put(CRYPTO_CEK_ALGORITHM, scheme.getCipherAlgorithm());
int tagLen = scheme.getTagLengthInBits();
if (tagLen > 0)
map.put(Headers.CRYPTO_TAG_LENGTH, String.valueOf(tagLen));
InternalKeyWrapAlgorithm keyWrapAlgo = getKeyWrappingAlgorithm();
if (keyWrapAlgo != null)
map.put(Headers.CRYPTO_KEYWRAP_ALGORITHM, keyWrapAlgo.algorithmName());
return Jackson.toJsonString(map);
}
/**
* Returns the key-encrypting-key material description as a non-null json
* string;
*/
private String kekMaterialDescAsJson() {
Map kekMaterialDesc = getKEKMaterialsDescription();
if (kekMaterialDesc == null)
kekMaterialDesc = Collections.emptyMap();
return Jackson.toJsonString(kekMaterialDesc);
}
/**
* Returns the corresponding kek material description from the given json;
* or null if the input is null.
*/
private static Map matdescFromJson(String json) {
Map map = Jackson.stringMapFromJsonString(json);
return map == null ? null : Collections.unmodifiableMap(map);
}
/**
* Returns the content encrypting key unwrapped or decrypted. Note if KMS
* is used for key protection, a remote call will be made to KMS to decrypt
* the ciphertext blob.
*
* @param context contextual information needed to decrypt the CEK stored in a {@link KeyWrapperContext}
* @return the content encrypting key unwrapped or decrypted
*/
private static SecretKey decryptCEK(KeyWrapperContext context) {
if (isV1DecryptContext(context)){
return decryptV1CEK(context);
}
if (context.internalKeyWrapAlgorithm().isKMS()) {
validateKMSParameters(context);
}
Key kek = getDecryptionKeyFrom(context.materials());
String keyGeneratorAlgorithm = context.internalKeyWrapAlgorithm().isKMS() ?
context.contentCryptoScheme().getKeyGeneratorAlgorithm() :
kek.getAlgorithm();
KeyWrapper keyWrapper = KeyWrapperFactory.defaultInstance().createKeyWrapper(context);
return new SecretKeySpec(keyWrapper.unwrapCek(context.cekSecured(), kek), keyGeneratorAlgorithm);
}
private static boolean isV1DecryptContext(KeyWrapperContext context) {
InternalKeyWrapAlgorithm keyWrapAlgorithm = context.internalKeyWrapAlgorithm();
return keyWrapAlgorithm == null || keyWrapAlgorithm.isV1Algorithm();
}
private static void validateKMSParameters(KeyWrapperContext context) {
KMSKeyWrapperContext kmsKeyWrapperContext = context.kmsKeyWrapperContext();
if (kmsKeyWrapperContext == null) {
throw new IllegalStateException("Missing KMS parameters");
}
Map kmsMaterialsDescription = kmsKeyWrapperContext.kmsMaterialsDescription();
if (kmsMaterialsDescription == null) {
throw new IllegalStateException("Key materials from KMS must contain description entries");
}
String cekAlgoFromMaterials = kmsMaterialsDescription.get(AWS_CRYPTO_CEK_ALGORITHM);
if (cekAlgoFromMaterials == null) {
throw new IllegalStateException("Could not find required description in key material: "
+ AWS_CRYPTO_CEK_ALGORITHM);
}
String cekAlgoFromCryptoScheme =
CryptoUtils.normalizeContentAlgorithmForValidation(context.contentCryptoScheme().getCipherAlgorithm());
if (!cekAlgoFromMaterials.equals(cekAlgoFromCryptoScheme)) {
throw new IllegalStateException("Algorithm values from materials and metadata/instruction file don't match:"
+ cekAlgoFromMaterials + ", " + cekAlgoFromCryptoScheme);
}
}
/**
* Returns the content encrypting key unwrapped or decrypted. Note if KMS
* is used for key protection, a remote call will be made to KMS to decrypt
* the ciphertext blob.
*/
private static SecretKey decryptV1CEK(KeyWrapperContext context) {
InternalKeyWrapAlgorithm internalKeyWrapAlgorithm = context.internalKeyWrapAlgorithm();
if (internalKeyWrapAlgorithm != null && internalKeyWrapAlgorithm.isKMS()) {
return decryptV1CEKByKMS(context);
}
String keyWrapAlgo = internalKeyWrapAlgorithm != null ?
internalKeyWrapAlgorithm.algorithmName() : null;
Key kek;
if (context.materials().getKeyPair() != null) {
kek = context.materials().getKeyPair().getPrivate();
} else {
kek = context.materials().getSymmetricKey();
}
if (kek == null) {
throw new SdkClientException("Key encrypting key not available");
}
Provider securityProvider = context.cryptoProvider();
try {
if (keyWrapAlgo != null) {
Cipher cipher = securityProvider == null ? Cipher
.getInstance(keyWrapAlgo) : Cipher.getInstance(
keyWrapAlgo, securityProvider);
cipher.init(Cipher.UNWRAP_MODE, kek);
return (SecretKey) cipher.unwrap(context.cekSecured(), keyWrapAlgo,
Cipher.SECRET_KEY);
}
// fall back to the Encryption Only (EO) key decrypting method
Cipher cipher;
if (securityProvider != null) {
cipher = Cipher.getInstance(kek.getAlgorithm(),
securityProvider);
} else {
cipher = Cipher.getInstance(kek.getAlgorithm());
}
cipher.init(Cipher.DECRYPT_MODE, kek);
byte[] decryptedSymmetricKeyBytes = cipher.doFinal(context.cekSecured());
return new SecretKeySpec(decryptedSymmetricKeyBytes,
JceEncryptionConstants.SYMMETRIC_KEY_ALGORITHM);
} catch (Exception e) {
throw failure(e, "Unable to decrypt symmetric key from object metadata");
}
}
/**
* Decrypts the secured CEK via KMS; involves network calls.
*
* @return the CEK (in plaintext).
*/
private static SecretKey decryptV1CEKByKMS(KeyWrapperContext context) {
KMSKeyWrapperContext kmsKeyWrapperContext = context.kmsKeyWrapperContext();
if (kmsKeyWrapperContext == null) {
throw new IllegalStateException("Missing KMS parameters");
}
String cmk = context.materials().getCustomerMasterKeyId();
if (null == cmk || cmk.isEmpty()) {
throw new IllegalArgumentException("The CMK must be specified to decrypt KMS protected objects");
}
DecryptRequest kmsreq = new DecryptRequest()
.withEncryptionContext(context.materials().getMaterialsDescription())
.withCiphertextBlob(ByteBuffer.wrap(context.cekSecured()))
.withKeyId(cmk);
DecryptResult result = kmsKeyWrapperContext.kms().decrypt(kmsreq);
return new SecretKeySpec(copyAllBytesFrom(result.getPlaintext()),
context.contentCryptoScheme().getKeyGeneratorAlgorithm());
}
/**
* @return a non-null content crypto material.
*/
static ContentCryptoMaterial fromObjectMetadata(
Map metadata,
EncryptionMaterialsAccessor kekMaterialAccessor,
CryptoConfigurationV2 cryptoConfiguration,
boolean keyWrapExpected,
AWSKMS kms) {
return fromObjectMetadata0(metadata,
kekMaterialAccessor,
cryptoConfiguration,
null,
NONE,
keyWrapExpected,
kms);
}
/**
* Factory method to return the content crypto material from the S3 object
* meta data, using the specified key encrypting key material accessor and
* an optional security provider.
*
* @return a non-null content crypto material.
*/
static ContentCryptoMaterial fromObjectMetadata(
Map metadata,
EncryptionMaterialsAccessor kekMaterialAccessor,
CryptoConfigurationV2 cryptoConfiguration,
long[] range,
ExtraMaterialsDescription extra,
boolean keyWrapExpected,
AWSKMS kms) {
return fromObjectMetadata0(metadata,
kekMaterialAccessor,
cryptoConfiguration,
range,
extra,
keyWrapExpected,
kms);
}
/**
* @return a non-null content crypto material.
*/
private static ContentCryptoMaterial fromObjectMetadata0(Map userMeta,
EncryptionMaterialsAccessor kekMaterialAccessor,
CryptoConfigurationV2 cryptoConfiguration, long[] range,
ExtraMaterialsDescription extra, boolean keyWrapExpected,
AWSKMS kms) {
// CEK and IV
String b64key = userMeta.get(Headers.CRYPTO_KEY_V2);
if (b64key == null) {
b64key = userMeta.get(Headers.CRYPTO_KEY);
if (b64key == null)
throw new SdkClientException(
"Content encrypting key not found.");
}
byte[] cekWrapped = Base64.decode(b64key);
byte[] iv = Base64.decode(userMeta.get(Headers.CRYPTO_IV));
if (cekWrapped == null || iv == null) {
throw new SdkClientException(
"Content encrypting key or IV not found.");
}
// Material description
String matdescStr = userMeta.get(Headers.MATERIALS_DESCRIPTION);
final String keyWrapAlgo = userMeta.get(Headers.CRYPTO_KEYWRAP_ALGORITHM);
final Map coreMatDesc = matdescFromJson(matdescStr);
InternalKeyWrapAlgorithm internalKeyWrapAlgorithm = InternalKeyWrapAlgorithm.fromAlgorithmName(keyWrapAlgo);
validateKeyWrapAlgorithmForDecrypt(internalKeyWrapAlgorithm, keyWrapExpected, cryptoConfiguration.getCryptoMode());
final boolean isKMS = internalKeyWrapAlgorithm != null && internalKeyWrapAlgorithm.isKMS();
final Map mergedMatDesc = isKMS || extra == null ? coreMatDesc : extra.mergeInto(coreMatDesc);
final EncryptionMaterials materials;
if (isKMS) {
materials = (kekMaterialAccessor instanceof EncryptionMaterialsProvider) ?
((EncryptionMaterialsProvider) kekMaterialAccessor).getEncryptionMaterials() : null;
} else {
materials = kekMaterialAccessor.getEncryptionMaterials(mergedMatDesc);
}
validateMaterialsForDecrypt(materials, mergedMatDesc, cryptoConfiguration.getCryptoMode(), internalKeyWrapAlgorithm);
String cekAlgo = userMeta.get(CRYPTO_CEK_ALGORITHM);
boolean isRangeGet = range != null;
// The content crypto scheme may vary depending on whether
// it is a range get operation
ContentCryptoScheme contentCryptoScheme =
ContentCryptoScheme.fromCEKAlgo(cekAlgo, isRangeGet);
if (isRangeGet) {
assertCryptoSchemeAllowedForRangeGet(contentCryptoScheme,cryptoConfiguration.getCryptoMode(),
cryptoConfiguration.getRangeGetMode());
// Adjust the IV as needed
iv = contentCryptoScheme.adjustIV(iv, range[0]);
} else {
// Validate the tag length supported
int tagLenExpected = contentCryptoScheme.getTagLengthInBits();
if (tagLenExpected > 0) {
String s = userMeta.get(Headers.CRYPTO_TAG_LENGTH);
int tagLenActual = Integer.parseInt(s);
if (tagLenExpected != tagLenActual) {
throw new SdkClientException("Unsupported tag length: "
+ tagLenActual + ", expected: " + tagLenExpected);
}
}
}
SecretKey cek = decryptCEK(
KeyWrapperContext.builder()
.cekSecured(cekWrapped)
.internalKeyWrapAlgorithm(internalKeyWrapAlgorithm)
.materials(materials)
.cryptoProvider(cryptoConfiguration.getCryptoProvider())
.secureRandom(cryptoConfiguration.getSecureRandom())
.contentCryptoScheme(contentCryptoScheme)
.kmsKeyWrapperContext(KMSKeyWrapperContext.builder()
.kms(kms)
.kmsMaterialsDescription(mergedMatDesc)
.build())
.build());
Provider securityProvider = cryptoConfiguration.getCryptoProvider();
boolean alwaysUseSecurityProvider = cryptoConfiguration.getAlwaysUseCryptoProvider();
return new ContentCryptoMaterial(mergedMatDesc, cekWrapped, internalKeyWrapAlgorithm,
contentCryptoScheme.createCipherLite(cek, iv, Cipher.DECRYPT_MODE, securityProvider,
alwaysUseSecurityProvider));
}
/**
* @return a non-null content crypto material.
*/
static ContentCryptoMaterial fromInstructionFile(Map instFile,
EncryptionMaterialsAccessor kekMaterialAccessor,
CryptoConfigurationV2 cryptoConfiguration, boolean keyWrapExpected,
AWSKMS kms) {
return fromInstructionFile0(instFile, kekMaterialAccessor, cryptoConfiguration, null, NONE,
keyWrapExpected, kms);
}
/**
* Factory method to return the content crypto material from the S3
* instruction file, using the specified key encrypting key material
* accessor and an optional security provider.
*
* @return a non-null content crypto material.
*/
static ContentCryptoMaterial fromInstructionFile(
Map instFile,
EncryptionMaterialsAccessor kekMaterialAccessor,
CryptoConfigurationV2 cryptoConfiguration,
long[] range,
ExtraMaterialsDescription extra,
boolean keyWrapExpected,
AWSKMS kms) {
return fromInstructionFile0(instFile, kekMaterialAccessor,
cryptoConfiguration, range, extra,
keyWrapExpected, kms);
}
/**
* @return a non-null content crypto material.
*/
private static ContentCryptoMaterial fromInstructionFile0(
Map instFile,
EncryptionMaterialsAccessor kekMaterialAccessor,
CryptoConfigurationV2 cryptoConfiguration,
long[] range,
ExtraMaterialsDescription extra,
boolean keyWrapExpected,
AWSKMS kms) {
// CEK and IV
String b64key = instFile.get(Headers.CRYPTO_KEY_V2);
if (b64key == null) {
b64key = instFile.get(Headers.CRYPTO_KEY);
if (b64key == null)
throw new SdkClientException(
"Content encrypting key not found.");
}
byte[] cekWrapped = Base64.decode(b64key);
byte[] iv = Base64.decode(instFile.get(Headers.CRYPTO_IV));
if (cekWrapped == null || iv == null) {
throw new SdkClientException(
"Necessary encryption info not found in the instruction file "
+ instFile);
}
final String keyWrapAlgo = instFile.get(Headers.CRYPTO_KEYWRAP_ALGORITHM);
InternalKeyWrapAlgorithm internalKeyWrapAlgorithm = InternalKeyWrapAlgorithm.fromAlgorithmName(keyWrapAlgo);
validateKeyWrapAlgorithmForDecrypt(internalKeyWrapAlgorithm, keyWrapExpected, cryptoConfiguration.getCryptoMode());
final boolean isKMS = internalKeyWrapAlgorithm != null && internalKeyWrapAlgorithm.isKMS();
String matdescStr = instFile.get(Headers.MATERIALS_DESCRIPTION);
final Map coreMatDesc = matdescFromJson(matdescStr);
final Map mergedMatDesc = extra == null || isKMS ? coreMatDesc : extra.mergeInto(coreMatDesc);
EncryptionMaterials materials;
if (isKMS) {
materials = (kekMaterialAccessor instanceof EncryptionMaterialsProvider) ?
((EncryptionMaterialsProvider) kekMaterialAccessor).getEncryptionMaterials() : null;
} else {
materials = kekMaterialAccessor.getEncryptionMaterials(mergedMatDesc);
}
validateMaterialsForDecrypt(materials, mergedMatDesc, cryptoConfiguration.getCryptoMode(), internalKeyWrapAlgorithm);
// CEK algorithm
final String cekAlgo = instFile.get(CRYPTO_CEK_ALGORITHM);
final boolean isRangeGet = range != null;
// The content crypto scheme may vary depending on whether
// it is a range get operation
ContentCryptoScheme contentCryptoScheme = ContentCryptoScheme.fromCEKAlgo(cekAlgo, isRangeGet);
if (isRangeGet) {
assertCryptoSchemeAllowedForRangeGet(contentCryptoScheme, cryptoConfiguration.getCryptoMode(),
cryptoConfiguration.getRangeGetMode());
// Adjust the IV as needed
iv = contentCryptoScheme.adjustIV(iv, range[0]);
} else {
// Validate the tag length supported
int tagLenExpected = contentCryptoScheme.getTagLengthInBits();
if (tagLenExpected > 0) {
String s = instFile.get(Headers.CRYPTO_TAG_LENGTH);
int tagLenActual = Integer.parseInt(s);
if (tagLenExpected != tagLenActual) {
throw new SdkClientException("Unsupported tag length: "
+ tagLenActual + ", expected: " + tagLenExpected);
}
}
}
SecretKey cek = decryptCEK(
KeyWrapperContext.builder()
.cekSecured(cekWrapped)
.internalKeyWrapAlgorithm(internalKeyWrapAlgorithm)
.materials(materials)
.cryptoProvider(cryptoConfiguration.getCryptoProvider())
.secureRandom(cryptoConfiguration.getSecureRandom())
.contentCryptoScheme(contentCryptoScheme)
.kmsKeyWrapperContext(KMSKeyWrapperContext.builder()
.kms(kms)
.kmsMaterialsDescription(mergedMatDesc)
.build())
.build());
return new ContentCryptoMaterial(mergedMatDesc, cekWrapped, internalKeyWrapAlgorithm,
contentCryptoScheme.createCipherLite(cek,
iv,
Cipher.DECRYPT_MODE,
cryptoConfiguration.getCryptoProvider(),
cryptoConfiguration.getAlwaysUseCryptoProvider()));
}
/**
* Parses instruction data retrieved from S3 and returns a JSON string
* representing the instruction. Made for testing purposes.
*/
static String parseInstructionFile(S3Object instructionFile) {
try {
return convertStreamToString(instructionFile.getObjectContent());
} catch (Exception e) {
throw failure(e, "Error parsing JSON instruction file");
}
}
/**
* Converts the contents of an input stream to a String
*/
private static String convertStreamToString(InputStream inputStream)
throws IOException {
if (inputStream == null) {
return "";
} else {
StringBuilder stringBuilder = new StringBuilder();
String line;
try {
BufferedReader reader =
new BufferedReader(new InputStreamReader(inputStream,
StringUtils.UTF8));
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
} finally {
inputStream.close();
}
return stringBuilder.toString();
}
}
/**
* Return the cipher lite used for content encryption/decryption purposes.
*/
CipherLite getCipherLite() {
return cipherLite;
}
/**
* Returns the description of the kek materials that were used to encrypt
* the cek.
*/
Map getKEKMaterialsDescription() {
return this.kekMaterialsDescription;
}
/**
* Returns an array of bytes representing the encrypted envelope symmetric
* key.
*
* @return an array of bytes representing the encrypted envelope symmetric
* key.
*/
byte[] getEncryptedCEK() {
return this.encryptedCEK.clone();
}
/**
* Recreates a new content crypto material from the current material given a
* new KEK encryption materials. The purpose is to re-encrypt the CEK under
* the new KEK.
*
* Note network calls are involved if the CEK has been or is to be protected
* by KMS.
*
* @param accessor
* used to retrieve the original KEK given the corresponding
* material description
* @throws SecurityException
* if the old and new material description are the same; or if
* the old and new KEK are the same
*/
ContentCryptoMaterial recreate(EncryptionMaterialsAccessor accessor,
CryptoConfigurationV2 config,
String keyWrapAlgoFromMetadata, AWSKMS kms,
PutInstructionFileRequest req) {
EncryptionMaterials newKEK = getNewEncryptionMaterials(req, accessor);
if (!InternalKeyWrapAlgorithm.KMS.equals(keyWrappingAlgorithm) &&
newKEK.getMaterialsDescription().equals(kekMaterialsDescription)) {
throw new SecurityException(
"Material description of the new KEK must differ from the current one");
}
EncryptionMaterials origKEK;
if (InternalKeyWrapAlgorithm.KMS.equals(keyWrappingAlgorithm)) {
throw new SdkClientException("Recreating KMS encrypted CEK is not supported.");
} else {
origKEK = accessor.getEncryptionMaterials(kekMaterialsDescription);
}
validateKeyWrapAlgorithmForDecrypt(keyWrappingAlgorithm, config.getCryptoMode());
InternalKeyWrapAlgorithm originalKeyWrapAlgo =
InternalKeyWrapAlgorithm.fromAlgorithmName(keyWrapAlgoFromMetadata);
SecretKey cek = decryptCEK(KeyWrapperContext.builder()
.cekSecured(encryptedCEK)
.internalKeyWrapAlgorithm(originalKeyWrapAlgo)
.materials(origKEK)
.cryptoProvider(config.getCryptoProvider())
.secureRandom(config.getSecureRandom())
.contentCryptoScheme(getContentCryptoScheme())
.kmsKeyWrapperContext(KMSKeyWrapperContext.builder()
.kms(kms)
.build())
.build());
ContentCryptoMaterial output = create(cek, cipherLite.getIV(), newKEK, getContentCryptoScheme(), config, kms, req);
if (Arrays.equals(output.encryptedCEK, encryptedCEK)) {
throw new SecurityException("The new KEK must differ from the original");
}
return output;
}
private EncryptionMaterials getNewEncryptionMaterials(PutInstructionFileRequest req,
EncryptionMaterialsAccessor accessor) {
EncryptionMaterials newKEK = req.getEncryptionMaterials();
if (newKEK == null) {
Map materialsDescription = req.getMaterialsDescription();
newKEK = accessor.getEncryptionMaterials(materialsDescription);
}
if (newKEK == null) {
throw new SdkClientException("No material available with the description " + req.getMaterialsDescription()
+ " from the encryption material provider");
}
return newKEK;
}
static ContentCryptoMaterial create(SecretKey cek,
byte[] iv,
EncryptionMaterials kekMaterials,
ContentCryptoScheme contentCryptoScheme,
CryptoConfigurationV2 config,
AWSKMS kms,
AmazonWebServiceRequest req) {
KeyWrapperContext keyWrapperContext = createEncryptionKeyWrapperContext(kekMaterials, contentCryptoScheme, config, kms, req);
SecuredCEK cekSecured = encryptCEK(cek, keyWrapperContext);
return wrap(cek, iv, contentCryptoScheme, config.getCryptoProvider(), config.getAlwaysUseCryptoProvider(),
cekSecured);
}
private static KeyWrapperContext createEncryptionKeyWrapperContext(EncryptionMaterials materials,
ContentCryptoScheme cryptoScheme,
CryptoConfigurationV2 config,
AWSKMS kms,
AmazonWebServiceRequest req) {
CryptoKeyWrapAlgorithm keyWrapAlgorithm = KeyWrapAlgorithmResolver.getDefaultKeyWrapAlgorithm(materials);
InternalKeyWrapAlgorithm internalKeyWrapAlgorithm = InternalKeyWrapAlgorithm.fromExternal(keyWrapAlgorithm);
if (materials.isKMSEnabled()) {
Map matdesc = KMSMaterialsHandler.createKMSContextMaterialsDescription(
KMSMaterialsHandler.mergeMaterialsDescription((KMSEncryptionMaterials) materials, req),
cryptoScheme.getCipherAlgorithm());
KMSKeyWrapperContext kmsKeyWrapperContext = KMSKeyWrapperContext.builder()
.kms(kms)
.kmsMaterialsDescription(matdesc)
.originalRequest(req)
.build();
return KeyWrapperContext.builder()
.cryptoProvider(config.getCryptoProvider())
.secureRandom(config.getSecureRandom())
.materials(materials)
.internalKeyWrapAlgorithm(internalKeyWrapAlgorithm)
.kmsKeyWrapperContext(kmsKeyWrapperContext)
.contentCryptoScheme(cryptoScheme)
.build();
} else {
return KeyWrapperContext.builder()
.cryptoProvider(config.getCryptoProvider())
.secureRandom(config.getSecureRandom())
.materials(materials)
.internalKeyWrapAlgorithm(internalKeyWrapAlgorithm)
.contentCryptoScheme(cryptoScheme)
.build();
}
}
/**
* Returns a new instance of ContentCryptoMaterial
by wrapping
* the input parameters, including the already secured CEK. No network calls
* are involved.
*/
static ContentCryptoMaterial wrap(SecretKey cek,
byte[] iv,
ContentCryptoScheme contentCryptoScheme,
Provider provider,
boolean alwaysUseProvider,
SecuredCEK cekSecured) {
return new ContentCryptoMaterial(
cekSecured.getMaterialDescription(),
cekSecured.getEncrypted(),
cekSecured.getKeyWrapAlgorithm(),
contentCryptoScheme.createCipherLite(cek, iv, Cipher.ENCRYPT_MODE, provider, alwaysUseProvider));
}
private static SecuredCEK encryptCEK(SecretKey cek,
KeyWrapperContext context) {
EncryptionMaterials materials = context.materials();
validateKeyWrapAlgorithmForEncrypt(materials, context.internalKeyWrapAlgorithm());
Key kek = getEncryptionKeyFrom(materials);
final Map matdesc = materials.isKMSEnabled() ?
context.kmsKeyWrapperContext().kmsMaterialsDescription() :
materials.getMaterialsDescription();
KeyWrapper keyWrapper = KeyWrapperFactory.defaultInstance().createKeyWrapper(context);
return new SecuredCEK(keyWrapper.wrapCek(cek.getEncoded(), kek), context.internalKeyWrapAlgorithm(), matdesc);
}
private static Key getEncryptionKeyFrom(EncryptionMaterials materials) {
if (materials.isKMSEnabled()) {
return null;
}
return materials.getKeyPair() != null ? materials.getKeyPair().getPublic() : materials.getSymmetricKey();
}
private static Key getDecryptionKeyFrom(EncryptionMaterials materials) {
if (materials.isKMSEnabled()) {
return null;
}
return materials.getKeyPair() != null ? materials.getKeyPair().getPrivate() : materials.getSymmetricKey();
}
private static void validateKeyWrapAlgorithmForEncrypt(EncryptionMaterials materials,
InternalKeyWrapAlgorithm keyWrapAlgorithm) {
if (materials.isKMSEnabled()) {
validateKMSKeyWrapAlgorithmForEncrypt(materials, keyWrapAlgorithm);
return;
}
if (materials.getKeyPair() != null && !keyWrapAlgorithm.isAsymmetric()) {
throw new IllegalStateException(
String.format("Encryption materials with asymmetric keys are not consistent with selected " +
"key wrap algorithm %s.", keyWrapAlgorithm));
}
if (materials.getSymmetricKey() != null && !keyWrapAlgorithm.isSymmetric()) {
throw new IllegalStateException(
String.format("Encryption materials with a symmetric key are not consistent with selected " +
"key wrap algorithm %s.", keyWrapAlgorithm));
}
return;
}
private static void validateKMSKeyWrapAlgorithmForEncrypt(EncryptionMaterials materials,
InternalKeyWrapAlgorithm keyWrapAlgorithm) {
if (!InternalKeyWrapAlgorithm.KMS.equals(keyWrapAlgorithm)) {
throw new IllegalStateException(
String.format("KMS enabled encryption materials are not consistent with selected " +
"key wrap algorithm %s.", keyWrapAlgorithm));
}
return;
}
private static void validateKeyWrapAlgorithmForDecrypt(InternalKeyWrapAlgorithm keyWrapAlgo, CryptoMode cryptoMode) {
validateKeyWrapAlgorithmForDecrypt(keyWrapAlgo, false, cryptoMode);
}
private static void validateKeyWrapAlgorithmForDecrypt(InternalKeyWrapAlgorithm keyWrapAlgo,
boolean keyWrapExpected, CryptoMode cryptoMode) {
if (CryptoMode.StrictAuthenticatedEncryption.equals(cryptoMode)) {
if (keyWrapAlgo == null) {
throw new KeyWrapException("No key wrap algorithm detected. Use crypto mode " +
CryptoMode.AuthenticatedEncryption + " to decrypt object.");
}
else if (keyWrapAlgo.isV1Algorithm()) {
throw new KeyWrapException("Detected key wrap algorithm used with previous version of client. Use " +
"crypto mode " + CryptoMode.AuthenticatedEncryption + " to " +
"decrypt object.");
}
} else {
if (keyWrapExpected && keyWrapAlgo == null) {
throw new KeyWrapException("Key wrap expected, but no key wrap algorithm was found.");
}
}
}
private static void validateMaterialsForDecrypt(EncryptionMaterials materials, Map mergedMatDesc,
CryptoMode cryptoMode, InternalKeyWrapAlgorithm keyWrapAlgorithm) {
if (materials == null) {
throw new SdkClientException("Unable to retrieve the client encryption materials");
}
if (keyWrapAlgorithm == null || !keyWrapAlgorithm.isKMS()) {
return;
}
if (!keyWrapAlgorithm.isV1Algorithm()
&& KMSMaterialsHandler.isValidV2Description(materials.getMaterialsDescription(), mergedMatDesc)) {
return;
}
boolean isValidV1MaterialsDescription =
KMSMaterialsHandler.isValidV1Description(materials.getMaterialsDescription(), mergedMatDesc);
if (keyWrapAlgorithm.isV1Algorithm() && isValidV1MaterialsDescription) {
if (CryptoMode.AuthenticatedEncryption.equals(cryptoMode)) {
return;
} else {
throw new IllegalStateException("A previous version of the client may have been used to encrypt " +
"key via KMS. Use crypto mode " +
CryptoMode.AuthenticatedEncryption + " to decrypt object.");
}
}
throw new IllegalStateException("Provided encryption materials do not match information" +
" retrieved from the encrypted object");
}
private static void assertCryptoSchemeAllowedForRangeGet(ContentCryptoScheme scheme,
CryptoMode cryptoMode,
CryptoRangeGetMode rangeGetMode) {
if (!rangeGetMode.permitsCipherAlgorithm(cryptoMode, scheme.getCipherAlgorithm())) {
if (CryptoRangeGetMode.DISABLED.equals(rangeGetMode)) {
throw new SecurityException("Unable to perform range get request: Range get support has been disabled. " +
"See https://docs.aws.amazon.com/general/latest/gr/aws_sdk_cryptography.html");
} else {
throw new SecurityException(
"Range get support is not enabled for this content encryption type. Use " +
CryptoMode.AuthenticatedEncryption + " instead. See " +
"https://docs.aws.amazon.com/general/latest/gr/aws_sdk_cryptography.html");
}
}
}
}