![JAR search and dependency download from the Maven repository](/logo.png)
com.amazonaws.encryptionsdk.kms.AwsKmsMrkAwareMasterKeyProvider Maven / Gradle / Ivy
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package com.amazonaws.encryptionsdk.kms;
import com.amazonaws.SdkClientException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.encryptionsdk.*;
import com.amazonaws.encryptionsdk.exception.AwsCryptoException;
import com.amazonaws.encryptionsdk.exception.NoSuchMasterKeyException;
import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException;
import com.amazonaws.encryptionsdk.internal.AwsKmsCmkArnInfo;
import com.amazonaws.handlers.RequestHandler2;
import com.amazonaws.services.kms.AWSKMS;
import com.amazonaws.services.kms.AWSKMSClient;
import com.amazonaws.services.kms.AWSKMSClientBuilder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import static com.amazonaws.encryptionsdk.internal.AwsKmsCmkArnInfo.*;
import static com.amazonaws.encryptionsdk.internal.AwsKmsCmkArnInfo.parseInfoFromKeyArn;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.5
//# MUST implement the Master Key Provider Interface (../master-key-
//# provider-interface.md#interface)
/**
* Represents a list Aws KMS keys
* and is used to encrypt/decrypt data with
* {@link AwsCrypto}.
* Some of these keys may be multi region keys,
* in which case this component
* is able to recognize
* different regional replicas
* of this multi region key as the same.
*/
public final class AwsKmsMrkAwareMasterKeyProvider extends MasterKeyProvider {
private static final String PROVIDER_NAME = "aws-kms";
private final List keyIds_;
private final List grantTokens_;
private final boolean isDiscovery_;
private final DiscoveryFilter discoveryFilter_;
private final String discoveryMrkRegion_;
private final KmsMasterKeyProvider.RegionalClientSupplier regionalClientSupplier_;
private final String defaultRegion_;
public static class Builder implements Cloneable {
private String defaultRegion_ = getSdkDefaultRegion();
private Optional regionalClientSupplier_ = Optional.empty();
private AWSKMSClientBuilder templateBuilder_ = null;
private DiscoveryFilter discoveryFilter_ = null;
private String discoveryMrkRegion_ = this.defaultRegion_;
Builder() {
// Default access: Don't allow outside classes to extend this class
}
public Builder clone() {
try {
AwsKmsMrkAwareMasterKeyProvider.Builder cloned = (AwsKmsMrkAwareMasterKeyProvider.Builder) super.clone();
if (templateBuilder_ != null) {
cloned.templateBuilder_ = cloneClientBuilder(templateBuilder_);
}
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 AwsKmsMrkAwareMasterKeyProvider#getMasterKey(String)} that are not full ARNs, but are instead bare key IDs or
* aliases.
*
* If the default region is not specified,
* the AWS SDK default region will be used.
* @see KmsMasterKeyProvider.Builder#withDefaultRegion(String)
* @param defaultRegion The default region to use.
*/
public AwsKmsMrkAwareMasterKeyProvider.Builder withDefaultRegion(String defaultRegion) {
this.defaultRegion_ = defaultRegion;
return this;
}
/**
* Sets the region contacted for multi-region keys
* when in Discovery mode.
* This region will be used when a multi-region key is discovered
* on decrypt by {@link AwsKmsMrkAwareMasterKeyProvider#getMasterKey(String)}.
*
*
* @param discoveryMrkRegion The region to contact to attempt to decrypt multi-region keys.
*/
public AwsKmsMrkAwareMasterKeyProvider.Builder withDiscoveryMrkRegion(String discoveryMrkRegion) {
this.discoveryMrkRegion_ = discoveryMrkRegion;
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 #withCredentials(AWSCredentialsProvider)} or
* {@link #withClientBuilder(AWSKMSClientBuilder)}; if you try to use these in combination, an
* {@link IllegalStateException} will be thrown.
*
* @see KmsMasterKeyProvider.Builder#withCustomClientFactory(KmsMasterKeyProvider.RegionalClientSupplier)
*/
public AwsKmsMrkAwareMasterKeyProvider.Builder withCustomClientFactory(KmsMasterKeyProvider.RegionalClientSupplier regionalClientSupplier) {
if (templateBuilder_ != null) {
throw clientSupplierComboException();
}
regionalClientSupplier_ = Optional.of(regionalClientSupplier);
return this;
}
private RuntimeException clientSupplierComboException() {
return new IllegalStateException("withCustomClientFactory cannot be used in conjunction with " +
"withCredentials or withClientBuilder");
}
/**
* Configures the {@link AwsKmsMrkAwareMasterKeyProvider} to use specific credentials. If a builder was previously set,
* this will override whatever credentials it set.
*
* @see KmsMasterKeyProvider.Builder#withCredentials(AWSCredentialsProvider)
*/
public AwsKmsMrkAwareMasterKeyProvider.Builder withCredentials(AWSCredentialsProvider credentialsProvider) {
if (regionalClientSupplier_.isPresent()) {
throw clientSupplierComboException();
}
if (templateBuilder_ == null) {
templateBuilder_ = AWSKMSClientBuilder.standard();
}
templateBuilder_.setCredentials(credentialsProvider);
return this;
}
/**
* Configures the {@link AwsKmsMrkAwareMasterKeyProvider} to use specific credentials. If a builder was previously set,
* this will override whatever credentials it set.
*
* @see KmsMasterKeyProvider.Builder#withCredentials(AWSCredentials)
*/
public AwsKmsMrkAwareMasterKeyProvider.Builder withCredentials(AWSCredentials credentials) {
return withCredentials(new AWSStaticCredentialsProvider(credentials));
}
/**
* Configures the {@link AwsKmsMrkAwareMasterKeyProvider} to use settings from this {@link AWSKMSClientBuilder} 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.
*
* This method will overwrite any credentials set using {@link #withCredentials(AWSCredentialsProvider)}.
*
* @see KmsMasterKeyProvider.Builder#withClientBuilder(AWSKMSClientBuilder)
*/
public AwsKmsMrkAwareMasterKeyProvider.Builder withClientBuilder(AWSKMSClientBuilder builder) {
if (regionalClientSupplier_.isPresent()) {
throw clientSupplierComboException();
}
final AWSKMSClientBuilder newBuilder = cloneClientBuilder(builder);
this.templateBuilder_ = newBuilder;
return this;
}
/**
* 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.
*
* @see KmsMasterKeyProvider.Builder#buildDiscovery()
*
*/
public AwsKmsMrkAwareMasterKeyProvider buildDiscovery() {
final boolean isDiscovery = true;
return new AwsKmsMrkAwareMasterKeyProvider(
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.6
//# The regional client
//# supplier MUST be defined in discovery mode.
regionalClientSupplier_.orElse(clientFactory(new ConcurrentHashMap<>(), templateBuilder_)),
defaultRegion_,
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.6
//# The key id list MUST be empty in discovery mode.
emptyList(),
emptyList(),
isDiscovery,
discoveryFilter_,
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.6
//# In
//# discovery mode if a default MRK Region is not configured the AWS SDK
//# Default Region MUST be used.
discoveryMrkRegion_ == null ? defaultRegion_ : discoveryMrkRegion_
);
}
/**
* 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.
*
* @see KmsMasterKeyProvider.Builder#buildDiscovery(DiscoveryFilter)
*/
public AwsKmsMrkAwareMasterKeyProvider buildDiscovery(DiscoveryFilter 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 Master Key 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.
*
* @see KmsMasterKeyProvider.Builder#buildStrict(List)
*/
public AwsKmsMrkAwareMasterKeyProvider buildStrict(List keyIds) {
final boolean isDiscovery = false;
return new AwsKmsMrkAwareMasterKeyProvider(
regionalClientSupplier_.orElse(clientFactory(new ConcurrentHashMap<>(), templateBuilder_)),
defaultRegion_,
new ArrayList(keyIds),
emptyList(),
isDiscovery,
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.6
//# A discovery filter MUST NOT be configured in strict mode.
null,
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.6
//# A default MRK Region MUST NOT be configured in strict mode.
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 Master Key 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.
*
* @see KmsMasterKeyProvider.Builder#buildStrict(String...)
*
*/
public AwsKmsMrkAwareMasterKeyProvider buildStrict(String... keyIds) {
return buildStrict(asList(keyIds));
}
static KmsMasterKeyProvider.RegionalClientSupplier clientFactory(
ConcurrentHashMap clientCache,
AWSKMSClientBuilder templateBuilder
) {
// Clone again; this MKP builder might be reused to build a second MKP with different creds.
AWSKMSClientBuilder builder = templateBuilder != null ? cloneClientBuilder(templateBuilder)
: AWSKMSClientBuilder.standard();
return region -> {
/* Check for early return (Postcondition): If a client already exists, use that. */
if (clientCache.containsKey(region)) {
return clientCache.get(region);
}
// 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.
final KmsMasterKeyProvider.SuccessfulRequestCacher cacher = new KmsMasterKeyProvider.SuccessfulRequestCacher(clientCache, region);
final ArrayList handlers = new ArrayList<>();
if (builder.getRequestHandlers() != null) {
handlers.addAll(builder.getRequestHandlers());
}
handlers.add(cacher);
final AWSKMS kms = cloneClientBuilder(builder)
.withRegion(region)
.withRequestHandlers(handlers.toArray(new RequestHandler2[handlers.size()]))
.build();
return cacher.setClient(kms);
};
}
static AWSKMSClientBuilder cloneClientBuilder(final AWSKMSClientBuilder builder) {
// We need to copy all arguments out of the builder in case it's mutated later on.
// Unfortunately AWSKMSClientBuilder doesn't support .clone() so we'll have to do it by hand.
if (builder.getEndpoint() != null) {
// We won't be able to set the region later if a custom endpoint is set.
throw new IllegalArgumentException("Setting endpoint configuration is not compatible with passing a " +
"builder to the KmsMasterKeyProvider. Use withCustomClientFactory" +
" instead.");
}
final AWSKMSClientBuilder newBuilder = AWSKMSClient.builder();
newBuilder.setClientConfiguration(builder.getClientConfiguration());
newBuilder.setCredentials(builder.getCredentials());
newBuilder.setEndpointConfiguration(builder.getEndpoint());
newBuilder.setMetricsCollector(builder.getMetricsCollector());
if (builder.getRequestHandlers() != null) {
newBuilder.setRequestHandlers(builder.getRequestHandlers().toArray(new RequestHandler2[0]));
}
return newBuilder;
}
/**
* The AWS SDK has a default process for evaluating the default Region.
* This returns null if no default region is found.
* Because a default region _may_ not be needed.
*
*/
private static String getSdkDefaultRegion() {
try {
return new com.amazonaws.regions.DefaultAwsRegionProviderChain().getRegion();
} catch (SdkClientException ex) {
return null;
}
}
}
public static AwsKmsMrkAwareMasterKeyProvider.Builder builder() {
return new AwsKmsMrkAwareMasterKeyProvider.Builder();
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.6
//# On initialization the caller MUST provide:
private AwsKmsMrkAwareMasterKeyProvider(
KmsMasterKeyProvider.RegionalClientSupplier supplier,
String defaultRegion,
List keyIds,
List grantTokens,
boolean isDiscovery,
DiscoveryFilter discoveryFilter,
String discoveryMrkRegion
) {
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.6
//# The key id list MUST NOT be empty or null in strict mode.
if (!isDiscovery && (keyIds == null || keyIds.isEmpty())) {
throw new IllegalArgumentException("Strict mode must be configured with a non-empty " +
"list of keyIds.");
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.6
//# The key id
//# list MUST NOT contain any null or empty string values.
if (!isDiscovery && (keyIds.contains(null) || keyIds.contains(""))) {
throw new IllegalArgumentException("Strict mode cannot be configured with a " +
"null key identifier.");
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.6
//# All AWS KMS
//# key identifiers are be passed to Assert AWS KMS MRK are unique (aws-
//# kms-mrk-are-unique.md#Implementation) and the function MUST return
//# success.
assertMrksAreUnique(keyIds);
/* Precondition: A region is required to contact AWS KMS.
* This is an edge case because the default region will be the same as the SDK default,
* but it is still possible.
*/
if (
!isDiscovery &&
defaultRegion == null &&
keyIds
.stream()
.map(identifier -> parseInfoFromKeyArn(identifier))
.anyMatch(info -> info == null)
) {
throw new AwsCryptoException("Can't use non-ARN key identifiers or aliases when " +
"no default region is set");
}
/* Precondition (untested): Discovery filter is only valid in discovery mode. */
if (!isDiscovery && discoveryFilter != null) {
throw new IllegalArgumentException("Strict mode cannot be configured with a " +
"discovery filter.");
}
/* Precondition (untested): Discovery mode can not have any keys to filter. */
if (isDiscovery && !keyIds.isEmpty()) {
throw new IllegalArgumentException("Discovery mode can not be configured with keys.");
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.6
//# If an AWS SDK Default Region can not be
//# obtained initialization MUST fail.
if (isDiscovery && discoveryMrkRegion == null) {
throw new IllegalArgumentException("Discovery MRK region can not be null.");
}
this.regionalClientSupplier_ = supplier;
this.defaultRegion_ = defaultRegion;
this.keyIds_ = Collections.unmodifiableList(new ArrayList<>(keyIds));
this.isDiscovery_ = isDiscovery;
this.discoveryFilter_ = discoveryFilter;
this.discoveryMrkRegion_ = discoveryMrkRegion;
this.grantTokens_ = grantTokens;
}
//= compliance/framework/aws-kms/aws-kms-mrk-are-unique.txt#2.5
//# The caller MUST provide:
/**
* Refactored into a pure function
* to facilitate testing and correctness.
*
*/
static void assertMrksAreUnique(List keyIdentifiers) {
List duplicateMultiRegionKeyIdentifiers = keyIdentifiers
.stream()
/* Collect a map of resource to identifier.
* This lets me group duplicates by "resource".
* This is because the identifier can be either an ARN or a raw identifier.
* By having the both the key id and the identifier I can ensure the uniqueness of
* the key id and the error message to the caller can contain both identifiers
* to facilitate debugging.
*/
.collect(Collectors.groupingBy(AwsKmsMrkAwareMasterKeyProvider::getResourceForResourceTypeKey))
.entrySet()
.stream()
//= compliance/framework/aws-kms/aws-kms-mrk-are-unique.txt#2.5
//# If there are zero duplicate resource ids between the multi-region
//# keys, this function MUST exit successfully
.filter(maybeDuplicate -> maybeDuplicate.getValue().size() > 1)
//= compliance/framework/aws-kms/aws-kms-mrk-are-unique.txt#2.5
//# If the list does not contain any multi-Region keys (aws-kms-key-
//# arn.md#identifying-an-aws-kms-multi-region-key) this function MUST
//# exit successfully.
//
/* Postcondition: Filter out duplicate resources that are not multi-region keys.
* I expect only have duplicates of specific multi-region keys.
* In JSON something like
* {
* "mrk-edb7fe6942894d32ac46dbb1c922d574" : [
* "arn:aws:kms:us-west-2:111122223333:key/mrk-edb7fe6942894d32ac46dbb1c922d574",
* "arn:aws:kms:us-east-2:111122223333:key/mrk-edb7fe6942894d32ac46dbb1c922d574"
* ]
* }
*/
.filter(maybeMrk -> isMRK(maybeMrk.getKey()))
/* Flatten the duplicate identifiers into a single list. */
.flatMap(mrkEntry -> mrkEntry.getValue().stream())
.collect(Collectors.toList());
//= compliance/framework/aws-kms/aws-kms-mrk-are-unique.txt#2.5
//# If any duplicate multi-region resource ids exist, this function MUST
//# yield an error that includes all identifiers with duplicate resource
//# ids not only the first duplicate found.
if (duplicateMultiRegionKeyIdentifiers.size() > 1) {
throw new IllegalArgumentException("Duplicate multi-region keys are not allowed:\n" +
String.join(", ", duplicateMultiRegionKeyIdentifiers));
}
}
/**
* Helper method for
* @see AwsKmsMrkAwareMasterKeyProvider#assertMrksAreUnique(List)
*
* Refoactored into a pure function
* to simplify testing and ensure correctness.
*
*/
static String getResourceForResourceTypeKey(String identifier) {
final AwsKmsCmkArnInfo info = parseInfoFromKeyArn(identifier);
/* Check for early return (Postcondition): Non-ARNs may be raw resources.
* Raw aliases ('alias/my-key')
* or key ids ('mrk-edb7fe6942894d32ac46dbb1c922d574').
*/
if (info == null) return identifier;
/* Check for early return (Postcondition): Return the identifier for non-key resource types.
* I only care about duplicate multi-region *keys*.
* Any other resource type
* should get filtered out.
* I return the entire identifier
* on the off chance that
* a customer has created
* an alias with a name `mrk-*`.
* This way such an alias
* can never accidentally
* collided with an existing multi-region key
* or a duplicate alias.
*/
if (!info.getResourceType().equals("key")) {
return identifier;
}
/* Postcondition: Return the key id.
* This will be used
* to find different regional replicas of
* the same multi-region key
* because the key id for replicas is always the same.
*/
return info.getResource();
}
/**
* Returns "aws-kms"
*/
@Override
public String getDefaultProviderId() {
return PROVIDER_NAME;
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
//# The input MUST be the same as the Master Key Provider Get Master Key
//# (../master-key-provider-interface.md#get-master-key) interface.
/**
* Added flexibility in matching multi-Region keys from different regions.
*
* @see KmsMasterKey#getMasterKey(String, String)
*/
@Override
public AwsKmsMrkAwareMasterKey getMasterKey(
final String providerId,
final String requestedKeyArn
) throws UnsupportedProviderException, NoSuchMasterKeyException {
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
//# The function MUST only provide master keys if the input provider id
//# equals "aws-kms".
if (!canProvide(providerId)) {
throw new UnsupportedProviderException();
}
/* There SHOULD only be one match.
* An unambiguous multi-region key for the family
* of related multi-region keys is required.
* See `assertMrksAreUnique`.
* However, in the case of single region keys or aliases,
* duplicates _are_ possible.
*/
Optional matchedArn = keyIds_
.stream()
.filter(t -> awsKmsArnMatchForDecrypt(t, requestedKeyArn))
.findFirst();
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
//# In strict mode, the requested AWS KMS key ARN MUST
//# match a member of the configured key ids by using AWS KMS MRK Match
//# for Decrypt (aws-kms-mrk-match-for-decrypt.md#implementation)
//# otherwise this function MUST error.
if (!isDiscovery_ && !matchedArn.isPresent()) {
throw new NoSuchMasterKeyException("Key must be in supplied list of keyIds.");
}
final AwsKmsCmkArnInfo requestedKeyArnInfo = parseInfoFromKeyArn(requestedKeyArn);
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
//# In discovery mode, the requested
//# AWS KMS key identifier MUST be a well formed AWS KMS ARN.
/* Precondition: Discovery mode requires requestedKeyArn be an ARN.
* This function is called on the encrypt path.
* It _may_ be the case that a raw key id, for example, was configured.
*/
if (isDiscovery_ && requestedKeyArnInfo == null) {
throw new NoSuchMasterKeyException("Cannot use AWS KMS identifiers "
+ "when in discovery mode.");
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
//# In
//# discovery mode if a discovery filter is configured the requested AWS
//# KMS key ARN's "partition" MUST match the discovery filter's
//# "partition" and the AWS KMS key ARN's "account" MUST exist in the
//# discovery filter's account id set.
if (isDiscovery_ && discoveryFilter_ != null &&
!discoveryFilter_.allowsPartitionAndAccount(requestedKeyArnInfo.getPartition(), requestedKeyArnInfo.getAccountId())
) {
throw new NoSuchMasterKeyException("Cannot use key in partition " + requestedKeyArnInfo.getPartition() +
" with account id " + requestedKeyArnInfo.getAccountId() + " with configured discovery filter.");
}
final String regionName_ = extractRegion(
defaultRegion_,
discoveryMrkRegion_,
matchedArn,
requestedKeyArnInfo,
isDiscovery_
);
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
//# An AWS KMS client
//# MUST be obtained by calling the regional client supplier with this
//# AWS Region.
AWSKMS kms = regionalClientSupplier_.getClient(regionName_);
String keyIdentifier = isDiscovery_
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
//# In discovery mode a AWS KMS MRK Aware Master Key (aws-kms-mrk-aware-
//# master-key.md) MUST be returned configured with
? requestedKeyArnInfo.toString(regionName_)
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
//# In strict mode a AWS KMS MRK Aware Master Key (aws-kms-mrk-aware-
//# master-key.md) MUST be returned configured with
: matchedArn.get();
final AwsKmsMrkAwareMasterKey result = AwsKmsMrkAwareMasterKey
.getInstance(kms, keyIdentifier, this);
result.setGrantTokens(grantTokens_);
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
//# The output MUST be the same as the Master Key Provider Get Master Key
//# (../master-key-provider-interface.md#get-master-key) interface.
return result;
}
/**
* Select the correct region from multiple default configurations
* and potentially related multi-Region keys from different regions.
*
* Refactored into a pure function to facilitate testing and ensure correctness.
*
*/
static String extractRegion(
final String defaultRegion,
final String discoveryMrkRegion,
final Optional matchedArn,
final AwsKmsCmkArnInfo requestedKeyArnInfo,
final boolean isDiscovery
) {
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
//# If the requested AWS KMS key identifier is not a well formed ARN the
//# AWS Region MUST be the configured default region this SHOULD be
//# obtained from the AWS SDK.
if (requestedKeyArnInfo == null) return defaultRegion;
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
//# Otherwise if the requested AWS KMS key
//# identifier is identified as a multi-Region key (aws-kms-key-
//# arn.md#identifying-an-aws-kms-multi-region-key), then AWS Region MUST
//# be the region from the AWS KMS key ARN stored in the provider info
//# from the encrypted data key.
if (
!isMRK(requestedKeyArnInfo.getResource()) ||
!requestedKeyArnInfo.getResourceType().equals("key")
) {
return requestedKeyArnInfo.getRegion();
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
//# Otherwise if the mode is discovery then
//# the AWS Region MUST be the discovery MRK region.
if (isDiscovery) return discoveryMrkRegion;
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
//# Finally if the
//# provider info is identified as a multi-Region key (aws-kms-key-
//# arn.md#identifying-an-aws-kms-multi-region-key) the AWS Region MUST
//# be the region from the AWS KMS key in the configured key ids matched
//# to the requested AWS KMS key by using AWS KMS MRK Match for Decrypt
//# (aws-kms-mrk-match-for-decrypt.md#implementation).
return parseInfoFromKeyArn(matchedArn.get()).getRegion();
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.8
//# The input MUST be the same as the Master Key Provider Get Master Keys
//# For Encryption (../master-key-provider-interface.md#get-master-keys-
//# for-encryption) interface.
/**
* Returns all CMKs provided to the constructor of this object.
* @see KmsMasterKey#getMasterKeysForEncryption(MasterKeyRequest)
*/
@Override
public List getMasterKeysForEncryption(final MasterKeyRequest request) {
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.8
//# If the configured mode is discovery the function MUST return an empty
//# list.
if (isDiscovery_) {
return emptyList();
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.8
//# If the configured mode is strict this function MUST return a
//# list of master keys obtained by calling Get Master Key (aws-kms-mrk-
//# aware-master-key-provider.md#get-master-key) for each AWS KMS key
//# identifier in the configured key ids
List result = new ArrayList<>(keyIds_.size());
for (String id : keyIds_) {
result.add(getMasterKey(id));
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.8
//# The output MUST be the same as the Master Key Provider Get Master
//# Keys For Encryption (../master-key-provider-interface.md#get-master-
//# keys-for-encryption) interface.
return result;
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9
//# The input MUST be the same as the Master Key Provider Decrypt Data
//# Key (../master-key-provider-interface.md#decrypt-data-key) interface.
/**
* @see KmsMasterKey#decryptDataKey(CryptoAlgorithm, Collection, Map)
* @throws AwsCryptoException
*/
@Override
public DataKey decryptDataKey(final CryptoAlgorithm algorithm,
final Collection extends EncryptedDataKey> encryptedDataKeys,
final Map encryptionContext)
throws AwsCryptoException {
final List exceptions = new ArrayList<>();
return encryptedDataKeys
.stream()
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9
//# The set of encrypted data keys MUST first be filtered to match this
//# master key's configuration.
.filter(edk -> {
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9
//# To match the encrypted data key's
//# provider ID MUST exactly match the value "aws-kms".
if (!canProvide(edk.getProviderId())) return false;
final String providerInfo = new String(edk.getProviderInformation(), StandardCharsets.UTF_8);
final AwsKmsCmkArnInfo providerArnInfo = parseInfoFromKeyArn(providerInfo);
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9
//# Additionally
//# each provider info MUST be a valid AWS KMS ARN (aws-kms-key-arn.md#a-
//# valid-aws-kms-arn) with a resource type of "key".
if (providerArnInfo == null || !"key".equals(providerArnInfo.getResourceType())) {
throw new IllegalStateException("Invalid provider info in message.");
}
return true;
})
.map(edk -> {
try {
final String keyArn = new String(edk.getProviderInformation(), StandardCharsets.UTF_8);
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9
//# For each encrypted data key in the filtered set, one at a time, the
//# master key provider MUST call Get Master Key (aws-kms-mrk-aware-
//# master-key-provider.md#get-master-key) with the encrypted data key's
//# provider info as the AWS KMS key ARN.
// This will throw if we can't use this key for whatever reason
return getMasterKey(
edk.getProviderId(),
keyArn)
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9
//# It MUST call Decrypt Data Key
//# (aws-kms-mrk-aware-master-key.md#decrypt-data-key) on this master key
//# with the input algorithm, this single encrypted data key, and the
//# input encryption context.
.decryptDataKey(algorithm, singletonList(edk), encryptionContext);
} catch (final Exception ex) {
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9
//# If this attempt results in an error, then
//# these errors MUST be collected.
exceptions.add(ex);
return null;
}
})
/* Need to filter null because an Optional of a null is crazy.
* `findFirst` will throw if it sees `null`.
*/
.filter(Objects::nonNull)
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9
//# If the decrypt data key call is
//# successful, then this function MUST return this result and not
//# attempt to decrypt any more encrypted data keys.
.findFirst()
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9
//# If all the input encrypted data keys have been processed then this
//# function MUST yield an error that includes all the collected errors.
//
//= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.9
//# The output MUST be the same as the Master Key Provider Decrypt Data
//# Key (../master-key-provider-interface.md#decrypt-data-key) interface.
.orElseThrow(() -> buildCannotDecryptDksException(exceptions));
}
public List getGrantTokens() {
return new ArrayList<>(grantTokens_);
}
/**
* Returns a new {@link AwsKmsMrkAwareMasterKeyProvider} 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).
*
*/
public AwsKmsMrkAwareMasterKeyProvider withGrantTokens(List grantTokens) {
grantTokens = Collections.unmodifiableList(new ArrayList<>(grantTokens));
return new AwsKmsMrkAwareMasterKeyProvider(
regionalClientSupplier_,
defaultRegion_,
keyIds_,
grantTokens,
isDiscovery_,
discoveryFilter_,
discoveryMrkRegion_
);
}
/**
* Returns a new {@link AwsKmsMrkAwareMasterKeyProvider} 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).
*
*/
public AwsKmsMrkAwareMasterKeyProvider withGrantTokens(String... grantTokens) {
return withGrantTokens(asList(grantTokens));
}
}