github.com/boxboat/in-toto-golang@v0.0.3-0.20210303203820-2fa16ecbe6f6/in_toto/verifylib.go (about) 1 /* 2 Package in_toto implements types and routines to verify a software supply chain 3 according to the in-toto specification. 4 See https://github.com/in-toto/docs/blob/master/in-toto-spec.md 5 */ 6 package in_toto 7 8 import ( 9 "crypto/x509" 10 "fmt" 11 osPath "path" 12 "path/filepath" 13 "reflect" 14 "regexp" 15 "strings" 16 "time" 17 ) 18 19 /* 20 RunInspections iteratively executes the command in the Run field of all 21 inspections of the passed layout, creating unsigned link metadata that records 22 all files found in the current working directory as materials (before command 23 execution) and products (after command execution). A map with inspection names 24 as keys and Metablocks containing the generated link metadata as values is 25 returned. The format is: 26 27 { 28 <inspection name> : Metablock, 29 <inspection name> : Metablock, 30 ... 31 } 32 33 If executing the inspection command fails, or if the executed command has a 34 non-zero exit code, the first return value is an empty Metablock map and the 35 second return value is the error. 36 */ 37 func RunInspections(layout Layout) (map[string]Metablock, error) { 38 inspectionMetadata := make(map[string]Metablock) 39 40 for _, inspection := range layout.Inspect { 41 42 linkMb, err := InTotoRun(inspection.Name, []string{"."}, []string{"."}, 43 inspection.Run, Key{}, []string{"sha256"}, nil, nil) 44 if err != nil { 45 return nil, err 46 } 47 48 retVal := linkMb.Signed.(Link).ByProducts["return-value"] 49 if retVal != float64(0) { 50 return nil, fmt.Errorf("Inspection command '%s' of inspection '%s'"+ 51 " returned a non-zero value: %d", inspection.Run, inspection.Name, 52 retVal) 53 } 54 55 // Dump inspection link to cwd using the short link name format 56 linkName := fmt.Sprintf(LinkNameFormatShort, inspection.Name) 57 if err := linkMb.Dump(linkName); err != nil { 58 fmt.Printf("JSON serialization or writing failed: %s", err) 59 } 60 61 inspectionMetadata[inspection.Name] = linkMb 62 } 63 return inspectionMetadata, nil 64 } 65 66 // verifyMatchRule is a helper function to process artifact rules of 67 // type MATCH. See VerifyArtifacts for more details. 68 func verifyMatchRule(ruleData map[string]string, 69 srcArtifacts map[string]interface{}, srcArtifactQueue Set, 70 itemsMetadata map[string]Metablock) Set { 71 consumed := NewSet() 72 // Get destination link metadata 73 dstLinkMb, exists := itemsMetadata[ruleData["dstName"]] 74 if !exists { 75 // Destination link does not exist, rule can't consume any 76 // artifacts 77 return consumed 78 } 79 80 // Get artifacts from destination link metadata 81 var dstArtifacts map[string]interface{} 82 switch ruleData["dstType"] { 83 case "materials": 84 dstArtifacts = dstLinkMb.Signed.(Link).Materials 85 86 case "products": 87 dstArtifacts = dstLinkMb.Signed.(Link).Products 88 } 89 90 // Normalize optional source and destination prefixes, i.e. if 91 // there is a prefix, then add a trailing slash if not there yet 92 for _, prefix := range []string{"srcPrefix", "dstPrefix"} { 93 if ruleData[prefix] != "" && 94 !strings.HasSuffix(ruleData[prefix], "/") { 95 ruleData[prefix] += "/" 96 } 97 } 98 // Iterate over queue and mark consumed artifacts 99 for srcPath := range srcArtifactQueue { 100 // Remove optional source prefix from source artifact path 101 // Noop if prefix is empty, or artifact does not have it 102 srcBasePath := strings.TrimPrefix(srcPath, ruleData["srcPrefix"]) 103 104 // Ignore artifacts not matched by rule pattern 105 matched, err := filepath.Match(ruleData["pattern"], srcBasePath) 106 if err != nil || !matched { 107 continue 108 } 109 110 // Construct corresponding destination artifact path, i.e. 111 // an optional destination prefix plus the source base path 112 dstPath := osPath.Join(ruleData["dstPrefix"], srcBasePath) 113 114 // Try to find the corresponding destination artifact 115 dstArtifact, exists := dstArtifacts[dstPath] 116 // Ignore artifacts without corresponding destination artifact 117 if !exists { 118 continue 119 } 120 121 // Ignore artifact pairs with no matching hashes 122 if !reflect.DeepEqual(srcArtifacts[srcPath], dstArtifact) { 123 continue 124 } 125 126 // Only if a source and destination artifact pair was found and 127 // their hashes are equal, will we mark the source artifact as 128 // successfully consumed, i.e. it will be removed from the queue 129 consumed.Add(srcPath) 130 } 131 return consumed 132 } 133 134 /* 135 VerifyArtifacts iteratively applies the material and product rules of the 136 passed items (step or inspection) to enforce and authorize artifacts (materials 137 or products) reported by the corresponding link and to guarantee that 138 artifacts are linked together across links. In the beginning all artifacts are 139 placed in a queue according to their type. If an artifact gets consumed by a 140 rule it is removed from the queue. An artifact can only be consumed once in 141 the course of processing the set of rules in ExpectedMaterials or 142 ExpectedProducts. 143 144 Rules of type MATCH, ALLOW, CREATE, DELETE, MODIFY and DISALLOW are supported. 145 146 All rules except for DISALLOW consume queued artifacts on success, and 147 leave the queue unchanged on failure. Hence, it is left to a terminal 148 DISALLOW rule to fail overall verification, if artifacts are left in the queue 149 that should have been consumed by preceding rules. 150 */ 151 func VerifyArtifacts(items []interface{}, 152 itemsMetadata map[string]Metablock) error { 153 // Verify artifact rules for each item in the layout 154 for _, itemI := range items { 155 // The layout item (interface) must be a Link or an Inspection we are only 156 // interested in the name and the expected materials and products 157 var itemName string 158 var expectedMaterials [][]string 159 var expectedProducts [][]string 160 161 switch item := itemI.(type) { 162 case Step: 163 itemName = item.Name 164 expectedMaterials = item.ExpectedMaterials 165 expectedProducts = item.ExpectedProducts 166 167 case Inspection: 168 itemName = item.Name 169 expectedMaterials = item.ExpectedMaterials 170 expectedProducts = item.ExpectedProducts 171 172 default: // Something wrong 173 return fmt.Errorf("VerifyArtifacts received an item of invalid type,"+ 174 " elements of passed slice 'items' must be one of 'Step' or"+ 175 " 'Inspection', got: '%s'", reflect.TypeOf(item)) 176 } 177 178 // Use the item's name to extract the corresponding link 179 srcLinkMb, exists := itemsMetadata[itemName] 180 if !exists { 181 return fmt.Errorf("VerifyArtifacts could not find metadata"+ 182 " for item '%s', got: '%s'", itemName, itemsMetadata) 183 } 184 185 // Create shortcuts to materials and products (including hashes) reported 186 // by the item's link, required to verify "match" rules 187 materials := srcLinkMb.Signed.(Link).Materials 188 products := srcLinkMb.Signed.(Link).Products 189 190 // All other rules only require the material or product paths (without 191 // hashes). We extract them from the corresponding maps and store them as 192 // sets for convenience in further processing 193 materialPaths := NewSet(InterfaceKeyStrings(materials)...) 194 productPaths := NewSet(InterfaceKeyStrings(products)...) 195 196 // For `create`, `delete` and `modify` rules we prepare sets of artifacts 197 // (without hashes) that were created, deleted or modified in the current 198 // step or inspection 199 created := productPaths.Difference(materialPaths) 200 deleted := materialPaths.Difference(productPaths) 201 remained := materialPaths.Intersection(productPaths) 202 modified := NewSet() 203 for name := range remained { 204 if !reflect.DeepEqual(materials[name], products[name]) { 205 modified.Add(name) 206 } 207 } 208 209 // For each item we have to run rule verification, once per artifact type. 210 // Here we prepare the corresponding data for each round. 211 verificationDataList := []map[string]interface{}{ 212 { 213 "srcType": "materials", 214 "rules": expectedMaterials, 215 "artifacts": materials, 216 "artifactPaths": materialPaths, 217 }, 218 { 219 "srcType": "products", 220 "rules": expectedProducts, 221 "artifacts": products, 222 "artifactPaths": productPaths, 223 }, 224 } 225 // TODO: Add logging library (see in-toto/in-toto-golang#4) 226 // fmt.Printf("Verifying %s '%s' ", reflect.TypeOf(itemI), itemName) 227 228 // Process all material rules using the corresponding materials and all 229 // product rules using the corresponding products 230 for _, verificationData := range verificationDataList { 231 // TODO: Add logging library (see in-toto/in-toto-golang#4) 232 // fmt.Printf("%s...\n", verificationData["srcType"]) 233 234 rules := verificationData["rules"].([][]string) 235 artifacts := verificationData["artifacts"].(map[string]interface{}) 236 237 // Use artifacts (without hashes) as base queue. Each rule only operates 238 // on artifacts in that queue. If a rule consumes an artifact (i.e. can 239 // be applied successfully), the artifact is removed from the queue. By 240 // applying a DISALLOW rule eventually, verification may return an error, 241 // if the rule matches any artifacts in the queue that should have been 242 // consumed earlier. 243 queue := verificationData["artifactPaths"].(Set) 244 245 // TODO: Add logging library (see in-toto/in-toto-golang#4) 246 // fmt.Printf("Initial state\nMaterials: %s\nProducts: %s\nQueue: %s\n\n", 247 // materialPaths.Slice(), productPaths.Slice(), queue.Slice()) 248 249 // Verify rules sequentially 250 for _, rule := range rules { 251 // Parse rule and error out if it is malformed 252 // NOTE: the rule format should have been validated before 253 ruleData, err := UnpackRule(rule) 254 if err != nil { 255 return err 256 } 257 258 // Apply rule pattern to filter queued artifacts that are up for rule 259 // specific consumption 260 filtered := queue.Filter(ruleData["pattern"]) 261 262 var consumed Set 263 switch ruleData["type"] { 264 case "match": 265 // Note: here we need to perform more elaborate filtering 266 consumed = verifyMatchRule(ruleData, artifacts, queue, itemsMetadata) 267 268 case "allow": 269 // Consumes all filtered artifacts 270 consumed = filtered 271 272 case "create": 273 // Consumes filtered artifacts that were created 274 consumed = filtered.Intersection(created) 275 276 case "delete": 277 // Consumes filtered artifacts that were deleted 278 consumed = filtered.Intersection(deleted) 279 280 case "modify": 281 // Consumes filtered artifacts that were modified 282 consumed = filtered.Intersection(modified) 283 284 case "disallow": 285 // Does not consume but errors out if artifacts were filtered 286 if len(filtered) > 0 { 287 return fmt.Errorf("Artifact verification failed for %s '%s',"+ 288 " %s %s disallowed by rule %s.", 289 reflect.TypeOf(itemI).Name(), itemName, 290 verificationData["srcType"], filtered.Slice(), rule) 291 } 292 case "require": 293 // REQUIRE is somewhat of a weird animal that does not use 294 // patterns bur rather single filenames (for now). 295 if !queue.Has(ruleData["pattern"]) { 296 return fmt.Errorf("Artifact verification failed for %s in REQUIRE '%s',"+ 297 " because %s is not in %s.", verificationData["srcType"], 298 ruleData["pattern"], ruleData["pattern"], queue.Slice()) 299 } 300 } 301 // Update queue by removing consumed artifacts 302 queue = queue.Difference(consumed) 303 // TODO: Add logging library (see in-toto/in-toto-golang#4) 304 // fmt.Printf("Rule: %s\nQueue: %s\n\n", rule, queue.Slice()) 305 } 306 } 307 } 308 return nil 309 } 310 311 /* 312 ReduceStepsMetadata merges for each step of the passed Layout all the passed 313 per-functionary links into a single link, asserting that the reported Materials 314 and Products are equal across links for a given step. This function may be 315 used at a time during the overall verification, where link threshold's have 316 been verified and subsequent verification only needs one exemplary link per 317 step. The function returns a map with one Metablock (link) per step: 318 319 { 320 <step name> : Metablock, 321 <step name> : Metablock, 322 ... 323 } 324 325 If links corresponding to the same step report different Materials or different 326 Products, the first return value is an empty Metablock map and the second 327 return value is the error. 328 */ 329 func ReduceStepsMetadata(layout Layout, 330 stepsMetadata map[string]map[string]Metablock) (map[string]Metablock, 331 error) { 332 stepsMetadataReduced := make(map[string]Metablock) 333 334 for _, step := range layout.Steps { 335 linksPerStep, ok := stepsMetadata[step.Name] 336 // We should never get here, layout verification must fail earlier 337 if !ok || len(linksPerStep) < 1 { 338 panic("Could not reduce metadata for step '" + step.Name + 339 "', no link metadata found.") 340 } 341 342 // Get the first link (could be any link) for the current step, which will 343 // serve as reference link for below comparisons 344 var referenceKeyID string 345 var referenceLinkMb Metablock 346 for keyID, linkMb := range linksPerStep { 347 referenceLinkMb = linkMb 348 referenceKeyID = keyID 349 break 350 } 351 352 // Only one link, nothing to reduce, take the reference link 353 if len(linksPerStep) == 1 { 354 stepsMetadataReduced[step.Name] = referenceLinkMb 355 356 // Multiple links, reduce but first check 357 } else { 358 // Artifact maps must be equal for each type among all links 359 // TODO: What should we do if there are more links, than the 360 // threshold requires, but not all of them are equal? Right now we would 361 // also error. 362 for keyID, linkMb := range linksPerStep { 363 if !reflect.DeepEqual(linkMb.Signed.(Link).Materials, 364 referenceLinkMb.Signed.(Link).Materials) || 365 !reflect.DeepEqual(linkMb.Signed.(Link).Products, 366 referenceLinkMb.Signed.(Link).Products) { 367 return nil, fmt.Errorf("Link '%s' and '%s' have different"+ 368 " artifacts.", 369 fmt.Sprintf(LinkNameFormat, step.Name, referenceKeyID), 370 fmt.Sprintf(LinkNameFormat, step.Name, keyID)) 371 } 372 } 373 // We haven't errored out, so we can reduce (i.e take the reference link) 374 stepsMetadataReduced[step.Name] = referenceLinkMb 375 } 376 } 377 return stepsMetadataReduced, nil 378 } 379 380 /* 381 VerifyStepCommandAlignment (soft) verifies that for each step of the passed 382 layout the command executed, as per the passed link, matches the expected 383 command, as per the layout. Soft verification means that, in case a command 384 does not align, a warning is issued. 385 */ 386 func VerifyStepCommandAlignment(layout Layout, 387 stepsMetadata map[string]map[string]Metablock) { 388 for _, step := range layout.Steps { 389 linksPerStep, ok := stepsMetadata[step.Name] 390 // We should never get here, layout verification must fail earlier 391 if !ok || len(linksPerStep) < 1 { 392 panic("Could not verify command alignment for step '" + step.Name + 393 "', no link metadata found.") 394 } 395 396 for signerKeyID, linkMb := range linksPerStep { 397 expectedCommandS := strings.Join(step.ExpectedCommand, " ") 398 executedCommandS := strings.Join(linkMb.Signed.(Link).Command, " ") 399 400 if expectedCommandS != executedCommandS { 401 linkName := fmt.Sprintf(LinkNameFormat, step.Name, signerKeyID) 402 fmt.Printf("WARNING: Expected command for step '%s' (%s) and command"+ 403 " reported by '%s' (%s) differ.\n", 404 step.Name, expectedCommandS, linkName, executedCommandS) 405 } 406 } 407 } 408 } 409 410 /* 411 LoadLayoutCertificates loads the root and intermediate CAs from the layout if ne in the layout. 412 This will be used to check signatures that were used to sign links but not configured 413 in the PubKeys section of the step. No configured CAs means we don't want to allow this. 414 Returned CertPools will empty in this case. 415 */ 416 func LoadLayoutCertificates(layout Layout, intermediatePems [][]byte) (*x509.CertPool, *x509.CertPool, error) { 417 rootPool := x509.NewCertPool() 418 for _, certPem := range layout.RootCas { 419 ok := rootPool.AppendCertsFromPEM([]byte(certPem)) 420 if !ok { 421 return nil, nil, fmt.Errorf("Failed to load root certificates for layout.") 422 } 423 } 424 425 intermediatePool := x509.NewCertPool() 426 for _, intermediatePem := range layout.IntermediateCas { 427 ok := intermediatePool.AppendCertsFromPEM([]byte(intermediatePem)) 428 if !ok { 429 return nil, nil, fmt.Errorf("Failed to load intermediate certificates for layout.") 430 } 431 } 432 433 for _, intermediatePem := range intermediatePems { 434 ok := intermediatePool.AppendCertsFromPEM(intermediatePem) 435 if !ok { 436 return nil, nil, fmt.Errorf("Failed to load provided intermediate certificates.") 437 } 438 } 439 440 return rootPool, intermediatePool, nil 441 } 442 443 /* 444 VerifyLinkSignatureThesholds verifies that for each step of the passed layout, 445 there are at least Threshold links, validly signed by different authorized 446 functionaries. The returned map of link metadata per steps contains only 447 links with valid signatures from distinct functionaries and has the format: 448 449 { 450 <step name> : { 451 <key id>: Metablock, 452 <key id>: Metablock, 453 ... 454 }, 455 <step name> : { 456 <key id>: Metablock, 457 <key id>: Metablock, 458 ... 459 } 460 ... 461 } 462 463 If for any step of the layout there are not enough links available, the first 464 return value is an empty map of Metablock maps and the second return value is 465 the error. 466 */ 467 func VerifyLinkSignatureThesholds(layout Layout, 468 stepsMetadata map[string]map[string]Metablock, rootCertPool, intermediateCertPool *x509.CertPool) ( 469 map[string]map[string]Metablock, error) { 470 // This will stores links with valid signature from an authorized functionary 471 // for all steps 472 stepsMetadataVerified := make(map[string]map[string]Metablock) 473 474 // Try to find enough (>= threshold) links each with a valid signature from 475 // distinct authorized functionaries for each step 476 for _, step := range layout.Steps { 477 // This will store links with valid signature from an authorized 478 // functionary for the given step 479 linksPerStepVerified := make(map[string]Metablock) 480 481 // Check if there are any links at all for the given step 482 linksPerStep, ok := stepsMetadata[step.Name] 483 if !ok || len(linksPerStep) < 1 { 484 continue 485 } 486 487 // For each link corresponding to a step, check that the signer key was 488 // authorized, the layout contains a verification key and the signature 489 // verification passes. Only good links are stored, to verify thresholds 490 // below. 491 isAuthorizedSignature := false 492 for signerKeyID, linkMb := range linksPerStep { 493 for _, authorizedKeyID := range step.PubKeys { 494 if signerKeyID == authorizedKeyID { 495 if verifierKey, ok := layout.Keys[authorizedKeyID]; ok { 496 if err := linkMb.VerifySignature(verifierKey); err == nil { 497 linksPerStepVerified[signerKeyID] = linkMb 498 isAuthorizedSignature = true 499 break 500 } 501 } 502 } 503 } 504 505 // If the signer's key wasn't in our step's pubkeys array, check the cert pool to 506 // see if the key is known to us. 507 if !isAuthorizedSignature { 508 sig, err := linkMb.GetSignatureForKeyID(signerKeyID) 509 if err != nil { 510 continue 511 } 512 513 cert, err := sig.GetCertificate() 514 if err != nil { 515 continue 516 } 517 518 // test certificate against the step's constraints to make sure it's a valid functionary 519 if !step.CheckCertConstraints(cert) { 520 continue 521 } 522 523 // test against the root pool with the key's certificate if it has any 524 if err := VerifyCertificateTrust(cert, rootCertPool, intermediateCertPool); err != nil { 525 continue 526 } 527 528 if err := linkMb.VerifySignature(cert); err == nil { 529 linksPerStepVerified[signerKeyID] = linkMb 530 } 531 } 532 } 533 534 // Store all good links for a step 535 stepsMetadataVerified[step.Name] = linksPerStepVerified 536 } 537 538 // Verify threshold for each step 539 for _, step := range layout.Steps { 540 linksPerStepVerified, _ := stepsMetadataVerified[step.Name] 541 542 if len(linksPerStepVerified) < step.Threshold { 543 linksPerStep, _ := stepsMetadata[step.Name] 544 return nil, fmt.Errorf("Step '%s' requires '%d' link metadata file(s)."+ 545 " '%d' out of '%d' available link(s) have a valid signature from an"+ 546 " authorized signer.", step.Name, step.Threshold, 547 len(linksPerStepVerified), len(linksPerStep)) 548 } 549 } 550 return stepsMetadataVerified, nil 551 } 552 553 /* 554 LoadLinksForLayout loads for every Step of the passed Layout a Metablock 555 containing the corresponding Link. A base path to a directory that contains 556 the links may be passed using linkDir. Link file names are constructed, 557 using LinkNameFormat together with the corresponding step name and authorized 558 functionary key ids. A map of link metadata is returned and has the following 559 format: 560 561 { 562 <step name> : { 563 <key id>: Metablock, 564 <key id>: Metablock, 565 ... 566 }, 567 <step name> : { 568 <key id>: Metablock, 569 <key id>: Metablock, 570 ... 571 } 572 ... 573 } 574 575 If a link cannot be loaded at a constructed link name or is invalid, it is 576 ignored. Only a preliminary threshold check is performed, that is, if there 577 aren't at least Threshold links for any given step, the first return value 578 is an empty map of Metablock maps and the second return value is the error. 579 */ 580 func LoadLinksForLayout(layout Layout, linkDir string) (map[string]map[string]Metablock, error) { 581 stepsMetadata := make(map[string]map[string]Metablock) 582 583 for _, step := range layout.Steps { 584 linksPerStep := make(map[string]Metablock) 585 // Since we can verify against certificates belonging to a CA, we need to 586 // load any possible links 587 linkFiles, err := filepath.Glob(osPath.Join(linkDir, fmt.Sprintf(LinkGlobFormat, step.Name))) 588 if err != nil { 589 return nil, err 590 } 591 592 for _, linkPath := range linkFiles { 593 var linkMb Metablock 594 if err := linkMb.Load(linkPath); err != nil { 595 continue 596 } 597 598 // To get the full key from the metadata's signatures, we have to check 599 // for one with the same short id... 600 signerShortKeyID := strings.TrimSuffix(strings.TrimPrefix(filepath.Base(linkPath), step.Name+"."), ".link") 601 for _, sig := range linkMb.Signatures { 602 if strings.HasPrefix(sig.KeyID, signerShortKeyID) { 603 linksPerStep[sig.KeyID] = linkMb 604 break 605 } 606 } 607 } 608 609 if len(linksPerStep) < step.Threshold { 610 return nil, fmt.Errorf("Step '%s' requires '%d' link metadata file(s),"+ 611 " found '%d'.", step.Name, step.Threshold, len(linksPerStep)) 612 } 613 614 stepsMetadata[step.Name] = linksPerStep 615 } 616 617 return stepsMetadata, nil 618 } 619 620 /* 621 VerifyLayoutExpiration verifies that the passed Layout has not expired. It 622 returns an error if the (zulu) date in the Expires field is in the past. 623 */ 624 func VerifyLayoutExpiration(layout Layout) error { 625 expires, err := time.Parse(ISO8601DateSchema, layout.Expires) 626 if err != nil { 627 return err 628 } 629 // Uses timezone of expires, i.e. UTC 630 if time.Until(expires) < 0 { 631 return fmt.Errorf("layout has expired on '%s'", expires) 632 } 633 return nil 634 } 635 636 /* 637 VerifyLayoutSignatures verifies for each key in the passed key map the 638 corresponding signature of the Layout in the passed Metablock's Signed field. 639 Signatures and keys are associated by key id. If the key map is empty, or the 640 Metablock's Signature field does not have a signature for one or more of the 641 passed keys, or a matching signature is invalid, an error is returned. 642 */ 643 func VerifyLayoutSignatures(layoutMb Metablock, 644 layoutKeys map[string]Key) error { 645 if len(layoutKeys) < 1 { 646 return fmt.Errorf("layout verification requires at least one key") 647 } 648 649 for _, key := range layoutKeys { 650 if err := layoutMb.VerifySignature(key); err != nil { 651 return err 652 } 653 } 654 return nil 655 } 656 657 /* 658 GetSummaryLink merges the materials of the first step (as mentioned in the 659 layout) and the products of the last step and returns a new link. This link 660 reports the materials and products and summarizes the overall software supply 661 chain. 662 NOTE: The assumption is that the steps mentioned in the layout are to be 663 performed sequentially. So, the first step mentioned in the layout denotes what 664 comes into the supply chain and the last step denotes what goes out. 665 */ 666 func GetSummaryLink(layout Layout, stepsMetadataReduced map[string]Metablock, 667 stepName string) (Metablock, error) { 668 var summaryLink Link 669 var result Metablock 670 if len(layout.Steps) > 0 { 671 firstStepLink := stepsMetadataReduced[layout.Steps[0].Name] 672 lastStepLink := stepsMetadataReduced[layout.Steps[len(layout.Steps)-1].Name] 673 674 summaryLink.Materials = firstStepLink.Signed.(Link).Materials 675 summaryLink.Name = stepName 676 summaryLink.Type = firstStepLink.Signed.(Link).Type 677 678 summaryLink.Products = lastStepLink.Signed.(Link).Products 679 summaryLink.ByProducts = lastStepLink.Signed.(Link).ByProducts 680 // Using the last command of the sublayout as the command 681 // of the summary link can be misleading. Is it necessary to 682 // include all the commands executed as part of sublayout? 683 summaryLink.Command = lastStepLink.Signed.(Link).Command 684 } 685 686 result.Signed = summaryLink 687 688 return result, nil 689 } 690 691 /* 692 VerifySublayouts checks if any step in the supply chain is a sublayout, and if 693 so, recursively resolves it and replaces it with a summary link summarizing the 694 steps carried out in the sublayout. 695 */ 696 func VerifySublayouts(layout Layout, 697 stepsMetadataVerified map[string]map[string]Metablock, 698 superLayoutLinkPath string, intermediatePems [][]byte) (map[string]map[string]Metablock, error) { 699 for stepName, linkData := range stepsMetadataVerified { 700 for keyID, metadata := range linkData { 701 if _, ok := metadata.Signed.(Layout); ok { 702 layoutKeys := make(map[string]Key) 703 layoutKeys[keyID] = layout.Keys[keyID] 704 705 sublayoutLinkDir := fmt.Sprintf(SublayoutLinkDirFormat, 706 stepName, keyID) 707 sublayoutLinkPath := filepath.Join(superLayoutLinkPath, 708 sublayoutLinkDir) 709 summaryLink, err := InTotoVerify(metadata, layoutKeys, 710 sublayoutLinkPath, stepName, make(map[string]string), intermediatePems) 711 if err != nil { 712 return nil, err 713 } 714 linkData[keyID] = summaryLink 715 } 716 717 } 718 } 719 return stepsMetadataVerified, nil 720 } 721 722 // TODO: find a better way than two helper functions for the replacer op 723 724 func substituteParamatersInSlice(replacer *strings.Replacer, slice []string) []string { 725 newSlice := make([]string, 0) 726 for _, item := range slice { 727 newSlice = append(newSlice, replacer.Replace(item)) 728 } 729 return newSlice 730 } 731 732 func substituteParametersInSliceOfSlices(replacer *strings.Replacer, 733 slice [][]string) [][]string { 734 newSlice := make([][]string, 0) 735 for _, item := range slice { 736 newSlice = append(newSlice, substituteParamatersInSlice(replacer, 737 item)) 738 } 739 return newSlice 740 } 741 742 /* 743 SubstituteParameters performs parameter substitution in steps and inspections 744 in the following fields: 745 - Expected Materials and Expected Products of both 746 - Run of inspections 747 - Expected Command of steps 748 The substitution marker is '{}' and the keyword within the braces is replaced 749 by a value found in the substitution map passed, parameterDictionary. The 750 layout with parameters substituted is returned to the calling function. 751 */ 752 func SubstituteParameters(layout Layout, 753 parameterDictionary map[string]string) (Layout, error) { 754 755 if len(parameterDictionary) == 0 { 756 return layout, nil 757 } 758 759 parameters := make([]string, 0) 760 761 for parameter, value := range parameterDictionary { 762 parameterFormatCheck, _ := regexp.MatchString("^[a-zA-Z0-9_-]+$", 763 parameter) 764 if !parameterFormatCheck { 765 return layout, fmt.Errorf("invalid format for parameter") 766 } 767 768 parameters = append(parameters, "{"+parameter+"}") 769 parameters = append(parameters, value) 770 } 771 772 replacer := strings.NewReplacer(parameters...) 773 774 for i := range layout.Steps { 775 layout.Steps[i].ExpectedMaterials = substituteParametersInSliceOfSlices( 776 replacer, layout.Steps[i].ExpectedMaterials) 777 layout.Steps[i].ExpectedProducts = substituteParametersInSliceOfSlices( 778 replacer, layout.Steps[i].ExpectedProducts) 779 layout.Steps[i].ExpectedCommand = substituteParamatersInSlice(replacer, 780 layout.Steps[i].ExpectedCommand) 781 } 782 783 for i := range layout.Inspect { 784 layout.Inspect[i].ExpectedMaterials = 785 substituteParametersInSliceOfSlices(replacer, 786 layout.Inspect[i].ExpectedMaterials) 787 layout.Inspect[i].ExpectedProducts = 788 substituteParametersInSliceOfSlices(replacer, 789 layout.Inspect[i].ExpectedProducts) 790 layout.Inspect[i].Run = substituteParamatersInSlice(replacer, 791 layout.Inspect[i].Run) 792 } 793 794 return layout, nil 795 } 796 797 /* 798 InTotoVerify can be used to verify an entire software supply chain according to 799 the in-toto specification. It requires the metadata of the root layout, a map 800 that contains public keys to verify the root layout signatures, a path to a 801 directory from where it can load link metadata files, which are treated as 802 signed evidence for the steps defined in the layout, a step name, and a 803 paramater dictionary used for parameter substitution. The step name only 804 matters for sublayouts, where it's important to associate the summary of that 805 step with a unique name. The verification routine is as follows: 806 807 1. Verify layout signature(s) using passed key(s) 808 2. Verify layout expiration date 809 3. Substitute parameters in layout 810 4. Load link metadata files for steps of layout 811 5. Verify signatures and signature thresholds for steps of layout 812 6. Verify sublayouts recursively 813 7. Verify command alignment for steps of layout (only warns) 814 8. Verify artifact rules for steps of layout 815 9. Execute inspection commands (generates link metadata for each inspection) 816 10. Verify artifact rules for inspections of layout 817 818 InTotoVerify returns a summary link wrapped in a Metablock object and an error 819 value. If any of the verification routines fail, verification is aborted and 820 error is returned. In such an instance, the first value remains an empty 821 Metablock object. 822 823 NOTE: Artifact rules of type "create", "modify" 824 and "delete" are currently not supported. 825 */ 826 func InTotoVerify(layoutMb Metablock, layoutKeys map[string]Key, 827 linkDir string, stepName string, parameterDictionary map[string]string, intermediatePems [][]byte) ( 828 Metablock, error) { 829 830 var summaryLink Metablock 831 var err error 832 833 // Verify root signatures 834 if err := VerifyLayoutSignatures(layoutMb, layoutKeys); err != nil { 835 return summaryLink, err 836 } 837 838 // Extract the layout from its Metablock container (for further processing) 839 layout := layoutMb.Signed.(Layout) 840 841 // Verify layout expiration 842 if err := VerifyLayoutExpiration(layout); err != nil { 843 return summaryLink, err 844 } 845 846 // Substitute parameters in layout 847 layout, err = SubstituteParameters(layout, parameterDictionary) 848 if err != nil { 849 return summaryLink, err 850 } 851 852 rootCertPool, intermediateCertPool, err := LoadLayoutCertificates(layout, intermediatePems) 853 if err != nil { 854 return summaryLink, err 855 } 856 857 // Load links for layout 858 stepsMetadata, err := LoadLinksForLayout(layout, linkDir) 859 if err != nil { 860 return summaryLink, err 861 } 862 863 // Verify link signatures 864 stepsMetadataVerified, err := VerifyLinkSignatureThesholds(layout, 865 stepsMetadata, rootCertPool, intermediateCertPool) 866 if err != nil { 867 return summaryLink, err 868 } 869 870 // Verify and resolve sublayouts 871 stepsSublayoutVerified, err := VerifySublayouts(layout, 872 stepsMetadataVerified, linkDir, intermediatePems) 873 if err != nil { 874 return summaryLink, err 875 } 876 877 // Verify command alignment (WARNING only) 878 VerifyStepCommandAlignment(layout, stepsSublayoutVerified) 879 880 // Given that signature thresholds have been checked above and the rest of 881 // the relevant link properties, i.e. materials and products, have to be 882 // exactly equal, we can reduce the map of steps metadata. However, we error 883 // if the relevant properties are not equal among links of a step. 884 stepsMetadataReduced, err := ReduceStepsMetadata(layout, 885 stepsSublayoutVerified) 886 if err != nil { 887 return summaryLink, err 888 } 889 890 // Verify artifact rules 891 if err = VerifyArtifacts(layout.stepsAsInterfaceSlice(), 892 stepsMetadataReduced); err != nil { 893 return summaryLink, err 894 } 895 896 inspectionMetadata, err := RunInspections(layout) 897 if err != nil { 898 return summaryLink, err 899 } 900 901 // Add steps metadata to inspection metadata, because inspection artifact 902 // rules may also refer to artifacts reported by step links 903 for k, v := range stepsMetadataReduced { 904 inspectionMetadata[k] = v 905 } 906 907 if err = VerifyArtifacts(layout.inspectAsInterfaceSlice(), 908 inspectionMetadata); err != nil { 909 return summaryLink, err 910 } 911 912 summaryLink, err = GetSummaryLink(layout, stepsMetadataReduced, stepName) 913 if err != nil { 914 return summaryLink, err 915 } 916 917 return summaryLink, nil 918 }