aboutsummaryrefslogtreecommitdiff
path: root/internal/feed
diff options
context:
space:
mode:
authorPaul Duncan <pabs@pablotron.org>2022-01-31 11:09:58 -0500
committerPaul Duncan <pabs@pablotron.org>2022-01-31 11:09:58 -0500
commitb3dc36421f133ea6983574891720e974cf7974dd (patch)
treec6375903fa820bd0ac64ce40827c2c8302d38737 /internal/feed
downloadcvez-b3dc36421f133ea6983574891720e974cf7974dd.tar.bz2
cvez-b3dc36421f133ea6983574891720e974cf7974dd.zip
initial commit
Diffstat (limited to 'internal/feed')
-rw-r--r--internal/feed/feed.go770
-rw-r--r--internal/feed/meta.go104
2 files changed, 874 insertions, 0 deletions
diff --git a/internal/feed/feed.go b/internal/feed/feed.go
new file mode 100644
index 0000000..1c15a0a
--- /dev/null
+++ b/internal/feed/feed.go
@@ -0,0 +1,770 @@
+package feed
+
+import (
+ "encoding/json"
+ "fmt"
+ // "strconv"
+ "regexp"
+ "time"
+)
+
+const (
+ CveType = iota // CVE data type
+ MitreFormat // MITRE data format
+ DataVersion40 // Version 4.0
+
+ OrNodeOp // OR operator
+ AndNodeOp // And operator
+
+ AdjacentNetwork // Adjacent Network attack vector.
+ Network // Network attack vector.
+ Local // Local attack vector.
+ Physical // Physical attack vector.
+
+ None // no priv req/user interaction
+ Low // low complexity/priv req
+ Medium // medium complexity/priv req
+ High // high complexity/priv req
+
+ Required // user interaction required
+
+ Changed // scope changed
+ Unchanged // scope unchanged
+
+ Complete // complete integrity impact
+ Partial // partial integrity impact
+
+ Critical // critical severity
+
+ CvssVersion31 // CVSS version 3.1
+ CvssVersion20 // CVSS version 2.0
+
+ Single // Single authentication
+)
+
+// TODO: parse cpe, cvss vectors (v3.x, v2)
+
+// Data type for NVD feeds and feed items.
+type DataType int
+
+// Unmarshal DataType from JSON.
+func (me *DataType) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "CVE":
+ *me = CveType
+ default:
+ // return error
+ return fmt.Errorf("unknown data type: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// Data format for NVD feeds and feed items.
+type DataFormat int
+
+// Unmarshal DataFormat from JSON.
+func (me *DataFormat) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "MITRE":
+ *me = MitreFormat
+ default:
+ // return error
+ return fmt.Errorf("unknown data format: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// Data version for NVD feeds and feed items.
+type DataVersion int
+
+// Unmarshal DataVersion from JSON.
+func (me *DataVersion) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "4.0":
+ *me = DataVersion40
+ default:
+ // return error
+ return fmt.Errorf("unknown data version: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// partial timestamp
+type PartialTime time.Time
+
+var partialTimeRe = regexp.MustCompile("\\A\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}Z\\z")
+
+// Unmarshal partial timestamp from JSON.
+func (me *PartialTime) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // match partial string regex
+ if !partialTimeRe.MatchString(s) {
+ return fmt.Errorf("invalid partial time string: %s", s)
+ }
+
+ // correct string suffix
+ s = s[0:16] + ":00Z"
+
+ // unmarshal time
+ var t time.Time
+ if err := t.UnmarshalText([]byte(s)); err != nil {
+ return err
+ }
+
+ // return success
+ return nil
+}
+
+// Configuration node boolean operator.
+type NodeOp int
+
+// Unmarshal DataVersion from JSON.
+func (me *NodeOp) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "AND":
+ *me = AndNodeOp
+ case "OR":
+ *me = OrNodeOp
+ default:
+ // return error
+ return fmt.Errorf("unknown operator: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// CVSS attack vector
+type AttackVector int
+
+// Unmarshal CVSS attack vector from JSON.
+func (me *AttackVector) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "ADJACENT_NETWORK":
+ *me = AdjacentNetwork
+ case "LOCAL":
+ *me = Local
+ case "NETWORK":
+ *me = Network
+ case "PHYSICAL":
+ *me = Physical
+ default:
+ // return error
+ return fmt.Errorf("unknown attack vector: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// CVSS attack complexity
+type AttackComplexity int
+
+// Unmarshal CVSS attack complexity from JSON.
+func (me *AttackComplexity) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "LOW":
+ *me = Low
+ case "MEDIUM":
+ *me = Medium
+ case "HIGH":
+ *me = High
+ default:
+ // return error
+ return fmt.Errorf("unknown attack complexity: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// CVSS privileges required
+type PrivilegesRequired int
+
+// Unmarshal CVSS privileges required from JSON.
+func (me *PrivilegesRequired) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "NONE":
+ *me = None
+ case "LOW":
+ *me = Low
+ case "MEDIUM":
+ *me = Medium
+ case "HIGH":
+ *me = High
+ default:
+ // return error
+ return fmt.Errorf("unknown privileges required: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// CVSS user interaction
+type UserInteraction int
+
+// Unmarshal CVSS user interaction from JSON.
+func (me *UserInteraction) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "NONE":
+ *me = None
+ case "REQUIRED":
+ *me = Required
+ default:
+ // return error
+ return fmt.Errorf("unknown user interaction: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// CVSS scope
+type Scope int
+
+// Unmarshal CVSS scope from JSON.
+func (me *Scope) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "CHANGED":
+ *me = Changed
+ case "UNCHANGED":
+ *me = Unchanged
+ default:
+ // return error
+ return fmt.Errorf("unknown scope: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// CVSS integrity/availability impact level
+type ImpactLevel int
+
+// Unmarshal CVSS integrity/availability impact level from JSON.
+func (me *ImpactLevel) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "NONE":
+ *me = None
+ case "LOW":
+ *me = Low
+ case "PARTIAL":
+ *me = Partial
+ case "HIGH":
+ *me = High
+ case "COMPLETE":
+ *me = Complete
+ default:
+ // return error
+ return fmt.Errorf("unknown impact level: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// CVSS score
+type Score float32
+
+// Unmarshal CVSS score from JSON.
+func (me *Score) UnmarshalJSON(b []byte) error {
+ // decode float, check for error
+ var v float32
+ if err := json.Unmarshal(b, &v); err != nil {
+ return err
+ }
+
+ // check score
+ if v < 0.0 || v > 10.0 {
+ return fmt.Errorf("score out of bounds: %f", v)
+ }
+
+ // save result, return success
+ *me = Score(v)
+ return nil
+}
+
+// CVSS severity
+type Severity int
+
+// Unmarshal CVSS severity from JSON.
+func (me *Severity) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "LOW":
+ *me = Low
+ case "MEDIUM":
+ *me = Medium
+ case "HIGH":
+ *me = High
+ case "CRITICAL":
+ *me = Critical
+ default:
+ // return error
+ return fmt.Errorf("unknown severity: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+type AccessVector int
+
+// Unmarshal CVSS V2 access vector from JSON.
+func (me *AccessVector) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "ADJACENT_NETWORK":
+ *me = AdjacentNetwork
+ case "LOCAL":
+ *me = Local
+ case "NETWORK":
+ *me = Network
+ default:
+ // return error
+ return fmt.Errorf("unknown CVSS access vector: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// CVSS V2 attack complexity
+type AccessComplexity int
+
+// Unmarshal CVSS V2 access complexity from JSON.
+func (me *AccessComplexity) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "LOW":
+ *me = Low
+ case "MEDIUM":
+ *me = Medium
+ case "HIGH":
+ *me = High
+ default:
+ // return error
+ return fmt.Errorf("unknown access complexity: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+
+// CVSS V2 authentication
+type Authentication int
+
+// Unmarshal CVSS V2 authentication from JSON.
+func (me *Authentication) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "NONE":
+ *me = None
+ case "SINGLE":
+ *me = Single
+ default:
+ // return error
+ return fmt.Errorf("unknown authentication: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// CVE metadata
+type CveMetadata struct {
+ // CVE ID
+ Id string `json:"ID"`
+
+ // CVE assigner email address
+ Assigner string `json:"ASSIGNER"`
+}
+
+// CVE description string.
+type Description struct {
+ // Language code
+ Lang string `json:"lang"`
+
+ // String value
+ Value string `json:"value"`
+}
+
+// CVE problem type
+type CveProblemType struct {
+ // problem type descriptions
+ Descriptions []Description `json:"description"`
+}
+
+// Slice of CVE problem types.
+type CveProblemTypes struct {
+ // problem types
+ ProblemTypes []CveProblemType `json:"problemtype_data"`
+}
+
+// CVE reference
+type CveReference struct {
+ // reference URL
+ Url string `json:"url"`
+
+ // reference name
+ Name string `json:"name"`
+
+ // reference source
+ RefSource string `json:"refsource"`
+
+ // tags
+ Tags []string `json:"tags"`
+}
+
+// Slice of CVE references
+type CveReferences struct {
+ References []CveReference `json:"reference_data"`
+}
+
+// CVE item descriptions
+type CveDescription struct {
+ // slice of descriptions
+ Descriptions []Description `json:"description_data"`
+}
+
+// CVE data
+type Cve struct {
+ // feed data type
+ DataType DataType `json:"CVE_data_type"`
+
+ // feed data format
+ DataFormat DataFormat `json:"CVE_data_format"`
+
+ // feed data format version
+ DataVersion DataVersion `json:"CVE_data_version"`
+
+ // CVE metadata
+ Metadata CveMetadata `json:"CVE_data_meta"`
+
+ // CVE problem types
+ ProblemTypes CveProblemTypes `json:"problemtype"`
+
+ // CVE references
+ References CveReferences `json:"references"`
+
+ // CVE description
+ Description CveDescription `json:"description"`
+}
+
+// CPE match
+type CpeMatch struct {
+ // Vulnerable?
+ Vulnerable bool `json:"vulnerable"`
+
+ VersionEndExcluding string `json:"versionEndExcluding"`
+
+ // CPE URI (FIXME: decode this)
+ Cpe23Uri string `json:"cpe23Uri"`
+
+ // CPE names (not sure if this is correct)
+ Names []string `json:"cpe_name"`
+}
+
+// CVE item configuration node
+type ConfigurationNode struct {
+ // node operator
+ Operator NodeOp `json:"operator"`
+
+ // node children
+ Children []ConfigurationNode `json:"children"`
+
+ CpeMatches []CpeMatch `json:"cpe_match"`
+}
+
+// CVE item configurations
+type ItemConfigurations struct {
+ // data version
+ DataVersion DataVersion `json:"CVE_data_version"`
+
+ // slice of configuration nodes
+ Nodes []ConfigurationNode `json:"nodes"`
+}
+
+// CVSS version.
+type CvssV3Version int
+
+// Unmarshal CVSS version from JSON.
+func (me *CvssV3Version) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "3.1":
+ *me = CvssVersion31
+ default:
+ // return error
+ return fmt.Errorf("unknown CVSS V3 version: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// CVSS V2 version.
+type CvssV2Version int
+
+// Unmarshal CVSS version from JSON.
+func (me *CvssV2Version) UnmarshalJSON(b []byte) error {
+ // decode string, check for error
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+
+ // check value
+ switch s {
+ case "2.0":
+ *me = CvssVersion20
+ default:
+ // return error
+ return fmt.Errorf("unknown CVSS V2 version: %s", s)
+ }
+
+ // return success
+ return nil
+}
+
+// CVSS V3
+type CvssV3 struct {
+ // CVSS V3 version
+ Version CvssV3Version `json:"version"`
+
+ // CVSS V3 vector string (FIXME: add custom type)
+ VectorString string `json:"vectorString"`
+
+ // attack vector
+ AttackVector AttackVector `json:"attackVector"`
+
+ // attack complexity
+ AttackComplexity AttackComplexity `json:"attackComplexity"`
+
+ // privileges required
+ PrivilegesRequired PrivilegesRequired `json:"privilegesRequired"`
+
+ // user interaction
+ UserInteraction UserInteraction `json:"userInteraction"`
+
+ // scope
+ Scope Scope `json:"scope"`
+
+ // integrity impact
+ IntegrityImpact ImpactLevel `json:"integrityImpact"`
+
+ // availability impact
+ AvailabilityImpact ImpactLevel `json:"availabilityImpact"`
+
+ // base score
+ BaseScore Score `json:"baseScore"`
+
+ // base severity
+ BaseSeverity Severity `json:"baseSeverity"`
+}
+
+// CVSS V3 base metrics
+type BaseMetricV3 struct {
+ CvssV3 CvssV3 `json:"cvssV3"`
+ ExploitabilityScore Score `json:"exploitabilityScore"`
+ ImpactScore Score `json:"impactScore"`
+}
+
+// CVSS V2
+type CvssV2 struct {
+ // CVSS V2 version
+ Version CvssV2Version `json:"version"`
+
+ // CVSS V3 vector string (FIXME: add custom type)
+ VectorString string `json:"vectorString"`
+
+ // attack vector
+ AccessVector AccessVector `json:"accessVector"`
+
+ // attack complexity
+ AccessComplexity AccessComplexity `json:"accessComplexity"`
+
+ // authentication
+ Authentication Authentication `json:"authentication"`
+
+ ConfidentialityImpact ImpactLevel `json:"confidentialityImpact"`
+ IntegrityImpact ImpactLevel `json:"integrityImpact"`
+ AvailabilityImpact ImpactLevel `json:"availabilityImpact"`
+
+ // base score
+ BaseScore Score `json:"baseScore"`
+}
+
+// CVSS V2 base metrics
+type BaseMetricV2 struct {
+ CvssV2 CvssV2 `json:"cvssV2"`
+ severity Severity `json:"severity"`
+ ExploitabilityScore Score `json:"impactScore"`
+ ImpactScore Score `json:"impactScore"`
+ InsufficientInfo bool `json:"acInsufInfo"`
+ ObtainAllPrivilege bool `json:"obtainAllPrivilege"`
+ ObtainUserPrivilege bool `json:"obtainUserPrivilege"`
+ ObtainOtherPrivilege bool `json:"obtainOtherPrivilege"`
+ UserInteractionRequired bool `json:"userInteractionRequired"`
+}
+
+// Item impact
+type Impact struct {
+ // CVSS V3 base metrics
+ BaseMetricV3 BaseMetricV3 `json:"baseMetricV3"`
+
+ // CVSS V2 base metrics
+ BaseMetricV2 BaseMetricV2 `json:"baseMetricV2"`
+}
+
+// CVE feed item
+type Item struct {
+ // item CVE data
+ Cve Cve `json:"cve"`
+
+ // item configuration
+ Configurations ItemConfigurations `json:"configurations"`
+
+ // item impact
+ Impact Impact `json:"impact"`
+
+ // item published date
+ PublishedDate PartialTime `json:"publishedDate"`
+
+ // last modification date
+ LastModifiedDate PartialTime `json:"lastModifiedDate"`
+}
+
+// NVD feed
+type Feed struct {
+ // feed data type
+ DataType DataType `json:"CVE_data_type"`
+
+ // feed data format
+ DataFormat DataFormat `json:"CVE_data_format"`
+
+ // feed data format version
+ DataVersion DataVersion `json:"CVE_data_version"`
+
+ // number of CVEs in feed
+ NumCVEs uint64 `json:"CVE_data_numberOfCVEs,string"`
+
+ // data timestamp
+ Timestamp PartialTime `json:"CVE_data_timestamp"`
+
+ // CVE items
+ Items []Item `json:"CVE_Items"`
+}
diff --git a/internal/feed/meta.go b/internal/feed/meta.go
new file mode 100644
index 0000000..da2849d
--- /dev/null
+++ b/internal/feed/meta.go
@@ -0,0 +1,104 @@
+package feed
+
+import (
+ "bufio"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// NVD metadata.
+type Meta struct {
+ LastModifiedDate time.Time // last modified time
+ Size uint64 // uncompressed size, in bytes
+ ZipSize uint64 // zip file size, in bytes
+ GzSize uint64 // gz file size, in bytes
+ Sha256 [32]byte // sha256 hash of uncompressed data
+}
+
+func parseMetaSize(name, val string) (uint64, error) {
+ // parse value, check for error
+ v, err := strconv.ParseUint(val, 10, 64)
+ if err == nil {
+ // return size
+ return v, nil
+ } else {
+ // return error
+ return 0, fmt.Errorf("invalid %s: \"%s\"", name, val)
+ }
+}
+
+// Unmarshal new Metadata from reader.
+func NewMeta(r io.Reader) (*Meta, error) {
+ // declare result
+ var m Meta
+
+ // create scanner
+ scanner := bufio.NewScanner(r)
+
+ // read lines
+ for scanner.Scan() {
+ // split into key/value pair, check for error
+ pair := strings.SplitN(scanner.Text(), ":", 2)
+ if len(pair) != 2 {
+ return nil, fmt.Errorf("bad meta line: \"%s\"", scanner.Text())
+ }
+
+ switch pair[0] {
+ case "lastModifiedDate":
+ // parse time, check for error
+ if err := m.LastModifiedDate.UnmarshalText([]byte(pair[1])); err != nil {
+ return nil, err
+ }
+ case "size":
+ if v, err := parseMetaSize("size", pair[1]); err == nil {
+ m.Size = v
+ } else {
+ return nil, err
+ }
+ case "zipSize":
+ if v, err := parseMetaSize("zipSize", pair[1]); err == nil {
+ m.ZipSize = v
+ } else {
+ return nil, err
+ }
+ case "gzSize":
+ if v, err := parseMetaSize("gzSize", pair[1]); err == nil {
+ m.GzSize = v
+ } else {
+ return nil, err
+ }
+ case "sha256":
+ // check hash length
+ if len(pair[1]) != 64 {
+ return nil, fmt.Errorf("invalid sha256 hash length: \"%d\"", len(pair[1]))
+ }
+
+ // decode hex, check for error
+ buf, err := hex.DecodeString(pair[1])
+ if err != nil {
+ return nil, fmt.Errorf("invalid sha256 hash: %v", err)
+ }
+
+ // save to buffer, check for error
+ len := copy(m.Sha256[:], buf[0:32])
+ if len != 32 {
+ return nil, fmt.Errorf("invalid sha256 hash length: %d", len)
+ }
+ default:
+ // return error
+ return nil, fmt.Errorf("unknown key: \"%s\"", pair[0])
+ }
+ }
+
+ // check for scanner error
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+
+ // return success
+ return &m, nil
+}