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