Skip to content

Commit 14f6783

Browse files
m-brophymrizzi
andauthored
TC-1841 cyclonedx 1.5 validation avoiding 1.3 default call (trustification#1955)
* TC-1841 cyclonedx 1.5 validation avoiding 1.3 default call Signed-off-by: m-brophy <[email protected]> * fix formatting errors Signed-off-by: m-brophy <[email protected]> * TC-1841 get_cyclonedx_spec_version (trustification#2) Signed-off-by: mrizzi <[email protected]> --------- Signed-off-by: m-brophy <[email protected]> Signed-off-by: mrizzi <[email protected]> Co-authored-by: Marco Rizzi <[email protected]>
1 parent d5cba58 commit 14f6783

File tree

2 files changed

+233
-2
lines changed

2 files changed

+233
-2
lines changed

bombastic/model/src/data.rs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use cyclonedx_bom::errors::JsonReadError;
2-
use cyclonedx_bom::prelude::{Validate, ValidationResult};
2+
use cyclonedx_bom::prelude::{SpecVersion, Validate, ValidationResult};
33
use cyclonedx_bom::validation::ValidationErrorsKind;
4+
use serde_json::Value;
45
use std::collections::HashSet;
56
use std::fmt::Formatter;
7+
use std::str::FromStr;
68
use tracing::{info_span, instrument};
79

810
#[derive(Debug)]
@@ -77,7 +79,8 @@ impl SBOM {
7779
// the serial number is missing and this isn't what we want because
7880
// serial number is mandatory for trustification to correlate properly
7981
Some(_) => {
80-
let result = bom.validate();
82+
let spec_version = Self::get_cyclonedx_spec_version(data)?;
83+
let result = bom.validate_version(spec_version);
8184
match result.passed() {
8285
true => return Ok(SBOM::CycloneDX(bom)),
8386
false => {
@@ -118,6 +121,33 @@ impl SBOM {
118121
Err(err)
119122
}
120123

124+
fn get_cyclonedx_spec_version(data: &[u8]) -> Result<SpecVersion, Error> {
125+
let mut err: Error = Default::default();
126+
let spec_version_error: serde_json::Error = serde::de::Error::custom("No field 'specVersion' found");
127+
let error = Some(JsonReadError::from(spec_version_error));
128+
//workaround to deal with cyclonedx-rust-cargo validate() method
129+
//validating against SpecVersion::V1_3, the default, in all cases
130+
//we therefore have to discover the spec version from the json data
131+
//to pass into validate_version() as the parsed bom doesn't contain this info
132+
// let mut spec_version = SpecVersion::V1_3;
133+
match serde_json::from_slice::<Value>(data) {
134+
Ok(parsed_json) => match parsed_json.get("specVersion") {
135+
Some(version) => match version.as_str() {
136+
Some(version) => match SpecVersion::from_str(version) {
137+
Ok(spec_version) => return Ok(spec_version),
138+
Err(e) => err.cyclonedx = Some(JsonReadError::from(e)),
139+
},
140+
None => err.cyclonedx = error,
141+
},
142+
None => {
143+
err.cyclonedx = error;
144+
}
145+
},
146+
Err(e) => err.cyclonedx = Some(JsonReadError::from(e)),
147+
}
148+
Err(err)
149+
}
150+
121151
fn get_validation_error_messages(validation_result: ValidationResult) -> HashSet<String> {
122152
let mut result = HashSet::<String>::new();
123153
validation_result.errors().for_each(|(_, error_kind)| match error_kind {
@@ -179,6 +209,13 @@ mod tests {
179209
assert!(result.is_ok());
180210
}
181211

212+
#[test]
213+
fn parse_cdx_valid_15_license_id() {
214+
let data = include_bytes!("../../testdata/cdx-1.5-valid-license-id.json");
215+
let result = SBOM::parse(data);
216+
assert!(result.is_ok());
217+
}
218+
182219
#[test]
183220
fn parse_cyclonedx_valid_14_newline() {
184221
let data = include_bytes!("../../testdata/syft.cyclonedx.newline.json");
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
{
2+
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3+
"bomFormat": "CycloneDX",
4+
"specVersion": "1.5",
5+
"serialNumber": "urn:uuid:d2525dbf-827b-45af-b074-c759ba9eebbb",
6+
"version": 1,
7+
"metadata": {
8+
"timestamp": "2024-10-18T06:13:14Z",
9+
"tools": {
10+
"components": [
11+
{
12+
"type": "application",
13+
"author": "anchore",
14+
"name": "syft",
15+
"version": "1.4.1"
16+
},
17+
{
18+
"author": "red hat",
19+
"name": "cachi2",
20+
"type": "application"
21+
}
22+
]
23+
},
24+
"component": {
25+
"bom-ref": "f259ee149f7c0477",
26+
"type": "file",
27+
"name": "/var/lib/containers/storage/vfs/dir/a0fa5b9d218b35edc7d814d3cd8e666cd7f3005f59b73b7291678ab24d43412c"
28+
}
29+
},
30+
"components": [
31+
{
32+
"bom-ref": "pkg:pypi/[email protected]?package-id=6de7e16fa6904c51",
33+
"type": "library",
34+
"author": "Ben Bangert, Mike Bayer, Philip Jenvey, Alessandro Molina <[email protected], [email protected], [email protected]>",
35+
"name": "Beaker",
36+
"version": "1.12.1",
37+
"licenses": [
38+
{
39+
"expression": "TCL AND GPL-3.0-or-later WITH Bison-exception-2.2 AND BSD-3-Clause"
40+
}
41+
],
42+
"cpe": "cpe:2.3:a:beakerbrowser:beaker:1.12.1:*:*:*:*:python:*:*",
43+
"purl": "pkg:pypi/[email protected]",
44+
"properties": [
45+
{
46+
"name": "syft:package:foundBy",
47+
"value": "python-installed-package-cataloger"
48+
},
49+
{
50+
"name": "syft:package:language",
51+
"value": "python"
52+
},
53+
{
54+
"name": "syft:package:type",
55+
"value": "python"
56+
},
57+
{
58+
"name": "syft:package:metadataType",
59+
"value": "python-package"
60+
},
61+
{
62+
"name": "syft:location:0:path",
63+
"value": "/usr/lib/python3.12/site-packages/Beaker-1.12.1-py3.12.egg-info/PKG-INFO"
64+
},
65+
{
66+
"name": "syft:location:1:path",
67+
"value": "/usr/lib/python3.12/site-packages/Beaker-1.12.1-py3.12.egg-info/top_level.txt"
68+
}
69+
]
70+
},
71+
{
72+
"bom-ref": "pkg:pypi/[email protected]?package-id=365de27b8ece63b4",
73+
"type": "library",
74+
"author": "The Brotli Authors",
75+
"name": "Brotli",
76+
"version": "1.1.0",
77+
"licenses": [
78+
{
79+
"license": {
80+
"id": "TTWL"
81+
}
82+
}
83+
],
84+
"cpe": "cpe:2.3:a:brotli_authors_project:python-Brotli:1.1.0:*:*:*:*:*:*:*",
85+
"purl": "pkg:pypi/[email protected]",
86+
"properties": [
87+
{
88+
"name": "syft:package:foundBy",
89+
"value": "python-installed-package-cataloger"
90+
},
91+
{
92+
"name": "syft:package:language",
93+
"value": "python"
94+
},
95+
{
96+
"name": "syft:package:type",
97+
"value": "python"
98+
},
99+
{
100+
"name": "syft:package:metadataType",
101+
"value": "python-package"
102+
},
103+
{
104+
"name": "syft:cpe23",
105+
"value": "cpe:2.3:a:brotli_authors_project:python_Brotli:1.1.0:*:*:*:*:*:*:*"
106+
},
107+
{
108+
"name": "syft:cpe23",
109+
"value": "cpe:2.3:a:brotli_authorsproject:python-Brotli:1.1.0:*:*:*:*:*:*:*"
110+
},
111+
{
112+
"name": "syft:cpe23",
113+
"value": "cpe:2.3:a:brotli_authorsproject:python_Brotli:1.1.0:*:*:*:*:*:*:*"
114+
},
115+
{
116+
"name": "syft:cpe23",
117+
"value": "cpe:2.3:a:brotli_authors_project:Brotli:1.1.0:*:*:*:*:*:*:*"
118+
},
119+
{
120+
"name": "syft:cpe23",
121+
"value": "cpe:2.3:a:brotli_authors:python-Brotli:1.1.0:*:*:*:*:*:*:*"
122+
},
123+
{
124+
"name": "syft:cpe23",
125+
"value": "cpe:2.3:a:brotli_authors:python_Brotli:1.1.0:*:*:*:*:*:*:*"
126+
},
127+
{
128+
"name": "syft:cpe23",
129+
"value": "cpe:2.3:a:brotli_authorsproject:Brotli:1.1.0:*:*:*:*:*:*:*"
130+
},
131+
{
132+
"name": "syft:cpe23",
133+
"value": "cpe:2.3:a:python-Brotli:python-Brotli:1.1.0:*:*:*:*:*:*:*"
134+
},
135+
{
136+
"name": "syft:cpe23",
137+
"value": "cpe:2.3:a:python-Brotli:python_Brotli:1.1.0:*:*:*:*:*:*:*"
138+
},
139+
{
140+
"name": "syft:cpe23",
141+
"value": "cpe:2.3:a:python_Brotli:python-Brotli:1.1.0:*:*:*:*:*:*:*"
142+
},
143+
{
144+
"name": "syft:cpe23",
145+
"value": "cpe:2.3:a:python_Brotli:python_Brotli:1.1.0:*:*:*:*:*:*:*"
146+
},
147+
{
148+
"name": "syft:cpe23",
149+
"value": "cpe:2.3:a:brotli_authors:Brotli:1.1.0:*:*:*:*:*:*:*"
150+
},
151+
{
152+
"name": "syft:cpe23",
153+
"value": "cpe:2.3:a:Brotli:python-Brotli:1.1.0:*:*:*:*:*:*:*"
154+
},
155+
{
156+
"name": "syft:cpe23",
157+
"value": "cpe:2.3:a:Brotli:python_Brotli:1.1.0:*:*:*:*:*:*:*"
158+
},
159+
{
160+
"name": "syft:cpe23",
161+
"value": "cpe:2.3:a:python-Brotli:Brotli:1.1.0:*:*:*:*:*:*:*"
162+
},
163+
{
164+
"name": "syft:cpe23",
165+
"value": "cpe:2.3:a:python:python-Brotli:1.1.0:*:*:*:*:*:*:*"
166+
},
167+
{
168+
"name": "syft:cpe23",
169+
"value": "cpe:2.3:a:python:python_Brotli:1.1.0:*:*:*:*:*:*:*"
170+
},
171+
{
172+
"name": "syft:cpe23",
173+
"value": "cpe:2.3:a:python_Brotli:Brotli:1.1.0:*:*:*:*:*:*:*"
174+
},
175+
{
176+
"name": "syft:cpe23",
177+
"value": "cpe:2.3:a:Brotli:Brotli:1.1.0:*:*:*:*:*:*:*"
178+
},
179+
{
180+
"name": "syft:cpe23",
181+
"value": "cpe:2.3:a:python:Brotli:1.1.0:*:*:*:*:*:*:*"
182+
},
183+
{
184+
"name": "syft:location:0:path",
185+
"value": "/usr/lib64/python3.12/site-packages/Brotli-1.1.0-py3.12.egg-info/PKG-INFO"
186+
},
187+
{
188+
"name": "syft:location:1:path",
189+
"value": "/usr/lib64/python3.12/site-packages/Brotli-1.1.0-py3.12.egg-info/top_level.txt"
190+
}
191+
]
192+
}
193+
]
194+
}

0 commit comments

Comments
 (0)