daavelino/vulnerability-catalog

View on GitHub
base/catalog/static/cvss/calculator/cvsscalc30.js

Summary

Maintainability
F
1 mo
Test Coverage
/* Copyright (c) 2015, FIRST.ORG, INC.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
 *    products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* This JavaScript contains two main functions. Both take CVSS metric values and calculate CVSS scores for Base,
 * Temporal and Environmental metric groups, their associated severity ratings, and an overall Vector String.
 *
 * Use CVSS.calculateCVSSFromMetrics if you wish to pass metric values as individual parameters.
 * Use CVSS.calculateCVSSFromVector if you wish to pass metric values as a single Vector String.
 *
 * Changelog
 *
 * 2015-08-04  Darius Wiles   Added CVSS.generateXMLFromMetrics and CVSS.generateXMLFromVector functions to return
 *                            XML string representations of: a set of metric values; or a Vector String respectively.
 *                            Moved all constants and functions to an object named "CVSS" to
 *                            reduce the chance of conflicts in global variables when this file is combined with
 *                            other JavaScript code. This will break all existing code that uses this file until
 *                            the string "CVSS." is prepended to all references. The "Exploitability" metric has been
 *                            renamed "Exploit Code Maturity" in the specification, so the same change has been made
 *                            in the code in this file.
 *
 * 2015-04-24  Darius Wiles   Environmental formula modified to eliminate undesirable behavior caused by subtle
 *                            differences in rounding between Temporal and Environmental formulas that often
 *                            caused the latter to be 0.1 lower than than the former when all Environmental
 *                            metrics are "Not defined". Also added a RoundUp1 function to simplify formulas.
 *
 * 2015-04-09  Darius Wiles   Added calculateCVSSFromVector function, license information, cleaned up code and improved
 *                            comments.
 *
 * 2014-12-12  Darius Wiles   Initial release for CVSS 3.0 Preview 2.
 */

// Constants used in the formula. They are not declared as "const" to avoid problems in older browsers.

var CVSS = {};

CVSS.CVSSVersionIdentifier = "CVSS:3.0";
CVSS.exploitabilityCoefficient = 8.22;
CVSS.scopeCoefficient = 1.08;

// A regular expression to validate that a CVSS 3.0 vector string is well formed. It checks metrics and metric
// values. It does not check that a metric is specified more than once and it does not check that all base
// metrics are present. These checks need to be performed separately.

