-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
5 changed files
with
292 additions
and
0 deletions.
There are no files selected for viewing
27 changes: 27 additions & 0 deletions
27
save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/cvsscalculator/BaseMetrics.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.saveourtool.save.cvsscalculator | ||
|
||
import kotlinx.serialization.Serializable | ||
|
||
/** | ||
* @property version | ||
* @property attackVector | ||
* @property attackComplexity | ||
* @property privilegeRequired | ||
* @property userInteraction | ||
* @property scopeMetric | ||
* @property confidentiality | ||
* @property integrity | ||
* @property availability | ||
*/ | ||
@Serializable | ||
data class BaseMetrics( | ||
var version: Float, | ||
var attackVector: AttackVectorType, | ||
var attackComplexity: AttackComplexityType, | ||
var privilegeRequired: PrivilegesRequiredType, | ||
var userInteraction: UserInteractionType, | ||
var scopeMetric: ScopeType, | ||
var confidentiality: CiaType, | ||
var integrity: CiaType, | ||
var availability: CiaType, | ||
) |
117 changes: 117 additions & 0 deletions
117
...cloud-common/src/commonMain/kotlin/com/saveourtool/save/cvsscalculator/ScoreCalculator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
@file:Suppress("FILE_NAME_MATCH_CLASS") | ||
|
||
package com.saveourtool.save.cvsscalculator | ||
|
||
import com.saveourtool.save.cvsscalculator.CvssMetrics.Companion.ATTACK_COMPLEXITY | ||
import com.saveourtool.save.cvsscalculator.CvssMetrics.Companion.ATTACK_VECTOR | ||
import com.saveourtool.save.cvsscalculator.CvssMetrics.Companion.AVAILABILITY | ||
import com.saveourtool.save.cvsscalculator.CvssMetrics.Companion.CONFIDENTIALITY | ||
import com.saveourtool.save.cvsscalculator.CvssMetrics.Companion.CVSS_VERSION | ||
import com.saveourtool.save.cvsscalculator.CvssMetrics.Companion.INTEGRITY | ||
import com.saveourtool.save.cvsscalculator.CvssMetrics.Companion.PRIVILEGES_REQUIRED | ||
import com.saveourtool.save.cvsscalculator.CvssMetrics.Companion.SCOPE | ||
import com.saveourtool.save.cvsscalculator.CvssMetrics.Companion.USER_INTERACTION | ||
import kotlin.math.min | ||
import kotlin.math.pow | ||
import kotlin.math.roundToInt | ||
|
||
@Suppress("UtilityClassWithPublicConstructor") | ||
private class CvssMetrics { | ||
companion object { | ||
const val ATTACK_COMPLEXITY = "AC" | ||
const val ATTACK_VECTOR = "AV" | ||
const val AVAILABILITY = "A" | ||
const val CONFIDENTIALITY = "C" | ||
const val CVSS_VERSION = "CVSS" | ||
const val INTEGRITY = "I" | ||
const val PRIVILEGES_REQUIRED = "PR" | ||
const val SCOPE = "S" | ||
const val USER_INTERACTION = "UI" | ||
} | ||
} | ||
|
||
@Suppress( | ||
"UnsafeCallOnNullableType", | ||
) | ||
private fun String.parsingVector(): BaseMetrics { | ||
val values = this.toMap() | ||
return BaseMetrics( | ||
version = values.getValue(CVSS_VERSION).toFloat(), | ||
attackVector = values.findOrElseThrow(ATTACK_VECTOR, AttackVectorType::value), | ||
attackComplexity = values.findOrElseThrow(ATTACK_COMPLEXITY, AttackComplexityType::value), | ||
privilegeRequired = values.findOrElseThrow(PRIVILEGES_REQUIRED, PrivilegesRequiredType::value), | ||
userInteraction = values.findOrElseThrow(USER_INTERACTION, UserInteractionType::value), | ||
scopeMetric = values.findOrElseThrow(SCOPE, ScopeType::value), | ||
confidentiality = values.findOrElseThrow(CONFIDENTIALITY, CiaType::value), | ||
integrity = values.findOrElseThrow(INTEGRITY, CiaType::value), | ||
availability = values.findOrElseThrow(AVAILABILITY, CiaType::value), | ||
) | ||
} | ||
|
||
private inline fun <reified T : Enum<T>> Map<String, String>.findOrElseThrow(vector: String, getValue: (T) -> String): T = | ||
get(vector)?.let { value -> enumValues<T>().find { getValue(it) == value } } | ||
?: throw IllegalArgumentException("No such value for $vector.") | ||
|
||
@Suppress("MagicNumber") | ||
private fun String.toMap() = split("/").associate { value -> | ||
value.split(":") | ||
.also { require(it.size == 2) } | ||
.let { it[0] to it[1] } | ||
} | ||
|
||
private fun Map<String, Float>.getWeight(key: String): Float = this.getOrElse(key) { | ||
throw IllegalArgumentException("No such weight for value $key.") | ||
} | ||
|
||
/** | ||
* @param vector | ||
* @return base score criticality | ||
*/ | ||
fun calculateScore(vector: String): Float { | ||
val baseMetrics = vector.parsingVector() | ||
return calculate(baseMetrics) | ||
} | ||
|
||
@Suppress( | ||
"FLOAT_IN_ACCURATE_CALCULATIONS", | ||
"MagicNumber", | ||
"UnsafeCallOnNullableType", | ||
) | ||
private fun calculate(baseMetrics: BaseMetrics): Float { | ||
val iss = 1f - (1f - cia.getWeight(baseMetrics.confidentiality.value)) * (1f - cia.getWeight(baseMetrics.integrity.value)) * (1f - | ||
cia.getWeight(baseMetrics.availability.value)) | ||
val impact: Float = if (baseMetrics.scopeMetric == ScopeType.CHANGED) { | ||
7.52f * (iss - 0.029f) - 3.25f * (iss - 0.02f).pow(15f) | ||
} else { | ||
6.42f * iss | ||
} | ||
val pr = scope.getOrElse(baseMetrics.scopeMetric.value) { throw IllegalArgumentException("No such weights for Scope type ${baseMetrics.scopeMetric.value}.") } | ||
val exploitability = 8.22f * av.getWeight(baseMetrics.attackVector.value) * ac.getWeight(baseMetrics.attackComplexity.value) * | ||
pr.getWeight(baseMetrics.privilegeRequired.value) * | ||
ui.getWeight(baseMetrics.userInteraction.value) | ||
|
||
val baseScore: Float = if (impact <= 0) { | ||
0f | ||
} else { | ||
if (baseMetrics.scopeMetric == ScopeType.UNCHANGED) { | ||
min(impact + exploitability, 10f) | ||
} else { | ||
min((impact + exploitability) * 1.08f, 10f) | ||
} | ||
} | ||
|
||
return roundup(baseScore) | ||
} | ||
|
||
@Suppress( | ||
"FLOAT_IN_ACCURATE_CALCULATIONS", | ||
"MagicNumber", | ||
) | ||
private fun roundup(number: Float): Float { | ||
val value = (number * 100_000).roundToInt() | ||
return if (value % 10_000 == 0) { | ||
value / 100_000f | ||
} else { | ||
(value / 10_000 + 1) / 10f | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/cvsscalculator/VectorTypes.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
@file:Suppress("HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE") | ||
|
||
package com.saveourtool.save.cvsscalculator | ||
|
||
import kotlinx.serialization.Serializable | ||
|
||
/** | ||
* Type of Attack Vector | ||
* | ||
* @property value abbreviated value from the cvss vector | ||
*/ | ||
@Serializable | ||
enum class AttackVectorType(val value: String) { | ||
ADJACENT_NETWORK("A"), | ||
LOCAL("L"), | ||
NETWORK("N"), | ||
PHYSICAL("P"), | ||
; | ||
} | ||
|
||
/** | ||
* Type of Attack Complexity | ||
* | ||
* @property value abbreviated value from the cvss vector | ||
*/ | ||
@Serializable | ||
enum class AttackComplexityType(val value: String) { | ||
HIGH("H"), | ||
LOW("L"), | ||
; | ||
} | ||
|
||
/** | ||
* Type of Privileges Required | ||
* | ||
* @property value abbreviated value from the cvss vector | ||
*/ | ||
@Serializable | ||
enum class PrivilegesRequiredType(val value: String) { | ||
HIGH("H"), | ||
LOW("L"), | ||
NONE("N"), | ||
; | ||
} | ||
|
||
/** | ||
* Type of User Interaction | ||
* | ||
* @property value abbreviated value from the cvss vector | ||
*/ | ||
@Serializable | ||
enum class UserInteractionType(val value: String) { | ||
NONE("N"), | ||
REQUIRED("R"), | ||
; | ||
} | ||
|
||
/** | ||
* Type of Scope | ||
* | ||
* @property value abbreviated value from the cvss vector | ||
*/ | ||
@Serializable | ||
enum class ScopeType(val value: String) { | ||
CHANGED("C"), | ||
UNCHANGED("U"), | ||
; | ||
} | ||
|
||
/** | ||
* Type of CIA | ||
* | ||
* @property value abbreviated value from the cvss vector | ||
*/ | ||
@Serializable | ||
enum class CiaType(val value: String) { | ||
HIGH("H"), | ||
LOW("L"), | ||
NONE("N"), | ||
; | ||
} |
48 changes: 48 additions & 0 deletions
48
save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/cvsscalculator/Weight.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
@file:Suppress( | ||
"HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE", | ||
"MagicNumber", | ||
) | ||
|
||
package com.saveourtool.save.cvsscalculator | ||
|
||
val av = mapOf( | ||
AttackVectorType.NETWORK.value to 0.85f, | ||
AttackVectorType.ADJACENT_NETWORK.value to 0.62f, | ||
AttackVectorType.LOCAL.value to 0.55f, | ||
AttackVectorType.PHYSICAL.value to 0.2f, | ||
) | ||
|
||
val ac = mapOf( | ||
AttackComplexityType.LOW.value to 0.77f, | ||
AttackComplexityType.HIGH.value to 0.44f, | ||
) | ||
|
||
// if Scope / Modified Scope is Unchanged | ||
val prSu = mapOf( | ||
PrivilegesRequiredType.NONE.value to 0.85f, | ||
PrivilegesRequiredType.LOW.value to 0.62f, | ||
PrivilegesRequiredType.HIGH.value to 0.27f, | ||
) | ||
|
||
// if Scope / Modified Scope is Changed | ||
val prSc = mapOf( | ||
PrivilegesRequiredType.NONE.value to 0.85f, | ||
PrivilegesRequiredType.LOW.value to 0.68f, | ||
PrivilegesRequiredType.HIGH.value to 0.50f, | ||
) | ||
|
||
val scope = mapOf( | ||
ScopeType.CHANGED.value to prSc, | ||
ScopeType.UNCHANGED.value to prSu, | ||
) | ||
|
||
val ui = mapOf( | ||
UserInteractionType.NONE.value to 0.85f, | ||
UserInteractionType.REQUIRED.value to 0.62f, | ||
) | ||
|
||
val cia = mapOf( | ||
CiaType.HIGH.value to 0.56f, | ||
CiaType.LOW.value to 0.22f, | ||
CiaType.NONE.value to 0f, | ||
) |
19 changes: 19 additions & 0 deletions
19
...ud-common/src/commonTest/kotlin/com/saveourtool/save/cvsscalculator/CvssCalculatorTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.saveourtool.save.cvsscalculator | ||
|
||
import kotlin.js.JsName | ||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
|
||
class CvssCalculatorTest { | ||
|
||
@Test | ||
@JsName("parsingVector") | ||
fun `parsing vector`() { | ||
|
||
val vector = "CVSS:3.1/AV:A/AC:H/PR:N/UI:R/S:C/C:H/I:N/A:H" | ||
val score = calculateScore(vector); | ||
|
||
assertEquals(score, 7.5f) | ||
} | ||
|
||
} |