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

     1  package in_toto
     2  
     3  import (
     4  	"crypto/x509"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"reflect"
    10  	"sort"
    11  	"strings"
    12  	"testing"
    13  )
    14  
    15  func TestInTotoVerifyPass(t *testing.T) {
    16  	layoutPath := "demo.layout"
    17  	pubKeyPath := "alice.pub"
    18  	linkDir := "."
    19  
    20  	var layoutMb Metablock
    21  	if err := layoutMb.Load(layoutPath); err != nil {
    22  		t.Error(err)
    23  	}
    24  
    25  	var pubKey Key
    26  	if err := pubKey.LoadKey(pubKeyPath, "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
    27  		t.Error(err)
    28  	}
    29  
    30  	var layouKeys = map[string]Key{
    31  		pubKey.KeyID: pubKey,
    32  	}
    33  
    34  	// No error should occur
    35  	if _, err := InTotoVerify(layoutMb, layouKeys, linkDir, "",
    36  		make(map[string]string), [][]byte{}); err != nil {
    37  		t.Error(err)
    38  	}
    39  }
    40  
    41  func TestGetSummaryLink(t *testing.T) {
    42  	var demoLayout Metablock
    43  	if err := demoLayout.Load("demo.layout"); err != nil {
    44  		t.Error(err)
    45  	}
    46  	var codeLink Metablock
    47  	if err := codeLink.Load("write-code.776a00e2.link"); err != nil {
    48  		t.Error(err)
    49  	}
    50  	var packageLink Metablock
    51  	if err := packageLink.Load("package.2f89b927.link"); err != nil {
    52  		t.Error(err)
    53  	}
    54  	demoLink := make(map[string]Metablock)
    55  	demoLink["write-code"] = codeLink
    56  	demoLink["package"] = packageLink
    57  
    58  	var summaryLink Metablock
    59  	var err error
    60  	if summaryLink, err = GetSummaryLink(demoLayout.Signed.(Layout),
    61  		demoLink, ""); err != nil {
    62  		t.Error(err)
    63  	}
    64  	if summaryLink.Signed.(Link).Type != codeLink.Signed.(Link).Type {
    65  		t.Errorf("Summary Link isn't of type Link")
    66  	}
    67  	if summaryLink.Signed.(Link).Name != "" {
    68  		t.Errorf("Summary Link name doesn't match. Expected '%s', returned "+
    69  			"'%s", codeLink.Signed.(Link).Name, summaryLink.Signed.(Link).Name)
    70  	}
    71  	if !reflect.DeepEqual(summaryLink.Signed.(Link).Materials,
    72  		codeLink.Signed.(Link).Materials) {
    73  		t.Errorf("Summary Link materials don't match. Expected '%s', "+
    74  			"returned '%s", codeLink.Signed.(Link).Materials,
    75  			summaryLink.Signed.(Link).Materials)
    76  	}
    77  
    78  	if !reflect.DeepEqual(summaryLink.Signed.(Link).Products,
    79  		packageLink.Signed.(Link).Products) {
    80  		t.Errorf("Summary Link products don't match. Expected '%s', "+
    81  			"returned '%s", packageLink.Signed.(Link).Products,
    82  			summaryLink.Signed.(Link).Products)
    83  	}
    84  	if !reflect.DeepEqual(summaryLink.Signed.(Link).Command,
    85  		packageLink.Signed.(Link).Command) {
    86  		t.Errorf("Summary Link command doesn't match. Expected '%s', "+
    87  			"returned '%s", packageLink.Signed.(Link).Command,
    88  			summaryLink.Signed.(Link).Command)
    89  	}
    90  	if !reflect.DeepEqual(summaryLink.Signed.(Link).ByProducts,
    91  		packageLink.Signed.(Link).ByProducts) {
    92  		t.Errorf("Summary Link by-products don't match. Expected '%s', "+
    93  			"returned '%s", packageLink.Signed.(Link).ByProducts,
    94  			summaryLink.Signed.(Link).ByProducts)
    95  	}
    96  }
    97  
    98  func TestVerifySublayouts(t *testing.T) {
    99  	sublayoutName := "sub_layout"
   100  	var aliceKey Key
   101  	if err := aliceKey.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
   102  		t.Errorf("Unable to load Alice's public key")
   103  	}
   104  	sublayoutDirectory := fmt.Sprintf(SublayoutLinkDirFormat, sublayoutName,
   105  		aliceKey.KeyID)
   106  	defer func(sublayoutDirectory string) {
   107  		if err := os.RemoveAll(sublayoutDirectory); err != nil {
   108  			t.Errorf("Unable to remove directory %s: %s", sublayoutDirectory, err)
   109  		}
   110  	}(sublayoutDirectory)
   111  
   112  	if err := os.Mkdir(sublayoutDirectory, 0700); err != nil {
   113  		t.Errorf("Unable to create sublayout directory")
   114  	}
   115  	writeCodePath := path.Join(sublayoutDirectory, "write-code.776a00e2.link")
   116  	if err := os.Link("write-code.776a00e2.link", writeCodePath); err != nil {
   117  		t.Errorf("Unable to link write-code metadata.")
   118  	}
   119  	packagePath := path.Join(sublayoutDirectory, "package.2f89b927.link")
   120  	if err := os.Link("package.2f89b927.link", packagePath); err != nil {
   121  		t.Errorf("Unable to link package metadata")
   122  	}
   123  
   124  	var superLayoutMb Metablock
   125  	if err := superLayoutMb.Load("super.layout"); err != nil {
   126  		t.Errorf("Unable to load super layout")
   127  	}
   128  
   129  	stepsMetadata := make(map[string]map[string]Metablock)
   130  	var err error
   131  	if stepsMetadata, err = LoadLinksForLayout(superLayoutMb.Signed.(Layout),
   132  		"."); err != nil {
   133  		t.Errorf("Unable to load link metadata for super layout")
   134  	}
   135  
   136  	rootCertPool, intermediateCertPool, err := LoadLayoutCertificates(superLayoutMb.Signed.(Layout), [][]byte{})
   137  	if err != nil {
   138  		t.Errorf("Unable to load layout certificates")
   139  	}
   140  
   141  	stepsMetadataVerified := make(map[string]map[string]Metablock)
   142  	if stepsMetadataVerified, err = VerifyLinkSignatureThesholds(
   143  		superLayoutMb.Signed.(Layout), stepsMetadata, rootCertPool, intermediateCertPool); err != nil {
   144  		t.Errorf("Unable to verify link threshold values: %v", err)
   145  	}
   146  
   147  	result, err := VerifySublayouts(superLayoutMb.Signed.(Layout),
   148  		stepsMetadataVerified, ".", [][]byte{})
   149  	if err != nil {
   150  		t.Errorf("Unable to verify sublayouts")
   151  	}
   152  
   153  	for _, stepData := range result {
   154  		for _, metadata := range stepData {
   155  			if _, ok := metadata.Signed.(Link); !ok {
   156  				t.Errorf("Sublayout expansion error: found non link")
   157  			}
   158  		}
   159  	}
   160  }
   161  
   162  func TestRunInspections(t *testing.T) {
   163  	// Load layout template used as basis for all tests
   164  	var mb Metablock
   165  	if err := mb.Load("demo.layout"); err != nil {
   166  		t.Errorf("Unable to parse template file: %s", err)
   167  	}
   168  	layout := mb.Signed.(Layout)
   169  
   170  	// Test 1
   171  	// Successfully run two inspections foo and bar, testing that each generates
   172  	// a link file and they record the correct materials and products.
   173  	layout.Inspect = []Inspection{
   174  		{
   175  			SupplyChainItem: SupplyChainItem{Name: "foo"},
   176  			Run:             []string{"sh", "-c", "true"},
   177  		},
   178  		{
   179  			SupplyChainItem: SupplyChainItem{Name: "bar"},
   180  			Run:             []string{"sh", "-c", "true"},
   181  		},
   182  	}
   183  
   184  	// Make a list of files in current dir (all must be recorded as artifacts)
   185  	availableFiles, _ := filepath.Glob("*")
   186  	result, err := RunInspections(layout)
   187  
   188  	// Error must be nil
   189  	if err != nil {
   190  		t.Errorf("RunInspections returned %s as error, expected nil.",
   191  			err)
   192  	}
   193  
   194  	// Assert contents of inspection link metadata for both inspections
   195  	for _, inspectionName := range []string{"foo", "bar"} {
   196  		// Available files must be sorted after each inspection because the link
   197  		// file is added below
   198  		sort.Strings(availableFiles)
   199  		// Compare material and products (only file names) to files that were
   200  		// in the directory before calling RunInspections
   201  		materialNames := InterfaceKeyStrings(result[inspectionName].Signed.(Link).Materials)
   202  		productNames := InterfaceKeyStrings(result[inspectionName].Signed.(Link).Products)
   203  		sort.Strings(materialNames)
   204  		sort.Strings(productNames)
   205  		if !reflect.DeepEqual(materialNames, availableFiles) ||
   206  			!reflect.DeepEqual(productNames, availableFiles) {
   207  			t.Errorf("RunInspections recorded materials and products '%s' and %s'"+
   208  				" for insepction '%s', expected '%s' and '%s'.", materialNames,
   209  				productNames, inspectionName, availableFiles, availableFiles)
   210  		}
   211  		linkName := inspectionName + ".link"
   212  		// Append link created by an inspection to available files because it
   213  		// is recorded by succeeding inspections
   214  		availableFiles = append(availableFiles, linkName)
   215  
   216  		// Remove generated inspection link
   217  		err = os.Remove(linkName)
   218  		if os.IsNotExist(err) {
   219  			t.Errorf("RunInspections didn't generate expected '%s'", linkName)
   220  		}
   221  	}
   222  
   223  	// Test 2
   224  	// Fail RunInspections due to inexistent command
   225  	layout.Inspect = []Inspection{
   226  		{
   227  			SupplyChainItem: SupplyChainItem{Name: "foo"},
   228  			Run:             []string{"command-does-not-exist"},
   229  		},
   230  	}
   231  
   232  	result, err = RunInspections(layout)
   233  	if result != nil || err == nil {
   234  		t.Errorf("RunInspections returned '(%s, %s)', expected"+
   235  			" '(nil, *exec.Error)'", result, err)
   236  	}
   237  
   238  	// Test 2
   239  	// Fail RunInspections due to non-zero exiting command
   240  	layout.Inspect = []Inspection{
   241  		{
   242  			SupplyChainItem: SupplyChainItem{Name: "foo"},
   243  			Run:             []string{"sh", "-c", "false"},
   244  		},
   245  	}
   246  	result, err = RunInspections(layout)
   247  	if result != nil || err == nil {
   248  		t.Errorf("RunInspections returned '(%s, %s)', expected"+
   249  			" '(nil, *exec.Error)'", result, err)
   250  	}
   251  }
   252  
   253  func TestVerifyArtifacts(t *testing.T) {
   254  	items := []interface{}{
   255  		Step{
   256  			SupplyChainItem: SupplyChainItem{
   257  				Name: "foo",
   258  				ExpectedMaterials: [][]string{
   259  					{"DELETE", "foo-delete"},
   260  					{"MODIFY", "foo-modify"},
   261  					{"MATCH", "foo-match", "WITH", "MATERIALS", "FROM", "foo"}, // not-modify
   262  					{"ALLOW", "foo-allow"},
   263  					{"DISALLOW", "*"},
   264  				},
   265  				ExpectedProducts: [][]string{
   266  					{"CREATE", "foo-create"},
   267  					{"MODIFY", "foo-modify"},
   268  					{"MATCH", "foo-match", "WITH", "MATERIALS", "FROM", "foo"}, // not-modify
   269  					{"REQUIRE", "foo-allow"},
   270  					{"ALLOW", "foo-allow"},
   271  					{"DISALLOW", "*"},
   272  				},
   273  			},
   274  		},
   275  	}
   276  
   277  	itemsMetadata := map[string]Metablock{
   278  		"foo": {
   279  			Signed: Link{
   280  				Name: "foo",
   281  				Materials: map[string]interface{}{
   282  					"foo-delete": map[string]interface{}{"sha265": "abc"},
   283  					"foo-modify": map[string]interface{}{"sha265": "abc"},
   284  					"foo-match":  map[string]interface{}{"sha265": "abc"},
   285  					"foo-allow":  map[string]interface{}{"sha265": "abc"},
   286  				},
   287  				Products: map[string]interface{}{
   288  					"foo-create": map[string]interface{}{"sha265": "abc"},
   289  					"foo-modify": map[string]interface{}{"sha265": "abcdef"},
   290  					"foo-match":  map[string]interface{}{"sha265": "abc"},
   291  					"foo-allow":  map[string]interface{}{"sha265": "abc"},
   292  				},
   293  			},
   294  		},
   295  	}
   296  
   297  	err := VerifyArtifacts(items, itemsMetadata)
   298  	if err != nil {
   299  		t.Errorf("VerifyArtifacts returned '%s', expected no error", err)
   300  	}
   301  }
   302  
   303  func TestVerifyArtifactErrors(t *testing.T) {
   304  	// Test error cases for combinations of Step and Inspection items and
   305  	// material and product rules:
   306  	// - Item must be one of step or inspection
   307  	// - Can't find link metadata for step
   308  	// - Can't find link metadata for inspection
   309  	// - Wrong step expected material
   310  	// - Wrong step expected product
   311  	// - Wrong inspection expected material
   312  	// - Wrong inspection expected product
   313  	// - Disallowed material in step
   314  	// - Disallowed product in step
   315  	// - Disallowed material in inspection
   316  	// - Disallowed product in inspection
   317  	// - Required but missing material in step
   318  	// - Required but missing product in step
   319  	// - Required but missing material in inspection
   320  	// - Required but missing product in inspection
   321  	items := [][]interface{}{
   322  		{nil},
   323  		{Step{SupplyChainItem: SupplyChainItem{Name: "foo"}}},
   324  		{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo"}}},
   325  		{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"INVALID", "rule"}}}}},
   326  		{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"INVALID", "rule"}}}}},
   327  		{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"INVALID", "rule"}}}}},
   328  		{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"INVALID", "rule"}}}}},
   329  		{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"DISALLOW", "*"}}}}},
   330  		{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"DISALLOW", "*"}}}}},
   331  		{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"DISALLOW", "*"}}}}},
   332  		{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"DISALLOW", "*"}}}}},
   333  		{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"REQUIRE", "foo"}}}}},
   334  		{Step{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"REQUIRE", "foo"}}}}},
   335  		{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedMaterials: [][]string{{"REQUIRE", "foo"}}}}},
   336  		{Inspection{SupplyChainItem: SupplyChainItem{Name: "foo", ExpectedProducts: [][]string{{"REQUIRE", "foo"}}}}},
   337  	}
   338  	itemsMetadata := []map[string]Metablock{
   339  		{},
   340  		{},
   341  		{},
   342  		{"foo": {Signed: Link{Name: "foo"}}},
   343  		{"foo": {Signed: Link{Name: "foo"}}},
   344  		{"foo": {Signed: Link{Name: "foo"}}},
   345  		{"foo": {Signed: Link{Name: "foo"}}},
   346  		{"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   347  		{"foo": {Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   348  		{"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   349  		{"foo": {Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   350  		{"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   351  		{"foo": {Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   352  		{"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   353  		{"foo": {Signed: Link{Name: "foo", Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   354  	}
   355  	errorPart := []string{
   356  		"item of invalid type",
   357  		"could not find metadata",
   358  		"could not find metadata",
   359  		"rule format",
   360  		"rule format",
   361  		"rule format",
   362  		"rule format",
   363  		"materials [foo.py] disallowed by rule",
   364  		"products [foo.py] disallowed by rule",
   365  		"materials [foo.py] disallowed by rule",
   366  		"products [foo.py] disallowed by rule",
   367  		"materials in REQUIRE 'foo'",
   368  		"products in REQUIRE 'foo'",
   369  		"materials in REQUIRE 'foo'",
   370  		"products in REQUIRE 'foo'",
   371  	}
   372  
   373  	for i := 0; i < len(items); i++ {
   374  		err := VerifyArtifacts(items[i], itemsMetadata[i])
   375  		if err == nil || !strings.Contains(err.Error(), errorPart[i]) {
   376  			t.Errorf("VerifyArtifacts returned '%s', expected '%s' error",
   377  				err, errorPart[i])
   378  		}
   379  	}
   380  }
   381  
   382  func TestVerifyMatchRule(t *testing.T) {
   383  	// Test MatchRule queue processing:
   384  	// - Can't find destination link (invalid rule) -> queue unmodified (empty)
   385  	// - Can't find destination link (empty metadata map) -> queue unmodified
   386  	// - Match material foo.py -> remove from queue
   387  	// - Match material foo.py with foo.d/foo.py -> remove from queue
   388  	// - Match material foo.d/foo.py with foo.py -> remove from queue
   389  	// - Don't match material (different name) -> queue unmodified
   390  	// - Don't match material (different hash) -> queue unmodified
   391  	ruleData := []map[string]string{
   392  		{},
   393  		{"pattern": "*", "dstName": "foo", "dstType": "materials"},
   394  		{"pattern": "*", "dstName": "foo", "dstType": "materials"},
   395  		{"pattern": "*", "dstName": "foo", "dstType": "materials", "dstPrefix": "foo.d"},
   396  		{"pattern": "*", "dstName": "foo", "dstType": "materials", "srcPrefix": "foo.d"},
   397  		{"pattern": "*", "dstName": "foo", "dstType": "materials"},
   398  		{"pattern": "*", "dstName": "foo", "dstType": "materials"},
   399  	}
   400  	srcArtifacts := []map[string]interface{}{
   401  		{},
   402  		{"foo.py": map[string]interface{}{"sha265": "abc"}},
   403  		{"foo.py": map[string]interface{}{"sha265": "abc"}},
   404  		{"foo.py": map[string]interface{}{"sha265": "abc"}},
   405  		{"foo.d/foo.py": map[string]interface{}{"sha265": "abc"}},
   406  		{"foo.py": map[string]interface{}{"sha265": "dead"}},
   407  		{"bar.py": map[string]interface{}{"sha265": "abc"}},
   408  	}
   409  	// queue[i] = InterfaceKeyStrings(srcArtifacts[i])
   410  	itemsMetadata := []map[string]Metablock{
   411  		{},
   412  		{},
   413  		{"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   414  		{"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.d/foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   415  		{"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   416  		{"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   417  		{"foo": {Signed: Link{Name: "foo", Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}}},
   418  	}
   419  	expected := []Set{
   420  		NewSet(),
   421  		NewSet(),
   422  		NewSet("foo.py"),
   423  		NewSet("foo.py"),
   424  		NewSet("foo.d/foo.py"),
   425  		NewSet(),
   426  		NewSet(),
   427  	}
   428  
   429  	for i := 0; i < len(ruleData); i++ {
   430  
   431  		queue := NewSet(InterfaceKeyStrings(srcArtifacts[i])...)
   432  		result := verifyMatchRule(ruleData[i], srcArtifacts[i], queue,
   433  			itemsMetadata[i])
   434  		if !reflect.DeepEqual(result, expected[i]) {
   435  			t.Errorf("verifyMatchRule returned '%s', expected '%s'", result,
   436  				expected[i])
   437  		}
   438  	}
   439  }
   440  
   441  func TestReduceStepsMetadata(t *testing.T) {
   442  	var mb Metablock
   443  	if err := mb.Load("demo.layout"); err != nil {
   444  		t.Errorf("Unable to parse template file: %s", err)
   445  	}
   446  	layout := mb.Signed.(Layout)
   447  	layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{Name: "foo"}}}
   448  
   449  	// Test 1: Successful reduction of multiple links for one step (foo)
   450  	stepsMetadata := map[string]map[string]Metablock{
   451  		"foo": {
   452  			"a": Metablock{Signed: Link{
   453  				Type:      "link",
   454  				Name:      "foo",
   455  				Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}},
   456  				Products:  map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "cde"}},
   457  			}},
   458  			"b": Metablock{Signed: Link{
   459  				Type:      "link",
   460  				Name:      "foo",
   461  				Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}},
   462  				Products:  map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "cde"}},
   463  			}},
   464  		},
   465  	}
   466  
   467  	result, err := ReduceStepsMetadata(layout, stepsMetadata)
   468  	if !reflect.DeepEqual(result["foo"], stepsMetadata["foo"]["a"]) || err != nil {
   469  		t.Errorf("ReduceStepsMetadata returned (%s, %s), expected (%s, nil)"+
   470  			" and a 'different artifacts' error", result, err, stepsMetadata["foo"]["a"])
   471  	}
   472  
   473  	// Test 2: Test different error scenarios when creating one link out of
   474  	// multiple links for the same step:
   475  	// - Different materials (hash)
   476  	// - Different materials (name)
   477  	// - Different products (hash)
   478  	// - Different products (name)
   479  	stepsMetadataList := []map[string]map[string]Metablock{
   480  		{"foo": {
   481  			"a": Metablock{Signed: Link{Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}},
   482  			"b": Metablock{Signed: Link{Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "def"}}}},
   483  		}},
   484  		{"foo": {
   485  			"a": Metablock{Signed: Link{Materials: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}},
   486  			"b": Metablock{Signed: Link{Materials: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "abc"}}}},
   487  		}},
   488  		{"foo": {
   489  			"a": Metablock{Signed: Link{Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}},
   490  			"b": Metablock{Signed: Link{Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "def"}}}},
   491  		}},
   492  		{"foo": {
   493  			"a": Metablock{Signed: Link{Products: map[string]interface{}{"foo.py": map[string]interface{}{"sha265": "abc"}}}},
   494  			"b": Metablock{Signed: Link{Products: map[string]interface{}{"bar.py": map[string]interface{}{"sha265": "abc"}}}},
   495  		}},
   496  	}
   497  
   498  	for i := 0; i < len(stepsMetadataList); i++ {
   499  		result, err := ReduceStepsMetadata(layout, stepsMetadataList[i])
   500  		if err == nil || !strings.Contains(err.Error(), "different artifacts") {
   501  			t.Errorf("ReduceStepsMetadata returned (%s, %s), expected an empty map"+
   502  				" and a 'different artifacts' error", result, err)
   503  		}
   504  	}
   505  
   506  	// Panic due to missing link metadata for step (final product verification
   507  	// should gracefully error earlier)
   508  	defer func() {
   509  		if r := recover(); r == nil {
   510  			t.Errorf("ReduceStepsMetadata should have panicked due to missing link" +
   511  				" metadata")
   512  		}
   513  	}()
   514  	if _, err := ReduceStepsMetadata(layout, nil); err != nil {
   515  		t.Errorf("Error while calling ReduceStepsMetadata: %s", err)
   516  	}
   517  	//NOTE: This test won't get any further because of panic
   518  }
   519  
   520  func TestVerifyStepCommandAlignment(t *testing.T) {
   521  	var mb Metablock
   522  	if err := mb.Load("demo.layout"); err != nil {
   523  		t.Errorf("Unable to load template file: %s", err)
   524  	}
   525  	layout := mb.Signed.(Layout)
   526  	layout.Steps = []Step{
   527  		{
   528  			SupplyChainItem: SupplyChainItem{Name: "foo"},
   529  			ExpectedCommand: []string{"rm", "-rf", "."},
   530  		},
   531  	}
   532  
   533  	stepsMetadata := map[string]map[string]Metablock{
   534  		"foo": {"a": Metablock{Signed: Link{Command: []string{"rm", "-rf", "/"}}}},
   535  	}
   536  	// Test warning due to non-aligning commands
   537  	// FIXME: Assert warning?
   538  	fmt.Printf("[begin test warning output]\n")
   539  	VerifyStepCommandAlignment(layout, stepsMetadata)
   540  	fmt.Printf("[end test warning output]\n")
   541  
   542  	// Panic due to missing link metadata for step (final product verification
   543  	// should gracefully error earlier)
   544  	defer func() {
   545  		if r := recover(); r == nil {
   546  			t.Errorf("ReduceStepsMetadata should have panicked due to missing link" +
   547  				" metadata")
   548  		}
   549  	}()
   550  	VerifyStepCommandAlignment(layout, nil)
   551  	//NOTE: This test won't get any further because of panic
   552  }
   553  
   554  func TestVerifyLinkSignatureThesholds(t *testing.T) {
   555  	keyID1 := "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498"
   556  	keyID2 := "776a00e29f3559e0141b3b096f696abc6cfb0c657ab40f441132b345b08453f5"
   557  	keyID3 := "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabca"
   558  
   559  	var mb Metablock
   560  	if err := mb.Load("demo.layout"); err != nil {
   561  		t.Errorf("Unable to load template file: %s", err)
   562  	}
   563  	layout := mb.Signed.(Layout)
   564  
   565  	layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{
   566  		Name: "foo"},
   567  		Threshold: 2,
   568  		PubKeys:   []string{keyID1, keyID2, keyID3}}}
   569  
   570  	var mbLink1 Metablock
   571  	if err := mbLink1.Load("foo.2f89b927.link"); err != nil {
   572  		t.Errorf("Unable to load link file: %s", err)
   573  	}
   574  	var mbLink2 Metablock
   575  	if err := mbLink2.Load("foo.776a00e2.link"); err != nil {
   576  		t.Errorf("Unable to load link file: %s", err)
   577  	}
   578  	var mbLinkBroken Metablock
   579  	if err := mbLinkBroken.Load("foo.776a00e2.link"); err != nil {
   580  		t.Errorf("Unable to load link file: %s", err)
   581  	}
   582  	mbLinkBroken.Signatures[0].Sig = "breaksignature"
   583  
   584  	// Test less then threshold distinct valid links errors:
   585  	// - Missing step name in step metadata map
   586  	// - Missing links for step
   587  	// - Less than threshold links for step
   588  	// - Less than threshold distinct links for step
   589  	// - Less than threshold validly signed links for step
   590  	stepsMetadata := []map[string]map[string]Metablock{
   591  		{"bar": nil},
   592  		{"foo": nil},
   593  		{"foo": {keyID1: mbLink1}},
   594  		{"foo": {keyID1: mbLink1, keyID2: mbLink1}},
   595  		{"foo": {keyID1: mbLink1, keyID2: mbLinkBroken}},
   596  	}
   597  	for i := 0; i < len(stepsMetadata); i++ {
   598  		result, err := VerifyLinkSignatureThesholds(layout, stepsMetadata[i], x509.NewCertPool(), x509.NewCertPool())
   599  		if err == nil {
   600  			t.Errorf("VerifyLinkSignatureThesholds returned (%s, %s), expected"+
   601  				" 'not enough distinct valid links' error.", result, err)
   602  		}
   603  	}
   604  
   605  	// Test successfully return threshold distinct valid links:
   606  	// - Threshold 2, two valid links
   607  	// - Threshold 2, two valid links, one invalid link ignored
   608  	stepsMetadata = []map[string]map[string]Metablock{
   609  		{"foo": {keyID1: mbLink1, keyID2: mbLink2}},
   610  		{"foo": {keyID1: mbLink1, keyID2: mbLink2, keyID3: mbLinkBroken}},
   611  	}
   612  	for i := 0; i < len(stepsMetadata); i++ {
   613  		result, err := VerifyLinkSignatureThesholds(layout, stepsMetadata[i], x509.NewCertPool(), x509.NewCertPool())
   614  		validLinks, ok := result["foo"]
   615  		if !ok || len(validLinks) != 2 {
   616  			t.Errorf("VerifyLinkSignatureThesholds returned (%s, %s), expected"+
   617  				" a map of two valid foo links.", result, err)
   618  		}
   619  	}
   620  }
   621  
   622  func TestLoadLinksForLayout(t *testing.T) {
   623  	keyID1 := "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498"
   624  	keyID2 := "776a00e29f3559e0141b3b096f696abc6cfb0c657ab40f441132b345b08453f5"
   625  	var mb Metablock
   626  	if err := mb.Load("demo.layout"); err != nil {
   627  		t.Errorf("Unable to load template file: %s", err)
   628  	}
   629  	layout := mb.Signed.(Layout)
   630  
   631  	layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{
   632  		Name: "foo"},
   633  		Threshold: 2,
   634  		PubKeys:   []string{keyID1, keyID2}}}
   635  
   636  	// Test successfully load two links for layout (one step foo, threshold 2)
   637  	result, err := LoadLinksForLayout(layout, ".")
   638  	links, ok := result["foo"]
   639  	if !ok || len(links) != 2 {
   640  		t.Errorf("VerifyLoadLinksForLayout returned (%s, %s), expected"+
   641  			" a map of two foo links.", result, err)
   642  	}
   643  
   644  	// Test threshold error, can't find enough links for step
   645  	keyID3 := "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabca"
   646  	layout.Steps = []Step{{SupplyChainItem: SupplyChainItem{
   647  		Name: "foo"},
   648  		Threshold: 3,
   649  		PubKeys:   []string{keyID1, keyID2, keyID3}}}
   650  	result, err = LoadLinksForLayout(layout, ".")
   651  	if err == nil {
   652  		t.Errorf("VerifyLoadLinksForLayout returned (%s, %s), expected"+
   653  			" 'not enough links' error.", result, err)
   654  	}
   655  }
   656  
   657  func TestVerifyLayoutExpiration(t *testing.T) {
   658  	var mb Metablock
   659  	if err := mb.Load("demo.layout"); err != nil {
   660  		t.Errorf("Unable to load template file: %s", err)
   661  	}
   662  	layout := mb.Signed.(Layout)
   663  
   664  	// Test layout expiration check failure:
   665  	// - invalid date
   666  	// - expired date
   667  	expirationDates := []string{"bad date", "1970-01-01T00:00:00Z"}
   668  	expectedErrors := []string{"cannot parse", "has expired"}
   669  
   670  	for i := 0; i < len(expirationDates); i++ {
   671  		layout.Expires = expirationDates[i]
   672  		err := VerifyLayoutExpiration(layout)
   673  		if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) {
   674  			t.Errorf("VerifyLayoutExpiration returned '%s', expected '%s' error",
   675  				err, expectedErrors[i])
   676  		}
   677  	}
   678  
   679  	// Test not (yet) expired layout :)
   680  	layout.Expires = "3000-01-01T00:00:00Z"
   681  	err := VerifyLayoutExpiration(layout)
   682  	if err != nil {
   683  		t.Errorf("VerifyLayoutExpiration returned '%s', expected nil", err)
   684  	}
   685  }
   686  
   687  func TestVerifyLayoutSignatures(t *testing.T) {
   688  	var mbLayout Metablock
   689  	if err := mbLayout.Load("demo.layout"); err != nil {
   690  		t.Errorf("Unable to load template file: %s", err)
   691  	}
   692  	var layoutKey Key
   693  	if err := layoutKey.LoadKey("alice.pub", "rsassa-pss-sha256", []string{"sha256", "sha512"}); err != nil {
   694  		t.Errorf("Unable to load public key file: %s", err)
   695  	}
   696  
   697  	// Test layout signature verification errors:
   698  	// - Not verification keys (must be at least one)
   699  	// - No signature found for verification key
   700  	layoutKeysList := []map[string]Key{{}, {layoutKey.KeyID: Key{}}}
   701  	expectedErrors := []string{"at least one key", "No signature found for key"}
   702  
   703  	for i := 0; i < len(layoutKeysList); i++ {
   704  		err := VerifyLayoutSignatures(mbLayout, layoutKeysList[i])
   705  		if err == nil || !strings.Contains(err.Error(), expectedErrors[i]) {
   706  			t.Errorf("VerifyLayoutSignatures returned '%s', expected '%s' error",
   707  				err, expectedErrors[i])
   708  		}
   709  	}
   710  
   711  	// Test successful layout signature verification
   712  	err := VerifyLayoutSignatures(mbLayout, map[string]Key{layoutKey.KeyID: layoutKey})
   713  	if err != nil {
   714  		t.Errorf("VerifyLayoutSignatures returned '%s', expected nil",
   715  			err)
   716  	}
   717  }
   718  
   719  func TestSubstituteParamaters(t *testing.T) {
   720  	parameterDictionary := map[string]string{
   721  		"EDITOR":       "vim",
   722  		"NEW_THING":    "new_thing",
   723  		"SOURCE_STEP":  "source_step",
   724  		"SOURCE_THING": "source_thing",
   725  		"UNTAR":        "tar",
   726  	}
   727  
   728  	layout := Layout{
   729  		Type: "_layout",
   730  		Inspect: []Inspection{
   731  			{
   732  				SupplyChainItem: SupplyChainItem{
   733  					Name: "verify-the-thing",
   734  					ExpectedMaterials: [][]string{{"MATCH", "{SOURCE_THING}",
   735  						"WITH", "MATERIALS", "FROM", "{SOURCE_STEP}"}},
   736  					ExpectedProducts: [][]string{{"CREATE", "{NEW_THING}"}},
   737  				},
   738  				Run: []string{"{UNTAR}", "xzf", "foo.tar.gz"},
   739  			},
   740  		},
   741  		Steps: []Step{
   742  			{
   743  				SupplyChainItem: SupplyChainItem{
   744  					Name: "run-command",
   745  					ExpectedMaterials: [][]string{{"MATCH", "{SOURCE_THING}",
   746  						"WITH", "MATERIALS", "FROM", "{SOURCE_STEP}"}},
   747  					ExpectedProducts: [][]string{{"CREATE", "{NEW_THING}"}},
   748  				},
   749  				ExpectedCommand: []string{"{EDITOR}"},
   750  			},
   751  		},
   752  	}
   753  
   754  	newLayout, err := SubstituteParameters(layout, parameterDictionary)
   755  	if err != nil {
   756  		t.Errorf("parameter substitution error: got %s", err)
   757  	}
   758  
   759  	if newLayout.Steps[0].ExpectedCommand[0] != "vim" {
   760  		t.Errorf("parameter substitution failed - expected 'vim', got %s",
   761  			newLayout.Steps[0].ExpectedCommand[0])
   762  	}
   763  
   764  	if newLayout.Steps[0].ExpectedProducts[0][1] != "new_thing" {
   765  		t.Errorf("parameter substitution failed - expected 'new_thing',"+
   766  			" got %s", newLayout.Steps[0].ExpectedProducts[0][1])
   767  	}
   768  
   769  	if newLayout.Steps[0].ExpectedMaterials[0][1] != "source_thing" {
   770  		t.Errorf("parameter substitution failed - expected 'source_thing', "+
   771  			"got %s", newLayout.Steps[0].ExpectedMaterials[0][1])
   772  	}
   773  
   774  	if newLayout.Steps[0].ExpectedMaterials[0][5] != "source_step" {
   775  		t.Errorf("parameter substitution failed - expected 'source_step', "+
   776  			"got %s", newLayout.Steps[0].ExpectedMaterials[0][5])
   777  	}
   778  
   779  	if newLayout.Inspect[0].Run[0] != "tar" {
   780  		t.Errorf("parameter substitution failed - expected 'tar', got %s",
   781  			newLayout.Inspect[0].Run[0])
   782  	}
   783  
   784  	if newLayout.Inspect[0].ExpectedProducts[0][1] != "new_thing" {
   785  		t.Errorf("parameter substitution failed - expected 'new_thing',"+
   786  			" got %s", newLayout.Inspect[0].ExpectedProducts[0][1])
   787  	}
   788  
   789  	if newLayout.Inspect[0].ExpectedMaterials[0][1] != "source_thing" {
   790  		t.Errorf("parameter substitution failed - expected 'source_thing', "+
   791  			"got %s", newLayout.Inspect[0].ExpectedMaterials[0][1])
   792  	}
   793  
   794  	if newLayout.Inspect[0].ExpectedMaterials[0][5] != "source_step" {
   795  		t.Errorf("parameter substitution failed - expected 'source_step', "+
   796  			"got %s", newLayout.Inspect[0].ExpectedMaterials[0][5])
   797  	}
   798  
   799  	parameterDictionary = map[string]string{
   800  		"invalid$": "some_replacement",
   801  	}
   802  
   803  	_, err = SubstituteParameters(layout, parameterDictionary)
   804  	if err.Error() != "invalid format for parameter" {
   805  		t.Errorf("invalid parameter format not detected")
   806  	}
   807  }