Files
acdl/docs/kms-provisioning.md
T
Jon Chery dee6d88d87 feat(P04): KMS ECDSA P-256 signing + DER->raw conversion (REQ-337, C-1.1, security-engineer)
---ci---
project: acdl
phase: 4
milestone: v1.28
status: execute
persona: security-engineer
---
2026-08-19 23:05:25 +00:00

2.0 KiB

KMS asymmetric key provisioning (C-1.1)

This document records the C-1.1 verification for the Nova OIDC signing KMS key and the provisioning path used by nova idp setup.

C-1.1 verification (P4)

C-1.1 requires verifying KMS asymmetric key support before implementation. The verification command is:

aws kms create-key \
  --key-spec ECC_NIST_P256 \
  --key-usage SIGN_VERIFY \
  --description nova-oidc-signing

Result on the P4 build host: AWS credentials are not available (Unable to locate credentials), so the live verification could not run. This is recorded as a P4 CI gate: the nova idp setup --check command (Wave 8) performs this verification when AWS creds are present and reports it as a missing prerequisite when they are not. The code proceeds against the documented KMS API (REQ-337); tests use a test ECDSA P-256 keypair + mocked boto3.client("kms") (no real AWS calls).

KMS asymmetric signing keys (ECC_NIST_P256 + SIGN_VERIFY) are GA in all commercial regions (announced 2020-11). The ECDSA_SHA_256 signing algorithm is supported. Confidence: high.

Key spec (REQ-337)

  • Key spec: ECC_NIST_P256 (NIST P-256 / secp256r1)
  • Key usage: SIGN_VERIFY
  • Signing algorithm: ECDSA_SHA_256 (JWS ES256)
  • Alias: alias/nova-oidc-signing
  • Rotation: manual, 90 days (matches D-069 CMK cadence). New key + re-point alias + JWKS serves both kids during overlap.

DER → raw ECDSA conversion (the #1 gotcha)

KMS sign() returns a DER-encoded ASN.1 ECDSA signature. JWS (RFC 7515 §3.1.3) requires the raw r‖s concatenation, each coordinate 32 bytes big-endian. The conversion (in core/kms_signing.py:der_to_raw_ecdsa):

from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
r, s = decode_dss_signature(der_sig)
raw = r.to_bytes(32, "big") + s.to_bytes(32, "big")

This is verified by tests/test_kms_signing.py and the CAP-037 round-trip test (tests/test_kms_roundtrip.py).