Skip to content

Commit

Permalink
Merge pull request #90 from CycloneDX/spec-v1.5
Browse files Browse the repository at this point in the history
feat: add support for spec v1.5
  • Loading branch information
nscuro authored Dec 9, 2023
2 parents 55ff321 + 64eb0c8 commit b9654ae
Show file tree
Hide file tree
Showing 49 changed files with 3,713 additions and 278 deletions.
11 changes: 1 addition & 10 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,6 @@ linters:
enable:
- asciicheck
- errorlint
- gci
- gofumpt
- gofmt
- gosec
- wastedassign

linters-settings:
gci:
sections:
- standard
- default
- prefix(github.com/CycloneDX)
custom-order: true
178 changes: 173 additions & 5 deletions convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func (b *BOM) convert(specVersion SpecVersion) {
}
if specVersion < SpecVersion1_5 {
b.Annotations = nil
b.Formulation = nil
}

if b.Metadata != nil {
Expand All @@ -61,13 +62,13 @@ func (b *BOM) convert(specVersion SpecVersion) {
b.Metadata.Lifecycles = nil
}

if specVersion < SpecVersion1_5 {
b.Metadata.Lifecycles = nil
}

recurseComponent(b.Metadata.Component, componentConverter(specVersion))
convertLicenses(b.Metadata.Licenses, specVersion)
if b.Metadata.Tools != nil {
for i := range *b.Metadata.Tools {
convertTool(&(*b.Metadata.Tools)[i], specVersion)
}
}
convertTools(b.Metadata.Tools, specVersion)
}

if b.Components != nil {
Expand All @@ -82,6 +83,18 @@ func (b *BOM) convert(specVersion SpecVersion) {
}
}

if b.Vulnerabilities != nil {
convertVulnerabilities(b.Vulnerabilities, specVersion)
}

if b.Compositions != nil {
convertCompositions(b.Compositions, specVersion)
}

if b.ExternalReferences != nil {
convertExternalReferences(b.ExternalReferences, specVersion)
}

b.SpecVersion = specVersion
b.XMLNS = xmlNamespaces[specVersion]
b.JSONSchema = jsonSchemas[specVersion]
Expand Down Expand Up @@ -121,6 +134,17 @@ func componentConverter(specVersion SpecVersion) func(*Component) {
}
}

if specVersion < SpecVersion1_5 {
c.ModelCard = nil
c.Data = nil

if c.Evidence != nil {
c.Evidence.Identity = nil
c.Evidence.Occurrences = nil
c.Evidence.Callstack = nil
}
}

if !specVersion.supportsComponentType(c.Type) {
c.Type = ComponentTypeApplication
}
Expand All @@ -133,6 +157,19 @@ func componentConverter(specVersion SpecVersion) func(*Component) {
}
}

func convertCompositions(comps *[]Composition, specVersion SpecVersion) {
if comps == nil {
return
}

for i := range *comps {
comp := &(*comps)[i]
if !specVersion.supportsCompositionAggregate(comp.Aggregate) {
comp.Aggregate = CompositionAggregateUnknown
}
}
}

