Skip to content

Why two keys

A certificate assembled here needs two KMS keys: an ECC_NIST_P256 KEY_AGREEMENT key for encryption, and an RSA_4096 SIGN_VERIFY key for certification. People reasonably ask why one will not do.

The structural reason

An OpenPGP certificate is a public key plus signatures. The encryption subkey is attached to the primary by a binding signature, which the primary makes.

A KEY_AGREEMENT key cannot sign anything — that is what the usage means, and KMS enforces it. So the encryption key cannot bind itself, and something else has to. That something is the certification primary.

This is not an AWS constraint. It is how OpenPGP certificates are built: the primary certifies, subkeys do the work. Any key service would need the same pair.

Why the primary is RSA

Because it must sign, and it must sign the way OpenPGP expects.

An OpenPGP RSA signature is defined as PKCS#1 v1.5. KMS offers RSASSA_PKCS1_V15_SHA_256/384/512, which is exactly right. It also offers PSS, which would produce a perfectly valid signature that no OpenPGP implementation will accept — the certificate would assemble cleanly and then be rejected by every consumer, which is a spectacularly annoying failure to diagnose.

The provider refuses PSS explicitly rather than letting it through.

An ECDSA primary would also be legal OpenPGP. RSA was chosen because it is what the estate's key specification already provisions, and because the RSA path was the one proven end to end against real keys.

Why the encryption key cannot be RSA

The mirror question, and the one that decided the whole design.

OpenPGP encrypts the session key into a PKESK packet using PKCS#1 v1.5 padding. KMS supports only RSAES_OAEP_SHA_1 and RSAES_OAEP_SHA_256 for RSA decryption — there is no PKCS#1 v1.5 decryption path at all.

An ENCRYPT_DECRYPT RSA key would provision perfectly and then decrypt nothing.

ECDH escapes this because it involves no padding: the shared secret feeds a KDF, and the result unwraps the session key. DeriveSharedSecret returns exactly the value that KDF takes. The full argument is in the core's why ECDH, not RSA.