1use 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#[derive(Clone, Debug, Default, Eq, PartialEq)]
48pub struct Vector {
49 pub minor_version: usize,
51
52 pub(crate) ac: Option<AttackComplexity>,
54 pub(crate) at: Option<AttackRequirements>,
56 pub(crate) av: Option<AttackVector>,
58 pub(crate) pr: Option<PrivilegesRequired>,
60 pub(crate) sa: Option<AvailabilityImpactToTheSubsequentSystem>,
62 pub(crate) sc: Option<ConfidentialityImpactToTheSubsequentSystem>,
64 pub(crate) si: Option<IntegrityImpactToTheSubsequentSystem>,
66 pub(crate) ui: Option<UserInteraction>,
68 pub(crate) va: Option<AvailabilityImpactToTheVulnerableSystem>,
70 pub(crate) vc: Option<ConfidentialityImpactToTheVulnerableSystem>,
72 pub(crate) vi: Option<IntegrityImpactToTheVulnerableSystem>,
74 pub(crate) e: Option<ExploitMaturity>,
76 pub(crate) ar: Option<AvailabilityRequirements>,
78 pub(crate) cr: Option<ConfidentialityRequirements>,
80 pub(crate) ir: Option<IntegrityRequirements>,
82 pub(crate) mac: Option<ModifiedAttackComplexity>,
84 pub(crate) mat: Option<ModifiedAttackRequirements>,
86 pub(crate) mav: Option<ModifiedAttackVector>,
88 pub(crate) mpr: Option<ModifiedPrivilegesRequired>,
90 pub(crate) msa: Option<ModifiedAvailabilityImpactToTheSubsequentSystem>,
92 pub(crate) msc: Option<ModifiedConfidentialityImpactToTheSubsequentSystem>,
94 pub(crate) msi: Option<ModifiedIntegrityImpactToTheSubsequentSystem>,
96 pub(crate) mui: Option<ModifiedUserInteraction>,
98 pub(crate) mva: Option<ModifiedAvailabilityImpactToTheVulnerableSystem>,
100 pub(crate) mvc: Option<ModifiedConfidentialityImpactToTheVulnerableSystem>,
102 pub(crate) mvi: Option<ModifiedIntegrityImpactToTheVulnerableSystem>,
104 pub(crate) au: Option<Automatable>,
106 pub(crate) r: Option<Recovery>,
108 pub(crate) re: Option<VulnerabilityResponseEffort>,
110 pub(crate) s: Option<Safety>,
112 pub(crate) u: Option<ProviderUrgency>,
114 pub(crate) v: Option<ValueDensity>,
116}
117
118impl Vector {
119 #[cfg(feature = "std")]
121 pub fn score(&self) -> Score {
122 self.into()
123 }
124
125 pub fn nomenclature(&self) -> Nomenclature {
129 Nomenclature::from(self)
130 }
131
132 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 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 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 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 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}