AwsEncryptionSdk.Keyring.AwsKmsDiscovery (AWS Encryption SDK v0.7.0)

View Source

AWS KMS Discovery Keyring implementation.

A decrypt-only keyring that can decrypt data encrypted with ANY KMS key the caller has access to. Unlike the standard AwsKms keyring, this keyring does not require knowing the key ARN in advance.

Use Cases

  • Decryption services: Services that decrypt data from multiple sources
  • Migration: Decrypt data while transitioning between KMS keys
  • Flexible decryption: When the encrypting key is not known at decrypt time

Security Warning

Discovery keyrings will attempt to decrypt using ANY KMS key ARN found in the encrypted data keys. Use a discovery filter to restrict which keys can be used:

{:ok, keyring} = AwsKmsDiscovery.new(client,
  discovery_filter: %{
    partition: "aws",
    accounts: ["123456789012"]  # Only allow keys from this account
  }
)

Operations

Encryption

Discovery keyrings cannot encrypt. wrap_key/2 always returns {:error, :discovery_keyring_cannot_encrypt}.

For encryption, use:

  • AwsKms keyring if you know the key ARN
  • Multi keyring with an AwsKms generator for encryption + discovery for decryption

Decryption (unwrap_key)

  1. Filters EDKs by provider ID "aws-kms"
  2. Validates each EDK's key ARN format
  3. Applies discovery filter (if configured)
  4. Attempts KMS Decrypt using the ARN from each EDK
  5. Returns on first successful decryption

Discovery Filter

Restrict which KMS keys can be used for decryption:

FieldDescriptionRequired
partitionAWS partition ("aws", "aws-cn", "aws-us-gov")Yes
accountsList of allowed AWS account IDsYes

IAM Permissions Required

The principal needs kms:Decrypt on ALL keys that might be encountered:

{
  "Effect": "Allow",
  "Action": "kms:Decrypt",
  "Resource": [
    "arn:aws:kms:*:123456789012:key/*",
    "arn:aws:kms:*:987654321098:key/*"
  ]
}

Or use a condition to limit to specific accounts:

{
  "Effect": "Allow",
  "Action": "kms:Decrypt",
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:CallerAccount": ["123456789012", "987654321098"]
    }
  }
}

Examples

Basic Discovery Decryption

alias AwsEncryptionSdk.Keyring.AwsKmsDiscovery
alias AwsEncryptionSdk.Keyring.KmsClient.ExAws
alias AwsEncryptionSdk.Cmm.Default
alias AwsEncryptionSdk.Client

# Create discovery keyring
{:ok, kms_client} = ExAws.new(region: "us-west-2")
{:ok, keyring} = AwsKmsDiscovery.new(kms_client)

# Create client
cmm = Default.new(keyring)
client = Client.new(cmm)

# Decrypt data (encrypted with any accessible KMS key)
{:ok, {plaintext, context}} = Client.decrypt(client, ciphertext)
{:ok, keyring} = AwsKmsDiscovery.new(kms_client,
  discovery_filter: %{
    partition: "aws",
    accounts: ["123456789012", "987654321098"]
  }
)

Encrypt with KMS, Decrypt with Discovery

alias AwsEncryptionSdk.Keyring.{AwsKms, AwsKmsDiscovery, Multi}

# Encryption keyring - knows the key
{:ok, encrypt_keyring} = AwsKms.new("arn:aws:kms:us-west-2:123:key/abc", kms_client)

# Decryption keyring - discovery mode
{:ok, decrypt_keyring} = AwsKmsDiscovery.new(kms_client,
  discovery_filter: %{partition: "aws", accounts: ["123456789012"]}
)

# Use different clients for encrypt vs decrypt
encrypt_client = Client.new(Default.new(encrypt_keyring))
decrypt_client = Client.new(Default.new(decrypt_keyring))

Spec Reference

https://github.com/awslabs/aws-encryption-sdk-specification/blob/master/framework/aws-kms/aws-kms-discovery-keyring.md

Summary

Functions

Creates a new AWS KMS Discovery Keyring.

Unwraps a data key using AWS KMS Discovery.

Discovery keyrings cannot encrypt - this always fails.

Types

discovery_filter()

@type discovery_filter() :: %{partition: String.t(), accounts: [String.t(), ...]}

t()

@type t() :: %AwsEncryptionSdk.Keyring.AwsKmsDiscovery{
  discovery_filter: discovery_filter() | nil,
  grant_tokens: [String.t()],
  kms_client: struct()
}

Functions

new(kms_client, opts \\ [])

@spec new(struct(), keyword()) :: {:ok, t()} | {:error, term()}

Creates a new AWS KMS Discovery Keyring.

Parameters

  • kms_client - KMS client struct implementing KmsClient behaviour
  • opts - Optional keyword list:
    • :discovery_filter - Map with :partition (string) and :accounts (list of strings)
    • :grant_tokens - List of grant tokens for KMS API calls

Returns

  • {:ok, keyring} on success
  • {:error, reason} on validation failure

Errors

  • {:error, :client_required} - kms_client is nil
  • {:error, :invalid_client_type} - kms_client is not a struct
  • {:error, :invalid_discovery_filter} - filter missing partition or accounts
  • {:error, :discovery_filter_accounts_empty} - accounts list is empty
  • {:error, :invalid_account_ids} - accounts contains non-string values

Examples

{:ok, client} = KmsClient.Mock.new(%{})
{:ok, keyring} = AwsKmsDiscovery.new(client)

# With discovery filter
{:ok, keyring} = AwsKmsDiscovery.new(client,
  discovery_filter: %{partition: "aws", accounts: ["123456789012"]}
)

unwrap_key(keyring, materials, edks)

Unwraps a data key using AWS KMS Discovery.

Iterates through EDKs, filtering by provider ID and ARN validity. For each matching EDK, extracts the key ARN from provider info and attempts decryption with KMS.

Returns

  • {:ok, materials} - Data key successfully decrypted
  • {:error, :plaintext_data_key_already_set} - Materials already have key
  • {:error, {:unable_to_decrypt_any_data_key, errors}} - All decryption attempts failed

Examples

{:ok, result} = AwsKmsDiscovery.unwrap_key(keyring, materials, edks)

wrap_key(aws_kms_discovery, encryption_materials)

@spec wrap_key(t(), AwsEncryptionSdk.Materials.EncryptionMaterials.t()) ::
  {:error, :discovery_keyring_cannot_encrypt}

Discovery keyrings cannot encrypt - this always fails.

Returns

Always returns {:error, :discovery_keyring_cannot_encrypt}