vex2pdf/
lib.rs

1//! # vex2pdf library
2//!
3//! Core functionality for converting CycloneDX VEX documents to PDF format.
4//!
5//! ## CycloneDX Compatibility
6//!
7//! This library fully supports CycloneDX schema version 1.5 and provides compatibility
8//! for version 1.6 documents that only use 1.5 fields. Documents using 1.6-specific
9//! fields may not process correctly.
10//!
11//! ## Features
12//!
13//! This library provides:
14//! - PDF generation capabilities for CycloneDX VEX documents
15//! - Support for various VEX elements including vulnerabilities, components, and metadata
16//!
17//! ## Architecture
18//!
19//! The library is organized into modules:
20//! - `pdf`: PDF generation functionality
21
22// Re-export cyclonedx-bom models for use by consumers of this library
23pub use cyclonedx_bom as model;
24
25pub mod pdf {
26    pub mod generator;
27}
28
29#[cfg(test)]
30mod tests {
31    use cyclonedx_bom::models::bom::Bom;
32    use cyclonedx_bom::models::metadata::Metadata;
33    use cyclonedx_bom::models::tool::{Tool, Tools};
34    use cyclonedx_bom::models::vulnerability::{Vulnerabilities, Vulnerability};
35
36    use cyclonedx_bom::models::vulnerability_rating::{
37        Score, ScoreMethod, Severity, VulnerabilityRating, VulnerabilityRatings,
38    };
39    use cyclonedx_bom::prelude::{DateTime, NormalizedString, SpecVersion, UrnUuid};
40    use std::fs;
41    use std::io::BufReader;
42
43    fn create_sample_vex() -> Bom {
44        // Create a VEX document following CycloneDX structure
45
46        Bom {
47            spec_version: SpecVersion::V1_5,
48            version: 1,
49            serial_number: Some(
50                UrnUuid::new("urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79".to_string())
51                    .expect("Unable to create urn:uuid"),
52            ),
53            metadata: Some(Metadata {
54                timestamp: Some(DateTime::now().expect("failed to convert date")),
55                tools: Some(Tools::List(vec![Tool {
56                    name: Some(NormalizedString::new("my_tool")),
57                    ..Tool::default()
58                }])),
59                ..Metadata::default()
60            }),
61            vulnerabilities: Some(Vulnerabilities(vec![
62                Vulnerability {
63                    bom_ref: None,
64                    id: None,
65                    vulnerability_source: None,
66                    description: Some(
67                        "Known vulnerability in library that allows unauthorized access"
68                            .to_string(),
69                    ),
70                    detail: Some(
71                        "Detailed explanation of the vulnerability and its potential impact."
72                            .to_string(),
73                    ),
74                    recommendation: Some("Upgrade to version 1.2.4 or later".to_string()),
75                    workaround: None,
76                    proof_of_concept: None,
77                    advisories: None,
78                    created: None,
79                    published: None,
80                    updated: None,
81                    rejected: None,
82                    vulnerability_credits: None,
83                    tools: None,
84                    vulnerability_analysis: None,
85                    vulnerability_targets: None,
86                    vulnerability_ratings: Some(VulnerabilityRatings(vec![VulnerabilityRating {
87                        score: Some(Score::from(8.1)),
88                        severity: Some(Severity::High),
89                        score_method: Some(ScoreMethod::CVSSv31),
90                        vector: Some(NormalizedString::new(
91                            "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
92                        )),
93                        vulnerability_source: None,
94                        justification: None,
95                    }])),
96
97                    vulnerability_references: None,
98                    cwes: None,
99                    properties: None,
100                },
101                Vulnerability {
102                    bom_ref: None,
103                    id: None,
104                    vulnerability_source: None,
105                    description: Some("Component does not use the affected library".to_string()),
106                    detail: Some(
107                        "Detailed explanation of the vulnerability and its potential impact."
108                            .to_string(),
109                    ),
110                    recommendation: Some("Upgrade to version 1.2.3 or later".to_string()),
111                    workaround: None,
112                    proof_of_concept: None,
113                    advisories: None,
114                    created: None,
115                    published: None,
116                    updated: None,
117                    rejected: None,
118                    vulnerability_credits: None,
119                    tools: None,
120                    vulnerability_analysis: None,
121                    vulnerability_targets: None,
122                    vulnerability_ratings: Some(VulnerabilityRatings(vec![VulnerabilityRating {
123                        score: Some(Score::from(6.5)),
124                        severity: Some(Severity::High),
125                        score_method: Some(ScoreMethod::CVSSv31),
126                        vector: Some(NormalizedString::new(
127                            "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L",
128                        )),
129                        vulnerability_source: None,
130                        justification: None,
131                    }])),
132
133                    vulnerability_references: None,
134                    cwes: None,
135                    properties: None,
136                },
137            ])),
138            ..Bom::default()
139        }
140    }
141
142    #[test]
143    fn test_vex_serialization() {
144        let vex = create_sample_vex();
145
146        // Test serialization
147        let mut output = Vec::<u8>::new();
148        vex.clone()
149            .output_as_json_v1_5(&mut output)
150            .expect("failed to read vex object");
151
152        let json_str = String::from_utf8(output).expect("failed to serialize json object");
153
154        println!("Serialized VEX: {}", json_str);
155
156        let parsed_json =
157            serde_json::from_str(&json_str).expect("serde failed to read json from string object");
158        let deserialization_result = Bom::parse_json_value(parsed_json);
159
160        // Test deserialization
161        match deserialization_result {
162            Ok(deserialized) => {
163                println!("Deserialized CycloneDX: {:?}", deserialized);
164                // Verify the round trip works
165                assert_eq!(vex.serial_number, deserialized.serial_number);
166                assert_eq!(vex.spec_version, deserialized.spec_version);
167            }
168            Err(err) => {
169                panic!("Deserialization failed: {:?}", err);
170            }
171        }
172    }
173
174    #[test]
175    fn test_vex_file_io() {
176        use std::io::Write;
177
178        let vex = create_sample_vex();
179        let mut output = Vec::<u8>::new();
180        vex.clone()
181            .output_as_json_v1_5(&mut output)
182            .expect("failed to read vex object");
183        let json_str = String::from_utf8(output).expect("failed to serialize json object");
184
185        // Create a temporary file
186        let mut temp_file = std::env::temp_dir();
187        temp_file.push("test_vex.json");
188
189        // Write the VEX to the file
190        let mut file = fs::File::create(&temp_file).expect("Failed to create temp file");
191        file.write_all(json_str.as_bytes())
192            .expect("Failed to write to temp file");
193
194        // Read it back
195        let content_reader =
196            BufReader::new(fs::File::open(&temp_file).expect("failed to open file"));
197        let loaded_vex: Bom = Bom::parse_from_json(content_reader).expect("Failed to parse JSON");
198
199        // Clean up
200        fs::remove_file(&temp_file).expect("Failed to remove temp file");
201
202        // Verify
203        assert_eq!(vex.serial_number, loaded_vex.serial_number);
204    }
205
206    #[test]
207    fn test_generate_sample_file() {
208        let vex = create_sample_vex();
209        let mut output = Vec::<u8>::new();
210        vex.clone()
211            .output_as_json_v1_5(&mut output)
212            .expect("failed to read vex object");
213        let json_str = String::from_utf8(output).expect("failed to serialize json object");
214
215        // Create a sample file in the current directory
216        fs::write("sample_vex.json", json_str).expect("Failed to write sample file");
217
218        println!("Sample VEX file created at sample_vex.json");
219    }
220}