1 package client
2
3 import (
4 "crypto/sha256"
5 "encoding/json"
6 "fmt"
7 "io"
8 "net"
9 "net/http"
10 "os"
11 "strings"
12 "testing"
13 "time"
14
15 "github.com/stretchr/testify/assert"
16 "github.com/theupdateframework/go-tuf/data"
17 "github.com/theupdateframework/go-tuf/util"
18 "github.com/theupdateframework/go-tuf/verify"
19 )
20
21 func TestGetTargetMeta(t *testing.T) {
22 verify.IsExpired = func(t time.Time) bool { return false }
23 c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
24 defer func() { assert.Nil(t, closer()) }()
25 _, err := c.Update()
26 assert.Nil(t, err)
27
28 f, err := c.getTargetFileMeta("f.txt")
29 assert.Nil(t, err)
30 hash := sha256.Sum256([]byte("Contents: f.txt"))
31 assert.Equal(t, data.HexBytes(hash[:]), f.Hashes["sha256"])
32
33 f, err = c.getTargetFileMeta("targets.txt")
34 assert.Nil(t, err)
35 hash = sha256.Sum256([]byte("Contents: targets.txt"))
36 assert.Equal(t, data.HexBytes(hash[:]), f.Hashes["sha256"])
37 }
38
39 func TestMaxDelegations(t *testing.T) {
40 verify.IsExpired = func(t time.Time) bool { return false }
41 c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
42 defer func() { assert.Nil(t, closer()) }()
43 _, err := c.Update()
44 assert.Nil(t, err)
45 c.MaxDelegations = 2
46 _, err = c.getTargetFileMeta("c.txt")
47 assert.Equal(t, ErrMaxDelegations{Target: "c.txt", MaxDelegations: 2, SnapshotVersion: 2}, err)
48 }
49
50 func TestMetaNotFound(t *testing.T) {
51 verify.IsExpired = func(t time.Time) bool { return false }
52 c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
53 defer func() { assert.Nil(t, closer()) }()
54 _, err := c.Update()
55 assert.Nil(t, err)
56 _, err = c.getTargetFileMeta("unknown.txt")
57 assert.Equal(t, ErrUnknownTarget{Name: "unknown.txt", SnapshotVersion: 2}, err)
58 }
59
60 type fakeRemote struct {
61 getMeta func(name string) (stream io.ReadCloser, size int64, err error)
62 getTarget func(path string) (stream io.ReadCloser, size int64, err error)
63 }
64
65 func (f fakeRemote) GetMeta(name string) (stream io.ReadCloser, size int64, err error) {
66 return f.getMeta(name)
67 }
68
69 func (f fakeRemote) GetTarget(name string) (stream io.ReadCloser, size int64, err error) {
70 return f.getTarget(name)
71 }
72
73 func TestTargetsNotFound(t *testing.T) {
74 verify.IsExpired = func(t time.Time) bool { return false }
75 c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
76 defer func() { assert.Nil(t, closer()) }()
77 _, err := c.Update()
78 assert.Nil(t, err)
79
80 previousRemote := c.remote
81 newRemote := fakeRemote{
82 getMeta: func(path string) (stream io.ReadCloser, size int64, err error) {
83 if path == "1.c.json" {
84 return nil, 0, ErrNotFound{}
85 }
86 return previousRemote.GetMeta(path)
87 },
88 getTarget: previousRemote.GetTarget,
89 }
90 c.remote = newRemote
91
92 _, err = c.getTargetFileMeta("c.txt")
93 assert.Equal(t, ErrMissingRemoteMetadata{Name: "c.json"}, err)
94 }
95
96 func TestUnverifiedTargets(t *testing.T) {
97 verify.IsExpired = func(t time.Time) bool { return false }
98 c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
99 defer closer()
100 _, err := c.Update()
101 assert.Nil(t, err)
102
103 previousRemote := c.remote
104 newRemote := fakeRemote{
105 getMeta: func(path string) (stream io.ReadCloser, size int64, err error) {
106 if path == "1.c.json" {
107
108 return previousRemote.GetMeta("1.d.json")
109 }
110 return previousRemote.GetMeta(path)
111 },
112 getTarget: previousRemote.GetTarget,
113 }
114 c.remote = newRemote
115
116 _, err = c.getTargetFileMeta("c.txt")
117 assert.Equal(t, ErrDecodeFailed{File: "c.json", Err: verify.ErrRoleThreshold{Expected: 1, Actual: 0}}, err)
118 }
119
120 func TestPersistedMeta(t *testing.T) {
121 verify.IsExpired = func(t time.Time) bool { return false }
122 c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
123 defer closer()
124 _, err := c.Update()
125 assert.Nil(t, err)
126
127 _, err = c.local.GetMeta()
128 assert.Nil(t, err)
129
130 type expectedTargets struct {
131 name string
132 version int64
133 }
134 var persistedTests = []struct {
135 file string
136 targets []expectedTargets
137 downloadError error
138 targetError error
139 fileContent string
140 }{
141 {
142 file: "unknown",
143 targets: []expectedTargets{
144 {
145 name: "targets.json",
146 version: 2,
147 },
148 },
149 downloadError: ErrUnknownTarget{Name: "unknown", SnapshotVersion: 2},
150 targetError: ErrNotFound{File: "unknown"},
151 fileContent: "",
152 },
153 {
154 file: "b.txt",
155 targets: []expectedTargets{
156 {
157 name: "targets.json",
158 version: 2,
159 },
160 {
161 name: "a.json",
162 version: 1,
163 },
164 {
165 name: "b.json",
166 version: 1,
167 },
168 },
169 downloadError: nil,
170 targetError: nil,
171 fileContent: "Contents: b.txt",
172 },
173 {
174 file: "f.txt",
175 targets: []expectedTargets{
176 {
177 name: "targets.json",
178 version: 2,
179 },
180 {
181 name: "a.json",
182 version: 1,
183 },
184 {
185 name: "b.json",
186 version: 1,
187 },
188 {
189 name: "c.json",
190 version: 1,
191 },
192 {
193 name: "d.json",
194 version: 1,
195 },
196 {
197 name: "e.json",
198 version: 1,
199 },
200 {
201 name: "f.json",
202 version: 1,
203 },
204 },
205 downloadError: nil,
206 targetError: nil,
207 fileContent: "Contents: f.txt",
208 },
209 }
210
211 for _, tt := range persistedTests {
212 t.Run("search "+tt.file, func(t *testing.T) {
213 var dest testDestination
214 err = c.Download(tt.file, &dest)
215 assert.Equal(t, tt.downloadError, err)
216 assert.Equal(t, tt.fileContent, dest.String())
217
218 target, err := c.Target(tt.file)
219 assert.Equal(t, tt.targetError, err)
220 if tt.targetError == nil {
221 meta, err := util.GenerateTargetFileMeta(strings.NewReader(tt.fileContent), target.HashAlgorithms()...)
222 assert.Nil(t, err)
223 assert.Nil(t, util.TargetFileMetaEqual(target, meta))
224 }
225
226 p, err := c.local.GetMeta()
227 assert.Nil(t, err)
228 persisted := copyStore(p)
229 persistedLocal := copyStore(c.localMeta)
230
231 for _, notTargets := range []string{"root.json", "snapshot.json", "timestamp.json"} {
232 delete(persisted, notTargets)
233 delete(persistedLocal, notTargets)
234 }
235 for _, targets := range tt.targets {
236
237 storedVersion, err := versionOfStoredTargets(targets.name, persisted)
238 assert.Equal(t, targets.version, storedVersion)
239 assert.Nil(t, err)
240 delete(persisted, targets.name)
241
242
243 storedVersion, err = versionOfStoredTargets(targets.name, persistedLocal)
244 assert.Equal(t, targets.version, storedVersion)
245 assert.Nil(t, err)
246 delete(persistedLocal, targets.name)
247 }
248 assert.Empty(t, persisted)
249 assert.Empty(t, persistedLocal)
250 })
251 }
252 }
253
254 func TestGetDelegationPathWithNoTargetFile(t *testing.T) {
255
256
257
258
259 verify.IsExpired = func(t time.Time) bool { return false }
260 client, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture2LevelDelegation")
261 defer closer()
262 _, err := client.Update()
263 assert.Nil(t, err)
264
265 err = client.getLocalMeta()
266 assert.Nil(t, err)
267
268 _, ok := client.localMeta["a.json"]
269 assert.True(t, ok)
270
271 _, ok = client.localMeta["b.json"]
272 assert.True(t, ok)
273
274 _, ok = client.localMeta["c.json"]
275 assert.False(t, ok)
276 }
277
278 func versionOfStoredTargets(name string, store map[string]json.RawMessage) (int64, error) {
279 rawTargets, ok := store[name]
280 if !ok {
281 return 0, nil
282 }
283 s := &data.Signed{}
284 if err := json.Unmarshal(rawTargets, s); err != nil {
285 return 0, err
286 }
287 targets := &data.Targets{}
288 if err := json.Unmarshal(s.Signed, targets); err != nil {
289 return 0, err
290 }
291 return targets.Version, nil
292 }
293
294 func initTestDelegationClient(t *testing.T, dirPrefix string) (*Client, func() error) {
295 serverDir := dirPrefix + "/server"
296 initialStateDir := dirPrefix + "/client/metadata/current"
297 l, err := net.Listen("tcp", "127.0.0.1:0")
298 assert.Nil(t, err)
299 addr := l.Addr().String()
300 go http.Serve(l, http.FileServer(http.Dir(serverDir)))
301
302 opts := &HTTPRemoteOptions{
303 MetadataPath: "metadata",
304 TargetsPath: "targets",
305 }
306 remote, err := HTTPRemoteStore(fmt.Sprintf("http://%s/", addr), opts, nil)
307 assert.Nil(t, err)
308
309 c := NewClient(MemoryLocalStore(), remote)
310 rawFile, err := os.ReadFile(initialStateDir + "/" + "root.json")
311 assert.Nil(t, err)
312 assert.Nil(t, c.Init(rawFile))
313 files, err := os.ReadDir(initialStateDir)
314 assert.Nil(t, err)
315
316
317 for _, f := range files {
318 if f.IsDir() {
319 continue
320 }
321 name := f.Name()
322
323 if len(strings.Split(name, ".")) < 3 && strings.HasSuffix(name, ".json") {
324 rawFile, err := os.ReadFile(initialStateDir + "/" + name)
325 assert.Nil(t, err)
326 assert.Nil(t, c.local.SetMeta(name, rawFile))
327 }
328 }
329 return c, l.Close
330 }
331
332 func copyStore(store map[string]json.RawMessage) map[string]json.RawMessage {
333 new := make(map[string]json.RawMessage, len(store))
334 for k, raw := range store {
335 newRaw := make([]byte, len(raw))
336 copy(newRaw, []byte(raw))
337 new[k] = json.RawMessage(newRaw)
338 }
339 return new
340 }
341
View as plain text