From d4b400e50e02e668b92806a4e20c8c9f643dd491 Mon Sep 17 00:00:00 2001
From: Paul Duncan <pabs@pablotron.org>
Date: Thu, 17 Feb 2022 20:28:58 -0500
Subject: cvss: move v31 utility functions to work with all 3.x vectors

---
 cvss/v31vector.go | 228 +++++++-----------------------------------------------
 cvss/v3metric.go  | 181 ++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 207 insertions(+), 202 deletions(-)

(limited to 'cvss')

diff --git a/cvss/v31vector.go b/cvss/v31vector.go
index 1950e5a..e5e3dff 100644
--- a/cvss/v31vector.go
+++ b/cvss/v31vector.go
@@ -119,183 +119,9 @@ func isV31VectorString(s string) bool {
          v31VecRe.MatchString(s)
 }
 
-// Does the map have at least one of the keys needed for a temporal
-// score defined?
-func hasTemporalScoreKeys(keys map[Key]v3Metric) bool {
-  ecm, ecm_ok := keys[v3ExploitCodeMaturity] // E
-  rl, rl_ok := keys[v3RemediationLevel] // RL
-  rc, rc_ok := keys[v3ReportConfidence] // RC
-
-  return (ecm_ok && ecm != v3ENotDefined) ||
-         (rl_ok && rl != v3RLNotDefined) ||
-         (rc_ok && rc != v3RCNotDefined)
-}
-
-// Does the map have at least one of the keys needed for an env score to
-// be defined?
-func hasEnvScoreKeys(keys map[Key]v3Metric) bool {
-  mav, mav_ok := keys[v3ModifiedAttackVector] // MAV
-  mac, mac_ok := keys[v3ModifiedAttackComplexity] // MAC
-  mpr, mpr_ok := keys[v3ModifiedPrivilegesRequired] // MPR
-  mui, mui_ok := keys[v3ModifiedUserInteraction] // MUI
-  ms, ms_ok := keys[v3ModifiedScope] // MS
-  mc, mc_ok := keys[v3ModifiedConfidentiality] // MC
-  mi, mi_ok := keys[v3ModifiedIntegrity] // MI
-  ma, ma_ok := keys[v3ModifiedAvailability] // MA
-  cr, cr_ok := keys[v3ConfidentialityRequirement] // CR
-  ir, ir_ok := keys[v3IntegrityRequirement] // IR
-  ar, ar_ok := keys[v3AvailabilityRequirement] // AR
-
-  return (mav_ok && mav != v3MAVNotDefined) ||
-         (mac_ok && mac != v3MACNotDefined) ||
-         (mpr_ok && mpr != v3MPRNotDefined) ||
-         (mui_ok && mui != v3MUINotDefined) ||
-         (ms_ok && ms != v3MSNotDefined) ||
-         (mc_ok && mc != v3MCNotDefined) ||
-         (mi_ok && mi != v3MINotDefined) ||
-         (ma_ok && ma != v3MANotDefined) ||
-         (cr_ok && cr != v3CRNotDefined) ||
-         (ir_ok && ir != v3IRNotDefined) ||
-         (ar_ok && ar != v3ARNotDefined);
-}
-
-// roundup implemention (from CVSS v3.1 spec, appendix A)
-func roundup(val float64) float64 {
-  return math.Ceil(10.0 * val) / 10.0
-}
-
-var v31MetricCoefs = map[v3Metric]float64 {
-  v3AVNetwork: 0.85, // AV:N
-  v3AVAdjacentNetwork: 0.62, // AV:A
-  v3AVLocal: 0.55, // AV:L
-  v3AVPhysical: 0.2, // AV:P
-
-  v3ACLow: 0.77, // AC:L
-  v3ACHigh: 0.44, // AC:H
-
-  v3PRNone: 0.85, // PR:N
-  v3PRLow: 0.62, // PR:L
-  v3PRHigh: 0.27, // PR:H
-
-  v3UINone: 0.85, // UI:N
-  v3UIRequired: 0.62, // UI:R
-
-  v3CHigh: 0.56, // C:H
-  v3CLow: 0.22, // C:L
-  v3CNone: 0.0, // C:N
-
-  v3IHigh: 0.56, // I:H
-  v3ILow: 0.22, // I:L
-  v3INone: 0.0, // I:N
-
-  v3AHigh: 0.56, // A:H
-  v3ALow: 0.22, // A:L
-  v3ANone: 0.0, // A:N
-
-  v3ENotDefined: 1.0, // E:X
-  v3EHigh: 1.0, // E:H
-  v3EFunctional: 0.97, // E:F
-  v3EProofOfConcept: 0.94, // E:P
-  v3EUnproven: 0.91, // E:U
-
-  v3RLNotDefined: 1.0, // RL:X
-  v3RLUnavailable: 1.0, // RL:U
-  v3RLWorkaround: 0.97, // RL:W
-  v3RLTemporaryFix: 0.96, // RL:T
-  v3RLOfficialFix: 0.95, // RL:O
-
-  v3RCNotDefined: 1.0, // RC:X
-  v3RCConfirmed: 1.0, // RC:C
-  v3RCReasonable: 0.96, // RC:R
-  v3RCUnknown: 0.92, // RC:U
-
-  v3CRNotDefined: 1.0, // CR:X
-  v3CRHigh: 1.5, // CR:H
-  v3CRMedium: 1.0, // CR:M
-  v3CRLow: 0.5, // CR:L
-
-  v3IRNotDefined: 1.0, // IR:X
-  v3IRHigh: 1.5, // IR:H
-  v3IRMedium: 1.0, // IR:M
-  v3IRLow: 0.5, // IR:L
-
-  v3ARNotDefined: 1.0, // AR:X
-  v3ARHigh: 1.5, // AR:H
-  v3ARMedium: 1.0, // AR:M
-  v3ARLow: 0.5, // AR:L
-
-  v3MAVNotDefined: 1.0, // MAV:X
-  v3MAVNetwork: 0.85, // MAV:N
-  v3MAVAdjacentNetwork: 0.62, // MAV:A
-  v3MAVLocal: 0.55, // MAV:L
-  v3MAVPhysical: 0.2, // MAV:P
-
-  v3MACNotDefined: 1.0, // MAC:X
-  v3MACLow: 0.77, // MAC:L
-  v3MACHigh: 0.44, // MAC:H
-
-  v3MPRNotDefined: 1.0, // MPR:X
-  v3MPRNone: 0.85, // MPR:N
-  v3MPRLow: 0.62, // MPR:L
-  v3MPRHigh: 0.27, // MPR:H
-
-  v3MUINotDefined: 1.0, // MUI:X
-  v3MUINone: 0.85, // MUI:N
-  v3MUIRequired: 0.62, // MUI:R
-
-  v3MCNotDefined: 1.0, // MC:X
-  v3MCHigh: 0.56, // MC:H
-  v3MCLow: 0.22, // MC:L
-  v3MCNone: 0.0, // MC:N
-
-  v3MINotDefined: 1.0, // MI:X
-  v3MIHigh: 0.56, // MI:H
-  v3MILow: 0.22, // MI:L
-  v3MINone: 0.0, // MI:N
-
-  v3MANotDefined: 1.0, // MA:X
-  v3MAHigh: 0.56, // MA:H
-  v3MALow: 0.22, // MA:L
-  v3MANone: 0.0, // MA:N
-}
-
-// map of modified metrics to not defined value and base key
-var v31ModMetricDefaults = map[v3Key]struct {
-  notDefined v3Metric
-  baseKey v3Key
-} {
-  v3ModifiedAttackVector: { v3MAVNotDefined, v3AttackVector },
-  v3ModifiedAttackComplexity: { v3MACNotDefined, v3AttackComplexity },
-  v3ModifiedUserInteraction: { v3MUINotDefined, v3UserInteraction },
-  v3ModifiedConfidentiality: { v3MCNotDefined, v3Confidentiality },
-  v3ModifiedIntegrity: { v3MINotDefined, v3Integrity },
-  v3ModifiedAvailability: { v3MANotDefined, v3Availability },
-}
-
-// Get modified metric coefficient, or fall back to base coefficient if
-// modified metric is not defined.
-func getModCoef(keys map[Key]v3Metric, key v3Key) float64 {
-  d := v31ModMetricDefaults[key]
-  if v, _ := keys[key]; v != d.notDefined {
-    // return modified metric coefficient
-    return v31MetricCoefs[v]
-  } else {
-    // return base coefficient
-    return v31MetricCoefs[keys[d.baseKey]]
-  }
-}
-//  var metricWeightMPR = CVSS31.Weight.PR    [MS  !== "X" ? MS  : S] [MPR !== "X" ? MPR : PR];  // Depends on MS.
-
-var v31PrivReqCoefs = map[v3Metric]map[bool]float64 {
-  v3PRNone: map[bool]float64 { false: 0.85, true: 0.85 },
-  v3PRLow: map[bool]float64 { false: 0.62, true: 0.68 },
-  v3PRHigh: map[bool]float64 { false: 0.27, true: 0.50 },
-  v3MPRNone: map[bool]float64 { false: 0.85, true: 0.85 },
-  v3MPRLow: map[bool]float64 { false: 0.62, true: 0.68 },
-  v3MPRHigh: map[bool]float64 { false: 0.27, true: 0.50 },
-}
-
 // Return numerical scores for this vector.
