image: reimplement and adapt measurement generation in Go

This commit is contained in:
Malte Poll 2023-09-21 14:50:18 +02:00 committed by Malte Poll
parent 8e706d6de3
commit f6d9f91877
31 changed files with 1343 additions and 286 deletions

View File

@ -142,6 +142,14 @@ def go_dependencies():
sum = "h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=",
version = "v1.4.1",
)
go_repository(
name = "com_github_anatol_vmtest",
build_file_generation = "on",
build_file_proto_mode = "disable_global",
importpath = "github.com/anatol/vmtest",
sum = "h1:t4JGeY9oaF5LB4Rdx9e2wARRRPAYt8Ow4eCf5SwO3fA=",
version = "v0.0.0-20220413190228-7a42f1f6d7b8",
)
go_repository(
name = "com_github_andybalholm_brotli",
@ -1720,6 +1728,14 @@ def go_dependencies():
sum = "h1:/l4kBbb4/vGSsdtB5nUe8L7B9mImVMaBPw9L/0TBHU8=",
version = "v3.2.5+incompatible",
)
go_repository(
name = "com_github_foxboron_go_uefi",
build_file_generation = "on",
build_file_proto_mode = "disable_global",
importpath = "github.com/foxboron/go-uefi",
sum = "h1:SJMQFT74bCrP+kQ24oWhmuyPFHDTavrd3JMIe//2NhU=",
version = "v0.0.0-20230808201820-18b9ba9cd4c3",
)
go_repository(
name = "com_github_foxcpp_go_mockdns",

3
go.mod
View File

@ -196,6 +196,7 @@ require (
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/foxboron/go-uefi v0.0.0-20230808201820-18b9ba9cd4c3
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-chi/chi v4.1.2+incompatible // indirect
github.com/go-errors/errors v1.4.2 // indirect
@ -320,7 +321,7 @@ require (
golang.org/x/oauth2 v0.9.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/term v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/text v0.13.0
golang.org/x/time v0.3.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect

3
go.sum
View File

@ -332,6 +332,8 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/foxboron/go-uefi v0.0.0-20230808201820-18b9ba9cd4c3 h1:SJMQFT74bCrP+kQ24oWhmuyPFHDTavrd3JMIe//2NhU=
github.com/foxboron/go-uefi v0.0.0-20230808201820-18b9ba9cd4c3/go.mod h1:VdozURTQHi5Rs54l+4Szi3yIJQDMfXXYrRLAjKKowWI=
github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
@ -1056,6 +1058,7 @@ go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4x
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
go.mongodb.org/mongo-driver v1.11.3 h1:Ql6K6qYHEzB6xvu4+AU0BoRoqf9vFPcc4o7MUIdPW8Y=
go.mongodb.org/mongo-driver v1.11.3/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=

View File

@ -0,0 +1,34 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
go_library(
name = "cmd_lib",
srcs = ["main.go"],
importpath = "github.com/edgelesssys/constellation/v2/image/measured-boot/cmd",
visibility = ["//visibility:private"],
deps = [
"//image/measured-boot/extract",
"//image/measured-boot/measure",
"//image/measured-boot/pesection",
"@com_github_spf13_afero//:afero",
],
)
go_binary(
name = "cmd",
# keep
data = select({
"@rules_nixpkgs_core//constraints:support_nix": [
"@systemd//:bin/systemd-dissect",
],
"//conditions:default": [],
}),
embed = [":cmd_lib"],
# keep
env = select({
"@rules_nixpkgs_core//constraints:support_nix": {
"DISSECT_TOOLCHAIN": "$(rootpath @systemd//:bin/systemd-dissect)",
},
"//conditions:default": {},
}),
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,227 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package main
import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"github.com/edgelesssys/constellation/v2/image/measured-boot/extract"
"github.com/edgelesssys/constellation/v2/image/measured-boot/measure"
"github.com/edgelesssys/constellation/v2/image/measured-boot/pesection"
"github.com/spf13/afero"
)
const (
ukiPath = "/efi/EFI/BOOT/BOOTX64.EFI"
)
func precalculatePCRs(fs afero.Fs, dissectToolchain, imageFile string) (*measure.Simulator, error) {
dir, err := afero.TempDir(fs, "", "con-measure")
if err != nil {
return nil, err
}
defer func() { _ = fs.RemoveAll(dir) }()
simulator := measure.NewDefaultSimulator()
// extract UKI from raw image
ukiFile := filepath.Join(dir, "uki.efi")
if err := extract.CopyFrom(dissectToolchain, imageFile, ukiPath, ukiFile); err != nil {
return nil, fmt.Errorf("failed to extract UKI: %v", err)
}
// extract section digests from UKI
ukiReader, err := fs.Open(ukiFile)
if err != nil {
return nil, err
}
defer ukiReader.Close()
ukiSections, err := extract.PeFileSectionDigests(ukiReader)
if err != nil {
return nil, fmt.Errorf("failed to extract UKI section digests: %v", err)
}
if err := precalculatePCR4(simulator, fs, ukiFile); err != nil {
return nil, err
}
if err := precalculatePCR9(simulator, fs, ukiFile); err != nil {
return nil, err
}
if err := precalculatePCR11(simulator, ukiSections); err != nil {
return nil, err
}
fmt.Fprintf(os.Stderr, "PCR[ 4]: %x\n", simulator.Bank[4])
fmt.Fprintf(os.Stderr, "PCR[ 9]: %x\n", simulator.Bank[9])
fmt.Fprintf(os.Stderr, "PCR[11]: %x\n", simulator.Bank[11])
// TODO(malt3): with systemd-stub >= 254, PCR[12] will
// contain the "rendered" kernel command line,
// credentials, and sysexts. We should measure these
// values here.
// For now, we expect the PCR to be zero.
fmt.Fprintf(os.Stderr, "PCR[12]: %x\n", simulator.Bank[12])
// PCR[13] would contain extension images for the initrd
// We enforce the absence of extension images by
// expecting PCR[13] to be zero.
fmt.Fprintf(os.Stderr, "PCR[13]: %x\n", simulator.Bank[13])
// PCR[15] can be used to measure from userspace (systemd-pcrphase and others)
// We enforce the absence of userspace measurements by
// expecting PCR[15] to be zero at boot.
fmt.Fprintf(os.Stderr, "PCR[15]: %x\n", simulator.Bank[15])
return simulator, nil
}
func measurePE(fs afero.Fs, peFile string) ([]byte, error) {
f, err := fs.Open(peFile)
if err != nil {
return nil, err
}
defer f.Close()
return measure.Authentihash(f, sha256.New())
}
func precalculatePCR4(simulator *measure.Simulator, fs afero.Fs, ukiFile string) error {
ukiMeasurement, err := measurePE(fs, ukiFile)
if err != nil {
return fmt.Errorf("failed to measure UKI: %v", err)
}
ukiPe, err := fs.Open(ukiFile)
if err != nil {
return err
}
defer ukiPe.Close()
linuxSectionReader, err := extract.PeSectionReader(ukiPe, ".linux")
if err != nil {
return fmt.Errorf("uki does not contain linux kernel image: %v", err)
}
linuxMeasurement, err := measure.Authentihash(linuxSectionReader, sha256.New())
if err != nil {
return fmt.Errorf("failed to measure linux kernel image: %v", err)
}
bootStages := []measure.EFIBootStage{
{Name: "Unified Kernel Image (UKI)", Digest: measure.PCR256(ukiMeasurement)},
{Name: "Linux", Digest: measure.PCR256(linuxMeasurement)},
}
if err := measure.DescribeBootStages(os.Stderr, bootStages); err != nil {
return err
}
return measure.PredictPCR4(simulator, bootStages)
}
func precalculatePCR9(simulator *measure.Simulator, fs afero.Fs, ukiFile string) error {
// load cmdline and initrd from UKI
ukiPe, err := fs.Open(ukiFile)
if err != nil {
return err
}
defer ukiPe.Close()
cmdlineSectionReader, err := extract.PeSectionReader(ukiPe, ".cmdline")
if err != nil {
return fmt.Errorf("uki does not contain cmdline: %v", err)
}
cmdline := new(bytes.Buffer)
if _, err := cmdline.ReadFrom(cmdlineSectionReader); err != nil {
return err
}
initrdSectionReader, err := extract.PeSectionReader(ukiPe, ".initrd")
if err != nil {
return fmt.Errorf("uki does not contain initrd: %v", err)
}
initrdDigest := sha256.New()
if _, err := io.Copy(initrdDigest, initrdSectionReader); err != nil {
return err
}
cmdlineBytes := cmdline.Bytes()
initrdDigestBytes := [32]byte(initrdDigest.Sum(nil))
if err := measure.DescribeLinuxLoad2(os.Stderr, cmdlineBytes, initrdDigestBytes); err != nil {
return err
}
return measure.PredictPCR9(simulator, cmdlineBytes, initrdDigestBytes)
}
func precalculatePCR11(simulator *measure.Simulator, ukiSections []pesection.PESection) error {
if err := measure.DescribeUKISections(os.Stderr, ukiSections); err != nil {
return err
}
return measure.PredictPCR11(simulator, ukiSections)
}
func loadToolchain(key, fallback string) string {
toolchain := os.Getenv(key)
if toolchain == "" {
toolchain = fallback
}
toolchain, err := exec.LookPath(toolchain)
if err != nil {
return ""
}
absolutePath, err := filepath.Abs(toolchain)
if err != nil {
return ""
}
return absolutePath
}
func writeOutput(fs afero.Fs, outputFile string, simulator *measure.Simulator) error {
out, err := fs.Create(outputFile)
if err != nil {
return err
}
defer out.Close()
return json.NewEncoder(out).Encode(simulator)
}
func main() {
if len(os.Args) != 3 {
fmt.Fprintln(os.Stderr, "Usage: measured-boot-precalc <image-file> <output-file>")
os.Exit(1)
}
imageFile := os.Args[1]
outputFile := os.Args[2]
fs := afero.NewOsFs()
dissectToolchain := loadToolchain("DISSECT_TOOLCHAIN", "systemd-dissect")
simulator, err := precalculatePCRs(fs, dissectToolchain, imageFile)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err := writeOutput(fs, outputFile, simulator); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

View File

@ -0,0 +1,22 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("//bazel/go:go_test.bzl", "go_test")
go_library(
name = "extract",
srcs = ["extract.go"],
importpath = "github.com/edgelesssys/constellation/v2/image/measured-boot/extract",
visibility = ["//visibility:public"],
deps = ["//image/measured-boot/pesection"],
)
go_test(
name = "extract_test",
srcs = ["extract_test.go"],
embed = [":extract"],
deps = [
"//image/measured-boot/fixtures",
"//image/measured-boot/pesection",
"@com_github_stretchr_testify//assert",
"@org_uber_go_goleak//:goleak",
],
)

View File

@ -0,0 +1,116 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package extract
import (
"crypto/sha256"
"debug/pe"
"fmt"
"io"
"os/exec"
"sort"
"github.com/edgelesssys/constellation/v2/image/measured-boot/pesection"
)
// CopyFrom is a wrapper for systemd-dissect --copy-from.
func CopyFrom(dissectToolchain, image, path, output string) error {
if dissectToolchain == "" {
dissectToolchain = "systemd-dissect"
}
out, err := exec.Command(dissectToolchain, "--copy-from", image, path, output).CombinedOutput()
if err != nil {
return fmt.Errorf("failed to extract %s from %s: %v\n%s", path, image, err, out)
}
return nil
}
// PeSectionReader returns a reader for the named section of a PE file.
func PeSectionReader(peFile io.ReaderAt, section string) (io.Reader, error) {
f, err := pe.NewFile(peFile)
if err != nil {
return nil, err
}
defer f.Close()
for _, s := range f.Sections {
if s.Name == section {
return io.LimitReader(s.Open(), int64(s.VirtualSize)), nil
}
}
return nil, fmt.Errorf("section %q not found", section)
}
// PeFileSectionDigests returns the section digests of a PE file.
func PeFileSectionDigests(peFile io.ReaderAt) ([]pesection.PESection, error) {
f, err := pe.NewFile(peFile)
if err != nil {
return nil, err
}
defer f.Close()
sections := make([]pesection.PESection, len(f.Sections))
for i, section := range f.Sections {
sectionDigest := sha256.New()
sectionReader := section.Open()
_, err := io.CopyN(sectionDigest, sectionReader, int64(section.VirtualSize))
if err != nil {
return nil, err
}
sections[i].Name = section.Name
sections[i].Size = section.VirtualSize
sections[i].Digest = ([32]byte)(sectionDigest.Sum(nil))
sections[i].Measure = shouldMeasureSection(section.Name)
sections[i].MeasureOrder = sectionMeasureOrder(section.Name)
}
sort.Slice(sections, func(i, j int) bool {
if sections[i].Measure != sections[j].Measure {
return sections[i].Measure
}
if sections[i].MeasureOrder == sections[j].MeasureOrder {
return sections[i].Name < sections[j].Name
}
return sections[i].MeasureOrder < sections[j].MeasureOrder
})
return sections, nil
}
var ukiSections = []string{
".linux",
".osrel",
".cmdline",
".initrd",
".splash",
".dtb",
// uanme and sbat will be added in systemd-stub >= 254
// ".uname",
// ".sbat",
".pcrsig",
".pcrkey",
}
func shouldMeasureSection(name string) bool {
for _, section := range ukiSections {
if name == section && name != ".pcrsig" {
return true
}
}
return false
}
func sectionMeasureOrder(name string) int {
for i, section := range ukiSections {
if name == section {
return i
}
}
return -1
}

View File

@ -0,0 +1,239 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package extract
import (
"bytes"
"io"
"testing"
"github.com/edgelesssys/constellation/v2/image/measured-boot/fixtures"
"github.com/edgelesssys/constellation/v2/image/measured-boot/pesection"
"github.com/stretchr/testify/assert"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
func TestPeSectionReader(t *testing.T) {
assert := assert.New(t)
// can read existing section ".uname"
peReader := bytes.NewReader(fixtures.UKI())
unameSectionReader, err := PeSectionReader(peReader, ".uname")
assert.NoError(err)
uname, err := io.ReadAll(unameSectionReader)
assert.NoError(err)
assert.Equal("0.0.0-100.constellation.fc0.x86_64", string(uname))
// fails to read non-existing section
_, err = PeSectionReader(peReader, ".non-existing")
assert.Error(err)
// fails to read non-PE file
_, err = PeSectionReader(bytes.NewReader([]byte("not a PE file")), ".uname")
assert.Error(err)
}
func TestPeFileSectionDigests(t *testing.T) {
assert := assert.New(t)
// can calculate section digests
peReader := bytes.NewReader(fixtures.UKI())
sectionDigests, err := PeFileSectionDigests(peReader)
assert.NoError(err)
assert.Equal([]pesection.PESection{
{
Name: ".linux", Size: 0x1f8,
Digest: [32]uint8{
0x01, 0xe5, 0xce, 0xe2, 0xd1, 0x8e, 0xaa, 0xce,
0x36, 0xb5, 0xbc, 0x39, 0x4f, 0x70, 0x31, 0xaa,
0xe1, 0x66, 0x8e, 0x4a, 0x7f, 0x7c, 0xc0, 0xe9,
0x49, 0x52, 0x5e, 0xa6, 0x5c, 0x40, 0xf7, 0x95,
},
Measure: true, MeasureOrder: 0,
},
{
Name: ".osrel", Size: 0x2b0,
Digest: [32]uint8{
0x65, 0x83, 0x80, 0x1d, 0xa2, 0x9b, 0x3b, 0x74,
0x0f, 0x0e, 0xb0, 0xc4, 0x27, 0xd5, 0xb8, 0x52,
0x0b, 0xfb, 0xf7, 0xff, 0x63, 0x69, 0xc2, 0x2e,
0xf2, 0xf4, 0xc4, 0x80, 0xf0, 0xea, 0x99, 0xfc,
},
Measure: true, MeasureOrder: 1,
},
{
Name: ".cmdline",
Size: 0x94,
Digest: [32]uint8{
0xf0, 0x47, 0xd0, 0x3a, 0x36, 0xf0, 0xde, 0x1f,
0x77, 0x91, 0x6c, 0x2a, 0xab, 0x88, 0x77, 0xa9,
0xd8, 0x80, 0xac, 0xf9, 0x17, 0x68, 0x3c, 0xc7,
0x7b, 0x7c, 0x01, 0xdf, 0x18, 0xb1, 0x31, 0xc7,
},
Measure: true, MeasureOrder: 2,
},
{
Name: ".initrd",
Size: 0x12,
Digest: [32]uint8{
0x4e, 0x50, 0x30, 0x6a, 0x07, 0x84, 0x47, 0x1f,
0x02, 0xde, 0x7e, 0x54, 0xd9, 0x0f, 0xdc, 0xa1,
0x0e, 0x8e, 0x12, 0xec, 0xcc, 0x2d, 0x7a, 0x9d,
0x97, 0x02, 0xf6, 0xe7, 0x38, 0xe1, 0xc2, 0xca,
},
Measure: true, MeasureOrder: 3,
},
{
Name: ".splash",
Size: 0x12,
Digest: [32]uint8{
0x36, 0xb5, 0xf4, 0x82, 0x37, 0x2e, 0x50, 0x49,
0x83, 0x9d, 0x17, 0x6c, 0xf4, 0xd1, 0x4a, 0xcb,
0xfd, 0xfe, 0xda, 0xc1, 0xbf, 0x77, 0xea, 0x0e,
0xa4, 0xb1, 0x72, 0xa8, 0x76, 0xae, 0x2d, 0x2e,
},
Measure: true, MeasureOrder: 4,
},
{
Name: ".dtb",
Size: 0xf,
Digest: [32]uint8{
0x46, 0xa0, 0x01, 0x53, 0xca, 0xd9, 0x9d, 0x19,
0x4a, 0xf1, 0x14, 0x48, 0x30, 0x5c, 0x8c, 0xa1,
0x87, 0x2a, 0xba, 0xe9, 0x20, 0xee, 0x42, 0x3c,
0x19, 0x35, 0x01, 0x05, 0x0f, 0x36, 0xe7, 0x8d,
},
Measure: true, MeasureOrder: 5,
},
{
Name: ".pcrkey",
Size: 0x12,
Digest: [32]uint8{
0x35, 0x4b, 0x67, 0xd5, 0xa3, 0xef, 0x2a, 0xff,
0xda, 0xdb, 0x3d, 0xfc, 0x1f, 0x8b, 0xd0, 0xf6,
0x69, 0xd0, 0x86, 0xa6, 0xd6, 0x7d, 0x5f, 0xee,
0x88, 0xdb, 0x21, 0x90, 0xc4, 0xa7, 0x07, 0x26,
},
Measure: true, MeasureOrder: 7,
},
{
Name: ".data",
Size: 0x10,
Digest: [32]uint8{
0xc3, 0xde, 0x14, 0xca, 0x16, 0x45, 0x87, 0x5e,
0x3b, 0xb0, 0xdd, 0xab, 0x9f, 0x60, 0x91, 0x46,
0xf2, 0x1c, 0xc0, 0xeb, 0xd0, 0xea, 0x9b, 0x4f,
0x22, 0xd3, 0x98, 0x40, 0xc0, 0xea, 0x29, 0xc5,
},
Measure: false, MeasureOrder: -1,
},
{
Name: ".dynamic",
Size: 0x13,
Digest: [32]uint8{
0x2b, 0x75, 0x29, 0xc8, 0x3a, 0x74, 0xbc, 0xb0,
0xac, 0x63, 0x15, 0x18, 0xa1, 0x14, 0x95, 0x10,
0x1a, 0x8d, 0x8e, 0x40, 0x69, 0x93, 0xed, 0x05,
0xed, 0x8a, 0xcc, 0x2d, 0x88, 0xec, 0x13, 0x79,
},
Measure: false, MeasureOrder: -1,
},
{
Name: ".dynsym",
Size: 0x12,
Digest: [32]uint8{
0xb6, 0x0a, 0x7d, 0x65, 0x69, 0xeb, 0xa3, 0xd9,
0x9e, 0xec, 0x13, 0x32, 0x57, 0x2b, 0x61, 0x19,
0x32, 0x0b, 0x57, 0x1b, 0x43, 0xc1, 0x96, 0x75,
0x37, 0x5a, 0x85, 0x76, 0xda, 0xf7, 0x81, 0x24,
},
Measure: false, MeasureOrder: -1,
},
{
//nolint:misspell
Name: ".rela",
Size: 0x10,
Digest: [32]uint8{
0x1c, 0xd6, 0xfb, 0x4f, 0xb8, 0x74, 0xfd, 0xb2,
0xf3, 0xb7, 0xf5, 0x3d, 0xc1, 0x8c, 0x5b, 0x8e,
0x5b, 0xa1, 0x4d, 0x00, 0x6c, 0x56, 0x41, 0x5e,
0x9b, 0x8e, 0x22, 0x1d, 0xbf, 0x59, 0xdd, 0x9d,
},
Measure: false, MeasureOrder: -1,
},
{
Name: ".reloc",
Size: 0xa,
Digest: [32]uint8{
0x4f, 0xfa, 0xdb, 0x1d, 0xbd, 0xe9, 0x2d, 0xce,
0x21, 0x37, 0xae, 0x1e, 0x24, 0x74, 0xad, 0x09,
0xf2, 0x7b, 0x62, 0xe4, 0xbb, 0xa5, 0xcc, 0xc6,
0x49, 0x0a, 0xb0, 0xda, 0x45, 0xfa, 0x45, 0xc3,
},
Measure: false, MeasureOrder: -1,
},
{
Name: ".sbat", Size: 0x10,
Digest: [32]uint8{
0x66, 0x30, 0xfb, 0x7d, 0x5b, 0xaf, 0x9d, 0x6c,
0xd5, 0x1c, 0x9a, 0xc9, 0x54, 0x10, 0xe6, 0x8a,
0xa3, 0xfe, 0xdb, 0x4a, 0xdd, 0xd4, 0x2b, 0x34,
0x0e, 0x47, 0x11, 0xe2, 0x3c, 0xcc, 0xd4, 0xb2,
},
Measure: false, MeasureOrder: -1,
},
{
Name: ".sdmagic", Size: 0x2d,
Digest: [32]uint8{
0xc1, 0x02, 0x12, 0x0d, 0xe9, 0xfa, 0x62, 0x43,
0xf2, 0x16, 0xdd, 0xb4, 0x58, 0x28, 0xe2, 0xa2,
0xb6, 0x4a, 0x65, 0x82, 0x30, 0xd0, 0xca, 0xe6,
0xc2, 0xf2, 0x98, 0x39, 0x67, 0xba, 0xbe, 0x95,
},
Measure: false, MeasureOrder: -1,
},
{
Name: ".text", Size: 0x10,
Digest: [32]uint8{
0xaf, 0x54, 0x41, 0x9a, 0x3f, 0xbe, 0x76, 0x0c,
0xf7, 0xd3, 0x6a, 0x86, 0x37, 0xf0, 0x1d, 0x13,
0xd4, 0x4b, 0xb5, 0xf3, 0x92, 0x15, 0xe2, 0x2e,
0xad, 0x52, 0x15, 0x51, 0xfa, 0xe4, 0x2f, 0x2d,
},
Measure: false, MeasureOrder: -1,
},
{
Name: ".uname", Size: 0x22,
Digest: [32]uint8{
0x32, 0xd5, 0x9d, 0x99, 0x0e, 0x9c, 0x1f, 0x7d,
0xa5, 0x54, 0xcb, 0x88, 0x8e, 0x32, 0x38, 0xac,
0x61, 0x93, 0xe5, 0xe7, 0x23, 0x0f, 0x99, 0xb1,
0x97, 0x13, 0x8d, 0xd7, 0x23, 0xc0, 0xeb, 0xb6,
},
Measure: false, MeasureOrder: -1,
},
{
Name: ".pcrsig", Size: 0x216,
Digest: [32]uint8{
0xcc, 0x41, 0xa5, 0x48, 0xbd, 0x02, 0x03, 0x17,
0x49, 0x39, 0xf5, 0x0c, 0x3d, 0xf1, 0x77, 0x59,
0xb8, 0x13, 0xb5, 0x31, 0xb0, 0x56, 0x3e, 0x91,
0x20, 0x55, 0x6c, 0xf7, 0x25, 0x01, 0xa3, 0x26,
},
Measure: false, MeasureOrder: 6,
},
}, sectionDigests)
// fails to read non-PE file
_, err = PeFileSectionDigests(bytes.NewReader([]byte("not a PE file")))
assert.Error(err)
}

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python
# Copyright (c) Edgeless Systems GmbH
#
# SPDX-License-Identifier: AGPL-3.0-only
# This script calculates the authentihash of a PE / EFI binary.
# Install prerequisites:
# pip install lief
import sys
import lief
def authentihash(filename):
pe = lief.parse(filename)
return pe.authentihash(lief.PE.ALGORITHMS.SHA_256)
if __name__ == '__main__':
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <filename>")
sys.exit(1)
print(authentihash(sys.argv[1]).hex())

View File

@ -0,0 +1,9 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "fixtures",
srcs = ["fixtures.go"],
embedsrcs = ["uki.efi"],
importpath = "github.com/edgelesssys/constellation/v2/image/measured-boot/fixtures",
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,17 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package fixtures
import _ "embed"
// UKI returns the UKI EFI binary.
func UKI() []byte {
return ukiEFI[:]
}
//go:embed uki.efi
var ukiEFI []byte

Binary file not shown.

View File

@ -0,0 +1,39 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("//bazel/go:go_test.bzl", "go_test")
go_library(
name = "measure",
srcs = [
"authentihash.go",
"pcr.go",
"pcr04.go",
"pcr09.go",
"pcr11.go",
],
importpath = "github.com/edgelesssys/constellation/v2/image/measured-boot/measure",
visibility = ["//visibility:public"],
deps = [
"//image/measured-boot/pesection",
"@com_github_foxboron_go_uefi//efi/pecoff",
"@org_golang_x_text//encoding/unicode",
],
)
go_test(
name = "measure_test",
srcs = [
"authentihash_test.go",
"measure_test.go",
"pcr04_test.go",
"pcr09_test.go",
"pcr11_test.go",
"pcr_test.go",
],
embed = [":measure"],
deps = [
"//image/measured-boot/fixtures",
"//image/measured-boot/pesection",
"@com_github_stretchr_testify//assert",
"@org_uber_go_goleak//:goleak",
],
)

View File

@ -0,0 +1,31 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package measure
import (
"bytes"
"fmt"
"hash"
"io"
"github.com/foxboron/go-uefi/efi/pecoff"
)
// Authentihash returns the PE/COFF hash / Authentihash of a file.
func Authentihash(r io.Reader, h hash.Hash) ([]byte, error) {
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(r); err != nil {
return nil, fmt.Errorf("failed to read pe file: %v", err)
}
signingCtx := pecoff.PECOFFChecksum(buf.Bytes())
pecoff.PaddSigCtx(signingCtx)
h.Write(signingCtx.SigData.Bytes())
return h.Sum(nil), nil
}

View File

@ -0,0 +1,33 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package measure
import (
"bytes"
"crypto/sha256"
"testing"
"github.com/edgelesssys/constellation/v2/image/measured-boot/fixtures"
"github.com/stretchr/testify/assert"
)
func TestPeSectionReader(t *testing.T) {
assert := assert.New(t)
peReader := bytes.NewReader(fixtures.UKI())
digest, err := Authentihash(peReader, sha256.New())
assert.NoError(err)
assert.Equal(
[]byte{
0xd3, 0x43, 0xbe, 0x62, 0x65, 0xeb, 0x3e, 0x23,
0xf7, 0x8b, 0x0a, 0xe0, 0x96, 0xbf, 0xf3, 0x34,
0xe3, 0x7a, 0x76, 0x0a, 0xe8, 0x30, 0x73, 0x62,
0x83, 0xf9, 0xb0, 0x26, 0x8e, 0xce, 0xdc, 0xf2,
},
digest,
)
}

View File

@ -0,0 +1,17 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package measure
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}

View File

@ -0,0 +1,120 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package measure
import (
"crypto/sha256"
"fmt"
)
// PCR256 is a 256-bit PCR value.
type PCR256 [32]byte
// MarshalJSON implements json.Marshaler.
func (p PCR256) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("{\"expected\": \"%x\"}", p[:])), nil
}
// Digest256 is a 256-bit digest value (sha256).
type Digest256 [32]byte
// MarshalJSON implements json.Marshaler.
func (d Digest256) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("\"%x\"", d[:])), nil
}
// PCR256Bank is a map of PCR index to PCR256 value.
type PCR256Bank map[uint32]PCR256
// Event is a pcr extend event.
type Event struct {
PCRIndex uint32
Digest Digest256
Data []byte `json:",omitempty"`
Description string
}
// EventLog is a list of events.
type EventLog struct {
Events []Event
}
// Simulator is a TPM PCR simulator.
type Simulator struct {
Bank PCR256Bank `json:"measurements"`
EventLog EventLog
}
// NewDefaultSimulator returns a new Simulator with default PCR values.
func NewDefaultSimulator() *Simulator {
return &Simulator{
Bank: PCR256Bank{
4: ZeroPCR256(),
8: ZeroPCR256(),
9: ZeroPCR256(),
11: ZeroPCR256(),
12: ZeroPCR256(),
13: ZeroPCR256(),
15: ZeroPCR256(),
},
}
}
// ExtendPCR extends the PCR at index with the digest and data.
func (s *Simulator) ExtendPCR(index uint32, digest [32]byte, data []byte, description string) error {
hashCtx := sha256.New()
old, ok := s.Bank[index]
if !ok {
return fmt.Errorf("PCR index %d not found", index)
}
hashCtx.Write(old[:])
hashCtx.Write(digest[:])
newHash := hashCtx.Sum(nil)
s.Bank[index] = PCR256(newHash)
var eventData []byte
if data != nil {
eventData = make([]byte, len(data))
copy(eventData, data)
}
s.EventLog.Events = append(s.EventLog.Events, Event{
PCRIndex: index,
Digest: digest,
Data: eventData,
Description: description,
})
return nil
}
// ZeroPCR256 returns a zeroed PCR256 value.
func ZeroPCR256() PCR256 {
return PCR256{}
}
// EVEFIActionPCR256 returns the expected PCR256 value for EV_EFI_ACTION.
func EVEFIActionPCR256() PCR256 {
return PCR256{
0x3d, 0x67, 0x72, 0xb4, 0xf8, 0x4e, 0xd4, 0x75,
0x95, 0xd7, 0x2a, 0x2c, 0x4c, 0x5f, 0xfd, 0x15,
0xf5, 0xbb, 0x72, 0xc7, 0x50, 0x7f, 0xe2, 0x6f,
0x2a, 0xae, 0xe2, 0xc6, 0x9d, 0x56, 0x33, 0xba,
}
}
// EVSeparatorPCR256 returns the expected PCR256 value for EV_SEPARATOR.
func EVSeparatorPCR256() PCR256 {
return PCR256{
0xdf, 0x3f, 0x61, 0x98, 0x04, 0xa9, 0x2f, 0xdb,
0x40, 0x57, 0x19, 0x2d, 0xc4, 0x3d, 0xd7, 0x48,
0xea, 0x77, 0x8a, 0xdc, 0x52, 0xbc, 0x49, 0x8c,
0xe8, 0x05, 0x24, 0xc0, 0x14, 0xb8, 0x11, 0x19,
}
}

