cvss3/vector.go (190 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cvss3 import ( "fmt" "strings" ) const ( prefix = "CVSS:" partSeparator = "/" metricSeparator = ":" ) // vector version, int part represents the minor version // version(1) would mean version 3.1 type version int func (v version) String() string { return fmt.Sprintf("3.%d", int(v)) } func versionFromString(v string) (version, error) { switch v { case "3.0": return version(0), nil case "3.1": return version(1), nil default: return version(0), fmt.Errorf("cvss v3 version %s not supported", v) } } // Vector represents a CVSSv3 vector, holds all metrics inside (base, temporal and environmental) type Vector struct { version version BaseMetrics TemporalMetrics EnvironmentalMetrics } // For some metrics, "undefined" is equivalent to specifying another value. eg. // E:X is equivalent to E:H. var undefinedEquivalent = map[string]string{ // Temporal metrics "E": "H", "RL": "U", "RC": "C", // Environmental metrics "CR": "M", "IR": "M", "AR": "M", } func equivalent(metric, value string) string { if value != "X" { return value } e, ok := undefinedEquivalent[metric] if !ok { return value } return e } // Equal returns true if o represents the same vector as v. // // Note that the definition of equal here means that two vectors with different // string representations can still be equal. For instance RL:X is defined as // the same as RL:U. From the spec: // // Assigning this value indicates there is insufficient information to choose // one of the other values, and has no impact on the overall Temporal Score, // i.e., it has the same effect on scoring as assigning Unavailable. // // https://www.first.org/cvss/specification-document: func (v Vector) Equal(o Vector) bool { vDefs := v.definables() oDefs := o.definables() for _, metric := range order { a := equivalent(metric, vDefs[metric].String()) b := equivalent(metric, oDefs[metric].String()) if a != b { return false } } return true } // String returns this vectors representation as a string // it shouldn't depend on the order of metrics func (v Vector) String() string { var sb strings.Builder fmt.Fprintf(&sb, "%s%s/", prefix, v.version) defineables := v.definables() first := true for _, metric := range order { def := defineables[metric] if !def.defined() { continue } if !first { fmt.Fprint(&sb, partSeparator) } else { first = false } fmt.Fprintf(&sb, "%s%s%s", metric, metricSeparator, def) } return sb.String() } // VectorFromString will parse a string into a Vector, or return an error if it can't be parsed func VectorFromString(str string) (Vector, error) { var v Vector // check for prefix and trim it str = strings.ToUpper(str) if !strings.HasPrefix(str, prefix) { return v, fmt.Errorf("vector missing %q prefix: %q", prefix, str) } str = strings.TrimPrefix(str, prefix) // extract version slashIdx := strings.IndexByte(str, '/') var err error if v.version, err = versionFromString(str[:slashIdx]); err != nil { return v, err } str = str[slashIdx+1:] // parse all metrics parseables := v.parseables() for _, part := range strings.Split(str, partSeparator) { tmp := strings.Split(part, metricSeparator) if len(tmp) != 2 { return v, fmt.Errorf("need two values separated by %s, got %q", metricSeparator, part) } metric, value := tmp[0], tmp[1] if p, ok := parseables[metric]; !ok { return v, fmt.Errorf("undefined metric %s with value %s", metric, value) } else if err := p.parse(value); err != nil { return v, fmt.Errorf("error occurred while parsing metric %s: %v", metric, err) } } return v, nil } // Absorb will override only metrics in the current vector from the one given which are defined // If the other vector specifies only a single metric with all others undefined, the resulting // vector will contain all metrics it previously did, with only the new one overriden func (v *Vector) Absorb(other Vector) { parseables := v.parseables() for metric, defineable := range other.definables() { if defineable.defined() { parseables[metric].parse(defineable.String()) } } } // AbsorbIfDefined is like Absorb but will not override vector components that // are not present in v. func (v *Vector) AbsorbIfDefined(other Vector) { parseables := v.parseables() old := v.definables() for metric, defineable := range other.definables() { if old[metric].defined() && defineable.defined() { parseables[metric].parse(defineable.String()) } } } // helpers var order = []string{"AV", "AC", "PR", "UI", "S", "C", "I", "A", "E", "RL", "RC", "CR", "IR", "AR", "MAV", "MAC", "MPR", "MUI", "MS", "MC", "MI", "MA", "ME", "MRL", "MRC"} type defineable interface { defined() bool String() string } type parseable interface { parse(string) error } func (v *Vector) definables() map[string]defineable { return map[string]defineable{ // base metrics "AV": v.BaseMetrics.AttackVector, "AC": v.BaseMetrics.AttackComplexity, "PR": v.BaseMetrics.PrivilegesRequired, "UI": v.BaseMetrics.UserInteraction, "S": v.BaseMetrics.Scope, "C": v.BaseMetrics.Confidentiality, "I": v.BaseMetrics.Integrity, "A": v.BaseMetrics.Availability, // temporal metrics "E": v.TemporalMetrics.ExploitCodeMaturity, "RL": v.TemporalMetrics.RemediationLevel, "RC": v.TemporalMetrics.ReportConfidence, // environmental metrics "CR": v.EnvironmentalMetrics.ConfidentialityRequirement, "IR": v.EnvironmentalMetrics.IntegrityRequirement, "AR": v.EnvironmentalMetrics.AvailabilityRequirement, "MAV": v.EnvironmentalMetrics.ModifiedAttackVector, "MAC": v.EnvironmentalMetrics.ModifiedAttackComplexity, "MPR": v.EnvironmentalMetrics.ModifiedPrivilegesRequired, "MUI": v.EnvironmentalMetrics.ModifiedUserInteraction, "MS": v.EnvironmentalMetrics.ModifiedScope, "MC": v.EnvironmentalMetrics.ModifiedConfidentiality, "MI": v.EnvironmentalMetrics.ModifiedIntegrity, "MA": v.EnvironmentalMetrics.ModifiedAvailability, "ME": v.EnvironmentalMetrics.ModifiedExploitCodeMaturity, "MRL": v.EnvironmentalMetrics.ModifiedRemediationLevel, "MRC": v.EnvironmentalMetrics.ModifiedReportConfidence, } } func (v *Vector) parseables() map[string]parseable { return map[string]parseable{ // base metrics "AV": &v.BaseMetrics.AttackVector, "AC": &v.BaseMetrics.AttackComplexity, "PR": &v.BaseMetrics.PrivilegesRequired, "UI": &v.BaseMetrics.UserInteraction, "S": &v.BaseMetrics.Scope, "C": &v.BaseMetrics.Confidentiality, "I": &v.BaseMetrics.Integrity, "A": &v.BaseMetrics.Availability, // temporal metrics "E": &v.TemporalMetrics.ExploitCodeMaturity, "RL": &v.TemporalMetrics.RemediationLevel, "RC": &v.TemporalMetrics.ReportConfidence, // environmental metrics "CR": &v.EnvironmentalMetrics.ConfidentialityRequirement, "IR": &v.EnvironmentalMetrics.IntegrityRequirement, "AR": &v.EnvironmentalMetrics.AvailabilityRequirement, "MAV": &v.EnvironmentalMetrics.ModifiedAttackVector, "MAC": &v.EnvironmentalMetrics.ModifiedAttackComplexity, "MPR": &v.EnvironmentalMetrics.ModifiedPrivilegesRequired, "MUI": &v.EnvironmentalMetrics.ModifiedUserInteraction, "MS": &v.EnvironmentalMetrics.ModifiedScope, "MC": &v.EnvironmentalMetrics.ModifiedConfidentiality, "MI": &v.EnvironmentalMetrics.ModifiedIntegrity, "MA": &v.EnvironmentalMetrics.ModifiedAvailability, "ME": &v.EnvironmentalMetrics.ModifiedExploitCodeMaturity, "MRL": &v.EnvironmentalMetrics.ModifiedRemediationLevel, "MRC": &v.EnvironmentalMetrics.ModifiedReportConfidence, } }