-
Notifications
You must be signed in to change notification settings - Fork 158
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
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 80768b1
use reflection for marshalling helpers
chrisfenner a588031
break update most 2Bs, left off fixing the weird TPMU_SENSITIVE_CREATE
chrisfenner 19767bc
finish making all the 2Bs generic
chrisfenner 2a4d4ef
get some of the unions migrated over
chrisfenner d6e0322
add maybe
chrisfenner f56968e
design the nicer tpmu api
chrisfenner c5f9000
delete the old unwraps
chrisfenner c194a85
replace the weird boxed types with a generic type
chrisfenner e24118f
fix the box
chrisfenner 885e02f
sig scheme
chrisfenner f5ae2a0
kdf
chrisfenner 8745d7c
asym scheme
chrisfenner 4664afb
add signature
chrisfenner 6c5e9af
get the rest of the unions working
chrisfenner 59fca5b
fix overuse of memory by switching from tags to tag function
chrisfenner 62153a3
remove old TODOs
chrisfenner e99d3da
rename allocateAndGet to create
chrisfenner efce1b6
put pointers into box
chrisfenner 9a427ad
make the News return by value
chrisfenner bb631ab
rename NewTPMUs to TPMU
chrisfenner bc7a3e8
rename NewTPM2Bs to TPM2B
chrisfenner cc3e50d
clean up some comments
chrisfenner 15e029b
fix stray deletions
chrisfenner 20d185c
little bit of linting
chrisfenner 27eb18c
go back to 1.18
chrisfenner 892576d
move the rest of the unions over
chrisfenner cb5551d
delete wrappers
chrisfenner 6e752c1
rename TPM2BSensitiveCreate
chrisfenner 37ece20
fix some linter issues
chrisfenner b47f2a0
fix bug in TPM properties return value
chrisfenner 4ada408
update go-tpm-tools dep
chrisfenner f614469
hide unmarshallableWithHint
chrisfenner 1317f8a
get rid of maybe
chrisfenner 2c4ed7d
remove unnecessary generics from Marshal
chrisfenner 2a3dd05
gofmt
chrisfenner eecf011
execute commands by value and tweak the generic interface
chrisfenner ed1c3ed
Switch to taking Marshallable types by value
chrisfenner 9003b1a
make TPM2B a generic type
chrisfenner 89afd86
make TPM2Bs all public
chrisfenner b90e66f
make TPMU and the Contents constraints public
chrisfenner 4567fe4
Add comments for the TPMU constructors
chrisfenner 889e9f5
fix issues found by linter; fix broken test
chrisfenner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
|
||
// Unmarshallable represents any TPM type that can be marshalled or unmarshalled. | ||
type Unmarshallable interface { | ||
Marshallable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
chrisfenner marked this conversation as resolved.
Show resolved
Hide resolved
chrisfenner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// *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 { | ||
chrisfenner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
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.
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?Yeah, this is a great point. I'll make e.g., the
TPM2B
types themselves public and provide public constructors.There was a problem hiding this comment.
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:Then in the main
tpm2
module will have just the typedefs:I think not having
Marshall
/marshal
return an error makes the library easier to use.