Request Templates
Blocky AS uses TEEs to transform a set of request templates into API requests and return a set of attestations over API responses. The template allows you encrypt sensitive data that can be decrypted by the TEE only. In other words, even Blocky cannot see that data.
To demonstrate that capability, let's fetch the current weather of Blocky's Headquarters in Bozeman, Montana, or whatever location you prefer. You will need an API key from the weather API. You can get a free API key from the Tomorrow API.
Next, create a collection of request templates as requests.json
:
[
{
"environment": {
"location": "Bozeman",
"apikey": "super-secret"
},
"template": {
"method": "GET",
"url": "https://api.tomorrow.io/v4/weather/realtime?location={{location}}&apikey={{apikey}}&units=imperial",
"body": {},
"header": {
"Content-Type": [
"application/json"
]
}
}
}
]
In each request template, just one in the above example, you specify:
environment
(optional)- A collection of variables to use in the template. The variables can contain secrets for accessing the API, or other information you do not want to leak to Blocky, or to the third parties that verify Blocky AS attestations. These variables will be encrypted by
bky-as
and decrypted by the server.
- A collection of variables to use in the template. The variables can contain secrets for accessing the API, or other information you do not want to leak to Blocky, or to the third parties that verify Blocky AS attestations. These variables will be encrypted by
template
- (required)- The template the Blocky AS server will use to form an API request, including:
method
- (required) The HTTP method for the requesturl
- (required) The URL template for the requestbody
- (optional) The body template of the requestheader
- (optional) The header template of the request
- Note that the
url
,body
, andheader
fields can all use the variables from theenvironment
collection. Blocky AS servers use the Mustache syntax in thetemplate
to replace variable names with values from theenvironment
.
- The template the Blocky AS server will use to form an API request, including:
Finally, update the environment.apikey
with your API key from tomorrow.io.
Attestations
To obtain Blocky AS attestations using the request templates in requests.json
, run:
cat requests.json | ./bky-as attest-api-call > out.json
The resulting out.json
contains the attestations for each templated request. Diving deeper into the structure of out.json
with jq
,
cat out.json | jq keys
shows that out.json
contains
[
"api_calls",
"enclave_attested_application_public_key"
]
Let's go deeper into the enclave_attested_application_public_key
and run:
cat out.json | jq '.enclave_attested_application_public_key'
which produces shows an enclave attestation data structure:
{
"Platform": "nitro",
"PlAttests": [
"hEShATgioFkUq6...",
"hEShATgioFkRHa...",
]
}
Platform
contains type of the platform on which the Blocky AS server runs. If you are running the demo usinglocal-server
inconfig.toml
, the"Platform": "plain"
indicates that the server is not running in a TEE.PlAttests
contains platform attestations created by the TEE hardware module. The platform attestations collectively attest a serialized application public key that is specific to an instance of a Blocky AS server.
Next, let's inspect the api_calls
. In general, the i-th entry of api_calls
in out.json
corresponds to the i-th request in requests.json
. Here, we have only one request, so we have only one response. Let's explore the response.
cat out.json | jq '.api_calls[0] | keys'
shows that it contains
[
"claims",
"transitive_attestation"
]
But what are these things? Let's discuss them. Recall that each instance of Blocky AS has a unique application public key with an associated private key. We use that private key to sign data, which we call claims. We call the data structure containing the claims, the signature, and other information needed for checking the signature a transitive attestation. (In fact, transitive attestations follow the JWT standard.)
In the context of Blocky AS making an API call, we create a transitive attestation where the claims are the fields needed for interpreting and proving the authenticity of an API call. These fields include the request template, the upstream API server's response, and a certificate chain among others.
Now, let's see how we use this transitive attestation in practice. When we call bky-as attest-api-call
, it performs four steps. First, it verifies the enclave_attested_application_public_key
. Second, it extracts the application public key. Third, it uses the public key to verify the transitive_attestation
. Fourth, it extracts the request and response data from the transitive_attestation
as claims
.
These claims contain the request template in request
, the time at which the request was issued at, iat
, and of course, the response
including the HTTP status_code
, response body
as well as other data.
We can see the response body with the following command:
cat out.json | jq -r '.api_calls[0].claims.response.body' | base64 -d | jq .
Now, let's say that you want to archive Blocky AS attestations for later, or send them to a third party for verification. You can store the enclave_attested_application_public_key
and transitive_attestations
individually in an archival system of your choice, or package them up in a single file to-verify.json
with:
jq '{
enclave_attested_application_public_key: .enclave_attested_application_public_key,
transitive_attested_api_calls: [ .api_calls[] | .transitive_attestation ]
}' out.json > to-verify.json
You, or a third party, can at verify this collection with bky-as
by running:
cat to-verify.json | ./bky-as verify > verified.json
Again you can use jq
to explore the results, but you will find that verified.json
is identical to out.json
. You can double-check that by running:
diff out.json verified.json
which will show no output, indicating that contents of the files are the same.
Protocol
For a final deep dive, let's explore the protocol used in Blocky AS.
You can interact with Blocky AS in the same way in which you would any other API server. You can use CLI tools like curl
, platforms like postman
, or language specific libraries like requests
in python. We chose that design so that anybody can integrate Blocky AS into their system in a way that works best for them. However, implementing the protocol requires some developer effort.
We have a few Go libraries that we use for testing. We used those libraries to build a tool called bky-as
that does most of the work for you. And, since it is written in Go, we can build static binaries that you can use on your system, use in a CI/CD pipeline, or in production.
The figure below shows the details of the interactions between a user, bky-as
, the Blocky AS server running on a TEE, and an upstream API server.
The Blocky AS server generates the
application_public_key
/application_private_key
on startup. The server will use theapplication_private_key
to sign transitive attestations.Also on startup, the Blocky AS server generates the
encryption_public_key
/encryption_private_key
keypair. The server will useencryption_private_key
to decrypt elements of theencrypted_environment
.The user creates
requests.json
containing an array of requests:[ { "environment": { "user-id": "secret_user_id", "user-password": "secret_password" }, "template": { "method": "GET", "url": "https://upstream-server.com/data?user={{user-id}}", "header": { "password": "{{user-password}}" } } } ]
where
environment
specifies user secrets andtemplate
specifies the template for the HTTP request. The server will render the request from the template and the decrypted environment.The user pipes
requests.json
tobky-as attest-api-call
to obtain a transitive attested API call.bky-as
issues aGET
request to the Blocky AS server/enclave-attested-application-public-key
endpoint.The Blocky AS server creates an
enclave_attested_application_public_key
, which contains theapplication_public_key
and thecode_measurement
of the Blocky AS code running on the enclave and is signed by the AWS Nitro private key.The Blocky AS server replies with the
enclave_attested_application_public_key
to thebky-as
CLI.bky-as
verifies theenclave_attested_application_public_key
using the AWS Nitro Enclave well-knownnitro_certificate
and extracts theapplicaiton_public_key
and thecode_measurement
. Moreover, it verifies that theenclave_attested_application_public_key.code_measurement
is on the list ofacceptable_measurements
inconfig.toml
.To obtain the
encryption_public_key
,bky-as
issues aGET
request to the Blocky AS server/transitive-attested-encryption-key
endpoint.The Blocky AS server creates a
transitive_attested_encryption_key
, which is a JWT containing theencryption_public_key
signed with theapplication_private_key
.The Blocky AS server replies with the
transitive_attested_encryption_key
to thebky-as
CLI.bky-as
checks the signature oftransitive_attested_encryption_key
using theapplication_public_key
to extract theencryption_public_key
.For each
request
,bky-as
encrypts elements of itsenvironment
using theencryption_public_key
encrypted_user_id = Encrypt("secret_user_id", encryption_public_key) encrypted_user_password = Encrypt("secret_user_password", encryption_public_key)
to create an encrypted environment (
encr_env
) and aprotected_request
:{ "encr_env": { "user-id": encrypted_user_id, "user-password": encrypted_user_password }, "template": { "method": "GET", "url": "https://upstream-server.com/data?user={{user-id}}", "header": { "password": "{{user-password}}" } }
To obtain a
transitive_attested_api_call
,bky-as
issues aPOST
request to the Blocky AS server/transitive-attested-api-call
endpoint with theprotected_request
in thebody
.The Blocky AS server uses the
encryption_private_key
to decrypt the elements of theencr_env
in theprotected_request
user_id = Decrypt(encrypted_user_id, encryption_private_key) user_password = Decrypt(encrypted_user_password, encryption_private_key)
The Blocky AS server renders an HTTP request from the
template
and decrypted elements of theencr_env
and makes the HTTP request to an upstream API server.The Blocky AS server collects the upstream API server reply, and prepares a
response
claim, which contains the status code, headers, body, and the upstream server certificate chain.The Blocky AS server obtains a timestamp from the enclave's NTP-synchronized
kvm-clock
to prepare the "Issued At"iat
claim.The Blocky AS server prepares a
request
claim, which is set to the value ofprotected_request.template
.The Blocky AS server creates a
transitive_attested_api_call
, which is a JWT over therequest
,iat
, andresponse
claims signed with theapplication_private_key
.The Blocky AS server returns the
transitive_attested_api_call
.bky-as
checks eachtransitive_attested_api_call
using theapplication_public_key
and extracts therequest
,iat
, andresponse
claims corresponding to eachprotected_request
.bky-as
returns to the user the array ofrequest
,iat
, andresponse
claims corresponding to eachtransitive_attested_api_call
.