diff --git a/lib/shared/csafAjv.js b/lib/shared/csafAjv.js index b528ce65..651907e7 100644 --- a/lib/shared/csafAjv.js +++ b/lib/shared/csafAjv.js @@ -4,6 +4,10 @@ import cvss_v2_0 from './csafAjv/cvss-v2.0.js' import cvss_v3_0 from './csafAjv/cvss-v3.0.js' import cvss_v3_1 from './csafAjv/cvss-v3.1.js' import cvss_v4_0 from './csafAjv/cvss-v4.0.js' +import meta from './csafAjv/meta.js' +import formatAssertion from './csafAjv/format-assertion.js' +import ssvcDecisionPointValueSelection from './csafAjv/Decision_Point_Value_Selection-1-0-1.js' +import ssvcDecisionPoint from './csafAjv/Decision_Point-1-0-1.js' const csafAjv = new Ajv2020({ strict: false, allErrors: true }) addFormats(csafAjv) @@ -11,5 +15,21 @@ csafAjv.addSchema(cvss_v2_0, 'https://www.first.org/cvss/cvss-v2.0.json') csafAjv.addSchema(cvss_v3_0, 'https://www.first.org/cvss/cvss-v3.0.json') csafAjv.addSchema(cvss_v3_1, 'https://www.first.org/cvss/cvss-v3.1.json') csafAjv.addSchema(cvss_v4_0, 'https://www.first.org/cvss/cvss-v4.0.json') +csafAjv.addSchema( + meta, + 'https://docs.oasis-open.org/csaf/csaf/v2.1/schema/meta.json' +) +csafAjv.addSchema( + formatAssertion, + 'https://json-schema.org/draft/2020-12/meta/format-assertion' +) +csafAjv.addSchema( + ssvcDecisionPointValueSelection, + 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point_Value_Selection-1-0-1.schema.json' +) +csafAjv.addSchema( + ssvcDecisionPoint, + 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json' +) export default csafAjv diff --git a/lib/shared/csafAjv/Decision_Point-1-0-1.js b/lib/shared/csafAjv/Decision_Point-1-0-1.js new file mode 100644 index 00000000..509d9288 --- /dev/null +++ b/lib/shared/csafAjv/Decision_Point-1-0-1.js @@ -0,0 +1,106 @@ +export default { + $schema: 'https://json-schema.org/draft/2020-12/schema', + title: 'Decision Point schema definition', + $id: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json', + description: + 'Decision points are the basic building blocks of SSVC decision functions. Individual decision points describe a single aspect of the input to a decision function.', + $defs: { + schemaVersion: { + description: 'Schema version used to represent this Decision Point.', + type: 'string', + enum: ['1-0-1'], + }, + decision_point_value: { + type: 'object', + additionalProperties: false, + properties: { + key: { + type: 'string', + description: + 'A short, unique string (or key) used as a shorthand identifier for a Decision Point Value.', + minLength: 1, + examples: ['P', 'Y'], + }, + name: { + type: 'string', + description: 'A short label that identifies a Decision Point Value', + minLength: 1, + examples: ['Public PoC', 'Yes'], + }, + description: { + type: 'string', + description: 'A full description of the Decision Point Value.', + minLength: 1, + examples: [ + 'One of the following is true: (1) Typical public PoC exists in sources such as Metasploit or websites like ExploitDB; or (2) the vulnerability has a well-known method of exploitation.', + 'Attackers can reliably automate steps 1-4 of the kill chain.', + ], + }, + }, + required: ['key', 'name', 'description'], + }, + decision_point: { + type: 'object', + additionalProperties: false, + properties: { + schemaVersion: { + $ref: '#/$defs/schemaVersion', + }, + namespace: { + type: 'string', + description: + 'Namespace (a short, unique string): The value must be one of the official namespaces, currenlty "ssvc", "cvss" OR can start with \'x_\' for private namespaces. See SSVC Documentation for details.', + pattern: '^(?=.{3,100}$)(x_)?[a-z0-9]{3}([/.-]?[a-z0-9]+){0,97}$', + examples: ['ssvc', 'cvss', 'x_custom', 'x_custom/extension'], + }, + version: { + type: 'string', + description: + 'Version (a semantic version string) that identifies the version of a Decision Point.', + pattern: + '^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$', + examples: ['1.0.1', '1.0.1-alpha'], + }, + key: { + type: 'string', + description: + 'A short, unique string (or key) used as a shorthand identifier for a Decision Point.', + + minLength: 1, + examples: ['E', 'A'], + }, + name: { + type: 'string', + description: 'A short label that identifies a Decision Point.', + minLength: 1, + examples: ['Exploitation', 'Automatable'], + }, + description: { + type: 'string', + description: + 'A full description of the Decision Point, explaining what it represents and how it is used in SSVC.', + minLength: 1, + }, + values: { + description: 'A set of possible answers for a given Decision Point', + uniqueItems: true, + type: 'array', + minItems: 1, + items: { + $ref: '#/$defs/decision_point_value', + }, + }, + }, + required: [ + 'namespace', + 'version', + 'key', + 'name', + 'description', + 'values', + 'schemaVersion', + ], + }, + }, + $ref: '#/$defs/decision_point', +} diff --git a/lib/shared/csafAjv/Decision_Point_Value_Selection-1-0-1.js b/lib/shared/csafAjv/Decision_Point_Value_Selection-1-0-1.js new file mode 100644 index 00000000..71324bf2 --- /dev/null +++ b/lib/shared/csafAjv/Decision_Point_Value_Selection-1-0-1.js @@ -0,0 +1,83 @@ +export default { + $schema: 'https://json-schema.org/draft/2020-12/schema', + $id: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point_Value_Selection-1-0-1.schema.json', + description: + 'This schema defines the structure for selecting SSVC Decision Points and their evaluated values for a given vulnerability. Each vulnerability can have multiple Decision Points, and each Decision Point can have multiple selected values when full certainty is not available.', + $defs: { + id: { + type: 'string', + description: + 'Identifier for the vulnerability that was evaluation, such as CVE, CERT/CC VU#, OSV id, Bugtraq, GHSA etc.', + examples: ['CVE-1900-1234', 'VU#11111', 'GHSA-11a1-22b2-33c3'], + minLength: 1, + }, + role: { + type: 'string', + description: + 'The role of the stakeholder performing the evaluation (e.g., Supplier, Deployer, Coordinator). See SSVC documentation for a currently identified list: https://certcc.github.io/SSVC/topics/enumerating_stakeholders/', + examples: ['Supplier', 'Deployer', 'Coordinator'], + minLength: 1, + }, + timestamp: { + description: + 'Date and time when the evaluation of the Vulnerability was performed according to RFC 3339, section 5.6.', + type: 'string', + format: 'date-time', + }, + SsvcdecisionpointselectionSchema: { + description: + 'A down-selection of SSVC Decision Points that represent an evaluation at a specific time of a Vulnerability evaluation.', + properties: { + name: { + $ref: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json#/$defs/decision_point/properties/name', + }, + namespace: { + $ref: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json#/$defs/decision_point/properties/namespace', + }, + values: { + description: + 'One or more Decision Point Values that were selected for this Decision Point. If the evaluation is uncertain, multiple values may be listed to reflect the potential range of possibilities.', + title: 'values', + type: 'array', + minItems: 1, + items: { + $ref: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json#/$defs/decision_point_value/properties/name', + }, + }, + version: { + $ref: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json#/$defs/decision_point/properties/version', + }, + }, + type: 'object', + required: ['name', 'namespace', 'values', 'version'], + additionalProperties: false, + }, + }, + properties: { + id: { + $ref: '#/$defs/id', + }, + role: { + $ref: '#/$defs/role', + }, + schemaVersion: { + $ref: 'https://certcc.github.io/SSVC/data/schema/v1/Decision_Point-1-0-1.schema.json#/$defs/schemaVersion', + }, + timestamp: { + $ref: '#/$defs/timestamp', + }, + selections: { + description: + 'An array of Decision Points and their selected values for the identified Vulnerability. If a clear evaluation is uncertain, multiple values may be listed for a Decision Point instead of waiting for perfect clarity.', + title: 'selections', + type: 'array', + minItems: 1, + items: { + $ref: '#/$defs/SsvcdecisionpointselectionSchema', + }, + }, + }, + type: 'object', + required: ['selections', 'id', 'timestamp', 'schemaVersion'], + additionalProperties: false, +} diff --git a/lib/shared/csafAjv/format-assertion.js b/lib/shared/csafAjv/format-assertion.js new file mode 100644 index 00000000..793d6fcb --- /dev/null +++ b/lib/shared/csafAjv/format-assertion.js @@ -0,0 +1,11 @@ +export default { + $schema: 'https://json-schema.org/draft/2020-12/schema', + $id: 'https://json-schema.org/draft/2020-12/meta/format-assertion', + $dynamicAnchor: 'meta', + + title: 'Format vocabulary meta-schema for assertion results', + type: ['object', 'boolean'], + properties: { + format: { type: 'string' }, + }, +} diff --git a/lib/shared/csafAjv/meta.js b/lib/shared/csafAjv/meta.js new file mode 100644 index 00000000..ce939a99 --- /dev/null +++ b/lib/shared/csafAjv/meta.js @@ -0,0 +1,13 @@ +export default { + $schema: 'https://json-schema.org/draft/2020-12/schema', + $id: 'https://docs.oasis-open.org/csaf/csaf/v2.1/schema/meta.json', + $dynamicAnchor: 'meta', + $vocabulary: { + 'https://json-schema.org/draft/2020-12/vocab/core': true, + 'https://json-schema.org/draft/2020-12/vocab/format-assertion': true, + }, + allOf: [ + { $ref: 'https://json-schema.org/draft/2020-12/meta/core' }, + { $ref: 'https://json-schema.org/draft/2020-12/meta/format-assertion' }, + ], +} diff --git a/tests/csaf_2_1/schemaTests.js b/tests/csaf_2_1/schemaTests.js new file mode 100644 index 00000000..48ec849e --- /dev/null +++ b/tests/csaf_2_1/schemaTests.js @@ -0,0 +1,45 @@ +import assert from 'node:assert/strict' +import { csaf_2_1, csaf_2_1_strict } from '../../csaf_2_1/schemaTests.js' + +const minimalValidDocument = { + $schema: 'https://docs.oasis-open.org/csaf/csaf/v2.1/schema/csaf.json', + document: { + title: 'Basic CSAF document', + csaf_version: '2.1', + category: 'csaf_base', + distribution: { + tlp: { label: 'AMBER' }, + }, + publisher: { + name: 'Some publisher', + namespace: 'https://example.com', + category: 'coordinator', + }, + tracking: { + id: 'some-id', + initial_release_date: '2025-02-18T14:37:32.671Z', + current_release_date: '2025-06-18T14:37:32.671Z', + revision_history: [ + { + date: '2025-02-18T14:37:32.671Z', + number: '1', + summary: 'Initial release', + }, + ], + version: '1', + status: 'draft', + }, + }, +} + +describe('csaf_2_1_strict', function () { + it('validates a basic document', function () { + assert.ok(csaf_2_1_strict(minimalValidDocument).isValid) + }) +}) + +describe('csaf_2_1', function () { + it('validates a basic document', function () { + assert.ok(csaf_2_1(minimalValidDocument).isValid) + }) +})