Skip to content

Commit 7af49c7

Browse files
author
Alistair McLean
committed
Adding missing fields for v1.6 CDX specification
- Added Provides array which was missing from the Dependency struct - Added TrustZone which was missing from the Service - Added Additional reference types that were missing - Added a test for cryptographic asset (CBOM) json & xml tests. Signed-off-by: Alistair McLean <alistair.mclean@netrise.io>
1 parent 72e4629 commit 7af49c7

9 files changed

+453
-2
lines changed

convert.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ func (b *BOM) convert(specVersion SpecVersion) {
5757
b.Definitions = nil
5858
}
5959

60+
if b.Dependencies != nil && specVersion < SpecVersion1_6 {
61+
for i := range *b.Dependencies {
62+
(*b.Dependencies)[i].Provides = nil
63+
}
64+
}
65+
6066
if b.Metadata != nil {
6167
if specVersion < SpecVersion1_3 {
6268
b.Metadata.Licenses = nil

cyclonedx.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,7 @@ type Signatory struct {
603603
type Dependency struct {
604604
Ref string `json:"ref"`
605605
Dependencies *[]string `json:"dependsOn,omitempty"`
606+
Provides *[]string `json:"provides,omitempty"`
606607
}
607608

608609
type Diff struct {
@@ -711,6 +712,8 @@ const (
711712
ERTypeDistributionIntake ExternalReferenceType = "distribution-intake"
712713
ERTypeDocumentation ExternalReferenceType = "documentation"
713714
ERTypeDynamicAnalysisReport ExternalReferenceType = "dynamic-analysis-report"
715+
ERTypeDigitalSignature ExternalReferenceType = "digital-signature"
716+
ERTypeElectronicSignature ExternalReferenceType = "electronic-signature"
714717
ERTypeEvidence ExternalReferenceType = "evidence"
715718
ERTypeExploitabilityStatement ExternalReferenceType = "exploitability-statement"
716719
ERTypeFormulation ExternalReferenceType = "formulation"
@@ -722,12 +725,15 @@ const (
722725
ERTypeModelCard ExternalReferenceType = "model-card"
723726
ERTypeOther ExternalReferenceType = "other"
724727
ERTypePentestReport ExternalReferenceType = "pentest-report"
728+
ERTypePOAM ExternalReferenceType = "poam"
725729
ERTypeQualityMetrics ExternalReferenceType = "quality-metrics"
726730
ERTypeReleaseNotes ExternalReferenceType = "release-notes"
727731
ERTypeRiskAssessment ExternalReferenceType = "risk-assessment"
732+
ERTypeRFC9116 ExternalReferenceType = "rfc-9116"
728733
ERTypeRuntimeAnalysisReport ExternalReferenceType = "runtime-analysis-report"
729734
ERTypeSecurityContact ExternalReferenceType = "security-contact"
730735
ERTypeSocial ExternalReferenceType = "social"
736+
ERTypeSourceDistribution ExternalReferenceType = "source-distribution"
731737
ERTypeStaticAnalysisReport ExternalReferenceType = "static-analysis-report"
732738
ERTypeSupport ExternalReferenceType = "support"
733739
ERTypeThreatModel ExternalReferenceType = "threat-model"
@@ -1297,6 +1303,7 @@ type Service struct {
12971303
Services *[]Service `json:"services,omitempty" xml:"services>service,omitempty"`
12981304
ReleaseNotes *ReleaseNotes `json:"releaseNotes,omitempty" xml:"releaseNotes,omitempty"`
12991305
Tags *[]string `json:"tags,omitempty" xml:"tags>tag,omitempty"`
1306+
TrustZone string `json:"trustZone,omitempty" xml:"trustZone,omitempty"`
13001307
Signature *JSFSignature `json:"signature,omitempty" xml:"-"`
13011308
}
13021309

cyclonedx_json_test.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,190 @@ func TestEvidence_UnmarshalJSON(t *testing.T) {
330330
}, evidence.Identity)
331331
})
332332
}
333+
334+
func TestService_TrustZone_MarshalJSON(t *testing.T) {
335+
t.Run("WithTrustZone", func(t *testing.T) {
336+
service := Service{
337+
Name: "Payment API",
338+
TrustZone: "trusted",
339+
}
340+
jsonBytes, err := json.Marshal(service)
341+
require.NoError(t, err)
342+
require.Contains(t, string(jsonBytes), `"trustZone":"trusted"`)
343+
require.Contains(t, string(jsonBytes), `"name":"Payment API"`)
344+
})
345+
346+
t.Run("WithoutTrustZone", func(t *testing.T) {
347+
service := Service{
348+
Name: "Payment API",
349+
}
350+
jsonBytes, err := json.Marshal(service)
351+
require.NoError(t, err)
352+
require.NotContains(t, string(jsonBytes), "trustZone")
353+
require.Contains(t, string(jsonBytes), `"name":"Payment API"`)
354+
})
355+
}
356+
357+
func TestService_TrustZone_UnmarshalJSON(t *testing.T) {
358+
t.Run("WithTrustZone", func(t *testing.T) {
359+
var service Service
360+
err := json.Unmarshal([]byte(`{"name":"Payment API","trustZone":"trusted"}`), &service)
361+
require.NoError(t, err)
362+
require.Equal(t, "Payment API", service.Name)
363+
require.Equal(t, "trusted", service.TrustZone)
364+
})
365+
366+
t.Run("WithoutTrustZone", func(t *testing.T) {
367+
var service Service
368+
err := json.Unmarshal([]byte(`{"name":"Payment API"}`), &service)
369+
require.NoError(t, err)
370+
require.Equal(t, "Payment API", service.Name)
371+
require.Empty(t, service.TrustZone)
372+
})
373+
}
374+
375+
func TestDependency_Provides_MarshalJSON(t *testing.T) {
376+
t.Run("WithProvides", func(t *testing.T) {
377+
dependency := Dependency{
378+
Ref: "crypto-library",
379+
Provides: &[]string{"aes128gcm", "sha256"},
380+
}
381+
jsonBytes, err := json.Marshal(dependency)
382+
require.NoError(t, err)
383+
require.Contains(t, string(jsonBytes), `"ref":"crypto-library"`)
384+
require.Contains(t, string(jsonBytes), `"provides":["aes128gcm","sha256"]`)
385+
})
386+
387+
t.Run("WithProvidesAndDependsOn", func(t *testing.T) {
388+
dependency := Dependency{
389+
Ref: "crypto-library",
390+
Dependencies: &[]string{"base-library"},
391+
Provides: &[]string{"aes128gcm"},
392+
}
393+
jsonBytes, err := json.Marshal(dependency)
394+
require.NoError(t, err)
395+
require.Contains(t, string(jsonBytes), `"ref":"crypto-library"`)
396+
require.Contains(t, string(jsonBytes), `"dependsOn":["base-library"]`)
397+
require.Contains(t, string(jsonBytes), `"provides":["aes128gcm"]`)
398+
})
399+
400+
t.Run("WithoutProvides", func(t *testing.T) {
401+
dependency := Dependency{
402+
Ref: "app-component",
403+
Dependencies: &[]string{"library-a"},
404+
}
405+
jsonBytes, err := json.Marshal(dependency)
406+
require.NoError(t, err)
407+
require.Contains(t, string(jsonBytes), `"ref":"app-component"`)
408+
require.NotContains(t, string(jsonBytes), "provides")
409+
})
410+
}
411+
412+
func TestDependency_Provides_UnmarshalJSON(t *testing.T) {
413+
t.Run("WithProvides", func(t *testing.T) {
414+
var dependency Dependency
415+
err := json.Unmarshal([]byte(`{"ref":"crypto-library","provides":["aes128gcm","sha256"]}`), &dependency)
416+
require.NoError(t, err)
417+
require.Equal(t, "crypto-library", dependency.Ref)
418+
require.NotNil(t, dependency.Provides)
419+
require.Equal(t, 2, len(*dependency.Provides))
420+
require.Equal(t, "aes128gcm", (*dependency.Provides)[0])
421+
require.Equal(t, "sha256", (*dependency.Provides)[1])
422+
})
423+
424+
t.Run("WithProvidesAndDependsOn", func(t *testing.T) {
425+
var dependency Dependency
426+
err := json.Unmarshal([]byte(`{"ref":"crypto-library","dependsOn":["base-library"],"provides":["aes128gcm"]}`), &dependency)
427+
require.NoError(t, err)
428+
require.Equal(t, "crypto-library", dependency.Ref)
429+
require.NotNil(t, dependency.Dependencies)
430+
require.Equal(t, 1, len(*dependency.Dependencies))
431+
require.Equal(t, "base-library", (*dependency.Dependencies)[0])
432+
require.NotNil(t, dependency.Provides)
433+
require.Equal(t, 1, len(*dependency.Provides))
434+
require.Equal(t, "aes128gcm", (*dependency.Provides)[0])
435+
})
436+
437+
t.Run("WithoutProvides", func(t *testing.T) {
438+
var dependency Dependency
439+
err := json.Unmarshal([]byte(`{"ref":"app-component","dependsOn":["library-a"]}`), &dependency)
440+
require.NoError(t, err)
441+
require.Equal(t, "app-component", dependency.Ref)
442+
require.Nil(t, dependency.Provides)
443+
})
444+
}
445+
446+
func TestExternalReferenceType_NewValues(t *testing.T) {
447+
t.Run("DigitalSignature", func(t *testing.T) {
448+
extRef := ExternalReference{
449+
Type: ERTypeDigitalSignature,
450+
URL: "https://example.com/signature",
451+
}
452+
jsonBytes, err := json.Marshal(extRef)
453+
require.NoError(t, err)
454+
require.Contains(t, string(jsonBytes), `"type":"digital-signature"`)
455+
})
456+
457+
t.Run("ElectronicSignature", func(t *testing.T) {
458+
extRef := ExternalReference{
459+
Type: ERTypeElectronicSignature,
460+
URL: "https://example.com/esignature",
461+
}
462+
jsonBytes, err := json.Marshal(extRef)
463+
require.NoError(t, err)
464+
require.Contains(t, string(jsonBytes), `"type":"electronic-signature"`)
465+
})
466+
467+
t.Run("POAM", func(t *testing.T) {
468+
extRef := ExternalReference{
469+
Type: ERTypePOAM,
470+
URL: "https://example.com/poam",
471+
}
472+
jsonBytes, err := json.Marshal(extRef)
473+
require.NoError(t, err)
474+
require.Contains(t, string(jsonBytes), `"type":"poam"`)
475+
})
476+
477+
t.Run("RFC9116", func(t *testing.T) {
478+
extRef := ExternalReference{
479+
Type: ERTypeRFC9116,
480+
URL: "https://example.com/security.txt",
481+
}
482+
jsonBytes, err := json.Marshal(extRef)
483+
require.NoError(t, err)
484+
require.Contains(t, string(jsonBytes), `"type":"rfc-9116"`)
485+
})
486+
487+
t.Run("SourceDistribution", func(t *testing.T) {
488+
extRef := ExternalReference{
489+
Type: ERTypeSourceDistribution,
490+
URL: "https://example.com/source.tar.gz",
491+
}
492+
jsonBytes, err := json.Marshal(extRef)
493+
require.NoError(t, err)
494+
require.Contains(t, string(jsonBytes), `"type":"source-distribution"`)
495+
})
496+
497+
t.Run("UnmarshalNewTypes", func(t *testing.T) {
498+
testCases := []struct {
499+
name string
500+
json string
501+
expected ExternalReferenceType
502+
}{
503+
{"digital-signature", `{"type":"digital-signature","url":"https://example.com"}`, ERTypeDigitalSignature},
504+
{"electronic-signature", `{"type":"electronic-signature","url":"https://example.com"}`, ERTypeElectronicSignature},
505+
{"poam", `{"type":"poam","url":"https://example.com"}`, ERTypePOAM},
506+
{"rfc-9116", `{"type":"rfc-9116","url":"https://example.com"}`, ERTypeRFC9116},
507+
{"source-distribution", `{"type":"source-distribution","url":"https://example.com"}`, ERTypeSourceDistribution},
508+
}
509+
510+
for _, tc := range testCases {
511+
t.Run(tc.name, func(t *testing.T) {
512+
var extRef ExternalReference
513+
err := json.Unmarshal([]byte(tc.json), &extRef)
514+
require.NoError(t, err)
515+
require.Equal(t, tc.expected, extRef.Type)
516+
})
517+
}
518+
})
519+
}

cyclonedx_xml.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ func (c *Copyright) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
6161
type dependencyXML struct {
6262
Ref string `xml:"ref,attr"`
6363
Dependencies *[]dependencyXML `xml:"dependency,omitempty"`
64+
Provides *[]dependencyXML `xml:"provides,omitempty"`
6465
}
6566

6667
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 {
7475
xmlDep.Dependencies = &xmlDeps
7576
}
7677

78+
if d.Provides != nil && len(*d.Provides) > 0 {
79+
xmlProvides := make([]dependencyXML, len(*d.Provides))
80+
for i := range *d.Provides {
81+
xmlProvides[i] = dependencyXML{Ref: (*d.Provides)[i]}
82+
}
83+
xmlDep.Provides = &xmlProvides
84+
}
85+
7786
return e.EncodeElement(xmlDep, start)
7887
}
7988

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

105+
if xmlDep.Provides != nil && len(*xmlDep.Provides) > 0 {
106+
provides := make([]string, len(*xmlDep.Provides))
107+
for i := range *xmlDep.Provides {
108+
provides[i] = (*xmlDep.Provides)[i].Ref
109+
}
110+
dep.Provides = &provides
111+
}
112+
96113
*d = dep
97114
return nil
98115
}

