Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ func (b *BOM) convert(specVersion SpecVersion) {
b.Definitions = nil
}

if b.Dependencies != nil && specVersion < SpecVersion1_6 {
for i := range *b.Dependencies {
(*b.Dependencies)[i].Provides = nil
}
}

if b.Metadata != nil {
if specVersion < SpecVersion1_3 {
b.Metadata.Licenses = nil
Expand Down Expand Up @@ -456,6 +462,10 @@ func serviceConverter(specVersion SpecVersion) func(*Service) {
s.ReleaseNotes = nil
}

if specVersion < SpecVersion1_5 {
s.TrustZone = ""
}

convertOrganizationalEntity(s.Provider, specVersion)
convertExternalReferences(s.ExternalReferences, specVersion)
}
Expand Down
28 changes: 28 additions & 0 deletions convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,34 @@ func Test_convertAuthors(t *testing.T) {
})
}

func Test_convertTrustZone(t *testing.T) {
t.Run("spec 1.4 and lower", func(t *testing.T) {
bom := NewBOM()
bom.Services = &[]Service{
{
Name: "Payment API",
TrustZone: "trusted",
},
}

bom.convert(SpecVersion1_4)

assert.Empty(t, (*bom.Services)[0].TrustZone)
})

t.Run("spec 1.5 and higher", func(t *testing.T) {
bom := NewBOM()
bom.Services = &[]Service{
{
Name: "Payment API",
TrustZone: "trusted",
},
}
bom.convert(SpecVersion1_5)
assert.Equal(t, "trusted", (*bom.Services)[0].TrustZone)
})
}

