scitt-ccf-ledger

Reproducibility

The ledger application is running in a trusted execution environment and has a measurement associated with it which does not change. The goal is to reproduce the same measured value from the source code to ensure the code can be trusted, transparent and auditable.

The assumption here is that the original build was done using a Docker.

Prerequisites

You need a couple pieces of information to begin with:

Extract measurements from the report

The quote contains the attestation report that has the necessary measurements. Measurement will have the launch measurement of the guest virtual machine (aka utility VM or UVM) which was used to run the container. Host data will have the security policy measurement which was used to make sure no other container was launched except the one you are inspecting. More detail on implementing relying party logic can be found in Confidential ACI scheme documentation.

Extract image layers from security policy

Inspect the service join policy content and extract the Rego policy used to validate the container, there might be multiple join policies but they are keyed by their digest which is in the host data measurement above:

$ cat service-join-policy.json | jq -r '.snp.hostData["5ae7b14ee0c9c4fe267d191f25b20fffe24e29c4ac419c50501d20c869bbba65"]' | printf "%s" "$(cat)" > ccepolicy.rego

printf "%s" "$(cat)" is important to not to create additional line in the file as otherwise sha256 digests will not match.

The policy is used by the Utility VM (UVM) to launch a container group (this ledger application). It contains the image layers we want to compare and be able to reproduce.

You can use an OPA agent to convert the Rego file to JSON to then select layers or do that manually, e.g.:

$ curl -s -LO https://openpolicyagent.org/downloads/v1.1.0/opa_linux_amd64_static
$ chmod 755 opa_linux_amd64_static
$ ./opa_linux_amd64_static parse ccepolicy.rego -f json > ccepolicy.json

# jq find the containers rule and extract layers of each container
$ cat ccepolicy.json | jq '[ .rules[] | select(.head.name == "containers") | .head.value.value[].value[] | select(.[].value == "layers") | .[1].value | map(.value) ]' > containerlayers.json

containerlayers.json should contain layers of all containers, our application and a pause container with a single layer.

Note: image layers in the security policy use dmverity hashes, hence you will need to convert the built container image before comparison, see microsoft/integrity-vhd CLI.

Note: In the example here the policy was created with the az confcom acipolicygen CLI for the confidential Azure container instances (C-ACI). But the policy could also be for confidential AKS (C-AKS) and the location of the containers and layers would be slightly different.

Reproduce measurements

1. Verify security policy is the same

Host data contains the sha-256 digest of the security policy (e.g. 5ae7b14ee0c9c4fe267d191f25b20fffe24e29c4ac419c50501d20c869bbba65). The policy can be obtained like it was shown above and saved to a file ccepolicy.rego. The hash of the Rego policy is the same as the one in the report:

$ sha256sum ccepolicy.rego

5ae7b14ee0c9c4fe267d191f25b20fffe24e29c4ac419c50501d20c869bbba65  ccepolicy.rego

Now it is clear that the contents of the policy (image layers) can be trusted in the next step.

2. Build container and compare layers

3. Verify UVM

The details of how to reproduce the UVM (to compare it to a measurement in the report) are not ready yet.

UVM measurements can be authenticated using platform endorsements. To get platform endorsements:

$ curl -s --cacert cacert.pem https://<LEDGER-URL>/node/quotes/self > node-quote.json
$ cat node-quote.json | jq -r '.uvm_endorsements' | base64 -d > uvm_endorsements.cose
... verify cose signing envelope ...

UVM endorsement policy can also be seen in service-join-policy.json.