mirror of
https://github.com/gocsaf/csaf.git
synced 2025-12-22 05:40:11 +01:00
Merge pull request #655 from gocsaf/json-eof
Make json parsing more strict
This commit is contained in:
commit
ae184eb189
16 changed files with 455 additions and 36 deletions
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -25,6 +24,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gocsaf/csaf/v3/csaf"
|
"github.com/gocsaf/csaf/v3/csaf"
|
||||||
|
"github.com/gocsaf/csaf/v3/internal/misc"
|
||||||
"github.com/gocsaf/csaf/v3/util"
|
"github.com/gocsaf/csaf/v3/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ func (w *worker) checkInterims(
|
||||||
if err := func() error {
|
if err := func() error {
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
tee := io.TeeReader(res.Body, hasher)
|
tee := io.TeeReader(res.Body, hasher)
|
||||||
return json.NewDecoder(tee).Decode(&doc)
|
return misc.StrictJSONParse(tee, &doc)
|
||||||
}(); err != nil {
|
}(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
@ -31,6 +30,7 @@ import (
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
|
|
||||||
"github.com/gocsaf/csaf/v3/csaf"
|
"github.com/gocsaf/csaf/v3/csaf"
|
||||||
|
"github.com/gocsaf/csaf/v3/internal/misc"
|
||||||
"github.com/gocsaf/csaf/v3/util"
|
"github.com/gocsaf/csaf/v3/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -539,7 +539,7 @@ func (w *worker) mirrorFiles(tlpLabel csaf.TLPLabel, files []csaf.AdvisoryFile)
|
||||||
|
|
||||||
download := func(r io.Reader) error {
|
download := func(r io.Reader) error {
|
||||||
tee := io.TeeReader(r, hasher)
|
tee := io.TeeReader(r, hasher)
|
||||||
return json.NewDecoder(tee).Decode(&advisory)
|
return misc.StrictJSONParse(tee, &advisory)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := downloadJSON(w.client, file.URL(), download); err != nil {
|
if err := downloadJSON(w.client, file.URL(), download); err != nil {
|
||||||
|
|
@ -628,7 +628,6 @@ func (w *worker) mirrorFiles(tlpLabel csaf.TLPLabel, files []csaf.AdvisoryFile)
|
||||||
// If this fails it creates a signature itself with the configured key.
|
// If this fails it creates a signature itself with the configured key.
|
||||||
func (w *worker) downloadSignatureOrSign(url, fname string, data []byte) error {
|
func (w *worker) downloadSignatureOrSign(url, fname string, data []byte) error {
|
||||||
sig, err := w.downloadSignature(url)
|
sig, err := w.downloadSignature(url)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != errNotFound {
|
if err != errNotFound {
|
||||||
w.log.Error("Could not find signature URL", "url", url, "err", err)
|
w.log.Error("Could not find signature URL", "url", url, "err", err)
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,8 @@ import (
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gocsaf/csaf/v3/internal/misc"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -30,6 +28,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gocsaf/csaf/v3/internal/misc"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
|
|
@ -518,7 +518,7 @@ func (p *processor) rolieFeedEntries(feed string) ([]csaf.AdvisoryFile, error) {
|
||||||
return nil, nil, fmt.Errorf("%s: %v", feed, err)
|
return nil, nil, fmt.Errorf("%s: %v", feed, err)
|
||||||
}
|
}
|
||||||
var rolieDoc any
|
var rolieDoc any
|
||||||
err = json.NewDecoder(bytes.NewReader(all)).Decode(&rolieDoc)
|
err = misc.StrictJSONParse(bytes.NewReader(all), &rolieDoc)
|
||||||
return rfeed, rolieDoc, err
|
return rfeed, rolieDoc, err
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -702,7 +702,7 @@ func (p *processor) integrity(
|
||||||
if err := func() error {
|
if err := func() error {
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
tee := io.TeeReader(res.Body, hasher)
|
tee := io.TeeReader(res.Body, hasher)
|
||||||
return json.NewDecoder(tee).Decode(&doc)
|
return misc.StrictJSONParse(tee, &doc)
|
||||||
}(); err != nil {
|
}(); err != nil {
|
||||||
lg(ErrorType, "Reading %s failed: %v", u, err)
|
lg(ErrorType, "Reading %s failed: %v", u, err)
|
||||||
continue
|
continue
|
||||||
|
|
@ -1035,8 +1035,7 @@ func (p *processor) checkChanges(base string, mask whereType) error {
|
||||||
}
|
}
|
||||||
path := r[pathColumn]
|
path := r[pathColumn]
|
||||||
|
|
||||||
times, files =
|
times, files = append(times, t),
|
||||||
append(times, t),
|
|
||||||
append(files, csaf.DirectoryAdvisoryFile{Path: path})
|
append(files, csaf.DirectoryAdvisoryFile{Path: path})
|
||||||
p.timesChanges[path] = t
|
p.timesChanges[path] = t
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/gocsaf/csaf/v3/csaf"
|
"github.com/gocsaf/csaf/v3/csaf"
|
||||||
|
"github.com/gocsaf/csaf/v3/internal/misc"
|
||||||
"github.com/gocsaf/csaf/v3/util"
|
"github.com/gocsaf/csaf/v3/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -551,7 +552,7 @@ func (dc *downloadContext) downloadAdvisory(
|
||||||
|
|
||||||
tee := io.TeeReader(resp.Body, hasher)
|
tee := io.TeeReader(resp.Body, hasher)
|
||||||
|
|
||||||
if err := json.NewDecoder(tee).Decode(&doc); err != nil {
|
if err := misc.StrictJSONParse(tee, &doc); err != nil {
|
||||||
dc.stats.downloadFailed++
|
dc.stats.downloadFailed++
|
||||||
slog.Warn("Downloading failed",
|
slog.Warn("Downloading failed",
|
||||||
"url", file.URL(),
|
"url", file.URL(),
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -91,7 +90,7 @@ func (p *processor) create() error {
|
||||||
Errors []string `json:"errors"`
|
Errors []string `json:"errors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
if err := misc.StrictJSONParse(resp.Body, &result); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +114,7 @@ func (p *processor) uploadRequest(filename string) (*http.Request, error) {
|
||||||
|
|
||||||
if !p.cfg.NoSchemaCheck {
|
if !p.cfg.NoSchemaCheck {
|
||||||
var doc any
|
var doc any
|
||||||
if err := json.NewDecoder(bytes.NewReader(data)).Decode(&doc); err != nil {
|
if err := misc.StrictJSONParse(bytes.NewReader(data), &doc); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
errs, err := csaf.ValidateCSAF(doc)
|
errs, err := csaf.ValidateCSAF(doc)
|
||||||
|
|
@ -239,7 +238,7 @@ func (p *processor) process(filename string) error {
|
||||||
Errors []string `json:"errors"`
|
Errors []string `json:"errors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
if err := misc.StrictJSONParse(resp.Body, &result); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -19,6 +18,7 @@ import (
|
||||||
"github.com/jessevdk/go-flags"
|
"github.com/jessevdk/go-flags"
|
||||||
|
|
||||||
"github.com/gocsaf/csaf/v3/csaf"
|
"github.com/gocsaf/csaf/v3/csaf"
|
||||||
|
"github.com/gocsaf/csaf/v3/internal/misc"
|
||||||
"github.com/gocsaf/csaf/v3/util"
|
"github.com/gocsaf/csaf/v3/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -301,7 +301,7 @@ func loadJSONFromFile(fname string) (any, error) {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
var doc any
|
var doc any
|
||||||
if err = json.NewDecoder(f).Decode(&doc); err != nil {
|
if err = misc.StrictJSONParse(f, &doc); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return doc, err
|
return doc, err
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/gocsaf/csaf/v3/internal/misc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Acknowledgement reflects the 'acknowledgement' object in the list of acknowledgements.
|
// Acknowledgement reflects the 'acknowledgement' object in the list of acknowledgements.
|
||||||
|
|
@ -383,7 +385,6 @@ type Relationship struct {
|
||||||
FullProductName *FullProductName `json:"full_product_name"` // required
|
FullProductName *FullProductName `json:"full_product_name"` // required
|
||||||
ProductReference *ProductID `json:"product_reference"` // required
|
ProductReference *ProductID `json:"product_reference"` // required
|
||||||
RelatesToProductReference *ProductID `json:"relates_to_product_reference"` // required
|
RelatesToProductReference *ProductID `json:"relates_to_product_reference"` // required
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relationships is a list of Relationship.
|
// Relationships is a list of Relationship.
|
||||||
|
|
@ -1391,7 +1392,7 @@ func LoadAdvisory(fname string) (*Advisory, error) {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
var advisory Advisory
|
var advisory Advisory
|
||||||
if err := json.NewDecoder(f).Decode(&advisory); err != nil {
|
if err := misc.StrictJSONParse(f, &advisory); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := advisory.Validate(); err != nil {
|
if err := advisory.Validate(); err != nil {
|
||||||
|
|
|
||||||
45
csaf/advisory_test.go
Normal file
45
csaf/advisory_test.go
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package csaf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadAdvisory(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
jsonDir string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{{
|
||||||
|
name: "Valid documents",
|
||||||
|
args: args{jsonDir: "csaf-documents/valid"},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Garbage trailing data",
|
||||||
|
args: args{jsonDir: "csaf-documents/trailing-garbage-data"},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := filepath.Walk("../testdata/"+tt.args.jsonDir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.Mode().IsRegular() && filepath.Ext(info.Name()) == ".json" {
|
||||||
|
if _, err := LoadAdvisory(path); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("LoadAdvisory() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/format"
|
"go/format"
|
||||||
|
|
@ -22,6 +21,8 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/gocsaf/csaf/v3/internal/misc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// We from Intevation consider the source code parts in the following
|
// We from Intevation consider the source code parts in the following
|
||||||
|
|
@ -98,7 +99,7 @@ func loadSchema(filename string) (*schema, error) {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
var s schema
|
var s schema
|
||||||
if err := json.NewDecoder(f).Decode(&s); err != nil {
|
if err := misc.StrictJSONParse(f, &s); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &s, nil
|
return &s, nil
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gocsaf/csaf/v3/internal/misc"
|
||||||
"github.com/gocsaf/csaf/v3/util"
|
"github.com/gocsaf/csaf/v3/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -575,7 +576,6 @@ func (d *Distribution) Validate() error {
|
||||||
// Validate checks if the provider metadata is valid.
|
// Validate checks if the provider metadata is valid.
|
||||||
// Returns an error if the validation fails otherwise nil.
|
// Returns an error if the validation fails otherwise nil.
|
||||||
func (pmd *ProviderMetadata) Validate() error {
|
func (pmd *ProviderMetadata) Validate() error {
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case pmd.CanonicalURL == nil:
|
case pmd.CanonicalURL == nil:
|
||||||
return errors.New("canonical_url is mandatory")
|
return errors.New("canonical_url is mandatory")
|
||||||
|
|
@ -695,8 +695,7 @@ func (pmd *ProviderMetadata) WriteTo(w io.Writer) (int64, error) {
|
||||||
func LoadProviderMetadata(r io.Reader) (*ProviderMetadata, error) {
|
func LoadProviderMetadata(r io.Reader) (*ProviderMetadata, error) {
|
||||||
|
|
||||||
var pmd ProviderMetadata
|
var pmd ProviderMetadata
|
||||||
dec := json.NewDecoder(r)
|
if err := misc.StrictJSONParse(r, &pmd); err != nil {
|
||||||
if err := dec.Decode(&pmd); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,13 @@ package csaf
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gocsaf/csaf/v3/internal/misc"
|
||||||
"github.com/gocsaf/csaf/v3/util"
|
"github.com/gocsaf/csaf/v3/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -149,7 +149,6 @@ func (pmdl *ProviderMetadataLoader) Enumerate(domain string) []*LoadedProviderMe
|
||||||
}
|
}
|
||||||
dnsURL := "https://csaf.data.security." + domain
|
dnsURL := "https://csaf.data.security." + domain
|
||||||
return []*LoadedProviderMetadata{pmdl.loadFromURL(dnsURL)}
|
return []*LoadedProviderMetadata{pmdl.loadFromURL(dnsURL)}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load loads one valid provider metadata for a given path.
|
// Load loads one valid provider metadata for a given path.
|
||||||
|
|
@ -323,7 +322,7 @@ func (pmdl *ProviderMetadataLoader) loadFromURL(path string) *LoadedProviderMeta
|
||||||
|
|
||||||
var doc any
|
var doc any
|
||||||
|
|
||||||
if err := json.NewDecoder(tee).Decode(&doc); err != nil {
|
if err := misc.StrictJSONParse(tee, &doc); err != nil {
|
||||||
result.Messages.Add(
|
result.Messages.Add(
|
||||||
JSONDecodingFailed,
|
JSONDecodingFailed,
|
||||||
fmt.Sprintf("JSON decoding failed: %v", err))
|
fmt.Sprintf("JSON decoding failed: %v", err))
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gocsaf/csaf/v3/internal/misc"
|
||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -180,7 +181,6 @@ func prepareCache(config string) (cache, error) {
|
||||||
return create()
|
return create()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
db.Close()
|
db.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -256,7 +256,7 @@ func deserialize(value []byte) (*RemoteValidationResult, error) {
|
||||||
}
|
}
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
var rvr RemoteValidationResult
|
var rvr RemoteValidationResult
|
||||||
if err := json.NewDecoder(r).Decode(&rvr); err != nil {
|
if err := misc.StrictJSONParse(r, &rvr); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &rvr, nil
|
return &rvr, nil
|
||||||
|
|
@ -323,7 +323,7 @@ func (v *remoteValidator) Validate(doc any) (*RemoteValidationResult, error) {
|
||||||
// no cache -> process directly.
|
// no cache -> process directly.
|
||||||
in = resp.Body
|
in = resp.Body
|
||||||
}
|
}
|
||||||
return json.NewDecoder(in).Decode(&rvr)
|
return misc.StrictJSONParse(in, &rvr)
|
||||||
}(); err != nil {
|
}(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gocsaf/csaf/v3/internal/misc"
|
||||||
"github.com/gocsaf/csaf/v3/util"
|
"github.com/gocsaf/csaf/v3/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -54,7 +55,7 @@ type ROLIEServiceDocument struct {
|
||||||
// LoadROLIEServiceDocument loads a ROLIE service document from a reader.
|
// LoadROLIEServiceDocument loads a ROLIE service document from a reader.
|
||||||
func LoadROLIEServiceDocument(r io.Reader) (*ROLIEServiceDocument, error) {
|
func LoadROLIEServiceDocument(r io.Reader) (*ROLIEServiceDocument, error) {
|
||||||
var rsd ROLIEServiceDocument
|
var rsd ROLIEServiceDocument
|
||||||
if err := json.NewDecoder(r).Decode(&rsd); err != nil {
|
if err := misc.StrictJSONParse(r, &rsd); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &rsd, nil
|
return &rsd, nil
|
||||||
|
|
@ -122,7 +123,7 @@ func (rcd *ROLIECategoryDocument) Merge(categories ...string) bool {
|
||||||
// LoadROLIECategoryDocument loads a ROLIE category document from a reader.
|
// LoadROLIECategoryDocument loads a ROLIE category document from a reader.
|
||||||
func LoadROLIECategoryDocument(r io.Reader) (*ROLIECategoryDocument, error) {
|
func LoadROLIECategoryDocument(r io.Reader) (*ROLIECategoryDocument, error) {
|
||||||
var rcd ROLIECategoryDocument
|
var rcd ROLIECategoryDocument
|
||||||
if err := json.NewDecoder(r).Decode(&rcd); err != nil {
|
if err := misc.StrictJSONParse(r, &rcd); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &rcd, nil
|
return &rcd, nil
|
||||||
|
|
@ -195,9 +196,8 @@ type ROLIEFeed struct {
|
||||||
|
|
||||||
// LoadROLIEFeed loads a ROLIE feed from a reader.
|
// LoadROLIEFeed loads a ROLIE feed from a reader.
|
||||||
func LoadROLIEFeed(r io.Reader) (*ROLIEFeed, error) {
|
func LoadROLIEFeed(r io.Reader) (*ROLIEFeed, error) {
|
||||||
dec := json.NewDecoder(r)
|
|
||||||
var rf ROLIEFeed
|
var rf ROLIEFeed
|
||||||
if err := dec.Decode(&rf); err != nil {
|
if err := misc.StrictJSONParse(r, &rf); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &rf, nil
|
return &rf, nil
|
||||||
|
|
|
||||||
35
internal/misc/json.go
Normal file
35
internal/misc/json.go
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
// 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: 2025 German Federal Office for Information Security (BSI) <https://www.bsi.bund.de>
|
||||||
|
// Software-Engineering: 2025 Intevation GmbH <https://intevation.de>
|
||||||
|
|
||||||
|
package misc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StrictJSONParse creates a JSON decoder that decodes an interface
|
||||||
|
// while not allowing unknown fields nor trailing data
|
||||||
|
func StrictJSONParse(jsonData io.Reader, target any) error {
|
||||||
|
decoder := json.NewDecoder(jsonData)
|
||||||
|
|
||||||
|
if err := decoder.Decode(target); err != nil {
|
||||||
|
return fmt.Errorf("JSON decoding error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for any trailing data after the main JSON structure
|
||||||
|
if _, err := decoder.Token(); err != io.EOF {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading trailing data: %w", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unexpected trailing data after JSON object")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
171
testdata/csaf-documents/trailing-garbage-data/avendor-advisory-0004.json
vendored
Normal file
171
testdata/csaf-documents/trailing-garbage-data/avendor-advisory-0004.json
vendored
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
{
|
||||||
|
"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"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
invalid data
|
||||||
170
testdata/csaf-documents/valid/avendor-advisory-0004.json
vendored
Normal file
170
testdata/csaf-documents/valid/avendor-advisory-0004.json
vendored
Normal file
|
|
@ -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"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue