Skip to content

Commit

Permalink
Add support for TPM2_PCR_Allocate
Browse files Browse the repository at this point in the history
This adds test for the actual command and in testutil. Whilst it's not
expected that this code will be used in production, adjusting the PCR
allocation in unit test code that uses the simulator is a valid use
case.
  • Loading branch information
chrisccoulson committed Jul 31, 2024
1 parent 12ba3f6 commit 60cdf72
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 41 deletions.
22 changes: 22 additions & 0 deletions cmds_pcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,28 @@ func (t *TPMContext) PCRRead(pcrSelectionIn PCRSelectionList, sessions ...Sessio
return pcrUpdateCounter, pcrValues, nil
}

// PCRAllocate executes the TPM2_PCR_Allocate command to set the PCR allocation, which is
// persistent even across TPM2_Clear. The supplied authContext parameter must correspond to
// [HandlePlatform]. This command requires authorization of authContext with the user auth
// role, with session based authorization provided via authContextAuthSession.
//
// The desired PCR allocation is supplied via the pcrAllocation argument. The function indicates
// whether the allocation was successful. This will only be true if no error is returned. Note
// that the allocation takes effect after the next TPM reset. The function returns the maximum
// number of PCRs supported per bank, plus the size needed for the new allocation and the the
// size available.
func (t *TPMContext) PCRAllocate(authContext ResourceContext, pcrAllocation PCRSelectionList, authContextAuthSession SessionContext, sessions ...SessionContext) (allocationSuccess bool, maxPCR uint32, sizeNeeded uint32, sizeAvailable uint32, err error) {
if err := t.StartCommand(CommandPCRAllocate).
AddHandles(UseResourceContextWithAuth(authContext, authContextAuthSession)).
AddParams(pcrAllocation).
AddExtraSessions(sessions...).
Run(nil, &allocationSuccess, &maxPCR, &sizeNeeded, &sizeAvailable); err != nil {
return false, 0, 0, 0, err
}

return allocationSuccess, maxPCR, sizeNeeded, sizeAvailable, nil
}

// PCRReset executes the TPM2_PCR_Reset command to reset the PCR associated with pcrContext in all
// banks. This command requires authorization with the user auth role for pcrContext, with session
// based authorization provided via pcrContextAuthSession.
Expand Down
85 changes: 85 additions & 0 deletions cmds_pcr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import (
"bytes"
"testing"

"github.com/canonical/go-tpm2"
. "github.com/canonical/go-tpm2"
internal_testutil "github.com/canonical/go-tpm2/internal/testutil"
"github.com/canonical/go-tpm2/testutil"
. "gopkg.in/check.v1"
)

func TestPCRExtend(t *testing.T) {
Expand Down Expand Up @@ -380,3 +383,85 @@ func TestPCRReset(t *testing.T) {
})
}
}

type pcrSuite struct {
testutil.TPMSimulatorTest
}

var _ = Suite(&pcrSuite{})

type testPCRAllocationParams struct {
allocation PCRSelectionList
authContextAuthSession SessionContext
}

func (s *pcrSuite) testPCRAllocation(c *C, params *testPCRAllocationParams) error {
sessionHandles := []Handle{authSessionHandle(params.authContextAuthSession)}

success, _, sizeNeeded, _, err := s.TPM.PCRAllocate(s.TPM.PlatformHandleContext(), params.allocation, params.authContextAuthSession)
if err != nil {
c.Check(success, internal_testutil.IsFalse)
return err
}

c.Check(success, internal_testutil.IsTrue)

var expectedSizeNeeded uint32
for _, selection := range params.allocation {
digestSize := selection.Hash.Size()
expectedSizeNeeded += uint32(len(selection.Select) * digestSize)
}
c.Check(sizeNeeded, internal_testutil.IntEqual, expectedSizeNeeded)

authArea := s.LastCommand(c).CmdAuthArea
c.Assert(authArea, internal_testutil.LenEquals, 1)
c.Check(authArea[0].SessionHandle, Equals, sessionHandles[0])

s.ResetTPMSimulator(c)

current, err := s.TPM.GetCapabilityPCRs()
c.Check(err, IsNil)
c.Check(current, testutil.TPMValueDeepEquals, params.allocation)

return nil
}

