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:

  1. Set up a KMS key, which is described on Setting up an AWS KMS key in AWS KMS.

  2. Encrypt one or more secrets in KMS as described on Using AWS KMS to encrypt a secret.

  3. 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:

  1. Generate an ephemeral RSA keypair.

  2. Use the Anjuna Nitro Attestation Endpoint to generate an attestation report, including the RSA public key as publicKey.

  3. Call the kms:Decrypt API, including the attestation report.

  4. Retrieve the response, which has been wrapped using publicKey.

  5. Use the RSA private key to unwrap the response into plaintext.

  6. 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

openssl is not automatically available from within an Anjuna Nitro Enclave. In order for the command above to work, you must install openssl as part of the application’s Docker image.

Instead of shelling out to openssl, you can also use the AWS Nitro Enclave SDK written in C. You can use your language’s preferred library for binding C code; for example, Golang applications typically use Cgo.

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.