+//
+// Reference implementation: https://www.first.org/cvss/calculator/cvsscalc31.js
 func (v v31Vector) Scores() (Scores, error) {
   scopeChanged := false
   modScopeChanged := false
@@ -335,29 +161,29 @@ func (v v31Vector) Scores() (Scores, error) {
     }
   }
 
-  attackVector := v31MetricCoefs[keys[v3AttackVector]]
-  attackComplexity := v31MetricCoefs[keys[v3AttackComplexity]]
-  userInteraction := v31MetricCoefs[keys[v3UserInteraction]]
-  conf := v31MetricCoefs[keys[v3Confidentiality]]
-  integ := v31MetricCoefs[keys[v3Integrity]]
-  avail := v31MetricCoefs[keys[v3Availability]]
-  ecm := v31MetricCoefs[keys[v3ExploitCodeMaturity]]
-  remediationLevel := v31MetricCoefs[keys[v3RemediationLevel]]
-  reportConfidence := v31MetricCoefs[keys[v3ReportConfidence]]
-  confReq := v31MetricCoefs[keys[v3ConfidentialityRequirement]]
-  availReq := v31MetricCoefs[keys[v3AvailabilityRequirement]]
-  integReq := v31MetricCoefs[keys[v3IntegrityRequirement]]
+  attackVector := v3MetricCoefs[keys[v3AttackVector]]
+  attackComplexity := v3MetricCoefs[keys[v3AttackComplexity]]
+  userInteraction := v3MetricCoefs[keys[v3UserInteraction]]
+  conf := v3MetricCoefs[keys[v3Confidentiality]]
+  integ := v3MetricCoefs[keys[v3Integrity]]
+  avail := v3MetricCoefs[keys[v3Availability]]
+  ecm := v3MetricCoefs[keys[v3ExploitCodeMaturity]]
+  remediationLevel := v3MetricCoefs[keys[v3RemediationLevel]]
+  reportConfidence := v3MetricCoefs[keys[v3ReportConfidence]]
+  confReq := v3MetricCoefs[keys[v3ConfidentialityRequirement]]
+  availReq := v3MetricCoefs[keys[v3AvailabilityRequirement]]
+  integReq := v3MetricCoefs[keys[v3IntegrityRequirement]]
 
   // adjust privsRequired based on scopeChanged
   // (CVSS v3.1 spec, section 7.4, table 16)
