---
title: "AWS KMS"
description: "This guide shows you how to create and manage signing keys in AWS KMS through the Enterprise Stack KMS service."
stack: "Enterprise Stack (commercial) — version 0.21.0"
stack_version: "0.21.0"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/enterprise-stack/services/key-management-service/aws-kms
generated: 2026-07-20
---
# AWS KMS

This guide shows you how to create and manage signing keys in AWS KMS through the Enterprise Stack KMS service.

## What you can do

- Generate signing keys in AWS KMS and use them across Enterprise Stack services (credential issuance, DID operations, etc.)
- Use IRSA (IAM Roles for Service Accounts) for secure, credential-free authentication on EKS
- Create multi-region keys with automatic failover for high-availability deployments

## Prerequisites

Setting up AWS KMS involves two separate places: your Enterprise Stack config (step 1) and your own AWS account (step 2, and optionally IRSA below, done via the AWS Console, CLI, or IaC — not via any Enterprise Stack file).

### 1. Enable the crypto-aws feature

**Note:**

The `crypto-aws` feature flag is **enabled by default**, so you usually don't need to touch this — it only needs action if your deployment has explicitly disabled it.

Check `disabledFeatures` in your [`_features.conf`](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/features.md) (found at `waltid-enterprise-api/config/_features.conf` in a local checkout) and remove `crypto-aws` from that list if present:

```hocon [_features.conf]
disabledFeatures = [
    # crypto-aws
]
```

The API must be restarted for feature flag changes to take effect.

### 2. Set up IAM permissions in your AWS account

Create an IAM role or user in your own AWS account with these KMS permissions:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "WaltIDKMSBasic",
      "Effect": "Allow",
      "Action": [
        "kms:CreateKey",
        "kms:CreateAlias",
        "kms:DeleteAlias",
        "kms:Describe*",
        "kms:Get*",
        "kms:List*",
        "kms:Sign",
        "kms:Verify",
        "kms:TagResource",
        "kms:UntagResource"
      ],
      "Resource": "*"
    }
  ]
}
```

**For multi-region keys**, add:
```json
"kms:ReplicateKey",
"kms:ScheduleKeyDeletion"
```

### IRSA setup for EKS (recommended)

For production EKS deployments, use IRSA instead of long-lived access keys:

**1. Create IAM role with the KMS policy above**

**2. Add trust relationship for your service account:**
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::YOUR_ACCOUNT:oidc-provider/oidc.eks.YOUR_REGION.amazonaws.com/id/YOUR_OIDC_ID"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.YOUR_REGION.amazonaws.com/id/YOUR_OIDC_ID:sub": "system:serviceaccount:YOUR_NAMESPACE:YOUR_SERVICE_ACCOUNT"
        }
      }
    }
  ]
}
```

**3. Annotate your Kubernetes service account:**
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: waltid-enterprise-api
  namespace: your-namespace
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::YOUR_ACCOUNT:role/waltid-eks-kms-role
```

**4. Use `"backend": "aws"` in requests** - IRSA credentials are used automatically.

## Supported key algorithms

- `secp256r1` (P-256)
- `secp256k1`
- `secp384r1` (P-384)
- `secp521r1` (P-521)
- `RSA` (2048-bit)
- `RSA3072` (3072-bit)
- `RSA4096` (4096-bit)

## Create a basic key

### With IRSA / environment credentials (recommended)

**Option: CURL**

**Endpoint:** `POST /v1/{target}/kms-service-api/keys/generate`

##### Example Request

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/waltid.tenant1.kms1/kms-service-api/keys/generate' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "backend": "aws",
  "keyType": "secp256r1",
  "config": {
    "auth": {
      "region": "eu-central-1"
    },
    "keyName": "my-signing-key",
    "tags": {
      "environment": "production",
      "purpose": "credential-signing"
    }
  }
}'
```

**Body Parameters**

