1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
use std::collections::HashMap;
use crate::csaf_traits::{ContentTrait, CsafTrait, MetricTrait, VulnerabilityTrait};
use crate::validation::ValidationError;
fn generate_cvss_and_qualitative_error(
product_id: &str,
source: &Option<String>,
v_i: usize,
m_i: usize,
) -> ValidationError {
let source_str = match source {
Some(s) => format!("and source '{s}'"),
None => "by author".to_string(),
};
ValidationError {
message: format!(
"Vulnerability has both a CVSS score and qualitative severity rating for product_id '{product_id}' {source_str}"
),
// Metric instances paths are usually constructed using "ContentTrait::get_content_json_path".
// We are not doing that here, as qualitative severity rating is CSAF 2.1 only.
// So even if we implement running CSAF 2.1 tests for CSAF 2.0 docs, this test will always pass,
// as the offending metric type does not exist on CSAF 2.0, and this will never print the wrong path.
instance_path: format!("/vulnerabilities/{v_i}/metrics/{m_i}/content/qualitative_severity_rating",),
}
}
/// 6.1.56 Use of CVSS and Qualitative Severity Rating
///
/// In each vulnerability, for each tuple of Product ID and source, there cannot be both a CVSS score
/// and a qualitative severity rating present.
pub fn test_6_1_56_cvss_and_qualitative_severity_rating(doc: &impl CsafTrait) -> Result<(), Vec<ValidationError>> {
type ProductIdSourceTuple = (String, Option<String>);
type HasRatingsTuple = (usize, bool, bool);
let mut errors: Option<Vec<ValidationError>> = None;
// for each vulnerability
for (v_i, vulnerability) in doc.get_vulnerabilities().iter().enumerate() {
// construct a hashmap of (product_id, Option<source>) to (has_any_cvss_score, has_qualitative_severity_rating)
let mut ratings_map: Option<HashMap<ProductIdSourceTuple, Vec<HasRatingsTuple>>> = None;
if let Some(metrics) = vulnerability.get_metrics() {
for (m_i, metric) in metrics.iter().enumerate() {
for product_id in metric.get_products() {
let product_id_source_tuple = (product_id.to_owned(), metric.get_source().map(|s| s.to_owned()));
let content = metric.get_content();
let has_ratings_tuple = (m_i, content.has_any_cvss(), content.has_qualitative_severity());
ratings_map
.get_or_insert_default()
.entry(product_id_source_tuple)
.or_default()
.push(has_ratings_tuple);
}
}
}
if let Some(ratings_map) = ratings_map {
for ((product_id, source), ratings) in &ratings_map {
let has_cvss_score = ratings.iter().any(|(_, has_cvss, _)| *has_cvss);
if has_cvss_score {
for (m_i, _, has_qualitative) in ratings {
if *has_qualitative {
errors
.get_or_insert_default()
.push(generate_cvss_and_qualitative_error(product_id, source, v_i, *m_i));
}
}
}
}
}
}
errors.map_or(Ok(()), Err)
}
crate::test_validation::impl_validator!(
csaf2_1,
ValidatorForTest6_1_56,
test_6_1_56_cvss_and_qualitative_severity_rating
);
#[cfg(test)]
mod tests {
use super::*;
use crate::csaf2_1::testcases::TESTS_2_1;
#[test]
fn test_test_6_1_56() {
let qualitative_in_second_metric_no_source = Err(vec![generate_cvss_and_qualitative_error(
"CSAFPID-9080700",
&None,
0,
1,
)]);
let case_02_qualitative_in_first_metric_no_source = Err(vec![generate_cvss_and_qualitative_error(
"CSAFPID-9080700",
&None,
0,
0,
)]);
let case_05_err = Err(vec![
// First vulnerability has 2 metrics, same two metrics, same product id, same source
// one has CVSS and the other qualitative
generate_cvss_and_qualitative_error(
"CSAFPID-9080700",
&Some("https://www.example.com/.well-known/csaf/clear/2024/esa-2024-0001.json".to_string()),
0,
1,
),
// 2nd vulnerability has 3 metrics, partially overlapping product ids, the second and third
// with the same source and CVSS, but only the first without source has qualitative
// 3rd vulnerability has 2 metrics, partially overlapping product ids, same source
// one has CVSS and the other qualitative
generate_cvss_and_qualitative_error(
"CSAFPID-9080701",
&Some("https://www.example.net/awesome-research-blog-post".to_string()),
2,
1,
),
// 4th vulnerability has 2 metrics, partially overlapping product ids but different sources,
// each metric has both CVSS and qualitative
generate_cvss_and_qualitative_error(
"CSAFPID-9080701",
&Some("https://www.example.net/awesome-research-blog-post".to_string()),
3,
0,
),
generate_cvss_and_qualitative_error(
"CSAFPID-9080700",
&Some("https://www.example.com/.well-known/csaf/clear/2024/esa-2024-0001.json".to_string()),
3,
1,
),
generate_cvss_and_qualitative_error(
"CSAFPID-9080701",
&Some("https://www.example.com/.well-known/csaf/clear/2024/esa-2024-0001.json".to_string()),
3,
1,
),
]);
// Case 11: 2 vulnerabilities, same product has CVSS in one vuln and qualitative in the other (cvss 3.1)
// Case 12: 2 metrics, same product, one has CVSS and the other qualitative, but with different source
// Case 13: multiple vulnerabilities, same product, qualitative is in a separate vulnerability
// Case 14: 2 vulnerabilities, same product has CVSS in one vuln and qualitative in the other (cvss 2.0
// Case 15: 2 vulnerabilities, same product has CVSS in one vuln and qualitative in the other (cvss 4.1)
// Case 16: different products, one has CVSS and the other qualitative
// Case 17: 2 metrics, same product, one has CVSS and the other qualitative, but with different source
// Case 18: complex case, 4 vulnerabilities, all of them with 2 metrics, overlapping product,
// different sources, one with CVSS and the other with qualitative, but differently ordered
TESTS_2_1.test_6_1_56.expect(
qualitative_in_second_metric_no_source.clone(),
case_02_qualitative_in_first_metric_no_source,
qualitative_in_second_metric_no_source.clone(),
qualitative_in_second_metric_no_source,
case_05_err,
Ok(()),
Ok(()),
Ok(()),
Ok(()),
Ok(()),
Ok(()),
Ok(()),
Ok(()),
);
}
}