diff --git a/attestationreport/attestationreport.go b/attestationreport/attestationreport.go index 1ed5b651..77f15a10 100644 --- a/attestationreport/attestationreport.go +++ b/attestationreport/attestationreport.go @@ -239,6 +239,8 @@ type ReferenceValue struct { Sgx *SGXDetails `json:"sgx,omitempty" cbor:"8,keyasint,omitempty"` Description string `json:"description,omitempty" cbor:"9,keyasint,omitempty"` EventData *EventData `json:"eventdata,omitempty" cbor:"10,keyasint,omitempty"` + + manifest Manifest } // AppDescription represents the attestation report @@ -276,6 +278,9 @@ type Environment struct { Value string `json:"value" cbor:"1,keyasint"` } +// Generic manifest +type Manifest interface{} + // AppManifest represents the attestation report // element of type 'App Manifest' type AppManifest struct { @@ -387,3 +392,11 @@ type AttestationReport struct { CompanyDescription []byte `json:"companyDescription,omitempty" cbor:"5,keyasint,omitempty"` DeviceDescription []byte `json:"deviceDescription" cbor:"6,keyasint"` } + +func (r *ReferenceValue) GetManifest() Manifest { + return r.manifest +} + +func (r *ReferenceValue) SetManifest(m Manifest) { + r.manifest = m +} diff --git a/attestationreport/validationreport.go b/attestationreport/validationreport.go index a1743192..811ba897 100644 --- a/attestationreport/validationreport.go +++ b/attestationreport/validationreport.go @@ -140,7 +140,8 @@ type DigestResult struct { Description string `json:"description,omitempty"` // Optional description, measured PCR in case of PCR result Success bool `json:"success"` // Indicates whether match was found Type string `json:"type,omitempty"` // On fail, indicates whether digest is reference or measurement - EventData *EventData `json:"eventdata,omitempty"` // data that was included from bioseventlog + EventData *EventData `json:"eventData,omitempty"` // data that was included from bioseventlog + CtrData *CtrData `json:"ctrData,omitempty"` // data that was included from container log } type VersionCheck struct { diff --git a/est/estserver/snp.go b/est/estserver/snp.go index 7c8adb44..7aaaa5ae 100644 --- a/est/estserver/snp.go +++ b/est/estserver/snp.go @@ -63,6 +63,8 @@ func (s *Server) unlockVcekMutex() { // in DER format from the cache or downloads it from the AMD server if not present func (s *Server) getVcek(chipId []byte, tcb uint64) (*x509.Certificate, error) { + log.Tracef("Fetching VCEK for chip ID %v, TCB %x", hex.EncodeToString(chipId), tcb) + // Allow only one download and caching of the VCEK certificate in parallel // as the AMD KDF server allows only one request in 10s s.lockVcekMutex() diff --git a/example-setup/metadata-raw/snp.referencevalue.template b/example-setup/metadata-raw/snp.referencevalue.template new file mode 100644 index 00000000..791b675f --- /dev/null +++ b/example-setup/metadata-raw/snp.referencevalue.template @@ -0,0 +1,28 @@ +{ + "type": "SNP Reference Value", + "sha384": "", + "snp": { + "version": 0, + "caFingerprint": "", + "policy": { + "type": "SNP Policy", + "SingleSocket": false, + "Debug": false, + "Migration": false, + "Smt": false, + "AbiMajor": 0, + "AbiMinor": 0 + }, + "fw": { + "build": 0, + "major": 0, + "minor": 0 + }, + "tcb": { + "bl": 0, + "tee": 0, + "snp": 0, + "ucode": 0 + } + } +} diff --git a/example-setup/setup-cmc-snp b/example-setup/setup-cmc-snp new file mode 100755 index 00000000..db08453d --- /dev/null +++ b/example-setup/setup-cmc-snp @@ -0,0 +1,58 @@ +#!/bin/bash + +set -euo pipefail + +trap '[ $? -eq 0 ] && exit 0; printf "%s failed\n" "$0"' EXIT +dir="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P)" +source "${dir}/utils.sh" +export PATH=${PATH}:${HOME}/go/bin + +if [[ "$#" -ne 3 ]]; then + echo "Usage: ./setup-cmc " + exit +fi + +cmc=$(set -e; abs_path "$1") +data=$(set -e; abs_path "$2") +ser="${3}" +client="docker" + +if [[ ! -d "${cmc}" ]]; then + echo "CMC directory does not exist. Did you clone the repository? Abort.." + exit 1 +fi + +if [[ -d "${data}" ]]; then + echo "Data directory does already exist. Please choose a new directory. Abort.." + exit 1 +fi + +echo "Using CMC: ${cmc}" +echo "Using ${data} as directory for local data" + +# Create a folder for the cmc configuration and metadata +mkdir -p "${data}" + +# Install dependencies +sudo apt install -y moreutils golang-cfssl build-essential zlib1g-dev libssl-dev jq + +# Install virtee sev-snp measure tools +git clone https://github.com/virtee/sev-snp-measure "${data}/" + +# Build CMC +cd "${cmc}" +echo "Building CMC.." +go build ./... + +# Install CMC to $GOPATH/bin +echo "Installing CMC" +go install ./... + +# Copy metadata templates +cp -r "${cmc}/example-setup/"* "${data}" + +# Generate a PKI +"${data}/setup-pki" -i "${data}" -o "${data}/pki" + +# Generate and sign platform (RTM and OS) metadata +"${data}/update-platform-snp" "${data}" "${ser}" \ No newline at end of file diff --git a/example-setup/update-container-manifest-live b/example-setup/update-container-manifest-live index 4c96a2fa..5dcdaa3c 100755 --- a/example-setup/update-container-manifest-live +++ b/example-setup/update-container-manifest-live @@ -56,12 +56,12 @@ echo "Creating reference values for container: ${target} with args ${args}" echo "Using ${data} as directory for local data" # Delete temporary manifests -rm -rf "${tmp}"/* +rm -rf "${tmp:?}"/* # Temporarily configure runc to generate reference values if [ ! -f "${runc_config}" ]; then exists=false - sudo printf "{\"generateRefVals\":\"true\"}" > "${runc_config}" + echo "{\"generateRefVals\":\"true\"}" | tee "${runc_config}" else exists=true json=$(cat "${runc_config}") @@ -76,21 +76,21 @@ sudo rm -f /tmp/container-refs echo "Generating reference values for ${client} client" if [[ "${client}" == "ctr" ]]; then containers=("${target}") - sudo ctr image pull ${target} + sudo ctr image pull "${target}" set +e - sudo ctr run --detach ${args} ${target} REF_CONTAINER + sudo ctr run --detach ${args} "${target}" REF_CONTAINER sudo ctr task kill -s SIGKILL REF_CONTAINER sudo ctr container delete REF_CONTAINER set -e elif [[ "${client}" == "shim" ]]; then containers=("${target}") - sudo ctr image pull ${target} + sudo ctr image pull "${target}" set +e - sudo ctr run --runtime ${runtime} -t --rm ${args} ${target} CMC_GENERATE_APP_MANIFEST + sudo ctr run --runtime "${runtime}" -t --rm ${args} "${target}" CMC_GENERATE_APP_MANIFEST set -e elif [[ "${client}" == "docker" ]]; then containers=("${target}") - sudo docker run ${args} ${target} + sudo docker run ${args} "${target}" elif [[ "${client}" == "docker-compose" ]]; then containers=($(yq eval '.services | keys | .[]' "${target}")) set +e @@ -103,7 +103,7 @@ elif [[ "${client}" == "docker-compose" ]]; then set -e elif [[ "${client}" == "runc" ]]; then containers=("${target}") - cd ${target} + cd "${target}" sudo runc create references sudo runc delete references cd - @@ -157,8 +157,8 @@ for ((i = 0; i < num_containers; i++)); do appdesc=$(echo "${appdesc}" | jq ".name = \"${container}-${sha256}.description\"") appdesc=$(echo "${appdesc}" | jq ".appManifest = \"${container}-${sha256}\"") echo "Adding environment variables" - for i in "${!keys[@]}"; do - envs="{\"key\": \"${keys[$i]}\", \"value\": \"${values[$i]}\"}" + for j in "${!keys[@]}"; do + envs="{\"key\": \"${keys[${j}]}\", \"value\": \"${values[${j}]}\"}" echo "Adding $envs" appdesc=$(echo "${appdesc}" | jq --argjson envs "${envs}" '.environment += [$envs]') done @@ -198,4 +198,4 @@ for ((i = 0; i < num_containers; i++)); do cmc-signing-tool -in "${tmp}/${container_name}-${sha256}.manifest.${ser}" -out "${output}/${container_name}-${sha256}.manifest.${ser}" -keys "${key}" -x5cs "${chain}" cmc-signing-tool -in "${tmp}/device.description.${ser}" -out "${output}/device.description.${ser}" -keys "${key}" -x5cs "${chain}" -done \ No newline at end of file +done diff --git a/example-setup/update-platform-snp b/example-setup/update-platform-snp new file mode 100755 index 00000000..e48685d7 --- /dev/null +++ b/example-setup/update-platform-snp @@ -0,0 +1,124 @@ +#!/bin/bash + +set -euo pipefail + +trap '[ $? -eq 0 ] && exit 0; printf "%s failed\n" "$0"' EXIT +dir="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P)" +source "${dir}/utils.sh" +export PATH=${PATH}:${HOME}/go/bin + +if [[ "$#" -ne 2 ]]; then + echo "Usage: ./update-platform " + exit 1 +fi + +data=$(set -e; abs_path "${1}") +input="${data}/metadata-raw" +tmp="${data}/metadata-tmp" +out="${data}/metadata-signed" +ovmf="${2}" +ser="${3}" + +if [[ ! -d "${data}" ]]; then + echo "Data directory ${1} does not exist. Did you run the setup-cmc script? Abort.." + exit 1 +fi + +echo "Using ${data} as directory for local data" + +# Retrieve high level details +set +e +firmware="unknown" +bootloader=$(grub-install --version) +kernel=$(uname -r) +os=$(lsb_release -sd 2>/dev/null) +set -e + +# Replace existing app description in the device description (will be added through +# update-app-manifest scripts) +json=$(cat "${input}/device.description.json") +json=$(echo "${json}" | jq 'del(.appDescriptions[])') +printf "%s\n" "${json}" > "${input}/device.description.json" + +# Load RTM manifest +manifestjson=$(cat "${input}/rtm.manifest.json") + +# Insert high-level details +manifestjson=$(echo "${json}" | jq ".details.firmware = \"${firmware}\"") +manifestjson=$(echo "${json}" | jq ".details.bootloader = \"${bootloader}\"") + +json=$(cat "${dir}/metadata-raw/snp.referencevalue.template") + +# TODO parameters vor vcpus and vmm-type +ref=$("${dir}/sev-snp-measure/sev-snp-measure.py" --mode snp --vcpus=2 --vmm-type=ec2 --ovmf="${ovmf}") + +fingerprint=$(openssl x509 -in "${dir}"/ark_milan.pem -fingerprint -noout -sha256 | sed 's/://g' | cut -d "=" -f2) + +# Insert reference values +setjson "sha384" "${ref}" +setjson "snp.version" 2 +setjson "snp.caFingerprint" "${fingerprint}" +setjson "snp.policy.SingleSocket" false +setjson "snp.policy.Debug" false +setjson "snp.policy.Migration" false +setjson "snp.policy.Smt" true +setjson "snp.policy.AbiMajor" 0 +setjson "snp.policy.AbiMinor" 0 +setjson "snp.fw.build" 3 +setjson "snp.fw.major" 1 +setjson "snp.fw.minor" 49 +setjson "snp.tcb.bl" 2 +setjson "snp.tcb.tee" 0 +setjson "snp.tcb.snp" 5 +setjson "snp.tcb.ucode" 55 + +refval="${json}" +json="${manifestjson}" + +extendarr "referenceValues" "${refval}" + +# Save the RTM manifest +printf "%s\n" "${json}" > "${input}/rtm.manifest.json" + +# Load OS manifest +json=$(cat "${input}/os.manifest.json") + +# Insert high-level details +json=$(echo "${json}" | jq ".details.kernel = \"${kernel}\"") +json=$(echo "${json}" | jq ".details.os = \"${os}\"") + +# Replace existing reference values with new reference values in the OS Manifest +json=$(echo "${json}" | jq 'del(.referenceValues[])') +printf "%s\n" "${json}" > "${input}/os.manifest.json" + +# Sign the metadata* +key="${data}/pki/signing-cert-key.pem" +chain="${data}/pki/signing-cert.pem,${data}/pki/ca.pem" + +rm -rf "${tmp}" +rm -rf "${out}" + +mkdir -p "${tmp}" +mkdir -p "${out}" + +if [[ "${ser,,}" = "json" ]]; then + echo "using json serialization" + cp "${input}/rtm.manifest.json" "${tmp}/rtm.manifest.json" + cp "${input}/os.manifest.json" "${tmp}/os.manifest.json" + cp "${input}/device.description.json" "${tmp}/device.description.json" + cp "${input}/device.config.json" "${tmp}/device.config.json" +elif [[ "${ser,,}" = "cbor" ]]; then + echo "using cbor serialiation" + cmc-converter -in "${input}/rtm.manifest.json" -out "${tmp}/rtm.manifest.cbor" -outform cbor + cmc-converter -in "${input}/os.manifest.json" -out "${tmp}/os.manifest.cbor" -outform cbor + cmc-converter -in "${input}/device.description.json" -out "${tmp}/device.description.cbor" -outform cbor + cmc-converter -in "${input}/device.config.json" -out "${tmp}/device.config.cbor" -outform cbor +else + echo "serialization format ${ser} is not supported" + exit 1 +fi + +cmc-signing-tool -in "${tmp}/rtm.manifest.${ser}" -out "${out}/rtm.manifest.${ser}" -keys "${key}" -x5cs "${chain}" +cmc-signing-tool -in "${tmp}/os.manifest.${ser}" -out "${out}/os.manifest.${ser}" -keys "${key}" -x5cs "${chain}" +cmc-signing-tool -in "${tmp}/device.description.${ser}" -out "${out}/device.description.${ser}" -keys "${key}" -x5cs "${chain}" +cmc-signing-tool -in "${tmp}/device.config.${ser}" -out "${out}/device.config.${ser}" -keys "${key}" -x5cs "${chain}" diff --git a/example-setup/utils.sh b/example-setup/utils.sh index dd305699..192c910c 100644 --- a/example-setup/utils.sh +++ b/example-setup/utils.sh @@ -15,3 +15,20 @@ extendarr() { # Add new value json="$(echo "${json}" | jq ".${key} += [${param}]")" } + + +# Updates the JSON key given in the first argument with the value given +# in the second argument. Numbers must be \"\" quoted, if they should +# be treated as a JSON string +setjson() { + if [[ $2 == \"*\" || $2 == \'*\' ]]; then + # Parameter is string in quotes, use quotes + json="$(echo "${json}" | jq ".$1 = $2")" + elif [[ $2 =~ ^[0-9]+$ || $2 == "true" || $2 == "false" ]]; then + # Parameter is number or boolean, do not use quotes + json="$(echo "${json}" | jq ".$1 = $2")" + else + # Parameter is string, use quotes + json="$(echo "${json}" | jq ".$1 = \"$2\"")" + fi +} \ No newline at end of file diff --git a/go.mod b/go.mod index d014d576..c030df2f 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/edgelesssys/ego v1.4.1 github.com/fxamacker/cbor/v2 v2.4.0 github.com/google/go-attestation v0.4.4-0.20230613144338-a9b6eb1eb888 + github.com/google/go-sev-guest v0.11.1 github.com/google/go-tpm v0.9.0 github.com/mattn/go-sqlite3 v1.14.16 github.com/opencontainers/runtime-spec v1.2.0 @@ -26,15 +27,21 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/certificate-transparency-go v1.1.6 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-configfs-tsm v0.2.2 // indirect github.com/google/go-tspi v0.3.0 // indirect + github.com/google/logger v1.1.1 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/pborman/uuid v1.2.1 // indirect github.com/pion/dtls/v2 v2.2.7 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/transport/v2 v2.2.1 // indirect github.com/pion/udp/v2 v2.0.1 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/x448/float16 v0.8.4 // indirect go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/sync v0.3.0 // indirect diff --git a/go.sum b/go.sum index 780acc68..96e949ea 100644 --- a/go.sum +++ b/go.sum @@ -20,11 +20,20 @@ github.com/google/go-attestation v0.4.4-0.20230613144338-a9b6eb1eb888/go.mod h1: github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98= +github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= +github.com/google/go-sev-guest v0.11.1 h1:gnww4U8fHV5DCPz4gykr1s8SEX1fFNcxCBy+vvXN24k= +github.com/google/go-sev-guest v0.11.1/go.mod h1:qBOfb+JmgsUI3aUyzQoGC13Kpp9zwLeWvuyXmA9q77w= github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba h1:qJEJcuLzH5KDR0gKc0zcktin6KSAwL7+jWKBYceddTc= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= +github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ= +github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -34,6 +43,8 @@ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwp github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= +github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= @@ -43,6 +54,8 @@ github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/udp/v2 v2.0.1 h1:xP0z6WNux1zWEjhC7onRA3EwwSliXqu1ElUZAQhUP54= github.com/pion/udp/v2 v2.0.1/go.mod h1:B7uvTMP00lzWdyMr/1PVZXtV3wpPIxBRd4Wl6AksXn8= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/plgd-dev/go-coap/v3 v3.1.2 h1:OLkqXNQS+cVkBXR/yaq4Mvjt2iPNIw2kK+j9QTnTq9U= github.com/plgd-dev/go-coap/v3 v3.1.2/go.mod h1:rNmGfLHGOikQUcM5sdH2o0HcfnabjIzrGTuiipiNPxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -69,6 +82,8 @@ go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdH go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= @@ -93,6 +108,7 @@ golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/measure/measure.go b/measure/measure.go index 83e33131..3371b50d 100644 --- a/measure/measure.go +++ b/measure/measure.go @@ -55,9 +55,6 @@ func Measure(name string, configSha256, rootfsSha256 []byte, mc *MeasureConfig) } // Hash config entry - // TODO discuss if name is required - // tbh := []byte(name) - // tbh = append(tbh, configSha256...) tbh := []byte(configSha256) tbh = append(tbh, rootfsSha256...) diff --git a/measure/rtconfig.go b/measure/rtconfig.go index 98a305de..e2c2fea7 100644 --- a/measure/rtconfig.go +++ b/measure/rtconfig.go @@ -22,6 +22,7 @@ import ( "fmt" "path/filepath" "slices" + "sort" "strings" "github.com/opencontainers/runtime-spec/specs-go" @@ -29,7 +30,7 @@ import ( jcs "github.com/Fraunhofer-AISEC/cmc/jsoncanonicalizer" ) -func GetConfigMeasurement(id string, configData []byte) ([]byte, []byte, string, error) { +func GetSpecMeasurement(id string, configData []byte) ([]byte, []byte, string, error) { normalizedConfig, rootfs, err := normalize(id, configData) if err != nil { @@ -111,6 +112,14 @@ func normalize(id string, configData []byte) ([]byte, string, error) { // List environment variables alphabetically to guarantee reproducibility slices.Sort(config.Process.Env) + // List mounts alphabetically to guarantee reproducibility + sort.Slice(config.Mounts, func(i, j int) bool { + if config.Mounts[i].Source == config.Mounts[j].Source { + return config.Mounts[i].Destination < config.Mounts[j].Destination + } + return config.Mounts[i].Source < config.Mounts[j].Source + }) + data, err := json.Marshal(config) if err != nil { return nil, "", fmt.Errorf("failed to marshal config: %w", err) diff --git a/measure/rtconfig_test.go b/measure/rtconfig_test.go index 9b796e5d..4d0c3a84 100644 --- a/measure/rtconfig_test.go +++ b/measure/rtconfig_test.go @@ -39,23 +39,23 @@ func TestGetConfigMeasurement(t *testing.T) { configData: configData, }, want: []byte{ - 0xaf, 0xd6, 0xa3, 0x51, 0xca, 0x87, 0xb4, 0x1c, - 0x84, 0x82, 0x5d, 0x76, 0x7a, 0x71, 0x91, 0x80, - 0x3d, 0x08, 0x77, 0xc3, 0x33, 0xf1, 0xf1, 0xee, - 0xaa, 0xd0, 0xa5, 0x9a, 0xf5, 0x01, 0x11, 0x0e, + 0x54, 0x96, 0xca, 0x7d, 0x37, 0x8e, 0x44, 0xb3, + 0x70, 0x47, 0x8e, 0x8d, 0xf4, 0x79, 0xdd, 0x99, + 0x71, 0x65, 0x11, 0x66, 0xe9, 0x93, 0x3f, 0x7f, + 0x48, 0x6e, 0x44, 0x88, 0x75, 0xea, 0xe9, 0x2e, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, _, _, err := GetConfigMeasurement(tt.args.id, tt.args.configData) + got, _, _, err := GetSpecMeasurement(tt.args.id, tt.args.configData) if (err != nil) != tt.wantErr { - t.Errorf("GetConfigMeasurement() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("GetSpecMeasurement() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetConfigMeasurement() = %v, want %v", hex.EncodeToString(got), hex.EncodeToString(tt.want)) + t.Errorf("GetSpecMeasurement() = %v, want %v", hex.EncodeToString(got), hex.EncodeToString(tt.want)) } }) } diff --git a/snpdriver/ioctl.c b/snpdriver/ioctl.c deleted file mode 100644 index 464fda6b..00000000 --- a/snpdriver/ioctl.c +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2021 Fraunhofer AISEC -// Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ioctl.h" - -#define __packed __attribute__ ((__packed__)) - -int -snp_dump_ar(uint8_t *buf, size_t size, uint8_t *user_data, size_t user_data_size) -{ - struct snp_guest_req_ioctl data; - struct snp_report_req req; - struct snp_report_resp resp; - int ret = -1; - int fd = -1; - - if (size < 4000) { - return -1; - } - if (user_data_size > sizeof(req.user_data)) { - return -1; - } - - memset(&data, 0, sizeof(data)); - memset(&req, 0, sizeof(req)); - memset(&resp, 0, sizeof(resp)); - - memcpy(req.user_data, user_data, user_data_size); - - data.msg_version = 1; - data.req_data = (uint64_t) &req; - data.resp_data = (uint64_t) &resp; - - errno = 0; - fd = open("/dev/sev-guest", O_RDWR); - ret = ioctl(fd, SNP_GET_REPORT, &data); - - if (ret != 0) { - fprintf(stderr, "snp-ar: ioctl SNP_GET_REPORT on /dev/sev-guest returned %d\n\n", ret); - fprintf(stderr, "errno: %d (%s)\n", errno, strerror(errno)); - fprintf(stderr, "fw_err: 0x%lx\n\n", data.fw_err); - } - - memcpy(buf, resp.data, sizeof(resp.data)); - - close(fd); - - return ret; -} diff --git a/snpdriver/ioctl.h b/snpdriver/ioctl.h deleted file mode 100644 index f9e3fee3..00000000 --- a/snpdriver/ioctl.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2021 Fraunhofer AISEC -// Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -struct snp_report_req { - uint8_t user_data[64]; - uint32_t vmpl; - uint8_t rsvd[28]; -}; - -struct snp_report_resp { - uint8_t data[4000]; -}; - -struct snp_guest_req_ioctl { - uint8_t msg_version; - uint64_t req_data; - uint64_t resp_data; - uint64_t fw_err; -}; - -#define SNP_GUEST_REQ_IOC_TYPE 'S' - -#define SNP_GET_REPORT _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x0, struct snp_guest_req_ioctl) -#define SNP_GET_DERIVED_KEY _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x1, struct snp_guest_req_ioctl) -#define SNP_GET_EXT_REPORT _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x2, struct snp_guest_req_ioctl) - -int -snp_dump_ar(uint8_t *buf, size_t size, uint8_t *user_data, size_t user_data_size); \ No newline at end of file diff --git a/snpdriver/snpdriver.go b/snpdriver/snpdriver.go index 7064f01a..cc263824 100644 --- a/snpdriver/snpdriver.go +++ b/snpdriver/snpdriver.go @@ -15,19 +15,14 @@ package snpdriver -/* -#include -#include -#include -#include "ioctl.h" -*/ -import "C" import ( + "bytes" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" + "encoding/binary" "encoding/hex" "encoding/pem" "errors" @@ -37,20 +32,29 @@ import ( "path" "net/http" - "unsafe" ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" est "github.com/Fraunhofer-AISEC/cmc/est/estclient" "github.com/Fraunhofer-AISEC/cmc/internal" "github.com/Fraunhofer-AISEC/cmc/verify" + "github.com/google/go-sev-guest/client" "github.com/sirupsen/logrus" ) var log = logrus.WithField("service", "snpdriver") +type certFormat int + const ( PEM = iota - DER = iota + DER +) + +type SigningKey int + +const ( + VCEK = iota + VLEK ) const ( snpChainFile = "akchain.pem" @@ -58,11 +62,11 @@ const ( snpPrivFile = "ikpriv.key" ) -type certFormat int - var ( - vcekUrlPrefix = "https://kdsintf.amd.com/vcek/v1/Milan/" - milanUrl = "https://kdsintf.amd.com/vcek/v1/Milan/cert_chain" + milanUrlVcek = "https://kdsintf.amd.com/vcek/v1/Milan/cert_chain" + milanUrlVlek = "https://kdsintf.amd.com/vlek/v1/Milan/cert_chain" + + vlekUuid = []byte{0xa8, 0x07, 0x4b, 0xc2, 0xa2, 0x5a, 0x48, 0x3e, 0xaa, 0xe6, 0x39, 0xc0, 0x45, 0xa0, 0xb8, 0xa1} ) // Snp is a structure required for implementing the Measure method @@ -73,6 +77,12 @@ type Snp struct { priv crypto.PrivateKey } +type SnpCertTableEntry struct { + Uuid [16]byte + Offset uint32 + Length uint32 +} + // Init initializaes the SNP driver with the specifified configuration func (snp *Snp) Init(c *ar.DriverConfig) error { var err error @@ -113,7 +123,7 @@ func (snp *Snp) Init(c *ar.DriverConfig) error { return fmt.Errorf("failed to get signing cert chain: %w", err) } - // Fetch SNP certificate chain for VCEK (AK) + // Fetch SNP certificate chain for VCEK/VLEK (SNP Attestation Key) snp.snpCertChain, err = getSnpCertChain(c.ServerAddr) if err != nil { return fmt.Errorf("failed to get SNP cert chain: %w", err) @@ -196,20 +206,18 @@ func getMeasurement(nonce []byte) ([]byte, error) { log.Tracef("Generating SNP attestation report with nonce: %v", hex.EncodeToString(nonce)) - buf := make([]byte, 4000) - cBuf := (*C.uint8_t)(unsafe.Pointer(&buf[0])) - cLen := C.size_t(4000) - cUserData := (*C.uint8_t)(unsafe.Pointer(&nonce[0])) - cUserDataLen := C.size_t(len(nonce)) - - cRes, err := C.snp_dump_ar(cBuf, cLen, cUserData, cUserDataLen) + d, err := client.OpenDevice() if err != nil { - return nil, fmt.Errorf("failed to retrieve SNP AR: %w", err) + return nil, fmt.Errorf("failed to open /dev/sev-guest") } + defer d.Close() - res := int(cRes) - if res != 0 { - return nil, fmt.Errorf("failed to retrieve SNP AR. C.snp_dump_ar returned %v", res) + var ud [64]byte + copy(ud[:], nonce) + //lint:ignore SA1019 will be updated later + buf, err := client.GetRawReport(d, ud) + if err != nil { + return nil, fmt.Errorf("failed to get SNP attestation report") } log.Trace("Generated SNP attestation report") @@ -217,6 +225,49 @@ func getMeasurement(nonce []byte) ([]byte, error) { return buf, nil } +func getVlek() ([]byte, error) { + + log.Trace("Fetching VLEK via extended attestation report request") + + d, err := client.OpenDevice() + if err != nil { + return nil, fmt.Errorf("failed to open /dev/sev-guest") + } + defer d.Close() + + //lint:ignore SA1019 will be updated later + _, certs, err := client.GetRawExtendedReport(d, [64]byte{0}) + if err != nil { + return nil, fmt.Errorf("failed to get SNP attestation report") + } + + log.Tracef("Fetched extended SNP attestation report with certs length %v", len(certs)) + + b := bytes.NewBuffer(certs) + for { + log.Trace("Parsing cert table entry..") + var entry SnpCertTableEntry + err = binary.Read(b, binary.LittleEndian, &entry) + if err != nil { + return nil, fmt.Errorf("failed to decode cert table entry: %w", err) + } + + if entry == (SnpCertTableEntry{}) { + log.Tracef("Reached last (zero) SNP cert table entry") + break + } + + log.Tracef("Found cert table entry with UUID %v", hex.EncodeToString(entry.Uuid[:])) + + if bytes.Equal(entry.Uuid[:], vlekUuid) { + log.Tracef("Found VLEK offset %v length %v", entry.Offset, entry.Length) + return certs[entry.Offset : entry.Offset+entry.Length], nil + } + } + + return nil, errors.New("could not find VLEK in certificates") +} + func getCerts(url string, format certFormat) ([]*x509.Certificate, int, error) { log.Tracef("Requesting Cert from %v", url) @@ -261,20 +312,10 @@ func getCerts(url string, format certFormat) ([]*x509.Certificate, int, error) { } func getSnpCertChain(addr string) ([]*x509.Certificate, error) { - ca, _, err := getCerts(milanUrl, PEM) - if err != nil { - return nil, fmt.Errorf("failed to get SNP certificate chain: %w", err) - } - if len(ca) != 2 { - return nil, - fmt.Errorf("failed to get SNP certificate chain. Expected 2 certificates, got %v", - len(ca)) - } - // Fetch the VCEK. TODO as a workaround, we get the parameters through - // fetching an initial attestation report and request the VCEK from the - // Provisioning server. As soon as we can reliably get the correct TCB, - // the host should provide the VCEK + // Fetch the SNP attestation report signing key. Usually, this is the VCEK. However, + // in cloud environments, the CSP might disable VCEK usage, instead the VLEK is used. + // Fetch an initial attestation report to determine which key is used. arRaw, err := getMeasurement(make([]byte, 64)) if err != nil { return nil, fmt.Errorf("failed to get SNP report: %w", err) @@ -284,17 +325,62 @@ func getSnpCertChain(addr string) ([]*x509.Certificate, error) { return nil, fmt.Errorf("failed to decode SNP report: %w", err) } + arkey := (s.KeySelection >> 2) & 0x7 + var signingKey SigningKey + if arkey == 0 { + log.Trace("VCEK is used to sign attestation report") + signingKey = VCEK + } else if arkey == 1 { + log.Trace("VLEK is used to sign attestation report") + signingKey = VLEK + } else { + return nil, fmt.Errorf("could not determine SNP attestation report signing key") + } + // TODO mandate server authentication in the future, otherwise // this step has to happen in a secure environment log.Warn("Creating new EST client without server authentication") client := est.NewClient(nil) - vcek, err := client.SnpEnroll(addr, s.ChipId, s.CurrentTcb) + var signingCert *x509.Certificate + var caUrl string + if signingKey == VCEK { + // VCEK is used, simply request EST enrollment for SNP chip ID and TCB + log.Trace("Enrolling VCEK via EST") + signingCert, err = client.SnpEnroll(addr, s.ChipId, s.CurrentTcb) + if err != nil { + return nil, fmt.Errorf("failed to enroll SNP: %w", err) + } + caUrl = milanUrlVcek + } else if signingKey == VLEK { + // VLEK is used, in this case we fetch the VLEK from the host + vlek, err := getVlek() + if err != nil { + return nil, fmt.Errorf("failed to fetch VLEK: %w", err) + } + log.Trace("Parsing VLEK") + signingCert, err = x509.ParseCertificate(vlek) + if err != nil { + return nil, fmt.Errorf("failed to parse VLEK") + } + log.Tracef("Successfully parsed VLEK CN=%v", signingCert.Subject.CommonName) + caUrl = milanUrlVlek + } else { + return nil, fmt.Errorf("internal error: signing cert not initialized") + } + + // Fetch intermediate CAs and CA depending on signing key (VLEK / VCEK) + ca, _, err := getCerts(caUrl, PEM) if err != nil { - return nil, fmt.Errorf("failed to enroll SNP: %w", err) + return nil, fmt.Errorf("failed to get SNP certificate chain: %w", err) + } + if len(ca) != 2 { + return nil, + fmt.Errorf("failed to get SNP certificate chain. Expected 2 certificates, got %v", + len(ca)) } - return append([]*x509.Certificate{vcek}, ca...), nil + return append([]*x509.Certificate{signingCert}, ca...), nil } func getSigningCertChain(priv crypto.PrivateKey, s ar.Serializer, metadata [][]byte, diff --git a/testtool/coap.go b/testtool/coap.go index 00674cff..b650d887 100644 --- a/testtool/coap.go +++ b/testtool/coap.go @@ -148,7 +148,7 @@ func (a CoapApi) measure(c *config) { log.Fatalf("Failed to read config: %v", err) } - configHash, _, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + configHash, _, _, err := m.GetSpecMeasurement("mycontainer", ctrConfig) if err != nil { log.Fatalf("Failed to measure config: %v", err) } diff --git a/testtool/grpc.go b/testtool/grpc.go index 719e77cf..216da8db 100644 --- a/testtool/grpc.go +++ b/testtool/grpc.go @@ -158,7 +158,7 @@ func (a GrpcApi) measure(c *config) { log.Fatalf("Failed to read container config: %v", err) } - configHash, _, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + configHash, _, _, err := m.GetSpecMeasurement("mycontainer", ctrConfig) if err != nil { log.Fatalf("Failed to measure config: %v", err) } diff --git a/testtool/libapi.go b/testtool/libapi.go index 605f4938..58876c7c 100644 --- a/testtool/libapi.go +++ b/testtool/libapi.go @@ -147,7 +147,7 @@ func (a LibApi) measure(c *config) { log.Fatalf("Failed to read config: %v", err) } - configHash, _, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + configHash, _, _, err := m.GetSpecMeasurement("mycontainer", ctrConfig) if err != nil { log.Fatalf("Failed to measure config: %v", err) } diff --git a/testtool/publish.go b/testtool/publish.go index 772a20e3..5d0f9ae4 100644 --- a/testtool/publish.go +++ b/testtool/publish.go @@ -108,10 +108,19 @@ func saveResult(file, addr string, result []byte) error { var out bytes.Buffer json.Indent(&out, result, "", " ") + // Log the result + r := new(ar.VerificationResult) + json.Unmarshal(result, r) + if r.Success { + log.Infof("SUCCESS: Verification for Prover %v (%v)", r.Prover, r.Created) + } else { + log.Infof("FAILED: Verification for Prover %v (%v)", r.Prover, r.Created) + } + // Save the Attestation Result to file if file != "" { os.WriteFile(file, out.Bytes(), 0644) - log.Infof("Wrote file %v", file) + log.Debugf("Wrote file %v", file) } else { log.Debug("No config file specified: will not save attestation report") } diff --git a/testtool/socket.go b/testtool/socket.go index 485389f1..509cee5f 100644 --- a/testtool/socket.go +++ b/testtool/socket.go @@ -140,7 +140,7 @@ func (a SocketApi) measure(c *config) { log.Fatalf("Failed to read config: %v", err) } - configHash, _, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + configHash, _, _, err := m.GetSpecMeasurement("mycontainer", ctrConfig) if err != nil { log.Fatalf("Failed to measure config: %v", err) } diff --git a/tools/measure-bundle/main.go b/tools/measure-bundle/main.go index 8ca4d9b6..ed3ce272 100644 --- a/tools/measure-bundle/main.go +++ b/tools/measure-bundle/main.go @@ -52,7 +52,7 @@ func main() { log.Fatalf("Failed to read container config: %v", err) } - configHash, _, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + configHash, _, _, err := m.GetSpecMeasurement("mycontainer", ctrConfig) if err != nil { log.Fatalf("Failed to measure config: %v", err) } diff --git a/verify/snp.go b/verify/snp.go index 4d503d54..acc7bfd7 100644 --- a/verify/snp.go +++ b/verify/snp.go @@ -32,10 +32,6 @@ import ( ) type snpreport struct { - // Table 24 @ https://www.amd.com/system/files/TechDocs/56860.pdf - Status uint32 - ReportSize uint32 - Reserved0 [24]byte // Table 21 @ https://www.amd.com/system/files/TechDocs/56860.pdf Version uint32 GuestSvn uint32 @@ -46,7 +42,7 @@ type snpreport struct { SignatureAlgo uint32 CurrentTcb uint64 // platform_version PlatformInfo uint64 - AuthorKeyEn uint32 + KeySelection uint32 Reserved1 uint32 ReportData [64]byte Measurement [48]byte @@ -70,7 +66,7 @@ type snpreport struct { Reserved3b uint8 LaunchTcb uint64 Reserved3c [168]byte - // Table 23 @ https://www.amd.com/system/files/TechDocs/56860.pdf + // Table 119 @ https://www.amd.com/system/files/TechDocs/56860.pdf SignatureR [72]byte SignatureS [72]byte Reserved4 [368]byte @@ -81,7 +77,6 @@ const ( ) const ( - header_offset = 0x20 signature_offset = 0x2A0 ) @@ -369,15 +364,15 @@ func verifySnpSignature( result := ar.SignatureResult{} - if len(reportRaw) < (header_offset + signature_offset) { + if len(reportRaw) < signature_offset { log.Warn("Internal Error: Report buffer too small") result.SignCheck.SetErr(ar.Internal) return result, false } - // Strip the header and the signature from the report and hash for signature verification - // Table 21, 23 @ https://www.amd.com/system/files/TechDocs/56860.pdf - digest := sha512.Sum384(reportRaw[0x20 : 0x20+0x2A0]) + // Strip the signature from the report and hash for signature verification + // Table 23 @ https://www.amd.com/system/files/TechDocs/56860.pdf: signature 0x2A0 - 0x49F + digest := sha512.Sum384(reportRaw[:signature_offset]) // Golang SetBytes expects BigEndian byte array, but SNP values are little endian rRaw := report.SignatureR[:] diff --git a/verify/snp_test.go b/verify/snp_test.go index e20e9851..e2d2195a 100644 --- a/verify/snp_test.go +++ b/verify/snp_test.go @@ -382,10 +382,7 @@ func Test_checkMinVersion(t *testing.T) { } var ( - validReport = []byte{ - 0x00, 0x00, 0x00, 0x00, 0xa0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + validReport = []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -486,10 +483,7 @@ var ( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} - invalidReportSignature = []byte{ - 0x00, 0x00, 0x00, 0x00, 0xa0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + invalidReportSignature = []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/verify/sw.go b/verify/sw.go index 19bb2ad0..f41cbc0b 100644 --- a/verify/sw.go +++ b/verify/sw.go @@ -57,11 +57,11 @@ func verifySwMeasurements(swMeasurement ar.Measurement, nonce []byte, cas []*x50 } // Check that reference values are reflected by mandatory measurements - for _, v := range refVals { + for _, r := range refVals { found := false for _, swm := range swMeasurement.Artifacts { for _, event := range swm.Events { - if bytes.Equal(event.Sha256, v.Sha256) { + if bytes.Equal(event.Sha256, r.Sha256) { found = true break } @@ -70,13 +70,13 @@ func verifySwMeasurements(swMeasurement ar.Measurement, nonce []byte, cas []*x50 break } } - if !found && !v.Optional { - log.Tracef("no SW Measurement found for SW Reference Value %v (hash: %v)", v.Name, hex.EncodeToString(v.Sha256)) + if !found && !r.Optional { + log.Tracef("no SW Measurement found for SW Reference Value %v (hash: %v)", r.Name, hex.EncodeToString(r.Sha256)) r := ar.DigestResult{ Type: "Reference Value", Success: false, - Name: v.Name, - Digest: hex.EncodeToString(v.Sha256), + Name: r.Name, + Digest: hex.EncodeToString(r.Sha256), } result.Artifacts = append(result.Artifacts, r) ok = false @@ -105,10 +105,12 @@ func verifySwMeasurements(swMeasurement ar.Measurement, nonce []byte, cas []*x50 } if !found { r := ar.DigestResult{ - Type: "Measurement", - Success: false, - Name: event.EventName, - Digest: hex.EncodeToString(event.Sha256), + Type: "Measurement", + Success: false, + Name: event.EventName, + Digest: hex.EncodeToString(event.Sha256), + EventData: event.EventData, + CtrData: event.CtrData, } result.Artifacts = append(result.Artifacts, r) log.Tracef("no SW Reference Value found for SW Measurement: %v", hex.EncodeToString(event.Sha256)) diff --git a/verify/tpm.go b/verify/tpm.go index 08dfcb3a..42057f0a 100644 --- a/verify/tpm.go +++ b/verify/tpm.go @@ -70,7 +70,6 @@ func verifyTpmMeasurements(tpmM ar.Measurement, nonce []byte, cas []*x509.Certif log.Tracef("Successfully verified nonce %v", hex.EncodeToString(nonce)) // Verify aggregated PCR against TPM Quote PCRDigest: Hash all reference values - // together then compare sum := make([]byte, 0) for i := range tpmM.Artifacts { @@ -191,6 +190,7 @@ func recalculatePcrs(measurement ar.Measurement, referenceValues []ar.ReferenceV Success: false, Name: event.EventName, EventData: event.EventData, + CtrData: event.CtrData, } detailedResults = append(detailedResults, measResult) log.Tracef("Failed to find PCR%v measurement %v: %v in reference values", diff --git a/verify/verify.go b/verify/verify.go index a4ffb87c..ff5d7f01 100644 --- a/verify/verify.go +++ b/verify/verify.go @@ -560,33 +560,46 @@ func checkValidity(val ar.Validity) ar.Result { } func collectReferenceValues(metadata *ar.Metadata) (map[string][]ar.ReferenceValue, error) { + + // Add a reference to the corresponding manifest to each reference value + for i := range metadata.RtmManifest.ReferenceValues { + metadata.RtmManifest.ReferenceValues[i].SetManifest(metadata.RtmManifest) + } + for i := range metadata.OsManifest.ReferenceValues { + metadata.OsManifest.ReferenceValues[i].SetManifest(metadata.OsManifest) + } + for i := range metadata.AppManifests { + for j := range metadata.AppManifests[i].ReferenceValues { + metadata.AppManifests[i].ReferenceValues[j].SetManifest(metadata.AppManifests[i]) + } + } + // Gather a list of all reference values independent of the type - verList := append(metadata.RtmManifest.ReferenceValues, metadata.OsManifest.ReferenceValues...) + refvals := append(metadata.RtmManifest.ReferenceValues, metadata.OsManifest.ReferenceValues...) for _, appManifest := range metadata.AppManifests { - verList = append(verList, appManifest.ReferenceValues...) + refvals = append(refvals, appManifest.ReferenceValues...) } - verMap := make(map[string][]ar.ReferenceValue) + refmap := make(map[string][]ar.ReferenceValue) // Iterate through the reference values and sort them into the different types - for _, v := range verList { - if v.Type != "SNP Reference Value" && v.Type != "SW Reference Value" && v.Type != "TPM Reference Value" && v.Type != "TDX Reference Value" && v.Type != "SGX Reference Value" { - return nil, fmt.Errorf("reference value of type %v is not supported", v.Type) + for _, r := range refvals { + if r.Type != "SNP Reference Value" && + r.Type != "SW Reference Value" && + r.Type != "TPM Reference Value" && + r.Type != "TDX Reference Value" && + r.Type != "SGX Reference Value" { + return nil, fmt.Errorf("reference value of type %v is not supported", r.Type) } - verMap[v.Type] = append(verMap[v.Type], v) + refmap[r.Type] = append(refmap[r.Type], r) } - return verMap, nil + return refmap, nil } func checkExtensionUint8(cert *x509.Certificate, oid string, value uint8) (ar.Result, bool) { - pem := internal.WriteCertPem(cert) - log.Warnf("%v", string(pem)) - for _, ext := range cert.Extensions { - log.Tracef("OID: %v", ext.Id.String()) - if ext.Id.String() == oid { log.Tracef("Found %v, length %v, values %v", oid, len(ext.Value), ext.Value) if len(ext.Value) != 3 && len(ext.Value) != 4 { diff --git a/verify/verify_test.go b/verify/verify_test.go index 3743a57c..2446a8fb 100644 --- a/verify/verify_test.go +++ b/verify/verify_test.go @@ -24,7 +24,6 @@ import ( "crypto/x509/pkix" "fmt" "math/big" - "reflect" "testing" "time" @@ -269,8 +268,18 @@ func Test_collectReferenceValues(t *testing.T) { t.Errorf("collectReferenceValues() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("collectReferenceValues() = %v, want %v", got, tt.want) + for wantedkey, wantedrefvals := range tt.want { + for _, wantedrefval := range wantedrefvals { + found := false + for _, gotrefval := range got[wantedkey] { + if gotrefval.Name == wantedrefval.Name { + found = true + } + } + if !found { + t.Errorf("collectReferenceValues() failed to find %v", wantedrefval.Name) + } + } } }) }