diff --git a/convert.go b/convert.go
index d428d01..d53814b 100644
--- a/convert.go
+++ b/convert.go
@@ -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
@@ -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)
}
diff --git a/convert_test.go b/convert_test.go
index eb47a9a..6636fb0 100644
--- a/convert_test.go
+++ b/convert_test.go
@@ -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()
diff --git a/cyclonedx.go b/cyclonedx.go
index 3be6b63..e421335 100644
--- a/cyclonedx.go
+++ b/cyclonedx.go
@@ -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 {
@@ -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"
@@ -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"
@@ -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:"-"`
}
diff --git a/cyclonedx_json_test.go b/cyclonedx_json_test.go
index bf1b627..c93b54b 100644
--- a/cyclonedx_json_test.go
+++ b/cyclonedx_json_test.go
@@ -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)
+ })
+ }
+ })
+}
diff --git a/cyclonedx_xml.go b/cyclonedx_xml.go
index 0bb560a..6a7cb57 100644
--- a/cyclonedx_xml.go
+++ b/cyclonedx_xml.go
@@ -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 {
@@ -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)
}
@@ -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
}
diff --git a/cyclonedx_xml_test.go b/cyclonedx_xml_test.go
index c158e60..9c858ee 100644
--- a/cyclonedx_xml_test.go
+++ b/cyclonedx_xml_test.go
@@ -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
}
diff --git a/testdata/snapshots/cyclonedx-go-TestRoundTripJSON-func1-valid-cryptographic-asset.json b/testdata/snapshots/cyclonedx-go-TestRoundTripJSON-func1-valid-cryptographic-asset.json
new file mode 100644
index 0000000..84a9b2f
--- /dev/null
+++ b/testdata/snapshots/cyclonedx-go-TestRoundTripJSON-func1-valid-cryptographic-asset.json
@@ -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"
+ ]
+ }
+ ]
+}
+
diff --git a/testdata/snapshots/cyclonedx-go-TestRoundTripXML-func1-valid-cryptographic-asset.xml b/testdata/snapshots/cyclonedx-go-TestRoundTripXML-func1-valid-cryptographic-asset.xml
new file mode 100644
index 0000000..0bd9d55
--- /dev/null
+++ b/testdata/snapshots/cyclonedx-go-TestRoundTripXML-func1-valid-cryptographic-asset.xml
@@ -0,0 +1,51 @@
+
+
+
+
+ Acme Application
+ 1.0
+
+
+
+
+ AES
+
+ algorithm
+
+ ae
+ 128
+ software-plain-ram
+ x86_64
+ none
+ gcm
+
+ keygen
+ encrypt
+ decrypt
+ tag
+
+ 128
+ 1
+
+ 2.16.840.1.101.3.4.1.6
+
+
+
+ Crypto library
+ 1.0.0
+
+
+ Some library
+ 1.0.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testdata/valid-cryptographic-asset.json b/testdata/valid-cryptographic-asset.json
new file mode 100644
index 0000000..e508b2c
--- /dev/null
+++ b/testdata/valid-cryptographic-asset.json
@@ -0,0 +1,59 @@
+{
+ "bomFormat": "CycloneDX",
+ "specVersion": "1.6",
+ "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
+ "version": 1,
+ "metadata": {
+ "component": {
+ "type": "application",
+ "bom-ref": "acme-application",
+ "name": "Acme Application",
+ "version": "1.0"
+ }
+ },
+ "components": [
+ {
+ "type": "cryptographic-asset",
+ "bom-ref": "aes128gcm",
+ "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"
+ }
+ },
+ {
+ "type": "library",
+ "bom-ref": "crypto-library",
+ "name": "Crypto library",
+ "version": "1.0.0"
+ },
+ {
+ "type": "library",
+ "bom-ref": "some-library",
+ "name": "Some library",
+ "version": "1.0.0"
+ }
+ ],
+ "dependencies": [
+ {
+ "ref": "acme-application",
+ "dependsOn": ["crypto-library"]
+ },
+ {
+ "ref": "crypto-library",
+ "provides": ["aes128gcm"],
+ "dependsOn": ["some-library"]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/testdata/valid-cryptographic-asset.xml b/testdata/valid-cryptographic-asset.xml
new file mode 100644
index 0000000..364efe8
--- /dev/null
+++ b/testdata/valid-cryptographic-asset.xml
@@ -0,0 +1,51 @@
+
+
+
+
+ Acme Application
+ 1.0
+
+
+
+
+ AES
+
+ algorithm
+
+ ae
+ 128
+ software-plain-ram
+ x86_64
+ none
+ gcm
+
+ keygen
+ encrypt
+ decrypt
+ tag
+
+ 128
+ 1
+
+ 2.16.840.1.101.3.4.1.6
+
+
+
+ Crypto library
+ 1.0.0
+
+
+ Some library
+ 1.0.0
+
+
+
+
+
+
+
+
+
+
+
+