Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 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
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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"metadata": {
"component": {
"bom-ref": "acme-application",
"type": "application",
"name": "Acme Application",
"version": "1.0"
}
},
"components": [
{
"bom-ref": "aes128gcm",
"type": "cryptographic-asset",
"name": "AES",
"cryptoProperties": {
"assetType": "algorithm",
"algorithmProperties": {
"primitive": "ae",
"parameterSetIdentifier": "128",
"executionEnvironment": "software-plain-ram",
"implementationPlatform": "x86_64",
"certificationLevel": [
"none"
],
"mode": "gcm",
"cryptoFunctions": [
"keygen",
"encrypt",
"decrypt",
"tag"
],
"classicalSecurityLevel": 128,
"nistQuantumSecurityLevel": 1
},
"oid": "2.16.840.1.101.3.4.1.6"
}
},
{
"bom-ref": "crypto-library",
"type": "library",
"name": "Crypto library",
"version": "1.0.0"
},
{
"bom-ref": "some-library",
"type": "library",
"name": "Some library",
"version": "1.0.0"
}
],
"dependencies": [
{
"ref": "acme-application",
"dependsOn": [
"crypto-library"
]
},
{
"ref": "crypto-library",
"dependsOn": [
"some-library"
],
"provides": [
"aes128gcm"
]
}
]
}

Loading
Loading