package com.amazonaws.encryptionsdk.internal;
import com.amazonaws.encryptionsdk.CryptoAlgorithm;
import com.amazonaws.encryptionsdk.DataKey;
import com.amazonaws.encryptionsdk.EncryptedDataKey;
import com.amazonaws.encryptionsdk.MasterKey;
import com.amazonaws.encryptionsdk.exception.AwsCryptoException;
import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Static implementation of the {@link MasterKey} interface that should only used for unit-tests.
*
*
Contains a statically defined asymmetric master key-pair that can be used to encrypt and
* decrypt (randomly generated) symmetric data key.
*
*
*
* @author patye
*/
@NotThreadSafe
public class StaticMasterKey extends MasterKey {
private static final String PROVIDER_ID = "static_provider";
/** Generates random strings that can be used to create data keys. */
private static final SecureRandom SRAND = new SecureRandom();
/** Encryption algorithm for the master key-pair */
private static final String MASTER_KEY_ENCRYPTION_ALGORITHM = "RSA/ECB/PKCS1Padding";
/** Encryption algorithm for the KeyFactory */
private static final String MASTER_KEY_ALGORITHM = "RSA";
/** Encryption algorithm for the randomly generated data key */
private static final String DATA_KEY_ENCRYPTION_ALGORITHM = "AES";
/** The ID of the master key */
@Nonnull private String keyId_;
/**
* The {@link Cipher} object created with the public part of the master-key. It's used to encrypt
* data keys.
*/
@Nonnull private final Cipher masterKeyEncryptionCipher_;
/**
* The {@link Cipher} object created with the private part of the master-key. It's used to decrypt
* encrypted data keys.
*/
@Nonnull private final Cipher masterKeyDecryptionCipher_;
/** Generates random data keys. */
@Nonnull private KeyGenerator keyGenerator_;
/**
* Creates a new object that encrypts the data key with a master key whose id is {@code keyId}.
*
* The value of {@code keyId} does not affect how the data key will be generated or encrypted.
* The {@code keyId} forms part of the header of the encrypted data, and is used to ensure that
* the header cannot be tempered with.
*/
public StaticMasterKey(@Nonnull final String keyId) {
this.keyId_ = Objects.requireNonNull(keyId);
try {
KeyFactory keyFactory = KeyFactory.getInstance(MASTER_KEY_ALGORITHM);
KeySpec publicKeySpec = new X509EncodedKeySpec(publicKey_v1);
PublicKey pubKey = keyFactory.generatePublic(publicKeySpec);
KeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKey_v1);
PrivateKey privKey = keyFactory.generatePrivate(privateKeySpec);
masterKeyEncryptionCipher_ = Cipher.getInstance(MASTER_KEY_ENCRYPTION_ALGORITHM);
masterKeyEncryptionCipher_.init(Cipher.ENCRYPT_MODE, pubKey);
masterKeyDecryptionCipher_ = Cipher.getInstance(MASTER_KEY_ENCRYPTION_ALGORITHM);
masterKeyDecryptionCipher_.init(Cipher.DECRYPT_MODE, privKey);
} catch (GeneralSecurityException ex) {
throw new RuntimeException(ex);
}
}
/**
* Changes the {@link #keyId_}. This method is expected to be used to test that header of an
* encrypted message cannot be tempered with.
*/
public void setKeyId(@Nonnull String keyId) {
this.keyId_ = Objects.requireNonNull(keyId);
}
@Override
public String getProviderId() {
return PROVIDER_ID;
}
@Override
public String getKeyId() {
return keyId_;
}
@Override
public DataKey generateDataKey(
CryptoAlgorithm algorithm, Map encryptionContext) {
try {
this.keyGenerator_ = KeyGenerator.getInstance(DATA_KEY_ENCRYPTION_ALGORITHM);
this.keyGenerator_.init(algorithm.getDataKeyLength() * 8, SRAND);
SecretKey key =
new SecretKeySpec(keyGenerator_.generateKey().getEncoded(), algorithm.getDataKeyAlgo());
byte[] encryptedKey = masterKeyEncryptionCipher_.doFinal(key.getEncoded());
return new DataKey<>(key, encryptedKey, keyId_.getBytes(StandardCharsets.UTF_8), this);
} catch (GeneralSecurityException ex) {
throw new RuntimeException(ex);
}
}
@Override
public DataKey encryptDataKey(
CryptoAlgorithm algorithm, Map encryptionContext, DataKey> dataKey) {
try {
byte[] unencryptedKey = dataKey.getKey().getEncoded();
byte[] encryptedKey = masterKeyEncryptionCipher_.doFinal(unencryptedKey);
SecretKey newKey =
new SecretKeySpec(dataKey.getKey().getEncoded(), algorithm.getDataKeyAlgo());
return new DataKey<>(newKey, encryptedKey, keyId_.getBytes(StandardCharsets.UTF_8), this);
} catch (GeneralSecurityException ex) {
throw new RuntimeException(ex);
}
}
@Override
public DataKey decryptDataKey(
CryptoAlgorithm algorithm,
Collection extends EncryptedDataKey> encryptedDataKeys,
Map encryptionContext)
throws UnsupportedProviderException, AwsCryptoException {
try {
for (EncryptedDataKey edk : encryptedDataKeys) {
if (keyId_.equals(new String(edk.getProviderInformation(), StandardCharsets.UTF_8))) {
byte[] unencryptedDataKey = masterKeyDecryptionCipher_.doFinal(edk.getEncryptedDataKey());
SecretKey key = new SecretKeySpec(unencryptedDataKey, algorithm.getDataKeyAlgo());
return new DataKey<>(key, edk.getEncryptedDataKey(), edk.getProviderInformation(), this);
}
}
} catch (GeneralSecurityException ex) {
throw new RuntimeException(ex);
}
return null;
}
/** Statically configured private key. */
private static final byte[] privateKey_v1 =
Utils.decodeBase64String(
"MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKLpwqjYtYExVilW/Hg0ogWv9xZ+"
+ "THj4IzvISLlPtK8W6KXMcqukfuxdYmndPv8UD1DbdHFYSSistdqoBN32vVQOQnJZyYm45i2TDOV0"
+ "M2DtHtR6aMMlBLGtdPeeaT88nQfI1ORjRDyR1byMwomvmKifZYga6FjLt/sgqfSE9BUnAgMBAAEC"
+ "gYAqnewGL2qLuVRIzDCPYXVg938zqyZmHsNYyDP+BhPGGcASX0FAFW/+dQ9hkjcAk0bOaBo17Fp3"
+ "AXcxE/Lx/bHY+GWZ0wOJfl3aJBVJOpW8J6kwu68BUCmuFtRgbLSFu5+fbey3pKafYSptbX1fAI+z"
+ "hTx+a9B8pnn79ad4ziJ2QQJBAM+YHPGAEbr5qcNkwyy0xZgR/TLlcW2NQUt8HZpmErdX6d328iBC"
+ "SPb8+whXxCXZC3Mr+35IZ1pxxf0go/zGQv0CQQDI5oH0z1CKxoT6ErswNzB0oHxq/wD5mhutyqHa"
+ "mxbG5G3fN7I2IclwaXEA2eutIKxFMQNZYsX5mNYsrveSKivzAkABiujUJpZ7JDXNvObyYxmAyslt"
+ "4mSYYs9UZ0S1DAMhl6amPpqIANYX98NJyZUsjtNV9MK2qoUSF/xXqDFvxG1lAkBhP5Ow2Zn3U1mT"
+ "Y/XQxSZjjjwr3vyt1neHjQsEMwa3iGPXJbLSmVBVZfUZoGOBDsvVQoCIiFOlGuKyBpA45MkZAkAH"
+ "ksUrS9xLrDIUOI2BzMNRsK0bH7KJ+PFxm2SBgJOF9+Uf2A9LIP4IvESZq+ufp6c8YaqgR6Id1vws"
+ "7rUyGoa5");
/** Statically configured public key. */
private static final byte[] publicKey_v1 =
Utils.decodeBase64String(
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCi6cKo2LWBMVYpVvx4NKIFr/cWfkx4+CM7yEi5"
+ "T7SvFuilzHKrpH7sXWJp3T7/FA9Q23RxWEkorLXaqATd9r1UDkJyWcmJuOYtkwzldDNg7R7UemjD"
+ "JQSxrXT3nmk/PJ0HyNTkY0Q8kdW8jMKJr5ion2WIGuhYy7f7IKn0hPQVJwIDAQAB");
}