Skip to content

Defining Keyring interface, RawAesKeyring and RawRsaKeyring. #142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 21, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,11 @@ public DefaultCryptoMaterialsManager(MasterKeyProvider<?> mkp) {
}

return DecryptionMaterials.newBuilder()
.setDataKey(dataKey)
.setTrailingSignatureKey(pubKey)
.build();
.setAlgorithm(request.getAlgorithm())
.setCleartextDataKey(dataKey.getKey())
.setMasterKey(dataKey.getMasterKey())
.setTrailingSignatureKey(pubKey)
.build();
}

private PublicKey deserializeTrailingKeyFromEc(CryptoAlgorithm algo, String pubKey) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@

import com.amazonaws.encryptionsdk.CryptoAlgorithm;
import com.amazonaws.encryptionsdk.CryptoMaterialsManager;
import com.amazonaws.encryptionsdk.DataKey;
import com.amazonaws.encryptionsdk.DefaultCryptoMaterialsManager;
import com.amazonaws.encryptionsdk.MasterKey;
import com.amazonaws.encryptionsdk.MasterKeyProvider;
Expand Down Expand Up @@ -61,7 +60,8 @@ public class DecryptionHandler<K extends MasterKey<K>> implements MessageCryptoH

private CryptoHandler contentCryptoHandler_;

