Skip to content

Commit 482df9e

Browse files
Merge pull request #417 from secvisogram/feat/197-csaf-2.1-mandatory-test-6.1.45
feat: add mandatory test 6.1.45
2 parents caa634a + e13b835 commit 482df9e

File tree

5 files changed

+169
-2
lines changed

5 files changed

+169
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,6 @@ The following tests are not yet implemented and therefore missing:
316316
- Mandatory Test 6.1.27.18
317317
- Mandatory Test 6.1.42
318318
- Mandatory Test 6.1.44
319-
- Mandatory Test 6.1.45
320319
- Mandatory Test 6.1.46
321320
- Mandatory Test 6.1.47
322321
- Mandatory Test 6.1.48
@@ -434,6 +433,7 @@ export const mandatoryTest_6_1_39: DocumentTest
434433
export const mandatoryTest_6_1_40: DocumentTest
435434
export const mandatoryTest_6_1_41: DocumentTest
436435
export const mandatoryTest_6_1_43: DocumentTest
436+
export const mandatoryTest_6_1_45: DocumentTest
437437
export const mandatoryTest_6_1_52: DocumentTest
438438
```
439439

csaf_2_1/mandatoryTests.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,5 @@ 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_45 } from './mandatoryTests/mandatoryTest_6_1_45.js'
6162
export { mandatoryTest_6_1_52 } from './mandatoryTests/mandatoryTest_6_1_52.js'
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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+
distribution: {
19+
additionalProperties: true,
20+
properties: {
21+
tlp: {
22+
additionalProperties: true,
23+
properties: {
24+
label: { type: 'string' },
25+
},
26+
},
27+
},
28+
},
29+
tracking: {
30+
additionalProperties: true,
31+
properties: {
32+
revision_history: {
33+
elements: {
34+
additionalProperties: true,
35+
optionalProperties: {
36+
date: { type: 'string' },
37+
},
38+
},
39+
},
40+
},
41+
},
42+
},
43+
},
44+
vulnerabilities: {
45+
elements: {
46+
additionalProperties: true,
47+
optionalProperties: {
48+
disclosure_date: { type: 'string' },
49+
},
50+
},
51+
},
52+
},
53+
})
54+
55+
const validate = ajv.compile(inputSchema)
56+
57+
/**
58+
* This implements the mandatory test 6.1.45 of the CSAF 2.1 standard.
59+
*
60+
* @param {any} doc
61+
*/
62+
export function mandatoryTest_6_1_45(doc) {
63+
const ctx = {
64+
errors:
65+
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
66+
isValid: true,
67+
}
68+
if (!validate(doc)) {
69+
return ctx
70+
}
71+
const status = doc.document.tracking.status
72+
const tlpLabel = doc.document.distribution.tlp.label
73+
if (!(tlpLabel === 'CLEAR' && (status === 'final' || status === 'interim'))) {
74+
return ctx
75+
}
76+
77+
const revisionHistory = doc.document.tracking?.revision_history
78+
// sort the revision history (descending) and save the newest entry
79+
const newestRevisionHistoryItem = revisionHistory
80+
.filter((item) => item.date !== undefined)
81+
.sort((a, b) =>
82+
compareZonedDateTimes(
83+
/** @type {string} */ (b.date),
84+
/** @type {string} */ (a.date)
85+
)
86+
)[0]
87+
88+
doc.vulnerabilities?.forEach((vulnerability, vulnerabilityIndex) => {
89+
const disclosureDate = vulnerability.disclosure_date
90+
// compare the disclosure date with the date of the newest item in the revision history
91+
if (
92+
disclosureDate &&
93+
compareZonedDateTimes(
94+
disclosureDate,
95+
/** @type {string} */ (newestRevisionHistoryItem.date)
96+
) > 0
97+
) {
98+
ctx.isValid = false
99+
ctx.errors.push({
100+
instancePath: `/vulnerabilities/${vulnerabilityIndex}/disclosure_date`,
101+
message: `the "status" is ${status}, but the "disclosure date" is newer than the date of the newest item of the revision_history`,
102+
})
103+
}
104+
})
105+
106+
return ctx
107+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import assert from 'node:assert/strict'
2+
import { mandatoryTest_6_1_45 } from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_45.js'
3+
4+
describe('mandatoryTest_6_1_45', function () {
5+
it('only runs on relevant documents', function () {
6+
assert.equal(mandatoryTest_6_1_45({ document: 'mydoc' }).isValid, true)
7+
})
8+
9+
it('skips status draft', function () {
10+
assert.equal(
11+
mandatoryTest_6_1_45({
12+
document: {
13+
distribution: {
14+
tlp: {
15+
label: 'CLEAR',
16+
},
17+
},
18+
tracking: {
19+
revision_history: [{ date: '2024-01-24T10:00:00.000Z' }],
20+
status: 'draft',
21+
},
22+
},
23+
vulnerabilities: [
24+
{
25+
disclosure_date: '2025-01-24T12:34:56.789Z',
26+
},
27+
],
28+
}).isValid,
29+
true
30+
)
31+
})
32+
33+
it('skips empty objects', function () {
34+
assert.equal(
35+
mandatoryTest_6_1_45({
36+
document: {
37+
distribution: {
38+
tlp: {
39+
label: 'CLEAR',
40+
},
41+
},
42+
tracking: {
43+
revision_history: [
44+
{}, // should be ignored
45+
{ date: '2024-01-24T10:00:00.000Z' },
46+
],
47+
status: 'final',
48+
},
49+
},
50+
vulnerabilities: [
51+
{}, // should be ignored
52+
{
53+
disclosure_date: '2025-01-24T12:34:56.789Z',
54+
},
55+
],
56+
}).isValid,
57+
false
58+
)
59+
})
60+
})

tests/csaf_2_1/oasis.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ const excluded = [
2020
'6.1.37',
2121
'6.1.42',
2222
'6.1.44',
23-
'6.1.45',
2423
'6.1.46',
2524
'6.1.47',
2625
'6.1.48',

0 commit comments

Comments
 (0)