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

Introduce Direct TPM2 API #266

Merged
merged 9 commits into from
Feb 19, 2022
Merged

Introduce Direct TPM2 API #266

merged 9 commits into from
Feb 19, 2022

Conversation

chrisfenner
Copy link
Member

@chrisfenner chrisfenner commented Dec 15, 2021

Apologies that this is a humongous code review. I thought about splitting up the commits, but that would not decrease the total review work. One PR makes it easier to understand how the "big dumb" part of the code (structures.go, commands.go) are designed to support the "small smart" part of the code (sessions.go, reflect.go).

This PR introduces a new method of interacting with a TPM 2.0.
Instead of plumbing each TPM API into one or more Go functions, this
code defines structures for every TPM 2.0 command request and response.
These map 1:1 with the actual command parameters comprehended by the
TPM, so any invocation of any command is possible (once all the command
structures are written).

This PR introduces enough of the TPM 2.0 API surface to put together
some interesting end-to-end tests, mostly around sealing.

Another objective of the Direct API is to facilitate use of the TPM's
session-based command transport features (e.g., audit and encryption
sessions). See the test code for examples of how to easily use these
APIs to, e.g., set up an EK-salted session for session-encrypted unseal.

An excerpt of how this API supports advanced interactions with the TPM, based on sealing_test.go:

	// Create the SRK
	createSRK := tpm2.CreatePrimary{
		PrimaryHandle: tpm.RHOwner,
		InPublic: srkTemplate,
	}
	createSRKRsp, err := createSRK.Execute(tpm)
	if err != nil {
		...
	}

	// Create a sealed blob under the SRK using an SRK-salted decrypt session
	data := []byte("secrets")
	auth := []byte("p@ssw0rd")
	create := tpm2.Create{
		ParentHandle: NamedHandle{
			Handle: createSRKRsp.ObjectHandle,
			Name:   createSRKRsp.Name,
		},
		InSensitive: tpm2b.SensitiveCreate{
			Sensitive: tmps.SensitiveCreate{
				UserAuth: tpm2b.Auth{
					Buffer: auth,
				},
				Data: tpm2b.Data{
					Buffer: data,
				},
			},
		},
		InPublic: tpm2b.Public{
			PublicArea: tpmt.Public{
				Type:    tpm.AlgKeyedHash,
				NameAlg: tpm.AlgSHA256,
				ObjectAttributes: tpma.Object{
					FixedTPM:     true,
					FixedParent:  true,
					UserWithAuth: true,
					NoDA:         true,
				},
			},
		},
	}
	createBlobRsp, err := create.Execute(tpm,
		tpm2.HMAC(tpm.AlgSHA256, 16,
			tpm2.Salted(createSRKRsp.ObjectHandle, createSRKRsp.OutPublic.PublicArea),
			tpm2.AESEncryption(128, tpm2.EncryptIn)))
	if err != nil {
		...
	}

	// Load the sealed blob
	loadBlob := tpm2.Load{
		ParentHandle: tpm2.NamedHandle{
			Handle: createSRKRsp.ObjectHandle,
			Name:   createSRKRsp.Name,
		},
		InPrivate: createBlobRsp.OutPrivate,
		InPublic:  createBlobRsp.OutPublic,
	}
	loadBlobRsp, err := loadBlob.Execute(tpm)
	if err != nil {
		...
	}
	defer func() {
		// Flush the blob
		flush := tpm2.FlushContext{
			FlushHandle: loadBlobRsp.ObjectHandle,
		}
		if _, err := flush.Execute(tpm); err != nil {
			...
		}
	}()

	// Unseal the blob, using the HMAC auth session for encryption
	unseal := tpm2.Unseal{
		ItemHandle: tpm2.AuthHandle{
			Handle: loadBlobRsp.ObjectHandle,
			Name:   loadBlobRsp.Name,
			Auth:   tpm2.HMAC(TPMAlgSHA256, 16, tpm2.Auth(auth2),
				tpm2.AESEncryption(128, EncryptOut)),
		},
	}
	unsealRsp, err := unseal.Execute(tpm)
	if err != nil {
		...
	}

Fixes #259

This commit introduces a new method of interacting with a TPM 2.0.
Instead of plumbing each TPM API into one or more Go functions, this
code defines structures for every TPM 2.0 command request and response.
These map 1:1 with the actual command parameters comprehended by the
TPM, so any invocation of any command is possible (once all the command
structures are written).

