From 4519dec673c08d559557ad0d4b7143c1461abdd2 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Thu, 5 Dec 2024 00:33:41 +0530 Subject: [PATCH 1/4] add sbomasm as a tool for spdx Signed-off-by: Vivek Kumar Sahu --- pkg/edit/spdx.go | 2 +- pkg/edit/spdx_edit.go | 135 +++++++++++++++++++++++++++--------------- 2 files changed, 87 insertions(+), 50 deletions(-) diff --git a/pkg/edit/spdx.go b/pkg/edit/spdx.go index 196a503..c53726e 100644 --- a/pkg/edit/spdx.go +++ b/pkg/edit/spdx.go @@ -51,7 +51,7 @@ var spdx_strings_to_types = map[string]string{ } func spdxEdit(c *configParams) error { - //log := logger.FromContext(*c.ctx) + // log := logger.FromContext(*c.ctx) bom, err := loadSpdxSbom(*c.ctx, c.inputFilePath) if err != nil { diff --git a/pkg/edit/spdx_edit.go b/pkg/edit/spdx_edit.go index 3b05186..57144a9 100644 --- a/pkg/edit/spdx_edit.go +++ b/pkg/edit/spdx_edit.go @@ -9,6 +9,11 @@ import ( "github.com/spdx/tools-golang/spdx" ) +const ( + SBOMASM = "sbomasm" + SBOMASM_VERSION = "0.1.9" +) + type spdxEditDoc struct { bom *spdx.Document pkg *spdx.Package @@ -353,49 +358,98 @@ func (d *spdxEditDoc) hashes() error { } func (d *spdxEditDoc) tools() error { - if !d.c.shouldTools() { - return errNoConfiguration + // default sbomasm tool + sbomasmTool := spdx.Creator{ + Creator: fmt.Sprintf("%s-%s", SBOMASM, SBOMASM_VERSION), + CreatorType: "Tool", } - if d.c.search.subject != "document" { - return errNotSupported + if d.bom.CreationInfo == nil { + d.bom.CreationInfo = &spdx.CreationInfo{} + } + + if d.bom.CreationInfo.Creators == nil { + d.bom.CreationInfo.Creators = []spdx.Creator{} + } + + newTools := spdxConstructTools(d.bom, d.c) + + explicitSbomasm := false + for _, tool := range newTools { + if strings.HasPrefix(tool.Creator, SBOMASM) { + sbomasmTool = tool + explicitSbomasm = true + break + } } - tools := spdxConstructTools(d.bom, d.c) + if explicitSbomasm { + d.bom.CreationInfo.Creators = removeCreator(d.bom.CreationInfo.Creators, SBOMASM) + } if d.c.onMissing() { - if d.bom.CreationInfo == nil { - d.bom.CreationInfo = &spdx.CreationInfo{ - Creators: tools, + for _, tool := range newTools { + if !creatorExists(d.bom.CreationInfo.Creators, tool) { + d.bom.CreationInfo.Creators = spdxUniqueCreators(d.bom.CreationInfo.Creators, []spdx.Creator{tool}) } - } else if d.bom.CreationInfo.Creators == nil { - d.bom.CreationInfo.Creators = tools - } else { - d.bom.CreationInfo.Creators = append(d.bom.CreationInfo.Creators, tools...) } - } else if d.c.onAppend() { - if d.bom.CreationInfo == nil { - d.bom.CreationInfo = &spdx.CreationInfo{ - Creators: tools, - } - } else if d.bom.CreationInfo.Creators == nil { - d.bom.CreationInfo.Creators = tools - } else { - //d.bom.CreationInfo.Creators = append(d.bom.CreationInfo.Creators, tools...) - d.bom.CreationInfo.Creators = spdxUniqueTools(d.bom.CreationInfo.Creators, tools) + if !creatorExists(d.bom.CreationInfo.Creators, sbomasmTool) { + d.bom.CreationInfo.Creators = spdxUniqueCreators(d.bom.CreationInfo.Creators, []spdx.Creator{sbomasmTool}) } - } else { - if d.bom.CreationInfo == nil { - d.bom.CreationInfo = &spdx.CreationInfo{ - Creators: tools, - } - } else { - d.bom.CreationInfo.Creators = tools + return nil + } + + if d.c.onAppend() { + d.bom.CreationInfo.Creators = spdxUniqueCreators(d.bom.CreationInfo.Creators, newTools) + if !creatorExists(d.bom.CreationInfo.Creators, sbomasmTool) { + d.bom.CreationInfo.Creators = spdxUniqueCreators(d.bom.CreationInfo.Creators, []spdx.Creator{sbomasmTool}) } + return nil + } + + d.bom.CreationInfo.Creators = spdxUniqueCreators(d.bom.CreationInfo.Creators, newTools) + if !creatorExists(d.bom.CreationInfo.Creators, sbomasmTool) { + d.bom.CreationInfo.Creators = spdxUniqueCreators(d.bom.CreationInfo.Creators, []spdx.Creator{sbomasmTool}) } + return nil } +// remove a creator by name +func removeCreator(creators []spdx.Creator, creatorName string) []spdx.Creator { + result := []spdx.Creator{} + for _, c := range creators { + if !strings.HasPrefix(c.Creator, creatorName) { + result = append(result, c) + } + } + return result +} + +// ensure no duplicate creator +func creatorExists(creators []spdx.Creator, creator spdx.Creator) bool { + for _, c := range creators { + if c.Creator == creator.Creator && c.CreatorType == creator.CreatorType { + return true + } + } + return false +} + +// ensure unique creators +func spdxUniqueCreators(existing, newCreators []spdx.Creator) []spdx.Creator { + creatorSet := make(map[string]struct{}) + for _, c := range existing { + creatorSet[c.Creator] = struct{}{} + } + for _, c := range newCreators { + if _, exists := creatorSet[c.Creator]; !exists { + existing = append(existing, c) + } + } + return existing +} + func (d *spdxEditDoc) copyright() error { if !d.c.shouldCopyRight() { return errNoConfiguration @@ -489,29 +543,12 @@ func (d *spdxEditDoc) typ() error { } func (d *spdxEditDoc) timeStamp() error { - if !d.c.shouldTimeStamp() { - return errNoConfiguration + if d.bom.CreationInfo == nil { + d.bom.CreationInfo = &spdx.CreationInfo{} } - if d.c.search.subject != "document" { - return errNotSupported - } - - if d.c.onMissing() { - if d.bom.CreationInfo == nil { - d.bom.CreationInfo = &spdx.CreationInfo{} - } + d.bom.CreationInfo.Created = utcNowTime() - if d.bom.CreationInfo.Created == "" { - d.bom.CreationInfo.Created = utcNowTime() - } - } else { - if d.bom.CreationInfo == nil { - d.bom.CreationInfo = &spdx.CreationInfo{} - } - - d.bom.CreationInfo.Created = utcNowTime() - } return nil } From 781813a4fcd85fc9e88ac20b5ca9164e92c79c45 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Thu, 5 Dec 2024 14:13:59 +0530 Subject: [PATCH 2/4] add sbomasm as a tool for cdx Signed-off-by: Vivek Kumar Sahu --- pkg/edit/cdx_edit.go | 269 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 255 insertions(+), 14 deletions(-) diff --git a/pkg/edit/cdx_edit.go b/pkg/edit/cdx_edit.go index d6f3479..bbb7551 100644 --- a/pkg/edit/cdx_edit.go +++ b/pkg/edit/cdx_edit.go @@ -2,6 +2,7 @@ package edit import ( "fmt" + "strings" cydx "github.com/CycloneDX/cyclonedx-go" "github.com/interlynk-io/sbomasm/pkg/logger" @@ -63,7 +64,6 @@ func (d *cdxEditDoc) update() { } } } - } func (d *cdxEditDoc) timeStamp() error { @@ -269,31 +269,272 @@ func (d *cdxEditDoc) copyright() error { } func (d *cdxEditDoc) tools() error { - if !d.c.shouldTools() { - return errNoConfiguration + // default sbomasm tool for tools.tools + sbomasmTool := cydx.Tool{ + Name: SBOMASM, + Version: SBOMASM_VERSION, } - if d.c.search.subject != "document" { - return errNotSupported + // default sbomasm tool for tools.components + sbomasmComponent := cydx.Component{ + Type: cydx.ComponentTypeApplication, + Name: SBOMASM, + Version: SBOMASM_VERSION, } - choice := cdxConstructTools(d.bom, d.c) + // initialize the tool to cover case when tool section is not present + // in that we still need to add sbomasm as a tool + d.initializeMetadataTools() + + // get all tools explicity specified by the user via flag `--tool` + newTools := cdxConstructTools(d.bom, d.c) + + // detect whether sbomasm is explicity specified by the user via flag `--tool` or not + // if present then replace default sbomasm tool by provided sbomasm tool with version + explicitSbomasm := d.detectExplicitTool(newTools.Tools, SBOMASM, &sbomasmTool) + explicitSbomasmComponent := d.detectExplicitComponent(newTools.Components, SBOMASM, &sbomasmComponent) + + if explicitSbomasm { + d.bom.Metadata.Tools.Tools = removeTool(d.bom.Metadata.Tools.Tools, SBOMASM) + } + if explicitSbomasmComponent { + d.bom.Metadata.Tools.Components = removeComponent(d.bom.Metadata.Tools.Components, SBOMASM) + } if d.c.onMissing() { + d.addMissingToolsOrComponents(newTools, sbomasmTool, sbomasmComponent) + return nil + } + + if d.c.onAppend() { + d.appendToolsOrComponents(newTools, sbomasmTool, sbomasmComponent) + return nil + } + + // neither missing nor append case + d.mergeToolsOrComponents(newTools, sbomasmTool, sbomasmComponent) + + return nil +} + +func (d *cdxEditDoc) initializeMetadataTools() { + if d.bom.SpecVersion > cydx.SpecVersion1_4 { if d.bom.Metadata.Tools == nil { - d.bom.Metadata.Tools = choice + d.bom.Metadata.Tools = &cydx.ToolsChoice{ + Components: new([]cydx.Component), + } } - } else if d.c.onAppend() { - if d.bom.Metadata.Tools != nil { - d.bom.Metadata.Tools = cdxUniqTools(d.bom.Metadata.Tools, choice) - } else { - d.bom.Metadata.Tools = choice + if d.bom.Metadata.Tools.Components == nil { + d.bom.Metadata.Tools.Components = new([]cydx.Component) } } else { - d.bom.Metadata.Tools = choice + if d.bom.Metadata.Tools == nil { + d.bom.Metadata.Tools = &cydx.ToolsChoice{ + Tools: new([]cydx.Tool), + } + } + if d.bom.Metadata.Tools.Tools == nil { + d.bom.Metadata.Tools.Tools = new([]cydx.Tool) + } } +} - return nil +func (d *cdxEditDoc) detectExplicitTool(tools *[]cydx.Tool, sbomasmName string, sbomasmTool *cydx.Tool) bool { + if tools != nil { + for _, tool := range *tools { + if tool.Name == sbomasmName { + *sbomasmTool = tool + return true + } + } + } + return false +} + +func (d *cdxEditDoc) detectExplicitComponent(components *[]cydx.Component, sbomasmName string, sbomasmComponent *cydx.Component) bool { + if components != nil { + for _, component := range *components { + if component.Name == sbomasmName { + *sbomasmComponent = component + return true + } + } + } + return false +} + +// handle missing case for tools.tools and tools.components case +func (d *cdxEditDoc) addMissingToolsOrComponents(newTools *cydx.ToolsChoice, sbomasmTool cydx.Tool, sbomasmComponent cydx.Component) { + if d.bom.SpecVersion > cydx.SpecVersion1_4 { + if newTools.Components != nil { + for _, component := range *newTools.Components { + if !componentExists(d.bom.Metadata.Tools.Components, component) { + *d.bom.Metadata.Tools.Components = append(*d.bom.Metadata.Tools.Components, component) + } + } + } + if !componentExists(d.bom.Metadata.Tools.Components, sbomasmComponent) { + *d.bom.Metadata.Tools.Components = append(*d.bom.Metadata.Tools.Components, sbomasmComponent) + } + } else { + if newTools.Tools != nil { + for _, tool := range *newTools.Tools { + if !toolExists(d.bom.Metadata.Tools.Tools, tool) { + *d.bom.Metadata.Tools.Tools = append(*d.bom.Metadata.Tools.Tools, tool) + } + } + } + if !toolExists(d.bom.Metadata.Tools.Tools, sbomasmTool) { + *d.bom.Metadata.Tools.Tools = append(*d.bom.Metadata.Tools.Tools, sbomasmTool) + } + } +} + +// handle append case for tools.tools and tools.components case +func (d *cdxEditDoc) appendToolsOrComponents(newTools *cydx.ToolsChoice, sbomasmTool cydx.Tool, sbomasmComponent cydx.Component) { + if d.bom.SpecVersion > cydx.SpecVersion1_4 { + d.bom.Metadata.Tools.Components = cdxUniqueComponents(*d.bom.Metadata.Tools.Components, *newTools.Components) + if !componentExists(d.bom.Metadata.Tools.Components, sbomasmComponent) { + *d.bom.Metadata.Tools.Components = append(*d.bom.Metadata.Tools.Components, sbomasmComponent) + } + } else { + d.bom.Metadata.Tools.Tools = cdxUniqueTools(*d.bom.Metadata.Tools.Tools, *newTools.Tools) + if !toolExists(d.bom.Metadata.Tools.Tools, sbomasmTool) { + *d.bom.Metadata.Tools.Tools = append(*d.bom.Metadata.Tools.Tools, sbomasmTool) + } + } +} + +// handle default case for tools.tools and tools.components case +func (d *cdxEditDoc) mergeToolsOrComponents(newTools *cydx.ToolsChoice, sbomasmTool cydx.Tool, sbomasmComponent cydx.Component) { + if d.bom.SpecVersion > cydx.SpecVersion1_4 { + d.bom.Metadata.Tools.Components = cdxUniqueComponents(*d.bom.Metadata.Tools.Components, *newTools.Components) + if !componentExists(d.bom.Metadata.Tools.Components, sbomasmComponent) { + *d.bom.Metadata.Tools.Components = append(*d.bom.Metadata.Tools.Components, sbomasmComponent) + } + } else { + d.bom.Metadata.Tools.Tools = cdxUniqueTools(*d.bom.Metadata.Tools.Tools, *newTools.Tools) + if !toolExists(d.bom.Metadata.Tools.Tools, sbomasmTool) { + *d.bom.Metadata.Tools.Tools = append(*d.bom.Metadata.Tools.Tools, sbomasmTool) + } + } +} + +func toolExists(tools *[]cydx.Tool, tool cydx.Tool) bool { + if tools == nil { + return false + } + for _, t := range *tools { + if t.Name == tool.Name && t.Version == tool.Version { + return true + } + } + return false +} + +// Check if a component exists +func componentExists(components *[]cydx.Component, component cydx.Component) bool { + if components == nil { + return false + } + for _, c := range *components { + if c.Name == component.Name && c.Version == component.Version { + return true + } + } + return false +} + +func cdxUniqToolsByTool(tools []cydx.Tool) *[]cydx.Tool { + uniqMap := make(map[string]cydx.Tool) + for _, tool := range tools { + key := fmt.Sprintf("%s-%s", strings.ToLower(tool.Name), strings.ToLower(tool.Version)) + uniqMap[key] = tool + } + uniqTools := []cydx.Tool{} + for _, tool := range uniqMap { + uniqTools = append(uniqTools, tool) + } + return &uniqTools +} + +func cdxUniqComponentsByComponent(components []cydx.Component) *[]cydx.Component { + uniqMap := make(map[string]cydx.Component) + for _, component := range components { + key := fmt.Sprintf("%s-%s", strings.ToLower(component.Name), strings.ToLower(component.Version)) + uniqMap[key] = component + } + uniqComponents := []cydx.Component{} + for _, component := range uniqMap { + uniqComponents = append(uniqComponents, component) + } + return &uniqComponents +} + +func cdxUniqueTools(existing, newTools []cydx.Tool) *[]cydx.Tool { + toolSet := make(map[string]struct{}) + uniqueTools := []cydx.Tool{} + + for _, t := range existing { + key := fmt.Sprintf("%s-%s", strings.ToLower(t.Name), strings.ToLower(t.Version)) + toolSet[key] = struct{}{} + uniqueTools = append(uniqueTools, t) + } + + for _, t := range newTools { + key := fmt.Sprintf("%s-%s", strings.ToLower(t.Name), strings.ToLower(t.Version)) + if _, exists := toolSet[key]; !exists { + uniqueTools = append(uniqueTools, t) + } + } + + return &uniqueTools +} + +func cdxUniqueComponents(existing, newComponents []cydx.Component) *[]cydx.Component { + componentSet := make(map[string]struct{}) + uniqueComponents := []cydx.Component{} + + for _, c := range existing { + key := fmt.Sprintf("%s-%s", strings.ToLower(c.Name), strings.ToLower(c.Version)) + componentSet[key] = struct{}{} + uniqueComponents = append(uniqueComponents, c) + } + + for _, c := range newComponents { + key := fmt.Sprintf("%s-%s", strings.ToLower(c.Name), strings.ToLower(c.Version)) + if _, exists := componentSet[key]; !exists { + uniqueComponents = append(uniqueComponents, c) + } + } + + return &uniqueComponents +} + +func removeTool(tools *[]cydx.Tool, name string) *[]cydx.Tool { + if tools == nil { + return nil + } + filtered := []cydx.Tool{} + for _, t := range *tools { + if t.Name != name { + filtered = append(filtered, t) + } + } + return &filtered +} + +func removeComponent(components *[]cydx.Component, name string) *[]cydx.Component { + if components == nil { + return nil + } + filtered := []cydx.Component{} + for _, c := range *components { + if c.Name != name { + filtered = append(filtered, c) + } + } + return &filtered } func (d *cdxEditDoc) hashes() error { From 0c33df4434acca1666d3782495c187efc3ab6500 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Thu, 5 Dec 2024 19:05:42 +0530 Subject: [PATCH 3/4] also update timestamp on each edit & update code Signed-off-by: Vivek Kumar Sahu --- pkg/edit/cdx_edit.go | 59 +++----------------------------------------- 1 file changed, 3 insertions(+), 56 deletions(-) diff --git a/pkg/edit/cdx_edit.go b/pkg/edit/cdx_edit.go index bbb7551..bba1a61 100644 --- a/pkg/edit/cdx_edit.go +++ b/pkg/edit/cdx_edit.go @@ -67,22 +67,7 @@ func (d *cdxEditDoc) update() { } func (d *cdxEditDoc) timeStamp() error { - if !d.c.shouldTimeStamp() { - return errNoConfiguration - } - - if d.c.search.subject != "document" { - return errNotSupported - } - - if d.c.onMissing() { - if d.bom.Metadata.Timestamp == "" { - d.bom.Metadata.Timestamp = utcNowTime() - } - } else { - d.bom.Metadata.Timestamp = utcNowTime() - } - + d.bom.Metadata.Timestamp = utcNowTime() return nil } @@ -366,24 +351,12 @@ func (d *cdxEditDoc) detectExplicitComponent(components *[]cydx.Component, sboma // handle missing case for tools.tools and tools.components case func (d *cdxEditDoc) addMissingToolsOrComponents(newTools *cydx.ToolsChoice, sbomasmTool cydx.Tool, sbomasmComponent cydx.Component) { if d.bom.SpecVersion > cydx.SpecVersion1_4 { - if newTools.Components != nil { - for _, component := range *newTools.Components { - if !componentExists(d.bom.Metadata.Tools.Components, component) { - *d.bom.Metadata.Tools.Components = append(*d.bom.Metadata.Tools.Components, component) - } - } - } + d.bom.Metadata.Tools.Components = cdxUniqueComponents(*d.bom.Metadata.Tools.Components, *newTools.Components) if !componentExists(d.bom.Metadata.Tools.Components, sbomasmComponent) { *d.bom.Metadata.Tools.Components = append(*d.bom.Metadata.Tools.Components, sbomasmComponent) } } else { - if newTools.Tools != nil { - for _, tool := range *newTools.Tools { - if !toolExists(d.bom.Metadata.Tools.Tools, tool) { - *d.bom.Metadata.Tools.Tools = append(*d.bom.Metadata.Tools.Tools, tool) - } - } - } + d.bom.Metadata.Tools.Tools = cdxUniqueTools(*d.bom.Metadata.Tools.Tools, *newTools.Tools) if !toolExists(d.bom.Metadata.Tools.Tools, sbomasmTool) { *d.bom.Metadata.Tools.Tools = append(*d.bom.Metadata.Tools.Tools, sbomasmTool) } @@ -445,32 +418,6 @@ func componentExists(components *[]cydx.Component, component cydx.Component) boo return false } -func cdxUniqToolsByTool(tools []cydx.Tool) *[]cydx.Tool { - uniqMap := make(map[string]cydx.Tool) - for _, tool := range tools { - key := fmt.Sprintf("%s-%s", strings.ToLower(tool.Name), strings.ToLower(tool.Version)) - uniqMap[key] = tool - } - uniqTools := []cydx.Tool{} - for _, tool := range uniqMap { - uniqTools = append(uniqTools, tool) - } - return &uniqTools -} - -func cdxUniqComponentsByComponent(components []cydx.Component) *[]cydx.Component { - uniqMap := make(map[string]cydx.Component) - for _, component := range components { - key := fmt.Sprintf("%s-%s", strings.ToLower(component.Name), strings.ToLower(component.Version)) - uniqMap[key] = component - } - uniqComponents := []cydx.Component{} - for _, component := range uniqMap { - uniqComponents = append(uniqComponents, component) - } - return &uniqComponents -} - func cdxUniqueTools(existing, newTools []cydx.Tool) *[]cydx.Tool { toolSet := make(map[string]struct{}) uniqueTools := []cydx.Tool{} From 05ee179dbe01d383ec79c442ec70e343e2526e26 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Fri, 6 Dec 2024 18:17:29 +0530 Subject: [PATCH 4/4] update timestamp only on explicty specified Signed-off-by: Vivek Kumar Sahu --- pkg/edit/cdx_edit.go | 16 +++++++++++++++- pkg/edit/spdx_edit.go | 23 ++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/pkg/edit/cdx_edit.go b/pkg/edit/cdx_edit.go index bba1a61..03209a9 100644 --- a/pkg/edit/cdx_edit.go +++ b/pkg/edit/cdx_edit.go @@ -67,7 +67,21 @@ func (d *cdxEditDoc) update() { } func (d *cdxEditDoc) timeStamp() error { - d.bom.Metadata.Timestamp = utcNowTime() + if !d.c.shouldTimeStamp() { + return errNoConfiguration + } + + if d.c.search.subject != "document" { + return errNotSupported + } + + if d.c.onMissing() { + if d.bom.Metadata.Timestamp == "" { + d.bom.Metadata.Timestamp = utcNowTime() + } + } else { + d.bom.Metadata.Timestamp = utcNowTime() + } return nil } diff --git a/pkg/edit/spdx_edit.go b/pkg/edit/spdx_edit.go index 57144a9..9396e74 100644 --- a/pkg/edit/spdx_edit.go +++ b/pkg/edit/spdx_edit.go @@ -543,12 +543,29 @@ func (d *spdxEditDoc) typ() error { } func (d *spdxEditDoc) timeStamp() error { - if d.bom.CreationInfo == nil { - d.bom.CreationInfo = &spdx.CreationInfo{} + if !d.c.shouldTimeStamp() { + return errNoConfiguration } - d.bom.CreationInfo.Created = utcNowTime() + if d.c.search.subject != "document" { + return errNotSupported + } + + if d.c.onMissing() { + if d.bom.CreationInfo == nil { + d.bom.CreationInfo = &spdx.CreationInfo{} + } + + if d.bom.CreationInfo.Created == "" { + d.bom.CreationInfo.Created = utcNowTime() + } + } else { + if d.bom.CreationInfo == nil { + d.bom.CreationInfo = &spdx.CreationInfo{} + } + d.bom.CreationInfo.Created = utcNowTime() + } return nil }