mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-01-26 07:16:08 -05:00
image: reimplement and adapt measurement generation in Go
This commit is contained in:
parent
8e706d6de3
commit
f6d9f91877
@ -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
3
go.mod
@ -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
3
go.sum
@ -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=
|
||||
|
34
image/measured-boot/cmd/BUILD.bazel
Normal file
34
image/measured-boot/cmd/BUILD.bazel
Normal 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"],
|
||||
)
|
227
image/measured-boot/cmd/main.go
Normal file
227
image/measured-boot/cmd/main.go
Normal 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)
|
||||
}
|
||||
}
|
22
image/measured-boot/extract/BUILD.bazel
Normal file
22
image/measured-boot/extract/BUILD.bazel
Normal 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",
|
||||
],
|
||||
)
|
116
image/measured-boot/extract/extract.go
Normal file
116
image/measured-boot/extract/extract.go
Normal 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
|
||||
}
|
239
image/measured-boot/extract/extract_test.go
Normal file
239
image/measured-boot/extract/extract_test.go
Normal 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)
|
||||
}
|
@ -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())
|
9
image/measured-boot/fixtures/BUILD.bazel
Normal file
9
image/measured-boot/fixtures/BUILD.bazel
Normal 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"],
|
||||
)
|
17
image/measured-boot/fixtures/fixtures.go
Normal file
17
image/measured-boot/fixtures/fixtures.go
Normal 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
|
BIN
image/measured-boot/fixtures/uki.efi
Normal file
BIN
image/measured-boot/fixtures/uki.efi
Normal file
Binary file not shown.
39
image/measured-boot/measure/BUILD.bazel
Normal file
39
image/measured-boot/measure/BUILD.bazel
Normal 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",
|
||||
],
|
||||
)
|
31
image/measured-boot/measure/authentihash.go
Normal file
31
image/measured-boot/measure/authentihash.go
Normal 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
|
||||
}
|
33
image/measured-boot/measure/authentihash_test.go
Normal file
33
image/measured-boot/measure/authentihash_test.go
Normal 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,
|
||||
)
|
||||
}
|
17
image/measured-boot/measure/measure_test.go
Normal file
17
image/measured-boot/measure/measure_test.go
Normal 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)
|
||||
}
|
120
image/measured-boot/measure/pcr.go
Normal file
120
image/measured-boot/measure/pcr.go
Normal 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,
|
||||
}
|
||||
}
|
59
image/measured-boot/measure/pcr04.go
Normal file
59
image/measured-boot/measure/pcr04.go
Normal 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
|
||||
}
|
51
image/measured-boot/measure/pcr04_test.go
Normal file
51
image/measured-boot/measure/pcr04_test.go
Normal 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])
|
||||
}
|
60
image/measured-boot/measure/pcr09.go
Normal file
60
image/measured-boot/measure/pcr09.go
Normal 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
|
||||
}
|
38
image/measured-boot/measure/pcr09_test.go
Normal file
38
image/measured-boot/measure/pcr09_test.go
Normal 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])
|
||||
}
|
68
image/measured-boot/measure/pcr11.go
Normal file
68
image/measured-boot/measure/pcr11.go
Normal 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
|
||||
}
|
61
image/measured-boot/measure/pcr11_test.go
Normal file
61
image/measured-boot/measure/pcr11_test.go
Normal 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])
|
||||
}
|
49
image/measured-boot/measure/pcr_test.go
Normal file
49
image/measured-boot/measure/pcr_test.go
Normal 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)
|
||||
}
|
@ -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}"
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
{
|
||||
"measurements": {
|
||||
"8": {
|
||||
"expected": "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"11": {
|
||||
"expected": "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"13": {
|
||||
"expected": "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"15": {
|
||||
"expected": "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
}
|
||||
}
|
8
image/measured-boot/pesection/BUILD.bazel
Normal file
8
image/measured-boot/pesection/BUILD.bazel
Normal 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"],
|
||||
)
|
24
image/measured-boot/pesection/pesection.go
Normal file
24
image/measured-boot/pesection/pesection.go
Normal 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)
|
||||
}
|
@ -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}"
|
@ -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"
|
@ -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"
|
Loading…
x
Reference in New Issue
Block a user