Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ export const mandatoryTest_6_1_41: DocumentTest
export const mandatoryTest_6_1_43: DocumentTest
export const mandatoryTest_6_1_45: DocumentTest
export const mandatoryTest_6_1_52: DocumentTest
export const mandatoryTest_6_1_56: DocumentTest
```

[(back to top)](#bsi-csaf-validator-lib)
Expand Down
1 change: 1 addition & 0 deletions csaf_2_1/mandatoryTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@ export { mandatoryTest_6_1_41 } from './mandatoryTests/mandatoryTest_6_1_41.js'
export { mandatoryTest_6_1_43 } from './mandatoryTests/mandatoryTest_6_1_43.js'
export { mandatoryTest_6_1_45 } from './mandatoryTests/mandatoryTest_6_1_45.js'
export { mandatoryTest_6_1_52 } from './mandatoryTests/mandatoryTest_6_1_52.js'
export { mandatoryTest_6_1_56 } from './mandatoryTests/mandatoryTest_6_1_56.js'
153 changes: 153 additions & 0 deletions csaf_2_1/mandatoryTests/mandatoryTest_6_1_56.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import Ajv from 'ajv/dist/jtd.js'

/** @typedef {string} Product

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

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

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

const jtdAjv = new Ajv()

const inputSchema = /** @type {const} */ ({
additionalProperties: true,
properties: {
vulnerabilities: {
elements: {
additionalProperties: true,
optionalProperties: {
metrics: {
elements: {
additionalProperties: true,
optionalProperties: {
source: {
type: 'string',
},
products: {
elements: { type: 'string' },
},
content: {
additionalProperties: true,
optionalProperties: {
cvss_v2: {
additionalProperties: true,
optionalProperties: {
version: { type: 'string' },
},
},
cvss_v3: {
additionalProperties: true,
optionalProperties: {
version: { type: 'string' },
},
},
cvss_v4: {
additionalProperties: true,
optionalProperties: {
version: { type: 'string' },
},
},
qualitative_severity_rating: {
type: 'string',
},
},
},
},
},
},
},
},
},
},
})

const validate = jtdAjv.compile(inputSchema)

/**
* For each item in `/vulnerabilities` it MUST be tested that no Qualitative Severity Rating and CVSS values are
* listed for the tuple of Product ID and source.
* @param {unknown} doc
*/
export function mandatoryTest_6_1_56(doc) {
const ctx = {
errors:
/** @type {Array<{ instancePath: string; message: string }>} */ ([]),
isValid: true,
}

/** @type {Array<{ message: string; instancePath: string }>} */
const errors = []

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

/** @type {Array<Vulnerability>} */
const vulnerabilities = doc.vulnerabilities

/**
* Create a unique string for the tuple of productId and source
* to compare them easily
* @param {string} productId
* @param {string | undefined} source
*
* @return string
*/
function createTupleStringForProductAndSource(productId, source) {
return JSON.stringify({ productId: productId, source: source ?? '' })
}

/**
* check whether the given metric contains a cvss (v2, v3 or v4) content
* @param {Metric} metric
* @returns {boolean}
*/
function hasCvssContent(metric) {
return (
metric.content?.cvss_v2?.version !== undefined ||
metric.content?.cvss_v3?.version !== undefined ||
metric.content?.cvss_v4?.version !== undefined
)
}

vulnerabilities.forEach((vulnerabilityItem, vulnerabilityIndex) => {
/** @type {Map<string,string>} */
const productIdServiceTuplesCvss = new Map()
/** @type {Map<string,string>} */
const productIdServiceTuplesRating = new Map()

/** @type {Array<Metric> | undefined} */
const metrics = vulnerabilityItem.metrics
metrics?.forEach((metric, metricIndex) => {
/** @type {Array<Product> | undefined} */
const productsOfMetric = metric.products
productsOfMetric?.forEach((product, productIndex) => {
if (hasCvssContent(metric)) {
productIdServiceTuplesCvss.set(
createTupleStringForProductAndSource(product, metric.source),
`/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/products/${productIndex}`
)
}
if (metric.content?.qualitative_severity_rating) {
productIdServiceTuplesRating.set(
createTupleStringForProductAndSource(product, metric.source),
`/vulnerabilities/${vulnerabilityIndex}/metrics/${metricIndex}/products/${productIndex}`
)
}
})
})

productIdServiceTuplesCvss.forEach((value, key) => {
if (productIdServiceTuplesRating.has(key))
errors.push({
message:
'in the metrics of the vulnerability a Qualitative Severity Rating and CVSS value ' +
'with the same product id and source is used.',
instancePath: value,
})
})
})

return { errors: errors, isValid: errors.length === 0 }
}
8 changes: 8 additions & 0 deletions tests/csaf_2_1/mandatoryTest_6_1_56.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import assert from 'node:assert/strict'
import { mandatoryTest_6_1_56 } from '../../csaf_2_1/mandatoryTests/mandatoryTest_6_1_56.js'

describe('mandatoryTest_6_1_56', function () {
it('only runs on relevant documents', function () {
assert.equal(mandatoryTest_6_1_56({ document: 'mydoc' }).isValid, true)
})
})
1 change: 0 additions & 1 deletion tests/csaf_2_1/oasis.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ const excluded = [
'6.1.53',
'6.1.54',
'6.1.55',
'6.1.56',
'6.2.11',
'6.2.19',
'6.2.20',
Expand Down