Skip to content

Commit 5894082

Browse files
authored
Merge pull request #360 from secvisogram/197-csaf-2.1-mandatory-test-6.1.52
feat: add mandatory test 6.1.52
2 parents 1ad3349 + 2141799 commit 5894082

File tree

5 files changed

+225
-2
lines changed

5 files changed

+225
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,6 @@ The following tests are not yet implemented and therefore missing:
323323
- Mandatory Test 6.1.49
324324
- Mandatory Test 6.1.50
325325
- Mandatory Test 6.1.51
326-
- Mandatory Test 6.1.52
327326
- Mandatory Test 6.1.53
328327
- Mandatory Test 6.1.54
329328
- Mandatory Test 6.1.55
@@ -435,6 +434,7 @@ export const mandatoryTest_6_1_39: DocumentTest
435434
export const mandatoryTest_6_1_40: DocumentTest
436435
export const mandatoryTest_6_1_41: DocumentTest
437436
export const mandatoryTest_6_1_43: DocumentTest
437+
export const mandatoryTest_6_1_52: DocumentTest
438438
```
439439
440440
[(back to top)](#bsi-csaf-validator-lib)

csaf_2_1/mandatoryTests.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,4 @@ export { mandatoryTest_6_1_39 } from './mandatoryTests/mandatoryTest_6_1_39.js'
5858
export { mandatoryTest_6_1_40 } from './mandatoryTests/mandatoryTest_6_1_40.js'
5959
export { mandatoryTest_6_1_41 } from './mandatoryTests/mandatoryTest_6_1_41.js'
6060
export { mandatoryTest_6_1_43 } from './mandatoryTests/mandatoryTest_6_1_43.js'
61+
export { mandatoryTest_6_1_52 } from './mandatoryTests/mandatoryTest_6_1_52.js'
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import Ajv from 'ajv/dist/jtd.js'
2+
import { compareZonedDateTimes } from '../../lib/shared/dateHelper.js'
3+
4+
const ajv = new Ajv()
5+
6+
/*
7+
This is the jtd schema that needs to match the input document so that the
8+
test is activated. If this schema doesn't match it normally means that the input
9+
document does not validate against the csaf json schema or optional fields that
10+
the test checks are not present.
11+
*/
12+
const inputSchema = /** @type {const} */ ({
13+
additionalProperties: true,
14+
properties: {
15+
document: {
16+
additionalProperties: true,
17+
properties: {
18+
tracking: {
19+
additionalProperties: true,
20+
properties: {
21+
revision_history: {
22+
elements: {
23+
additionalProperties: true,
24+
optionalProperties: {
25+
date: { type: 'string' },
26+
},
27+
},
28+
},
29+
status: { type: 'string' },
30+
},
31+
},
32+
},
33+
},
34+
vulnerabilities: {
35+
elements: {
36+
additionalProperties: true,
37+
optionalProperties: {
38+
first_known_exploitation_dates: {
39+
elements: {
40+
additionalProperties: true,
41+
optionalProperties: {
42+
date: { type: 'string' },
43+
exploitation_date: { type: 'string' },
44+
},
45+
},
46+
},
47+
},
48+
},
49+
},
50+
},
51+
})
52+
53+
const validate = ajv.compile(inputSchema)
54+
55+
/**
56+
* This implements the mandatory test 6.1.52 of the CSAF 2.1 standard.
57+
*
58+
* @param {any} doc
59+
*/
60+
export function mandatoryTest_6_1_52(doc) {
61+
/*
62+
The `ctx` variable holds the state that is accumulated during the test ran and is
63+
finally returned by the function.
64+
*/
65+
const ctx = {
66+
errors:
67+
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
68+
isValid: true,
69+
}
70+
71+
if (!validate(doc)) {
72+
return ctx
73+
}
74+
const status = doc.document.tracking.status
75+
if (status !== 'final' && status !== 'interim') {
76+
return ctx
77+
}
78+
79+
const newestRevisionHistoryItem = doc.document.tracking.revision_history
80+
.filter((item) => item.date != null)
81+
.sort((a, z) =>
82+
compareZonedDateTimes(
83+
/** @type {string} */ (z.date),
84+
/** @type {string} */ (a.date)
85+
)
86+
)[0]
87+
88+
doc.vulnerabilities?.forEach((vulnerability, vulnerabilityIndex) => {
89+
const exploitDate = vulnerability.first_known_exploitation_dates || []
90+
exploitDate.forEach((exploit, exploitIdx) => {
91+
const date = exploit.date
92+
const exploitationDate = exploit.exploitation_date
93+
if (
94+
newestRevisionHistoryItem &&
95+
compareZonedDateTimes(
96+
/** @type {string} */ (newestRevisionHistoryItem.date),
97+
/** @type {string} */ (date)
98+
) < 0
99+
) {
100+
ctx.isValid = false
101+
ctx.errors.push({
102+
instancePath: `/vulnerabilities/${vulnerabilityIndex}/first_known_exploitation_dates/${exploitIdx}/date`,
103+
message: `the status is ${status}, but the "date" of the First Known Exploitation Dates is newer than the newest revision history date`,
104+
})
105+
}
106+
if (
107+
newestRevisionHistoryItem &&
108+
compareZonedDateTimes(
109+
/** @type {string} */ (newestRevisionHistoryItem.date),
110+
/** @type {string} */ (exploitationDate)
111+
) < 0
112+
) {
113+
ctx.isValid = false
114+
ctx.errors.push({
115+
instancePath: `/vulnerabilities/${vulnerabilityIndex}/first_known_exploitation_dates/${exploitIdx}/exploitation_date`,
116+
message: `the status is ${status}, but the "exploitation_date" of the First Known Exploitation Dates is newer than the newest revision history date`,
117+
})
118+
}
119+
})
120+
})
121+
122+
return ctx
123+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import assert from 'node:assert/strict'
2+
import { mandatoryTest_6_1_52 } from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_52.js'
3+
4+
describe('mandatoryTest_6_1_52', function () {
5+
it('only runs on relevant documents', function () {
6+
assert.equal(mandatoryTest_6_1_52({ document: 'mydoc' }).isValid, true)
7+
})
8+
9+
it('skips status draft', function () {
10+
assert.equal(
11+
mandatoryTest_6_1_52({
12+
document: {
13+
tracking: {
14+
revision_history: [],
15+
status: 'draft',
16+
},
17+
},
18+
vulnerabilities: [],
19+
}).isValid,
20+
true
21+
)
22+
})
23+
24+
it('skips empty revision_history object', function () {
25+
assert.equal(
26+
mandatoryTest_6_1_52({
27+
document: {
28+
tracking: {
29+
revision_history: [
30+
{}, // should be ignored
31+
{ date: '2024-01-24T10:00:00.000Z' },
32+
],
33+
status: 'final',
34+
},
35+
},
36+
vulnerabilities: [
37+
{
38+
first_known_exploitation_dates: [
39+
{
40+
date: '2024-01-24T13:00:00.000Z',
41+
exploitation_date: '2024-01-24T12:34:56.789Z',
42+
},
43+
],
44+
},
45+
],
46+
}).isValid,
47+
false
48+
)
49+
})
50+
51+
it('skips empty vulnerability object', function () {
52+
assert.equal(
53+
mandatoryTest_6_1_52({
54+
document: {
55+
tracking: {
56+
revision_history: [{ date: '2024-01-24T10:00:00.000Z' }],
57+
status: 'final',
58+
},
59+
},
60+
vulnerabilities: [
61+
{}, // should be ignored
62+
{
63+
first_known_exploitation_dates: [
64+
{
65+
date: '2024-01-24T13:00:00.000Z',
66+
exploitation_date: '2024-01-24T12:34:56.789Z',
67+
},
68+
],
69+
},
70+
],
71+
}).isValid,
72+
false
73+
)
74+
})
75+
76+
it('skips empty first_known_exploitation_date object', function () {
77+
assert.equal(
78+
mandatoryTest_6_1_52({
79+
document: {
80+
tracking: {
81+
revision_history: [{ date: '2024-01-24T10:00:00.000Z' }],
82+
status: 'final',
83+
},
84+
},
85+
vulnerabilities: [
86+
{
87+
first_known_exploitation_dates: [
88+
{}, // should be ignored
89+
{
90+
date: '2024-01-24T13:00:00.000Z',
91+
exploitation_date: '2024-01-24T12:34:56.789Z',
92+
},
93+
],
94+
},
95+
],
96+
}).isValid,
97+
false
98+
)
99+
})
100+
})

tests/csaf_2_1/oasis.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ const excluded = [
2727
'6.1.49',
2828
'6.1.50',
2929
'6.1.51',
30-
'6.1.52',
3130
'6.1.53',
3231
'6.1.54',
3332
'6.1.55',

0 commit comments

Comments
 (0)