mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 18:13:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			1092 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1092 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Package in_toto implements types and routines to verify a software supply chain
 | 
						|
according to the in-toto specification.
 | 
						|
See https://github.com/in-toto/docs/blob/master/in-toto-spec.md
 | 
						|
*/
 | 
						|
package in_toto
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/x509"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"os"
 | 
						|
	"path"
 | 
						|
	osPath "path"
 | 
						|
	"path/filepath"
 | 
						|
	"reflect"
 | 
						|
	"regexp"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
// ErrInspectionRunDirIsSymlink gets thrown if the runDir is a symlink
 | 
						|
var ErrInspectionRunDirIsSymlink = errors.New("runDir is a symlink. This is a security risk")
 | 
						|
 | 
						|
/*
 | 
						|
RunInspections iteratively executes the command in the Run field of all
 | 
						|
inspections of the passed layout, creating unsigned link metadata that records
 | 
						|
all files found in the current working directory as materials (before command
 | 
						|
execution) and products (after command execution).  A map with inspection names
 | 
						|
as keys and Metablocks containing the generated link metadata as values is
 | 
						|
returned.  The format is:
 | 
						|
 | 
						|
	{
 | 
						|
		<inspection name> : Metablock,
 | 
						|
		<inspection name> : Metablock,
 | 
						|
		...
 | 
						|
	}
 | 
						|
 | 
						|
If executing the inspection command fails, or if the executed command has a
 | 
						|
non-zero exit code, the first return value is an empty Metablock map and the
 | 
						|
second return value is the error.
 | 
						|
*/
 | 
						|
func RunInspections(layout Layout, runDir string, lineNormalization bool) (map[string]Metablock, error) {
 | 
						|
	inspectionMetadata := make(map[string]Metablock)
 | 
						|
 | 
						|
	for _, inspection := range layout.Inspect {
 | 
						|
 | 
						|
		paths := []string{"."}
 | 
						|
		if runDir != "" {
 | 
						|
			paths = []string{runDir}
 | 
						|
		}
 | 
						|
 | 
						|
		linkMb, err := InTotoRun(inspection.Name, runDir, paths, paths,
 | 
						|
			inspection.Run, Key{}, []string{"sha256"}, nil, nil, lineNormalization)
 | 
						|
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		retVal := linkMb.Signed.(Link).ByProducts["return-value"]
 | 
						|
		if retVal != float64(0) {
 | 
						|
			return nil, fmt.Errorf("inspection command '%s' of inspection '%s'"+
 | 
						|
				" returned a non-zero value: %d", inspection.Run, inspection.Name,
 | 
						|
				retVal)
 | 
						|
		}
 | 
						|
 | 
						|
		// Dump inspection link to cwd using the short link name format
 | 
						|
		linkName := fmt.Sprintf(LinkNameFormatShort, inspection.Name)
 | 
						|
		if err := linkMb.Dump(linkName); err != nil {
 | 
						|
			fmt.Printf("JSON serialization or writing failed: %s", err)
 | 
						|
		}
 | 
						|
 | 
						|
		inspectionMetadata[inspection.Name] = linkMb
 | 
						|
	}
 | 
						|
	return inspectionMetadata, nil
 | 
						|
}
 | 
						|
 | 
						|
// verifyMatchRule is a helper function to process artifact rules of
 | 
						|
// type MATCH. See VerifyArtifacts for more details.
 | 
						|
