Repository: google/go-tpm
Pull request #144 (2019-10-15 10:08:44)

TPM 1.2: NVDefineSpace
- Add NVDefineSpace and TestNVDefineSpace
- Fixed all dependencies of localities


ChriMarMe: Running into the following problem when testing against swtpm:

Couldn't define space in NV RAM failed to define space in NVRAM: unable to read all contents in to U32Bytes

I can't figure out what the problem is. I checked all structs, fieldnames, ordinals, etc. 
Maybe someone with more knowledge can help me finding the bug.

Patch:
diff --git a/tpm/commands.go b/tpm/commands.go
index 4da41ba..90cd014 100644
--- a/tpm/commands.go
+++ b/tpm/commands.go
@@ -158,6 +158,22 @@ func getCapability(rw io.ReadWriter, cap, subcap uint32) ([]byte, error) {
 	return b, nil
 }
 
+// nvDefineSpace allocates space in NVRAM
+func nvDefineSpace(rw io.ReadWriter, nvData NVDataPublic, enc digest, ca *commandAuth) (*responseAuth, uint32, error) {
+	var ra responseAuth
+	in := []interface{}{nvData, enc}
+	if ca != nil {
+		in = append(in, ca)
+	}
+	out := []interface{}{&ra}
+	ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordNVDefineSpace, in, out)
+	if err != nil {
+		return nil, 0, err
+	}
+	return &ra, ret, nil
+
+}
+
 // nvReadValue reads from the NVRAM
 // If TPM isn't locked, no authentification is needed.
 // See TPM-Main-Part-3-Commands-20.4
