diff --git a/csaf/schema/provider_json_schema.json b/csaf/schema/provider_json_schema.json new file mode 100644 index 0000000..f3a3cef --- /dev/null +++ b/csaf/schema/provider_json_schema.json @@ -0,0 +1,211 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://docs.oasis-open.org/csaf/csaf/v2.0/provider_json_schema.json", + "title": "CSAF provider metadata", + "description": "Representation of metadata information of a CSAF provider as a JSON document.", + "type": "object", + "$defs": { + "json_url_t": { + "title": "JSON URL type", + "description": "Contains a URL of a JSON file.", + "type": "string", + "format": "uri", + "pattern": "\\.json$" + }, + "provider_url_t": { + "title": "Provider URL type", + "description": "Contains a URL of a provider-metadata.json.", + "type": "string", + "format": "uri", + "pattern": "/provider-metadata\\.json$" + }, + "url_t": { + "title": "Generic URL type", + "description": "Contains a URL.", + "type": "string", + "format": "uri" + } + }, + "required": [ + "canonical_url", + "last_updated", + "list_on_CSAF_aggregators", + "mirror_on_CSAF_aggregators", + "metadata_version", + "publisher", + "role" + ], + "properties": { + "canonical_url": { + "title": "Canonical URL", + "description": "Contains the URL for this document.", + "$ref": "#/$defs/provider_url_t" + }, + "distributions": { + "title": "List of Distribution", + "description": "Contains a list of used distribution mechanisms.", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "title": "Distribution", + "description": "Contains the information of a used distribution mechanism.", + "type": "object", + "minProperties": 1, + "properties": { + "directory_url": { + "title": "Directory URL", + "description": "Contains the base url for the directory distribution.", + "$ref": "#/$defs/url_t" + }, + "rolie": { + "title": "ROLIE", + "description": "Contains all information for ROLIE distribution.", + "type": "object", + "required": [ + "feeds" + ], + "properties": { + "categories": { + "title": "List of ROLIE category document URLs", + "description": "Contains a list of URLs which contain ROLIE category documents.", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "title": "ROLIE category document URL", + "description": "Contains a URL of a ROLIE category document.", + "$ref": "#/$defs/json_url_t" + } + }, + "feeds": { + "title": "List of ROLIE feeds", + "description": "Contains a list of information about ROLIE feeds.", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "title": "ROLIE feed", + "description": "Contains information about the ROLIE feed.", + "type": "object", + "required": [ + "tlp_label", + "url" + ], + "properties": { + "summary": { + "title": "Summary of the feed", + "description": "Contains a summary of the feed.", + "type": "string", + "examples": [ + "All TLP:WHITE advisories of Example Company." + ] + }, + "tlp_label": { + "title": "TLP label", + "description": "Provides the TLP label for the feed.", + "type": "string", + "enum": [ + "UNLABELED", + "WHITE", + "GREEN", + "AMBER", + "RED" + ] + }, + "url": { + "title": "URL of the feed", + "description": "Contains the URL of the feed.", + "$ref": "#/$defs/json_url_t" + } + } + } + }, + "services": { + "title": "List of ROLIE service document URLs", + "description": "Contains a list of URLs which contain ROLIE service documents.", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "title": "ROLIE service document URL", + "description": "Contains a URL of a ROLIE service document.", + "$ref": "#/$defs/json_url_t" + } + } + } + } + } + } + }, + "last_updated": { + "title": "Last updated", + "description": "Holds the date and time when the document was last updated.", + "type": "string", + "format": "date-time" + }, + "list_on_CSAF_aggregators": { + "title": "List on CSAF aggregators", + "description": "Decides whether this file should be linked in the list of a CSAF aggregator.", + "type": "boolean", + "default": true + }, + "metadata_version": { + "title": "CSAF provider metadata version", + "description": "Gives the version of the CSAF provider metadata specification which the document was generated for.", + "type": "string", + "enum": [ + "2.0" + ] + }, + "mirror_on_CSAF_aggregators": { + "title": "Mirror on CSAF aggregators", + "description": "Decides whether the CSAF documents can be mirrored and provided by a CSAF aggregator.", + "type": "boolean", + "default": true + }, + "pgp_keys": { + "title": "List of PGP keys", + "description": "Contains a list of pgp keys used to sign CSAF documents.", + "type": "array", + "items": { + "title": "PGP keys", + "description": "Contains all information about a pgp keys used to sign CSAF documents.", + "type": "object", + "required": [ + "url" + ], + "properties": { + "fingerprint": { + "title": "Fingerprint of the key", + "description": "Contains the fingerprint of the pgp key.", + "type": "string", + "minLength": 40, + "pattern": "^[0-9a-fA-F]{40,}$" + }, + "url": { + "title": "URL of the key", + "description": "Contains the URL where the key can be retrieved.", + "$ref": "#/$defs/url_t" + } + } + } + }, + "publisher": { + "title": "Publisher", + "description": "Provides information about the publisher of the CSAF documents in this repository.", + "$ref": "https://docs.oasis-open.org/csaf/csaf/v2.0/csaf_json_schema.json#/properties/document/properties/publisher" + }, + "role": { + "title": "Role of the issuing party", + "description": "Contains the role of the issuing party according to section 7 in the CSAF standard.", + "type": "string", + "default": "csaf_provider", + "enum": [ + "csaf_publisher", + "csaf_provider", + "csaf_trusted_provider" + ] + } + } +} diff --git a/csaf/validation.go b/csaf/validation.go index bb06412..d753b54 100644 --- a/csaf/validation.go +++ b/csaf/validation.go @@ -22,43 +22,63 @@ var cvss30 []byte //go:embed schema/cvss-v3.1.json var cvss31 []byte +//go:embed schema/provider_json_schema.json +var providerSchema []byte + var ( - compileSchemaOnce sync.Once - compileError error - compiledSchema *jsonschema.Schema + compiledCSAFSchema compiledSchema + compiledProviderSchema compiledSchema ) -func compileSchema() { - c := jsonschema.NewCompiler() - - for _, s := range []struct { - url string - data []byte - }{ +func init() { + compiledCSAFSchema.compiler([]schemaData{ {"https://docs.oasis-open.org/csaf/csaf/v2.0/csaf_json_schema.json", csafSchema}, {"https://www.first.org/cvss/cvss-v2.0.json", cvss20}, {"https://www.first.org/cvss/cvss-v3.0.json", cvss30}, {"https://www.first.org/cvss/cvss-v3.1.json", cvss31}, - } { - if compileError = c.AddResource(s.url, bytes.NewReader(s.data)); compileError != nil { - return - } - } - - compiledSchema, compileError = c.Compile( - "https://docs.oasis-open.org/csaf/csaf/v2.0/csaf_json_schema.json") + }) + compiledProviderSchema.compiler([]schemaData{ + {"https://docs.oasis-open.org/csaf/csaf/v2.0/provider_json_schema.json", providerSchema}, + {"https://docs.oasis-open.org/csaf/csaf/v2.0/csaf_json_schema.json", csafSchema}, + }) } -// ValidateCSAF validates the document data against the JSON schema -// of CSAF. -func ValidateCSAF(doc interface{}) ([]string, error) { +type schemaData struct { + url string + data []byte +} - compileSchemaOnce.Do(compileSchema) - if compileError != nil { - return nil, compileError +type compiledSchema struct { + once sync.Once + compile func() + err error + compiled *jsonschema.Schema +} + +func (cs *compiledSchema) compiler(sds []schemaData) { + if len(sds) == 0 { + panic("missing schema data") + } + cs.compile = func() { + c := jsonschema.NewCompiler() + for _, s := range sds { + if cs.err = c.AddResource( + s.url, bytes.NewReader(s.data)); cs.err != nil { + return + } + } + cs.compiled, cs.err = c.Compile(sds[0].url) + } +} + +func (cs *compiledSchema) validate(doc interface{}) ([]string, error) { + cs.once.Do(cs.compile) + + if cs.err != nil { + return nil, cs.err } - err := compiledSchema.Validate(doc) + err := cs.compiled.Validate(doc) if err == nil { return nil, nil } @@ -100,3 +120,15 @@ func ValidateCSAF(doc interface{}) ([]string, error) { return res, nil } + +// ValidateCSAF validates the document doc against the JSON schema +// of CSAF. +func ValidateCSAF(doc interface{}) ([]string, error) { + return compiledCSAFSchema.validate(doc) +} + +// ValidateProviderMetadata validates the document doc against the JSON schema +// of provider metadata. +func ValidateProviderMetadata(doc interface{}) ([]string, error) { + return compiledProviderSchema.validate(doc) +}