github.com/boxboat/in-toto-golang@v0.0.3-0.20210303203820-2fa16ecbe6f6/in_toto/verifylib_test.go (about) 1 package in_toto 2 3 import ( 4 "crypto/x509" 5 "fmt" 6 "os" 7 "path" 8 "path/filepath" 9 "reflect" 10 "sort" 11 "strings" 12 "testing" 13 ) 14 15 func TestInTotoVerifyPass(t *testing.T) { 16 layoutPath := "demo.layout" 17 pubKeyPath := "alice.pub" 18 linkDir := "." 19 20 var layoutMb Metablock 21 if err := layoutMb.Load(layoutPath); err != nil { 22 t.Error(err) 23 } 24 25 var pubKey Key 26 if err := pubKey.LoadKey(pubKeyPath, "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil { 27 t.Error(err) 28 } 29 30 var layouKeys = map[string]Key{ 31 pubKey.KeyID: pubKey, 32 } 33 34 // No error should occur 35 if _, err := InTotoVerify(layoutMb, layouKeys, linkDir, "", 36 make(map[string]string), [][]byte{}); err != nil { 37 t.Error(err) 38 } 39 } 40 41 func TestGetSummaryLink(t *testing.T) { 42 var demoLayout Metablock 43 if err := demoLayout.Load("demo.layout"); err != nil { 44 t.Error(err) 45 } 46 var codeLink Metablock 47 if err := codeLink.Load("write-code.776a00e2.link"); err != nil { 48 t.Error(err) 49 } 50 var packageLink Metablock 51 if err := packageLink.Load("package.2f89b927.link"); err != nil { 52 t.Error(err) 53 } 54 demoLink := make(map[string]Metablock) 55 demoLink["write-code"] = codeLink 56 demoLink["package"] = packageLink 57 58 var summaryLink Metablock 59 var err error 60 if summaryLink, err = GetSummaryLink(demoLayout.Signed.(Layout), 61 demoLink, ""); err != nil { 62 t.Error(err) 63 } 64 if summaryLink.Signed.(Link).Type != codeLink.Signed.(Link).Type { 65 t.Errorf("Summary Link isn't of type Link") 66 } 67 if summaryLink.Signed.(Link).Name != "" { 68 t.Errorf("Summary Link name doesn't match. Expected '%s', returned "+ 69 "'%s", codeLink.Signed.(Link).Name, summaryLink.Signed.(Link).Name) 70 } 71 if !reflect.DeepEqual(summaryLink.Signed.(Link).Materials, 72 codeLink.Signed.(Link).Materials) { 73 t.Errorf("Summary Link materials don't match. Expected '%s', "+ 74 "returned '%s", codeLink.Signed.(Link).Materials, 75 summaryLink.Signed.(Link).Materials) 76 } 77 78 if !reflect.DeepEqual(summaryLink.Signed.(Link).Products, 79 packageLink.Signed.(Link).Products) { 80 t.Errorf("Summary Link products don't match. Expected '%s', "+ 81 "returned '%s", packageLink.Signed.(Link).Products, 82 summaryLink.Signed.(Link).Products) 83 } 84 if !reflect.DeepEqual(summaryLink.Signed.(Link).Command, 85 packageLink.Signed.(Link).Command) { 86 t.Errorf("Summary Link command doesn't match. Expected '%s', "+ 87 "returned '%s", packageLink.Signed.(Link).Command, 88 summaryLink.Signed.(Link).Command) 89 } 90 if !reflect.DeepEqual(summaryLink.Signed.(Link).ByProducts, 91 packageLink.Signed.(Link).ByProducts) { 92 t.Errorf("Summary Link by-products don't match. Expected '%s', "+ 93 "returned '%s", packageLink.Signed.(Link).ByProducts, 94 summaryLink.Signed.(Link).ByProducts) 95 } 96 } 97 98 func TestVerifySublayouts(t *testing.T) { 99 sublayoutName := "sub_layout" 100 var aliceKey Key 101 if err := aliceKey.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil { 102 t.Errorf("Unable to load Alice's public key") 103 } 104 sublayoutDirectory := fmt.Sprintf(SublayoutLinkDirFormat, sublayoutName, 105 aliceKey.KeyID) 106 defer func(sublayoutDirectory string) { 107 if err := os.RemoveAll(sublayoutDirectory); err != nil { 108 t.Errorf("Unable to remove directory %s: %s", sublayoutDirectory, err) 109 } 110 }(sublayoutDirectory) 111 112 if err := os.Mkdir(sublayoutDirectory, 0700); err != nil { 113 t.Errorf("Unable to create sublayout directory") 114 } 115 writeCodePath := path.Join(sublayoutDirectory, "write-code.776a00e2.link") 116 if err := os.Link("write-code.776a00e2.link", writeCodePath); err != nil { 117 t.Errorf("Unable to link write-code metadata.") 118 } 119 packagePath := path.Join(sublayoutDirectory, "package.2f89b927.link") 120 if err := os.Link("package.2f89b927.link", packagePath); err != nil { 121 t.Errorf("Unable to link package metadata") 122 } 123 124 var superLayoutMb Metablock 125 if err := superLayoutMb.Load("super.layout"); err != nil { 126 t.Errorf("Unable to load super layout") 127 } 128 129 stepsMetadata := make(map[string]map[string]Metablock) 130 var err error 131 if stepsMetadata, err = LoadLinksForLayout(superLayoutMb.Signed.(Layout), 132 "."); err != nil { 133 t.Errorf("Unable to load link metadata for super layout") 134 } 135 136 rootCertPool, intermediateCertPool, err := LoadLayoutCertificates(superLayoutMb.Signed.(Layout), [][]byte{}) 137 if err != nil { 138 t.Errorf("Unable to load layout certificates") 139 } 140 141 stepsMetadataVerified := make(map[string]map[string]Metablock) 142 if stepsMetadataVerified, err = VerifyLinkSignatureThesholds( 143 superLayoutMb.Signed.(Layout), stepsMetadata, rootCertPool, intermediateCertPool); err != nil { 144 t.Errorf("Unable to verify link threshold values: %v", err) 145 } 146 147 result, err := VerifySublayouts(superLayoutMb.Signed.(Layout), 148 stepsMetadataVerified, ".", [][]byte{}) 149 if err != nil { 150 t.Errorf("Unable to verify sublayouts") 151 } 152 153 for _, stepData := range result { 154 for _, metadata := range stepData { 155 if _, ok := metadata.Signed.(Link); !ok { 156 t.Errorf("Sublayout expansion error: found non link") 157 } 158 } 159 } 160 } 161 162 func TestRunInspections(t *testing.T) { 163 // Load layout template used as basis for all tests 164 var mb Metablock 165 if err := mb.Load("demo.layout"); err != nil { 166 t.Errorf("Unable to parse template file: %s", err) 167 } 168 layout := mb.Signed.(Layout) 169 170 // Test 1 171 // Successfully run two inspections foo and bar, testing that each generates 172 // a link file and they record the correct materials and products. 173 layout.Inspect = []Inspection{ 174 { 175 SupplyChainItem: SupplyChainItem{Name: "foo"}, 176 Run: []string{"sh", "-c", "true"}, 177 }, 178 { 179 SupplyChainItem: SupplyChainItem{Name: "bar"}, 180 Run: []string{"sh", "-c", "true"}, 181 }, 182 } 183 184 // Make a list of files in current dir (all must be recorded as artifacts) 185 availableFiles, _ := filepath.Glob("*") 186 result, err := RunInspections(layout) 187 188 // Error must be nil 189 if err != nil { 190 t.Errorf("RunInspections returned %s as error, expected nil.", 191 err) 192 } 193 194 // Assert contents of inspection link metadata for both inspections 195 for _, inspectionName := range []string{"foo", "bar"} { 196 // Available files must be sorted after each inspection because the link 197 // file is added below 198 sort.Strings(availableFiles) 199 // Compare material and products (only file names) to files that were 200 // in the directory before calling RunInspections 201 materialNames := InterfaceKeyStrings(result[inspectionName].Signed.(Link).Materials) 202 productNames := InterfaceKeyStrings(result[inspectionName].Signed.(Link).Products) 203 sort.Strings(materialNames) 204 sort.Strings(productNames) 205 if !reflect.DeepEqual(materialNames, availableFiles) || 206 !reflect.DeepEqual(productNames, availableFiles) { 207 t.Errorf("RunInspections recorded materials and products '%s' and %s'"+ 208 " for insepction '%s', expected '%s' and '%s'.", materialNames, 209 productNames, inspectionName, availableFiles, availableFiles) 210 } 211 linkName := inspectionName + ".link" 212 // Append link created by an inspection to available files because it 213 // is recorded by succeeding inspections 214 availableFiles = append(availableFiles, linkName) 215 216 // Remove generated inspection link 217 err = os.Remove(linkName) 218 if os.IsNotExist(err) { 219 t.Errorf("RunInspections didn't generate expected '%s'", linkName) 220 } 221 } 222 223 // Test 2 224 // Fail RunInspections due to inexistent command 225 layout.Inspect = []Inspection{ 226 { 227 SupplyChainItem: SupplyChainItem{Name: "foo"}, 228 Run: []string{"command-does-not-exist"}, 229 }, 230 } 231 232 result, err = RunInspections(layout) 233 if result != nil || err == nil { 234 t.Errorf("RunInspections returned '(%s, %s)', expected"+ 235 " '(nil, *exec.Error)'", result, err) 236 } 237 238 // Test 2 239 // Fail RunInspections due to non-zero exiting command 240 layout.Inspect = []Inspection{ 241 { 242 SupplyChainItem: SupplyChainItem{Name: "foo"}, 243 Run: []string{"sh", "-c", "false"}, 244 }, 245 } 246 result, err = RunInspections(layout) 247 if result != nil || err == nil { 248 t.Errorf("RunInspections returned '(%s, %s)', expected"+ 249 " '(nil, *exec.Error)'", result, err) 250 } 251 } 252 253 func TestVerifyArtifacts(t *testing.T) { 254 items := []interface{}{ 255 Step{ 256 SupplyChainItem: SupplyChainItem{ 257 Name: "foo", 258 ExpectedMaterials: [][]string{ 259 {"DELETE", "foo-delete"}, 260 {"MODIFY", "foo-modify"}, 261 {"MATCH", "foo-match", "WITH", "MATERIALS", "FROM", "foo"}, // not-modify 262 {"ALLOW", "foo-allow"}, 263 {"DISALLOW", "*"}, 264 }, 265 ExpectedProducts: [][]string{ 266 {"CREATE", "foo-create"}, 267 {"MODIFY", "foo-modify"}, 268 {"MATCH", "foo-match", "WITH", "MATERIALS", "FROM", "foo"}, // not-modify 269 {"REQUIRE", "foo-allow"}, 270 {"ALLOW", "foo-allow"}, 271 {"DISALLOW", "*"}, 272 }, 273 }, 274 }, 275 } 276 277 itemsMetadata := map[string]Metablock{ 278 "foo": { 279 Signed: Link{ 280 Name: "foo", 281 Materials: map[string]interface{}{ 282 "foo-delete": map[string]interface{}{"sha265": "abc"}, 283 "foo-modify": map[string]interface{}{"sha265": "abc"}, 284 "foo-match": map[string]interface{}{"sha265": "abc"}, 285 "foo-allow": map[string]interface{}{"sha265": "abc"}, 286 }, 287 Products: map[string]interface{}{ 288 "foo-create": map[string]interface{}{"sha265": "abc"}, 289 "foo-modify": map[string]interface{}{"sha265": "abcdef"}, 290 "foo-match": map[string]interface{}{"sha265": "abc"}, 291 "foo-allow": map[string]interface{}{"sha265": "abc"}, 292 }, 293 }, 294 }, 295 } 296 297 err := VerifyArtifacts(items, itemsMetadata) 298 if err != nil { 299 t.Errorf("VerifyArtifacts returned '%s', expected no error", err) 300 } 301 } 302 303 func TestVerifyArtifactErrors(t *testing.T) { 304 // Test error cases for combinations of Step and Inspection items and 305 // material and product rules: 306 // - Item must be one of step or inspection 307 // - Can't find link metadata for step 308 // - Can't find link metadata for inspection 309 // - Wrong step expected material 310 // - Wrong step expected product 311 // - Wrong inspection expected material 312 // - Wrong inspection expected product 313 // - Disallowed material in step 314 // - Disallowed product in step 315 // - Disallowed material in inspection 316 // - Disallowed product in inspection 317 // - Required but missing material in step 318 // - Required but missing product in step 319 // - Required but missing material in inspection 320 // - Required but missing product in inspection 321 items := [][]interface{}{ 322 {nil}, 323 {Step{SupplyChainItem: SupplyChainItem{Name: "foo"}}}, 324 {Inspection{SupplyChainItem: SupplyChainItem{Name: "foo"}}}, 325 {Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"INVALID", "rule"}}}}}, 326 {Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"INVALID", "rule"}}}}}, 327 {Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"INVALID", "rule"}}}}}, 328 {Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"INVALID", "rule"}}}}}, 329 {Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"DISALLOW", "*"}}}}}, 330 {Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"DISALLOW", "*"}}}}}, 331 {Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"DISALLOW", "*"}}}}}, 332 {Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"DISALLOW", "*"}}}}}, 333 {Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"REQUIRE", "foo"}}}}}, 334 {Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"REQUIRE", "foo"}}}}}, 335 {Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"REQUIRE", "foo"}}}}}, 336 {Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"REQUIRE", "foo"}}}}}, 337 } 338 itemsMetadata := []map[string]Metablock{ 339 {}, 340 {}, 341 {}, 342 {"foo": {Signed: Link{Name: "foo"}}}, 343 {"foo": {Signed: Link{Name: "foo"}}}, 344 {"foo": {Signed: Link{Name: "foo"}}}, 345 {"foo": {Signed: Link{Name: "foo"}}}, 346 {"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}}, 347 {"foo": {Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}}, 348 {"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}}, 349 {"foo": {Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}}, 350 {"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}}, 351 {"foo": {Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}}, 352 {"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}}, 353 {"foo": {Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}}, 354 } 355 errorPart := []string{ 356 "item of invalid type", 357 "could not find metadata", 358 "could not find metadata", 359 "rule format", 360 "rule format", 361 "rule format", 362 "rule format", 363 "materials [foo.py] disallowed by rule", 364 "products [foo.py] disallowed by rule", 365 "materials [foo.py] disallowed by rule", 366 "products [foo.py] disallowed by rule", 367 "materials in REQUIRE 'foo'", 368 "products in REQUIRE 'foo'", 369 "materials in REQUIRE 'foo'", 370 "products in REQUIRE 'foo'", 371 } 372 373 for i := 0; i < len(items); i++ { 374 err := VerifyArtifacts(items[i], itemsMetadata[i]) 375 if err == nil || !strings.Contains(err.Error(), errorPart[i]) { 376 t.Errorf("VerifyArtifacts returned '%s', expected '%s' error", 377 err, errorPart[i]) 378 } 379 } 380 } 381 382 func TestVerifyMatchRule(t *testing.T) { 383 // Test MatchRule queue processing: 384 // - Can't find destination link (invalid rule) -> queue unmodified (empty) 385 // - Can't find destination link (empty metadata map) -> queue unmodified 386 // - Match material foo.py -> remove from queue 387 // - Match material foo.py with foo.d/foo.py -> remove from queue 388 // - Match material foo.d/foo.py with foo.py -> remove from queue 389 // - Don't match material (different name) -> queue unmodified 390 // - Don't match material (different hash) -> queue unmodified 391 ruleData := []map[string]string{ 392 {}, 393 {"pattern": "*", "dstName": "foo", "dstType": "materials"}, 394 {"pattern": "*", "dstName": "foo", "dstType": "materials"}, 395 {"pattern": "*", "dstName": "foo", "dstType": "materials", "dstPrefix": "foo.d"}, 396 {"pattern": "*", "dstName": "foo", "dstType": "materials", "srcPrefix": "foo.d"}, 397 {"pattern": "*", "dstName": "foo", "dstType": "materials"}, 398 {"pattern": "*", "dstName": "foo", "dstType": "materials"}, 399 } 400 srcArtifacts := []map[string]interface{}{ 401 {}, 402 {"foo.py": map[string]interface{}{"sha265": "abc"}}, 403 {"foo.py": map[string]interface{}{"sha265": "abc"}}, 404 {"foo.py": map[string]interface{}{"sha265": "abc"}}, 405 {"foo.d/foo.py": map[string]interface{}{"sha265": "abc"}}, 406 {"foo.py": map[string]interface{}{"sha265": "dead"}}, 407 {"bar.py": map[string]interface{}{"sha265": "abc"}}, 408 } 409 // queue[i] = InterfaceKeyStrings(srcArtifacts[i]) 410 itemsMetadata := []map[string]Metablock{ 411 {}, 412 {}, 413 {"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}}, 414 {"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.d/foo.py": map[string]interface{}{"sha265": "abc"}}}}}, 415 {"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}}, 416 {"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}}, 417 {"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}}, 418 } 419 expected := []Set{ 420 NewSet(), 421 NewSet(), 422 NewSet("foo.py"), 423 NewSet("foo.py"), 424 NewSet("foo.d/foo.py"), 425 NewSet(), 426 NewSet(), 427 } 428 429 for i := 0; i < len(ruleData); i++ { 430 431 queue := NewSet(InterfaceKeyStrings(srcArtifacts[i])...) 432 result := verifyMatchRule(ruleData[i], srcArtifacts[i], queue, 433 itemsMetadata[i]) 434 if !reflect.DeepEqual(result, expected[i]) { 435 t.Errorf("verifyMatchRule returned '%s', expected '%s'", result, 436 expected[i]) 437 } 438 } 439 } 440 441 func TestReduceStepsMetadata(t *testing.T) { 442 var mb Metablock 443 if err := mb.Load("demo.layout"); err != nil { 444 t.Errorf("Unable to parse template file: %s", err) 445 } 446 layout := mb.Signed.(Layout) 447 layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{Name: "foo"}}} 448 449 // Test 1: Successful reduction of multiple links for one step (foo) 450 stepsMetadata := map[string]map[string]Metablock{ 451 "foo": { 452 "a": Metablock{Signed: Link{ 453 Type: "link", 454 Name: "foo", 455 Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}, 456 Products: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "cde"}}, 457 }}, 458 "b": Metablock{Signed: Link{ 459 Type: "link", 460 Name: "foo", 461 Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}, 462 Products: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "cde"}}, 463 }}, 464 }, 465 } 466 467 result, err := ReduceStepsMetadata(layout, stepsMetadata) 468 if !reflect.DeepEqual(result["foo"], stepsMetadata["foo"]["a"]) || err != nil { 469 t.Errorf("ReduceStepsMetadata returned (%s, %s), expected (%s, nil)"+ 470 " and a 'different artifacts' error", result, err, stepsMetadata["foo"]["a"]) 471 } 472 473 // Test 2: Test different error scenarios when creating one link out of 474 // multiple links for the same step: 475 // - Different materials (hash) 476 // - Different materials (name) 477 // - Different products (hash) 478 // - Different products (name) 479 stepsMetadataList := []map[string]map[string]Metablock{ 480 {"foo": { 481 "a": Metablock{Signed: Link{Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}, 482 "b": Metablock{Signed: Link{Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "def"}}}}, 483 }}, 484 {"foo": { 485 "a": Metablock{Signed: Link{Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}, 486 "b": Metablock{Signed: Link{Materials: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "abc"}}}}, 487 }}, 488 {"foo": { 489 "a": Metablock{Signed: Link{Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}, 490 "b": Metablock{Signed: Link{Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "def"}}}}, 491 }}, 492 {"foo": { 493 "a": Metablock{Signed: Link{Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}, 494 "b": Metablock{Signed: Link{Products: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "abc"}}}}, 495 }}, 496 } 497 498 for i := 0; i < len(stepsMetadataList); i++ { 499 result, err := ReduceStepsMetadata(layout, stepsMetadataList[i]) 500 if err == nil || !strings.Contains(err.Error(), "different artifacts") { 501 t.Errorf("ReduceStepsMetadata returned (%s, %s), expected an empty map"+ 502 " and a 'different artifacts' error", result, err) 503 } 504 } 505 506 // Panic due to missing link metadata for step (final product verification 507 // should gracefully error earlier) 508 defer func() { 509 if r := recover(); r == nil { 510 t.Errorf("ReduceStepsMetadata should have panicked due to missing link" + 511 " metadata") 512 } 513 }() 514 if _, err := ReduceStepsMetadata(layout, nil); err != nil { 515 t.Errorf("Error while calling ReduceStepsMetadata: %s", err) 516 } 517 //NOTE: This test won't get any further because of panic 518 } 519 520 func TestVerifyStepCommandAlignment(t *testing.T) { 521 var mb Metablock 522 if err := mb.Load("demo.layout"); err != nil { 523 t.Errorf("Unable to load template file: %s", err) 524 } 525 layout := mb.Signed.(Layout) 526 layout.Steps = []Step{ 527 { 528 SupplyChainItem: SupplyChainItem{Name: "foo"}, 529 ExpectedCommand: []string{"rm", "-rf", "."}, 530 }, 531 } 532 533 stepsMetadata := map[string]map[string]Metablock{ 534 "foo": {"a": Metablock{Signed: Link{Command: []string{"rm", "-rf", "/"}}}}, 535 } 536 // Test warning due to non-aligning commands 537 // FIXME: Assert warning? 538 fmt.Printf("[begin test warning output]\n") 539 VerifyStepCommandAlignment(layout, stepsMetadata) 540 fmt.Printf("[end test warning output]\n") 541 542 // Panic due to missing link metadata for step (final product verification 543 // should gracefully error earlier) 544 defer func() { 545 if r := recover(); r == nil { 546 t.Errorf("ReduceStepsMetadata should have panicked due to missing link" + 547 " metadata") 548 } 549 }() 550 VerifyStepCommandAlignment(layout, nil) 551 //NOTE: This test won't get any further because of panic 552 } 553 554 func TestVerifyLinkSignatureThesholds(t *testing.T) { 555 keyID1 := "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498" 556 keyID2 := "776a00e29f3559e0141b3b096f696abc6cfb0c657ab40f441132b345b08453f5" 557 keyID3 := "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabca" 558 559 var mb Metablock 560 if err := mb.Load("demo.layout"); err != nil { 561 t.Errorf("Unable to load template file: %s", err) 562 } 563 layout := mb.Signed.(Layout) 564 565 layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{ 566 Name: "foo"}, 567 Threshold: 2, 568 PubKeys: []string{keyID1, keyID2, keyID3}}} 569 570 var mbLink1 Metablock 571 if err := mbLink1.Load("foo.2f89b927.link"); err != nil { 572 t.Errorf("Unable to load link file: %s", err) 573 } 574 var mbLink2 Metablock 575 if err := mbLink2.Load("foo.776a00e2.link"); err != nil { 576 t.Errorf("Unable to load link file: %s", err) 577 } 578 var mbLinkBroken Metablock 579 if err := mbLinkBroken.Load("foo.776a00e2.link"); err != nil { 580 t.Errorf("Unable to load link file: %s", err) 581 } 582 mbLinkBroken.Signatures[0].Sig = "breaksignature" 583 584 // Test less then threshold distinct valid links errors: 585 // - Missing step name in step metadata map 586 // - Missing links for step 587 // - Less than threshold links for step 588 // - Less than threshold distinct links for step 589 // - Less than threshold validly signed links for step 590 stepsMetadata := []map[string]map[string]Metablock{ 591 {"bar": nil}, 592 {"foo": nil}, 593 {"foo": {keyID1: mbLink1}}, 594 {"foo": {keyID1: mbLink1, keyID2: mbLink1}}, 595 {"foo": {keyID1: mbLink1, keyID2: mbLinkBroken}}, 596 } 597 for i := 0; i < len(stepsMetadata); i++ { 598 result, err := VerifyLinkSignatureThesholds(layout, stepsMetadata[i], x509.NewCertPool(), x509.NewCertPool()) 599 if err == nil { 600 t.Errorf("VerifyLinkSignatureThesholds returned (%s, %s), expected"+ 601 " 'not enough distinct valid links' error.", result, err) 602 } 603 } 604 605 // Test successfully return threshold distinct valid links: 606 // - Threshold 2, two valid links 607 // - Threshold 2, two valid links, one invalid link ignored 608 stepsMetadata = []map[string]map[string]Metablock{ 609 {"foo": {keyID1: mbLink1, keyID2: mbLink2}}, 610 {"foo": {keyID1: mbLink1, keyID2: mbLink2, keyID3: mbLinkBroken}}, 611 } 612 for i := 0; i < len(stepsMetadata); i++ { 613 result, err := VerifyLinkSignatureThesholds(layout, stepsMetadata[i], x509.NewCertPool(), x509.NewCertPool()) 614 validLinks, ok := result["foo"] 615 if !ok || len(validLinks) != 2 { 616 t.Errorf("VerifyLinkSignatureThesholds returned (%s, %s), expected"+ 617 " a map of two valid foo links.", result, err) 618 } 619 } 620 } 621 622 func TestLoadLinksForLayout(t *testing.T) { 623 keyID1 := "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498" 624 keyID2 := "776a00e29f3559e0141b3b096f696abc6cfb0c657ab40f441132b345b08453f5" 625 var mb Metablock 626 if err := mb.Load("demo.layout"); err != nil { 627 t.Errorf("Unable to load template file: %s", err) 628 } 629 layout := mb.Signed.(Layout) 630 631 layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{ 632 Name: "foo"}, 633 Threshold: 2, 634 PubKeys: []string{keyID1, keyID2}}} 635 636 // Test successfully load two links for layout (one step foo, threshold 2) 637 result, err := LoadLinksForLayout(layout, ".") 638 links, ok := result["foo"] 639 if !ok || len(links) != 2 { 640 t.Errorf("VerifyLoadLinksForLayout returned (%s, %s), expected"+ 641 " a map of two foo links.", result, err) 642 } 643 644 // Test threshold error, can't find enough links for step 645 keyID3 := "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabca" 646 layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{ 647 Name: "foo"}, 648 Threshold: 3, 649 PubKeys: []string{keyID1, keyID2, keyID3}}} 650 result, err = LoadLinksForLayout(layout, ".") 651 if err == nil { 652 t.Errorf("VerifyLoadLinksForLayout returned (%s, %s), expected"+ 653 " 'not enough links' error.", result, err) 654 } 655 } 656 657 func TestVerifyLayoutExpiration(t *testing.T) { 658 var mb Metablock 659 if err := mb.Load("demo.layout"); err != nil { 660 t.Errorf("Unable to load template file: %s", err) 661 } 662 layout := mb.Signed.(Layout) 663 664 // Test layout expiration check failure: 665 // - invalid date 666 // - expired date 667 expirationDates := []string{"bad date", "1970-01-01T00:00:00Z"} 668 expectedErrors := []string{"cannot parse", "has expired"} 669 670 for i := 0; i < len(expirationDates); i++ { 671 layout.Expires = expirationDates[i] 672 err := VerifyLayoutExpiration(layout) 673 if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) { 674 t.Errorf("VerifyLayoutExpiration returned '%s', expected '%s' error", 675 err, expectedErrors[i]) 676 } 677 } 678 679 // Test not (yet) expired layout :) 680 layout.Expires = "3000-01-01T00:00:00Z" 681 err := VerifyLayoutExpiration(layout) 682 if err != nil { 683 t.Errorf("VerifyLayoutExpiration returned '%s', expected nil", err) 684 } 685 } 686 687 func TestVerifyLayoutSignatures(t *testing.T) { 688 var mbLayout Metablock 689 if err := mbLayout.Load("demo.layout"); err != nil { 690 t.Errorf("Unable to load template file: %s", err) 691 } 692 var layoutKey Key 693 if err := layoutKey.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil { 694 t.Errorf("Unable to load public key file: %s", err) 695 } 696 697 // Test layout signature verification errors: 698 // - Not verification keys (must be at least one) 699 // - No signature found for verification key 700 layoutKeysList := []map[string]Key{{}, {layoutKey.KeyID: Key{}}} 701 expectedErrors := []string{"at least one key", "No signature found for key"} 702 703 for i := 0; i < len(layoutKeysList); i++ { 704 err := VerifyLayoutSignatures(mbLayout, layoutKeysList[i]) 705 if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) { 706 t.Errorf("VerifyLayoutSignatures returned '%s', expected '%s' error", 707 err, expectedErrors[i]) 708 } 709 } 710 711 // Test successful layout signature verification 712 err := VerifyLayoutSignatures(mbLayout, map[string]Key{layoutKey.KeyID: layoutKey}) 713 if err != nil { 714 t.Errorf("VerifyLayoutSignatures returned '%s', expected nil", 715 err) 716 } 717 } 718 719 func TestSubstituteParamaters(t *testing.T) { 720 parameterDictionary := map[string]string{ 721 "EDITOR": "vim", 722 "NEW_THING": "new_thing", 723 "SOURCE_STEP": "source_step", 724 "SOURCE_THING": "source_thing", 725 "UNTAR": "tar", 726 } 727 728 layout := Layout{ 729 Type: "_layout", 730 Inspect: []Inspection{ 731 { 732 SupplyChainItem: SupplyChainItem{ 733 Name: "verify-the-thing", 734 ExpectedMaterials: [][]string{{"MATCH", "{SOURCE_THING}", 735 "WITH", "MATERIALS", "FROM", "{SOURCE_STEP}"}}, 736 ExpectedProducts: [][]string{{"CREATE", "{NEW_THING}"}}, 737 }, 738 Run: []string{"{UNTAR}", "xzf", "foo.tar.gz"}, 739 }, 740 }, 741 Steps: []Step{ 742 { 743 SupplyChainItem: SupplyChainItem{ 744 Name: "run-command", 745 ExpectedMaterials: [][]string{{"MATCH", "{SOURCE_THING}", 746 "WITH", "MATERIALS", "FROM", "{SOURCE_STEP}"}}, 747 ExpectedProducts: [][]string{{"CREATE", "{NEW_THING}"}}, 748 }, 749 ExpectedCommand: []string{"{EDITOR}"}, 750 }, 751 }, 752 } 753 754 newLayout, err := SubstituteParameters(layout, parameterDictionary) 755 if err != nil { 756 t.Errorf("parameter substitution error: got %s", err) 757 } 758 759 if newLayout.Steps[0].ExpectedCommand[0] != "vim" { 760 t.Errorf("parameter substitution failed - expected 'vim', got %s", 761 newLayout.Steps[0].ExpectedCommand[0]) 762 } 763 764 if newLayout.Steps[0].ExpectedProducts[0][1] != "new_thing" { 765 t.Errorf("parameter substitution failed - expected 'new_thing',"+ 766 " got %s", newLayout.Steps[0].ExpectedProducts[0][1]) 767 } 768 769 if newLayout.Steps[0].ExpectedMaterials[0][1] != "source_thing" { 770 t.Errorf("parameter substitution failed - expected 'source_thing', "+ 771 "got %s", newLayout.Steps[0].ExpectedMaterials[0][1]) 772 } 773 774 if newLayout.Steps[0].ExpectedMaterials[0][5] != "source_step" { 775 t.Errorf("parameter substitution failed - expected 'source_step', "+ 776 "got %s", newLayout.Steps[0].ExpectedMaterials[0][5]) 777 } 778 779 if newLayout.Inspect[0].Run[0] != "tar" { 780 t.Errorf("parameter substitution failed - expected 'tar', got %s", 781 newLayout.Inspect[0].Run[0]) 782 } 783 784 if newLayout.Inspect[0].ExpectedProducts[0][1] != "new_thing" { 785 t.Errorf("parameter substitution failed - expected 'new_thing',"+ 786 " got %s", newLayout.Inspect[0].ExpectedProducts[0][1]) 787 } 788 789 if newLayout.Inspect[0].ExpectedMaterials[0][1] != "source_thing" { 790 t.Errorf("parameter substitution failed - expected 'source_thing', "+ 791 "got %s", newLayout.Inspect[0].ExpectedMaterials[0][1]) 792 } 793 794 if newLayout.Inspect[0].ExpectedMaterials[0][5] != "source_step" { 795 t.Errorf("parameter substitution failed - expected 'source_step', "+ 796 "got %s", newLayout.Inspect[0].ExpectedMaterials[0][5]) 797 } 798 799 parameterDictionary = map[string]string{ 800 "invalid$": "some_replacement", 801 } 802 803 _, err = SubstituteParameters(layout, parameterDictionary) 804 if err.Error() != "invalid format for parameter" { 805 t.Errorf("invalid parameter format not detected") 806 } 807 }