Skip to content

Commit 0d45455

Browse files
committed
feat: add mandatory test 6.1.52
1 parent c8064b6 commit 0d45455

File tree

5 files changed

+217
-2
lines changed

5 files changed

+217
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,6 @@ The following tests are not yet implemented and therefore missing:
332332
- Mandatory Test 6.1.49
333333
- Mandatory Test 6.1.50
334334
- Mandatory Test 6.1.51
335-
- Mandatory Test 6.1.52
336335
- Mandatory Test 6.1.53
337336
- Mandatory Test 6.1.54
338337
- Mandatory Test 6.1.55
@@ -435,6 +434,7 @@ export const mandatoryTest_6_1_38: DocumentTest
435434
export const mandatoryTest_6_1_39: DocumentTest
436435
export const mandatoryTest_6_1_40: DocumentTest
437436
export const mandatoryTest_6_1_41: 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
@@ -49,3 +49,4 @@ export { mandatoryTest_6_1_38 } from './mandatoryTests/mandatoryTests_6_1_38.js'
4949
export { mandatoryTest_6_1_39 } from './mandatoryTests/mandatoryTest_6_1_39.js'
5050
export { mandatoryTest_6_1_40 } from './mandatoryTests/mandatoryTest_6_1_40.js'
5151
export { mandatoryTest_6_1_41 } from './mandatoryTests/mandatoryTest_6_1_41.js'
52+
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: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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, false)
47+
})
48+
49+
it('skips empty vulnerability object', function () {
50+
assert.equal(
51+
mandatoryTest_6_1_52({
52+
document: {
53+
tracking: {
54+
revision_history: [{ "date": "2024-01-24T10:00:00.000Z" }],
55+
status: 'final',
56+
},
57+
},
58+
vulnerabilities: [
59+
{}, // should be ignored
60+
{ first_known_exploitation_dates: [
61+
{
62+
"date": "2024-01-24T13:00:00.000Z",
63+
"exploitation_date": "2024-01-24T12:34:56.789Z",
64+
}
65+
]
66+
}]
67+
}).isValid, false
68+
)
69+
})
70+
71+
it('skips empty first_known_exploitation_date object', function () {
72+
assert.equal(
73+
mandatoryTest_6_1_52({
74+
document: {
75+
tracking: {
76+
revision_history: [{ "date": "2024-01-24T10:00:00.000Z" }],
77+
status: 'final',
78+
},
79+
},
80+
vulnerabilities: [
81+
{ first_known_exploitation_dates: [
82+
{}, // should be ignored
83+
{
84+
"date": "2024-01-24T13:00:00.000Z",
85+
"exploitation_date": "2024-01-24T12:34:56.789Z",
86+
}
87+
]
88+
}]
89+
}).isValid, false
90+
)
91+
})
92+
})

tests/csaf_2_1/oasis.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ const excluded = [
3737
'6.1.49',
3838
'6.1.50',
3939
'6.1.51',
40-
'6.1.52',
4140
'6.1.53',
4241
'6.1.54',
4342
'6.1.55',

0 commit comments

Comments
 (0)