From adf98736cc8950fb165c87b05d73b2ee2e8002cc Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Sun, 29 Jan 2023 19:31:36 +0100 Subject: [PATCH 1/3] Add csaf_validator --- README.md | 3 + cmd/csaf_validator/main.go | 130 +++++++++++++++++++++++++++++++++++++ docs/csaf_validator.md | 18 +++++ 3 files changed, 151 insertions(+) create mode 100644 cmd/csaf_validator/main.go create mode 100644 docs/csaf_validator.md diff --git a/README.md b/README.md index 7439286..f8d6595 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ is a tool for testing a CSAF Trusted Provider according to [Section 7 of the CSA ## [csaf_downloader](docs/csaf_downloader.md) is a tool for downloading advisories from a provider. +## [csaf_validator](docs/csaf_validator.md) +is a tool to validate local advisories files against the JSON Schema and an optional remote validator. + ## Setup Note that binaries for the server side are only available and tested for GNU/Linux-Systems, e.g. Ubuntu LTS. diff --git a/cmd/csaf_validator/main.go b/cmd/csaf_validator/main.go new file mode 100644 index 0000000..1603ee3 --- /dev/null +++ b/cmd/csaf_validator/main.go @@ -0,0 +1,130 @@ +// This file is Free Software under the MIT License +// without warranty, see README.md and LICENSES/MIT.txt for details. +// +// SPDX-License-Identifier: MIT +// +// SPDX-FileCopyrightText: 2023 German Federal Office for Information Security (BSI) +// Software-Engineering: 2023 Intevation GmbH + +// Package main implements the csaf_validator tool. +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "github.com/csaf-poc/csaf_distribution/csaf" + "github.com/csaf-poc/csaf_distribution/util" + "github.com/jessevdk/go-flags" +) + +type options struct { + Version bool `long:"version" description:"Display version of the binary"` + RemoteValidator string `long:"validator" description:"URL to validate documents remotely" value-name:"URL"` + RemoteValidatorCache string `long:"validatorcache" description:"FILE to cache remote validations" value-name:"FILE"` + RemoteValidatorPresets []string `long:"validatorpreset" description:"One or more presets to validate remotely" default:"mandatory"` +} + +func main() { + opts := new(options) + + parser := flags.NewParser(opts, flags.Default) + parser.Usage = "[OPTIONS] files..." + files, err := parser.Parse() + errCheck(err) + + if opts.Version { + fmt.Println(util.SemVersion) + return + } + + if len(files) == 0 { + log.Println("No domains given.") + return + } + + errCheck(run(opts, files)) +} + +// run validates the given files. +func run(opts *options, files []string) error { + + var validator csaf.RemoteValidator + + if opts.RemoteValidator != "" { + validatorOptions := csaf.RemoteValidatorOptions{ + URL: opts.RemoteValidator, + Presets: opts.RemoteValidatorPresets, + Cache: opts.RemoteValidatorCache, + } + var err error + if validator, err = validatorOptions.Open(); err != nil { + return fmt.Errorf( + "preparing remote validator failed: %w", err) + } + defer validator.Close() + } + + for _, file := range files { + doc, err := loadJSONFromFile(file) + if err != nil { + log.Printf("error: loading %q as JSON failed: %v\n", file, err) + continue + } + // Validate agsinst Schema. + validationErrs, err := csaf.ValidateCSAF(doc) + if err != nil { + log.Printf("error: validating %q against schema failed: %v\n", + file, err) + + } + if len(validationErrs) > 0 { + fmt.Printf("schema validation errors of %q\n", file) + for _, vErr := range validationErrs { + fmt.Printf(" * %s\n", vErr) + } + } else { + fmt.Printf("%s passes the schema validation.\n", file) + } + // Validate against remote validator. + validate, err := validator.Validate(doc) + if err != nil { + return fmt.Errorf("remote validation of %q failed: %w", + file, err) + } + var passes string + if validate { + passes = "passes" + } else { + passes = "does not pass" + } + fmt.Printf("%q %s remote validation.\n", file, passes) + } + + return nil +} + +func errCheck(err error) { + if err != nil { + if flags.WroteHelp(err) { + os.Exit(0) + } + log.Fatalf("error: %v\n", err) + } +} + +// loadJSONFromFile loads a JSON document from a file. +func loadJSONFromFile(fname string) (any, error) { + f, err := os.Open(fname) + if err != nil { + return nil, err + } + defer f.Close() + var doc any + if err = json.NewDecoder(f).Decode(&doc); err != nil { + return nil, err + } + return doc, err +} diff --git a/docs/csaf_validator.md b/docs/csaf_validator.md new file mode 100644 index 0000000..8aefa43 --- /dev/null +++ b/docs/csaf_validator.md @@ -0,0 +1,18 @@ +## csaf_validator + +is a tool to validate local advisories files against the JSON Schema and an optional remote validator. + +### Usage + +``` +csaf_validator [OPTIONS] files... + +Application Options: + --version Display version of the binary + --validator=URL URL to validate documents remotely + --validatorcache=FILE FILE to cache remote validations + --validatorpreset= One or more presets to validate remotely (default: mandatory) + +Help Options: + -h, --help Show this help message +``` From 6a60e8d8ce839b4f4a58cc493569974f59bebe51 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Mon, 30 Jan 2023 08:23:05 +0100 Subject: [PATCH 2/3] Fix crash if remote validator is not configured. Small cosmetics. --- cmd/csaf_validator/main.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/cmd/csaf_validator/main.go b/cmd/csaf_validator/main.go index 1603ee3..3a8446c 100644 --- a/cmd/csaf_validator/main.go +++ b/cmd/csaf_validator/main.go @@ -41,7 +41,7 @@ func main() { } if len(files) == 0 { - log.Println("No domains given.") + log.Println("No files given.") return } @@ -86,21 +86,23 @@ func run(opts *options, files []string) error { fmt.Printf(" * %s\n", vErr) } } else { - fmt.Printf("%s passes the schema validation.\n", file) + fmt.Printf("%q passes the schema validation.\n", file) } // Validate against remote validator. - validate, err := validator.Validate(doc) - if err != nil { - return fmt.Errorf("remote validation of %q failed: %w", - file, err) + if validator != nil { + validate, err := validator.Validate(doc) + if err != nil { + return fmt.Errorf("remote validation of %q failed: %w", + file, err) + } + var passes string + if validate { + passes = "passes" + } else { + passes = "does not pass" + } + fmt.Printf("%q %s remote validation.\n", file, passes) } - var passes string - if validate { - passes = "passes" - } else { - passes = "does not pass" - } - fmt.Printf("%q %s remote validation.\n", file, passes) } return nil From a8493c0dd28f8995a873c42b1f372aab29e56806 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Mon, 30 Jan 2023 08:29:54 +0100 Subject: [PATCH 3/3] Check if the file name is valid --- cmd/csaf_validator/main.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/csaf_validator/main.go b/cmd/csaf_validator/main.go index 3a8446c..8676218 100644 --- a/cmd/csaf_validator/main.go +++ b/cmd/csaf_validator/main.go @@ -14,6 +14,7 @@ import ( "fmt" "log" "os" + "path/filepath" "github.com/csaf-poc/csaf_distribution/csaf" "github.com/csaf-poc/csaf_distribution/util" @@ -68,6 +69,10 @@ func run(opts *options, files []string) error { } for _, file := range files { + // Check if the file name is valid. + if !util.ConfirmingFileName(filepath.Base(file)) { + fmt.Printf("%q is not a valid advisory name.\n", file) + } doc, err := loadJSONFromFile(file) if err != nil { log.Printf("error: loading %q as JSON failed: %v\n", file, err)