This repository has been archived by the owner on Sep 6, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add subcommand "computePCR7" to compute pcr7 for a keyset
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
Showing
4 changed files
with
315 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ func main() { | |
provisionCmd, | ||
tpmPolicyGenCmd, | ||
extendPCR7Cmd, | ||
computePCR7Cmd, | ||
|
||
// keyset | ||
keysetCmd, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.