Skip to content

Commit 3c6d530

Browse files
Merge commit '38c14b1a51b8dc17945041ee7deabe05389c7217' into chore/dependency-updates
2 parents 8308688 + 38c14b1 commit 3c6d530

File tree

18 files changed

+6483
-24
lines changed

18 files changed

+6483
-24
lines changed

csaf-validator-lib/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,6 @@ The following tests are not yet implemented and therefore missing:
313313
314314
- Mandatory Test 6.1.26
315315
- Mandatory Test 6.1.27.13
316-
- Mandatory Test 6.1.42
317-
- Mandatory Test 6.1.44
318316
- Mandatory Test 6.1.46
319317
- Mandatory Test 6.1.47
320318
- Mandatory Test 6.1.48
@@ -332,10 +330,6 @@ The following tests are not yet implemented and therefore missing:
332330
- Recommended Test 6.2.21
333331
- Recommended Test 6.2.24
334332
- Recommended Test 6.2.26
335-
- Recommended Test 6.2.27
336-
- Recommended Test 6.2.28
337-
- Recommended Test 6.2.29
338-
- Recommended Test 6.2.30
339333
- Recommended Test 6.2.31
340334
- Recommended Test 6.2.32
341335
- Recommended Test 6.2.33
@@ -429,7 +423,9 @@ export const mandatoryTest_6_1_38: DocumentTest
429423
export const mandatoryTest_6_1_39: DocumentTest
430424
export const mandatoryTest_6_1_40: DocumentTest
431425
export const mandatoryTest_6_1_41: DocumentTest
426+
export const mandatoryTest_6_1_42: DocumentTest
432427
export const mandatoryTest_6_1_43: DocumentTest
428+
export const mandatoryTest_6_1_44: DocumentTest
433429
export const mandatoryTest_6_1_45: DocumentTest
434430
export const mandatoryTest_6_1_51: DocumentTest
435431
export const mandatoryTest_6_1_52: DocumentTest
@@ -460,6 +456,10 @@ export const recommendedTest_6_2_18: DocumentTest
460456
export const recommendedTest_6_2_22: DocumentTest
461457
export const recommendedTest_6_2_23: DocumentTest
462458
export const recommendedTest_6_2_25: DocumentTest
459+
export const recommendedTest_6_2_27: DocumentTest
460+
export const recommendedTest_6_2_28: DocumentTest
461+
export const recommendedTest_6_2_29: DocumentTest
462+
export const recommendedTest_6_2_30: DocumentTest
463463
export const recommendedTest_6_2_43: DocumentTest
464464
```
465465
@@ -480,6 +480,7 @@ export const informativeTest_6_3_9: DocumentTest
480480
export const informativeTest_6_3_10: DocumentTest
481481
export const informativeTest_6_3_11: DocumentTest
482482
export const informativeTest_6_3_12: DocumentTest
483+
export const informativeTest_6_3_18: DocumentTest
483484
```
484485
485486
[(back to top)](#bsi-csaf-validator-lib)

csaf-validator-lib/csaf_2_1/informativeTests.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export { informativeTest_6_3_1 } from './informativeTests/informativeTest_6_3_1.
1212
export { informativeTest_6_3_2 } from './informativeTests/informativeTest_6_3_2.js'
1313
export { informativeTest_6_3_4 } from './informativeTests/informativeTest_6_3_4.js'
1414
export { informativeTest_6_3_12 } from './informativeTests/informativeTest_6_3_12.js'
15+
export { informativeTest_6_3_18 } from './informativeTests/informativeTest_6_3_18.js'
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import Ajv from 'ajv/dist/jtd.js'
2+
3+
const ajv = new Ajv()
4+
5+
/**
6+
* @typedef {object} MetricContent
7+
* @property {string} [qualitative_severity_rating]
8+
*/
9+
10+
/**
11+
* @typedef {object} Metric
12+
* @property {MetricContent} [content]
13+
* @property {Array<string>} [products]
14+
*/
15+
16+
const inputSchema = /** @type {const} */ ({
17+
additionalProperties: true,
18+
properties: {
19+
vulnerabilities: {
20+
elements: {
21+
additionalProperties: true,
22+
optionalProperties: {
23+
metrics: {
24+
elements: {
25+
additionalProperties: true,
26+
optionalProperties: {
27+
content: {
28+
additionalProperties: true,
29+
optionalProperties: {
30+
qualitative_severity_rating: {
31+
type: 'string',
32+
},
33+
},
34+
},
35+
},
36+
},
37+
},
38+
},
39+
},
40+
},
41+
},
42+
})
43+
44+
const validateInput = ajv.compile(inputSchema)
45+
46+
/**
47+
* For each item in metrics it MUST be tested that it does not use the qualitative severity rating.
48+
* @param {any} doc
49+
* @returns
50+
*/
51+
export function informativeTest_6_3_18(doc) {
52+
const ctx = {
53+
infos: /** @type {Array<{ message: string; instancePath: string }>} */ ([]),
54+
}
55+
56+
if (!validateInput(doc)) {
57+
return ctx
58+
}
59+
60+
const vulnerabilities = doc.vulnerabilities
61+
62+
vulnerabilities.forEach((vulnerability, vulnerabilityIndex) => {
63+
/** @type {Array<Metric> | undefined} */
64+
const metrics = vulnerability.metrics
65+
metrics?.forEach((metric, metricIndex) => {
66+
if (metric?.content?.qualitative_severity_rating) {
67+
ctx.infos.push({
68+
instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/qualitative_severity_rating`,
69+
message: 'qualitative_severity_rating object is present',
70+
})
71+
}
72+
})
73+
})
74+
75+
return ctx
76+
}

csaf-validator-lib/csaf_2_1/mandatoryTests.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ export { mandatoryTest_6_1_38 } from './mandatoryTests/mandatoryTests_6_1_38.js'
5858
export { mandatoryTest_6_1_39 } from './mandatoryTests/mandatoryTest_6_1_39.js'
5959
export { mandatoryTest_6_1_40 } from './mandatoryTests/mandatoryTest_6_1_40.js'
6060
export { mandatoryTest_6_1_41 } from './mandatoryTests/mandatoryTest_6_1_41.js'
61+
export { mandatoryTest_6_1_42 } from './mandatoryTests/mandatoryTest_6_1_42.js'
6162
export { mandatoryTest_6_1_43 } from './mandatoryTests/mandatoryTest_6_1_43.js'
63+
export { mandatoryTest_6_1_44 } from './mandatoryTests/mandatoryTest_6_1_44.js'
6264
export { mandatoryTest_6_1_45 } from './mandatoryTests/mandatoryTest_6_1_45.js'
6365
export { mandatoryTest_6_1_51 } from './mandatoryTests/mandatoryTest_6_1_51.js'
6466
export { mandatoryTest_6_1_52 } from './mandatoryTests/mandatoryTest_6_1_52.js'
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import { PackageURL } from 'packageurl-js'
2+
import Ajv from 'ajv/dist/jtd.js'
3+
4+
const ajv = new Ajv()
5+
6+
const fullProductNameSchema = /** @type {const} */ ({
7+
additionalProperties: true,
8+
optionalProperties: {
9+
product_identification_helper: {
10+
additionalProperties: true,
11+
optionalProperties: {
12+
purls: { elements: { type: 'string' } },
13+
},
14+
},
15+
},
16+
})
17+
18+
const branchSchema = /** @type {const} */ ({
19+
additionalProperties: true,
20+
optionalProperties: {
21+
branches: {
22+
elements: {
23+
additionalProperties: true,
24+
properties: {},
25+
},
26+
},
27+
product: fullProductNameSchema,
28+
},
29+
})
30+
31+
const validateBranch = ajv.compile(branchSchema)
32+
33+
/*
34+
This is the jtd schema that needs to match the input document so that the
35+
test is activated. If this schema doesn't match, it normally means that the input
36+
document does not validate against the csaf JSON schema or optional fields that
37+
the test checks are not present.
38+
*/
39+
const inputSchema = /** @type {const} */ ({
40+
additionalProperties: true,
41+
optionalProperties: {
42+
product_tree: {
43+
additionalProperties: true,
44+
optionalProperties: {
45+
branches: {
46+
elements: branchSchema,
47+
},
48+
full_product_names: {
49+
elements: fullProductNameSchema,
50+
},
51+
relationships: {
52+
elements: {
53+
additionalProperties: true,
54+
optionalProperties: {
55+
full_product_name: fullProductNameSchema,
56+
},
57+
},
58+
},
59+
},
60+
},
61+
},
62+
})
63+
64+
const validate = ajv.compile(inputSchema)
65+
66+
/**
67+
* @typedef {import('ajv/dist/core').JTDDataType<typeof branchSchema>} Branch
68+
* @typedef {import('ajv/dist/core').JTDDataType<typeof fullProductNameSchema>} FullProductName
69+
*/
70+
71+
/**
72+
*
73+
* @param {PackageURL | null} firstPurl
74+
* @param {PackageURL | null} otherPurl
75+
* @return {Array<string>} the parts of the PURLS that differ
76+
*/
77+
function purlPartsThatDifferExceptQualifiers(firstPurl, otherPurl) {
78+
/** @type {Array<string>}*/
79+
const partsThatDiffer = []
80+
81+
if (firstPurl && otherPurl) {
82+
if (firstPurl.type !== otherPurl.type) {
83+
partsThatDiffer.push('type')
84+
}
85+
if (firstPurl.namespace !== otherPurl.namespace) {
86+
partsThatDiffer.push('namespace')
87+
}
88+
if (firstPurl.name !== otherPurl.name) {
89+
partsThatDiffer.push('name')
90+
}
91+
if (firstPurl.version !== otherPurl.version) {
92+
partsThatDiffer.push('version')
93+
}
94+
}
95+
return partsThatDiffer
96+
}
97+
98+
/**
99+
* Validates all given PURLs and check whether the PURLs
100+
* differ only in qualifiers to the first URL
101+
*
102+
* @param {Array<string> | undefined} purls PURLs to check
103+
* @return {Array<{index:number, purlParts: Array<string> }>} indexes and parts of the PURLs that differ
104+
*/
105+
export function checkPurls(purls) {
106+
/** @type {Array<{index:number, purlParts: Array<string> }>} */
107+
const invalidPurls = []
108+
if (purls) {
109+
/** @type {Array<PackageURL | null>} */
110+
const packageUrls = purls.map((purl) => {
111+
try {
112+
return PackageURL.fromString(purl)
113+
} catch (e) {
114+
// ignore, tested in CSAF 2.1 test 6.1.13
115+
return null
116+
}
117+
})
118+
119+
/**
120+
* @type {Array<PackageURL>}
121+
*/
122+
if (packageUrls.length > 1) {
123+
const firstPurl = packageUrls[0]
124+
for (let i = 1; i < packageUrls.length; i++) {
125+
/** @type {Array<string>}*/
126+
const purlParts = purlPartsThatDifferExceptQualifiers(
127+
firstPurl,
128+
packageUrls[i]
129+
)
130+
if (purlParts.length > 0) {
131+
invalidPurls.push({ index: i, purlParts: purlParts })
132+
}
133+
}
134+
}
135+
}
136+
return invalidPurls
137+
}
138+
139+
/**
140+
* For each product_identification_helper object containing multiple purls,
141+
* it MUST be tested that the purls only differ in their qualifiers.
142+
*
143+
* @param {unknown} doc
144+
*/
145+
export function mandatoryTest_6_1_42(doc) {
146+
/*
147+
The `ctx` variable holds the state that is accumulated during the test ran and is
148+
finally returned by the function.
149+
*/
150+
const ctx = {
151+
errors:
152+
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
153+
isValid: true,
154+
}
155+
156+
if (!validate(doc)) {
157+
return ctx
158+
}
159+
160+
doc.product_tree?.branches?.forEach((branch, index) => {
161+
checkBranch(`/product_tree/branches/${index}`, branch)
162+
})
163+
164+
doc.product_tree?.full_product_names?.forEach((fullProduceName, index) => {
165+
checkFullProductName(
166+
`/product_tree/full_product_names/${index}`,
167+
fullProduceName
168+
)
169+
})
170+
171+
doc.product_tree?.relationships?.forEach((relationship, index) => {
172+
const fullProductName = relationship.full_product_name
173+
if (fullProductName) {
174+
checkFullProductName(
175+
`/product_tree/relationships/${index}/full_product_name`,
176+
fullProductName
177+
)
178+
}
179+
})
180+
181+
return ctx
182+
183+
/**
184+
* Check whether the PURLs only differ in their qualifiers for a full product name.
185+
*
186+
* @param {string} prefix The instance path prefix of the "full product name". It is
187+
* used to generate error messages.
188+
* @param {FullProductName} fullProductName The "full product name" object.
189+
*/
190+
function checkFullProductName(prefix, fullProductName) {
191+
const invalidPurls = checkPurls(
192+
fullProductName.product_identification_helper?.purls
193+
)
194+
invalidPurls.forEach((invalidPurl) => {
195+
ctx.isValid = false
196+
ctx.errors.push({
197+
instancePath: `${prefix}/product_identification_helper/purls/${invalidPurl.index}`,
198+
message: `the PURL differs from the first PURL in the following part(s): ${invalidPurl.purlParts.join()}`,
199+
})
200+
})
201+
}
202+
203+
/**
204+
* Check whether the PURLs only differ in their qualifiers for the given branch object
205+
* and its branch children.
206+
*
207+
* @param {string} prefix The instance path prefix of the "branch". It is
208+
* used to generate error messages.
209+
* @param {Branch} branch The "branch" object.
210+
*/
211+
function checkBranch(prefix, branch) {
212+
const invalidPurls = checkPurls(
213+
branch.product?.product_identification_helper?.purls
214+
)
215+
invalidPurls.forEach((invalidPurl) => {
216+
ctx.isValid = false
217+
ctx.errors.push({
218+
instancePath: `${prefix}/product/product_identification_helper/purls/${invalidPurl.index}`,
219+
message: `the PURL differs from the first PURL in the following parts: ${invalidPurl.purlParts.join()}`,
220+
})
221+
})
222+
branch.branches?.forEach((branch, index) => {
223+
if (validateBranch(branch)) {
224+
checkBranch(`${prefix}/branches/${index}`, branch)
225+
}
226+
})
227+
}
228+
}

0 commit comments

Comments
 (0)