Skip to content
Draft
19 changes: 16 additions & 3 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

## Table of Contents

- [Code Style](#code-style)
- [Formatting with prettier](#formatting-with-prettier)
- [Quoting Strings](#quoting-strings)
<!-- TOC -->

- [Developing CSAF Validator Lib](#developing-csaf-validator-lib)
- [Table of Contents](#table-of-contents)
- [Code Style](#code-style)
- [Formatting with prettier](#formatting-with-prettier)
- [Quoting Strings](#quoting-strings)
- [Generated Files](#generated-files)
- [SSVC](#ssvc)

## Code Style

Expand Down Expand Up @@ -49,3 +55,10 @@ message:
'the ssvc id does neither match the "cve" nor it '+
'matches the "text" of any item in the "ids" array',
```

## Generated Files

### SSVC

The script `scripts/read-ssvc-decision-points.js` reads the SSVC decision points and writes them to
the file `lib/ssvc/decision_points.js`. See the comments in the script for further usage information.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,6 @@ The following tests are not yet implemented and therefore missing:

**Informative Tests**

- Informative Test 6.2.13
- Informative Test 6.2.14
- Informative Test 6.2.15
- Informative Test 6.2.16
Expand Down Expand Up @@ -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_13: DocumentTest
```

[(back to top)](#bsi-csaf-validator-lib)
Expand Down
1 change: 1 addition & 0 deletions csaf_2_1/informativeTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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_13 } from './informativeTests/informativeTest_6_3_13.js'
118 changes: 118 additions & 0 deletions csaf_2_1/informativeTests/informativeTest_6_3_13.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import Ajv from 'ajv/dist/jtd.js'
import decision_points from '../../lib/ssvc/decision_points.js'

const ajv = new Ajv()

/** @typedef {import('ajv/dist/jtd.js').JTDDataType<typeof inputSchema>} InputSchema */

/** @typedef {InputSchema['vulnerabilities'][number]} Vulnerability */

/** @typedef {NonNullable<Vulnerability['metrics']>[number]} Metric */

const inputSchema = /** @type {const} */ ({
additionalProperties: true,
properties: {
vulnerabilities: {
elements: {
additionalProperties: true,
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)

/** from https://github.com/CERTCC/SSVC/blob/main/src/ssvc/namespaces.py */
const REGISTERED_NAMESPACES = [
'ssvc',
'cvss',
'cisa',
'basic',
'example',
'test',
]

/**
* For each SSVC decision point given under `selections` with a registered
* `namespace`, it MUST be tested the latest decision point
* `version` available at the time of the `timestamp` was used.
* The test SHALL fail if a later `version` was used.
* Namespaces reserved for special purpose MUST be treated as per their
* definition.
*
* @param {unknown} doc
* @returns
*/
export function informativeTest_6_3_13(doc) {
const ctx = {
infos: /** @type {Array<{ message: string; instancePath: string }>} */ ([]),
}

if (!validateInput(doc)) {
return ctx
}

const decisionPointName2Version = new Map()
decision_points.decisionPoints.forEach((obj) => {
const currentVersion = decisionPointName2Version.get(obj.name)
if (!currentVersion || currentVersion < obj.version) {
decisionPointName2Version.set(obj.name, obj.version)
}
})

const vulnerabilities = doc.vulnerabilities

vulnerabilities.forEach((vulnerability, vulnerabilityIndex) => {
/** @type {Array<Metric> | undefined} */
const metrics = vulnerability.metrics
metrics?.forEach((metric, metricIndex) => {
const selections = metric?.content?.ssvc_v2?.selections
selections?.forEach((selection, selectionIndex) => {
const latestVersion = decisionPointName2Version.get(selection?.name)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm - where do we check the timestamp stuff?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we have the Informations to check this?

/** @type {string | undefined} */
const namespace = selection.namespace
if (
selection.version !== latestVersion &&
namespace &&
REGISTERED_NAMESPACES.includes(namespace)
) {
ctx.infos.push({
instancePath: `/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/content/ssvc_v2/selections/${selectionIndex}/version`,
message: `ssvc_v1 version '${selection.version}' is not latest decision point version '${latestVersion}'`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message is misleading as well - it is all about the decision point (esp. the part ssvc_v1 version '${selection.version})

})
}
})
})
})

return ctx
}
Loading