func (s *pcrSuite) TestPCRAllocation1(c *C) {
current, err := s.TPM.GetCapabilityPCRs()
c.Assert(err, IsNil)

for i := range current {
current[i].Select = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}
}
err = s.testPCRAllocation(c, &testPCRAllocationParams{
allocation: current,
})
c.Check(err, IsNil)
}

func (s *pcrSuite) TestPCRAllocation2(c *C) {
current, err := s.TPM.GetCapabilityPCRs()
c.Assert(err, IsNil)
c.Assert(current, internal_testutil.LenGreater, 1)

current[0].Select = nil

err = s.testPCRAllocation(c, &testPCRAllocationParams{
allocation: current,
})
c.Check(err, IsNil)
}

func (s *pcrSuite) TestPCRAllocationWithSession(c *C) {
current, err := s.TPM.GetCapabilityPCRs()
c.Assert(err, IsNil)

for i := range current {
current[i].Select = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}
}
err = s.testPCRAllocation(c, &testPCRAllocationParams{
allocation: current,
authContextAuthSession: s.StartAuthSession(c, nil, nil, tpm2.SessionTypeHMAC, nil, tpm2.HashAlgorithmSHA256),
})
c.Check(err, IsNil)
}
8 changes: 8 additions & 0 deletions internal/testutil/checkers.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,14 @@ func (checker *hasLenChecker) Check(params []interface{}, names []string) (resul
var LenEquals Checker = &hasLenChecker{
&CheckerInfo{Name: "LenEquals", Params: []string{"value", "n"}}, IntEqual}

// LenGreater checks that the value has a length that is greater than n.
//
// For example:
//
// c.Check(value, LenGreater, 5)
var LenGreater Checker = &hasLenChecker{
&CheckerInfo{Name: "LenGreater", Params: []string{"value", "n"}}, IntGreater}

// LenGreaterEquals checks that the value has a length that is greater than or equal
// to n.
//
Expand Down
6 changes: 5 additions & 1 deletion testutil/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import (
"github.com/canonical/go-tpm2"
)

func MockWrapMssimTransport(fn func(tpm2.Transport) (*Transport, error)) (restore func()) {
const (
TpmFeatureSimulatorOnlyPCRAllocation = tpmFeatureSimulatorOnlyPCRAllocation
)

func MockWrapMssimTransport(fn func(tpm2.Transport, TPMFeatureFlags) (*Transport, error)) (restore func()) {
origWrapMssimTransport := wrapMssimTransport
wrapMssimTransport = fn
return func() {
Expand Down
16 changes: 15 additions & 1 deletion testutil/suites.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func (b *TPMTest) initTPMContextIfNeeded(c *C) {
case b.Transport != nil:
// A transport has been provided by the test using the new field.
// Create a TPMContext from the supplied transport
b.TPM, _ = OpenTPMDevice(c, newTransportPassthroughDevice(b.Transport))
b.TPM, _ = OpenTPMDevice(c, NewTransportPassthroughDevice(b.Transport))
b.TCTI = b.Transport // populate the deprecated field
case b.Device != nil:
// A device has been provided by the test.
Expand Down Expand Up @@ -456,6 +456,20 @@ func (b *TPMSimulatorTest) SetUpTest(c *C) {
return
}
b.ResetAndClearTPMSimulatorUsingPlatformHierarchy(c)
c.Check(b.TPM.Close(), IsNil)
b.TPM = nil

if b.Transport.didUpdatePcrAllocation {
// We need to give the TPM one more reset
c.Assert(b.Device, NotNil)
tpm, transport := OpenTPMDevice(c, b.Device)
b.TPM = tpm
b.Transport = transport
b.TCTI = transport
b.ResetTPMSimulator(c)
c.Check(b.TPM.Close(), IsNil)
b.TPM = nil
}
})
}

Expand Down
44 changes: 29 additions & 15 deletions testutil/tpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ const (
// were not created by the test, such as writing to or undefining NV indices or evicting
// persistent objects.
TPMFeaturePersistent

tpmFeatureSimulatorOnlyPCRAllocation = 1 << 31
)

func (f TPMFeatureFlags) String() string {
Expand Down Expand Up @@ -168,9 +170,7 @@ var (
// MssimPort defines the port number of the TPM simulator command port where TPMBackend is TPMBackendMssim.
MssimPort uint = 2321

wrapMssimTransport = func(transport tpm2.Transport) (*Transport, error) {
return WrapTransport(transport, TPMFeatureFlags(math.MaxUint32))
}
wrapMssimTransport = WrapTransport

ErrSkipNoTPM = errors.New("no TPM configured for the test")
)
Expand Down Expand Up @@ -634,8 +634,10 @@ func NewTransportT(t *testing.T, features TPMFeatureFlags) *Transport {
}

// TransportBackedDevice is a TPMDevice that is backed by an already
// opened transport, and just returns the same transport on each open
// call. It keeps track of how many transports are currently open.
// opened transport, and just returns a new transport that wraps the same
// supplied transport on each open call. It keeps track of how many transports
// are currently open.
//
// Consider that whilst [tpm2.TPMDevice] implementations can generally be
// used by more than one goroutine, each opened [tpm2.Transport] is only
// safe to use from a single goroutine, and this device returns multiple
Expand All @@ -647,10 +649,10 @@ type TransportBackedDevice struct {
}

// NewTransportBackedDevice returns a new TPMDevice from the supplied
// transport that just returns the same transport on each call to Open.
// It's useful in tests where it is necessary to create multiple
// [tpm2.TPMContext] instances from the same underlying transport, although
// this isn't something one would do in normal production use. An example
// transport that just returns a new transport that wraps the same transport
// on each call to Open. It's useful in tests where it is necessary to create
// multiple [tpm2.TPMContext] instances from the same underlying transport,
// although this isn't something one would do in normal production use. An example
// here is creating a [tpm2.TPMContext] in the code under test that shares
// a transport with the unit test code (which has its own [tpm2.TPMContext]).
//
Expand All @@ -659,12 +661,14 @@ type TransportBackedDevice struct {
// calls to Open will return a transport that is already closed. If it is
// false, the returned device will keep track of how many transports are open,
// but calling Close on any returned transport will not actually close the
// underlying transport.
// underlying transport - it will mark that specific one as closed.
//
// Consider that whilst [tpm2.TPMDevice] implementations can generally be
// used by more than one goroutine, each opened [tpm2.Transport] is only
// safe to use from a single goroutine, and this device returns multiple
// pointers to the same transport.
//
// Note that this device does not work with [OpenTPMDevice] or [OpenTPMDeviceT].
func NewTransportBackedDevice(transport *Transport, closable bool) *TransportBackedDevice {
return &TransportBackedDevice{
transport: transport,
Expand Down Expand Up @@ -704,8 +708,10 @@ func (t *duplicateTransport) Unwrap() tpm2.Transport {
}

// Open implements [tpm2.TPMDevice.Open]. Whilst most implementations of this
// return a new transport, this repeatedly returns the same transport which means each
// call to this returns transports that generally have to be used on the same gorountine.
// return a new transport, and this does return a new transport structure, it is just
// a wrapper for the transport that was supplied to [NewTransportBackedDevice], which
// means each call to this returns transports that generally have to be used on the
// same gorountine.
func (d *TransportBackedDevice) Open() (tpm2.Transport, error) {
d.opened += 1
return &duplicateTransport{
Expand All @@ -722,12 +728,20 @@ type transportPassthroughDevice struct {
transport *Transport
}

func newTransportPassthroughDevice(transport *Transport) tpm2.TPMDevice {
// NewTransportPassthroughDevice returns a device that returns the supplied transport
// on the first and only call to its Open method. On subsequent calls, it returns the
// [ErrSkipNoTPM] error, which makes it suitable for [OpenTPMDevice] and [OpenTPMDeviceT].
func NewTransportPassthroughDevice(transport *Transport) tpm2.TPMDevice {
return &transportPassthroughDevice{transport: transport}
}

func (d *transportPassthroughDevice) Open() (tpm2.Transport, error) {
return d.transport, nil
transport := d.transport
d.transport = nil
if transport == nil {
return nil, ErrSkipNoTPM
}
return transport, nil
}

func (d *transportPassthroughDevice) String() string {
Expand Down Expand Up @@ -972,7 +986,7 @@ func (d *simulatorDevice) Open() (tpm2.Transport, error) {
return nil, err
}

return wrapMssimTransport(transport)
return wrapMssimTransport(transport, TPMFeatureFlags(math.MaxUint32))
}

func (d *simulatorDevice) String() string {
Expand Down
Loading

0 comments on commit 60cdf72

Please sign in to comment.