cyclonedx_xml_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,7 @@ func TestEvidence_UnmarshalXML(t *testing.T) {
707707
})
708708
}
709709

710-
func toPointer[V int | float32](t *testing.T, v V) *V {
710+
func toPointer[T any](t *testing.T, value T) *T {
711711
t.Helper()
712-
return &v
712+
return &value
713713
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{
2+
"bomFormat": "CycloneDX",
3+
"specVersion": "1.6",
4+
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
5+
"version": 1,
6+
"metadata": {
7+
"component": {
8+
"bom-ref": "acme-application",
9+
"type": "application",
10+
"name": "Acme Application",
11+
"version": "1.0"
12+
}
13+
},
14+
"components": [
15+
{
16+
"bom-ref": "aes128gcm",
17+
"type": "cryptographic-asset",
18+
"name": "AES",
19+
"cryptoProperties": {
20+
"assetType": "algorithm",
21+
"algorithmProperties": {
22+
"primitive": "ae",
23+
"parameterSetIdentifier": "128",
24+
"executionEnvironment": "software-plain-ram",
25+
"implementationPlatform": "x86_64",
26+
"certificationLevel": [
27+
"none"
28+
],
29+
"mode": "gcm",
30+
"cryptoFunctions": [
31+
"keygen",
32+
"encrypt",
33+
"decrypt",
34+
"tag"
35+
],
36+
"classicalSecurityLevel": 128,
37+
"nistQuantumSecurityLevel": 1
38+
},
39+
"oid": "2.16.840.1.101.3.4.1.6"
40+
}
41+
},
42+
{
43+
"bom-ref": "crypto-library",
44+
"type": "library",
45+
"name": "Crypto library",
46+
"version": "1.0.0"
47+
},
48+
{
49+
"bom-ref": "some-library",
50+
"type": "library",
51+
"name": "Some library",
52+
"version": "1.0.0"
53+
}
54+
],
55+
"dependencies": [
56+
{
57+
"ref": "acme-application",
58+
"dependsOn": [
59+
"crypto-library"
60+
]
61+
},
62+
{
63+
"ref": "crypto-library",
64+
"dependsOn": [
65+
"some-library"
66+
],
67+
"provides": [
68+
"aes128gcm"
69+
]
70+
}
71+
]
72+
}
73+

0 commit comments

Comments
 (0)