This commit introduces enough of the TPM 2.0 API surface to put together
some interesting end-to-end tests, mostly around sealing.

Another objective of the Direct API is to facilitate use of the TPM's
session-based command transport features (e.g., audit and encryption
sessions). See the test code for examples of how to easily use these
APIs to, e.g., set up an EK-salted session for session-encrypted unseal.

Change-Id: I1549dd596869d79ddd41ff3c5f9ffdadc9628ed4
@chrisfenner chrisfenner marked this pull request as ready for review December 15, 2021 21:25
@chrisfenner chrisfenner requested review from alexmwu, jkl73 and a team as code owners December 15, 2021 21:25
tpm2/direct/commands.go Outdated Show resolved Hide resolved
tpm2/direct/constants.go Outdated Show resolved Hide resolved
// SET (1): an algorithm that may be used as an object type
// CLEAR (0): an algorithm that is not used as an object type
Object bool `gotpm:"bit=3"`
Reserved1 uint8 `gotpm:"bit=7:4"`
Copy link
Member Author

Choose a reason for hiding this comment

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

Open question: should we omit this from the definition, and check that reserved bits are all zeros?

Copy link
Member Author

Choose a reason for hiding this comment

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

Resolved with @josephlr's suggestion to embed an unexported type whose job it is to store reserved bits.

tpm2/direct/reflect.go Outdated Show resolved Hide resolved
tpm2/direct/reflect.go Outdated Show resolved Hide resolved
* WIP: put stuff into subpackages to make the names nice

* use internal monolithic package to avoid cycles

* complete the tpm2 package

* fix test references to renamed types

* switch to command.Execute pattern

* fix some unkeyed field values detected by go vet

* stop panicking from Hash
@chrisfenner
Copy link
Member Author

@alexmwu, @josephlr, @jkl73: I've refactored this code quite a bit according to our discussion a couple of weeks ago. I've not yet resolved the issue of representing reserved bits in the struct in a future-compatible way, because I'm still thinking about how to do that. However I don't think that blocks you all from reviewing the code now.

direct/tpm2/reflect.go Outdated Show resolved Hide resolved
direct/tpm2/reflect.go Outdated Show resolved Hide resolved
direct/tpm2/reflect.go Outdated Show resolved Hide resolved
@josephlr
Copy link
Member

For handling Bit-fields I think we can have a nice solution for forwards/backwards compatibility:

We have private struct members reserved32 and reserved8 that can be embedded in a tpma struct. These structs would contain only the reserved bits (for encoding and decoding). However, you couldn't set them directly. We would have methods in the tpma subpackage like:

func GetBit(g Getter, pos int) bool
func SetBit(s Setter, pos int, val bool)

Where something like tpma.Algorithm would automatically implement tpma.Getter (and *tpma.Algorithm would automatically implement tpma.Setter). These methods would use reflection to either set the desired field or set a bit in the reserved field.

Our struct would look like:

type Algorithm struct {
	reserved32
	Asymmetric bool `gotpm:"bit=0"`
	Symmetric  bool `gotpm:"bit=1"`
	Hash       bool `gotpm:"bit=2"`
	Object     bool `gotpm:"bit=3"`
	Signing    bool `gotpm:"bit=8"`
	Encrypting bool `gotpm:"bit=9"`
	Method     bool `gotpm:"bit=10"`
}

Example test:

func TestSetBit(t *testing.T) {
	a := Algorithm{Object: true, Symmetric: true}
	SetBit(&a, 0, true)
	SetBit(&a, 3, false)
	SetBit(&a, 11, true)
	if !a.Asymmetric {
		t.Error("Asymmetric should be set")
	}
	if a.Object {
		t.Error("Object should be unset")
	}
	if !GetBit(a, 11) {
		t.Error("Bit 11 should be set")
	}
	if GetBit(a, 12) {
		t.Error("Bit 12 should be unset")
	}
	if !GetBit(a, 1) {
		t.Error("Bit 1 (Symmetric) should be set")
	}
}

See https://go.dev/play/p/qwMzWqHgWj_k for the full implementation

@chrisfenner
Copy link
Member Author

For handling Bit-fields I think we can have a nice solution for forwards/backwards compatibility:

I like this solution very much, thank you! I'll implement this change here and let you know if I run into any unforeseen problems, but I think your prototype is pretty complete.

@chrisfenner
Copy link
Member Author

I've prepared a draft of @josephlr's awesome suggestion - it works great. The embedded unexported field allows us to put these "SetBit" and "GitBit" methods onto the tpma types that completely hides that problem from the user.