// convertExternalReferences modifies an ExternalReference slice such that it adheres to a given SpecVersion.
func convertExternalReferences(extRefs *[]ExternalReference, specVersion SpecVersion) {
if extRefs == nil {
Expand Down Expand Up @@ -218,6 +255,33 @@ func convertLicenses(licenses *Licenses, specVersion SpecVersion) {
}
}

func convertVulnerabilities(vulns *[]Vulnerability, specVersion SpecVersion) {
if vulns == nil {
return
}

for i := range *vulns {
vuln := &(*vulns)[i]

convertTools(vuln.Tools, specVersion)

if specVersion < SpecVersion1_5 {
vuln.ProofOfConcept = nil
vuln.Rejected = ""
vuln.Workaround = ""
}

if vuln.Ratings != nil {
for j := range *vuln.Ratings {
rating := &(*vuln.Ratings)[j]
if !specVersion.supportsScoringMethod(rating.Method) {
rating.Method = ScoringMethodOther
}
}
}
}
}

// serviceConverter modifies a Service such that it adheres to a given SpecVersion.
func serviceConverter(specVersion SpecVersion) func(*Service) {
return func(s *Service) {
Expand All @@ -233,6 +297,50 @@ func serviceConverter(specVersion SpecVersion) func(*Service) {
}
}

// convertTools modifies a ToolsChoice such that it adheres to a given SpecVersion.
func convertTools(tools *ToolsChoice, specVersion SpecVersion) {
if tools == nil {
return
}

if specVersion < SpecVersion1_5 {
convertedTools := make([]Tool, 0)
if tools.Components != nil {
for i := range *tools.Components {
tool := convertComponentToTool((*tools.Components)[i], specVersion)
if tool != nil {
convertedTools = append(convertedTools, *tool)
}
}
tools.Components = nil
}

if tools.Services != nil {
for i := range *tools.Services {
tool := convertServiceToTool((*tools.Services)[i], specVersion)
if tool != nil {
convertedTools = append(convertedTools, *tool)
}
}
tools.Services = nil
}

if len(convertedTools) > 0 {
if tools.Tools == nil {
tools.Tools = &convertedTools
} else {
*tools.Tools = append(*tools.Tools, convertedTools...)
}
}
}

if tools.Tools != nil {
for i := range *tools.Tools {
convertTool(&(*tools.Tools)[i], specVersion)
}
}
}

// convertTool modifies a Tool such that it adheres to a given SpecVersion.
func convertTool(tool *Tool, specVersion SpecVersion) {
if tool == nil {
Expand All @@ -247,6 +355,40 @@ func convertTool(tool *Tool, specVersion SpecVersion) {
convertHashes(tool.Hashes, specVersion)
}

// convertComponentToTool converts a Component to a Tool for use in ToolsChoice.Tools.
func convertComponentToTool(component Component, _ SpecVersion) *Tool {
tool := Tool{
Vendor: component.Author,
Name: component.Name,
Version: component.Version,
Hashes: component.Hashes,
ExternalReferences: component.ExternalReferences,
}

if component.Supplier != nil {
// There is no perfect 1:1 mapping for the Vendor field, but Supplier comes closest.
// https://github.com/CycloneDX/cyclonedx-go/issues/115#issuecomment-1688710539
tool.Vendor = component.Supplier.Name
}

return &tool
}

// convertServiceToTool converts a Service to a Tool for use in ToolsChoice.Tools.
func convertServiceToTool(service Service, _ SpecVersion) *Tool {
tool := Tool{
Name: service.Name,
Version: service.Version,
ExternalReferences: service.ExternalReferences,
}

if service.Provider != nil {
tool.Vendor = service.Provider.Name
}

return &tool
}

func recurseComponent(component *Component, f func(c *Component)) {
if component == nil {
return
Expand Down Expand Up @@ -307,17 +449,32 @@ func (sv SpecVersion) supportsComponentType(cType ComponentType) bool {
return false
}

func (sv SpecVersion) supportsCompositionAggregate(ca CompositionAggregate) bool {
switch ca {
case CompositionAggregateIncompleteFirstPartyOpenSourceOnly, CompositionAggregateIncompleteFirstPartyProprietaryOnly,
CompositionAggregateIncompleteThirdPartyOpenSourceOnly, CompositionAggregateIncompleteThirdPartyProprietaryOnly:
return sv >= SpecVersion1_5
}

return sv >= SpecVersion1_3
}

func (sv SpecVersion) supportsExternalReferenceType(ert ExternalReferenceType) bool {
switch ert {
case ERTypeAdversaryModel,
ERTypeAttestation,
ERTypeCertificationReport,
ERTypeCodifiedInfrastructure,
ERTypeComponentAnalysisReport,
ERTypeConfiguration,
ERTypeDistributionIntake,
ERTypeDynamicAnalysisReport,
ERTypeEvidence,
ERTypeExploitabilityStatement,
ERTypeFormulation,
ERTypeLog,
ERTypeMaturityReport,
ERTypeModelCard,
ERTypePentestReport,
ERTypeQualityMetrics,
ERTypeRiskAssessment,
Expand Down Expand Up @@ -352,3 +509,14 @@ func (sv SpecVersion) supportsScope(scope Scope) bool {

return false
}

func (sv SpecVersion) supportsScoringMethod(method ScoringMethod) bool {
switch method {
case ScoringMethodCVSSv2, ScoringMethodCVSSv3, ScoringMethodCVSSv31, ScoringMethodOWASP, ScoringMethodOther:
return sv >= SpecVersion1_4
case ScoringMethodCVSSv4, ScoringMethodSSVC:
return sv >= SpecVersion1_5
}

return false
}
Loading

0 comments on commit b9654ae

Please sign in to comment.