mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-27 16:39:38 -05:00
144 lines
4.7 KiB
Go
144 lines
4.7 KiB
Go
|
/*
|
||
|
Copyright (c) Edgeless Systems GmbH
|
||
|
|
||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||
|
*/
|
||
|
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"encoding/hex"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"sort"
|
||
|
|
||
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||
|
"github.com/fatih/color"
|
||
|
)
|
||
|
|
||
|
func main() {
|
||
|
if len(os.Args) < 3 {
|
||
|
if len(os.Args) == 0 {
|
||
|
fmt.Println("Usage:", "pcr-compare", "<expected-measurements> <actual-measurements>")
|
||
|
} else {
|
||
|
fmt.Println("Usage:", os.Args[0], "<expected-measurements> <actual-measurements>")
|
||
|
}
|
||
|
fmt.Println("<expected-measurements> is supposed to be a JSON file from the 'Build OS image' pipeline.")
|
||
|
fmt.Println("<actual-measurements> in supposed to be a JSON file with metadata from the PCR reader which is supposed to be verified.")
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
|
||
|
parsedExpectedMeasurements, err := parseMeasurements(os.Args[1])
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
parsedActualMeasurements, err := parseMeasurements(os.Args[2])
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
// Extract the PCR values we care about.
|
||
|
strippedActualMeasurements := stripMeasurements(parsedActualMeasurements)
|
||
|
strippedExpectedMeasurements := stripMeasurements(parsedExpectedMeasurements)
|
||
|
|
||
|
// Do the check early.
|
||
|
areEqual := strippedExpectedMeasurements.EqualTo(strippedActualMeasurements)
|
||
|
|
||
|
// Print values and similarities / differences in addition.
|
||
|
compareMeasurements(strippedExpectedMeasurements, strippedActualMeasurements)
|
||
|
|
||
|
if !areEqual {
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// parseMeasurements unmarshals a JSON file containing the expected or actual measurements.
|
||
|
func parseMeasurements(filename string) (measurements.M, error) {
|
||
|
fileData, err := os.ReadFile(filename)
|
||
|
if err != nil {
|
||
|
return measurements.M{}, err
|
||
|
}
|
||
|
|
||
|
// Technically the expected version does not hold metadata, but we can use the same struct as both hold the measurements in `measurements`.
|
||
|
// This uses the fallback mechanism of the Measurements unmarshaller which accepts strings without the full struct, defaulting to warnOnly = false.
|
||
|
// warnOnly = false is expected for the expected measurements, so that's fine.
|
||
|
// We don't verify metadata here, the CLI has to do that.
|
||
|
var parsedMeasurements measurements.WithMetadata
|
||
|
if err := json.Unmarshal(fileData, &parsedMeasurements); err != nil {
|
||
|
return measurements.M{}, err
|
||
|
}
|
||
|
|
||
|
return parsedMeasurements.Measurements, nil
|
||
|
}
|
||
|
|
||
|
// stripMeasurements extracts only the measurements we want to compare.
|
||
|
// This excludes PCR 15 since the actual measurements come from an initialized cluster, but the expected measurements are supposed to be from an uninitialized state.
|
||
|
func stripMeasurements(input measurements.M) measurements.M {
|
||
|
toBeChecked := []uint32{4, 8, 9, 11, 12, 13}
|
||
|
|
||
|
strippedMeasurements := make(measurements.M, len(toBeChecked))
|
||
|
for _, pcr := range toBeChecked {
|
||
|
if _, ok := input[pcr]; ok {
|
||
|
strippedMeasurements[pcr] = input[pcr]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return strippedMeasurements
|
||
|
}
|
||
|
|
||
|
// compareMeasurements compares the expected PCRs with the actual PCRs.
|
||
|
func compareMeasurements(expectedMeasurements, actualMeasurements measurements.M) {
|
||
|
redPrint := color.New(color.FgRed).SprintFunc()
|
||
|
greenPrint := color.New(color.FgGreen).SprintFunc()
|
||
|
|
||
|
expectedPCRs := getSortedKeysOfMap(expectedMeasurements)
|
||
|
for _, pcr := range expectedPCRs {
|
||
|
if _, ok := actualMeasurements[pcr]; !ok {
|
||
|
color.Magenta("Expected PCR %d not found in the calculated measurements.\n", pcr)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
actualValue := actualMeasurements[pcr]
|
||
|
expectedValue := expectedMeasurements[pcr]
|
||
|
|
||
|
fmt.Printf("Expected PCR %02d: %s (warnOnly: %t)\n", pcr, hex.EncodeToString(expectedValue.Expected[:]), expectedValue.WarnOnly)
|
||
|
|
||
|
var foundMismatch bool
|
||
|
var coloredValue string
|
||
|
var coloredWarnOnly string
|
||
|
if actualValue.Expected == expectedValue.Expected {
|
||
|
coloredValue = greenPrint(hex.EncodeToString(actualValue.Expected[:]))
|
||
|
} else {
|
||
|
coloredValue = redPrint(hex.EncodeToString(actualValue.Expected[:]))
|
||
|
foundMismatch = true
|
||
|
}
|
||
|
|
||
|
if actualValue.WarnOnly == expectedValue.WarnOnly {
|
||
|
coloredWarnOnly = greenPrint(fmt.Sprintf("%t", actualValue.WarnOnly))
|
||
|
} else {
|
||
|
coloredWarnOnly = redPrint(fmt.Sprintf("%t", actualValue.WarnOnly))
|
||
|
foundMismatch = true
|
||
|
}
|
||
|
|
||
|
fmt.Printf("Measured PCR %02d: %s (warnOnly: %s)\n", pcr, coloredValue, coloredWarnOnly)
|
||
|
if !foundMismatch {
|
||
|
color.Green("PCR %02d matches.\n", pcr)
|
||
|
} else {
|
||
|
color.Red("PCR %02d does not match.\n", pcr)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// getSortedKeysOfMap returns the sorted keys of a map to allow the PCR output to be ordered in the output.
|
||
|
func getSortedKeysOfMap(inputMap measurements.M) []uint32 {
|
||
|
keys := make([]uint32, 0, len(inputMap))
|
||
|
for singleKey := range inputMap {
|
||
|
keys = append(keys, singleKey)
|
||
|
}
|
||
|
sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
|
||
|
|
||
|
return keys
|
||
|
}
|