Skip to content

Commit

Permalink
Define the MasterKeyProvider Keyring. (#149)
Browse files Browse the repository at this point in the history
  • Loading branch information
WesleyRosenblum authored Dec 18, 2019
1 parent 77f46c2 commit a6893b6
Show file tree
Hide file tree
Showing 11 changed files with 530 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class AesGcmJceKeyCipher extends JceKeyCipher {
private static final int SPEC_LENGTH = Integer.BYTES + Integer.BYTES + NONCE_LENGTH;

AesGcmJceKeyCipher(SecretKey key) {
super(key, key);
super(key, key, true);
}

private static byte[] specToBytes(final GCMParameterSpec spec) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public abstract class JceKeyCipher {
private final Key wrappingKey;
private final Key unwrappingKey;
private static final Charset KEY_NAME_ENCODING = StandardCharsets.UTF_8;
private final boolean encryptionContextSigned;

/**
* Returns a new instance of a JceKeyCipher based on the
Expand All @@ -60,16 +61,26 @@ public static JceKeyCipher rsa(PublicKey wrappingKey, PrivateKey unwrappingKey,
return new RsaJceKeyCipher(wrappingKey, unwrappingKey, transformation);
}

JceKeyCipher(Key wrappingKey, Key unwrappingKey) {
JceKeyCipher(Key wrappingKey, Key unwrappingKey, boolean encryptionContextSigned) {
this.wrappingKey = wrappingKey;
this.unwrappingKey = unwrappingKey;
this.encryptionContextSigned = encryptionContextSigned;
}

abstract WrappingData buildWrappingCipher(Key key, Map<String, String> encryptionContext) throws GeneralSecurityException;

abstract Cipher buildUnwrappingCipher(Key key, byte[] extraInfo, int offset,
Map<String, String> encryptionContext) throws GeneralSecurityException;

/**
* Returns true if this key cipher supports signing and verification
* of the encryption context.
*
* @return True if encryption context signing/verification is supported.
*/
public boolean isEncryptionContextSigned() {
return encryptionContextSigned;
}

/**
* Encrypts the given key, incorporating the given keyName and encryptionContext.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class RsaJceKeyCipher extends JceKeyCipher {
private final String transformation_;

RsaJceKeyCipher(PublicKey wrappingKey, PrivateKey unwrappingKey, String transformation) {
super(wrappingKey, unwrappingKey);
super(wrappingKey, unwrappingKey, false);

final Matcher matcher = SUPPORTED_TRANSFORMATIONS.matcher(transformation);
if (matcher.matches()) {
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ public String getKeyId() {
return keyId_;
}

/**
* Returns true if the underlying key cipher supports signing and
* verification of the encryption context.
*
* @return True if encryption context signing/verification is supported.
*/
public boolean isEncryptionContextSigned() {
return jceKeyCipher_.isEncryptionContextSigned();
}

@Override
public DataKey<JceMasterKey> generateDataKey(final CryptoAlgorithm algorithm,
final Map<String, String> encryptionContext) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* Copyright 2019 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.keyrings;

import com.amazonaws.encryptionsdk.DataKey;
import com.amazonaws.encryptionsdk.EncryptedDataKey;
import com.amazonaws.encryptionsdk.MasterKey;
import com.amazonaws.encryptionsdk.MasterKeyProvider;
import com.amazonaws.encryptionsdk.MasterKeyRequest;
import com.amazonaws.encryptionsdk.exception.AwsCryptoException;
import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException;
import com.amazonaws.encryptionsdk.jce.JceMasterKey;
import com.amazonaws.encryptionsdk.kms.KmsMasterKey;

import java.util.ArrayList;
import java.util.List;

import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.DECRYPTED_DATA_KEY;
import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.ENCRYPTED_DATA_KEY;
import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT;
import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT;
import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang3.ArrayUtils.EMPTY_BYTE_ARRAY;

/**
* A keyring which wraps a legacy MasterKeyProvider to
* facilitate transition to keyrings.
*/
class MasterKeyProviderKeyring<K extends MasterKey<K>> implements Keyring {

private final MasterKeyProvider<K> masterKeyProvider;

MasterKeyProviderKeyring(MasterKeyProvider<K> masterKeyProvider) {
requireNonNull(masterKeyProvider, "masterKeyProvider is required");

this.masterKeyProvider = masterKeyProvider;
}

@Override
public void onEncrypt(EncryptionMaterials encryptionMaterials) {
requireNonNull(encryptionMaterials, "encryptionMaterials are required");

final List<K> masterKeys = masterKeyProvider.getMasterKeysForEncryption(MasterKeyRequest.newBuilder()
.setEncryptionContext(encryptionMaterials.getEncryptionContext()).build());

if (masterKeys == null || masterKeys.isEmpty()) {
throw new AwsCryptoException("No master keys available from the master key provider.");
}

final K primaryMasterKey = masterKeys.get(0);
final List<K> masterKeysToEncryptWith = new ArrayList<>(masterKeys);

if (!encryptionMaterials.hasPlaintextDataKey()) {
final DataKey<K> dataKey = primaryMasterKey.generateDataKey(
encryptionMaterials.getAlgorithmSuite(), encryptionMaterials.getEncryptionContext());
encryptionMaterials.setPlaintextDataKey(dataKey.getKey(), new KeyringTraceEntry(
primaryMasterKey.getProviderId(), primaryMasterKey.getKeyId(), KeyringTraceFlag.GENERATED_DATA_KEY));
encryptionMaterials.addEncryptedDataKey(dataKey, encryptTraceEntry(primaryMasterKey));
// The primary master key has already been used for encryption, so remove it from the list to encrypt with
masterKeysToEncryptWith.remove(primaryMasterKey);
}

final DataKey<K> dataKey = new DataKey<>(encryptionMaterials.getPlaintextDataKey(), EMPTY_BYTE_ARRAY,
EMPTY_BYTE_ARRAY, primaryMasterKey);

for (K masterKey : masterKeysToEncryptWith) {
final EncryptedDataKey encryptedDataKey = masterKey.encryptDataKey(encryptionMaterials.getAlgorithmSuite(),
encryptionMaterials.getEncryptionContext(), dataKey);
encryptionMaterials.addEncryptedDataKey(encryptedDataKey, encryptTraceEntry(masterKey));
}
}

@Override
public void onDecrypt(DecryptionMaterials decryptionMaterials, List<? extends EncryptedDataKey> encryptedDataKeys) {
requireNonNull(decryptionMaterials, "decryptionMaterials are required");
requireNonNull(encryptedDataKeys, "encryptedDataKeys are required");

if (decryptionMaterials.hasPlaintextDataKey()) {
return;
}

final DataKey<K> dataKey;
try {
dataKey = masterKeyProvider.decryptDataKey(decryptionMaterials.getAlgorithmSuite(), encryptedDataKeys,
decryptionMaterials.getEncryptionContext());
} catch (CannotUnwrapDataKeyException e) {
return;
}

decryptionMaterials.setPlaintextDataKey(dataKey.getKey(), decryptTraceEntry(dataKey.getMasterKey()));
}

private boolean signedEncryptionContext(MasterKey<K> masterKey) {
if (masterKey instanceof KmsMasterKey) {
return true;
}

if (masterKey instanceof JceMasterKey) {
return ((JceMasterKey) masterKey).isEncryptionContextSigned();
}

return false;
}

private KeyringTraceEntry encryptTraceEntry(MasterKey<K> masterKey) {
final List<KeyringTraceFlag> flags = new ArrayList<>();
flags.add(ENCRYPTED_DATA_KEY);

if (signedEncryptionContext(masterKey)) {
flags.add(SIGNED_ENCRYPTION_CONTEXT);
}

return new KeyringTraceEntry(masterKey.getProviderId(), masterKey.getKeyId(), flags.toArray(new KeyringTraceFlag[]{}));
}

private KeyringTraceEntry decryptTraceEntry(MasterKey<K> masterKey) {
final List<KeyringTraceFlag> flags = new ArrayList<>();
flags.add(DECRYPTED_DATA_KEY);

if (signedEncryptionContext(masterKey)) {
flags.add(VERIFIED_ENCRYPTION_CONTEXT);
}

return new KeyringTraceEntry(masterKey.getProviderId(), masterKey.getKeyId(), flags.toArray(new KeyringTraceFlag[]{}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,4 @@ boolean validToDecrypt(EncryptedDataKey encryptedDataKey) {

return true;
}

@Override
KeyringTraceEntry traceOnEncrypt() {
return new KeyringTraceEntry(keyNamespace, keyName,
KeyringTraceFlag.ENCRYPTED_DATA_KEY,
KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT);
}

@Override
KeyringTraceEntry traceOnDecrypt() {
return new KeyringTraceEntry(keyNamespace, keyName,
KeyringTraceFlag.DECRYPTED_DATA_KEY,
KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT);
}
}
42 changes: 25 additions & 17 deletions src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
import java.util.logging.Logger;

import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING;
import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.DECRYPTED_DATA_KEY;
import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.ENCRYPTED_DATA_KEY;
import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.GENERATED_DATA_KEY;
import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT;
import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT;
import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang3.Validate.notBlank;

Expand Down Expand Up @@ -57,20 +62,6 @@ abstract class RawKeyring implements Keyring {
*/
abstract boolean validToDecrypt(EncryptedDataKey encryptedDataKey);

/**
* Gets the trace entry to add the the keyring trace upon successful encryption.
*
* @return The keyring trace entry.
*/
abstract KeyringTraceEntry traceOnEncrypt();

/**
* Gets the trace entry to add to the keyring trace upon successful decryption.
*
* @return The keyring trace entry.
*/
abstract KeyringTraceEntry traceOnDecrypt();

@Override
public void onEncrypt(EncryptionMaterials encryptionMaterials) {
requireNonNull(encryptionMaterials, "encryptionMaterials are required");
Expand All @@ -82,7 +73,8 @@ public void onEncrypt(EncryptionMaterials encryptionMaterials) {
final EncryptedDataKey encryptedDataKey = jceKeyCipher.encryptKey(
encryptionMaterials.getPlaintextDataKey().getEncoded(),
keyName, keyNamespace, encryptionMaterials.getEncryptionContext());
encryptionMaterials.addEncryptedDataKey(encryptedDataKey, traceOnEncrypt());
encryptionMaterials.addEncryptedDataKey(encryptedDataKey,
new KeyringTraceEntry(keyNamespace, keyName, encryptTraceFlags()));
}

@Override
Expand All @@ -101,7 +93,7 @@ public void onDecrypt(DecryptionMaterials decryptionMaterials, List<? extends En
encryptedDataKey, keyName, decryptionMaterials.getEncryptionContext());
decryptionMaterials.setPlaintextDataKey(
new SecretKeySpec(decryptedKey, decryptionMaterials.getAlgorithmSuite().getDataKeyAlgo()),
traceOnDecrypt());
new KeyringTraceEntry(keyNamespace, keyName, decryptTraceFlags()));
return;
} catch (Exception e) {
LOGGER.info("Could not decrypt key due to: " + e.getMessage());
Expand All @@ -117,6 +109,22 @@ private void generateDataKey(EncryptionMaterials encryptionMaterials) {
Utils.getSecureRandom().nextBytes(rawKey);
final SecretKey key = new SecretKeySpec(rawKey, encryptionMaterials.getAlgorithmSuite().getDataKeyAlgo());

encryptionMaterials.setPlaintextDataKey(key, new KeyringTraceEntry(keyNamespace, keyName, KeyringTraceFlag.GENERATED_DATA_KEY));
encryptionMaterials.setPlaintextDataKey(key, new KeyringTraceEntry(keyNamespace, keyName, GENERATED_DATA_KEY));
}

private KeyringTraceFlag[] encryptTraceFlags() {
if(jceKeyCipher.isEncryptionContextSigned()) {
return new KeyringTraceFlag[]{ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT} ;
} else {
return new KeyringTraceFlag[]{ENCRYPTED_DATA_KEY};
}
}

private KeyringTraceFlag[] decryptTraceFlags() {
if(jceKeyCipher.isEncryptionContextSigned()) {
return new KeyringTraceFlag[]{DECRYPTED_DATA_KEY, VERIFIED_ENCRYPTION_CONTEXT} ;
} else {
return new KeyringTraceFlag[]{DECRYPTED_DATA_KEY};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,4 @@ boolean validToDecrypt(EncryptedDataKey encryptedDataKey) {

return true;
}

@Override
KeyringTraceEntry traceOnEncrypt() {
return new KeyringTraceEntry(keyNamespace, keyName, KeyringTraceFlag.ENCRYPTED_DATA_KEY);
}

@Override
KeyringTraceEntry traceOnDecrypt() {
return new KeyringTraceEntry(keyNamespace, keyName, KeyringTraceFlag.DECRYPTED_DATA_KEY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

package com.amazonaws.encryptionsdk.keyrings;

import com.amazonaws.encryptionsdk.MasterKey;
import com.amazonaws.encryptionsdk.MasterKeyProvider;
import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao;
import com.amazonaws.encryptionsdk.kms.KmsClientSupplier;

Expand Down Expand Up @@ -60,6 +62,16 @@ public static Keyring rawRsa(String keyNamespace, String keyName, PublicKey publ
}

/**
* Constructs a {@code Keyring} which wraps a {@code MasterKeyProvider} to facilitate transitioning to keyrings.
*
* @param masterKeyProvider The master key provider.
* @return The {@link Keyring}
*/
public static Keyring masterKeyProvider(MasterKeyProvider<? extends MasterKey> masterKeyProvider) {
return new MasterKeyProviderKeyring<>(masterKeyProvider);
}

/**
* Constructs a {@code Keyring} which interacts with AWS Key Management Service (KMS) to create,
* encrypt, and decrypt data keys using KMS defined Customer Master Keys (CMKs).
*
Expand Down
Loading

0 comments on commit a6893b6

Please sign in to comment.