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

View Source

Multi-Keyring implementation.

Composes multiple keyrings together, enabling encryption with multiple keys and flexible decryption with any available key.

Use Cases

  • Redundancy: Encrypt with multiple keys so any one can decrypt
  • Key rotation: Include both old and new keys during transitions
  • Multi-party access: Different parties can decrypt with their respective keys

Encryption Behavior

  • Generator keyring (if provided) generates and wraps the plaintext data key
  • Each child keyring wraps the plaintext data key (adding additional EDKs)
  • All keyrings must succeed (fail-fast)
  • EDKs accumulate through the pipeline

Decryption Behavior

  • Attempts decryption with generator first (if provided), then children
  • Each keyring receives the original, unmodified materials
  • Returns immediately on first successful decryption
  • Fails only if all keyrings fail to decrypt

Security Note

Any keyring in the multi-keyring can decrypt data encrypted with this keyring. Users should understand the security implications of their keyring composition.

Example

# Create keyrings
{:ok, aes_keyring} = RawAes.new("ns", "aes-key", aes_key, :aes_256_gcm)
{:ok, rsa_keyring} = RawRsa.new("ns", "rsa-key", {:oaep, :sha256}, public_key: pub, private_key: priv)

# Create multi-keyring with generator and child
{:ok, multi} = Multi.new(generator: aes_keyring, children: [rsa_keyring])

# Encrypt - AES generates key, both keyrings wrap it
{:ok, enc_materials} = Multi.wrap_key(multi, materials)

# Decrypt - tries AES first, then RSA
{:ok, dec_materials} = Multi.unwrap_key(multi, materials, edks)

Spec Reference

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

Summary

Functions

Returns the list of all keyrings in this multi-keyring.

Creates a new Multi-Keyring.

Creates a Multi-Region Key (MRK) aware Multi-Keyring.

Creates a Multi-Keyring with an AWS KMS keyring as the generator.

Unwraps a data key using the keyrings in the multi-keyring.

Wraps a data key using all keyrings in the multi-keyring.

Types

keyring()

@type keyring() :: struct()

t()

@type t() :: %AwsEncryptionSdk.Keyring.Multi{
  children: [keyring()],
  generator: keyring() | nil
}

Functions

list_keyrings(multi)

@spec list_keyrings(t()) :: [keyring()]

Returns the list of all keyrings in this multi-keyring.

Useful for understanding which keyrings will be used for encryption/decryption.

Examples

{:ok, multi} = Multi.new(generator: gen, children: [child1, child2])
Multi.list_keyrings(multi)
# => [gen, child1, child2]

new(opts \\ [])

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

Creates a new Multi-Keyring.

Options

  • :generator - Optional keyring that generates the plaintext data key during encryption
  • :children - List of keyrings that wrap the data key (default: [])

At least one of generator or children must be provided. If children is empty, generator is required.

Returns

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

Errors

  • {:error, :no_keyrings_provided} - Neither generator nor children provided
  • {:error, :generator_required_when_no_children} - Children empty but no generator

Examples

# Generator with children
{:ok, multi} = Multi.new(generator: aes_keyring, children: [rsa_keyring])

# Generator only
{:ok, multi} = Multi.new(generator: aes_keyring)

# Children only (materials must already have plaintext data key for encryption)
{:ok, multi} = Multi.new(children: [rsa_keyring_1, rsa_keyring_2])

new_mrk_aware(primary_key_id, primary_client, replicas, opts \\ [])

@spec new_mrk_aware(String.t(), struct(), [{String.t(), struct()}], keyword()) ::
  {:ok, t()} | {:error, term()}

Creates a Multi-Region Key (MRK) aware Multi-Keyring.

Creates a multi-keyring optimized for cross-region scenarios using MRK replicas. The primary key is used as the generator (using AwsKmsMrk keyring), and MRK keyrings for each replica region are added as children for cross-region decryption.

Parameters

  • primary_key_id - Primary MRK key identifier (should be an mrk-* key for cross-region functionality)
  • primary_client - KMS client for the primary region
  • replicas - List of {region, kms_client} tuples for replica regions
  • opts - Optional keyword list:
    • :grant_tokens - Grant tokens for all KMS keyrings

Returns

  • {:ok, multi_keyring} on success
  • {:error, reason} if any keyring creation fails

Examples

# Primary in us-west-2, replicas in us-east-1 and eu-west-1
{:ok, multi} = Multi.new_mrk_aware(
  "arn:aws:kms:us-west-2:123:key/mrk-abc",
  west_client,
  [
    {"us-east-1", east_client},
    {"eu-west-1", eu_client}
  ]
)

Notes

For true cross-region MRK functionality, the key_id should be an MRK (key ID starting with mrk-). Non-MRK keys will work but won't provide cross-region decryption capability.

new_with_kms_generator(kms_key_id, kms_client, child_keyrings, opts \\ [])

@spec new_with_kms_generator(String.t(), struct(), [keyring()], keyword()) ::
  {:ok, t()} | {:error, term()}

Creates a Multi-Keyring with an AWS KMS keyring as the generator.

Convenience function for the common pattern of using a KMS key as the primary generator with additional child keyrings for backup decryption.

Parameters

  • kms_key_id - AWS KMS key identifier for the generator
  • kms_client - KMS client struct
  • child_keyrings - List of child keyrings (can be empty)
  • opts - Optional keyword list:
    • :grant_tokens - Grant tokens for the KMS generator keyring

Returns

  • {:ok, multi_keyring} on success
  • {:error, reason} if KMS keyring creation fails or validation fails

Examples

{:ok, multi} = Multi.new_with_kms_generator(
  "arn:aws:kms:us-west-2:123:key/abc",
  kms_client,
  [backup_keyring]
)

unwrap_key(keyring, materials, edks)

Unwraps a data key using the keyrings in the multi-keyring.

Attempts decryption with generator first (if present), then each child keyring in order. Returns immediately when any keyring successfully decrypts.

Each keyring receives the original, unmodified materials (not chained).

Returns

  • {:ok, materials} - Data key successfully unwrapped by one of the keyrings
  • {:error, :plaintext_data_key_already_set} - Materials already have a key
  • {:error, {:all_keyrings_failed, [reasons]}} - All keyrings failed to decrypt

Examples

{:ok, multi} = Multi.new(generator: keyring1, children: [keyring2])
dec_materials = DecryptionMaterials.new_for_decrypt(suite, ec)
{:ok, result} = Multi.unwrap_key(multi, dec_materials, edks)

wrap_key(keyring, materials)

Wraps a data key using all keyrings in the multi-keyring.

If a generator is present, it generates and wraps the plaintext data key. Each child keyring then wraps the same plaintext data key, adding additional EDKs.

All keyrings must succeed (fail-fast on any error).

Returns

  • {:ok, materials} - Data key wrapped by all keyrings
  • {:error, :plaintext_data_key_already_set} - Materials already have key (with generator)
  • {:error, :no_plaintext_data_key} - No generator and materials have no plaintext key
  • {:error, {:generator_failed, reason}} - Generator keyring failed
  • {:error, {:generator_did_not_produce_key}} - Generator didn't set plaintext key
  • {:error, {:child_keyring_failed, index, reason}} - Child keyring failed

Examples

{:ok, multi} = Multi.new(generator: keyring1, children: [keyring2])
enc_materials = EncryptionMaterials.new_for_encrypt(suite, ec)
{:ok, result} = Multi.wrap_key(multi, enc_materials)