Skip to content

Commit bbc52d5

Browse files
committed
Merge branch 'main' into 316-mandatory-test-6.1.27.14
2 parents 7df1592 + c8064b6 commit bbc52d5

18 files changed

+941
-14
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,6 @@ The following tests are not yet implemented and therefore missing:
311311
312312
**Mandatory Tests**
313313
314-
- Mandatory Test 6.1.7
315-
- Mandatory Test 6.1.10
316314
- Mandatory Test 6.1.14
317315
- Mandatory Test 6.1.16
318316
- Mandatory Test 6.1.27.12
@@ -394,8 +392,10 @@ export const mandatoryTest_6_1_3: DocumentTest
394392
export const mandatoryTest_6_1_4: DocumentTest
395393
export const mandatoryTest_6_1_5: DocumentTest
396394
export const mandatoryTest_6_1_6: DocumentTest
395+
export const mandatoryTest_6_1_7: DocumentTest
397396
export const mandatoryTest_6_1_8: DocumentTest
398397
export const mandatoryTest_6_1_9: DocumentTest
398+
export const mandatoryTest_6_1_10: DocumentTest
399399
export const mandatoryTest_6_1_11: DocumentTest
400400
export const mandatoryTest_6_1_12: DocumentTest
401401
export const mandatoryTest_6_1_13: DocumentTest

csaf

