CVE-2022-29173: Improper Validation of Integrity Check Value (CWE-354)
value: go-tuf is a Go implementation of The Update Framework (TUF). go-tuf does not correctly implement the client workflow for updating the metadata files for roles other than the root role. Specifically, checks for rollback attacks are not implemented correctly meaning an attacker can cause clients to install software that is older than the software which the client previously knew to be available, and may include software with known vulnerabilities. In more detail, the client code of go-tuf has several issues in regards to preventing rollback attacks: 1. It does not take into account the content of any previously trusted metadata, if available, before proceeding with updating roles other than the root role (i.e., steps 5.4.3.1 and 5.5.5 of the detailed client workflow). This means that any form of version verification done on the newly-downloaded metadata is made using the default value of zero, which always passes. 2. For both timestamp and snapshot roles, go-tuf saves these metadata files as trusted before verifying if the version of the metafiles they refer to is correct (i.e., steps 5.5.4 and 5.6.4 of the detailed client workflow). A fix is available in version 0.3.0 or newer. No workarounds are known for this issue apart from upgrading.
CWE description: The product does not validate or incorrectly validates the integrity check values or "checksums" of a message. This may prevent it from detecting if the data has been modified or corrupted in transmission.
CVSS v3 base score: 8.8
Published: 2022-05-05T23:15Z
Repository: https://github.com/theupdateframework/go-tuf
Language: Go
Files changed: client/client.go, client/errors.go, client/interop_test.go, cmd/tuf-client/get.go, cmd/tuf-client/list.go, util/util.go, verify/errors.go, verify/verify.go
Commit date: 2022-05-05 16:49:25 +0100
Commit message: Merge pull request from GHSA-66x3-6cw3-v5gj

* Remove obsolete snapshot error check

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Add a dedicated error for missing target metadata file

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Fix protection against metadata rollback attacks

The go-tuf client now loads any previously trusted metadata before
proceeding with the update process. This is mandatory for the
protection against rollback attacks. It also fixes the detailed order
of operations necessary to implement such protection.

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Don't abort the update process if loading trusted metadata fails

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Update getLocalMeta so it tries loading every verified metadata file

If some of the metadata files fail to load, getLocalMeta will proceed
with trying to load the rest, but still return an error at the end,
if such occurred.

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Revert the preliminary targets.json download check

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Use current instead of old when addressing metadata

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Timestamp metadata do not require hashes and lenght being present

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* fix: reload local meta based on the latest root

Clear the in-memory copy of the local metadata. The goal is to reload
and take into account only the metadata files that are verified by
the latest root. Otherwise, their content should be ignored.

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* fix: update client unit tests for cases where metadata is now invalidated

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* chore: clarify the case where targets rollback verification will be skipped

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* chore: update getLocalMeta() description

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* chore: simplify getLocalMeta() so it wraps the inner error upon failure

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* chore: remove unused ErrLoadLocalFailed error type

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* chore: improve code layout for decodeSnapshot()

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>
Diff: diff --git a/client/client.go b/client/client.go
index bf22041..0241a74 100644
--- a/client/client.go
+++ b/client/client.go
@@ -175,54 +175,63 @@ func (c *Client) Update() (data.TargetFiles, error) {
 			return nil, ErrDecodeFailed{"root.json", err}
 		}
 		return nil, err
 	}
 
-	// Get timestamp.json, extract snapshot.json file meta and save the
-	// timestamp.json locally
+	// Load trusted metadata files, if any, and verify them against the latest root
+	c.getLocalMeta()
+
+	// 5.4.1 - Download the timestamp metadata
 	timestampJSON, err := c.downloadMetaUnsafe("timestamp.json", defaultTimestampDownloadLimit)
 	if err != nil {
 		return nil, err
 	}
+	// 5.4.(2,3 and 4) - Verify timestamp against various attacks
+	// Returns the extracted snapshot metadata
 	snapshotMeta, err := c.decodeTimestamp(timestampJSON)
 	if err != nil {
 		return nil, err
 	}
