Skip to content

Commit 95659a5

Browse files
Add client method for initializing from root metadata (#210)
* Add client method for initializing from root metadata 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 #208 Signed-off-by: Hayden Blauzvern <[email protected]> * Prefer InitLocal over Init in client and tests Signed-off-by: Hayden Blauzvern <[email protected]>
1 parent 87caa18 commit 95659a5

File tree

6 files changed

+84
-68
lines changed

6 files changed

+84
-68
lines changed

client/client.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ func NewClient(local LocalStore, remote RemoteStore) *Client {
111111
// The latest root.json is fetched from remote storage, verified using rootKeys
112112
// and threshold, and then saved in local storage. It is expected that rootKeys
113113
// were securely distributed with the software being updated.
114+
//
115+
// Deprecated: Use c.InitLocal and c.Update to initialize a local repository.
114116
func (c *Client) Init(rootKeys []*data.PublicKey, threshold int) error {
115117
if len(rootKeys) < threshold {
116118
return ErrInsufficientKeys
@@ -147,6 +149,20 @@ func (c *Client) Init(rootKeys []*data.PublicKey, threshold int) error {
147149
return c.local.SetMeta("root.json", rootJSON)
148150
}
149151

152+
// InitLocal initializes a local repository from root metadata.
153+
//
154+
// The root's keys are extracted from the root and saved in local storage.
155+
// Root expiration is not checked.
156+
// It is expected that rootJSON was securely distributed with the software
157+
// being updated.
158+
func (c *Client) InitLocal(rootJSON []byte) error {
159+
err := c.loadAndVerifyRootMeta(rootJSON, true /*ignoreExpiredCheck*/)
160+
if err != nil {
161+
return err
162+
}
163+
return c.local.SetMeta("root.json", rootJSON)
164+
}
165+
150166
// Update downloads and verifies remote metadata and returns updated targets.
151167
// It always performs root update (5.2 and 5.3) section of the v1.0.19 spec.
152168
//
@@ -430,6 +446,12 @@ func (c *Client) loadAndVerifyLocalRootMeta(ignoreExpiredCheck bool) error {
430446
if !ok {
431447
return ErrNoRootKeys
432448
}
449+
return c.loadAndVerifyRootMeta(rootJSON, ignoreExpiredCheck)
450+
}
451+
452+
// loadAndVerifyRootMeta decodes and verifies root metadata and loads the top-level keys.
453+
// This method first clears the DB for top-level keys and then loads the new keys.
454+
func (c *Client) loadAndVerifyRootMeta(rootJSON []byte, ignoreExpiredCheck bool) error {
433455
// unmarshal root.json without verifying as we need the root
434456
// keys first
435457
s := &data.Signed{}

client/client_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,50 @@ func (s *ClientSuite) TestInit(c *C) {
280280
c.Assert(err, IsNil)
281281
}
282282

283+
func (s *ClientSuite) TestInitLocalAllowsExpired(c *C) {
284+
s.genKeyExpired(c, "targets")
285+
c.Assert(s.repo.Snapshot(), IsNil)
286+
c.Assert(s.repo.Timestamp(), IsNil)
287+
c.Assert(s.repo.Commit(), IsNil)
288+
s.syncRemote(c)
289+
client := NewClient(MemoryLocalStore(), s.remote)
290+
bytes, err := io.ReadAll(s.remote.meta["root.json"])
291+
c.Assert(err, IsNil)
292+
s.withMetaExpired(func() {
293+
c.Assert(client.InitLocal(bytes), IsNil)
294+
})
295+
}
296+
297+
func (s *ClientSuite) TestInitLocal(c *C) {
298+
client := NewClient(MemoryLocalStore(), s.remote)
299+
bytes, err := io.ReadAll(s.remote.meta["root.json"])
300+
c.Assert(err, IsNil)
301+
dataSigned := &data.Signed{}
302+
c.Assert(json.Unmarshal(bytes, dataSigned), IsNil)
303+
root := &data.Root{}
304+
c.Assert(json.Unmarshal(dataSigned.Signed, root), IsNil)
305+
306+
// check Update() returns ErrNoRootKeys when uninitialized
307+
_, err = client.Update()
308+
c.Assert(err, Equals, ErrNoRootKeys)
309+
310+
// check InitLocal() returns ErrInvalid when the root's signature is
311+
// invalid
312+
// modify root and marshal without regenerating signatures
313+
root.Version = root.Version + 1
314+
rootBytes, err := json.Marshal(root)
315+
c.Assert(err, IsNil)
316+
dataSigned.Signed = rootBytes
317+
dataBytes, err := json.Marshal(dataSigned)
318+
c.Assert(err, IsNil)
319+
c.Assert(client.InitLocal(dataBytes), Equals, verify.ErrInvalid)
320+
321+
// check Update() does not return ErrNoRootKeys after initialization
322+
c.Assert(client.InitLocal(bytes), IsNil)
323+
_, err = client.Update()
324+
c.Assert(err, IsNil)
325+
}
326+
283327
func (s *ClientSuite) TestFirstUpdate(c *C) {
284328
files, err := s.newClient(c).Update()
285329
c.Assert(err, IsNil)

client/delegations_test.go

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -268,19 +268,7 @@ func initTestDelegationClient(t *testing.T, dirPrefix string) (*Client, func() e
268268
c := NewClient(MemoryLocalStore(), remote)
269269
rawFile, err := ioutil.ReadFile(initialStateDir + "/" + "root.json")
270270
assert.Nil(t, err)
271-
s := &data.Signed{}
272-
root := &data.Root{}
273-
assert.Nil(t, json.Unmarshal(rawFile, s))
274-
assert.Nil(t, json.Unmarshal(s.Signed, root))
275-
var keys []*data.PublicKey
276-
for _, sig := range s.Signatures {
277-
k, ok := root.Keys[sig.KeyID]
278-
if ok {
279-
keys = append(keys, k)
280-
}
281-
}
282-
283-
assert.Nil(t, c.Init(keys, 1))
271+
assert.Nil(t, c.InitLocal(rawFile))
284272
files, err := ioutil.ReadDir(initialStateDir)
285273
assert.Nil(t, err)
286274

client/interop_test.go

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@ package client
22

33
import (
44
"bytes"
5-
"encoding/json"
65
"fmt"
6+
"io"
77
"io/ioutil"
88
"net"
99
"net/http"
1010
"os"
1111
"path/filepath"
1212
"strconv"
1313

14-
"github.com/theupdateframework/go-tuf/data"
1514
"github.com/theupdateframework/go-tuf/util"
1615
. "gopkg.in/check.v1"
1716

@@ -150,9 +149,12 @@ func (t *testCase) runStep(c *C, stepName string) {
150149
c.Assert(err, IsNil)
151150

152151
client := NewClient(t.local, remote)
153-
// initiate a client with the root keys
154-
keys := getKeys(c, remote)
155-
c.Assert(client.Init(keys, 1), IsNil)
152+
// initiate a client with the root metadata
153+
ioReader, _, err := remote.GetMeta("root.json")
154+
c.Assert(err, IsNil)
155+
rootJsonBytes, err := io.ReadAll(ioReader)
156+
c.Assert(err, IsNil)
157+
c.Assert(client.InitLocal(rootJsonBytes), IsNil)
156158

157159
// check update returns the correct updated targets
158160
files, err := client.Update()
@@ -191,31 +193,3 @@ func startFileServer(c *C, dir string) (string, func() error) {
191193
go http.Serve(l, http.FileServer(http.Dir(dir)))
192194
return addr, l.Close
193195
}
194-
195-
func getKeys(c *C, remote RemoteStore) []*data.PublicKey {
196-
r, _, err := remote.GetMeta("root.json")
197-
c.Assert(err, IsNil)
198-
199-
type SignedRoot struct {
200-
Signed data.Root
201-
}
202-
root := &SignedRoot{}
203-
err = json.NewDecoder(r).Decode(&root)
204-
c.Assert(err, IsNil)
205-
206-
rootRole, exists := root.Signed.Roles["root"]
207-
c.Assert(exists, Equals, true)
208-
209-
rootKeys := []*data.PublicKey{}
210-
211-
for _, keyID := range rootRole.KeyIDs {
212-
key, exists := root.Signed.Keys[keyID]
213-
c.Assert(exists, Equals, true)
214-
215-
rootKeys = append(rootKeys, key)
216-
}
217-
218-
c.Assert(rootKeys, Not(HasLen), 0)
219-
220-
return rootKeys
221-
}

client/python_interop/python_interop_test.go

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package client
22

33
import (
44
"bytes"
5-
"encoding/json"
65
"fmt"
76
"io/ioutil"
87
"net"
@@ -14,10 +13,7 @@ import (
1413

1514
tuf "github.com/theupdateframework/go-tuf"
1615
client "github.com/theupdateframework/go-tuf/client"
17-
"github.com/theupdateframework/go-tuf/data"
18-
"github.com/theupdateframework/go-tuf/pkg/keys"
1916
"github.com/theupdateframework/go-tuf/util"
20-
"golang.org/x/crypto/ed25519"
2117
. "gopkg.in/check.v1"
2218
)
2319

@@ -59,17 +55,11 @@ func (InteropSuite) TestGoClientPythonGenerated(c *C) {
5955
)
6056
c.Assert(err, IsNil)
6157

62-
// initiate a client with the root keys
63-
f, err := os.Open(filepath.Join(testDataDir, dir, "keystore", "root_key.pub"))
64-
c.Assert(err, IsNil)
65-
key := &data.PublicKey{}
66-
c.Assert(json.NewDecoder(f).Decode(key), IsNil)
67-
c.Assert(key.Type, Equals, "ed25519")
68-
pk, err := keys.GetVerifier(key)
69-
c.Assert(err, IsNil)
70-
c.Assert(pk.Public(), HasLen, ed25519.PublicKeySize)
58+
// initiate a client with the root metadata
7159
client := client.NewClient(client.MemoryLocalStore(), remote)
72-
c.Assert(client.Init([]*data.PublicKey{key}, 1), IsNil)
60+
rootJSON, err := ioutil.ReadFile(filepath.Join(testDataDir, dir, "repository", "metadata", "root.json"))
61+
c.Assert(err, IsNil)
62+
c.Assert(client.InitLocal(rootJSON), IsNil)
7363

7464
// check update returns the correct updated targets
7565
files, err := client.Update()

cmd/tuf-client/init.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
11
package main
22

33
import (
4-
"encoding/json"
54
"io"
65
"os"
76

87
"github.com/flynn/go-docopt"
98
tuf "github.com/theupdateframework/go-tuf/client"
10-
"github.com/theupdateframework/go-tuf/data"
119
)
1210

1311
func init() {
1412
register("init", cmdInit, `
15-
usage: tuf-client init [-s|--store=<path>] <url> [<root-keys-file>]
13+
usage: tuf-client init [-s|--store=<path>] <url> [<root-metadata-file>]
1614
1715
Options:
1816
-s <path> The path to the local file store [default: tuf.db]
1917
20-
Initialize the local file store with root keys.
18+
Initialize the local file store with root metadata.
2119
`)
2220
}
2321

2422
func cmdInit(args *docopt.Args, client *tuf.Client) error {
25-
file := args.String["<root-keys-file>"]
23+
file := args.String["<root-metadata-file>"]
2624
var in io.Reader
2725
if file == "" || file == "-" {
2826
in = os.Stdin
@@ -33,9 +31,9 @@ func cmdInit(args *docopt.Args, client *tuf.Client) error {
3331
return err
3432
}
3533
}
36-
var rootKeys []*data.PublicKey
37-
if err := json.NewDecoder(in).Decode(&rootKeys); err != nil {
34+
bytes, err := io.ReadAll(in)
35+
if err != nil {
3836
return err
3937
}
40-
return client.Init(rootKeys, len(rootKeys))
38+
return client.InitLocal(bytes)
4139
}

0 commit comments

Comments
 (0)