-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
*Issue #, if available:* #102 *Description of changes:* This change defines the Keyring interface and an implementation of a RawKeyring which supports both AES and RSA. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. # Check any applicable: - [ ] Were any files moved? Moving files changes their URL, which breaks all hyperlinks to the files.
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* | ||
* 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 | ||
* | ||
* @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<EncryptedDataKey> encryptedDataKeys); | ||
|
||
/** | ||
* Constructs a {@link Keyring} which does local AES-GCM encryption | ||
* decryption of data keys using the provided wrapping key. | ||
* | ||
* @param keyNamespace A UTF-8 encoded value that, together with the key name, identifies the wrapping key. | ||
* @param keyName A UTF-8 encoded value that, together with the key namespace, identifies the wrapping key. | ||
* @param wrappingKey The AES key input to AES-GCM to encrypt plaintext data keys. | ||
* @return The {@link Keyring} | ||
*/ | ||
static Keyring rawAes(String keyNamespace, String keyName, SecretKey wrappingKey) { | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
WesleyRosenblum
Author
Contributor
|
||
return RawKeyring.aes(keyNamespace, keyName, wrappingKey); | ||
} | ||
|
||
/** | ||
* Constructs a {@link Keyring} which does local RSA encryption and decryption of data keys using the | ||
* provided public and private keys. If {@code privateKey} is {@code null} then the returned {@link Keyring} | ||
* can only be used for encryption. | ||
* | ||
* @param keyNamespace A UTF-8 encoded value that, together with the key name, identifies the wrapping key. | ||
* @param keyName A UTF-8 encoded value that, together with the key namespace, identifies the wrapping key. | ||
* @param publicKey The RSA public key used by this keyring to encrypt data keys. | ||
* @param privateKey The RSA private key used by this keyring to decrypt data keys. | ||
* @param wrappingAlgorithm The RSA algorithm to use with this keyring. | ||
* @return The {@link Keyring} | ||
*/ | ||
static Keyring rawRsa(String keyNamespace, String keyName, PublicKey publicKey, PrivateKey privateKey, String wrappingAlgorithm) { | ||
return RawKeyring.rsa(keyNamespace, keyName, publicKey, privateKey, wrappingAlgorithm); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/* | ||
* 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.security.PrivateKey; | ||
import java.security.PublicKey; | ||
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. | ||
*/ | ||
public class RawKeyring implements Keyring { | ||
|
||
private final String keyNamespace; | ||
private final String keyName; | ||
private final JceKeyCipher jceKeyCipher; | ||
private static final Charset KEY_NAME_ENCODING = StandardCharsets.UTF_8; | ||
private static final Logger LOGGER = Logger.getLogger(RawKeyring.class.getName()); | ||
|
||
static Keyring aes(String keyNamespace, String keyName, SecretKey wrappingKey) { | ||
This comment has been minimized.
Sorry, something went wrong.
mattsb42-aws
Member
|
||
return new RawKeyring(keyNamespace, keyName, JceKeyCipher.aesGcm(wrappingKey)); | ||
} | ||
|
||
static Keyring rsa(String keyNamespace, String keyName, PublicKey publicKey, PrivateKey privateKey, String transformation) { | ||
return new RawKeyring(keyNamespace, keyName, JceKeyCipher.rsa(publicKey, privateKey, transformation)); | ||
} | ||
|
||
private 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.jceKeyCipher = jceKeyCipher; | ||
} | ||
|
||
@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)); | ||
|
||
encryptionMaterials.getKeyringTrace().add(keyNamespace, keyName, KeyringTraceFlag.ENCRYPTED_DATA_KEY); | ||
This comment has been minimized.
Sorry, something went wrong.
mattsb42-aws
Member
|
||
} | ||
|
||
@Override | ||
public void onDecrypt(DecryptionMaterials decryptionMaterials, List<EncryptedDataKey> encryptedDataKeys) { | ||
notNull(decryptionMaterials, "decryptionMaterials are required"); | ||
notNull(encryptedDataKeys, "encryptedDataKeys are required"); | ||
|
||
if (decryptionMaterials.getCleartextDataKey() != null) { | ||
return; | ||
} | ||
|
||
final byte[] keyNameBytes = keyName.getBytes(KEY_NAME_ENCODING); | ||
|
||
for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) { | ||
if (!keyNamespace.equals(encryptedDataKey.getProviderId())) { | ||
continue; | ||
} | ||
|
||
if (!Utils.arrayPrefixEquals(encryptedDataKey.getProviderInformation(), keyNameBytes, keyNameBytes.length)) { | ||
This comment has been minimized.
Sorry, something went wrong.
mattsb42-aws
Member
|
||
continue; | ||
} | ||
|
||
try { | ||
final byte[] decryptedKey = jceKeyCipher.decryptKey( | ||
encryptedDataKey, keyName, decryptionMaterials.getEncryptionContext()); | ||
decryptionMaterials.setCleartextDataKey( | ||
new SecretKeySpec(decryptedKey, decryptionMaterials.getAlgorithm().getDataKeyAlgo())); | ||
decryptionMaterials.getKeyringTrace().add(keyNamespace, keyName, KeyringTraceFlag.DECRYPTED_DATA_KEY); | ||
return; | ||
} catch (Exception e) { | ||
LOGGER.info("Could not decrypt key due to: " + e.getMessage()); | ||
} | ||
} | ||
|
||
LOGGER.warning("Could not decrypt any data keys"); | ||
} | ||
|
||
private void generateDataKey(EncryptionMaterials encryptionMaterials) { | ||
if (encryptionMaterials.getCleartextDataKey() != null) { | ||
throw new IllegalStateException("Plaintext data key already exists"); | ||
} | ||
|
||
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); | ||
} | ||
} |
Generally speaking, we've avoided trying to have any sort of central "registry" like this and instead opted for just providing people with the classes to create their own instances directly. I'm not sure what value this method and the
rawRsa
method would add, and they seem like they would just add maintenance overhead and complexity.