diff --git a/README.md b/README.md index 6f4f9297..e552357f 100644 --- a/README.md +++ b/README.md @@ -360,7 +360,6 @@ The following tests are not yet implemented and therefore missing: - Informative Test 6.2.13 - Informative Test 6.2.14 -- Informative Test 6.2.15 - Informative Test 6.2.16 - Informative Test 6.2.17 @@ -480,6 +479,7 @@ export const informativeTest_6_3_9: DocumentTest export const informativeTest_6_3_10: DocumentTest export const informativeTest_6_3_11: DocumentTest export const informativeTest_6_3_12: DocumentTest +export const informativeTest_6_3_15: DocumentTest ``` [(back to top)](#bsi-csaf-validator-lib) diff --git a/csaf_2_1/informativeTests.js b/csaf_2_1/informativeTests.js index b3142316..28f11615 100644 --- a/csaf_2_1/informativeTests.js +++ b/csaf_2_1/informativeTests.js @@ -12,3 +12,4 @@ export { informativeTest_6_3_1 } from './informativeTests/informativeTest_6_3_1. export { informativeTest_6_3_2 } from './informativeTests/informativeTest_6_3_2.js' export { informativeTest_6_3_4 } from './informativeTests/informativeTest_6_3_4.js' export { informativeTest_6_3_12 } from './informativeTests/informativeTest_6_3_12.js' +export { informativeTest_6_3_15 } from './informativeTests/informativeTest_6_3_15.js' diff --git a/csaf_2_1/informativeTests/informativeTest_6_3_15.js b/csaf_2_1/informativeTests/informativeTest_6_3_15.js new file mode 100644 index 00000000..dfc306fb --- /dev/null +++ b/csaf_2_1/informativeTests/informativeTest_6_3_15.js @@ -0,0 +1,134 @@ +import Ajv from 'ajv/dist/jtd.js' + +const ajv = new Ajv() + +const PREFIX_EXTENSION_NAMESPACE = 'x_' + +/** + * @typedef {object} Selection + * @property {string} [name] + * @property {string} [namespace] + * @property {string} [version] + */ + +/** + * @typedef {object} Ssvc2 + * @property {Array} [selections] + */ + +/** + * @typedef {object} MetricContent + * @property {Ssvc2} [ssvc_v2] + */ + +/** + * @typedef {object} Metric + * @property {MetricContent} [content] + */ + +const inputSchema = /** @type {const} */ ({ + additionalProperties: true, + properties: { + document: { + additionalProperties: true, + properties: { + distribution: { + additionalProperties: true, + properties: { + tlp: { + additionalProperties: true, + properties: { + label: { type: 'string' }, + }, + }, + }, + }, + }, + }, + vulnerabilities: { + elements: { + additionalProperties: true, + properties: {}, + optionalProperties: { + metrics: { + elements: { + additionalProperties: true, + optionalProperties: { + content: { + additionalProperties: true, + optionalProperties: { + ssvc_v2: { + additionalProperties: true, + optionalProperties: { + selections: { + elements: { + additionalProperties: true, + optionalProperties: { + name: { type: 'string' }, + namespace: { type: 'string' }, + version: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}) + +const validateInput = ajv.compile(inputSchema) + +/** + * @param {string | undefined } namespace + */ +function namespaceUsesExtension(namespace) { + return namespace ? namespace.startsWith(PREFIX_EXTENSION_NAMESPACE) : false +} + +/** + * For each SSVC decision point given under `selections`, it MUST be tested that the `namespace` does not use an extension + * if the document is not labeled `TLP:CLEAR`. + * Namespaces reserved for special purpose MUST be treated as per their definition. + * @param {unknown} doc + * @returns + */ +export function informativeTest_6_3_15(doc) { + const ctx = { + infos: /** @type {Array<{ message: string; instancePath: string }>} */ ([]), + } + + if (!validateInput(doc)) { + return ctx + } + + const vulnerabilities = doc.vulnerabilities + + vulnerabilities.forEach((vulnerability, vulnerabilityIndex) => { + /** @type {Array | undefined} */ + const metrics = vulnerability.metrics + metrics?.forEach((metric, metricIndex) => { + const selections = metric?.content?.ssvc_v2?.selections + selections?.forEach((selection, selectionIndex) => { + if ( + namespaceUsesExtension(selection.namespace) && + doc.document.distribution.tlp.label !== 'TLP:CLEAR' + ) { + ctx.infos.push({ + instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/ssvc_v2/selections/${selectionIndex}/namespace`, + message: + 'namespace uses an extension and document is not labeled TLP:CLEAR', + }) + } + }) + }) + }) + + return ctx +} diff --git a/tests/csaf_2_1/informativeTest_6_3_15.js b/tests/csaf_2_1/informativeTest_6_3_15.js new file mode 100644 index 00000000..a87fd4d5 --- /dev/null +++ b/tests/csaf_2_1/informativeTest_6_3_15.js @@ -0,0 +1,53 @@ +import assert from 'node:assert' +import { informativeTest_6_3_15 } from '../../csaf_2_1/informativeTests.js' + +describe('informativeTest_6_3_15', function () { + it('only runs on relevant documents', function () { + assert.equal(informativeTest_6_3_15({ document: 'mydoc' }).infos.length, 0) + }) + it('test input schema with not considered json object in vulnerabilities', function () { + assert.equal( + informativeTest_6_3_15({ + document: { + distribution: { + tlp: { + label: 'GREEN', + }, + }, + }, + vulnerabilities: [ + {}, + { + cve: 'CVE-1900-0001', + metrics: [ + { + content: { + ssvc_v2: { + id: 'CVE-1900-0001', + schemaVersion: '1-0-1', + selections: [ + { + name: 'Technical Impact', + namespace: 'x_additional-technical-impacts', + values: ['Total'], + version: '1.0.0', + }, + { + name: 'Technical Impact', + values: ['Total'], + version: '1.0.0', + }, + ], + timestamp: '2024-01-24T10:00:00.000Z', + }, + }, + products: ['CSAFPID-9080700'], + }, + ], + }, + ], + }).infos.length, + 1 + ) + }) +}) diff --git a/tests/csaf_2_1/oasis.js b/tests/csaf_2_1/oasis.js index 0e9d2e60..5b621df7 100644 --- a/tests/csaf_2_1/oasis.js +++ b/tests/csaf_2_1/oasis.js @@ -62,7 +62,6 @@ const excluded = [ '6.3.12', '6.3.13', '6.3.14', - '6.3.15', '6.3.16', '6.3.17', '6.3.18',