CVSS.vectorStringRegex_30 = /^CVSS:3\.0\/((AV:[NALP]|AC:[LH]|PR:[UNLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XUNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])\/)*(AV:[NALP]|AC:[LH]|PR:[UNLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XUNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$/;


// Associative arrays mapping each metric value to the constant defined in the CVSS scoring formula in the CVSS v3.0
// specification.

CVSS.Weight = {
  AV:   { N: 0.85,  A: 0.62,  L: 0.55,  P: 0.2},
  AC:   { H: 0.44,  L: 0.77},
  PR:   { U:       {N: 0.85,  L: 0.62,  H: 0.27},         // These values are used if Scope is Unchanged
            C:       {N: 0.85,  L: 0.68,  H: 0.5}},         // These values are used if Scope is Changed
  UI:   { N: 0.85,  R: 0.62},
  S:    { U: 6.42,  C: 7.52},                             // Note: not defined as constants in specification
  CIA:  { N: 0,     L: 0.22,  H: 0.56},                   // C, I and A have the same weights

  E:    { X: 1,     U: 0.91,  P: 0.94,  F: 0.97,  H: 1},
  RL:   { X: 1,     O: 0.95,  T: 0.96,  W: 0.97,  U: 1},
  RC:   { X: 1,     U: 0.92,  R: 0.96,  C: 1},

  CIAR: { X: 1,     L: 0.5,   M: 1,     H: 1.5}           // CR, IR and AR have the same weights
};


// Severity rating bands, as defined in the CVSS v3.0 specification.

CVSS.severityRatings  = [ { name: "None",     bottom: 0.0, top:  0.0},
                          { name: "Low",      bottom: 0.1, top:  3.9},
                          { name: "Medium",   bottom: 4.0, top:  6.9},
                          { name: "High",     bottom: 7.0, top:  8.9},
                          { name: "Critical", bottom: 9.0, top: 10.0} ];




/* ** CVSS.calculateCVSSFromMetrics **
 *
 * Takes Base, Temporal and Environmental metric values as individual parameters. Their values are in the short format
 * defined in the CVSS v3.0 standard definition of the Vector String. For example, the AttackComplexity parameter
 * should be either "H" or "L".
 *
 * Returns Base, Temporal and Environmental scores, severity ratings, and an overall Vector String. All Base metrics
 * are required to generate this output. All Temporal and Environmental metric values are optional. Any that are not
 * passed default to "X" ("Not Defined").
 *
 * The output is an object which always has a property named "success".
 *
 * If no errors are encountered, success is Boolean "true", and the following other properties are defined containing
 * scores, severities and a vector string:
 *   baseMetricScore, baseSeverity,
 *   temporalMetricScore, temporalSeverity,
 *   environmentalMetricScore, environmentalSeverity,
 *   vectorString
 *
 * If errors are encountered, success is Boolean "false", and the following other properties are defined:
 *   errorType - a string indicating the error. Either:
 *                 "MissingBaseMetric", if at least one Base metric has not been defined; or
 *                 "UnknownMetricValue", if at least one metric value is invalid.
 *   errorMetrics - an array of strings representing the metrics at fault. The strings are abbreviated versions of the
 *                  metrics, as defined in the CVSS v3.0 standard definition of the Vector String.
 */
CVSS.calculateCVSSFromMetrics = function (
  AttackVector, AttackComplexity, PrivilegesRequired, UserInteraction, Scope, Confidentiality, Integrity, Availability,
  ExploitCodeMaturity, RemediationLevel, ReportConfidence,
  ConfidentialityRequirement, IntegrityRequirement, AvailabilityRequirement,
  ModifiedAttackVector, ModifiedAttackComplexity, ModifiedPrivilegesRequired, ModifiedUserInteraction, ModifiedScope,
  ModifiedConfidentiality, ModifiedIntegrity, ModifiedAvailability) {

  // If input validation fails, this array is populated with strings indicating which metrics failed validation.
  var badMetrics = [];

  // ENSURE ALL BASE METRICS ARE DEFINED
  //
  // We need values for all Base Score metrics to calculate scores.
  // If any Base Score parameters are undefined, create an array of missing metrics and return it with an error.

  if (typeof AttackVector       === "undefined" || AttackVector       === "") { badMetrics.push("AV"); }
  if (typeof AttackComplexity   === "undefined" || AttackComplexity   === "") { badMetrics.push("AC"); }
  if (typeof PrivilegesRequired === "undefined" || PrivilegesRequired === "") { badMetrics.push("PR"); }
  if (typeof UserInteraction    === "undefined" || UserInteraction    === "") { badMetrics.push("UI"); }
  if (typeof Scope              === "undefined" || Scope              === "") { badMetrics.push("S");  }
  if (typeof Confidentiality    === "undefined" || Confidentiality    === "") { badMetrics.push("C");  }
  if (typeof Integrity          === "undefined" || Integrity          === "") { badMetrics.push("I");  }
  if (typeof Availability       === "undefined" || Availability       === "") { badMetrics.push("A");  }

  if (badMetrics.length > 0) {
    return { success: false, errorType: "MissingBaseMetric", errorMetrics: badMetrics };
  }


  // STORE THE METRIC VALUES THAT WERE PASSED AS PARAMETERS
  //
  // Temporal and Environmental metrics are optional, so set them to "X" ("Not Defined") if no value was passed.

  var AV = AttackVector;
  var AC = AttackComplexity;
  var PR = PrivilegesRequired;
  var UI = UserInteraction;
  var S  = Scope;
  var C  = Confidentiality;
  var I  = Integrity;
  var A  = Availability;

  var E =   ExploitCodeMaturity || "X";
  var RL =  RemediationLevel    || "X";
  var RC =  ReportConfidence    || "X";

  var CR =  ConfidentialityRequirement || "X";
  var IR =  IntegrityRequirement       || "X";
  var AR =  AvailabilityRequirement    || "X";
  var MAV = ModifiedAttackVector       || "X";
  var MAC = ModifiedAttackComplexity   || "X";
  var MPR = ModifiedPrivilegesRequired || "X";
  var MUI = ModifiedUserInteraction    || "X";
  var MS =  ModifiedScope              || "X";
  var MC =  ModifiedConfidentiality    || "X";
  var MI =  ModifiedIntegrity          || "X";
  var MA =  ModifiedAvailability       || "X";


  // CHECK VALIDITY OF METRIC VALUES
  //
  // Use the Weight object to ensure that, for every metric, the metric value passed is valid.
  // If any invalid values are found, create an array of their metrics and return it with an error.
  //
  // The Privileges Required (PR) weight depends on Scope, but when checking the validity of PR we must not assume
  // that the given value for Scope is valid. We therefore always look at the weights for Unchanged Scope when
  // performing this check. The same applies for validation of Modified Privileges Required (MPR).
  //
  // The Weights object does not contain "X" ("Not Defined") values for Environmental metrics because we replace them
  // with their Base metric equivalents later in the function. For example, an MAV of "X" will be replaced with the
  // value given for AV. We therefore need to explicitly allow a value of "X" for Environmental metrics.

  if (!CVSS.Weight.AV.hasOwnProperty(AV))   { badMetrics.push("AV"); }
  if (!CVSS.Weight.AC.hasOwnProperty(AC))   { badMetrics.push("AC"); }
  if (!CVSS.Weight.PR.U.hasOwnProperty(PR)) { badMetrics.push("PR"); }
  if (!CVSS.Weight.UI.hasOwnProperty(UI))   { badMetrics.push("UI"); }
  if (!CVSS.Weight.S.hasOwnProperty(S))     { badMetrics.push("S"); }
  if (!CVSS.Weight.CIA.hasOwnProperty(C))   { badMetrics.push("C"); }
  if (!CVSS.Weight.CIA.hasOwnProperty(I))   { badMetrics.push("I"); }
  if (!CVSS.Weight.CIA.hasOwnProperty(A))   { badMetrics.push("A"); }

  if (!CVSS.Weight.E.hasOwnProperty(E))     { badMetrics.push("E"); }
  if (!CVSS.Weight.RL.hasOwnProperty(RL))   { badMetrics.push("RL"); }
  if (!CVSS.Weight.RC.hasOwnProperty(RC))   { badMetrics.push("RC"); }

  if (!(CR  === "X" || CVSS.Weight.CIAR.hasOwnProperty(CR)))  { badMetrics.push("CR"); }
  if (!(IR  === "X" || CVSS.Weight.CIAR.hasOwnProperty(IR)))  { badMetrics.push("IR"); }
  if (!(AR  === "X" || CVSS.Weight.CIAR.hasOwnProperty(AR)))  { badMetrics.push("AR"); }
  if (!(MAV === "X" || CVSS.Weight.AV.hasOwnProperty(MAV)))   { badMetrics.push("MAV"); }
  if (!(MAC === "X" || CVSS.Weight.AC.hasOwnProperty(MAC)))   { badMetrics.push("MAC"); }
  if (!(MPR === "X" || CVSS.Weight.PR.U.hasOwnProperty(MPR))) { badMetrics.push("MPR"); }
  if (!(MUI === "X" || CVSS.Weight.UI.hasOwnProperty(MUI)))   { badMetrics.push("MUI"); }
  if (!(MS  === "X" || CVSS.Weight.S.hasOwnProperty(MS)))     { badMetrics.push("MS"); }
  if (!(MC  === "X" || CVSS.Weight.CIA.hasOwnProperty(MC)))   { badMetrics.push("MC"); }
  if (!(MI  === "X" || CVSS.Weight.CIA.hasOwnProperty(MI)))   { badMetrics.push("MI"); }
  if (!(MA  === "X" || CVSS.Weight.CIA.hasOwnProperty(MA)))   { badMetrics.push("MA"); }

  if (badMetrics.length > 0) {
    return { success: false, errorType: "UnknownMetricValue", errorMetrics: badMetrics };
  }



  // GATHER WEIGHTS FOR ALL METRICS

  var metricWeightAV  = CVSS.Weight.AV    [AV];
  var metricWeightAC  = CVSS.Weight.AC    [AC];
  var metricWeightPR  = CVSS.Weight.PR    [S][PR];  // PR depends on the value of Scope (S).
  var metricWeightUI  = CVSS.Weight.UI    [UI];
  var metricWeightS   = CVSS.Weight.S     [S];
  var metricWeightC   = CVSS.Weight.CIA   [C];
  var metricWeightI   = CVSS.Weight.CIA   [I];
  var metricWeightA   = CVSS.Weight.CIA   [A];

  var metricWeightE   = CVSS.Weight.E     [E];
  var metricWeightRL  = CVSS.Weight.RL    [RL];
  var metricWeightRC  = CVSS.Weight.RC    [RC];

  // For metrics that are modified versions of Base Score metrics, e.g. Modified Attack Vector, use the value of
  // the Base Score metric if the modified version value is "X" ("Not Defined").
  var metricWeightCR  = CVSS.Weight.CIAR  [CR];
  var metricWeightIR  = CVSS.Weight.CIAR  [IR];
  var metricWeightAR  = CVSS.Weight.CIAR  [AR];
  var metricWeightMAV = CVSS.Weight.AV    [MAV !== "X" ? MAV : AV];
  var metricWeightMAC = CVSS.Weight.AC    [MAC !== "X" ? MAC : AC];
  var metricWeightMPR = CVSS.Weight.PR    [MS  !== "X" ? MS  : S] [MPR !== "X" ? MPR : PR];  // Depends on MS.
  var metricWeightMUI = CVSS.Weight.UI    [MUI !== "X" ? MUI : UI];
  var metricWeightMS  = CVSS.Weight.S     [MS  !== "X" ? MS  : S];
  var metricWeightMC  = CVSS.Weight.CIA   [MC  !== "X" ? MC  : C];
  var metricWeightMI  = CVSS.Weight.CIA   [MI  !== "X" ? MI  : I];
  var metricWeightMA  = CVSS.Weight.CIA   [MA  !== "X" ? MA  : A];



  // CALCULATE THE CVSS BASE SCORE

  var baseScore;
  var impactSubScore;
  var exploitabalitySubScore = CVSS.exploitabilityCoefficient * metricWeightAV * metricWeightAC * metricWeightPR * metricWeightUI;
  var impactSubScoreMultiplier = (1 - ((1 - metricWeightC) * (1 - metricWeightI) * (1 - metricWeightA)));

  if (S === 'U') {
    impactSubScore = metricWeightS * impactSubScoreMultiplier;
  } else {
    impactSubScore = metricWeightS * (impactSubScoreMultiplier - 0.029) - 3.25 * Math.pow(impactSubScoreMultiplier - 0.02, 15);
  }

  if (impactSubScore <= 0) {
    baseScore = 0;
  } else {
    if (S === 'U') {
      baseScore = CVSS.roundUp1(Math.min((exploitabalitySubScore + impactSubScore), 10));
    } else {
      baseScore = CVSS.roundUp1(Math.min((exploitabalitySubScore + impactSubScore) * CVSS.scopeCoefficient, 10));
    }
  }



  // CALCULATE THE CVSS TEMPORAL SCORE

  var temporalScore = CVSS.roundUp1(baseScore * metricWeightE * metricWeightRL * metricWeightRC);


  // CALCULATE THE CVSS ENVIRONMENTAL SCORE
  //
  // - envExploitabalitySubScore recalculates the Base Score Exploitability sub-score using any modified values from the
  //   Environmental metrics group in place of the values specified in the Base Score, if any have been defined.
  // - envAdjustedImpactSubScore recalculates the Base Score Impact sub-score using any modified values from the
  //   Environmental metrics group in place of the values specified in the Base Score, and any additional weightings
  //   given in the Environmental metrics group.

  var envScore;
  var envModifiedImpactSubScore;
  var envModifiedExploitabalitySubScore = CVSS.exploitabilityCoefficient * metricWeightMAV * metricWeightMAC * metricWeightMPR * metricWeightMUI;

  var envImpactSubScoreMultiplier = Math.min (1 - (
                                               (1 - metricWeightMC * metricWeightCR) *
                                               (1 - metricWeightMI * metricWeightIR) *
                                               (1 - metricWeightMA * metricWeightAR)), 0.915);

  if (MS === "U" ||
     (MS === "X" && S === "U")) {
    envModifiedImpactSubScore = metricWeightMS * envImpactSubScoreMultiplier;
    envScore = CVSS.roundUp1(CVSS.roundUp1(Math.min(envModifiedImpactSubScore + envModifiedExploitabalitySubScore), 10) *
                        metricWeightE * metricWeightRL * metricWeightRC);
    } else {
    envModifiedImpactSubScore = metricWeightMS * (envImpactSubScoreMultiplier - 0.029) - 3.25 * Math.pow(envImpactSubScoreMultiplier - 0.02, 15);
    envScore = CVSS.roundUp1(CVSS.roundUp1(Math.min(CVSS.scopeCoefficient * (envModifiedImpactSubScore + envModifiedExploitabalitySubScore), 10)) *
                        metricWeightE * metricWeightRL * metricWeightRC);
  }

  if (envModifiedImpactSubScore <= 0) {
    envScore = 0;
  }


  // CONSTRUCT THE VECTOR STRING

  var vectorString =
    CVSS.CVSSVersionIdentifier +
    "/AV:" + AV +
    "/AC:" + AC +
    "/PR:" + PR +
    "/UI:" + UI +
    "/S:"  + S +
    "/C:"  + C +
    "/I:"  + I +
    "/A:"  + A;

  if (E  !== "X")  {vectorString = vectorString + "/E:" + E;}
  if (RL !== "X")  {vectorString = vectorString + "/RL:" + RL;}
  if (RC !== "X")  {vectorString = vectorString + "/RC:" + RC;}

  if (CR  !== "X") {vectorString = vectorString + "/CR:" + CR;}
  if (IR  !== "X") {vectorString = vectorString + "/IR:"  + IR;}
  if (AR  !== "X") {vectorString = vectorString + "/AR:"  + AR;}
  if (MAV !== "X") {vectorString = vectorString + "/MAV:" + MAV;}
  if (MAC !== "X") {vectorString = vectorString + "/MAC:" + MAC;}
  if (MPR !== "X") {vectorString = vectorString + "/MPR:" + MPR;}
  if (MUI !== "X") {vectorString = vectorString + "/MUI:" + MUI;}
  if (MS  !== "X") {vectorString = vectorString + "/MS:"  + MS;}
  if (MC  !== "X") {vectorString = vectorString + "/MC:"  + MC;}
  if (MI  !== "X") {vectorString = vectorString + "/MI:"  + MI;}
  if (MA  !== "X") {vectorString = vectorString + "/MA:"  + MA;}


  // Return an object containing the scores for all three metric groups, and an overall vector string.

  return {
    success: true,
    baseMetricScore: baseScore.toFixed(1),
    baseSeverity: CVSS.severityRating( baseScore.toFixed(1) ),

    temporalMetricScore: temporalScore.toFixed(1),
    temporalSeverity: CVSS.severityRating( temporalScore.toFixed(1) ),

    environmentalMetricScore: envScore.toFixed(1),
    environmentalSeverity: CVSS.severityRating( envScore.toFixed(1) ),

    vectorString: vectorString
  };
};




/* ** CVSS.calculateCVSSFromVector **
 *
 * Takes Base, Temporal and Environmental metric values as a single string in the Vector String format defined
 * in the CVSS v3.0 standard definition of the Vector String.
 *
 * Returns Base, Temporal and Environmental scores, severity ratings, and an overall Vector String. All Base metrics
 * are required to generate this output. All Temporal and Environmental metric values are optional. Any that are not
 * passed default to "X" ("Not Defined").
 *
 * See the comment for the CVSS.calculateCVSSFromMetrics function for details on the function output. In addition to
 * the error conditions listed for that function, this function can also return:
 *   "MalformedVectorString", if the Vector String passed is does not conform to the format in the standard; or
 *   "MultipleDefinitionsOfMetric", if the Vector String is well formed but defines the same metric (or metrics),
 *                                  more than once.
 */
CVSS.calculateCVSSFromVector = function ( vectorString ) {

  var metricValues = {
    AV:  undefined, AC:  undefined, PR:  undefined, UI:  undefined, S:  undefined,
    C:   undefined, I:   undefined, A:   undefined,
    E:   undefined, RL:  undefined, RC:  undefined,
    CR:  undefined, IR:  undefined, AR:  undefined,
    MAV: undefined, MAC: undefined, MPR: undefined, MUI: undefined, MS: undefined,
    MC:  undefined, MI:  undefined, MA:  undefined
  };

  // If input validation fails, this array is populated with strings indicating which metrics failed validation.
  var badMetrics = [];

  if (!CVSS.vectorStringRegex_30.test(vectorString)) {
    return { success: false, errorType: "MalformedVectorString" };
  }

  var metricNameValue = vectorString.substring(CVSS.CVSSVersionIdentifier.length).split("/");

  for (var i in metricNameValue) {
    if (metricNameValue.hasOwnProperty(i)) {

      var singleMetric = metricNameValue[i].split(":");

      if (typeof metricValues[singleMetric[0]] === "undefined") {
        metricValues[singleMetric[0]] = singleMetric[1];
      } else {
        badMetrics.push(singleMetric[0]);
      }
    }
  }

  if (badMetrics.length > 0) {
    return { success: false, errorType: "MultipleDefinitionsOfMetric", errorMetrics: badMetrics };
  }

  return CVSS.calculateCVSSFromMetrics (
    metricValues.AV,  metricValues.AC,  metricValues.PR,  metricValues.UI,  metricValues.S,
    metricValues.C,   metricValues.I,   metricValues.A,
    metricValues.E,   metricValues.RL,  metricValues.RC,
    metricValues.CR,  metricValues.IR,  metricValues.AR,
    metricValues.MAV, metricValues.MAC, metricValues.MPR, metricValues.MUI, metricValues.MS,
    metricValues.MC,  metricValues.MI,  metricValues.MA);
};




/* ** CVSS.roundUp1 **
 *
 * Rounds up the number passed as a parameter to 1 decimal place and returns the result.
 *
 * Standard JavaScript errors thrown when arithmetic operations are performed on non-numbers will be returned if the
 * given input is not a number.
 */
CVSS.roundUp1 = function (d) {
  return Math.ceil (d * 10) / 10;
};




/* ** CVSS.severityRating **
 *
 * Given a CVSS score, returns the name of the severity rating as defined in the CVSS standard.
 * The input needs to be a number between 0.0 to 10.0, to one decimal place of precision.
 *
 * The following error values may be returned instead of a severity rating name:
 *   NaN (JavaScript "Not a Number") - if the input is not a number.
 *   undefined - if the input is a number that is not within the range of any defined severity rating.
 */
CVSS.severityRating = function (score) {
  var severityRatingLength = CVSS.severityRatings.length;

  var validatedScore = Number(score);

  if (isNaN(validatedScore)) {
    return validatedScore;
  }

  for (var i = 0; i < severityRatingLength; i++) {
    if (score >= CVSS.severityRatings[i].bottom && score <= CVSS.severityRatings[i].top) {
      return CVSS.severityRatings[i].name;
    }
  }

  return undefined;
};



///////////////////////////////////////////////////////////////////////////
// DATA AND FUNCTIONS FOR CREATING AN XML REPRESENTATION OF A CVSS SCORE //
///////////////////////////////////////////////////////////////////////////

// A mapping between abbreviated metric values and the string used in the XML representation.
// For example, a Remediation Level (RL) abbreviated metric value of "W" maps to "WORKAROUND".
// For brevity, Base metric values their modified equivalents in the Environmental metric group. We can do this
// because the latter is the same as the former, except it also includes a "NOT_DEFINED" value.

CVSS.XML_MetricNames = {
  E:    { X: "NOT_DEFINED", U: "UNPROVEN",     P: "PROOF_OF_CONCEPT",  F: "FUNCTIONAL",  H: "HIGH"},
  RL:   { X: "NOT_DEFINED", O: "OFFICIAL_FIX", T: "TEMPORARY_FIX",     W: "WORKAROUND",  U: "UNAVAILABLE"},
  RC:   { X: "NOT_DEFINED", U: "UNKNOWN",      R: "REASONABLE",        C: "CONFIRMED"},

  CIAR: { X: "NOT_DEFINED", L: "LOW",              M: "MEDIUM", H: "HIGH"},         // CR, IR and AR use the same metric names
  MAV:  { N: "NETWORK",     A: "ADJACENT_NETWORK", L: "LOCAL",  P: "PHYSICAL", X: "NOT_DEFINED" },
  MAC:  { H: "HIGH",        L: "LOW",              X: "NOT_DEFINED" },
  MPR:  { N: "NONE",        L: "LOW",              H: "HIGH",   X: "NOT_DEFINED" },
  MUI:  { N: "NONE",        R: "REQUIRED",         X: "NOT_DEFINED" },
  MS:   { U: "UNCHANGED",   C: "CHANGED",          X: "NOT_DEFINED" },
  MCIA: { N: "NONE",        L: "LOW",              H: "HIGH",   X: "NOT_DEFINED" }  // C, I and A use the same metric names
};



/* ** CVSS.generateXMLFromMetrics **
 *
 * Takes Base, Temporal and Environmental metric values as individual parameters. Their values are in the short format
 * defined in the CVSS v3.0 standard definition of the Vector String. For example, the AttackComplexity parameter
 * should be either "H" or "L".
 *
 * Returns a single string containing the metric values in XML form. All Base metrics are required to generate this
 * output. All Temporal and Environmental metric values are optional. Any that are not passed will be represented in
 * the XML as NOT_DEFINED. The function returns a string for simplicity. It is arguably better to return the XML as
 * a DOM object, but at the time of writing this leads to complexity due to older browsers using different JavaScript
 * interfaces to do this. Also for simplicity, all Temporal and Environmental metrics are include in the string,
 * even though those with a value of "Not Defined" do not need to be included.
 *
 * The output of this function is an object which always has a property named "success".
 *
 * If no errors are encountered, success is Boolean "true", and the "xmlString" property contains the XML string
 * representation.
 *
 * If errors are encountered, success is Boolean "false", and other properties are defined as per the
 * CVSS.calculateCVSSFromMetrics function. Refer to the comment for that function for more details.
 */
CVSS.generateXMLFromMetrics = function (
  AttackVector, AttackComplexity, PrivilegesRequired, UserInteraction, Scope, Confidentiality, Integrity, Availability,
  ExploitCodeMaturity, RemediationLevel, ReportConfidence,
  ConfidentialityRequirement, IntegrityRequirement, AvailabilityRequirement,
  ModifiedAttackVector, ModifiedAttackComplexity, ModifiedPrivilegesRequired, ModifiedUserInteraction, ModifiedScope,
  ModifiedConfidentiality, ModifiedIntegrity, ModifiedAvailability) {

  // A string containing the XML we wish to output, with placeholders for the CVSS metrics we will substitute for
  // their values, based on the inputs passed to this function.
  var xmlTemplate =
    '<?xml version="1.0" encoding="UTF-8"?>\n' +
    '<cvssv3.0 xmlns="https://www.first.org/cvss/cvss-v3.0.xsd"\n' +
    '  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' +
    '  xsi:schemaLocation="https://www.first.org/cvss/cvss-v3.0.xsd https://www.first.org/cvss/cvss-v3.0.xsd"\n' +
    '  >\n' +
    '\n' +
    '  <base_metrics>\n' +
    '    <attack-vector>__AttackVector__</attack-vector>\n' +
    '    <attack-complexity>__AttackComplexity__</attack-complexity>\n' +
    '    <privileges-required>__PrivilegesRequired__</privileges-required>\n' +
    '    <user-interaction>__UserInteraction__</user-interaction>\n' +
    '    <scope>__Scope__</scope>\n' +
    '    <confidentiality-impact>__Confidentiality__</confidentiality-impact>\n' +
    '    <integrity-impact>__Integrity__</integrity-impact>\n' +
    '    <availability-impact>__Availability__</availability-impact>\n' +
    '    <base-score>__BaseScore__</base-score>\n' +
    '    <base-severity>__BaseSeverityRating__</base-severity>\n' +
    '  </base_metrics>\n' +
    '\n' +
    '  <temporal_metrics>\n' +
    '    <exploit-code-maturity>__ExploitCodeMaturity__</exploit-code-maturity>\n' +
    '    <remediation-level>__RemediationLevel__</remediation-level>\n' +
    '    <report-confidence>__ReportConfidence__</report-confidence>\n' +
    '    <temporal-score>__TemporalScore__</temporal-score>\n' +
    '    <temporal-severity>__TemporalSeverityRating__</temporal-severity>\n' +
    '  </temporal_metrics>\n' +
    '\n' +
    '  <environmental_metrics>\n' +
    '    <confidentiality-requirement>__ConfidentialityRequirement__</confidentiality-requirement>\n' +
    '    <integrity-requirement>__IntegrityRequirement__</integrity-requirement>\n' +
    '    <availability-requirement>__AvailabilityRequirement__</availability-requirement>\n' +
    '    <modified-attack-vector>__ModifiedAttackVector__</modified-attack-vector>\n' +
    '    <modified-attack-complexity>__ModifiedAttackComplexity__</modified-attack-complexity>\n' +
    '    <modified-privileges-required>__ModifiedPrivilegesRequired__</modified-privileges-required>\n' +
    '    <modified-user-interaction>__ModifiedUserInteraction__</modified-user-interaction>\n' +
    '    <modified-scope>__ModifiedScope__</modified-scope>\n' +
    '    <modified-confidentiality-impact>__ModifiedConfidentiality__</modified-confidentiality-impact>\n' +
    '    <modified-integrity-impact>__ModifiedIntegrity__</modified-integrity-impact>\n' +
    '    <modified-availability-impact>__ModifiedAvailability__</modified-availability-impact>\n' +
    '    <environmental-score>__EnvironmentalScore__</environmental-score>\n' +
    '    <environmental-severity>__EnvironmentalSeverityRating__</environmental-severity>\n' +
    '  </environmental_metrics>\n' +
    '\n' +
    '</cvssv3.0>\n';


  // Call CVSS.calculateCVSSFromMetrics to validate all the parameters and generate scores and severity ratings.
  // If that function returns an error, immediately return it to the caller of this function.
  var result = CVSS.calculateCVSSFromMetrics (
    AttackVector, AttackComplexity, PrivilegesRequired, UserInteraction, Scope, Confidentiality, Integrity, Availability,
    ExploitCodeMaturity, RemediationLevel, ReportConfidence,
    ConfidentialityRequirement, IntegrityRequirement, AvailabilityRequirement,
    ModifiedAttackVector, ModifiedAttackComplexity, ModifiedPrivilegesRequired, ModifiedUserInteraction, ModifiedScope,
    ModifiedConfidentiality, ModifiedIntegrity, ModifiedAvailability);

  if (result.success !== true) {
    return result;
  }

  var xmlOutput = xmlTemplate;
  xmlOutput = xmlOutput.replace ("__AttackVector__",        CVSS.XML_MetricNames["MAV"][AttackVector]);
  xmlOutput = xmlOutput.replace ("__AttackComplexity__",    CVSS.XML_MetricNames["MAC"][AttackComplexity]);
  xmlOutput = xmlOutput.replace ("__PrivilegesRequired__",  CVSS.XML_MetricNames["MPR"][PrivilegesRequired]);
  xmlOutput = xmlOutput.replace ("__UserInteraction__",     CVSS.XML_MetricNames["MUI"][UserInteraction]);
  xmlOutput = xmlOutput.replace ("__Scope__",               CVSS.XML_MetricNames["MS"][Scope]);
  xmlOutput = xmlOutput.replace ("__Confidentiality__",     CVSS.XML_MetricNames["MCIA"][Confidentiality]);
  xmlOutput = xmlOutput.replace ("__Integrity__",           CVSS.XML_MetricNames["MCIA"][Integrity]);
  xmlOutput = xmlOutput.replace ("__Availability__",        CVSS.XML_MetricNames["MCIA"][Availability]);
  xmlOutput = xmlOutput.replace ("__BaseScore__",           result.baseMetricScore);
  xmlOutput = xmlOutput.replace ("__BaseSeverityRating__",  result.baseSeverity);

  xmlOutput = xmlOutput.replace ("__ExploitCodeMaturity__",     CVSS.XML_MetricNames["E"][ExploitCodeMaturity || "X"]);
  xmlOutput = xmlOutput.replace ("__RemediationLevel__",        CVSS.XML_MetricNames["RL"][RemediationLevel || "X"]);
  xmlOutput = xmlOutput.replace ("__ReportConfidence__",        CVSS.XML_MetricNames["RC"][ReportConfidence || "X"]);
  xmlOutput = xmlOutput.replace ("__TemporalScore__",           result.temporalMetricScore);
  xmlOutput = xmlOutput.replace ("__TemporalSeverityRating__",  result.temporalSeverity);

  xmlOutput = xmlOutput.replace ("__ConfidentialityRequirement__",  CVSS.XML_MetricNames["CIAR"][ConfidentialityRequirement || "X"]);
  xmlOutput = xmlOutput.replace ("__IntegrityRequirement__",        CVSS.XML_MetricNames["CIAR"][IntegrityRequirement || "X"]);
  xmlOutput = xmlOutput.replace ("__AvailabilityRequirement__",     CVSS.XML_MetricNames["CIAR"][AvailabilityRequirement || "X"]);
  xmlOutput = xmlOutput.replace ("__ModifiedAttackVector__",        CVSS.XML_MetricNames["MAV"][ModifiedAttackVector || "X"]);
  xmlOutput = xmlOutput.replace ("__ModifiedAttackComplexity__",    CVSS.XML_MetricNames["MAC"][ModifiedAttackComplexity || "X"]);
  xmlOutput = xmlOutput.replace ("__ModifiedPrivilegesRequired__",  CVSS.XML_MetricNames["MPR"][ModifiedPrivilegesRequired || "X"]);
  xmlOutput = xmlOutput.replace ("__ModifiedUserInteraction__",     CVSS.XML_MetricNames["MUI"][ModifiedUserInteraction || "X"]);
  xmlOutput = xmlOutput.replace ("__ModifiedScope__",               CVSS.XML_MetricNames["MS"][ModifiedScope || "X"]);
  xmlOutput = xmlOutput.replace ("__ModifiedConfidentiality__",     CVSS.XML_MetricNames["MCIA"][ModifiedConfidentiality || "X"]);
  xmlOutput = xmlOutput.replace ("__ModifiedIntegrity__",           CVSS.XML_MetricNames["MCIA"][ModifiedIntegrity || "X"]);
  xmlOutput = xmlOutput.replace ("__ModifiedAvailability__",        CVSS.XML_MetricNames["MCIA"][ModifiedAvailability || "X"]);
  xmlOutput = xmlOutput.replace ("__EnvironmentalScore__",          result.environmentalMetricScore);
  xmlOutput = xmlOutput.replace ("__EnvironmentalSeverityRating__", result.environmentalSeverity);

  return { success: true, xmlString: xmlOutput };
};



/* ** CVSS.generateXMLFromVector **
 *
 * Takes Base, Temporal and Environmental metric values as a single string in the Vector String format defined
 * in the CVSS v3.0 standard definition of the Vector String.
 *
 * Returns an XML string representation of this input. See the comment for CVSS.generateXMLFromMetrics for more
 * detail on inputs, return values and errors. In addition to the error conditions listed for that function, this
 * function can also return:
 *   "MalformedVectorString", if the Vector String passed is does not conform to the format in the standard; or
 *   "MultipleDefinitionsOfMetric", if the Vector String is well formed but defines the same metric (or metrics),
 *                                  more than once.
 */
CVSS.generateXMLFromVector = function ( vectorString ) {

  var metricValues = {
    AV:  undefined, AC:  undefined, PR:  undefined, UI:  undefined, S:  undefined,
    C:   undefined, I:   undefined, A:   undefined,
    E:   undefined, RL:  undefined, RC:  undefined,
    CR:  undefined, IR:  undefined, AR:  undefined,
    MAV: undefined, MAC: undefined, MPR: undefined, MUI: undefined, MS: undefined,
    MC:  undefined, MI:  undefined, MA:  undefined
  };

  // If input validation fails, this array is populated with strings indicating which metrics failed validation.
  var badMetrics = [];

  if (!CVSS.vectorStringRegex_30.test(vectorString)) {
    return { success: false, errorType: "MalformedVectorString" };
  }

  var metricNameValue = vectorString.substring(CVSS.CVSSVersionIdentifier.length).split("/");

  for (var i in metricNameValue) {
    if (metricNameValue.hasOwnProperty(i)) {

      var singleMetric = metricNameValue[i].split(":");

      if (typeof metricValues[singleMetric[0]] === "undefined") {
        metricValues[singleMetric[0]] = singleMetric[1];
      } else {
        badMetrics.push(singleMetric[0]);
      }
    }
  }

  if (badMetrics.length > 0) {
    return { success: false, errorType: "MultipleDefinitionsOfMetric", errorMetrics: badMetrics };
  }

  return CVSS.generateXMLFromMetrics (
    metricValues.AV,  metricValues.AC,  metricValues.PR,  metricValues.UI,  metricValues.S,
    metricValues.C,   metricValues.I,   metricValues.A,
    metricValues.E,   metricValues.RL,  metricValues.RC,
    metricValues.CR,  metricValues.IR,  metricValues.AR,
    metricValues.MAV, metricValues.MAC, metricValues.MPR, metricValues.MUI, metricValues.MS,
    metricValues.MC,  metricValues.MI,  metricValues.MA);
};