Skip to content

Commit 040092c

Browse files
authored
fix: abandon updates if timestamp.json isn't new (#387)
Adds a new test for this case: if a client sees a new `timestamp.json` file with the same version as its current `timestamp.json` file, it should do nothing (no update, but also no error). A few other tests were implicitly relying on the fact that the client did a full update each time, so they've been updated to commit a new timestamp. This updates go-tuf for TUF specification v1.0.30 (fixes #321). The only substantive change was [theupdateframework/specification#209][tuf-spec-209], which clarifies the intended behavior for updating metadata files. Updates for other roles were already in compliance: - Root metadata: https://github.com/theupdateframework/go-tuf/blob/13eff30efd6c61f165e1bf06e8c0e72f5a0e5703/client/client.go#L258 - Timestamp, checking snapshot version: https://github.com/theupdateframework/go-tuf/blob/13eff30efd6c61f165e1bf06e8c0e72f5a0e5703/client/client.go#L751 - Snapshot, must match version from timestamp: https://github.com/theupdateframework/go-tuf/blob/13eff30efd6c61f165e1bf06e8c0e72f5a0e5703/client/client.go#L667 - Snapshot, no rollback of targets: https://github.com/theupdateframework/go-tuf/blob/13eff30efd6c61f165e1bf06e8c0e72f5a0e5703/client/client.go#L685 - Targets: https://github.com/theupdateframework/go-tuf/blob/13eff30efd6c61f165e1bf06e8c0e72f5a0e5703/client/client.go#L643 [tuf-spec-209]: (theupdateframework/specification#209). Signed-off-by: Zachary Newman <[email protected]> Signed-off-by: Zachary Newman <[email protected]>
1 parent 13eff30 commit 040092c

File tree

2 files changed

+60
-7
lines changed

2 files changed

+60
-7
lines changed

client/client.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,13 @@ func (c *Client) Update() (data.TargetFiles, error) {
143143
}
144144
// 5.4.(2,3 and 4) - Verify timestamp against various attacks
145145
// Returns the extracted snapshot metadata
146-
snapshotMeta, err := c.decodeTimestamp(timestampJSON)
146+
snapshotMeta, sameTimestampVersion, err := c.decodeTimestamp(timestampJSON)
147+
if sameTimestampVersion {
148+
// The new timestamp.json file had the same version; we don't need to
149+
// update, so bail early.
150+
return c.targets, nil
151+
}
152+
147153
if err != nil {
148154
return nil, err
149155
}
@@ -740,22 +746,31 @@ func (c *Client) decodeTargets(b json.RawMessage) (data.TargetFiles, error) {
740746
}
741747