csaf_2_1/informativeTests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
export {
2-
informativeTest_6_3_2,
32
informativeTest_6_3_3,
43
informativeTest_6_3_5,
54
informativeTest_6_3_6,
@@ -11,3 +10,4 @@ export {
1110
} from '../informativeTests.js'
1211
export { informativeTest_6_3_1 } from './informativeTests/informativeTest_6_3_1.js'
1312
export { informativeTest_6_3_4 } from './informativeTests/informativeTest_6_3_4.js'
13+
export { informativeTest_6_3_2 } from './informativeTests/informativeTest_6_3_2.js'
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import Ajv from 'ajv/dist/jtd.js'
2+
3+
const ajv = new Ajv()
4+
5+
const inputSchema = /** @type {const} */ ({
6+
additionalProperties: true,
7+
properties: {
8+
vulnerabilities: {
9+
elements: {
10+
additionalProperties: true,
11+
optionalProperties: {
12+
metrics: {
13+
elements: {
14+
additionalProperties: true,
15+
optionalProperties: {
16+
content: {
17+
additionalProperties: true,
18+
optionalProperties: {
19+
cvss_v3: {
20+
additionalProperties: true,
21+
optionalProperties: {
22+
version: { type: 'string' },
23+
vectorString: { type: 'string' },
24+
},
25+
},
26+
},
27+
},
28+
},
29+
},
30+
},
31+
},
32+
},
33+
},
34+
},
35+
})
36+
37+
const validateInput = ajv.compile(inputSchema)
38+
39+
/**
40+
* For each item in the list of metrics which contains the cvss_v3 object under
41+
* content it MUST be tested that CVSS v3.0 is not used.
42+
* @param {unknown} doc
43+
* @returns
44+
*/
45+
export function informativeTest_6_3_2(doc) {
46+
const ctx = {
47+
infos: /** @type {Array<{ message: string; instancePath: string }>} */ ([]),
48+
}
49+
50+
if (!validateInput(doc)) {
51+
return ctx
52+
}
53+
54+
doc.vulnerabilities.forEach((vulnerability, vulnerabilityIndex) => {
55+
const metrics = vulnerability.metrics
56+
metrics?.forEach((metric, metricIndex) => {
57+
if (metric.content?.cvss_v3) {
58+
if (
59+
metric.content.cvss_v3.version === '3.0' ||
60+
metric.content.cvss_v3.vectorString?.startsWith('CVSS:3.0')
61+
) {
62+
ctx.infos.push({
63+
instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/cvss_v3/version`,
64+
message: 'It is recommended to upgrade to CVSS v3.1.',
65+
})
66+
}
67+
}
68+
})
69+
})
70+
71+
return ctx
72+
}

csaf_2_1/mandatoryTests.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ export {
3535
mandatoryTest_6_1_33,
3636
} from '../mandatoryTests.js'
3737
export { mandatoryTest_6_1_1 } from './mandatoryTests/mandatoryTest_6_1_1.js'
38+
export { mandatoryTest_6_1_7 } from './mandatoryTests/mandatoryTest_6_1_7.js'
3839
export { mandatoryTest_6_1_8 } from './mandatoryTests/mandatoryTest_6_1_8.js'
3940
export { mandatoryTest_6_1_11 } from './mandatoryTests/mandatoryTest_6_1_11.js'
4041
export { mandatoryTest_6_1_13 } from './mandatoryTests/mandatoryTest_6_1_13.js'
42+
export { mandatoryTest_6_1_10 } from './mandatoryTests/mandatoryTest_6_1_10.js'
4143
export { mandatoryTest_6_1_27_14 } from './mandatoryTests/mandatoryTest_6_1_27_14.js'
4244
export { mandatoryTest_6_1_34 } from './mandatoryTests/mandatoryTest_6_1_34.js'
4345
export { mandatoryTest_6_1_35 } from './mandatoryTests/mandatoryTest_6_1_35.js'
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import * as cvss2 from '../../lib/shared/cvss2.js'
2+
import * as cvss3 from '../../lib/shared/cvss3.js'
3+
import * as cvss4 from '../../lib/shared/cvss4.js'
4+
import Ajv from 'ajv/dist/jtd.js'
5+
6+
/** @typedef {import('ajv/dist/jtd.js').JTDDataType<typeof inputSchema>} InputSchema */
7+
8+
/** @typedef {InputSchema['vulnerabilities'][number]} Vulnerability */
9+
10+
/** @typedef {NonNullable<Vulnerability['metrics']>[number]} Metric */
11+
12+
/** @typedef {NonNullable<Metric['content']>} MetricContent */
13+
14+
const inputSchema = /** @type {const} */ ({
15+
additionalProperties: true,
16+
properties: {
17+
vulnerabilities: {
18+
elements: {
19+
additionalProperties: true,
20+
optionalProperties: {
21+
metrics: {
22+
elements: {
23+
additionalProperties: true,
24+
optionalProperties: {
25+
content: {
26+
additionalProperties: true,
27+
optionalProperties: {
28+
cvss_v2: {
29+
additionalProperties: true,
30+
optionalProperties: {
31+
vectorString: { type: 'string' },
32+
version: { type: 'string' },
33+
},
34+
},
35+
cvss_v3: {
36+
additionalProperties: true,
37+
optionalProperties: {
38+
vectorString: { type: 'string' },
39+
version: { type: 'string' },
40+
},
41+
},
42+
cvss_v4: {
43+
additionalProperties: true,
44+
optionalProperties: {
45+
vectorString: { type: 'string' },
46+
version: { type: 'string' },
47+
},
48+
},
49+
},
50+
},
51+
},
52+
},
53+
},
54+
},
55+
},
56+
},
57+
},
58+
})
59+
const ajv = new Ajv()
60+
const validateInput = ajv.compile(inputSchema)
61+
62+
/** @type { Record<string, { jsonName:string, optionsByKey:Record<string, string>}>} */
63+
const cvssV3MappingByMetricKey = Object.fromEntries(
64+
cvss3.mapping.map((mapping) => {
65+
return [
66+
mapping[1],
67+
{
68+
jsonName: mapping[0],
69+
optionsByKey: Object.fromEntries(
70+
Object.entries(mapping[2]).map(([key, value]) => [value, key])
71+
),
72+
},
73+
]
74+
})
75+
)
76+
77+
/** @type { Record<string, { jsonName:string, optionsByKey:Record<string, string>}>} */
78+
const cvssV2MappingByMetricKey = Object.fromEntries(
79+
cvss2.mapping.map((mapping) => {
80+
return [
81+
mapping[1],
82+
{
83+
jsonName: mapping[0],
84+
optionsByKey: Object.fromEntries(
85+
Object.entries(mapping[2]).map(([key, value]) => [value.id, key])
86+
),
87+
},
88+
]
89+
})
90+
)
91+
92+
/**
93+
* @param {{optionName: string, optionValue: string, optionKey: string}[]} optionsArray
94+
* @return {Record<string, string>}
95+
*/
96+
function convertOptionsArrayToObject(optionsArray) {
97+
/** @type {Record<string, string>} */
98+
const result = {}
99+
optionsArray.forEach((option) => {
100+
result[option.optionKey] = option.optionValue
101+
})
102+
return result
103+
}
104+
105+
/** @type { Record<string, { jsonName:string, optionsByKey:Record<string, string>}>} */
106+
const cvssV4MappingByMetricKey = Object.fromEntries(
107+
cvss4.flatMetrics.map((flatMetric) => {
108+
return [
109+
flatMetric.metricShort,
110+
{
111+
jsonName: flatMetric.jsonName,
112+
optionsByKey: convertOptionsArrayToObject(flatMetric.options),
113+
},
114+
]
115+
})
116+
)
117+
118+
/**
119+
* @param {Metric} metric
120+
*/
121+
function validateCvss2(metric) {
122+
if (typeof metric.content?.cvss_v2?.vectorString === 'string') {
123+
return validateCVSSAttributes(
124+
cvssV2MappingByMetricKey,
125+
metric.content.cvss_v2
126+
)
127+
} else {
128+
return []
129+
}
130+
}
131+
132+
/**
133+
* @param {Metric} metric
134+
*/
135+
function validateCvss3(metric) {
136+
if (
137+
typeof metric?.content?.cvss_v3?.vectorString === 'string' &&
138+
(metric.content.cvss_v3.version === '3.1' ||
139+
metric.content.cvss_v3.version === '3.0')
140+
) {
141+
return validateCVSSAttributes(
142+
cvssV3MappingByMetricKey,
143+
metric.content.cvss_v3
144+
)
145+
} else {
146+
return []
147+
}
148+
}
149+
150+
/**
151+
* @param {Metric} metric
152+
*/
153+
function validateCvss4(metric) {
154+
if (typeof metric?.content?.cvss_v4?.vectorString === 'string') {
155+
return validateCVSSAttributes(
156+
cvssV4MappingByMetricKey,
157+
metric.content.cvss_v4
158+
)
159+
} else {
160+
return []
161+
}
162+
}
163+
164+
/**
165+
* @param {unknown} doc
166+
*/
167+
export function mandatoryTest_6_1_10(doc) {
168+
/** @type {Array<{ message: string; instancePath: string }>} */
169+
const errors = []
170+
171+
if (!validateInput(doc)) {
172+
return { errors, isValid: true }
173+
}
174+
175+
if (Array.isArray(doc.vulnerabilities)) {
176+
/** @type {Array<Vulnerability>} */
177+
const vulnerabilities = doc.vulnerabilities
178+
vulnerabilities.forEach((vulnerability, vulnerabilityIndex) => {
179+
if (!Array.isArray(vulnerability.metrics)) return
180+
/** @type {Array<Metric>} */
181+
const metrics = vulnerability.metrics
182+
metrics.forEach((metric, metricIndex) => {
183+
validateCvss2(metric).forEach((attributeKey) => {
184+
errors.push({
185+
instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/cvss_v2/${attributeKey}`,
186+
message: 'value is not consistent with the vector string',
187+
})
188+
})
189+
190+
validateCvss3(metric).forEach((attributeKey) => {
191+
errors.push({
192+
instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/cvss_v3/${attributeKey}`,
193+
message: 'value is not consistent with the vector string',
194+
})
195+
})
196+
197+
validateCvss4(metric).forEach((attributeKey) => {
198+
errors.push({
199+
instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/cvss_v4/${attributeKey}`,
200+
message: 'value is not consistent with the vector string',
201+
})
202+
})
203+
})
204+
})
205+
}
206+
207+
return { errors, isValid: errors.length === 0 }
208+
}
209+
210+
/**
211+
* validate the cvss vector against the cvss properties
212+
* @param {Record<string, { jsonName:string, optionsByKey:Record<string, string>}>}mappingByMetricKey cvss version specific mapping
213+
* @param {Record<string, unknown>} cvss cvss object
214+
215+
*/
216+
function validateCVSSAttributes(mappingByMetricKey, cvss) {
217+
const vectorString = /** @type {string} */ (cvss.vectorString)
218+
const vectorValues = vectorString.split('/').slice(1)
219+
/**
220+
* @type {string[]}
221+
*/
222+
const invalidKeys = []
223+
vectorValues.forEach((vectorValue) => {
224+
const [vectorMetricKey, vectorOptionKey] = vectorValue.split(':')
225+
const mapping = mappingByMetricKey[vectorMetricKey]
226+
if (mapping) {
227+
const metricOptionValue = cvss[mapping.jsonName]
228+
if (typeof metricOptionValue == 'string') {
229+
const expectedOptionValue = mapping.optionsByKey[vectorOptionKey]
230+
if (metricOptionValue !== expectedOptionValue) {
231+
invalidKeys.push(mapping.jsonName)
232+
}
233+
}
234+
}
235+
})
236+
return invalidKeys
237+
}

0 commit comments

Comments
 (0)