Using AWS KMS with the Anjuna Nitro Attestation Endpoint
In Using the Anjuna Nitro Attestation Endpoint, you learned to generate and verify attestation reports for Anjuna Nitro Enclaves.
In this guide, you will use the AWS KMS integration with the Anjuna Nitro Attestation Endpoint to dynamically access encrypted secrets using the attestation report.
Overview
As described in the AWS documentation, when an AWS KMS key is configured to use attestation, an application must include an attestation report with a public key in its KMS API requests. AWS KMS will verify the attestation report to confirm that the application runs in an AWS Nitro Enclave and check its PCR measurements for the expected values.
Then, AWS KMS will use the application attestation report’s UserPublicKey
to encrypt the response.
This ensures that only the enclave application can read the response,
even if an attacker is able to intercept and breach the TLS connection.
The following steps can be used to set up an AWS KMS key to be used with AWS Nitro Enclaves:
-
Set up a KMS key, which is described on Setting up an AWS KMS key in AWS KMS.
-
Encrypt one or more secrets in KMS as described on Using AWS KMS to encrypt a secret.
-
Update the KMS key policy to authorize your enclave as described on Updating the KMS policy to authorize AWS Nitro Enclaves.
Using this key, you can now encrypt secrets in a way that only an AWS Nitro Enclave could decrypt them. In your enclave application, the following steps are required to decrypt the secret:
-
Generate an ephemeral RSA keypair.
-
Use the Anjuna Nitro Attestation Endpoint to generate an attestation report, including the RSA public key as
publicKey
. -
Call the
kms:Decrypt
API, including the attestation report. -
Retrieve the response, which has been wrapped using
publicKey
. -
Use the RSA private key to unwrap the response into plaintext.
-
Use the plaintext secret.
These steps are used by the Anjuna Nitro Runtime’s encrypted configuration feature to inject secrets at boot time. For more flexible usage at runtime, you can also perform these steps by following the instructions in this guide.
Example application
This example shows how to write a Go program to safely decrypt using AWS KMS.
It uses the attester
and verifier
packages from Anjuna’s
go-nitro-attestation library.
Create an RSA keypair
First, generate an ephemeral RSA keypair with Golang’s standard crypto/rsa
library.
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
// handle error
Generate a new attestation report
Using the Anjuna Nitro Attestation Endpoint, generate a new attestation report,
passing rsaKey
as the publicKey
parameter:
attestDocReader, err := attester.GetAttestationReport(&rsaKey.PublicKey, nil, nil)
// handle error
Parse the attestation report
Parse the attestation report and confirm it was created successfully:
attestDoc, err := verifier.NewSignedAttestationReport(attestDocReader)
// handle error
Define the payload for AWS KMS
Use the AWS Golang SDK to
define the DecryptInput
request payload.
The AttestationDocument
is the raw bytes of the attestation report,
and the KeyEncryptionAlgorithm
is always RSAES_OAEP_SHA_256
.
var encryptedData []byte
var keyARN string
encryptedData = ... // assign here the encrypted blob
keyARN = ... // assign here the ARN of the KMS key
algo := "RSAES_OAEP_SHA_256"
decryptInput := &kms.DecryptInput{
CiphertextBlob: encryptedData,
Recipient: &kms.RecipientInfo{
AttestationDocument: attestDoc.Raw,
KeyEncryptionAlgorithm: &algo,
},
KeyId: &keyARN,
}
Send the decrypt request to AWS KMS
Using the same AWS Golang SDK, send the KMS Decrypt request.
var region string
region = ... // set region to the AWS region where the KMS key was created
kmsClient := kms.New(session.Must(session.NewSession(&aws.Config{
Region: aws.String(region),
})))
output, err := kmsClient.Decrypt(decryptInput)
// handle error
Since the attestation report was included in the request,
the response’s Plaintext
field will be nil
.
The payload will be returned in the field CiphertextForRecipient
,
in RFC 5652 RecipientInfo
format,
encrypted using the publicKey
RSA public key from earlier.
Unwrap the response and use the plaintext
Unfortunately, AWS’s Golang SDK does not yet provide a way to unwrap the CiphertextForRecipient
field.
Instead, the following example uses openssl
to unwrap the decrypted content.
err = os.WriteFile("/tmp/ciphertext", output.CiphertextForRecipient, os.ModePerm)
// handle error
err = os.WriteFile("/tmp/keypair", x509.MarshalPKCS1PrivateKey(keyPair), os.ModePerm)
// handle error
err = exec.Command("openssl", "cms", "-decrypt",
"-inform=DER",
"-in", "/tmp/ciphertext",
"-inkey", "/tmp/keypair",
"-out", "/tmp/output",
).Run()
// handle error
decryptedData, err := os.ReadFile("/tmp/output")
// handle error
Instead of shelling out to |
Now, you can use the plaintext data - decryptedData
in the example code above.
Conclusion
AWS Nitro Enclaves add additional security to the use of AWS KMS. When using AWS KMS alone, an attacker could access secrets by compromising an EC2 instance, an IAM role, or the TLS keys used to encrypt the data in-transit.
When using the AWS Nitro Enclaves integration with AWS KMS,
all of these threats are prevented.
An attacker cannot create an attestation report with the correct measurements,
and the publicKey
ensures secure communication even if TLS is compromised.