Skip to content
This repository has been archived by the owner on Sep 6, 2023. It is now read-only.

Commit

Permalink
Add subcommand "computePCR7" to compute pcr7 for a keyset
Browse files Browse the repository at this point in the history
This subcommand takes the keysetName as an argument. It
computes the expected pcr7 value based on expected order of tpm
events that occur during a secureboot boot.
Expected uefi secureboot variables, including key databases, and
certificates known to extend into pcr7 during boot are generated
according to tpm specs using the specified keyset and measured to
produce expected pcr7 value.

Should additional drivers and uefi programs be added to the boot, then
this implementation would need to be revisited to include those certificates
that would be extended into pcr7.

The DBX hash and guid is hardcoded from the DBX value in current ovmf_vars.fd
from bootkit. Should this change or when we decide how to manage DBX, this
code will need to be revisited.

Because the mos shim includes 3 uki keys, "computePCR7" returns 3 pcr7 values,
one for each key in the order, pcr7 value, for production, limited, tpm.

Signed-off-by: Joy Latten <[email protected]>
  • Loading branch information
Joy Latten authored and hallyn committed Jul 24, 2023
1 parent 44d0eaa commit f464673
Show file tree
Hide file tree
Showing 4 changed files with 315 additions and 17 deletions.
273 changes: 273 additions & 0 deletions cmd/trust/computepcr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
package main

import (
"bytes"
"crypto"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/canonical/go-efilib"
"github.com/canonical/tcglog-parser"
"github.com/urfave/cli"
)

const ShimLockGUID = "605dab50-e046-4300-abb6-3dd810dd8b23"
const vendordbGUID = "00000000-0000-0000-0000-000000000000"
const SBAT = "sbat,1,2021030218\012"
// Using DBX data from current ovmf_vars.fd in bootkit.
// Revisit if ovmf or dbx changes. We need to eventually manage dbx.
const DBX = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
const DBXGuid = "a3a8baa01d04a848bc87c36d121b5e3d"

var computePCR7Cmd = cli.Command{
Name: "computePCR7",
Usage: "Compute PCR7 value for a given keyset",
ArgsUsage: "<keyset-name>",
Action: doComputePCR7,
}

func doComputePCR7(ctx *cli.Context) error {
args := ctx.Args()
if len(args) != 1 {
return errors.New("Required argument: keysetName")
}
keysetName := args[0]
if keysetName == "" {
return errors.New("Please specify a keyset name")
}
pcr7prod, pcr7lim, pcr7tpm, err := computepcr7(keysetName)
if err != nil {
return fmt.Errorf("Failed to generate pcr7 values for %s keyset: (%w)\n", keysetName, err)
}
fmt.Printf("uki-production: %x\nuki-limited: %x\nuki-tpm: %x\n", pcr7prod, pcr7lim, pcr7tpm)

return nil
}

type efiVarInfo struct {
varguid efi.GUID
hashed []byte
}

func getCert(certfile string) ([]byte, error) {
// read cert data from certfile and put in DER
certData, err := os.ReadFile(certfile)
if err != nil {
return nil, fmt.Errorf("Failed to read %q: (%w)", certfile, err)
}

block, _ := pem.Decode(certData)
if block == nil {
return nil, errors.New("failed to pem.Decode cert")
}
return block.Bytes, nil
}

func getCertGUID(guidfile string) (efi.GUID, error) {
// Read and decode the guid for the cert
cGuid, err := os.ReadFile(guidfile)
if err != nil {
return efi.GUID{}, fmt.Errorf("Failed to read %q: (%w)", guidfile, err)
}
certGuid, err := efi.DecodeGUIDString(string(cGuid))
if err != nil {
return efi.GUID{}, fmt.Errorf("Failed to decode the guid in %q: (%w)", guidfile, err)
}

return certGuid, nil
}

