Provision the keys¶
Two keys, because an agreement key cannot sign its own binding signature.
Create them¶
aws kms create-key \
--key-usage KEY_AGREEMENT \
--key-spec ECC_NIST_P256 \
--description 'OpenPGP encryption subkey'
aws kms create-key \
--key-usage SIGN_VERIFY \
--key-spec RSA_4096 \
--description 'OpenPGP certification primary'
Give each an alias — the code takes a key id, ARN or alias, and an alias is what makes rotation a policy change rather than a code change.
aws kms create-alias --alias-name alias/security-contact-v1-encrypt --target-key-id <id>
aws kms create-alias --alias-name alias/security-contact-v1-certify --target-key-id <id>
Version the alias (-v1). Rotating an asymmetric KMS key means creating a new
key, and a certificate is bound to the key material it was built from — so a
rotation is a new certificate, and the alias should say which generation it is.
Grant the two roles¶
See IAM actions for the exact action lists. In short:
- reader:
GetPublicKeyandDeriveSharedSecreton the encryption key. - certifier:
GetPublicKeyon both keys,Signon the certification key.
The grant most often missed is the certifier's GetPublicKey on the
encryption key. Without it there is no subkey packet to emit, so there is
nothing to bind.
Check the split actually holds¶
Assert the refusal, not just the permission:
aws kms sign --key-id alias/security-contact-v1-certify \
--message <(echo test) --signing-algorithm RSASSA_PKCS1_V15_SHA_256
Run as the reader role, this must fail with AccessDenied. If it succeeds,
the split exists only on paper — see
the role split.
Confirm the shapes¶
aws kms describe-key --key-id alias/security-contact-v1-encrypt \
--query 'KeyMetadata.[KeyUsage,KeySpec,KeyAgreementAlgorithms]'
Expect KEY_AGREEMENT, ECC_NIST_P256, and ["ECDH"]. NewDeriverForKey
checks all three, so a mismatch is caught at construction — but catching it here
is cheaper than catching it in an application.