+	// 5.4.5 - Persist the timestamp metadata
 	if err := c.local.SetMeta("timestamp.json", timestampJSON); err != nil {
 		return nil, err
 	}
 
-	// Get snapshot.json, then extract file metas.
-	// root.json meta should not be stored in the snapshot, if it is,
-	// the root will be checked, re-downloaded
+	// 5.5.1 - Download snapshot metadata
+	// 5.5.2 and 5.5.4 - Check against timestamp role's snapshot hash and version
 	snapshotJSON, err := c.downloadMetaFromTimestamp("snapshot.json", snapshotMeta)
 	if err != nil {
 		return nil, err
 	}
+	// 5.5.(3,5 and 6) - Verify snapshot against various attacks
+	// Returns the extracted metadata files
 	snapshotMetas, err := c.decodeSnapshot(snapshotJSON)
 	if err != nil {
 		return nil, err
 	}
-
-	// Save the snapshot.json
+	// 5.5.7 - Persist snapshot metadata
 	if err := c.local.SetMeta("snapshot.json", snapshotJSON); err != nil {
 		return nil, err
 	}
 
 	// If we don't have the targets.json, download it, determine updated
 	// targets and save targets.json in local storage
 	var updatedTargets data.TargetFiles
 	targetsMeta := snapshotMetas["targets.json"]
 	if !c.hasMetaFromSnapshot("targets.json", targetsMeta) {
+		// 5.6.1 - Download the top-level targets metadata file
+		// 5.6.2 and 5.6.4 - Check against snapshot role's targets hash and version
 		targetsJSON, err := c.downloadMetaFromSnapshot("targets.json", targetsMeta)
 		if err != nil {
 			return nil, err
 		}
+		// 5.6.(3 and 5) - Verify signatures and check against freeze attack
 		updatedTargets, err = c.decodeTargets(targetsJSON)
 		if err != nil {
 			return nil, err
 		}
+		// 5.6.6 - Persist targets metadata
 		if err := c.local.SetMeta("targets.json", targetsJSON); err != nil {
 			return nil, err
 		}
 	}
 
@@ -391,48 +400,73 @@ func (c *Client) UpdateRoots() error {
 }
 
 // getLocalMeta decodes and verifies metadata from local storage.
 // The verification of local files is purely for consistency, if an attacker
 // has compromised the local storage, there is no guarantee it can be trusted.
+// Before trying to load the metadata files, it clears the in-memory copy of the local metadata.
+// This is to insure that all of the loaded metadata files at the end are indeed verified by the latest root.
+// If some of the metadata files fail to load it will proceed with trying to load the rest,
+// but still return an error at the end, if such occurred. Otherwise returns nil.
 func (c *Client) getLocalMeta() error {
+	var retErr error
+	loadFailed := false
+	// Clear the in-memory copy of the local metadata. The goal is to reload and take into account
+	// only the metadata files that are verified by the latest root. Otherwise, their content should
+	// be ignored.
+	c.localMeta = make(map[string]json.RawMessage)
+
+	// Load the latest root meta
 	if err := c.loadAndVerifyLocalRootMeta( /*ignoreExpiredCheck=*/ false); err != nil {
 		return err
 	}
 
+	// Load into memory the existing meta, if any, from the local storage
 	meta, err := c.local.GetMeta()
 	if err != nil {
 		return nil
 	}
 
+	// Verify the top-level metadata (timestamp, snapshot and targets) against the latest root and load it, if okay
 	if timestampJSON, ok := meta["timestamp.json"]; ok {
 		timestamp := &data.Timestamp{}
 		if err := c.db.UnmarshalTrusted(timestampJSON, timestamp, "timestamp"); err != nil {
-			return err
+			loadFailed = true
+			retErr = err
+		} else {
+			c.localMeta["timestamp.json"] = meta["timestamp.json"]
+			c.timestampVer = timestamp.Version
 		}
-		c.timestampVer = timestamp.Version
 	}
 
 	if snapshotJSON, ok := meta["snapshot.json"]; ok {
 		snapshot := &data.Snapshot{}
 		if err := c.db.UnmarshalTrusted(snapshotJSON, snapshot, "snapshot"); err != nil {
-			return err
+			loadFailed = true
+			retErr = err
+		} else {
+			c.localMeta["snapshot.json"] = meta["snapshot.json"]
+			c.snapshotVer = snapshot.Version
 		}
-		c.snapshotVer = snapshot.Version
 	}
 
 	if targetsJSON, ok := meta["targets.json"]; ok {
 		targets := &data.Targets{}
 		if err := c.db.UnmarshalTrusted(targetsJSON, targets, "targets"); err != nil {
-			return err
+			loadFailed = true
+			retErr = err
+		} else {
+			c.localMeta["targets.json"] = meta["targets.json"]
+			c.targetsVer = targets.Version
+			// FIXME(TUF-0.9) temporarily support files with leading path separators.
+			// c.targets = targets.Targets
+			c.loadTargets(targets.Targets)
 		}
-		c.targetsVer = targets.Version
-		// FIXME(TUF-0.9) temporarily support files with leading path separators.
-		// c.targets = targets.Targets
-		c.loadTargets(targets.Targets)
 	}
-
-	c.localMeta = meta
+	if loadFailed {
+		// If any of the metadata failed to be verified, return the reason for that failure
+		return retErr
+	}
 	return nil
 }
 
 // loadAndVerifyLocalRootMeta decodes and verifies root metadata from
 // local storage and loads the top-level keys. This method first clears
