1 package in_toto
2
3 import (
4 "bytes"
5 "crypto/x509"
6 "crypto/x509/pkix"
7 "fmt"
8 "os"
9 "path"
10 "path/filepath"
11 "reflect"
12 "sort"
13 "strings"
14 "testing"
15 "time"
16
17 "github.com/stretchr/testify/assert"
18 )
19
20 func TestInTotoVerifyPass(t *testing.T) {
21 t.Run("metablock layout", func(t *testing.T) {
22 layoutPath := "demo.layout"
23 pubKeyPath := "alice.pub"
24 linkDir := "."
25
26 layoutMb, err := LoadMetadata(layoutPath)
27 if err != nil {
28 t.Fatal(err)
29 }
30
31 var pubKey Key
32 if err := pubKey.LoadKey(pubKeyPath, "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
33 t.Error(err)
34 }
35
36 var layoutKeys = map[string]Key{
37 pubKey.KeyID: pubKey,
38 }
39
40
41 if _, err := InTotoVerify(layoutMb, layoutKeys, linkDir, "",
42 make(map[string]string), [][]byte{}, testOSisWindows()); err != nil {
43 t.Error(err)
44 }
45 })
46
47 t.Run("DSSE layout", func(t *testing.T) {
48 layoutPath := "demo.dsse.layout"
49 pubKeyPath := "alice.pub"
50 linkDir := "."
51
52 layoutEnv, err := LoadMetadata(layoutPath)
53 if err != nil {
54 t.Fatal(err)
55 }
56
57 var pubKey Key
58 if err := pubKey.LoadKey(pubKeyPath, "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
59 t.Error(err)
60 }
61
62 var layoutKeys = map[string]Key{
63 pubKey.KeyID: pubKey,
64 }
65
66
67 if _, err := InTotoVerify(layoutEnv, layoutKeys, linkDir, "",
68 make(map[string]string), [][]byte{}, testOSisWindows()); err != nil {
69 t.Error(err)
70 }
71 })
72
73 t.Run("verifying with only DSSE metadata", func(t *testing.T) {
74 layoutPath := "dsse-only.root.layout"
75 pubKeyPath := "alice.pub"
76 linkDir := "."
77
78 layoutEnv, err := LoadMetadata(layoutPath)
79 if err != nil {
80 t.Fatal(err)
81 }
82
83 var pubKey Key
84 if err := pubKey.LoadKey(pubKeyPath, "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
85 t.Error(err)
86 }
87
88 var layoutKeys = map[string]Key{
89 pubKey.KeyID: pubKey,
90 }
91
92
93 if _, err := InTotoVerify(layoutEnv, layoutKeys, linkDir, "",
94 make(map[string]string), [][]byte{}, testOSisWindows()); err != nil {
95 t.Error(err)
96 }
97 })
98 }
99
100 func TestGetSummaryLink(t *testing.T) {
101 demoLayout, err := LoadMetadata("demo.layout")
102 if err != nil {
103 t.Fatal(err)
104 }
105 codeLink, err := LoadMetadata("write-code.b7d643de.link")
106 if err != nil {
107 t.Error(err)
108 }
109 packageLink, err := LoadMetadata("package.d3ffd108.link")
110 if err != nil {
111 t.Error(err)
112 }
113 demoLink := make(map[string]Metadata)
114 demoLink["write-code"] = codeLink
115 demoLink["package"] = packageLink
116
117 var summaryLink Metadata
118 if summaryLink, err = GetSummaryLink(demoLayout.GetPayload().(Layout),
119 demoLink, "", false); err != nil {
120 t.Error(err)
121 }
122 if summaryLink.GetPayload().(Link).Type != codeLink.GetPayload().(Link).Type {
123 t.Errorf("summary Link isn't of type Link")
124 }
125 if summaryLink.GetPayload().(Link).Name != "" {
126 t.Errorf("summary Link name doesn't match. Expected '%s', returned "+
127 "'%s", codeLink.GetPayload().(Link).Name, summaryLink.GetPayload().(Link).Name)
128 }
129 if !reflect.DeepEqual(summaryLink.GetPayload().(Link).Materials,
130 codeLink.GetPayload().(Link).Materials) {
131 t.Errorf("summary Link materials don't match. Expected '%s', "+
132 "returned '%s", codeLink.GetPayload().(Link).Materials,
133 summaryLink.GetPayload().(Link).Materials)
134 }
135
136 if !reflect.DeepEqual(summaryLink.GetPayload().(Link).Products,
137 packageLink.GetPayload().(Link).Products) {
138 t.Errorf("summary Link products don't match. Expected '%s', "+
139 "returned '%s", packageLink.GetPayload().(Link).Products,
140 summaryLink.GetPayload().(Link).Products)
141 }
142 if !reflect.DeepEqual(summaryLink.GetPayload().(Link).Command,
143 packageLink.GetPayload().(Link).Command) {
144 t.Errorf("summary Link command doesn't match. Expected '%s', "+
145 "returned '%s", packageLink.GetPayload().(Link).Command,
146 summaryLink.GetPayload().(Link).Command)
147 }
148 if !reflect.DeepEqual(summaryLink.GetPayload().(Link).ByProducts,
149 packageLink.GetPayload().(Link).ByProducts) {
150 t.Errorf("summary Link by-products don't match. Expected '%s', "+
151 "returned '%s", packageLink.GetPayload().(Link).ByProducts,
152 summaryLink.GetPayload().(Link).ByProducts)
153 }
154 }
155
156 func TestVerifySublayouts(t *testing.T) {
157 sublayoutName := "sub_layout"
158 var aliceKey Key
159 if err := aliceKey.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
160 t.Errorf("unable to load Alice's public key")
161 }
162 sublayoutDirectory := fmt.Sprintf(SublayoutLinkDirFormat, sublayoutName,
163 aliceKey.KeyID)
164 defer func(sublayoutDirectory string) {
165 if err := os.RemoveAll(sublayoutDirectory); err != nil {
166 t.Errorf("unable to remove directory %s: %s", sublayoutDirectory, err)
167 }
168 }(sublayoutDirectory)
169
170 if err := os.Mkdir(sublayoutDirectory, 0700); err != nil {
171 t.Errorf("unable to create sublayout directory")
172 }
173 writeCodePath := path.Join(sublayoutDirectory, "write-code.b7d643de.link")
174 if err := os.Link("write-code.b7d643de.link", writeCodePath); err != nil {
175 t.Errorf("unable to link write-code metadata.")
176 }
177 packagePath := path.Join(sublayoutDirectory, "package.d3ffd108.link")
178 if err := os.Link("package.d3ffd108.link", packagePath); err != nil {
179 t.Errorf("unable to link package metadata")
180 }
181
182 superLayoutMb, err := LoadMetadata("super.layout")
183 if err != nil {
184 t.Errorf("unable to load super layout")
185 }
186
187 stepsMetadata, err := LoadLinksForLayout(superLayoutMb.GetPayload().(Layout), ".")
188 if err != nil {
189 t.Errorf("unable to load link metadata for super layout")
190 }
191
192 rootCertPool, intermediateCertPool, err := LoadLayoutCertificates(superLayoutMb.GetPayload().(Layout), [][]byte{})
193 if err != nil {
194 t.Errorf("unable to load layout certificates")
195 }
196
197 stepsMetadataVerified, err := VerifyLinkSignatureThesholds(
198 superLayoutMb.GetPayload().(Layout), stepsMetadata, rootCertPool, intermediateCertPool)
199 if err != nil {
200 t.Errorf("unable to verify link threshold values: %v", err)
201 }
202
203 result, err := VerifySublayouts(superLayoutMb.GetPayload().(Layout),
204 stepsMetadataVerified, ".", [][]byte{}, testOSisWindows())
205 if err != nil {
206 t.Errorf("unable to verify sublayouts: %v", err)
207 }
208
209 for _, stepData := range result {
210 for _, metadata := range stepData {
211 if _, ok := metadata.GetPayload().(Link); !ok {
212 t.Errorf("sublayout expansion error: found non link")
213 }
214 }
215 }
216 }
217
218 func TestRunInspections(t *testing.T) {
219
220 mb, err := LoadMetadata("demo.layout")
221 if err != nil {
222 t.Errorf("unable to parse template file: %s", err)
223 }
224 layout := mb.GetPayload().(Layout)
225
226
227
228
229 layout.Inspect = []Inspection{
230 {
231 SupplyChainItem: SupplyChainItem{Name: "foo"},
232 Run: []string{"sh", "-c", "true"},
233 },
234 {
235 SupplyChainItem: SupplyChainItem{Name: "bar"},
236 Run: []string{"sh", "-c", "true"},
237 },
238 }
239
240
241 availableFiles, _ := filepath.Glob("*")
242 result, err := RunInspections(layout, "", testOSisWindows(), false)
243
244
245 if err != nil {
246 t.Errorf("RunInspections returned %s as error, expected nil.",
247 err)
248 }
249
250
251 for _, inspectionName := range []string{"foo", "bar"} {
252
253
254 sort.Strings(availableFiles)
255
256
257 materialNames := InterfaceKeyStrings(result[inspectionName].GetPayload().(Link).Materials)
258 productNames := InterfaceKeyStrings(result[inspectionName].GetPayload().(Link).Products)
259 sort.Strings(materialNames)
260 sort.Strings(productNames)
261 if !reflect.DeepEqual(materialNames, availableFiles) ||
262 !reflect.DeepEqual(productNames, availableFiles) {
263 t.Errorf("RunInspections recorded materials and products '%s' and %s'"+
264 " for insepction '%s', expected '%s' and '%s'.", materialNames,
265 productNames, inspectionName, availableFiles, availableFiles)
266 }
267 linkName := inspectionName + ".link"
268
269
270 availableFiles = append(availableFiles, linkName)
271
272
273 err = os.Remove(linkName)
274 if os.IsNotExist(err) {
275 t.Errorf("RunInspections didn't generate expected '%s'", linkName)
276 }
277 }
278
279
280
281 layout.Inspect = []Inspection{
282 {
283 SupplyChainItem: SupplyChainItem{Name: "foo"},
284 Run: []string{"command-does-not-exist"},
285 },
286 }
287
288 result, err = RunInspections(layout, "", testOSisWindows(), false)
289 if result != nil || err == nil {
290 t.Errorf("RunInspections returned '(%s, %s)', expected"+
291 " '(nil, *exec.Error)'", result, err)
292 }
293
294
295
296 layout.Inspect = []Inspection{
297 {
298 SupplyChainItem: SupplyChainItem{Name: "foo"},
299 Run: []string{"sh", "-c", "false"},
300 },
301 }
302 result, err = RunInspections(layout, "", testOSisWindows(), false)
303 if result != nil || err == nil {
304 t.Errorf("RunInspections returned '(%s, %s)', expected"+
305 " '(nil, *exec.Error)'", result, err)
306 }
307 }
308
309 func TestVerifyArtifact(t *testing.T) {
310 var testCases = []struct {
311 name string
312 item []interface{}
313 metadata map[string]Metadata
314 expectErr string
315 }{
316 {
317 name: "Verify artifacts",
318 item: []interface{}{
319 Step{
320 SupplyChainItem: SupplyChainItem{
321 Name: "foo",
322 ExpectedMaterials: [][]string{
323 {"DELETE", "foo-delete"},
324 {"MODIFY", "foo-modify"},
325 {"MATCH", "foo-match", "WITH", "MATERIALS", "FROM", "foo"},
326 {"ALLOW", "foo-allow"},
327 {"DISALLOW", "*"},
328 },
329 ExpectedProducts: [][]string{
330 {"CREATE", "foo-create"},
331 {"MODIFY", "foo-modify"},
332 {"MATCH", "foo-match", "WITH", "MATERIALS", "FROM", "foo"},
333 {"REQUIRE", "foo-allow"},
334 {"ALLOW", "foo-allow"},
335 {"DISALLOW", "*"},
336 },
337 },
338 },
339 },
340 metadata: map[string]Metadata{
341 "foo": &Metablock{
342 Signed: Link{
343 Name: "foo",
344 Materials: map[string]interface{}{
345 "foo-delete": map[string]interface{}{"sha265": "abc"},
346 "foo-modify": map[string]interface{}{"sha265": "abc"},
347 "foo-match": map[string]interface{}{"sha265": "abc"},
348 "foo-allow": map[string]interface{}{"sha265": "abc"},
349 },
350 Products: map[string]interface{}{
351 "foo-create": map[string]interface{}{"sha265": "abc"},
352 "foo-modify": map[string]interface{}{"sha265": "abcdef"},
353 "foo-match": map[string]interface{}{"sha265": "abc"},
354 "foo-allow": map[string]interface{}{"sha265": "abc"},
355 },
356 },
357 },
358 },
359 expectErr: "",
360 },
361 {
362 name: "Verify match with relative paths",
363 item: []interface{}{
364 Step{
365 SupplyChainItem: SupplyChainItem{
366 Name: "foo",
367 ExpectedMaterials: [][]string{
368 {"MATCH", "*", "WITH", "PRODUCTS", "FROM", "bar"},
369 {"DISALLOW", "*"},
370 },
371 },
372 },
373 },
374 metadata: map[string]Metadata{
375 "foo": &Metablock{
376 Signed: Link{
377 Name: "foo",
378 Materials: map[string]interface{}{
379 "./foo.d/foo.py": map[string]interface{}{"sha265": "abc"},
380 "bar.d/bar.py": map[string]interface{}{"sha265": "abc"},
381 },
382 },
383 },
384 "bar": &Metablock{
385 Signed: Link{
386 Name: "bar",
387 Products: map[string]interface{}{
388 "foo.d/foo.py": map[string]interface{}{"sha265": "abc"},
389 "./baz/../bar.d/bar.py": map[string]interface{}{"sha265": "abc"},
390 },
391 },
392 },
393 },
394 expectErr: "",
395 },
396 {
397 name: "Verify match detection of hash mismatch",
398 item: []interface{}{
399 Step{
400 SupplyChainItem: SupplyChainItem{
401 Name: "foo",
402 ExpectedMaterials: [][]string{
403 {"MATCH", "*", "WITH", "PRODUCTS", "FROM", "bar"},
404 {"DISALLOW", "*"},
405 },
406 },
407 },
408 },
409 metadata: map[string]Metadata{
410 "foo": &Metablock{
411 Signed: Link{
412 Name: "foo",
413 Materials: map[string]interface{}{
414 "foo.d/foo.py": map[string]interface{}{"sha265": "abc"},
415 "bar.d/bar.py": map[string]interface{}{"sha265": "def"},
416 },
417 },
418 },
419 "bar": &Metablock{
420 Signed: Link{
421 Name: "bar",
422 Products: map[string]interface{}{
423 "foo.d/foo.py": map[string]interface{}{"sha265": "abc"},
424 "bar.d/bar.py": map[string]interface{}{"sha265": "abc"},
425 },
426 },
427 },
428 },
429 expectErr: "materials [bar.d/bar.py] disallowed by rule",
430 },
431 {
432 name: "Item must be one of step or inspection",
433 item: []interface{}{nil},
434 metadata: map[string]Metadata{},
435 expectErr: "item of invalid type",
436 },
437 {
438 name: "Can't find link metadata for step",
439 item: []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo"}}},
440 metadata: map[string]Metadata{},
441 expectErr: "could not find metadata",
442 },
443 {
444 name: "Can't find link metadata for inspection",
445 item: []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo"}}},
446 metadata: map[string]Metadata{},
447 expectErr: "could not find metadata",
448 },
449 {
450 name: "Wrong step expected material",
451 item: []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"INVALID", "rule"}}}}},
452 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo"}}},
453 expectErr: "rule format",
454 },
455 {
456 name: "Wrong step expected product",
457 item: []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"INVALID", "rule"}}}}},
458 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo"}}},
459 expectErr: "rule format",
460 },
461 {
462 name: "Wrong inspection expected material",
463 item: []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"INVALID", "rule"}}}}},
464 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo"}}},
465 expectErr: "rule format",
466 },
467 {
468 name: "Wrong inspection expected product",
469 item: []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"INVALID", "rule"}}}}},
470 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo"}}},
471 expectErr: "rule format",
472 },
473 {
474 name: "Disallowed material in step",
475 item: []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"DISALLOW", "*"}}}}},
476 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
477 expectErr: "materials [foo.py] disallowed by rule",
478 },
479 {
480 name: "Disallowed product in step",
481 item: []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"DISALLOW", "*"}}}}},
482 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
483 expectErr: "products [foo.py] disallowed by rule",
484 },
485 {
486 name: "Disallowed material in inspection",
487 item: []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"DISALLOW", "*"}}}}},
488 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
489 expectErr: "materials [foo.py] disallowed by rule",
490 },
491 {
492 name: "Disallowed product in inspection",
493 item: []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"DISALLOW", "*"}}}}},
494 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
495 expectErr: "products [foo.py] disallowed by rule",
496 },
497 {
498 name: "Required but missing material in step",
499 item: []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"REQUIRE", "foo"}}}}},
500 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
501 expectErr: "materials in REQUIRE 'foo'",
502 },
503 {
504 name: "Required but missing product in step",
505 item: []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"REQUIRE", "foo"}}}}},
506 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
507 expectErr: "products in REQUIRE 'foo'",
508 },
509 {
510 name: "Required but missing material in inspection",
511 item: []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"REQUIRE", "foo"}}}}},
512 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
513 expectErr: "materials in REQUIRE 'foo'",
514 },
515 {
516 name: "Required but missing product in inspection",
517 item: []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"REQUIRE", "foo"}}}}},
518 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
519 expectErr: "products in REQUIRE 'foo'",
520 },
521 {
522 name: "Disallowed subdirectory material in step",
523 item: []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"DISALLOW", "*"}}}}},
524 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"dir/foo.py": map[string]interface{}{"sha265": "abc"}}}}},
525 expectErr: "materials [dir/foo.py] disallowed by rule",
526 },
527 {
528 name: "Disallowed subdirectory product in step",
529 item: []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"DISALLOW", "*"}}}}},
530 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"dir/foo.py": map[string]interface{}{"sha265": "abc"}}}}},
531 expectErr: "products [dir/foo.py] disallowed by rule",
532 },
533 {
534 name: "Disallowed subdirectory material in inspection",
535 item: []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"DISALLOW", "*"}}}}},
536 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"dir/foo.py": map[string]interface{}{"sha265": "abc"}}}}},
537 expectErr: "materials [dir/foo.py] disallowed by rule",
538 },
539 {
540 name: "Disallowed subdirectory product in inspection",
541 item: []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"DISALLOW", "*"}}}}},
542 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"dir/foo.py": map[string]interface{}{"sha265": "abc"}}}}},
543 expectErr: "products [dir/foo.py] disallowed by rule",
544 },
545 {
546 name: "Consuming filename material in step",
547 item: []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"ALLOW", "foo.py"}, {"DISALLOW", "*"}}}}},
548 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"./bar/..//foo.py": map[string]interface{}{"sha265": "abc"}}}}},
549 expectErr: "",
550 },
551 {
552 name: "Consuming filename product in step",
553 item: []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"ALLOW", "foo.py"}, {"DISALLOW", "*"}}}}},
554 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"./bar/..//foo.py": map[string]interface{}{"sha265": "abc"}}}}},
555 expectErr: "",
556 },
557 {
558 name: "Consuming filename material in inspection",
559 item: []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"ALLOW", "foo.py"}, {"DISALLOW", "*"}}}}},
560 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"./bar/..//foo.py": map[string]interface{}{"sha265": "abc"}}}}},
561 expectErr: "",
562 },
563 {
564 name: "Consuming filename product in inspection",
565 item: []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"ALLOW", "foo.py"}, {"DISALLOW", "*"}}}}},
566 metadata: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"./bar/..//foo.py": map[string]interface{}{"sha265": "abc"}}}}},
567 expectErr: "",
568 },
569 }
570
571 for _, tt := range testCases {
572 t.Run(tt.name, func(t *testing.T) {
573 err := VerifyArtifacts(tt.item, tt.metadata)
574 if (err == nil && tt.expectErr != "") ||
575 (err != nil && tt.expectErr == "") ||
576 (err != nil && !strings.Contains(err.Error(), tt.expectErr)) {
577 t.Errorf("VerifyArtifacts returned '%s', expected '%s' error",
578 err, tt.expectErr)
579 }
580 })
581 }
582 }
583
584 func TestVerifyMatchRule(t *testing.T) {
585 var testCases = []struct {
586 name string
587 rule map[string]string
588 srcArtifact map[string]interface{}
589 item map[string]Metadata
590 expectSet Set
591 }{
592 {
593 name: "Can't find destination link (invalid rule)",
594 rule: map[string]string{},
595 srcArtifact: map[string]interface{}{},
596 item: map[string]Metadata{},
597 expectSet: NewSet(),
598 },
599 {
600 name: "Can't find destination link (empty metadata map)",
601 rule: map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials"},
602 srcArtifact: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}},
603 item: map[string]Metadata{},
604 expectSet: NewSet(),
605 },
606 {
607 name: "Match material foo.py",
608 rule: map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials"},
609 srcArtifact: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}},
610 item: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
611 expectSet: NewSet("foo.py"),
612 },
613 {
614 name: "Match material foo.py with foo.d/foo.py",
615 rule: map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials", "dstPrefix": "foo.d"},
616 srcArtifact: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}},
617 item: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.d/foo.py": map[string]interface{}{"sha265": "abc"}}}}},
618 expectSet: NewSet("foo.py"),
619 },
620 {
621 name: "Match material foo.d/foo.py with foo.py",
622 rule: map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials", "srcPrefix": "foo.d"},
623 srcArtifact: map[string]interface{}{"foo.d/foo.py": map[string]interface{}{"sha265": "abc"}},
624 item: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
625 expectSet: NewSet("foo.d/foo.py"),
626 },
627 {
628 name: "Don't match material (different name)",
629 rule: map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials"},
630 srcArtifact: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "abc"}},
631 item: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
632 expectSet: NewSet(),
633 },
634 {
635 name: "Don't match material (different hash)",
636 rule: map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials"},
637 srcArtifact: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "dead"}},
638 item: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
639 expectSet: NewSet(),
640 },
641 {
642 name: "Match material in sub-directories dir/foo.py",
643 rule: map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials"},
644 srcArtifact: map[string]interface{}{"bar/foo.py": map[string]interface{}{"sha265": "abc"}},
645 item: map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"bar/foo.py": map[string]interface{}{"sha265": "abc"}}}}},
646 expectSet: NewSet("bar/foo.py"),
647 },
648 }
649
650 for _, tt := range testCases {
651 t.Run(tt.name, func(t *testing.T) {
652 queue := NewSet(InterfaceKeyStrings(tt.srcArtifact)...)
653 result := verifyMatchRule(tt.rule, tt.srcArtifact, queue, tt.item)
654 if !reflect.DeepEqual(result, tt.expectSet) {
655 t.Errorf("verifyMatchRule returned '%s', expected '%s'", result, tt.expectSet)
656 }
657 })
658 }
659 }
660
661 func TestReduceStepsMetadata(t *testing.T) {
662 mb, err := LoadMetadata("demo.layout")
663 if err != nil {
664 t.Errorf("unable to parse template file: %s", err)
665 }
666 layout := mb.GetPayload().(Layout)
667 layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{Name: "foo"}}}
668
669
670 stepsMetadata := map[string]map[string]Metadata{
671 "foo": {
672 "a": &Metablock{Signed: Link{
673 Type: "link",
674 Name: "foo",
675 Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}},
676 Products: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "cde"}},
677 }},
678 "b": &Metablock{Signed: Link{
679 Type: "link",
680 Name: "foo",
681 Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}},
682 Products: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "cde"}},
683 }},
684 },
685 }
686
687 result, err := ReduceStepsMetadata(layout, stepsMetadata)
688 if !reflect.DeepEqual(result["foo"], stepsMetadata["foo"]["a"]) || err != nil {
689 t.Errorf("ReduceStepsMetadata returned (%s, %s), expected (%s, nil)"+
690 " and a 'different artifacts' error", result, err, stepsMetadata["foo"]["a"])
691 }
692
693
694
695
696
697
698
699 stepsMetadataList := []map[string]map[string]Metadata{
700 {"foo": {
701 "a": &Metablock{Signed: Link{Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}},
702 "b": &Metablock{Signed: Link{Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "def"}}}},
703 }},
704 {"foo": {
705 "a": &Metablock{Signed: Link{Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}},
706 "b": &Metablock{Signed: Link{Materials: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "abc"}}}},
707 }},
708 {"foo": {
709 "a": &Metablock{Signed: Link{Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}},
710 "b": &Metablock{Signed: Link{Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "def"}}}},
711 }},
712 {"foo": {
713 "a": &Metablock{Signed: Link{Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}},
714 "b": &Metablock{Signed: Link{Products: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "abc"}}}},
715 }},
716 }
717
718 for i := 0; i < len(stepsMetadataList); i++ {
719 result, err := ReduceStepsMetadata(layout, stepsMetadataList[i])
720 if err == nil || !strings.Contains(err.Error(), "different artifacts") {
721 t.Errorf("ReduceStepsMetadata returned (%s, %s), expected an empty map"+
722 " and a 'different artifacts' error", result, err)
723 }
724 }
725
726
727
728 defer func() {
729 if r := recover(); r == nil {
730 t.Errorf("ReduceStepsMetadata should have panicked due to missing link" +
731 " metadata")
732 }
733 }()
734 if _, err := ReduceStepsMetadata(layout, nil); err != nil {
735 t.Errorf("error while calling ReduceStepsMetadata: %s", err)
736 }
737
738 }
739
740 func TestVerifyStepCommandAlignment(t *testing.T) {
741 mb, err := LoadMetadata("demo.layout")
742 if err != nil {
743 t.Errorf("unable to load template file: %s", err)
744 }
745 layout := mb.GetPayload().(Layout)
746 layout.Steps = []Step{
747 {
748 SupplyChainItem: SupplyChainItem{Name: "foo"},
749 ExpectedCommand: []string{"rm", "-rf", "."},
750 },
751 }
752
753 stepsMetadata := map[string]map[string]Metadata{
754 "foo": {"a": &Metablock{Signed: Link{Command: []string{"rm", "-rf", "/"}}}},
755 }
756
757
758 fmt.Printf("[begin test warning output]\n")
759 VerifyStepCommandAlignment(layout, stepsMetadata)
760 fmt.Printf("[end test warning output]\n")
761
762
763
764 defer func() {
765 if r := recover(); r == nil {
766 t.Errorf("ReduceStepsMetadata should have panicked due to missing link" +
767 " metadata")
768 }
769 }()
770 VerifyStepCommandAlignment(layout, nil)
771
772 }
773
774 func TestVerifyLinkSignatureThesholds(t *testing.T) {
775 keyID1 := "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401"
776 keyID2 := "d3ffd1086938b3698618adf088bf14b13db4c8ae19e4e78d73da49ee88492710"
777 keyID3 := "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabca"
778
779 mb, err := LoadMetadata("demo.layout")
780 if err != nil {
781 t.Errorf("unable to load template file: %s", err)
782 }
783 layout := mb.GetPayload().(Layout)
784
785 layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{
786 Name: "foo"},
787 Threshold: 2,
788 PubKeys: []string{keyID1, keyID2, keyID3}}}
789
790 mbLink1, err := LoadMetadata("foo.b7d643de.link")
791 if err != nil {
792 t.Errorf("unable to load link file: %s", err)
793 }
794 mbLink2, err := LoadMetadata("foo.d3ffd108.link")
795 if err != nil {
796 t.Errorf("unable to load link file: %s", err)
797 }
798 mbLinkBroken, err := LoadMetadata("foo.d3ffd108.link")
799 if err != nil {
800 t.Errorf("unable to load link file: %s", err)
801 }
802 mbLinkBroken.Sigs()[0].Sig = "breaksignature"
803
804
805
806
807
808
809
810 stepsMetadata := []map[string]map[string]Metadata{
811 {"bar": nil},
812 {"foo": nil},
813 {"foo": {keyID1: mbLink1}},
814 {"foo": {keyID1: mbLink1, keyID2: mbLink1}},
815 {"foo": {keyID1: mbLink1, keyID2: mbLinkBroken}},
816 }
817 for i := 0; i < len(stepsMetadata); i++ {
818 result, err := VerifyLinkSignatureThesholds(layout, stepsMetadata[i], x509.NewCertPool(), x509.NewCertPool())
819 if err == nil {
820 t.Errorf("VerifyLinkSignatureThesholds returned (%s, %s), expected"+
821 " 'not enough distinct valid links' error.", result, err)
822 }
823 }
824
825
826
827
828 stepsMetadata = []map[string]map[string]Metadata{
829 {"foo": {keyID1: mbLink1, keyID2: mbLink2}},
830 {"foo": {keyID1: mbLink1, keyID2: mbLink2, keyID3: mbLinkBroken}},
831 }
832 for i := 0; i < len(stepsMetadata); i++ {
833 result, err := VerifyLinkSignatureThesholds(layout, stepsMetadata[i], x509.NewCertPool(), x509.NewCertPool())
834 validLinks, ok := result["foo"]
835 if !ok || len(validLinks) != 2 {
836 t.Errorf("VerifyLinkSignatureThesholds returned (%s, %s), expected"+
837 " a map of two valid foo links.", result, err)
838 }
839 }
840 }
841
842 func TestLoadLinksForLayout(t *testing.T) {
843 keyID1 := "d3ffd1086938b3698618adf088bf14b13db4c8ae19e4e78d73da49ee88492710"
844 keyID2 := "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401"
845 mb, err := LoadMetadata("demo.layout")
846 if err != nil {
847 t.Errorf("unable to load template file: %s", err)
848 }
849 layout := mb.GetPayload().(Layout)
850
851 layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{
852 Name: "foo"},
853 Threshold: 2,
854 PubKeys: []string{keyID1, keyID2}}}
855
856
857 result, err := LoadLinksForLayout(layout, ".")
858 links, ok := result["foo"]
859 if !ok || len(links) != 2 {
860 t.Errorf("VerifyLoadLinksForLayout returned (%s, %s), expected"+
861 " a map of two foo links.", result, err)
862 }
863
864
865 keyID3 := "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabca"
866 layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{
867 Name: "foo"},
868 Threshold: 3,
869 PubKeys: []string{keyID1, keyID2, keyID3}}}
870 result, err = LoadLinksForLayout(layout, ".")
871 if err == nil {
872 t.Errorf("VerifyLoadLinksForLayout returned (%s, %s), expected"+
873 " 'not enough links' error.", result, err)
874 }
875 }
876
877 func TestVerifyLayoutExpiration(t *testing.T) {
878 mb, err := LoadMetadata("demo.layout")
879 if err != nil {
880 t.Errorf("unable to load template file: %s", err)
881 }
882 layout := mb.GetPayload().(Layout)
883
884
885
886
887 expirationDates := []string{"bad date", "1970-01-01T00:00:00Z"}
888 expectedErrors := []string{"cannot parse", "has expired"}
889
890 for i := 0; i < len(expirationDates); i++ {
891 layout.Expires = expirationDates[i]
892 err := VerifyLayoutExpiration(layout)
893 if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) {
894 t.Errorf("VerifyLayoutExpiration returned '%s', expected '%s' error",
895 err, expectedErrors[i])
896 }
897 }
898
899
900 layout.Expires = "3000-01-01T00:00:00Z"
901 err = VerifyLayoutExpiration(layout)
902 if err != nil {
903 t.Errorf("VerifyLayoutExpiration returned '%s', expected nil", err)
904 }
905 }
906
907 func TestVerifyLayoutSignatures(t *testing.T) {
908 mbLayout, err := LoadMetadata("demo.layout")
909 if err != nil {
910 t.Errorf("unable to load template file: %s", err)
911 }
912 var layoutKey Key
913 if err := layoutKey.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
914 t.Errorf("unable to load public key file: %s", err)
915 }
916
917
918
919
920 layoutKeysList := []map[string]Key{{}, {layoutKey.KeyID: Key{}}}
921 expectedErrors := []string{"at least one key", "no signature found for key"}
922
923 for i := 0; i < len(layoutKeysList); i++ {
924 err := VerifyLayoutSignatures(mbLayout, layoutKeysList[i])
925 if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) {
926 t.Errorf("VerifyLayoutSignatures returned '%s', expected '%s' error",
927 err, expectedErrors[i])
928 }
929 }
930
931
932 err = VerifyLayoutSignatures(mbLayout, map[string]Key{layoutKey.KeyID: layoutKey})
933 if err != nil {
934 t.Errorf("VerifyLayoutSignatures returned '%s', expected nil",
935 err)
936 }
937 }
938
939 func TestSubstituteParamaters(t *testing.T) {
940 parameterDictionary := map[string]string{
941 "EDITOR": "vim",
942 "NEW_THING": "new_thing",
943 "SOURCE_STEP": "source_step",
944 "SOURCE_THING": "source_thing",
945 "UNTAR": "tar",
946 }
947
948 layout := Layout{
949 Type: "_layout",
950 Inspect: []Inspection{
951 {
952 SupplyChainItem: SupplyChainItem{
953 Name: "verify-the-thing",
954 ExpectedMaterials: [][]string{{"MATCH", "{SOURCE_THING}",
955 "WITH", "MATERIALS", "FROM", "{SOURCE_STEP}"}},
956 ExpectedProducts: [][]string{{"CREATE", "{NEW_THING}"}},
957 },
958 Run: []string{"{UNTAR}", "xzf", "foo.tar.gz"},
959 },
960 },
961 Steps: []Step{
962 {
963 SupplyChainItem: SupplyChainItem{
964 Name: "run-command",
965 ExpectedMaterials: [][]string{{"MATCH", "{SOURCE_THING}",
966 "WITH", "MATERIALS", "FROM", "{SOURCE_STEP}"}},
967 ExpectedProducts: [][]string{{"CREATE", "{NEW_THING}"}},
968 },
969 ExpectedCommand: []string{"{EDITOR}"},
970 },
971 },
972 }
973
974 newLayout, err := SubstituteParameters(layout, parameterDictionary)
975 if err != nil {
976 t.Errorf("parameter substitution error: got %s", err)
977 }
978
979 if newLayout.Steps[0].ExpectedCommand[0] != "vim" {
980 t.Errorf("parameter substitution failed - expected 'vim', got %s",
981 newLayout.Steps[0].ExpectedCommand[0])
982 }
983
984 if newLayout.Steps[0].ExpectedProducts[0][1] != "new_thing" {
985 t.Errorf("parameter substitution failed - expected 'new_thing',"+
986 " got %s", newLayout.Steps[0].ExpectedProducts[0][1])
987 }
988
989 if newLayout.Steps[0].ExpectedMaterials[0][1] != "source_thing" {
990 t.Errorf("parameter substitution failed - expected 'source_thing', "+
991 "got %s", newLayout.Steps[0].ExpectedMaterials[0][1])
992 }
993
994 if newLayout.Steps[0].ExpectedMaterials[0][5] != "source_step" {
995 t.Errorf("parameter substitution failed - expected 'source_step', "+
996 "got %s", newLayout.Steps[0].ExpectedMaterials[0][5])
997 }
998
999 if newLayout.Inspect[0].Run[0] != "tar" {
1000 t.Errorf("parameter substitution failed - expected 'tar', got %s",
1001 newLayout.Inspect[0].Run[0])
1002 }
1003
1004 if newLayout.Inspect[0].ExpectedProducts[0][1] != "new_thing" {
1005 t.Errorf("parameter substitution failed - expected 'new_thing',"+
1006 " got %s", newLayout.Inspect[0].ExpectedProducts[0][1])
1007 }
1008
1009 if newLayout.Inspect[0].ExpectedMaterials[0][1] != "source_thing" {
1010 t.Errorf("parameter substitution failed - expected 'source_thing', "+
1011 "got %s", newLayout.Inspect[0].ExpectedMaterials[0][1])
1012 }
1013
1014 if newLayout.Inspect[0].ExpectedMaterials[0][5] != "source_step" {
1015 t.Errorf("parameter substitution failed - expected 'source_step', "+
1016 "got %s", newLayout.Inspect[0].ExpectedMaterials[0][5])
1017 }
1018
1019 parameterDictionary = map[string]string{
1020 "invalid$": "some_replacement",
1021 }
1022
1023 _, err = SubstituteParameters(layout, parameterDictionary)
1024 if err.Error() != "invalid format for parameter" {
1025 t.Errorf("invalid parameter format not detected")
1026 }
1027 }
1028
1029 func TestInTotoVerifyWithDirectory(t *testing.T) {
1030 layoutPath := "demo.layout"
1031 pubKeyPath := "alice.pub"
1032 linkDir := "."
1033
1034 layoutMb, err := LoadMetadata(layoutPath)
1035 if err != nil {
1036 t.Error(err)
1037 }
1038
1039 var pubKey Key
1040 if err := pubKey.LoadKey(pubKeyPath, "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
1041 t.Error(err)
1042 }
1043
1044 var layouKeys = map[string]Key{
1045 pubKey.KeyID: pubKey,
1046 }
1047
1048
1049 if _, err := InTotoVerifyWithDirectory(layoutMb, layouKeys, linkDir, ".", "",
1050 make(map[string]string), [][]byte{}, testOSisWindows()); err != nil {
1051 t.Error(err)
1052 }
1053 }
1054
1055 func TestLoadLayoutCertificates(t *testing.T) {
1056 certTemplate := &x509.Certificate{
1057 Subject: pkix.Name{
1058 CommonName: "step1.example.com",
1059 Organization: []string{"example"},
1060 },
1061 }
1062
1063 _, intermediate, root, err := createTestCert(certTemplate, x509.Ed25519, time.Hour)
1064 assert.Nil(t, err, "unexpected error when creating test cert")
1065 rootKey := Key{}
1066 err = rootKey.LoadKeyReader(bytes.NewReader(generatePEMBlock(root.Raw, "CERTIFICATE")), "ed25519", []string{"sha512"})
1067 assert.Nil(t, err, "unexpected error loading Key for root")
1068 intermediateKey := Key{}
1069 intermediatePem := generatePEMBlock(intermediate.Raw, "CERTIFICATE")
1070 err = intermediateKey.LoadKeyReader(bytes.NewReader(intermediatePem), "ed25519", []string{"sha512"})
1071 assert.Nil(t, err, "unexpected error loading Key for intermediate")
1072 testLayout := Layout{
1073 RootCas: map[string]Key{
1074 rootKey.KeyID: rootKey,
1075 },
1076 IntermediateCas: map[string]Key{
1077 intermediateKey.KeyID: intermediateKey,
1078 },
1079 }
1080
1081 _, _, err = LoadLayoutCertificates(testLayout, [][]byte{intermediatePem})
1082 assert.Nil(t, err, "unexpected error loading layout's certificates")
1083
1084
1085 invalidRootKey := rootKey
1086 invalidRootKey.KeyVal.Certificate = "123123123"
1087 testLayout.RootCas[rootKey.KeyID] = invalidRootKey
1088 _, _, err = LoadLayoutCertificates(testLayout, [][]byte{intermediatePem})
1089 assert.NotNil(t, err, "expected error with invalid root key")
1090
1091
1092 testLayout.RootCas[rootKey.KeyID] = rootKey
1093 invalidIntermediateKey := intermediateKey
1094 invalidIntermediateKey.KeyVal.Certificate = "123123123"
1095 testLayout.IntermediateCas[intermediateKey.KeyID] = invalidIntermediateKey
1096 _, _, err = LoadLayoutCertificates(testLayout, [][]byte{})
1097 assert.NotNil(t, err, "expected error with invalid intermediate key")
1098
1099
1100 testLayout.IntermediateCas[intermediateKey.KeyID] = intermediateKey
1101 _, _, err = LoadLayoutCertificates(testLayout, [][]byte{[]byte("123123123")})
1102 assert.NotNil(t, err, "expected error with invalid extra intermediates")
1103 }
1104
View as plain text