diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 3e8d403..47978d6 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -43,6 +43,7 @@ type processor struct { redirects map[string]string noneTLS map[string]struct{} alreadyChecked map[string]whereType + pmdURL string pmd256 []byte pmd interface{} keys []*crypto.KeyRing @@ -119,6 +120,7 @@ func (p *processor) clean() { for k := range p.alreadyChecked { delete(p.alreadyChecked, k) } + p.pmdURL = "" p.pmd256 = nil p.pmd = nil p.keys = nil @@ -131,6 +133,7 @@ func (p *processor) clean() { p.badIndices = nil p.badChanges = nil } + func (p *processor) run(reporters []reporter, domains []string) (*Report, error) { var report Report @@ -607,7 +610,7 @@ func (p *processor) checkChanges(base string, mask whereType) error { func (p *processor) processROLIEFeeds(domain string, feeds [][]csaf.Feed) error { - base, err := url.Parse("https://" + domain + "/.well-known/csaf/") + base, err := url.Parse(p.pmdURL) if err != nil { return err } @@ -654,7 +657,10 @@ func (p *processor) checkCSAFs(domain string) error { } // No rolie feeds - base := "https://" + domain + "/.well-known/csaf" + base, err := basePath(p.pmdURL) + if err != nil { + return err + } if err := p.checkIndex(base, indexMask); err != nil && err != errContinue { return err @@ -701,49 +707,91 @@ func (p *processor) checkMissing(string) error { return nil } -func (p *processor) checkProviderMetadata(domain string) error { +var providerMetadataLocations = [...]string{ + ".well-known/csaf", + "security/data/csaf", + "advisories/csaf", + "security/csaf", +} + +// locateProviderMetadata searches for provider-metadata.json at various +// locations mentioned in "7.1.7 Requirement 7: provider-metadata.json". +func (p *processor) locateProviderMetadata( + domain string, + found func(string, io.Reader) error, +) error { client := p.httpClient() - url := "https://" + domain + "/.well-known/csaf/provider-metadata.json" + for _, loc := range providerMetadataLocations { + url := "https://" + domain + "/" + loc + res, err := client.Get(url) + if err != nil { + continue + } + if res.StatusCode != http.StatusOK { + continue + } + if res.Header.Get("Content-Type") != "application/json" { + continue + } + + if err := func() error { + defer res.Body.Close() + return found(url, res.Body) + }(); err != nil { + if err == errContinue { + continue + } + return err + } + break + } + + // TODO: Read from security.txt + + return nil +} + +func (p *processor) checkProviderMetadata(domain string) error { use(&p.badProviderMetadatas) - res, err := client.Get(url) - if err != nil { - p.badProviderMetadata("Fetching %s: %v.", url, err) - return errStop + found := func(url string, content io.Reader) error { + + // Calculate checksum for later comparison. + hash := sha256.New() + + tee := io.TeeReader(content, hash) + if err := json.NewDecoder(tee).Decode(&p.pmd); err != nil { + p.badProviderMetadata("%s: Decoding JSON failed: %v", url, err) + return errContinue + } + + p.pmd256 = hash.Sum(nil) + + errors, err := csaf.ValidateProviderMetadata(p.pmd) + if err != nil { + return err + } + if len(errors) > 0 { + p.badProviderMetadata("%s: Validating against JSON schema failed:", url) + for _, msg := range errors { + p.badProviderMetadata(strings.ReplaceAll(msg, `%`, `%%`)) + } + return errStop + } + p.pmdURL = url + return nil } - if res.StatusCode != http.StatusOK { - p.badProviderMetadata("Fetching %s failed. Status code: %d (%s)", - url, res.StatusCode, res.Status) - return errStop - } - - // Calculate checksum for later comparison. - hash := sha256.New() - - if err := func() error { - defer res.Body.Close() - tee := io.TeeReader(res.Body, hash) - return json.NewDecoder(tee).Decode(&p.pmd) - }(); err != nil { - p.badProviderMetadata("Decoding JSON failed: %v", err) - return errStop - } - - p.pmd256 = hash.Sum(nil) - - errors, err := csaf.ValidateProviderMetadata(p.pmd) - if err != nil { + if err := p.locateProviderMetadata(domain, found); err != nil { return err } - if len(errors) > 0 { - p.badProviderMetadata("Validating against JSON schema failed:") - for _, msg := range errors { - p.badProviderMetadata(strings.ReplaceAll(msg, `%`, `%%`)) - } + + if p.pmdURL == "" { + p.badProviderMetadata("No provider-metadata.json found.") + return errStop } return nil } @@ -851,7 +899,7 @@ func (p *processor) checkPGPKeys(domain string) error { client := p.httpClient() - base, err := url.Parse("https://" + domain + "/.well-known/csaf/provider-metadata.json") + base, err := url.Parse(p.pmdURL) if err != nil { return err }