diff --git a/README.md b/README.md index 5753a4a1..1554ce78 100644 --- a/README.md +++ b/README.md @@ -332,7 +332,6 @@ The following tests are not yet implemented and therefore missing: - Recommended Test 6.2.26 - Recommended Test 6.2.31 - Recommended Test 6.2.32 -- Recommended Test 6.2.33 - Recommended Test 6.2.34 - Recommended Test 6.2.35 - Recommended Test 6.2.36 @@ -460,6 +459,7 @@ export const recommendedTest_6_2_27: DocumentTest export const recommendedTest_6_2_28: DocumentTest export const recommendedTest_6_2_29: DocumentTest export const recommendedTest_6_2_30: DocumentTest +export const recommendedTest_6_2_33: DocumentTest export const recommendedTest_6_2_43: DocumentTest ``` diff --git a/csaf_2_1/recommendedTests.js b/csaf_2_1/recommendedTests.js index d90a5174..8f5791e2 100644 --- a/csaf_2_1/recommendedTests.js +++ b/csaf_2_1/recommendedTests.js @@ -33,5 +33,6 @@ export { recommendedTest_6_2_27 } from './recommendedTests/recommendedTest_6_2_2 export { recommendedTest_6_2_28 } from './recommendedTests/recommendedTest_6_2_28.js' export { recommendedTest_6_2_29 } from './recommendedTests/recommendedTest_6_2_29.js' export { recommendedTest_6_2_30 } from './recommendedTests/recommendedTest_6_2_30.js' +export { recommendedTest_6_2_33 } from './recommendedTests/recommendedTest_6_2_33.js' export { recommendedTest_6_2_38 } from './recommendedTests/recommendedTest_6_2_38.js' export { recommendedTest_6_2_43 } from './recommendedTests/recommendedTest_6_2_43.js' diff --git a/csaf_2_1/recommendedTests/recommendedTest_6_2_33.js b/csaf_2_1/recommendedTests/recommendedTest_6_2_33.js new file mode 100644 index 00000000..7214540f --- /dev/null +++ b/csaf_2_1/recommendedTests/recommendedTest_6_2_33.js @@ -0,0 +1,98 @@ +import Ajv from 'ajv/dist/jtd.js' +import { compareZonedDateTimes } from '../dateHelper.js' +import { Temporal } from 'temporal-polyfill' + +const ajv = new Ajv() + +const inputSchema = /** @type {const} */ ({ + additionalProperties: true, + properties: { + document: { + additionalProperties: true, + properties: { + tracking: { + additionalProperties: true, + properties: { + revision_history: { + elements: { + additionalProperties: true, + optionalProperties: { + date: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }, + vulnerabilities: { + elements: { + additionalProperties: true, + optionalProperties: { + disclosure_date: { type: 'string' }, + }, + }, + }, + }, +}) + +const validate = ajv.compile(inputSchema) + +/** + * This implements the recommended test 6.2.33 of the CSAF 2.1 standard. + * + * @param {any} doc + */ +export function recommendedTest_6_2_33(doc) { + /** @type {Array<{ message: string; instancePath: string }>} */ + const warnings = [] + const context = { warnings } + + if (!validate(doc)) { + return context + } + const currentTimestampUtc = Temporal.Now.zonedDateTimeISO('UTC').toString() + for (let i = 0; i < doc.vulnerabilities.length; ++i) { + const disclosureDate = doc.vulnerabilities[i].disclosure_date + // check if the disclosure date is in the past + if ( + disclosureDate && + compareZonedDateTimes(disclosureDate, currentTimestampUtc) < 1 + ) { + const revisionHistory = doc.document.tracking.revision_history + // sort the revision history (ascending) so one don't need to loop through it + // to find the date of its newest item + revisionHistory.sort((a, b) => { + // if both dates are undefined, consider them equal + if (!a.date && !b.date) { + return 0 + } + // move undefined items to the beginning of the array + if (!a.date) { + return -1 + } + if (!b.date) { + return 1 + } + + // if both dates are not undefined, compare them + return compareZonedDateTimes(a.date, b.date) + }) + // compare the disclosure date with the date of the newest item of the revision history + const newestItemInRevisionHistory = + revisionHistory[revisionHistory.length - 1].date + if ( + newestItemInRevisionHistory && + compareZonedDateTimes(disclosureDate, newestItemInRevisionHistory) > 0 + ) { + context.warnings.push({ + message: + 'The disclosure_date is in the past but newer than the date of the newest item of the revision_history.', + instancePath: `/vulnerabilities/${i}/disclosure_date`, + }) + } + } + } + + return context +} diff --git a/tests/csaf_2_1/oasis.js b/tests/csaf_2_1/oasis.js index 6caa005c..b303bf66 100644 --- a/tests/csaf_2_1/oasis.js +++ b/tests/csaf_2_1/oasis.js @@ -36,7 +36,6 @@ const excluded = [ '6.2.26', '6.2.31', '6.2.32', - '6.2.33', '6.2.34', '6.2.35', '6.2.36', diff --git a/tests/csaf_2_1/recommendedTest_6_2_33.js b/tests/csaf_2_1/recommendedTest_6_2_33.js new file mode 100644 index 00000000..c93f9184 --- /dev/null +++ b/tests/csaf_2_1/recommendedTest_6_2_33.js @@ -0,0 +1,36 @@ +import assert from 'node:assert' +import { recommendedTest_6_2_33 } from '../../csaf_2_1/recommendedTests.js' + +describe('recommendedTest_6_2_33', function () { + it('only runs on relevant documents', function () { + assert.equal( + recommendedTest_6_2_33({ vulnerabilities: 'mydoc' }).warnings.length, + 0 + ) + }) + it('skips empty objects', function () { + assert.equal( + recommendedTest_6_2_33({ + document: { + tracking: { + revision_history: [ + { + date: '2024-01-22T10:00:00.000Z', + number: '1', + summary: 'Initial version.', + }, + {}, // should be ignored + ], + }, + }, + vulnerabilities: [ + { + disclosure_date: '2024-02-24T10:00:00.000Z', + }, + {}, // should be ignored + ], + }).warnings.length, + 1 + ) + }) +})