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 30 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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
)

require golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb h1:sgcyLNYiHqEd8eFVh0PflG5ABPTGcPSJacD3s19RTcY=
golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
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
237 changes: 237 additions & 0 deletions tpm2/marshalling.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
package tpm2

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

// maybe represents some type that came out of an unmarshalling process.
// We may have the value, or we may have an error.
type maybe[T any] struct {
err error
value *T
}

// OK returns true if the maybe contains contents instead of an error.
func (m maybe[_]) OK() bool {
return m.err == nil
}

// Unwrap unwraps the result of an unmarshalling operation.
// Panics if the data was not unmarshalled.
func (m maybe[T]) Unwrap() *T {
if m.err != nil {
panic(fmt.Sprintf("could not unwrap: %v", m.err))
}
return m.value
}

// CheckUnwrap unwraps the result of an unmarshalling operation.
func (m maybe[T]) CheckUnwrap() (*T, error) {
if m.err != nil {
return nil, m.err
}
return m.value, nil
}

// asMaybe returns a maybe enclosing the given contents.
func asMaybe[T any](t *T) maybe[T] {
return maybe[T]{
value: t,
}
}

// maybeNot returns a maybe with the given error.
func maybeNot[T any](err error) maybe[T] {
return maybe[T]{
err: err,
}
}

// 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[T Marshallable](v T) []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) maybe[T] {
buf := bytes.NewBuffer(data)
var t T
value := reflect.New(reflect.TypeOf(t))
if err := unmarshal(buf, value.Elem()); err != nil {
return maybe[T]{
err: err,
}
}
return maybe[T]{
value: value.Interface().(*T),
}
}

// 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 can be provided either by structure or by byte-array.
// When deserialized (e.g., from a TPM response), both contents and Buffer are populated.
// When serialized, if contents is non-nil, the value of contents is used.
//
// 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 bufferm 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() maybe[T] {
if value.contents != nil {
return asMaybe(value.contents)
}
if value.buffer == nil {
return maybeNot[T](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 maybeNot[T](err)
}
return asMaybe(&result)
}

// 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))
}
Loading