use std::io::{Read, Seek};
use std::path::Path;
use crate::crypto::{base64_encode, sm3};
use crate::model::signature::{Signature, Signatures};
use crate::types::{StId, StLoc, parent_dir, resolve_path};
use crate::{OfdReader, Result};
pub const OID_SM3: &str = "1.2.156.10197.1.401";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CheckMethod {
Sm3,
Unsupported(String),
}
impl CheckMethod {
pub fn from_oid(oid: Option<&str>) -> Self {
match oid.map(str::trim) {
None | Some("") | Some(OID_SM3) => CheckMethod::Sm3,
Some(other) => CheckMethod::Unsupported(other.to_string()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RefStatus {
Ok,
Mismatch {
expected: String,
actual: String,
},
Missing,
Unsupported,
}
#[derive(Debug, Clone)]
pub struct RefCheck {
pub file_ref: String,
pub status: RefStatus,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SigVerdict {
Valid,
Invalid,
Unverified,
}
#[derive(Debug, Clone)]
pub struct SignatureReport {
pub id: StId,
pub sig_type: String,
pub base_loc: String,
pub method: CheckMethod,
pub references: Vec<RefCheck>,
}
impl SignatureReport {
pub fn verdict(&self) -> SigVerdict {
if matches!(self.method, CheckMethod::Unsupported(_)) {
return SigVerdict::Unverified;
}
if self
.references
.iter()
.any(|r| !matches!(r.status, RefStatus::Ok))
{
SigVerdict::Invalid
} else {
SigVerdict::Valid
}
}
pub fn failures(&self) -> impl Iterator<Item = &RefCheck> {
self.references
.iter()
.filter(|r| matches!(r.status, RefStatus::Mismatch { .. } | RefStatus::Missing))
}
}
#[derive(Debug, Clone, Default)]
pub struct CheckReport {
pub problems: Vec<String>,
pub signatures: Vec<SignatureReport>,
}
impl CheckReport {
pub fn conforms(&self) -> bool {
self.problems.is_empty()
&& self
.signatures
.iter()
.all(|s| s.verdict() != SigVerdict::Invalid)
}
}
impl<R: Read + Seek> OfdReader<R> {
pub fn load_signatures(&mut self) -> Result<Vec<LoadedSignature>> {
let sig_locs: Vec<StLoc> = self
.ofd()
.doc_bodies
.iter()
.filter_map(|b| b.signatures.clone())
.collect();
let mut loaded = Vec::new();
for loc in &sig_locs {
let list_path = resolve_path("", loc);
let base = parent_dir(&list_path).to_string();
let list: Signatures = self.package_mut().parse(&list_path)?;
for sig_ref in &list.signatures {
let path = resolve_path(&base, &sig_ref.base_loc);
let signature: Signature = self.package_mut().parse(&path)?;
loaded.push(LoadedSignature {
id: sig_ref.id,
sig_type: sig_ref.type_or_default().to_string(),
base_loc: path,
signature,
});
}
}
Ok(loaded)
}
pub fn verify_signature(&mut self, loaded: &LoadedSignature) -> SignatureReport {
let refs = &loaded.signature.signed_info.references;
let method = CheckMethod::from_oid(refs.check_method.as_deref());
let mut checks = Vec::with_capacity(refs.references.len());
for reference in &refs.references {
let file_ref = reference.file_ref.to_string();
let status = if method != CheckMethod::Sm3 {
RefStatus::Unsupported
} else {
let path = resolve_path("", &reference.file_ref);
match self.package_mut().read(&path) {
Err(_) => RefStatus::Missing,
Ok(bytes) => {
let actual = base64_encode(&sm3(&bytes));
if actual == reference.check_value.trim() {
RefStatus::Ok
} else {
RefStatus::Mismatch {
expected: reference.check_value.trim().to_string(),
actual,
}
}
}
}
};
checks.push(RefCheck { file_ref, status });
}
SignatureReport {
id: loaded.id,
sig_type: loaded.sig_type.clone(),
base_loc: loaded.base_loc.clone(),
method,
references: checks,
}
}
pub fn verify_signatures(&mut self) -> Result<Vec<SignatureReport>> {
let loaded = self.load_signatures()?;
Ok(loaded.iter().map(|l| self.verify_signature(l)).collect())
}
}
#[derive(Debug, Clone)]
pub struct LoadedSignature {
pub id: StId,
pub sig_type: String,
pub base_loc: String,
pub signature: Signature,
}
pub fn check_reader<R: Read + Seek>(reader: &mut OfdReader<R>) -> CheckReport {
let mut report = CheckReport::default();
check_structure(reader, &mut report);
match reader.verify_signatures() {
Ok(sigs) => report.signatures = sigs,
Err(e) => report.problems.push(format!("签名装载失败: {e}")),
}
report
}
pub fn check_path<P: AsRef<Path>>(path: P) -> CheckReport {
let mut report = CheckReport::default();
let mut reader = match OfdReader::open(path) {
Ok(r) => r,
Err(e) => {
report
.problems
.push(format!("无法打开或解析 OFD 主入口: {e}"));
return report;
}
};
let sub = check_reader(&mut reader);
report.problems.extend(sub.problems);
report.signatures = sub.signatures;
report
}
fn check_structure<R: Read + Seek>(reader: &mut OfdReader<R>, report: &mut CheckReport) {
let ofd = reader.ofd();
if ofd.version.trim().is_empty() {
report
.problems
.push("OFD.xml 缺少 Version 属性".to_string());
}
if ofd.doc_type != "OFD" && ofd.doc_type != "OFD-A" {
report
.problems
.push(format!("OFD.xml 的 DocType 非法: {:?}", ofd.doc_type));
}
if ofd.doc_bodies.is_empty() {
report.problems.push("OFD.xml 不含任何 DocBody".to_string());
}
let bodies = ofd.doc_bodies.clone();
for (i, body) in bodies.iter().enumerate() {
let Some(root) = &body.doc_root else {
report
.problems
.push(format!("DocBody #{i} 缺少 DocRoot,无法定位文档根节点"));
continue;
};
let doc = match reader.load_document(body) {
Ok(d) => d,
Err(e) => {
report
.problems
.push(format!("DocBody #{i} 文档根节点 {root} 解析失败: {e}"));
continue;
}
};
let pages: Vec<_> = doc.pages().to_vec();
for page in &pages {
if let Err(e) = reader.load_page(&doc, page) {
report.problems.push(format!(
"DocBody #{i} 页 {} ({}) 解析失败: {e}",
page.id, page.base_loc
));
}
}
let templates: Vec<_> = doc.template_pages().to_vec();
for tpl in &templates {
if let Err(e) = reader.load_template(&doc, tpl) {
report.problems.push(format!(
"DocBody #{i} 模板页 {} 解析失败: {e}",
tpl.base_loc
));
}
}
let resources: Vec<_> = doc
.public_res()
.iter()
.chain(doc.document_res())
.cloned()
.collect();
for loc in &resources {
if let Err(e) = reader.load_resource(&doc, loc) {
report
.problems
.push(format!("DocBody #{i} 资源 {loc} 解析失败: {e}"));
}
}
}
}