private DataKey<K> dataKey_;
private SecretKey dataKey_;
private K masterKey_;
private SecretKey decryptionKey_;
private CryptoAlgorithm cryptoAlgo_;
private Signature trailingSig_;
Expand Down Expand Up @@ -454,12 +454,13 @@ private void readHeaderFields(final CiphertextHeaders ciphertextHeaders) {

DecryptionMaterials result = materialsManager_.decryptMaterials(request);

dataKey_ = result.getCleartextDataKey();
//noinspection unchecked
dataKey_ = (DataKey<K>)result.getDataKey();
masterKey_ = (K)result.getMasterKey();
PublicKey trailingPublicKey = result.getTrailingSignatureKey();

try {
decryptionKey_ = cryptoAlgo_.getEncryptionKeyFromDataKey(dataKey_.getKey(), ciphertextHeaders);
decryptionKey_ = cryptoAlgo_.getEncryptionKeyFromDataKey(dataKey_, ciphertextHeaders);
} catch (final InvalidKeyException ex) {
throw new AwsCryptoException(ex);
}
Expand Down Expand Up @@ -536,7 +537,7 @@ public CiphertextHeaders getHeaders() {

@Override
public List<K> getMasterKeys() {
return Collections.singletonList(dataKey_.getMasterKey());
return Collections.singletonList(masterKey_);
}

@Override
Expand Down
45 changes: 45 additions & 0 deletions src/main/java/com/amazonaws/encryptionsdk/keyrings/Keyring.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.EncryptedDataKey;
import com.amazonaws.encryptionsdk.model.DecryptionMaterials;
import com.amazonaws.encryptionsdk.model.EncryptionMaterials;

import javax.crypto.SecretKey;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.List;

/**
* Keyrings are responsible for the generation, encryption, and decryption of data keys.
*/
public interface Keyring {

/**
* Generate a data key if not present and encrypt it using any available wrapping key
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phrase "any available wrapping key" isn't particularly meaningful for me there, especially because some keyrings will encrypt with multiple keys.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to be more general

*
* @param encryptionMaterials Materials needed for encryption that the keyring may modify.
*/
void onEncrypt(EncryptionMaterials encryptionMaterials);

/**
* Attempt to decrypt the encrypted data keys
*
* @param decryptionMaterials Materials needed for decryption that the keyring may modify.
* @param encryptedDataKeys List of encrypted data keys.
*/
void onDecrypt(DecryptionMaterials decryptionMaterials, List<? extends EncryptedDataKey> encryptedDataKeys);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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.EncryptedDataKey;
import com.amazonaws.encryptionsdk.internal.JceKeyCipher;
import com.amazonaws.encryptionsdk.internal.Utils;

import javax.crypto.SecretKey;

/**
* A {@code Keyring} which does local AES-GCM encryption
* decryption of data keys using the provided wrapping key.
*
* Instantiate by using the {@code StandardKeyrings.rawAes(...)} factory method.
*/
class RawAesKeyring extends RawKeyring {

RawAesKeyring(String keyNamespace, String keyName, SecretKey wrappingKey) {
super(keyNamespace, keyName, JceKeyCipher.aesGcm(wrappingKey));
}

@Override
boolean validToDecrypt(EncryptedDataKey encryptedDataKey) {

// the key provider ID of the encrypted data key must
// have a value equal to this keyring's key namespace.
if (!keyNamespace.equals(encryptedDataKey.getProviderId())) {
return false;
}

// the key name obtained from the encrypted data key's key provider
// information must have a value equal to this keyring's key name.
if (!Utils.arrayPrefixEquals(encryptedDataKey.getProviderInformation(), keyNameBytes, keyNameBytes.length)) {
return false;
}

return true;
}

@Override
void traceOnEncrypt(KeyringTrace keyringTrace) {
keyringTrace.add(keyNamespace, keyName,
KeyringTraceFlag.ENCRYPTED_DATA_KEY,
KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT);
}

@Override
void traceOnDecrypt(KeyringTrace keyringTrace) {
keyringTrace.add(keyNamespace, keyName,
KeyringTraceFlag.DECRYPTED_DATA_KEY,
KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT);
}
}
136 changes: 136 additions & 0 deletions src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* 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.EncryptedDataKey;
import com.amazonaws.encryptionsdk.internal.JceKeyCipher;
import com.amazonaws.encryptionsdk.internal.Utils;
import com.amazonaws.encryptionsdk.model.DecryptionMaterials;
import com.amazonaws.encryptionsdk.model.EncryptionMaterials;
import com.amazonaws.encryptionsdk.model.KeyBlob;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.logging.Logger;

import static org.apache.commons.lang3.Validate.notBlank;
import static org.apache.commons.lang3.Validate.notNull;

/**
* A keyring supporting local encryption and decryption using either RSA or AES-GCM.
*/
abstract class RawKeyring implements Keyring {

final String keyNamespace;
final String keyName;
final byte[] keyNameBytes;
private final JceKeyCipher jceKeyCipher;
private static final Charset KEY_NAME_ENCODING = StandardCharsets.UTF_8;
private static final Logger LOGGER = Logger.getLogger(RawKeyring.class.getName());

RawKeyring(final String keyNamespace, final String keyName, JceKeyCipher jceKeyCipher) {
notBlank(keyNamespace, "keyNamespace is required");
notBlank(keyName, "keyName is required");
notNull(jceKeyCipher, "jceKeyCipher is required");

this.keyNamespace = keyNamespace;
this.keyName = keyName;
this.keyNameBytes = keyName.getBytes(KEY_NAME_ENCODING);
this.jceKeyCipher = jceKeyCipher;
}

/**
* Returns true if the given encrypted data key may be decrypted with this keyring.
*
* @param encryptedDataKey The encrypted data key.
* @return True if the key may be decrypted, false otherwise.
*/
abstract boolean validToDecrypt(EncryptedDataKey encryptedDataKey);

/**
* Records trace entries for the given keyring upon successful encryption.
*
* @param keyringTrace The keyring trace to record to.
*/
abstract void traceOnEncrypt(KeyringTrace keyringTrace);

/**
* Records trace entries for the given keyring upon successful decryption.
*
* @param keyringTrace The keyring trace to record to.
*/
abstract void traceOnDecrypt(KeyringTrace keyringTrace);

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

if (encryptionMaterials.getCleartextDataKey() == null) {
generateDataKey(encryptionMaterials);
}

final SecretKey cleartextDataKey = encryptionMaterials.getCleartextDataKey();

if (!cleartextDataKey.getAlgorithm().equalsIgnoreCase(encryptionMaterials.getAlgorithm().getDataKeyAlgo())) {
throw new IllegalArgumentException("Incorrect key algorithm. Expected " + cleartextDataKey.getAlgorithm()
+ " but got " + encryptionMaterials.getAlgorithm().getDataKeyAlgo());
}

final EncryptedDataKey encryptedDataKey = jceKeyCipher.encryptKey(
cleartextDataKey.getEncoded(), keyName, keyNamespace, encryptionMaterials.getEncryptionContext());
encryptionMaterials.getEncryptedDataKeys().add(new KeyBlob(encryptedDataKey));

traceOnEncrypt(encryptionMaterials.getKeyringTrace());
}

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

if (decryptionMaterials.getCleartextDataKey() != null) {
return;
}

for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) {
if (validToDecrypt(encryptedDataKey)) {
try {
final byte[] decryptedKey = jceKeyCipher.decryptKey(
encryptedDataKey, keyName, decryptionMaterials.getEncryptionContext());
decryptionMaterials.setCleartextDataKey(
new SecretKeySpec(decryptedKey, decryptionMaterials.getAlgorithm().getDataKeyAlgo()));
traceOnDecrypt(decryptionMaterials.getKeyringTrace());
return;
} catch (Exception e) {
LOGGER.info("Could not decrypt key due to: " + e.getMessage());
}
}
}

LOGGER.warning("Could not decrypt any data keys");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case that onDecrypt gets an empty EDK list the correct behavior is to return. We should not log this warning in that case.

I am also unsure if we want to log a warning here generally. What is the current behavior with the MK/MKP setup?

Copy link
Contributor Author

@WesleyRosenblum WesleyRosenblum Nov 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case that onDecrypt gets an empty EDK list the correct behavior is to return. We should not log this warning in that case.

Good catch

I am also unsure if we want to log a warning here generally. What is the current behavior with the MK/MKP setup?

The Java ESDK basically has no logging anywhere. The current behavior with MK/MKP is to throw a CannotUnwrapDataKeyException if no key can be decrypted, but exception throwing is not specified in the spec so I didn't replicate that.

}

private void generateDataKey(EncryptionMaterials encryptionMaterials) {
final byte[] rawKey = new byte[encryptionMaterials.getAlgorithm().getDataKeyLength()];
Utils.getSecureRandom().nextBytes(rawKey);
final SecretKey key = new SecretKeySpec(rawKey, encryptionMaterials.getAlgorithm().getDataKeyAlgo());

encryptionMaterials.setCleartextDataKey(key);
encryptionMaterials.getKeyringTrace().add(keyNamespace, keyName, KeyringTraceFlag.GENERATED_DATA_KEY);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.EncryptedDataKey;
import com.amazonaws.encryptionsdk.internal.JceKeyCipher;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Arrays;

/**
* A {@link Keyring} which does local RSA encryption and decryption of data keys using the
* provided public and private keys.
*
* Instantiate by using the {@code StandardKeyrings.rawRsa(...)} factory method.
*/
class RawRsaKeyring extends RawKeyring {

RawRsaKeyring(String keyNamespace, String keyName, PublicKey publicKey, PrivateKey privateKey, String transformation) {
super(keyNamespace, keyName, JceKeyCipher.rsa(publicKey, privateKey, transformation));
}

@Override
boolean validToDecrypt(EncryptedDataKey encryptedDataKey) {

// the key provider ID of the encrypted data key must
// have a value equal to this keyring's key namespace.
if (!keyNamespace.equals(encryptedDataKey.getProviderId())) {
return false;
}

// the encrypted data key's key provider information
// must have a value equal to this keyring's key name.
if(!Arrays.equals(encryptedDataKey.getProviderInformation(), keyNameBytes))
{
return false;
}

return true;
}

@Override
void traceOnEncrypt(KeyringTrace keyringTrace) {
keyringTrace.add(keyNamespace, keyName, KeyringTraceFlag.ENCRYPTED_DATA_KEY);
}

@Override
void traceOnDecrypt(KeyringTrace keyringTrace) {
keyringTrace.add(keyNamespace, keyName, KeyringTraceFlag.DECRYPTED_DATA_KEY);
}
}
Loading