Skip to content

Commit

Permalink
feat: add support for cyclonedx spec v1.4 (#16)
Browse files Browse the repository at this point in the history
* ci: update cyclonedx-cli: `0.15.2` -> `0.22.0`

Signed-off-by: nscuro <nscuro@protonmail.com>

* test: fix cyclonedx-cli invocation

Signed-off-by: nscuro <nscuro@protonmail.com>

* test: rename testdata files to not include spec version

will allow us to spot differences after v1.4 update more easily

Signed-off-by: nscuro <nscuro@protonmail.com>

* test: add testdata input files for v1.4

Signed-off-by: nscuro <nscuro@protonmail.com>

* wip: implement v1.4 changes

adds release notes and vulnerabilities

Signed-off-by: nscuro <nscuro@protonmail.com>

* use `string` for timestamp fields; fix `response` field for json

Signed-off-by: nscuro <nscuro@protonmail.com>

* move new types to `cyclonedx.go`

Signed-off-by: nscuro <nscuro@protonmail.com>

* fix affected versions for json

Signed-off-by: nscuro <nscuro@protonmail.com>

* update snapshots for v1.4

Signed-off-by: nscuro <nscuro@protonmail.com>

* make json validator happy

see CycloneDX/cyclonedx-cli#202

Signed-off-by: nscuro <nscuro@protonmail.com>

* add jsf model

Signed-off-by: nscuro <nscuro@protonmail.com>

* Revert "add jsf model"

This reverts commit abee4cf.

JSF support will be implemented with #17

Signed-off-by: nscuro <nscuro@protonmail.com>

Closes #14
  • Loading branch information
nscuro committed Feb 7, 2022
1 parent e0d515e commit 9082cd7
Show file tree
Hide file tree
Showing 117 changed files with 1,452 additions and 132 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Expand Up @@ -38,7 +38,8 @@ jobs:
run: |
mkdir -p "$HOME/.local/bin"
echo "$HOME/.local/bin" >> $GITHUB_PATH
wget -O "$HOME/.local/bin/cyclonedx" https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.15.2/cyclonedx-linux-x64
wget -O "$HOME/.local/bin/cyclonedx" https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.22.0/cyclonedx-linux-x64
echo "ae39404a9dc8b2e7be0a9559781ee9fe3492201d2629de139d702fd4535ffdd6 $HOME/.local/bin/cyclonedx" | sha256sum -c
chmod +x "$HOME/.local/bin/cyclonedx"
- name: Checkout Repository
uses: actions/checkout@v2
Expand Down
167 changes: 159 additions & 8 deletions cyclonedx.go
Expand Up @@ -28,10 +28,26 @@ import (
const (
BOMFormat = "CycloneDX"
defaultVersion = 1
SpecVersion = "1.3"
XMLNamespace = "http://cyclonedx.org/schema/bom/1.3"
SpecVersion = "1.4"
XMLNamespace = "http://cyclonedx.org/schema/bom/1.4"
)

type Advisory struct {
Title string `json:"title,omitempty" xml:"title,omitempty"`
URL string `json:"url" xml:"url"`
}

type AffectedVersions struct {
Version string `json:"version,omitempty" xml:"version,omitempty"`
Range string `json:"range,omitempty" xml:"range,omitempty"`
Status VulnerabilityStatus `json:"status" xml:"status"`
}

type Affects struct {
Ref string `json:"ref" xml:"ref"`
Range *[]AffectedVersions `json:"versions,omitempty" xml:"versions>version,omitempty"`
}

type AttachedText struct {
Content string `json:"content" xml:",innerxml"`
ContentType string `json:"contentType,omitempty" xml:"content-type,attr,omitempty"`
Expand All @@ -56,6 +72,7 @@ type BOM struct {
Dependencies *[]Dependency `json:"dependencies,omitempty" xml:"dependencies>dependency,omitempty"`
Compositions *[]Composition `json:"compositions,omitempty" xml:"compositions>composition,omitempty"`
Properties *[]Property `json:"properties,omitempty" xml:"properties>property,omitempty"`
Vulnerabilities *[]Vulnerability `json:"vulnerabilities,omitempty" xml:"vulnerabilities>vulnerability,omitempty"`
}

func NewBOM() *BOM {
Expand Down Expand Up @@ -129,7 +146,7 @@ type Component struct {
Publisher string `json:"publisher,omitempty" xml:"publisher,omitempty"`
Group string `json:"group,omitempty" xml:"group,omitempty"`
Name string `json:"name" xml:"name"`
Version string `json:"version" xml:"version"`
Version string `json:"version,omitempty" xml:"version,omitempty"`
Description string `json:"description,omitempty" xml:"description,omitempty"`
Scope Scope `json:"scope,omitempty" xml:"scope,omitempty"`
Hashes *[]Hash `json:"hashes,omitempty" xml:"hashes>hash,omitempty"`
Expand All @@ -144,6 +161,7 @@ type Component struct {
Properties *[]Property `json:"properties,omitempty" xml:"properties>property,omitempty"`
Components *[]Component `json:"components,omitempty" xml:"components>component,omitempty"`
Evidence *Evidence `json:"evidence,omitempty" xml:"evidence,omitempty"`
ReleaseNotes *ReleaseNotes `json:"releaseNotes,omitempty" xml:"releaseNotes,omitempty"`
}

type Composition struct {
Expand Down Expand Up @@ -180,6 +198,11 @@ func (c *Copyright) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return nil
}

type Credits struct {
Organizations *[]OrganizationalEntity `json:"organizations,omitempty" xml:"organizations>organization,omitempty"`
Individuals *[]OrganizationalContact `json:"individuals,omitempty" xml:"individuals>individual,omitempty"`
}

type DataClassification struct {
Flow DataFlow `json:"flow" xml:"flow,attr"`
Classification string `json:"classification" xml:",innerxml"`
Expand Down Expand Up @@ -276,6 +299,7 @@ const (
ERTypeMailingList ExternalReferenceType = "mailing-list"
ERTypeOther ExternalReferenceType = "other"
ERTypeIssueTracker ExternalReferenceType = "issue-tracker"
ERTypeReleaseNotes ExternalReferenceType = "release-notes"
ERTypeSocial ExternalReferenceType = "social"
ERTypeSupport ExternalReferenceType = "support"
ERTypeVCS ExternalReferenceType = "vcs"
Expand Down Expand Up @@ -309,9 +333,44 @@ type IdentifiableAction struct {
EMail string `json:"email,omitempty" xml:"email,omitempty"`
}

type ImpactAnalysisJustification string

const (
IAJCodeNotPresent ImpactAnalysisJustification = "code_not_present"
IAJCodeNotReachable ImpactAnalysisJustification = "code_not_reachable"
IAJRequiresConfiguration ImpactAnalysisJustification = "requires_configuration"
IAJRequiresDependency ImpactAnalysisJustification = "requires_dependency"
IAJRequiresEnvironment ImpactAnalysisJustification = "requires_environment"
IAJProtectedByCompiler ImpactAnalysisJustification = "protected_by_compiler"
IAJProtectedAtRuntime ImpactAnalysisJustification = "protected_at_runtime"
IAJProtectedAtPerimeter ImpactAnalysisJustification = "protected_at_perimeter"
IAJProtectedByMitigatingControl ImpactAnalysisJustification = "protected_by_mitigating_control"
)

type ImpactAnalysisResponse string

const (
IARCanNotFix ImpactAnalysisResponse = "can_not_fix"
IARWillNotFix ImpactAnalysisResponse = "will_not_fix"
IARUpdate ImpactAnalysisResponse = "update"
IARRollback ImpactAnalysisResponse = "rollback"
IARWorkaroundAvailable ImpactAnalysisResponse = "workaround_available"
)

type ImpactAnalysisState string

const (
IASResolved ImpactAnalysisState = "resolved"
IASResolvedWithPedigree ImpactAnalysisState = "resolved_with_pedigree"
IASExploitable ImpactAnalysisState = "exploitable"
IASInTriage ImpactAnalysisState = "in_triage"
IASFalsePositive ImpactAnalysisState = "false_positive"
IASNotAffected ImpactAnalysisState = "not_affected"
)

type Issue struct {
ID string `json:"id" xml:"id"`
Name string `json:"name" xml:"name"`
Name string `json:"name,omitempty" xml:"name,omitempty"`
Description string `json:"description" xml:"description"`
Source *Source `json:"source,omitempty" xml:"source,omitempty"`
References *[]string `json:"references,omitempty" xml:"references>url,omitempty"`
Expand Down Expand Up @@ -415,6 +474,11 @@ type Metadata struct {
Properties *[]Property `json:"properties,omitempty" xml:"properties>property,omitempty"`
}

type Note struct {
Locale string `json:"locale,omitempty" xml:"locale,omitempty"`
Text AttachedText `json:"text" xml:"text"`
}

type OrganizationalContact struct {
Name string `json:"name,omitempty" xml:"name,omitempty"`
EMail string `json:"email,omitempty" xml:"email,omitempty"`
Expand Down Expand Up @@ -456,6 +520,20 @@ type Property struct {
Value string `json:"value" xml:",innerxml"`
}

type ReleaseNotes struct {
Type string `json:"type" xml:"type"`
Title string `json:"title,omitempty" xml:"title,omitempty"`
FeaturedImage string `json:"featuredImage,omitempty" xml:"featuredImage,omitempty"`
SocialImage string `json:"socialImage,omitempty" xml:"socialImage,omitempty"`
Description string `json:"description,omitempty" xml:"description,omitempty"`
Timestamp string `json:"timestamp,omitempty" xml:"timestamp,omitempty"`
Aliases *[]string `json:"aliases,omitempty" xml:"aliases>alias,omitempty"`
Tags *[]string `json:"tags,omitempty" xml:"tags>tag,omitempty"`
Resolves *[]Issue `json:"resolves,omitempty" xml:"resolves>issue,omitempty"`
Notes *[]Note `json:"notes,omitempty" xml:"notes>note,omitempty"`
Properties *[]Property `json:"properties,omitempty" xml:"properties>property,omitempty"`
}

type Scope string

const (
Expand All @@ -464,6 +542,16 @@ const (
ScopeRequired Scope = "required"
)

type ScoringMethod string

const (
ScoringMethodOther ScoringMethod = "other"
ScoringMethodCVSSv2 ScoringMethod = "CVSSv2"
ScoringMethodCVSSv3 ScoringMethod = "CVSSv3"
ScoringMethodCVSSv31 ScoringMethod = "CVSSv31"
ScoringMethodOWASP ScoringMethod = "OWASP"
)

type Service struct {
BOMRef string `json:"bom-ref,omitempty" xml:"bom-ref,attr,omitempty"`
Provider *OrganizationalEntity `json:"provider,omitempty" xml:"provider,omitempty"`
Expand All @@ -479,8 +567,21 @@ type Service struct {
ExternalReferences *[]ExternalReference `json:"externalReferences,omitempty" xml:"externalReferences>reference,omitempty"`
Properties *[]Property `json:"properties,omitempty" xml:"properties>property,omitempty"`
Services *[]Service `json:"services,omitempty" xml:"services>service,omitempty"`
ReleaseNotes *ReleaseNotes `json:"releaseNotes,omitempty" xml:"releaseNotes,omitempty"`
}

type Severity string

const (
SeverityUnknown Severity = "unknown"
SeverityNone Severity = "none"
SeverityInfo Severity = "info"
SeverityLow Severity = "low"
SeverityMedium Severity = "medium"
SeverityHigh Severity = "high"
SeverityCritical Severity = "critical"
)

type Source struct {
Name string `json:"name,omitempty" xml:"name,omitempty"`
URL string `json:"url,omitempty" xml:"url,omitempty"`
Expand All @@ -497,8 +598,58 @@ type SWID struct {
}

type Tool struct {
Vendor string `json:"vendor,omitempty" xml:"vendor,omitempty"`
Name string `json:"name" xml:"name"`
Version string `json:"version,omitempty" xml:"version,omitempty"`
Hashes *[]Hash `json:"hashes,omitempty" xml:"hashes>hash,omitempty"`
Vendor string `json:"vendor,omitempty" xml:"vendor,omitempty"`
Name string `json:"name" xml:"name"`
Version string `json:"version,omitempty" xml:"version,omitempty"`
Hashes *[]Hash `json:"hashes,omitempty" xml:"hashes>hash,omitempty"`
ExternalReferences *[]ExternalReference `json:"externalReferences,omitempty" xml:"externalReferences>reference,omitempty"`
}

type Vulnerability struct {
BOMRef string `json:"bom-ref,omitempty" xml:"bom-ref,attr,omitempty"`
ID string `json:"id" xml:"id"`
Source *Source `json:"source,omitempty" xml:"source,omitempty"`
References *[]VulnerabilityReference `json:"references,omitempty" xml:"references>reference,omitempty"`
Ratings *[]VulnerabilityRating `json:"ratings,omitempty" xml:"ratings>rating,omitempty"`
CWEs *[]int `json:"cwes,omitempty" xml:"cwes>cwe,omitempty"`
Description string `json:"description,omitempty" xml:"description,omitempty"`
Detail string `json:"detail,omitempty" xml:"detail,omitempty"`
Recommendation string `json:"recommendation,omitempty" xml:"recommendation,omitempty"`
Advisories *[]Advisory `json:"advisories,omitempty" xml:"advisories>advisory,omitempty"`
Created string `json:"created,omitempty" xml:"created,omitempty"`
Published string `json:"published,omitempty" xml:"published,omitempty"`
Updated string `json:"updated,omitempty" xml:"updated,omitempty"`
Credits *Credits `json:"credits,omitempty" xml:"credits,omitempty"`
Tools *[]Tool `json:"tools,omitempty" xml:"tools>tool,omitempty"`
Analysis *VulnerabilityAnalysis `json:"analysis,omitempty" xml:"analysis,omitempty"`
Affects *[]Affects `json:"affects,omitempty" xml:"affects>target,omitempty"`
}

type VulnerabilityAnalysis struct {
State ImpactAnalysisState `json:"state,omitempty" xml:"state,omitempty"`
Justification ImpactAnalysisJustification `json:"justification,omitempty" xml:"justification,omitempty"`
Response *[]ImpactAnalysisResponse `json:"response,omitempty" xml:"responses>response,omitempty"`
Detail string `json:"detail,omitempty" xml:"detail,omitempty"`
}

type VulnerabilityRating struct {
Source *Source `json:"source,omitempty" xml:"source,omitempty"`
Score float64 `json:"score" xml:"score"`
Severity Severity `json:"severity,omitempty" xml:"severity,omitempty"`
Method ScoringMethod `json:"method,omitempty" xml:"method,omitempty"`
Vector string `json:"vector,omitempty" xml:"vector,omitempty"`
Justification string `json:"justification,omitempty" xml:"justification,omitempty"`
}

type VulnerabilityReference struct {
ID string `json:"id,omitempty" xml:"id,omitempty"`
Source *Source `json:"source,omitempty" xml:"source,omitempty"`
}

type VulnerabilityStatus string

const (
VulnerabilityStatusUnknown VulnerabilityStatus = "unknown"
VulnerabilityStatusAffected VulnerabilityStatus = "affected"
VulnerabilityStatusNotAffected VulnerabilityStatus = "unaffected"
)
4 changes: 2 additions & 2 deletions encode_test.go
Expand Up @@ -48,7 +48,7 @@ func TestJsonBOMEncoder_SetPretty(t *testing.T) {

assert.Equal(t, `{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"specVersion": "1.4",
"version": 1,
"metadata": {
"authors": [
Expand Down Expand Up @@ -78,7 +78,7 @@ func TestXmlBOMEncoder_SetPretty(t *testing.T) {
require.NoError(t, encoder.Encode(bom))

assert.Equal(t, `<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" version="1">
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" version="1">
<metadata>
<authors>
<author>
Expand Down
5 changes: 3 additions & 2 deletions example_test.go
Expand Up @@ -2,9 +2,10 @@ package cyclonedx_test

import (
"fmt"
cdx "github.com/CycloneDX/cyclonedx-go"
"net/http"
"os"

cdx "github.com/CycloneDX/cyclonedx-go"
)

// This example demonstrates how to create and encode a BOM in CycloneDX format.
Expand Down Expand Up @@ -70,7 +71,7 @@ func Example_encode() {

// Output:
// <?xml version="1.0" encoding="UTF-8"?>
// <bom xmlns="http://cyclonedx.org/schema/bom/1.3" version="1">
// <bom xmlns="http://cyclonedx.org/schema/bom/1.4" version="1">
// <metadata>
// <component bom-ref="pkg:golang/acme-inc/acme-app@v1.0.0" type="application">
// <name>ACME Application</name>
Expand Down
6 changes: 3 additions & 3 deletions roundtrip_test.go
Expand Up @@ -109,11 +109,11 @@ func TestRoundTripXML(t *testing.T) {
}

func assertValidBOM(t *testing.T, bomFilePath string) {
inputFormat := "xml_v1_3"
inputFormat := "xml"
if strings.HasSuffix(bomFilePath, ".json") {
inputFormat = "json_v1_3"
inputFormat = "json"
}
valCmd := exec.Command("cyclonedx", "validate", "--input-file", bomFilePath, "--input-format", inputFormat, "--fail-on-errors")
valCmd := exec.Command("cyclonedx", "validate", "--input-file", bomFilePath, "--input-format", inputFormat, "--input-version", "v1_4", "--fail-on-errors")
valOut, err := valCmd.CombinedOutput()
if !assert.NoError(t, err) {
// Provide some context when test is failing
Expand Down
@@ -1,6 +1,6 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"components": [
Expand Down
@@ -1,6 +1,6 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"metadata": {
Expand Down
@@ -1,6 +1,6 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"components": [
Expand Down
@@ -1,6 +1,6 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"components": [
Expand Down
@@ -1,6 +1,6 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"components": [
Expand Down
@@ -1,6 +1,6 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"components": [
Expand Down
@@ -1,6 +1,6 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"components": [
Expand Down
@@ -1,6 +1,6 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"metadata": {
Expand Down
@@ -1,6 +1,6 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"components": [
Expand Down
@@ -1,6 +1,6 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"components": []
Expand Down

0 comments on commit 9082cd7

Please sign in to comment.