1use std::fmt::{Display, Formatter};
18use std::str::FromStr;
19
20use serde::{Deserialize, Serialize};
21
22use crate::error::{CVSSError, Result};
23use crate::metric::{Metric, MetricLevelType};
24use crate::severity::SeverityType;
25use crate::v3::attack_complexity::AttackComplexityType;
26use crate::v3::attack_vector::AttackVectorType;
27use crate::v3::impact_metrics::{
28 AvailabilityImpactType, ConfidentialityImpactType, Impact, IntegrityImpactType,
29};
30use crate::v3::privileges_required::PrivilegesRequiredType;
31use crate::v3::scope::ScopeType;
32use crate::v3::user_interaction::UserInteractionType;
33use crate::version::Version;
34
35pub mod attack_complexity;
36pub mod attack_vector;
37pub mod impact_metrics;
38pub mod privileges_required;
39pub mod scope;
40pub mod user_interaction;
41
42#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
47#[serde(rename_all = "camelCase")]
48pub struct ExploitAbility {
49 pub attack_vector: AttackVectorType,
51 pub attack_complexity: AttackComplexityType,
53 pub privileges_required: PrivilegesRequiredType,
55 pub user_interaction: UserInteractionType,
57}
58
59impl ExploitAbility {
60 pub fn score(&self, scope_is_changed: bool) -> f32 {
62 8.22
63 * self.attack_vector.score()
64 * self.attack_complexity.score()
65 * self.user_interaction.score()
66 * self.privileges_required.scoped_score(scope_is_changed)
67 }
68}
69
70#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
80#[serde(rename_all = "camelCase")]
81pub struct CVSS {
82 pub version: Version,
84 pub vector_string: String,
86 #[serde(flatten)]
87 pub exploit_ability: ExploitAbility,
88 pub scope: ScopeType,
90 #[serde(flatten)]
91 pub impact: Impact,
92 pub base_score: f32,
94 pub base_severity: SeverityType,
96}
97
98impl CVSS {
99 fn base_score(&self) -> f32 {
116 let exploit_ability_score = self.exploitability_score();
117 let impact_score_scope = self.impact_score();
118 if impact_score_scope <= 0.0 {
122 0.0
123 } else if !self.scope.is_changed() {
124 roundup((impact_score_scope + exploit_ability_score).min(10.0))
125 } else {
126 roundup((1.08 * (impact_score_scope + exploit_ability_score)).min(10.0))
127 }
128 }
129 pub fn exploitability_score(&self) -> f32 {
130 self.exploit_ability.score(self.scope.is_changed())
131 }
132 pub fn impact_score(&self) -> f32 {
135 let impact_sub_score_base = self.impact.impact_sub_score_base();
136
137 if !self.scope.is_changed() {
138 self.scope.score() * impact_sub_score_base
139 } else {
140 (self.scope.score() * (impact_sub_score_base - 0.029).abs())
141 - (3.25 * (impact_sub_score_base - 0.02).abs().powf(15.0))
142 }
143 }
144 pub fn builder(
145 version: Version,
146 exploit_ability: ExploitAbility,
147 scope: ScopeType,
148 impact: Impact,
149 ) -> CVSSBuilder {
150 CVSSBuilder::new(version, exploit_ability, scope, impact)
151 }
152}
153
154impl Display for CVSS {
155 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
156 write!(
157 f,
158 "CVSS:{}/{}/{}/{}/{}/{}/{}/{}/{}",
159 self.version,
160 self.exploit_ability.attack_vector,
161 self.exploit_ability.attack_complexity,
162 self.exploit_ability.privileges_required,
163 self.exploit_ability.user_interaction,
164 self.scope,
165 self.impact.confidentiality_impact,
166 self.impact.integrity_impact,
167 self.impact.availability_impact
168 )
169 }
170}
171
172impl FromStr for CVSS {
173 type Err = CVSSError;
174 fn from_str(vector_string: &str) -> Result<Self> {
175 let (version, vectors) = match vector_string.split_once('/') {
176 None => {
177 return Err(CVSSError::InvalidPrefix {
178 value: vector_string.to_string(),
179 });
180 }
181 Some((v, vector)) => {
182 let version = Version::from_str(v).unwrap_or_default();
183 (version, vector)
184 }
185 };
186 if matches!(version, Version::None) {
187 return Err(CVSSError::InvalidCVSSVersion {
188 value: version.to_string(),
189 expected: "3.0 or 3.1".to_string(),
190 });
191 }
192 let mut vector = vectors.split('/');
193 let error = CVSSError::InvalidCVSS {
195 key: "CVSS:3.1".to_string(),
196 value: vector_string.to_string(),
197 expected: "".to_string(),
198 };
199 let exploit_ability = ExploitAbility {
200 attack_vector: AttackVectorType::from_str(vector.next().ok_or(&error)?)?,
201 attack_complexity: AttackComplexityType::from_str(vector.next().ok_or(&error)?)?,
202 privileges_required: PrivilegesRequiredType::from_str(vector.next().ok_or(&error)?)?,
203 user_interaction: UserInteractionType::from_str(vector.next().ok_or(&error)?)?,
204 };
205 let scope = ScopeType::from_str(vector.next().ok_or(&error)?)?;
206 let impact = Impact {
207 confidentiality_impact: ConfidentialityImpactType::from_str(vector.next().ok_or(&error)?)?,
208 integrity_impact: IntegrityImpactType::from_str(vector.next().ok_or(&error)?)?,
209 availability_impact: AvailabilityImpactType::from_str(vector.next().ok_or(&error)?)?,
210 };
211 let mut cvss = CVSS {
212 version,
213 vector_string: vector_string.to_string(),
214 exploit_ability,
215 scope,
216 impact,
217 base_score: 0.0,
218 base_severity: SeverityType::None,
219 };
220 cvss.base_score = cvss.base_score();
221 cvss.base_severity = SeverityType::from(cvss.base_score);
222 cvss.vector_string = cvss.to_string();
223 Ok(cvss)
224 }
225}
226
227pub struct CVSSBuilder {
228 pub version: Version,
230 pub exploit_ability: ExploitAbility,
231 pub scope: ScopeType,
233 pub impact: Impact,
234}
235
236impl CVSSBuilder {
238 pub fn new(
239 version: Version,
240 exploit_ability: ExploitAbility,
241 scope: ScopeType,
242 impact: Impact,
243 ) -> Self {
244 Self {
245 version,
246 exploit_ability,
247 scope,
248 impact,
249 }
250 }
251 pub fn build(self) -> CVSS {
252 let Self {
253 version,
254 exploit_ability,
255 scope,
256 impact,
257 } = self;
258 let mut cvss = CVSS {
259 version,
260 vector_string: "".to_string(),
261 exploit_ability,
262 scope,
263 impact,
264 base_score: 0.0,
265 base_severity: SeverityType::None,
266 };
267 cvss.vector_string = cvss.to_string();
268 cvss.base_score = cvss.base_score();
269 cvss.base_severity = SeverityType::from(cvss.base_score);
270 cvss
271 }
272}
273
274#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
278#[serde(rename_all = "camelCase", deny_unknown_fields)]
279pub struct ImpactMetricV3 {
280 #[serde(default)]
281 pub source: Option<String>,
282 #[serde(default)]
283 pub r#type: MetricLevelType,
284 #[serde(alias = "cvssData")]
285 pub cvss_v3: CVSS,
286 pub exploitability_score: f32,
288 pub impact_score: f32,
290}
291
292impl FromStr for ImpactMetricV3 {
293 type Err = CVSSError;
294
295 fn from_str(s: &str) -> Result<Self> {
296 match CVSS::from_str(s) {
297 Ok(c) => {
298 let exploit_ability_score = c.exploitability_score();
299 let impact_score = c.impact_score();
300 Ok(Self {
301 source: None,
302 r#type: Default::default(),
303 cvss_v3: c,
304 exploitability_score: exploit_ability_score,
305 impact_score,
306 })
307 }
308 Err(err) => Err(err),
309 }
310 }
311}
312
313fn roundup(input: f32) -> f32 {
326 let int_input = (input * 100_000.0) as u32;
327 if int_input % 10000 == 0 {
328 (int_input as f32) / 100_000.0
329 } else {
330 let score_floor = ((int_input as f32) / 10_000.0).floor();
331 (score_floor + 1.0) / 10.0
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use crate::v3::roundup;
338
339 #[test]
340 fn roundup_test() {
341 assert_eq!(roundup(4.00), 4.0);
342 assert_eq!(roundup(4.02), 4.1);
343 assert_eq!(roundup(0.8619848), 0.9);
344 assert_eq!(roundup(0.9006104), 1.0)
345 }
346}