Skip to content

Commit

Permalink
Introduce helper for formatting PCR selection bitmasks
Browse files Browse the repository at this point in the history
This change introduces PCClientCompatible.PCRs(), a function that
converts a variadic list of PCR indices (as ints) into a PCR selection
bitmask. Because of the vagaries of TPM:

1. That the minimum size of a PCR selection bitmask is not 0, but
   related to the minimum number of PCRs specified by the profile
2. That the PC Client Platform TPM Profile specification mandates a
   minimum but not a maximum number of implementation PCR,

this change creates an interface that could be implemented for other TPM
profiles that specify different amounts of PCRs. The vast majority of
on-market TPMs will just work with PCClientCompatible.PCRs, even if they
implement more than 24 PCRs.

PCRs() can panic if given invalid values; this is to allow it to be
inlined into the definition of a structure that needs a PCR selection.
  • Loading branch information
chrisfenner committed Sep 13, 2023
1 parent f397c27 commit bff027a
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 83 deletions.
56 changes: 56 additions & 0 deletions tpm2/pcrs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package tpm2

import "fmt"

type PCRSelectionFormatter interface {
// PCRs returns the TPM PCR selection bitmask associated with the given PCR indices.
// May panic if passed invalid PCR indices.
PCRs(pcrs ...int) []byte
}

// PCClientCompatible is a PCRSelecter that formats PCR selections suitable for
// use in PC Client PTP-compatible TPMs:
// https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/
// PC Client mandates at least 24 PCRs but does not provide an upper limit.
// This implementation allows PCR indices up to 255 to be selected, to allow
// for use out-of-the-box with even slightly bespoke TPMs that are still PC
// Client-Compatible.
var PCClientCompatible = pcClient{}

type pcClient struct{}

func (pcClient) PCRs(pcrs ...int) []byte {
// The PC Client spec does not limit the number of implemented PCRs.
// This implementation chooses 255 as a reasonable upper bound on even
// a very bespoke TPM's PCR allocation.
const pcrSelectLimit = 255
// Find the biggest PCR we selected within the limit.
maxPCR := 23
for _, pcr := range pcrs {
if pcr > maxPCR && pcr <= pcrSelectLimit {
maxPCR = pcr
}
}

// Allocate a byte array to store the bitfield, that has at least
// enough bits to store our selections.
selection := make([]byte, maxPCR/8+1)
for _, pcr := range pcrs {
// Ignore invalid values.
if pcr < 0 || pcr > pcrSelectLimit {
panic(fmt.Sprintf("invalid PCR index %v selected", pcr))
}

// The PCR selection mask is byte-wise little-endian:
// select[0] contains bits representing the selection of PCRs 0 through 7
// select[1] contains PCRs 8 through 15, and so on.
byteIdx := pcr / 8
// Within the byte, the PCR selection is bit-wise big-endian:
// bit 0 of select[0] contains the selection of PCR 0
// bit 1 of select[0] contains the selection of PCR 1, and so on.
bitIdx := pcr % 8

selection[byteIdx] |= (1 << bitIdx)
}
return selection
}
12 changes: 2 additions & 10 deletions tpm2/test/certify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ func TestCertify(t *testing.T) {

Auth := []byte("password")

PCR7, err := CreatePCRSelection([]int{7})
if err != nil {
t.Fatalf("Failed to create PCRSelection")
}
public := New2B(TPMTPublic{
Type: TPMAlgRSA,
NameAlg: TPMAlgSHA256,
Expand Down Expand Up @@ -58,7 +54,7 @@ func TestCertify(t *testing.T) {
PCRSelections: []TPMSPCRSelection{
{
Hash: TPMAlgSHA256,
PCRSelect: PCR7,
PCRSelect: PCClientCompatible.PCRs(7),
},
},
}
Expand Down Expand Up @@ -211,15 +207,11 @@ func TestCreateAndCertifyCreation(t *testing.T) {
),
})

PCR7, err := CreatePCRSelection([]int{7})
if err != nil {
t.Fatalf("Failed to create PCRSelection")
}
pcrSelection := TPMLPCRSelection{
PCRSelections: []TPMSPCRSelection{
{
Hash: TPMAlgSHA1,
PCRSelect: PCR7,
PCRSelect: PCClientCompatible.PCRs(7),
},
},
}
Expand Down
7 changes: 1 addition & 6 deletions tpm2/test/combined_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ func TestCombinedContext(t *testing.T) {
}
defer thetpm.Close()

PCR7, err := CreatePCRSelection([]int{7})
if err != nil {
t.Fatalf("Failed to create PCRSelection")
}

createPrimary := CreatePrimary{
PrimaryHandle: TPMRHOwner,

Expand Down Expand Up @@ -67,7 +62,7 @@ func TestCombinedContext(t *testing.T) {
PCRSelections: []TPMSPCRSelection{
{
Hash: TPMAlgSHA1,
PCRSelect: PCR7,
PCRSelect: PCClientCompatible.PCRs(7),
},
},
},
Expand Down
62 changes: 50 additions & 12 deletions tpm2/test/pcr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,54 @@ import (
"github.com/google/go-tpm/tpm2/transport/simulator"
)

func TestPCRs(t *testing.T) {
for i, tc := range []struct {
pcrs []int
wantSelect []byte
}{
{
pcrs: nil,
wantSelect: []byte{0x00, 0x00, 0x00},
},
{
pcrs: []int{0},
wantSelect: []byte{0x01, 0x00, 0x00},
},
{
pcrs: []int{0, 1, 2},
wantSelect: []byte{0x07, 0x00, 0x00},
},
{
pcrs: []int{0, 7},
wantSelect: []byte{0x81, 0x00, 0x00},
},
{
pcrs: []int{8},
wantSelect: []byte{0x00, 0x01, 0x00},
},
{
pcrs: []int{1, 8, 9},
wantSelect: []byte{0x02, 0x03, 0x00},
},
{
pcrs: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
wantSelect: []byte{0xff, 0xff, 0xff},
},
{
pcrs: []int{255},
wantSelect: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80},
},
} {
t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
selection := PCClientCompatible.PCRs(tc.pcrs...)
if !bytes.Equal(selection, tc.wantSelect) {
t.Errorf("PCRs() = 0x%x, want 0x%x", selection, tc.wantSelect)
}
})
}
}

