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    /// Check for required base metrics presence
133    ///
134    /// Defined in <https://www.first.org/cvss/v4.0/specification-document#Vector-String>
135    fn check_mandatory_metrics(&self) -> Result<(), Error> {
136        fn ensure_present<T>(metric: Option<T>, metric_type: MetricType) -> Result<(), Error> {
137            if metric.is_none() {
138                return Err(Error::MissingMandatoryMetricV4 { metric_type });
139            }
140            Ok(())
141        }
142
143        ensure_present(self.ac.as_ref(), MetricType::AC)?;
144        ensure_present(self.at.as_ref(), MetricType::AT)?;
145        ensure_present(self.av.as_ref(), MetricType::AV)?;
146        ensure_present(self.pr.as_ref(), MetricType::PR)?;
147        ensure_present(self.sa.as_ref(), MetricType::SA)?;
148        ensure_present(self.sc.as_ref(), MetricType::SC)?;
149        ensure_present(self.si.as_ref(), MetricType::SI)?;
150        ensure_present(self.ui.as_ref(), MetricType::UI)?;
151        ensure_present(self.va.as_ref(), MetricType::VA)?;
152        ensure_present(self.vc.as_ref(), MetricType::VC)?;
153        ensure_present(self.vi.as_ref(), MetricType::VI)?;
154        Ok(())
155    }
156}
157
158macro_rules! write_metrics {
159    ($f:expr, $($metric:expr),+) => {
160        $(
161            if let Some(metric) = $metric {
162                write!($f, "/{}", metric)?;
163            }
164        )+
165    };
166}
167
168impl fmt::Display for Vector {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        write!(f, "{}:4.{}", PREFIX, self.minor_version)?;
171        write_metrics!(
172            f, self.av, self.ac, self.at, self.pr, self.ui, self.vc, self.vi, self.va, self.sc,
173            self.si, self.sa, self.e, self.cr, self.ir, self.ar, self.mav, self.mac, self.mat,
174            self.mpr, self.mui, self.mvc, self.mvi, self.mva, self.msc, self.msi, self.msa, self.s,
175            self.au, self.r, self.v, self.re, self.u
176        );
177        Ok(())
178    }
179}
180
181impl FromStr for Vector {
182    type Err = Error;
183
184    fn from_str(s: &str) -> crate::Result<Self> {
185        let component_vec = s
186            .split('/')
187            .map(|component| {
188                let mut parts = component.split(':');
189
190                let id = parts.next().ok_or_else(|| Error::InvalidComponent {
191                    component: component.to_owned(),
192                })?;
193
194                let value = parts.next().ok_or_else(|| Error::InvalidComponent {
195                    component: component.to_owned(),
196                })?;
197
198                if parts.next().is_some() {
199                    return Err(Error::InvalidComponent {
200                        component: component.to_owned(),
201                    });
202                }
203
204                Ok((id, value))
205            })
206            .collect::<crate::Result<Vec<_>>>()?;
207
208        let mut components = component_vec.iter();
209        let &(id, version_string) = components.next().ok_or(Error::InvalidPrefix {
210            prefix: s.to_owned(),
211        })?;
212
213        if id != PREFIX {
214            return Err(Error::InvalidPrefix {
215                prefix: id.to_owned(),
216            });
217        }
218
219        let mut metrics = Self {
220            minor_version: match version_string {
221                "4.0" => 0,
222                _ => {
223                    return Err(Error::UnsupportedVersion {
224                        version: version_string.to_owned(),
225                    });
226                }
227            },
228            ..Default::default()
229        };
230
231        for &component in components {
232            let id = component.0.to_ascii_uppercase();
233            let value = component.1.to_ascii_uppercase();
234
235            fn get_value<T: FromStr<Err = Error>>(
236                metric_type: MetricType,
237                current_val: Option<T>,
238                new_val: String,
239            ) -> Result<Option<T>, Error> {
240                let parsed: T = new_val.parse()?;
241                if current_val.is_some() {
242                    return Err(Error::DuplicateMetricV4 { metric_type });
243                }
244                Ok(Some(parsed))
245            }
246
247            match id.parse::<MetricType>()? {
248                MetricType::AV => metrics.av = get_value(MetricType::AV, metrics.av, value)?,
249                MetricType::AC => metrics.ac = get_value(MetricType::AC, metrics.ac, value)?,
250                MetricType::PR => metrics.pr = get_value(MetricType::PR, metrics.pr, value)?,
251                MetricType::UI => metrics.ui = get_value(MetricType::UI, metrics.ui, value)?,
252                MetricType::S => metrics.s = get_value(MetricType::S, metrics.s, value)?,
253                MetricType::AT => metrics.at = get_value(MetricType::AT, metrics.at, value)?,
254                MetricType::SA => metrics.sa = get_value(MetricType::SA, metrics.sa, value)?,
255                MetricType::SC => metrics.sc = get_value(MetricType::SC, metrics.sc, value)?,
256                MetricType::SI => metrics.si = get_value(MetricType::SI, metrics.si, value)?,
257                MetricType::VA => metrics.va = get_value(MetricType::VA, metrics.va, value)?,
258                MetricType::VC => metrics.vc = get_value(MetricType::VC, metrics.vc, value)?,
259                MetricType::VI => metrics.vi = get_value(MetricType::VI, metrics.vi, value)?,
260                MetricType::E => metrics.e = get_value(MetricType::E, metrics.e, value)?,
261                MetricType::AR => metrics.ar = get_value(MetricType::AR, metrics.ar, value)?,
262                MetricType::CR => metrics.cr = get_value(MetricType::CR, metrics.cr, value)?,
263                MetricType::IR => metrics.ir = get_value(MetricType::IR, metrics.ir, value)?,
264                MetricType::MAC => metrics.mac = get_value(MetricType::MAC, metrics.mac, value)?,
265                MetricType::MAT => metrics.mat = get_value(MetricType::MAT, metrics.mat, value)?,
266                MetricType::MAV => metrics.mav = get_value(MetricType::MAV, metrics.mav, value)?,
267                MetricType::MPR => metrics.mpr = get_value(MetricType::MPR, metrics.mpr, value)?,
268                MetricType::MSA => metrics.msa = get_value(MetricType::MSA, metrics.msa, value)?,
269                MetricType::MSC => metrics.msc = get_value(MetricType::MSC, metrics.msc, value)?,
270                MetricType::MSI => metrics.msi = get_value(MetricType::MSI, metrics.msi, value)?,
271                MetricType::MUI => metrics.mui = get_value(MetricType::MUI, metrics.mui, value)?,
272                MetricType::MVA => metrics.mva = get_value(MetricType::MVA, metrics.mva, value)?,
273                MetricType::MVC => metrics.mvc = get_value(MetricType::MVC, metrics.mvc, value)?,
274                MetricType::MVI => metrics.mvi = get_value(MetricType::MVI, metrics.mvi, value)?,
275                MetricType::AU => metrics.au = get_value(MetricType::AU, metrics.au, value)?,
276                MetricType::R => metrics.r = get_value(MetricType::R, metrics.r, value)?,
277                MetricType::RE => metrics.re = get_value(MetricType::RE, metrics.re, value)?,
278                MetricType::U => metrics.u = get_value(MetricType::U, metrics.u, value)?,
279                MetricType::V => metrics.v = get_value(MetricType::V, metrics.v, value)?,
280            }
281        }
282
283        metrics.check_mandatory_metrics()?;
284
285        Ok(metrics)
286    }
287}
288
289#[cfg(feature = "serde")]
290#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
291impl<'de> Deserialize<'de> for Vector {
292    fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
293        String::deserialize(deserializer)?
294            .parse()
295            .map_err(de::Error::custom)
296    }
297}
298
299#[cfg(feature = "serde")]
300#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
301impl Serialize for Vector {
302    fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
303        self.to_string().serialize(serializer)
304    }
305}
306
307#[cfg(test)]
308#[cfg(feature = "std")]
309mod tests {
310    use super::*;
311    use alloc::{borrow::ToOwned, string::ToString};
312
313    #[test]
314    fn fails_to_parse_invalid_cvss4() {
315        // Version 5.0 is not supported
316        assert_eq!(
317            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"),
318            Err(Error::UnsupportedVersion {
319                version: "5.0".to_string(),
320            })
321        );
322        // Invalid prefix CSS
323        assert_eq!(
324            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"),
325            Err(Error::InvalidPrefix {
326                prefix: "CSS".to_owned(),
327            })
328        );
329        // “F” is not a valid value for “AV”
330        assert_eq!(
331            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"),
332            Err(Error::InvalidMetricV4 {
333                metric_type: MetricType::AV,
334                value: "F".to_owned()
335            })
336        );
337        // Missing mandatory metric “AC”
338        assert_eq!(
339            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"),
340            Err(Error::MissingMandatoryMetricV4 {
341                metric_type: MetricType::AC
342            })
343        );
344    }
345
346    #[test]
347    fn parse_base_cvss4() {
348        assert!(
349            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")
350                .is_ok()
351        );
352    }
353
354    #[test]
355    fn parse_full_cvss4() {
356        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";
357        let v = Vector::from_str(vector_s).unwrap();
358        assert_eq!(vector_s, v.to_string());
359    }
360}