I still need to clean up the code a bit more and address some of the other comments.

@chrisfenner
Copy link
Member Author

Thanks for your patience, everyone. I've made the cleanups suggested by @josephlr's last round of review.

In prototyping some other projects that use this library, I noticed a very inconvenient pattern:

	nvw := tpm2.SomePolicyCommand{
		PolicySession: tpm2.Handle{Handle: sess.Handle()},
	}
	...

The word Handle appears 3 times in one line. So, I created an internal interface called handle that all the TPM2 Commands use in their handle area. This interface takes care of pairing up Names with handle values, and is satisfied by tpm.Handle:

	nvw := tpm2.SomePolicyCommand{
		PolicySession: sess.Handle(),
	}
	...

Under the hood, tpm.Handle understands when the Name of a handle is well-known (i.e., PCR, session, or reserved entity handles) and when it is not (all other cases). This allows the execution code under the hood to:

  • Provide the well-known name of a handle by itself whenever possible
  • Ignore missing Names when they don't matter (i.e., when the command has no sessions)
  • Return a clear error to the user when they needed to provide a Name but didn't

Two helper types are provided for when callers want to provide a nontrivial (i.e., not just the handle value) Name: NamedHandle and AuthHandle. AuthHandle also provides the Session for authoriozing the handle and has special considerations inside the reflection library. These special considerations were unfortunately not convenient to push into the tpm2.handle interface without pushing all of the Session interface into structures/internal. I've put in some comments warning readers about this. Moreover, this is why the tpm2.handle interface is not exported: there's no replacing AuthHandle works from any other package because it the reflection library checks for it in particular.

@chrisfenner
Copy link
Member Author

For these reasons:

  • @alexmwu, @josephlr, and @jkl73 have all provided some fantastic feedback both offline and on this PR, which I have addressed
  • This is prototype work for a development branch, with additional follow-up work anticipated before merge to main
  • This PR is very large and it is unlikely that we will find all the issues by inspecting the new files; rather I expect that we'll find more things as we experiment and finish the 1:1 implementation
  • Googlers are OOO Monday and then will be busy with Perf for some time after that

I will go ahead and merge this to google:tpmdirect. I've created a special label called #tpmdirect just for tracking issues in this development work - please feel free to provide additional feedback on this work by creating an issue.

@chrisfenner chrisfenner merged commit 0b55c34 into google:tpmdirect Feb 19, 2022
matt-tsai pushed a commit to matt-tsai/go-tpm that referenced this pull request Jun 22, 2022
* Introduce Direct TPM2 API

This commit introduces a new method of interacting with a TPM 2.0.
Instead of plumbing each TPM API into one or more Go functions, this
code defines structures for every TPM 2.0 command request and response.
These map 1:1 with the actual command parameters comprehended by the
TPM, so any invocation of any command is possible (once all the command
structures are written).

This commit introduces enough of the TPM 2.0 API surface to put together
some interesting end-to-end tests, mostly around sealing.

Another objective of the Direct API is to facilitate use of the TPM's
session-based command transport features (e.g., audit and encryption
sessions). See the test code for examples of how to easily use these
APIs to, e.g., set up an EK-salted session for session-encrypted unseal.

Change-Id: I1549dd596869d79ddd41ff3c5f9ffdadc9628ed4

* fix problems identified by go vet

* fix some more issues identified by go vet

* fix some more issues surfaced by vet

* fix more go vet issues

* one last round of go vet fixes

* Use subpackages and put the Execute function on the command types (#1)

* WIP: put stuff into subpackages to make the names nice

* use internal monolithic package to avoid cycles

* complete the tpm2 package

* fix test references to renamed types

* switch to command.Execute pattern

* fix some unkeyed field values detected by go vet

* stop panicking from Hash

* Draft implementation of Joe's embedded-reserved-field bitwise solution

* Turn command handles into an interface to avoid caller stuttering
@Foxboron
Copy link
Contributor

Foxboron commented May 3, 2023

What is currently missing from getting this merged into master?

@chrisfenner
Copy link
Member Author

Not much, sorry for the delays. We are very close though.

We plan to merge tpmdirect to main and release it as go-tpm 0.9.0 very soon as a preview for 1.0.0 (in case there is last minute feedback on the design)

@Foxboron
Copy link
Contributor

Foxboron commented May 3, 2023

Awesome :) Thanks for the work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants