...

Source file src/github.com/in-toto/in-toto-golang/in_toto/verifylib_test.go

Documentation: github.com/in-toto/in-toto-golang/in_toto

     1  package in_toto
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/x509"
     6  	"crypto/x509/pkix"
     7  	"fmt"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"reflect"
    12  	"sort"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/stretchr/testify/assert"
    18  )
    19  
    20  func TestInTotoVerifyPass(t *testing.T) {
    21  	t.Run("metablock layout", func(t *testing.T) {
    22  		layoutPath := "demo.layout"
    23  		pubKeyPath := "alice.pub"
    24  		linkDir := "."
    25  
    26  		layoutMb, err := LoadMetadata(layoutPath)
    27  		if err != nil {
    28  			t.Fatal(err)
    29  		}
    30  
    31  		var pubKey Key
    32  		if err := pubKey.LoadKey(pubKeyPath, "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
    33  			t.Error(err)
    34  		}
    35  
    36  		var layoutKeys = map[string]Key{
    37  			pubKey.KeyID: pubKey,
    38  		}
    39  
    40  		// No error should occur
    41  		if _, err := InTotoVerify(layoutMb, layoutKeys, linkDir, "",
    42  			make(map[string]string), [][]byte{}, testOSisWindows()); err != nil {
    43  			t.Error(err)
    44  		}
    45  	})
    46  
    47  	t.Run("DSSE layout", func(t *testing.T) {
    48  		layoutPath := "demo.dsse.layout" // This layout is identical to demo.layout minus the signature wrapper
    49  		pubKeyPath := "alice.pub"
    50  		linkDir := "."
    51  
    52  		layoutEnv, err := LoadMetadata(layoutPath)
    53  		if err != nil {
    54  			t.Fatal(err)
    55  		}
    56  
    57  		var pubKey Key
    58  		if err := pubKey.LoadKey(pubKeyPath, "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
    59  			t.Error(err)
    60  		}
    61  
    62  		var layoutKeys = map[string]Key{
    63  			pubKey.KeyID: pubKey,
    64  		}
    65  
    66  		// No error should occur, verification is using a DSSE layout and Metablock links
    67  		if _, err := InTotoVerify(layoutEnv, layoutKeys, linkDir, "",
    68  			make(map[string]string), [][]byte{}, testOSisWindows()); err != nil {
    69  			t.Error(err)
    70  		}
    71  	})
    72  
    73  	t.Run("verifying with only DSSE metadata", func(t *testing.T) {
    74  		layoutPath := "dsse-only.root.layout" // This layout is from in-toto/demo but skips the inspection
    75  		pubKeyPath := "alice.pub"
    76  		linkDir := "."
    77  
    78  		layoutEnv, err := LoadMetadata(layoutPath)
    79  		if err != nil {
    80  			t.Fatal(err)
    81  		}
    82  
    83  		var pubKey Key
    84  		if err := pubKey.LoadKey(pubKeyPath, "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
    85  			t.Error(err)
    86  		}
    87  
    88  		var layoutKeys = map[string]Key{
    89  			pubKey.KeyID: pubKey,
    90  		}
    91  
    92  		// No error should occur, verification is using a DSSE layout and links
    93  		if _, err := InTotoVerify(layoutEnv, layoutKeys, linkDir, "",
    94  			make(map[string]string), [][]byte{}, testOSisWindows()); err != nil {
    95  			t.Error(err)
    96  		}
    97  	})
    98  }
    99  
   100  func TestGetSummaryLink(t *testing.T) {
   101  	demoLayout, err := LoadMetadata("demo.layout")
   102  	if err != nil {
   103  		t.Fatal(err)
   104  	}
   105  	codeLink, err := LoadMetadata("write-code.b7d643de.link")
   106  	if err != nil {
   107  		t.Error(err)
   108  	}
   109  	packageLink, err := LoadMetadata("package.d3ffd108.link")
   110  	if err != nil {
   111  		t.Error(err)
   112  	}
   113  	demoLink := make(map[string]Metadata)
   114  	demoLink["write-code"] = codeLink
   115  	demoLink["package"] = packageLink
   116  
   117  	var summaryLink Metadata
   118  	if summaryLink, err = GetSummaryLink(demoLayout.GetPayload().(Layout),
   119  		demoLink, "", false); err != nil {
   120  		t.Error(err)
   121  	}
   122  	if summaryLink.GetPayload().(Link).Type != codeLink.GetPayload().(Link).Type {
   123  		t.Errorf("summary Link isn't of type Link")
   124  	}
   125  	if summaryLink.GetPayload().(Link).Name != "" {
   126  		t.Errorf("summary Link name doesn't match. Expected '%s', returned "+
   127  			"'%s", codeLink.GetPayload().(Link).Name, summaryLink.GetPayload().(Link).Name)
   128  	}
   129  	if !reflect.DeepEqual(summaryLink.GetPayload().(Link).Materials,
   130  		codeLink.GetPayload().(Link).Materials) {
   131  		t.Errorf("summary Link materials don't match. Expected '%s', "+
   132  			"returned '%s", codeLink.GetPayload().(Link).Materials,
   133  			summaryLink.GetPayload().(Link).Materials)
   134  	}
   135  
   136  	if !reflect.DeepEqual(summaryLink.GetPayload().(Link).Products,
   137  		packageLink.GetPayload().(Link).Products) {
   138  		t.Errorf("summary Link products don't match. Expected '%s', "+
   139  			"returned '%s", packageLink.GetPayload().(Link).Products,
   140  			summaryLink.GetPayload().(Link).Products)
   141  	}
   142  	if !reflect.DeepEqual(summaryLink.GetPayload().(Link).Command,
   143  		packageLink.GetPayload().(Link).Command) {
   144  		t.Errorf("summary Link command doesn't match. Expected '%s', "+
   145  			"returned '%s", packageLink.GetPayload().(Link).Command,
   146  			summaryLink.GetPayload().(Link).Command)
   147  	}
   148  	if !reflect.DeepEqual(summaryLink.GetPayload().(Link).ByProducts,
   149  		packageLink.GetPayload().(Link).ByProducts) {
   150  		t.Errorf("summary Link by-products don't match. Expected '%s', "+
   151  			"returned '%s", packageLink.GetPayload().(Link).ByProducts,
   152  			summaryLink.GetPayload().(Link).ByProducts)
   153  	}
   154  }
   155  
   156  func TestVerifySublayouts(t *testing.T) {
   157  	sublayoutName := "sub_layout"
   158  	var aliceKey Key
   159  	if err := aliceKey.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
   160  		t.Errorf("unable to load Alice's public key")
   161  	}
   162  	sublayoutDirectory := fmt.Sprintf(SublayoutLinkDirFormat, sublayoutName,
   163  		aliceKey.KeyID)
   164  	defer func(sublayoutDirectory string) {
   165  		if err := os.RemoveAll(sublayoutDirectory); err != nil {
   166  			t.Errorf("unable to remove directory %s: %s", sublayoutDirectory, err)
   167  		}
   168  	}(sublayoutDirectory)
   169  
   170  	if err := os.Mkdir(sublayoutDirectory, 0700); err != nil {
   171  		t.Errorf("unable to create sublayout directory")
   172  	}
   173  	writeCodePath := path.Join(sublayoutDirectory, "write-code.b7d643de.link")
   174  	if err := os.Link("write-code.b7d643de.link", writeCodePath); err != nil {
   175  		t.Errorf("unable to link write-code metadata.")
   176  	}
   177  	packagePath := path.Join(sublayoutDirectory, "package.d3ffd108.link")
   178  	if err := os.Link("package.d3ffd108.link", packagePath); err != nil {
   179  		t.Errorf("unable to link package metadata")
   180  	}
   181  
   182  	superLayoutMb, err := LoadMetadata("super.layout")
   183  	if err != nil {
   184  		t.Errorf("unable to load super layout")
   185  	}
   186  
   187  	stepsMetadata, err := LoadLinksForLayout(superLayoutMb.GetPayload().(Layout), ".")
   188  	if err != nil {
   189  		t.Errorf("unable to load link metadata for super layout")
   190  	}
   191  
   192  	rootCertPool, intermediateCertPool, err := LoadLayoutCertificates(superLayoutMb.GetPayload().(Layout), [][]byte{})
   193  	if err != nil {
   194  		t.Errorf("unable to load layout certificates")
   195  	}
   196  
   197  	stepsMetadataVerified, err := VerifyLinkSignatureThesholds(
   198  		superLayoutMb.GetPayload().(Layout), stepsMetadata, rootCertPool, intermediateCertPool)
   199  	if err != nil {
   200  		t.Errorf("unable to verify link threshold values: %v", err)
   201  	}
   202  
   203  	result, err := VerifySublayouts(superLayoutMb.GetPayload().(Layout),
   204  		stepsMetadataVerified, ".", [][]byte{}, testOSisWindows())
   205  	if err != nil {
   206  		t.Errorf("unable to verify sublayouts: %v", err)
   207  	}
   208  
   209  	for _, stepData := range result {
   210  		for _, metadata := range stepData {
   211  			if _, ok := metadata.GetPayload().(Link); !ok {
   212  				t.Errorf("sublayout expansion error: found non link")
   213  			}
   214  		}
   215  	}
   216  }
   217  
   218  func TestRunInspections(t *testing.T) {
   219  	// Load layout template used as basis for all tests
   220  	mb, err := LoadMetadata("demo.layout")
   221  	if err != nil {
   222  		t.Errorf("unable to parse template file: %s", err)
   223  	}
   224  	layout := mb.GetPayload().(Layout)
   225  
   226  	// Test 1
   227  	// Successfully run two inspections foo and bar, testing that each generates
   228  	// a link file and they record the correct materials and products.
   229  	layout.Inspect = []Inspection{
   230  		{
   231  			SupplyChainItem: SupplyChainItem{Name: "foo"},
   232  			Run:             []string{"sh", "-c", "true"},
   233  		},
   234  		{
   235  			SupplyChainItem: SupplyChainItem{Name: "bar"},
   236  			Run:             []string{"sh", "-c", "true"},
   237  		},
   238  	}
   239  
   240  	// Make a list of files in current dir (all must be recorded as artifacts)
   241  	availableFiles, _ := filepath.Glob("*")
   242  	result, err := RunInspections(layout, "", testOSisWindows(), false)
   243  
   244  	// Error must be nil
   245  	if err != nil {
   246  		t.Errorf("RunInspections returned %s as error, expected nil.",
   247  			err)
   248  	}
   249  
   250  	// Assert contents of inspection link metadata for both inspections
   251  	for _, inspectionName := range []string{"foo", "bar"} {
   252  		// Available files must be sorted after each inspection because the link
   253  		// file is added below
   254  		sort.Strings(availableFiles)
   255  		// Compare material and products (only file names) to files that were
   256  		// in the directory before calling RunInspections
   257  		materialNames := InterfaceKeyStrings(result[inspectionName].GetPayload().(Link).Materials)
   258  		productNames := InterfaceKeyStrings(result[inspectionName].GetPayload().(Link).Products)
   259  		sort.Strings(materialNames)
   260  		sort.Strings(productNames)
   261  		if !reflect.DeepEqual(materialNames, availableFiles) ||
   262  			!reflect.DeepEqual(productNames, availableFiles) {
   263  			t.Errorf("RunInspections recorded materials and products '%s' and %s'"+
   264  				" for insepction '%s', expected '%s' and '%s'.", materialNames,
   265  				productNames, inspectionName, availableFiles, availableFiles)
   266  		}
   267  		linkName := inspectionName + ".link"
   268  		// Append link created by an inspection to available files because it
   269  		// is recorded by succeeding inspections
   270  		availableFiles = append(availableFiles, linkName)
   271  
   272  		// Remove generated inspection link
   273  		err = os.Remove(linkName)
   274  		if os.IsNotExist(err) {
   275  			t.Errorf("RunInspections didn't generate expected '%s'", linkName)
   276  		}
   277  	}
   278  
   279  	// Test 2
   280  	// Fail RunInspections due to inexistent command
   281  	layout.Inspect = []Inspection{
   282  		{
   283  			SupplyChainItem: SupplyChainItem{Name: "foo"},
   284  			Run:             []string{"command-does-not-exist"},
   285  		},
   286  	}
   287  
   288  	result, err = RunInspections(layout, "", testOSisWindows(), false)
   289  	if result != nil || err == nil {
   290  		t.Errorf("RunInspections returned '(%s, %s)', expected"+
   291  			" '(nil, *exec.Error)'", result, err)
   292  	}
   293  
   294  	// Test 2
   295  	// Fail RunInspections due to non-zero exiting command
   296  	layout.Inspect = []Inspection{
   297  		{
   298  			SupplyChainItem: SupplyChainItem{Name: "foo"},
   299  			Run:             []string{"sh", "-c", "false"},
   300  		},
   301  	}
   302  	result, err = RunInspections(layout, "", testOSisWindows(), false)
   303  	if result != nil || err == nil {
   304  		t.Errorf("RunInspections returned '(%s, %s)', expected"+
   305  			" '(nil, *exec.Error)'", result, err)
   306  	}
   307  }
   308  
   309  func TestVerifyArtifact(t *testing.T) {
   310  	var testCases = []struct {
   311  		name      string
   312  		item      []interface{}
   313  		metadata  map[string]Metadata
   314  		expectErr string
   315  	}{
   316  		{
   317  			name: "Verify artifacts",
   318  			item: []interface{}{
   319  				Step{
   320  					SupplyChainItem: SupplyChainItem{
   321  						Name: "foo",
   322  						ExpectedMaterials: [][]string{
   323  							{"DELETE", "foo-delete"},
   324  							{"MODIFY", "foo-modify"},
   325  							{"MATCH", "foo-match", "WITH", "MATERIALS", "FROM", "foo"}, // not-modify
   326  							{"ALLOW", "foo-allow"},
   327  							{"DISALLOW", "*"},
   328  						},
   329  						ExpectedProducts: [][]string{
   330  							{"CREATE", "foo-create"},
   331  							{"MODIFY", "foo-modify"},
   332  							{"MATCH", "foo-match", "WITH", "MATERIALS", "FROM", "foo"}, // not-modify
   333  							{"REQUIRE", "foo-allow"},
   334  							{"ALLOW", "foo-allow"},
   335  							{"DISALLOW", "*"},
   336  						},
   337  					},
   338  				},
   339  			},
   340  			metadata: map[string]Metadata{
   341  				"foo": &Metablock{
   342  					Signed: Link{
   343  						Name: "foo",
   344  						Materials: map[string]interface{}{
   345  							"foo-delete": map[string]interface{}{"sha265": "abc"},
   346  							"foo-modify": map[string]interface{}{"sha265": "abc"},
   347  							"foo-match":  map[string]interface{}{"sha265": "abc"},
   348  							"foo-allow":  map[string]interface{}{"sha265": "abc"},
   349  						},
   350  						Products: map[string]interface{}{
   351  							"foo-create": map[string]interface{}{"sha265": "abc"},
   352  							"foo-modify": map[string]interface{}{"sha265": "abcdef"},
   353  							"foo-match":  map[string]interface{}{"sha265": "abc"},
   354  							"foo-allow":  map[string]interface{}{"sha265": "abc"},
   355  						},
   356  					},
   357  				},
   358  			},
   359  			expectErr: "",
   360  		},
   361  		{
   362  			name: "Verify match with relative paths",
   363  			item: []interface{}{
   364  				Step{
   365  					SupplyChainItem: SupplyChainItem{
   366  						Name: "foo",
   367  						ExpectedMaterials: [][]string{
   368  							{"MATCH", "*", "WITH", "PRODUCTS", "FROM", "bar"},
   369  							{"DISALLOW", "*"},
   370  						},
   371  					},
   372  				},
   373  			},
   374  			metadata: map[string]Metadata{
   375  				"foo": &Metablock{
   376  					Signed: Link{
   377  						Name: "foo",
   378  						Materials: map[string]interface{}{
   379  							"./foo.d/foo.py": map[string]interface{}{"sha265": "abc"},
   380  							"bar.d/bar.py":   map[string]interface{}{"sha265": "abc"},
   381  						},
   382  					},
   383  				},
   384  				"bar": &Metablock{
   385  					Signed: Link{
   386  						Name: "bar",
   387  						Products: map[string]interface{}{
   388  							"foo.d/foo.py":          map[string]interface{}{"sha265": "abc"},
   389  							"./baz/../bar.d/bar.py": map[string]interface{}{"sha265": "abc"},
   390  						},
   391  					},
   392  				},
   393  			},
   394  			expectErr: "",
   395  		},
   396  		{
   397  			name: "Verify match detection of hash mismatch",
   398  			item: []interface{}{
   399  				Step{
   400  					SupplyChainItem: SupplyChainItem{
   401  						Name: "foo",
   402  						ExpectedMaterials: [][]string{
   403  							{"MATCH", "*", "WITH", "PRODUCTS", "FROM", "bar"},
   404  							{"DISALLOW", "*"},
   405  						},
   406  					},
   407  				},
   408  			},
   409  			metadata: map[string]Metadata{
   410  				"foo": &Metablock{
   411  					Signed: Link{
   412  						Name: "foo",
   413  						Materials: map[string]interface{}{
   414  							"foo.d/foo.py": map[string]interface{}{"sha265": "abc"},
   415  							"bar.d/bar.py": map[string]interface{}{"sha265": "def"}, // modified by mitm
   416  						},
   417  					},
   418  				},
   419  				"bar": &Metablock{
   420  					Signed: Link{
   421  						Name: "bar",
   422  						Products: map[string]interface{}{
   423  							"foo.d/foo.py": map[string]interface{}{"sha265": "abc"},
   424  							"bar.d/bar.py": map[string]interface{}{"sha265": "abc"},
   425  						},
   426  					},
   427  				},
   428  			},
   429  			expectErr: "materials [bar.d/bar.py] disallowed by rule",
   430  		},
   431  		{
   432  			name:      "Item must be one of step or inspection",
   433  			item:      []interface{}{nil},
   434  			metadata:  map[string]Metadata{},
   435  			expectErr: "item of invalid type",
   436  		},
   437  		{
   438  			name:      "Can't find link metadata for step",
   439  			item:      []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo"}}},
   440  			metadata:  map[string]Metadata{},
   441  			expectErr: "could not find metadata",
   442  		},
   443  		{
   444  			name:      "Can't find link metadata for inspection",
   445  			item:      []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo"}}},
   446  			metadata:  map[string]Metadata{},
   447  			expectErr: "could not find metadata",
   448  		},
   449  		{
   450  			name:      "Wrong step expected material",
   451  			item:      []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"INVALID", "rule"}}}}},
   452  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo"}}},
   453  			expectErr: "rule format",
   454  		},
   455  		{
   456  			name:      "Wrong step expected product",
   457  			item:      []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"INVALID", "rule"}}}}},
   458  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo"}}},
   459  			expectErr: "rule format",
   460  		},
   461  		{
   462  			name:      "Wrong inspection expected material",
   463  			item:      []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"INVALID", "rule"}}}}},
   464  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo"}}},
   465  			expectErr: "rule format",
   466  		},
   467  		{
   468  			name:      "Wrong inspection expected product",
   469  			item:      []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"INVALID", "rule"}}}}},
   470  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo"}}},
   471  			expectErr: "rule format",
   472  		},
   473  		{
   474  			name:      "Disallowed material in step",
   475  			item:      []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"DISALLOW", "*"}}}}},
   476  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   477  			expectErr: "materials [foo.py] disallowed by rule",
   478  		},
   479  		{
   480  			name:      "Disallowed product in step",
   481  			item:      []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"DISALLOW", "*"}}}}},
   482  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   483  			expectErr: "products [foo.py] disallowed by rule",
   484  		},
   485  		{
   486  			name:      "Disallowed material in inspection",
   487  			item:      []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"DISALLOW", "*"}}}}},
   488  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   489  			expectErr: "materials [foo.py] disallowed by rule",
   490  		},
   491  		{
   492  			name:      "Disallowed product in inspection",
   493  			item:      []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"DISALLOW", "*"}}}}},
   494  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   495  			expectErr: "products [foo.py] disallowed by rule",
   496  		},
   497  		{
   498  			name:      "Required but missing material in step",
   499  			item:      []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"REQUIRE", "foo"}}}}},
   500  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   501  			expectErr: "materials in REQUIRE 'foo'",
   502  		},
   503  		{
   504  			name:      "Required but missing product in step",
   505  			item:      []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"REQUIRE", "foo"}}}}},
   506  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   507  			expectErr: "products in REQUIRE 'foo'",
   508  		},
   509  		{
   510  			name:      "Required but missing material in inspection",
   511  			item:      []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"REQUIRE", "foo"}}}}},
   512  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   513  			expectErr: "materials in REQUIRE 'foo'",
   514  		},
   515  		{
   516  			name:      "Required but missing product in inspection",
   517  			item:      []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"REQUIRE", "foo"}}}}},
   518  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   519  			expectErr: "products in REQUIRE 'foo'",
   520  		},
   521  		{
   522  			name:      "Disallowed subdirectory material in step",
   523  			item:      []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"DISALLOW", "*"}}}}},
   524  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"dir/foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   525  			expectErr: "materials [dir/foo.py] disallowed by rule",
   526  		},
   527  		{
   528  			name:      "Disallowed subdirectory product in step",
   529  			item:      []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"DISALLOW", "*"}}}}},
   530  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"dir/foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   531  			expectErr: "products [dir/foo.py] disallowed by rule",
   532  		},
   533  		{
   534  			name:      "Disallowed subdirectory material in inspection",
   535  			item:      []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"DISALLOW", "*"}}}}},
   536  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"dir/foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   537  			expectErr: "materials [dir/foo.py] disallowed by rule",
   538  		},
   539  		{
   540  			name:      "Disallowed subdirectory product in inspection",
   541  			item:      []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"DISALLOW", "*"}}}}},
   542  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"dir/foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   543  			expectErr: "products [dir/foo.py] disallowed by rule",
   544  		},
   545  		{
   546  			name:      "Consuming filename material in step",
   547  			item:      []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"ALLOW", "foo.py"}, {"DISALLOW", "*"}}}}},
   548  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"./bar/..//foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   549  			expectErr: "",
   550  		},
   551  		{
   552  			name:      "Consuming filename product in step",
   553  			item:      []interface{}{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"ALLOW", "foo.py"}, {"DISALLOW", "*"}}}}},
   554  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"./bar/..//foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   555  			expectErr: "",
   556  		},
   557  		{
   558  			name:      "Consuming filename material in inspection",
   559  			item:      []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"ALLOW", "foo.py"}, {"DISALLOW", "*"}}}}},
   560  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"./bar/..//foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   561  			expectErr: "",
   562  		},
   563  		{
   564  			name:      "Consuming filename product in inspection",
   565  			item:      []interface{}{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"ALLOW", "foo.py"}, {"DISALLOW", "*"}}}}},
   566  			metadata:  map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Products: map[string]interface{}{"./bar/..//foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   567  			expectErr: "",
   568  		},
   569  	}
   570  
   571  	for _, tt := range testCases {
   572  		t.Run(tt.name, func(t *testing.T) {
   573  			err := VerifyArtifacts(tt.item, tt.metadata)
   574  			if (err == nil && tt.expectErr != "") ||
   575  				(err != nil && tt.expectErr == "") ||
   576  				(err != nil && !strings.Contains(err.Error(), tt.expectErr)) {
   577  				t.Errorf("VerifyArtifacts returned '%s', expected '%s' error",
   578  					err, tt.expectErr)
   579  			}
   580  		})
   581  	}
   582  }
   583  
   584  func TestVerifyMatchRule(t *testing.T) {
   585  	var testCases = []struct {
   586  		name        string
   587  		rule        map[string]string
   588  		srcArtifact map[string]interface{}
   589  		item        map[string]Metadata
   590  		expectSet   Set
   591  	}{
   592  		{
   593  			name:        "Can't find destination link (invalid rule)",
   594  			rule:        map[string]string{},
   595  			srcArtifact: map[string]interface{}{},
   596  			item:        map[string]Metadata{},
   597  			expectSet:   NewSet(),
   598  		},
   599  		{
   600  			name:        "Can't find destination link (empty metadata map)",
   601  			rule:        map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials"},
   602  			srcArtifact: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}},
   603  			item:        map[string]Metadata{},
   604  			expectSet:   NewSet(),
   605  		},
   606  		{
   607  			name:        "Match material foo.py",
   608  			rule:        map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials"},
   609  			srcArtifact: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}},
   610  			item:        map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   611  			expectSet:   NewSet("foo.py"),
   612  		},
   613  		{
   614  			name:        "Match material foo.py with foo.d/foo.py",
   615  			rule:        map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials", "dstPrefix": "foo.d"},
   616  			srcArtifact: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}},
   617  			item:        map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.d/foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   618  			expectSet:   NewSet("foo.py"),
   619  		},
   620  		{
   621  			name:        "Match material foo.d/foo.py with foo.py",
   622  			rule:        map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials", "srcPrefix": "foo.d"},
   623  			srcArtifact: map[string]interface{}{"foo.d/foo.py": map[string]interface{}{"sha265": "abc"}},
   624  			item:        map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   625  			expectSet:   NewSet("foo.d/foo.py"),
   626  		},
   627  		{
   628  			name:        "Don't match material (different name)",
   629  			rule:        map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials"},
   630  			srcArtifact: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "abc"}},
   631  			item:        map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   632  			expectSet:   NewSet(),
   633  		},
   634  		{
   635  			name:        "Don't match material (different hash)",
   636  			rule:        map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials"},
   637  			srcArtifact: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "dead"}},
   638  			item:        map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   639  			expectSet:   NewSet(),
   640  		},
   641  		{
   642  			name:        "Match material in sub-directories dir/foo.py",
   643  			rule:        map[string]string{"pattern": "*", "dstName": "foo", "dstType": "materials"},
   644  			srcArtifact: map[string]interface{}{"bar/foo.py": map[string]interface{}{"sha265": "abc"}},
   645  			item:        map[string]Metadata{"foo": &Metablock{Signed: Link{Name: "foo", Materials: map[string]interface{}{"bar/foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   646  			expectSet:   NewSet("bar/foo.py"),
   647  		},
   648  	}
   649  
   650  	for _, tt := range testCases {
   651  		t.Run(tt.name, func(t *testing.T) {
   652  			queue := NewSet(InterfaceKeyStrings(tt.srcArtifact)...)
   653  			result := verifyMatchRule(tt.rule, tt.srcArtifact, queue, tt.item)
   654  			if !reflect.DeepEqual(result, tt.expectSet) {
   655  				t.Errorf("verifyMatchRule returned '%s', expected '%s'", result, tt.expectSet)
   656  			}
   657  		})
   658  	}
   659  }
   660  
   661  func TestReduceStepsMetadata(t *testing.T) {
   662  	mb, err := LoadMetadata("demo.layout")
   663  	if err != nil {
   664  		t.Errorf("unable to parse template file: %s", err)
   665  	}
   666  	layout := mb.GetPayload().(Layout)
   667  	layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{Name: "foo"}}}
   668  
   669  	// Test 1: Successful reduction of multiple links for one step (foo)
   670  	stepsMetadata := map[string]map[string]Metadata{
   671  		"foo": {
   672  			"a": &Metablock{Signed: Link{
   673  				Type:      "link",
   674  				Name:      "foo",
   675  				Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}},
   676  				Products:  map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "cde"}},
   677  			}},
   678  			"b": &Metablock{Signed: Link{
   679  				Type:      "link",
   680  				Name:      "foo",
   681  				Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}},
   682  				Products:  map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "cde"}},
   683  			}},
   684  		},
   685  	}
   686  
   687  	result, err := ReduceStepsMetadata(layout, stepsMetadata)
   688  	if !reflect.DeepEqual(result["foo"], stepsMetadata["foo"]["a"]) || err != nil {
   689  		t.Errorf("ReduceStepsMetadata returned (%s, %s), expected (%s, nil)"+
   690  			" and a 'different artifacts' error", result, err, stepsMetadata["foo"]["a"])
   691  	}
   692  
   693  	// Test 2: Test different error scenarios when creating one link out of
   694  	// multiple links for the same step:
   695  	// - Different materials (hash)
   696  	// - Different materials (name)
   697  	// - Different products (hash)
   698  	// - Different products (name)
   699  	stepsMetadataList := []map[string]map[string]Metadata{
   700  		{"foo": {
   701  			"a": &Metablock{Signed: Link{Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}},
   702  			"b": &Metablock{Signed: Link{Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "def"}}}},
   703  		}},
   704  		{"foo": {
   705  			"a": &Metablock{Signed: Link{Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}},
   706  			"b": &Metablock{Signed: Link{Materials: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "abc"}}}},
   707  		}},
   708  		{"foo": {
   709  			"a": &Metablock{Signed: Link{Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}},
   710  			"b": &Metablock{Signed: Link{Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "def"}}}},
   711  		}},
   712  		{"foo": {
   713  			"a": &Metablock{Signed: Link{Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}},
   714  			"b": &Metablock{Signed: Link{Products: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "abc"}}}},
   715  		}},
   716  	}
   717  
   718  	for i := 0; i < len(stepsMetadataList); i++ {
   719  		result, err := ReduceStepsMetadata(layout, stepsMetadataList[i])
   720  		if err == nil || !strings.Contains(err.Error(), "different artifacts") {
   721  			t.Errorf("ReduceStepsMetadata returned (%s, %s), expected an empty map"+
   722  				" and a 'different artifacts' error", result, err)
   723  		}
   724  	}
   725  
   726  	// Panic due to missing link metadata for step (final product verification
   727  	// should gracefully error earlier)
   728  	defer func() {
   729  		if r := recover(); r == nil {
   730  			t.Errorf("ReduceStepsMetadata should have panicked due to missing link" +
   731  				" metadata")
   732  		}
   733  	}()
   734  	if _, err := ReduceStepsMetadata(layout, nil); err != nil {
   735  		t.Errorf("error while calling ReduceStepsMetadata: %s", err)
   736  	}
   737  	//NOTE: This test won't get any further because of panic
   738  }
   739  
   740  func TestVerifyStepCommandAlignment(t *testing.T) {
   741  	mb, err := LoadMetadata("demo.layout")
   742  	if err != nil {
   743  		t.Errorf("unable to load template file: %s", err)
   744  	}
   745  	layout := mb.GetPayload().(Layout)
   746  	layout.Steps = []Step{
   747  		{
   748  			SupplyChainItem: SupplyChainItem{Name: "foo"},
   749  			ExpectedCommand: []string{"rm", "-rf", "."},
   750  		},
   751  	}
   752  
   753  	stepsMetadata := map[string]map[string]Metadata{
   754  		"foo": {"a": &Metablock{Signed: Link{Command: []string{"rm", "-rf", "/"}}}},
   755  	}
   756  	// Test warning due to non-aligning commands
   757  	// FIXME: Assert warning?
   758  	fmt.Printf("[begin test warning output]\n")
   759  	VerifyStepCommandAlignment(layout, stepsMetadata)
   760  	fmt.Printf("[end test warning output]\n")
   761  
   762  	// Panic due to missing link metadata for step (final product verification
   763  	// should gracefully error earlier)
   764  	defer func() {
   765  		if r := recover(); r == nil {
   766  			t.Errorf("ReduceStepsMetadata should have panicked due to missing link" +
   767  				" metadata")
   768  		}
   769  	}()
   770  	VerifyStepCommandAlignment(layout, nil)
   771  	//NOTE: This test won't get any further because of panic
   772  }
   773  
   774  func TestVerifyLinkSignatureThesholds(t *testing.T) {
   775  	keyID1 := "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401"
   776  	keyID2 := "d3ffd1086938b3698618adf088bf14b13db4c8ae19e4e78d73da49ee88492710"
   777  	keyID3 := "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabca"
   778  
   779  	mb, err := LoadMetadata("demo.layout")
   780  	if err != nil {
   781  		t.Errorf("unable to load template file: %s", err)
   782  	}
   783  	layout := mb.GetPayload().(Layout)
   784  
   785  	layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{
   786  		Name: "foo"},
   787  		Threshold: 2,
   788  		PubKeys:   []string{keyID1, keyID2, keyID3}}}
   789  
   790  	mbLink1, err := LoadMetadata("foo.b7d643de.link")
   791  	if err != nil {
   792  		t.Errorf("unable to load link file: %s", err)
   793  	}
   794  	mbLink2, err := LoadMetadata("foo.d3ffd108.link")
   795  	if err != nil {
   796  		t.Errorf("unable to load link file: %s", err)
   797  	}
   798  	mbLinkBroken, err := LoadMetadata("foo.d3ffd108.link")
   799  	if err != nil {
   800  		t.Errorf("unable to load link file: %s", err)
   801  	}
   802  	mbLinkBroken.Sigs()[0].Sig = "breaksignature"
   803  
   804  	// Test less then threshold distinct valid links errors:
   805  	// - Missing step name in step metadata map
   806  	// - Missing links for step
   807  	// - Less than threshold links for step
   808  	// - Less than threshold distinct links for step
   809  	// - Less than threshold validly signed links for step
   810  	stepsMetadata := []map[string]map[string]Metadata{
   811  		{"bar": nil},
   812  		{"foo": nil},
   813  		{"foo": {keyID1: mbLink1}},
   814  		{"foo": {keyID1: mbLink1, keyID2: mbLink1}},
   815  		{"foo": {keyID1: mbLink1, keyID2: mbLinkBroken}},
   816  	}
   817  	for i := 0; i < len(stepsMetadata); i++ {
   818  		result, err := VerifyLinkSignatureThesholds(layout, stepsMetadata[i], x509.NewCertPool(), x509.NewCertPool())
   819  		if err == nil {
   820  			t.Errorf("VerifyLinkSignatureThesholds returned (%s, %s), expected"+
   821  				" 'not enough distinct valid links' error.", result, err)
   822  		}
   823  	}
   824  
   825  	// Test successfully return threshold distinct valid links:
   826  	// - Threshold 2, two valid links
   827  	// - Threshold 2, two valid links, one invalid link ignored
   828  	stepsMetadata = []map[string]map[string]Metadata{
   829  		{"foo": {keyID1: mbLink1, keyID2: mbLink2}},
   830  		{"foo": {keyID1: mbLink1, keyID2: mbLink2, keyID3: mbLinkBroken}},
   831  	}
   832  	for i := 0; i < len(stepsMetadata); i++ {
   833  		result, err := VerifyLinkSignatureThesholds(layout, stepsMetadata[i], x509.NewCertPool(), x509.NewCertPool())
   834  		validLinks, ok := result["foo"]
   835  		if !ok || len(validLinks) != 2 {
   836  			t.Errorf("VerifyLinkSignatureThesholds returned (%s, %s), expected"+
   837  				" a map of two valid foo links.", result, err)
   838  		}
   839  	}
   840  }
   841  
   842  func TestLoadLinksForLayout(t *testing.T) {
   843  	keyID1 := "d3ffd1086938b3698618adf088bf14b13db4c8ae19e4e78d73da49ee88492710"
   844  	keyID2 := "b7d643dec0a051096ee5d87221b5d91a33daa658699d30903e1cefb90c418401"
   845  	mb, err := LoadMetadata("demo.layout")
   846  	if err != nil {
   847  		t.Errorf("unable to load template file: %s", err)
   848  	}
   849  	layout := mb.GetPayload().(Layout)
   850  
   851  	layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{
   852  		Name: "foo"},
   853  		Threshold: 2,
   854  		PubKeys:   []string{keyID1, keyID2}}}
   855  
   856  	// Test successfully load two links for layout (one step foo, threshold 2)
   857  	result, err := LoadLinksForLayout(layout, ".")
   858  	links, ok := result["foo"]
   859  	if !ok || len(links) != 2 {
   860  		t.Errorf("VerifyLoadLinksForLayout returned (%s, %s), expected"+
   861  			" a map of two foo links.", result, err)
   862  	}
   863  
   864  	// Test threshold error, can't find enough links for step
   865  	keyID3 := "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabca"
   866  	layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{
   867  		Name: "foo"},
   868  		Threshold: 3,
   869  		PubKeys:   []string{keyID1, keyID2, keyID3}}}
   870  	result, err = LoadLinksForLayout(layout, ".")
   871  	if err == nil {
   872  		t.Errorf("VerifyLoadLinksForLayout returned (%s, %s), expected"+
   873  			" 'not enough links' error.", result, err)
   874  	}
   875  }
   876  
   877  func TestVerifyLayoutExpiration(t *testing.T) {
   878  	mb, err := LoadMetadata("demo.layout")
   879  	if err != nil {
   880  		t.Errorf("unable to load template file: %s", err)
   881  	}
   882  	layout := mb.GetPayload().(Layout)
   883  
   884  	// Test layout expiration check failure:
   885  	// - invalid date
   886  	// - expired date
   887  	expirationDates := []string{"bad date", "1970-01-01T00:00:00Z"}
   888  	expectedErrors := []string{"cannot parse", "has expired"}
   889  
   890  	for i := 0; i < len(expirationDates); i++ {
   891  		layout.Expires = expirationDates[i]
   892  		err := VerifyLayoutExpiration(layout)
   893  		if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) {
   894  			t.Errorf("VerifyLayoutExpiration returned '%s', expected '%s' error",
   895  				err, expectedErrors[i])
   896  		}
   897  	}
   898  
   899  	// Test not (yet) expired layout :)
   900  	layout.Expires = "3000-01-01T00:00:00Z"
   901  	err = VerifyLayoutExpiration(layout)
   902  	if err != nil {
   903  		t.Errorf("VerifyLayoutExpiration returned '%s', expected nil", err)
   904  	}
   905  }
   906  
   907  func TestVerifyLayoutSignatures(t *testing.T) {
   908  	mbLayout, err := LoadMetadata("demo.layout")
   909  	if err != nil {
   910  		t.Errorf("unable to load template file: %s", err)
   911  	}
   912  	var layoutKey Key
   913  	if err := layoutKey.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
   914  		t.Errorf("unable to load public key file: %s", err)
   915  	}
   916  
   917  	// Test layout signature verification errors:
   918  	// - Not verification keys (must be at least one)
   919  	// - No signature found for verification key
   920  	layoutKeysList := []map[string]Key{{}, {layoutKey.KeyID: Key{}}}
   921  	expectedErrors := []string{"at least one key", "no signature found for key"}
   922  
   923  	for i := 0; i < len(layoutKeysList); i++ {
   924  		err := VerifyLayoutSignatures(mbLayout, layoutKeysList[i])
   925  		if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) {
   926  			t.Errorf("VerifyLayoutSignatures returned '%s', expected '%s' error",
   927  				err, expectedErrors[i])
   928  		}
   929  	}
   930  
   931  	// Test successful layout signature verification
   932  	err = VerifyLayoutSignatures(mbLayout, map[string]Key{layoutKey.KeyID: layoutKey})
   933  	if err != nil {
   934  		t.Errorf("VerifyLayoutSignatures returned '%s', expected nil",
   935  			err)
   936  	}
   937  }
   938  
   939  func TestSubstituteParamaters(t *testing.T) {
   940  	parameterDictionary := map[string]string{
   941  		"EDITOR":       "vim",
   942  		"NEW_THING":    "new_thing",
   943  		"SOURCE_STEP":  "source_step",
   944  		"SOURCE_THING": "source_thing",
   945  		"UNTAR":        "tar",
   946  	}
   947  
   948  	layout := Layout{
   949  		Type: "_layout",
   950  		Inspect: []Inspection{
   951  			{
   952  				SupplyChainItem: SupplyChainItem{
   953  					Name: "verify-the-thing",
   954  					ExpectedMaterials: [][]string{{"MATCH", "{SOURCE_THING}",
   955  						"WITH", "MATERIALS", "FROM", "{SOURCE_STEP}"}},
   956  					ExpectedProducts: [][]string{{"CREATE", "{NEW_THING}"}},
   957  				},
   958  				Run: []string{"{UNTAR}", "xzf", "foo.tar.gz"},
   959  			},
   960  		},
   961  		Steps: []Step{
   962  			{
   963  				SupplyChainItem: SupplyChainItem{
   964  					Name: "run-command",
   965  					ExpectedMaterials: [][]string{{"MATCH", "{SOURCE_THING}",
   966  						"WITH", "MATERIALS", "FROM", "{SOURCE_STEP}"}},
   967  					ExpectedProducts: [][]string{{"CREATE", "{NEW_THING}"}},
   968  				},
   969  				ExpectedCommand: []string{"{EDITOR}"},
   970  			},
   971  		},
   972  	}
   973  
   974  	newLayout, err := SubstituteParameters(layout, parameterDictionary)
   975  	if err != nil {
   976  		t.Errorf("parameter substitution error: got %s", err)
   977  	}
   978  
   979  	if newLayout.Steps[0].ExpectedCommand[0] != "vim" {
   980  		t.Errorf("parameter substitution failed - expected 'vim', got %s",
   981  			newLayout.Steps[0].ExpectedCommand[0])
   982  	}
   983  
   984  	if newLayout.Steps[0].ExpectedProducts[0][1] != "new_thing" {
   985  		t.Errorf("parameter substitution failed - expected 'new_thing',"+
   986  			" got %s", newLayout.Steps[0].ExpectedProducts[0][1])
   987  	}
   988  
   989  	if newLayout.Steps[0].ExpectedMaterials[0][1] != "source_thing" {
   990  		t.Errorf("parameter substitution failed - expected 'source_thing', "+
   991  			"got %s", newLayout.Steps[0].ExpectedMaterials[0][1])
   992  	}
   993  
   994  	if newLayout.Steps[0].ExpectedMaterials[0][5] != "source_step" {
   995  		t.Errorf("parameter substitution failed - expected 'source_step', "+
   996  			"got %s", newLayout.Steps[0].ExpectedMaterials[0][5])
   997  	}
   998  
   999  	if newLayout.Inspect[0].Run[0] != "tar" {
  1000  		t.Errorf("parameter substitution failed - expected 'tar', got %s",
  1001  			newLayout.Inspect[0].Run[0])
  1002  	}
  1003  
  1004  	if newLayout.Inspect[0].ExpectedProducts[0][1] != "new_thing" {
  1005  		t.Errorf("parameter substitution failed - expected 'new_thing',"+
  1006  			" got %s", newLayout.Inspect[0].ExpectedProducts[0][1])
  1007  	}
  1008  
  1009  	if newLayout.Inspect[0].ExpectedMaterials[0][1] != "source_thing" {
  1010  		t.Errorf("parameter substitution failed - expected 'source_thing', "+
  1011  			"got %s", newLayout.Inspect[0].ExpectedMaterials[0][1])
  1012  	}
  1013  
  1014  	if newLayout.Inspect[0].ExpectedMaterials[0][5] != "source_step" {
  1015  		t.Errorf("parameter substitution failed - expected 'source_step', "+
  1016  			"got %s", newLayout.Inspect[0].ExpectedMaterials[0][5])
  1017  	}
  1018  
  1019  	parameterDictionary = map[string]string{
  1020  		"invalid$": "some_replacement",
  1021  	}
  1022  
  1023  	_, err = SubstituteParameters(layout, parameterDictionary)
  1024  	if err.Error() != "invalid format for parameter" {
  1025  		t.Errorf("invalid parameter format not detected")
  1026  	}
  1027  }
  1028  
  1029  func TestInTotoVerifyWithDirectory(t *testing.T) {
  1030  	layoutPath := "demo.layout"
  1031  	pubKeyPath := "alice.pub"
  1032  	linkDir := "."
  1033  
  1034  	layoutMb, err := LoadMetadata(layoutPath)
  1035  	if err != nil {
  1036  		t.Error(err)
  1037  	}
  1038  
  1039  	var pubKey Key
  1040  	if err := pubKey.LoadKey(pubKeyPath, "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
  1041  		t.Error(err)
  1042  	}
  1043  
  1044  	var layouKeys = map[string]Key{
  1045  		pubKey.KeyID: pubKey,
  1046  	}
  1047  
  1048  	// No error should occur
  1049  	if _, err := InTotoVerifyWithDirectory(layoutMb, layouKeys, linkDir, ".", "",
  1050  		make(map[string]string), [][]byte{}, testOSisWindows()); err != nil {
  1051  		t.Error(err)
  1052  	}
  1053  }
  1054  
  1055  func TestLoadLayoutCertificates(t *testing.T) {
  1056  	certTemplate := &x509.Certificate{
  1057  		Subject: pkix.Name{
  1058  			CommonName:   "step1.example.com",
  1059  			Organization: []string{"example"},
  1060  		},
  1061  	}
  1062  
  1063  	_, intermediate, root, err := createTestCert(certTemplate, x509.Ed25519, time.Hour)
  1064  	assert.Nil(t, err, "unexpected error when creating test cert")
  1065  	rootKey := Key{}
  1066  	err = rootKey.LoadKeyReader(bytes.NewReader(generatePEMBlock(root.Raw, "CERTIFICATE")), "ed25519", []string{"sha512"})
  1067  	assert.Nil(t, err, "unexpected error loading Key for root")
  1068  	intermediateKey := Key{}
  1069  	intermediatePem := generatePEMBlock(intermediate.Raw, "CERTIFICATE")
  1070  	err = intermediateKey.LoadKeyReader(bytes.NewReader(intermediatePem), "ed25519", []string{"sha512"})
  1071  	assert.Nil(t, err, "unexpected error loading Key for intermediate")
  1072  	testLayout := Layout{
  1073  		RootCas: map[string]Key{
  1074  			rootKey.KeyID: rootKey,
  1075  		},
  1076  		IntermediateCas: map[string]Key{
  1077  			intermediateKey.KeyID: intermediateKey,
  1078  		},
  1079  	}
  1080  
  1081  	_, _, err = LoadLayoutCertificates(testLayout, [][]byte{intermediatePem})
  1082  	assert.Nil(t, err, "unexpected error loading layout's certificates")
  1083  
  1084  	// Test with an invalid root in the layout and valid intermediate
  1085  	invalidRootKey := rootKey
  1086  	invalidRootKey.KeyVal.Certificate = "123123123"
  1087  	testLayout.RootCas[rootKey.KeyID] = invalidRootKey
  1088  	_, _, err = LoadLayoutCertificates(testLayout, [][]byte{intermediatePem})
  1089  	assert.NotNil(t, err, "expected error with invalid root key")
  1090  
  1091  	// Test with a valid root but invalid intermediate in the layout
  1092  	testLayout.RootCas[rootKey.KeyID] = rootKey
  1093  	invalidIntermediateKey := intermediateKey
  1094  	invalidIntermediateKey.KeyVal.Certificate = "123123123"
  1095  	testLayout.IntermediateCas[intermediateKey.KeyID] = invalidIntermediateKey
  1096  	_, _, err = LoadLayoutCertificates(testLayout, [][]byte{})
  1097  	assert.NotNil(t, err, "expected error with invalid intermediate key")
  1098  
  1099  	// Now test failure with an invalid extra intermediate
  1100  	testLayout.IntermediateCas[intermediateKey.KeyID] = intermediateKey
  1101  	_, _, err = LoadLayoutCertificates(testLayout, [][]byte{[]byte("123123123")})
  1102  	assert.NotNil(t, err, "expected error with invalid extra intermediates")
  1103  }
  1104  

View as plain text