742748
// decodeTimestamp decodes and verifies timestamp metadata, and returns the
743-
// new snapshot file meta.
744-
func (c *Client) decodeTimestamp(b json.RawMessage) (data.TimestampFileMeta, error) {
749+
// new snapshot file meta and signals whether the update should be aborted early
750+
// (the new timestamp has the same version as the old one, so there's no need to
751+
// complete the update).
752+
func (c *Client) decodeTimestamp(b json.RawMessage) (data.TimestampFileMeta, bool, error) {
745753
timestamp := &data.Timestamp{}
754+
746755
if err := c.db.Unmarshal(b, timestamp, "timestamp", c.timestampVer); err != nil {
747-
return data.TimestampFileMeta{}, ErrDecodeFailed{"timestamp.json", err}
756+
return data.TimestampFileMeta{}, false, ErrDecodeFailed{"timestamp.json", err}
757+
}
758+
// 5.4.3.1 - Check for timestamp rollback attack
759+
// We already checked for timestamp.Version < c.timestampVer in the Unmarshal call above.
760+
// Here, we're checking for version equality, which indicates that we can abandon this update.
761+
if timestamp.Version == c.timestampVer {
762+
return data.TimestampFileMeta{}, true, nil
748763
}
749764
// 5.4.3.2 - Check for snapshot rollback attack
750765
// Verify that the current snapshot meta version is less than or equal to the new one
751766
if timestamp.Meta["snapshot.json"].Version < c.snapshotVer {
752-
return data.TimestampFileMeta{}, verify.ErrLowVersion{Actual: timestamp.Meta["snapshot.json"].Version, Current: c.snapshotVer}
767+
return data.TimestampFileMeta{}, false, verify.ErrLowVersion{Actual: timestamp.Meta["snapshot.json"].Version, Current: c.snapshotVer}
753768
}
754-
// At this point we can trust the new timestamp and the snaphost version it refers to
769+
// At this point we can trust the new timestamp and the snapshot version it refers to
755770
// so we can update the client's trusted versions and proceed with persisting the new timestamp
756771
c.timestampVer = timestamp.Version
757772
c.snapshotVer = timestamp.Meta["snapshot.json"].Version
758-
return timestamp.Meta["snapshot.json"], nil
773+
return timestamp.Meta["snapshot.json"], false, nil
759774
}
760775

761776
// hasMetaFromSnapshot checks whether local metadata has the given meta

client/client_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,7 @@ func (s *ClientSuite) TestUpdateMixAndMatchAttack(c *C) {
896896
c.Assert(s.repo.AddTargetWithExpires("foo.txt", nil, expires), IsNil)
897897
c.Assert(s.repo.Snapshot(), IsNil)
898898
c.Assert(s.repo.Timestamp(), IsNil)
899+
c.Assert(s.repo.Commit(), IsNil)
899900
s.syncRemote(c)
900901
client := s.updatedClient(c)
901902

@@ -909,6 +910,7 @@ func (s *ClientSuite) TestUpdateMixAndMatchAttack(c *C) {
909910
c.Assert(s.repo.AddTargetWithExpires("bar.txt", nil, expires), IsNil)
910911
c.Assert(s.repo.Snapshot(), IsNil)
911912
c.Assert(s.repo.Timestamp(), IsNil)
913+
c.Assert(s.repo.Commit(), IsNil)
912914
s.syncRemote(c)
913915
newTargets, ok := s.remote.meta["targets.json"]
914916
if !ok {
@@ -964,6 +966,41 @@ func (s *ClientSuite) TestUpdateReplayAttack(c *C) {
964966
})
965967
}
966968

969+
func (s *ClientSuite) TestUpdateForkTimestamp(c *C) {
970+
client := s.updatedClient(c)
971+
972+
// grab the remote timestamp.json
973+
oldTimestamp, ok := s.remote.meta["timestamp.json"]
974+
if !ok {
975+
c.Fatal("missing remote timestamp.json")
976+
}
977+
978+
// generate a new timestamp and sync with the client
979+
version := client.timestampVer
980+
c.Assert(version > 0, Equals, true)
981+
c.Assert(s.repo.Timestamp(), IsNil)
982+
s.syncRemote(c)
983+
_, err := client.Update()
984+
c.Assert(err, IsNil)
985+
newVersion := client.timestampVer
986+
c.Assert(newVersion > version, Equals, true)
987+
988+
// generate a new, different timestamp with the *same version*
989+
s.remote.meta["timestamp.json"] = oldTimestamp
990+
c.Assert(s.repo.Timestamp(), IsNil)
991+
c.Assert(client.timestampVer, Equals, newVersion) // double-check: same version?
992+
s.syncRemote(c)
993+
994+
oldMeta, err := client.local.GetMeta()
995+
c.Assert(err, IsNil)
996+
_, err = client.Update()
997+
c.Assert(err, IsNil) // no error: the targets.json version didn't change, so there was no update!
998+
// Client shouldn't update!
999+
newMeta, err := client.local.GetMeta()
1000+
c.Assert(err, IsNil)
1001+
c.Assert(oldMeta, DeepEquals, newMeta)
1002+
}
1003+
9671004
func (s *ClientSuite) TestUpdateTamperedTargets(c *C) {
9681005
client := s.newClient(c)
9691006

@@ -998,6 +1035,7 @@ func (s *ClientSuite) TestUpdateTamperedTargets(c *C) {
9981035
c.Assert(err, IsNil)
9991036
s.store.SetMeta("targets.json", tamperedJSON)
10001037
s.store.Commit(false, nil, nil)
1038+
c.Assert(s.repo.Timestamp(), IsNil) // unless timestamp changes, the client doesn't even look at "targets.json"
10011039
s.syncRemote(c)
10021040
_, err = client.Update()
10031041
c.Assert(err, DeepEquals, ErrWrongSize{"targets.json", int64(len(tamperedJSON)), int64(len(targetsJSON))})

0 commit comments

Comments
 (0)