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 }