func Test_convertTags(t *testing.T) {
t.Run("spec 1.5 and lower", func(t *testing.T) {
bom := NewBOM()
Expand Down
7 changes: 7 additions & 0 deletions cyclonedx.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ type Signatory struct {
type Dependency struct {
Ref string `json:"ref"`
Dependencies *[]string `json:"dependsOn,omitempty"`
Provides *[]string `json:"provides,omitempty"`
}

type Diff struct {
Expand Down Expand Up @@ -711,6 +712,8 @@ const (
ERTypeDistributionIntake ExternalReferenceType = "distribution-intake"
ERTypeDocumentation ExternalReferenceType = "documentation"
ERTypeDynamicAnalysisReport ExternalReferenceType = "dynamic-analysis-report"
ERTypeDigitalSignature ExternalReferenceType = "digital-signature"
ERTypeElectronicSignature ExternalReferenceType = "electronic-signature"
ERTypeEvidence ExternalReferenceType = "evidence"
ERTypeExploitabilityStatement ExternalReferenceType = "exploitability-statement"
ERTypeFormulation ExternalReferenceType = "formulation"
Expand All @@ -722,12 +725,15 @@ const (
ERTypeModelCard ExternalReferenceType = "model-card"
ERTypeOther ExternalReferenceType = "other"
ERTypePentestReport ExternalReferenceType = "pentest-report"
ERTypePOAM ExternalReferenceType = "poam"
ERTypeQualityMetrics ExternalReferenceType = "quality-metrics"
ERTypeReleaseNotes ExternalReferenceType = "release-notes"
ERTypeRiskAssessment ExternalReferenceType = "risk-assessment"
ERTypeRFC9116 ExternalReferenceType = "rfc-9116"
ERTypeRuntimeAnalysisReport ExternalReferenceType = "runtime-analysis-report"
ERTypeSecurityContact ExternalReferenceType = "security-contact"
ERTypeSocial ExternalReferenceType = "social"
ERTypeSourceDistribution ExternalReferenceType = "source-distribution"
ERTypeStaticAnalysisReport ExternalReferenceType = "static-analysis-report"
ERTypeSupport ExternalReferenceType = "support"
ERTypeThreatModel ExternalReferenceType = "threat-model"
Expand Down Expand Up @@ -1297,6 +1303,7 @@ type Service struct {
Services *[]Service `json:"services,omitempty" xml:"services>service,omitempty"`
ReleaseNotes *ReleaseNotes `json:"releaseNotes,omitempty" xml:"releaseNotes,omitempty"`
Tags *[]string `json:"tags,omitempty" xml:"tags>tag,omitempty"`
TrustZone string `json:"trustZone,omitempty" xml:"trustZone,omitempty"`
Signature *JSFSignature `json:"signature,omitempty" xml:"-"`
}

Expand Down
187 changes: 187 additions & 0 deletions cyclonedx_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,190 @@ func TestEvidence_UnmarshalJSON(t *testing.T) {
}, evidence.Identity)
})
}

func TestService_TrustZone_MarshalJSON(t *testing.T) {
t.Run("WithTrustZone", func(t *testing.T) {
service := Service{
Name: "Payment API",
TrustZone: "trusted",
}
jsonBytes, err := json.Marshal(service)
require.NoError(t, err)
require.Contains(t, string(jsonBytes), `"trustZone":"trusted"`)
require.Contains(t, string(jsonBytes), `"name":"Payment API"`)
})

t.Run("WithoutTrustZone", func(t *testing.T) {
service := Service{
Name: "Payment API",
}
jsonBytes, err := json.Marshal(service)
require.NoError(t, err)
require.NotContains(t, string(jsonBytes), "trustZone")
require.Contains(t, string(jsonBytes), `"name":"Payment API"`)
})
}

func TestService_TrustZone_UnmarshalJSON(t *testing.T) {
t.Run("WithTrustZone", func(t *testing.T) {
var service Service
err := json.Unmarshal([]byte(`{"name":"Payment API","trustZone":"trusted"}`), &service)
require.NoError(t, err)
require.Equal(t, "Payment API", service.Name)
require.Equal(t, "trusted", service.TrustZone)
})

t.Run("WithoutTrustZone", func(t *testing.T) {
var service Service
err := json.Unmarshal([]byte(`{"name":"Payment API"}`), &service)
require.NoError(t, err)
require.Equal(t, "Payment API", service.Name)
require.Empty(t, service.TrustZone)
})
}

func TestDependency_Provides_MarshalJSON(t *testing.T) {
t.Run("WithProvides", func(t *testing.T) {
dependency := Dependency{
Ref: "crypto-library",
Provides: &[]string{"aes128gcm", "sha256"},
}
jsonBytes, err := json.Marshal(dependency)
require.NoError(t, err)
require.Contains(t, string(jsonBytes), `"ref":"crypto-library"`)
require.Contains(t, string(jsonBytes), `"provides":["aes128gcm","sha256"]`)
})

t.Run("WithProvidesAndDependsOn", func(t *testing.T) {
dependency := Dependency{
Ref: "crypto-library",
Dependencies: &[]string{"base-library"},
Provides: &[]string{"aes128gcm"},
}
jsonBytes, err := json.Marshal(dependency)
require.NoError(t, err)
require.Contains(t, string(jsonBytes), `"ref":"crypto-library"`)
require.Contains(t, string(jsonBytes), `"dependsOn":["base-library"]`)
require.Contains(t, string(jsonBytes), `"provides":["aes128gcm"]`)
})

t.Run("WithoutProvides", func(t *testing.T) {
dependency := Dependency{
Ref: "app-component",
Dependencies: &[]string{"library-a"},
}
jsonBytes, err := json.Marshal(dependency)
require.NoError(t, err)
require.Contains(t, string(jsonBytes), `"ref":"app-component"`)
require.NotContains(t, string(jsonBytes), "provides")
})
}

func TestDependency_Provides_UnmarshalJSON(t *testing.T) {
t.Run("WithProvides", func(t *testing.T) {
var dependency Dependency
err := json.Unmarshal([]byte(`{"ref":"crypto-library","provides":["aes128gcm","sha256"]}`), &dependency)
require.NoError(t, err)
require.Equal(t, "crypto-library", dependency.Ref)
require.NotNil(t, dependency.Provides)
require.Equal(t, 2, len(*dependency.Provides))
require.Equal(t, "aes128gcm", (*dependency.Provides)[0])
require.Equal(t, "sha256", (*dependency.Provides)[1])
})

t.Run("WithProvidesAndDependsOn", func(t *testing.T) {
var dependency Dependency
err := json.Unmarshal([]byte(`{"ref":"crypto-library","dependsOn":["base-library"],"provides":["aes128gcm"]}`), &dependency)
require.NoError(t, err)
require.Equal(t, "crypto-library", dependency.Ref)
require.NotNil(t, dependency.Dependencies)
require.Equal(t, 1, len(*dependency.Dependencies))
require.Equal(t, "base-library", (*dependency.Dependencies)[0])
require.NotNil(t, dependency.Provides)
require.Equal(t, 1, len(*dependency.Provides))
require.Equal(t, "aes128gcm", (*dependency.Provides)[0])
})

t.Run("WithoutProvides", func(t *testing.T) {
var dependency Dependency
err := json.Unmarshal([]byte(`{"ref":"app-component","dependsOn":["library-a"]}`), &dependency)
require.NoError(t, err)
require.Equal(t, "app-component", dependency.Ref)
require.Nil(t, dependency.Provides)
})
}

func TestExternalReferenceType_NewValues(t *testing.T) {
t.Run("DigitalSignature", func(t *testing.T) {
extRef := ExternalReference{
Type: ERTypeDigitalSignature,
URL: "https://example.com/signature",
}
jsonBytes, err := json.Marshal(extRef)
require.NoError(t, err)
require.Contains(t, string(jsonBytes), `"type":"digital-signature"`)
})

t.Run("ElectronicSignature", func(t *testing.T) {
extRef := ExternalReference{
Type: ERTypeElectronicSignature,
URL: "https://example.com/esignature",
}
jsonBytes, err := json.Marshal(extRef)
require.NoError(t, err)
require.Contains(t, string(jsonBytes), `"type":"electronic-signature"`)
})

t.Run("POAM", func(t *testing.T) {
extRef := ExternalReference{
Type: ERTypePOAM,
URL: "https://example.com/poam",
}
jsonBytes, err := json.Marshal(extRef)
require.NoError(t, err)
require.Contains(t, string(jsonBytes), `"type":"poam"`)
})

t.Run("RFC9116", func(t *testing.T) {
extRef := ExternalReference{
Type: ERTypeRFC9116,
URL: "https://example.com/security.txt",
}
jsonBytes, err := json.Marshal(extRef)
require.NoError(t, err)
require.Contains(t, string(jsonBytes), `"type":"rfc-9116"`)
})

t.Run("SourceDistribution", func(t *testing.T) {
extRef := ExternalReference{
Type: ERTypeSourceDistribution,
URL: "https://example.com/source.tar.gz",
}
jsonBytes, err := json.Marshal(extRef)
require.NoError(t, err)
require.Contains(t, string(jsonBytes), `"type":"source-distribution"`)
})

t.Run("UnmarshalNewTypes", func(t *testing.T) {
testCases := []struct {
name string
json string
expected ExternalReferenceType
}{
{"digital-signature", `{"type":"digital-signature","url":"https://example.com"}`, ERTypeDigitalSignature},
{"electronic-signature", `{"type":"electronic-signature","url":"https://example.com"}`, ERTypeElectronicSignature},
{"poam", `{"type":"poam","url":"https://example.com"}`, ERTypePOAM},
{"rfc-9116", `{"type":"rfc-9116","url":"https://example.com"}`, ERTypeRFC9116},
{"source-distribution", `{"type":"source-distribution","url":"https://example.com"}`, ERTypeSourceDistribution},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var extRef ExternalReference
err := json.Unmarshal([]byte(tc.json), &extRef)
require.NoError(t, err)
require.Equal(t, tc.expected, extRef.Type)
})
}
})
}
17 changes: 17 additions & 0 deletions cyclonedx_xml.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func (c *Copyright) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type dependencyXML struct {
Ref string `xml:"ref,attr"`
Dependencies *[]dependencyXML `xml:"dependency,omitempty"`
Provides *[]dependencyXML `xml:"provides,omitempty"`
}

func (d Dependency) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
Expand All @@ -74,6 +75,14 @@ func (d Dependency) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
xmlDep.Dependencies = &xmlDeps
}

if d.Provides != nil && len(*d.Provides) > 0 {
xmlProvides := make([]dependencyXML, len(*d.Provides))
for i := range *d.Provides {
xmlProvides[i] = dependencyXML{Ref: (*d.Provides)[i]}
}
xmlDep.Provides = &xmlProvides
}

return e.EncodeElement(xmlDep, start)
}

Expand All @@ -93,6 +102,14 @@ func (d *Dependency) UnmarshalXML(dec *xml.Decoder, start xml.StartElement) erro
dep.Dependencies = &deps
}

if xmlDep.Provides != nil && len(*xmlDep.Provides) > 0 {
provides := make([]string, len(*xmlDep.Provides))
for i := range *xmlDep.Provides {
provides[i] = (*xmlDep.Provides)[i].Ref
}
dep.Provides = &provides
}

*d = dep
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions cyclonedx_xml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ func TestEvidence_UnmarshalXML(t *testing.T) {
})
}

func toPointer[V int | float32](t *testing.T, v V) *V {
func toPointer[T any](t *testing.T, value T) *T {
t.Helper()
return &v
return &value
}
Loading
Loading