var extendstpm2 = map[TPMAlgID][]struct {
digest []byte
}{
Expand Down Expand Up @@ -58,11 +106,6 @@ func TestPCRReset(t *testing.T) {

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
PCRs, err := CreatePCRSelection([]int{DebugPCR})
if err != nil {
t.Fatalf("Failed to create PCRSelection")
}

authHandle := AuthHandle{
Handle: TPMHandle(DebugPCR),
Auth: PasswordAuth(nil),
Expand All @@ -73,7 +116,7 @@ func TestPCRReset(t *testing.T) {
PCRSelections: []TPMSPCRSelection{
{
Hash: c.hashalg,
PCRSelect: PCRs,
PCRSelect: PCClientCompatible.PCRs(DebugPCR),
},
},
},
Expand Down Expand Up @@ -146,17 +189,12 @@ func TestPCREvent(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
for i := 0; i < 17; i++ {
t.Run(fmt.Sprintf("PCR%02d", i), func(t *testing.T) {
PCRs, err := CreatePCRSelection([]int{i})
if err != nil {
t.Fatalf("Failed to create PCRSelection")
}

pcrRead := PCRRead{
PCRSelectionIn: TPMLPCRSelection{
PCRSelections: []TPMSPCRSelection{
{
Hash: c.hashalg,
PCRSelect: PCRs,
PCRSelect: PCClientCompatible.PCRs(20),
},
},
},
Expand Down
7 changes: 1 addition & 6 deletions tpm2/test/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,16 +366,11 @@ func TestPolicyPCR(t *testing.T) {
}
defer thetpm.Close()

PCRs, err := CreatePCRSelection([]int{0, 1, 2, 3, 7})
if err != nil {
t.Fatalf("Failed to create PCRSelection")
}

selection := TPMLPCRSelection{
PCRSelections: []TPMSPCRSelection{
{
Hash: TPMAlgSHA1,
PCRSelect: PCRs,
PCRSelect: PCClientCompatible.PCRs(0, 1, 2, 3, 7),
},
},
}
Expand Down
50 changes: 1 addition & 49 deletions tpm2/test/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,12 @@ import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"fmt"
"testing"

"github.com/google/go-cmp/cmp"

. "github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpm2/transport/simulator"
"github.com/google/go-tpm/tpmutil"
)

func CreatePCRSelection(s []int) ([]byte, error) {

const sizeOfPCRSelect = 3

PCRs := make(tpmutil.RawBytes, sizeOfPCRSelect)

for _, n := range s {
if n >= 8*sizeOfPCRSelect {
return nil, fmt.Errorf("PCR index %d is out of range (exceeds maximum value %d)", n, 8*sizeOfPCRSelect-1)
}
byteNum := n / 8
bytePos := byte(1 << (n % 8))
PCRs[byteNum] |= bytePos
}

return PCRs, nil
}

func TestCreatePCRSelection(t *testing.T) {

emptyTest, err := CreatePCRSelection([]int{})
if err != nil {
t.Fatalf("Failed to create PCRSelection")
}

if !cmp.Equal(emptyTest, []byte{0, 0, 0}) {
t.Fatalf("emptyTest does not return valid PCRs")
}

filledTest, err := CreatePCRSelection([]int{0, 1, 2})
if err != nil {
t.Fatalf("Failed to create PCRSelection")
}

if !cmp.Equal(filledTest, []byte{7, 0, 0}) {
t.Fatalf("filledTest does not return valid PCRs")
}
}

func TestSign(t *testing.T) {

thetpm, err := simulator.OpenSimulator()
Expand All @@ -61,11 +18,6 @@ func TestSign(t *testing.T) {
}
defer thetpm.Close()

PCR7, err := CreatePCRSelection([]int{7})
if err != nil {
t.Fatalf("Failed to create PCRSelection")
}

createPrimary := CreatePrimary{
PrimaryHandle: TPMRHOwner,

Expand Down Expand Up @@ -99,7 +51,7 @@ func TestSign(t *testing.T) {
PCRSelections: []TPMSPCRSelection{
{
Hash: TPMAlgSHA1,
PCRSelect: PCR7,
PCRSelect: PCClientCompatible.PCRs(7),
},
},
},
Expand Down

0 comments on commit bff027a

Please sign in to comment.