func verifyMatchRule(ruleData map[string]string,
 | 
						|
	srcArtifacts map[string]interface{}, srcArtifactQueue Set,
 | 
						|
	itemsMetadata map[string]Metablock) Set {
 | 
						|
	consumed := NewSet()
 | 
						|
	// Get destination link metadata
 | 
						|
	dstLinkMb, exists := itemsMetadata[ruleData["dstName"]]
 | 
						|
	if !exists {
 | 
						|
		// Destination link does not exist, rule can't consume any
 | 
						|
		// artifacts
 | 
						|
		return consumed
 | 
						|
	}
 | 
						|
 | 
						|
	// Get artifacts from destination link metadata
 | 
						|
	var dstArtifacts map[string]interface{}
 | 
						|
	switch ruleData["dstType"] {
 | 
						|
	case "materials":
 | 
						|
		dstArtifacts = dstLinkMb.Signed.(Link).Materials
 | 
						|
	case "products":
 | 
						|
		dstArtifacts = dstLinkMb.Signed.(Link).Products
 | 
						|
	}
 | 
						|
 | 
						|
	// cleanup paths in pattern and artifact maps
 | 
						|
	if ruleData["pattern"] != "" {
 | 
						|
		ruleData["pattern"] = path.Clean(ruleData["pattern"])
 | 
						|
	}
 | 
						|
	for k := range srcArtifacts {
 | 
						|
		if path.Clean(k) != k {
 | 
						|
			srcArtifacts[path.Clean(k)] = srcArtifacts[k]
 | 
						|
			delete(srcArtifacts, k)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for k := range dstArtifacts {
 | 
						|
		if path.Clean(k) != k {
 | 
						|
			dstArtifacts[path.Clean(k)] = dstArtifacts[k]
 | 
						|
			delete(dstArtifacts, k)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Normalize optional source and destination prefixes, i.e. if
 | 
						|
	// there is a prefix, then add a trailing slash if not there yet
 | 
						|
	for _, prefix := range []string{"srcPrefix", "dstPrefix"} {
 | 
						|
		if ruleData[prefix] != "" {
 | 
						|
			ruleData[prefix] = path.Clean(ruleData[prefix])
 | 
						|
			if !strings.HasSuffix(ruleData[prefix], "/") {
 | 
						|
				ruleData[prefix] += "/"
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	// Iterate over queue and mark consumed artifacts
 | 
						|
	for srcPath := range srcArtifactQueue {
 | 
						|
		// Remove optional source prefix from source artifact path
 | 
						|
		// Noop if prefix is empty, or artifact does not have it
 | 
						|
		srcBasePath := strings.TrimPrefix(srcPath, ruleData["srcPrefix"])
 | 
						|
 | 
						|
		// Ignore artifacts not matched by rule pattern
 | 
						|
		matched, err := match(ruleData["pattern"], srcBasePath)
 | 
						|
		if err != nil || !matched {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Construct corresponding destination artifact path, i.e.
 | 
						|
		// an optional destination prefix plus the source base path
 | 
						|
		dstPath := path.Clean(osPath.Join(ruleData["dstPrefix"], srcBasePath))
 | 
						|
 | 
						|
		// Try to find the corresponding destination artifact
 | 
						|
		dstArtifact, exists := dstArtifacts[dstPath]
 | 
						|
		// Ignore artifacts without corresponding destination artifact
 | 
						|
		if !exists {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Ignore artifact pairs with no matching hashes
 | 
						|
		if !reflect.DeepEqual(srcArtifacts[srcPath], dstArtifact) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Only if a source and destination artifact pair was found and
 | 
						|
		// their hashes are equal, will we mark the source artifact as
 | 
						|
		// successfully consumed, i.e. it will be removed from the queue
 | 
						|
		consumed.Add(srcPath)
 | 
						|
	}
 | 
						|
	return consumed
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
VerifyArtifacts iteratively applies the material and product rules of the
 | 
						|
passed items (step or inspection) to enforce and authorize artifacts (materials
 | 
						|
or products) reported by the corresponding link and to guarantee that
 | 
						|
artifacts are linked together across links.  In the beginning all artifacts are
 | 
						|
placed in a queue according to their type.  If an artifact gets consumed by a
 | 
						|
rule it is removed from the queue.  An artifact can only be consumed once in
 | 
						|
the course of processing the set of rules in ExpectedMaterials or
 | 
						|
ExpectedProducts.
 | 
						|
 | 
						|
Rules of type MATCH, ALLOW, CREATE, DELETE, MODIFY and DISALLOW are supported.
 | 
						|
 | 
						|
All rules except for DISALLOW consume queued artifacts on success, and
 | 
						|
leave the queue unchanged on failure.  Hence, it is left to a terminal
 | 
						|
DISALLOW rule to fail overall verification, if artifacts are left in the queue
 | 
						|
that should have been consumed by preceding rules.
 | 
						|
*/
 | 
						|
func VerifyArtifacts(items []interface{},
 | 
						|
	itemsMetadata map[string]Metablock) error {
 | 
						|
	// Verify artifact rules for each item in the layout
 | 
						|
	for _, itemI := range items {
 | 
						|
		// The layout item (interface) must be a Link or an Inspection we are only
 | 
						|
		// interested in the name and the expected materials and products
 | 
						|
		var itemName string
 | 
						|
		var expectedMaterials [][]string
 | 
						|
		var expectedProducts [][]string
 | 
						|
 | 
						|
		switch item := itemI.(type) {
 | 
						|
		case Step:
 | 
						|
			itemName = item.Name
 | 
						|
			expectedMaterials = item.ExpectedMaterials
 | 
						|
			expectedProducts = item.ExpectedProducts
 | 
						|
 | 
						|
		case Inspection:
 | 
						|
			itemName = item.Name
 | 
						|
			expectedMaterials = item.ExpectedMaterials
 | 
						|
			expectedProducts = item.ExpectedProducts
 | 
						|
 | 
						|
		default: // Something wrong
 | 
						|
			return fmt.Errorf("VerifyArtifacts received an item of invalid type,"+
 | 
						|
				" elements of passed slice 'items' must be one of 'Step' or"+
 | 
						|
				" 'Inspection', got: '%s'", reflect.TypeOf(item))
 | 
						|
		}
 | 
						|
 | 
						|
		// Use the item's name to extract the corresponding link
 | 
						|
		srcLinkMb, exists := itemsMetadata[itemName]
 | 
						|
		if !exists {
 | 
						|
			return fmt.Errorf("VerifyArtifacts could not find metadata"+
 | 
						|
				" for item '%s', got: '%s'", itemName, itemsMetadata)
 | 
						|
		}
 | 
						|
 | 
						|
		// Create shortcuts to materials and products (including hashes) reported
 | 
						|
		// by the item's link, required to verify "match" rules
 | 
						|
		materials := srcLinkMb.Signed.(Link).Materials
 | 
						|
		products := srcLinkMb.Signed.(Link).Products
 | 
						|
 | 
						|
		// All other rules only require the material or product paths (without
 | 
						|
		// hashes). We extract them from the corresponding maps and store them as
 | 
						|
		// sets for convenience in further processing
 | 
						|
		materialPaths := NewSet()
 | 
						|
		for _, p := range InterfaceKeyStrings(materials) {
 | 
						|
			materialPaths.Add(path.Clean(p))
 | 
						|
		}
 | 
						|
		productPaths := NewSet()
 | 
						|
		for _, p := range InterfaceKeyStrings(products) {
 | 
						|
			productPaths.Add(path.Clean(p))
 | 
						|
		}
 | 
						|
 | 
						|
		// For `create`, `delete` and `modify` rules we prepare sets of artifacts
 | 
						|
		// (without hashes) that were created, deleted or modified in the current
 | 
						|
		// step or inspection
 | 
						|
		created := productPaths.Difference(materialPaths)
 | 
						|
		deleted := materialPaths.Difference(productPaths)
 | 
						|
		remained := materialPaths.Intersection(productPaths)
 | 
						|
		modified := NewSet()
 | 
						|
		for name := range remained {
 | 
						|
			if !reflect.DeepEqual(materials[name], products[name]) {
 | 
						|
				modified.Add(name)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// For each item we have to run rule verification, once per artifact type.
 | 
						|
		// Here we prepare the corresponding data for each round.
 | 
						|
		verificationDataList := []map[string]interface{}{
 | 
						|
			{
 | 
						|
				"srcType":       "materials",
 | 
						|
				"rules":         expectedMaterials,
 | 
						|
				"artifacts":     materials,
 | 
						|
				"artifactPaths": materialPaths,
 | 
						|
			},
 | 
						|
			{
 | 
						|
				"srcType":       "products",
 | 
						|
				"rules":         expectedProducts,
 | 
						|
				"artifacts":     products,
 | 
						|
				"artifactPaths": productPaths,
 | 
						|
			},
 | 
						|
		}
 | 
						|
		// TODO: Add logging library (see in-toto/in-toto-golang#4)
 | 
						|
		// fmt.Printf("Verifying %s '%s' ", reflect.TypeOf(itemI), itemName)
 | 
						|
 | 
						|
		// Process all material rules using the corresponding materials and all
 | 
						|
		// product rules using the corresponding products
 | 
						|
		for _, verificationData := range verificationDataList {
 | 
						|
			// TODO: Add logging library (see in-toto/in-toto-golang#4)
 | 
						|
			// fmt.Printf("%s...\n", verificationData["srcType"])
 | 
						|
 | 
						|
			rules := verificationData["rules"].([][]string)
 | 
						|
			artifacts := verificationData["artifacts"].(map[string]interface{})
 | 
						|
 | 
						|
			// Use artifacts (without hashes) as base queue. Each rule only operates
 | 
						|
			// on artifacts in that queue.  If a rule consumes an artifact (i.e. can
 | 
						|
			// be applied successfully), the artifact is removed from the queue. By
 | 
						|
			// applying a DISALLOW rule eventually, verification may return an error,
 | 
						|
			// if the rule matches any artifacts in the queue that should have been
 | 
						|
			// consumed earlier.
 | 
						|
			queue := verificationData["artifactPaths"].(Set)
 | 
						|
 | 
						|
			// TODO: Add logging library (see in-toto/in-toto-golang#4)
 | 
						|
			// fmt.Printf("Initial state\nMaterials: %s\nProducts: %s\nQueue: %s\n\n",
 | 
						|
			// 	materialPaths.Slice(), productPaths.Slice(), queue.Slice())
 | 
						|
 | 
						|
			// Verify rules sequentially
 | 
						|
			for _, rule := range rules {
 | 
						|
				// Parse rule and error out if it is malformed
 | 
						|
				// NOTE: the rule format should have been validated before
 | 
						|
				ruleData, err := UnpackRule(rule)
 | 
						|
				if err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
 | 
						|
				// Apply rule pattern to filter queued artifacts that are up for rule
 | 
						|
				// specific consumption
 | 
						|
				filtered := queue.Filter(path.Clean(ruleData["pattern"]))
 | 
						|
 | 
						|
				var consumed Set
 | 
						|
				switch ruleData["type"] {
 | 
						|
				case "match":
 | 
						|
					// Note: here we need to perform more elaborate filtering
 | 
						|
					consumed = verifyMatchRule(ruleData, artifacts, queue, itemsMetadata)
 | 
						|
 | 
						|
				case "allow":
 | 
						|
					// Consumes all filtered artifacts
 | 
						|
					consumed = filtered
 | 
						|
 | 
						|
				case "create":
 | 
						|
					// Consumes filtered artifacts that were created
 | 
						|
					consumed = filtered.Intersection(created)
 | 
						|
 | 
						|
				case "delete":
 | 
						|
					// Consumes filtered artifacts that were deleted
 | 
						|
					consumed = filtered.Intersection(deleted)
 | 
						|
 | 
						|
				case "modify":
 | 
						|
					// Consumes filtered artifacts that were modified
 | 
						|
					consumed = filtered.Intersection(modified)
 | 
						|
 | 
						|
				case "disallow":
 | 
						|
					// Does not consume but errors out if artifacts were filtered
 | 
						|
					if len(filtered) > 0 {
 | 
						|
						return fmt.Errorf("artifact verification failed for %s '%s',"+
 | 
						|
							" %s %s disallowed by rule %s",
 | 
						|
							reflect.TypeOf(itemI).Name(), itemName,
 | 
						|
							verificationData["srcType"], filtered.Slice(), rule)
 | 
						|
					}
 | 
						|
				case "require":
 | 
						|
					// REQUIRE is somewhat of a weird animal that does not use
 | 
						|
					// patterns bur rather single filenames (for now).
 | 
						|
					if !queue.Has(ruleData["pattern"]) {
 | 
						|
						return fmt.Errorf("artifact verification failed for %s in REQUIRE '%s',"+
 | 
						|
							" because %s is not in %s", verificationData["srcType"],
 | 
						|
							ruleData["pattern"], ruleData["pattern"], queue.Slice())
 | 
						|
					}
 | 
						|
				}
 | 
						|
				// Update queue by removing consumed artifacts
 | 
						|
				queue = queue.Difference(consumed)
 | 
						|
				// TODO: Add logging library (see in-toto/in-toto-golang#4)
 | 
						|
				// fmt.Printf("Rule: %s\nQueue: %s\n\n", rule, queue.Slice())
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
ReduceStepsMetadata merges for each step of the passed Layout all the passed
 | 
						|
per-functionary links into a single link, asserting that the reported Materials
 | 
						|
and Products are equal across links for a given step.  This function may be
 | 
						|
used at a time during the overall verification, where link threshold's have
 | 
						|
been verified and subsequent verification only needs one exemplary link per
 | 
						|
step.  The function returns a map with one Metablock (link) per step:
 | 
						|
 | 
						|
	{
 | 
						|
		<step name> : Metablock,
 | 
						|
		<step name> : Metablock,
 | 
						|
		...
 | 
						|
	}
 | 
						|
 | 
						|
If links corresponding to the same step report different Materials or different
 | 
						|
Products, the first return value is an empty Metablock map and the second
 | 
						|
return value is the error.
 | 
						|
*/
 | 
						|
func ReduceStepsMetadata(layout Layout,
 | 
						|
	stepsMetadata map[string]map[string]Metablock) (map[string]Metablock,
 | 
						|
	error) {
 | 
						|
	stepsMetadataReduced := make(map[string]Metablock)
 | 
						|
 | 
						|
	for _, step := range layout.Steps {
 | 
						|
		linksPerStep, ok := stepsMetadata[step.Name]
 | 
						|
		// We should never get here, layout verification must fail earlier
 | 
						|
		if !ok || len(linksPerStep) < 1 {
 | 
						|
			panic("Could not reduce metadata for step '" + step.Name +
 | 
						|
				"', no link metadata found.")
 | 
						|
		}
 | 
						|
 | 
						|
		// Get the first link (could be any link) for the current step, which will
 | 
						|
		// serve as reference link for below comparisons
 | 
						|
		var referenceKeyID string
 | 
						|
		var referenceLinkMb Metablock
 | 
						|
		for keyID, linkMb := range linksPerStep {
 | 
						|
			referenceLinkMb = linkMb
 | 
						|
			referenceKeyID = keyID
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		// Only one link, nothing to reduce, take the reference link
 | 
						|
		if len(linksPerStep) == 1 {
 | 
						|
			stepsMetadataReduced[step.Name] = referenceLinkMb
 | 
						|
 | 
						|
			// Multiple links, reduce but first check
 | 
						|
		} else {
 | 
						|
			// Artifact maps must be equal for each type among all links
 | 
						|
			// TODO: What should we do if there are more links, than the
 | 
						|
			// threshold requires, but not all of them are equal? Right now we would
 | 
						|
			// also error.
 | 
						|
			for keyID, linkMb := range linksPerStep {
 | 
						|
				if !reflect.DeepEqual(linkMb.Signed.(Link).Materials,
 | 
						|
					referenceLinkMb.Signed.(Link).Materials) ||
 | 
						|
					!reflect.DeepEqual(linkMb.Signed.(Link).Products,
 | 
						|
						referenceLinkMb.Signed.(Link).Products) {
 | 
						|
					return nil, fmt.Errorf("link '%s' and '%s' have different"+
 | 
						|
						" artifacts",
 | 
						|
						fmt.Sprintf(LinkNameFormat, step.Name, referenceKeyID),
 | 
						|
						fmt.Sprintf(LinkNameFormat, step.Name, keyID))
 | 
						|
				}
 | 
						|
			}
 | 
						|
			// We haven't errored out, so we can reduce (i.e take the reference link)
 | 
						|
			stepsMetadataReduced[step.Name] = referenceLinkMb
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return stepsMetadataReduced, nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
VerifyStepCommandAlignment (soft) verifies that for each step of the passed
 | 
						|
layout the command executed, as per the passed link, matches the expected
 | 
						|
command, as per the layout.  Soft verification means that, in case a command
 | 
						|
does not align, a warning is issued.
 | 
						|
*/
 | 
						|
func VerifyStepCommandAlignment(layout Layout,
 | 
						|
	stepsMetadata map[string]map[string]Metablock) {
 | 
						|
	for _, step := range layout.Steps {
 | 
						|
		linksPerStep, ok := stepsMetadata[step.Name]
 | 
						|
		// We should never get here, layout verification must fail earlier
 | 
						|
		if !ok || len(linksPerStep) < 1 {
 | 
						|
			panic("Could not verify command alignment for step '" + step.Name +
 | 
						|
				"', no link metadata found.")
 | 
						|
		}
 | 
						|
 | 
						|
		for signerKeyID, linkMb := range linksPerStep {
 | 
						|
			expectedCommandS := strings.Join(step.ExpectedCommand, " ")
 | 
						|
			executedCommandS := strings.Join(linkMb.Signed.(Link).Command, " ")
 | 
						|
 | 
						|
			if expectedCommandS != executedCommandS {
 | 
						|
				linkName := fmt.Sprintf(LinkNameFormat, step.Name, signerKeyID)
 | 
						|
				fmt.Printf("WARNING: Expected command for step '%s' (%s) and command"+
 | 
						|
					" reported by '%s' (%s) differ.\n",
 | 
						|
					step.Name, expectedCommandS, linkName, executedCommandS)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
LoadLayoutCertificates loads the root and intermediate CAs from the layout if in the layout.
 | 
						|
This will be used to check signatures that were used to sign links but not configured
 | 
						|
in the PubKeys section of the step.  No configured CAs means we don't want to allow this.
 | 
						|
Returned CertPools will be empty in this case.
 | 
						|
*/
 | 
						|
func LoadLayoutCertificates(layout Layout, intermediatePems [][]byte) (*x509.CertPool, *x509.CertPool, error) {
 | 
						|
	rootPool := x509.NewCertPool()
 | 
						|
	for _, certPem := range layout.RootCas {
 | 
						|
		ok := rootPool.AppendCertsFromPEM([]byte(certPem.KeyVal.Certificate))
 | 
						|
		if !ok {
 | 
						|
			return nil, nil, fmt.Errorf("failed to load root certificates for layout")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	intermediatePool := x509.NewCertPool()
 | 
						|
	for _, intermediatePem := range layout.IntermediateCas {
 | 
						|
		ok := intermediatePool.AppendCertsFromPEM([]byte(intermediatePem.KeyVal.Certificate))
 | 
						|
		if !ok {
 | 
						|
			return nil, nil, fmt.Errorf("failed to load intermediate certificates for layout")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for _, intermediatePem := range intermediatePems {
 | 
						|
		ok := intermediatePool.AppendCertsFromPEM(intermediatePem)
 | 
						|
		if !ok {
 | 
						|
			return nil, nil, fmt.Errorf("failed to load provided intermediate certificates")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return rootPool, intermediatePool, nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
VerifyLinkSignatureThesholds verifies that for each step of the passed layout,
 | 
						|
there are at least Threshold links, validly signed by different authorized
 | 
						|
functionaries.  The returned map of link metadata per steps contains only
 | 
						|
links with valid signatures from distinct functionaries and has the format:
 | 
						|
 | 
						|
	{
 | 
						|
		<step name> : {
 | 
						|
		<key id>: Metablock,
 | 
						|
		<key id>: Metablock,
 | 
						|
		...
 | 
						|
		},
 | 
						|
		<step name> : {
 | 
						|
		<key id>: Metablock,
 | 
						|
		<key id>: Metablock,
 | 
						|
		...
 | 
						|
		}
 | 
						|
		...
 | 
						|
	}
 | 
						|
 | 
						|
If for any step of the layout there are not enough links available, the first
 | 
						|
return value is an empty map of Metablock maps and the second return value is
 | 
						|
the error.
 | 
						|
*/
 | 
						|
func VerifyLinkSignatureThesholds(layout Layout,
 | 
						|
	stepsMetadata map[string]map[string]Metablock, rootCertPool, intermediateCertPool *x509.CertPool) (
 | 
						|
	map[string]map[string]Metablock, error) {
 | 
						|
	// This will stores links with valid signature from an authorized functionary
 | 
						|
	// for all steps
 | 
						|
	stepsMetadataVerified := make(map[string]map[string]Metablock)
 | 
						|
 | 
						|
	// Try to find enough (>= threshold) links each with a valid signature from
 | 
						|
	// distinct authorized functionaries for each step
 | 
						|
	for _, step := range layout.Steps {
 | 
						|
		var stepErr error
 | 
						|
 | 
						|
		// This will store links with valid signature from an authorized
 | 
						|
		// functionary for the given step
 | 
						|
		linksPerStepVerified := make(map[string]Metablock)
 | 
						|
 | 
						|
		// Check if there are any links at all for the given step
 | 
						|
		linksPerStep, ok := stepsMetadata[step.Name]
 | 
						|
		if !ok || len(linksPerStep) < 1 {
 | 
						|
			stepErr = fmt.Errorf("no links found")
 | 
						|
		}
 | 
						|
 | 
						|
		// For each link corresponding to a step, check that the signer key was
 | 
						|
		// authorized, the layout contains a verification key and the signature
 | 
						|
		// verification passes.  Only good links are stored, to verify thresholds
 | 
						|
		// below.
 | 
						|
		isAuthorizedSignature := false
 | 
						|
		for signerKeyID, linkMb := range linksPerStep {
 | 
						|
			for _, authorizedKeyID := range step.PubKeys {
 | 
						|
				if signerKeyID == authorizedKeyID {
 | 
						|
					if verifierKey, ok := layout.Keys[authorizedKeyID]; ok {
 | 
						|
						if err := linkMb.VerifySignature(verifierKey); err == nil {
 | 
						|
							linksPerStepVerified[signerKeyID] = linkMb
 | 
						|
							isAuthorizedSignature = true
 | 
						|
							break
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// If the signer's key wasn't in our step's pubkeys array, check the cert pool to
 | 
						|
			// see if the key is known to us.
 | 
						|
			if !isAuthorizedSignature {
 | 
						|
				sig, err := linkMb.GetSignatureForKeyID(signerKeyID)
 | 
						|
				if err != nil {
 | 
						|
					stepErr = err
 | 
						|
					continue
 | 
						|
				}
 | 
						|
 | 
						|
				cert, err := sig.GetCertificate()
 | 
						|
				if err != nil {
 | 
						|
					stepErr = err
 | 
						|
					continue
 | 
						|
				}
 | 
						|
 | 
						|
				// test certificate against the step's constraints to make sure it's a valid functionary
 | 
						|
				err = step.CheckCertConstraints(cert, layout.RootCAIDs(), rootCertPool, intermediateCertPool)
 | 
						|
				if err != nil {
 | 
						|
					stepErr = err
 | 
						|
					continue
 | 
						|
				}
 | 
						|
 | 
						|
				err = linkMb.VerifySignature(cert)
 | 
						|
				if err != nil {
 | 
						|
					stepErr = err
 | 
						|
					continue
 | 
						|
				}
 | 
						|
 | 
						|
				linksPerStepVerified[signerKeyID] = linkMb
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Store all good links for a step
 | 
						|
		stepsMetadataVerified[step.Name] = linksPerStepVerified
 | 
						|
 | 
						|
		if len(linksPerStepVerified) < step.Threshold {
 | 
						|
			linksPerStep := stepsMetadata[step.Name]
 | 
						|
			return nil, fmt.Errorf("step '%s' requires '%d' link metadata file(s)."+
 | 
						|
				" '%d' out of '%d' available link(s) have a valid signature from an"+
 | 
						|
				" authorized signer: %v", step.Name, step.Threshold,
 | 
						|
				len(linksPerStepVerified), len(linksPerStep), stepErr)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return stepsMetadataVerified, nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
LoadLinksForLayout loads for every Step of the passed Layout a Metablock
 | 
						|
containing the corresponding Link.  A base path to a directory that contains
 | 
						|
the links may be passed using linkDir.  Link file names are constructed,
 | 
						|
using LinkNameFormat together with the corresponding step name and authorized
 | 
						|
functionary key ids.  A map of link metadata is returned and has the following
 | 
						|
format:
 | 
						|
 | 
						|
	{
 | 
						|
		<step name> : {
 | 
						|
			<key id>: Metablock,
 | 
						|
			<key id>: Metablock,
 | 
						|
			...
 | 
						|
		},
 | 
						|
		<step name> : {
 | 
						|
		<key id>: Metablock,
 | 
						|
		<key id>: Metablock,
 | 
						|
		...
 | 
						|
		}
 | 
						|
		...
 | 
						|
	}
 | 
						|
 | 
						|
If a link cannot be loaded at a constructed link name or is invalid, it is
 | 
						|
ignored. Only a preliminary threshold check is performed, that is, if there
 | 
						|
aren't at least Threshold links for any given step, the first return value
 | 
						|
is an empty map of Metablock maps and the second return value is the error.
 | 
						|
*/
 | 
						|
func LoadLinksForLayout(layout Layout, linkDir string) (map[string]map[string]Metablock, error) {
 | 
						|
	stepsMetadata := make(map[string]map[string]Metablock)
 | 
						|
 | 
						|
	for _, step := range layout.Steps {
 | 
						|
		linksPerStep := make(map[string]Metablock)
 | 
						|
		// Since we can verify against certificates belonging to a CA, we need to
 | 
						|
		// load any possible links
 | 
						|
		linkFiles, err := filepath.Glob(osPath.Join(linkDir, fmt.Sprintf(LinkGlobFormat, step.Name)))
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		for _, linkPath := range linkFiles {
 | 
						|
			var linkMb Metablock
 | 
						|
			if err := linkMb.Load(linkPath); err != nil {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			// To get the full key from the metadata's signatures, we have to check
 | 
						|
			// for one with the same short id...
 | 
						|
			signerShortKeyID := strings.TrimSuffix(strings.TrimPrefix(filepath.Base(linkPath), step.Name+"."), ".link")
 | 
						|
			for _, sig := range linkMb.Signatures {
 | 
						|
				if strings.HasPrefix(sig.KeyID, signerShortKeyID) {
 | 
						|
					linksPerStep[sig.KeyID] = linkMb
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if len(linksPerStep) < step.Threshold {
 | 
						|
			return nil, fmt.Errorf("step '%s' requires '%d' link metadata file(s),"+
 | 
						|
				" found '%d'", step.Name, step.Threshold, len(linksPerStep))
 | 
						|
		}
 | 
						|
 | 
						|
		stepsMetadata[step.Name] = linksPerStep
 | 
						|
	}
 | 
						|
 | 
						|
	return stepsMetadata, nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
VerifyLayoutExpiration verifies that the passed Layout has not expired.  It
 | 
						|
returns an error if the (zulu) date in the Expires field is in the past.
 | 
						|
*/
 | 
						|
func VerifyLayoutExpiration(layout Layout) error {
 | 
						|
	expires, err := time.Parse(ISO8601DateSchema, layout.Expires)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	// Uses timezone of expires, i.e. UTC
 | 
						|
	if time.Until(expires) < 0 {
 | 
						|
		return fmt.Errorf("layout has expired on '%s'", expires)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
VerifyLayoutSignatures verifies for each key in the passed key map the
 | 
						|
corresponding signature of the Layout in the passed Metablock's Signed field.
 | 
						|
Signatures and keys are associated by key id.  If the key map is empty, or the
 | 
						|
Metablock's Signature field does not have a signature for one or more of the
 | 
						|
passed keys, or a matching signature is invalid, an error is returned.
 | 
						|
*/
 | 
						|
func VerifyLayoutSignatures(layoutMb Metablock,
 | 
						|
	layoutKeys map[string]Key) error {
 | 
						|
	if len(layoutKeys) < 1 {
 | 
						|
		return fmt.Errorf("layout verification requires at least one key")
 | 
						|
	}
 | 
						|
 | 
						|
	for _, key := range layoutKeys {
 | 
						|
		if err := layoutMb.VerifySignature(key); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
GetSummaryLink merges the materials of the first step (as mentioned in the
 | 
						|
layout) and the products of the last step and returns a new link. This link
 | 
						|
reports the materials and products and summarizes the overall software supply
 | 
						|
chain.
 | 
						|
NOTE: The assumption is that the steps mentioned in the layout are to be
 | 
						|
performed sequentially. So, the first step mentioned in the layout denotes what
 | 
						|
comes into the supply chain and the last step denotes what goes out.
 | 
						|
*/
 | 
						|
func GetSummaryLink(layout Layout, stepsMetadataReduced map[string]Metablock,
 | 
						|
	stepName string) (Metablock, error) {
 | 
						|
	var summaryLink Link
 | 
						|
	var result Metablock
 | 
						|
	if len(layout.Steps) > 0 {
 | 
						|
		firstStepLink := stepsMetadataReduced[layout.Steps[0].Name]
 | 
						|
		lastStepLink := stepsMetadataReduced[layout.Steps[len(layout.Steps)-1].Name]
 | 
						|
 | 
						|
		summaryLink.Materials = firstStepLink.Signed.(Link).Materials
 | 
						|
		summaryLink.Name = stepName
 | 
						|
		summaryLink.Type = firstStepLink.Signed.(Link).Type
 | 
						|
 | 
						|
		summaryLink.Products = lastStepLink.Signed.(Link).Products
 | 
						|
		summaryLink.ByProducts = lastStepLink.Signed.(Link).ByProducts
 | 
						|
		// Using the last command of the sublayout as the command
 | 
						|
		// of the summary link can be misleading. Is it necessary to
 | 
						|
		// include all the commands executed as part of sublayout?
 | 
						|
		summaryLink.Command = lastStepLink.Signed.(Link).Command
 | 
						|
	}
 | 
						|
 | 
						|
	result.Signed = summaryLink
 | 
						|
 | 
						|
	return result, nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
VerifySublayouts checks if any step in the supply chain is a sublayout, and if
 | 
						|
so, recursively resolves it and replaces it with a summary link summarizing the
 | 
						|
steps carried out in the sublayout.
 | 
						|
*/
 | 
						|
func VerifySublayouts(layout Layout,
 | 
						|
	stepsMetadataVerified map[string]map[string]Metablock,
 | 
						|
	superLayoutLinkPath string, intermediatePems [][]byte, lineNormalization bool) (map[string]map[string]Metablock, error) {
 | 
						|
	for stepName, linkData := range stepsMetadataVerified {
 | 
						|
		for keyID, metadata := range linkData {
 | 
						|
			if _, ok := metadata.Signed.(Layout); ok {
 | 
						|
				layoutKeys := make(map[string]Key)
 | 
						|
				layoutKeys[keyID] = layout.Keys[keyID]
 | 
						|
 | 
						|
				sublayoutLinkDir := fmt.Sprintf(SublayoutLinkDirFormat,
 | 
						|
					stepName, keyID)
 | 
						|
				sublayoutLinkPath := filepath.Join(superLayoutLinkPath,
 | 
						|
					sublayoutLinkDir)
 | 
						|
				summaryLink, err := InTotoVerify(metadata, layoutKeys,
 | 
						|
					sublayoutLinkPath, stepName, make(map[string]string), intermediatePems, lineNormalization)
 | 
						|
				if err != nil {
 | 
						|
					return nil, err
 | 
						|
				}
 | 
						|
				linkData[keyID] = summaryLink
 | 
						|
			}
 | 
						|
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return stepsMetadataVerified, nil
 | 
						|
}
 | 
						|
 | 
						|
// TODO: find a better way than two helper functions for the replacer op
 | 
						|
 | 
						|
func substituteParamatersInSlice(replacer *strings.Replacer, slice []string) []string {
 | 
						|
	newSlice := make([]string, 0)
 | 
						|
	for _, item := range slice {
 | 
						|
		newSlice = append(newSlice, replacer.Replace(item))
 | 
						|
	}
 | 
						|
	return newSlice
 | 
						|
}
 | 
						|
 | 
						|
func substituteParametersInSliceOfSlices(replacer *strings.Replacer,
 | 
						|
	slice [][]string) [][]string {
 | 
						|
	newSlice := make([][]string, 0)
 | 
						|
	for _, item := range slice {
 | 
						|
		newSlice = append(newSlice, substituteParamatersInSlice(replacer,
 | 
						|
			item))
 | 
						|
	}
 | 
						|
	return newSlice
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
SubstituteParameters performs parameter substitution in steps and inspections
 | 
						|
in the following fields:
 | 
						|
- Expected Materials and Expected Products of both
 | 
						|
- Run of inspections
 | 
						|
- Expected Command of steps
 | 
						|
The substitution marker is '{}' and the keyword within the braces is replaced
 | 
						|
by a value found in the substitution map passed, parameterDictionary. The
 | 
						|
layout with parameters substituted is returned to the calling function.
 | 
						|
*/
 | 
						|
func SubstituteParameters(layout Layout,
 | 
						|
	parameterDictionary map[string]string) (Layout, error) {
 | 
						|
 | 
						|
	if len(parameterDictionary) == 0 {
 | 
						|
		return layout, nil
 | 
						|
	}
 | 
						|
 | 
						|
	parameters := make([]string, 0)
 | 
						|
 | 
						|
	re := regexp.MustCompile("^[a-zA-Z0-9_-]+$")
 | 
						|
 | 
						|
	for parameter, value := range parameterDictionary {
 | 
						|
		parameterFormatCheck := re.MatchString(parameter)
 | 
						|
		if !parameterFormatCheck {
 | 
						|
			return layout, fmt.Errorf("invalid format for parameter")
 | 
						|
		}
 | 
						|
 | 
						|
		parameters = append(parameters, "{"+parameter+"}")
 | 
						|
		parameters = append(parameters, value)
 | 
						|
	}
 | 
						|
 | 
						|
	replacer := strings.NewReplacer(parameters...)
 | 
						|
 | 
						|
	for i := range layout.Steps {
 | 
						|
		layout.Steps[i].ExpectedMaterials = substituteParametersInSliceOfSlices(
 | 
						|
			replacer, layout.Steps[i].ExpectedMaterials)
 | 
						|
		layout.Steps[i].ExpectedProducts = substituteParametersInSliceOfSlices(
 | 
						|
			replacer, layout.Steps[i].ExpectedProducts)
 | 
						|
		layout.Steps[i].ExpectedCommand = substituteParamatersInSlice(replacer,
 | 
						|
			layout.Steps[i].ExpectedCommand)
 | 
						|
	}
 | 
						|
 | 
						|
	for i := range layout.Inspect {
 | 
						|
		layout.Inspect[i].ExpectedMaterials =
 | 
						|
			substituteParametersInSliceOfSlices(replacer,
 | 
						|
				layout.Inspect[i].ExpectedMaterials)
 | 
						|
		layout.Inspect[i].ExpectedProducts =
 | 
						|
			substituteParametersInSliceOfSlices(replacer,
 | 
						|
				layout.Inspect[i].ExpectedProducts)
 | 
						|
		layout.Inspect[i].Run = substituteParamatersInSlice(replacer,
 | 
						|
			layout.Inspect[i].Run)
 | 
						|
	}
 | 
						|
 | 
						|
	return layout, nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
InTotoVerify can be used to verify an entire software supply chain according to
 | 
						|
the in-toto specification.  It requires the metadata of the root layout, a map
 | 
						|
that contains public keys to verify the root layout signatures, a path to a
 | 
						|
directory from where it can load link metadata files, which are treated as
 | 
						|
signed evidence for the steps defined in the layout, a step name, and a
 | 
						|
paramater dictionary used for parameter substitution. The step name only
 | 
						|
matters for sublayouts, where it's important to associate the summary of that
 | 
						|
step with a unique name. The verification routine is as follows:
 | 
						|
 | 
						|
1. Verify layout signature(s) using passed key(s)
 | 
						|
2. Verify layout expiration date
 | 
						|
3. Substitute parameters in layout
 | 
						|
4. Load link metadata files for steps of layout
 | 
						|
5. Verify signatures and signature thresholds for steps of layout
 | 
						|
6. Verify sublayouts recursively
 | 
						|
7. Verify command alignment for steps of layout (only warns)
 | 
						|
8. Verify artifact rules for steps of layout
 | 
						|
9. Execute inspection commands (generates link metadata for each inspection)
 | 
						|
10. Verify artifact rules for inspections of layout
 | 
						|
 | 
						|
InTotoVerify returns a summary link wrapped in a Metablock object and an error
 | 
						|
value. If any of the verification routines fail, verification is aborted and
 | 
						|
error is returned. In such an instance, the first value remains an empty
 | 
						|
Metablock object.
 | 
						|
 | 
						|
NOTE: Artifact rules of type "create", "modify"
 | 
						|
and "delete" are currently not supported.
 | 
						|
*/
 | 
						|
func InTotoVerify(layoutMb Metablock, layoutKeys map[string]Key,
 | 
						|
	linkDir string, stepName string, parameterDictionary map[string]string, intermediatePems [][]byte, lineNormalization bool) (
 | 
						|
	Metablock, error) {
 | 
						|
 | 
						|
	var summaryLink Metablock
 | 
						|
	var err error
 | 
						|
 | 
						|
	// Verify root signatures
 | 
						|
	if err := VerifyLayoutSignatures(layoutMb, layoutKeys); err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Extract the layout from its Metablock container (for further processing)
 | 
						|
	layout := layoutMb.Signed.(Layout)
 | 
						|
 | 
						|
	// Verify layout expiration
 | 
						|
	if err := VerifyLayoutExpiration(layout); err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Substitute parameters in layout
 | 
						|
	layout, err = SubstituteParameters(layout, parameterDictionary)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	rootCertPool, intermediateCertPool, err := LoadLayoutCertificates(layout, intermediatePems)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Load links for layout
 | 
						|
	stepsMetadata, err := LoadLinksForLayout(layout, linkDir)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Verify link signatures
 | 
						|
	stepsMetadataVerified, err := VerifyLinkSignatureThesholds(layout,
 | 
						|
		stepsMetadata, rootCertPool, intermediateCertPool)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Verify and resolve sublayouts
 | 
						|
	stepsSublayoutVerified, err := VerifySublayouts(layout,
 | 
						|
		stepsMetadataVerified, linkDir, intermediatePems, lineNormalization)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Verify command alignment (WARNING only)
 | 
						|
	VerifyStepCommandAlignment(layout, stepsSublayoutVerified)
 | 
						|
 | 
						|
	// Given that signature thresholds have been checked above and the rest of
 | 
						|
	// the relevant link properties, i.e. materials and products, have to be
 | 
						|
	// exactly equal, we can reduce the map of steps metadata. However, we error
 | 
						|
	// if the relevant properties are not equal among links of a step.
 | 
						|
	stepsMetadataReduced, err := ReduceStepsMetadata(layout,
 | 
						|
		stepsSublayoutVerified)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Verify artifact rules
 | 
						|
	if err = VerifyArtifacts(layout.stepsAsInterfaceSlice(),
 | 
						|
		stepsMetadataReduced); err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	inspectionMetadata, err := RunInspections(layout, "", lineNormalization)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Add steps metadata to inspection metadata, because inspection artifact
 | 
						|
	// rules may also refer to artifacts reported by step links
 | 
						|
	for k, v := range stepsMetadataReduced {
 | 
						|
		inspectionMetadata[k] = v
 | 
						|
	}
 | 
						|
 | 
						|
	if err = VerifyArtifacts(layout.inspectAsInterfaceSlice(),
 | 
						|
		inspectionMetadata); err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	summaryLink, err = GetSummaryLink(layout, stepsMetadataReduced, stepName)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	return summaryLink, nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
InTotoVerifyWithDirectory provides the same functionality as IntotoVerify, but
 | 
						|
adds the possibility to select a local directory from where the inspections are run.
 | 
						|
*/
 | 
						|
func InTotoVerifyWithDirectory(layoutMb Metablock, layoutKeys map[string]Key,
 | 
						|
	linkDir string, runDir string, stepName string, parameterDictionary map[string]string, intermediatePems [][]byte, lineNormalization bool) (
 | 
						|
	Metablock, error) {
 | 
						|
 | 
						|
	var summaryLink Metablock
 | 
						|
	var err error
 | 
						|
 | 
						|
	// runDir sanity checks
 | 
						|
	// check if path exists
 | 
						|
	info, err := os.Stat(runDir)
 | 
						|
	if err != nil {
 | 
						|
		return Metablock{}, err
 | 
						|
	}
 | 
						|
 | 
						|
	// check if runDir is a symlink
 | 
						|
	if info.Mode()&os.ModeSymlink == os.ModeSymlink {
 | 
						|
		return Metablock{}, ErrInspectionRunDirIsSymlink
 | 
						|
	}
 | 
						|
 | 
						|
	// check if runDir is writable and a directory
 | 
						|
	err = isWritable(runDir)
 | 
						|
	if err != nil {
 | 
						|
		return Metablock{}, err
 | 
						|
	}
 | 
						|
 | 
						|
	// check if runDir is empty (we do not want to overwrite files)
 | 
						|
	// We abuse File.Readdirnames for this action.
 | 
						|
	f, err := os.Open(runDir)
 | 
						|
	if err != nil {
 | 
						|
		return Metablock{}, err
 | 
						|
	}
 | 
						|
	defer f.Close()
 | 
						|
	// We use Readdirnames(1) for performance reasons, one child node
 | 
						|
	// is enough to proof that the directory is not empty
 | 
						|
	_, err = f.Readdirnames(1)
 | 
						|
	// if io.EOF gets returned as error the directory is empty
 | 
						|
	if err == io.EOF {
 | 
						|
		return Metablock{}, err
 | 
						|
	}
 | 
						|
	err = f.Close()
 | 
						|
	if err != nil {
 | 
						|
		return Metablock{}, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Verify root signatures
 | 
						|
	if err := VerifyLayoutSignatures(layoutMb, layoutKeys); err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Extract the layout from its Metablock container (for further processing)
 | 
						|
	layout := layoutMb.Signed.(Layout)
 | 
						|
 | 
						|
	// Verify layout expiration
 | 
						|
	if err := VerifyLayoutExpiration(layout); err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Substitute parameters in layout
 | 
						|
	layout, err = SubstituteParameters(layout, parameterDictionary)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	rootCertPool, intermediateCertPool, err := LoadLayoutCertificates(layout, intermediatePems)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Load links for layout
 | 
						|
	stepsMetadata, err := LoadLinksForLayout(layout, linkDir)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Verify link signatures
 | 
						|
	stepsMetadataVerified, err := VerifyLinkSignatureThesholds(layout,
 | 
						|
		stepsMetadata, rootCertPool, intermediateCertPool)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Verify and resolve sublayouts
 | 
						|
	stepsSublayoutVerified, err := VerifySublayouts(layout,
 | 
						|
		stepsMetadataVerified, linkDir, intermediatePems, lineNormalization)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Verify command alignment (WARNING only)
 | 
						|
	VerifyStepCommandAlignment(layout, stepsSublayoutVerified)
 | 
						|
 | 
						|
	// Given that signature thresholds have been checked above and the rest of
 | 
						|
	// the relevant link properties, i.e. materials and products, have to be
 | 
						|
	// exactly equal, we can reduce the map of steps metadata. However, we error
 | 
						|
	// if the relevant properties are not equal among links of a step.
 | 
						|
	stepsMetadataReduced, err := ReduceStepsMetadata(layout,
 | 
						|
		stepsSublayoutVerified)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Verify artifact rules
 | 
						|
	if err = VerifyArtifacts(layout.stepsAsInterfaceSlice(),
 | 
						|
		stepsMetadataReduced); err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	inspectionMetadata, err := RunInspections(layout, runDir, lineNormalization)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Add steps metadata to inspection metadata, because inspection artifact
 | 
						|
	// rules may also refer to artifacts reported by step links
 | 
						|
	for k, v := range stepsMetadataReduced {
 | 
						|
		inspectionMetadata[k] = v
 | 
						|
	}
 | 
						|
 | 
						|
	if err = VerifyArtifacts(layout.inspectAsInterfaceSlice(),
 | 
						|
		inspectionMetadata); err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	summaryLink, err = GetSummaryLink(layout, stepsMetadataReduced, stepName)
 | 
						|
	if err != nil {
 | 
						|
		return summaryLink, err
 | 
						|
	}
 | 
						|
 | 
						|
	return summaryLink, nil
 | 
						|
}
 |