From b3dc36421f133ea6983574891720e974cf7974dd Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Mon, 31 Jan 2022 11:09:58 -0500 Subject: initial commit --- .gitignore | 2 + go.mod | 10 + go.sum | 8 + internal/cvss/cvss.go | 926 +++++++++++++++++++++++++++++++++ internal/cvss/metriccategory_string.go | 25 + internal/cvss/v2metric_string.go | 77 +++ internal/cvss/v2metrickey_string.go | 36 ++ internal/cvss/v3metric_string.go | 100 ++++ internal/cvss/v3metrickey_string.go | 44 ++ internal/cvss/version_string.go | 25 + internal/feed/feed.go | 770 +++++++++++++++++++++++++++ internal/feed/meta.go | 104 ++++ main.go | 88 ++++ 13 files changed, 2215 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/cvss/cvss.go create mode 100644 internal/cvss/metriccategory_string.go create mode 100644 internal/cvss/v2metric_string.go create mode 100644 internal/cvss/v2metrickey_string.go create mode 100644 internal/cvss/v3metric_string.go create mode 100644 internal/cvss/v3metrickey_string.go create mode 100644 internal/cvss/version_string.go create mode 100644 internal/feed/feed.go create mode 100644 internal/feed/meta.go create mode 100644 main.go 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() +} -- cgit v1.2.3