- `backend`: `"aws"` for SDK-based (supports IRSA)
- `keyType`: One of the supported algorithms above
- `config.auth.region`: AWS region where to create the key (e.g., `"eu-central-1"`)
- `config.keyName`: _(optional)_ Human-readable alias (must be unique)
- `config.tags`: _(optional)_ The metadata tags to add to the key for [AWS](https://docs.aws.amazon.com/kms/latest/developerguide/tagging-keys.html)

---

##### Example Response

```json
{
  "_id": "waltid.tenant1.kms1.my-signing-key",
  "key": {
    "type": "aws",
    "config": {
      "auth": { "region": "eu-central-1" }
    },
    "id": "a1b2c3d4-5678-90ab-cdef-EXAMPLE",
    "_keyType": "secp256r1"
  },
  "parent": "waltid.tenant1.kms1"
}
```

**Response codes:**
- `201 Created` – Success
- `400 Bad Request` – Invalid configuration
- `401 Unauthorized` – Missing/invalid token
- `403 Forbidden` – Insufficient permissions
- `409 Conflict` – Key alias already exists

### With explicit access keys

Use when running outside AWS or without IAM roles. Same endpoint as above — only the request body's `backend` and `config.auth` shape change:

```json
{
  "backend": "aws-rest-api",
  "keyType": "secp256r1",
  "config": {
    "auth": {
      "accessKeyId": "AKIAIOSFODNN7EXAMPLE",
      "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
      "region": "eu-central-1"
    }
  }
}
```

**Note:** Rotate access keys regularly. IRSA is preferred for production. Instead of passing keys inline in every request, you can also define a reusable **KMS profile** in `resource-access.conf` and reference it by name via `configRef` — see [KMS Profiles](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/resource-access.md#kms-profiles) for details.

**Alternative: instance role name (EC2/ECS)**

If the host running the Enterprise Stack already has an IAM role attached (EC2/ECS instance profile), you can omit `accessKeyId`/`secretAccessKey` and provide `config.auth.roleName` (matching that role's name) instead. The `aws-rest-api` backend then fetches temporary credentials from the instance metadata service (IMDSv2) for that role:

```json
{
  "backend": "aws-rest-api",
  "keyType": "secp256r1",
  "config": {
    "auth": {
      "roleName": "my-instance-role",
      "region": "eu-central-1"
    }
  }
}
```

**Always set `roleName` explicitly to the instance's actual attached role** — if it doesn't match, the request fails with a role mismatch error.

## Error handling

### 409 Conflict - Alias already exists

```json
{
  "exception": true,
  "id": "KeyAlreadyExistsException",
  "status": "Conflict",
  "code": "409",
  "message": "A key with name 'my-signing-key' already exists."
}
```

The Enterprise Stack attempts to clean up the orphaned key. If cleanup fails due to missing `kms:ScheduleKeyDeletion` permission, the error is logged but 409 is still returned.

### 400 Bad Request - Invalid configuration

```json
{
  "exception": true,
  "id": "IllegalArgumentException",
  "status": "Bad Request",
  "code": "400",
  "message": "Invalid replica region 'us-central-1'. Valid AWS regions: ..."
}
```

Common causes:
- Typo in region name (e.g., `us-central-1` vs `us-west-1`)
- Multi-region config without `multiRegion: true`
- Invalid failover configuration

## Advanced: Multi-region keys with failover

For high-availability deployments, AWS KMS supports [multi-region keys](https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html) - keys that share the same cryptographic material across multiple regions.

### Benefits

- **Disaster recovery**: Continue operations if a region becomes unavailable
- **Lower latency**: Place replicas closer to workloads
- **Compliance**: Keep keys in required regions while maintaining DR

### How it works

**Primary key**: Created in your chosen region. Supports all operations.

**Replica keys**: Created in additional regions. Share:
- Same key material (can verify signatures from any region)
- Same key ID (starts with `mrk-`)
- Independent policies and metadata

**Failover**: When enabled, signing operations automatically retry in replica regions if the primary is unavailable.

### Configuration

| Field | Required | Description |
|-------|----------|-------------|
| `multiRegion` | Yes | Set to `true` |
| `replicaRegions` | Optional | AWS regions for replicas, e.g., `["eu-west-1", "us-east-1"]` |
| `enableFailover` | Optional | Auto-retry in replicas when primary unavailable |
| `failoverOrder` | Optional | Custom region order. Default: `[primary, ...replicaRegions]` |

### Validation rules

Configuration is validated **before** creating any keys:

- All regions must be valid AWS regions
- `multiRegion: true` required for replica/failover config
- Replica regions cannot duplicate primary
- `failoverOrder` regions must exist in primary + replicas
- `enableFailover` requires `replicaRegions`

Validation failures return `400 Bad Request` with a helpful message.

### Example: Multi-region key with failover

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/waltid.tenant1.kms1/kms-service-api/keys/generate' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "backend": "aws",
  "keyType": "secp256r1",
  "config": {
    "auth": {
      "region": "eu-central-1"
    },
    "keyName": "production-signing-key",
    "multiRegion": true,
    "replicaRegions": [
      "eu-west-1",
      "us-east-1"
    ],
    "enableFailover": true,
    "failoverOrder": [
      "eu-central-1",
      "eu-west-1",
      "us-east-1"
    ],
    "tags": {
      "environment": "production",
      "ha-enabled": "true"
    }
  }
}'
```

**What this creates:**
1. Primary key in `eu-central-1`
2. Replica in `eu-west-1`
3. Replica in `us-east-1`
4. Automatic failover: tries regions in order

### Failover behavior

**Normal operation:**
- All requests use the primary region
- No performance impact

**Primary unavailable:**
- Automatically retries in first replica
- Transparent to the caller

**Multiple regions unavailable:**
- Continues through `failoverOrder`
- Returns error only if all regions fail

**Performance:** Failover latency is added only during actual outages.

## Next steps

- [Use KMS keys for credential issuance](https://docs.walt.id/enterprise-stack/services/issuer-service/credential-issuance/vc-oid4vc.md)
- [Configure DID methods with KMS](https://docs.walt.id/enterprise-stack/services/did-service/overview.md)