func createSigData(unicodeName string, varGUID efi.GUID, certfile, guidfile string) ([]byte, error) {
// Read cert.pem and guid
certBytes, err := getCert(certfile)
if err != nil {
return nil, err
}
certGuid, err := getCertGUID(guidfile)
if err != nil {
return nil, err
}

// Create SignatureData
sd := efi.SignatureData{Owner: certGuid, Data: certBytes}
var b bytes.Buffer
sd.Write(&b)
return tcglog.ComputeEFIVariableDataDigest(crypto.SHA256, unicodeName, varGUID, b.Bytes()), nil
}

func createESLfromCert(unicodeName string, varGUID efi.GUID, certfile, guidfile string) ([]byte, error) {
// Read cert.pem and guid
certBytes, err := getCert(certfile)
if err != nil {
return nil, err
}
certGuid, err := getCertGUID(guidfile)
if err != nil {
return nil, err
}

// create a SignatureList
sl := efi.SignatureList{Type: efi.CertX509Guid, Header: []byte{}}
sl.Signatures = append(sl.Signatures, &efi.SignatureData{Owner: certGuid, Data: certBytes})

var b bytes.Buffer
sl.Write(&b)
return tcglog.ComputeEFIVariableDataDigest(crypto.SHA256, unicodeName, varGUID, b.Bytes()), nil
}

func createESLfromHash(unicodeName string, varGUID efi.GUID, hdata []byte, hguid efi.GUID) ([]byte, error) {
sl := efi.SignatureList{Type: efi.CertSHA256Guid, Header: []byte{}}
sl.Signatures = append(sl.Signatures, &efi.SignatureData{Owner: hguid, Data: hdata})
var b bytes.Buffer
sl.Write(&b)
return tcglog.ComputeEFIVariableDataDigest(crypto.SHA256, unicodeName, varGUID, b.Bytes()), nil
}

func getHash(unicodeName string, varGuid efi.GUID, keysetPath string) ([]byte, error) {

switch unicodeName {
case "SecureBoot", "MokListTrusted":
efiData := []byte{1}
return tcglog.ComputeEFIVariableDataDigest(crypto.SHA256, unicodeName, varGuid, efiData), nil

case "SbatLevel":
return tcglog.ComputeEFIVariableDataDigest(crypto.SHA256, unicodeName, varGuid, []byte(SBAT)), nil

case "PK", "KEK", "db":
dir := strings.ToLower(unicodeName)
certfile := filepath.Join(keysetPath, "uefi-"+dir+"/cert.pem")
guidfile := filepath.Join(keysetPath, "uefi-"+dir+"/guid")

hashed, err := createESLfromCert(unicodeName, varGuid, certfile, guidfile)
if err != nil {
return nil, err
}
return hashed, nil

case "dbx":
hdata, err := hex.DecodeString(DBX)
if err != nil {
return nil, err
}

guiddata, err := hex.DecodeString(DBXGuid)
if err != nil {
return nil, fmt.Errorf("Failed to decode the dbx guid: (%w)", err)
}

r := bytes.NewReader(guiddata)
hguid, err := efi.ReadGUID(r)
if err != nil {
return nil, err
}

hashed, err := createESLfromHash(unicodeName, varGuid, hdata, hguid)
if err != nil {
return nil, err
}
return hashed, nil

case "separator":
return tcglog.ComputeSeparatorEventDigest(crypto.SHA256, tcglog.SeparatorEventNormalValue), nil

case "shim-cert":
certfile := filepath.Join(keysetPath, "uefi-db/cert.pem")
guidfile := filepath.Join(keysetPath, "uefi-db/guid")
hashed, err := createSigData("db", varGuid, certfile, guidfile)
if err != nil {
return nil, err
}
return hashed, nil

case "production", "tpm", "limited":
certfile := filepath.Join(keysetPath, "uki-"+unicodeName, "cert.pem")
certBytes, err := getCert(certfile)
if err != nil {
return nil, err
}

certGuid, err := efi.DecodeGUIDString(vendordbGUID)
if err != nil {
return nil, err
}

// Create SignatureData
sd := efi.SignatureData{Owner: certGuid, Data: certBytes}
var b bytes.Buffer
sd.Write(&b)
return tcglog.ComputeEFIVariableDataDigest(crypto.SHA256, "vendor_db", varGuid, b.Bytes()), nil

default:
return nil, nil
}
}