View File

@ -0,0 +1,59 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package measure
import (
"fmt"
"io"
)
// EFIBootStage is a stage (bootloader) of the EFI boot process.
type EFIBootStage struct {
Name string
Digest [32]byte
}
// DescribeBootStages prints a description of the EFIBootStages to a writer.
func DescribeBootStages(w io.Writer, bootStages []EFIBootStage) error {
if _, err := fmt.Fprintf(w, "EFI Boot Stages:\n"); err != nil {
return err
}
var maxNameLen int
for _, bootStage := range bootStages {
if len(bootStage.Name) > maxNameLen {
maxNameLen = len(bootStage.Name)
}
}
for i, bootStage := range bootStages {
if _, err := fmt.Fprintf(w, " Stage %d - %-*s:\t%x\n", i+1, maxNameLen, bootStage.Name, bootStage.Digest); err != nil {
return err
}
}
return nil
}
// PredictPCR4 predicts the PCR4 value based on the EFIBootStages.
func PredictPCR4(simulator *Simulator, efiBootStages []EFIBootStage) error {
// TCG PC Client Platform Firmware Profile Family "2.0 Section" 7.2.4.4.a
if err := simulator.ExtendPCR(4, EVEFIActionPCR256(), nil, "EV_EFI_ACTION: Calling EFI Application from Boot Option"); err != nil {
return err
}
// TCG PC Client Platform Firmware Profile Family "2.0 Section" 7.2.4.4.b
if err := simulator.ExtendPCR(4, EVSeparatorPCR256(), []byte{0x00, 0x00, 0x00, 0x00}, "EV_SEPARATOR"); err != nil {
return err
}
for i, efiBootStage := range efiBootStages {
// TCG PC Client Platform Firmware Profile Family "2.0 Section" 7.2.4.4.e
err := simulator.ExtendPCR(4, efiBootStage.Digest, nil, fmt.Sprintf("Boot Stage %d: %s", i+1, efiBootStage.Name))
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,51 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package measure
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPredictPCR4(t *testing.T) {
assert := assert.New(t)
sim := NewDefaultSimulator()
bootstages := []EFIBootStage{
{
Name: "stage0",
Digest: [32]byte{},
},
{
Name: "stage1",
Digest: [32]byte{
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
},
},
}
out := bytes.NewBuffer(nil)
assert.NoError(DescribeBootStages(out, bootstages))
assert.Equal("EFI Boot Stages:\n"+
" Stage 1 - stage0:\t0000000000000000000000000000000000000000000000000000000000000000\n"+
" Stage 2 - stage1:\t0101010101010101010101010101010101010101010101010101010101010101\n",
out.String())
assert.NoError(PredictPCR4(sim, bootstages))
assert.Equal(PCR256{
0x22, 0x11, 0x6d, 0xee, 0x86, 0x1a, 0xa6, 0xb4,
0x42, 0x42, 0xac, 0x46, 0x9e, 0xab, 0x24, 0xce,
0xad, 0x34, 0x4d, 0x52, 0xc7, 0x71, 0x31, 0xf5,
0x4a, 0xc1, 0xca, 0xc9, 0xd6, 0xa2, 0x40, 0x8e,
}, sim.Bank[4])
}

View File

@ -0,0 +1,60 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package measure
import (
"crypto/sha256"
"fmt"
"io"
"golang.org/x/text/encoding/unicode"
)
// DescribeLinuxLoad2 describes the expected measurements for the Linux LOAD_FILE2 protocol.
func DescribeLinuxLoad2(w io.Writer, cmdline []byte, initrdDigest [32]byte) error {
if _, err := fmt.Fprintf(w, "Linux LOAD_FILE2 protocol:\n"); err != nil {
return err
}
if _, err := fmt.Fprintf(w, " cmdline: %q\n", cmdline); err != nil {
return err
}
if _, err := fmt.Fprintf(w, " initrd (digest %x)\n", initrdDigest); err != nil {
return err
}
return nil
}
// PredictPCR9 predicts the PCR9 value based on the kernel command line and initrd.
func PredictPCR9(simulator *Simulator, cmdline []byte, initrdDigest [32]byte) error {
// Linux LOAD_FILE2 protocol
// Linux LOAD_FILE2 protocol - efi_convert_cmdline
// https://github.com/torvalds/linux/blob/42dc814987c1feb6410904e58cfd4c36c4146150/drivers/firmware/efi/libstub/efi-stub-helper.c#L280
// kernel cmdline is null terminated utf-8
// will be loaded / measured as UTF-16LE
if len(cmdline) == 0 || cmdline[len(cmdline)-1] != 0 {
return fmt.Errorf("kernel cmdline must be null terminated")
}
cmdlineUTF16LE, err := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder().Bytes(cmdline)
if err != nil {
return err
}
err = simulator.ExtendPCR(9, sha256.Sum256(cmdlineUTF16LE), cmdlineUTF16LE, fmt.Sprintf("EV_EVENT_TAG: Linux LOAD_FILE2 protocol: cmdline %q", cmdline))
if err != nil {
return err
}
// Linux LOAD_FILE2 protocol - efi_load_initrd
// https://github.com/torvalds/linux/blob/42dc814987c1feb6410904e58cfd4c36c4146150/drivers/firmware/efi/libstub/efi-stub-helper.c#L559
// initrd is hashed as-is and measured
err = simulator.ExtendPCR(9, initrdDigest, nil, fmt.Sprintf("EV_EVENT_TAG: Linux LOAD_FILE2 protocol: initrd (digest %x)", initrdDigest))
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,38 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package measure
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPredictPCR9(t *testing.T) {
assert := assert.New(t)
sim := NewDefaultSimulator()
cmdline := []byte("console=tty0\x00")
initrdDigest := [32]byte{}
out := bytes.NewBuffer(nil)
assert.NoError(DescribeLinuxLoad2(out, cmdline, initrdDigest))
assert.Equal("Linux LOAD_FILE2 protocol:\n"+
" cmdline: \"console=tty0\\x00\"\n"+
" initrd (digest 0000000000000000000000000000000000000000000000000000000000000000)\n",
out.String())
assert.NoError(PredictPCR9(sim, cmdline, initrdDigest))
assert.Equal(PCR256{
0xeb, 0x4f, 0x7b, 0xca, 0x86, 0x58, 0x07, 0xd3,
0x16, 0x3b, 0x95, 0x17, 0x4d, 0x6e, 0x66, 0xcf,
0xc7, 0x4a, 0xcf, 0x8b, 0x93, 0x0a, 0x55, 0x3e,
0x95, 0xec, 0x94, 0x66, 0x2c, 0xb6, 0xfa, 0xcd,
}, sim.Bank[9])
}

View File

@ -0,0 +1,68 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package measure
import (
"crypto/sha256"
"fmt"
"io"
"github.com/edgelesssys/constellation/v2/image/measured-boot/pesection"
)
// DescribeUKISections describes the expected measurements for the UKI sections.
func DescribeUKISections(w io.Writer, ukiSections []pesection.PESection) error {
if _, err := fmt.Fprintf(w, "UKI sections:\n"); err != nil {
return err
}
var maxNameLen int
for _, ukiSection := range ukiSections {
if len(ukiSection.Name) > maxNameLen {
maxNameLen = len(ukiSection.Name)
}
}
for i, ukiSection := range ukiSections {
if ukiSection.Measure {
if _, err := fmt.Fprintf(w, " Section %2d - %-*s (%10d bytes):\t%x, %x\n", i+1, maxNameLen, ukiSection.Name, ukiSection.Size, sha256.Sum256(ukiSection.NullTerminatedName()), ukiSection.Digest); err != nil {
return err
}
continue
}
if _, err := fmt.Fprintf(w, " Section %2d - %-*s:\t%s\n", i+1, maxNameLen, ukiSection.Name, "not measured"); err != nil {
return err
}
}
return nil
}
// PredictPCR11 predicts the PCR11 value based on the components of unified kernel images.
func PredictPCR11(simulator *Simulator, ukiSections []pesection.PESection) error {
for i, ukiSection := range ukiSections {
// systemd-stub documentation TPM PCR Notes
// https://github.com/systemd/systemd/blob/7c52d5236a3bc85db1755de6a458934be095cd1c/src/boot/efi/stub.c#L409-L441
if !ukiSection.Measure {
continue
}
// first, measure the name
name := ukiSection.NullTerminatedName()
err := simulator.ExtendPCR(11, sha256.Sum256(name), name, fmt.Sprintf("EV_IPL: UKI section %d name: %s", i+1, ukiSection.Name))
if err != nil {
return err
}
// then, measure the data
err = simulator.ExtendPCR(11, ukiSection.Digest, nil, fmt.Sprintf("EV_IPL: UKI section %d data: %x", i+1, ukiSection.Digest))
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,61 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package measure
import (
"bytes"
"testing"
"github.com/edgelesssys/constellation/v2/image/measured-boot/pesection"
"github.com/stretchr/testify/assert"
)
func TestPredictPCR11(t *testing.T) {
assert := assert.New(t)
sim := NewDefaultSimulator()
peSections := []pesection.PESection{
{
Name: ".text",
Size: 100,
Digest: [32]byte{},
},
{
Name: ".linux",
Size: 100,
Digest: [32]byte{},
Measure: true,
},
{
Name: ".initrd",
Digest: [32]byte{
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
},
Measure: true,
},
}
out := bytes.NewBuffer(nil)
assert.NoError(DescribeUKISections(out, peSections))
assert.Equal("UKI sections:\n"+
" Section 1 - .text :\tnot measured\n"+
" Section 2 - .linux ( 100 bytes):\t0da293e37ad5511c59be47993769aacb91b243f7d010288e118dc90e95aaef5a, 0000000000000000000000000000000000000000000000000000000000000000\n"+
" Section 3 - .initrd ( 0 bytes):\t15ee37e75f1e8d42080e91fdbbd2560780918c81fe3687ae6d15c472bbdaac75, 0101010101010101010101010101010101010101010101010101010101010101\n",
out.String())
assert.NoError(PredictPCR11(sim, peSections))
assert.Equal(PCR256{
0x9d, 0xfe, 0x39, 0x9f, 0xcd, 0x44, 0x32, 0x63,
0x9f, 0x0e, 0x20, 0xf4, 0x9d, 0xf8, 0x23, 0xaa,
0x66, 0xb0, 0x95, 0xf0, 0x66, 0x4f, 0x0a, 0x4b,
0x9f, 0xbd, 0xc1, 0x1e, 0xa6, 0x46, 0x83, 0xe2,
}, sim.Bank[11])
}

View File

@ -0,0 +1,49 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package measure
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestExtendPCR(t *testing.T) {
assert := assert.New(t)
sim := NewDefaultSimulator()
assert.Equal(ZeroPCR256(), sim.Bank[4])
assert.NoError(sim.ExtendPCR(4, EVSeparatorPCR256(), []byte{0x00, 0x00, 0x00, 0x00}, "EV_SEPARATOR"))
assert.Equal(PCR256{
0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea,
0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d,
0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a,
0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69,
}, sim.Bank[4])
assert.NoError(sim.ExtendPCR(4, EVEFIActionPCR256(), nil, "EV_EFI_ACTION: Calling EFI Application from Boot Option"))
assert.Equal(PCR256{
0xdd, 0x50, 0xc8, 0xda, 0x0f, 0x89, 0x9f, 0x65,
0x5b, 0x43, 0x05, 0xd2, 0x43, 0x86, 0x63, 0xc1,
0xb3, 0xda, 0x6d, 0x19, 0x22, 0xa0, 0xc8, 0x22,
0x65, 0x33, 0xac, 0x41, 0x7a, 0xbc, 0xd5, 0x23,
}, sim.Bank[4])
assert.Equal([]Event{
{
PCRIndex: 0x4, Digest: Digest256(EVSeparatorPCR256()),
Data: []uint8{0x0, 0x0, 0x0, 0x0},
Description: "EV_SEPARATOR",
},
{
PCRIndex: 0x4, Digest: Digest256(EVEFIActionPCR256()),
Data: []uint8(nil),
Description: "EV_EFI_ACTION: Calling EFI Application from Boot Option",
},
}, sim.EventLog.Events)
}

View File

@ -1,35 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) Edgeless Systems GmbH
#
# SPDX-License-Identifier: AGPL-3.0-only
# This script contains shared functions for pcr calculation.
set -euo pipefail
shopt -s inherit_errexit
pcr_extend() {
local CURRENT_PCR="$1"
local EXTEND_WITH="$2"
local HASH_FUNCTION="$3"
(
echo -n "${CURRENT_PCR}" | xxd -r -p
echo -n "${EXTEND_WITH}" | xxd -r -p
) | ${HASH_FUNCTION} | cut -d " " -f 1
}
extract() {
local image="$1"
local path="$2"
local output="$3"
sudo systemd-dissect --copy-from "${image}" "${path}" "${output}"
}
mktempdir() {
mktemp -d
}
cleanup() {
local dir="$1"
rm -rf "${dir}"
}

View File

@ -1,16 +0,0 @@
{
"measurements": {
"8": {
"expected": "0000000000000000000000000000000000000000000000000000000000000000"
},
"11": {
"expected": "0000000000000000000000000000000000000000000000000000000000000000"
},
"13": {
"expected": "0000000000000000000000000000000000000000000000000000000000000000"
},
"15": {
"expected": "0000000000000000000000000000000000000000000000000000000000000000"
}
}
}

View File

@ -0,0 +1,8 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "pesection",
srcs = ["pesection.go"],
importpath = "github.com/edgelesssys/constellation/v2/image/measured-boot/pesection",
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,24 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package pesection
// PESection describes a PE section.
type PESection struct {
Name string
Size uint32
Digest [32]byte
Measure bool
MeasureOrder int
}
// NullTerminatedName returns the name of the section with a null terminator.
func (u PESection) NullTerminatedName() []byte {
if len(u.Name) > 0 && u.Name[len(u.Name)-1] == 0x00 {
return []byte(u.Name)
}
return append([]byte(u.Name), 0x00)
}

View File

@ -1,77 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) Edgeless Systems GmbH
#
# SPDX-License-Identifier: AGPL-3.0-only
# This script is used to precalculate the PCR[12] value for a Constellation OS image.
# PCR[12] contains the hash of the kernel command line and is measured by systemd-boot.
# This value was previously measured into PCR[8].
# This script may produce wrong results for systemd-boot versions < 251.
# Usage: precalculate_pcr_12.sh <path to image> <path to output file> <csp>
set -euo pipefail
shopt -s inherit_errexit
source "$(dirname "$0")/measure_util.sh"
get_cmdline_from_uki() {
local uki="$1"
local output="$2"
objcopy -O binary --only-section=.cmdline "${uki}" "${output}"
}
cmdline_measure() {
local path="$1"
local tmp
tmp=$(mktemp)
# convert to utf-16le
iconv -f utf-8 -t utf-16le "${path}" -o "${tmp}"
sha256sum "${tmp}" | cut -d " " -f 1
rm "${tmp}"
}
write_output() {
local out="$1"
cat > "${out}" << EOF
{
"measurements": {
"12": {
"expected": "${expected_pcr_12}"
}
},
"cmdline": "${cmdline}",
"cmdline-sha256": "${cmdline_hash}"
}
EOF
}
IMAGE="$1"
OUT="$2"
CSP="$3"
DIR=$(mktempdir)
trap 'cleanup "${DIR}"' EXIT
extract "${IMAGE}" "/efi/EFI/Linux" "${DIR}/uki"
sudo chown -R "${USER}:${USER}" "${DIR}/uki"
cp "${DIR}"/uki/*.efi "${DIR}/03-uki.efi"
get_cmdline_from_uki "${DIR}/03-uki.efi" "${DIR}/cmdline"
cmdline=$(cat "${DIR}/cmdline")
cmdline_hash=$(cmdline_measure "${DIR}/cmdline")
cleanup "${DIR}"
expected_pcr_12=0000000000000000000000000000000000000000000000000000000000000000
expected_pcr_12=$(pcr_extend "${expected_pcr_12}" "${cmdline_hash}" "sha256sum")
if [[ ${CSP} == "azure" ]]; then
# Azure displays the boot menu
# triggering an extra measurement of the kernel command line.
expected_pcr_12=$(pcr_extend "${expected_pcr_12}" "${cmdline_hash}" "sha256sum")
fi
echo "Kernel commandline: ${cmdline}"
echo "Kernel Commandline measurement ${cmdline_hash}"
echo ""
echo "Expected PCR[12]: ${expected_pcr_12}"
echo ""
write_output "${OUT}"

View File

@ -1,77 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) Edgeless Systems GmbH
#
# SPDX-License-Identifier: AGPL-3.0-only
# This script is used to precalculate the PCR[4] value for a Constellation OS image.
# Usage: precalculate_pcr_4.sh <path to image> <path to output file>
set -euo pipefail
shopt -s inherit_errexit
source "$(dirname "$0")/measure_util.sh"
ev_efi_action_sha256=3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba
ev_efi_separator_sha256=df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119
authentihash() {
local path="$1"
"$(dirname "$0")/extract_authentihash.py" "${path}"
}
write_output() {
local out="$1"
cat > "${out}" << EOF
{
"measurements": {
"4": {
"expected": "${expected_pcr_4}"
}
},
"efistages": [
{
"name": "shim",
"sha256": "${shim_authentihash}"
},
{
"name": "systemd-boot",
"sha256": "${sd_boot_authentihash}"
},
{
"name": "uki",
"sha256": "${uki_authentihash}"
}
]
}
EOF
}
DIR=$(mktempdir)
trap 'cleanup "${DIR}"' EXIT
extract "$1" "/efi/EFI/BOOT/BOOTX64.EFI" "${DIR}/01-shim.efi"
extract "$1" "/efi/EFI/BOOT/grubx64.efi" "${DIR}/02-sd-boot.efi"
extract "$1" "/efi/EFI/Linux" "${DIR}/uki"
sudo chown -R "${USER}:${USER}" "${DIR}/uki"
cp "${DIR}"/uki/*.efi "${DIR}/03-uki.efi"
shim_authentihash=$(authentihash "${DIR}/01-shim.efi")
sd_boot_authentihash=$(authentihash "${DIR}/02-sd-boot.efi")
uki_authentihash=$(authentihash "${DIR}/03-uki.efi")
cleanup "${DIR}"
expected_pcr_4=0000000000000000000000000000000000000000000000000000000000000000
expected_pcr_4=$(pcr_extend "${expected_pcr_4}" "${ev_efi_action_sha256}" "sha256sum")
expected_pcr_4=$(pcr_extend "${expected_pcr_4}" "${ev_efi_separator_sha256}" "sha256sum")
expected_pcr_4=$(pcr_extend "${expected_pcr_4}" "${shim_authentihash}" "sha256sum")
expected_pcr_4=$(pcr_extend "${expected_pcr_4}" "${sd_boot_authentihash}" "sha256sum")
expected_pcr_4=$(pcr_extend "${expected_pcr_4}" "${uki_authentihash}" "sha256sum")
echo "Authentihashes:"
echo "Stage 1 - shim: ${shim_authentihash}"
echo "Stage 2 - sd-boot: ${sd_boot_authentihash}"
echo "Stage 3 - Unified Kernel Image (UKI): ${uki_authentihash}"
echo ""
echo "Expected PCR[4]: ${expected_pcr_4}"
echo ""
write_output "$2"

View File

@ -1,59 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) Edgeless Systems GmbH
#
# SPDX-License-Identifier: AGPL-3.0-only
# This script is used to precalculate the PCR[9] value for a Constellation OS image.
# PCR[9] contains the hash of the initrd and is measured by the linux kernel after loading the initrd.
# Usage: precalculate_pcr_9.sh <path to image> <path to output file>
set -euo pipefail
shopt -s inherit_errexit
source "$(dirname "$0")/measure_util.sh"
get_initrd_from_uki() {
local uki="$1"
local output="$2"
objcopy -O binary --only-section=.initrd "${uki}" "${output}"
}
initrd_measure() {
local path="$1"
sha256sum "${path}" | cut -d " " -f 1
}
write_output() {
local out="$1"
cat > "${out}" << EOF
{
"measurements": {
"9": {
"expected": "${expected_pcr_9}"
}
},
"initrd-sha256": "${initrd_hash}"
}
EOF
}
DIR=$(mktempdir)
trap 'cleanup "${DIR}"' EXIT
extract "$1" "/efi/EFI/Linux" "${DIR}/uki"
sudo chown -R "${USER}:${USER}" "${DIR}/uki"
cp "${DIR}"/uki/*.efi "${DIR}/03-uki.efi"
get_initrd_from_uki "${DIR}/03-uki.efi" "${DIR}/initrd"
initrd_hash=$(initrd_measure "${DIR}/initrd")
cleanup "${DIR}"
expected_pcr_9=0000000000000000000000000000000000000000000000000000000000000000
expected_pcr_9=$(pcr_extend "${expected_pcr_9}" "${initrd_hash}" "sha256sum")
echo "Initrd measurement ${initrd_hash}"
echo ""
echo "Expected PCR[9]: ${expected_pcr_9}"
echo ""
write_output "$2"