github.com/in-toto/in-toto-golang@v0.9.1-0.20240517212500-990269f763cf/in_toto/runlib_test.go (about)

     1  package in_toto
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  	"reflect"
    11  	"runtime"
    12  	"sort"
    13  	"testing"
    14  
    15  	"github.com/secure-systems-lab/go-securesystemslib/dsse"
    16  	"github.com/stretchr/testify/assert"
    17  )
    18  
    19  // Helper function checking whether running environment is Windows
    20  func testOSisWindows() bool {
    21  	os := runtime.GOOS
    22  	return os == "windows"
    23  }
    24  
    25  func TestRecordArtifact(t *testing.T) {
    26  	// Test successfully record one artifact
    27  	result, err := RecordArtifact("foo.tar.gz", []string{"sha256"}, testOSisWindows())
    28  	expected := HashObj{
    29  		"sha256": "52947cb78b91ad01fe81cd6aef42d1f6817e92b9e6936c1e5aabb7c98514f355",
    30  	}
    31  	if !reflect.DeepEqual(result, expected) || err != nil {
    32  		t.Errorf("RecordArtifact returned '(%s, %s)', expected '(%s, nil)'",
    33  			result, err, expected)
    34  	}
    35  
    36  	// Test error by recording nonexistent artifact
    37  	result, err = RecordArtifact("file-does-not-exist", []string{"sha256"}, testOSisWindows())
    38  	if !os.IsNotExist(err) {
    39  		t.Errorf("RecordArtifact returned '(%s, %s)', expected '(nil, %s)'",
    40  			result, err, os.ErrNotExist)
    41  	}
    42  
    43  	result, err = RecordArtifact("foo.tar.gz", []string{"invalid"}, testOSisWindows())
    44  	if !errors.Is(err, ErrUnsupportedHashAlgorithm) {
    45  		t.Errorf("RecordArtifact returned '(%s, %s)', expected '(nil, %s)'", result, err, ErrUnsupportedHashAlgorithm)
    46  	}
    47  }
    48  
    49  // copy helper function for building more complex test cases
    50  // for our TestGitPathSpec
    51  func copy(src, dst string) (int64, error) {
    52  	sourceFileStat, err := os.Stat(src)
    53  	if err != nil {
    54  		return 0, err
    55  	}
    56  
    57  	if !sourceFileStat.Mode().IsRegular() {
    58  		return 0, fmt.Errorf("%s is not a regular file", src)
    59  	}
    60  
    61  	source, err := os.Open(src)
    62  	if err != nil {
    63  		return 0, err
    64  	}
    65  	defer source.Close()
    66  
    67  	destination, err := os.Create(dst)
    68  	if err != nil {
    69  		return 0, err
    70  	}
    71  	defer destination.Close()
    72  	nBytes, err := io.Copy(destination, source)
    73  	return nBytes, err
    74  }
    75  
    76  func TestGitPathSpec(t *testing.T) {
    77  	// Create a more complex test scenario via building subdirectories
    78  	// and copying existing files.
    79  	directoriesToBeCreated := []string{
    80  		"pathSpecTest",
    81  		"pathSpecTest/alpha",
    82  		"pathSpecTest/beta",
    83  		"pathSpecTest/alpha/charlie",
    84  	}
    85  	for _, v := range directoriesToBeCreated {
    86  		if err := os.Mkdir(v, 0700); err != nil {
    87  			t.Errorf("could not create tmpdir: %s", err)
    88  		}
    89  	}
    90  	filesToBeCreated := map[string]string{
    91  		"heidi.pub":  "pathSpecTest/heidi.pub",
    92  		"foo.tar.gz": "pathSpecTest/beta/foo.tar.gz",
    93  		"dan":        "pathSpecTest/alpha/charlie/dan",
    94  		"dan.pub":    "pathSpecTest/beta/dan.pub",
    95  	}
    96  	for k, v := range filesToBeCreated {
    97  		_, err := copy(k, v)
    98  		if err != nil {
    99  			t.Errorf("could not copy file: %s", err)
   100  		}
   101  	}
   102  
   103  	expected := map[string]HashObj{}
   104  	// Let's start our test in the test/data directory
   105  	result, err := RecordArtifacts([]string{"pathSpecTest"}, []string{"sha256"}, []string{
   106  		"*.pub",           // Match all .pub files (even the ones in subdirectories)
   107  		"beta/foo.tar.gz", // Match full path
   108  		"alpha/**",        // Match all directories and files beneath alpha
   109  	}, nil, testOSisWindows(), false)
   110  	if !reflect.DeepEqual(result, expected) {
   111  		t.Errorf("RecordArtifacts returned '(%s, %s)', expected '(%s, nil)'",
   112  			result, err, expected)
   113  	}
   114  
   115  	// clean up
   116  	err = os.RemoveAll("pathSpecTest")
   117  	if err != nil {
   118  		t.Errorf("could not clean up pathSpecTest directory: %s", err)
   119  	}
   120  }
   121  
   122  // TestSymlinkToFile checks if we can follow symlinks to a file
   123  // Note: Symlink files are invisible for InToto right now.
   124  // Therefore if we have a symlink like: foo.tar.gz.sym -> foo.tar.gz
   125  // We will only calculate the hash for for.tar.gz
   126  // The symlink will not be added to the list right now, nor will we calculate a checksum for it.
   127  func TestSymlinkToFile(t *testing.T) {
   128  	if err := os.Symlink("foo.tar.gz", "foo.tar.gz.sym"); err != nil {
   129  		t.Errorf("could not create a symlink: %s", err)
   130  	}
   131  
   132  	expected := map[string]HashObj{
   133  		"foo.tar.gz.sym": {
   134  			"sha256": "52947cb78b91ad01fe81cd6aef42d1f6817e92b9e6936c1e5aabb7c98514f355",
   135  		},
   136  	}
   137  	result, err := RecordArtifacts([]string{"foo.tar.gz.sym"}, []string{"sha256"}, nil, nil, testOSisWindows(), false)
   138  	if !reflect.DeepEqual(result, expected) {
   139  		t.Errorf("RecordArtifacts returned '(%s, %s)', expected '(%s, nil)'",
   140  			result, err, expected)
   141  	}
   142  
   143  	if err := os.Remove("foo.tar.gz.sym"); err != nil {
   144  		t.Errorf("could not remove foo.tar.gz.sym: %s", err)
   145  	}
   146  }
   147  
   148  // TestIndirectSymlinkCycles() tests for indirect symlink cycles in the form:
   149  // symTestA/linkToB -> symTestB and symTestB/linkToA -> symTestA
   150  func TestIndirectSymlinkCycles(t *testing.T) {
   151  	if err := os.Mkdir("symTestA", 0700); err != nil {
   152  		t.Errorf("could not create tmpdir: %s", err)
   153  	}
   154  	if err := os.Mkdir("symTestB", 0700); err != nil {
   155  		t.Errorf("could not create tmpdir: %s", err)
   156  	}
   157  
   158  	// we need to get the current working directory here, otherwise
   159  	// os.Symlink() will create a wrong symlink
   160  	dir, err := os.Getwd()
   161  	if err != nil {
   162  		t.Error(err)
   163  	}
   164  
   165  	linkB := filepath.FromSlash("symTestA/linkToB.sym")
   166  	linkA := filepath.FromSlash("symTestB/linkToA.sym")
   167  
   168  	if err := os.Symlink(dir+"/symTestA", linkA); err != nil {
   169  		t.Errorf("could not create a symlink: %s", err)
   170  	}
   171  	if err := os.Symlink(dir+"/symTestB", linkB); err != nil {
   172  		t.Errorf("could not create a symlink: %s", err)
   173  	}
   174  
   175  	// provoke "symlink cycle detected" error
   176  	_, err = RecordArtifacts([]string{"symTestA/linkToB.sym", "symTestB/linkToA.sym", "foo.tar.gz"}, []string{"sha256"}, nil, nil, testOSisWindows(), true)
   177  	if !errors.Is(err, ErrSymCycle) {
   178  		t.Errorf("we expected: %s, we got: %s", ErrSymCycle, err)
   179  	}
   180  
   181  	// make sure to clean up everything
   182  	if err := os.Remove("symTestA/linkToB.sym"); err != nil {
   183  		t.Errorf("could not remove path: %s", err)
   184  	}
   185  
   186  	if err := os.Remove("symTestB/linkToA.sym"); err != nil {
   187  		t.Errorf("could not remove path: %s", err)
   188  	}
   189  
   190  	if err := os.Remove("symTestA"); err != nil {
   191  		t.Errorf("could not remove path: %s", err)
   192  	}
   193  
   194  	if err := os.Remove("symTestB"); err != nil {
   195  		t.Errorf("could not remove path: %s", err)
   196  	}
   197  
   198  }
   199  
   200  // TestSymlinkToFolder checks if we are successfully following symlinks to folders
   201  func TestSymlinkToFolder(t *testing.T) {
   202  	if err := os.MkdirAll("symTest/symTest2", 0700); err != nil {
   203  		t.Errorf("could not create tmpdir: %s", err)
   204  	}
   205  
   206  	if err := os.Symlink("symTest/symTest2", "symTmpfile.sym"); err != nil {
   207  		t.Errorf("could not create a symlink: %s", err)
   208  	}
   209  
   210  	// create a filepath from slash, because otherwise
   211  	// our tests are going to fail, because the path matching will
   212  	// not work correctly on Windows
   213  	p := filepath.FromSlash("symTest/symTest2/symTmpfile")
   214  
   215  	if err := os.WriteFile(p, []byte("abc"), 0400); err != nil {
   216  		t.Errorf("could not write symTmpfile: %s", err)
   217  	}
   218  
   219  	result, err := RecordArtifacts([]string{"symTmpfile.sym"}, []string{"sha256"}, nil, nil, testOSisWindows(), true)
   220  	if err != nil {
   221  		t.Error(err)
   222  	}
   223  
   224  	expected := map[string]HashObj{
   225  		path.Join("symTmpfile.sym", "symTmpfile"): {
   226  			"sha256": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
   227  		},
   228  	}
   229  
   230  	if !reflect.DeepEqual(result, expected) {
   231  		t.Errorf("RecordArtifacts returned '(%s, %s)', expected '(%s, nil)'",
   232  			result, err, expected)
   233  	}
   234  
   235  	// make sure to clean up everything
   236  	if err := os.Remove("symTest/symTest2/symTmpfile"); err != nil {
   237  		t.Errorf("could not remove path symTest/symTest2/symTmpfile: %s", err)
   238  	}
   239  
   240  	if err := os.Remove("symTmpfile.sym"); err != nil {
   241  		t.Errorf("could not remove path symTest/symTest2/symTmpfile.sym: %s", err)
   242  	}
   243  
   244  	if err := os.Remove("symTest/symTest2"); err != nil {
   245  		t.Errorf("could not remove path symTest/symTest2: %s", err)
   246  	}
   247  
   248  	if err := os.Remove("symTest/"); err != nil {
   249  		t.Errorf("could not remove path symTest: %s", err)
   250  	}
   251  }
   252  
   253  // This test provokes a symlink cycle
   254  func TestSymlinkCycle(t *testing.T) {
   255  	if err := os.Mkdir("symlinkCycle/", 0700); err != nil {
   256  		t.Errorf("could not create tmpdir: %s", err)
   257  	}
   258  
   259  	// we need to get the current working directory here, otherwise
   260  	// os.Symlink() will create a wrong symlink
   261  	dir, err := os.Getwd()
   262  	if err != nil {
   263  		t.Error(err)
   264  	}
   265  	// create a cycle ./symlinkCycle/symCycle.sym -> ./symlinkCycle/
   266  	if err := os.Symlink(dir+"/symlinkCycle", "symlinkCycle/symCycle.sym"); err != nil {
   267  		t.Errorf("could not create a symlink: %s", err)
   268  	}
   269  
   270  	// provoke "symlink cycle detected" error
   271  	_, err = RecordArtifacts([]string{"symlinkCycle/symCycle.sym", "foo.tar.gz"}, []string{"sha256"}, nil, nil, testOSisWindows(), true)
   272  	if !errors.Is(err, ErrSymCycle) {
   273  		t.Errorf("we expected: %s, we got: %s", ErrSymCycle, err)
   274  	}
   275  
   276  	if err := os.Remove("symlinkCycle/symCycle.sym"); err != nil {
   277  		t.Errorf("could not remove path symlinkCycle/symCycle.sym: %s", err)
   278  	}
   279  
   280  	if err := os.Remove("symlinkCycle"); err != nil {
   281  		t.Errorf("could not remove path symlinkCycle: %s", err)
   282  	}
   283  }
   284  
   285  func TestRecordArtifacts(t *testing.T) {
   286  	// Test successfully record multiple artifacts including temporary subdir
   287  	if err := os.Mkdir("tmpdir", 0700); err != nil {
   288  		t.Errorf("could not create tmpdir: %s", err)
   289  	}
   290  	if err := os.WriteFile("tmpdir/tmpfile", []byte("abc"), 0400); err != nil {
   291  		t.Errorf("could not write tmpfile: %s", err)
   292  	}
   293  	result, err := RecordArtifacts([]string{"foo.tar.gz",
   294  		"tmpdir/tmpfile"}, []string{"sha256"}, nil, []string{"tmpdir/"}, testOSisWindows(), false)
   295  	expected := map[string]HashObj{
   296  		"foo.tar.gz": {
   297  			"sha256": "52947cb78b91ad01fe81cd6aef42d1f6817e92b9e6936c1e5aabb7c98514f355",
   298  		},
   299  		"tmpfile": {
   300  			"sha256": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
   301  		},
   302  	}
   303  	if !reflect.DeepEqual(result, expected) {
   304  		t.Errorf("RecordArtifacts returned '(%s, %s)', expected '(%s, nil)'",
   305  			result, err, expected)
   306  	}
   307  	// Test duplicated artifact after left strip
   308  	if err := os.WriteFile("tmpdir/foo.tar.gz", []byte("abc"), 0400); err != nil {
   309  		t.Errorf("could not write tmpfile: %s", err)
   310  	}
   311  	_, err = RecordArtifacts([]string{"foo.tar.gz",
   312  		"tmpdir/foo.tar.gz"}, []string{"sha256"}, nil, []string{"tmpdir/"}, testOSisWindows(), false)
   313  	if err == nil {
   314  		t.Error("duplicated path error expected")
   315  	}
   316  
   317  	if err := os.RemoveAll("tmpdir"); err != nil {
   318  		t.Errorf("could not remove tmpdir: %s", err)
   319  	}
   320  
   321  	// Test error by recording nonexistent artifact
   322  	result, err = RecordArtifacts([]string{"file-does-not-exist"}, []string{"sha256"}, nil, nil, testOSisWindows(), false)
   323  	if !os.IsNotExist(err) {
   324  		t.Errorf("RecordArtifacts returned '(%s, %s)', expected '(nil, %s)'",
   325  			result, err, os.ErrNotExist)
   326  	}
   327  }
   328  
   329  func TestWaitErrToExitCode(t *testing.T) {
   330  	// TODO: Find way to test/mock ExitError
   331  	// Test exit code from error assessment
   332  	parameters := []error{
   333  		nil,
   334  		errors.New(""),
   335  		// &exec.ExitError{ProcessState: &os.ProcessState},
   336  	}
   337  	expected := []int{
   338  		0,
   339  		-1,
   340  		// -1,
   341  	}
   342  
   343  	for i := 0; i < len(parameters); i++ {
   344  		result := waitErrToExitCode(parameters[i])
   345  		if result != expected[i] {
   346  			t.Errorf("waitErrToExitCode returned %d, expected %d",
   347  				result, expected[i])
   348  		}
   349  	}
   350  }
   351  
   352  func TestRunCommand(t *testing.T) {
   353  	// Successfully run command and return metadata
   354  	parameters := [][]string{
   355  		{"sh", "-c", "true"},
   356  		{"sh", "-c", "false"},
   357  		{"sh", "-c", "printf out"},
   358  		{"sh", "-c", "printf err >&2"},
   359  	}
   360  	expected := []map[string]interface{}{
   361  		{"return-value": float64(0), "stdout": "", "stderr": ""},
   362  		{"return-value": float64(1), "stdout": "", "stderr": ""},
   363  		{"return-value": float64(0), "stdout": "out", "stderr": ""},
   364  		{"return-value": float64(0), "stdout": "", "stderr": "err"},
   365  	}
   366  	for i := 0; i < len(parameters); i++ {
   367  		result, err := RunCommand(parameters[i], "")
   368  		if !reflect.DeepEqual(result, expected[i]) || err != nil {
   369  			t.Errorf("RunCommand returned '(%s, %s)', expected '(%s, nil)'",
   370  				result, err, expected[i])
   371  		}
   372  	}
   373  
   374  	// Fail run command
   375  	result, err := RunCommand([]string{"command-does-not-exist"}, "")
   376  	if result != nil || err == nil {
   377  		t.Errorf("RunCommand returned '(%s, %s)', expected '(nil, *exec.Error)'",
   378  			result, err)
   379  	}
   380  }
   381  
   382  func TestRunCommandErrors(t *testing.T) {
   383  	tables := []struct {
   384  		CmdArgs       []string
   385  		RunDir        string
   386  		ExpectedError error
   387  	}{
   388  		{nil, "", ErrEmptyCommandArgs},
   389  		{[]string{}, "", ErrEmptyCommandArgs},
   390  	}
   391  	for _, table := range tables {
   392  		_, err := RunCommand(table.CmdArgs, table.RunDir)
   393  		if !errors.Is(err, ErrEmptyCommandArgs) {
   394  			t.Errorf("RunCommand did not provoke expected error. Got: %s, want: %s", err, ErrEmptyCommandArgs)
   395  		}
   396  	}
   397  }
   398  
   399  func TestInTotoRun(t *testing.T) {
   400  	// Successfully run InTotoRun
   401  	linkName := "Name"
   402  
   403  	var validKey Key
   404  	if err := validKey.LoadKey("carol", "ed25519", []string{"sha256", "sha512"}); err != nil {
   405  		t.Error(err)
   406  	}
   407  
   408  	tablesCorrect := []struct {
   409  		materialPaths  []string
   410  		productPaths   []string
   411  		cmdArgs        []string
   412  		key            Key
   413  		hashAlgorithms []string
   414  		useDSSE        bool
   415  		result         Metadata
   416  	}{
   417  		{[]string{"alice.pub"}, []string{"foo.tar.gz"}, []string{"sh", "-c", "printf out; printf err >&2"}, validKey, []string{"sha256"}, false, &Metablock{
   418  			Signed: Link{
   419  				Name: linkName,
   420  				Type: "link",
   421  				Materials: map[string]HashObj{
   422  					"alice.pub": {
   423  						"sha256": "f051e8b561835b7b2aa7791db7bc72f2613411b0b7d428a0ac33d45b8c518039",
   424  					},
   425  				},
   426  				Products: map[string]HashObj{
   427  					"foo.tar.gz": {
   428  						"sha256": "52947cb78b91ad01fe81cd6aef42d1f6817e92b9e6936c1e5aabb7c98514f355",
   429  					},
   430  				},
   431  				ByProducts: map[string]interface{}{
   432  					"return-value": float64(0), "stdout": "out", "stderr": "err",
   433  				},
   434  				Command:     []string{"sh", "-c", "printf out; printf err >&2"},
   435  				Environment: map[string]interface{}{},
   436  			},
   437  			Signatures: []Signature{{
   438  				KeyID: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6",
   439  				Sig:   "71dfec1af747d02f6463d4baf3bb2c1d903c107470be86c12349433f780b1030e5ca36a10ee5c5d74de16344fe16b459154fd2be05a58fb556dff934d6682403",
   440  			}},
   441  		},
   442  		},
   443  		{[]string{"alice.pub"}, []string{"foo.tar.gz"}, []string{}, validKey, []string{"sha256"}, false, &Metablock{
   444  			Signed: Link{
   445  				Name: linkName,
   446  				Type: "link",
   447  				Materials: map[string]HashObj{
   448  					"alice.pub": {
   449  						"sha256": "f051e8b561835b7b2aa7791db7bc72f2613411b0b7d428a0ac33d45b8c518039",
   450  					},
   451  				},
   452  				Products: map[string]HashObj{
   453  					"foo.tar.gz": HashObj{
   454  						"sha256": "52947cb78b91ad01fe81cd6aef42d1f6817e92b9e6936c1e5aabb7c98514f355",
   455  					},
   456  				},
   457  				ByProducts:  map[string]interface{}{},
   458  				Command:     []string{},
   459  				Environment: map[string]interface{}{},
   460  			},
   461  			Signatures: []Signature{{
   462  				KeyID: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6",
   463  				Sig:   "f4a2d468965d595b4d29615fb2083ef7ac22a948e1530925612d73ba580ce9765d93db7b7ed1b9755d96f13a6a1e858c64693c2f7adcb311afb28cb57fbadc0c",
   464  			}},
   465  		},
   466  		},
   467  		{[]string{"alice.pub"}, []string{"foo.tar.gz"}, []string{}, validKey, []string{"sha256"}, true, &Envelope{
   468  			envelope: &dsse.Envelope{
   469  				Payload:     "eyJfdHlwZSI6ImxpbmsiLCJieXByb2R1Y3RzIjp7fSwiY29tbWFuZCI6W10sImVudmlyb25tZW50Ijp7fSwibWF0ZXJpYWxzIjp7ImFsaWNlLnB1YiI6eyJzaGEyNTYiOiJmMDUxZThiNTYxODM1YjdiMmFhNzc5MWRiN2JjNzJmMjYxMzQxMWIwYjdkNDI4YTBhYzMzZDQ1YjhjNTE4MDM5In19LCJuYW1lIjoiTmFtZSIsInByb2R1Y3RzIjp7ImZvby50YXIuZ3oiOnsic2hhMjU2IjoiNTI5NDdjYjc4YjkxYWQwMWZlODFjZDZhZWY0MmQxZjY4MTdlOTJiOWU2OTM2YzFlNWFhYmI3Yzk4NTE0ZjM1NSJ9fX0=",
   470  				PayloadType: PayloadType,
   471  				Signatures: []dsse.Signature{{
   472  					KeyID: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6",
   473  					Sig:   "XgNp1Q5N/ivFxNyuUNHcjOarMIj3WXZpb00/ZVy2pxdiAeOZYKpJkXPa7wRAM5auuwrVph9TrwoJQuDpJrZaCw==",
   474  				}},
   475  			},
   476  		}},
   477  	}
   478  
   479  	for _, table := range tablesCorrect {
   480  		result, err := InTotoRun(linkName, "", table.materialPaths, table.productPaths, table.cmdArgs, table.key, table.hashAlgorithms, nil, nil, testOSisWindows(), false, table.useDSSE)
   481  		if table.useDSSE {
   482  			assert.Equal(t, table.result.(*Envelope).envelope, result.(*Envelope).envelope, fmt.Sprintf("InTotoRun returned '(%s, %s)', expected '(%s, nil)'", result, err, table.result))
   483  		} else {
   484  			assert.True(t, reflect.DeepEqual(result.(*Metablock), table.result.(*Metablock)), fmt.Sprintf("InTotoRun returned '(%s, %s)', expected '(%s, nil)'", result, err, table.result))
   485  		}
   486  
   487  		if result != nil {
   488  			if err := result.Dump(linkName + ".link"); err != nil {
   489  				t.Errorf("error while dumping link metablock to file")
   490  			}
   491  			loadedResult, err := LoadMetadata(linkName + ".link")
   492  			if err != nil {
   493  				t.Errorf("error while loading link metablock from file")
   494  			}
   495  			if table.useDSSE {
   496  				assert.Equal(t, result.(*Envelope).envelope, loadedResult.(*Envelope).envelope, fmt.Sprintf("dump and loading of signed Link failed. Loaded result: '%s', dumped result '%s'", loadedResult, result))
   497  			} else {
   498  				assert.True(t, reflect.DeepEqual(loadedResult, result), fmt.Sprintf("dump and loading of signed Link failed. Loaded result: '%s', dumped result '%s'", loadedResult, result))
   499  			}
   500  
   501  			if err := os.Remove(linkName + ".link"); err != nil {
   502  				t.Errorf("removing created link file failed")
   503  			}
   504  		}
   505  	}
   506  
   507  	// Run InToToRun with errors
   508  	tablesInvalid := []struct {
   509  		materialPaths  []string
   510  		productPaths   []string
   511  		cmdArgs        []string
   512  		key            Key
   513  		hashAlgorithms []string
   514  	}{
   515  		{[]string{"material-does-not-exist"}, []string{""}, []string{"sh", "-c", "printf test"}, Key{}, []string{"sha256"}},
   516  		{[]string{"demo.layout"}, []string{"product-does-not-exist"}, []string{"sh", "-c", "printf test"}, Key{}, []string{"sha256"}},
   517  		{[]string{""}, []string{"/invalid-path/"}, []string{"sh", "-c", "printf test"}, Key{}, []string{"sha256"}},
   518  		{[]string{}, []string{}, []string{"command-does-not-exist"}, Key{}, []string{"sha256"}},
   519  		{[]string{"demo.layout"}, []string{"foo.tar.gz"}, []string{"sh", "-c", "printf out; printf err >&2"}, Key{
   520  			KeyID:               "this-is-invalid",
   521  			KeyIDHashAlgorithms: nil,
   522  			KeyType:             "",
   523  			KeyVal:              KeyVal{},
   524  			Scheme:              "",
   525  		}, []string{"sha256"}},
   526  	}
   527  
   528  	for _, table := range tablesInvalid {
   529  		result, err := InTotoRun(linkName, "", table.materialPaths, table.productPaths, table.cmdArgs, table.key, table.hashAlgorithms, nil, nil, testOSisWindows(), false, false)
   530  		if err == nil {
   531  			t.Errorf("InTotoRun returned '(%s, %s)', expected error",
   532  				result, err)
   533  		}
   534  	}
   535  }
   536  
   537  func TestInTotoRecord(t *testing.T) {
   538  	// Successfully run InTotoRecordStart
   539  	linkName := "Name"
   540  
   541  	var validKey Key
   542  	if err := validKey.LoadKey("carol", "ed25519", []string{"sha256", "sha512"}); err != nil {
   543  		t.Error(err)
   544  	}
   545  
   546  	tablesCorrect := []struct {
   547  		materialPaths  []string
   548  		productPaths   []string
   549  		key            Key
   550  		hashAlgorithms []string
   551  		useDSSE        bool
   552  		startResult    Metadata
   553  		stopResult     Metadata
   554  	}{
   555  		{[]string{"alice.pub"}, []string{"foo.tar.gz"}, validKey, []string{"sha256"}, false, &Metablock{
   556  			Signed: Link{
   557  				Name: linkName,
   558  				Type: "link",
   559  				Materials: map[string]HashObj{
   560  					"alice.pub": {
   561  						"sha256": "f051e8b561835b7b2aa7791db7bc72f2613411b0b7d428a0ac33d45b8c518039",
   562  					},
   563  				},
   564  				Products:    map[string]HashObj{},
   565  				ByProducts:  map[string]interface{}{},
   566  				Command:     []string{},
   567  				Environment: map[string]interface{}{},
   568  			},
   569  			Signatures: []Signature{{
   570  				KeyID: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6",
   571  				Sig:   "f02db2e08d065840f266df850eaef7cfb5364bbe1808708945eb45373f4757cfe70c86f7ad5e4d5f746d41489410e0407921b4480788cfae5a7d695e3aa62f06",
   572  			}},
   573  		}, &Metablock{
   574  			Signed: Link{
   575  				Name: linkName,
   576  				Type: "link",
   577  				Materials: map[string]HashObj{
   578  					"alice.pub": {
   579  						"sha256": "f051e8b561835b7b2aa7791db7bc72f2613411b0b7d428a0ac33d45b8c518039",
   580  					},
   581  				},
   582  				Products: map[string]HashObj{
   583  					"foo.tar.gz": {
   584  						"sha256": "52947cb78b91ad01fe81cd6aef42d1f6817e92b9e6936c1e5aabb7c98514f355",
   585  					},
   586  				},
   587  				ByProducts:  map[string]interface{}{},
   588  				Command:     []string{},
   589  				Environment: map[string]interface{}{},
   590  			},
   591  			Signatures: []Signature{{
   592  				KeyID: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6",
   593  				Sig:   "f4a2d468965d595b4d29615fb2083ef7ac22a948e1530925612d73ba580ce9765d93db7b7ed1b9755d96f13a6a1e858c64693c2f7adcb311afb28cb57fbadc0c",
   594  			}},
   595  		},
   596  		},
   597  		{[]string{"alice.pub"}, []string{"foo.tar.gz"}, validKey, []string{"sha256"}, true, &Envelope{
   598  			envelope: &dsse.Envelope{
   599  				PayloadType: PayloadType,
   600  				Payload:     "eyJfdHlwZSI6ImxpbmsiLCJieXByb2R1Y3RzIjp7fSwiY29tbWFuZCI6W10sImVudmlyb25tZW50Ijp7fSwibWF0ZXJpYWxzIjp7ImFsaWNlLnB1YiI6eyJzaGEyNTYiOiJmMDUxZThiNTYxODM1YjdiMmFhNzc5MWRiN2JjNzJmMjYxMzQxMWIwYjdkNDI4YTBhYzMzZDQ1YjhjNTE4MDM5In19LCJuYW1lIjoiTmFtZSIsInByb2R1Y3RzIjp7fX0=",
   601  				Signatures: []dsse.Signature{{
   602  					KeyID: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6",
   603  					Sig:   "1u46q3nVmmvqKz/exUviEBPyfRndXwxouG+Jk1GadqKvhyfZv8to//xLPQWC+zy4bPQTicOp1yIBHqFO0bNeBw==",
   604  				}},
   605  			},
   606  		}, &Envelope{
   607  			envelope: &dsse.Envelope{
   608  				PayloadType: PayloadType,
   609  				Payload:     "eyJfdHlwZSI6ImxpbmsiLCJieXByb2R1Y3RzIjp7fSwiY29tbWFuZCI6W10sImVudmlyb25tZW50Ijp7fSwibWF0ZXJpYWxzIjp7ImFsaWNlLnB1YiI6eyJzaGEyNTYiOiJmMDUxZThiNTYxODM1YjdiMmFhNzc5MWRiN2JjNzJmMjYxMzQxMWIwYjdkNDI4YTBhYzMzZDQ1YjhjNTE4MDM5In19LCJuYW1lIjoiTmFtZSIsInByb2R1Y3RzIjp7ImZvby50YXIuZ3oiOnsic2hhMjU2IjoiNTI5NDdjYjc4YjkxYWQwMWZlODFjZDZhZWY0MmQxZjY4MTdlOTJiOWU2OTM2YzFlNWFhYmI3Yzk4NTE0ZjM1NSJ9fX0=",
   610  				Signatures: []dsse.Signature{{
   611  					KeyID: "be6371bc627318218191ce0780fd3183cce6c36da02938a477d2e4dfae1804a6",
   612  					Sig:   "XgNp1Q5N/ivFxNyuUNHcjOarMIj3WXZpb00/ZVy2pxdiAeOZYKpJkXPa7wRAM5auuwrVph9TrwoJQuDpJrZaCw==",
   613  				}},
   614  			},
   615  		},
   616  		},
   617  	}
   618  
   619  	for _, table := range tablesCorrect {
   620  		result, err := InTotoRecordStart(linkName, table.materialPaths, table.key, table.hashAlgorithms, nil, nil, testOSisWindows(), false, table.useDSSE)
   621  		assert.Nil(t, err, "unexpected error while running record start")
   622  		if table.useDSSE {
   623  			assert.Equal(t, table.startResult.(*Envelope).envelope, result.(*Envelope).envelope, "result from record start did not match expected result")
   624  		} else {
   625  			assert.Equal(t, table.startResult.(*Metablock), result.(*Metablock), "result from record start did not match expected result")
   626  		}
   627  		stopResult, err := InTotoRecordStop(result, table.productPaths, table.key, table.hashAlgorithms, nil, nil, testOSisWindows(), false, table.useDSSE)
   628  		assert.Nil(t, err, "unexpected error while running record stop")
   629  		if table.useDSSE {
   630  			assert.Equal(t, table.stopResult.(*Envelope).envelope, stopResult.(*Envelope).envelope, "result from record stop did not match expected result")
   631  		} else {
   632  			assert.Equal(t, table.stopResult.(*Metablock), stopResult.(*Metablock), "result from record stop did not match expected result")
   633  		}
   634  	}
   635  }
   636  
   637  // TestRecordArtifactWithBlobs ensures that we calculate the same hash for blobs
   638  func TestRecordArtifactWithBlobs(t *testing.T) {
   639  	type args struct {
   640  		path           string
   641  		hashAlgorithms []string
   642  	}
   643  	tests := []struct {
   644  		name    string
   645  		args    args
   646  		want    HashObj
   647  		wantErr error
   648  	}{
   649  		{
   650  			name: "test binary blob without line normalization segments",
   651  			args: args{
   652  				path:           "foo.tar.gz",
   653  				hashAlgorithms: []string{"sha256", "sha384", "sha512"},
   654  			},
   655  			want: HashObj{"sha256": "52947cb78b91ad01fe81cd6aef42d1f6817e92b9e6936c1e5aabb7c98514f355",
   656  				"sha384": "ce17464027a7d7c15b15032b404fc76fdbadfa1fa566d7f7747020df2542a293b3098873a98dbbda6e461f7767b8ff6c",
   657  				"sha512": "bb040966a5a6aefb646909f636f7f99c9e16b684a1f0e83a87dc30c3ab4d9dec2f9b0091d8be74bbc78ba29cb0c2dd027c223579028cf9822b0bccc49d493a6d"},
   658  			wantErr: nil,
   659  		},
   660  		{
   661  			name: "test binary blob with windows-like line breaks as byte segments",
   662  			args: args{
   663  				path:           "helloworld",
   664  				hashAlgorithms: []string{"sha256", "sha384", "sha512"},
   665  			},
   666  			want: HashObj{"sha256": "fd895747460401ca62d81f310538110734ff5401f8ef86c3ab27168598225db8",
   667  				"sha384": "ddc3ac40ca8d04929e13c42d555a5a6774d35bfac9e2f4cde5847ab3f12f36831faa3baf1b33922b53d288b352ae4b9a",
   668  				"sha512": "46f0e37e72879843f95ddecc4d511c9ba90241c34b471c2f2caca2784abe185da50ddc5252562b2a911b7cfedafa3e878f0e6b7aa843c136915da5306061e501"},
   669  			wantErr: nil,
   670  		},
   671  	}
   672  	for _, tt := range tests {
   673  		t.Run(tt.name, func(t *testing.T) {
   674  			got, err := RecordArtifact(tt.args.path, tt.args.hashAlgorithms, false)
   675  			if err != tt.wantErr {
   676  				t.Errorf("RecordArtifact() error = %v, wantErr %v", err, tt.wantErr)
   677  				return
   678  			}
   679  			if !reflect.DeepEqual(got, tt.want) {
   680  				t.Errorf("RecordArtifact() got = %v, want %v", got, tt.want)
   681  			}
   682  		})
   683  	}
   684  }
   685  
   686  // Copy of TestRecordArtifact and TestRecordArtifactWithBlobs with lineNormalization parameter set as true.
   687  // Need to be changed when line normalization is properly implemented.
   688  func TestLineNormalizationFlag(t *testing.T) {
   689  	type args struct {
   690  		path           string
   691  		hashAlgorithms []string
   692  	}
   693  	tests := []struct {
   694  		name    string
   695  		args    args
   696  		wantErr error
   697  	}{
   698  		{
   699  			name: "test line normalization with only new line character",
   700  			args: args{
   701  				path:           "line-ending-linux",
   702  				hashAlgorithms: []string{"sha256", "sha384", "sha512"},
   703  			},
   704  			wantErr: nil,
   705  		},
   706  		{
   707  			name: "test line normalization with carriage return and new line characters",
   708  			args: args{
   709  				path:           "line-ending-windows",
   710  				hashAlgorithms: []string{"sha256", "sha384", "sha512"},
   711  			},
   712  			wantErr: nil,
   713  		},
   714  		{
   715  			name: "test line normalization with only carriage return character",
   716  			args: args{
   717  				path:           "line-ending-macos",
   718  				hashAlgorithms: []string{"sha256", "sha384", "sha512"},
   719  			},
   720  			wantErr: nil,
   721  		},
   722  		{
   723  			name: "test line normalization with combination of all of the above",
   724  			args: args{
   725  				path:           "line-ending-mixed",
   726  				hashAlgorithms: []string{"sha256", "sha384", "sha512"},
   727  			},
   728  			wantErr: nil,
   729  		},
   730  	}
   731  	expected := HashObj{
   732  		"sha256": "efb929dfabd55c93796fc61cbf1fe6157445f093167dbee82e8b069842a4fceb",
   733  		"sha384": "936e88775dfd17c24ed41e3a896dfdf3395707acee1b6f16a52ae144bdcd8611fd17e817f5b75e5a3cf7a1dacf187bae",
   734  		"sha512": "1d7a485cb2c3cf22c11b4be9afbf1745e053e21a40301d3e8143350d6d2873117c12acef49d4b3650b5262e8a26ffe809b177f968845bd268f26ffd978d314bd",
   735  	}
   736  	for _, tt := range tests {
   737  		t.Run(tt.name, func(t *testing.T) {
   738  			got, err := RecordArtifact(tt.args.path, tt.args.hashAlgorithms, true)
   739  			if err != tt.wantErr {
   740  				t.Errorf("RecordArtifact() error = %v, wantErr %v", err, tt.wantErr)
   741  				return
   742  			}
   743  			if !reflect.DeepEqual(got, expected) {
   744  				t.Errorf("RecordArtifact() got = %v, want %v", got, expected)
   745  			}
   746  		})
   747  	}
   748  }
   749  
   750  func TestInTotoMatchProducts(t *testing.T) {
   751  	link := &Link{
   752  		Products: map[string]HashObj{
   753  			"foo": {
   754  				"sha256": "8a51c03f1ff77c2b8e76da512070c23c5e69813d5c61732b3025199e5f0c14d5",
   755  			},
   756  			"bar": {
   757  				"sha256": "bb97edb3507a35b119539120526d00da595f14575da261cd856389ecd89d3186",
   758  			},
   759  			"baz": {
   760  				"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
   761  			},
   762  		},
   763  	}
   764  	if _, err := os.Create("bar"); err != nil {
   765  		t.Fatal(err)
   766  	}
   767  	if _, err := os.Create("baz"); err != nil {
   768  		t.Fatal(err)
   769  	}
   770  	if _, err := os.Create("quux"); err != nil {
   771  		t.Fatal(err)
   772  	}
   773  
   774  	testDir, err := os.Getwd()
   775  	if err != nil {
   776  		t.Fatal(err)
   777  	}
   778  
   779  	tests := []struct {
   780  		paths                  []string
   781  		excludePatterns        []string
   782  		lstripPaths            []string
   783  		expectedOnlyInProducts []string
   784  		expectedNotInProducts  []string
   785  		expectedDiffer         []string
   786  	}{
   787  		{
   788  			paths:                  []string{"bar", "baz", "quux"},
   789  			expectedOnlyInProducts: []string{"foo"},
   790  			expectedNotInProducts:  []string{"quux"},
   791  			expectedDiffer:         []string{"bar"},
   792  		},
   793  		{
   794  			paths:                  []string{"bar", "baz", "quux"},
   795  			excludePatterns:        []string{"ba*"},
   796  			expectedOnlyInProducts: []string{"bar", "baz", "foo"},
   797  			expectedNotInProducts:  []string{"quux"},
   798  			expectedDiffer:         []string{},
   799  		},
   800  		{
   801  			paths:                  []string{"baz"},
   802  			expectedOnlyInProducts: []string{"bar", "foo"},
   803  			expectedNotInProducts:  []string{},
   804  			expectedDiffer:         []string{},
   805  		},
   806  		{
   807  			paths:                  []string{filepath.Join(testDir, "baz")},
   808  			lstripPaths:            []string{fmt.Sprintf("%s%s", testDir, string(os.PathSeparator))},
   809  			expectedOnlyInProducts: []string{"bar", "foo"},
   810  			expectedNotInProducts:  []string{},
   811  			expectedDiffer:         []string{},
   812  		},
   813  	}
   814  
   815  	for _, test := range tests {
   816  		onlyInProducts, notInProducts, differ, err := InTotoMatchProducts(link, test.paths, []string{"sha256"}, test.excludePatterns, test.lstripPaths)
   817  		assert.Nil(t, err)
   818  
   819  		sort.Slice(onlyInProducts, func(i, j int) bool {
   820  			return onlyInProducts[i] < onlyInProducts[j]
   821  		})
   822  		sort.Slice(notInProducts, func(i, j int) bool {
   823  			return notInProducts[i] < notInProducts[j]
   824  		})
   825  		sort.Slice(differ, func(i, j int) bool {
   826  			return differ[i] < differ[j]
   827  		})
   828  
   829  		assert.Equal(t, test.expectedOnlyInProducts, onlyInProducts)
   830  		assert.Equal(t, test.expectedNotInProducts, notInProducts)
   831  		assert.Equal(t, test.expectedDiffer, differ)
   832  	}
   833  }