@@ -658,10 +692,11 @@ func (c *Client) downloadMetaFromSnapshot(name string, m data.SnapshotFileMeta)
 
 	meta, err := util.GenerateSnapshotFileMeta(bytes.NewReader(b), m.HashAlgorithms()...)
 	if err != nil {
 		return nil, err
 	}
+	// 5.6.2 and 5.6.4 - Check against snapshot role's targets hash and version
 	if err := util.SnapshotFileMetaEqual(meta, m); err != nil {
 		return nil, ErrDownloadFailed{name, err}
 	}
 	return b, nil
 }
@@ -674,10 +709,11 @@ func (c *Client) downloadMetaFromTimestamp(name string, m data.TimestampFileMeta
 
 	meta, err := util.GenerateTimestampFileMeta(bytes.NewReader(b), m.HashAlgorithms()...)
 	if err != nil {
 		return nil, err
 	}
+	// 5.5.2 and 5.5.4 - Check against timestamp role's snapshot hash and version
 	if err := util.TimestampFileMetaEqual(meta, m); err != nil {
 		return nil, ErrDownloadFailed{name, err}
 	}
 	return b, nil
 }
@@ -695,34 +731,67 @@ func (c *Client) decodeRoot(b json.RawMessage) error {
 
 // decodeSnapshot decodes and verifies snapshot metadata, and returns the new
 // root and targets file meta.
 func (c *Client) decodeSnapshot(b json.RawMessage) (data.SnapshotFiles, error) {
 	snapshot := &data.Snapshot{}
+	// 5.5.(3 and 6) - Verify it's signed correctly and it's not expired
 	if err := c.db.Unmarshal(b, snapshot, "snapshot", c.snapshotVer); err != nil {
 		return data.SnapshotFiles{}, ErrDecodeFailed{"snapshot.json", err}
 	}
-	c.snapshotVer = snapshot.Version
+	// 5.5.5 - Check for top-level targets rollback attack
+	// Verify explicitly that current targets meta version is less than or equal to the new one
+	if snapshot.Meta["targets.json"].Version < c.targetsVer {
+		return data.SnapshotFiles{}, verify.ErrLowVersion{Actual: snapshot.Meta["targets.json"].Version, Current: c.targetsVer}
+	}
+
+	// 5.5.5 - Get the local/trusted snapshot metadata, if any, and check all target metafiles against rollback attack
+	// In case the local snapshot metadata was not verified by the keys in the latest root during getLocalMeta(),
+	// snapshot.json won't be present in c.localMeta and thus this check will not be processed.
+	if snapshotJSON, ok := c.localMeta["snapshot.json"]; ok {
+		currentSnapshot := &data.Snapshot{}
+		if err := c.db.UnmarshalTrusted(snapshotJSON, currentSnapshot, "snapshot"); err != nil {
+			return data.SnapshotFiles{}, err
+		}
+		// 5.5.5 - Check for rollback attacks in both top-level and delegated targets roles (note that the Meta object includes both)
+		for path, local := range currentSnapshot.Meta {
+			if newMeta, ok := snapshot.Meta[path]; ok {
+				// 5.5.5 - Check for rollback attack
+				if newMeta.Version < local.Version {
+					return data.SnapshotFiles{}, verify.ErrLowVersion{Actual: newMeta.Version, Current: local.Version}
+				}
+			} else {
+				// 5.5.5 - Abort the update if a target file has been removed from the new snapshot file
+				return data.SnapshotFiles{}, verify.ErrMissingTargetFile
+			}
+		}
+	}
+	// At this point we can trust the new snapshot, the top-level targets, and any delegated targets versions it refers to
+	// so we can update the client's trusted versions and proceed with persisting the new snapshot metadata
+	// c.snapshotVer was already set when we verified the timestamp metadata
+	c.targetsVer = snapshot.Meta["targets.json"].Version
 	return snapshot.Meta, nil
 }
 
 // decodeTargets decodes and verifies targets metadata, sets c.targets and
 // returns updated targets.
 func (c *Client) decodeTargets(b json.RawMessage) (data.TargetFiles, error) {
 	targets := &data.Targets{}
+	// 5.6.(3 and 5) - Verify signatures and check against freeze attack
 	if err := c.db.Unmarshal(b, targets, "targets", c.targetsVer); err != nil {
 		return nil, ErrDecodeFailed{"targets.json", err}
 	}
+	// Generate a list with the updated targets
 	updatedTargets := make(data.TargetFiles)
 	for path, meta := range targets.Targets {
 		if local, ok := c.targets[path]; ok {
 			if err := util.TargetFileMetaEqual(local, meta); err == nil {
 				continue
 			}
 		}
 		updatedTargets[path] = meta
 	}
-	c.targetsVer = targets.Version
+	// c.targetsVer was already updated when we verified the snapshot metadata
 	// FIXME(TUF-0.9) temporarily support files with leading path separators.
 	// c.targets = targets.Targets
 	c.loadTargets(targets.Targets)
 	return updatedTargets, nil
 }
@@ -732,11 +801,19 @@ func (c *Client) decodeTargets(b json.RawMessage) (data.TargetFiles, error) {
 func (c *Client) decodeTimestamp(b json.RawMessage) (data.TimestampFileMeta, error) {
 	timestamp := &data.Timestamp{}
 	if err := c.db.Unmarshal(b, timestamp, "timestamp", c.timestampVer); err != nil {
 		return data.TimestampFileMeta{}, ErrDecodeFailed{"timestamp.json", err}
 	}
+	// 5.4.3.2 - Check for snapshot rollback attack
+	// Verify that the current snapshot meta version is less than or equal to the new one
+	if timestamp.Meta["snapshot.json"].Version < c.snapshotVer {
+		return data.TimestampFileMeta{}, verify.ErrLowVersion{Actual: timestamp.Meta["snapshot.json"].Version, Current: c.snapshotVer}
+	}
+	// At this point we can trust the new timestamp and the snaphost version it refers to
+	// so we can update the client's trusted versions and proceed with persisting the new timestamp
 	c.timestampVer = timestamp.Version
+	c.snapshotVer = timestamp.Meta["snapshot.json"].Version
 	return timestamp.Meta["snapshot.json"], nil
 }
 
 // hasMetaFromSnapshot checks whether local metadata has the given meta
 func (c *Client) hasMetaFromSnapshot(name string, m data.SnapshotFileMeta) bool {
diff --git a/client/errors.go b/client/errors.go
index 0edd957..3e7a5dc 100644
--- a/client/errors.go
+++ b/client/errors.go
@@ -68,23 +68,10 @@ type ErrWrongSize struct {
 
 func (e ErrWrongSize) Error() string {
 	return fmt.Sprintf("tuf: unexpected file size: %s (expected %d bytes, got %d bytes)", e.File, e.Expected, e.Actual)
 }
 
-type ErrLatestSnapshot struct {
-	Version int64
-}
-
-func (e ErrLatestSnapshot) Error() string {
-	return fmt.Sprintf("tuf: the local snapshot version (%d) is the latest", e.Version)
-}
-
-func IsLatestSnapshot(err error) bool {
-	_, ok := err.(ErrLatestSnapshot)
-	return ok
-}
-
 type ErrUnknownTarget struct {
 	Name            string
 	SnapshotVersion int64
 }
 
diff --git a/client/interop_test.go b/client/interop_test.go
index 19a8d9b..8e4becb 100644
--- a/client/interop_test.go
+++ b/client/interop_test.go
@@ -7,11 +7,10 @@ import (
 	"io/ioutil"
 	"net"
 	"net/http"
 	"os"
 	"path/filepath"
-	"strconv"
 
 	"github.com/theupdateframework/go-tuf/util"
 	. "gopkg.in/check.v1"
 
 	goTufGenerator "github.com/theupdateframework/go-tuf/client/testdata/go-tuf/generator"
@@ -157,13 +156,21 @@ func (t *testCase) runStep(c *C, stepName string) {
 	c.Assert(client.InitLocal(rootJsonBytes), IsNil)
 
 	// check update returns the correct updated targets
 	files, err := client.Update()
 	c.Assert(err, IsNil)
-	l, _ := strconv.Atoi(stepName)
-	c.Assert(files, HasLen, l+1)
-
+	if stepName != "2" {
+		// The rest of the test cases add one target file at a time for each cycle, so this is why we expect that
+		// the number of updated targets returned by Update() should equals to 1
+		c.Assert(files, HasLen, 1)
+	} else {
+		// The following test case (#2) verifies that when a targets key has been rotated in the latest 3.root.json,
+		// the local targets.json meta is indeed ignored since it's signed with a key that has been now changed.
+		// The reason we check for 3 here is that the updated targets corresponds to all target files listed in the
+		// targets.json for test case #2
+		c.Assert(files, HasLen, 3)
+	}
 	targetName := stepName
 	t.targets[targetName] = []byte(targetName)
 
 	file, ok := files[targetName]
 	if !ok {
diff --git a/cmd/tuf-client/get.go b/cmd/tuf-client/get.go
index 3922b94..1f60429 100644
--- a/cmd/tuf-client/get.go
+++ b/cmd/tuf-client/get.go
@@ -29,11 +29,11 @@ func (t *tmpFile) Delete() error {
 	t.Close()
 	return os.Remove(t.Name())
 }
 
 func cmdGet(args *docopt.Args, client *tuf.Client) error {
-	if _, err := client.Update(); err != nil && !tuf.IsLatestSnapshot(err) {
+	if _, err := client.Update(); err != nil {
 		return err
 	}
 	target := util.NormalizeTarget(args.String["<target>"])
 	file, err := ioutil.TempFile("", "go-tuf")
 	if err != nil {
diff --git a/cmd/tuf-client/list.go b/cmd/tuf-client/list.go
index a3cca66..f00209d 100644
--- a/cmd/tuf-client/list.go
+++ b/cmd/tuf-client/list.go
@@ -20,11 +20,11 @@ Options:
 List available target files.
   `)
 }
 
 func cmdList(args *docopt.Args, client *tuf.Client) error {
-	if _, err := client.Update(); err != nil && !tuf.IsLatestSnapshot(err) {
+	if _, err := client.Update(); err != nil {
 		return err
 	}
 	targets, err := client.Targets()
 	if err != nil {
 		return err
diff --git a/util/util.go b/util/util.go
index b6bf9cf..5690b88 100644
--- a/util/util.go
+++ b/util/util.go
@@ -116,17 +116,17 @@ func SnapshotFileMetaEqual(actual data.SnapshotFileMeta, expected data.SnapshotF
 	// the Mercury paper:
 	// https://github.com/theupdateframework/specification/pull/40
 	if expected.Length != 0 && actual.Length != expected.Length {
 		return ErrWrongLength{expected.Length, actual.Length}
 	}
-
+	// 5.6.2 - Check against snapshot role's targets hash
 	if len(expected.Hashes) != 0 {
 		if err := hashEqual(actual.Hashes, expected.Hashes); err != nil {
 			return err
 		}
 	}
-
+	// 5.6.4 - Check against snapshot role's snapshot version
 	if err := versionEqual(actual.Version, expected.Version); err != nil {
 		return err
 	}
 
 	return nil
@@ -135,17 +135,22 @@ func SnapshotFileMetaEqual(actual data.SnapshotFileMeta, expected data.SnapshotF
 func TargetFileMetaEqual(actual data.TargetFileMeta, expected data.TargetFileMeta) error {
 	return FileMetaEqual(actual.FileMeta, expected.FileMeta)
 }
 
 func TimestampFileMetaEqual(actual data.TimestampFileMeta, expected data.TimestampFileMeta) error {
-	// As opposed to snapshots, the length and hashes are still required in
-	// TUF-1.0. See:
-	// https://github.com/theupdateframework/specification/issues/38
-	if err := FileMetaEqual(actual.FileMeta, expected.FileMeta); err != nil {
-		return err
+	// TUF no longer considers the length and hashes to be a required
+	// member of Timestamp.
+	if expected.Length != 0 && actual.Length != expected.Length {
+		return ErrWrongLength{expected.Length, actual.Length}
 	}
-
+	// 5.5.2 - Check against timestamp role's snapshot hash
+	if len(expected.Hashes) != 0 {
+		if err := hashEqual(actual.Hashes, expected.Hashes); err != nil {
+			return err
+		}
+	}
+	// 5.5.4 - Check against timestamp role's snapshot version
 	if err := versionEqual(actual.Version, expected.Version); err != nil {
 		return err
 	}
 
 	return nil
diff --git a/verify/errors.go b/verify/errors.go
index 11dfa14..f94321e 100644
--- a/verify/errors.go
+++ b/verify/errors.go
@@ -16,10 +16,11 @@ var (
 	ErrInvalidKey           = errors.New("tuf: invalid key")
 	ErrInvalidRole          = errors.New("tuf: invalid role")
 	ErrInvalidDelegatedRole = errors.New("tuf: invalid delegated role")
 	ErrInvalidKeyID         = errors.New("tuf: invalid key id")
 	ErrInvalidThreshold     = errors.New("tuf: invalid role threshold")
+	ErrMissingTargetFile    = errors.New("tuf: missing previously listed targets metadata file")
 )
 
 type ErrWrongID struct{}
 
 func (ErrWrongID) Error() string {
diff --git a/verify/verify.go b/verify/verify.go
index 7b6726f..c12aaaf 100644
--- a/verify/verify.go
+++ b/verify/verify.go
@@ -45,22 +45,22 @@ func (db *DB) VerifyIgnoreExpiredCheck(s *data.Signed, role string, minVersion i
 
 	return nil
 }
 
 func (db *DB) Verify(s *data.Signed, role string, minVersion int64) error {
-
+	// Verify signatures and versions
 	err := db.VerifyIgnoreExpiredCheck(s, role, minVersion)
 
 	if err != nil {
 		return err
 	}
 
 	sm := &signedMeta{}
 	if err := json.Unmarshal(s.Signed, sm); err != nil {
 		return err
 	}
-
+	// Verify expiration
 	if IsExpired(sm.Expires) {
 		return ErrExpired{sm.Expires}
 	}
 
 	return nil

Vulnerable code: 	// Get timestamp.json, extract snapshot.json file meta and save the
	// timestamp.json locally
	// Get snapshot.json, then extract file metas.
	// root.json meta should not be stored in the snapshot, if it is,
	// the root will be checked, re-downloaded

	// Save the snapshot.json
			return err
		c.timestampVer = timestamp.Version
			return err
		c.snapshotVer = snapshot.Version
			return err
		c.targetsVer = targets.Version
		// FIXME(TUF-0.9) temporarily support files with leading path separators.
		// c.targets = targets.Targets
		c.loadTargets(targets.Targets)

	c.localMeta = meta
	c.snapshotVer = snapshot.Version
	c.targetsVer = targets.Version
type ErrLatestSnapshot struct {
	Version int64
}

func (e ErrLatestSnapshot) Error() string {
	return fmt.Sprintf("tuf: the local snapshot version (%d) is the latest", e.Version)
}

func IsLatestSnapshot(err error) bool {
	_, ok := err.(ErrLatestSnapshot)
	return ok
}

	"strconv"
	l, _ := strconv.Atoi(stepName)
	c.Assert(files, HasLen, l+1)

	if _, err := client.Update(); err != nil && !tuf.IsLatestSnapshot(err) {
	if _, err := client.Update(); err != nil && !tuf.IsLatestSnapshot(err) {


	// As opposed to snapshots, the length and hashes are still required in
	// TUF-1.0. See:
	// https://github.com/theupdateframework/specification/issues/38
	if err := FileMetaEqual(actual.FileMeta, expected.FileMeta); err != nil {
		return err



Fixed code: 	// Load trusted metadata files, if any, and verify them against the latest root
	c.getLocalMeta()

	// 5.4.1 - Download the timestamp metadata
	// 5.4.(2,3 and 4) - Verify timestamp against various attacks
	// Returns the extracted snapshot metadata
	// 5.4.5 - Persist the timestamp metadata
	// 5.5.1 - Download snapshot metadata
	// 5.5.2 and 5.5.4 - Check against timestamp role's snapshot hash and version
	// 5.5.(3,5 and 6) - Verify snapshot against various attacks
	// Returns the extracted metadata files
	// 5.5.7 - Persist snapshot metadata
		// 5.6.1 - Download the top-level targets metadata file
		// 5.6.2 and 5.6.4 - Check against snapshot role's targets hash and version
		// 5.6.(3 and 5) - Verify signatures and check against freeze attack
		// 5.6.6 - Persist targets metadata
// Before trying to load the metadata files, it clears the in-memory copy of the local metadata.
// This is to insure that all of the loaded metadata files at the end are indeed verified by the latest root.
// If some of the metadata files fail to load it will proceed with trying to load the rest,
// but still return an error at the end, if such occurred. Otherwise returns nil.
	var retErr error
	loadFailed := false
	// Clear the in-memory copy of the local metadata. The goal is to reload and take into account
	// only the metadata files that are verified by the latest root. Otherwise, their content should
	// be ignored.
	c.localMeta = make(map[string]json.RawMessage)

	// Load the latest root meta
	// Load into memory the existing meta, if any, from the local storage
	// Verify the top-level metadata (timestamp, snapshot and targets) against the latest root and load it, if okay
			loadFailed = true
			retErr = err
		} else {
			c.localMeta["timestamp.json"] = meta["timestamp.json"]
			c.timestampVer = timestamp.Version
			loadFailed = true
			retErr = err
		} else {
			c.localMeta["snapshot.json"] = meta["snapshot.json"]
			c.snapshotVer = snapshot.Version
			loadFailed = true
			retErr = err
		} else {
			c.localMeta["targets.json"] = meta["targets.json"]
			c.targetsVer = targets.Version
			// FIXME(TUF-0.9) temporarily support files with leading path separators.
			// c.targets = targets.Targets
			c.loadTargets(targets.Targets)
	if loadFailed {
		// If any of the metadata failed to be verified, return the reason for that failure
		return retErr
	}
	// 5.6.2 and 5.6.4 - Check against snapshot role's targets hash and version
	// 5.5.2 and 5.5.4 - Check against timestamp role's snapshot hash and version
	// 5.5.(3 and 6) - Verify it's signed correctly and it's not expired
	// 5.5.5 - Check for top-level targets rollback attack
	// Verify explicitly that current targets meta version is less than or equal to the new one
	if snapshot.Meta["targets.json"].Version < c.targetsVer {
		return data.SnapshotFiles{}, verify.ErrLowVersion{Actual: snapshot.Meta["targets.json"].Version, Current: c.targetsVer}
	}

	// 5.5.5 - Get the local/trusted snapshot metadata, if any, and check all target metafiles against rollback attack
	// In case the local snapshot metadata was not verified by the keys in the latest root during getLocalMeta(),
	// snapshot.json won't be present in c.localMeta and thus this check will not be processed.
	if snapshotJSON, ok := c.localMeta["snapshot.json"]; ok {
		currentSnapshot := &data.Snapshot{}
		if err := c.db.UnmarshalTrusted(snapshotJSON, currentSnapshot, "snapshot"); err != nil {
			return data.SnapshotFiles{}, err
		}
		// 5.5.5 - Check for rollback attacks in both top-level and delegated targets roles (note that the Meta object includes both)
		for path, local := range currentSnapshot.Meta {
			if newMeta, ok := snapshot.Meta[path]; ok {
				// 5.5.5 - Check for rollback attack
				if newMeta.Version < local.Version {
					return data.SnapshotFiles{}, verify.ErrLowVersion{Actual: newMeta.Version, Current: local.Version}
				}
			} else {
				// 5.5.5 - Abort the update if a target file has been removed from the new snapshot file
				return data.SnapshotFiles{}, verify.ErrMissingTargetFile
			}
		}
	}
	// At this point we can trust the new snapshot, the top-level targets, and any delegated targets versions it refers to
	// so we can update the client's trusted versions and proceed with persisting the new snapshot metadata
	// c.snapshotVer was already set when we verified the timestamp metadata
	c.targetsVer = snapshot.Meta["targets.json"].Version
	// 5.6.(3 and 5) - Verify signatures and check against freeze attack
	// Generate a list with the updated targets
	// c.targetsVer was already updated when we verified the snapshot metadata
	// 5.4.3.2 - Check for snapshot rollback attack
	// Verify that the current snapshot meta version is less than or equal to the new one
	if timestamp.Meta["snapshot.json"].Version < c.snapshotVer {
		return data.TimestampFileMeta{}, verify.ErrLowVersion{Actual: timestamp.Meta["snapshot.json"].Version, Current: c.snapshotVer}
	}
	// At this point we can trust the new timestamp and the snaphost version it refers to
	// so we can update the client's trusted versions and proceed with persisting the new timestamp
	c.snapshotVer = timestamp.Meta["snapshot.json"].Version
	if stepName != "2" {
		// The rest of the test cases add one target file at a time for each cycle, so this is why we expect that
		// the number of updated targets returned by Update() should equals to 1
		c.Assert(files, HasLen, 1)
	} else {
		// The following test case (#2) verifies that when a targets key has been rotated in the latest 3.root.json,
		// the local targets.json meta is indeed ignored since it's signed with a key that has been now changed.
		// The reason we check for 3 here is that the updated targets corresponds to all target files listed in the
		// targets.json for test case #2
		c.Assert(files, HasLen, 3)
	}
	if _, err := client.Update(); err != nil {
	if _, err := client.Update(); err != nil {
	// 5.6.2 - Check against snapshot role's targets hash
	// 5.6.4 - Check against snapshot role's snapshot version
	// TUF no longer considers the length and hashes to be a required
	// member of Timestamp.
	if expected.Length != 0 && actual.Length != expected.Length {
		return ErrWrongLength{expected.Length, actual.Length}
	// 5.5.2 - Check against timestamp role's snapshot hash
	if len(expected.Hashes) != 0 {
		if err := hashEqual(actual.Hashes, expected.Hashes); err != nil {
			return err
		}
	}
	// 5.5.4 - Check against timestamp role's snapshot version
	ErrMissingTargetFile    = errors.New("tuf: missing previously listed targets metadata file")
	// Verify signatures and versions
	// Verify expiration