builderSupplier_ = null;
private RegionalClientSupplier regionalClientSupplier_ = null;
private DiscoveryFilter discoveryFilter_ = null;
Builder() {
// Default access: Don't allow outside classes to extend this class
}
public Builder clone() {
try {
Builder cloned = (Builder) super.clone();
cloned.builderSupplier_ = builderSupplier_;
return cloned;
} catch (CloneNotSupportedException e) {
throw new Error("Impossible: CloneNotSupportedException", e);
}
}
/**
* Sets the default region. This region will be used when specifying key IDs for encryption or
* in {@link KmsMasterKeyProvider#getMasterKey(String)} that are not full ARNs, but are instead
* bare key IDs or aliases.
*
* If the default region is not specified, only full key ARNs will be usable.
*
* @param defaultRegion The default region to use.
* @return
*/
public Builder defaultRegion(Region defaultRegion) {
this.defaultRegion_ = defaultRegion;
return this;
}
/**
* Provides a custom factory function that will vend KMS clients. This is provided for advanced
* use cases which require complete control over the client construction process.
*
*
Because the regional client supplier fully controls the client construction process, it is
* not possible to configure the client through methods such as {@link
* #builderSupplier(Supplier)}; if you try to use these in combination, an {@link
* IllegalStateException} will be thrown.
*
*
Note: The AWS Encryption SDK for Java does not support the {@code KmsAsyncClient}
* interface.
*
* @param regionalClientSupplier
* @return
*/
public Builder customRegionalClientSupplier(RegionalClientSupplier regionalClientSupplier) {
if (builderSupplier_ != null) {
throw clientSupplierComboException();
}
regionalClientSupplier_ = regionalClientSupplier;
return this;
}
/**
* Configures the {@link KmsMasterKeyProvider} to use settings from this {@link
* KmsClientBuilder} to configure KMS clients. Note that the region set on this builder will be
* ignored, but all other settings will be propagated into the regional clients.
*
*
Trying to use this method in combination with {@link
* #customRegionalClientSupplier(RegionalClientSupplier)} will cause an {@link
* IllegalStateException} to be thrown.
*
*
Note: The AWS Encryption SDK for Java does not support the {@code KmsAsyncClient}
* interface.
*
* @param supplier Should return a new {@link KmsClientBuilder} on each invocation.
* @return
*/
public Builder builderSupplier(Supplier supplier) {
if (regionalClientSupplier_ != null) {
throw clientSupplierComboException();
}
this.builderSupplier_ = supplier;
return this;
}
private RuntimeException clientSupplierComboException() {
return new IllegalStateException(
"only one of builderSupplier and customRegionalClientSupplier may be used");
}
/**
* Builds the master key provider in Discovery Mode. In Discovery Mode the KMS Master Key
* Provider will attempt to decrypt using any key identifier it discovers in the encrypted
* message. KMS Master Key Providers in Discovery Mode will not encrypt data keys.
*
* @return
*/
public KmsMasterKeyProvider buildDiscovery() {
final boolean isDiscovery = true;
RegionalClientSupplier supplier = clientFactory();
return new KmsMasterKeyProvider(
supplier, defaultRegion_, emptyList(), emptyList(), isDiscovery, discoveryFilter_);
}
/**
* Builds the master key provider in Discovery Mode with a {@link DiscoveryFilter}. In Discovery
* Mode the KMS Master Key Provider will attempt to decrypt using any key identifier it
* discovers in the encrypted message that is accepted by the {@code filter}. KMS Master Key
* Providers in Discovery Mode will not encrypt data keys.
*
* @param filter
* @return
*/
public KmsMasterKeyProvider buildDiscovery(DiscoveryFilter filter) {
if (filter == null) {
throw new IllegalArgumentException(
"Discovery filter must not be null if specifying " + "a discovery filter.");
}
discoveryFilter_ = filter;
return buildDiscovery();
}
/**
* Builds the master key provider in Strict Mode. KMS Master Key Providers in Strict Mode will
* only attempt to decrypt using key ARNs listed in {@code keyIds}. KMS Master Key Providers in
* Strict Mode will encrypt data keys using the keys listed in {@code keyIds}
*
* In Strict Mode, one or more CMKs must be provided. For providers that will only be used
* for encryption, you can use any valid KMS key identifier. For providers that will be used for
* decryption, you must use the key ARN; key ids, alias names, and alias ARNs are not supported.
*
* @param keyIds
* @return
*/
public KmsMasterKeyProvider buildStrict(List keyIds) {
if (keyIds == null) {
throw new IllegalArgumentException(
"Strict mode must be configured with a non-empty " + "list of keyIds.");
}
final boolean isDiscovery = false;
RegionalClientSupplier supplier = clientFactory();
return new KmsMasterKeyProvider(
supplier, defaultRegion_, new ArrayList<>(keyIds), emptyList(), isDiscovery, null);
}
/**
* Builds the master key provider in strict mode. KMS Master Key Providers in Strict Mode will
* only attempt to decrypt using key ARNs listed in {@code keyIds}. KMS Master Key Providers in
* Strict Mode will encrypt data keys using the keys listed in {@code keyIds}
*
* In Strict Mode, one or more CMKs must be provided. For providers that will only be used
* for encryption, you can use any valid KMS key identifier. For providers that will be used for
* decryption, you must use the key ARN; key ids, alias names, and alias ARNs are not supported.
*
* @param keyIds
* @return
*/
public KmsMasterKeyProvider buildStrict(String... keyIds) {
return buildStrict(asList(keyIds));
}
RegionalClientSupplier clientFactory() {
if (regionalClientSupplier_ != null) {
return regionalClientSupplier_;
}
ConcurrentHashMap clientCache = new ConcurrentHashMap<>();
snoopClientCache(clientCache);
return region -> {
KmsClient client = clientCache.get(region);
if (client != null) return client;
KmsClientBuilder builder =
builderSupplier_ != null ? builderSupplier_.get() : KmsClient.builder();
// We can't just use computeIfAbsent as we need to avoid leaking KMS clients if we're asked
// to decrypt
// an EDK with a bogus region in its ARN. So we'll install a request handler to identify the
// first
// successful call, and cache it when we see that.
RequestClientCacher cacher = new RequestClientCacher(clientCache, region);
ClientOverrideConfiguration overrideConfig =
builder.overrideConfiguration().toBuilder().addExecutionInterceptor(cacher).build();
client = builder.region(region).overrideConfiguration(overrideConfig).build();
return cacher.setClient(client);
};
}
protected void snoopClientCache(ConcurrentHashMap map) {
// no-op - this is a test hook
}
}
public static Builder builder() {
return new Builder();
}
KmsMasterKeyProvider(
RegionalClientSupplier supplier,
Region defaultRegion,
List keyIds,
List grantTokens,
boolean isDiscovery,
DiscoveryFilter discoveryFilter) {
if (!isDiscovery && (keyIds == null || keyIds.isEmpty())) {
throw new IllegalArgumentException(
"Strict mode must be configured with a non-empty " + "list of keyIds.");
}
if (!isDiscovery && keyIds.contains(null)) {
throw new IllegalArgumentException(
"Strict mode cannot be configured with a " + "null key identifier.");
}
if (!isDiscovery && discoveryFilter != null) {
throw new IllegalArgumentException(
"Strict mode cannot be configured with a " + "discovery filter.");
}
// If we don't have a default region, we need to check that all key IDs will be usable
if (!isDiscovery && defaultRegion == null) {
for (String keyId : keyIds) {
final AwsKmsCmkArnInfo arnInfo = parseInfoFromKeyArn(keyId);
if (arnInfo == null) {
throw new AwsCryptoException(
"Can't use non-ARN key identifiers or aliases when " + "no default region is set");
}
}
}
this.regionalClientSupplier_ = supplier;
this.defaultRegion_ = defaultRegion;
this.keyIds_ = Collections.unmodifiableList(new ArrayList<>(keyIds));
this.isDiscovery_ = isDiscovery;
this.discoveryFilter_ = discoveryFilter;
this.grantTokens_ = grantTokens;
}
/** Returns "aws-kms" */
@Override
public String getDefaultProviderId() {
return PROVIDER_NAME;
}
@Override
public KmsMasterKey getMasterKey(final String provider, final String keyId)
throws UnsupportedProviderException, NoSuchMasterKeyException {
if (!canProvide(provider)) {
throw new UnsupportedProviderException();
}
if (!isDiscovery_ && !keyIds_.contains(keyId)) {
throw new NoSuchMasterKeyException("Key must be in supplied list of keyIds.");
}
final AwsKmsCmkArnInfo arnInfo = parseInfoFromKeyArn(keyId);
if (isDiscovery_ && discoveryFilter_ != null && (arnInfo == null)) {
throw new NoSuchMasterKeyException(
"Cannot use non-ARN key identifiers or aliases if " + "discovery filter is configured.");
} else if (isDiscovery_
&& discoveryFilter_ != null
&& !discoveryFilter_.allowsPartitionAndAccount(
arnInfo.getPartition(), arnInfo.getAccountId())) {
throw new NoSuchMasterKeyException(
"Cannot use key in partition "
+ arnInfo.getPartition()
+ " with account id "
+ arnInfo.getAccountId()
+ " with configured discovery filter.");
}
Region region = defaultRegion_;
if (arnInfo != null) {
region = Region.of(arnInfo.getRegion());
}
final Region region_ = region;
Supplier kmsSupplier =
() -> {
KmsClient client = regionalClientSupplier_.getClient(region_);
if (client == null) {
throw new AwsCryptoException("Can't use keys from region " + region_.id());
}
return client;
};
final KmsMasterKey result = KmsMasterKey.getInstance(kmsSupplier, keyId, this);
result.setGrantTokens(grantTokens_);
return result;
}
/** Returns all CMKs provided to the constructor of this object. */
@Override
public List getMasterKeysForEncryption(final MasterKeyRequest request) {
if (keyIds_ == null) {
return emptyList();
}
List result = new ArrayList<>(keyIds_.size());
for (String id : keyIds_) {
result.add(getMasterKey(id));
}
return result;
}
@Override
public DataKey decryptDataKey(
final CryptoAlgorithm algorithm,
final Collection extends EncryptedDataKey> encryptedDataKeys,
final Map encryptionContext)
throws AwsCryptoException {
final List exceptions = new ArrayList<>();
for (final EncryptedDataKey edk : encryptedDataKeys) {
if (canProvide(edk.getProviderId())) {
try {
final String keyArn = new String(edk.getProviderInformation(), StandardCharsets.UTF_8);
// This will throw if we can't use this key for whatever reason
return getMasterKey(keyArn)
.decryptDataKey(algorithm, singletonList(edk), encryptionContext);
} catch (final Exception ex) {
exceptions.add(ex);
}
}
}
throw buildCannotDecryptDksException(exceptions);
}
/**
* @deprecated This method is inherently not thread safe. Use {@link
* KmsMasterKey#setGrantTokens(List)} instead. {@link KmsMasterKeyProvider}s constructed using
* the builder will throw an exception on attempts to modify the list of grant tokens.
*/
@Deprecated
@Override
public void setGrantTokens(final List grantTokens) {
try {
this.grantTokens_.clear();
this.grantTokens_.addAll(grantTokens);
} catch (UnsupportedOperationException e) {
throw grantTokenError();
}
}
@Override
public List getGrantTokens() {
return new ArrayList<>(grantTokens_);
}
/**
* @deprecated This method is inherently not thread safe. Use {@link #withGrantTokens(List)} or
* {@link KmsMasterKey#setGrantTokens(List)} instead. {@link KmsMasterKeyProvider}s
* constructed using the builder will throw an exception on attempts to modify the list of
* grant tokens.
*/
@Deprecated
@Override
public void addGrantToken(final String grantToken) {
try {
grantTokens_.add(grantToken);
} catch (UnsupportedOperationException e) {
throw grantTokenError();
}
}
private RuntimeException grantTokenError() {
return new IllegalStateException(
"This master key provider is immutable. Use withGrantTokens instead.");
}
/**
* Returns a new {@link KmsMasterKeyProvider} that is configured identically to this one, except
* with the given list of grant tokens. The grant token list in the returned provider is immutable
* (but can be further overridden by invoking withGrantTokens again).
*
* @param grantTokens
* @return
*/
public KmsMasterKeyProvider withGrantTokens(List grantTokens) {
grantTokens = Collections.unmodifiableList(new ArrayList<>(grantTokens));
return new KmsMasterKeyProvider(
regionalClientSupplier_,
defaultRegion_,
keyIds_,
grantTokens,
isDiscovery_,
discoveryFilter_);
}
/**
* Returns a new {@link KmsMasterKeyProvider} that is configured identically to this one, except
* with the given list of grant tokens. The grant token list in the returned provider is immutable
* (but can be further overridden by invoking withGrantTokens again).
*
* @param grantTokens
* @return
*/
public KmsMasterKeyProvider withGrantTokens(String... grantTokens) {
return withGrantTokens(asList(grantTokens));
}
}