Skip to content

Commit

Permalink
Add client method for initializing from root metadata
Browse files Browse the repository at this point in the history
This will allow users of go-tuf to initialize a client
without requiring a remote connection. This is useful
for when a root has been updated and the local root
verification keys can no longer verify the the latest
remote root, except by verifying the chain with
client.Update().

Ref theupdateframework#208

Signed-off-by: Hayden Blauzvern <[email protected]>
  • Loading branch information
haydentherapper committed Feb 10, 2022
1 parent 2c5d73b commit 7c68b42
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 0 deletions.
20 changes: 20 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,20 @@ func (c *Client) Init(rootKeys []*data.PublicKey, threshold int) error {
return c.local.SetMeta("root.json", rootJSON)
}

// InitLocal initializes a local repository from root metadata.
//
// The root's keys are extracted from the root and saved in local storage.
// Root expiration is not checked.
// It is expected that rootJSON was securely distributed with the software
// being updated.
func (c *Client) InitLocal(rootJSON []byte) error {
err := c.loadAndVerifyRootMeta(rootJSON, true /*ignoreExpiredCheck*/)
if err != nil {
return err
}
return c.local.SetMeta("root.json", rootJSON)
}

// Update downloads and verifies remote metadata and returns updated targets.
// It always performs root update (5.2 and 5.3) section of the v1.0.19 spec.
//
Expand Down Expand Up @@ -430,6 +444,12 @@ func (c *Client) loadAndVerifyLocalRootMeta(ignoreExpiredCheck bool) error {
if !ok {
return ErrNoRootKeys
}
return c.loadAndVerifyRootMeta(rootJSON, ignoreExpiredCheck)
}

// loadAndVerifyRootMeta decodes and verifies root metadata and loads the top-level keys.
// This method first clears the DB for top-level keys and then loads the new keys.
func (c *Client) loadAndVerifyRootMeta(rootJSON []byte, ignoreExpiredCheck bool) error {
// unmarshal root.json without verifying as we need the root
// keys first
s := &data.Signed{}
Expand Down
44 changes: 44 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,50 @@ func (s *ClientSuite) TestInit(c *C) {
c.Assert(err, IsNil)
}

func (s *ClientSuite) TestInitLocalAllowsExpired(c *C) {
s.genKeyExpired(c, "targets")
c.Assert(s.repo.Snapshot(), IsNil)
c.Assert(s.repo.Timestamp(), IsNil)
c.Assert(s.repo.Commit(), IsNil)
s.syncRemote(c)
client := NewClient(MemoryLocalStore(), s.remote)
bytes, err := io.ReadAll(s.remote.meta["root.json"])
c.Assert(err, IsNil)
s.withMetaExpired(func() {
c.Assert(client.InitLocal(bytes), IsNil)
})
}

func (s *ClientSuite) TestInitLocal(c *C) {
client := NewClient(MemoryLocalStore(), s.remote)
bytes, err := io.ReadAll(s.remote.meta["root.json"])
c.Assert(err, IsNil)
dataSigned := &data.Signed{}
c.Assert(json.Unmarshal(bytes, dataSigned), IsNil)
root := &data.Root{}
c.Assert(json.Unmarshal(dataSigned.Signed, root), IsNil)

// check Update() returns ErrNoRootKeys when uninitialized
_, err = client.Update()
c.Assert(err, Equals, ErrNoRootKeys)

// check InitLocal() returns ErrInvalid when the root's signature is
// invalid
// modify root and marshal without regenerating signatures
root.Version = root.Version + 1
rootBytes, err := json.Marshal(root)
c.Assert(err, IsNil)
dataSigned.Signed = rootBytes
dataBytes, err := json.Marshal(dataSigned)
c.Assert(err, IsNil)
c.Assert(client.InitLocal(dataBytes), Equals, verify.ErrInvalid)

// check Update() does not return ErrNoRootKeys after initialization
c.Assert(client.InitLocal(bytes), IsNil)
_, err = client.Update()
c.Assert(err, IsNil)
}

func (s *ClientSuite) TestFirstUpdate(c *C) {
files, err := s.newClient(c).Update()
c.Assert(err, IsNil)
Expand Down

0 comments on commit 7c68b42

Please sign in to comment.