1use std::path::PathBuf;
6
7use anyhow::Result;
8
9use crate::parsers::parse_sbom;
10use crate::pipeline::exit_codes;
11use crate::verification::{
12 ModelVerifyResult, audit_component_hashes, verify_file_hash, verify_model_dir,
13};
14
15#[derive(Debug, Clone, clap::Subcommand)]
17pub enum VerifyAction {
18 Hash {
20 file: PathBuf,
22 #[arg(long)]
24 expected: Option<String>,
25 #[arg(long, conflicts_with = "expected")]
27 hash_file: Option<PathBuf>,
28 },
29 AuditHashes {
31 file: PathBuf,
33 #[arg(
35 short = 'f',
36 long = "output",
37 alias = "format",
38 default_value = "table"
39 )]
40 format: String,
41 },
42 ModelWeights {
44 file: PathBuf,
46 #[arg(long = "model-dir")]
49 model_dir: PathBuf,
50 #[arg(
52 short = 'f',
53 long = "output",
54 alias = "format",
55 default_value = "table"
56 )]
57 format: String,
58 },
59}
60
61pub fn run_verify(action: VerifyAction, quiet: bool) -> Result<i32> {
63 match action {
64 VerifyAction::Hash {
65 file,
66 expected,
67 hash_file,
68 } => {
69 let expected_hash = if let Some(e) = expected {
70 e
71 } else if let Some(hf) = hash_file {
72 crate::verification::read_hash_file(&hf)?
73 } else {
74 let sha_path = file.with_extension(
76 file.extension()
77 .map(|e| format!("{}.sha256", e.to_string_lossy()))
78 .unwrap_or_else(|| "sha256".to_string()),
79 );
80 if sha_path.exists() {
81 if !quiet {
82 eprintln!("Using sidecar hash file: {}", sha_path.display());
83 }
84 crate::verification::read_hash_file(&sha_path)?
85 } else {
86 anyhow::bail!(
87 "no hash provided. Use --expected <hash> or --hash-file <path>, \
88 or place a .sha256 sidecar file alongside the SBOM"
89 );
90 }
91 };
92
93 let result = verify_file_hash(&file, &expected_hash)?;
94
95 if !quiet {
96 println!("{result}");
97 }
98
99 if result.verified {
100 Ok(exit_codes::SUCCESS)
101 } else {
102 Ok(exit_codes::ERROR)
103 }
104 }
105 VerifyAction::AuditHashes { file, format } => {
106 let sbom = parse_sbom(&file)?;
107 let report = audit_component_hashes(&sbom);
108
109 if format == "json" {
110 println!("{}", serde_json::to_string_pretty(&report)?);
111 } else {
112 println!("Component Hash Audit");
113 println!("====================");
114 println!(
115 "Total: {} Strong: {} Weak-only: {} Missing: {}",
116 report.total_components,
117 report.strong_count,
118 report.weak_only_count,
119 report.missing_count
120 );
121 println!("Pass rate: {:.1}%\n", report.pass_rate());
122
123 if report.weak_only_count > 0 || report.missing_count > 0 {
124 println!("Issues:");
125 for comp in &report.components {
126 match comp.result {
127 crate::verification::HashAuditResult::WeakOnly => {
128 println!(
129 " WEAK {} {} ({})",
130 comp.name,
131 comp.version.as_deref().unwrap_or(""),
132 comp.algorithms.join(", ")
133 );
134 }
135 crate::verification::HashAuditResult::Missing => {
136 println!(
137 " MISSING {} {}",
138 comp.name,
139 comp.version.as_deref().unwrap_or("")
140 );
141 }
142 crate::verification::HashAuditResult::Strong => {}
143 }
144 }
145 }
146 }
147
148 if report.missing_count > 0 || report.weak_only_count > 0 {
149 Ok(exit_codes::CHANGES_DETECTED) } else {
151 Ok(exit_codes::SUCCESS)
152 }
153 }
154 VerifyAction::ModelWeights {
155 file,
156 model_dir,
157 format,
158 } => {
159 let sbom = parse_sbom(&file)?;
160 let report = verify_model_dir(&sbom, &model_dir);
161
162 if format == "json" {
163 println!("{}", serde_json::to_string_pretty(&report)?);
164 } else {
165 println!("Model Weight Verification");
166 println!("=========================");
167 println!("Model dir: {}", report.model_dir);
168 println!(
169 "Models: {} Verified: {} Mismatch: {} Missing: {} No-hash: {}",
170 report.total_models,
171 report.verified_count,
172 report.mismatch_count,
173 report.missing_count,
174 report.no_hash_count,
175 );
176
177 for comp in &report.components {
178 match comp.result {
181 ModelVerifyResult::Verified => {
182 println!(
183 " {} {} {} -> {}",
184 comp.result.label(),
185 comp.name,
186 comp.version.as_deref().unwrap_or(""),
187 comp.file.as_deref().unwrap_or("?"),
188 );
189 }
190 _ => {
191 println!(
192 " {} {} {}{}",
193 comp.result.label(),
194 comp.name,
195 comp.version.as_deref().unwrap_or(""),
196 comp.hash
197 .as_deref()
198 .map(|h| format!(" ({h})"))
199 .unwrap_or_default(),
200 );
201 }
202 }
203 }
204 }
205
206 if report.has_failures() {
207 Ok(exit_codes::ERROR) } else {
209 Ok(exit_codes::SUCCESS)
210 }
211 }
212 }
213}