1use std::io::{Read, Seek};
33use std::path::Path;
34
35use crate::crypto::{base64_encode, sm3};
36use crate::model::signature::{Signature, Signatures};
37use crate::types::{StId, StLoc, parent_dir, resolve_path};
38use crate::{OfdReader, Result};
39
40pub const OID_SM3: &str = "1.2.156.10197.1.401";
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum CheckMethod {
46 Sm3,
48 Unsupported(String),
50}
51
52impl CheckMethod {
53 pub fn from_oid(oid: Option<&str>) -> Self {
55 match oid.map(str::trim) {
56 None | Some("") | Some(OID_SM3) => CheckMethod::Sm3,
57 Some(other) => CheckMethod::Unsupported(other.to_string()),
58 }
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum RefStatus {
65 Ok,
67 Mismatch {
69 expected: String,
71 actual: String,
73 },
74 Missing,
76 Unsupported,
78}
79
80#[derive(Debug, Clone)]
82pub struct RefCheck {
83 pub file_ref: String,
85 pub status: RefStatus,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum SigVerdict {
92 Valid,
94 Invalid,
96 Unverified,
98}
99
100#[derive(Debug, Clone)]
102pub struct SignatureReport {
103 pub id: StId,
105 pub sig_type: String,
107 pub base_loc: String,
109 pub method: CheckMethod,
111 pub references: Vec<RefCheck>,
113}
114
115impl SignatureReport {
116 pub fn verdict(&self) -> SigVerdict {
118 if matches!(self.method, CheckMethod::Unsupported(_)) {
119 return SigVerdict::Unverified;
120 }
121 if self
122 .references
123 .iter()
124 .any(|r| !matches!(r.status, RefStatus::Ok))
125 {
126 SigVerdict::Invalid
127 } else {
128 SigVerdict::Valid
129 }
130 }
131
132 pub fn failures(&self) -> impl Iterator<Item = &RefCheck> {
134 self.references
135 .iter()
136 .filter(|r| matches!(r.status, RefStatus::Mismatch { .. } | RefStatus::Missing))
137 }
138}
139
140#[derive(Debug, Clone, Default)]
142pub struct CheckReport {
143 pub problems: Vec<String>,
145 pub signatures: Vec<SignatureReport>,
147}
148
149impl CheckReport {
150 pub fn conforms(&self) -> bool {
154 self.problems.is_empty()
155 && self
156 .signatures
157 .iter()
158 .all(|s| s.verdict() != SigVerdict::Invalid)
159 }
160}
161
162impl<R: Read + Seek> OfdReader<R> {
163 pub fn load_signatures(&mut self) -> Result<Vec<LoadedSignature>> {
168 let sig_locs: Vec<StLoc> = self
169 .ofd()
170 .doc_bodies
171 .iter()
172 .filter_map(|b| b.signatures.clone())
173 .collect();
174
175 let mut loaded = Vec::new();
176 for loc in &sig_locs {
177 let list_path = resolve_path("", loc);
178 let base = parent_dir(&list_path).to_string();
179 let list: Signatures = self.package_mut().parse(&list_path)?;
180 for sig_ref in &list.signatures {
181 let path = resolve_path(&base, &sig_ref.base_loc);
182 let signature: Signature = self.package_mut().parse(&path)?;
183 loaded.push(LoadedSignature {
184 id: sig_ref.id,
185 sig_type: sig_ref.type_or_default().to_string(),
186 base_loc: path,
187 signature,
188 });
189 }
190 }
191 Ok(loaded)
192 }
193
194 pub fn verify_signature(&mut self, loaded: &LoadedSignature) -> SignatureReport {
196 let refs = &loaded.signature.signed_info.references;
197 let method = CheckMethod::from_oid(refs.check_method.as_deref());
198
199 let mut checks = Vec::with_capacity(refs.references.len());
200 for reference in &refs.references {
201 let file_ref = reference.file_ref.to_string();
202 let status = if method != CheckMethod::Sm3 {
203 RefStatus::Unsupported
204 } else {
205 let path = resolve_path("", &reference.file_ref);
206 match self.package_mut().read(&path) {
207 Err(_) => RefStatus::Missing,
208 Ok(bytes) => {
209 let actual = base64_encode(&sm3(&bytes));
210 if actual == reference.check_value.trim() {
211 RefStatus::Ok
212 } else {
213 RefStatus::Mismatch {
214 expected: reference.check_value.trim().to_string(),
215 actual,
216 }
217 }
218 }
219 }
220 };
221 checks.push(RefCheck { file_ref, status });
222 }
223
224 SignatureReport {
225 id: loaded.id,
226 sig_type: loaded.sig_type.clone(),
227 base_loc: loaded.base_loc.clone(),
228 method,
229 references: checks,
230 }
231 }
232
233 pub fn verify_signatures(&mut self) -> Result<Vec<SignatureReport>> {
235 let loaded = self.load_signatures()?;
236 Ok(loaded.iter().map(|l| self.verify_signature(l)).collect())
237 }
238}
239
240#[derive(Debug, Clone)]
242pub struct LoadedSignature {
243 pub id: StId,
245 pub sig_type: String,
247 pub base_loc: String,
249 pub signature: Signature,
251}
252
253pub fn check_reader<R: Read + Seek>(reader: &mut OfdReader<R>) -> CheckReport {
258 let mut report = CheckReport::default();
259 check_structure(reader, &mut report);
260
261 match reader.verify_signatures() {
262 Ok(sigs) => report.signatures = sigs,
263 Err(e) => report.problems.push(format!("签名装载失败: {e}")),
264 }
265 report
266}
267
268pub fn check_path<P: AsRef<Path>>(path: P) -> CheckReport {
273 let mut report = CheckReport::default();
274 let mut reader = match OfdReader::open(path) {
275 Ok(r) => r,
276 Err(e) => {
277 report
278 .problems
279 .push(format!("无法打开或解析 OFD 主入口: {e}"));
280 return report;
281 }
282 };
283 let sub = check_reader(&mut reader);
284 report.problems.extend(sub.problems);
285 report.signatures = sub.signatures;
286 report
287}
288
289fn check_structure<R: Read + Seek>(reader: &mut OfdReader<R>, report: &mut CheckReport) {
291 let ofd = reader.ofd();
292 if ofd.version.trim().is_empty() {
293 report
294 .problems
295 .push("OFD.xml 缺少 Version 属性".to_string());
296 }
297 if ofd.doc_type != "OFD" && ofd.doc_type != "OFD-A" {
298 report
299 .problems
300 .push(format!("OFD.xml 的 DocType 非法: {:?}", ofd.doc_type));
301 }
302 if ofd.doc_bodies.is_empty() {
303 report.problems.push("OFD.xml 不含任何 DocBody".to_string());
304 }
305
306 let bodies = ofd.doc_bodies.clone();
307 for (i, body) in bodies.iter().enumerate() {
308 let Some(root) = &body.doc_root else {
309 report
310 .problems
311 .push(format!("DocBody #{i} 缺少 DocRoot,无法定位文档根节点"));
312 continue;
313 };
314
315 let doc = match reader.load_document(body) {
316 Ok(d) => d,
317 Err(e) => {
318 report
319 .problems
320 .push(format!("DocBody #{i} 文档根节点 {root} 解析失败: {e}"));
321 continue;
322 }
323 };
324
325 let pages: Vec<_> = doc.pages().to_vec();
327 for page in &pages {
328 if let Err(e) = reader.load_page(&doc, page) {
329 report.problems.push(format!(
330 "DocBody #{i} 页 {} ({}) 解析失败: {e}",
331 page.id, page.base_loc
332 ));
333 }
334 }
335
336 let templates: Vec<_> = doc.template_pages().to_vec();
338 for tpl in &templates {
339 if let Err(e) = reader.load_template(&doc, tpl) {
340 report.problems.push(format!(
341 "DocBody #{i} 模板页 {} 解析失败: {e}",
342 tpl.base_loc
343 ));
344 }
345 }
346
347 let resources: Vec<_> = doc
349 .public_res()
350 .iter()
351 .chain(doc.document_res())
352 .cloned()
353 .collect();
354 for loc in &resources {
355 if let Err(e) = reader.load_resource(&doc, loc) {
356 report
357 .problems
358 .push(format!("DocBody #{i} 资源 {loc} 解析失败: {e}"));
359 }
360 }
361 }
362}