attest: Implement discovery of supported PCR banks, rather than always blithely assuming we have exactly SHA1 and SHA256. (#404)
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Test / test-linux (1.22.x) (push) Has been cancelled
Test / test-linux-tpm12 (1.22.x) (push) Has been cancelled
Test / test-macos (1.22.x) (push) Has been cancelled
Test / test-windows (1.22.x) (push) Has been cancelled

To do this, add a function to attest.TPM called PCRBanks() which enumerates the available PCR banks on a TPM. This requires plumbing through tpmBase and its implementations; the TPM1.2 implementations statically return []HashAlg{HashSHA1}, as one might expect.

To accomplish all of this, the implementation of HashAlg needed to be rethought. Now, instead of a reimplementation of tpm2.Algorithm, it's a lightweight wrapper around it. Dependent methods -- like Hash() and String() -- no longer have case HashSHA1/case HashSHA256 blocks; instead, they simply delegate to go-tpm2 for their implementations. As a result, we should never need to do something like this again.

Also add convenience constants HashSHA384 and HashSHA512.
This commit is contained in:
zhsh 2025-02-14 18:38:32 +11:00 committed by GitHub
parent f44f5ffe7e
commit d9d8fdc48e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 71 additions and 33 deletions

View File

@ -399,41 +399,30 @@ func (a *AKPublic) VerifyAll(quotes []Quote, pcrs []PCR, nonce []byte) error {
// HashAlg identifies a hashing Algorithm.
type HashAlg uint8
// Valid hash algorithms.
// Known valid hash algorithms.
var (
HashSHA1 = HashAlg(tpm2.AlgSHA1)
HashSHA256 = HashAlg(tpm2.AlgSHA256)
HashSHA384 = HashAlg(tpm2.AlgSHA384)
HashSHA512 = HashAlg(tpm2.AlgSHA512)
)
func (a HashAlg) cryptoHash() crypto.Hash {
switch a {
case HashSHA1:
return crypto.SHA1
case HashSHA256:
return crypto.SHA256
g := a.goTPMAlg()
h, err := g.Hash()
if err != nil {
panic(fmt.Sprintf("HashAlg %v (corresponding to TPM2.Algorithm %v) has no corresponding crypto.Hash", a, g))
}
return 0
return h
}
func (a HashAlg) goTPMAlg() tpm2.Algorithm {
switch a {
case HashSHA1:
return tpm2.AlgSHA1
case HashSHA256:
return tpm2.AlgSHA256
}
return 0
return tpm2.Algorithm(a)
}
// String returns a human-friendly representation of the hash algorithm.
func (a HashAlg) String() string {
switch a {
case HashSHA1:
return "SHA1"
case HashSHA256:
return "SHA256"
}
return fmt.Sprintf("HashAlg<%d>", int(a))
return a.goTPMAlg().String()
}
// PlatformParameters encapsulates the set of information necessary to attest

View File

@ -533,15 +533,7 @@ func ParseEventLog(measurementLog []byte) (*EventLog, error) {
return nil, fmt.Errorf("failed to parse spec ID event: %v", err)
}
for _, alg := range specID.algs {
switch tpm2.Algorithm(alg.ID) {
case tpm2.AlgSHA1:
el.Algs = append(el.Algs, HashSHA1)
case tpm2.AlgSHA256:
el.Algs = append(el.Algs, HashSHA256)
}
}
if len(el.Algs) == 0 {
return nil, fmt.Errorf("measurement log didn't use sha1 or sha256 digests")
el.Algs = append(el.Algs, HashAlg(alg.ID))
}
// Switch to parsing crypto agile events. Don't include this in the
// replayed events since it intentionally doesn't extend the PCRs.

View File

@ -29,6 +29,7 @@ import (
"github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/tpmutil"
"go.uber.org/multierr"
)
const (
@ -312,6 +313,27 @@ func quote20(tpm io.ReadWriter, akHandle tpmutil.Handle, hashAlg tpm2.Algorithm,
}, err
}
func pcrbanks(tpm io.ReadWriter) ([]HashAlg, error) {
vals, _, err := tpm2.GetCapability(tpm, tpm2.CapabilityPCRs, 1024, 0)
if err != nil {
return nil, fmt.Errorf("failed to get TPM available PCR banks: %w", err)
}
var hAlgs []HashAlg
var errs error
for i, v := range vals {
pcrb, ok := v.(tpm2.PCRSelection)
if !ok {
errs = multierr.Append(errs, fmt.Errorf("failed to convert value %d to tpm2.PCRSelection: %v", i, v))
continue
}
hAlgs = append(hAlgs, HashAlg(pcrb.Hash))
}
return hAlgs, errs
}
func readAllPCRs20(tpm io.ReadWriter, alg tpm2.Algorithm) (map[uint32][]byte, error) {
numPCRs := 24
out := map[uint32][]byte{}
@ -357,6 +379,7 @@ type tpmBase interface {
eks() ([]EK, error)
ekCertificates() ([]EK, error)
info() (*TPMInfo, error)
pcrbanks() ([]HashAlg, error)
loadAK(opaqueBlob []byte) (*AK, error)
loadAKWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*AK, error)
@ -483,6 +506,14 @@ func (t *TPM) PCRs(alg HashAlg) ([]PCR, error) {
return t.tpm.pcrs(alg)
}
// PCRBanks returns the list of supported PCR banks on the TPM.
//
// This is a low-level API. Consumers seeking to attest the state of the
// platform should use tpm.AttestPlatform() instead.
func (t *TPM) PCRBanks() ([]HashAlg, error) {
return t.tpm.pcrbanks()
}
func (t *TPM) attestPCRs(ak *AK, nonce []byte, alg HashAlg) (*Quote, []PCR, error) {
pcrs, err := t.PCRs(alg)
if err != nil {
@ -514,9 +545,9 @@ func (t *TPM) attestPlatform(ak *AK, nonce []byte, eventLog []byte) (*PlatformPa
EventLog: eventLog,
}
algs := []HashAlg{HashSHA1}
if t.Version() == TPMVersion20 {
algs = []HashAlg{HashSHA1, HashSHA256}
algs, err := t.PCRBanks()
if err != nil {
return nil, fmt.Errorf("failed to get PCR banks: %w", err)
}
var lastErr error

View File

@ -163,6 +163,10 @@ func allPCRs12(ctx *tspi.Context) (map[uint32][]byte, error) {
return PCRs, nil
}
func (t *trousersTPM) pcrbanks() ([]HashAlg, error) {
return []HashAlg{HashSHA1}, nil
}
func (t *trousersTPM) pcrs(alg HashAlg) ([]PCR, error) {
if alg != HashSHA1 {
return nil, fmt.Errorf("non-SHA1 algorithm %v is not supported on TPM 1.2", alg)

View File

@ -376,6 +376,23 @@ func allPCRs12(tpm io.ReadWriter) (map[uint32][]byte, error) {
return out, nil
}
func (t *windowsTPM) pcrbanks() ([]HashAlg, error) {
switch t.version {
case TPMVersion12:
return []HashAlg{HashSHA1}, nil
case TPMVersion20:
tpm, err := t.pcp.TPMCommandInterface()
if err != nil {
return nil, fmt.Errorf("TPMCommandInterface() failed: %v", err)
}
return pcrbanks(tpm)
default:
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
}
}
func (t *windowsTPM) pcrs(alg HashAlg) ([]PCR, error) {
var PCRs map[uint32][]byte

View File

@ -462,6 +462,10 @@ func (t *wrappedTPM20) loadKeyWithParent(opaqueBlob []byte, parent ParentKeyConf
return &Key{key: newWrappedKey20(hnd, sKey.Blob, sKey.Public, sKey.CreateData, sKey.CreateAttestation, sKey.CreateSignature), pub: pub, tpm: t}, nil
}
func (t *wrappedTPM20) pcrbanks() ([]HashAlg, error) {
return pcrbanks(t.rwc)
}
func (t *wrappedTPM20) pcrs(alg HashAlg) ([]PCR, error) {
PCRs, err := readAllPCRs20(t.rwc, alg.goTPMAlg())
if err != nil {

1
go.mod
View File

@ -9,6 +9,7 @@ require (
github.com/google/go-tpm v0.9.3
github.com/google/go-tpm-tools v0.4.4
github.com/google/go-tspi v0.3.0
go.uber.org/multierr v1.11.0
golang.org/x/sys v0.30.0
)