diff --git a/tools/compliance/cmd/sbom/sbom.go b/tools/compliance/cmd/sbom/sbom.go index c378e39efd..f61289e742 100644 --- a/tools/compliance/cmd/sbom/sbom.go +++ b/tools/compliance/cmd/sbom/sbom.go @@ -55,6 +55,7 @@ type context struct { product string stripPrefix []string creationTime creationTimeGetter + buildid string } func (ctx context) strip(installPath string) string { @@ -124,6 +125,7 @@ Options: depsFile := flags.String("d", "", "Where to write the deps file") product := flags.String("product", "", "The name of the product for which the notice is generated.") stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") + buildid := flags.String("build_id", "", "Uniquely identifies the build. (default timestamp)") flags.Parse(expandedArgs) @@ -162,7 +164,7 @@ Options: ofile = obuf } - ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, actualTime} + ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, actualTime, *buildid} spdxDoc, deps, err := sbomGenerator(ctx, flags.Args()...) @@ -317,14 +319,21 @@ func inputFiles(lg *compliance.LicenseGraph, pmix *projectmetadata.Index, licens } // generateSPDXNamespace generates a unique SPDX Document Namespace using a SHA1 checksum -// and the CreationInfo.Created field as the date. -func generateSPDXNamespace(created string) string { - // Compute a SHA1 checksum of the CreationInfo.Created field. - hash := sha1.Sum([]byte(created)) - checksum := hex.EncodeToString(hash[:]) +func generateSPDXNamespace(buildid string, created string, files ...string) string { - // Combine the checksum and timestamp to generate the SPDX Namespace. - namespace := fmt.Sprintf("SPDXRef-DOCUMENT-%s-%s", created, checksum) + seed := strings.Join(files, "") + + if buildid == "" { + seed += created + } else { + seed += buildid + } + + // Compute a SHA1 checksum of the seed. + hash := sha1.Sum([]byte(seed)) + uuid := hex.EncodeToString(hash[:]) + + namespace := fmt.Sprintf("SPDXRef-DOCUMENT-%s", uuid) return namespace } @@ -523,7 +532,7 @@ func sbomGenerator(ctx *context, files ...string) (*spdx.Document, []string, err DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: docName, - DocumentNamespace: generateSPDXNamespace(ci.Created), + DocumentNamespace: generateSPDXNamespace(ctx.buildid, ci.Created, files...), CreationInfo: ci, Packages: pkgs, Relationships: relationships, diff --git a/tools/compliance/cmd/sbom/sbom_test.go b/tools/compliance/cmd/sbom/sbom_test.go index 6472f517cb..8a62713a83 100644 --- a/tools/compliance/cmd/sbom/sbom_test.go +++ b/tools/compliance/cmd/sbom/sbom_test.go @@ -59,7 +59,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-firstparty-highest.apex", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/firstparty/highest.apex.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -187,7 +187,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-firstparty-application", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/firstparty/application.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -266,7 +266,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-firstparty-container.zip", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/firstparty/container.zip.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -394,7 +394,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-firstparty-bin-bin1", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/firstparty/bin/bin1.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -460,7 +460,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-firstparty-lib-libd.so", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/firstparty/lib/libd.so.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -500,7 +500,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-notice-highest.apex", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/notice/highest.apex.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -634,7 +634,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-notice-container.zip", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/notice/container.zip.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -768,7 +768,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-notice-application", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/notice/application.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -853,7 +853,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-notice-bin-bin1", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/notice/bin/bin1.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -925,7 +925,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-notice-lib-libd.so", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/notice/lib/libd.so.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -965,7 +965,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-reciprocal-highest.apex", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/reciprocal/highest.apex.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -1105,7 +1105,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-reciprocal-application", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/reciprocal/application.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -1196,7 +1196,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-reciprocal-bin-bin1", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/reciprocal/bin/bin1.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -1268,7 +1268,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-reciprocal-lib-libd.so", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/reciprocal/lib/libd.so.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -1308,7 +1308,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-restricted-highest.apex", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/restricted/highest.apex.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -1454,7 +1454,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-restricted-container.zip", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/restricted/container.zip.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -1600,7 +1600,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-restricted-bin-bin1", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/restricted/bin/bin1.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -1678,7 +1678,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-restricted-lib-libd.so", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/restricted/lib/libd.so.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -1718,7 +1718,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-proprietary-highest.apex", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/proprietary/highest.apex.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -1864,7 +1864,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-proprietary-container.zip", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/proprietary/container.zip.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -2010,7 +2010,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-proprietary-application", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/proprietary/application.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -2101,7 +2101,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-proprietary-bin-bin1", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/proprietary/bin/bin1.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -2173,7 +2173,7 @@ func Test(t *testing.T) { DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "testdata-proprietary-lib-libd.so", - DocumentNamespace: generateSPDXNamespace("1970-01-01T00:00:00Z"), + DocumentNamespace: generateSPDXNamespace("", "1970-01-01T00:00:00Z", "testdata/proprietary/lib/libd.so.meta_lic"), CreationInfo: getCreationInfo(t), Packages: []*spdx.Package{ { @@ -2215,7 +2215,7 @@ func Test(t *testing.T) { rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r) } - ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), "", []string{tt.stripPrefix}, fakeTime} + ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), "", []string{tt.stripPrefix}, fakeTime, ""} spdxDoc, deps, err := sbomGenerator(&ctx, rootFiles...) if err != nil { @@ -2262,6 +2262,96 @@ func Test(t *testing.T) { } } +func TestGenerateSPDXNamespace(t *testing.T) { + + buildID1 := "example-1" + buildID2 := "example-2" + files1 := "file1" + timestamp1 := "2022-05-01" + timestamp2 := "2022-05-02" + files2 := "file2" + + // Test case 1: different timestamps, same files + nsh1 := generateSPDXNamespace("", timestamp1, files1) + nsh2 := generateSPDXNamespace("", timestamp2, files1) + + if nsh1 == "" { + t.Errorf("generateSPDXNamespace(%s, %s, %s): expected non-empty string, but got empty string", "", timestamp1, files1) + } + + if nsh2 == "" { + t.Errorf("generateSPDXNamespace(%s, %s, %s): expected non-empty string, but got empty string", "", timestamp2, files1) + } + + if nsh1 == nsh2 { + t.Errorf("generateSPDXNamespace(%s, %s, %s) and generateSPDXNamespace(%s, %s, %s): expected different namespace hashes, but got the same", "", timestamp1, files1, "", timestamp2, files1) + } + + // Test case 2: different build ids, same timestamps and files + nsh1 = generateSPDXNamespace(buildID1, timestamp1, files1) + nsh2 = generateSPDXNamespace(buildID2, timestamp1, files1) + + if nsh1 == "" { + t.Errorf("generateSPDXNamespace(%s, %s, %s): expected non-empty string, but got empty string", buildID1, timestamp1, files1) + } + + if nsh2 == "" { + t.Errorf("generateSPDXNamespace(%s, %s, %s): expected non-empty string, but got empty string", buildID2, timestamp1, files1) + } + + if nsh1 == nsh2 { + t.Errorf("generateSPDXNamespace(%s, %s, %s) and generateSPDXNamespace(%s, %s, %s): expected different namespace hashes, but got the same", buildID1, timestamp1, files1, buildID2, timestamp1, files1) + } + + // Test case 3: same build ids and files, different timestamps + nsh1 = generateSPDXNamespace(buildID1, timestamp1, files1) + nsh2 = generateSPDXNamespace(buildID1, timestamp2, files1) + + if nsh1 == "" { + t.Errorf("generateSPDXNamespace(%s, %s, %s): expected non-empty string, but got empty string", buildID1, timestamp1, files1) + } + + if nsh2 == "" { + t.Errorf("generateSPDXNamespace(%s, %s, %s): expected non-empty string, but got empty string", buildID1, timestamp2, files1) + } + + if nsh1 != nsh2 { + t.Errorf("generateSPDXNamespace(%s, %s, %s) and generateSPDXNamespace(%s, %s, %s): expected same namespace hashes, but got different: %s and %s", buildID1, timestamp1, files1, buildID2, timestamp1, files1, nsh1, nsh2) + } + + // Test case 4: same build ids and timestamps, different files + nsh1 = generateSPDXNamespace(buildID1, timestamp1, files1) + nsh2 = generateSPDXNamespace(buildID1, timestamp1, files2) + + if nsh1 == "" { + t.Errorf("generateSPDXNamespace(%s, %s, %s): expected non-empty string, but got empty string", buildID1, timestamp1, files1) + } + + if nsh2 == "" { + t.Errorf("generateSPDXNamespace(%s, %s, %s): expected non-empty string, but got empty string", buildID1, timestamp1, files2) + } + + if nsh1 == nsh2 { + t.Errorf("generateSPDXNamespace(%s, %s, %s) and generateSPDXNamespace(%s, %s, %s): expected different namespace hashes, but got the same", buildID1, timestamp1, files1, buildID1, timestamp1, files2) + } + + // Test case 5: empty build ids, same timestamps and different files + nsh1 = generateSPDXNamespace("", timestamp1, files1) + nsh2 = generateSPDXNamespace("", timestamp1, files2) + + if nsh1 == "" { + t.Errorf("generateSPDXNamespace(%s, %s, %s): expected non-empty string, but got empty string", "", timestamp1, files1) + } + + if nsh2 == "" { + t.Errorf("generateSPDXNamespace(%s, %s, %s): expected non-empty string, but got empty string", "", timestamp1, files2) + } + + if nsh1 == nsh2 { + t.Errorf("generateSPDXNamespace(%s, %s, %s) and generateSPDXNamespace(%s, %s, %s): expected different namespace hashes, but got the same", "", timestamp1, files1, "", timestamp1, files2) + } +} + func getCreationInfo(t *testing.T) *spdx.CreationInfo { ci, err := builder2v2.BuildCreationInfoSection2_2("Organization", "Google LLC", nil) if err != nil {