Repository: google/go-tpm
Pull request #314 (2023-02-22 17:54:51)

tpm2: Add RSADecryptWithSession
I need to use RSADecrypt with a PCR policy. I added RSADecryptWithSession similar to the already existing ...WithSession funcs.

google-cla[bot]: Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this [failed invocation](https://github.com/google/go-tpm/pull/314/checks?check_run_id=11527566999) of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.
3u13r: We'd like to move away from our fork and back to mainline. Could someone take a look in the near future @jkl73 @alexmwu ?
alexmwu: This LGTM.

FYI @chrisfenner is working on a refactor of the API, which you may be interested in. https://github.com/google/go-tpm/tree/tpmdirect. We have 1:1 parameter mapping and support for most commands (with an easy route to adding new commands). We are planning a stable release at that point. As part of the tpmdirect work, we plan on deprecating the current tpm2 package.

@chrisfenner are we planning on accepting new contributions to the legacy package?
daniel-weisse: Can this be rebased to main and merged?

Patch:
diff --git a/.cirrus.yml b/.cirrus.yml
index f98596d..63caa0b 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -18,17 +18,4 @@ lint_task:
       - GOOS: linux
       - GOOS: windows
   lint_script:
-    golangci-lint run
-      -D errcheck
-      -E stylecheck
-      -E goimports
-      -E misspell
-      -E revive
-      --exclude-use-default=false
-      --exclude stutters
-      --exclude underscores
-      --exclude unexported-return
-      --max-same-issues=0
-      --max-issues-per-linter=0
-      ./tpmutil/...
-      ./tpm2/...
+    golangci-lint run ./tpmutil/... ./tpm2/...
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..46a8a73
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,21 @@
+linters:
+  disable:
+    - errcheck
+  enable:
+    - stylecheck
+    - goimports
+    - misspell
+    - revive
+linters-settings:
+  revive:
+    rules:
+    - name: dot-imports
+      disabled: true
+issues:
+  exclude-use-default: false
+  exclude:
+    - stutters
+    - underscores
+    - unexported-return
+  max-issues-per-linter: 0
+  max-same-issues: 0
diff --git a/Dockerfile b/Dockerfile
index 347204c..d236165 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,10 +1,10 @@
-FROM golang:latest
+FROM golang:1.22
 # We need OpenSSL headers to build the simulator
 RUN apt-get update && apt-get install -y \
     libssl-dev \
  && rm -rf /var/lib/apt/lists/*
 # We need golangci-lint for linting
-ARG VERSION=1.52.2
+ARG VERSION=1.56.2
 RUN curl -SL \
     https://github.com/golangci/golangci-lint/releases/download/v${VERSION}/golangci-lint-${VERSION}-linux-amd64.tar.gz \
     --output golangci.tar.gz \
diff --git a/examples/tpm-genaik/genaik.go b/examples/tpm-genaik/genaik.go
index dd4ed7d..2303177 100644
--- a/examples/tpm-genaik/genaik.go
+++ b/examples/tpm-genaik/genaik.go
@@ -20,7 +20,6 @@ import (
 	"crypto/sha1"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"os"
 
 	"github.com/google/go-tpm/tpm"
@@ -72,7 +71,7 @@ func main() {
 		return
 	}
 
-	if err := ioutil.WriteFile(*blobname, blob, 0600); err != nil {
+	if err := os.WriteFile(*blobname, blob, 0600); err != nil {
 		fmt.Fprintf(os.Stderr, "Couldn't write to file %s: %s\n", *blobname, err)
 		return
 	}
diff --git a/examples/tpm-sign/extend_pcr.go b/examples/tpm-sign/extend_pcr.go
index 63a68b3..43c3899 100644
--- a/examples/tpm-sign/extend_pcr.go
+++ b/examples/tpm-sign/extend_pcr.go
@@ -20,7 +20,7 @@ import (
 	"crypto/sha1"
 	"flag"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"os"
 
 	"github.com/google/go-tpm/tpm"
@@ -48,9 +48,9 @@ func extendPcrAction() {
 	} else {
 		var data []byte
 		if *dataPath == "" {
-			data, err = ioutil.ReadAll(os.Stdin)
+			data, err = io.ReadAll(os.Stdin)
 		} else {
-			data, err = ioutil.ReadFile(*dataPath)
+			data, err = os.ReadFile(*dataPath)
 		}
 		if err != nil {
 			fmt.Fprintf(os.Stderr, "Unable to read input: %s\n", err)
diff --git a/examples/tpm-sign/generate.go b/examples/tpm-sign/generate.go
index 3061e6c..ae052f9 100644
--- a/examples/tpm-sign/generate.go
+++ b/examples/tpm-sign/generate.go
@@ -21,7 +21,6 @@ import (
 	"crypto/x509"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"strconv"
 	"strings"
@@ -83,7 +82,7 @@ func generateAction() {
 		return
 	}
 	fmt.Printf("Writing keyblob to %s\n", *keyblobPath)
-	if err = ioutil.WriteFile(*keyblobPath, keyblob, 0644); err != nil {
+	if err = os.WriteFile(*keyblobPath, keyblob, 0644); err != nil {
 		fmt.Fprintf(os.Stderr, "Error writing keyblob file: %s\n", err)
 		return
 	}
@@ -100,7 +99,7 @@ func generateAction() {
 		return
 	}
 	fmt.Printf("Writing public key to %s\n", *pubKeyPath)
-	if err = ioutil.WriteFile(*pubKeyPath, pubKeyBytes, 0644); err != nil {
+	if err = os.WriteFile(*pubKeyPath, pubKeyBytes, 0644); err != nil {
 		fmt.Fprintf(os.Stderr, "Error writing public key file: %s\n", err)
 		return
 	}
diff --git a/examples/tpm-sign/sign.go b/examples/tpm-sign/sign.go
index fd86962..20545c4 100644
--- a/examples/tpm-sign/sign.go
+++ b/examples/tpm-sign/sign.go
@@ -20,7 +20,7 @@ import (
 	"crypto/sha1"
 	"flag"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"os"
 
 	"github.com/google/go-tpm/tpm"
@@ -69,7 +69,7 @@ func signAction() {
 		copy(migrationAuth[:], ma[:])
 	}
 
-	keyblob, err := ioutil.ReadFile(*keyblobPath)
+	keyblob, err := os.ReadFile(*keyblobPath)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Error reading keyblob file: %s\n", err)
 		return
@@ -83,9 +83,9 @@ func signAction() {
 
 	var data []byte
 	if *dataPath == "" {
-		data, err = ioutil.ReadAll(os.Stdin)
+		data, err = io.ReadAll(os.Stdin)
 	} else {
-		data, err = ioutil.ReadFile(*dataPath)
+		data, err = os.ReadFile(*dataPath)
 	}
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Error reading input data: %s\n", err)
@@ -104,7 +104,7 @@ func signAction() {
 		return
 	}
 	fmt.Printf("Writing signature to %s\n", *signaturePath)
-	if err = ioutil.WriteFile(*signaturePath, signature, 0644); err != nil {
+	if err = os.WriteFile(*signaturePath, signature, 0644); err != nil {
 		fmt.Fprintf(os.Stderr, "Unable to write signature to file: %s\n", err)
 		return
 	}
diff --git a/examples/tpm-sign/verify.go b/examples/tpm-sign/verify.go
index 68b23f1..c8d18e5 100644
--- a/examples/tpm-sign/verify.go
+++ b/examples/tpm-sign/verify.go
@@ -21,7 +21,7 @@ import (
 	"crypto/x509"
 	"flag"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"os"
 )
 
@@ -41,22 +41,22 @@ func verifyAction() {
 	var err error
 	var data []byte
 	if *dataPath == "" {
-		data, err = ioutil.ReadAll(os.Stdin)
+		data, err = io.ReadAll(os.Stdin)
 	} else {
-		data, err = ioutil.ReadFile(*dataPath)
+		data, err = os.ReadFile(*dataPath)
 	}
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Error reading input data: %s\n", err)
 		return
 	}
 
-	signature, err := ioutil.ReadFile(*signaturePath)
+	signature, err := os.ReadFile(*signaturePath)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Error reading signature file: %s\n", err)
 		return
 	}
 
-	pubKeyBytes, err := ioutil.ReadFile(*pubKeyPath)
+	pubKeyBytes, err := os.ReadFile(*pubKeyPath)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Error reading public key file: %s\n", err)
 		return
diff --git a/examples/tpm2-ekcert/main.go b/examples/tpm2-ekcert/main.go
index 41e7e74..738e66d 100644
--- a/examples/tpm2-ekcert/main.go
+++ b/examples/tpm2-ekcert/main.go
@@ -9,7 +9,6 @@ import (
 	"errors"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"reflect"
 
@@ -64,7 +63,7 @@ func main() {
 		fmt.Println(string(cert))
 		return
 	}
-	if err := ioutil.WriteFile(*outPath, cert, os.ModePerm); err != nil {
+	if err := os.WriteFile(*outPath, cert, os.ModePerm); err != nil {
 		fmt.Fprintln(os.Stderr, err)
 		os.Exit(1)
 	}
diff --git a/go.mod b/go.mod
index b7bb002..04ef731 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/google/go-tpm
 
-go 1.20
+go 1.22
 
 require (
 	github.com/google/go-cmp v0.5.9
diff --git a/go.sum b/go.sum
index 974eb39..d7a1b31 100644
--- a/go.sum
+++ b/go.sum
@@ -1,13 +1,20 @@
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-sev-guest v0.6.1 h1:NajHkAaLqN9/aW7bCFSUplUMtDgk2+HcN7jC2btFtk0=
+github.com/google/go-sev-guest v0.6.1/go.mod h1:UEi9uwoPbLdKGl1QHaq1G8pfCbQ4QP0swWX4J0k6r+Q=
 github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba h1:qJEJcuLzH5KDR0gKc0zcktin6KSAwL7+jWKBYceddTc=
 github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba/go.mod h1:EFYHy8/1y2KfgTAsx7Luu7NGhoxtuVHnNo8jE7FikKc=
 github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ=
+github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
+golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
diff --git a/legacy/tpm2/tpm2.go b/legacy/tpm2/tpm2.go
index 8de0c40..18d5a96 100644
--- a/legacy/tpm2/tpm2.go
+++ b/legacy/tpm2/tpm2.go
@@ -1276,7 +1276,6 @@ func NVDefineSpace(rw io.ReadWriter, owner, handle tpmutil.Handle, ownerAuth, au
 		Auth:       []byte(ownerAuth),
 	}
 	return NVDefineSpaceEx(rw, owner, authString, nvPub, authArea)
-
 }
 
 // NVDefineSpaceEx accepts NVPublic structure and AuthCommand, allowing more flexibility.
@@ -2121,12 +2120,12 @@ func RSAEncrypt(rw io.ReadWriter, key tpmutil.Handle, message []byte, scheme *As
 	return decodeRSAEncrypt(resp)
 }
 
-func encodeRSADecrypt(key tpmutil.Handle, password string, message tpmutil.U16Bytes, scheme *AsymScheme, label string) ([]byte, error) {
+func encodeRSADecrypt(sessionHandle, key tpmutil.Handle, password string, message tpmutil.U16Bytes, scheme *AsymScheme, label string) ([]byte, error) {
 	ha, err := tpmutil.Pack(key)
 	if err != nil {
 		return nil, err
 	}
-	auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(password)})
+	auth, err := encodeAuthArea(AuthCommand{Session: sessionHandle, Attributes: AttrContinueSession, Auth: []byte(password)})
 	if err != nil {
 		return nil, err
 	}
@@ -2160,7 +2159,15 @@ func decodeRSADecrypt(resp []byte) ([]byte, error) {
 // label, a null byte is appended to the label and the null byte is included in the
 // padding scheme.
 func RSADecrypt(rw io.ReadWriter, key tpmutil.Handle, password string, message []byte, scheme *AsymScheme, label string) ([]byte, error) {
-	Cmd, err := encodeRSADecrypt(key, password, message, scheme, label)
+	return RSADecryptWithSession(rw, HandlePasswordSession, key, password, message, scheme, label)
+}
+
+// RSADecryptWithSession performs RSA decryption in the TPM according to RFC 3447. The key must be
+// a private RSA key in the TPM with FlagDecrypt set. Note that when using OAEP with a
+// label, a null byte is appended to the label and the null byte is included in the
+// padding scheme.
+func RSADecryptWithSession(rw io.ReadWriter, sessionHandle, key tpmutil.Handle, password string, message []byte, scheme *AsymScheme, label string) ([]byte, error) {
+	Cmd, err := encodeRSADecrypt(sessionHandle, key, password, message, scheme, label)
 	if err != nil {
 		return nil, err
 	}
diff --git a/tpm2/constants.go b/tpm2/constants.go
index bb517c3..159c81f 100644
--- a/tpm2/constants.go
+++ b/tpm2/constants.go
@@ -182,7 +182,7 @@ const (
 	TPMCCPolicyPhysicalPresence     TPMCC = 0x00000187
 	TPMCCPolicyDuplicationSelect    TPMCC = 0x00000188
 	TPMCCPolicyGetDigest            TPMCC = 0x00000189
-	TPMCCTestParams                 TPMCC = 0x0000018A
+	TPMCCTestParms                  TPMCC = 0x0000018A
 	TPMCCCommit                     TPMCC = 0x0000018B
 	TPMCCPolicyPassword             TPMCC = 0x0000018C
 	TPMCCZGen2Phase                 TPMCC = 0x0000018D
@@ -634,6 +634,18 @@ const (
 	TPMHTAC            TPMHT = 0x90
 )
 
+// Saved Context transient object handles.
+// See definition in Part 2: Structures, section 14.6.2
+// Context Handle Values come from table 211
+const (
+	// an ordinary transient object
+	TPMIDHSavedTransient TPMIDHSaved = 0x80000000
+	// a sequence object
+	TPMIDHSavedSequence TPMIDHSaved = 0x80000001
+	// a transient object with the stClear attribute SET
+	TPMIDHSavedTransientClear TPMIDHSaved = 0x80000002
+)
+
 // TPMHandle represents a TPM_HANDLE.
 // See definition in Part 2: Structures, section 7.1.
 type TPMHandle uint32
diff --git a/tpm2/crypto.go b/tpm2/crypto.go
index c8eb2bc..3082ccc 100644
--- a/tpm2/crypto.go
+++ b/tpm2/crypto.go
@@ -1,8 +1,11 @@
 package tpm2
 
 import (
+	"crypto/ecdh"
+	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/rsa"
+	"fmt"
 	"math/big"
 )
 
@@ -21,23 +24,53 @@ func RSAPub(parms *TPMSRSAParms, pub *TPM2BPublicKeyRSA) (*rsa.PublicKey, error)
 	return &result, nil
 }
 
-// ECDHPub is a convenience wrapper around the necessary info to perform point
-// multiplication with the elliptic package.
-type ECDHPub struct {
-	Curve elliptic.Curve
-	X, Y  *big.Int
-}
+// ECDHPubKey converts a TPM ECC public key into one recognized by the ecdh package
+func ECDHPubKey(curve ecdh.Curve, pub *TPMSECCPoint) (*ecdh.PublicKey, error) {
 
-// ECCPub converts a TPM ECC public key into one recognized by the elliptic
-// package's point-multiplication functions, for use in ECDH.
-func ECCPub(parms *TPMSECCParms, pub *TPMSECCPoint) (*ECDHPub, error) {
-	curve, err := parms.CurveID.Curve()
-	if err != nil {
-		return nil, err
+	var c elliptic.Curve
+	switch curve {
+	case ecdh.P256():
+		c = elliptic.P256()
+	case ecdh.P384():
+		c = elliptic.P384()
+	case ecdh.P521():
+		c = elliptic.P521()
+	default:
+		return nil, fmt.Errorf("unknown curve: %v", curve)
 	}
-	return &ECDHPub{
-		Curve: curve,
+
+	pubKey := ecdsa.PublicKey{
+		Curve: c,
 		X:     big.NewInt(0).SetBytes(pub.X.Buffer),
 		Y:     big.NewInt(0).SetBytes(pub.Y.Buffer),
-	}, nil
+	}
+
+	return pubKey.ECDH()
+}
+
+// ECCPoint returns an uncompressed ECC Point
+func ECCPoint(pubKey *ecdh.PublicKey) (*big.Int, *big.Int, error) {
+	b := pubKey.Bytes()
+	size, err := elementLength(pubKey.Curve())
+	if err != nil {
+		return nil, nil, fmt.Errorf("ECCPoint: %w", err)
+	}
+	return big.NewInt(0).SetBytes(b[1 : size+1]),
+		big.NewInt(0).SetBytes(b[size+1:]), nil
+}
+
+func elementLength(c ecdh.Curve) (int, error) {
+	switch c {
+	case ecdh.P256():
+		// crypto/internal/nistec/fiat.p256ElementLen
+		return 32, nil
+	case ecdh.P384():
+		// crypto/internal/nistec/fiat.p384ElementLen
+		return 48, nil
+	case ecdh.P521():
+		// crypto/internal/nistec/fiat.p521ElementLen
+		return 66, nil
+	default:
+		return 0, fmt.Errorf("unknown element length for curve: %v", c)
+	}
 }
diff --git a/tpm2/pcrs.go b/tpm2/pcrs.go
new file mode 100644
index 0000000..3a250c5
--- /dev/null
+++ b/tpm2/pcrs.go
@@ -0,0 +1,55 @@
+package tpm2
+
+// pcrSelectionFormatter is a Platform TPM Profile-specific interface for
+// formatting TPM PCR selections.
+// This interface isn't (yet) part of the go-tpm public interface. After we
+// add a second implementation, we should consider making it public.
+type pcrSelectionFormatter interface {
+	// PCRs returns the TPM PCR selection bitmask associated with the given PCR indices.
+	PCRs(pcrs ...uint) []byte
+}
+
+// PCClientCompatible is a pcrSelectionFormatter that formats PCR selections
+// suitable for use in PC Client PTP-compatible TPMs (the vast majority):
+// https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/
+// PC Client mandates at least 24 PCRs but does not provide an upper limit.
+var PCClientCompatible pcrSelectionFormatter = pcClient{}
+
+type pcClient struct{}
+
+// The TPM requires all PCR selections to be at least big enough to select all
+// the PCRs in the minimum PCR allocation.
+const pcClientMinimumPCRCount = 24
+
+func (pcClient) PCRs(pcrs ...uint) []byte {
+	// Find the biggest PCR we selected.
+	maxPCR := uint(0)
+	for _, pcr := range pcrs {
+		if pcr > maxPCR {
+			maxPCR = pcr
+		}
+	}
+	selectionSize := maxPCR/8 + 1
+
+	// Enforce the minimum PCR selection size.
+	if selectionSize < (pcClientMinimumPCRCount / 8) {
+		selectionSize = (pcClientMinimumPCRCount / 8)
+	}
+
+	// Allocate a byte array to store the bitfield, that has at least
+	// enough bits to store our selections.
+	selection := make([]byte, selectionSize)
+	for _, pcr := range pcrs {
+		// The PCR selection mask is byte-wise little-endian:
+		//   select[0] contains bits representing the selection of PCRs 0 through 7
+		//   select[1] contains PCRs 8 through 15, and so on.
+		byteIdx := pcr / 8
+		// Within the byte, the PCR selection is bit-wise big-endian:
+		//   bit 0 of select[0] contains the selection of PCR 0
+		//   bit 1 of select[0] contains the selection of PCR 1, and so on.
+		bitIdx := pcr % 8
+
+		selection[byteIdx] |= (1 << bitIdx)
+	}
+	return selection
+}
diff --git a/tpm2/reflect.go b/tpm2/reflect.go
index ea9b172..60c9d58 100644
--- a/tpm2/reflect.go
+++ b/tpm2/reflect.go
@@ -765,6 +765,25 @@ func cmdAuths[R any](cmd Command[R, *R]) ([]Session, error) {
 	return result, nil
 }
 
+func asHandle(value reflect.Value) (handle, error) {
+	// Special case: `handle`-typed members.
+	// Since `handle` is an interface, the zero-value is nil.
+	// https://go.dev/ref/spec#Type_assertions in this case will return false.
+	// Similarly, reflect.AssignableTo() will panic.
+	// Workaround: treat any nil interface value annotated as a `handle` as TPMRHNull.
+	var h handle
+	if value.Kind() == reflect.Interface && value.IsNil() {
+		h = TPMRHNull
+	} else {
+		var ok bool
+		h, ok = value.Interface().(handle)
+		if !ok {
+			return nil, fmt.Errorf("value of type %q does not satisfy handle", value.Type())
+		}
+	}
+	return h, nil
+}
+
 // cmdHandles returns the handles area of the command.
 func cmdHandles[R any](cmd Command[R, *R]) ([]byte, error) {
 	handles := taggedMembers(reflect.ValueOf(cmd), "handle", false)
@@ -772,19 +791,12 @@ func cmdHandles[R any](cmd Command[R, *R]) ([]byte, error) {
 	// Initial capacity is enough to hold 3 handles
 	result := bytes.NewBuffer(make([]byte, 0, 12))
 
-	for i, maybeHandle := range handles {
-		h, ok := maybeHandle.Interface().(handle)
-		if !ok {
-			return nil, fmt.Errorf("'handle'-tagged member of '%v' was of type '%v', which does not satisfy handle",
-				reflect.TypeOf(cmd), maybeHandle.Type())
-		}
-
-		// Special behavior: nullable handles have an effective zero-value of
-		// TPM_RH_NULL.
-		if h.HandleValue() == 0 && hasTag(reflect.TypeOf(cmd).Field(i), "nullable") {
-			h = TPMRHNull
+	for _, maybeHandle := range handles {
+		h, err := asHandle(maybeHandle)
+		if err != nil {
+			return nil, fmt.Errorf("invalid 'handle'-tagged member of %q: %v",
+				reflect.TypeOf(cmd), err)
 		}
-
 		binary.Write(result, binary.BigEndian, h.HandleValue())
 	}
 
@@ -796,18 +808,11 @@ func cmdNames[R any](cmd Command[R, *R]) ([]TPM2BName, error) {
 	handles := taggedMembers(reflect.ValueOf(cmd), "handle", false)
 	var result []TPM2BName
 	for i, maybeHandle := range handles {
-		h, ok := maybeHandle.Interface().(handle)
-		if !ok {
-			return nil, fmt.Errorf("'handle'-tagged member of '%v' was of type '%v', which does not satisfy handle",
-				reflect.TypeOf(cmd), maybeHandle.Type())
-		}
-
-		// Special behavior: nullable handles have an effective zero-value of
-		// TPM_RH_NULL.
-		if h.HandleValue() == 0 && hasTag(reflect.TypeOf(cmd).Field(i), "nullable") {
-			h = TPMRHNull
+		h, err := asHandle(maybeHandle)
+		if err != nil {
+			return nil, fmt.Errorf("invalid 'handle'-tagged member of %q: %v",
+				reflect.TypeOf(cmd), err)
 		}
-
 		name := h.KnownName()
 		if name == nil {
 			return nil, fmt.Errorf("missing Name for '%v' parameter",
@@ -834,9 +839,9 @@ func marshalParameter[R any](buf *bytes.Buffer, cmd Command[R, *R], i int) error
 		return marshal(buf, reflect.ValueOf(TPMRHNull))
 	} else if parm.IsZero() && parm.Kind() == reflect.Uint16 && hasTag(field, "nullable") {
 		return marshal(buf, reflect.ValueOf(TPMAlgNull))
-	} else {
-		return marshal(buf, parm)
 	}
+
+	return marshal(buf, parm)
 }
 
 // cmdParameters returns the parameters area of the command.
diff --git a/tpm2/sessions.go b/tpm2/sessions.go
index 171adf8..bee12b2 100644
--- a/tpm2/sessions.go
+++ b/tpm2/sessions.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"crypto/aes"
 	"crypto/cipher"
-	"crypto/elliptic"
 	"crypto/hmac"
 	"crypto/rand"
 	"crypto/rsa"
@@ -425,24 +424,31 @@ func getEncryptedSaltRSA(nameAlg TPMIAlgHash, parms *TPMSRSAParms, pub *TPM2BPub
 
 // Part 1, 19.6.13
 func getEncryptedSaltECC(nameAlg TPMIAlgHash, parms *TPMSECCParms, pub *TPMSECCPoint) (*TPM2BEncryptedSecret, []byte, error) {
-	curve, err := parms.CurveID.Curve()
+	curve, err := parms.CurveID.ECDHCurve()
 	if err != nil {
-		return nil, nil, fmt.Errorf("could not encrypt salt to ECC key: %w", err)
+		return nil, nil, fmt.Errorf("ecc salt: param curve: %w", err)
 	}
-	eccPub, err := ECCPub(parms, pub)
+	eccPub, err := ECDHPubKey(curve, pub)
 	if err != nil {
-		return nil, nil, fmt.Errorf("could not encrypt salt to ECC key: %w", err)
+		return nil, nil, fmt.Errorf("ecc salt: unmarshalling tpm ecc key: %w", err)
 	}
-	ephPriv, ephPubX, ephPubY, err := elliptic.GenerateKey(curve, rand.Reader)
+
+	// Generate new ECDH key
+	ephPriv, err := curve.GenerateKey(rand.Reader)
+	if err != nil {
+		return nil, nil, fmt.Errorf("ecc salt: generating ecc private key: %w", err)
+	}
+	ephPubX, ephPubY, err := ECCPoint(ephPriv.PublicKey())
+	if err != nil {
+		return nil, nil, fmt.Errorf("ecc salt: ecc pubkey: %w", err)
+	}
+
+	// Calculate Z (ECDH key * TPM pub)
+	z, err := ephPriv.ECDH(eccPub)
 	if err != nil {
-		return nil, nil, fmt.Errorf("could not encrypt salt to ECC key: %w", err)
-	}
-	zx, _ := curve.Params().ScalarMult(eccPub.X, eccPub.Y, ephPriv)
-	// ScalarMult returns a big.Int, whose Bytes() function may return the
-	// compacted form. In our case, we want to left-pad zx to the size of
-	// the curve.
-	z := make([]byte, (curve.Params().BitSize+7)/8)
-	zx.FillBytes(z)
+		return nil, nil, fmt.Errorf("ecc salt: z calc: %w", err)
+	}
+
 	ha, err := nameAlg.Hash()
 	if err != nil {
 		return nil, nil, err
diff --git a/tpm2/structures.go b/tpm2/structures.go
index 7fe0d3c..92dfa47 100644
--- a/tpm2/structures.go
+++ b/tpm2/structures.go
@@ -4,6 +4,7 @@ package tpm2
 import (
 	"bytes"
 	"crypto"
+	"crypto/ecdh"
 	"crypto/elliptic"
 	"encoding/binary"
 	"reflect"
@@ -96,6 +97,20 @@ func (c TPMECCCurve) Curve() (elliptic.Curve, error) {
 	}
 }
 
+// ECDHCurve returns the ecdh.Curve associated with a TPMECCCurve.
+func (c TPMECCCurve) ECDHCurve() (ecdh.Curve, error) {
+	switch c {
+	case TPMECCNistP256:
+		return ecdh.P256(), nil
+	case TPMECCNistP384:
+		return ecdh.P384(), nil
+	case TPMECCNistP521:
+		return ecdh.P521(), nil
+	default:
+		return nil, fmt.Errorf("unsupported ECC curve: %v", c)
+	}
+}
+
 // HandleValue returns the handle value. This behavior is intended to satisfy
 // an interface that can be implemented by other, more complex types as well.
 func (h TPMHandle) HandleValue() uint32 {
@@ -107,14 +122,21 @@ func (h TPMHandle) HandleValue() uint32 {
 // only PCR, session, and permanent values have known constant Names.
 // See definition in part 1: Architecture, section 16.
 func (h TPMHandle) KnownName() *TPM2BName {
-	switch (byte)(h >> 24) {
-	case 0x00, 0x02, 0x03, 0x40:
+	switch (TPMHT)(h >> 24) {
+	case TPMHTPCR, TPMHTHMACSession, TPMHTPolicySession, TPMHTPermanent:
 		result := make([]byte, 4)
 		binary.BigEndian.PutUint32(result, h.HandleValue())
 		return &TPM2BName{Buffer: result}
-	default:
-		return nil
+	case TPMHTTransient:
+		// The Name of a sequence object is an Empty Buffer
+		// See part 1: Architecture, section 32.4.5
+		if h == TPMIDHSavedSequence {
+			return &TPM2BName{
+				Buffer: []byte{},
+			}
+		}
 	}
+	return nil
 }
 
 // TPMAAlgorithm represents a TPMA_ALGORITHM.
@@ -343,6 +365,10 @@ type TPMIYesNo = bool
 // See definition in Part 2: Structures, section 9.3.
 type TPMIDHObject = TPMHandle
 
+// TPMIDHPersistent represents a TPMI_DH_PERSISTENT.
+// See definition in Part 2: Structures, section 9.5.
+type TPMIDHPersistent = TPMHandle
+
 // TPMIDHEntity represents a TPMI_DH_ENTITY.
 // See definition in Part 2: Structures, section 9.6.
 type TPMIDHEntity = TPMHandle
@@ -2273,6 +2299,20 @@ type TPMTRSAScheme struct {
 	Details TPMUAsymScheme `gotpm:"tag=Scheme"`
 }
 
+// TPMIAlgRSADecrypt represents a TPMI_ALG_RSA_DECRYPT.
+// See definition in Part 2: Structures, section 11.2.4.3.
+type TPMIAlgRSADecrypt = TPMAlgID
+
+// TPMTRSADecrypt represents a TPMT_RSA_DECRYPT.
+// See definition in Part 2: Structures, section 11.2.4.4.
+type TPMTRSADecrypt struct {
+	marshalByReflection
+	// scheme selector
+	Scheme TPMIAlgRSADecrypt `gotpm:"nullable"`
+	// scheme parameters
+	Details TPMUAsymScheme `gotpm:"tag=Scheme"`
+}
+
 // TPM2BPublicKeyRSA represents a TPM2B_PUBLIC_KEY_RSA.
 // See definition in Part 2: Structures, section 11.2.4.5.
 type TPM2BPublicKeyRSA TPM2BData
@@ -2446,7 +2486,7 @@ func (u *TPMUSignature) ECDSA() (*TPMSSignatureECC, error) {
 
 // ECDAA returns the 'ecdaa' member of the union.
 func (u *TPMUSignature) ECDAA() (*TPMSSignatureECC, error) {
-	if u.selector == TPMAlgRSASSA {
+	if u.selector == TPMAlgECDAA {
 		return u.contents.(*TPMSSignatureECC), nil
 	}
 	return nil, fmt.Errorf("did not contain ecdaa (selector value was %v)", u.selector)
@@ -2756,6 +2796,16 @@ func (u *TPMUPublicParms) ECCDetail() (*TPMSECCParms, error) {
 	return nil, fmt.Errorf("did not contain eccDetail (selector value was %v)", u.selector)
 }
 
+// TPMTPublicParms represents a TPMT_PUBLIC_PARMS.
+// See definition in Part 2: Structures, section 12.2.3.8.
+type TPMTPublicParms struct {
+	marshalByReflection
+	// algorithm to be tested
+	Type TPMIAlgPublic
+	// algorithm details
+	Parameters TPMUPublicParms `gotpm:"tag=Type"`
+}
+
 // TPMTPublic represents a TPMT_PUBLIC.
 // See definition in Part 2: Structures, section 12.2.4.
 type TPMTPublic struct {
diff --git a/tpm2/tpm2.go b/tpm2/tpm2.go
index ad354bb..40c6116 100644
--- a/tpm2/tpm2.go
+++ b/tpm2/tpm2.go
@@ -126,10 +126,10 @@ type StartupResponse struct{}
 type StartAuthSession struct {
 	// handle of a loaded decrypt key used to encrypt salt
 	// may be TPM_RH_NULL
-	TPMKey handle `gotpm:"handle,nullable"`
+	TPMKey handle `gotpm:"handle"`
 	// entity providing the authValue
 	// may be TPM_RH_NULL
-	Bind handle `gotpm:"handle,nullable"`
+	Bind handle `gotpm:"handle"`
 	// initial nonceCaller, sets nonceTPM size for the session
 	// shall be at least 16 octets
 	NonceCaller TPM2BNonce
@@ -387,12 +387,41 @@ type UnsealResponse struct {
 	OutData TPM2BSensitiveData
 }
 
+// ObjectChangeAuth is the input to TPM2_ObjectChangeAuth.
+// See definition in Part 3, Commands, section 12.8
+type ObjectChangeAuth struct {
+	// TPM handle of an object
+	ObjectHandle handle `gotpm:"handle,auth"`
+	// handle of the parent
+	ParentHandle handle `gotpm:"handle"`
+	// new authorization value
+	NewAuth TPM2BAuth
+}
+
+// Command implements the Command interface.
+func (ObjectChangeAuth) Command() TPMCC { return TPMCCObjectChangeAuth }
+
+// Execute executes the command and returns the response.
+func (cmd ObjectChangeAuth) Execute(t transport.TPM, s ...Session) (*ObjectChangeAuthResponse, error) {
+	var rsp ObjectChangeAuthResponse
+	if err := execute[ObjectChangeAuthResponse](t, cmd, &rsp, s...); err != nil {
+		return nil, err
+	}
+	return &rsp, nil
+}
+
+// ObjectChangeAuthResponse the response from TPM2_ObjectChangeAuth.
+type ObjectChangeAuthResponse struct {
+	// private area containing the new authorization value
+	OutPrivate TPM2BPrivate
+}
+
 // CreateLoaded is the input to TPM2_CreateLoaded.
 // See definition in Part 3, Commands, section 12.9
 type CreateLoaded struct {
 	// Handle of a transient storage key, a persistent storage key,
 	// TPM_RH_ENDORSEMENT, TPM_RH_OWNER, TPM_RH_PLATFORM+{PP}, or TPM_RH_NULL
-	ParentHandle handle `gotpm:"handle,auth,nullable"`
+	ParentHandle handle `gotpm:"handle,auth"`
 	// the sensitive data, see TPM 2.0 Part 1 Sensitive Values
 	InSensitive TPM2BSensitiveCreate
 	// the public template
@@ -423,6 +452,68 @@ type CreateLoadedResponse struct {
 	Name TPM2BName
 }
 
+// RSAEncrypt is the input to TPM2_RSA_Encrypt
+// See definition in Part 3, Commands, section 14.2.
+type RSAEncrypt struct {
+	// reference to public portion of RSA key to use for encryption
+	KeyHandle handle `gotpm:"handle"`
+	// message to be encrypted
+	Message TPM2BPublicKeyRSA
+	// the padding scheme to use if scheme associated with keyHandle is TPM_ALG_NULL
+	InScheme TPMTRSADecrypt `gotpm:"nullable"`
+	// optional label L to be associated with the message
+	Label TPM2BData `gotpm:"optional"`
+}
+
+// Command implements the Command interface.
+func (RSAEncrypt) Command() TPMCC { return TPMCCRSAEncrypt }
+
+// Execute executes the command and returns the response.
+func (cmd RSAEncrypt) Execute(t transport.TPM, s ...Session) (*RSAEncryptResponse, error) {
+	var rsp RSAEncryptResponse
+	if err := execute[RSAEncryptResponse](t, cmd, &rsp, s...); err != nil {
+		return nil, err
+	}
+	return &rsp, nil
+}
+
+// RSAEncryptResponse is the response from TPM2_RSA_Encrypt
+type RSAEncryptResponse struct {
+	// encrypted output
+	OutData TPM2BPublicKeyRSA
+}
+
+// RSADecrypt is the input to TPM2_RSA_Decrypt
+// See definition in Part 3, Commands, section 14.3.
+type RSADecrypt struct {
+	// RSA key to use for decryption
+	KeyHandle handle `gotpm:"handle,auth"`
+	// cipher text to be decrypted
+	CipherText TPM2BPublicKeyRSA
+	// the padding scheme to use if scheme associated with keyHandle is TPM_ALG_NULL
+	InScheme TPMTRSADecrypt `gotpm:"nullable"`
+	// label whose association with the message is to be verified
+	Label TPM2BData `gotpm:"optional"`
+}
+
+// Command implements the Command interface.
+func (RSADecrypt) Command() TPMCC { return TPMCCRSADecrypt }
+
+// Execute executes the command and returns the response.
+func (cmd RSADecrypt) Execute(t transport.TPM, s ...Session) (*RSADecryptResponse, error) {
+	var rsp RSADecryptResponse
+	if err := execute[RSADecryptResponse](t, cmd, &rsp, s...); err != nil {
+		return nil, err
+	}
+	return &rsp, nil
+}
+
+// RSADecryptResponse is the response from TPM2_RSA_Decrypt
+type RSADecryptResponse struct {
+	// decrypted output
+	Message TPM2BPublicKeyRSA
+}
+
 // ECDHZGen is the input to TPM2_ECDHZGen.
 // See definition in Part 3, Commands, section 14.5
 type ECDHZGen struct {
@@ -535,6 +626,36 @@ type HashSequenceStartResponse struct {
 	SequenceHandle TPMIDHObject
 }
 
+// HmacStart is the input to TPM2_HMAC_Start.
+// See definition in Part 3, Commands, section 17.2.2
+type HmacStart struct {
+	// HMAC key handle requiring an authorization session for the USER role
+	Handle AuthHandle `gotpm:"handle,auth"`
+	// authorization value for subsequent use of the sequence
+	Auth TPM2BAuth
+	// the hash algorithm to use for the hmac sequence
+	HashAlg TPMIAlgHash
+}
+
+// Command implements the Command interface.
+func (HmacStart) Command() TPMCC { return TPMCCHMACStart }
+
+// Execute executes the command and returns the response.
+func (cmd HmacStart) Execute(t transport.TPM, s ...Session) (*HmacStartResponse, error) {
+	var rsp HmacStartResponse
+	if err := execute[HmacStartResponse](t, cmd, &rsp, s...); err != nil {
+		return nil, err
+	}
+	return &rsp, nil
+}
+
+// HmacStartResponse is the response from TPM2_HMAC_Start.
+// See definition in Part 3, Commands, section 17.2.2
+type HmacStartResponse struct {
+	// a handle to reference the sequence
+	SequenceHandle TPMIDHObject `gotpm:"handle"`
+}
+
 // SequenceUpdate is the input to TPM2_SequenceUpdate.
 // See definition in Part 3, Commands, section 17.4
 type SequenceUpdate struct {
@@ -1469,6 +1590,119 @@ func (cmd FlushContext) Execute(t transport.TPM, s ...Session) (*FlushContextRes
 // FlushContextResponse is the response from TPM2_FlushContext.
 type FlushContextResponse struct{}
 
+// EvictControl is the input to TPM2_EvictControl.
+// See definition in Part 3, Commands, section 28.5
+type EvictControl struct {
+	// TPM_RH_OWNER or TPM_RH_PLATFORM+{PP}
+	Auth             handle `gotpm:"handle,auth"`
+	ObjectHandle     handle `gotpm:"handle"`
+	PersistentHandle TPMIDHPersistent
+}
+
+// EvictControlResponse is the response from TPM2_EvictControl.
+type EvictControlResponse struct{}
+
+// Command implements the Command interface.
+func (EvictControl) Command() TPMCC { return TPMCCEvictControl }
+
+// Execute executes the command and returns the response.
+func (cmd EvictControl) Execute(t transport.TPM, s ...Session) (*EvictControlResponse, error) {
+	var rsp EvictControlResponse
+	if err := execute[EvictControlResponse](t, cmd, &rsp, s...); err != nil {
+		return nil, err
+	}
+	return &rsp, nil
+}
+
+// Duplicate is the input to TPM2_Duplicate.
+// See definition in Part 3, Commands, section 13.1
+type Duplicate struct {
+	// ObjectHandle is the handle of the object to dupliate.
+	ObjectHandle handle `gotpm:"handle,auth"`
+
+	// NewParentHandle is the handle of the new parent.
+	NewParentHandle handle `gotpm:"handle"`
+
+	// EncryptionKeyIn is the optional symmetric encryption key used as the
+	// inner wrapper. If SymmetricAlg is TPM_ALG_NULL, then this parameter
+	// shall be the Empty Buffer.
+	EncryptionKeyIn TPM2BData
+
+	// Definition of the symmetric algorithm to use for the inner wrapper.
+	// It may be TPM_ALG_NULL if no inner wrapper is applied.
+	Symmetric TPMTSymDef
+}
+
+// DuplicateResponse is the response from TPM2_Duplicate.
+type DuplicateResponse struct {
+	// EncryptionKeyOut is the symmetric encryption key used as the
+	// inner wrapper. If SymmetricAlg is TPM_ALG_NULL, this value
+	// shall be the Empty Buffer.
+	EncryptionKeyOut TPM2BData
+
+	// Duplicate is the private area of the object. It may be encrypted by
+	// EncryptionKeyIn and may be doubly encrypted.
+	Duplicate TPM2BPrivate
+
+	// OutSymSeed is the seed protected by the asymmetric algorithms of new
+	// parent.
+	OutSymSeed TPM2BEncryptedSecret
+}
+
+// Command implements the Command interface.
+func (Duplicate) Command() TPMCC { return TPMCCDuplicate }
+
+// Execute executes the command and returns the response.
+func (cmd Duplicate) Execute(t transport.TPM, s ...Session) (*DuplicateResponse, error) {
+	var rsp DuplicateResponse
+	if err := execute[DuplicateResponse](t, cmd, &rsp, s...); err != nil {
+		return nil, err
+	}
+	return &rsp, nil
+}
+
+// Import is the input to TPM2_Import.
+// See definition in Part 3, Commands, section 13.3
+type Import struct {
+	// handle of parent for new object
+	ParentHandle handle `gotpm:"handle,auth"`
+
+	// The optional symmetric encryption key used as the inner wrapper for duplicate
+	// If SymmetricAlg is TPM_ALG_NULL, then this parametert shall be the Empty Buffer
+	EncryptionKey TPM2BData
+
+	// The public area of the object to be imported
+	ObjectPublic TPM2BPublic
+
+	// The symmetrically encrypted duplicate object that may contain an inner
+	// symmetric wrapper
+	Duplicate TPM2BPrivate
+
+	// The seed for the symmetric key and HMAC key
+	InSymSeed TPM2BEncryptedSecret
+
+	// Definition of the symmetric algorithm to use for the inner wrapper
+	Symmetric TPMTSymDef
+}
+
+// ImportResponse is the response from TPM2_Import.
+type ImportResponse struct {
+	// the private portion of the object
+	OutPrivate TPM2BPrivate
+}
+
+// Command implements the Command interface.
+func (Import) Command() TPMCC { return TPMCCImport }
+
+// Execute executes the command and returns the response.
+func (cmd Import) Execute(t transport.TPM, s ...Session) (*ImportResponse, error) {
+	var rsp ImportResponse
+	if err := execute[ImportResponse](t, cmd, &rsp, s...); err != nil {
+		return nil, err
+	}
+	return &rsp, nil
+}
+
 // GetCapability is the input to TPM2_GetCapability.
 // See definition in Part 3, Commands, section 30.2
 type GetCapability struct {
@@ -1500,6 +1734,28 @@ type GetCapabilityResponse struct {
 	CapabilityData TPMSCapabilityData
 }
 
+// TestParms is the input to TPM2_TestParms.
+// See definition in Part 3, Commands, section 30.3
+type TestParms struct {
+	// Algorithms parameters to be validates
+	Parameters TPMTPublicParms
+}
+
+// Command implements the Command interface.
+func (TestParms) Command() TPMCC { return TPMCCTestParms }
+
+// Execute executes the command and returns the response.
+func (cmd TestParms) Execute(t transport.TPM, s ...Session) (*TestParmsResponse, error) {
+	var rsp TestParmsResponse
+	if err := execute[TestParmsResponse](t, cmd, &rsp, s...); err != nil {
+		return nil, err
+	}
+	return &rsp, nil
+}
+
+// TestParmsResponse is the response from TPM2_TestParms.
+type TestParmsResponse struct{}
+
 // NVDefineSpace is the input to TPM2_NV_DefineSpace.
 // See definition in Part 3, Commands, section 31.3.
 type NVDefineSpace struct {


Test patch:
diff --git a/tpm/tpm_test.go b/tpm/tpm_test.go
index d626866..3167e80 100644
--- a/tpm/tpm_test.go
+++ b/tpm/tpm_test.go
@@ -20,7 +20,6 @@ import (
 	"crypto/rsa"
 	"crypto/sha1"
 	"crypto/x509"
-	"io/ioutil"
 	mathrand "math/rand"
 	"os"
 	"testing"
@@ -372,7 +371,7 @@ func TestLoadKey2(t *testing.T) {
 	defer rwc.Close()
 
 	// Get the key from aikblob, assuming it exists. Otherwise, skip the test.
-	blob, err := ioutil.ReadFile("./aikblob")
+	blob, err := os.ReadFile("./aikblob")
 	if err != nil {
 		t.Skip("No aikblob file; skipping test")
 	}
@@ -394,7 +393,7 @@ func TestQuote2(t *testing.T) {
 	defer rwc.Close()
 
 	// Get the key from aikblob, assuming it exists. Otherwise, skip the test.
-	blob, err := ioutil.ReadFile("./aikblob")
+	blob, err := os.ReadFile("./aikblob")
 	if err != nil {
 		t.Skip("No aikblob file; skipping test")
 	}
@@ -429,7 +428,7 @@ func TestGetPubKey(t *testing.T) {
 	defer rwc.Close()
 
 	// Get the key from aikblob, assuming it exists. Otherwise, skip the test.
-	blob, err := ioutil.ReadFile("./aikblob")
+	blob, err := os.ReadFile("./aikblob")
 	if err != nil {
 		t.Skip("No aikblob file; skipping test")
 	}
@@ -458,7 +457,7 @@ func TestQuote(t *testing.T) {
 	defer rwc.Close()
 
 	// Get the key from aikblob, assuming it exists. Otherwise, skip the test.
-	blob, err := ioutil.ReadFile("./aikblob")
+	blob, err := os.ReadFile("./aikblob")
 	if err != nil {
 		t.Skip("No aikblob file; skipping test")
 	}
@@ -493,7 +492,7 @@ func TestQuote(t *testing.T) {
 
 func TestUnmarshalRSAPublicKey(t *testing.T) {
 	// Get the key from aikblob, assuming it exists. Otherwise, skip the test.
-	blob, err := ioutil.ReadFile("./aikblob")
+	blob, err := os.ReadFile("./aikblob")
 	if err != nil {
 		t.Skip("No aikblob file; skipping test")
 	}
diff --git a/tpm2/test/certify_test.go b/tpm2/test/certify_test.go
index d70a4bc..41955e9 100644
--- a/tpm2/test/certify_test.go
+++ b/tpm2/test/certify_test.go
@@ -21,10 +21,6 @@ func TestCertify(t *testing.T) {
 
 	Auth := []byte("password")
 
-	PCR7, err := CreatePCRSelection([]int{7})
-	if err != nil {
-		t.Fatalf("Failed to create PCRSelection")
-	}
 	public := New2B(TPMTPublic{
 		Type:    TPMAlgRSA,
 		NameAlg: TPMAlgSHA256,
@@ -58,7 +54,7 @@ func TestCertify(t *testing.T) {
 		PCRSelections: []TPMSPCRSelection{
 			{
 				Hash:      TPMAlgSHA256,
-				PCRSelect: PCR7,
+				PCRSelect: PCClientCompatible.PCRs(7),
 			},
 		},
 	}
@@ -211,15 +207,11 @@ func TestCreateAndCertifyCreation(t *testing.T) {
 		),
 	})
 
-	PCR7, err := CreatePCRSelection([]int{7})
-	if err != nil {
-		t.Fatalf("Failed to create PCRSelection")
-	}
 	pcrSelection := TPMLPCRSelection{
 		PCRSelections: []TPMSPCRSelection{
 			{
 				Hash:      TPMAlgSHA1,
-				PCRSelect: PCR7,
+				PCRSelect: PCClientCompatible.PCRs(7),
 			},
 		},
 	}
diff --git a/tpm2/test/combined_context_test.go b/tpm2/test/combined_context_test.go
index 3640431..d7cd40c 100644
--- a/tpm2/test/combined_context_test.go
+++ b/tpm2/test/combined_context_test.go
@@ -30,11 +30,6 @@ func TestCombinedContext(t *testing.T) {
 	}
 	defer thetpm.Close()
 
-	PCR7, err := CreatePCRSelection([]int{7})
-	if err != nil {
-		t.Fatalf("Failed to create PCRSelection")
-	}
-
 	createPrimary := CreatePrimary{
 		PrimaryHandle: TPMRHOwner,
 
@@ -67,7 +62,7 @@ func TestCombinedContext(t *testing.T) {
 			PCRSelections: []TPMSPCRSelection{
 				{
 					Hash:      TPMAlgSHA1,
-					PCRSelect: PCR7,
+					PCRSelect: PCClientCompatible.PCRs(7),
 				},
 			},
 		},
diff --git a/tpm2/test/create_loaded_test.go b/tpm2/test/create_loaded_test.go
index f55b5e4..fc7114f 100644
--- a/tpm2/test/create_loaded_test.go
+++ b/tpm2/test/create_loaded_test.go
@@ -74,6 +74,10 @@ func TestCreateLoaded(t *testing.T) {
 			ParentHandle: TPMRHEndorsement,
 			InPublic:     New2BTemplate(&ECCEKTemplate),
 		},
+		"NoParentPrimaryKey": {
+			// Make the object in the null hierarchy and ensure that go-tpm supports not providing the parent handle at all.
+			InPublic: New2BTemplate(&ECCEKTemplate),
+		},
 		"OrdinaryKey": {
 			ParentHandle: TPMRHOwner,
 			InSensitive: TPM2BSensitiveCreate{
diff --git a/tpm2/test/duplicate_test.go b/tpm2/test/duplicate_test.go
new file mode 100644
index 0000000..7d53705
--- /dev/null
+++ b/tpm2/test/duplicate_test.go
@@ -0,0 +1,181 @@
+package tpm2test
+
+import (
+	"testing"
+
+	. "github.com/google/go-tpm/tpm2"
+	"github.com/google/go-tpm/tpm2/transport"
+	"github.com/google/go-tpm/tpm2/transport/simulator"
+)
+
+// TestDuplicate creates an object under Owner->SRK and duplicates it to
+// Endorsement->SRK.
+func TestDuplicate(t *testing.T) {
+	thetpm, err := simulator.OpenSimulator()
+	if err != nil {
+		t.Fatalf("could not connect to TPM simulator: %v", err)
+	}
+	defer thetpm.Close()
+
+	t.Log("### Create Owner SRK")
+	srkCreateResp, err := CreatePrimary{
+		PrimaryHandle: TPMRHOwner,
+		InPublic:      New2B(ECCSRKTemplate),
+	}.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("could not generate SRK: %v", err)
+	}
+
+	srk := NamedHandle{
+		Handle: srkCreateResp.ObjectHandle,
+		Name:   srkCreateResp.Name,
+	}
+
+	policy, err := dupPolicyDigest(thetpm)
+	if err != nil {
+		t.Fatalf("dupPolicyDigest: %v", err)
+	}
+
+	keyPass := []byte("foo")
+
+	t.Log("### Create Object to be duplicated")
+	objectCreateLoadedResp, err := CreateLoaded{
+		ParentHandle: srk,
+		InSensitive: TPM2BSensitiveCreate{
+			Sensitive: &TPMSSensitiveCreate{
+				UserAuth: TPM2BAuth{
+					Buffer: keyPass,
+				},
+			},
+		},
+		InPublic: New2BTemplate(&TPMTPublic{
+			Type:    TPMAlgECC,
+			NameAlg: TPMAlgSHA256,
+			ObjectAttributes: TPMAObject{
+				FixedTPM:            false,
+				FixedParent:         false,
+				SensitiveDataOrigin: true,
+				UserWithAuth:        true,
+				Decrypt:             true,
+				SignEncrypt:         true,
+			},
+			AuthPolicy: TPM2BDigest{Buffer: policy},
+			Parameters: NewTPMUPublicParms(
+				TPMAlgECC,
+				&TPMSECCParms{
+					CurveID: TPMECCNistP256,
+				},
+			),
+			Unique: NewTPMUPublicID(
+				TPMAlgECC,
+				&TPMSECCPoint{
+					X: TPM2BECCParameter{Buffer: make([]byte, 32)},
+					Y: TPM2BECCParameter{Buffer: make([]byte, 32)},
+				},
+			),
+		}),
+	}.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("TPM2_CreateLoaded: %v", err)
+	}
+
+	// We don't need the owner SRK handle anymore.
+	FlushContext{FlushHandle: srkCreateResp.ObjectHandle}.Execute(thetpm)
+
+	t.Log("### Create Endorsement SRK (New Parent)")
+	srk2CreateResp, err := CreatePrimary{
+		PrimaryHandle: TPMRHEndorsement,
+		InPublic:      New2B(ECCSRKTemplate),
+	}.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("could not generate SRK: %v", err)
+	}
+	defer FlushContext{FlushHandle: srk2CreateResp.ObjectHandle}.Execute(thetpm)
+
+	srk2 := NamedHandle{
+		Handle: srk2CreateResp.ObjectHandle,
+		Name:   srk2CreateResp.Name,
+	}
+
+	t.Log("### Duplicate Object")
+	duplicateResp, err := Duplicate{
+		ObjectHandle: AuthHandle{
+			Handle: objectCreateLoadedResp.ObjectHandle,
+			Name:   objectCreateLoadedResp.Name,
+			Auth: Policy(TPMAlgSHA256, 16, PolicyCallback(func(tpm transport.TPM, handle TPMISHPolicy, _ TPM2BNonce) error {
+				_, err := PolicyCommandCode{
+					PolicySession: handle,
+					Code:          TPMCCDuplicate,
+				}.Execute(tpm)
+				return err
+			})),
+		},
+		NewParentHandle: srk2,
+		Symmetric: TPMTSymDef{
+			Algorithm: TPMAlgNull,
+		},
+	}.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("TPM2_Duplicate: %v", err)
+	}
+
+	// We don't need the original object handle anymore.
+	FlushContext{FlushHandle: objectCreateLoadedResp.ObjectHandle}.Execute(thetpm)
+
+	t.Log("### Import Object")
+	importResp, err := Import{
+		ParentHandle: AuthHandle{
+			Handle: srk2.Handle,
+			Name:   srk2.Name,
+			Auth:   PasswordAuth(nil),
+		},
+		ObjectPublic: objectCreateLoadedResp.OutPublic,
+		Duplicate:    duplicateResp.Duplicate,
+		InSymSeed:    duplicateResp.OutSymSeed,
+		Symmetric: TPMTSymDef{
+			Algorithm: TPMAlgNull,
+		},
+	}.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("TPM2_Import: %v", err)
+	}
+
+	t.Log("### Load Imported Object")
+	loadResp, err := Load{
+		ParentHandle: srk2,
+		InPrivate:    importResp.OutPrivate,
+		InPublic:     objectCreateLoadedResp.OutPublic,
+	}.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("TPM2_Load: %v", err)
+	}
+	defer FlushContext{FlushHandle: loadResp.ObjectHandle}.Execute(thetpm)
+}
+
+func dupPolicyDigest(thetpm transport.TPM) ([]byte, error) {
+	sess, cleanup, err := PolicySession(thetpm, TPMAlgSHA256, 16, Trial())
+	if err != nil {
+		return nil, err
+	}
+	defer cleanup()
+
+	_, err = PolicyCommandCode{
+		PolicySession: sess.Handle(),
+		Code:          TPMCCDuplicate,
+	}.Execute(thetpm)
+	if err != nil {
+		return nil, err
+	}
+
+	pgd, err := PolicyGetDigest{
+		PolicySession: sess.Handle(),
+	}.Execute(thetpm)
+	if err != nil {
+		return nil, err
+	}
+	_, err = FlushContext{FlushHandle: sess.Handle()}.Execute(thetpm)
+	if err != nil {
+		return nil, err
+	}
+	return pgd.PolicyDigest.Buffer, nil
+}
diff --git a/tpm2/test/ecdh_test.go b/tpm2/test/ecdh_test.go
index fbf333f..248e973 100644
--- a/tpm2/test/ecdh_test.go
+++ b/tpm2/test/ecdh_test.go
@@ -1,9 +1,8 @@
 package tpm2test
 
 import (
-	"crypto/elliptic"
+	"crypto/ecdh"
 	"crypto/rand"
-	"math/big"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -57,6 +56,9 @@ func TestECDH(t *testing.T) {
 		}),
 	}
 
+	// Use NIST P-256
+	curve := ecdh.P256()
+
 	tpmCreateRsp, err := tpmCreate.Execute(thetpm)
 	if err != nil {
 		t.Fatalf("could not create the TPM key: %v", err)
@@ -69,24 +71,33 @@ func TestECDH(t *testing.T) {
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	tpmX := big.NewInt(0).SetBytes(tpmPub.X.Buffer)
-	tpmY := big.NewInt(0).SetBytes(tpmPub.Y.Buffer)
+	tpmPubKey, err := ECDHPubKey(curve, tpmPub)
+	if err != nil {
+		t.Fatalf("could not unmarshall pubkey: %v", err)
+	}
 
 	// Create a SW ECDH key
-	priv, x, y, err := elliptic.GenerateKey(elliptic.P256(), rand.Reader)
+	swPriv, err := curve.GenerateKey(rand.Reader)
 	if err != nil {
 		t.Fatalf("could not create the SW key: %v", err)
 	}
+	x, y, err := ECCPoint(swPriv.PublicKey())
+	if err != nil {
+		t.Fatalf("could not get SW key point: %v", err)
+	}
 	swPub := TPMSECCPoint{
 		X: TPM2BECCParameter{Buffer: x.FillBytes(make([]byte, 32))},
 		Y: TPM2BECCParameter{Buffer: y.FillBytes(make([]byte, 32))},
 	}
 
 	// Calculate Z based on the SW priv * TPM pub
-	zx, zy := elliptic.P256().ScalarMult(tpmX, tpmY, priv)
+	zx, err := swPriv.ECDH(tpmPubKey)
+	if err != nil {
+		t.Fatalf("ecdh exchange: %v", err)
+	}
+
 	z := TPMSECCPoint{
-		X: TPM2BECCParameter{Buffer: zx.FillBytes(make([]byte, 32))},
-		Y: TPM2BECCParameter{Buffer: zy.FillBytes(make([]byte, 32))},
+		X: TPM2BECCParameter{Buffer: zx},
 	}
 
 	// Calculate Z based on TPM priv * SW pub
diff --git a/tpm2/test/evict_control_test.go b/tpm2/test/evict_control_test.go
new file mode 100644
index 0000000..fc84bb9
--- /dev/null
+++ b/tpm2/test/evict_control_test.go
@@ -0,0 +1,38 @@
+package tpm2test
+
+import (
+	"testing"
+
+	. "github.com/google/go-tpm/tpm2"
+	"github.com/google/go-tpm/tpm2/transport/simulator"
+)
+
+func TestEvictControl(t *testing.T) {
+	thetpm, err := simulator.OpenSimulator()
+	if err != nil {
+		t.Fatalf("could not connect to TPM simulator: %v", err)
+	}
+	defer thetpm.Close()
+
+	srkCreate := CreatePrimary{
+		PrimaryHandle: TPMRHOwner,
+		InPublic:      New2B(ECCSRKTemplate),
+	}
+
+	srkCreateRsp, err := srkCreate.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("could not generate SRK: %v", err)
+	}
+
+	_, err = EvictControl{
+		Auth: TPMRHOwner,
+		ObjectHandle: &NamedHandle{
+			Handle: srkCreateRsp.ObjectHandle,
+			Name:   srkCreateRsp.Name,
+		},
+		PersistentHandle: 0x81000000,
+	}.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("could not persist: %v", err)
+	}
+}
diff --git a/tpm2/test/hmac_start_test.go b/tpm2/test/hmac_start_test.go
new file mode 100644
index 0000000..88ef4a4
--- /dev/null
+++ b/tpm2/test/hmac_start_test.go
@@ -0,0 +1,141 @@
+package tpm2test
+
+import (
+	"bytes"
+	"crypto/rand"
+	"fmt"
+	"testing"
+
+	. "github.com/google/go-tpm/tpm2"
+	"github.com/google/go-tpm/tpm2/transport"
+	"github.com/google/go-tpm/tpm2/transport/simulator"
+)
+
+func TestHmacStart(t *testing.T) {
+	thetpm, err := simulator.OpenSimulator()
+	if err != nil {
+		t.Fatalf("could not connect to TPM simulator: %v", err)
+	}
+	defer thetpm.Close()
+
+	run := func(t *testing.T, data []byte, password []byte, hierarchy TPMHandle, thetpm transport.TPM) []byte {
+		maxInputBuffer := 1024
+
+		sas, sasCloser, err := HMACSession(thetpm, TPMAlgSHA256, 16)
+		if err != nil {
+			t.Fatalf("could not create hmac key authorization session: %v", err)
+		}
+		defer func() {
+			_ = sasCloser()
+		}()
+
+		createPrimary := CreatePrimary{
+			PrimaryHandle: AuthHandle{
+				Handle: hierarchy,
+				Auth:   sas,
+			},
+			InPublic: New2B(TPMTPublic{
+				Type:    TPMAlgKeyedHash,
+				NameAlg: TPMAlgSHA256,
+				ObjectAttributes: TPMAObject{
+					SignEncrypt:         true,
+					FixedTPM:            true,
+					FixedParent:         true,
+					SensitiveDataOrigin: true,
+					UserWithAuth:        true,
+				},
+				Parameters: NewTPMUPublicParms(TPMAlgKeyedHash,
+					&TPMSKeyedHashParms{
+						Scheme: TPMTKeyedHashScheme{
+							Scheme: TPMAlgHMAC,
+							Details: NewTPMUSchemeKeyedHash(TPMAlgHMAC,
+								&TPMSSchemeHMAC{
+									HashAlg: TPMAlgSHA256,
+								}),
+						},
+					}),
+			}),
+		}
+
+		rspCP, err := createPrimary.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("CreatePrimary HMAC key failed: %v", err)
+		}
+
+		flushContext := FlushContext{FlushHandle: rspCP.ObjectHandle}
+		defer func() {
+			_, _ = flushContext.Execute(thetpm)
+		}()
+
+		hmacStart := HmacStart{
+			Handle: AuthHandle{
+				Handle: rspCP.ObjectHandle,
+				Name:   rspCP.Name,
+				Auth:   sas,
+			},
+			Auth: TPM2BAuth{
+				Buffer: password,
+			},
+			HashAlg: TPMAlgNull,
+		}
+
+		rspHS, err := hmacStart.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("HmacStart failed: %v", err)
+		}
+
+		authHandle := AuthHandle{
+			Handle: rspHS.SequenceHandle,
+			Auth:   PasswordAuth(password),
+		}
+		for len(data) > maxInputBuffer {
+			sequenceUpdate := SequenceUpdate{
+				SequenceHandle: authHandle,
+				Buffer: TPM2BMaxBuffer{
+					Buffer: data[:maxInputBuffer],
+				},
+			}
+			_, err = sequenceUpdate.Execute(thetpm)
+			if err != nil {
+				t.Fatalf("SequenceUpdate failed: %v", err)
+			}
+
+			data = data[maxInputBuffer:]
+		}
+
+		sequenceComplete := SequenceComplete{
+			SequenceHandle: authHandle,
+			Buffer: TPM2BMaxBuffer{
+				Buffer: data,
+			},
+			Hierarchy: hierarchy,
+		}
+
+		rspSC, err := sequenceComplete.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("SequenceComplete failed: %v", err)
+		}
+		return rspSC.Result.Buffer
+
+	}
+
+	bufferSizes := []int{512, 1024, 2048, 4096}
+	password := make([]byte, 8)
+	_, _ = rand.Read(password)
+
+	for _, bufferSize := range bufferSizes {
+		data := make([]byte, bufferSize)
+		for _, hierarchy := range []TPMHandle{TPMRHNull, TPMRHOwner, TPMRHEndorsement} {
+			t.Run(fmt.Sprintf("Null hierarchy [bufferSize=%d]", bufferSize), func(t *testing.T) {
+				_, _ = rand.Read(data)
+				// HMAC Key is not exported and can not be externally validated,
+				// run HMAC twice with same data and confirm they are the same
+				hmac1 := run(t, data, password, hierarchy, thetpm)
+				hmac2 := run(t, data, password, hierarchy, thetpm)
+				if !bytes.Equal(hmac1, hmac2) {
+					t.Errorf("hmac %x is not expected %x", hmac1, hmac2)
+				}
+			})
+		}
+	}
+}
diff --git a/tpm2/test/import_test.go b/tpm2/test/import_test.go
new file mode 100644
index 0000000..fceb563
--- /dev/null
+++ b/tpm2/test/import_test.go
@@ -0,0 +1,100 @@
+package tpm2test
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"testing"
+
+	. "github.com/google/go-tpm/tpm2"
+	"github.com/google/go-tpm/tpm2/transport/simulator"
+)
+
+func TestImport(t *testing.T) {
+	thetpm, err := simulator.OpenSimulator()
+	if err != nil {
+		t.Fatalf("could not connect to TPM simulator: %v", err)
+	}
+	defer thetpm.Close()
+
+	srkCreate := CreatePrimary{
+		PrimaryHandle: TPMRHOwner,
+		InPublic:      New2B(ECCSRKTemplate),
+	}
+
+	srkCreateRsp, err := srkCreate.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("could not generate SRK: %v", err)
+	}
+	defer func() {
+		flush := FlushContext{
+			FlushHandle: srkCreateRsp.ObjectHandle,
+		}
+		_, err := flush.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("could not flush SRK: %v", err)
+		}
+	}()
+
+	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		t.Fatalf("failed to generate ecdsa key: %v", err)
+	}
+
+	sens2B := Marshal(TPMTSensitive{
+		SensitiveType: TPMAlgECC,
+		Sensitive: NewTPMUSensitiveComposite(
+			TPMAlgECC,
+			&TPM2BECCParameter{Buffer: pk.D.FillBytes(make([]byte, 32))},
+		),
+	})
+
+	l := Marshal(TPM2BPrivate{Buffer: sens2B})
+
+	_, err = Import{
+		ParentHandle: &AuthHandle{
+			Handle: srkCreateRsp.ObjectHandle,
+			Name:   srkCreateRsp.Name,
+			Auth:   PasswordAuth(nil),
+		},
+		Duplicate: TPM2BPrivate{Buffer: l},
+		ObjectPublic: New2B(TPMTPublic{
+			Type:    TPMAlgECC,
+			NameAlg: TPMAlgSHA256,
+			ObjectAttributes: TPMAObject{
+				SignEncrypt:          true,
+				SensitiveDataOrigin:  false,
+				EncryptedDuplication: false,
+			},
+			Parameters: NewTPMUPublicParms(
+				TPMAlgECC,
+				&TPMSECCParms{
+					CurveID: TPMECCNistP256,
+					Scheme: TPMTECCScheme{
+						Scheme: TPMAlgECDSA,
+						Details: NewTPMUAsymScheme(
+							TPMAlgECDSA,
+							&TPMSSigSchemeECDSA{
+								HashAlg: TPMAlgSHA256,
+							},
+						),
+					},
+				},
+			),
+			Unique: NewTPMUPublicID(
+				TPMAlgECC,
+				&TPMSECCPoint{
+					X: TPM2BECCParameter{
+						Buffer: pk.X.FillBytes(make([]byte, 32)),
+					},
+					Y: TPM2BECCParameter{
+						Buffer: pk.Y.FillBytes(make([]byte, 32)),
+					},
+				},
+			),
+		}),
+	}.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("could not import: %v", err)
+	}
+}
diff --git a/tpm2/test/object_change_auth_test.go b/tpm2/test/object_change_auth_test.go
new file mode 100644
index 0000000..bd46dd9
--- /dev/null
+++ b/tpm2/test/object_change_auth_test.go
@@ -0,0 +1,212 @@
+package tpm2test
+
+import (
+	"bytes"
+	"errors"
+	"testing"
+
+	. "github.com/google/go-tpm/tpm2"
+	"github.com/google/go-tpm/tpm2/transport/simulator"
+)
+
+func TestObjectChangeAuth(t *testing.T) {
+	thetpm, err := simulator.OpenSimulator()
+	if err != nil {
+		t.Fatalf("could not connect to TPM simulator: %v", err)
+	}
+	defer thetpm.Close()
+
+	// Create the SRK
+	// Put a password on the SRK to test more of the flows.
+	createSRKCmd := CreatePrimary{
+		PrimaryHandle: TPMRHOwner,
+		InSensitive: TPM2BSensitiveCreate{
+			Sensitive: &TPMSSensitiveCreate{
+				UserAuth: TPM2BAuth{
+					Buffer: []byte(nil),
+				},
+			},
+		},
+		InPublic: New2B(ECCSRKTemplate),
+	}
+	createSRKRsp, err := createSRKCmd.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	defer func() {
+		// Flush the SRK
+		flushSRKCmd := FlushContext{FlushHandle: createSRKRsp.ObjectHandle}
+		if _, err := flushSRKCmd.Execute(thetpm); err != nil {
+			t.Errorf("%v", err)
+		}
+	}()
+
+	// Data we are sealing
+	data := []byte("secrets")
+
+	// Original auth for the key
+	auth := []byte("oldauth")
+
+	// New auth we are changing to
+	newauth := []byte("newauth")
+
+	// Create a sealed blob under the SRK
+	createBlobCmd := Create{
+		ParentHandle: AuthHandle{
+			Handle: createSRKRsp.ObjectHandle,
+			Name:   createSRKRsp.Name,
+			Auth:   PasswordAuth(nil),
+		},
+		InSensitive: TPM2BSensitiveCreate{
+			Sensitive: &TPMSSensitiveCreate{
+				UserAuth: TPM2BAuth{
+					Buffer: auth,
+				},
+				Data: NewTPMUSensitiveCreate(&TPM2BSensitiveData{
+					Buffer: data,
+				}),
+			},
+		},
+		InPublic: New2B(TPMTPublic{
+			Type:    TPMAlgKeyedHash,
+			NameAlg: TPMAlgSHA256,
+			ObjectAttributes: TPMAObject{
+				FixedTPM:     true,
+				FixedParent:  true,
+				UserWithAuth: true,
+				NoDA:         true,
+			},
+		}),
+	}
+
+	var createBlobRsp *CreateResponse
+
+	t.Run("Create", func(t *testing.T) {
+		createBlobRsp, err = createBlobCmd.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("%v", err)
+		}
+	})
+
+	loadBlobCmd := Load{
+		ParentHandle: AuthHandle{
+			Handle: createSRKRsp.ObjectHandle,
+			Name:   createSRKRsp.Name,
+			Auth:   PasswordAuth(nil),
+		},
+		InPrivate: createBlobRsp.OutPrivate,
+		InPublic:  createBlobRsp.OutPublic,
+	}
+	loadBlobRsp, err := loadBlobCmd.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	defer func() {
+		// Flush the blob
+		flushBlobCmd := FlushContext{FlushHandle: loadBlobRsp.ObjectHandle}
+		if _, err := flushBlobCmd.Execute(thetpm); err != nil {
+			t.Errorf("%v", err)
+		}
+	}()
+
+	unsealCmd := Unseal{
+		ItemHandle: NamedHandle{
+			Handle: loadBlobRsp.ObjectHandle,
+			Name:   loadBlobRsp.Name,
+		},
+	}
+
+	// Unseal the blob with a password session
+	t.Run("WithPassword", func(t *testing.T) {
+		unsealCmd.ItemHandle = AuthHandle{
+			Handle: loadBlobRsp.ObjectHandle,
+			Name:   loadBlobRsp.Name,
+			Auth:   PasswordAuth(auth),
+		}
+		unsealRsp, err := unsealCmd.Execute(thetpm)
+		if err != nil {
+			t.Errorf("%v", err)
+		}
+		if !bytes.Equal(unsealRsp.OutData.Buffer, data) {
+			t.Errorf("want %x got %x", data, unsealRsp.OutData.Buffer)
+		}
+	})
+
+	// Change the auth of the object
+	t.Run("ObjectChangeAuth", func(t *testing.T) {
+		oca := ObjectChangeAuth{
+			ObjectHandle: AuthHandle{
+				Handle: loadBlobRsp.ObjectHandle,
+				Name:   loadBlobRsp.Name,
+				Auth:   PasswordAuth(auth),
+			},
+			ParentHandle: NamedHandle{
+				Handle: createSRKRsp.ObjectHandle,
+				Name:   createSRKRsp.Name,
+			},
+			NewAuth: TPM2BAuth{
+				Buffer: newauth,
+			},
+		}
+
+		ocaRsp, err := oca.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("failed objectchangeauthrequest: %v", err)
+		}
+
+		// Flush the old handle
+		flushBlobCmd := FlushContext{FlushHandle: loadBlobRsp.ObjectHandle}
+		if _, err := flushBlobCmd.Execute(thetpm); err != nil {
+			t.Errorf("%v", err)
+		}
+
+		// Load the new private blob, and the old public blob
+		loadBlobCmd := Load{
+			ParentHandle: AuthHandle{
+				Handle: createSRKRsp.ObjectHandle,
+				Name:   createSRKRsp.Name,
+				Auth:   PasswordAuth(nil),
+			},
+			InPrivate: ocaRsp.OutPrivate,
+			InPublic:  createBlobRsp.OutPublic,
+		}
+		loadBlobRsp, err = loadBlobCmd.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("%v", err)
+		}
+	})
+
+	// Unseal the blob with a password session
+	t.Run("WithOldPassword", func(t *testing.T) {
+		unsealCmd.ItemHandle = AuthHandle{
+			Handle: loadBlobRsp.ObjectHandle,
+			Name:   loadBlobRsp.Name,
+			Auth:   PasswordAuth(auth),
+		}
+		_, err := unsealCmd.Execute(thetpm)
+		if !errors.Is(err, TPMRCBadAuth) {
+			t.Errorf("want TPM_RC_BAD_AUTH, got %v", err)
+		}
+		var fmt1 TPMFmt1Error
+		if !errors.As(err, &fmt1) {
+			t.Errorf("want a Fmt1Error, got %v", err)
+		} else if isSession, session := fmt1.Session(); !isSession || session != 1 {
+			t.Errorf("want TPM_RC_BAD_AUTH on session 1, got %v", err)
+		}
+	})
+
+	t.Run("WithNewPassword", func(t *testing.T) {
+		unsealCmd.ItemHandle = AuthHandle{
+			Handle: loadBlobRsp.ObjectHandle,
+			Name:   loadBlobRsp.Name,
+			Auth:   PasswordAuth(newauth),
+		}
+		unsealRsp, err := unsealCmd.Execute(thetpm)
+		if err != nil {
+			t.Errorf("%v", err)
+		}
+		if !bytes.Equal(unsealRsp.OutData.Buffer, data) {
+			t.Errorf("want %x got %x", data, unsealRsp.OutData.Buffer)
+		}
+	})
+}
diff --git a/tpm2/test/pcr_test.go b/tpm2/test/pcr_test.go
index 6421213..09a2bc9 100644
--- a/tpm2/test/pcr_test.go
+++ b/tpm2/test/pcr_test.go
@@ -12,6 +12,54 @@ import (
 	"github.com/google/go-tpm/tpm2/transport/simulator"
 )
 
+func TestPCRs(t *testing.T) {
+	for i, tc := range []struct {
+		pcrs       []uint
+		wantSelect []byte
+	}{
+		{
+			pcrs:       nil,
+			wantSelect: []byte{0x00, 0x00, 0x00},
+		},
+		{
+			pcrs:       []uint{0},
+			wantSelect: []byte{0x01, 0x00, 0x00},
+		},
+		{
+			pcrs:       []uint{0, 1, 2},
+			wantSelect: []byte{0x07, 0x00, 0x00},
+		},
+		{
+			pcrs:       []uint{0, 7},
+			wantSelect: []byte{0x81, 0x00, 0x00},
+		},
+		{
+			pcrs:       []uint{8},
+			wantSelect: []byte{0x00, 0x01, 0x00},
+		},
+		{
+			pcrs:       []uint{1, 8, 9},
+			wantSelect: []byte{0x02, 0x03, 0x00},
+		},
+		{
+			pcrs:       []uint{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
+			wantSelect: []byte{0xff, 0xff, 0xff},
+		},
+		{
+			pcrs: []uint{255},
+			wantSelect: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80},
+		},
+	} {
+		t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
+			selection := PCClientCompatible.PCRs(tc.pcrs...)
+			if !bytes.Equal(selection, tc.wantSelect) {
+				t.Errorf("PCRs() = 0x%x, want 0x%x", selection, tc.wantSelect)
+			}
+		})
+	}
+}
+
 var extendstpm2 = map[TPMAlgID][]struct {
 	digest []byte
 }{
@@ -45,7 +93,7 @@ func TestPCRReset(t *testing.T) {
 	}
 	defer thetpm.Close()
 
-	DebugPCR := 16
+	DebugPCR := uint(16)
 
 	cases := []struct {
 		name    string
@@ -58,11 +106,6 @@ func TestPCRReset(t *testing.T) {
 
 	for _, c := range cases {
 		t.Run(c.name, func(t *testing.T) {
-			PCRs, err := CreatePCRSelection([]int{DebugPCR})
-			if err != nil {
-				t.Fatalf("Failed to create PCRSelection")
-			}
-
 			authHandle := AuthHandle{
 				Handle: TPMHandle(DebugPCR),
 				Auth:   PasswordAuth(nil),
@@ -73,7 +116,7 @@ func TestPCRReset(t *testing.T) {
 					PCRSelections: []TPMSPCRSelection{
 						{
 							Hash:      c.hashalg,
-							PCRSelect: PCRs,
+							PCRSelect: PCClientCompatible.PCRs(DebugPCR),
 						},
 					},
 				},
@@ -146,17 +189,12 @@ func TestPCREvent(t *testing.T) {
 		t.Run(c.name, func(t *testing.T) {
 			for i := 0; i < 17; i++ {
 				t.Run(fmt.Sprintf("PCR%02d", i), func(t *testing.T) {
-					PCRs, err := CreatePCRSelection([]int{i})
-					if err != nil {
-						t.Fatalf("Failed to create PCRSelection")
-					}
-
 					pcrRead := PCRRead{
 						PCRSelectionIn: TPMLPCRSelection{
 							PCRSelections: []TPMSPCRSelection{
 								{
 									Hash:      c.hashalg,
-									PCRSelect: PCRs,
+									PCRSelect: PCClientCompatible.PCRs(20),
 								},
 							},
 						},
diff --git a/tpm2/test/policy_test.go b/tpm2/test/policy_test.go
index 82d06fc..9720f45 100644
--- a/tpm2/test/policy_test.go
+++ b/tpm2/test/policy_test.go
@@ -10,6 +10,59 @@ import (
 	"github.com/google/go-tpm/tpm2/transport/simulator"
 )
 
+// This test isn't interesting, but it checks that you can omit the handles on `StartAuthSession`.
+func TestCreatePolicySession(t *testing.T) {
+	for _, tc := range []struct {
+		name string
+		typ  TPMSE
+	}{
+		{
+			"trial",
+			TPMSETrial,
+		},
+		{
+			"policy",
+			TPMSEPolicy,
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			thetpm, err := simulator.OpenSimulator()
+			if err != nil {
+				t.Fatalf("could not connect to TPM simulator: %v", err)
+			}
+			defer thetpm.Close()
+
+			sas, err := StartAuthSession{
+				SessionType: tc.typ,
+				AuthHash:    TPMAlgSHA256,
+				NonceCaller: TPM2BNonce{
+					Buffer: make([]byte, 16),
+				},
+			}.Execute(thetpm)
+			if err != nil {
+				t.Fatalf("StartAuthSession() = %v", err)
+			}
+
+			pgd, err := PolicyGetDigest{
+				PolicySession: sas.SessionHandle,
+			}.Execute(thetpm)
+			if err != nil {
+				t.Fatalf("PolicyGetDigest() = %v", err)
+			}
+			if digest := pgd.PolicyDigest.Buffer; !bytes.Equal(digest, make([]byte, len(digest))) {
+				t.Errorf("PolicyGetDigest() = %x, want all zeros", digest)
+			}
+
+			_, err = FlushContext{
+				FlushHandle: sas.SessionHandle,
+			}.Execute(thetpm)
+			if err != nil {
+				t.Errorf("FlushContext() = %v", err)
+			}
+		})
+	}
+}
+
 func signingKey(t *testing.T, thetpm transport.TPM) (NamedHandle, func()) {
 	t.Helper()
 	createPrimary := CreatePrimary{
@@ -313,16 +366,11 @@ func TestPolicyPCR(t *testing.T) {
 	}
 	defer thetpm.Close()
 
-	PCRs, err := CreatePCRSelection([]int{0, 1, 2, 3, 7})
-	if err != nil {
-		t.Fatalf("Failed to create PCRSelection")
-	}
-
 	selection := TPMLPCRSelection{
 		PCRSelections: []TPMSPCRSelection{
 			{
 				Hash:      TPMAlgSHA1,
-				PCRSelect: PCRs,
+				PCRSelect: PCClientCompatible.PCRs(0, 1, 2, 3, 7),
 			},
 		},
 	}
diff --git a/tpm2/test/rsa_encryption_test.go b/tpm2/test/rsa_encryption_test.go
new file mode 100644
index 0000000..926e23d
--- /dev/null
+++ b/tpm2/test/rsa_encryption_test.go
@@ -0,0 +1,137 @@
+package tpm2test
+
+import (
+	"bytes"
+	"testing"
+
+	. "github.com/google/go-tpm/tpm2"
+	"github.com/google/go-tpm/tpm2/transport/simulator"
+)
+
+func TestRSAEncryption(t *testing.T) {
+	theTpm, err := simulator.OpenSimulator()
+	if err != nil {
+		t.Fatalf("could not connect to TPM simulator: %v", err)
+	}
+	t.Cleanup(func() {
+		if err := theTpm.Close(); err != nil {
+			t.Errorf("%v", err)
+		}
+	})
+
+	createPrimaryCmd := CreatePrimary{
+		PrimaryHandle: TPMRHOwner,
+		InPublic:      New2B(RSASRKTemplate),
+	}
+	createPrimaryRsp, err := createPrimaryCmd.Execute(theTpm)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	t.Cleanup(func() {
+		flushContextCmd := FlushContext{FlushHandle: createPrimaryRsp.ObjectHandle}
+		if _, err := flushContextCmd.Execute(theTpm); err != nil {
+			t.Errorf("%v", err)
+		}
+	})
+
+	createCmd := Create{
+		ParentHandle: NamedHandle{
+			Handle: createPrimaryRsp.ObjectHandle,
+			Name:   createPrimaryRsp.Name,
+		},
+		InPublic: New2B(TPMTPublic{
+			Type:    TPMAlgRSA,
+			NameAlg: TPMAlgSHA256,
+			ObjectAttributes: TPMAObject{
+				FixedTPM:             true,
+				STClear:              false,
+				FixedParent:          true,
+				SensitiveDataOrigin:  true,
+				UserWithAuth:         true,
+				AdminWithPolicy:      false,
+				NoDA:                 true,
+				EncryptedDuplication: false,
+				Restricted:           false,
+				Decrypt:              true,
+				SignEncrypt:          true,
+			},
+			Parameters: NewTPMUPublicParms(
+				TPMAlgRSA,
+				&TPMSRSAParms{
+					KeyBits: 2048,
+				},
+			),
+			Unique: NewTPMUPublicID(
+				TPMAlgRSA,
+				&TPM2BPublicKeyRSA{
+					Buffer: make([]byte, 256),
+				},
+			),
+		}),
+	}
+	createRsp, err := createCmd.Execute(theTpm)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	loadCmd := Load{
+		ParentHandle: NamedHandle{
+			Handle: createPrimaryRsp.ObjectHandle,
+			Name:   createPrimaryRsp.Name,
+		},
+		InPrivate: createRsp.OutPrivate,
+		InPublic:  createRsp.OutPublic,
+	}
+	loadRsp, err := loadCmd.Execute(theTpm)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	t.Cleanup(func() {
+		flushContextCmd := FlushContext{FlushHandle: loadRsp.ObjectHandle}
+		if _, err := flushContextCmd.Execute(theTpm); err != nil {
+			t.Errorf("%v", err)
+		}
+	})
+
+	message := []byte("secret")
+
+	encryptCmd := RSAEncrypt{
+		KeyHandle: loadRsp.ObjectHandle,
+		Message:   TPM2BPublicKeyRSA{Buffer: message},
+		InScheme: TPMTRSADecrypt{
+			Scheme: TPMAlgOAEP,
+			Details: NewTPMUAsymScheme(
+				TPMAlgOAEP,
+				&TPMSEncSchemeOAEP{
+					HashAlg: TPMAlgSHA256,
+				},
+			),
+		},
+	}
+	encryptRsp, err := encryptCmd.Execute(theTpm)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	decryptCmd := RSADecrypt{
+		KeyHandle:  loadRsp.ObjectHandle,
+		CipherText: TPM2BPublicKeyRSA{Buffer: encryptRsp.OutData.Buffer},
+		InScheme: TPMTRSADecrypt{
+			Scheme: TPMAlgOAEP,
+			Details: NewTPMUAsymScheme(
+				TPMAlgOAEP,
+				&TPMSEncSchemeOAEP{
+					HashAlg: TPMAlgSHA256,
+				},
+			),
+		},
+	}
+	decryptRsp, err := decryptCmd.Execute(theTpm)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	if !bytes.Equal(message, decryptRsp.Message.Buffer) {
+		t.Errorf("want %x got %x", message, decryptRsp.Message.Buffer)
+	}
+}
diff --git a/tpm2/test/sign_test.go b/tpm2/test/sign_test.go
index 75c8557..c12caa2 100644
--- a/tpm2/test/sign_test.go
+++ b/tpm2/test/sign_test.go
@@ -4,55 +4,12 @@ import (
 	"crypto"
 	"crypto/rsa"
 	"crypto/sha256"
-	"fmt"
 	"testing"
 
-	"github.com/google/go-cmp/cmp"
-
 	. "github.com/google/go-tpm/tpm2"
 	"github.com/google/go-tpm/tpm2/transport/simulator"
-	"github.com/google/go-tpm/tpmutil"
 )
 
-func CreatePCRSelection(s []int) ([]byte, error) {
-
-	const sizeOfPCRSelect = 3
-
-	PCRs := make(tpmutil.RawBytes, sizeOfPCRSelect)
-
-	for _, n := range s {
-		if n >= 8*sizeOfPCRSelect {
-			return nil, fmt.Errorf("PCR index %d is out of range (exceeds maximum value %d)", n, 8*sizeOfPCRSelect-1)
-		}
-		byteNum := n / 8
-		bytePos := byte(1 << (n % 8))
-		PCRs[byteNum] |= bytePos
-	}
-
-	return PCRs, nil
-}
-
-func TestCreatePCRSelection(t *testing.T) {
-
-	emptyTest, err := CreatePCRSelection([]int{})
-	if err != nil {
-		t.Fatalf("Failed to create PCRSelection")
-	}
-
-	if !cmp.Equal(emptyTest, []byte{0, 0, 0}) {
-		t.Fatalf("emptyTest does not return valid PCRs")
-	}
-
-	filledTest, err := CreatePCRSelection([]int{0, 1, 2})
-	if err != nil {
-		t.Fatalf("Failed to create PCRSelection")
-	}
-
-	if !cmp.Equal(filledTest, []byte{7, 0, 0}) {
-		t.Fatalf("filledTest does not return valid PCRs")
-	}
-}
-
 func TestSign(t *testing.T) {
 
 	thetpm, err := simulator.OpenSimulator()
@@ -61,11 +18,6 @@ func TestSign(t *testing.T) {
 	}
 	defer thetpm.Close()
 
-	PCR7, err := CreatePCRSelection([]int{7})
-	if err != nil {
-		t.Fatalf("Failed to create PCRSelection")
-	}
-
 	createPrimary := CreatePrimary{
 		PrimaryHandle: TPMRHOwner,
 
@@ -99,7 +51,7 @@ func TestSign(t *testing.T) {
 			PCRSelections: []TPMSPCRSelection{
 				{
 					Hash:      TPMAlgSHA1,
-					PCRSelect: PCR7,
+					PCRSelect: PCClientCompatible.PCRs(7),
 				},
 			},
 		},
diff --git a/tpm2/test/test_parms_test.go b/tpm2/test/test_parms_test.go
new file mode 100644
index 0000000..d64e950
--- /dev/null
+++ b/tpm2/test/test_parms_test.go
@@ -0,0 +1,110 @@
+package tpm2test
+
+import (
+	"errors"
+	"testing"
+
+	. "github.com/google/go-tpm/tpm2"
+	"github.com/google/go-tpm/tpm2/transport/simulator"
+)
+
+func TestTestParms(t *testing.T) {
+	thetpm, err := simulator.OpenSimulator()
+	if err != nil {
+		t.Fatalf("could not connect to TPM simulator: %v", err)
+	}
+	defer thetpm.Close()
+
+	for _, tt := range []struct {
+		name    string
+		parms   TPMTPublicParms
+		wantErr error
+	}{
+		{
+			"p256",
+			TPMTPublicParms{
+				Type: TPMAlgECC,
+				Parameters: NewTPMUPublicParms(
+					TPMAlgECC,
+					&TPMSECCParms{
+						CurveID: TPMECCNistP256,
+					},
+				),
+			},
+			nil,
+		},
+		{
+			"p364",
+			TPMTPublicParms{
+				Type: TPMAlgECC,
+				Parameters: NewTPMUPublicParms(
+					TPMAlgECC,
+					&TPMSECCParms{
+						CurveID: TPMECCNistP384,
+					},
+				),
+			},
+			nil,
+		},
+		{
+			"p521",
+			TPMTPublicParms{
+				Type: TPMAlgECC,
+				Parameters: NewTPMUPublicParms(
+					TPMAlgECC,
+					&TPMSECCParms{
+						CurveID: TPMECCNistP521,
+					},
+				),
+			},
+			nil,
+		},
+		{
+			"rsa2048",
+			TPMTPublicParms{
+				Type: TPMAlgRSA,
+				Parameters: NewTPMUPublicParms(
+					TPMAlgRSA,
+					&TPMSRSAParms{
+						KeyBits: 2048,
+					},
+				),
+			},
+			nil,
+		},
+		{
+			"rsa3072 - unsupported",
+			TPMTPublicParms{
+				Type: TPMAlgRSA,
+				Parameters: NewTPMUPublicParms(
+					TPMAlgRSA,
+					&TPMSRSAParms{
+						KeyBits: 3072,
+					},
+				),
+			},
+			TPMRCValue,
+		},
+		{
+			"rsa4096 - unsupported",
+			TPMTPublicParms{
+				Type: TPMAlgRSA,
+				Parameters: NewTPMUPublicParms(
+					TPMAlgRSA,
+					&TPMSRSAParms{
+						KeyBits: 4096,
+					},
+				),
+			},
+			TPMRCValue,
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			grc := TestParms{Parameters: tt.parms}
+			_, err := grc.Execute(thetpm)
+			if !errors.Is(err, tt.wantErr) {
+				t.Fatalf("TestParms failed failed. Expecting err %v got %v", tt.wantErr, err)
+			}
+		})
+	}
+}