diff --git a/cve/Cargo.toml b/cve/Cargo.toml index 7385d78..b7e7b41 100644 --- a/cve/Cargo.toml +++ b/cve/Cargo.toml @@ -8,4 +8,5 @@ edition = "2021" [dependencies] serde = { version = "1", features = ["derive"] } cpe = { path = "../cpe" } +cvss = { path = "../cvss" } thiserror = "1.0" \ No newline at end of file diff --git a/cve/src/cve.rs b/cve/src/cve.rs index fa62b90..c4eaf22 100644 --- a/cve/src/cve.rs +++ b/cve/src/cve.rs @@ -1,4 +1,4 @@ -use crate::{cvss, node}; +use crate::node; use serde::{Deserialize, Serialize}; // 单个CVE信息 diff --git a/cve/src/cvss.rs b/cve/src/cvss.rs deleted file mode 100644 index fdcf0e3..0000000 --- a/cve/src/cvss.rs +++ /dev/null @@ -1,411 +0,0 @@ -// 通用漏洞评分系统 -// https://csrc.nist.gov/schema/nvd/feed/1.1-Beta/cvss-v3.x_beta.json - -pub mod v3 { - use std::fmt; - use std::fmt::Formatter; - use std::str::FromStr; - // https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator - use crate::error::{CVEError, Result}; - use serde::{Deserialize, Serialize}; - - - - - // CIA 影响指标 原json schema为ciaType - #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] - #[serde(rename_all = "UPPERCASE")] - pub enum ImpactMetricsType { - High, - Low, - None, - } - impl FromStr for ImpactMetricsType { - type Err = CVEError; - - fn from_str(s: &str) -> Result { - let mut s = s.to_uppercase(); - if let Some((p, v)) = s.split_once(':') { - if !matches!(p, "C" | "I" | "A") { - return Err(CVEError::InvalidCVSS { - value: p.to_string(), - scope: "ImpactMetricsType prefix".to_string(), - }); - } - s = v.to_string(); - } - let c = { - let c = s.to_uppercase().chars().next(); - c.ok_or(CVEError::InvalidCVSS { - value: s, - scope: "ImpactMetricsType".to_string(), - })? - }; - match c { - 'N' => Ok(Self::None), - 'L' => Ok(Self::Low), - 'H' => Ok(Self::High), - _ => Err(CVEError::InvalidCVSS { - value: c.to_string(), - scope: "ImpactMetricsType".to_string(), - }), - } - } - } - // S - #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] - #[serde(rename_all = "UPPERCASE")] - pub enum ScopeType { - // S:U - Unchanged, - // S:C - Changed, - } - impl FromStr for ScopeType { - type Err = CVEError; - - fn from_str(s: &str) -> Result { - let mut s = s.to_uppercase(); - if s.starts_with("S:") { - s = s.strip_prefix("S:").unwrap_or_default().to_string(); - } - let c = { - let c = s.to_uppercase().chars().next(); - c.ok_or(CVEError::InvalidCVSS { - value: s, - scope: "ScopeType from_str".to_string(), - })? - }; - match c { - 'U' => Ok(Self::Unchanged), - 'C' => Ok(Self::Changed), - _ => Err(CVEError::InvalidCVSS { - value: c.to_string(), - scope: "ScopeType".to_string(), - }), - } - } - } - // 严重性 - #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] - #[serde(rename_all = "UPPERCASE")] - pub enum SeverityType { - // 未校正 - None, - // 低危 - Low, - // 中危 - Medium, - // 高危 - High, - // 严重 - Critical, - } - impl FromStr for SeverityType { - type Err = CVEError; - - fn from_str(s: &str) -> Result { - let c = { - let c = s.to_uppercase().chars().next(); - c.ok_or(CVEError::InvalidCVSS { - value: s.to_string(), - scope: "SeverityType from_str".to_string(), - })? - }; - match c { - 'N' => Ok(Self::None), - 'L' => Ok(Self::Low), - 'M' => Ok(Self::Medium), - 'H' => Ok(Self::High), - 'C' => Ok(Self::Critical), - _ => Err(CVEError::InvalidCVSS { - value: c.to_string(), - scope: "SeverityType".to_string(), - }), - } - } - } - #[derive(Debug, Serialize, Deserialize, Clone)] - pub enum Version { - None, - #[serde(rename = "2.0")] - V2_0, - #[serde(rename = "3.0")] - V3_0, - #[serde(rename = "3.1")] - V3_1, - // todo V4 - } - - impl Default for Version { - fn default() -> Self { - Version::None - } - } - impl FromStr for Version { - type Err = CVEError; - - fn from_str(s: &str) -> std::result::Result { - let mut s = s.to_uppercase(); - if s.starts_with("CVSS:") { - s = s.strip_prefix("CVSS:").unwrap_or_default().to_string(); - } - match s.as_str() { - "2.0" => Ok(Self::V2_0), - "3.0" => Ok(Self::V3_0), - "3.1" => Ok(Self::V3_1), - _ => Ok(Self::None), - } - } - } - impl fmt::Display for Version { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Version::None => write!(f, "NONE"), - Version::V2_0 => write!(f, "2.0"), - Version::V3_0 => write!(f, "3.0"), - Version::V3_1 => write!(f, "3.1"), - } - } - } - #[derive(Debug, Serialize, Deserialize, Clone)] - #[serde(rename_all = "camelCase")] - pub struct CVSS { - // 版本: 3.0 和 3.1 - pub version: Version, - // 向量: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H" - pub vector_string: String, - // 访问途径(AV) - pub attack_vector: AttackVectorType, - // 攻击复杂度(AC) - pub attack_complexity: AttackComplexityType, - // 所需权限(PR) - pub privileges_required: PrivilegesRequiredType, - // 用户交互(UI) - pub user_interaction: UserInteractionType, - // 影响范围(S) - pub scope: ScopeType, - // 机密性影响(C) - pub confidentiality_impact: ImpactMetricsType, - // 完整性影响(I) - pub integrity_impact: ImpactMetricsType, - // 可用性影响(A) - pub availability_impact: ImpactMetricsType, - // 基础评分 - pub base_score: f64, - // 基础评级 - pub base_severity: SeverityType, - } - - impl CVSS { - // https://nvd.nist.gov/vuln-metrics/cvss - fn update_severity(&self) {} - fn update_score(&mut self) { - self.base_score = 0 as f64; - } - } - impl FromStr for CVSS { - type Err = CVEError; - fn from_str(vector_string: &str) -> Result { - let (version, vectors) = match vector_string.split_once('/') { - None => { - return Err(CVEError::InvalidPrefix { - value: vector_string.to_string(), - }) - } - Some((v, vector)) => { - let version = Version::from_str(v).unwrap_or_default(); - (version, vector) - } - }; - if matches!(version, Version::None) { - return Err(CVEError::InvalidCVSSVersion { - value: version.to_string(), - expected: "2.0, 3.0 or 3.1".to_string(), - }); - } - let mut vector = vectors.split('/'); - // "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H" - let error = CVEError::InvalidCVSS { - value: vector_string.to_string(), - scope: "CVSS parser".to_string(), - }; - let mut cvss = CVSS { - version, - vector_string: vector_string.to_string(), - attack_vector: AttackVectorType::from_str(vector.next().ok_or(&error)?)?, - attack_complexity: AttackComplexityType::from_str(vector.next().ok_or(&error)?)?, - privileges_required: PrivilegesRequiredType::from_str(vector.next().ok_or(&error)?)?, - user_interaction: UserInteractionType::from_str(vector.next().ok_or(&error)?)?, - scope: ScopeType::from_str(vector.next().ok_or(&error)?)?, - confidentiality_impact: ImpactMetricsType::from_str(vector.next().ok_or(&error)?)?, - integrity_impact: ImpactMetricsType::from_str(vector.next().ok_or(&error)?)?, - availability_impact: ImpactMetricsType::from_str(vector.next().ok_or(&error)?)?, - base_score: 0.0, - base_severity: SeverityType::None, - }; - cvss.update_score(); - cvss.update_severity(); - Ok(cvss) - } - } -} - -pub mod v2 { - use std::str::FromStr; - // https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator - use crate::error::CVEError; - use serde::{Deserialize, Serialize}; - - // AV - #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] - #[serde(rename_all = "SCREAMING_SNAKE_CASE")] - pub enum AccessVectorType { - // AV:N - Network, - // AV:A - AdjacentNetwork, - // AV:L - Local, - } - impl FromStr for AccessVectorType { - type Err = CVEError; - - fn from_str(s: &str) -> Result { - let c = { - let c = s.to_uppercase().chars().next(); - c.ok_or(CVEError::InvalidCVSS { - value: s.to_string(), - scope: "AccessVectorType from_str".to_string(), - })? - }; - match c { - 'N' => Ok(Self::Network), - 'A' => Ok(Self::AdjacentNetwork), - 'L' => Ok(Self::Local), - _ => Err(CVEError::InvalidCVSS { - value: c.to_string(), - scope: "AccessVectorType".to_string(), - }), - } - } - } - // AC - #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] - #[serde(rename_all = "UPPERCASE")] - pub enum AccessComplexityType { - // AC:H - High, - // AC:M - Medium, - // AC:L - Low, - } - impl FromStr for AccessComplexityType { - type Err = CVEError; - - fn from_str(s: &str) -> Result { - let c = { - let c = s.to_uppercase().chars().next(); - c.ok_or(CVEError::InvalidCVSS { - value: s.to_string(), - scope: "AccessComplexityType from_str".to_string(), - })? - }; - match c { - 'H' => Ok(Self::High), - 'M' => Ok(Self::Medium), - 'L' => Ok(Self::Low), - _ => Err(CVEError::InvalidCVSS { - value: c.to_string(), - scope: "AccessComplexityType".to_string(), - }), - } - } - } - // Au - #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] - #[serde(rename_all = "UPPERCASE")] - pub enum AuthenticationType { - // Au:M - Multiple, - // Au:S - Single, - // Au:N - None, - } - impl FromStr for AuthenticationType { - type Err = CVEError; - - fn from_str(s: &str) -> Result { - let c = { - let c = s.to_uppercase().chars().next(); - c.ok_or(CVEError::InvalidCVSS { - value: s.to_string(), - scope: "AuthenticationType from_str".to_string(), - })? - }; - match c { - 'M' => Ok(Self::Multiple), - 'S' => Ok(Self::Single), - 'N' => Ok(Self::None), - _ => Err(CVEError::InvalidCVSS { - value: c.to_string(), - scope: "AuthenticationType".to_string(), - }), - } - } - } - // CIA 影响指标 原json schema为ciaType - #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] - #[serde(rename_all = "UPPERCASE")] - pub enum ImpactMetricsType { - None, - Partial, - Complete, - } - impl FromStr for ImpactMetricsType { - type Err = CVEError; - - fn from_str(s: &str) -> Result { - let c = { - let c = s.to_uppercase().chars().next(); - c.ok_or(CVEError::InvalidCVSS { - value: s.to_string(), - scope: "ImpactMetricsType from_str".to_string(), - })? - }; - match c { - 'N' => Ok(Self::None), - 'A' => Ok(Self::Partial), - 'L' => Ok(Self::Complete), - _ => Err(CVEError::InvalidCVSS { - value: c.to_string(), - scope: "ImpactMetricsType".to_string(), - }), - } - } - } - #[derive(Debug, Serialize, Deserialize, Clone)] - #[serde(rename_all = "camelCase")] - pub struct CVSS { - // 版本 - pub version: String, - // 向量: CVSS:2.0/AV:L/AC:L/Au:N/C:C/I:C/A:C - pub vector_string: String, - // 访问向量 - pub access_vector: AccessVectorType, - // 访问复杂性 - pub access_complexity: AccessComplexityType, - // 认证 - pub authentication: AuthenticationType, - // 完整性影响(I) - pub confidentiality_impact: ImpactMetricsType, - // 完整性影响(I) - pub integrity_impact: ImpactMetricsType, - // 可用性影响(A) - pub availability_impact: ImpactMetricsType, - // 基础评分 - pub base_score: f64, - } -} \ No newline at end of file diff --git a/cve/src/lib.rs b/cve/src/lib.rs index 13e28ee..46d8180 100644 --- a/cve/src/lib.rs +++ b/cve/src/lib.rs @@ -1,5 +1,4 @@ pub mod cve; -pub mod cvss; pub mod error; pub mod node; diff --git a/cvss/src/error.rs b/cvss/src/error.rs index 6126ef2..f331e89 100644 --- a/cvss/src/error.rs +++ b/cvss/src/error.rs @@ -10,6 +10,7 @@ pub enum CVSSError { source: std::str::Utf8Error, value: String, }, + #[error("invalid prefix for `{value}`")] InvalidPrefix { value: String }, #[error("Invalid CVE type `{value}`")] diff --git a/cvss/src/lib.rs b/cvss/src/lib.rs index 3ecfe3e..a17844f 100644 --- a/cvss/src/lib.rs +++ b/cvss/src/lib.rs @@ -1,3 +1,8 @@ +// 通用漏洞评分系统 +// https://csrc.nist.gov/schema/nvd/feed/1.1-Beta/cvss-v3.x_beta.json +// https://www.first.org/cvss/specification-document pub mod error; +mod metric; pub mod v2; pub mod v3; +mod version; diff --git a/cvss/src/metric.rs b/cvss/src/metric.rs new file mode 100644 index 0000000..8292054 --- /dev/null +++ b/cvss/src/metric.rs @@ -0,0 +1,8 @@ +use std::fmt::{Debug, Display}; +use std::str::FromStr; + +pub trait Metric: Clone + Debug + FromStr + Display { + const NAME: &'static str; + fn score(&self) -> f32; + fn as_str(&self) -> &'static str; +} diff --git a/cvss/src/v2.rs b/cvss/src/v2.rs index e69de29..8b79481 100644 --- a/cvss/src/v2.rs +++ b/cvss/src/v2.rs @@ -0,0 +1,35 @@ +pub mod access_complexity; +pub mod access_vector; +pub mod authentication; +pub mod impact_metrics; + +// https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator +use crate::v2::access_complexity::AccessComplexityType; +use crate::v2::access_vector::AccessVectorType; +use crate::v2::authentication::AuthenticationType; +use crate::v2::impact_metrics::ImpactMetricsType; +use crate::version::Version; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct CVSS { + // 版本 + pub version: Version, + // 向量: CVSS:2.0/AV:L/AC:L/Au:N/C:C/I:C/A:C + pub vector_string: String, + // 访问向量 + pub access_vector: AccessVectorType, + // 访问复杂性 + pub access_complexity: AccessComplexityType, + // 认证 + pub authentication: AuthenticationType, + // 完整性影响(I) + pub confidentiality_impact: ImpactMetricsType, + // 完整性影响(I) + pub integrity_impact: ImpactMetricsType, + // 可用性影响(A) + pub availability_impact: ImpactMetricsType, + // 基础评分 + pub base_score: f64, +} diff --git a/cvss/src/v2/access_complexity.rs b/cvss/src/v2/access_complexity.rs new file mode 100644 index 0000000..a4727fe --- /dev/null +++ b/cvss/src/v2/access_complexity.rs @@ -0,0 +1,37 @@ +use crate::error::{CVSSError, Result}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +// AC +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum AccessComplexityType { + // AC:H + High, + // AC:M + Medium, + // AC:L + Low, +} +impl FromStr for AccessComplexityType { + type Err = CVSSError; + + fn from_str(s: &str) -> Result { + let c = { + let c = s.to_uppercase().chars().next(); + c.ok_or(CVSSError::InvalidCVSS { + value: s.to_string(), + scope: "AccessComplexityType from_str".to_string(), + })? + }; + match c { + 'H' => Ok(Self::High), + 'M' => Ok(Self::Medium), + 'L' => Ok(Self::Low), + _ => Err(CVSSError::InvalidCVSS { + value: c.to_string(), + scope: "AccessComplexityType".to_string(), + }), + } + } +} diff --git a/cvss/src/v2/access_vector.rs b/cvss/src/v2/access_vector.rs new file mode 100644 index 0000000..3550157 --- /dev/null +++ b/cvss/src/v2/access_vector.rs @@ -0,0 +1,36 @@ +use crate::error::{CVSSError, Result}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +// AV +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum AccessVectorType { + // AV:N + Network, + // AV:A + AdjacentNetwork, + // AV:L + Local, +} +impl FromStr for AccessVectorType { + type Err = CVSSError; + + fn from_str(s: &str) -> Result { + let c = { + let c = s.to_uppercase().chars().next(); + c.ok_or(CVSSError::InvalidCVSS { + value: s.to_string(), + scope: "AccessVectorType from_str".to_string(), + })? + }; + match c { + 'N' => Ok(Self::Network), + 'A' => Ok(Self::AdjacentNetwork), + 'L' => Ok(Self::Local), + _ => Err(CVSSError::InvalidCVSS { + value: c.to_string(), + scope: "AccessVectorType".to_string(), + }), + } + } +} diff --git a/cvss/src/v2/authentication.rs b/cvss/src/v2/authentication.rs new file mode 100644 index 0000000..fbea417 --- /dev/null +++ b/cvss/src/v2/authentication.rs @@ -0,0 +1,36 @@ +use crate::error::{CVSSError, Result}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +// Au +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum AuthenticationType { + // Au:M + Multiple, + // Au:S + Single, + // Au:N + None, +} +impl FromStr for AuthenticationType { + type Err = CVSSError; + + fn from_str(s: &str) -> Result { + let c = { + let c = s.to_uppercase().chars().next(); + c.ok_or(CVSSError::InvalidCVSS { + value: s.to_string(), + scope: "AuthenticationType from_str".to_string(), + })? + }; + match c { + 'M' => Ok(Self::Multiple), + 'S' => Ok(Self::Single), + 'N' => Ok(Self::None), + _ => Err(CVSSError::InvalidCVSS { + value: c.to_string(), + scope: "AuthenticationType".to_string(), + }), + } + } +} diff --git a/cvss/src/v2/impact_metrics.rs b/cvss/src/v2/impact_metrics.rs new file mode 100644 index 0000000..01f33f2 --- /dev/null +++ b/cvss/src/v2/impact_metrics.rs @@ -0,0 +1,34 @@ +use crate::error::{CVSSError, Result}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +// CIA 影响指标 原json schema为ciaType +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum ImpactMetricsType { + None, + Partial, + Complete, +} +impl FromStr for ImpactMetricsType { + type Err = CVSSError; + + fn from_str(s: &str) -> Result { + let c = { + let c = s.to_uppercase().chars().next(); + c.ok_or(CVSSError::InvalidCVSS { + value: s.to_string(), + scope: "ImpactMetricsType from_str".to_string(), + })? + }; + match c { + 'N' => Ok(Self::None), + 'A' => Ok(Self::Partial), + 'L' => Ok(Self::Complete), + _ => Err(CVSSError::InvalidCVSS { + value: c.to_string(), + scope: "ImpactMetricsType".to_string(), + }), + } + } +} diff --git a/cvss/src/v3.rs b/cvss/src/v3.rs index 774ecd0..015711d 100644 --- a/cvss/src/v3.rs +++ b/cvss/src/v3.rs @@ -1,4 +1,104 @@ -pub mod attack_vector; +use std::str::FromStr; +// https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator +use crate::error::{CVSSError, Result}; +use crate::v3::attack_complexity::AttackComplexityType; +use crate::v3::attack_vector::AttackVectorType; +use crate::v3::impact_metrics::{ + AvailabilityImpactType, ConfidentialityImpactType, IntegrityImpactType, +}; +use crate::v3::privileges_required::PrivilegesRequiredType; +use crate::v3::scope::ScopeType; +use crate::v3::severity::SeverityType; +use crate::v3::user_interaction::UserInteractionType; +use crate::version::Version; +use serde::{Deserialize, Serialize}; + pub mod attack_complexity; +pub mod attack_vector; +pub mod impact_metrics; pub mod privileges_required; -pub mod user_interaction; \ No newline at end of file +pub mod scope; +pub mod severity; +pub mod user_interaction; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct CVSS { + // 版本: 3.0 和 3.1 + pub version: Version, + // 向量: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H" + pub vector_string: String, + // 访问途径(AV) + pub attack_vector: AttackVectorType, + // 攻击复杂度(AC) + pub attack_complexity: AttackComplexityType, + // 所需权限(PR) + pub privileges_required: PrivilegesRequiredType, + // 用户交互(UI) + pub user_interaction: UserInteractionType, + // 影响范围(S) + pub scope: ScopeType, + // 机密性影响(C) + pub confidentiality_impact: ConfidentialityImpactType, + // 完整性影响(I) + pub integrity_impact: IntegrityImpactType, + // 可用性影响(A) + pub availability_impact: AvailabilityImpactType, + // 基础评分 + pub base_score: f32, + // 基础评级 + pub base_severity: SeverityType, +} + +impl CVSS { + // https://nvd.nist.gov/vuln-metrics/cvss + fn update_severity(&self) {} + fn update_score(&mut self) { + self.base_score = 0 as f32; + } +} +impl FromStr for CVSS { + type Err = CVSSError; + fn from_str(vector_string: &str) -> Result { + let (version, vectors) = match vector_string.split_once('/') { + None => { + return Err(CVSSError::InvalidPrefix { + value: vector_string.to_string(), + }) + } + Some((v, vector)) => { + let version = Version::from_str(v).unwrap_or_default(); + (version, vector) + } + }; + if matches!(version, Version::None) { + return Err(CVSSError::InvalidCVSSVersion { + value: version.to_string(), + expected: "2.0, 3.0 or 3.1".to_string(), + }); + } + let mut vector = vectors.split('/'); + // "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H" + let error = CVSSError::InvalidCVSS { + value: vector_string.to_string(), + scope: "CVSS parser".to_string(), + }; + let mut cvss = CVSS { + version, + vector_string: vector_string.to_string(), + attack_vector: AttackVectorType::from_str(vector.next().ok_or(&error)?)?, + attack_complexity: AttackComplexityType::from_str(vector.next().ok_or(&error)?)?, + privileges_required: PrivilegesRequiredType::from_str(vector.next().ok_or(&error)?)?, + user_interaction: UserInteractionType::from_str(vector.next().ok_or(&error)?)?, + scope: ScopeType::from_str(vector.next().ok_or(&error)?)?, + confidentiality_impact: ConfidentialityImpactType::from_str(vector.next().ok_or(&error)?)?, + integrity_impact: IntegrityImpactType::from_str(vector.next().ok_or(&error)?)?, + availability_impact: AvailabilityImpactType::from_str(vector.next().ok_or(&error)?)?, + base_score: 0.0, + base_severity: SeverityType::None, + }; + cvss.update_score(); + cvss.update_severity(); + Ok(cvss) + } +} diff --git a/cvss/src/v3/attack_complexity.rs b/cvss/src/v3/attack_complexity.rs index b644f3e..9b57fb5 100644 --- a/cvss/src/v3/attack_complexity.rs +++ b/cvss/src/v3/attack_complexity.rs @@ -1,46 +1,62 @@ use crate::error::{CVSSError, Result}; +use crate::metric::Metric; use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; use std::str::FromStr; + // AC #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] #[serde(rename_all = "UPPERCASE")] pub enum AttackComplexityType { - // AC:H - High, - // AC:L - Low, + // AC:H + High, + // AC:L + Low, +} +impl Display for AttackComplexityType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", Self::NAME, self.as_str()) + } } +impl Metric for AttackComplexityType { + const NAME: &'static str = "AC"; + + fn score(&self) -> f32 { + match self { + AttackComplexityType::High => 0.72, + AttackComplexityType::Low => 0.44, + } + } -impl From for f32 { - fn from(val: AttackComplexityType) -> Self { - match val { - AttackComplexityType::High => 0.72, - AttackComplexityType::Low => 0.44, - } + fn as_str(&self) -> &'static str { + match self { + AttackComplexityType::High => "H", + AttackComplexityType::Low => "L", } + } } impl FromStr for AttackComplexityType { - type Err = CVSSError; + type Err = CVSSError; - fn from_str(s: &str) -> Result { - let mut s = s.to_uppercase(); - if s.starts_with("AC:") { - s = s.strip_prefix("AC:").unwrap_or_default().to_string(); - } - let c = { - let c = s.to_uppercase().chars().next(); - c.ok_or(CVSSError::InvalidCVSS { - value: s, - scope: "AttackComplexityType from_str".to_string(), - })? - }; - match c { - 'L' => Ok(Self::Low), - 'H' => Ok(Self::High), - _ => Err(CVSSError::InvalidCVSS { - value: c.to_string(), - scope: "AttackComplexityType".to_string(), - }), - } + fn from_str(s: &str) -> Result { + let mut s = s.to_uppercase(); + if s.starts_with("AC:") { + s = s.strip_prefix("AC:").unwrap_or_default().to_string(); + } + let c = { + let c = s.to_uppercase().chars().next(); + c.ok_or(CVSSError::InvalidCVSS { + value: s, + scope: "AttackComplexityType from_str".to_string(), + })? + }; + match c { + 'L' => Ok(Self::Low), + 'H' => Ok(Self::High), + _ => Err(CVSSError::InvalidCVSS { + value: c.to_string(), + scope: "AttackComplexityType".to_string(), + }), } -} \ No newline at end of file + } +} diff --git a/cvss/src/v3/attack_vector.rs b/cvss/src/v3/attack_vector.rs index 61a7f20..95d7444 100644 --- a/cvss/src/v3/attack_vector.rs +++ b/cvss/src/v3/attack_vector.rs @@ -1,6 +1,8 @@ +use std::fmt::{Display, Formatter}; use crate::error::{CVSSError, Result}; use serde::{Deserialize, Serialize}; use std::str::FromStr; +use crate::metric::Metric; // AV #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] @@ -16,17 +18,33 @@ pub enum AttackVectorType { Physical, } -impl From for f32 { - fn from(val: AttackVectorType) -> Self { - match val { +impl Display for AttackVectorType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", Self::NAME, self.as_str()) + } +} + +impl Metric for AttackVectorType { + const NAME: &'static str = "AV"; + + fn score(&self) -> f32 { + match self { AttackVectorType::Network => 0.85, AttackVectorType::AdjacentNetwork => 0.62, AttackVectorType::Local => 0.55, AttackVectorType::Physical => 0.2, } } -} + fn as_str(&self) -> &'static str { + match self { + AttackVectorType::Physical => "P", + AttackVectorType::Local => "L", + AttackVectorType::AdjacentNetwork => "A", + AttackVectorType::Network => "N", + } + } +} impl FromStr for AttackVectorType { type Err = CVSSError; @@ -38,7 +56,7 @@ impl FromStr for AttackVectorType { let c = { let c = s.chars().next(); c.ok_or(CVSSError::InvalidCVSS { - value: s.to_string(), + value: s, scope: "AttackVectorType from_str".to_string(), })? }; diff --git a/cvss/src/v3/impact_metrics.rs b/cvss/src/v3/impact_metrics.rs new file mode 100644 index 0000000..b4f7022 --- /dev/null +++ b/cvss/src/v3/impact_metrics.rs @@ -0,0 +1,204 @@ +use std::fmt::{Display, Formatter}; +use crate::error::{CVSSError, Result}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use crate::metric::Metric; + +// CIA 影响指标 原json schema为ciaType +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum ConfidentialityImpactType { + High, + Low, + None, +} +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum IntegrityImpactType { + High, + Low, + None, +} +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum AvailabilityImpactType { + High, + Low, + None, +} + +impl FromStr for ConfidentialityImpactType { + type Err = CVSSError; + + fn from_str(s: &str) -> Result { + let mut s = s.to_uppercase(); + if let Some((p, v)) = s.split_once(':') { + if !matches!(p, "C" | "I" | "A") { + return Err(CVSSError::InvalidCVSS { + value: p.to_string(), + scope: "ImpactMetricsType prefix".to_string(), + }); + } + s = v.to_string(); + } + let c = { + let c = s.to_uppercase().chars().next(); + c.ok_or(CVSSError::InvalidCVSS { + value: s, + scope: "ImpactMetricsType".to_string(), + })? + }; + match c { + 'N' => Ok(Self::None), + 'L' => Ok(Self::Low), + 'H' => Ok(Self::High), + _ => Err(CVSSError::InvalidCVSS { + value: c.to_string(), + scope: "ImpactMetricsType".to_string(), + }), + } + } +} +impl FromStr for IntegrityImpactType { + type Err = CVSSError; + + fn from_str(s: &str) -> Result { + let mut s = s.to_uppercase(); + if let Some((p, v)) = s.split_once(':') { + if !matches!(p, "C" | "I" | "A") { + return Err(CVSSError::InvalidCVSS { + value: p.to_string(), + scope: "ImpactMetricsType prefix".to_string(), + }); + } + s = v.to_string(); + } + let c = { + let c = s.to_uppercase().chars().next(); + c.ok_or(CVSSError::InvalidCVSS { + value: s, + scope: "ImpactMetricsType".to_string(), + })? + }; + match c { + 'N' => Ok(Self::None), + 'L' => Ok(Self::Low), + 'H' => Ok(Self::High), + _ => Err(CVSSError::InvalidCVSS { + value: c.to_string(), + scope: "ImpactMetricsType".to_string(), + }), + } + } +} +impl FromStr for AvailabilityImpactType { + type Err = CVSSError; + + fn from_str(s: &str) -> Result { + let mut s = s.to_uppercase(); + if let Some((p, v)) = s.split_once(':') { + if !matches!(p, "C" | "I" | "A") { + return Err(CVSSError::InvalidCVSS { + value: p.to_string(), + scope: "ImpactMetricsType prefix".to_string(), + }); + } + s = v.to_string(); + } + let c = { + let c = s.to_uppercase().chars().next(); + c.ok_or(CVSSError::InvalidCVSS { + value: s, + scope: "ImpactMetricsType".to_string(), + })? + }; + match c { + 'N' => Ok(Self::None), + 'L' => Ok(Self::Low), + 'H' => Ok(Self::High), + _ => Err(CVSSError::InvalidCVSS { + value: c.to_string(), + scope: "ImpactMetricsType".to_string(), + }), + } + } +} + + +impl Display for ConfidentialityImpactType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", Self::NAME, self.as_str()) + } +} + +impl Metric for ConfidentialityImpactType { + const NAME: &'static str = "C"; + + fn score(&self) -> f32 { + match self { + ConfidentialityImpactType::None => 0.0, + ConfidentialityImpactType::Low => 0.22, + ConfidentialityImpactType::High => 0.56, + } + } + + fn as_str(&self) -> &'static str { + match self { + ConfidentialityImpactType::None => "N", + ConfidentialityImpactType::Low => "L", + ConfidentialityImpactType::High => "H", + } + } +} + +impl Display for IntegrityImpactType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", Self::NAME, self.as_str()) + } +} + +impl Metric for IntegrityImpactType { + const NAME: &'static str = "I"; + + fn score(&self) -> f32 { + match self { + IntegrityImpactType::None => 0.0, + IntegrityImpactType::Low => 0.22, + IntegrityImpactType::High => 0.56, + } + } + + fn as_str(&self) -> &'static str { + match self { + IntegrityImpactType::None => "N", + IntegrityImpactType::Low => "L", + IntegrityImpactType::High => "H", + } + } +} + +impl Display for AvailabilityImpactType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", Self::NAME, self.as_str()) + } +} + +impl Metric for AvailabilityImpactType { + const NAME: &'static str = "A"; + + fn score(&self) -> f32 { + match self { + AvailabilityImpactType::None => 0.0, + AvailabilityImpactType::Low => 0.22, + AvailabilityImpactType::High => 0.56, + } + } + + fn as_str(&self) -> &'static str { + match self { + AvailabilityImpactType::None => "N", + AvailabilityImpactType::Low => "L", + AvailabilityImpactType::High => "H", + } + } +} \ No newline at end of file diff --git a/cvss/src/v3/privileges_required.rs b/cvss/src/v3/privileges_required.rs index 79576d3..3789f4e 100644 --- a/cvss/src/v3/privileges_required.rs +++ b/cvss/src/v3/privileges_required.rs @@ -1,6 +1,9 @@ use crate::error::{CVSSError, Result}; +use crate::metric::Metric; use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; use std::str::FromStr; + // PR #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] #[serde(rename_all = "UPPERCASE")] @@ -13,15 +16,27 @@ pub enum PrivilegesRequiredType { None, } -// impl Into for PrivilegesRequiredType { -// fn into(self) -> f32 { -// match self { -// PrivilegesRequiredType::High => {} -// PrivilegesRequiredType::Low => {} -// PrivilegesRequiredType::None => {} -// } -// } -// } +impl Display for PrivilegesRequiredType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", Self::NAME, self.as_str()) + } +} + +impl Metric for PrivilegesRequiredType { + const NAME: &'static str = "PR"; + + fn score(&self) -> f32 { + self.scoped_score(false) + } + + fn as_str(&self) -> &'static str { + match self { + PrivilegesRequiredType::High => "H", + PrivilegesRequiredType::Low => "L", + PrivilegesRequiredType::None => "N", + } + } +} impl FromStr for PrivilegesRequiredType { type Err = CVSSError; @@ -48,3 +63,25 @@ impl FromStr for PrivilegesRequiredType { } } } + +impl PrivilegesRequiredType { + fn scoped_score(&self, scope_change: bool) -> f32 { + match self { + PrivilegesRequiredType::High => { + if scope_change { + 0.50 + } else { + 0.27 + } + } + PrivilegesRequiredType::Low => { + if scope_change { + 0.68 + } else { + 0.62 + } + } + PrivilegesRequiredType::None => 0.85, + } + } +} diff --git a/cvss/src/v3/scope.rs b/cvss/src/v3/scope.rs new file mode 100644 index 0000000..c385668 --- /dev/null +++ b/cvss/src/v3/scope.rs @@ -0,0 +1,57 @@ +use crate::error::{CVSSError, Result}; +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; +use std::str::FromStr; + +// S +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum ScopeType { + // S:U + Unchanged, + // S:C + Changed, +} + +impl ScopeType { + pub fn is_changed(&self) -> bool { + matches!(self, ScopeType::Changed) + } + + fn as_str(&self) -> &'static str { + match self { + ScopeType::Unchanged => "U", + ScopeType::Changed => "C", + } + } +} +impl Display for ScopeType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "S:{}", self.as_str()) + } +} +impl FromStr for ScopeType { + type Err = CVSSError; + + fn from_str(s: &str) -> Result { + let mut s = s.to_uppercase(); + if s.starts_with("S:") { + s = s.strip_prefix("S:").unwrap_or_default().to_string(); + } + let c = { + let c = s.to_uppercase().chars().next(); + c.ok_or(CVSSError::InvalidCVSS { + value: s, + scope: "ScopeType from_str".to_string(), + })? + }; + match c { + 'U' => Ok(Self::Unchanged), + 'C' => Ok(Self::Changed), + _ => Err(CVSSError::InvalidCVSS { + value: c.to_string(), + scope: "ScopeType".to_string(), + }), + } + } +} diff --git a/cvss/src/v3/severity.rs b/cvss/src/v3/severity.rs new file mode 100644 index 0000000..d84d25b --- /dev/null +++ b/cvss/src/v3/severity.rs @@ -0,0 +1,45 @@ +use crate::error::{CVSSError, Result}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +// 严重性 +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum SeverityType { + // 未校正 + None, + // 低危 + Low, + // 中危 + Medium, + // 高危 + High, + // 严重 + Critical, +} + + +impl FromStr for SeverityType { + type Err = CVSSError; + + fn from_str(s: &str) -> Result { + let c = { + let c = s.to_uppercase().chars().next(); + c.ok_or(CVSSError::InvalidCVSS { + value: s.to_string(), + scope: "SeverityType from_str".to_string(), + })? + }; + match c { + 'N' => Ok(Self::None), + 'L' => Ok(Self::Low), + 'M' => Ok(Self::Medium), + 'H' => Ok(Self::High), + 'C' => Ok(Self::Critical), + _ => Err(CVSSError::InvalidCVSS { + value: c.to_string(), + scope: "SeverityType".to_string(), + }), + } + } +} diff --git a/cvss/src/v3/user_interaction.rs b/cvss/src/v3/user_interaction.rs index ece2030..6be1417 100644 --- a/cvss/src/v3/user_interaction.rs +++ b/cvss/src/v3/user_interaction.rs @@ -1,6 +1,9 @@ +use std::fmt::{Display, Formatter}; use crate::error::{CVSSError, Result}; use serde::{Deserialize, Serialize}; use std::str::FromStr; +use crate::metric::Metric; + // UI #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] #[serde(rename_all = "UPPERCASE")] @@ -10,6 +13,31 @@ pub enum UserInteractionType { // UI:N None, } + +impl Display for UserInteractionType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", Self::NAME, self.as_str()) + } +} + +impl Metric for UserInteractionType { + const NAME: &'static str = "UI"; + + fn score(&self) -> f32 { + match self { + UserInteractionType::Required => 0.62, + UserInteractionType::None => 0.85, + } + } + + fn as_str(&self) -> &'static str { + match self { + UserInteractionType::Required => "R", + UserInteractionType::None => "N", + } + } +} + impl FromStr for UserInteractionType { type Err = CVSSError; diff --git a/cvss/src/version.rs b/cvss/src/version.rs new file mode 100644 index 0000000..34f79c5 --- /dev/null +++ b/cvss/src/version.rs @@ -0,0 +1,49 @@ +use crate::error::{CVSSError, Result}; +use serde::{Deserialize, Serialize}; +use std::fmt::Formatter; +use std::str::FromStr; +#[derive(Debug, Serialize, Deserialize, Clone)] +pub enum Version { + None, + #[serde(rename = "2.0")] + V2_0, + #[serde(rename = "3.0")] + V3_0, + #[serde(rename = "3.1")] + V3_1, + // todo V4 +} + +impl Default for Version { + fn default() -> Self { + Version::None + } +} + +impl FromStr for Version { + type Err = CVSSError; + + fn from_str(s: &str) -> Result { + let mut s = s.to_uppercase(); + if s.starts_with("CVSS:") { + s = s.strip_prefix("CVSS:").unwrap_or_default().to_string(); + } + match s.as_str() { + "2.0" => Ok(Self::V2_0), + "3.0" => Ok(Self::V3_0), + "3.1" => Ok(Self::V3_1), + _ => Ok(Self::None), + } + } +} + +impl std::fmt::Display for Version { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Version::None => write!(f, "NONE"), + Version::V2_0 => write!(f, "2.0"), + Version::V3_0 => write!(f, "3.0"), + Version::V3_1 => write!(f, "3.1"), + } + } +} diff --git a/tests/tests.rs b/tests/tests.rs index 71e2443..57846fd 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -5,7 +5,7 @@ mod tests { #[test] fn it_works() { let result = 2 + 2; - let cvss = cve::cvss::v3::CVSS::from_str("CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H"); + let cvss = cvss::v3::CVSS::from_str("CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H"); println!("{cvss:?}"); assert_eq!(result, 4); }