aboutsummaryrefslogtreecommitdiff
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
downloadcvez-b3dc36421f133ea6983574891720e974cf7974dd.tar.bz2
cvez-b3dc36421f133ea6983574891720e974cf7974dd.zip
initial commit
-rw-r--r--.gitignore2
-rw-r--r--go.mod10
-rw-r--r--go.sum8
-rw-r--r--internal/cvss/cvss.go926
-rw-r--r--internal/cvss/metriccategory_string.go25
-rw-r--r--internal/cvss/v2metric_string.go77
-rw-r--r--internal/cvss/v2metrickey_string.go36
-rw-r--r--internal/cvss/v3metric_string.go100
-rw-r--r--internal/cvss/v3metrickey_string.go44
-rw-r--r--internal/cvss/version_string.go25
-rw-r--r--internal/feed/feed.go770
-rw-r--r--internal/feed/meta.go104
-rw-r--r--main.go88
13 files changed, 2215 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fc024e4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.sw?
+nvd
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..5e67b9a
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,10 @@
+module nvd
+
+go 1.18
+
+require (
+ golang.org/x/mod v0.5.1 // indirect
+ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
+ golang.org/x/tools v0.1.9 // indirect
+ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..598fd54
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,8 @@
+golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
+golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
+golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/cvss/cvss.go b/internal/cvss/cvss.go
new file mode 100644
index 0000000..e50f718
--- /dev/null
+++ b/internal/cvss/cvss.go
@@ -0,0 +1,926 @@
+// CVSS vector parser.
+package cvss
+
+import (
+ "fmt"
+ "strings"
+)
+
+//go:generate stringer -linecomment -type=Version
+//go:generate stringer -linecomment -type=MetricCategory
+//go:generate stringer -linecomment -type=V2MetricKey
+//go:generate stringer -linecomment -type=V2Metric
+//go:generate stringer -linecomment -type=V3MetricKey
+//go:generate stringer -linecomment -type=V3Metric
+
+// CVSS version
+type Version byte
+
+const (
+ V20 Version = iota // 2.0
+ V30 // 3.0
+ V31 // 3.1
+)
+
+// CVSS metric category.
+type MetricCategory byte
+
+const (
+ Base MetricCategory = iota // Base
+ Temporal // Temporal
+ Environmental // Environmental
+)
+
+// CVSS metric key
+type V2MetricKey byte
+
+const (
+ V2AccessVector V2MetricKey = iota // AV
+ V2AccessComplexity // AC
+ V2Authentication // Au
+ V2ConfidentialityImpact // C
+ V2IntegrityImpact // I
+ V2AvailabilityImpact // A
+ V2Exploitability // E
+ V2RemediationLevel // RL
+ V2ReportConfidence // RC
+ V2CollateralDamagePotential // CDP
+ V2TargetDistribution // TD
+ V2ConfidentialityRequirement // CR
+ V2IntegrityRequirement // IR
+ V2AvailabilityRequirement // AR
+)
+
+// CVSS V2 metric key info lut
+var v2MetricKeys = map[V2MetricKey]struct {
+ Name string
+ Category MetricCategory
+} {
+ V2AccessVector: { "Access Vector", Base },
+ V2AccessComplexity: { "Access Complexity", Base },
+ V2Authentication: { "Authentication", Base },
+ V2ConfidentialityImpact: { "Confidentiality Impact", Base },
+ V2IntegrityImpact: { "Integrity Impact", Base },
+ V2AvailabilityImpact: { "Availability Impact", Base },
+ V2Exploitability: { "Exploitability", Temporal },
+ V2RemediationLevel: { "Remediation Level", Temporal },
+ V2ReportConfidence: { "Report Confidence", Temporal },
+ V2CollateralDamagePotential: { "Collateral Damage Potential", Environmental },
+ V2TargetDistribution: { "Target Distribution", Environmental },
+ V2ConfidentialityRequirement: { "Confidentiality Requirement", Environmental },
+ V2IntegrityRequirement: { "Integrity Requirement", Environmental },
+ V2AvailabilityRequirement: { "Availability Requirement", Environmental },
+}
+
+// v2 metric key IDs lut
+var v2MetricKeyIds = map[string]V2MetricKey {
+ "AV": V2AccessVector,
+ "AC": V2AccessComplexity,
+ "Au": V2Authentication,
+ "C": V2ConfidentialityImpact,
+ "I": V2IntegrityImpact,
+ "A": V2AvailabilityImpact,
+ "E": V2Exploitability,
+ "RL": V2RemediationLevel,
+ "RC": V2ReportConfidence,
+ "CDP": V2CollateralDamagePotential,
+ "TD": V2TargetDistribution,
+ "CR": V2ConfidentialityRequirement,
+ "IR": V2IntegrityRequirement,
+ "AR": V2AvailabilityRequirement,
+}
+
+// Get metric key from string.
+func GetV2MetricKeyFromString(s string) (V2MetricKey, error) {
+ k, ok := v2MetricKeyIds[s]
+ if ok {
+ return k, nil
+ } else {
+ return V2AccessVector, fmt.Errorf("unknown metric key: %s", s)
+ }
+}
+
+// Get metric key name.
+func (k V2MetricKey) Name() string {
+ return v2MetricKeys[k].Name
+}
+
+// Get metric key category.
+func (k V2MetricKey) Category() MetricCategory {
+ return v2MetricKeys[k].Category
+}
+
+// CVSS v2 metric value
+type V2Metric byte
+
+const (
+ V2AVNetwork V2Metric = iota // AV:N
+ V2AVAdjacentNetwork // AV:A
+ V2AVLocal // AV:L
+
+ V2ACLow // AC:L
+ V2ACMedium // AC:L
+ V2ACHigh // AC:H
+
+ V2AuMultiple // Au:M
+ V2AuSingle // Au:S
+ V2AuNone // Au:N
+
+ V2CNone // C:N
+ V2CPartial // C:P
+ V2CComplete // C:C
+
+ V2INone // I:N
+ V2IPartial // I:P
+ V2IComplete // I:C
+
+ V2ANone // A:N
+ V2APartial // A:P
+ V2AComplete // A:C
+
+ V2ENotDefined // E:ND
+ V2EUnproven // E:U
+ V2EProofOfConcept // E:POC
+ V2EFunctional // E:F
+ V2EHigh // E:H
+
+ V2RLOfficialFix // RL:OF
+ V2RLTemporaryFix // RL:TF
+ V2RLWorkaround // RL:W
+ V2RLUnavailable // RL:U
+ V2RLNotDefined // RL:ND
+
+ V2RCUnconfirmed // RC:UC
+ V2RCUncorroborated // RC:UR
+ V2RCConfirmed // RC:C
+ V2RCNotDefined // RC:ND
+
+ V2CDPNone // CDP:N
+ V2CDPLow // CDP:L
+ V2CDPLowMedium // CDP:LM
+ V2CDPMediumHigh // CDP:MH
+ V2CDPHigh // CDP:H
+ V2CDPNotDefined // CDP:ND
+
+ V2TDNone // TD:N
+ V2TDLow // TD:L
+ V2TDMedium // TD:M
+ V2TDHigh // TD:H
+ V2TDNotDefined // TD:ND
+
+ V2CRLow // CR:L
+ V2CRMedium // CR:M
+ V2CRHigh // CR:H
+ V2CRNotDefined // CR:ND
+
+ V2IRLow // IR:L
+ V2IRMedium // IR:M
+ V2IRHigh // IR:H
+ V2IRNotDefined // IR:ND
+
+ V2ARLow // AR:L
+ V2ARMedium // AR:M
+ V2ARHigh // AR:H
+ V2ARNotDefined // AR:ND
+)
+
+// map of metrics to metric keys
+var v2MetricKeyLut = map[V2Metric]V2MetricKey {
+ V2AVNetwork: V2AccessVector,
+ V2AVAdjacentNetwork: V2AccessVector,
+ V2AVLocal: V2AccessVector,
+
+ V2ACLow: V2AccessComplexity,
+ V2ACMedium: V2AccessComplexity,
+ V2ACHigh: V2AccessComplexity,
+
+ V2AuMultiple: V2Authentication,
+ V2AuSingle: V2Authentication,
+ V2AuNone: V2Authentication,
+
+ V2CNone: V2ConfidentialityImpact,
+ V2CPartial: V2ConfidentialityImpact,
+ V2CComplete: V2ConfidentialityImpact,
+
+ V2INone: V2IntegrityImpact,
+ V2IPartial: V2IntegrityImpact,
+ V2IComplete: V2IntegrityImpact,
+
+ V2ANone: V2AvailabilityImpact,
+ V2APartial: V2AvailabilityImpact,
+ V2AComplete: V2AvailabilityImpact,
+
+ V2ENotDefined: V2Exploitability,
+ V2EUnproven: V2Exploitability,
+ V2EProofOfConcept: V2Exploitability,
+ V2EFunctional: V2Exploitability,
+ V2EHigh: V2Exploitability,
+
+ V2RLOfficialFix: V2RemediationLevel,
+ V2RLTemporaryFix: V2RemediationLevel,
+ V2RLWorkaround: V2RemediationLevel,
+ V2RLUnavailable: V2RemediationLevel,
+ V2RLNotDefined: V2RemediationLevel,
+
+ V2RCUnconfirmed: V2ReportConfidence,
+ V2RCUncorroborated: V2ReportConfidence,
+ V2RCConfirmed: V2ReportConfidence,
+ V2RCNotDefined: V2ReportConfidence,
+
+ V2CDPNone: V2CollateralDamagePotential,
+ V2CDPLow: V2CollateralDamagePotential,
+ V2CDPLowMedium: V2CollateralDamagePotential,
+ V2CDPMediumHigh: V2CollateralDamagePotential,
+ V2CDPHigh: V2CollateralDamagePotential,
+ V2CDPNotDefined: V2CollateralDamagePotential,
+
+ V2TDNone: V2TargetDistribution,
+ V2TDLow: V2TargetDistribution,
+ V2TDMedium: V2TargetDistribution,
+ V2TDHigh: V2TargetDistribution,
+ V2TDNotDefined: V2TargetDistribution,
+
+ V2CRLow: V2ConfidentialityRequirement,
+ V2CRMedium: V2ConfidentialityRequirement,
+ V2CRHigh: V2ConfidentialityRequirement,
+ V2CRNotDefined: V2ConfidentialityRequirement,
+
+ V2IRLow: V2IntegrityRequirement,
+ V2IRMedium: V2IntegrityRequirement,
+ V2IRHigh: V2IntegrityRequirement,
+ V2IRNotDefined: V2IntegrityRequirement,
+
+ V2ARLow: V2AvailabilityRequirement,
+ V2ARMedium: V2AvailabilityRequirement,
+ V2ARHigh: V2AvailabilityRequirement,
+ V2ARNotDefined: V2AvailabilityRequirement,
+}
+
+// map of metric strings to metrics
+var v2MetricStrLut = map[string]V2Metric {
+ "AV:N": V2AVNetwork,
+ "AV:A": V2AVAdjacentNetwork,
+ "AV:L": V2AVLocal,
+
+ "AC:L": V2ACLow,
+ "AC:M": V2ACMedium,
+ "AC:H": V2ACHigh,
+
+ "Au:M": V2AuMultiple,
+ "Au:S": V2AuSingle,
+ "Au:N": V2AuNone,
+
+ "C:N": V2CNone,
+ "C:P": V2CPartial,
+ "C:C": V2CComplete,
+
+ "I:N": V2INone,
+ "I:P": V2IPartial,
+ "I:C": V2IComplete,
+
+ "A:N": V2ANone,
+ "A:P": V2APartial,
+ "A:C": V2AComplete,
+
+ "E:ND": V2ENotDefined,
+ "E:U": V2EUnproven,
+ "E:POC": V2EProofOfConcept,
+ "E:F": V2EFunctional,
+ "E:H": V2EHigh,
+
+ "RL:OF": V2RLOfficialFix,
+ "RL:TF": V2RLTemporaryFix,
+ "RL:W": V2RLWorkaround,
+ "RL:U": V2RLUnavailable,
+ "RL:ND": V2RLNotDefined,
+
+ "RC:UC": V2RCUnconfirmed,
+ "RC:UR": V2RCUncorroborated,
+ "RC:C": V2RCConfirmed,
+ "RC:ND": V2RCNotDefined,
+
+ "CDP:N": V2CDPNone,
+ "CDP:L": V2CDPLow,
+ "CDP:LM": V2CDPLowMedium,
+ "CDP:MH": V2CDPMediumHigh,
+ "CDP:H": V2CDPHigh,
+ "CDP:ND": V2CDPNotDefined,
+
+ "TD:N": V2TDNone,
+ "TD:L": V2TDLow,
+ "TD:M": V2TDMedium,
+ "TD:H": V2TDHigh,
+ "TD:ND": V2TDNotDefined,
+
+ "CR:L": V2CRLow,
+ "CR:M": V2CRMedium,
+ "CR:H": V2CRHigh,
+ "CR:ND": V2CRNotDefined,
+
+ "IR:L": V2IRLow,
+ "IR:M": V2IRMedium,
+ "IR:H": V2IRHigh,
+ "IR:ND": V2IRNotDefined,
+
+ "AR:L": V2ARLow,
+ "AR:M": V2ARMedium,
+ "AR:H": V2ARHigh,
+ "AR:ND": V2ARNotDefined,
+}
+
+// Convert string to CVSS 2.0 metric.
+func GetV2MetricFromString(s string) (V2Metric, error) {
+ // get metric
+ m, ok := v2MetricStrLut[s]
+ if !ok {
+ return V2AVNetwork, fmt.Errorf("invalid metric: %s", s)
+ }
+
+ // return success
+ return m, nil
+}
+
+// CVSS 2.0 vector.
+type v2Vector []V2Metric
+
+// Convert vector to string
+func (v v2Vector) String() string {
+ // convert to slice of metrics
+ metrics := []V2Metric(v)
+
+ // build vector
+ r := make([]string, len(metrics))
+ for i, m := range(metrics) {
+ r[i] = m.String()
+ }
+
+ // build and return string
+ return strings.Join(r, "/")
+}
+
+// Return CVSS version.
+func (v2Vector) Version() Version {
+ return V20
+}
+
+// create CVSS 2.0 vector from string
+func NewV2VectorFromString(s string) (Vector, error) {
+ strs := strings.Split(s, "/")
+ r := make([]V2Metric, len(strs))
+
+ // walk metric strings
+ for i, ms := range(strs) {
+ // convert string to vector
+ m, err := GetV2MetricFromString(ms)
+ if err != nil {
+ return nil, err
+ }
+
+ // add to results
+ r[i] = m
+ }
+
+ // build and return vector
+ return v2Vector(r), nil
+}
+
+// CVSS v3 metric key
+type V3MetricKey byte
+
+const (
+ V3AttackVector V3MetricKey = iota // AV
+ V3AttackComplexity // AC
+ V3PrivilegesRequired // PR
+ V3UserInteraction // UI
+ V3Scope // S
+ V3Confidentiality // C
+ V3Integrity // I
+ V3Availability // A
+ V3ExploitCodeMaturity // E
+ V3RemediationLevel // RL
+ V3ReportConfidence // RC
+ V3ConfidentialityRequirement // CR
+ V3IntegrityRequirement // IR
+ V3AvailabilityRequirement // AR
+ V3ModifiedAttackVector // MAV
+ V3ModifiedAttackComplexity // MAC
+ V3ModifiedPrivilegesRequired // MPR
+ V3ModifiedUserInteraction // MUI
+ V3ModifiedScope // MS
+ V3ModifiedConfidentiality // MC
+ V3ModifiedIntegrity // MI
+ V3ModifiedAvailability // MA
+)
+
+// CVSS V3 metric key info lut
+var v3MetricKeys = map[V3MetricKey]struct {
+ Name string
+ Category MetricCategory
+} {
+ V3AttackVector: { "Attack Vector", Base },
+ V3AttackComplexity: { "Attack Complexity", Base },
+ V3PrivilegesRequired: { "Privileges Required", Base },
+ V3UserInteraction: { "User Interaction", Base },
+ V3Scope: { "Scope", Base },
+ V3Confidentiality: { "Confidentiality", Base },
+ V3Integrity: { "Integrity", Base },
+ V3Availability: { "Availability", Base },
+ V3ExploitCodeMaturity: { "Exploit Code Maturity", Temporal },
+ V3RemediationLevel: { "Remediation Level", Temporal },
+ V3ReportConfidence: { "Report Confidence", Temporal },
+ V3ConfidentialityRequirement: { "Confidentiality Requirement", Environmental },
+ V3IntegrityRequirement: { "Integrity Requirement", Environmental },
+ V3AvailabilityRequirement: { "Availability Requirement", Environmental },
+ V3ModifiedAttackVector: { "Modified Attack Vector", Environmental },
+ V3ModifiedAttackComplexity: { "Modified Attack Complexity", Environmental },
+ V3ModifiedPrivilegesRequired: { "Modified Privileges Required", Environmental },
+ V3ModifiedUserInteraction: { "Modified User Interaction", Environmental },
+ V3ModifiedScope: { "Modified Scope", Environmental },
+ V3ModifiedConfidentiality: { "Modified Confidentiality", Environmental },
+ V3ModifiedIntegrity: { "Modified Integrity", Environmental },
+ V3ModifiedAvailability: { "Modified Availability", Environmental },
+}
+
+// metric key IDs lut
+var v3MetricKeyIds = map[string]V3MetricKey {
+ "AV": V3AttackVector,
+ "AC": V3AttackComplexity,
+ "PR": V3PrivilegesRequired,
+ "UI": V3UserInteraction,
+ "S": V3Scope,
+ "C": V3Confidentiality,
+ "I": V3Integrity,
+ "A": V3Availability,
+ "E": V3ExploitCodeMaturity,
+ "RL": V3RemediationLevel,
+ "RC": V3ReportConfidence,
+ "CR": V3ConfidentialityRequirement,
+ "IR": V3IntegrityRequirement,
+ "AR": V3AvailabilityRequirement,
+ "MAV": V3ModifiedAttackVector,
+ "MAC": V3ModifiedAttackComplexity,
+ "MPR": V3ModifiedPrivilegesRequired,
+ "MUI": V3ModifiedUserInteraction,
+ "MS": V3ModifiedScope,
+ "MC": V3ModifiedConfidentiality,
+ "MI": V3ModifiedIntegrity,
+ "MA": V3ModifiedAvailability,
+}
+
+// Get metric key from string.
+func GetV3MetricKeyFromString(s string) (V3MetricKey, error) {
+ k, ok := v3MetricKeyIds[s]
+ if ok {
+ return k, nil
+ } else {
+ return V3AttackVector, fmt.Errorf("unknown metric key: %s", s)
+ }
+}
+
+// Get metric key name.
+func (k V3MetricKey) Name() string {
+ return v3MetricKeys[k].Name
+}
+
+// Get metric key category.
+func (k V3MetricKey) Category() MetricCategory {
+ return v3MetricKeys[k].Category
+}
+
+// metric value
+type V3Metric byte
+
+const (
+ V3AVNetwork V3Metric = iota // AV:N
+ V3AVAdjacentNetwork // AV:A
+ V3AVLocal // AV:L
+ V3AVPhysical // AV:P
+
+ V3ACLow // AC:L
+ V3ACHigh // AC:H
+
+ V3PRNone // PR:N
+ V3PRLow // PR:L
+ V3PRHigh // PR:H
+
+ V3UINone // UI:N
+ V3UIRequired // UI:R
+
+ V3SUnchanged // S:U
+ V3SChanged // S:C
+
+ V3CHigh // C:H
+ V3CLow // C:L
+ V3CNone // C:N
+
+ V3IHigh // I:H
+ V3ILow // I:L
+ V3INone // I:N
+
+ V3AHigh // A:H
+ V3ALow // A:L
+ V3ANone // A:N
+
+ V3ENotDefined // E:X
+ V3EHigh // E:H
+ V3EFunctional // E:F
+ V3EProofOfConcept // E:P
+ V3EUnproven // E:U
+
+ V3RLNotDefined // RL:X
+ V3RLUnavailable // RL:U
+ V3RLWorkaround // RL:W
+ V3RLTemporaryFix // RL:T
+ V3RLOfficialFix // RL:O
+
+ V3RCNotDefined // RC:X
+ V3RCConfirmed // RC:C
+ V3RCReasonable // RC:R
+ V3RCUnknown // RC:U
+
+ V3CRNotDefined // CR:X
+ V3CRHigh // CR:H
+ V3CRMedium // CR:M
+ V3CRLow // CR:L
+
+ V3IRNotDefined // IR:X
+ V3IRHigh // IR:H
+ V3IRMedium // IR:M
+ V3IRLow // IR:L
+
+ V3ARNotDefined // AR:X
+ V3ARHigh // AR:H
+ V3ARMedium // AR:M
+ V3ARLow // AR:L
+
+ V3MAVNotDefined // MAV:X
+ V3MAVNetwork // MAV:N
+ V3MAVAdjacentNetwork // MAV:A
+ V3MAVLocal // MAV:L
+ V3MAVPhysical // MAV:P
+
+ V3MACNotDefined // MAC:X
+ V3MACLow // MAC:L
+ V3MACHigh // MAC:H
+
+ V3MMRNotDefined // MPR:X
+ V3MPRLow // MPR:L
+ V3MPRHigh // MPR:H
+
+ V3MUINotDefined // MUI:X
+ V3MUINone // MUI:N
+ V3MUIRequired // MUI:R
+
+ V3MSNotDefined // MMS:X
+ V3MSUnchanged // MMS:U
+ V3MSChanged // MMS:C
+
+ V3MCNotDefined // MC:X
+ V3MCHigh // MC:H
+ V3MCLow // MC:L
+ V3MCNone // MC:N
+
+ V3MINotDefined // MI:X
+ V3MIHigh // MI:H
+ V3MILow // MI:L
+ V3MINone // MI:N
+
+ V3MANotDefined // MA:X
+ V3MAHigh // MA:H
+ V3MALow // MA:L
+ V3MANone // MA:N
+ V3UnknownMetric // unknown
+)
+
+// map of metrics to metric keys
+var v3MetricKeyLut = map[V3Metric]V3MetricKey {
+ V3AVNetwork: V3AttackVector, // AV:N
+ V3AVAdjacentNetwork: V3AttackVector, // AV:A
+ V3AVLocal: V3AttackVector, // AV:L
+ V3AVPhysical: V3AttackVector, // AV:P
+
+ V3ACLow: V3AttackComplexity, // AC:L
+ V3ACHigh: V3AttackComplexity, // AC:H
+
+ V3PRNone: V3PrivilegesRequired, // PR:N
+ V3PRLow: V3PrivilegesRequired, // PR:L
+ V3PRHigh: V3PrivilegesRequired, // PR:H
+
+ V3UINone: V3UserInteraction, // UI:N
+ V3UIRequired: V3UserInteraction, // UI:R
+
+ V3SUnchanged: V3Scope, // S:U
+ V3SChanged: V3Scope, // S:C
+
+ V3CHigh: V3Confidentiality, // C:H
+ V3CLow: V3Confidentiality, // C:L
+ V3CNone: V3Confidentiality, // C:N
+
+ V3IHigh: V3Integrity, // I:H
+ V3ILow: V3Integrity, // I:L
+ V3INone: V3Integrity, // I:N
+
+ V3AHigh: V3Availability, // A:H
+ V3ALow: V3Availability, // A:L
+ V3ANone: V3Availability, // A:N
+
+ V3ENotDefined: V3ExploitCodeMaturity, // E:X
+ V3EHigh: V3ExploitCodeMaturity, // E:H
+ V3EFunctional: V3ExploitCodeMaturity, // E:F
+ V3EProofOfConcept: V3ExploitCodeMaturity, // E:P
+ V3EUnproven: V3ExploitCodeMaturity, // E:U
+
+ V3RLNotDefined: V3RemediationLevel, // RL:X
+ V3RLUnavailable: V3RemediationLevel, // RL:U
+ V3RLWorkaround: V3RemediationLevel, // RL:W
+ V3RLTemporaryFix: V3RemediationLevel, // RL:T
+ V3RLOfficialFix: V3RemediationLevel, // RL:O
+
+ V3RCNotDefined: V3ReportConfidence, // RC:X
+ V3RCConfirmed: V3ReportConfidence, // RC:C
+ V3RCReasonable: V3ReportConfidence, // RC:R
+ V3RCUnknown: V3ReportConfidence, // RC:U
+
+ V3CRNotDefined: V3ConfidentialityRequirement, // CR:X
+ V3CRHigh: V3ConfidentialityRequirement, // CR:H
+ V3CRMedium: V3ConfidentialityRequirement, // CR:M
+ V3CRLow: V3ConfidentialityRequirement, // CR:L
+
+ V3IRNotDefined: V3IntegrityRequirement, // IR:X
+ V3IRHigh: V3IntegrityRequirement, // IR:H
+ V3IRMedium: V3IntegrityRequirement, // IR:M
+ V3IRLow: V3IntegrityRequirement, // IR:L
+
+ V3ARNotDefined: V3AvailabilityRequirement, // AR:X
+ V3ARHigh: V3AvailabilityRequirement, // AR:H
+ V3ARMedium: V3AvailabilityRequirement, // AR:M
+ V3ARLow: V3AvailabilityRequirement, // AR:L
+
+ V3MAVNotDefined: V3ModifiedAttackVector, // MAV:X
+ V3MAVNetwork: V3ModifiedAttackVector, // MAV:N
+ V3MAVAdjacentNetwork: V3ModifiedAttackVector, // MAV:A
+ V3MAVLocal: V3ModifiedAttackVector, // MAV:L
+ V3MAVPhysical: V3ModifiedAttackVector, // MAV:P
+
+ V3MACNotDefined: V3ModifiedAttackComplexity, // MAC:X
+ V3MACLow: V3ModifiedAttackComplexity, // MAC:L
+ V3MACHigh: V3ModifiedAttackComplexity, // MAC:H
+
+ V3MMRNotDefined: V3ModifiedPrivilegesRequired, // MPR:X
+ V3MPRLow: V3ModifiedPrivilegesRequired, // MPR:L
+ V3MPRHigh: V3ModifiedPrivilegesRequired, // MPR:H
+
+ V3MUINotDefined: V3ModifiedUserInteraction, // MUI:X
+ V3MUINone: V3ModifiedUserInteraction, // MUI:N
+ V3MUIRequired: V3ModifiedUserInteraction, // MUI:R
+
+ V3MSNotDefined: V3ModifiedScope, // MMS:X
+ V3MSUnchanged: V3ModifiedConfidentiality, // MMS:U
+ V3MSChanged: V3ModifiedIntegrity, // MMS:C
+
+ V3MCNotDefined: V3ModifiedConfidentiality, // MC:X
+ V3MCHigh: V3ModifiedConfidentiality, // MC:H
+ V3MCLow: V3ModifiedConfidentiality, // MC:L
+ V3MCNone: V3ModifiedConfidentiality, // MC:N
+
+ V3MINotDefined: V3ModifiedIntegrity, // MI:X
+ V3MIHigh: V3ModifiedIntegrity, // MI:H
+ V3MILow: V3ModifiedIntegrity, // MI:L
+ V3MINone: V3ModifiedIntegrity, // MI:N
+
+ V3MANotDefined: V3ModifiedAvailability, // MA:X
+ V3MAHigh: V3ModifiedAvailability, // MA:H
+ V3MALow: V3ModifiedAvailability, // MA:L
+ V3MANone: V3ModifiedAvailability, // MA:N
+}
+
+// map of metric strings to metrics
+var v3MetricStrLut = map[string]V3Metric {
+ "AV:N": V3AVNetwork,
+ "AV:A": V3AVAdjacentNetwork,
+ "AV:L": V3AVLocal,
+ "AV:P": V3AVPhysical,
+
+ "AC:L": V3ACLow,
+ "AC:H": V3ACHigh,
+
+ "PR:N": V3PRNone,
+ "PR:L": V3PRLow,
+ "PR:H": V3PRHigh,
+
+ "UI:N": V3UINone,
+ "UI:R": V3UIRequired,
+
+ "S:U": V3SUnchanged,
+ "S:C": V3SChanged,
+
+ "C:H": V3CHigh,
+ "C:L": V3CLow,
+ "C:N": V3CNone,
+
+ "I:H": V3IHigh,
+ "I:L": V3ILow,
+ "I:N": V3INone,
+
+ "A:H": V3AHigh,
+ "A:L": V3ALow,
+ "A:N": V3ANone,
+
+ "E:X": V3ENotDefined,
+ "E:H": V3EHigh,
+ "E:F": V3EFunctional,
+ "E:P": V3EProofOfConcept,
+ "E:U": V3EUnproven,
+
+ "RL:X": V3RLNotDefined,
+ "RL:U": V3RLUnavailable,
+ "RL:W": V3RLWorkaround,
+ "RL:T": V3RLTemporaryFix,
+ "RL:O": V3RLOfficialFix,
+
+ "RC:X": V3RCNotDefined,
+ "RC:C": V3RCConfirmed,
+ "RC:R": V3RCReasonable,
+ "RC:U": V3RCUnknown,
+
+ "CR:X": V3CRNotDefined,
+ "CR:H": V3CRHigh,
+ "CR:M": V3CRMedium,
+ "CR:L": V3CRLow,
+
+ "IR:X": V3IRNotDefined,
+ "IR:H": V3IRHigh,
+ "IR:M": V3IRMedium,
+ "IR:L": V3IRLow,
+
+ "AR:X": V3ARNotDefined,
+ "AR:H": V3ARHigh,
+ "AR:M": V3ARMedium,
+ "AR:L": V3ARLow,
+
+ "MAV:X": V3MAVNotDefined,
+ "MAV:N": V3MAVNetwork,
+ "MAV:A": V3MAVAdjacentNetwork,
+ "MAV:L": V3MAVLocal,
+ "MAV:P": V3MAVPhysical,
+
+ "MAC:X": V3MACNotDefined,
+ "MAC:L": V3MACLow,
+ "MAC:H": V3MACHigh,
+
+ "MPR:X": V3MMRNotDefined,
+ "MPR:L": V3MPRLow,
+ "MPR:H": V3MPRHigh,
+
+ "MUI:X": V3MUINotDefined,
+ "MUI:N": V3MUINone,
+ "MUI:R": V3MUIRequired,
+
+ "MMS:X": V3MSNotDefined,
+ "MMS:U": V3MSUnchanged,
+ "MMS:C": V3MSChanged,
+
+ "MC:X": V3MCNotDefined,
+ "MC:H": V3MCHigh,
+ "MC:L": V3MCLow,
+ "MC:N": V3MCNone,
+
+ "MI:X": V3MINotDefined,
+ "MI:H": V3MIHigh,
+ "MI:L": V3MILow,
+ "MI:N": V3MINone,
+
+ "MA:X": V3MANotDefined,
+ "MA:H": V3MAHigh,
+ "MA:L": V3MALow,
+ "MA:N": V3MANone,
+}
+
+// Convert string to CVSS 3.1 metric.
+func GetV3MetricFromString(s string) (V3Metric, error) {
+ // get metric
+ m, ok := v3MetricStrLut[s]
+ if !ok {
+ return V3AVNetwork, fmt.Errorf("invalid metric: %s", s)
+ }
+
+ // return success
+ return m, nil
+}
+
+// CVSS v3.0 prefix
+var v30Prefix = "CVSS:3.0/"
+
+// CVSS 3.0 vector.
+type v30Vector []V3Metric
+
+// Convert vector to string
+func (v v30Vector) String() string {
+ // convert to slice of metrics
+ metrics := []V3Metric(v)
+
+ // build vector
+ r := make([]string, len(metrics))
+ for i, m := range(metrics) {
+ r[i] = m.String()
+ }
+
+ // build and return string
+ return v30Prefix + strings.Join(r, "/")
+}
+
+// Return CVSS version.
+func (v30Vector) Version() Version {
+ return V30
+}
+
+// create CVSS 3.0 vector from string
+func NewV30VectorFromString(s string) (Vector, error) {
+ strs := strings.Split(s, "/")
+ r := make([]V3Metric, len(strs))
+
+ // walk metric strings
+ for i, ms := range(strs) {
+ // convert string to vector
+ m, err := GetV3MetricFromString(ms)
+ if err != nil {
+ return nil, err
+ }
+
+ // add to results
+ r[i] = m
+ }
+
+ // build and return vector
+ return v30Vector(r), nil
+}
+
+// CVSS v3.1 prefix
+var v31Prefix = "CVSS:3.1/"
+
+// CVSS 3.1 vector.
+type v31Vector []V3Metric
+
+// Convert vector to string
+func (v v31Vector) String() string {
+ // convert to slice of metrics
+ metrics := []V3Metric(v)
+
+ // build vector
+ r := make([]string, len(metrics))
+ for i, m := range(metrics) {
+ r[i] = m.String()
+ }
+
+ // build and return string
+ return v31Prefix + strings.Join(r, "/")
+}
+
+// Return CVSS version.
+func (v31Vector) Version() Version {
+ return V31
+}
+
+// create CVSS 3.1 vector from string
+func NewV31VectorFromString(s string) (Vector, error) {
+ strs := strings.Split(s, "/")
+ r := make([]V3Metric, len(strs))
+
+ // walk metric strings
+ for i, ms := range(strs) {
+ // convert string to vector
+ m, err := GetV3MetricFromString(ms)
+ if err != nil {
+ return nil, err
+ }
+
+ // add to results
+ r[i] = m
+ }
+
+ // build and return vector
+ return v31Vector(r), nil
+}
+
+// CVSS vector
+type Vector interface {
+ // Get CVSS version.
+ Version() Version
+
+ // Get CVSS vector string.
+ String() string
+}
+
+// Create new CVSS vector from vector string.
+func NewVector(s string) (Vector, error) {
+ if len(s) > len(v31Prefix) && s[:len(v31Prefix)] == v31Prefix {
+ // create CVSS v2.0 vector.
+ return NewV31VectorFromString(s[len(v31Prefix):])
+ } else if len(s) > len(v30Prefix) && s[:len(v30Prefix)] == v30Prefix {
+ // create CVSS v3.0 vector.
+ return NewV30VectorFromString(s[len(v30Prefix):])
+ } else {
+ // create CVSS V2 vector
+ return NewV2VectorFromString(s)
+ }
+}
diff --git a/internal/cvss/metriccategory_string.go b/internal/cvss/metriccategory_string.go
new file mode 100644
index 0000000..9ad249c
--- /dev/null
+++ b/internal/cvss/metriccategory_string.go
@@ -0,0 +1,25 @@
+// Code generated by "stringer -linecomment -type=MetricCategory"; DO NOT EDIT.
+
+package cvss
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[Base-0]
+ _ = x[Temporal-1]
+ _ = x[Environmental-2]
+}
+
+const _MetricCategory_name = "BaseTemporalEnvironmental"
+
+var _MetricCategory_index = [...]uint8{0, 4, 12, 25}
+
+func (i MetricCategory) String() string {
+ if i >= MetricCategory(len(_MetricCategory_index)-1) {
+ return "MetricCategory(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _MetricCategory_name[_MetricCategory_index[i]:_MetricCategory_index[i+1]]
+}
diff --git a/internal/cvss/v2metric_string.go b/internal/cvss/v2metric_string.go
new file mode 100644
index 0000000..8191622
--- /dev/null
+++ b/internal/cvss/v2metric_string.go
@@ -0,0 +1,77 @@
+// Code generated by "stringer -linecomment -type=V2Metric"; DO NOT EDIT.
+
+package cvss
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[V2AVNetwork-0]
+ _ = x[V2AVAdjacentNetwork-1]
+ _ = x[V2AVLocal-2]
+ _ = x[V2ACLow-3]
+ _ = x[V2ACMedium-4]
+ _ = x[V2ACHigh-5]
+ _ = x[V2AuMultiple-6]
+ _ = x[V2AuSingle-7]
+ _ = x[V2AuNone-8]
+ _ = x[V2CNone-9]
+ _ = x[V2CPartial-10]
+ _ = x[V2CComplete-11]
+ _ = x[V2INone-12]
+ _ = x[V2IPartial-13]
+ _ = x[V2IComplete-14]
+ _ = x[V2ANone-15]
+ _ = x[V2APartial-16]
+ _ = x[V2AComplete-17]
+ _ = x[V2ENotDefined-18]
+ _ = x[V2EUnproven-19]
+ _ = x[V2EProofOfConcept-20]
+ _ = x[V2EFunctional-21]
+ _ = x[V2EHigh-22]
+ _ = x[V2RLOfficialFix-23]
+ _ = x[V2RLTemporaryFix-24]
+ _ = x[V2RLWorkaround-25]
+ _ = x[V2RLUnavailable-26]
+ _ = x[V2RLNotDefined-27]
+ _ = x[V2RCUnconfirmed-28]
+ _ = x[V2RCUncorroborated-29]
+ _ = x[V2RCConfirmed-30]
+ _ = x[V2RCNotDefined-31]
+ _ = x[V2CDPNone-32]
+ _ = x[V2CDPLow-33]
+ _ = x[V2CDPLowMedium-34]
+ _ = x[V2CDPMediumHigh-35]
+ _ = x[V2CDPHigh-36]
+ _ = x[V2CDPNotDefined-37]
+ _ = x[V2TDNone-38]
+ _ = x[V2TDLow-39]
+ _ = x[V2TDMedium-40]
+ _ = x[V2TDHigh-41]
+ _ = x[V2TDNotDefined-42]
+ _ = x[V2CRLow-43]
+ _ = x[V2CRMedium-44]
+ _ = x[V2CRHigh-45]
+ _ = x[V2CRNotDefined-46]
+ _ = x[V2IRLow-47]
+ _ = x[V2IRMedium-48]
+ _ = x[V2IRHigh-49]
+ _ = x[V2IRNotDefined-50]
+ _ = x[V2ARLow-51]
+ _ = x[V2ARMedium-52]
+ _ = x[V2ARHigh-53]
+ _ = x[V2ARNotDefined-54]
+}
+
+const _V2Metric_name = "AV:NAV:AAV:LAC:LAC:LAC:HAu:MAu:SAu:NC:NC:PC:CI:NI:PI:CA:NA:PA:CE:NDE:UE:POCE:FE:HRL:OFRL:TFRL:WRL:URL:NDRC:UCRC:URRC:CRC:NDCDP:NCDP:LCDP:LMCDP:MHCDP:HCDP:NDTD:NTD:LTD:MTD:HTD:NDCR:LCR:MCR:HCR:NDIR:LIR:MIR:HIR:NDAR:LAR:MAR:HAR:ND"
+
+var _V2Metric_index = [...]uint8{0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 67, 70, 75, 78, 81, 86, 91, 95, 99, 104, 109, 114, 118, 123, 128, 133, 139, 145, 150, 156, 160, 164, 168, 172, 177, 181, 185, 189, 194, 198, 202, 206, 211, 215, 219, 223, 228}
+
+func (i V2Metric) String() string {
+ if i >= V2Metric(len(_V2Metric_index)-1) {
+ return "V2Metric(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _V2Metric_name[_V2Metric_index[i]:_V2Metric_index[i+1]]
+}
diff --git a/internal/cvss/v2metrickey_string.go b/internal/cvss/v2metrickey_string.go
new file mode 100644
index 0000000..52b9834
--- /dev/null
+++ b/internal/cvss/v2metrickey_string.go
@@ -0,0 +1,36 @@
+// Code generated by "stringer -linecomment -type=V2MetricKey"; DO NOT EDIT.
+
+package cvss
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[V2AccessVector-0]
+ _ = x[V2AccessComplexity-1]
+ _ = x[V2Authentication-2]
+ _ = x[V2ConfidentialityImpact-3]
+ _ = x[V2IntegrityImpact-4]
+ _ = x[V2AvailabilityImpact-5]
+ _ = x[V2Exploitability-6]
+ _ = x[V2RemediationLevel-7]
+ _ = x[V2ReportConfidence-8]
+ _ = x[V2CollateralDamagePotential-9]
+ _ = x[V2TargetDistribution-10]
+ _ = x[V2ConfidentialityRequirement-11]
+ _ = x[V2IntegrityRequirement-12]
+ _ = x[V2AvailabilityRequirement-13]
+}
+
+const _V2MetricKey_name = "AVACAuCIAERLRCCDPTDCRIRAR"
+
+var _V2MetricKey_index = [...]uint8{0, 2, 4, 6, 7, 8, 9, 10, 12, 14, 17, 19, 21, 23, 25}
+
+func (i V2MetricKey) String() string {
+ if i >= V2MetricKey(len(_V2MetricKey_index)-1) {
+ return "V2MetricKey(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _V2MetricKey_name[_V2MetricKey_index[i]:_V2MetricKey_index[i+1]]
+}
diff --git a/internal/cvss/v3metric_string.go b/internal/cvss/v3metric_string.go
new file mode 100644
index 0000000..e59b024
--- /dev/null
+++ b/internal/cvss/v3metric_string.go
@@ -0,0 +1,100 @@
+// Code generated by "stringer -linecomment -type=V3Metric"; DO NOT EDIT.
+
+package cvss
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[V3AVNetwork-0]
+ _ = x[V3AVAdjacentNetwork-1]
+ _ = x[V3AVLocal-2]
+ _ = x[V3AVPhysical-3]
+ _ = x[V3ACLow-4]
+ _ = x[V3ACHigh-5]
+ _ = x[V3PRNone-6]
+ _ = x[V3PRLow-7]
+ _ = x[V3PRHigh-8]
+ _ = x[V3UINone-9]
+ _ = x[V3UIRequired-10]
+ _ = x[V3SUnchanged-11]
+ _ = x[V3SChanged-12]
+ _ = x[V3CHigh-13]
+ _ = x[V3CLow-14]
+ _ = x[V3CNone-15]
+ _ = x[V3IHigh-16]
+ _ = x[V3ILow-17]
+ _ = x[V3INone-18]
+ _ = x[V3AHigh-19]
+ _ = x[V3ALow-20]
+ _ = x[V3ANone-21]
+ _ = x[V3ENotDefined-22]
+ _ = x[V3EHigh-23]
+ _ = x[V3EFunctional-24]
+ _ = x[V3EProofOfConcept-25]
+ _ = x[V3EUnproven-26]
+ _ = x[V3RLNotDefined-27]
+ _ = x[V3RLUnavailable-28]
+ _ = x[V3RLWorkaround-29]
+ _ = x[V3RLTemporaryFix-30]
+ _ = x[V3RLOfficialFix-31]
+ _ = x[V3RCNotDefined-32]
+ _ = x[V3RCConfirmed-33]
+ _ = x[V3RCReasonable-34]
+ _ = x[V3RCUnknown-35]
+ _ = x[V3CRNotDefined-36]
+ _ = x[V3CRHigh-37]
+ _ = x[V3CRMedium-38]
+ _ = x[V3CRLow-39]
+ _ = x[V3IRNotDefined-40]
+ _ = x[V3IRHigh-41]
+ _ = x[V3IRMedium-42]
+ _ = x[V3IRLow-43]
+ _ = x[V3ARNotDefined-44]
+ _ = x[V3ARHigh-45]
+ _ = x[V3ARMedium-46]
+ _ = x[V3ARLow-47]
+ _ = x[V3MAVNotDefined-48]
+ _ = x[V3MAVNetwork-49]
+ _ = x[V3MAVAdjacentNetwork-50]
+ _ = x[V3MAVLocal-51]
+ _ = x[V3MAVPhysical-52]
+ _ = x[V3MACNotDefined-53]
+ _ = x[V3MACLow-54]
+ _ = x[V3MACHigh-55]
+ _ = x[V3MMRNotDefined-56]
+ _ = x[V3MPRLow-57]
+ _ = x[V3MPRHigh-58]
+ _ = x[V3MUINotDefined-59]
+ _ = x[V3MUINone-60]
+ _ = x[V3MUIRequired-61]
+ _ = x[V3MSNotDefined-62]
+ _ = x[V3MSUnchanged-63]
+ _ = x[V3MSChanged-64]
+ _ = x[V3MCNotDefined-65]
+ _ = x[V3MCHigh-66]
+ _ = x[V3MCLow-67]
+ _ = x[V3MCNone-68]
+ _ = x[V3MINotDefined-69]
+ _ = x[V3MIHigh-70]
+ _ = x[V3MILow-71]
+ _ = x[V3MINone-72]
+ _ = x[V3MANotDefined-73]
+ _ = x[V3MAHigh-74]
+ _ = x[V3MALow-75]
+ _ = x[V3MANone-76]
+ _ = x[V3UnknownMetric-77]
+}
+
+const _V3Metric_name = "AV:NAV:AAV:LAV:PAC:LAC:HPR:NPR:LPR:HUI:NUI:RS:US:CC:HC:LC:NI:HI:LI:NA:HA:LA:NE:XE:HE:FE:PE:URL:XRL:URL:WRL:TRL:ORC:XRC:CRC:RRC:UCR:XCR:HCR:MCR:LIR:XIR:HIR:MIR:LAR:XAR:HAR:MAR:LMAV:XMAV:NMAV:AMAV:LMAV:PMAC:XMAC:LMAC:HMPR:XMPR:LMPR:HMUI:XMUI:NMUI:RMMS:XMMS:UMMS:CMC:XMC:HMC:LMC:NMI:XMI:HMI:LMI:NMA:XMA:HMA:LMA:Nunknown"
+
+var _V3Metric_index = [...]uint16{0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 47, 50, 53, 56, 59, 62, 65, 68, 71, 74, 77, 80, 83, 86, 89, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 148, 152, 156, 160, 164, 168, 172, 176, 181, 186, 191, 196, 201, 206, 211, 216, 221, 226, 231, 236, 241, 246, 251, 256, 261, 265, 269, 273, 277, 281, 285, 289, 293, 297, 301, 305, 309, 316}
+
+func (i V3Metric) String() string {
+ if i >= V3Metric(len(_V3Metric_index)-1) {
+ return "V3Metric(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _V3Metric_name[_V3Metric_index[i]:_V3Metric_index[i+1]]
+}
diff --git a/internal/cvss/v3metrickey_string.go b/internal/cvss/v3metrickey_string.go
new file mode 100644
index 0000000..9459daa
--- /dev/null
+++ b/internal/cvss/v3metrickey_string.go
@@ -0,0 +1,44 @@
+// Code generated by "stringer -linecomment -type=V3MetricKey"; DO NOT EDIT.
+
+package cvss
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[V3AttackVector-0]
+ _ = x[V3AttackComplexity-1]
+ _ = x[V3PrivilegesRequired-2]
+ _ = x[V3UserInteraction-3]
+ _ = x[V3Scope-4]
+ _ = x[V3Confidentiality-5]
+ _ = x[V3Integrity-6]
+ _ = x[V3Availability-7]
+ _ = x[V3ExploitCodeMaturity-8]
+ _ = x[V3RemediationLevel-9]
+ _ = x[V3ReportConfidence-10]
+ _ = x[V3ConfidentialityRequirement-11]
+ _ = x[V3IntegrityRequirement-12]
+ _ = x[V3AvailabilityRequirement-13]
+ _ = x[V3ModifiedAttackVector-14]
+ _ = x[V3ModifiedAttackComplexity-15]
+ _ = x[V3ModifiedPrivilegesRequired-16]
+ _ = x[V3ModifiedUserInteraction-17]
+ _ = x[V3ModifiedScope-18]
+ _ = x[V3ModifiedConfidentiality-19]
+ _ = x[V3ModifiedIntegrity-20]
+ _ = x[V3ModifiedAvailability-21]
+}
+
+const _V3MetricKey_name = "AVACPRUISCIAERLRCCRIRARMAVMACMPRMUIMSMCMIMA"
+
+var _V3MetricKey_index = [...]uint8{0, 2, 4, 6, 8, 9, 10, 11, 12, 13, 15, 17, 19, 21, 23, 26, 29, 32, 35, 37, 39, 41, 43}
+
+func (i V3MetricKey) String() string {
+ if i >= V3MetricKey(len(_V3MetricKey_index)-1) {
+ return "V3MetricKey(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _V3MetricKey_name[_V3MetricKey_index[i]:_V3MetricKey_index[i+1]]
+}
diff --git a/internal/cvss/version_string.go b/internal/cvss/version_string.go
new file mode 100644
index 0000000..03e8904
--- /dev/null
+++ b/internal/cvss/version_string.go
@@ -0,0 +1,25 @@
+// Code generated by "stringer -linecomment -type=Version"; DO NOT EDIT.
+
+package cvss
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[V20-0]
+ _ = x[V30-1]
+ _ = x[V31-2]
+}
+
+const _Version_name = "2.03.03.1"
+
+var _Version_index = [...]uint8{0, 3, 6, 9}
+
+func (i Version) String() string {
+ if i >= Version(len(_Version_index)-1) {
+ return "Version(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _Version_name[_Version_index[i]:_Version_index[i+1]]
+}
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
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..fec4e36
--- /dev/null
+++ b/main.go
@@ -0,0 +1,88 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "nvd/internal/cvss"
+ "nvd/internal/feed"
+ "os"
+)
+
+const testMeta = `lastModifiedDate:2022-01-29T03:01:16-05:00
+size:73202582
+zipSize:3753799
+gzSize:3753663
+sha256:B86258D5D9861507A1894A7B92011764803D7267787B1487539E240EA2405440
+`
+
+// Test meta parser
+func testMetaParser() {
+ // create buffer
+ buf := bytes.NewBufferString(testMeta)
+
+ // decode meta, check for error
+ meta, err := feed.NewMeta(buf)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // create json encoder
+ e := json.NewEncoder(os.Stdout)
+ if err := e.Encode(meta); err != nil {
+ log.Fatal(err)
+ }
+}
+
+// Test feed parser
+func testFeedParser() {
+ var f feed.Feed
+
+ // decode cve feed
+ d := json.NewDecoder(os.Stdin)
+ if err := d.Decode(&f); err != nil {
+ log.Fatal(err)
+ }
+
+ // create json encoder
+ e := json.NewEncoder(os.Stdout)
+ if err := e.Encode(f); err != nil {
+ log.Fatal(err)
+ }
+}
+
+// test cvss v3.1 vector
+var testCvssV3 = "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H"
+
+// Test cvss v3 parser
+func testCvssV3Parser() {
+ // parse vector, check for error
+ v, err := cvss.NewVector(testCvssV3)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println(v)
+}
+
+// test cvss v2 vector
+var testCvssV2 = "AV:L/AC:L/Au:N/C:N/I:N/A:P"
+
+// Test cvss v2 parser
+func testCvssV2Parser() {
+ // parse vector, check for error
+ v, err := cvss.NewVector(testCvssV2)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println(v)
+}
+
+func main() {
+ testMetaParser()
+ // testFeedParser()
+ testCvssV3Parser()
+ testCvssV2Parser()
+}