func computepcr7(keysetName string) ([]byte, []byte, []byte, error) {
var pcr7Val = []byte{00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00}

// List of uefi Secure boot vars that get measured.
// It includes certs that get measured at boot. UKI certs are measured
// separately since we have 3 possible. Also measured in this order.
var uefiMeasured = []string{"SecureBoot", "PK", "KEK", "db", "dbx", "separator", "shim-cert", "SbatLevel", "MokListTrusted" }

shimLockGuid, err := efi.DecodeGUIDString(ShimLockGUID)
if err != nil {
return nil, nil, nil, err
}
var efiSBInfo = map[string]*efiVarInfo{
"SecureBoot": {efi.GlobalVariable, nil},
"PK": {efi.GlobalVariable, nil},
"KEK": {efi.GlobalVariable, nil},
"db": {efi.ImageSecurityDatabaseGuid, nil},
"dbx": {efi.ImageSecurityDatabaseGuid, nil},
"separator": {hashed: nil},
"shim-cert": {efi.ImageSecurityDatabaseGuid, nil},
"SbatLevel": {shimLockGuid, nil},
"MokListTrusted": {shimLockGuid, nil},
}

// List of UKI certs that can get measured.
// The value will be the resulting pcr7 value when the cert is extended.
var ukiKeys = map[string][]byte{"production": nil, "limited": nil, "tpm": nil}

moskeysetPath, err := getMosKeyPath()
if err != nil {
return nil, nil, nil, err
}

keysetPath := filepath.Join(moskeysetPath, keysetName)
if !PathExists(keysetPath) {
return nil, nil, nil, fmt.Errorf("Unknown keyset '%s', cannot find keyset at path: %q", keysetName, keysetPath)
}

// First calculate the uefi vars and certs in order they are extended into pcr7.
for _, k := range uefiMeasured {
efiSBInfo[k].hashed, err = getHash(k, efiSBInfo[k].varguid, keysetPath)
if err != nil {
return nil, nil, nil, err
}
h := crypto.SHA256.New()
h.Write(pcr7Val)
h.Write(efiSBInfo[k].hashed)
pcr7Val = h.Sum(nil)
}

// Now extend in the 3 different possible uki signing keys.
// This will result in 3 different pcr7 values, one for each possible uki key.
for uki, _ := range ukiKeys {
ukiHash, err := getHash(uki, efi.ImageSecurityDatabaseGuid, keysetPath)
if err != nil {
return nil, nil, nil, err
}
h := crypto.SHA256.New()
h.Write(pcr7Val)
h.Write(ukiHash)
ukiKeys[uki] = h.Sum(nil)
}
return ukiKeys["production"], ukiKeys["limited"], ukiKeys["tpm"], nil
}
1 change: 1 addition & 0 deletions cmd/trust/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func main() {
provisionCmd,
tpmPolicyGenCmd,
extendPCR7Cmd,
computePCR7Cmd,

// keyset
keysetCmd,
Expand Down
14 changes: 8 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ module github.com/project-machine/trust
require (
github.com/anuvu/disko v0.0.11
github.com/apex/log v1.9.0
github.com/canonical/go-tpm2 v0.0.0-20220823192114-7a7993f0fa1f
github.com/canonical/go-efilib v0.9.4
github.com/canonical/tcglog-parser v0.0.0-20230429160108-0d6d239de69d
github.com/fatih/color v1.15.0
github.com/foxboron/go-uefi v0.0.0-20230218004016-d1bb9a12f92c
github.com/go-git/go-git/v5 v5.4.2
Expand All @@ -17,6 +18,7 @@ require (
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/canonical/go-tpm2 v0.1.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
Expand All @@ -33,19 +35,19 @@ require (
github.com/sergi/go-diff v1.1.0 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/net v0.6.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/text v0.9.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

require (
github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9 // indirect
github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
github.com/jsipprell/keyctl v1.0.4
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)

Expand Down
Loading

0 comments on commit f464673

Please sign in to comment.