Skip to main content

AWS KMS Adapter

laravel-chronicle/kms-aws integrates Chronicle with AWS KMS in two ways:

  • Chronicle\KmsAws\AwsKmsSigningProvider - sign checkpoints and exports with an ECDSA P-256 KMS key. The private key never leaves KMS; verification is offline against a cached public key.
  • Chronicle\KmsAws\KmsKeyEncryptionProvider - hold the crypto-shredding Key Encryption Key (KEK) in KMS, so wrapped per-subject DEKs are protected outside the application.

Requirements

  • PHP 8.2+, ext-openssl
  • laravel-chronicle/core ^1.10
  • aws/aws-sdk-php ^3.0
composer require laravel-chronicle/kms-aws

The package auto-discovers KmsAwsServiceProvider, which binds a default Aws\Kms\KmsClient from AWS_DEFAULT_REGION and standard AWS credentials. Its own config (chronicle-kms.php) holds only the region.

Signing with KMS

Create the KMS key

Create an asymmetric key with key spec ECC_NIST_P256 and key usage SIGN_VERIFY - this matches the ecdsa-p256 algorithm Chronicle records in every artifact.

Retrieve and cache the public key

Fetch the public key once and store it (PEM) in config. It is used for offline verification and never fetched from KMS at runtime:

aws kms get-public-key \
--key-id arn:aws:kms:REGION:ACCOUNT_ID:key/KEY_ID \
--query 'PublicKey' --output text | base64 -d | \
openssl pkey -pubin -inform DER -outform PEM

Configure

Register it as a key in the key ring:

use Chronicle\KmsAws\AwsKmsSigningProvider;

'signing' => [
'active' => env('CHRONICLE_ACTIVE_KEY', 'kms-production'),
'keys' => [
'kms-production' => [
'provider' => AwsKmsSigningProvider::class,
'algorithm' => 'ecdsa-p256',
'key_arn' => env('CHRONICLE_KMS_KEY_ARN'),
'public_key' => env('CHRONICLE_KMS_PUBLIC_KEY'), // PEM string
],
],
],

Set AWS_DEFAULT_REGION, CHRONICLE_KMS_KEY_ARN, and CHRONICLE_KMS_PUBLIC_KEY (the PEM from the command above) in your environment.

IAM for signing

{
"Effect": "Allow",
"Action": ["kms:Sign", "kms:DescribeKey"],
"Resource": "arn:aws:kms:REGION:ACCOUNT_ID:key/KEY_ID"
}

kms:GetPublicKey is not required at runtime - the public key is cached in config.

How it works

OperationWhere it runs
sign()Remote - KMS Sign API with MessageType: DIGEST
verify()Local - openssl_verify against the cached PEM public key

Rotation

KMS keys rotate through the standard key-ring workflow: add the new KMS key as active and create the boundary checkpoint with chronicle:key:rotate.

warning

AwsKmsSigningProvider requires key_arn and cannot be a verify-only key. To retire a KMS key, switch its ring entry to core's Chronicle\Signing\EcdsaSigningProvider with only public_key set - that handles local verify-only mode. Keep the retired public key forever so historical artifacts stay verifiable.

KEK encryption (crypto-shredding)

Chronicle v1.12 encrypts PII payload fields under a per-subject DEK and wraps those DEKs under a Key Encryption Key. This package can hold that KEK in KMS. Point chronicle.encryption.kek at the provider:

use Chronicle\KmsAws\KmsKeyEncryptionProvider;

'encryption' => [
'enabled' => true,
'kek' => [
'provider' => KmsKeyEncryptionProvider::class,
'key' => env('CHRONICLE_KMS_KEK_ARN'), // KMS key for Encrypt/Decrypt
'id' => env('CHRONICLE_KMS_KEK_ID'), // kekId recorded per subject key
],
],

Required IAM actions on the KEK: kms:Encrypt, kms:Decrypt. The KmsClient is resolved from the container, so no extra wiring is needed beyond installing this package.

See also