Skip to main content

cvss_rs/
lib.rs

1//! A Rust library for representing and deserializing CVSS data.
2//!
3//! This crate provides Rust types that map directly to the official
4//! JSON schema representations for CVSS versions 2.0, 3.0, 3.1, and 4.0.
5//!
6//! # Example
7//!
8//! Deserializing a CVSS v3.1 JSON object:
9//!
10//! ```
11//! use cvss_rs::v3::AttackVector;
12//! use cvss_rs::{Cvss, Severity, Version};
13//!
14//! let json_data = r#"{
15//!   "version": "3.1",
16//!   "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
17//!   "attackVector": "NETWORK",
18//!   "attackComplexity": "LOW",
19//!   "privilegesRequired": "NONE",
20//!   "userInteraction": "NONE",
21//!   "scope": "UNCHANGED",
22//!   "confidentialityImpact": "HIGH",
23//!   "integrityImpact": "HIGH",
24//!   "availabilityImpact": "HIGH",
25//!   "baseScore": 9.8,
26//!   "baseSeverity": "CRITICAL"
27//! }"#;
28//!
29//! let cvss: Cvss = serde_json::from_str(json_data).unwrap();
30//!
31//! assert_eq!(cvss.version(), Version::V3_1);
32//! assert_eq!(cvss.base_score(), 9.8);
33//! assert_eq!(cvss.base_severity().unwrap(), Severity::Critical);
34//!
35//! // We can also get the inner struct and access some of its fields
36//! if let Cvss::V3_1(cvss_v3) = cvss {
37//!     assert_eq!(cvss_v3.attack_vector, Some(AttackVector::Network));
38//! } else {
39//!     // The example should panic if the if let fails
40//!     panic!("Expected Cvss::V3_1 variant");
41//! }
42//! ```
43
44use serde::Deserialize;
45use std::fmt::{self, Display, Formatter};
46use strum::{Display, EnumDiscriminants, EnumString};
47
48pub mod v2_0;
49pub mod v3;
50pub mod v4_0;
51pub mod version;
52
53/// An enum to hold any version of a CVSS object.
54#[derive(Debug, Deserialize, EnumDiscriminants)]
55#[serde(tag = "version")]
56#[strum_discriminants(name(Version))]
57#[strum_discriminants(vis(pub))]
58#[strum_discriminants(derive(Display, EnumString))]
59pub enum Cvss {
60    #[serde(rename = "2.0")]
61    V2(v2_0::CvssV2),
62    #[serde(rename = "3.0")]
63    V3_0(v3::CvssV3),
64    #[serde(rename = "3.1")]
65    V3_1(v3::CvssV3),
66    #[serde(rename = "4.0")]
67    V4(v4_0::CvssV4),
68}
69
70impl Display for Cvss {
71    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
72        write!(f, "{}", self.vector_string())
73    }
74}
75
76impl Cvss {
77    /// Returns the version of the CVSS standard.
78    pub fn version(&self) -> Version {
79        self.into()
80    }
81
82    /// Returns the CVSS vector string.
83    pub fn vector_string(&self) -> &str {
84        match self {
85            Cvss::V2(c) => c.vector_string(),
86            Cvss::V3_0(c) => c.vector_string(),
87            Cvss::V3_1(c) => c.vector_string(),
88            Cvss::V4(c) => c.vector_string(),
89        }
90    }
91
92    /// Returns the base score.
93    pub fn base_score(&self) -> f64 {
94        match self {
95            Cvss::V2(c) => c.base_score(),
96            Cvss::V3_0(c) => c.base_score(),
97            Cvss::V3_1(c) => c.base_score(),
98            Cvss::V4(c) => c.base_score(),
99        }
100    }
101
102    /// Returns the base severity.
103    pub fn base_severity(&self) -> Option<Severity> {
104        match self {
105            Cvss::V2(c) => c.base_severity(),
106            Cvss::V3_0(c) => c.base_severity(),
107            Cvss::V3_1(c) => c.base_severity(),
108            Cvss::V4(c) => c.base_severity(),
109        }
110    }
111}
112
113/// Represents the qualitative severity rating of a vulnerability.
114#[derive(Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
115#[serde(rename_all = "UPPERCASE")]
116pub enum Severity {
117    None,
118    Low,
119    Medium,
120    High,
121    Critical,
122}
123
124/// Errors that can occur when parsing CVSS vector strings.
125#[derive(Clone, Debug, PartialEq)]
126pub enum ParseError {
127    /// Vector string doesn't start with "CVSS" or expected prefix
128    InvalidPrefix { found: String },
129    /// Unsupported or invalid CVSS version
130    InvalidVersion { version: String },
131    /// Component is malformed (not in key:value format)
132    InvalidComponent { component: String },
133    /// Metric abbreviation not recognized
134    UnknownMetric { metric: String },
135    /// Metric value parsing failed
136    InvalidMetricValue { metric: String, value: String },
137    /// Required base metric is missing
138    MissingRequiredMetric { metric: String },
139    /// Same metric appears multiple times
140    DuplicateMetric { metric: String },
141}
142
143impl Display for ParseError {
144    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
145        match self {
146            ParseError::InvalidPrefix { found } => {
147                write!(
148                    f,
149                    "invalid vector prefix: expected 'CVSS', found '{}'",
150                    found
151                )
152            }
153            ParseError::InvalidVersion { version } => {
154                write!(f, "invalid or unsupported CVSS version: '{}'", version)
155            }
156            ParseError::InvalidComponent { component } => {
157                write!(
158                    f,
159                    "invalid component format: '{}' (expected 'KEY:VALUE')",
160                    component
161                )
162            }
163            ParseError::UnknownMetric { metric } => {
164                write!(f, "unknown metric abbreviation: '{}'", metric)
165            }
166            ParseError::InvalidMetricValue { metric, value } => {
167                write!(f, "invalid value '{}' for metric '{}'", value, metric)
168            }
169            ParseError::MissingRequiredMetric { metric } => {
170                write!(f, "missing required metric: '{}'", metric)
171            }
172            ParseError::DuplicateMetric { metric } => {
173                write!(f, "duplicate metric: '{}'", metric)
174            }
175        }
176    }
177}
178
179impl std::error::Error for ParseError {}