cvss/v4/
vector.rs

1//! Score computing for CVSSv4
2
3use crate::{
4    Error, PREFIX,
5    v4::{
6        MetricType,
7        metric::{
8            base::{
9                AttackComplexity, AttackRequirements, AttackVector,
10                AvailabilityImpactToTheSubsequentSystem, AvailabilityImpactToTheVulnerableSystem,
11                ConfidentialityImpactToTheSubsequentSystem,
12                ConfidentialityImpactToTheVulnerableSystem, IntegrityImpactToTheSubsequentSystem,
13                IntegrityImpactToTheVulnerableSystem, PrivilegesRequired, UserInteraction,
14            },
15            environmental::{
16                AvailabilityRequirements, ConfidentialityRequirements, IntegrityRequirements,
17                ModifiedAttackComplexity, ModifiedAttackRequirements, ModifiedAttackVector,
18                ModifiedAvailabilityImpactToTheSubsequentSystem,
19                ModifiedAvailabilityImpactToTheVulnerableSystem,
20                ModifiedConfidentialityImpactToTheSubsequentSystem,
21                ModifiedConfidentialityImpactToTheVulnerableSystem,
22                ModifiedIntegrityImpactToTheSubsequentSystem,
23                ModifiedIntegrityImpactToTheVulnerableSystem, ModifiedPrivilegesRequired,
24                ModifiedUserInteraction,
25            },
26            supplemental::{
27                Automatable, ProviderUrgency, Recovery, Safety, ValueDensity,
28                VulnerabilityResponseEffort,
29            },
30            threat::ExploitMaturity,
31        },
32    },
33};
34use alloc::{borrow::ToOwned, string::String, vec::Vec};
35use core::{fmt, str::FromStr};
36#[cfg(feature = "serde")]
37use {
38    alloc::string::ToString,
39    serde::{Deserialize, Serialize, de, ser},
40};
41
42#[cfg(feature = "std")]
43use crate::v4::Score;
44use crate::v4::score::Nomenclature;
45
46/// A CVSS 4.0 vector
47#[derive(Clone, Debug, Default, Eq, PartialEq)]
48pub struct Vector {
49    /// Minor component of the version
50    pub minor_version: usize,
51
52    /// Attack Complexity (AC)
53    pub(crate) ac: Option<AttackComplexity>,
54    /// Attack Requirements (AT)
55    pub(crate) at: Option<AttackRequirements>,
56    /// Attack Vector (AV)
57    pub(crate) av: Option<AttackVector>,
58    /// Privileges Required (PR)
59    pub(crate) pr: Option<PrivilegesRequired>,
60    /// Availability Impact to the Subsequent System (SA)
61    pub(crate) sa: Option<AvailabilityImpactToTheSubsequentSystem>,
62    /// Confidentiality Impact to the Subsequent System (SC)
63    pub(crate) sc: Option<ConfidentialityImpactToTheSubsequentSystem>,
64    /// Integrity Impact to the Subsequent System (SI)
65    pub(crate) si: Option<IntegrityImpactToTheSubsequentSystem>,
66    /// User Interaction (UI)
67    pub(crate) ui: Option<UserInteraction>,
68    /// Availability Impact to the Vulnerable System (VA)
69    pub(crate) va: Option<AvailabilityImpactToTheVulnerableSystem>,
70    /// Confidentiality Impact to the Vulnerable System (VC)
71    pub(crate) vc: Option<ConfidentialityImpactToTheVulnerableSystem>,
72    /// Integrity Impact to the Vulnerable System (VI)
73    pub(crate) vi: Option<IntegrityImpactToTheVulnerableSystem>,
74    /// Exploit Maturity (E)
75    pub(crate) e: Option<ExploitMaturity>,
76    /// Availability Requirements (AR)
77    pub(crate) ar: Option<AvailabilityRequirements>,
78    /// Confidentiality Requirements (CR)
79    pub(crate) cr: Option<ConfidentialityRequirements>,
80    /// Integrity Requirements (IR)
81    pub(crate) ir: Option<IntegrityRequirements>,
82    /// Modified Attack Complexity (AC)
83    pub(crate) mac: Option<ModifiedAttackComplexity>,
84    /// Modified Attack Requirements (MAT)
85    pub(crate) mat: Option<ModifiedAttackRequirements>,
86    /// Modified Attack Vector (MAV)
87    pub(crate) mav: Option<ModifiedAttackVector>,
88    /// Modified Privileges Required (MPR)
89    pub(crate) mpr: Option<ModifiedPrivilegesRequired>,
90    /// Modified Availability Impact to the Subsequent System (MSA)
91    pub(crate) msa: Option<ModifiedAvailabilityImpactToTheSubsequentSystem>,
92    /// Modified Confidentiality Impact to the Subsequent System (MSC)
93    pub(crate) msc: Option<ModifiedConfidentialityImpactToTheSubsequentSystem>,
94    /// Modified Integrity Impact to the Subsequent System (MSI)
95    pub(crate) msi: Option<ModifiedIntegrityImpactToTheSubsequentSystem>,
96    /// Modified User Interaction (MUI)
97    pub(crate) mui: Option<ModifiedUserInteraction>,
98    /// Modified Availability Impact to the Vulnerable System (MVA)
99    pub(crate) mva: Option<ModifiedAvailabilityImpactToTheVulnerableSystem>,
100    /// Modified Confidentiality Impact to the Vulnerable System (MVC)
101    pub(crate) mvc: Option<ModifiedConfidentialityImpactToTheVulnerableSystem>,
102    /// Modified Integrity Impact to the Vulnerable System (MVI)
103    pub(crate) mvi: Option<ModifiedIntegrityImpactToTheVulnerableSystem>,
104    /// Automatable (AU)
105    pub(crate) au: Option<Automatable>,
106    /// Recovery (R)
107    pub(crate) r: Option<Recovery>,
108    /// Vulnerability Response Effort (RE)
109    pub(crate) re: Option<VulnerabilityResponseEffort>,
110    /// Safety (S)
111    pub(crate) s: Option<Safety>,
112    /// Provider Urgency (U)
113    pub(crate) u: Option<ProviderUrgency>,
114    /// Value Density (V)
115    pub(crate) v: Option<ValueDensity>,
116}
117
118impl Vector {
119    /// Get the numerical score of the vector
120    #[cfg(feature = "std")]
121    pub fn score(&self) -> Score {
122        self.into()
123    }
124
125    /// Get the nomenclature of the vector
126    ///
127    /// This nomenclature should be used wherever a numerical CVSS value is displayed or communicated.
128    pub fn nomenclature(&self) -> Nomenclature {
129        Nomenclature::from(self)
130    }
131
132    /// Iterate over all defined vector metrics
133    pub fn metrics(&self) -> impl Iterator<Item = (MetricType, &dyn fmt::Debug)> {
134        [
135            (
136                MetricType::AC,
137                self.ac.as_ref().map(|m| m as &dyn fmt::Debug),
138            ),
139            (
140                MetricType::AT,
141                self.at.as_ref().map(|m| m as &dyn fmt::Debug),
142            ),
143            (
144                MetricType::AV,
145                self.av.as_ref().map(|m| m as &dyn fmt::Debug),
146            ),
147            (
148                MetricType::PR,
149                self.pr.as_ref().map(|m| m as &dyn fmt::Debug),
150            ),
151            (
152                MetricType::SA,
153                self.sa.as_ref().map(|m| m as &dyn fmt::Debug),
154            ),
155            (
156                MetricType::SC,
157                self.sc.as_ref().map(|m| m as &dyn fmt::Debug),
158            ),
159            (
160                MetricType::SI,
161                self.si.as_ref().map(|m| m as &dyn fmt::Debug),
162            ),
163            (
164                MetricType::UI,
165                self.ui.as_ref().map(|m| m as &dyn fmt::Debug),
166            ),
167            (
168                MetricType::VA,
169                self.va.as_ref().map(|m| m as &dyn fmt::Debug),
170            ),
171            (
172                MetricType::VC,
173                self.vc.as_ref().map(|m| m as &dyn fmt::Debug),
174            ),
175            (
176                MetricType::VI,
177                self.vi.as_ref().map(|m| m as &dyn fmt::Debug),
178            ),
179            (MetricType::E, self.e.as_ref().map(|m| m as &dyn fmt::Debug)),
180            (
181                MetricType::AR,
182                self.ar.as_ref().map(|m| m as &dyn fmt::Debug),
183            ),
184            (
185                MetricType::CR,
186                self.cr.as_ref().map(|m| m as &dyn fmt::Debug),
187            ),
188            (
189                MetricType::IR,
190                self.ir.as_ref().map(|m| m as &dyn fmt::Debug),
191            ),
192            (
193                MetricType::MAC,
194                self.mac.as_ref().map(|m| m as &dyn fmt::Debug),
195            ),
196            (
197                MetricType::MAT,
198                self.mat.as_ref().map(|m| m as &dyn fmt::Debug),
199            ),
200            (
201                MetricType::MAV,
202                self.mav.as_ref().map(|m| m as &dyn fmt::Debug),
203            ),
204            (
205                MetricType::MPR,
206                self.mpr.as_ref().map(|m| m as &dyn fmt::Debug),
207            ),
208            (
209                MetricType::MSA,
210                self.msa.as_ref().map(|m| m as &dyn fmt::Debug),
211            ),
212            (
213                MetricType::MSC,
214                self.msc.as_ref().map(|m| m as &dyn fmt::Debug),
215            ),
216            (
217                MetricType::MSI,
218                self.msi.as_ref().map(|m| m as &dyn fmt::Debug),
219            ),
220            (
221                MetricType::MUI,
222                self.mui.as_ref().map(|m| m as &dyn fmt::Debug),
223            ),
224            (
225                MetricType::MVA,
226                self.mva.as_ref().map(|m| m as &dyn fmt::Debug),
227            ),
228            (
229                MetricType::MVC,
230                self.mvc.as_ref().map(|m| m as &dyn fmt::Debug),
231            ),
232            (
233                MetricType::MVI,
234                self.mvi.as_ref().map(|m| m as &dyn fmt::Debug),
235            ),
236            (
237                MetricType::AU,
238                self.au.as_ref().map(|m| m as &dyn fmt::Debug),
239            ),
240            (MetricType::R, self.r.as_ref().map(|m| m as &dyn fmt::Debug)),
241            (
242                MetricType::RE,
243                self.re.as_ref().map(|m| m as &dyn fmt::Debug),
244            ),
245            (MetricType::S, self.s.as_ref().map(|m| m as &dyn fmt::Debug)),
246            (MetricType::U, self.u.as_ref().map(|m| m as &dyn fmt::Debug)),
247            (MetricType::V, self.v.as_ref().map(|m| m as &dyn fmt::Debug)),
248        ]
249        .into_iter()
250        .filter_map(|(ty, m)| m.map(|m| (ty, m)))
251    }
252
253    /// Check for required base metrics presence
254    ///
255    /// Defined in <https://www.first.org/cvss/v4.0/specification-document#Vector-String>
256    fn check_mandatory_metrics(&self) -> Result<(), Error> {
257        fn ensure_present<T>(metric: Option<T>, metric_type: MetricType) -> Result<(), Error> {
258            if metric.is_none() {
259                return Err(Error::MissingMandatoryMetricV4 { metric_type });
260            }
261            Ok(())
262        }
263
264        ensure_present(self.ac.as_ref(), MetricType::AC)?;
265        ensure_present(self.at.as_ref(), MetricType::AT)?;
266        ensure_present(self.av.as_ref(), MetricType::AV)?;
267        ensure_present(self.pr.as_ref(), MetricType::PR)?;
268        ensure_present(self.sa.as_ref(), MetricType::SA)?;
269        ensure_present(self.sc.as_ref(), MetricType::SC)?;
270        ensure_present(self.si.as_ref(), MetricType::SI)?;
271        ensure_present(self.ui.as_ref(), MetricType::UI)?;
272        ensure_present(self.va.as_ref(), MetricType::VA)?;
273        ensure_present(self.vc.as_ref(), MetricType::VC)?;
274        ensure_present(self.vi.as_ref(), MetricType::VI)?;
275        Ok(())
276    }
277}
278
279macro_rules! write_metrics {
280    ($f:expr, $($metric:expr),+) => {
281        $(
282            if let Some(metric) = $metric {
283                write!($f, "/{}", metric)?;
284            }
285        )+
286    };
287}
288
289impl fmt::Display for Vector {
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        write!(f, "{}:4.{}", PREFIX, self.minor_version)?;
292        write_metrics!(
293            f, self.av, self.ac, self.at, self.pr, self.ui, self.vc, self.vi, self.va, self.sc,
294            self.si, self.sa, self.e, self.cr, self.ir, self.ar, self.mav, self.mac, self.mat,
295            self.mpr, self.mui, self.mvc, self.mvi, self.mva, self.msc, self.msi, self.msa, self.s,
296            self.au, self.r, self.v, self.re, self.u
297        );
298        Ok(())
299    }
300}
301
302impl FromStr for Vector {
303    type Err = Error;
304
305    fn from_str(s: &str) -> crate::Result<Self> {
306        let component_vec = s
307            .split('/')
308            .map(|component| {
309                let mut parts = component.split(':');
310
311                let id = parts.next().ok_or_else(|| Error::InvalidComponent {
312                    component: component.to_owned(),
313                })?;
314
315                let value = parts.next().ok_or_else(|| Error::InvalidComponent {
316                    component: component.to_owned(),
317                })?;
318
319                if parts.next().is_some() {
320                    return Err(Error::InvalidComponent {
321                        component: component.to_owned(),
322                    });
323                }
324
325                Ok((id, value))
326            })
327            .collect::<crate::Result<Vec<_>>>()?;
328
329        let mut components = component_vec.iter();
330        let &(id, version_string) = components.next().ok_or(Error::InvalidPrefix {
331            prefix: s.to_owned(),
332        })?;
333
334        if id != PREFIX {
335            return Err(Error::InvalidPrefix {
336                prefix: id.to_owned(),
337            });
338        }
339
340        let mut metrics = Self {
341            minor_version: match version_string {
342                "4.0" => 0,
343                _ => {
344                    return Err(Error::UnsupportedVersion {
345                        version: version_string.to_owned(),
346                    });
347                }
348            },
349            ..Default::default()
350        };
351
352        for &component in components {
353            let id = component.0.to_ascii_uppercase();
354            let value = component.1.to_ascii_uppercase();
355
356            fn get_value<T: FromStr<Err = Error>>(
357                metric_type: MetricType,
358                current_val: Option<T>,
359                new_val: String,
360            ) -> Result<Option<T>, Error> {
361                let parsed: T = new_val.parse()?;
362                if current_val.is_some() {
363                    return Err(Error::DuplicateMetricV4 { metric_type });
364                }
365                Ok(Some(parsed))
366            }
367
368            match id.parse::<MetricType>()? {
369                MetricType::AV => metrics.av = get_value(MetricType::AV, metrics.av, value)?,
370                MetricType::AC => metrics.ac = get_value(MetricType::AC, metrics.ac, value)?,
371                MetricType::PR => metrics.pr = get_value(MetricType::PR, metrics.pr, value)?,
372                MetricType::UI => metrics.ui = get_value(MetricType::UI, metrics.ui, value)?,
373                MetricType::S => metrics.s = get_value(MetricType::S, metrics.s, value)?,
374                MetricType::AT => metrics.at = get_value(MetricType::AT, metrics.at, value)?,
375                MetricType::SA => metrics.sa = get_value(MetricType::SA, metrics.sa, value)?,
376                MetricType::SC => metrics.sc = get_value(MetricType::SC, metrics.sc, value)?,
377                MetricType::SI => metrics.si = get_value(MetricType::SI, metrics.si, value)?,
378                MetricType::VA => metrics.va = get_value(MetricType::VA, metrics.va, value)?,
379                MetricType::VC => metrics.vc = get_value(MetricType::VC, metrics.vc, value)?,
380                MetricType::VI => metrics.vi = get_value(MetricType::VI, metrics.vi, value)?,
381                MetricType::E => metrics.e = get_value(MetricType::E, metrics.e, value)?,
382                MetricType::AR => metrics.ar = get_value(MetricType::AR, metrics.ar, value)?,
383                MetricType::CR => metrics.cr = get_value(MetricType::CR, metrics.cr, value)?,
384                MetricType::IR => metrics.ir = get_value(MetricType::IR, metrics.ir, value)?,
385                MetricType::MAC => metrics.mac = get_value(MetricType::MAC, metrics.mac, value)?,
386                MetricType::MAT => metrics.mat = get_value(MetricType::MAT, metrics.mat, value)?,
387                MetricType::MAV => metrics.mav = get_value(MetricType::MAV, metrics.mav, value)?,
388                MetricType::MPR => metrics.mpr = get_value(MetricType::MPR, metrics.mpr, value)?,
389                MetricType::MSA => metrics.msa = get_value(MetricType::MSA, metrics.msa, value)?,
390                MetricType::MSC => metrics.msc = get_value(MetricType::MSC, metrics.msc, value)?,
391                MetricType::MSI => metrics.msi = get_value(MetricType::MSI, metrics.msi, value)?,
392                MetricType::MUI => metrics.mui = get_value(MetricType::MUI, metrics.mui, value)?,
393                MetricType::MVA => metrics.mva = get_value(MetricType::MVA, metrics.mva, value)?,
394                MetricType::MVC => metrics.mvc = get_value(MetricType::MVC, metrics.mvc, value)?,
395                MetricType::MVI => metrics.mvi = get_value(MetricType::MVI, metrics.mvi, value)?,
396                MetricType::AU => metrics.au = get_value(MetricType::AU, metrics.au, value)?,
397                MetricType::R => metrics.r = get_value(MetricType::R, metrics.r, value)?,
398                MetricType::RE => metrics.re = get_value(MetricType::RE, metrics.re, value)?,
399                MetricType::U => metrics.u = get_value(MetricType::U, metrics.u, value)?,
400                MetricType::V => metrics.v = get_value(MetricType::V, metrics.v, value)?,
401            }
402        }
403
404        metrics.check_mandatory_metrics()?;
405
406        Ok(metrics)
407    }
408}
409
410#[cfg(feature = "serde")]
411#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
412impl<'de> Deserialize<'de> for Vector {
413    fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
414        String::deserialize(deserializer)?
415            .parse()
416            .map_err(de::Error::custom)
417    }
418}
419
420#[cfg(feature = "serde")]
421#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
422impl Serialize for Vector {
423    fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
424        self.to_string().serialize(serializer)
425    }
426}
427
428#[cfg(test)]
429#[cfg(feature = "std")]
430mod tests {
431    use super::*;
432    use alloc::{borrow::ToOwned, string::ToString};
433
434    #[test]
435    fn fails_to_parse_invalid_cvss4() {
436        // Version 5.0 is not supported
437        assert_eq!(
438            Vector::from_str("CVSS:5.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N"),
439            Err(Error::UnsupportedVersion {
440                version: "5.0".to_string(),
441            })
442        );
443        // Invalid prefix CSS
444        assert_eq!(
445            Vector::from_str("CSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N"),
446            Err(Error::InvalidPrefix {
447                prefix: "CSS".to_owned(),
448            })
449        );
450        // “F” is not a valid value for “AV”
451        assert_eq!(
452            Vector::from_str("CVSS:4.0/AV:F/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N"),
453            Err(Error::InvalidMetricV4 {
454                metric_type: MetricType::AV,
455                value: "F".to_owned()
456            })
457        );
458        // Missing mandatory metric “AC”
459        assert_eq!(
460            Vector::from_str("CVSS:4.0/AV:N/AT:N/PR:H/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N"),
461            Err(Error::MissingMandatoryMetricV4 {
462                metric_type: MetricType::AC
463            })
464        );
465    }
466
467    #[test]
468    fn parse_base_cvss4() {
469        assert!(
470            Vector::from_str("CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N")
471                .is_ok()
472        );
473    }
474
475    #[test]
476    fn parse_full_cvss4() {
477        let vector_s = "CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N/E:U/CR:L/IR:X/AR:L/MAV:A/MAC:H/MAT:N/MPR:N/MUI:P/MVC:X/MVI:N/MVA:H/MSC:N/MSI:L/MSA:S/S:N/AU:N/R:I/V:C/RE:H/U:Green";
478        let v = Vector::from_str(vector_s).unwrap();
479        assert_eq!(vector_s, v.to_string());
480    }
481}