github.com/boxboat/in-toto-golang@v0.0.3-0.20210303203820-2fa16ecbe6f6/in_toto/verifylib.go (about)

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