-  privsRequired := v31PrivReqCoefs[keys[v3PrivilegesRequired]][scopeChanged]
+  privsRequired := v3PrivReqCoefs[keys[v3PrivilegesRequired]][scopeChanged]
 
-  modAttackVector := getModCoef(keys, v3ModifiedAttackVector)
-  modAttackComplexity := getModCoef(keys, v3ModifiedAttackComplexity)
-  modUserInteraction := getModCoef(keys, v3ModifiedUserInteraction)
-  modConf := getModCoef(keys, v3ModifiedConfidentiality)
-  modInteg := getModCoef(keys, v3ModifiedIntegrity)
-  modAvail := getModCoef(keys, v3ModifiedAvailability)
+  modAttackVector := getV3ModCoef(keys, v3ModifiedAttackVector)
+  modAttackComplexity := getV3ModCoef(keys, v3ModifiedAttackComplexity)
+  modUserInteraction := getV3ModCoef(keys, v3ModifiedUserInteraction)
+  modConf := getV3ModCoef(keys, v3ModifiedConfidentiality)
+  modInteg := getV3ModCoef(keys, v3ModifiedIntegrity)
+  modAvail := getV3ModCoef(keys, v3ModifiedAvailability)
 
   if v, _ := keys[v3ModifiedScope]; v == v3MSNotDefined {
     // default to base scopeChanged
@@ -373,15 +199,15 @@ func (v v31Vector) Scores() (Scores, error) {
     ms, _ := keys[v3ModifiedScope]
 
     if mpr != v3MPRNotDefined && ms != v3MSNotDefined {
-      modPrivsRequired = v31PrivReqCoefs[mpr][ms == v3MSChanged]
+      modPrivsRequired = v3PrivReqCoefs[mpr][ms == v3MSChanged]
     } else if mpr != v3MPRNotDefined && ms == v3MSNotDefined {
-      modPrivsRequired = v31PrivReqCoefs[mpr][scopeChanged]
+      modPrivsRequired = v3PrivReqCoefs[mpr][scopeChanged]
     } else if mpr == v3MPRNotDefined && ms != v3MSNotDefined {
-      modPrivsRequired = v31PrivReqCoefs[pr][ms == v3MSChanged]
+      modPrivsRequired = v3PrivReqCoefs[pr][ms == v3MSChanged]
     } else {
       // default to base privsRequired
       // modPrivsRequired = privsRequired
-      modPrivsRequired = v31PrivReqCoefs[pr][scopeChanged]
+      modPrivsRequired = v3PrivReqCoefs[pr][scopeChanged]
     }
   }
 
@@ -413,13 +239,13 @@ func (v v31Vector) Scores() (Scores, error) {
 
   // temporal score (CVSS v3.1 spec, section 7.2)
   tempScore := 0.0
-  if hasTemporalScoreKeys(keys) {
+  if hasV3TemporalScoreKeys(keys) {
     tempScore = roundup(baseScore * ecm * remediationLevel * reportConfidence)
   }
 
   // environmental score (CVSS v3.1 spec, section 7.3)
   envScore := 0.0
-  if hasEnvScoreKeys(keys) {
+  if hasV3EnvScoreKeys(keys) {
     // modified impact sub score (ISC_m)
     miss := math.Min(
       1 - (1 - confReq * modConf) * (1 - integReq * modInteg) * (1 - availReq * modAvail),
diff --git a/cvss/v3metric.go b/cvss/v3metric.go
index c0f00be..b4bb89b 100644
--- a/cvss/v3metric.go
+++ b/cvss/v3metric.go
@@ -1,5 +1,9 @@
 package cvss
 
+import (
+  "math"
+)
+
 //go:generate stringer -linecomment -type=v3Metric
 
 // metric value
@@ -324,7 +328,7 @@ func (m v3Metric) Key() Key {
   }
 }
 
-// Convert string to CVSS 3.1 metric.
+// Convert string to CVSS 3.x metric.
 func getV3Metric(version Version, s string) (v3Metric, error) {
   if m, ok := v3MetricStrLut[s]; ok {
     return m, nil
@@ -332,3 +336,178 @@ func getV3Metric(version Version, s string) (v3Metric, error) {
     return v3InvalidMetric, newBadMetric(version, s)
   }
 }
+
+// Does the map have at least one of the keys needed for a temporal
+// score defined?
+func hasV3TemporalScoreKeys(keys map[Key]v3Metric) bool {
+  ecm, ecm_ok := keys[v3ExploitCodeMaturity] // E
+  rl, rl_ok := keys[v3RemediationLevel] // RL
+  rc, rc_ok := keys[v3ReportConfidence] // RC
+
+  return (ecm_ok && ecm != v3ENotDefined) ||
+         (rl_ok && rl != v3RLNotDefined) ||
+         (rc_ok && rc != v3RCNotDefined)
+}
+
+// Does the map have at least one of the keys needed for an env score to
+// be defined?
+func hasV3EnvScoreKeys(keys map[Key]v3Metric) bool {
+  mav, mav_ok := keys[v3ModifiedAttackVector] // MAV
+  mac, mac_ok := keys[v3ModifiedAttackComplexity] // MAC
+  mpr, mpr_ok := keys[v3ModifiedPrivilegesRequired] // MPR
+  mui, mui_ok := keys[v3ModifiedUserInteraction] // MUI
+  ms, ms_ok := keys[v3ModifiedScope] // MS
+  mc, mc_ok := keys[v3ModifiedConfidentiality] // MC
+  mi, mi_ok := keys[v3ModifiedIntegrity] // MI
+  ma, ma_ok := keys[v3ModifiedAvailability] // MA
+  cr, cr_ok := keys[v3ConfidentialityRequirement] // CR
+  ir, ir_ok := keys[v3IntegrityRequirement] // IR
+  ar, ar_ok := keys[v3AvailabilityRequirement] // AR
+
+  return (mav_ok && mav != v3MAVNotDefined) ||
+         (mac_ok && mac != v3MACNotDefined) ||
+         (mpr_ok && mpr != v3MPRNotDefined) ||
+         (mui_ok && mui != v3MUINotDefined) ||
+         (ms_ok && ms != v3MSNotDefined) ||
+         (mc_ok && mc != v3MCNotDefined) ||
+         (mi_ok && mi != v3MINotDefined) ||
+         (ma_ok && ma != v3MANotDefined) ||
+         (cr_ok && cr != v3CRNotDefined) ||
+         (ir_ok && ir != v3IRNotDefined) ||
+         (ar_ok && ar != v3ARNotDefined);
+}
+
+// roundup implemention (from CVSS v3.1 spec, appendix A)
+func roundup(val float64) float64 {
+  return math.Ceil(10.0 * val) / 10.0
+}
+
+var v3MetricCoefs = map[v3Metric]float64 {
+  v3AVNetwork: 0.85, // AV:N
+  v3AVAdjacentNetwork: 0.62, // AV:A
+  v3AVLocal: 0.55, // AV:L
+  v3AVPhysical: 0.2, // AV:P
+
+  v3ACLow: 0.77, // AC:L
+  v3ACHigh: 0.44, // AC:H
+
+  v3PRNone: 0.85, // PR:N
+  v3PRLow: 0.62, // PR:L
+  v3PRHigh: 0.27, // PR:H
+
+  v3UINone: 0.85, // UI:N
+  v3UIRequired: 0.62, // UI:R
+
+  v3CHigh: 0.56, // C:H
+  v3CLow: 0.22, // C:L
+  v3CNone: 0.0, // C:N
+
+  v3IHigh: 0.56, // I:H
+  v3ILow: 0.22, // I:L
+  v3INone: 0.0, // I:N
+
+  v3AHigh: 0.56, // A:H
+  v3ALow: 0.22, // A:L
+  v3ANone: 0.0, // A:N
+
+  v3ENotDefined: 1.0, // E:X
+  v3EHigh: 1.0, // E:H
+  v3EFunctional: 0.97, // E:F
+  v3EProofOfConcept: 0.94, // E:P
+  v3EUnproven: 0.91, // E:U
+
+  v3RLNotDefined: 1.0, // RL:X
+  v3RLUnavailable: 1.0, // RL:U
+  v3RLWorkaround: 0.97, // RL:W
+  v3RLTemporaryFix: 0.96, // RL:T
+  v3RLOfficialFix: 0.95, // RL:O
+
+  v3RCNotDefined: 1.0, // RC:X
+  v3RCConfirmed: 1.0, // RC:C
+  v3RCReasonable: 0.96, // RC:R
+  v3RCUnknown: 0.92, // RC:U
+
+  v3CRNotDefined: 1.0, // CR:X
+  v3CRHigh: 1.5, // CR:H
+  v3CRMedium: 1.0, // CR:M
+  v3CRLow: 0.5, // CR:L
+
+  v3IRNotDefined: 1.0, // IR:X
+  v3IRHigh: 1.5, // IR:H
+  v3IRMedium: 1.0, // IR:M
+  v3IRLow: 0.5, // IR:L
+
+  v3ARNotDefined: 1.0, // AR:X
+  v3ARHigh: 1.5, // AR:H
+  v3ARMedium: 1.0, // AR:M
+  v3ARLow: 0.5, // AR:L
+
+  v3MAVNotDefined: 1.0, // MAV:X
+  v3MAVNetwork: 0.85, // MAV:N
+  v3MAVAdjacentNetwork: 0.62, // MAV:A
+  v3MAVLocal: 0.55, // MAV:L
+  v3MAVPhysical: 0.2, // MAV:P
+
+  v3MACNotDefined: 1.0, // MAC:X
+  v3MACLow: 0.77, // MAC:L
+  v3MACHigh: 0.44, // MAC:H
+
+  v3MPRNotDefined: 1.0, // MPR:X
+  v3MPRNone: 0.85, // MPR:N
+  v3MPRLow: 0.62, // MPR:L
+  v3MPRHigh: 0.27, // MPR:H
+
+  v3MUINotDefined: 1.0, // MUI:X
+  v3MUINone: 0.85, // MUI:N
+  v3MUIRequired: 0.62, // MUI:R
+
+  v3MCNotDefined: 1.0, // MC:X
+  v3MCHigh: 0.56, // MC:H
+  v3MCLow: 0.22, // MC:L
+  v3MCNone: 0.0, // MC:N
+
+  v3MINotDefined: 1.0, // MI:X
+  v3MIHigh: 0.56, // MI:H
+  v3MILow: 0.22, // MI:L
+  v3MINone: 0.0, // MI:N
+
+  v3MANotDefined: 1.0, // MA:X
+  v3MAHigh: 0.56, // MA:H
+  v3MALow: 0.22, // MA:L
+  v3MANone: 0.0, // MA:N
+}
+
+// map of modified metrics to not defined value and base key
+var v3ModMetricDefaults = map[v3Key]struct {
+  notDefined v3Metric
+  baseKey v3Key
+} {
+  v3ModifiedAttackVector: { v3MAVNotDefined, v3AttackVector },
+  v3ModifiedAttackComplexity: { v3MACNotDefined, v3AttackComplexity },
+  v3ModifiedUserInteraction: { v3MUINotDefined, v3UserInteraction },
+  v3ModifiedConfidentiality: { v3MCNotDefined, v3Confidentiality },
+  v3ModifiedIntegrity: { v3MINotDefined, v3Integrity },
+  v3ModifiedAvailability: { v3MANotDefined, v3Availability },
+}
+
+// Get modified metric coefficient, or fall back to base coefficient if
+// modified metric is not defined.
+func getV3ModCoef(keys map[Key]v3Metric, key v3Key) float64 {
+  d := v3ModMetricDefaults[key]
+  if v, _ := keys[key]; v != d.notDefined {
+    // return modified metric coefficient
+    return v3MetricCoefs[v]
+  } else {
+    // return base coefficient
+    return v3MetricCoefs[keys[d.baseKey]]
+  }
+}
+
+var v3PrivReqCoefs = map[v3Metric]map[bool]float64 {
+  v3PRNone: map[bool]float64 { false: 0.85, true: 0.85 },
+  v3PRLow: map[bool]float64 { false: 0.62, true: 0.68 },
+  v3PRHigh: map[bool]float64 { false: 0.27, true: 0.50 },
+  v3MPRNone: map[bool]float64 { false: 0.85, true: 0.85 },
+  v3MPRLow: map[bool]float64 { false: 0.62, true: 0.68 },
+  v3MPRHigh: map[bool]float64 { false: 0.27, true: 0.50 },
+}
-- 
cgit v1.2.3