Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use generics to simplify the TPMDirect interface #310

Merged
merged 43 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
7455056
introduce a generic type for TPM2B
chrisfenner Sep 5, 2022
80768b1
use reflection for marshalling helpers
chrisfenner Sep 6, 2022
a588031
break update most 2Bs, left off fixing the weird TPMU_SENSITIVE_CREATE
chrisfenner Sep 6, 2022
19767bc
finish making all the 2Bs generic
chrisfenner Sep 6, 2022
2a4d4ef
get some of the unions migrated over
chrisfenner Sep 7, 2022
d6e0322
add maybe
chrisfenner Sep 7, 2022
f56968e
design the nicer tpmu api
chrisfenner Sep 8, 2022
c5f9000
delete the old unwraps
chrisfenner Sep 8, 2022
c194a85
replace the weird boxed types with a generic type
chrisfenner Sep 8, 2022
e24118f
fix the box
chrisfenner Sep 8, 2022
885e02f
sig scheme
chrisfenner Sep 8, 2022
f5ae2a0
kdf
chrisfenner Sep 8, 2022
8745d7c
asym scheme
chrisfenner Sep 8, 2022
4664afb
add signature
chrisfenner Sep 8, 2022
6c5e9af
get the rest of the unions working
chrisfenner Sep 8, 2022
59fca5b
fix overuse of memory by switching from tags to tag function
chrisfenner Sep 8, 2022
62153a3
remove old TODOs
chrisfenner Sep 8, 2022
e99d3da
rename allocateAndGet to create
chrisfenner Sep 8, 2022
efce1b6
put pointers into box
chrisfenner Sep 8, 2022
9a427ad
make the News return by value
chrisfenner Sep 8, 2022
bb631ab
rename NewTPMUs to TPMU
chrisfenner Sep 8, 2022
bc7a3e8
rename NewTPM2Bs to TPM2B
chrisfenner Sep 8, 2022
cc3e50d
clean up some comments
chrisfenner Sep 8, 2022
15e029b
fix stray deletions
chrisfenner Sep 8, 2022
20d185c
little bit of linting
chrisfenner Sep 8, 2022
27eb18c
go back to 1.18
chrisfenner Sep 8, 2022
892576d
move the rest of the unions over
chrisfenner Sep 8, 2022
cb5551d
delete wrappers
chrisfenner Sep 8, 2022
6e752c1
rename TPM2BSensitiveCreate
chrisfenner Sep 8, 2022
37ece20
fix some linter issues
chrisfenner Sep 8, 2022
b47f2a0
fix bug in TPM properties return value
chrisfenner Sep 11, 2022
4ada408
update go-tpm-tools dep
chrisfenner Jan 13, 2023
f614469
hide unmarshallableWithHint
chrisfenner Jan 13, 2023
1317f8a
get rid of maybe
chrisfenner Jan 13, 2023
2c4ed7d
remove unnecessary generics from Marshal
chrisfenner Jan 13, 2023
2a3dd05
gofmt
chrisfenner Jan 13, 2023
eecf011
execute commands by value and tweak the generic interface
chrisfenner Jan 21, 2023
ed1c3ed
Switch to taking Marshallable types by value
chrisfenner Feb 3, 2023
9003b1a
make TPM2B a generic type
chrisfenner Feb 4, 2023
89afd86
make TPM2Bs all public
chrisfenner Feb 4, 2023
b90e66f
make TPMU and the Contents constraints public
chrisfenner Feb 4, 2023
4567fe4
Add comments for the TPMU constructors
chrisfenner Feb 4, 2023
889e9f5
fix issues found by linter; fix broken test
chrisfenner Feb 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ lint_task:
--exclude-use-default=false
--exclude stutters
--exclude underscores
--exclude unexported-return
--max-same-issues=0
--max-issues-per-linter=0
./tpmutil/...
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ module github.com/google/go-tpm
go 1.18

