Skip to main content

easyofd_core/signatures/
signed_info.rs

1//! 签名信息(SignedInfo)。
2//!
3//! 对应 Java: org.ofdrw.core.signatures.sig.SignedInfo
4//!
5//! 签名要保护的原文及本次签名相关的信息。
6//! GB/T 33190 第 18.2.1 节 图 86 表 67。
7
8use super::references::References;
9use super::seal::Seal;
10use super::stamp_annot::StampAnnot;
11
12/// 签章组件提供者信息。
13#[derive(Debug, Clone)]
14pub struct Provider {
15    /// 提供者名称。
16    pub name: String,
17    /// 提供者版本。
18    pub version: Option<String>,
19    /// 提供者公司。
20    pub company: Option<String>,
21}
22
23impl Provider {
24    /// 创建新的提供者信息。
25    #[must_use]
26    pub fn new(name: impl Into<String>) -> Self {
27        Self {
28            name: name.into(),
29            version: None,
30            company: None,
31        }
32    }
33
34    /// 设置版本。
35    #[must_use]
36    pub fn version(mut self, version: impl Into<String>) -> Self {
37        self.version = Some(version.into());
38        self
39    }
40
41    /// 设置公司。
42    #[must_use]
43    pub fn company(mut self, company: impl Into<String>) -> Self {
44        self.company = Some(company.into());
45        self
46    }
47
48    /// 序列化为 XML 字符串。
49    #[must_use]
50    pub fn to_xml_string(&self) -> String {
51        let mut xml = format!(r#"<ofd:Provider Name="{}""#, self.name);
52        if let Some(ref v) = self.version {
53            use std::fmt::Write;
54            let _ = write!(xml, r#" Version="{v}""#);
55        }
56        if let Some(ref c) = self.company {
57            use std::fmt::Write;
58            let _ = write!(xml, r#" Company="{c}""#);
59        }
60        xml.push_str(" />");
61        xml
62    }
63}
64
65/// 签名要保护的原文及本次签名相关的信息。
66///
67/// 包含签章组件提供者、签名方法、签名时间、
68/// 保护文件列表、签名外观和印章信息。
69#[derive(Debug, Clone)]
70pub struct SignedInfo {
71    /// 创建签名时所用的签章组件提供者信息(必选)。
72    pub provider: Provider,
73    /// 签名方法(可选),记录安全模块返回的签名算法代码。
74    pub signature_method: Option<String>,
75    /// 签名时间(可选),记录安全模块返回的签名时间。
76    pub signature_datetime: Option<String>,
77    /// 包内文件计算所得的摘要记录列表(必选)。
78    pub references: References,
79    /// 本签名关联的外观列表(可选),可出现多次。
80    pub stamp_annots: Vec<StampAnnot>,
81    /// 电子印章信息(可选)。
82    pub seal: Option<Seal>,
83}
84
85impl SignedInfo {
86    /// 创建新的签名信息。
87    #[must_use]
88    pub fn new(provider: Provider, references: References) -> Self {
89        Self {
90            provider,
91            signature_method: None,
92            signature_datetime: None,
93            references,
94            stamp_annots: Vec::new(),
95            seal: None,
96        }
97    }
98
99    /// 设置签名方法。
100    #[must_use]
101    pub fn signature_method(mut self, method: impl Into<String>) -> Self {
102        self.signature_method = Some(method.into());
103        self
104    }
105
106    /// 设置签名时间。
107    #[must_use]
108    pub fn signature_datetime(mut self, datetime: impl Into<String>) -> Self {
109        self.signature_datetime = Some(datetime.into());
110        self
111    }
112
113    /// 添加签名外观。
114    #[must_use]
115    pub fn add_stamp_annot(mut self, stamp_annot: StampAnnot) -> Self {
116        self.stamp_annots.push(stamp_annot);
117        self
118    }
119
120    /// 设置电子印章信息。
121    #[must_use]
122    pub fn seal(mut self, seal: Seal) -> Self {
123        self.seal = Some(seal);
124        self
125    }
126
127    /// 序列化为 XML 字符串。
128    #[must_use]
129    pub fn to_xml_string(&self) -> String {
130        use std::fmt::Write;
131
132        let mut xml = String::from("<ofd:SignedInfo>");
133        xml.push('\n');
134        xml.push_str(&self.provider.to_xml_string());
135
136        if let Some(ref method) = self.signature_method {
137            xml.push('\n');
138            let _ = write!(xml, "<ofd:SignatureMethod>{method}</ofd:SignatureMethod>");
139        }
140        if let Some(ref dt) = self.signature_datetime {
141            xml.push('\n');
142            let _ = write!(xml, "<ofd:SignatureDateTime>{dt}</ofd:SignatureDateTime>");
143        }
144
145        xml.push('\n');
146        xml.push_str(&self.references.to_xml_string());
147
148        for sa in &self.stamp_annots {
149            xml.push('\n');
150            xml.push_str(&sa.to_xml_string());
151        }
152
153        if let Some(ref seal) = self.seal {
154            xml.push('\n');
155            xml.push_str(&seal.to_xml_string());
156        }
157
158        xml.push_str("\n</ofd:SignedInfo>");
159        xml
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use crate::basic_type::{ST_Box, ST_RefID};
167
168    #[test]
169    fn test_signed_info_new() {
170        let si = SignedInfo::new(
171            Provider::new("TestProvider"),
172            References::new().add_reference(crate::signatures::reference::Reference::new(
173                "/Doc_0/Document.xml",
174                "hash",
175            )),
176        );
177        assert_eq!(si.provider.name, "TestProvider");
178        assert!(si.signature_method.is_none());
179        assert!(si.seal.is_none());
180        assert!(si.stamp_annots.is_empty());
181    }
182
183    #[test]
184    fn test_signed_info_builder() {
185        let si = SignedInfo::new(
186            Provider::new("MyProvider").version("1.0").company("MyCo"),
187            References::new().check_method("SM3"),
188        )
189        .signature_method("1.2.156.10197.1.501")
190        .signature_datetime("2025-01-01T00:00:00")
191        .add_stamp_annot(StampAnnot::new(
192            "s1",
193            ST_RefID::new(1),
194            ST_Box::new(0.0, 0.0, 100.0, 100.0),
195        ))
196        .seal(Seal::new("/Doc_0/Signs/Sign_0/Seal.esl"));
197        assert_eq!(si.signature_method.as_deref(), Some("1.2.156.10197.1.501"));
198        assert_eq!(
199            si.signature_datetime.as_deref(),
200            Some("2025-01-01T00:00:00")
201        );
202        assert_eq!(si.stamp_annots.len(), 1);
203        assert!(si.seal.is_some());
204    }
205
206    #[test]
207    fn test_signed_info_xml() {
208        let si = SignedInfo::new(
209            Provider::new("Prov"),
210            References::new().add_reference(crate::signatures::reference::Reference::new(
211                "/Doc_0/Document.xml",
212                "abc",
213            )),
214        )
215        .signature_method("SM2");
216        let xml = si.to_xml_string();
217        assert!(xml.contains("<ofd:SignedInfo>"));
218        assert!(xml.contains("Name=\"Prov\""));
219        assert!(xml.contains("<ofd:SignatureMethod>SM2</ofd:SignatureMethod>"));
220        assert!(xml.contains("</ofd:SignedInfo>"));
221    }
222
223    #[test]
224    fn test_provider_builder() {
225        let p = Provider::new("Test").version("2.0").company("ACME");
226        assert_eq!(p.name, "Test");
227        assert_eq!(p.version.as_deref(), Some("2.0"));
228        assert_eq!(p.company.as_deref(), Some("ACME"));
229        let xml = p.to_xml_string();
230        assert!(xml.contains("Name=\"Test\""));
231        assert!(xml.contains("Version=\"2.0\""));
232        assert!(xml.contains("Company=\"ACME\""));
233    }
234}