Description
One goal of the AWS Encryption SDK is to make doing the right thing easy, and doing the wrong thing difficult/impossible.
After we release keyrings, it will be very difficult to change any of the keyring APIs without breaking existing customers. Thus, we should endeavor to design the ergonomics of keyrings to achieve this goal before we release them. In this issue, we'll discuss the proposed changes to the ESDK to support keyrings, make sure that they achieve this goal, and improve our design if they don't.
In this issue, we introduce some of the major keyring-related changes, along with some potential improvements/alternative ergonomics. Please feel free to comment and add more areas for improvement.
StandardKeyrings
The only way to instantiate the built-in keyrings in the ESDK is by using one of the static factory methods in the StandardKeyrings class. All built-in keyring implementation classes are package-private so they can evolve with the assurance that no client is directly instantiating them.
The most commonly used factory method in StandardKeyrings will likely be
public static Keyring kms(KmsClientSupplier clientSupplier,
List<String> grantTokens,
List<String> keyIds,
String generator)
This method requires customers to instantiate a KmsClientSupplier (using a KmsClientSupplier.Builder
), supply optional grantTokens and keyIds, and an optional or required generator ARN.
Proposal: We could add an additional factory method to StandardKeyrings that uses a default KmsClientSupplier and eliminates some of the optional arguments, such as:
public static Keyring kms(String generator)
Proposal: We can replace factory methods with methods that return builders for each keyring type. This would allow optional inputs without too many methods:
public static KmsKeyringBuilder kms()
public static RawRsaKeyringBuilder rawRsa()
public static RawAesKeyringBuilder rawAes()
public static MultiKeyringBuilder multi()
AwsCryptoConfig
AwsCrypto
is the main entry point to the Java ESDK. Almost all existing public methods in this class have been deprecated and replaced by methods that take in AwsCryptoConfig
, such as:
public AwsCryptoResult<byte[]> encryptData(final AwsCryptoConfig config,
final byte[] plaintext)
AwsCryptoConfig is instantiated with a AwsCryptoConfig.Builder
that gives users the option to seta CryptoMaterialsManager
, Keyring
, and Encryption Context. This reduces the number of non-deprecated public methods in AwsCrypto from about 30 to 5.
EncryptRequest / DecryptRequest
AwsCrypto
is the main entry point to the Java ESDK. Almost all existing public methods in this class have been deprecated and replaced by methods that take in specific request types, such as:
public AwsCryptoResult<byte[]> encrypt(final EncryptRequest request)
public AwsCryptoResult<byte[]> decrypt(final DecryptRequest request)
public long estimateCiphertextSize(final EstimateCiphertextSizeRequest request)
public AwsCryptoOutputStream createEncryptingOutputStream(final CreateEncryptingOutputStreamRequest request)
Each of these types are constructed with a builder to easily allow for optional parameters. This matches the pattern that the AWS SDK uses.
Keyring Interface and Encryption/Decryption Materials
The Keyring interface defines two void methods:
void onEncrypt(EncryptionMaterials encryptionMaterials);
void onDecrypt(DecryptionMaterials decryptionMaterials, List<? extends EncryptedDataKey> encryptedDataKeys)
We've changed the materials classes to make some fields mutable, including the cleartextDataKey and the list of encryptedDataKeys.
Proposal: Make EncryptionMaterials and DecryptionMaterials immutable, and require users to reinstantiate whenever they change. To do this, we need to change the return type of the keyring interface methods from void to EncryptionMaterials and DecryptionMaterials. We can also introduce a new abstract KeyringBase class to reduce the amount of data each keyring implementation needs to pass back and forth. This class would look something like this:
public abstract class KeyringBase implements Keyring {
@Override
public EncryptionMaterials onEncrypt(EncryptionMaterials encryptionMaterials) {
OnEncryptResult onEncryptResult = onEncryptImpl(encryptionMaterials);
return encryptionMaterials.toBuilder()
.setEncryptedDataKeys(onEncryptResult.getEncryptedDataKeys())
.setCleartextDataKey(onEncryptResult.getCleartextDataKey())
.setKeyringTrace(onEncryptResult.getKeyringTrace())
.build();
}
@Override
public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, List<? extends EncryptedDataKey> encryptedDataKeys) {
OnDecryptResult onDecryptResult = onDecryptImpl(decryptionMaterials, encryptedDataKeys);
return decryptionMaterials.toBuilder()
.setCleartextDataKey(onDecryptResult.getCleartextDataKey())
.setKeyringTrace(onDecryptResult.getKeyringTrace())
}
public abstract OnEncryptResult onEncryptImpl(EncryptionMaterials encryptionMaterials);
public abstract OnDecryptResult onDecryptImpl(DecryptionMaterials encryptionMaterials, List<? extends EncryptedDataKey> encryptedDataKeys);
}