From 37d6692fa80a0065ce3e00b9b916486f8b9902ab Mon Sep 17 00:00:00 2001
From: "Sascha L. Teichmann"
Date: Wed, 1 Dec 2021 01:05:24 +0100
Subject: [PATCH 01/12] Create security.txt in .wellknown folder.
---
cmd/csaf_provider/files.go | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/cmd/csaf_provider/files.go b/cmd/csaf_provider/files.go
index ed96913..aadc7c7 100644
--- a/cmd/csaf_provider/files.go
+++ b/cmd/csaf_provider/files.go
@@ -27,11 +27,11 @@ func ensureFolders(c *config) error {
return err
}
- return createSecurity(c)
+ return createSecurity(c, wellknown)
}
-func createSecurity(c *config) error {
- security := filepath.Join(c.Web, "security.txt")
+func createSecurity(c *config, wellknown string) error {
+ security := filepath.Join(wellknown, "security.txt")
if _, err := os.Stat(security); err != nil {
if os.IsNotExist(err) {
f, err := os.Create(security)
From 70eb8875a42994b95cdf776c82679c16789335a9 Mon Sep 17 00:00:00 2001
From: "Sascha L. Teichmann"
Date: Wed, 1 Dec 2021 19:27:46 +0100
Subject: [PATCH 02/12] Read publisher from config.
---
cmd/csaf_provider/config.go | 28 ++++++---
cmd/csaf_provider/create.go | 99 ++++++++++++++++++++++++++++++++
cmd/csaf_provider/files.go | 76 ------------------------
cmd/csaf_provider/transaction.go | 41 +------------
csaf/models.go | 50 +++++++++++++---
5 files changed, 163 insertions(+), 131 deletions(-)
create mode 100644 cmd/csaf_provider/create.go
diff --git a/cmd/csaf_provider/config.go b/cmd/csaf_provider/config.go
index 4218da0..ac72e86 100644
--- a/cmd/csaf_provider/config.go
+++ b/cmd/csaf_provider/config.go
@@ -6,6 +6,7 @@ import (
"strings"
"github.com/BurntSushi/toml"
+ "github.com/csaf-poc/csaf_distribution/csaf"
)
const (
@@ -17,14 +18,15 @@ const (
)
type config struct {
- Key string `toml:"key"`
- Folder string `toml:"folder"`
- Web string `toml:"web"`
- TLPs []tlp `toml:"tlps"`
- UploadSignature bool `toml:"upload_signature"`
- PGPURL string `toml:"pgp_url"`
- Domain string `toml:"domain"`
- NoPassphrase bool `toml:"no_passphrase"`
+ Key string `toml:"key"`
+ Folder string `toml:"folder"`
+ Web string `toml:"web"`
+ TLPs []tlp `toml:"tlps"`
+ UploadSignature bool `toml:"upload_signature"`
+ PGPURL string `toml:"pgp_url"`
+ Domain string `toml:"domain"`
+ NoPassphrase bool `toml:"no_passphrase"`
+ Publisher *csaf.Publisher `toml:"publisher"`
}
type tlp string
@@ -58,6 +60,16 @@ func (cfg *config) GetPGPURL(key string) string {
return strings.ReplaceAll(cfg.PGPURL, "${KEY}", key)
}
+func (cfg *config) modelTLPs() []csaf.TLPLabel {
+ tlps := make([]csaf.TLPLabel, len(cfg.TLPs))
+ for _, t := range cfg.TLPs {
+ if t != tlpCSAF {
+ tlps = append(tlps, csaf.TLPLabel(t))
+ }
+ }
+ return tlps
+}
+
func loadConfig() (*config, error) {
path := os.Getenv(configEnv)
if path == "" {
diff --git a/cmd/csaf_provider/create.go b/cmd/csaf_provider/create.go
new file mode 100644
index 0000000..3641a47
--- /dev/null
+++ b/cmd/csaf_provider/create.go
@@ -0,0 +1,99 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/csaf-poc/csaf_distribution/csaf"
+)
+
+func ensureFolders(c *config) error {
+
+ wellknown := filepath.Join(c.Web, ".well-known")
+ wellknownCSAF := filepath.Join(wellknown, "csaf")
+
+ if err := createWellknown(wellknownCSAF); err != nil {
+ return err
+ }
+
+ if err := createFeedFolders(c, wellknownCSAF); err != nil {
+ return err
+ }
+
+ if err := createProviderMetadata(c, wellknownCSAF); err != nil {
+ return err
+ }
+
+ return createSecurity(c, wellknown)
+}
+
+func createWellknown(wellknown string) error {
+ st, err := os.Stat(wellknown)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return os.MkdirAll(wellknown, 0755)
+ }
+ return err
+ }
+ if !st.IsDir() {
+ return errors.New(".well-known/csaf is not a directory")
+ }
+ return nil
+}
+
+func createFeedFolders(c *config, wellknown string) error {
+ for _, t := range c.TLPs {
+ if t == tlpCSAF {
+ continue
+ }
+ tlpLink := filepath.Join(wellknown, string(t))
+ if _, err := filepath.EvalSymlinks(tlpLink); err != nil {
+ if os.IsNotExist(err) {
+ tlpFolder := filepath.Join(c.Folder, string(t))
+ if tlpFolder, err = mkUniqDir(tlpFolder); err != nil {
+ return err
+ }
+ if err = os.Symlink(tlpFolder, tlpLink); err != nil {
+ return err
+ }
+ } else {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func createSecurity(c *config, wellknown string) error {
+ security := filepath.Join(wellknown, "security.txt")
+ if _, err := os.Stat(security); err != nil {
+ if os.IsNotExist(err) {
+ f, err := os.Create(security)
+ if err != nil {
+ return err
+ }
+ fmt.Fprintf(
+ f, "CSAF: %s/.well-known/csaf/provider-metadata.json\n",
+ c.Domain)
+ return f.Close()
+ }
+ return err
+ }
+ return nil
+}
+
+func createProviderMetadata(c *config, wellknownCSAF string) error {
+ path := filepath.Join(wellknownCSAF, "provider-metadata.json")
+ _, err := os.Stat(path)
+ if err == nil {
+ return nil
+ }
+ if !os.IsNotExist(err) {
+ return err
+ }
+ pm := csaf.NewProviderMetadataDomain(c.Domain, c.modelTLPs())
+ pm.Publisher = c.Publisher
+ return saveToFile(path, pm)
+}
diff --git a/cmd/csaf_provider/files.go b/cmd/csaf_provider/files.go
index aadc7c7..3fb5454 100644
--- a/cmd/csaf_provider/files.go
+++ b/cmd/csaf_provider/files.go
@@ -4,7 +4,6 @@ import (
"bytes"
"crypto/sha256"
"crypto/sha512"
- "errors"
"fmt"
"hash"
"io"
@@ -16,81 +15,6 @@ import (
"time"
)
-func ensureFolders(c *config) error {
-
- wellknown, err := createWellknown(c)
- if err != nil {
- return err
- }
-
- if err := createFeedFolders(c, wellknown); err != nil {
- return err
- }
-
- return createSecurity(c, wellknown)
-}
-
-func createSecurity(c *config, wellknown string) error {
- security := filepath.Join(wellknown, "security.txt")
- if _, err := os.Stat(security); err != nil {
- if os.IsNotExist(err) {
- f, err := os.Create(security)
- if err != nil {
- return err
- }
- fmt.Fprintf(
- f, "CSAF: %s/.well-known/csaf/provider-metadata.json\n",
- c.Domain)
- return f.Close()
- }
- return err
- }
- return nil
-}
-
-func createFeedFolders(c *config, wellknown string) error {
- for _, t := range c.TLPs {
- if t == tlpCSAF {
- continue
- }
- tlpLink := filepath.Join(wellknown, string(t))
- if _, err := filepath.EvalSymlinks(tlpLink); err != nil {
- if os.IsNotExist(err) {
- tlpFolder := filepath.Join(c.Folder, string(t))
- if tlpFolder, err = mkUniqDir(tlpFolder); err != nil {
- return err
- }
- if err = os.Symlink(tlpFolder, tlpLink); err != nil {
- return err
- }
- } else {
- return err
- }
- }
- }
- return nil
-}
-
-func createWellknown(c *config) (string, error) {
- wellknown := filepath.Join(c.Web, ".well-known", "csaf")
-
- st, err := os.Stat(wellknown)
- if err != nil {
- if os.IsNotExist(err) {
- if err := os.MkdirAll(wellknown, 0755); err != nil {
- return "", err
- }
- } else {
- return "", err
- }
- } else {
- if !st.IsDir() {
- return "", errors.New(".well-known/csaf is not a directory")
- }
- }
- return wellknown, nil
-}
-
func deepCopy(dst, src string) error {
stack := []string{dst, src}
diff --git a/cmd/csaf_provider/transaction.go b/cmd/csaf_provider/transaction.go
index 6663d09..07d601d 100644
--- a/cmd/csaf_provider/transaction.go
+++ b/cmd/csaf_provider/transaction.go
@@ -3,49 +3,10 @@ package main
import (
"os"
"path/filepath"
- "strings"
"github.com/csaf-poc/csaf_distribution/csaf"
)
-func newProviderMetadata(cfg *config) *csaf.ProviderMetadata {
-
- pmd := csaf.NewProviderMetadata(
- cfg.Domain + "/.wellknown/csaf/provider-metadata.json")
-
- // Register feeds.
-
- var feeds []csaf.Feed
-
- for _, t := range cfg.TLPs {
- if t == tlpCSAF {
- continue
- }
- var (
- ts = string(t)
- feedName = "csaf-feed-tlp-" + ts + ".json"
- feedURL = csaf.JSONURL(
- cfg.Domain + "/.well-known/csaf/" + ts + "/" + feedName)
- tlpLabel = csaf.TLPLabel(strings.ToUpper(ts))
- )
- feeds = append(feeds, csaf.Feed{
- Summary: "TLP:" + string(tlpLabel) + " advisories",
- TLPLabel: &tlpLabel,
- URL: &feedURL,
- })
- }
-
- if len(feeds) > 0 {
- pmd.Distributions = []csaf.Distribution{{
- Rolie: []csaf.ROLIE{{
- Feeds: feeds,
- }},
- }}
- }
-
- return pmd
-}
-
func doTransaction(
cfg *config,
t tlp,
@@ -60,7 +21,7 @@ func doTransaction(
f, err := os.Open(metadata)
if err != nil {
if os.IsNotExist(err) {
- return newProviderMetadata(cfg), nil
+ return csaf.NewProviderMetadataDomain(cfg.Domain, cfg.modelTLPs()), nil
}
return nil, err
}
diff --git a/csaf/models.go b/csaf/models.go
index 29a9d1a..69c5b67 100644
--- a/csaf/models.go
+++ b/csaf/models.go
@@ -102,11 +102,11 @@ var csafCategoryPattern = alternativesUnmarshal(
// Publisher is the publisher of the feed.
type Publisher struct {
- Category *Category `json:"category"` // required
- Name *string `json:"name"` // required
- Namespace *string `json:"namespace"` // required
- ContactDetails string `json:"contact_details,omitempty"`
- IssuingAuthority string `json:"issuing_authority,omitempty"`
+ Category *Category `json:"category" toml:"category"` // required
+ Name *string `json:"name" toml:"name"` // required
+ Namespace *string `json:"namespace" toml:"namespace"` // required
+ ContactDetails string `json:"contact_details,omitempty" toml:"contact_details"`
+ IssuingAuthority string `json:"issuing_authority,omitempty" toml:"issuing_authority"`
}
// MetadataVersion is the metadata version of the feed.
@@ -148,8 +148,8 @@ type ProviderMetadata struct {
MetadataVersion *MetadataVersion `json:"metadata_version"` // required
MirrorOnCSAFAggregators *bool `json:"mirror_on_CSAF_aggregators"` // required
PGPKeys []PGPKey `json:"pgp_keys,omitempty"`
- Publisher *Publisher `json:"publisher"` // required
- Role *MetadataRole `json:"role"` // required
+ Publisher *Publisher `json:"publisher,omitempty"` // required
+ Role *MetadataRole `json:"role"` // required
}
func patternUnmarshal(pattern string) func([]byte) (string, error) {
@@ -384,6 +384,42 @@ func NewProviderMetadata(canonicalURL string) *ProviderMetadata {
return pm
}
+// NewProviderMetadataDomain creates a new provider with the given URL
+// and tlps feeds.
+func NewProviderMetadataDomain(domain string, tlps []TLPLabel) *ProviderMetadata {
+
+ pm := NewProviderMetadata(
+ domain + "/.wellknown/csaf/provider-metadata.json")
+
+ // Register feeds.
+
+ var feeds []Feed
+
+ for _, t := range tlps {
+ var (
+ ts = strings.ToLower(string(t))
+ feedName = "csaf-feed-tlp-" + ts + ".json"
+ feedURL = JSONURL(
+ domain + "/.well-known/csaf/" + ts + "/" + feedName)
+ )
+ feeds = append(feeds, Feed{
+ Summary: "TLP:" + string(t) + " advisories",
+ TLPLabel: &t,
+ URL: &feedURL,
+ })
+ }
+
+ if len(feeds) > 0 {
+ pm.Distributions = []Distribution{{
+ Rolie: []ROLIE{{
+ Feeds: feeds,
+ }},
+ }}
+ }
+
+ return pm
+}
+
// Save saves a metadata provider to a writer.
func (pmd *ProviderMetadata) Save(w io.Writer) error {
enc := json.NewEncoder(w)
From f2d8cd1e90ff24a1375e54154062cc38557f8361 Mon Sep 17 00:00:00 2001
From: "Sascha L. Teichmann"
Date: Wed, 1 Dec 2021 19:35:19 +0100
Subject: [PATCH 03/12] Work in bernhardreiter's PR#4.
---
cmd/csaf_provider/config.go | 12 ++++++------
cmd/csaf_provider/controller.go | 2 +-
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/cmd/csaf_provider/config.go b/cmd/csaf_provider/config.go
index ac72e86..75b006c 100644
--- a/cmd/csaf_provider/config.go
+++ b/cmd/csaf_provider/config.go
@@ -14,7 +14,7 @@ const (
defaultConfigPath = "/usr/lib/casf/config.toml"
defaultFolder = "/var/www/"
defaultWeb = "/var/www/html"
- defaultPGPURL = "http://pgp.mit.edu/pks/lookup?search=${KEY}&op=index"
+ defaultOpenPGPURL = "https://openpgp.circl.lu/pks/lookup?search=${KEY}&op=index"
)
type config struct {
@@ -23,7 +23,7 @@ type config struct {
Web string `toml:"web"`
TLPs []tlp `toml:"tlps"`
UploadSignature bool `toml:"upload_signature"`
- PGPURL string `toml:"pgp_url"`
+ OpenPGPURL string `toml:"openpgp_url"`
Domain string `toml:"domain"`
NoPassphrase bool `toml:"no_passphrase"`
Publisher *csaf.Publisher `toml:"publisher"`
@@ -56,8 +56,8 @@ func (t *tlp) UnmarshalText(text []byte) error {
return fmt.Errorf("invalid config TLP value: %v", string(text))
}
-func (cfg *config) GetPGPURL(key string) string {
- return strings.ReplaceAll(cfg.PGPURL, "${KEY}", key)
+func (cfg *config) GetOpenPGPURL(key string) string {
+ return strings.ReplaceAll(cfg.OpenPGPURL, "${KEY}", key)
}
func (cfg *config) modelTLPs() []csaf.TLPLabel {
@@ -98,8 +98,8 @@ func loadConfig() (*config, error) {
cfg.TLPs = []tlp{tlpCSAF, tlpWhite, tlpGreen, tlpAmber, tlpRed}
}
- if cfg.PGPURL == "" {
- cfg.PGPURL = defaultPGPURL
+ if cfg.OpenPGPURL == "" {
+ cfg.OpenPGPURL = defaultOpenPGPURL
}
return &cfg, nil
diff --git a/cmd/csaf_provider/controller.go b/cmd/csaf_provider/controller.go
index d982334..e693d93 100644
--- a/cmd/csaf_provider/controller.go
+++ b/cmd/csaf_provider/controller.go
@@ -339,7 +339,7 @@ func (c *controller) upload(rw http.ResponseWriter, r *http.Request) {
// TODO: Check for conflicts.
pmd.Publisher = ex.publisher
- pmd.SetPGP(fingerprint, c.cfg.GetPGPURL(fingerprint))
+ pmd.SetPGP(fingerprint, c.cfg.GetOpenPGPURL(fingerprint))
return nil
}); err != nil {
From 9cf4a7cb5c6bd32538c9b20f3405d2803f476496 Mon Sep 17 00:00:00 2001
From: "Sascha L. Teichmann"
Date: Wed, 1 Dec 2021 20:16:09 +0100
Subject: [PATCH 04/12] Add OpenPGP key to provider metadata when generated at
setup.
---
cmd/csaf_provider/config.go | 10 ++++++++++
cmd/csaf_provider/controller.go | 11 +----------
cmd/csaf_provider/create.go | 9 +++++++++
3 files changed, 20 insertions(+), 10 deletions(-)
diff --git a/cmd/csaf_provider/config.go b/cmd/csaf_provider/config.go
index 75b006c..dd94a17 100644
--- a/cmd/csaf_provider/config.go
+++ b/cmd/csaf_provider/config.go
@@ -6,6 +6,7 @@ import (
"strings"
"github.com/BurntSushi/toml"
+ "github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/csaf-poc/csaf_distribution/csaf"
)
@@ -70,6 +71,15 @@ func (cfg *config) modelTLPs() []csaf.TLPLabel {
return tlps
}
+func (cfg *config) loadCryptoKey() (*crypto.Key, error) {
+ f, err := os.Open(cfg.Key)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return crypto.NewKeyFromArmoredReader(f)
+}
+
func loadConfig() (*config, error) {
path := os.Getenv(configEnv)
if path == "" {
diff --git a/cmd/csaf_provider/controller.go b/cmd/csaf_provider/controller.go
index e693d93..520dca9 100644
--- a/cmd/csaf_provider/controller.go
+++ b/cmd/csaf_provider/controller.go
@@ -112,19 +112,10 @@ func loadCSAF(r *http.Request) (string, []byte, error) {
return cleanFileName(handler.Filename), buf.Bytes(), nil
}
-func (c *controller) loadCryptoKey() (*crypto.Key, error) {
- f, err := os.Open(c.cfg.Key)
- if err != nil {
- return nil, err
- }
- defer f.Close()
- return crypto.NewKeyFromArmoredReader(f)
-}
-
func (c *controller) handleSignature(r *http.Request, data []byte) (string, string, error) {
// Either way ... we need the key.
- key, err := c.loadCryptoKey()
+ key, err := c.cfg.loadCryptoKey()
if err != nil {
return "", "", err
}
diff --git a/cmd/csaf_provider/create.go b/cmd/csaf_provider/create.go
index 3641a47..0bc9127 100644
--- a/cmd/csaf_provider/create.go
+++ b/cmd/csaf_provider/create.go
@@ -95,5 +95,14 @@ func createProviderMetadata(c *config, wellknownCSAF string) error {
}
pm := csaf.NewProviderMetadataDomain(c.Domain, c.modelTLPs())
pm.Publisher = c.Publisher
+
+ // Set OpenPGP key.
+ key, err := c.loadCryptoKey()
+ if err != nil {
+ return err
+ }
+ fingerprint := key.GetFingerprint()
+ pm.SetPGP(fingerprint, c.GetOpenPGPURL(fingerprint))
+
return saveToFile(path, pm)
}
From fbe20dbf602994f8abac2bebdfeba6146d91fd18 Mon Sep 17 00:00:00 2001
From: "Sascha L. Teichmann"
Date: Wed, 1 Dec 2021 20:51:39 +0100
Subject: [PATCH 05/12] Use Key ID instead of fingerprint in OpenPGP URL
interpolation.
---
cmd/csaf_provider/controller.go | 32 +++++++++++++++++---------------
cmd/csaf_provider/create.go | 4 ++--
2 files changed, 19 insertions(+), 17 deletions(-)
diff --git a/cmd/csaf_provider/controller.go b/cmd/csaf_provider/controller.go
index 520dca9..c441106 100644
--- a/cmd/csaf_provider/controller.go
+++ b/cmd/csaf_provider/controller.go
@@ -112,65 +112,66 @@ func loadCSAF(r *http.Request) (string, []byte, error) {
return cleanFileName(handler.Filename), buf.Bytes(), nil
}
-func (c *controller) handleSignature(r *http.Request, data []byte) (string, string, error) {
+func (c *controller) handleSignature(
+ r *http.Request,
+ data []byte,
+) (string, *crypto.Key, error) {
// Either way ... we need the key.
key, err := c.cfg.loadCryptoKey()
if err != nil {
- return "", "", err
+ return "", nil, err
}
- fingerprint := key.GetFingerprint()
-
// Was the signature given via request?
if c.cfg.UploadSignature {
sigText := r.FormValue("signature")
if sigText == "" {
- return "", "", errors.New("missing signature in request")
+ return "", nil, errors.New("missing signature in request")
}
pgpSig, err := crypto.NewPGPSignatureFromArmored(sigText)
if err != nil {
- return "", "", err
+ return "", nil, err
}
// Use as public key
signRing, err := crypto.NewKeyRing(key)
if err != nil {
- return "", "", err
+ return "", nil, err
}
if err := signRing.VerifyDetached(
crypto.NewPlainMessage(data),
pgpSig, crypto.GetUnixTime(),
); err != nil {
- return "", "", err
+ return "", nil, err
}
- return sigText, fingerprint, nil
+ return sigText, key, nil
}
// Sign ourself
if passwd := r.FormValue("passphrase"); !c.cfg.NoPassphrase && passwd != "" {
if key, err = key.Unlock([]byte(passwd)); err != nil {
- return "", "", err
+ return "", nil, err
}
}
// Use as private key
signRing, err := crypto.NewKeyRing(key)
if err != nil {
- return "", "", err
+ return "", nil, err
}
sig, err := signRing.SignDetached(crypto.NewPlainMessage(data))
if err != nil {
- return "", "", err
+ return "", nil, err
}
armored, err := sig.GetArmored()
- return armored, fingerprint, err
+ return armored, key, err
}
func (c *controller) upload(rw http.ResponseWriter, r *http.Request) {
@@ -208,7 +209,7 @@ func (c *controller) upload(rw http.ResponseWriter, r *http.Request) {
}
}
- armored, fingerprint, err := c.handleSignature(r, data)
+ armored, key, err := c.handleSignature(r, data)
if err != nil {
c.failed(rw, "upload.html", err)
return
@@ -330,7 +331,8 @@ func (c *controller) upload(rw http.ResponseWriter, r *http.Request) {
// TODO: Check for conflicts.
pmd.Publisher = ex.publisher
- pmd.SetPGP(fingerprint, c.cfg.GetOpenPGPURL(fingerprint))
+ keyID, fingerprint := key.GetHexKeyID(), key.GetFingerprint()
+ pmd.SetPGP(fingerprint, c.cfg.GetOpenPGPURL(keyID))
return nil
}); err != nil {
diff --git a/cmd/csaf_provider/create.go b/cmd/csaf_provider/create.go
index 0bc9127..29a361d 100644
--- a/cmd/csaf_provider/create.go
+++ b/cmd/csaf_provider/create.go
@@ -101,8 +101,8 @@ func createProviderMetadata(c *config, wellknownCSAF string) error {
if err != nil {
return err
}
- fingerprint := key.GetFingerprint()
- pm.SetPGP(fingerprint, c.GetOpenPGPURL(fingerprint))
+ keyID, fingerprint := key.GetHexKeyID(), key.GetFingerprint()
+ pm.SetPGP(fingerprint, c.GetOpenPGPURL(keyID))
return saveToFile(path, pm)
}
From 45299f7e44f7fa34773297fada8bc58803925561 Mon Sep 17 00:00:00 2001
From: "Sascha L. Teichmann"
Date: Wed, 1 Dec 2021 21:10:02 +0100
Subject: [PATCH 06/12] Only write provider metadata back if it is marked as
dynamic in config.
---
cmd/csaf_provider/config.go | 19 ++++++++-------
cmd/csaf_provider/transaction.go | 42 +++++++++++++++++---------------
2 files changed, 32 insertions(+), 29 deletions(-)
diff --git a/cmd/csaf_provider/config.go b/cmd/csaf_provider/config.go
index dd94a17..12fce85 100644
--- a/cmd/csaf_provider/config.go
+++ b/cmd/csaf_provider/config.go
@@ -19,15 +19,16 @@ const (
)
type config struct {
- Key string `toml:"key"`
- Folder string `toml:"folder"`
- Web string `toml:"web"`
- TLPs []tlp `toml:"tlps"`
- UploadSignature bool `toml:"upload_signature"`
- OpenPGPURL string `toml:"openpgp_url"`
- Domain string `toml:"domain"`
- NoPassphrase bool `toml:"no_passphrase"`
- Publisher *csaf.Publisher `toml:"publisher"`
+ Key string `toml:"key"`
+ Folder string `toml:"folder"`
+ Web string `toml:"web"`
+ TLPs []tlp `toml:"tlps"`
+ UploadSignature bool `toml:"upload_signature"`
+ OpenPGPURL string `toml:"openpgp_url"`
+ Domain string `toml:"domain"`
+ NoPassphrase bool `toml:"no_passphrase"`
+ DynamicProviderMetaData bool `toml:"dynamic_provider_metadata"`
+ Publisher *csaf.Publisher `toml:"publisher"`
}
type tlp string
diff --git a/cmd/csaf_provider/transaction.go b/cmd/csaf_provider/transaction.go
index 07d601d..2d0c782 100644
--- a/cmd/csaf_provider/transaction.go
+++ b/cmd/csaf_provider/transaction.go
@@ -59,29 +59,31 @@ func doTransaction(
return err
}
- // Write back provider metadata.
- newMetaName, newMetaFile, err := mkUniqFile(metadata)
- if err != nil {
- os.RemoveAll(newDir)
- return err
- }
+ // Write back provider metadata if its dynamic.
+ if cfg.DynamicProviderMetaData {
+ newMetaName, newMetaFile, err := mkUniqFile(metadata)
+ if err != nil {
+ os.RemoveAll(newDir)
+ return err
+ }
- if err := pmd.Save(newMetaFile); err != nil {
- newMetaFile.Close()
- os.Remove(newMetaName)
- os.RemoveAll(newDir)
- return err
- }
+ if err := pmd.Save(newMetaFile); err != nil {
+ newMetaFile.Close()
+ os.Remove(newMetaName)
+ os.RemoveAll(newDir)
+ return err
+ }
- if err := newMetaFile.Close(); err != nil {
- os.Remove(newMetaName)
- os.RemoveAll(newDir)
- return err
- }
+ if err := newMetaFile.Close(); err != nil {
+ os.Remove(newMetaName)
+ os.RemoveAll(newDir)
+ return err
+ }
- if err := os.Rename(newMetaName, metadata); err != nil {
- os.RemoveAll(newDir)
- return err
+ if err := os.Rename(newMetaName, metadata); err != nil {
+ os.RemoveAll(newDir)
+ return err
+ }
}
// Switch directories.
From 5276cea0a0f7e57bfdc93e325261aadd293ff121 Mon Sep 17 00:00:00 2001
From: "Sascha L. Teichmann"
Date: Wed, 1 Dec 2021 23:57:47 +0100
Subject: [PATCH 07/12] Simplified code.
---
csaf/models.go | 36 ++++++++++++++++++------------------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/csaf/models.go b/csaf/models.go
index 69c5b67..b511cf6 100644
--- a/csaf/models.go
+++ b/csaf/models.go
@@ -391,31 +391,31 @@ func NewProviderMetadataDomain(domain string, tlps []TLPLabel) *ProviderMetadata
pm := NewProviderMetadata(
domain + "/.wellknown/csaf/provider-metadata.json")
+ if len(tlps) == 0 {
+ return pm
+ }
+
// Register feeds.
- var feeds []Feed
+ feeds := make([]Feed, len(tlps))
- for _, t := range tlps {
- var (
- ts = strings.ToLower(string(t))
- feedName = "csaf-feed-tlp-" + ts + ".json"
- feedURL = JSONURL(
- domain + "/.well-known/csaf/" + ts + "/" + feedName)
- )
- feeds = append(feeds, Feed{
+ for i, t := range tlps {
+ lt := strings.ToLower(string(t))
+ feed := "csaf-feed-tlp-" + lt + ".json"
+ url := JSONURL(domain + "/.well-known/csaf/" + lt + "/" + feed)
+
+ feeds[i] = Feed{
Summary: "TLP:" + string(t) + " advisories",
TLPLabel: &t,
- URL: &feedURL,
- })
+ URL: &url,
+ }
}
- if len(feeds) > 0 {
- pm.Distributions = []Distribution{{
- Rolie: []ROLIE{{
- Feeds: feeds,
- }},
- }}
- }
+ pm.Distributions = []Distribution{{
+ Rolie: []ROLIE{{
+ Feeds: feeds,
+ }},
+ }}
return pm
}
From 22c7da1ed162ee7779deded91de7e5c8ff2c9ba6 Mon Sep 17 00:00:00 2001
From: "Sascha L. Teichmann"
Date: Thu, 2 Dec 2021 00:24:27 +0100
Subject: [PATCH 08/12] use io.WriterTo instead of custom save interface ti
serialize metadata and rolie.
---
cmd/csaf_provider/files.go | 8 ++------
cmd/csaf_provider/transaction.go | 2 +-
csaf/models.go | 19 ++++++++++++++++---
csaf/rolie.go | 8 +++++---
4 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/cmd/csaf_provider/files.go b/cmd/csaf_provider/files.go
index 3fb5454..50ba98f 100644
--- a/cmd/csaf_provider/files.go
+++ b/cmd/csaf_provider/files.go
@@ -138,16 +138,12 @@ func writeHashedFile(fname, name string, data []byte, armored string) error {
return nil
}
-type saver interface {
- Save(io.Writer) error
-}
-
-func saveToFile(fname string, s saver) error {
+func saveToFile(fname string, wt io.WriterTo) error {
f, err1 := os.Create(fname)
if err1 != nil {
return err1
}
- err1 = s.Save(f)
+ _, err1 = wt.WriteTo(f)
err2 := f.Close()
if err1 != nil {
return err1
diff --git a/cmd/csaf_provider/transaction.go b/cmd/csaf_provider/transaction.go
index 2d0c782..04f4d45 100644
--- a/cmd/csaf_provider/transaction.go
+++ b/cmd/csaf_provider/transaction.go
@@ -67,7 +67,7 @@ func doTransaction(
return err
}
- if err := pmd.Save(newMetaFile); err != nil {
+ if _, err := pmd.WriteTo(newMetaFile); err != nil {
newMetaFile.Close()
os.Remove(newMetaName)
os.RemoveAll(newDir)
diff --git a/csaf/models.go b/csaf/models.go
index b511cf6..dcdbcf2 100644
--- a/csaf/models.go
+++ b/csaf/models.go
@@ -420,11 +420,24 @@ func NewProviderMetadataDomain(domain string, tlps []TLPLabel) *ProviderMetadata
return pm
}
-// Save saves a metadata provider to a writer.
-func (pmd *ProviderMetadata) Save(w io.Writer) error {
+type nWriter struct {
+ io.Writer
+ n int64
+}
+
+func (nw *nWriter) Write(p []byte) (int, error) {
+ n, err := nw.Write(p)
+ nw.n += int64(n)
+ return n, err
+}
+
+// WriteTo saves a metadata provider to a writer.
+func (pmd *ProviderMetadata) WriteTo(w io.Writer) (int64, error) {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
- return enc.Encode(pmd)
+ nw := nWriter{w, 0}
+ err := enc.Encode(&nw)
+ return nw.n, err
}
// LoadProviderMetadata loads a metadata provider from a reader.
diff --git a/csaf/rolie.go b/csaf/rolie.go
index e49641f..78d8c63 100644
--- a/csaf/rolie.go
+++ b/csaf/rolie.go
@@ -68,11 +68,13 @@ func LoadROLIEFeed(r io.Reader) (*ROLIEFeed, error) {
return &rf, nil
}
-// Save saves a ROLIE feed to a writer.
-func (rf *ROLIEFeed) Save(w io.Writer) error {
+// WriteTo saves a ROLIE feed to a writer.
+func (rf *ROLIEFeed) WriteTo(w io.Writer) (int64, error) {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
- return enc.Encode(rf)
+ nw := nWriter{w, 0}
+ err := enc.Encode(&nw)
+ return nw.n, err
}
// EntryByID looks up an entry by its ID.
From bd8846baa672b8085c0349f4c5f210cf8f991992 Mon Sep 17 00:00:00 2001
From: "Sascha L. Teichmann"
Date: Thu, 2 Dec 2021 00:41:43 +0100
Subject: [PATCH 09/12] Fixed stupid calling mistakes in new WriteTo methods.
---
csaf/models.go | 8 ++++----
csaf/rolie.go | 6 +++---
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/csaf/models.go b/csaf/models.go
index dcdbcf2..4b66811 100644
--- a/csaf/models.go
+++ b/csaf/models.go
@@ -426,17 +426,17 @@ type nWriter struct {
}
func (nw *nWriter) Write(p []byte) (int, error) {
- n, err := nw.Write(p)
+ n, err := nw.Writer.Write(p)
nw.n += int64(n)
return n, err
}
// WriteTo saves a metadata provider to a writer.
func (pmd *ProviderMetadata) WriteTo(w io.Writer) (int64, error) {
- enc := json.NewEncoder(w)
- enc.SetIndent("", " ")
nw := nWriter{w, 0}
- err := enc.Encode(&nw)
+ enc := json.NewEncoder(&nw)
+ enc.SetIndent("", " ")
+ err := enc.Encode(pmd)
return nw.n, err
}
diff --git a/csaf/rolie.go b/csaf/rolie.go
index 78d8c63..b79c56a 100644
--- a/csaf/rolie.go
+++ b/csaf/rolie.go
@@ -70,10 +70,10 @@ func LoadROLIEFeed(r io.Reader) (*ROLIEFeed, error) {
// WriteTo saves a ROLIE feed to a writer.
func (rf *ROLIEFeed) WriteTo(w io.Writer) (int64, error) {
- enc := json.NewEncoder(w)
- enc.SetIndent("", " ")
nw := nWriter{w, 0}
- err := enc.Encode(&nw)
+ enc := json.NewEncoder(&nw)
+ enc.SetIndent("", " ")
+ err := enc.Encode(rf)
return nw.n, err
}
From 048c0dce8918367cd89224ad599f98b3da391dce Mon Sep 17 00:00:00 2001
From: "Sascha L. Teichmann"
Date: Thu, 2 Dec 2021 00:56:21 +0100
Subject: [PATCH 10/12] Added "0x" before key id in openpgp url to make search
on key server happy.
---
cmd/csaf_provider/config.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cmd/csaf_provider/config.go b/cmd/csaf_provider/config.go
index 12fce85..6868995 100644
--- a/cmd/csaf_provider/config.go
+++ b/cmd/csaf_provider/config.go
@@ -59,7 +59,7 @@ func (t *tlp) UnmarshalText(text []byte) error {
}
func (cfg *config) GetOpenPGPURL(key string) string {
- return strings.ReplaceAll(cfg.OpenPGPURL, "${KEY}", key)
+ return strings.ReplaceAll(cfg.OpenPGPURL, "${KEY}", "0x"+key)
}
func (cfg *config) modelTLPs() []csaf.TLPLabel {
From e5a6a8e2da804cb06700aa0d16039e3f583d9d00 Mon Sep 17 00:00:00 2001
From: "Sascha L. Teichmann"
Date: Thu, 2 Dec 2021 01:36:57 +0100
Subject: [PATCH 11/12] Fixed TLP model conversion. Fixed wrong .well-known
path
---
cmd/csaf_provider/config.go | 4 ++--
csaf/models.go | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/cmd/csaf_provider/config.go b/cmd/csaf_provider/config.go
index 6868995..c21f1b2 100644
--- a/cmd/csaf_provider/config.go
+++ b/cmd/csaf_provider/config.go
@@ -63,10 +63,10 @@ func (cfg *config) GetOpenPGPURL(key string) string {
}
func (cfg *config) modelTLPs() []csaf.TLPLabel {
- tlps := make([]csaf.TLPLabel, len(cfg.TLPs))
+ tlps := make([]csaf.TLPLabel, 0, len(cfg.TLPs))
for _, t := range cfg.TLPs {
if t != tlpCSAF {
- tlps = append(tlps, csaf.TLPLabel(t))
+ tlps = append(tlps, csaf.TLPLabel(strings.ToUpper(string(t))))
}
}
return tlps
diff --git a/csaf/models.go b/csaf/models.go
index 4b66811..b5ebac4 100644
--- a/csaf/models.go
+++ b/csaf/models.go
@@ -389,7 +389,7 @@ func NewProviderMetadata(canonicalURL string) *ProviderMetadata {
func NewProviderMetadataDomain(domain string, tlps []TLPLabel) *ProviderMetadata {
pm := NewProviderMetadata(
- domain + "/.wellknown/csaf/provider-metadata.json")
+ domain + "/.well-known/csaf/provider-metadata.json")
if len(tlps) == 0 {
return pm
From f77bb5f1a8f75eb91dfa07789744b0d72dc2c8cc Mon Sep 17 00:00:00 2001
From: "Sascha L. Teichmann"
Date: Thu, 2 Dec 2021 10:51:25 +0100
Subject: [PATCH 12/12] Added default publisher if not configured. Warning if
uploads don't have the same publisher as in metadata.
---
cmd/csaf_provider/config.go | 8 +++++++
cmd/csaf_provider/controller.go | 19 ++++++++++++---
cmd/csaf_provider/tmpl/upload.html | 10 ++++++++
csaf/models.go | 37 ++++++++++++++++++++++++++----
4 files changed, 66 insertions(+), 8 deletions(-)
diff --git a/cmd/csaf_provider/config.go b/cmd/csaf_provider/config.go
index c21f1b2..1221397 100644
--- a/cmd/csaf_provider/config.go
+++ b/cmd/csaf_provider/config.go
@@ -113,5 +113,13 @@ func loadConfig() (*config, error) {
cfg.OpenPGPURL = defaultOpenPGPURL
}
+ if cfg.Publisher == nil {
+ cfg.Publisher = &csaf.Publisher{
+ Category: func(c csaf.Category) *csaf.Category { return &c }(csaf.CSAFCategoryVendor),
+ Name: func(s string) *string { return &s }("ACME"),
+ Namespace: func(s string) *string { return &s }("https://example.com"),
+ }
+ }
+
return &cfg, nil
}
diff --git a/cmd/csaf_provider/controller.go b/cmd/csaf_provider/controller.go
index c441106..d155594 100644
--- a/cmd/csaf_provider/controller.go
+++ b/cmd/csaf_provider/controller.go
@@ -215,6 +215,9 @@ func (c *controller) upload(rw http.ResponseWriter, r *http.Request) {
return
}
+ var warnings []string
+ warn := func(msg string) { warnings = append(warnings, msg) }
+
if err := doTransaction(
c.cfg, t,
func(folder string, pmd *csaf.ProviderMetadata) error {
@@ -328,14 +331,23 @@ func (c *controller) upload(rw http.ResponseWriter, r *http.Request) {
}
// Take over publisher
- // TODO: Check for conflicts.
- pmd.Publisher = ex.publisher
+ switch {
+ case pmd.Publisher == nil:
+ warn("Publisher in provider metadata is not initialized. Forgot to configure?")
+ if c.cfg.DynamicProviderMetaData {
+ warn("Taking publisher from CSAF")
+ pmd.Publisher = ex.publisher
+ }
+ case !pmd.Publisher.Equals(ex.publisher):
+ warn("Publishers in provider metadata and CSAF do not match.")
+ }
keyID, fingerprint := key.GetHexKeyID(), key.GetFingerprint()
pmd.SetPGP(fingerprint, c.cfg.GetOpenPGPURL(keyID))
return nil
- }); err != nil {
+ },
+ ); err != nil {
c.failed(rw, "upload.html", err)
return
}
@@ -343,6 +355,7 @@ func (c *controller) upload(rw http.ResponseWriter, r *http.Request) {
result := map[string]interface{}{
"Name": newCSAF,
"ReleaseDate": ex.currentReleaseDate.Format(dateFormat),
+ "Warnings": warnings,
}
c.render(rw, "upload.html", result)
diff --git a/cmd/csaf_provider/tmpl/upload.html b/cmd/csaf_provider/tmpl/upload.html
index d2501ff..72ebfed 100644
--- a/cmd/csaf_provider/tmpl/upload.html
+++ b/cmd/csaf_provider/tmpl/upload.html
@@ -14,6 +14,16 @@
| CSAF file: | {{ .Name }} |
| Release date: | {{ .ReleaseDate }} |
+ {{ if .Warnings }}
+
+ Warning(s):
+
+ {{ range .Warnings }}
+ - {{ . }}
+ {{ end }}
+
+
+ {{ end }}
{{ end }}
Back:
diff --git a/csaf/models.go b/csaf/models.go
index b5ebac4..b313ab3 100644
--- a/csaf/models.go
+++ b/csaf/models.go
@@ -283,20 +283,47 @@ func (r *ROLIE) Validate() error {
// Validate checks if the publisher is valid.
// Returns an error if the validation fails otherwise nil.
-func (cp *Publisher) Validate() error {
+func (p *Publisher) Validate() error {
switch {
- case cp == nil:
+ case p == nil:
return errors.New("publisher is mandatory")
- case cp.Category == nil:
+ case p.Category == nil:
return errors.New("publisher.category is mandatory")
- case cp.Name == nil:
+ case p.Name == nil:
return errors.New("publisher.name is mandatory")
- case cp.Namespace == nil:
+ case p.Namespace == nil:
return errors.New("publisher.namespace is mandatory")
}
return nil
}
+func strPtrEquals(a, b *string) bool {
+ switch {
+ case a == nil:
+ return b == nil
+ case b == nil:
+ return false
+ default:
+ return *a == *b
+ }
+}
+
+// Equals checks if the publisher is equal to other componentwise.
+func (p *Publisher) Equals(o *Publisher) bool {
+ switch {
+ case p == nil:
+ return o == nil
+ case o == nil:
+ return false
+ default:
+ return strPtrEquals((*string)(p.Category), (*string)(o.Category)) &&
+ strPtrEquals(p.Name, o.Name) &&
+ strPtrEquals(p.Namespace, o.Namespace) &&
+ p.ContactDetails == o.ContactDetails &&
+ p.IssuingAuthority == o.IssuingAuthority
+ }
+}
+
// Validate checks if the PGPKey is valid.
// Returns an error if the validation fails otherwise nil.
func (pk *PGPKey) Validate() error {