From 2961a70bf23c3b02c9a5d8a3342e5269d254a139 Mon Sep 17 00:00:00 2001 From: "Bernhard E. Reiter" Date: Tue, 14 Jun 2022 13:18:42 +0200 Subject: [PATCH 1/5] Change OpenPGP signatures to be without headers (#183) * Change options when creating the armored version of the signature to leave out the optional headers, which would be `Version:` and `Comment:`, as it is considered uncommon for a while now to set these. --- cmd/csaf_aggregator/mirror.go | 5 ++++- cmd/csaf_provider/actions.go | 5 ++++- cmd/csaf_uploader/main.go | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cmd/csaf_aggregator/mirror.go b/cmd/csaf_aggregator/mirror.go index bc29e64..37067c2 100644 --- a/cmd/csaf_aggregator/mirror.go +++ b/cmd/csaf_aggregator/mirror.go @@ -26,6 +26,8 @@ import ( "strings" "time" + "github.com/ProtonMail/gopenpgp/v2/armor" + "github.com/ProtonMail/gopenpgp/v2/constants" "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/csaf-poc/csaf_distribution/csaf" "github.com/csaf-poc/csaf_distribution/util" @@ -490,7 +492,8 @@ func (w *worker) sign(data []byte) (string, error) { if err != nil { return "", err } - return sig.GetArmored() + return armor.ArmorWithTypeAndCustomHeaders( + sig.Data, constants.PGPSignatureHeader, "", "") } func (w *worker) mirrorFiles(tlpLabel *csaf.TLPLabel, files []string) error { diff --git a/cmd/csaf_provider/actions.go b/cmd/csaf_provider/actions.go index 1ad193c..d4bac45 100644 --- a/cmd/csaf_provider/actions.go +++ b/cmd/csaf_provider/actions.go @@ -21,6 +21,8 @@ import ( "strings" "time" + "github.com/ProtonMail/gopenpgp/v2/armor" + "github.com/ProtonMail/gopenpgp/v2/constants" "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/csaf-poc/csaf_distribution/csaf" "github.com/csaf-poc/csaf_distribution/util" @@ -112,7 +114,8 @@ func (c *controller) handleSignature( return "", nil, err } - armored, err := sig.GetArmored() + armored, err := armor.ArmorWithTypeAndCustomHeaders( + sig.Data, constants.PGPSignatureHeader, "", "") return armored, key, err } diff --git a/cmd/csaf_uploader/main.go b/cmd/csaf_uploader/main.go index 9007f92..e392471 100644 --- a/cmd/csaf_uploader/main.go +++ b/cmd/csaf_uploader/main.go @@ -21,6 +21,8 @@ import ( "os" "path/filepath" + "github.com/ProtonMail/gopenpgp/v2/armor" + "github.com/ProtonMail/gopenpgp/v2/constants" "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/csaf-poc/csaf_distribution/csaf" "github.com/csaf-poc/csaf_distribution/util" @@ -239,7 +241,8 @@ func (p *processor) uploadRequest(filename string) (*http.Request, error) { if err != nil { return nil, err } - armored, err := sig.GetArmored() + armored, err := armor.ArmorWithTypeAndCustomHeaders( + sig.Data, constants.PGPSignatureHeader, "", "") if err != nil { return nil, err } From 1e9d31277d74cead314bd2e50733397db7828f31 Mon Sep 17 00:00:00 2001 From: "Bernhard E. Reiter" Date: Tue, 14 Jun 2022 13:19:30 +0200 Subject: [PATCH 2/5] Improve nginx setup docs (#182) * Change nginx config to return 403 on unauthorized access to the non-white TLP locations. We cannot hide the existence anyway, as it is listed in the provider-metadata.json, even when restricted. --- docs/client-certificate-setup.md | 2 +- docs/scripts/TLSClientConfigsForITest.sh | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/client-certificate-setup.md b/docs/client-certificate-setup.md index 0867118..3d0a3f5 100644 --- a/docs/client-certificate-setup.md +++ b/docs/client-certificate-setup.md @@ -6,7 +6,7 @@ a web browser. ### Configure nginx Assuming the relevant server block is in `/etc/nginx/sites-enabled/default` and the CA used to verify the client certificates is under `/etc/ssl/`, adjust the content of the `server{}` block like shown in the following example: - + ```sh ssl_client_certificate '${SSL_CLIENT_CERTIFICATE}'; # e.g. ssl_client_certificate /etc/ssl/rootca-cert.pem; diff --git a/docs/scripts/TLSClientConfigsForITest.sh b/docs/scripts/TLSClientConfigsForITest.sh index 348e4a3..3d11c3e 100755 --- a/docs/scripts/TLSClientConfigsForITest.sh +++ b/docs/scripts/TLSClientConfigsForITest.sh @@ -33,9 +33,7 @@ echo ' autoindex on; # in this location access is only allowed with client certs if ($ssl_client_verify != SUCCESS){ - # we use status code 404 == "Not Found", because we do - # not want to reveal if files within this location exist or not. - return 404; + return 403; } } '> ~/${FOLDERNAME}/clientCertificateConfigs.txt From 86a6f9abde8277ce5b82d54bcd86fa65f3eb1b11 Mon Sep 17 00:00:00 2001 From: "s-l-teichmann s-l-teichmann@users.noreply.github.com" Date: Tue, 14 Jun 2022 11:20:09 +0000 Subject: [PATCH 3/5] Apply automatic changes --- docs/client-certificate-setup.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/client-certificate-setup.md b/docs/client-certificate-setup.md index 3d0a3f5..d72eb68 100644 --- a/docs/client-certificate-setup.md +++ b/docs/client-certificate-setup.md @@ -20,9 +20,7 @@ adjust the content of the `server{}` block like shown in the following example: autoindex on; # in this location access is only allowed with client certs if ($ssl_client_verify != SUCCESS){ - # we use status code 404 == "Not Found", because we do - # not want to reveal if files within this location exist or not. - return 404; + return 403; } } ``` From fa434fa039013ba80ab044e476d1c9f9f6171e9d Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Tue, 14 Jun 2022 13:41:51 +0200 Subject: [PATCH 4/5] Improve checker regarding ROLIE feed advisory URLs, hashes and signatures * Add checking the ROLIE feed advisory URLs, hashes and signatures. --- cmd/csaf_checker/processor.go | 128 ++++++++++++++++++++++++++++++---- csaf/rolie.go | 7 ++ 2 files changed, 121 insertions(+), 14 deletions(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 98cf5f7..585b58e 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -329,8 +329,44 @@ func (p *processor) httpClient() util.Client { var yearFromURL = regexp.MustCompile(`.*/(\d{4})/[^/]+$`) +// checkFile constructs the urls of a remote file. +type checkFile interface { + url() string + sha256() string + sha512() string + sign() string +} + +// stringFile is a simple implementation of checkFile. +// The hash and signature files are directly constructed by extending +// the file name. +type stringFile string + +func (sf stringFile) url() string { return string(sf) } +func (sf stringFile) sha256() string { return string(sf) + ".sha256" } +func (sf stringFile) sha512() string { return string(sf) + ".sha512" } +func (sf stringFile) sign() string { return string(sf) + ".asc" } + +// hashFile 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 hashFile [4]string + +func (hf hashFile) name(i int, ext string) string { + if hf[i] != "" { + return hf[i] + } + return hf[0] + ext +} + +func (hf hashFile) url() string { return hf[0] } +func (hf hashFile) sha256() string { return hf.name(1, ".sha256") } +func (hf hashFile) sha512() string { return hf.name(2, ".sha512") } +func (hf hashFile) sign() string { return hf.name(3, ".asc") } + func (p *processor) integrity( - files []string, + files []checkFile, base string, mask whereType, lg func(MessageType, string, ...interface{}), @@ -344,7 +380,7 @@ func (p *processor) integrity( var data bytes.Buffer for _, f := range files { - fp, err := url.Parse(f) + fp, err := url.Parse(f.url()) if err != nil { lg(ErrorType, "Bad URL %s: %v", f, err) continue @@ -413,12 +449,18 @@ func (p *processor) integrity( for _, x := range []struct { ext string + url func() string hash []byte }{ - {"sha256", s256.Sum(nil)}, - {"sha512", s512.Sum(nil)}, + {"SHA256", f.sha256, s256.Sum(nil)}, + {"SHA512", f.sha512, s512.Sum(nil)}, } { - hashFile := u + "." + x.ext + hu, err := url.Parse(x.url()) + if err != nil { + lg(ErrorType, "Bad URL %s: %v", x.url(), err) + continue + } + hashFile := b.ResolveReference(hu).String() p.checkTLS(hashFile) if res, err = client.Get(hashFile); err != nil { p.badIntegrities.error("Fetching %s failed: %v.", hashFile, err) @@ -443,12 +485,17 @@ func (p *processor) integrity( } if !bytes.Equal(h, x.hash) { p.badIntegrities.error("%s hash of %s does not match %s.", - strings.ToUpper(x.ext), u, hashFile) + x.ext, u, hashFile) } } // Check signature - sigFile := u + ".asc" + su, err := url.Parse(f.sign()) + if err != nil { + lg(ErrorType, "Bad URL %s: %v", f.sign(), err) + continue + } + sigFile := b.ResolveReference(su).String() p.checkTLS(sigFile) p.badSignatures.use() @@ -545,7 +592,60 @@ func (p *processor) processROLIEFeed(feed string) error { } // Extract the CSAF files from feed. - files := rfeed.Files("self") + var files []checkFile + + rfeed.Entries(func(entry *csaf.Entry) { + + var url, sha256, sha512, sign string + + for i := range entry.Link { + link := &entry.Link[i] + lower := strings.ToLower(link.HRef) + switch link.Rel { + case "self": + if !strings.HasSuffix(lower, ".json") { + p.badProviderMetadata.warn( + `ROLIE feed entry link %s in %s with "rel": "self" has unexpected file extension.`, + link.HRef, feed) + } + url = link.HRef + case "signature": + if !strings.HasSuffix(lower, ".asc") { + p.badProviderMetadata.warn( + `ROLIE feed entry link %s in %s with "rel": "signature" has unexpected file extension.`, + link.HRef, feed) + } + sign = link.HRef + case "hash": + switch { + case strings.HasSuffix(lower, "sha256"): + sha256 = link.HRef + case strings.HasSuffix(lower, "sha512"): + sha512 = link.HRef + default: + p.badProviderMetadata.warn( + `ROLIE feed entry link %s in %s with "rel": "hash" has unsupported file extension.`, + link.HRef, feed) + } + } + } + + if url == "" { + p.badProviderMetadata.warn( + `ROLIE feed %s contains entry link with no "self" URL.`, feed) + return + } + + var file checkFile + + if sha256 != "" || sha512 != "" || sign != "" { + file = hashFile{url, sha256, sha512, sign} + } else { + file = stringFile(url) + } + + files = append(files, file) + }) if err := p.integrity(files, base, rolieMask, p.badProviderMetadata.add); err != nil && err != errContinue { @@ -588,12 +688,12 @@ func (p *processor) checkIndex(base string, mask whereType) error { return errContinue } - files, err := func() ([]string, error) { + files, err := func() ([]checkFile, error) { defer res.Body.Close() - var files []string + var files []checkFile scanner := bufio.NewScanner(res.Body) for scanner.Scan() { - files = append(files, scanner.Text()) + files = append(files, stringFile(scanner.Text())) } return files, scanner.Err() }() @@ -630,10 +730,10 @@ func (p *processor) checkChanges(base string, mask whereType) error { return errContinue } - times, files, err := func() ([]time.Time, []string, error) { + times, files, err := func() ([]time.Time, []checkFile, error) { defer res.Body.Close() var times []time.Time - var files []string + var files []checkFile c := csv.NewReader(res.Body) for { r, err := c.Read() @@ -650,7 +750,7 @@ func (p *processor) checkChanges(base string, mask whereType) error { if err != nil { return nil, nil, err } - times, files = append(times, t), append(files, r[1]) + times, files = append(times, t), append(files, stringFile(r[1])) } return times, files, nil }() diff --git a/csaf/rolie.go b/csaf/rolie.go index ac9c4c0..5478bf6 100644 --- a/csaf/rolie.go +++ b/csaf/rolie.go @@ -116,6 +116,13 @@ func (rf *ROLIEFeed) Files(filter string) []string { return files } +// Entries visits the entries of this feed. +func (rf *ROLIEFeed) Entries(fn func(*Entry)) { + for _, e := range rf.Feed.Entry { + fn(e) + } +} + // SortEntriesByUpdated sorts all the entries in the feed // by their update times. func (rf *ROLIEFeed) SortEntriesByUpdated() { From bb0a30aba3d60214a7ceacb9d06fa5b014b6446e Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer <107021473+JanHoefelmeyer@users.noreply.github.com> Date: Fri, 17 Jun 2022 09:26:27 +0200 Subject: [PATCH 5/5] Improve provider docs * Add issuing_authority and contact_details to publisher info example in docs/csaf_provider.md. * Add a link to toml's website. --- docs/csaf_provider.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/csaf_provider.md b/docs/csaf_provider.md index e17a383..bddd018 100644 --- a/docs/csaf_provider.md +++ b/docs/csaf_provider.md @@ -1,5 +1,5 @@ `csaf_provider` implements the CGI interface for webservers -and reads its configuration from a TOML file. +and reads its configuration from a [TOML](https://toml.io/en/) file. The [setup docs](../README.md#setup-trusted-provider) explain how to wire this up with nginx and where the config file lives. @@ -27,4 +27,4 @@ Following options are supported in the config file: - provider_metadata: Configure the provider metadata. - provider_metadata.list_on_CSAF_aggregators: List on aggregators - provider_metadata.mirror_on_CSAF_aggregators: Mirror on aggregators - - provider_metadata.publisher: Set the publisher. Default: `{"category"= "vendor", "name"= "Example Company", "namespace"= "https://example.com"}`. + - provider_metadata.publisher: Set the publisher. Default: `{"category"= "vendor", "name"= "Example Company", "namespace"= "https://example.com", "issuing_authority"= "We at Example Company are responsible for publishing and maintaining Product Y.", "contact_details"= "Example Company can be reached at contact_us@example.com, or via our website at https://www.example.com/contact."}`.