diff --git a/cmd/csaf_aggregator/client_test.go b/cmd/csaf_aggregator/client_test.go new file mode 100644 index 0000000..fc5b095 --- /dev/null +++ b/cmd/csaf_aggregator/client_test.go @@ -0,0 +1,67 @@ +// 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: 2022 German Federal Office for Information Security (BSI) +// Software-Engineering: 2022 Intevation GmbH + +package main + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gocsaf/csaf/v3/util" +) + +func Test_downloadJSON(t *testing.T) { + tests := []struct { + name string + statusCode int + contentType string + wantErr error + }{ + { + name: "status ok, application/json", + statusCode: http.StatusOK, + contentType: "application/json", + wantErr: nil, + }, + { + name: "status found, application/json", + statusCode: http.StatusFound, + contentType: "application/json", + wantErr: errNotFound, + }, + { + name: "status ok, application/xml", + statusCode: http.StatusOK, + contentType: "application/xml", + wantErr: errNotFound, + }, + } + + t.Parallel() + for _, testToRun := range tests { + test := testToRun + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + found := func(r io.Reader) error { + return nil + } + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Content-Type", test.contentType) + w.WriteHeader(test.statusCode) + })) + defer server.Close() + hClient := http.Client{} + client := util.Client(&hClient) + if gotErr := downloadJSON(client, server.URL, found); gotErr != test.wantErr { + t.Errorf("downloadJSON: Expected %q but got %q.", test.wantErr, gotErr) + } + }) + } +} diff --git a/cmd/csaf_checker/links_test.go b/cmd/csaf_checker/links_test.go index 8abf4e6..6baccf8 100644 --- a/cmd/csaf_checker/links_test.go +++ b/cmd/csaf_checker/links_test.go @@ -10,8 +10,12 @@ package main import ( "fmt" + "net/http" + "net/http/httptest" "strings" "testing" + + "github.com/gocsaf/csaf/v3/util" ) const page0 = ` @@ -31,7 +35,6 @@ const page0 = ` ` func TestLinksOnPage(t *testing.T) { - var links []string err := linksOnPage( @@ -58,3 +61,78 @@ func TestLinksOnPage(t *testing.T) { } } } + +func Test_listed(t *testing.T) { + tests := []struct { + name string + badDirs util.Set[string] + path string + want bool + }{ + { + name: "listed path", + badDirs: util.Set[string]{}, + path: "/white/avendor-advisory-0004.json", + want: true, + }, + { + name: "badDirs contains path", + badDirs: util.Set[string]{"/white/": {}}, + path: "/white/avendor-advisory-0004.json", + want: false, + }, + { + name: "not found", + badDirs: util.Set[string]{}, + path: "/not-found/resource.json", + want: false, + }, + { + name: "badDirs does not contain path", + badDirs: util.Set[string]{"/bad-dir/": {}}, + path: "/white/avendor-advisory-0004.json", + want: true, + }, + { + name: "unlisted path", + badDirs: util.Set[string]{}, + path: "/white/avendor-advisory-0004-not-listed.json", + want: false, + }, + } + + t.Parallel() + for _, testToRun := range tests { + test := testToRun + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + serverURL := "" + fs := http.FileServer(http.Dir("../../testdata/simple-directory-provider")) + server := httptest.NewTLSServer(fs) + defer server.Close() + + serverURL = server.URL + + hClient := server.Client() + client := util.Client(hClient) + + pgs := pages{} + cfg := config{RemoteValidator: "", RemoteValidatorCache: ""} + p, err := newProcessor(&cfg) + if err != nil { + t.Error(err) + } + p.client = client + + badDirs := util.Set[string]{} + for dir := range test.badDirs { + badDirs.Add(serverURL + dir) + } + + got, _ := pgs.listed(serverURL+test.path, p, badDirs) + if got != test.want { + t.Errorf("%q: Expected %t but got %t.", test.name, test.want, got) + } + }) + } +} diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index d6f0f6b..c0aafb2 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -83,10 +83,8 @@ type reporter interface { report(*processor, *Domain) } -var ( - // errContinue indicates that the current check should continue. - errContinue = errors.New("continue") -) +// errContinue indicates that the current check should continue. +var errContinue = errors.New("continue") type whereType byte @@ -138,7 +136,7 @@ func (m *topicMessages) info(format string, args ...any) { m.add(InfoType, format, args...) } -// use signals that we going to use this topic. +// use signals that we're going to use this topic. func (m *topicMessages) use() { if *m == nil { *m = []Message{} @@ -164,9 +162,8 @@ func (m *topicMessages) hasErrors() bool { return false } -// newProcessor returns an initilaized processor. +// newProcessor returns an initialized processor. func newProcessor(cfg *config) (*processor, error) { - var validator csaf.RemoteValidator if cfg.RemoteValidator != "" { @@ -239,7 +236,6 @@ func (p *processor) reset() { // Then it calls the report method on each report from the given "reporters" parameter for each domain. // It returns a pointer to the report and nil, otherwise an error. func (p *processor) run(domains []string) (*Report, error) { - report := Report{ Date: ReportTime{Time: time.Now().UTC()}, Version: util.SemVersion, @@ -296,7 +292,6 @@ func (p *processor) run(domains []string) (*Report, error) { // fillMeta fills the report with extra informations from provider metadata. func (p *processor) fillMeta(domain *Domain) error { - if p.pmd == nil { return nil } @@ -322,7 +317,6 @@ func (p *processor) fillMeta(domain *Domain) error { // domainChecks compiles a list of checks which should be performed // for a given domain. func (p *processor) domainChecks(domain string) []func(*processor, string) error { - // If we have a direct domain url we dont need to // perform certain checks. direct := strings.HasPrefix(domain, "https://") @@ -392,7 +386,6 @@ func (p *processor) markChecked(s string, mask whereType) bool { } func (p *processor) checkRedirect(r *http.Request, via []*http.Request) error { - url := r.URL.String() p.checkTLS(url) if p.redirects == nil { @@ -494,7 +487,6 @@ func (p *processor) usedAuthorizedClient() bool { // rolieFeedEntries loads the references to the advisory files for a given feed. func (p *processor) rolieFeedEntries(feed string) ([]csaf.AdvisoryFile, error) { - client := p.httpClient() res, err := client.Get(feed) p.badDirListings.use() @@ -545,7 +537,6 @@ func (p *processor) rolieFeedEntries(feed string) ([]csaf.AdvisoryFile, error) { var files []csaf.AdvisoryFile rfeed.Entries(func(entry *csaf.Entry) { - // Filter if we have date checking. if accept := p.cfg.Range; accept != nil { if t := time.Time(entry.Updated); !t.IsZero() && !accept.Contains(t) { @@ -594,11 +585,17 @@ func (p *processor) rolieFeedEntries(feed string) ([]csaf.AdvisoryFile, error) { var file csaf.AdvisoryFile - if sha256 != "" || sha512 != "" || sign != "" { - file = csaf.HashedAdvisoryFile{url, sha256, sha512, sign} - } else { - file = csaf.PlainAdvisoryFile(url) + switch { + case sha256 == "" && sha512 != "": + p.badROLIEFeed.info("%s has no sha256 hash file listed", url) + case sha256 != "" && sha512 == "": + p.badROLIEFeed.info("%s has no sha512 hash file listed", url) + case sha256 == "" && sha512 == "": + p.badROLIEFeed.error("No hash listed on ROLIE feed %s", url) + case sign == "": + p.badROLIEFeed.error("No signature listed on ROLIE feed %s", url) } + file = csaf.PlainAdvisoryFile{Path: url, SHA256: sha256, SHA512: sha512, Sign: sign} files = append(files, file) }) @@ -753,14 +750,23 @@ func (p *processor) integrity( // Check hashes p.badIntegrities.use() - for _, x := range []struct { + type hash struct { ext string url func() string hash []byte - }{ - {"SHA256", f.SHA256URL, s256.Sum(nil)}, - {"SHA512", f.SHA512URL, s512.Sum(nil)}, - } { + } + hashes := []hash{} + if f.SHA256URL() != "" { + hashes = append(hashes, hash{"SHA256", f.SHA256URL, s256.Sum(nil)}) + } + if f.SHA512URL() != "" { + hashes = append(hashes, hash{"SHA512", f.SHA512URL, s512.Sum(nil)}) + } + + couldFetchHash := false + hashFetchErrors := []string{} + + for _, x := range hashes { hu, err := url.Parse(x.url()) if err != nil { lg(ErrorType, "Bad URL %s: %v", x.url(), err) @@ -771,14 +777,15 @@ func (p *processor) integrity( p.checkTLS(hashFile) if res, err = client.Get(hashFile); err != nil { - p.badIntegrities.error("Fetching %s failed: %v.", hashFile, err) + hashFetchErrors = append(hashFetchErrors, fmt.Sprintf("Fetching %s failed: %v.", hashFile, err)) continue } if res.StatusCode != http.StatusOK { - p.badIntegrities.error("Fetching %s failed: Status code %d (%s)", - hashFile, res.StatusCode, res.Status) + hashFetchErrors = append(hashFetchErrors, fmt.Sprintf("Fetching %s failed: Status code %d (%s)", + hashFile, res.StatusCode, res.Status)) continue } + couldFetchHash = true h, err := func() ([]byte, error) { defer res.Body.Close() return util.HashFromReader(res.Body) @@ -796,6 +803,19 @@ func (p *processor) integrity( x.ext, u, hashFile) } } + + msgType := ErrorType + // Log only as warning, if the other hash could be fetched + if couldFetchHash { + msgType = WarnType + } + if f.IsDirectory() { + msgType = InfoType + } + for _, fetchError := range hashFetchErrors { + p.badIntegrities.add(msgType, fetchError) + } + // Check signature su, err := url.Parse(f.SignURL()) if err != nil { @@ -888,7 +908,8 @@ func (p *processor) checkIndex(base string, mask whereType) error { p.badIntegrities.error("index.txt contains invalid URL %q in line %d", u, line) continue } - files = append(files, csaf.PlainAdvisoryFile(u)) + + files = append(files, csaf.DirectoryAdvisoryFile{Path: u}) } return files, scanner.Err() }() @@ -911,7 +932,6 @@ func (p *processor) checkIndex(base string, mask whereType) error { // of the fields' values and if they are sorted properly. Then it passes the files to the // "integrity" functions. It returns error if some test fails, otherwise nil. func (p *processor) checkChanges(base string, mask whereType) error { - bu, err := url.Parse(base) if err != nil { return err @@ -970,9 +990,9 @@ func (p *processor) checkChanges(base string, mask whereType) error { continue } path := r[pathColumn] - times, files = - append(times, t), - append(files, csaf.PlainAdvisoryFile(path)) + + times, files = append(times, t), + append(files, csaf.DirectoryAdvisoryFile{Path: path}) } return times, files, nil }() @@ -1144,7 +1164,6 @@ func (p *processor) checkMissing(string) error { // checkInvalid goes over all found adivisories URLs and checks // if file name conforms to standard. func (p *processor) checkInvalid(string) error { - p.badDirListings.use() var invalids []string @@ -1166,7 +1185,6 @@ func (p *processor) checkInvalid(string) error { // checkListing goes over all found adivisories URLs and checks // if their parent directory is listable. func (p *processor) checkListing(string) error { - p.badDirListings.use() pgs := pages{} @@ -1201,7 +1219,6 @@ func (p *processor) checkListing(string) error { // checkWhitePermissions checks if the TLP:WHITE advisories are // available with unprotected access. func (p *processor) checkWhitePermissions(string) error { - var ids []string for id, open := range p.labelChecker.whiteAdvisories { if !open { @@ -1227,7 +1244,6 @@ func (p *processor) checkWhitePermissions(string) error { // According to the result, the respective error messages added to // badProviderMetadata. func (p *processor) checkProviderMetadata(domain string) bool { - p.badProviderMetadata.use() client := p.httpClient() @@ -1274,7 +1290,6 @@ func (p *processor) checkSecurity(domain string, legacy bool) (int, string) { // checkSecurityFolder checks the security.txt in a given folder. func (p *processor) checkSecurityFolder(folder string) string { - client := p.httpClient() path := folder + "security.txt" res, err := client.Get(path) @@ -1340,9 +1355,7 @@ func (p *processor) checkSecurityFolder(folder string) string { // checkDNS checks if the "csaf.data.security.domain.tld" DNS record is available // and serves the "provider-metadata.json". func (p *processor) checkDNS(domain string) { - p.badDNSPath.use() - client := p.httpClient() path := "https://csaf.data.security." + domain res, err := client.Get(path) @@ -1352,9 +1365,8 @@ func (p *processor) checkDNS(domain string) { return } if res.StatusCode != http.StatusOK { - p.badDNSPath.add(ErrorType, - fmt.Sprintf("Fetching %s failed. Status code %d (%s)", - path, res.StatusCode, res.Status)) + p.badDNSPath.add(ErrorType, fmt.Sprintf("Fetching %s failed. Status code %d (%s)", + path, res.StatusCode, res.Status)) } hash := sha256.New() defer res.Body.Close() @@ -1376,7 +1388,6 @@ func (p *processor) checkDNS(domain string) { func (p *processor) checkWellknown(domain string) { p.badWellknownMetadata.use() - client := p.httpClient() path := "https://" + domain + "/.well-known/csaf/provider-metadata.json" @@ -1405,9 +1416,7 @@ func (p *processor) checkWellknown(domain string) { // for the legacy location will be made. If this fails as well, then an // error is given. func (p *processor) checkWellknownSecurityDNS(domain string) error { - p.checkWellknown(domain) - // Security check for well known (default) and legacy location warnings, sDMessage := p.checkSecurity(domain, false) // if the security.txt under .well-known was not okay @@ -1445,7 +1454,6 @@ func (p *processor) checkWellknownSecurityDNS(domain string) error { // As a result of these checks respective error messages are passed // to badPGP methods. It returns nil if all checks are passed. func (p *processor) checkPGPKeys(_ string) error { - p.badPGPs.use() src, err := p.expr.Eval("$.public_openpgp_keys", p.pmd) @@ -1504,7 +1512,6 @@ func (p *processor) checkPGPKeys(_ string) error { defer res.Body.Close() return crypto.NewKeyFromArmoredReader(res.Body) }() - if err != nil { p.badPGPs.error("Reading public OpenPGP key %s failed: %v", u, err) continue diff --git a/cmd/csaf_checker/processor_test.go b/cmd/csaf_checker/processor_test.go new file mode 100644 index 0000000..0710f32 --- /dev/null +++ b/cmd/csaf_checker/processor_test.go @@ -0,0 +1,206 @@ +// This file is Free Software under the Apache-2.0 License +// without warranty, see README.md and LICENSES/Apache-2.0.txt for details. +// +// SPDX-License-Identifier: Apache-2.0 +// +// SPDX-FileCopyrightText: 2023 German Federal Office for Information Security (BSI) +// Software-Engineering: 2023 Intevation GmbH + +package main + +import ( + "bytes" + "encoding/json" + "net/http/httptest" + "os" + "reflect" + "testing" + "text/template" + + "github.com/gocsaf/csaf/v3/internal/testutil" + "github.com/gocsaf/csaf/v3/util" +) + +func getRequirementTestData(t *testing.T, params testutil.ProviderParams, directoryProvider bool) []Requirement { + path := "../../testdata/processor-requirements/" + if params.EnableSha256 { + path += "sha256-" + } + if params.EnableSha512 { + path += "sha512-" + } + if params.ForbidSha256 { + path += "forbid-sha256-" + } + if params.ForbidSha512 { + path += "forbid-sha512-" + } + if directoryProvider { + path += "directory" + } else { + path += "rolie" + } + path += ".json" + + content, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + + tmplt, err := template.New("base").Parse(string(content)) + if err != nil { + t.Fatal(err) + } + + var output bytes.Buffer + err = tmplt.Execute(&output, params) + if err != nil { + t.Fatal(err) + } + var requirement []Requirement + err = json.Unmarshal(output.Bytes(), &requirement) + if err != nil { + t.Fatal(err) + } + return requirement +} + +func TestShaMarking(t *testing.T) { + tests := []struct { + name string + directoryProvider bool + enableSha256 bool + enableSha512 bool + forbidSha256 bool + forbidSha512 bool + }{ + { + name: "deliver sha256 and sha512", + directoryProvider: false, + enableSha256: true, + enableSha512: true, + }, + { + name: "enable sha256 and sha512, forbid fetching", + directoryProvider: false, + enableSha256: true, + enableSha512: true, + forbidSha256: true, + forbidSha512: true, + }, + { + name: "enable sha256 and sha512, forbid sha256", + directoryProvider: false, + enableSha256: true, + enableSha512: true, + forbidSha256: true, + forbidSha512: false, + }, + { + name: "enable sha256 and sha512, forbid sha512", + directoryProvider: false, + enableSha256: true, + enableSha512: true, + forbidSha256: false, + forbidSha512: true, + }, + { + name: "only deliver sha256", + directoryProvider: false, + enableSha256: true, + enableSha512: false, + }, + { + name: "only deliver sha512", + directoryProvider: false, + enableSha256: false, + enableSha512: true, + }, + { + name: "deliver sha256 and sha512, directory provider", + directoryProvider: true, + enableSha256: true, + enableSha512: true, + }, + { + name: "only deliver sha256, directory provider", + directoryProvider: true, + enableSha256: true, + enableSha512: false, + }, + { + name: "only deliver sha512, directory provider", + directoryProvider: true, + enableSha256: false, + enableSha512: true, + }, + { + name: "no hash", + directoryProvider: false, + enableSha256: false, + enableSha512: false, + }, + { + name: "no hash, directory provider", + directoryProvider: true, + enableSha256: false, + enableSha512: false, + }, + } + + t.Parallel() + for _, testToRun := range tests { + test := testToRun + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + serverURL := "" + params := testutil.ProviderParams{ + URL: "", + EnableSha256: test.enableSha256, + EnableSha512: test.enableSha512, + ForbidSha256: test.forbidSha256, + ForbidSha512: test.forbidSha512, + } + server := httptest.NewTLSServer(testutil.ProviderHandler(¶ms, test.directoryProvider)) + defer server.Close() + + serverURL = server.URL + params.URL = server.URL + + hClient := server.Client() + client := util.Client(hClient) + + cfg := config{} + err := cfg.prepare() + if err != nil { + t.Fatalf("SHA marking config failed: %v", err) + } + p, err := newProcessor(&cfg) + if err != nil { + t.Fatalf("could not init downloader: %v", err) + } + p.client = client + + report, err := p.run([]string{serverURL + "/provider-metadata.json"}) + if err != nil { + t.Errorf("SHA marking %v: Expected no error, got: %v", test.name, err) + } + expected := getRequirementTestData(t, + testutil.ProviderParams{ + URL: serverURL, + EnableSha256: test.enableSha256, + EnableSha512: test.enableSha512, + ForbidSha256: test.forbidSha256, + ForbidSha512: test.forbidSha512, + }, + test.directoryProvider) + for i, got := range report.Domains[0].Requirements { + if !reflect.DeepEqual(expected[i], *got) { + t.Errorf("SHA marking %v: Expected %v, got %v", test.name, expected[i], *got) + } + } + + p.close() + }) + } +} diff --git a/cmd/csaf_downloader/config.go b/cmd/csaf_downloader/config.go index 33f8dc2..da911d7 100644 --- a/cmd/csaf_downloader/config.go +++ b/cmd/csaf_downloader/config.go @@ -41,6 +41,13 @@ const ( validationUnsafe = validationMode("unsafe") ) +type hashAlgorithm string + +const ( + algSha256 = hashAlgorithm("sha256") + algSha512 = hashAlgorithm("sha512") +) + type config struct { Directory string `short:"d" long:"directory" description:"DIRectory to store the downloaded files in" value-name:"DIR" toml:"directory"` Insecure bool `long:"insecure" description:"Do not check TLS certificates from provider" toml:"insecure"` @@ -79,6 +86,9 @@ type config struct { clientCerts []tls.Certificate ignorePattern filter.PatternMatcher + + //lint:ignore SA5008 We are using choice or than once: sha256, sha512 + PreferredHash hashAlgorithm `long:"preferred_hash" choice:"sha256" choice:"sha512" value-name:"HASH" description:"HASH to prefer" toml:"preferred_hash"` } // configPaths are the potential file locations of the config file. @@ -220,7 +230,7 @@ func (cfg *config) prepareLogging() error { w = f } ho := slog.HandlerOptions{ - //AddSource: true, + // AddSource: true, Level: cfg.LogLevel.Level, ReplaceAttr: dropSubSeconds, } diff --git a/cmd/csaf_downloader/downloader.go b/cmd/csaf_downloader/downloader.go index b7e7342..3270a88 100644 --- a/cmd/csaf_downloader/downloader.go +++ b/cmd/csaf_downloader/downloader.go @@ -25,6 +25,7 @@ import ( "os" "path" "path/filepath" + "slices" "strconv" "strings" "sync" @@ -37,8 +38,16 @@ import ( "github.com/gocsaf/csaf/v3/util" ) +type hashFetchInfo struct { + url string + preferred bool + warn bool + hashType hashAlgorithm +} + type downloader struct { cfg *config + client *util.Client // Used for testing keys *crypto.KeyRing validator csaf.RemoteValidator forwarder *forwarder @@ -53,7 +62,6 @@ type downloader struct { const failedValidationDir = "failed_validation" func newDownloader(cfg *config) (*downloader, error) { - var validator csaf.RemoteValidator if cfg.RemoteValidator != "" { @@ -103,7 +111,6 @@ func logRedirect(req *http.Request, via []*http.Request) error { } func (d *downloader) httpClient() util.Client { - hClient := http.Client{} if d.cfg.verbose() { @@ -126,6 +133,11 @@ func (d *downloader) httpClient() util.Client { client := util.Client(&hClient) + // Overwrite for testing purposes + if d.client != nil { + client = *d.client + } + // Add extra headers. client = &util.HeaderClient{ Client: client, @@ -252,7 +264,6 @@ func (d *downloader) downloadFiles( label csaf.TLPLabel, files []csaf.AdvisoryFile, ) error { - var ( advisoryCh = make(chan csaf.AdvisoryFile) errorCh = make(chan error) @@ -302,7 +313,6 @@ func (d *downloader) loadOpenPGPKeys( base *url.URL, expr *util.PathEval, ) error { - src, err := expr.Eval("$.public_openpgp_keys", doc) if err != nil { // no keys. @@ -356,7 +366,6 @@ func (d *downloader) loadOpenPGPKeys( defer res.Body.Close() return crypto.NewKeyFromArmoredReader(res.Body) }() - if err != nil { slog.Warn( "Reading public OpenPGP key failed", @@ -500,24 +509,42 @@ nextAdvisory: signData []byte ) - // Only hash when we have a remote counter part we can compare it with. - if remoteSHA256, s256Data, err = loadHash(client, file.SHA256URL()); err != nil { - slog.Warn("Cannot fetch SHA256", - "url", file.SHA256URL(), - "error", err) + hashToFetch := []hashFetchInfo{} + if file.SHA512URL() != "" { + hashToFetch = append(hashToFetch, hashFetchInfo{ + url: file.SHA512URL(), + warn: true, + hashType: algSha512, + preferred: strings.EqualFold(string(d.cfg.PreferredHash), string(algSha512)), + }) } else { - s256 = sha256.New() - writers = append(writers, s256) + slog.Info("SHA512 not present") + } + if file.SHA256URL() != "" { + hashToFetch = append(hashToFetch, hashFetchInfo{ + url: file.SHA256URL(), + warn: true, + hashType: algSha256, + preferred: strings.EqualFold(string(d.cfg.PreferredHash), string(algSha256)), + }) + } else { + slog.Info("SHA256 not present") + } + if file.IsDirectory() { + for i := range hashToFetch { + hashToFetch[i].warn = false + } } - if remoteSHA512, s512Data, err = loadHash(client, file.SHA512URL()); err != nil { - slog.Warn("Cannot fetch SHA512", - "url", file.SHA512URL(), - "error", err) - } else { + remoteSHA256, s256Data, remoteSHA512, s512Data = loadHashes(client, hashToFetch) + if remoteSHA512 != nil { s512 = sha512.New() writers = append(writers, s512) } + if remoteSHA256 != nil { + s256 = sha256.New() + writers = append(writers, s256) + } // Remember the data as we need to store it to file later. data.Reset() @@ -747,6 +774,50 @@ func loadSignature(client util.Client, p string) (*crypto.PGPSignature, []byte, return sign, data, nil } +func loadHashes(client util.Client, hashes []hashFetchInfo) ([]byte, []byte, []byte, []byte) { + var remoteSha256, remoteSha512, sha256Data, sha512Data []byte + + // Load preferred hashes first + slices.SortStableFunc(hashes, func(a, b hashFetchInfo) int { + if a.preferred == b.preferred { + return 0 + } + if a.preferred && !b.preferred { + return -1 + } + return 1 + }) + for _, h := range hashes { + if remote, data, err := loadHash(client, h.url); err != nil { + if h.warn { + slog.Warn("Cannot fetch hash", + "hash", h.hashType, + "url", h.url, + "error", err) + } else { + slog.Info("Hash not present", "hash", h.hashType, "file", h.url) + } + } else { + switch h.hashType { + case algSha512: + { + remoteSha512 = remote + sha512Data = data + } + case algSha256: + { + remoteSha256 = remote + sha256Data = data + } + } + if h.preferred { + break + } + } + } + return remoteSha256, sha256Data, remoteSha512, sha512Data +} + func loadHash(client util.Client, p string) ([]byte, []byte, error) { resp, err := client.Get(p) if err != nil { diff --git a/cmd/csaf_downloader/downloader_test.go b/cmd/csaf_downloader/downloader_test.go new file mode 100644 index 0000000..d7eaae3 --- /dev/null +++ b/cmd/csaf_downloader/downloader_test.go @@ -0,0 +1,162 @@ +// This file is Free Software under the Apache-2.0 License +// without warranty, see README.md and LICENSES/Apache-2.0.txt for details. +// +// SPDX-License-Identifier: Apache-2.0 +// +// SPDX-FileCopyrightText: 2023 German Federal Office for Information Security (BSI) +// Software-Engineering: 2023 Intevation GmbH + +package main + +import ( + "context" + "errors" + "log/slog" + "net/http/httptest" + "os" + "testing" + + "github.com/gocsaf/csaf/v3/internal/options" + "github.com/gocsaf/csaf/v3/internal/testutil" + "github.com/gocsaf/csaf/v3/util" +) + +func checkIfFileExists(path string, t *testing.T) bool { + if _, err := os.Stat(path); err == nil { + return true + } else if errors.Is(err, os.ErrNotExist) { + return false + } else { + t.Fatalf("Failed to check if file exists: %v", err) + return false + } +} + +func TestShaMarking(t *testing.T) { + tests := []struct { + name string + directoryProvider bool + wantSha256 bool + wantSha512 bool + enableSha256 bool + enableSha512 bool + preferredHash hashAlgorithm + }{ + { + name: "want sha256 and sha512", + directoryProvider: false, + wantSha256: true, + wantSha512: true, + enableSha256: true, + enableSha512: true, + }, + { + name: "only want sha256", + directoryProvider: false, + wantSha256: true, + wantSha512: false, + enableSha256: true, + enableSha512: true, + preferredHash: algSha256, + }, + { + name: "only want sha512", + directoryProvider: false, + wantSha256: false, + wantSha512: true, + enableSha256: true, + enableSha512: true, + preferredHash: algSha512, + }, + { + name: "only want sha512", + directoryProvider: false, + wantSha256: false, + wantSha512: true, + enableSha256: true, + enableSha512: true, + preferredHash: algSha512, + }, + + { + name: "only deliver sha256", + directoryProvider: false, + wantSha256: true, + wantSha512: false, + enableSha256: true, + enableSha512: false, + preferredHash: algSha512, + }, + { + name: "only want sha256, directory provider", + directoryProvider: true, + wantSha256: true, + wantSha512: false, + enableSha256: true, + enableSha512: true, + preferredHash: algSha256, + }, + { + name: "only want sha512, directory provider", + directoryProvider: true, + wantSha256: false, + wantSha512: true, + enableSha256: true, + enableSha512: true, + preferredHash: algSha512, + }, + } + + t.Parallel() + for _, testToRun := range tests { + test := testToRun + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + serverURL := "" + params := testutil.ProviderParams{ + URL: "", + EnableSha256: test.enableSha256, + EnableSha512: test.enableSha512, + } + server := httptest.NewTLSServer(testutil.ProviderHandler(¶ms, test.directoryProvider)) + defer server.Close() + + serverURL = server.URL + params.URL = server.URL + + hClient := server.Client() + client := util.Client(hClient) + + tempDir := t.TempDir() + cfg := config{LogLevel: &options.LogLevel{Level: slog.LevelDebug}, Directory: tempDir, PreferredHash: test.preferredHash} + err := cfg.prepare() + if err != nil { + t.Fatalf("SHA marking config failed: %v", err) + } + d, err := newDownloader(&cfg) + if err != nil { + t.Fatalf("could not init downloader: %v", err) + } + d.client = &client + + ctx := context.Background() + err = d.run(ctx, []string{serverURL + "/provider-metadata.json"}) + if err != nil { + t.Errorf("SHA marking %v: Expected no error, got: %v", test.name, err) + } + d.close() + + // Check for downloaded hashes + sha256Exists := checkIfFileExists(tempDir+"/white/2020/avendor-advisory-0004.json.sha256", t) + sha512Exists := checkIfFileExists(tempDir+"/white/2020/avendor-advisory-0004.json.sha512", t) + + if sha256Exists != test.wantSha256 { + t.Errorf("%v: expected sha256 hash present to be %v, got: %v", test.name, test.wantSha256, sha256Exists) + } + + if sha512Exists != test.wantSha512 { + t.Errorf("%v: expected sha512 hash present to be %v, got: %v", test.name, test.wantSha512, sha512Exists) + } + }) + } +} diff --git a/csaf/advisories.go b/csaf/advisories.go index c51c84c..df23935 100644 --- a/csaf/advisories.go +++ b/csaf/advisories.go @@ -29,58 +29,62 @@ type AdvisoryFile interface { SHA256URL() string SHA512URL() string SignURL() string + IsDirectory() bool } -// PlainAdvisoryFile is a simple implementation of checkFile. -// The hash and signature files are directly constructed by extending -// the file name. -type PlainAdvisoryFile string +// PlainAdvisoryFile contains all relevant urls of a remote file. +type PlainAdvisoryFile struct { + Path string + SHA256 string + SHA512 string + Sign string +} // URL returns the URL of this advisory. -func (paf PlainAdvisoryFile) URL() string { return string(paf) } +func (paf PlainAdvisoryFile) URL() string { return paf.Path } // SHA256URL returns the URL of SHA256 hash file of this advisory. -func (paf PlainAdvisoryFile) SHA256URL() string { return string(paf) + ".sha256" } +func (paf PlainAdvisoryFile) SHA256URL() string { return paf.SHA256 } // SHA512URL returns the URL of SHA512 hash file of this advisory. -func (paf PlainAdvisoryFile) SHA512URL() string { return string(paf) + ".sha512" } +func (paf PlainAdvisoryFile) SHA512URL() string { return paf.SHA512 } // SignURL returns the URL of signature file of this advisory. -func (paf PlainAdvisoryFile) SignURL() string { return string(paf) + ".asc" } +func (paf PlainAdvisoryFile) SignURL() string { return paf.Sign } + +// IsDirectory returns true, if was fetched via directory feeds. +func (paf PlainAdvisoryFile) IsDirectory() bool { return false } // LogValue implements [slog.LogValuer] func (paf PlainAdvisoryFile) LogValue() slog.Value { return slog.GroupValue(slog.String("url", paf.URL())) } -// HashedAdvisoryFile is a more involed version of checkFile. -// Here each component can be given explicitly. -// If a component is not given it is constructed by -// extending the first component. -type HashedAdvisoryFile [4]string - -func (haf HashedAdvisoryFile) name(i int, ext string) string { - if haf[i] != "" { - return haf[i] - } - return haf[0] + ext +// DirectoryAdvisoryFile only contains the base file path. +// The hash and signature files are directly constructed by extending +// the file name. +type DirectoryAdvisoryFile struct { + Path string } // URL returns the URL of this advisory. -func (haf HashedAdvisoryFile) URL() string { return haf[0] } +func (daf DirectoryAdvisoryFile) URL() string { return daf.Path } // SHA256URL returns the URL of SHA256 hash file of this advisory. -func (haf HashedAdvisoryFile) SHA256URL() string { return haf.name(1, ".sha256") } +func (daf DirectoryAdvisoryFile) SHA256URL() string { return daf.Path + ".sha256" } // SHA512URL returns the URL of SHA512 hash file of this advisory. -func (haf HashedAdvisoryFile) SHA512URL() string { return haf.name(2, ".sha512") } +func (daf DirectoryAdvisoryFile) SHA512URL() string { return daf.Path + ".sha512" } // SignURL returns the URL of signature file of this advisory. -func (haf HashedAdvisoryFile) SignURL() string { return haf.name(3, ".asc") } +func (daf DirectoryAdvisoryFile) SignURL() string { return daf.Path + ".asc" } + +// IsDirectory returns true, if was fetched via directory feeds. +func (daf DirectoryAdvisoryFile) IsDirectory() bool { return true } // LogValue implements [slog.LogValuer] -func (haf HashedAdvisoryFile) LogValue() slog.Value { - return slog.GroupValue(slog.String("url", haf.URL())) +func (daf DirectoryAdvisoryFile) LogValue() slog.Value { + return slog.GroupValue(slog.String("url", daf.URL())) } // AdvisoryFileProcessor implements the extraction of @@ -94,7 +98,7 @@ type AdvisoryFileProcessor struct { base *url.URL } -// NewAdvisoryFileProcessor constructs an filename extractor +// NewAdvisoryFileProcessor constructs a filename extractor // for a given metadata document. func NewAdvisoryFileProcessor( client util.Client, @@ -120,7 +124,7 @@ func empty(arr []string) bool { return true } -// Process extracts the adivisory filenames and passes them with +// Process extracts the advisory filenames and passes them with // the corresponding label to fn. func (afp *AdvisoryFileProcessor) Process( fn func(TLPLabel, []AdvisoryFile) error, @@ -257,8 +261,9 @@ func (afp *AdvisoryFileProcessor) loadChanges( lg("%q contains an invalid URL %q in line %d", changesURL, path, line) continue } + files = append(files, - PlainAdvisoryFile(base.JoinPath(path).String())) + DirectoryAdvisoryFile{Path: base.JoinPath(path).String()}) } return files, nil } @@ -325,7 +330,6 @@ func (afp *AdvisoryFileProcessor) processROLIE( } rfeed.Entries(func(entry *Entry) { - // Filter if we have date checking. if afp.AgeAccept != nil { if t := time.Time(entry.Updated); !t.IsZero() && !afp.AgeAccept(t) { @@ -359,10 +363,15 @@ func (afp *AdvisoryFileProcessor) processROLIE( var file AdvisoryFile - if sha256 != "" || sha512 != "" || sign != "" { - file = HashedAdvisoryFile{self, sha256, sha512, sign} - } else { - file = PlainAdvisoryFile(self) + switch { + case sha256 == "" && sha512 == "": + slog.Error("No hash listed on ROLIE feed", "file", self) + return + case sign == "": + slog.Error("No signature listed on ROLIE feed", "file", self) + return + default: + file = PlainAdvisoryFile{self, sha256, sha512, sign} } files = append(files, file) diff --git a/csaf/providermetaloader.go b/csaf/providermetaloader.go index b28b606..72412b3 100644 --- a/csaf/providermetaloader.go +++ b/csaf/providermetaloader.go @@ -352,7 +352,7 @@ func (pmdl *ProviderMetadataLoader) loadFromURL(path string) *LoadedProviderMeta case len(errors) > 0: result.Messages = []ProviderMetadataLoadMessage{{ Type: SchemaValidationFailed, - Message: fmt.Sprintf("%s: Validating against JSON schema failed: %v", path, err), + Message: fmt.Sprintf("%s: Validating against JSON schema failed", path), }} for _, msg := range errors { result.Messages.Add( diff --git a/docs/csaf_downloader.md b/docs/csaf_downloader.md index 04f93b2..d71b546 100644 --- a/docs/csaf_downloader.md +++ b/docs/csaf_downloader.md @@ -34,6 +34,7 @@ Application Options: --log_file=FILE FILE to log downloading to (default: downloader.log) --log_level=LEVEL[debug|info|warn|error] LEVEL of logging details (default: info) -c, --config=TOML-FILE Path to config TOML file + --preferred_hash=HASH[sha256|sha512] HASH to prefer Help Options: -h, --help Show this help message diff --git a/internal/testutil/testutil.go b/internal/testutil/testutil.go new file mode 100644 index 0000000..c7bad68 --- /dev/null +++ b/internal/testutil/testutil.go @@ -0,0 +1,81 @@ +// This file is Free Software under the Apache-2.0 License +// without warranty, see README.md and LICENSES/Apache-2.0.txt for details. +// +// SPDX-License-Identifier: Apache-2.0 +// +// SPDX-FileCopyrightText: 2023 German Federal Office for Information Security (BSI) +// Software-Engineering: 2023 Intevation GmbH + +// Package testutil contains shared helper functions for testing the application. +package testutil + +import ( + "html/template" + "net/http" + "os" + "strings" +) + +// ProviderParams configures the test provider. +type ProviderParams struct { + URL string + EnableSha256 bool + EnableSha512 bool + ForbidSha256 bool + ForbidSha512 bool +} + +// ProviderHandler returns a test provider handler with the specified configuration. +func ProviderHandler(params *ProviderParams, directoryProvider bool) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + path := "../../testdata/" + if directoryProvider { + path += "simple-directory-provider" + } else { + path += "simple-rolie-provider" + } + + path += r.URL.Path + + if strings.HasSuffix(r.URL.Path, "/") { + path += "index.html" + } + + content, err := os.ReadFile(path) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + switch { + case strings.HasSuffix(path, ".html"): + w.Header().Add("Content-Type", "text/html") + case strings.HasSuffix(path, ".json"): + w.Header().Add("Content-Type", "application/json") + case (strings.HasSuffix(path, ".sha256")) && params.ForbidSha256: + w.WriteHeader(http.StatusForbidden) + return + case strings.HasSuffix(path, ".sha512") && params.ForbidSha512: + w.WriteHeader(http.StatusForbidden) + return + case strings.HasSuffix(path, ".sha256") && directoryProvider && !params.EnableSha256: + w.WriteHeader(http.StatusNotFound) + return + case strings.HasSuffix(path, ".sha512") && directoryProvider && !params.EnableSha512: + w.WriteHeader(http.StatusNotFound) + return + default: + w.Header().Add("Content-Type", "text/plain") + } + + tmplt, err := template.New("base").Parse(string(content)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + err = tmplt.Execute(w, params) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + } +} diff --git a/testdata/processor-requirements/directory.json b/testdata/processor-requirements/directory.json new file mode 100644 index 0000000..ed61fcc --- /dev/null +++ b/testdata/processor-requirements/directory.json @@ -0,0 +1,210 @@ +[ + { + "num": 1, + "description": "Valid CSAF documents", + "messages": [ + { + "type": 1, + "text": "No remote validator configured" + }, + { + "type": 0, + "text": "All advisories validated fine against the schema." + } + ] + }, + { + "num": 2, + "description": "Filename", + "messages": [ + { + "type": 0, + "text": "All found filenames are conforming." + } + ] + }, + { + "num": 3, + "description": "TLS", + "messages": [ + { + "type": 0, + "text": "All tested URLs were HTTPS." + } + ] + }, + { + "num": 4, + "description": "TLP:WHITE", + "messages": [ + { + "type": 0, + "text": "All advisories labeled TLP:WHITE were freely accessible." + } + ] + }, + { + "num": 5, + "description": "TLP:AMBER and TLP:RED", + "messages": [ + { + "type": 0, + "text": "No advisories labeled TLP:AMBER or TLP:RED tested for accessibility." + } + ] + }, + { + "num": 6, + "description": "Redirects", + "messages": [ + { + "type": 0, + "text": "No redirections found." + } + ] + }, + { + "num": 7, + "description": "provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Found good provider metadata." + } + ] + }, + { + "num": 8, + "description": "security.txt", + "messages": [ + { + "type": 0, + "text": "Performed no test of security.txt since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 9, + "description": "/.well-known/csaf/provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Performed no test on whether the provider-metadata.json is available under the .well-known path since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 10, + "description": "DNS path", + "messages": [ + { + "type": 0, + "text": "Performed no test on the contents of https://csaf.data.security.DOMAIN since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 11, + "description": "One folder per year", + "messages": [ + { + "type": 2, + "text": "No year folder found in {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 12, + "description": "index.txt", + "messages": [ + { + "type": 0, + "text": "Found {{.URL}}/white/index.txt" + } + ] + }, + { + "num": 13, + "description": "changes.csv", + "messages": [ + { + "type": 0, + "text": "Found {{.URL}}/white/changes.csv" + } + ] + }, + { + "num": 14, + "description": "Directory listings", + "messages": [ + { + "type": 0, + "text": "All directory listings are valid." + } + ] + }, + { + "num": 15, + "description": "ROLIE feed", + "messages": [ + { + "type": 2, + "text": "ROLIE feed based distribution was not used." + } + ] + }, + { + "num": 16, + "description": "ROLIE service document", + "messages": [ + { + "type": 1, + "text": "No ROLIE service document found." + } + ] + }, + { + "num": 17, + "description": "ROLIE category document", + "messages": [ + { + "type": 1, + "text": "No ROLIE category document found." + } + ] + }, + { + "num": 18, + "description": "Integrity", + "messages": [ + { + "type": 0, + "text": "Fetching {{.URL}}/white/avendor-advisory-0004.json.sha256 failed: Status code 404 (404 Not Found)" + }, + { + "type": 0, + "text": "Fetching {{.URL}}/white/avendor-advisory-0004.json.sha512 failed: Status code 404 (404 Not Found)" + } + ] + }, + { + "num": 19, + "description": "Signatures", + "messages": [ + { + "type": 0, + "text": "All signatures verified." + } + ] + }, + { + "num": 20, + "description": "Public OpenPGP Key", + "messages": [ + { + "type": 0, + "text": "1 public OpenPGP key(s) loaded." + } + ] + } +] \ No newline at end of file diff --git a/testdata/processor-requirements/rolie.json b/testdata/processor-requirements/rolie.json new file mode 100644 index 0000000..cd65a7e --- /dev/null +++ b/testdata/processor-requirements/rolie.json @@ -0,0 +1,210 @@ +[ + { + "num": 1, + "description": "Valid CSAF documents", + "messages": [ + { + "type": 1, + "text": "No remote validator configured" + }, + { + "type": 0, + "text": "All advisories validated fine against the schema." + } + ] + }, + { + "num": 2, + "description": "Filename", + "messages": [ + { + "type": 0, + "text": "All found filenames are conforming." + } + ] + }, + { + "num": 3, + "description": "TLS", + "messages": [ + { + "type": 0, + "text": "All tested URLs were HTTPS." + } + ] + }, + { + "num": 4, + "description": "TLP:WHITE", + "messages": [ + { + "type": 0, + "text": "All advisories labeled TLP:WHITE were freely accessible." + } + ] + }, + { + "num": 5, + "description": "TLP:AMBER and TLP:RED", + "messages": [ + { + "type": 0, + "text": "No advisories labeled TLP:AMBER or TLP:RED tested for accessibility." + } + ] + }, + { + "num": 6, + "description": "Redirects", + "messages": [ + { + "type": 0, + "text": "No redirections found." + } + ] + }, + { + "num": 7, + "description": "provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Found good provider metadata." + } + ] + }, + { + "num": 8, + "description": "security.txt", + "messages": [ + { + "type": 0, + "text": "Performed no test of security.txt since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 9, + "description": "/.well-known/csaf/provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Performed no test on whether the provider-metadata.json is available under the .well-known path since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 10, + "description": "DNS path", + "messages": [ + { + "type": 0, + "text": "Performed no test on the contents of https://csaf.data.security.DOMAIN since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 11, + "description": "One folder per year", + "messages": [ + { + "type": 2, + "text": "No year folder found in {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 12, + "description": "index.txt", + "messages": [ + { + "type": 2, + "text": "Fetching index.txt failed: {{.URL}}/index.txt not found." + } + ] + }, + { + "num": 13, + "description": "changes.csv", + "messages": [ + { + "type": 2, + "text": "Fetching changes.csv failed: {{.URL}}/changes.csv not found." + } + ] + }, + { + "num": 14, + "description": "Directory listings", + "messages": [ + { + "type": 2, + "text": "Fetching {{.URL}}/white/ failed. Status code 404 (404 Not Found)" + }, + { + "type": 2, + "text": "Not listed advisories: {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 15, + "description": "ROLIE feed", + "messages": [ + { + "type": 2, + "text": "No hash listed on ROLIE feed {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 16, + "description": "ROLIE service document", + "messages": [ + { + "type": 0, + "text": "ROLIE service document validated fine." + } + ] + }, + { + "num": 17, + "description": "ROLIE category document", + "messages": [ + { + "type": 1, + "text": "Fetching {{.URL}}/white/category-white.json failed. Status code 404 (404 Not Found)" + } + ] + }, + { + "num": 18, + "description": "Integrity", + "messages": [ + { + "type": 0, + "text": "All checksums match." + } + ] + }, + { + "num": 19, + "description": "Signatures", + "messages": [ + { + "type": 0, + "text": "All signatures verified." + } + ] + }, + { + "num": 20, + "description": "Public OpenPGP Key", + "messages": [ + { + "type": 0, + "text": "1 public OpenPGP key(s) loaded." + } + ] + } +] diff --git a/testdata/processor-requirements/sha256-directory.json b/testdata/processor-requirements/sha256-directory.json new file mode 100644 index 0000000..46b4049 --- /dev/null +++ b/testdata/processor-requirements/sha256-directory.json @@ -0,0 +1,206 @@ +[ + { + "num": 1, + "description": "Valid CSAF documents", + "messages": [ + { + "type": 1, + "text": "No remote validator configured" + }, + { + "type": 0, + "text": "All advisories validated fine against the schema." + } + ] + }, + { + "num": 2, + "description": "Filename", + "messages": [ + { + "type": 0, + "text": "All found filenames are conforming." + } + ] + }, + { + "num": 3, + "description": "TLS", + "messages": [ + { + "type": 0, + "text": "All tested URLs were HTTPS." + } + ] + }, + { + "num": 4, + "description": "TLP:WHITE", + "messages": [ + { + "type": 0, + "text": "All advisories labeled TLP:WHITE were freely accessible." + } + ] + }, + { + "num": 5, + "description": "TLP:AMBER and TLP:RED", + "messages": [ + { + "type": 0, + "text": "No advisories labeled TLP:AMBER or TLP:RED tested for accessibility." + } + ] + }, + { + "num": 6, + "description": "Redirects", + "messages": [ + { + "type": 0, + "text": "No redirections found." + } + ] + }, + { + "num": 7, + "description": "provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Found good provider metadata." + } + ] + }, + { + "num": 8, + "description": "security.txt", + "messages": [ + { + "type": 0, + "text": "Performed no test of security.txt since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 9, + "description": "/.well-known/csaf/provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Performed no test on whether the provider-metadata.json is available under the .well-known path since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 10, + "description": "DNS path", + "messages": [ + { + "type": 0, + "text": "Performed no test on the contents of https://csaf.data.security.DOMAIN since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 11, + "description": "One folder per year", + "messages": [ + { + "type": 2, + "text": "No year folder found in {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 12, + "description": "index.txt", + "messages": [ + { + "type": 0, + "text": "Found {{.URL}}/white/index.txt" + } + ] + }, + { + "num": 13, + "description": "changes.csv", + "messages": [ + { + "type": 0, + "text": "Found {{.URL}}/white/changes.csv" + } + ] + }, + { + "num": 14, + "description": "Directory listings", + "messages": [ + { + "type": 0, + "text": "All directory listings are valid." + } + ] + }, + { + "num": 15, + "description": "ROLIE feed", + "messages": [ + { + "type": 2, + "text": "ROLIE feed based distribution was not used." + } + ] + }, + { + "num": 16, + "description": "ROLIE service document", + "messages": [ + { + "type": 1, + "text": "No ROLIE service document found." + } + ] + }, + { + "num": 17, + "description": "ROLIE category document", + "messages": [ + { + "type": 1, + "text": "No ROLIE category document found." + } + ] + }, + { + "num": 18, + "description": "Integrity", + "messages": [ + { + "type": 0, + "text": "Fetching {{.URL}}/white/avendor-advisory-0004.json.sha512 failed: Status code 404 (404 Not Found)" + } + ] + }, + { + "num": 19, + "description": "Signatures", + "messages": [ + { + "type": 0, + "text": "All signatures verified." + } + ] + }, + { + "num": 20, + "description": "Public OpenPGP Key", + "messages": [ + { + "type": 0, + "text": "1 public OpenPGP key(s) loaded." + } + ] + } +] \ No newline at end of file diff --git a/testdata/processor-requirements/sha256-rolie.json b/testdata/processor-requirements/sha256-rolie.json new file mode 100644 index 0000000..4ed47f1 --- /dev/null +++ b/testdata/processor-requirements/sha256-rolie.json @@ -0,0 +1,210 @@ +[ + { + "num": 1, + "description": "Valid CSAF documents", + "messages": [ + { + "type": 1, + "text": "No remote validator configured" + }, + { + "type": 0, + "text": "All advisories validated fine against the schema." + } + ] + }, + { + "num": 2, + "description": "Filename", + "messages": [ + { + "type": 0, + "text": "All found filenames are conforming." + } + ] + }, + { + "num": 3, + "description": "TLS", + "messages": [ + { + "type": 0, + "text": "All tested URLs were HTTPS." + } + ] + }, + { + "num": 4, + "description": "TLP:WHITE", + "messages": [ + { + "type": 0, + "text": "All advisories labeled TLP:WHITE were freely accessible." + } + ] + }, + { + "num": 5, + "description": "TLP:AMBER and TLP:RED", + "messages": [ + { + "type": 0, + "text": "No advisories labeled TLP:AMBER or TLP:RED tested for accessibility." + } + ] + }, + { + "num": 6, + "description": "Redirects", + "messages": [ + { + "type": 0, + "text": "No redirections found." + } + ] + }, + { + "num": 7, + "description": "provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Found good provider metadata." + } + ] + }, + { + "num": 8, + "description": "security.txt", + "messages": [ + { + "type": 0, + "text": "Performed no test of security.txt since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 9, + "description": "/.well-known/csaf/provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Performed no test on whether the provider-metadata.json is available under the .well-known path since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 10, + "description": "DNS path", + "messages": [ + { + "type": 0, + "text": "Performed no test on the contents of https://csaf.data.security.DOMAIN since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 11, + "description": "One folder per year", + "messages": [ + { + "type": 2, + "text": "No year folder found in {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 12, + "description": "index.txt", + "messages": [ + { + "type": 2, + "text": "Fetching index.txt failed: {{.URL}}/index.txt not found." + } + ] + }, + { + "num": 13, + "description": "changes.csv", + "messages": [ + { + "type": 2, + "text": "Fetching changes.csv failed: {{.URL}}/changes.csv not found." + } + ] + }, + { + "num": 14, + "description": "Directory listings", + "messages": [ + { + "type": 2, + "text": "Fetching {{.URL}}/white/ failed. Status code 404 (404 Not Found)" + }, + { + "type": 2, + "text": "Not listed advisories: {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 15, + "description": "ROLIE feed", + "messages": [ + { + "type": 0, + "text": "{{.URL}}/white/avendor-advisory-0004.json has no sha512 hash file listed" + } + ] + }, + { + "num": 16, + "description": "ROLIE service document", + "messages": [ + { + "type": 0, + "text": "ROLIE service document validated fine." + } + ] + }, + { + "num": 17, + "description": "ROLIE category document", + "messages": [ + { + "type": 1, + "text": "Fetching {{.URL}}/white/category-white.json failed. Status code 404 (404 Not Found)" + } + ] + }, + { + "num": 18, + "description": "Integrity", + "messages": [ + { + "type": 0, + "text": "All checksums match." + } + ] + }, + { + "num": 19, + "description": "Signatures", + "messages": [ + { + "type": 0, + "text": "All signatures verified." + } + ] + }, + { + "num": 20, + "description": "Public OpenPGP Key", + "messages": [ + { + "type": 0, + "text": "1 public OpenPGP key(s) loaded." + } + ] + } +] diff --git a/testdata/processor-requirements/sha256-sha512-directory.json b/testdata/processor-requirements/sha256-sha512-directory.json new file mode 100644 index 0000000..3e30b9a --- /dev/null +++ b/testdata/processor-requirements/sha256-sha512-directory.json @@ -0,0 +1,206 @@ +[ + { + "num": 1, + "description": "Valid CSAF documents", + "messages": [ + { + "type": 1, + "text": "No remote validator configured" + }, + { + "type": 0, + "text": "All advisories validated fine against the schema." + } + ] + }, + { + "num": 2, + "description": "Filename", + "messages": [ + { + "type": 0, + "text": "All found filenames are conforming." + } + ] + }, + { + "num": 3, + "description": "TLS", + "messages": [ + { + "type": 0, + "text": "All tested URLs were HTTPS." + } + ] + }, + { + "num": 4, + "description": "TLP:WHITE", + "messages": [ + { + "type": 0, + "text": "All advisories labeled TLP:WHITE were freely accessible." + } + ] + }, + { + "num": 5, + "description": "TLP:AMBER and TLP:RED", + "messages": [ + { + "type": 0, + "text": "No advisories labeled TLP:AMBER or TLP:RED tested for accessibility." + } + ] + }, + { + "num": 6, + "description": "Redirects", + "messages": [ + { + "type": 0, + "text": "No redirections found." + } + ] + }, + { + "num": 7, + "description": "provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Found good provider metadata." + } + ] + }, + { + "num": 8, + "description": "security.txt", + "messages": [ + { + "type": 0, + "text": "Performed no test of security.txt since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 9, + "description": "/.well-known/csaf/provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Performed no test on whether the provider-metadata.json is available under the .well-known path since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 10, + "description": "DNS path", + "messages": [ + { + "type": 0, + "text": "Performed no test on the contents of https://csaf.data.security.DOMAIN since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 11, + "description": "One folder per year", + "messages": [ + { + "type": 2, + "text": "No year folder found in {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 12, + "description": "index.txt", + "messages": [ + { + "type": 0, + "text": "Found {{.URL}}/white/index.txt" + } + ] + }, + { + "num": 13, + "description": "changes.csv", + "messages": [ + { + "type": 0, + "text": "Found {{.URL}}/white/changes.csv" + } + ] + }, + { + "num": 14, + "description": "Directory listings", + "messages": [ + { + "type": 0, + "text": "All directory listings are valid." + } + ] + }, + { + "num": 15, + "description": "ROLIE feed", + "messages": [ + { + "type": 2, + "text": "ROLIE feed based distribution was not used." + } + ] + }, + { + "num": 16, + "description": "ROLIE service document", + "messages": [ + { + "type": 1, + "text": "No ROLIE service document found." + } + ] + }, + { + "num": 17, + "description": "ROLIE category document", + "messages": [ + { + "type": 1, + "text": "No ROLIE category document found." + } + ] + }, + { + "num": 18, + "description": "Integrity", + "messages": [ + { + "type": 0, + "text": "All checksums match." + } + ] + }, + { + "num": 19, + "description": "Signatures", + "messages": [ + { + "type": 0, + "text": "All signatures verified." + } + ] + }, + { + "num": 20, + "description": "Public OpenPGP Key", + "messages": [ + { + "type": 0, + "text": "1 public OpenPGP key(s) loaded." + } + ] + } +] diff --git a/testdata/processor-requirements/sha256-sha512-forbid-sha256-forbid-sha512-rolie.json b/testdata/processor-requirements/sha256-sha512-forbid-sha256-forbid-sha512-rolie.json new file mode 100644 index 0000000..03359f0 --- /dev/null +++ b/testdata/processor-requirements/sha256-sha512-forbid-sha256-forbid-sha512-rolie.json @@ -0,0 +1,214 @@ +[ + { + "num": 1, + "description": "Valid CSAF documents", + "messages": [ + { + "type": 1, + "text": "No remote validator configured" + }, + { + "type": 0, + "text": "All advisories validated fine against the schema." + } + ] + }, + { + "num": 2, + "description": "Filename", + "messages": [ + { + "type": 0, + "text": "All found filenames are conforming." + } + ] + }, + { + "num": 3, + "description": "TLS", + "messages": [ + { + "type": 0, + "text": "All tested URLs were HTTPS." + } + ] + }, + { + "num": 4, + "description": "TLP:WHITE", + "messages": [ + { + "type": 0, + "text": "All advisories labeled TLP:WHITE were freely accessible." + } + ] + }, + { + "num": 5, + "description": "TLP:AMBER and TLP:RED", + "messages": [ + { + "type": 0, + "text": "No advisories labeled TLP:AMBER or TLP:RED tested for accessibility." + } + ] + }, + { + "num": 6, + "description": "Redirects", + "messages": [ + { + "type": 0, + "text": "No redirections found." + } + ] + }, + { + "num": 7, + "description": "provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Found good provider metadata." + } + ] + }, + { + "num": 8, + "description": "security.txt", + "messages": [ + { + "type": 0, + "text": "Performed no test of security.txt since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 9, + "description": "/.well-known/csaf/provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Performed no test on whether the provider-metadata.json is available under the .well-known path since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 10, + "description": "DNS path", + "messages": [ + { + "type": 0, + "text": "Performed no test on the contents of https://csaf.data.security.DOMAIN since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 11, + "description": "One folder per year", + "messages": [ + { + "type": 2, + "text": "No year folder found in {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 12, + "description": "index.txt", + "messages": [ + { + "type": 2, + "text": "Fetching index.txt failed: {{.URL}}/index.txt not found." + } + ] + }, + { + "num": 13, + "description": "changes.csv", + "messages": [ + { + "type": 2, + "text": "Fetching changes.csv failed: {{.URL}}/changes.csv not found." + } + ] + }, + { + "num": 14, + "description": "Directory listings", + "messages": [ + { + "type": 2, + "text": "Fetching {{.URL}}/white/ failed. Status code 404 (404 Not Found)" + }, + { + "type": 2, + "text": "Not listed advisories: {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 15, + "description": "ROLIE feed", + "messages": [ + { + "type": 0, + "text": "All checked ROLIE feeds validated fine." + } + ] + }, + { + "num": 16, + "description": "ROLIE service document", + "messages": [ + { + "type": 0, + "text": "ROLIE service document validated fine." + } + ] + }, + { + "num": 17, + "description": "ROLIE category document", + "messages": [ + { + "type": 1, + "text": "Fetching {{.URL}}/white/category-white.json failed. Status code 404 (404 Not Found)" + } + ] + }, + { + "num": 18, + "description": "Integrity", + "messages": [ + { + "type": 2, + "text": "Fetching {{.URL}}/white/avendor-advisory-0004.json.sha256 failed: Status code 403 (403 Forbidden)" + }, + { + "type": 2, + "text": "Fetching {{.URL}}/white/avendor-advisory-0004.json.sha512 failed: Status code 403 (403 Forbidden)" + } + ] + }, + { + "num": 19, + "description": "Signatures", + "messages": [ + { + "type": 0, + "text": "All signatures verified." + } + ] + }, + { + "num": 20, + "description": "Public OpenPGP Key", + "messages": [ + { + "type": 0, + "text": "1 public OpenPGP key(s) loaded." + } + ] + } +] diff --git a/testdata/processor-requirements/sha256-sha512-forbid-sha256-rolie.json b/testdata/processor-requirements/sha256-sha512-forbid-sha256-rolie.json new file mode 100644 index 0000000..72a173a --- /dev/null +++ b/testdata/processor-requirements/sha256-sha512-forbid-sha256-rolie.json @@ -0,0 +1,210 @@ +[ + { + "num": 1, + "description": "Valid CSAF documents", + "messages": [ + { + "type": 1, + "text": "No remote validator configured" + }, + { + "type": 0, + "text": "All advisories validated fine against the schema." + } + ] + }, + { + "num": 2, + "description": "Filename", + "messages": [ + { + "type": 0, + "text": "All found filenames are conforming." + } + ] + }, + { + "num": 3, + "description": "TLS", + "messages": [ + { + "type": 0, + "text": "All tested URLs were HTTPS." + } + ] + }, + { + "num": 4, + "description": "TLP:WHITE", + "messages": [ + { + "type": 0, + "text": "All advisories labeled TLP:WHITE were freely accessible." + } + ] + }, + { + "num": 5, + "description": "TLP:AMBER and TLP:RED", + "messages": [ + { + "type": 0, + "text": "No advisories labeled TLP:AMBER or TLP:RED tested for accessibility." + } + ] + }, + { + "num": 6, + "description": "Redirects", + "messages": [ + { + "type": 0, + "text": "No redirections found." + } + ] + }, + { + "num": 7, + "description": "provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Found good provider metadata." + } + ] + }, + { + "num": 8, + "description": "security.txt", + "messages": [ + { + "type": 0, + "text": "Performed no test of security.txt since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 9, + "description": "/.well-known/csaf/provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Performed no test on whether the provider-metadata.json is available under the .well-known path since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 10, + "description": "DNS path", + "messages": [ + { + "type": 0, + "text": "Performed no test on the contents of https://csaf.data.security.DOMAIN since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 11, + "description": "One folder per year", + "messages": [ + { + "type": 2, + "text": "No year folder found in {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 12, + "description": "index.txt", + "messages": [ + { + "type": 2, + "text": "Fetching index.txt failed: {{.URL}}/index.txt not found." + } + ] + }, + { + "num": 13, + "description": "changes.csv", + "messages": [ + { + "type": 2, + "text": "Fetching changes.csv failed: {{.URL}}/changes.csv not found." + } + ] + }, + { + "num": 14, + "description": "Directory listings", + "messages": [ + { + "type": 2, + "text": "Fetching {{.URL}}/white/ failed. Status code 404 (404 Not Found)" + }, + { + "type": 2, + "text": "Not listed advisories: {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 15, + "description": "ROLIE feed", + "messages": [ + { + "type": 0, + "text": "All checked ROLIE feeds validated fine." + } + ] + }, + { + "num": 16, + "description": "ROLIE service document", + "messages": [ + { + "type": 0, + "text": "ROLIE service document validated fine." + } + ] + }, + { + "num": 17, + "description": "ROLIE category document", + "messages": [ + { + "type": 1, + "text": "Fetching {{.URL}}/white/category-white.json failed. Status code 404 (404 Not Found)" + } + ] + }, + { + "num": 18, + "description": "Integrity", + "messages": [ + { + "type": 1, + "text": "Fetching {{.URL}}/white/avendor-advisory-0004.json.sha256 failed: Status code 403 (403 Forbidden)" + } + ] + }, + { + "num": 19, + "description": "Signatures", + "messages": [ + { + "type": 0, + "text": "All signatures verified." + } + ] + }, + { + "num": 20, + "description": "Public OpenPGP Key", + "messages": [ + { + "type": 0, + "text": "1 public OpenPGP key(s) loaded." + } + ] + } +] diff --git a/testdata/processor-requirements/sha256-sha512-forbid-sha512-rolie.json b/testdata/processor-requirements/sha256-sha512-forbid-sha512-rolie.json new file mode 100644 index 0000000..1ab8f1e --- /dev/null +++ b/testdata/processor-requirements/sha256-sha512-forbid-sha512-rolie.json @@ -0,0 +1,210 @@ +[ + { + "num": 1, + "description": "Valid CSAF documents", + "messages": [ + { + "type": 1, + "text": "No remote validator configured" + }, + { + "type": 0, + "text": "All advisories validated fine against the schema." + } + ] + }, + { + "num": 2, + "description": "Filename", + "messages": [ + { + "type": 0, + "text": "All found filenames are conforming." + } + ] + }, + { + "num": 3, + "description": "TLS", + "messages": [ + { + "type": 0, + "text": "All tested URLs were HTTPS." + } + ] + }, + { + "num": 4, + "description": "TLP:WHITE", + "messages": [ + { + "type": 0, + "text": "All advisories labeled TLP:WHITE were freely accessible." + } + ] + }, + { + "num": 5, + "description": "TLP:AMBER and TLP:RED", + "messages": [ + { + "type": 0, + "text": "No advisories labeled TLP:AMBER or TLP:RED tested for accessibility." + } + ] + }, + { + "num": 6, + "description": "Redirects", + "messages": [ + { + "type": 0, + "text": "No redirections found." + } + ] + }, + { + "num": 7, + "description": "provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Found good provider metadata." + } + ] + }, + { + "num": 8, + "description": "security.txt", + "messages": [ + { + "type": 0, + "text": "Performed no test of security.txt since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 9, + "description": "/.well-known/csaf/provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Performed no test on whether the provider-metadata.json is available under the .well-known path since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 10, + "description": "DNS path", + "messages": [ + { + "type": 0, + "text": "Performed no test on the contents of https://csaf.data.security.DOMAIN since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 11, + "description": "One folder per year", + "messages": [ + { + "type": 2, + "text": "No year folder found in {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 12, + "description": "index.txt", + "messages": [ + { + "type": 2, + "text": "Fetching index.txt failed: {{.URL}}/index.txt not found." + } + ] + }, + { + "num": 13, + "description": "changes.csv", + "messages": [ + { + "type": 2, + "text": "Fetching changes.csv failed: {{.URL}}/changes.csv not found." + } + ] + }, + { + "num": 14, + "description": "Directory listings", + "messages": [ + { + "type": 2, + "text": "Fetching {{.URL}}/white/ failed. Status code 404 (404 Not Found)" + }, + { + "type": 2, + "text": "Not listed advisories: {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 15, + "description": "ROLIE feed", + "messages": [ + { + "type": 0, + "text": "All checked ROLIE feeds validated fine." + } + ] + }, + { + "num": 16, + "description": "ROLIE service document", + "messages": [ + { + "type": 0, + "text": "ROLIE service document validated fine." + } + ] + }, + { + "num": 17, + "description": "ROLIE category document", + "messages": [ + { + "type": 1, + "text": "Fetching {{.URL}}/white/category-white.json failed. Status code 404 (404 Not Found)" + } + ] + }, + { + "num": 18, + "description": "Integrity", + "messages": [ + { + "type": 1, + "text": "Fetching {{.URL}}/white/avendor-advisory-0004.json.sha512 failed: Status code 403 (403 Forbidden)" + } + ] + }, + { + "num": 19, + "description": "Signatures", + "messages": [ + { + "type": 0, + "text": "All signatures verified." + } + ] + }, + { + "num": 20, + "description": "Public OpenPGP Key", + "messages": [ + { + "type": 0, + "text": "1 public OpenPGP key(s) loaded." + } + ] + } +] diff --git a/testdata/processor-requirements/sha256-sha512-rolie.json b/testdata/processor-requirements/sha256-sha512-rolie.json new file mode 100644 index 0000000..5875174 --- /dev/null +++ b/testdata/processor-requirements/sha256-sha512-rolie.json @@ -0,0 +1,210 @@ +[ + { + "num": 1, + "description": "Valid CSAF documents", + "messages": [ + { + "type": 1, + "text": "No remote validator configured" + }, + { + "type": 0, + "text": "All advisories validated fine against the schema." + } + ] + }, + { + "num": 2, + "description": "Filename", + "messages": [ + { + "type": 0, + "text": "All found filenames are conforming." + } + ] + }, + { + "num": 3, + "description": "TLS", + "messages": [ + { + "type": 0, + "text": "All tested URLs were HTTPS." + } + ] + }, + { + "num": 4, + "description": "TLP:WHITE", + "messages": [ + { + "type": 0, + "text": "All advisories labeled TLP:WHITE were freely accessible." + } + ] + }, + { + "num": 5, + "description": "TLP:AMBER and TLP:RED", + "messages": [ + { + "type": 0, + "text": "No advisories labeled TLP:AMBER or TLP:RED tested for accessibility." + } + ] + }, + { + "num": 6, + "description": "Redirects", + "messages": [ + { + "type": 0, + "text": "No redirections found." + } + ] + }, + { + "num": 7, + "description": "provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Found good provider metadata." + } + ] + }, + { + "num": 8, + "description": "security.txt", + "messages": [ + { + "type": 0, + "text": "Performed no test of security.txt since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 9, + "description": "/.well-known/csaf/provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Performed no test on whether the provider-metadata.json is available under the .well-known path since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 10, + "description": "DNS path", + "messages": [ + { + "type": 0, + "text": "Performed no test on the contents of https://csaf.data.security.DOMAIN since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 11, + "description": "One folder per year", + "messages": [ + { + "type": 2, + "text": "No year folder found in {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 12, + "description": "index.txt", + "messages": [ + { + "type": 2, + "text": "Fetching index.txt failed: {{.URL}}/index.txt not found." + } + ] + }, + { + "num": 13, + "description": "changes.csv", + "messages": [ + { + "type": 2, + "text": "Fetching changes.csv failed: {{.URL}}/changes.csv not found." + } + ] + }, + { + "num": 14, + "description": "Directory listings", + "messages": [ + { + "type": 2, + "text": "Fetching {{.URL}}/white/ failed. Status code 404 (404 Not Found)" + }, + { + "type": 2, + "text": "Not listed advisories: {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 15, + "description": "ROLIE feed", + "messages": [ + { + "type": 0, + "text": "All checked ROLIE feeds validated fine." + } + ] + }, + { + "num": 16, + "description": "ROLIE service document", + "messages": [ + { + "type": 0, + "text": "ROLIE service document validated fine." + } + ] + }, + { + "num": 17, + "description": "ROLIE category document", + "messages": [ + { + "type": 1, + "text": "Fetching {{.URL}}/white/category-white.json failed. Status code 404 (404 Not Found)" + } + ] + }, + { + "num": 18, + "description": "Integrity", + "messages": [ + { + "type": 0, + "text": "All checksums match." + } + ] + }, + { + "num": 19, + "description": "Signatures", + "messages": [ + { + "type": 0, + "text": "All signatures verified." + } + ] + }, + { + "num": 20, + "description": "Public OpenPGP Key", + "messages": [ + { + "type": 0, + "text": "1 public OpenPGP key(s) loaded." + } + ] + } +] diff --git a/testdata/processor-requirements/sha512-directory.json b/testdata/processor-requirements/sha512-directory.json new file mode 100644 index 0000000..5102fab --- /dev/null +++ b/testdata/processor-requirements/sha512-directory.json @@ -0,0 +1,207 @@ +[ + { + "num": 1, + "description": "Valid CSAF documents", + "messages": [ + { + "type": 1, + "text": "No remote validator configured" + }, + { + "type": 0, + "text": "All advisories validated fine against the schema." + } + ] + }, + { + "num": 2, + "description": "Filename", + "messages": [ + { + "type": 0, + "text": "All found filenames are conforming." + } + ] + }, + { + "num": 3, + "description": "TLS", + "messages": [ + { + "type": 0, + "text": "All tested URLs were HTTPS." + } + ] + }, + { + "num": 4, + "description": "TLP:WHITE", + "messages": [ + { + "type": 0, + "text": "All advisories labeled TLP:WHITE were freely accessible." + } + ] + }, + { + "num": 5, + "description": "TLP:AMBER and TLP:RED", + "messages": [ + { + "type": 0, + "text": "No advisories labeled TLP:AMBER or TLP:RED tested for accessibility." + } + ] + }, + { + "num": 6, + "description": "Redirects", + "messages": [ + { + "type": 0, + "text": "No redirections found." + } + ] + }, + { + "num": 7, + "description": "provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Found good provider metadata." + } + ] + }, + { + "num": 8, + "description": "security.txt", + "messages": [ + { + "type": 0, + "text": "Performed no test of security.txt since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 9, + "description": "/.well-known/csaf/provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Performed no test on whether the provider-metadata.json is available under the .well-known path since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 10, + "description": "DNS path", + "messages": [ + { + "type": 0, + "text": "Performed no test on the contents of https://csaf.data.security.DOMAIN since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 11, + "description": "One folder per year", + "messages": [ + { + "type": 2, + "text": "No year folder found in {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 12, + "description": "index.txt", + "messages": [ + { + "type": 0, + "text": "Found {{.URL}}/white/index.txt" + } + ] + }, + { + "num": 13, + "description": "changes.csv", + "messages": [ + { + "type": 0, + "text": "Found {{.URL}}/white/changes.csv" + } + ] + }, + { + "num": 14, + "description": "Directory listings", + "messages": [ + { + "type": 0, + "text": "All directory listings are valid." + } + ] + }, + { + "num": 15, + "description": "ROLIE feed", + "messages": [ + { + "type": 2, + "text": "ROLIE feed based distribution was not used." + } + ] + }, + { + "num": 16, + "description": "ROLIE service document", + "messages": [ + { + "type": 1, + "text": "No ROLIE service document found." + } + ] + }, + { + "num": 17, + "description": "ROLIE category document", + "messages": [ + { + "type": 1, + "text": "No ROLIE category document found." + } + ] + }, + { + "num": 18, + "description": "Integrity", + "messages": [ + { + "type": 0, + "text": "Fetching {{.URL}}/white/avendor-advisory-0004.json.sha256 failed: Status code 404 (404 Not Found)" + } + ] + }, + { + "num": 19, + "description": "Signatures", + "messages": [ + { + "type": 0, + "text": "All signatures verified." + } + ] + }, + { + "num": 20, + "description": "Public OpenPGP Key", + "messages": [ + { + "type": 0, + "text": "1 public OpenPGP key(s) loaded." + } + ] + } +] + diff --git a/testdata/processor-requirements/sha512-rolie.json b/testdata/processor-requirements/sha512-rolie.json new file mode 100644 index 0000000..a2a195d --- /dev/null +++ b/testdata/processor-requirements/sha512-rolie.json @@ -0,0 +1,210 @@ +[ + { + "num": 1, + "description": "Valid CSAF documents", + "messages": [ + { + "type": 1, + "text": "No remote validator configured" + }, + { + "type": 0, + "text": "All advisories validated fine against the schema." + } + ] + }, + { + "num": 2, + "description": "Filename", + "messages": [ + { + "type": 0, + "text": "All found filenames are conforming." + } + ] + }, + { + "num": 3, + "description": "TLS", + "messages": [ + { + "type": 0, + "text": "All tested URLs were HTTPS." + } + ] + }, + { + "num": 4, + "description": "TLP:WHITE", + "messages": [ + { + "type": 0, + "text": "All advisories labeled TLP:WHITE were freely accessible." + } + ] + }, + { + "num": 5, + "description": "TLP:AMBER and TLP:RED", + "messages": [ + { + "type": 0, + "text": "No advisories labeled TLP:AMBER or TLP:RED tested for accessibility." + } + ] + }, + { + "num": 6, + "description": "Redirects", + "messages": [ + { + "type": 0, + "text": "No redirections found." + } + ] + }, + { + "num": 7, + "description": "provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Found good provider metadata." + } + ] + }, + { + "num": 8, + "description": "security.txt", + "messages": [ + { + "type": 0, + "text": "Performed no test of security.txt since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 9, + "description": "/.well-known/csaf/provider-metadata.json", + "messages": [ + { + "type": 0, + "text": "Performed no test on whether the provider-metadata.json is available under the .well-known path since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 10, + "description": "DNS path", + "messages": [ + { + "type": 0, + "text": "Performed no test on the contents of https://csaf.data.security.DOMAIN since the direct url of the provider-metadata.json was used." + } + ] + }, + { + "num": 11, + "description": "One folder per year", + "messages": [ + { + "type": 2, + "text": "No year folder found in {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 12, + "description": "index.txt", + "messages": [ + { + "type": 2, + "text": "Fetching index.txt failed: {{.URL}}/index.txt not found." + } + ] + }, + { + "num": 13, + "description": "changes.csv", + "messages": [ + { + "type": 2, + "text": "Fetching changes.csv failed: {{.URL}}/changes.csv not found." + } + ] + }, + { + "num": 14, + "description": "Directory listings", + "messages": [ + { + "type": 2, + "text": "Fetching {{.URL}}/white/ failed. Status code 404 (404 Not Found)" + }, + { + "type": 2, + "text": "Not listed advisories: {{.URL}}/white/avendor-advisory-0004.json" + } + ] + }, + { + "num": 15, + "description": "ROLIE feed", + "messages": [ + { + "type": 0, + "text": "{{.URL}}/white/avendor-advisory-0004.json has no sha256 hash file listed" + } + ] + }, + { + "num": 16, + "description": "ROLIE service document", + "messages": [ + { + "type": 0, + "text": "ROLIE service document validated fine." + } + ] + }, + { + "num": 17, + "description": "ROLIE category document", + "messages": [ + { + "type": 1, + "text": "Fetching {{.URL}}/white/category-white.json failed. Status code 404 (404 Not Found)" + } + ] + }, + { + "num": 18, + "description": "Integrity", + "messages": [ + { + "type": 0, + "text": "All checksums match." + } + ] + }, + { + "num": 19, + "description": "Signatures", + "messages": [ + { + "type": 0, + "text": "All signatures verified." + } + ] + }, + { + "num": 20, + "description": "Public OpenPGP Key", + "messages": [ + { + "type": 0, + "text": "1 public OpenPGP key(s) loaded." + } + ] + } +] diff --git a/testdata/simple-directory-provider/openpgp/info.txt b/testdata/simple-directory-provider/openpgp/info.txt new file mode 100644 index 0000000..3a159f6 --- /dev/null +++ b/testdata/simple-directory-provider/openpgp/info.txt @@ -0,0 +1,2 @@ +The GPG key was generated with no passphrase and this command: +`gpg --default-new-key-algo "ed25519/cert,sign+cv25519/encr" --quick-generate-key security@example.com"` diff --git a/testdata/simple-directory-provider/openpgp/privkey.asc b/testdata/simple-directory-provider/openpgp/privkey.asc new file mode 100644 index 0000000..816f309 --- /dev/null +++ b/testdata/simple-directory-provider/openpgp/privkey.asc @@ -0,0 +1,15 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lFgEZtsQNxYJKwYBBAHaRw8BAQdASr3y4zW+4XGqUlvRJ7stRCUHv8HB4ZoMoTtU +KLgnHr4AAQD5G5xy/yTN5b+lvV5Ahrbz1qOZ/wmKTieGOH9GZb6JwhHwtBRzZWN1 +cml0eUBleGFtcGxlLmNvbYiZBBMWCgBBFiEEqJFMovEROcammgAY+zzZsV3mFZYF +AmbbEDcCGwMFCQWjmoAFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQ+zzZ +sV3mFZZskQEAg5Dttqm6TA7MtLxz7VSlklx95LQr9d5jm4jcOaqlGT0A/1mAAlUq +SDySFGI6DFQLcaZaUd9Yl+1b0Icr0tUiOaQHnF0EZtsQNxIKKwYBBAGXVQEFAQEH +QOTHP4FkopIGJMWXTYsaeQ1Dugd+yNYWB357vRYq6QsiAwEIBwAA/0RIazq1s8Oe +23jvNaZGb/adDYnRrkCMXXTBKsuA6WOAEhKIeAQYFgoAIBYhBKiRTKLxETnGppoA +GPs82bFd5hWWBQJm2xA3AhsMAAoJEPs82bFd5hWWDKABAOl+NoM6FBhKAvckUXDR +MLZ4k778N4Vy9VHbectjRKj1AQCO3JOmON+U6/mjohXrc2bwzKzt2yGiLP2HMxDx +uzMXBQ== +=4XHC +-----END PGP PRIVATE KEY BLOCK----- diff --git a/testdata/simple-directory-provider/openpgp/pubkey.asc b/testdata/simple-directory-provider/openpgp/pubkey.asc new file mode 100644 index 0000000..88cb720 --- /dev/null +++ b/testdata/simple-directory-provider/openpgp/pubkey.asc @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEZtsQNxYJKwYBBAHaRw8BAQdASr3y4zW+4XGqUlvRJ7stRCUHv8HB4ZoMoTtU +KLgnHr60FHNlY3VyaXR5QGV4YW1wbGUuY29tiJkEExYKAEEWIQSokUyi8RE5xqaa +ABj7PNmxXeYVlgUCZtsQNwIbAwUJBaOagAULCQgHAgIiAgYVCgkICwIEFgIDAQIe +BwIXgAAKCRD7PNmxXeYVlmyRAQCDkO22qbpMDsy0vHPtVKWSXH3ktCv13mObiNw5 +qqUZPQD/WYACVSpIPJIUYjoMVAtxplpR31iX7VvQhyvS1SI5pAe4OARm2xA3Egor +BgEEAZdVAQUBAQdA5Mc/gWSikgYkxZdNixp5DUO6B37I1hYHfnu9FirpCyIDAQgH +iHgEGBYKACAWIQSokUyi8RE5xqaaABj7PNmxXeYVlgUCZtsQNwIbDAAKCRD7PNmx +XeYVlgygAQDpfjaDOhQYSgL3JFFw0TC2eJO+/DeFcvVR23nLY0So9QEAjtyTpjjf +lOv5o6IV63Nm8Mys7dshoiz9hzMQ8bszFwU= +=rhGT +-----END PGP PUBLIC KEY BLOCK----- diff --git a/testdata/simple-directory-provider/provider-metadata.json b/testdata/simple-directory-provider/provider-metadata.json new file mode 100644 index 0000000..792afd3 --- /dev/null +++ b/testdata/simple-directory-provider/provider-metadata.json @@ -0,0 +1,25 @@ +{ + "canonical_url": "{{.URL}}/provider-metadata.json", + "distributions": [ + { + "directory_url": "{{.URL}}/white/" + } + ], + "last_updated": "2020-01-01T00:00:00Z", + "list_on_CSAF_aggregators": true, + "metadata_version": "2.0", + "mirror_on_CSAF_aggregators": true, + "public_openpgp_keys": [ + { + "fingerprint": "A8914CA2F11139C6A69A0018FB3CD9B15DE61596", + "url": "{{.URL}}/openpgp/pubkey.asc" + } + ], + "publisher": { + "category": "vendor", + "name": "ACME Inc", + "namespace": "https://example.com", + "contact_details": "mailto:security@example.com" + }, + "role": "csaf_trusted_provider" +} diff --git a/testdata/simple-directory-provider/security.txt b/testdata/simple-directory-provider/security.txt new file mode 100644 index 0000000..0ae943d --- /dev/null +++ b/testdata/simple-directory-provider/security.txt @@ -0,0 +1,2 @@ +CSAF: /provider-metadata.json + diff --git a/testdata/simple-directory-provider/white/avendor-advisory-0004-not-listed.json b/testdata/simple-directory-provider/white/avendor-advisory-0004-not-listed.json new file mode 100644 index 0000000..0e194e9 --- /dev/null +++ b/testdata/simple-directory-provider/white/avendor-advisory-0004-not-listed.json @@ -0,0 +1,170 @@ +{ + "document": { + "category": "csaf_vex", + "csaf_version": "2.0", + "distribution": { + "tlp": { + "label": "WHITE", + "url": "https://www.first.org/tlp/v1/" + } + }, + "notes": [ + { + "category": "summary", + "title": "Test document summary", + "text": "Auto generated test CSAF document" + } + ], + "publisher": { + "category": "vendor", + "name": "ACME Inc.", + "namespace": "https://www.example.com" + }, + "title": "Test CSAF document", + "tracking": { + "current_release_date": "2020-01-01T00:00:00Z", + "generator": { + "date": "2020-01-01T00:00:00Z", + "engine": { + "name": "csaf-tool", + "version": "0.3.2" + } + }, + "id": "Avendor-advisory-0004", + "initial_release_date": "2020-01-01T00:00:00Z", + "revision_history": [ + { + "date": "2020-01-01T00:00:00Z", + "number": "1", + "summary": "Initial version" + } + ], + "status": "final", + "version": "1" + } + }, + "product_tree": { + "branches": [ + { + "category": "vendor", + "name": "AVendor", + "branches": [ + { + "category": "product_name", + "name": "product_1", + "branches": [ + { + "category": "product_version", + "name": "1.1", + "product": { + "name": "AVendor product_1 1.1", + "product_id": "CSAFPID_0001" + } + }, + { + "category": "product_version", + "name": "1.2", + "product": { + "name": "AVendor product_1 1.2", + "product_id": "CSAFPID_0002" + } + }, + { + "category": "product_version", + "name": "2.0", + "product": { + "name": "AVendor product_1 2.0", + "product_id": "CSAFPID_0003" + } + } + ] + } + ] + }, + { + "category": "vendor", + "name": "AVendor1", + "branches": [ + { + "category": "product_name", + "name": "product_2", + "branches": [ + { + "category": "product_version", + "name": "1", + "product": { + "name": "AVendor1 product_2 1", + "product_id": "CSAFPID_0004" + } + } + ] + } + ] + }, + { + "category": "vendor", + "name": "AVendor", + "branches": [ + { + "category": "product_name", + "name": "product_3", + "branches": [ + { + "category": "product_version", + "name": "2022H2", + "product": { + "name": "AVendor product_3 2022H2", + "product_id": "CSAFPID_0005" + } + } + ] + } + ] + } + ] + }, + "vulnerabilities": [ + { + "cve": "CVE-2020-1234", + "notes": [ + { + "category": "description", + "title": "CVE description", + "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-1234" + } + ], + "product_status": { + "under_investigation": ["CSAFPID_0001"] + }, + "threats": [ + { + "category": "impact", + "details": "Customers should upgrade to the latest version of the product", + "date": "2020-01-01T00:00:00Z", + "product_ids": ["CSAFPID_0001"] + } + ] + }, + { + "cve": "CVE-2020-9876", + "notes": [ + { + "category": "description", + "title": "CVE description", + "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-9876" + } + ], + "product_status": { + "under_investigation": ["CSAFPID_0001"] + }, + "threats": [ + { + "category": "impact", + "details": "Still under investigation", + "date": "2020-01-01T00:00:00Z", + "product_ids": ["CSAFPID_0001"] + } + ] + } + ] +} diff --git a/testdata/simple-directory-provider/white/avendor-advisory-0004.json b/testdata/simple-directory-provider/white/avendor-advisory-0004.json new file mode 100644 index 0000000..0e194e9 --- /dev/null +++ b/testdata/simple-directory-provider/white/avendor-advisory-0004.json @@ -0,0 +1,170 @@ +{ + "document": { + "category": "csaf_vex", + "csaf_version": "2.0", + "distribution": { + "tlp": { + "label": "WHITE", + "url": "https://www.first.org/tlp/v1/" + } + }, + "notes": [ + { + "category": "summary", + "title": "Test document summary", + "text": "Auto generated test CSAF document" + } + ], + "publisher": { + "category": "vendor", + "name": "ACME Inc.", + "namespace": "https://www.example.com" + }, + "title": "Test CSAF document", + "tracking": { + "current_release_date": "2020-01-01T00:00:00Z", + "generator": { + "date": "2020-01-01T00:00:00Z", + "engine": { + "name": "csaf-tool", + "version": "0.3.2" + } + }, + "id": "Avendor-advisory-0004", + "initial_release_date": "2020-01-01T00:00:00Z", + "revision_history": [ + { + "date": "2020-01-01T00:00:00Z", + "number": "1", + "summary": "Initial version" + } + ], + "status": "final", + "version": "1" + } + }, + "product_tree": { + "branches": [ + { + "category": "vendor", + "name": "AVendor", + "branches": [ + { + "category": "product_name", + "name": "product_1", + "branches": [ + { + "category": "product_version", + "name": "1.1", + "product": { + "name": "AVendor product_1 1.1", + "product_id": "CSAFPID_0001" + } + }, + { + "category": "product_version", + "name": "1.2", + "product": { + "name": "AVendor product_1 1.2", + "product_id": "CSAFPID_0002" + } + }, + { + "category": "product_version", + "name": "2.0", + "product": { + "name": "AVendor product_1 2.0", + "product_id": "CSAFPID_0003" + } + } + ] + } + ] + }, + { + "category": "vendor", + "name": "AVendor1", + "branches": [ + { + "category": "product_name", + "name": "product_2", + "branches": [ + { + "category": "product_version", + "name": "1", + "product": { + "name": "AVendor1 product_2 1", + "product_id": "CSAFPID_0004" + } + } + ] + } + ] + }, + { + "category": "vendor", + "name": "AVendor", + "branches": [ + { + "category": "product_name", + "name": "product_3", + "branches": [ + { + "category": "product_version", + "name": "2022H2", + "product": { + "name": "AVendor product_3 2022H2", + "product_id": "CSAFPID_0005" + } + } + ] + } + ] + } + ] + }, + "vulnerabilities": [ + { + "cve": "CVE-2020-1234", + "notes": [ + { + "category": "description", + "title": "CVE description", + "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-1234" + } + ], + "product_status": { + "under_investigation": ["CSAFPID_0001"] + }, + "threats": [ + { + "category": "impact", + "details": "Customers should upgrade to the latest version of the product", + "date": "2020-01-01T00:00:00Z", + "product_ids": ["CSAFPID_0001"] + } + ] + }, + { + "cve": "CVE-2020-9876", + "notes": [ + { + "category": "description", + "title": "CVE description", + "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-9876" + } + ], + "product_status": { + "under_investigation": ["CSAFPID_0001"] + }, + "threats": [ + { + "category": "impact", + "details": "Still under investigation", + "date": "2020-01-01T00:00:00Z", + "product_ids": ["CSAFPID_0001"] + } + ] + } + ] +} diff --git a/testdata/simple-directory-provider/white/avendor-advisory-0004.json.asc b/testdata/simple-directory-provider/white/avendor-advisory-0004.json.asc new file mode 100644 index 0000000..9dff47b --- /dev/null +++ b/testdata/simple-directory-provider/white/avendor-advisory-0004.json.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- + +iHUEABYKAB0WIQSokUyi8RE5xqaaABj7PNmxXeYVlgUCZukv9QAKCRD7PNmxXeYV +ljq0AP9n/rTgoNCJzSTZzNrrMy28ZR+Ppp1MSPWGFUzsx6qLJgD/d8cu0lokMsXf +y0uc9k7hrla/ajFUzNt3AVvT+CPFtAo= +=7E66 +-----END PGP SIGNATURE----- diff --git a/testdata/simple-directory-provider/white/avendor-advisory-0004.json.sha256 b/testdata/simple-directory-provider/white/avendor-advisory-0004.json.sha256 new file mode 100644 index 0000000..851b27c --- /dev/null +++ b/testdata/simple-directory-provider/white/avendor-advisory-0004.json.sha256 @@ -0,0 +1 @@ +cb263bf1beab18b893de63f2966d0d8c5f38d60101c24d3fd7a5feebaad02c3b avendor-advisory-0004.json diff --git a/testdata/simple-directory-provider/white/avendor-advisory-0004.json.sha512 b/testdata/simple-directory-provider/white/avendor-advisory-0004.json.sha512 new file mode 100644 index 0000000..6703550 --- /dev/null +++ b/testdata/simple-directory-provider/white/avendor-advisory-0004.json.sha512 @@ -0,0 +1 @@ +39476e1d08a0871d166091c90de259544382a3599eebda118a93468499a30fd034286086c461a97d3d5298e093b0be3868e8d89d8a6a255c4aa6adb81ebbfcad avendor-advisory-0004.json diff --git a/testdata/simple-directory-provider/white/changes.csv b/testdata/simple-directory-provider/white/changes.csv new file mode 100644 index 0000000..4acdb29 --- /dev/null +++ b/testdata/simple-directory-provider/white/changes.csv @@ -0,0 +1 @@ +"avendor-advisory-0004.json","2020-01-01T00:00:00+00:00" diff --git a/testdata/simple-directory-provider/white/index.html b/testdata/simple-directory-provider/white/index.html new file mode 100644 index 0000000..bcfabd9 --- /dev/null +++ b/testdata/simple-directory-provider/white/index.html @@ -0,0 +1,6 @@ + + + + avendor-advisory-0004 + + diff --git a/testdata/simple-directory-provider/white/index.txt b/testdata/simple-directory-provider/white/index.txt new file mode 100644 index 0000000..d19d30f --- /dev/null +++ b/testdata/simple-directory-provider/white/index.txt @@ -0,0 +1 @@ +avendor-advisory-0004.json diff --git a/testdata/simple-rolie-provider/openpgp/info.txt b/testdata/simple-rolie-provider/openpgp/info.txt new file mode 100644 index 0000000..3a159f6 --- /dev/null +++ b/testdata/simple-rolie-provider/openpgp/info.txt @@ -0,0 +1,2 @@ +The GPG key was generated with no passphrase and this command: +`gpg --default-new-key-algo "ed25519/cert,sign+cv25519/encr" --quick-generate-key security@example.com"` diff --git a/testdata/simple-rolie-provider/openpgp/privkey.asc b/testdata/simple-rolie-provider/openpgp/privkey.asc new file mode 100644 index 0000000..816f309 --- /dev/null +++ b/testdata/simple-rolie-provider/openpgp/privkey.asc @@ -0,0 +1,15 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lFgEZtsQNxYJKwYBBAHaRw8BAQdASr3y4zW+4XGqUlvRJ7stRCUHv8HB4ZoMoTtU +KLgnHr4AAQD5G5xy/yTN5b+lvV5Ahrbz1qOZ/wmKTieGOH9GZb6JwhHwtBRzZWN1 +cml0eUBleGFtcGxlLmNvbYiZBBMWCgBBFiEEqJFMovEROcammgAY+zzZsV3mFZYF +AmbbEDcCGwMFCQWjmoAFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQ+zzZ +sV3mFZZskQEAg5Dttqm6TA7MtLxz7VSlklx95LQr9d5jm4jcOaqlGT0A/1mAAlUq +SDySFGI6DFQLcaZaUd9Yl+1b0Icr0tUiOaQHnF0EZtsQNxIKKwYBBAGXVQEFAQEH +QOTHP4FkopIGJMWXTYsaeQ1Dugd+yNYWB357vRYq6QsiAwEIBwAA/0RIazq1s8Oe +23jvNaZGb/adDYnRrkCMXXTBKsuA6WOAEhKIeAQYFgoAIBYhBKiRTKLxETnGppoA +GPs82bFd5hWWBQJm2xA3AhsMAAoJEPs82bFd5hWWDKABAOl+NoM6FBhKAvckUXDR +MLZ4k778N4Vy9VHbectjRKj1AQCO3JOmON+U6/mjohXrc2bwzKzt2yGiLP2HMxDx +uzMXBQ== +=4XHC +-----END PGP PRIVATE KEY BLOCK----- diff --git a/testdata/simple-rolie-provider/openpgp/pubkey.asc b/testdata/simple-rolie-provider/openpgp/pubkey.asc new file mode 100644 index 0000000..88cb720 --- /dev/null +++ b/testdata/simple-rolie-provider/openpgp/pubkey.asc @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEZtsQNxYJKwYBBAHaRw8BAQdASr3y4zW+4XGqUlvRJ7stRCUHv8HB4ZoMoTtU +KLgnHr60FHNlY3VyaXR5QGV4YW1wbGUuY29tiJkEExYKAEEWIQSokUyi8RE5xqaa +ABj7PNmxXeYVlgUCZtsQNwIbAwUJBaOagAULCQgHAgIiAgYVCgkICwIEFgIDAQIe +BwIXgAAKCRD7PNmxXeYVlmyRAQCDkO22qbpMDsy0vHPtVKWSXH3ktCv13mObiNw5 +qqUZPQD/WYACVSpIPJIUYjoMVAtxplpR31iX7VvQhyvS1SI5pAe4OARm2xA3Egor +BgEEAZdVAQUBAQdA5Mc/gWSikgYkxZdNixp5DUO6B37I1hYHfnu9FirpCyIDAQgH +iHgEGBYKACAWIQSokUyi8RE5xqaaABj7PNmxXeYVlgUCZtsQNwIbDAAKCRD7PNmx +XeYVlgygAQDpfjaDOhQYSgL3JFFw0TC2eJO+/DeFcvVR23nLY0So9QEAjtyTpjjf +lOv5o6IV63Nm8Mys7dshoiz9hzMQ8bszFwU= +=rhGT +-----END PGP PUBLIC KEY BLOCK----- diff --git a/testdata/simple-rolie-provider/provider-metadata.json b/testdata/simple-rolie-provider/provider-metadata.json new file mode 100644 index 0000000..7abb316 --- /dev/null +++ b/testdata/simple-rolie-provider/provider-metadata.json @@ -0,0 +1,33 @@ +{ + "canonical_url": "{{.URL}}/provider-metadata.json", + "distributions": [ + { + "rolie": { + "feeds": [ + { + "summary": "TLP:WHITE advisories", + "tlp_label": "WHITE", + "url": "{{.URL}}/white/white-feed.json" + } + ] + } + } + ], + "last_updated": "2020-01-01T00:00:00Z", + "list_on_CSAF_aggregators": true, + "metadata_version": "2.0", + "mirror_on_CSAF_aggregators": true, + "public_openpgp_keys": [ + { + "fingerprint": "A8914CA2F11139C6A69A0018FB3CD9B15DE61596", + "url": "{{.URL}}/openpgp/pubkey.asc" + } + ], + "publisher": { + "category": "vendor", + "name": "ACME Inc", + "namespace": "https://example.com", + "contact_details": "mailto:security@example.com" + }, + "role": "csaf_trusted_provider" +} diff --git a/testdata/simple-rolie-provider/security.txt b/testdata/simple-rolie-provider/security.txt new file mode 100644 index 0000000..0ae943d --- /dev/null +++ b/testdata/simple-rolie-provider/security.txt @@ -0,0 +1,2 @@ +CSAF: /provider-metadata.json + diff --git a/testdata/simple-rolie-provider/service.json b/testdata/simple-rolie-provider/service.json new file mode 100644 index 0000000..a398a40 --- /dev/null +++ b/testdata/simple-rolie-provider/service.json @@ -0,0 +1,23 @@ +{ + "service": { + "workspace": [ + { + "title": "CSAF feeds", + "collection": [ + { + "title": "CSAF feed (TLP:WHITE)", + "href": "{{.URL}}/white/white-feed.json", + "categories": { + "category": [ + { + "scheme": "urn:ietf:params:rolie:category:information-type", + "term": "csaf" + } + ] + } + } + ] + } + ] + } +} diff --git a/testdata/simple-rolie-provider/white/avendor-advisory-0004.json b/testdata/simple-rolie-provider/white/avendor-advisory-0004.json new file mode 100644 index 0000000..0e194e9 --- /dev/null +++ b/testdata/simple-rolie-provider/white/avendor-advisory-0004.json @@ -0,0 +1,170 @@ +{ + "document": { + "category": "csaf_vex", + "csaf_version": "2.0", + "distribution": { + "tlp": { + "label": "WHITE", + "url": "https://www.first.org/tlp/v1/" + } + }, + "notes": [ + { + "category": "summary", + "title": "Test document summary", + "text": "Auto generated test CSAF document" + } + ], + "publisher": { + "category": "vendor", + "name": "ACME Inc.", + "namespace": "https://www.example.com" + }, + "title": "Test CSAF document", + "tracking": { + "current_release_date": "2020-01-01T00:00:00Z", + "generator": { + "date": "2020-01-01T00:00:00Z", + "engine": { + "name": "csaf-tool", + "version": "0.3.2" + } + }, + "id": "Avendor-advisory-0004", + "initial_release_date": "2020-01-01T00:00:00Z", + "revision_history": [ + { + "date": "2020-01-01T00:00:00Z", + "number": "1", + "summary": "Initial version" + } + ], + "status": "final", + "version": "1" + } + }, + "product_tree": { + "branches": [ + { + "category": "vendor", + "name": "AVendor", + "branches": [ + { + "category": "product_name", + "name": "product_1", + "branches": [ + { + "category": "product_version", + "name": "1.1", + "product": { + "name": "AVendor product_1 1.1", + "product_id": "CSAFPID_0001" + } + }, + { + "category": "product_version", + "name": "1.2", + "product": { + "name": "AVendor product_1 1.2", + "product_id": "CSAFPID_0002" + } + }, + { + "category": "product_version", + "name": "2.0", + "product": { + "name": "AVendor product_1 2.0", + "product_id": "CSAFPID_0003" + } + } + ] + } + ] + }, + { + "category": "vendor", + "name": "AVendor1", + "branches": [ + { + "category": "product_name", + "name": "product_2", + "branches": [ + { + "category": "product_version", + "name": "1", + "product": { + "name": "AVendor1 product_2 1", + "product_id": "CSAFPID_0004" + } + } + ] + } + ] + }, + { + "category": "vendor", + "name": "AVendor", + "branches": [ + { + "category": "product_name", + "name": "product_3", + "branches": [ + { + "category": "product_version", + "name": "2022H2", + "product": { + "name": "AVendor product_3 2022H2", + "product_id": "CSAFPID_0005" + } + } + ] + } + ] + } + ] + }, + "vulnerabilities": [ + { + "cve": "CVE-2020-1234", + "notes": [ + { + "category": "description", + "title": "CVE description", + "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-1234" + } + ], + "product_status": { + "under_investigation": ["CSAFPID_0001"] + }, + "threats": [ + { + "category": "impact", + "details": "Customers should upgrade to the latest version of the product", + "date": "2020-01-01T00:00:00Z", + "product_ids": ["CSAFPID_0001"] + } + ] + }, + { + "cve": "CVE-2020-9876", + "notes": [ + { + "category": "description", + "title": "CVE description", + "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-9876" + } + ], + "product_status": { + "under_investigation": ["CSAFPID_0001"] + }, + "threats": [ + { + "category": "impact", + "details": "Still under investigation", + "date": "2020-01-01T00:00:00Z", + "product_ids": ["CSAFPID_0001"] + } + ] + } + ] +} diff --git a/testdata/simple-rolie-provider/white/avendor-advisory-0004.json.asc b/testdata/simple-rolie-provider/white/avendor-advisory-0004.json.asc new file mode 100644 index 0000000..9dff47b --- /dev/null +++ b/testdata/simple-rolie-provider/white/avendor-advisory-0004.json.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- + +iHUEABYKAB0WIQSokUyi8RE5xqaaABj7PNmxXeYVlgUCZukv9QAKCRD7PNmxXeYV +ljq0AP9n/rTgoNCJzSTZzNrrMy28ZR+Ppp1MSPWGFUzsx6qLJgD/d8cu0lokMsXf +y0uc9k7hrla/ajFUzNt3AVvT+CPFtAo= +=7E66 +-----END PGP SIGNATURE----- diff --git a/testdata/simple-rolie-provider/white/avendor-advisory-0004.json.sha256 b/testdata/simple-rolie-provider/white/avendor-advisory-0004.json.sha256 new file mode 100644 index 0000000..851b27c --- /dev/null +++ b/testdata/simple-rolie-provider/white/avendor-advisory-0004.json.sha256 @@ -0,0 +1 @@ +cb263bf1beab18b893de63f2966d0d8c5f38d60101c24d3fd7a5feebaad02c3b avendor-advisory-0004.json diff --git a/testdata/simple-rolie-provider/white/avendor-advisory-0004.json.sha512 b/testdata/simple-rolie-provider/white/avendor-advisory-0004.json.sha512 new file mode 100644 index 0000000..6703550 --- /dev/null +++ b/testdata/simple-rolie-provider/white/avendor-advisory-0004.json.sha512 @@ -0,0 +1 @@ +39476e1d08a0871d166091c90de259544382a3599eebda118a93468499a30fd034286086c461a97d3d5298e093b0be3868e8d89d8a6a255c4aa6adb81ebbfcad avendor-advisory-0004.json diff --git a/testdata/simple-rolie-provider/white/white-feed.json b/testdata/simple-rolie-provider/white/white-feed.json new file mode 100644 index 0000000..923a492 --- /dev/null +++ b/testdata/simple-rolie-provider/white/white-feed.json @@ -0,0 +1,61 @@ +{ + "feed": { + "id": "csaf-feed-tlp-white", + "title": "CSAF feed (TLP:WHITE)", + "link": [ + { + "rel": "self", + "href": "{{.URL}}/white/csaf-feed-tlp-white.json" + }, + { + "rel": "service", + "href": "{{.URL}}/service.json" + } + ], + "category": [ + { + "scheme": "urn:ietf:params:rolie:category:information-type", + "term": "csaf" + } + ], + "updated": "2020-01-01T00:00:00Z", + "entry": [ + { + "id": "Avendor-advisory-0004", + "title": "Test CSAF document", + "link": [ + { + "rel": "self", + "href": "{{.URL}}/white/avendor-advisory-0004.json" + }, + {{if .EnableSha256}} + { + "rel": "hash", + "href": "{{.URL}}/white/avendor-advisory-0004.json.sha256" + }, + {{end}} + {{if .EnableSha512}} + { + "rel": "hash", + "href": "{{.URL}}/white/avendor-advisory-0004.json.sha512" + }, + {{end}} + { + "rel": "signature", + "href": "{{.URL}}/white/avendor-advisory-0004.json.asc" + } + ], + "published": "2020-01-01T00:00:00Z", + "updated": "2020-01-01T00:00:00Z", + "content": { + "type": "application/json", + "src": "{{.URL}}/avendor-advisory-0004.json" + }, + "format": { + "schema": "https://docs.oasis-open.org/csaf/csaf/v2.0/csaf_json_schema.json", + "version": "2.0" + } + } + ] + } +}