diff --git a/tpm/constants.go b/tpm/constants.go
index 93d0e43..bc17071 100644
--- a/tpm/constants.go
+++ b/tpm/constants.go
@@ -23,6 +23,8 @@ import (
 // Supported TPM commands.
 const (
 	tagPCRInfoLong     uint16 = 0x06
+	tagNVAttributes    uint16 = 0x0017
+	tagNVDataPublic    uint16 = 0x0018
 	tagRQUCommand      uint16 = 0x00C1
 	tagRQUAuth1Command uint16 = 0x00C2
 	tagRQUAuth2Command uint16 = 0x00C3
@@ -56,6 +58,7 @@ const (
 	ordReadPubEK            uint32 = 0x0000007C
 	ordOwnerReadInternalPub uint32 = 0x00000081
 	ordFlushSpecific        uint32 = 0x000000BA
+	ordNVDefineSpace        uint32 = 0x000000CC
 	ordPcrReset             uint32 = 0x000000C8
 	ordNVReadValue          uint32 = 0x000000CF
 )
@@ -176,7 +179,7 @@ var locaMap = map[Locality]string{
 	locFour:  "Locality 4",
 }
 
-// PrettyPrint for Localities
+// // String returns a textual representation of the set of Localities
 func (l Locality) String() string {
 	var retString strings.Builder
 	for iterator, item := range locaMap {
diff --git a/tpm/pcrs.go b/tpm/pcrs.go
index 77fe4fb..9f84d2a 100644
--- a/tpm/pcrs.go
+++ b/tpm/pcrs.go
@@ -93,17 +93,16 @@ func (pcri pcrInfoShort) String() string {
 
 // createPCRInfoLong creates a pcrInfoLong structure from a mask and some PCR
 // values that match this mask, along with a TPM locality.
-func createPCRInfoLong(loc byte, mask pcrMask, pcrVals []byte) (*pcrInfoLong, error) {
+func createPCRInfoLong(loc Locality, mask pcrMask, pcrVals []byte) (*pcrInfoLong, error) {
 	d, err := createPCRComposite(mask, pcrVals)
 	if err != nil {
 		return nil, err
 	}
 
-	locVal := byte(1 << loc)
 	pcri := &pcrInfoLong{
 		Tag:            tagPCRInfoLong,
-		LocAtCreation:  locVal,
-		LocAtRelease:   locVal,
+		LocAtCreation:  loc,
+		LocAtRelease:   loc,
 		PCRsAtCreation: pcrSelection{3, mask},
 		PCRsAtRelease:  pcrSelection{3, mask},
 	}
@@ -114,9 +113,26 @@ func createPCRInfoLong(loc byte, mask pcrMask, pcrVals []byte) (*pcrInfoLong, er
 	return pcri, nil
 }
 
+// createPCRInfoShort creates a pcrInfoShort structure from a mask and some PCR
+// values that match this mask, alon with a TPM locality
+func createPCRInfoShort(loc Locality, mask pcrMask, pcrVals []byte) (*pcrInfoShort, error) {
+	d, err := createPCRComposite(mask, pcrVals)
+	if err != nil {
+		return nil, err
+	}
+
+	pcri := &pcrInfoShort{
+		PCRsAtRelease: pcrSelection{3, mask},
+		LocAtRelease:  loc,
+	}
+	copy(pcri.DigestAtRelease[:], d)
+
+	return pcri, nil
+}
+
 // newPCRInfoLong creates and returns a pcrInfoLong structure for the given PCR
 // values.
-func newPCRInfoLong(rw io.ReadWriter, loc byte, pcrNums []int) (*pcrInfoLong, error) {
+func newPCRInfoLong(rw io.ReadWriter, loc Locality, pcrNums []int) (*pcrInfoLong, error) {
 	var mask pcrMask
 	for _, pcr := range pcrNums {
 		if err := mask.setPCR(pcr); err != nil {
@@ -132,6 +148,20 @@ func newPCRInfoLong(rw io.ReadWriter, loc byte, pcrNums []int) (*pcrInfoLong, er
 	return createPCRInfoLong(loc, mask, pcrVals)
 }
 
+func newPCRInfoShort(rw io.ReadWriter, loc Locality, pcrNums []int) (*pcrInfoShort, error) {
+	var mask pcrMask
+	for _, pcr := range pcrNums {
+		if err := mask.setPCR(pcr); err != nil {
+			return nil, err
+		}
+	}
+	pcrVals, err := FetchPCRValues(rw, pcrNums)
+	if err != nil {
+		return nil, err
+	}
+	return createPCRInfoShort(loc, mask, pcrVals)
+}
+
 func newPCRInfo(rw io.ReadWriter, pcrNums []int) (*pcrInfo, error) {
 	var mask pcrMask
 	for _, pcr := range pcrNums {
@@ -159,7 +189,7 @@ func newPCRInfo(rw io.ReadWriter, pcrNums []int) (*pcrInfo, error) {
 
 // newPCRInfoLongWithHashes creates and returns a pcrInfoLong structure for the
 // given PCRs and hashes.
-func newPCRInfoLongWithHashes(locality byte, pcrs map[int][]byte) (*pcrInfoLong, error) {
+func newPCRInfoLongWithHashes(loc Locality, pcrs map[int][]byte) (*pcrInfoLong, error) {
 	var mask pcrMask
 	var hashes []byte
 	for index, hash := range pcrs {
@@ -169,5 +199,5 @@ func newPCRInfoLongWithHashes(locality byte, pcrs map[int][]byte) (*pcrInfoLong,
 		hashes = append(hashes, hash...)
 	}
 
-	return createPCRInfoLong(locality, mask, hashes)
+	return createPCRInfoLong(loc, mask, hashes)
 }
diff --git a/tpm/structures.go b/tpm/structures.go
index f88ddee..07a1af7 100644
--- a/tpm/structures.go
+++ b/tpm/structures.go
@@ -46,8 +46,8 @@ type pcrSelection struct {
 // pcrInfoLong stores detailed information about PCRs.
 type pcrInfoLong struct {
 	Tag              uint16
-	LocAtCreation    byte
-	LocAtRelease     byte
+	LocAtCreation    Locality
+	LocAtRelease     Locality
 	PCRsAtCreation   pcrSelection
 	PCRsAtRelease    pcrSelection
 	DigestAtCreation digest
@@ -56,8 +56,8 @@ type pcrInfoLong struct {
 
 // pcrInfoShort stores detailed information about PCRs.
 type pcrInfoShort struct {
-	LocAtRelease    byte
 	PCRsAtRelease   pcrSelection
+	LocAtRelease    Locality
 	DigestAtRelease digest
 }
 
@@ -109,27 +109,27 @@ type PermanentFlags struct {
 	DisableFullDALogicInfo       bool
 }
 
+// nvAttributes implements struct of TPM_NV_ATTRIBUTES
+// See: TPM-Main-Part-2-TPM-Structures_v1.2_rev116_01032011, P.140
+type nvAttributes struct {
+	Tag        uint16
+	Attributes permission
+}
+
 // NVDataPublic implements the structure of TPM_NV_DATA_PUBLIC
 // as described in TPM-Main-Part-2-TPM-Structures_v1.2_rev116_01032011, P. 142
 type NVDataPublic struct {
-	Tag          tpmutil.Tag
+	Tag          uint16
 	NVIndex      uint32
 	PCRInfoRead  pcrInfoShort
 	PCRInfoWrite pcrInfoShort
-	permission   NVAttributes
+	Permission   nvAttributes
 	ReadSTClear  bool
 	WriteSTClear bool
 	WriteDefine  bool
 	Size         uint32
 }
 
-// NVAttributes implements struct of TPM_NV_ATTRIBUTES
-// See: TPM-Main-Part-2-TPM-Structures_v1.2_rev116_01032011, P.140
-type NVAttributes struct {
-	tag        tpmutil.Tag
-	attributes permission
-}
-
 // CloseKey flushes the key associated with the tpmutil.Handle.
 func CloseKey(rw io.ReadWriter, h tpmutil.Handle) error {
 	return flushSpecific(rw, h, rtKey)
diff --git a/tpm/tpm.go b/tpm/tpm.go
index 48b4087..55cf5c3 100644
--- a/tpm/tpm.go
+++ b/tpm/tpm.go
@@ -374,8 +374,8 @@ func sealHelper(rw io.ReadWriter, pcrInfo *pcrInfoLong, data []byte, srkAuth []b
 }
 
 // Seal encrypts data against a given locality and PCRs and returns the sealed data.
-func Seal(rw io.ReadWriter, locality byte, pcrs []int, data []byte, srkAuth []byte) ([]byte, error) {
-	pcrInfo, err := newPCRInfoLong(rw, locality, pcrs)
+func Seal(rw io.ReadWriter, loc Locality, pcrs []int, data []byte, srkAuth []byte) ([]byte, error) {
+	pcrInfo, err := newPCRInfoLong(rw, loc, pcrs)
 	if err != nil {
 		return nil, err
 	}
@@ -386,8 +386,8 @@ func Seal(rw io.ReadWriter, locality byte, pcrs []int, data []byte, srkAuth []by
 // with a srkAuth. This function is necessary for PCR pre-calculation and later
 // sealing to provide a way of updating software which is part of a measured
 // boot process.
-func Reseal(rw io.ReadWriter, locality byte, pcrs map[int][]byte, data []byte, srkAuth []byte) ([]byte, error) {
-	pcrInfo, err := newPCRInfoLongWithHashes(locality, pcrs)
+func Reseal(rw io.ReadWriter, loc Locality, pcrs map[int][]byte, data []byte, srkAuth []byte) ([]byte, error) {
+	pcrInfo, err := newPCRInfoLongWithHashes(loc, pcrs)
 	if err != nil {
 		return nil, err
 	}
@@ -904,6 +904,47 @@ func ReadEKCert(rw io.ReadWriter, ownAuth digest) ([]byte, error) {
 	return ekbuf, nil
 }
 
+// NVDefineSpace implements the reservation of NVRAM as specified in:
+// TPM-Main-Part-3-Commands_v1.2_rev116_01032011, P. 212
+func NVDefineSpace(rw io.ReadWriter, nvData NVDataPublic, ownAuth []byte) error {
+	var ra *responseAuth
+	var ret uint32
+	if ownAuth == nil {
+	} else {
+		sharedSecretOwn, osaprOwn, err := newOSAPSession(rw, etOwner, khOwner, ownAuth[:])
+		if err != nil {
+			return fmt.Errorf("failed to start new auth session: %v", err)
+		}
+		defer osaprOwn.Close(rw)
+		defer zeroBytes(sharedSecretOwn[:])
+
+		// encAuth: NV_Define_Space is a special case where no encryption is used.
+		// See spec: TPM-Main-Part-1-Design-Principles_v1.2_rev116_01032011, P. 81
+		xorData, err := tpmutil.Pack(sharedSecretOwn, osaprOwn.NonceEven)
+		if err != nil {
+			return err
+		}
+		defer zeroBytes(xorData)
+
+		encAuthData := sha1.Sum(xorData)
+
+		authIn := []interface{}{ordNVDefineSpace, nvData, encAuthData}
+		ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, sharedSecretOwn[:], authIn)
+		if err != nil {
+			return err
+		}
+		ra, ret, err = nvDefineSpace(rw, nvData, encAuthData, ca)
+		if err != nil {
+			return fmt.Errorf("failed to define space in NVRAM: %v", err)
+		}
+		raIn := []interface{}{ret, ordNVReadValue}
+		if err := ra.verify(ca.NonceOdd, sharedSecretOwn[:], raIn); err != nil {
+			return fmt.Errorf("failed to verify authenticity of response: %v", err)
+		}
+	}
+	return nil
+}
+
 // NVReadValue returns the value from a given index, offset, and length in NVRAM.
 // See TPM-Main-Part-2-TPM-Structures 19.1.
 // If TPM isn't locked, no authentification is needed.
@@ -1028,7 +1069,7 @@ func GetNVList(rw io.ReadWriter) ([]uint32, error) {
 	return nvList, err
 }
 
-// GetNVIndex returns the structure of nvDataPublic which contains
+// GetNVIndex returns the structure of NVDataPublic which contains
 // information about the requested NV Index.
 // See: TPM-Main-Part-2-TPM-Structures_v1.2_rev116_01032011, P.167
 func GetNVIndex(rw io.ReadWriter, nvIndex uint32) (NVDataPublic, error) {


Test patch:
diff --git a/tpm/pcrs_test.go b/tpm/pcrs_test.go
index 534ae54..a8827ce 100644
--- a/tpm/pcrs_test.go
+++ b/tpm/pcrs_test.go
@@ -139,7 +139,7 @@ func TestNewPCRInfoLongWithHashes(t *testing.T) {
 	pcrMap[23] = make([]byte, 20)
 	pcrMap[16] = make([]byte, 20)
 
-	if _, err := newPCRInfoLongWithHashes(0, pcrMap); err != nil {
+	if _, err := newPCRInfoLongWithHashes(locZero, pcrMap); err != nil {
 		t.Fatal("Couldn't create pcrInfoLong structure")
 	}
 }
diff --git a/tpm/tpm_test.go b/tpm/tpm_test.go
index 5de7c02..dae62f8 100644
--- a/tpm/tpm_test.go
+++ b/tpm/tpm_test.go
@@ -200,8 +200,8 @@ func TestFetchPCRValues(t *testing.T) {
 		t.Fatal("Invalid PCR composite")
 	}
 
-	var locality byte
-	_, err = createPCRInfoLong(locality, mask, pcrs)
+	var loc Locality
+	_, err = createPCRInfoLong(loc, mask, pcrs)
 	if err != nil {
 		t.Fatal("Couldn't create a pcrInfoLong structure for these PCRs")
 	}
@@ -222,6 +222,33 @@ func TestGetRandom(t *testing.T) {
 	}
 }
 
+func TestNVDefineSpace(t *testing.T) {
+
+	rwc := openTPMOrSkip(t)
+	defer rwc.Close()
+	// Address reserved for testing as described in spec
+	// TPM-Main-Part-2-TPM-Structures_v1.2_rev116_01032011, P.139
+	owner := getAuth(ownerAuthEnvVar)
+	nvIndex := uint32(0x0000F004)
+	pcrInfoRead, err := newPCRInfoShort(rwc, locZero, []int{1})
+	if err != nil {
+		t.Fatalf("Couldn't create PCRInfoRead in define space")
+	}
+	pcrInfoWrite, err := newPCRInfoShort(rwc, locZero, []int{1})
+	if err != nil {
+		t.Fatalf("Couldn't create PCRInfoWrite in define space")
+	}
+	nvAtt := &nvAttributes{tagNVAttributes, nvPerOwnerWrite}
+	nvData := NVDataPublic{tagNVDataPublic, nvIndex, *pcrInfoRead, *pcrInfoWrite, *nvAtt, true, true, false, uint32(20)}
+
+	err = NVDefineSpace(rwc, nvData, []byte(owner[:]))
+	if err != nil {
+		t.Fatalf("Couldn't define space in NV RAM %v", err)
+	}
+	t.Log(err)
+
+}
+
 func TestOIAP(t *testing.T) {
 	rwc := openTPMOrSkip(t)
 	defer rwc.Close()
@@ -292,7 +319,7 @@ func TestSeal(t *testing.T) {
 	data[2] = 139
 
 	srkAuth := getAuth(srkAuthEnvVar)
-	sealed, err := Seal(rwc, 0 /* locality 0 */, []int{17} /* PCR 17 */, data, srkAuth[:])
+	sealed, err := Seal(rwc, locZero, []int{17} /* PCR 17 */, data, srkAuth[:])
 	if err != nil {
 		t.Fatal("Couldn't seal the data:", err)
 	}
@@ -320,7 +347,7 @@ func TestReseal(t *testing.T) {
 	pcrMap[23] = make([]byte, 20)
 	pcrMap[16] = make([]byte, 20)
 	srkAuth := getAuth(srkAuthEnvVar)
-	sealed, err := Reseal(rwc, 0 /* locality 0 */, pcrMap, data, srkAuth[:])
+	sealed, err := Reseal(rwc, locZero, pcrMap, data, srkAuth[:])
 	if err != nil {
 		t.Fatal("Couldn't seal the data:", err)
 	}