Repository: google/go-tpm
Pull request #356 (2024-03-18 03:25:39)

feat: TPM2_RSA_Encrypt and TPM2_RSA_Decrypt
Adds support for the TPM2_RSA_Encrypt and TPM2_RSA_Decrypt commands, as described in Part 3, Commands, section 14.2 and 14.3. A unit test covering a basic encrypt/decrypt flow is included.

This PR should resolve issue #346. A similar PR #347 was created to add these commands but was closed without being merged.

dflamand: Hey, not to be pushy but is there an estimate for if/when this will be reviewed?
@alexmwu @jkl73 @chrisfenner 

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 a5f2021..d236165 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,10 +1,10 @@
-FROM golang:1.21
+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/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/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/reflect.go b/tpm2/reflect.go
index c4be24e..60c9d58 100644
--- a/tpm2/reflect.go
+++ b/tpm2/reflect.go
@@ -839,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 b8b9d33..c0d2645 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 {
@@ -2284,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
diff --git a/tpm2/tpm2.go b/tpm2/tpm2.go
index 45a4bdf..7d63ab6 100644
--- a/tpm2/tpm2.go
+++ b/tpm2/tpm2.go
@@ -387,6 +387,35 @@ 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 {
@@ -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 {
@@ -1523,6 +1614,53 @@ func (cmd EvictControl) Execute(t transport.TPM, s ...Session) (*EvictControlRes
 	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 {


Test patch:
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/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/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)
+	}
+}