require (
github.com/google/go-cmp v0.5.0
github.com/google/go-tpm-tools v0.2.0
golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb
github.com/google/go-cmp v0.5.7
github.com/google/go-tpm-tools v0.3.10
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
)

require golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
require golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
193 changes: 15 additions & 178 deletions go.sum

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tpm2/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func auditRPHash(h TPMIAlgHash, r Response) ([]byte, error) {
parameters := taggedMembers(reflect.ValueOf(r).Elem(), "handle", true)
for i, parameter := range parameters {
if err := marshal(&parms, parameter); err != nil {
return nil, fmt.Errorf("marshalling parameter %v: %w", i, err)
return nil, fmt.Errorf("marshalling parameter %v: %w", i+1, err)
}
}
return rpHash(h, TPMRCSuccess, cc, parms.Bytes())
Expand Down
191 changes: 191 additions & 0 deletions tpm2/marshalling.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package tpm2

import (
"bytes"
"encoding/binary"
"fmt"
"io"
"reflect"
)

// Marshallable represents any TPM type that can be marshalled.
type Marshallable interface {
// marshal will serialize the given value, appending onto the given buffer.
// Returns an error if the value is not marshallable.
marshal(buf *bytes.Buffer)
Comment on lines +12 to +13
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to return an error.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I'll fix this.

To your other questions:

Why do we still have the Startup and Shutdown functions, can't we just use the direct API to call these functions?

As an adapter so that we can use https://github.com/google/go-tpm-tools/tree/master/simulator (which will call back to Startup and Shutdown as free functions in go-tpm) until there's a version of go-tpm-tools that calls TPMDirect startup and shutdown. I'll add a comment and TODO about this.

Why does the Response need a TPMCC as part of its interface?

This allows the internal consistency check in the execute function to match the correct command and response together. I guess this could be done with generics instead now, WDYT?

Having the Command/Response structs contain private types makes it hard to figure out how to construct our use these types.

Yeah, this is a great point. I'll make e.g., the TPM2B types themselves public and provide public constructors.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was playing around a little and I think the following would work well:

Have a tpm2b submodule:

// Package tpm2b ...
//
// Document here how the TPM2B types work and give examples of how to use them
package tpm2b

type TPM2B[T any] struct {
	contents *T
	buffer   []byte
}

func New[T any](t T) TPM2B[T] {
	return TPM2B[T]{contents: &t}
}

func FromBytes[T any](b []byte) TPM2B[T] {
	return TPM2B[T]{buffer: b}
}

func (t *TPM2B[T]) Contents() (*T, error) {
	if value.contents != nil {
		return value.contents, nil
	}
	if value.buffer == nil {
		panic("TPMB had no contents or buffer")
	}
	var result T
	if err := unmarshal(bytes.NewBuffer(value.buffer), reflect.ValueOf(&result).Elem()); err != nil {
		return nil, err
	}
	
	// Cache result
	value.contents = &result
	return value.contents, nil
}

func (t *TPM2B[T]) Bytes() []byte {
	if value.buffer != nil {
		return value.buffer
	}
	if value.contents == nil {
		panic("TPMB had no contents or buffer")
	}
	var temp bytes.Buffer
	marshal(&temp, reflect.ValueOf(value.contents))
	
	// Cache Bytes
	value.buffer = temp.Bytes()
	return value.buffer
}

func (value *tpm2b[T]) marshal(buf *bytes.Buffer) {
	b := value.Bytes()
	binary.Write(buf, binary.BigEndian, uint16(len(b)))
	buf.Write(b)
}

func (value *tpm2b[T]) unmarshal(buf *bytes.Buffer) error {
	var size uint16
	binary.Read(buf, binary.BigEndian, &size)
	value.contents = nil
	value.buffer = make([]byte, size)
	_, err := io.ReadAtLeast(buf, value.buffer)
	return err
}

Then in the main tpm2 module will have just the typedefs:

type TPM2BECCPoint = tpm2b.TPM2B[TPMSECCPoint]

Good catch. I'll fix this.

I think not having Marshall/marshal return an error makes the library easier to use.

}

// Unmarshallable represents any TPM type that can be marshalled or unmarshalled.
type Unmarshallable interface {
Marshallable
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to make it more clear the relationship between Marshallable and Unmarshallable

// marshal will deserialize the given value from the given buffer.
// Returns an error if there was an unmarshalling error or if there was not
// enough data in the buffer.
unmarshal(buf *bytes.Buffer) error
}

// unmarshallableWithHint represents any TPM type that can be marshalled or unmarshalled,
// but that requires a selector ("hint") value when unmarshalling. Most TPMU_ are
// an example of this.
type unmarshallableWithHint interface {
// create will instantiate and return the corresponding union member.
create(hint int64) (reflect.Value, error)
// get will return the corresponding union member by copy. If the union is
// uninitialized, it will initialize a new zero-valued one.
get(hint int64) (reflect.Value, error)
}

// Marshal will serialize the given values, returning them as a byte slice.
func Marshal(v Marshallable) []byte {
var buf bytes.Buffer
if err := marshal(&buf, reflect.ValueOf(v)); err != nil {
panic(fmt.Sprintf("unexpected error marshalling %v: %v", reflect.TypeOf(v).Name(), err))
}
return buf.Bytes()
}

// Unmarshal unmarshals the given type from the byte array.
// Returns an error if the buffer does not contain enough data to satisfy the
// types, or if the types are not unmarshallable.
func Unmarshal[T any, P interface {
// *T must satisfy Marshallable
*T
Unmarshallable
}](data []byte) (*T, error) {
buf := bytes.NewBuffer(data)
var t T
value := reflect.New(reflect.TypeOf(t))
if err := unmarshal(buf, value.Elem()); err != nil {
return nil, err
}
return value.Interface().(*T), nil
}

// marshallableByReflection is a placeholder interface, to hint to the unmarshalling
// library that it is supposed to use reflection.
type marshallableByReflection interface {
reflectionSafe()
}

// marshalByReflection is embedded into any type that can be marshalled by reflection,
// needing no custom logic.
type marshalByReflection struct{}

func (marshalByReflection) reflectionSafe() {}

// These placeholders are required because a type constraint cannot union another interface
// that contains methods.
// Otherwise, marshalByReflection would not implement Unmarshallable, and the Marshal/Unmarshal
// functions would accept interface{ Marshallable | marshallableByReflection } instead.

// Placeholder: because this type implements the defaultMarshallable interface,
// the reflection library knows not to call this.
func (*marshalByReflection) marshal(_ *bytes.Buffer) {
panic("not implemented")
}

// Placeholder: because this type implements the defaultMarshallable interface,
// the reflection library knows not to call this.
func (*marshalByReflection) unmarshal(_ *bytes.Buffer) error {
panic("not implemented")
}

// tpm2b is a helper type for a field that contains either a structure or by byte-array.
// When serialized, if contents is non-nil, the value of contents is used.
// Because it can be instantiated by value or by byte-array, Marshal must be used in order to get
// the flattened data.
//
// Else, the value of Buffer is used.
type tpm2b[T any] struct {
contents *T
buffer []byte
}

type bytesOr[T any] interface{ *T | []byte }

// tpm2bHelper is a helper function that can convert either a structure or a byte buffer into
// the proper TPM2B_ sub-type.
func tpm2bHelper[T any, C bytesOr[T]](contents C) tpm2b[T] {
if typed, ok := any(contents).(*T); ok {
return tpm2b[T]{contents: typed}
}
return tpm2b[T]{buffer: any(contents).([]byte)}
}

// marshal implements the Marshallable interface.
func (value *tpm2b[T]) marshal(buf *bytes.Buffer) {
if value.contents != nil {
var temp bytes.Buffer
marshal(&temp, reflect.ValueOf(value.contents))
binary.Write(buf, binary.BigEndian, uint16(temp.Len()))
io.Copy(buf, &temp)
} else {
binary.Write(buf, binary.BigEndian, uint16(len(value.buffer)))
buf.Write(value.buffer)
}
}

// unmarshal implements the Marshallable interface.
func (value *tpm2b[T]) unmarshal(buf *bytes.Buffer) error {
var size uint16
binary.Read(buf, binary.BigEndian, &size)
value.buffer = make([]byte, size)
n, err := buf.Read(value.buffer)
if err != nil {
return err
}
if n != int(size) {
return fmt.Errorf("ran out of data attempting to read %v bytes from the buffer, which only had %v", size, n)
}
rdr := bytes.NewBuffer(value.buffer)
value.contents = new(T)
return unmarshal(rdr, reflect.ValueOf(value.contents))
}

// Contents returns the structured contents of the tpm2b.
func (value *tpm2b[T]) Contents() (*T, error) {
if value.contents != nil {
return value.contents, nil
}
if value.buffer == nil {
return nil, fmt.Errorf("TPMB had no contents or buffer")
}
var result T
if err := unmarshal(bytes.NewBuffer(value.buffer), reflect.ValueOf(&result).Elem()); err != nil {
return nil, err
}
return &result, nil
}

// boxed is a helper type for corner cases such as unions, where all members must be structs.
type boxed[T any] struct {
Contents *T
}

// box will put a value into a box.
func box[T any](contents *T) boxed[T] {
return boxed[T]{
Contents: contents,
}
}

// unbox will take a value out of a box.
func (b *boxed[T]) unbox() *T {
return b.Contents
}

// marshal implements the Marshallable interface.
func (b *boxed[T]) marshal(buf *bytes.Buffer) {
if b.Contents == nil {
var contents T
marshal(buf, reflect.ValueOf(&contents))
} else {
marshal(buf, reflect.ValueOf(b.Contents))
}
}

// unmarshal implements the Unmarshallable interface.
func (b *boxed[T]) unmarshal(buf *bytes.Buffer) error {
b.Contents = new(T)
return unmarshal(buf, reflect.ValueOf(b.Contents))
}
156 changes: 156 additions & 0 deletions tpm2/marshalling_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package tpm2

import (
"bytes"
"testing"
)

func TestMarshal2B(t *testing.T) {
// Define some TPMT_Public
pub := TPMTPublic{
Type: TPMAlgKeyedHash,
NameAlg: TPMAlgSHA256,
ObjectAttributes: TPMAObject{
FixedTPM: true,
FixedParent: true,
UserWithAuth: true,
NoDA: true,
},
}

// Get the wire-format version
pubBytes := Marshal(&pub)

// Create two versions of the same 2B:
// one instantiated by the actual TPMTPublic
// one instantiated by the contents
var boxed1 tpm2bPublic
var boxed2 tpm2bPublic
boxed1 = TPM2BPublic(&pub)
boxed2 = TPM2BPublic(pubBytes)

boxed1Bytes := Marshal(&boxed1)
boxed2Bytes := Marshal(&boxed2)

if !bytes.Equal(boxed1Bytes, boxed2Bytes) {
t.Errorf("got %x want %x", boxed2Bytes, boxed1Bytes)
}

z, err := Unmarshal[tpm2bPublic](boxed1Bytes)
if err != nil {
t.Fatalf("could not unmarshal TPM2BPublic: %v", err)
}
t.Logf("%v", z)

boxed3Bytes := Marshal(z)
if !bytes.Equal(boxed1Bytes, boxed3Bytes) {
t.Errorf("got %x want %x", boxed3Bytes, boxed1Bytes)
}

// Make a nonsense 2B_Public, demonstrating that the library doesn't have to understand the serialization
boxed1 = TPM2BPublic([]byte{0xff})
}

func unwrap[T any](f func() (*T, error)) *T {
t, err := f()
if err != nil {
panic(err.Error())
}
return t
}

func TestMarshalT(t *testing.T) {
// Define some TPMT_Public
pub := TPMTPublic{
Type: TPMAlgECC,
NameAlg: TPMAlgSHA256,
ObjectAttributes: TPMAObject{
SignEncrypt: true,
},
Parameters: TPMUPublicParms(
TPMAlgECC,
&TPMSECCParms{
CurveID: TPMECCNistP256,
},
),
Unique: TPMUPublicID(
// This happens to be a P256 EKpub from the simulator
TPMAlgECC,
&TPMSECCPoint{
X: TPM2BECCParameter{},
Y: TPM2BECCParameter{},
},
),
}

// Marshal each component of the parameters
symBytes := Marshal(&unwrap(pub.Parameters.ECCDetail).Symmetric)
t.Logf("Symmetric: %x\n", symBytes)
sym, err := Unmarshal[TPMTSymDefObject](symBytes)
if err != nil {
t.Fatalf("could not unmarshal TPMTSymDefObject: %v", err)
}
symBytes2 := Marshal(sym)
if !bytes.Equal(symBytes, symBytes2) {
t.Errorf("want %x\ngot %x", symBytes, symBytes2)
}
schemeBytes := Marshal(&unwrap(pub.Parameters.ECCDetail).Scheme)
t.Logf("Scheme: %x\n", symBytes)
scheme, err := Unmarshal[TPMTECCScheme](schemeBytes)
if err != nil {
t.Fatalf("could not unmarshal TPMTECCScheme: %v", err)
}
schemeBytes2 := Marshal(scheme)
if !bytes.Equal(schemeBytes, schemeBytes2) {
t.Errorf("want %x\ngot %x", schemeBytes, schemeBytes2)
}
kdfBytes := Marshal(&unwrap(pub.Parameters.ECCDetail).KDF)
t.Logf("KDF: %x\n", kdfBytes)
kdf, err := Unmarshal[TPMTKDFScheme](kdfBytes)
if err != nil {
t.Fatalf("could not unmarshal TPMTKDFScheme: %v", err)
}
kdfBytes2 := Marshal(kdf)
if !bytes.Equal(kdfBytes, kdfBytes2) {
t.Errorf("want %x\ngot %x", kdfBytes, kdfBytes2)
}

// Marshal the parameters
parmsBytes := Marshal(unwrap(pub.Parameters.ECCDetail))
t.Logf("Parms: %x\n", parmsBytes)
parms, err := Unmarshal[TPMSECCParms](parmsBytes)
if err != nil {
t.Fatalf("could not unmarshal TPMSECCParms: %v", err)
}
parmsBytes2 := Marshal(parms)
if !bytes.Equal(parmsBytes, parmsBytes2) {
t.Errorf("want %x\ngot %x", parmsBytes, parmsBytes2)
}

// Marshal the unique area
uniqueBytes := Marshal(unwrap(pub.Unique.ECC))
t.Logf("Unique: %x\n", uniqueBytes)
unique, err := Unmarshal[TPMSECCPoint](uniqueBytes)
if err != nil {
t.Fatalf("could not unmarshal TPMSECCPoint: %v", err)
}
uniqueBytes2 := Marshal(unique)
if !bytes.Equal(uniqueBytes, uniqueBytes2) {
t.Errorf("want %x\ngot %x", uniqueBytes, uniqueBytes2)
}

// Get the wire-format version of the whole thing
pubBytes := Marshal(&pub)

pub2, err := Unmarshal[TPMTPublic](pubBytes)
if err != nil {
t.Fatalf("could not unmarshal TPMTPublic: %v", err)
}

// Some default fields might have been populated in the round-trip. Get the wire-format again and compare.
pub2Bytes := Marshal(pub2)

if !bytes.Equal(pubBytes, pub2Bytes) {
t.Errorf("want %x\ngot %x", pubBytes, pub2Bytes)
}
}
Loading