# Nitro Enclaves Attestation Process This document aims to give an in-detail overview of the entire Nitro Enclaves attestation flow and especially on the intrinsic restrictions of the Attestation Document. We will go through the attestation document definition, what is generated by the AWS Nitro system when we issue an __Attestation__ command and how a Key Management Service should process this Attestation Document. ## 1. Attestation in the Nitro Enclaves World The purpose of attestation is to prove that an enclave is a trustworthy entity, based on the code and configuration that is running within a particular enclave. The root of trust for the enclave resides within the __AWS Nitro system__, which provides __Attestation Documents__ to the enclave, signed by the __AWS Nitro Attestation Public Key Infrastructure (PKI)__. The root of trust component for the attestation is the Nitro hypervisor, that contains information about the enclave, including platform configuration registers (PCR) and produces an attestation document that contains details of the enclave: enclave signing key, hash of the enclave image, the instance it is attached to, the role attached to the instance and more. Attestation documents are signed by the AWS Nitro Public Key Infrastructure (PKI), which includes a published certificate authority that can be incorporated into any service. ## 2. Attestation Document ### 2.1. Attestation Document Definition The enclave is able to request from the Nitro hypervisor an attestation document that allows an external service to verify caller’s identity. The attestation document generated by the Nitro system follows this specific format, encoded in [Concise Binary Object Representation (CBOR)](https://www.rfc-editor.org/info/std94) and signed using [CBOR Object Signing and Encryption (COSE)](https://www.rfc-editor.org/info/std96). Attestation documents are signed by the AWS Nitro Attestation PKI, which includes a root certificate for the commercial AWS partitions that can be found [here](https://aws-nitro-enclaves.amazonaws.com/AWS_NitroEnclaves_Root-G1.zip) (SHA256 8cf60e2b2efca96c6a9e71e851d00c1b6991cc09eadbe64a6a1d1b1eb9faff7c). The root certificate is based on an ACM PCA private key and has a lifetime of 30 years. The subject of the CA should follow this format: ``` CN=.nitro-enclaves, C=US, O=Amazon, OU=AWS ``` An example of CN: `aws.nitro-enclaves`. ### 2.2. Attestation Document Specification Below you can find the structure of an Attestation Document. ``` AttestationDocument = { module_id: text, ; issuing Nitro hypervisor module ID timestamp: uint .size 8, ; UTC time when document was created, in ; milliseconds since UNIX epoch digest: digest, ; the digest function used for calculating the ; register values pcrs: { + index => pcr }, ; map of all locked PCRs at the moment the ; attestation document was generated certificate: cert, ; the infrastructure certificate used to sign this ; document, DER encoded cabundle: [* cert], ; issuing CA bundle for infrastructure certificate ? public_key: user_data, ; an optional DER-encoded key the attestation ; consumer can use to encrypt data with ? user_data: user_data, ; additional signed user data, defined by protocol ? nonce: user_data, ; an optional cryptographic nonce provided by the ; attestation consumer as a proof of authenticity } cert = bytes .size (1..1024) ; DER encoded certificate user_data = bytes .size (0..1024) pcr = bytes .size (32/48/64) ; PCR content index = 0..31 digest = "SHA384" ``` __2.2.1 Attestation Document Operation__ The enclave and the service that wants to attest the enclave need to first agree on a common protocol to follow. The optional parameters in the Attestation Document (`public_key`, `user_data`, `nonce`) allow the enclave and the entity to set up a variety of protocols depending on the security properties that the service and the enclave want to guarantee to each other. Services that rely on attestation need to define a protocol that can meet those guarantees, and the enclave software needs to agree to and follow these protocols. An enclave wishing to attest to a specific service will first have to open a TLS connection to that service and verify that the service's certificates are valid. These certificates will have to be included in the enclave during enclave build. __Note:__ A TLS session is not absolutely required, however, this integrates more readily with already existing services. __2.2.2 Attestation Document Optional Fields__ __Nonce__ - To avoid impersonation attacks where a service could re-use submitted attestation documents, attestation requires a challenge-response workflow. For example a Nitro Enclave application may connect to a service and initiate a “hello” request. The response to this request should include a nonce. The application will then use this nonce to request a new attestation document from the Nitro system. The Nitro System will respond with a signed attestation document that encapsulates the nonce, as well as the details of the application. The nonce needs to have a limited lifetime and most services should consider treating this nonce as a one-time token. The nonce guarantees that the service is talking to a live enclave and not some third party which is reusing an older attestation document. __Public Key__ - To allow a service to encrypt data for use by the enclave, and only the enclave, the attestation will require a public key. A public/private key pair will be generated by the application, and the application will include the public key in the attestation request. The Nitro system responds with an attestation document that contains the public key and other details of the application. The service uses the public key to encrypt the data that can be decrypted only by the enclave or to validate signatures made by the enclave. API calls that do not have mutating effects on the service side might consider not using a nonce and just encrypting responses to such a public key, since only the correct enclave could decrypt the responses. Note that this mechanism would not provide by itself protection against replay attacks. __User data__ - This is signed data that the enclave can include in the Attestation Document. This could have a specific meaning to the service, for example, this field could carry the API parameters, or at least a signature of the API parameters, which would guarantee that the Attestation Document could not be used for any other purpose than the intended one. It could also be used by the enclave to sign off on an operation it has completed, allowing a service to trust that the result comes from a valid source. ## 3. Attestation Document Validation When you issue in enclave an __Attestation__ call (to Nitro hypervisor) you will receive a binary blob containing the Signed Attestation Document. The Signed Attestation Document is a CBOR encoded, COSE signed object, using the `COSE_Sign1` signature structure. The overall validation flow can be seen in the following figure: ![Attestation Document Validation Flow](figures/attestation_document_validation_flow.png) ### 3.1. COSE and CBOR The `COSE_Sign1` signature structure is used when only one signature is going to be placed on a message. The parameters dealing with the content and the signature are placed in the same pair of buckets rather than having the separation of `COSE_Sign`. The structure can be encoded as either tagged or untagged depending on the context it will be used in. A tagged `COSE_Sign1` structure is identified by the CBOR tag 18. The CBOR object that carries the body, the signature, and the information about the body and signature is called the `COSE_Sign1` structure. The `COSE_Sign1` structure is a CBOR array. The fields of the array in order are: ``` [ protected: Header, unprotected: Header, payload: This field contains the serialized content to be signed, signature: This field contains the computed signature value. ] ``` When we talk about an Attestation Document we have the following (__Disclaimer__: this is text representation, all the values are actually CBOR encoded): ``` 18(/* COSE_Sign1 CBOR tag is 18 */ {1: -35}, /* This is equivalent with {algorithm: ECDS 384} */ {}, /* We have nothing in unprotected */ bstr .size (1..16384), /* Attestation Document */ bstr .size (32/48/64) /* Signature */ ) ``` ### 3.2. Syntactical Validation In the previous subsection we saw how the Signed Attestation Document looks like. We’re interested in extraction of the actual attestation document, validate that it was properly signed and its content is correct. When we write an Attestation Document validator we must ensure that we have access to the root certificate during computation. This can be seen as a prerequisite for Attestation Document validation. We’re going to receive from the Nitro hypervisor the Signed Attestation Document, which is an CBOR encoded, COSE signed object, using the `COSE_Sign1` signature structure. Briefly, we’re going to handle a bytes blob. The main flow: * Decode the CBOR object and map it to a `COSE_Sign1` structure; * Extract the Attestation Document from the `COSE_Sign1` structure; * Verify the certificates chain; * Ensure that the Signed Attestation Document was correctly signed. __3.2.1. COSE decode and validate signature operations__ We’re talking here about two operations: decoding from COSE and ensuring that the signature is correct. This can be done in two ways: implementing from zero the RFC from or using tools already existing tools for COSE computing. __3.2.2. Syntactical validation__ We’re going to process the payload from the Signed Attestation Document. The output will be a structure which follows the Attestation Document specification. We must ensure that the following restrictions are present in the currently processed CBOR object: __3.2.2.1. Check if the required fields are present__ The Attestation Document has a series of fields. For these fields we have the following restrictions: * No field content can be null; * List of Mandatory / Optional fields is the following: ``` Attestation Document Allowed Fields { "module_id" => Mandatory "digest" => Mandatory "timestamp" => Mandatory "pcrs" => Mandatory "certificate" => Mandatory "cabundle" => Mandatory "public_key" => Optional "user_data" => Optional "nonce" => Optional } ``` __Note:__ If any of the previously stated rules is not fulfilled, we __fail__ the validation. __3.2.2.2. Check content__ For every value present in the CBOR containing the attestation document, we adhere to the following restrictions (__Note:__ we talk about CBOR standard types): * `module_id` ``` Type: Text String $length != 0 /* Module ID must be non-empty */ ``` * `digest` ``` Type: Text String $value ∈ {"SHA384"} /* Digest can be exactly one of these values */ ``` * `timestamp` ``` Type: Integer $value > 0 /* Timestamp must be greater than 0 */ ``` * `pcrs` ``` Type: Map $size ∈ [1, 32] /* We must have at least one PCR and at most 32 */ /* The following rules apply for EACH PCR existing in the `pcrs` map. * We'll use an additional notation: pcrIdx for PCR index. * Note: CBOR can manage several types of keys, hence we must ensure that keys * also have the right type. */ Type pcrIdx: Integer /* The type of the key must be integer */ pcrIdx ∈ [0, 32) /* A PCR index can be in this interval [0, 32) */ Type pcrs[pcrIdx]: Byte String /* The type of a PCR content must be Byte String */ $length pcrs[pcrIdx] ∈ {32, 48, 64} /* Length of PCR can be one of this values {32, 48, 64} */ ``` * `cabundle` ``` Type: Array $length > 0 /* CA Bundle is not allowed to have 0 elements */ Type cabundle[idx]: Byte String /* CA bundle entry must have Byte String type */ $length cabundle[idx] ∈ [1, 1024] /* CA bundle entry must have length between 1 and 1024 */ ``` * `public_key` ``` Type: Byte String $length ∈ [1, 1024] ``` * `user_data` ``` Type: Byte String $length ∈ [0, 512] ``` * `nonce` ``` Type: Byte String $length ∈ [0, 512] ``` __3.2.3. Semantical validation__ Until now, we ensured that our CBOR encoded Attestation Document has the right fields and all the restrictions are present. Now we must verify the certificates (their validity, their critical extensions, the fact that they are correctly chaining - for this we must have access to the public root certificate, this certificate being guaranteed by AWS). An Attestation Document will always have its CA bundle in a well established order. This order is as it follows: ``` [ROOT_CERT, INTERM_1, INTERM_2, .... INTERM_N] 0 1 2 N - 1 ``` We must be aware of this ordering since already existing tools might require the order to be reversed. For example Java’s CertPath requires the order to be the other way around. When we begin the validation, we start from the Attestation Document CA bundle and generate the desired chain, where `TARGET_CERTIFICATE` is the certificate attach in the Attestation Document (`message[“certificate”]`): ``` [TARGET_CERT, INTERM_N, ..... , INTERM_2, INTERM_1, ROOT_CERT] ``` __Note:__ All certificates are [X509 Certificates](https://www.rfc-editor.org/info/rfc5280). __3.2.3.1. Certificates validity__ For all the certificates in the newly generated list of X509 certificates we must ensure that they are still valid: if the current date and time are within the validity period given in the certificate. This also applies for the root certificate we are checking the chain against. __3.2.3.2. Certificates critical extensions: basic constraints__ The X509 Public Key Infrastructure Certificate RFC states the following: the basic constraints extension identifies whether the subject of the certificate is a CA and how deep a certification path may exist through that CA. The `pathLenConstraint` field is meaningful only if CA is set to TRUE. In this case, it gives the maximum number of CA certificates that may follow this certificate in a certification path. A value of zero indicates that only an end-entity certificate may follow in the path. Where it appears, the pathLenConstraint field must be greater than or equal to zero. Where pathLenConstraint does not appear, there is no limit to the allowed length of the certification path. This extension must appear as a critical extension in all CA certificates. This extension should appear in end entity certificates. The identifier for basic constraints is `2.5.29.19`. ``` id-ce-basicConstraints OBJECT IDENTIFIER ::= { id-ce 19 } BasicConstraints ::= SEQUENCE { CA BOOLEAN DEFAULT FALSE, pathLenConstraint INTEGER (0..MAX) OPTIONAL } ``` Based on this, we want to make sure that the certificates in our previously built chain have proper pathLenConstraint set, based on its key usage. For pseudocode please check the following subsection. __3.2.3.3. Certificates critical extensions: key usage__ Based on the X509 Public Key Infrastructure Certificate RFC the key usage extension defines the purpose (encipherment, signature, certificate signing and others) of the key contained in the certificate. The usage restriction might be employed when a key that could be used for more than one operation is to be restricted. The Key Usage identifier is `2.5.29.15`. The key usages are the following: ``` KeyUsage ::= BIT STRING { digitalSignature (0), nonRepudiation (1), keyEncipherment (2), dataEncipherment (3), keyAgreement (4), keyCertSign (5), cRLSign (6), encipherOnly (7), decipherOnly (8) } ``` We’re interested in digitalSignature and keyCertSign. There are some brief definitions for these two: * The `digitalSignature` bit is asserted when the subject public key is used with a digital signature mechanism to support security services other than non-repudiation (bit 1), certificate signing (bit 5), or revocation information signing (bit 6). * The `keyCertSign` bit is asserted when the subject public key is used for verifying a signature on certificates. This bit may only be asserted in CA certificates. We have the following restrictions for the root certificate: * `pathLenConstraint` is greater or equal than the chain size; * Key usage: `keyCertSign` bit is present. For all the certificates in the chain excepting the target certificate (the first one) we must ensure: * no certificate was used to create a chain longer than its `pathLenConstraint`; * Key usage: `keyCertSign` bit is present. For the target certificate (the first one) we must ensure: * Key usage: `digitalSignature` bit is present; * `pathLenConstraint` must be undefined since this is a client certificate. __3.2.4. Certificates chain__ In general, a chain of multiple certificates may be needed, comprising a certificate of the public key owner (the end entity) signed by one CA, and zero or more additional certificates of CAs signed by other CAs. Such chains, called certification paths, are required because a public key user is only initialized with a limited number of assured CA public keys. Certification path validation procedures for the Internet PKI are based on the algorithm supplied in X.509. Certification path processing verifies the binding between the subject distinguished name and/or subject alternative name and subject public key. The binding is limited by constraints that are specified in the certificates that comprise the path and inputs that are specified by the relying party. The basic constraints and policy constraints extensions allow the certification path processing logic to automate the decision making process. We do not use CRL, so it must be disabled when doing the validation. Starting from our root path and the generated certificate chain, the chain validation looks like: ``` validateCertsPath(certChain, rootCertficate) { /* The trust anchor is the root CA to trust */ trustAnchors.add(rootCertificate); /* We need PKIX parameters to specify the trust anchors * and disable the CRL validation */ validationParameters = new PKIXParameters(trustAnchors); certPathValidator = CertPathValidator.getInstance(PKIX); validationParameters.setRevocationEnabled(false); /* We are ensuring that certificates are chained correctly */ certPathValidator.validate(certPath, validationParameters); } ```