├── .gitignore
├── LICENSE
├── README.md
├── attachments.go
├── attachments_test.go
├── common_data.go
├── common_data_test.go
├── content.go
├── content_info.go
├── custom_tags.go
├── document.go
├── document_res.go
├── document_res_test.go
├── document_test.go
├── go.mod
├── go.sum
├── ofd.go
├── ofd_reader.go
├── ofd_reader_test.go
├── ofd_test.go
├── pages.go
├── pages_test.go
├── public_res.go
├── public_res_test.go
├── ses_seal.go
├── ses_seal_test.go
├── signature.go
├── signature_test.go
├── signatures.go
├── signatures_test.go
├── validator.go
└── zip.go
/.gitignore:
--------------------------------------------------------------------------------
1 | /.vscode
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## ofd 文档
2 |
3 | OFD(Open Fixed-layout Documents的简称,意为开放版式文件)版式文档是版面呈现效果高度精确固定的电子文件,其呈现与设备无关。与pdf文件相仿,具有格式独立、版面固定、固化呈现等特点。OFD也逐渐开始在电子发票、电子公文、电子证照等等的领域中应用。
4 |
5 | ## ofd 特点
6 |
7 | OFD标准有一系列技术优势。
8 |
9 | 1. 体积精简,格式开放,利于理解,长期可读可用
10 | 2. 根据我国各领域特色需要进行特性扩展,更深入地贴合了应用需求
11 | 3. 标准可支持国产密码算法,是文档安全性的有力保证,也是文件具有法律效力的基本条件
12 | 4. 标准是自主可控的,国家再有需要对OFD做上面提到的扩展时,特别是在我国党政军严肃类文档应用领域,可以不受控于外部的厂商,我们有自主的标准话语权
13 |
14 | ## 自定义签名验证器
15 |
16 | 通过实现 Validator 接口 ,初始化的时候加载验证器WithValidator
17 |
18 | ```
19 | ofdReader, err :=NewOFDReader(testpath, WithValidator(XXValidator{}))
20 | if err != nil {
21 | t.Logf("%s", err)
22 | }
23 | defer ofdReader.Close()
24 | ```
25 |
26 | // 文本摘要
27 | Digest([]byte) []byte
28 |
29 | // 签名验证
30 | Verify([]byte, []byte, []byte) (bool, error)
31 |
32 | **完整例子**
33 | 以github.com/emmansun/gmsm 国密算法库为例
34 |
35 | ```
36 | package ofd
37 |
38 | import (
39 | "crypto/ecdsa"
40 | std "encoding/asn1"
41 | "math/big"
42 |
43 | "github.com/emmansun/gmsm/sm2"
44 | "github.com/emmansun/gmsm/sm3"
45 | "github.com/emmansun/gmsm/smx509"
46 | )
47 |
48 | type OtherValidator struct {
49 | }
50 |
51 |
52 | func (gm *OtherValidator) Digest(msg []byte) []byte {
53 | h := sm3.New()
54 | h.Write(msg)
55 | dataDash := h.Sum(nil)
56 | return dataDash
57 |
58 | }
59 | func (gm *OtherValidator) Verify(cert []byte, msg []byte, signature []byte) (bool, error) {
60 | certificate, err := smx509.ParseCertificate(cert)
61 | if err != nil {
62 | return false, err
63 | }
64 | pk := certificate.PublicKey.(*ecdsa.PublicKey)
65 | if len(signature) == 64 {
66 | r := new(big.Int).SetBytes(signature[0:32])
67 | s := new(big.Int).SetBytes(signature[32:64])
68 |
69 | result := sm2.VerifyWithSM2(pk, nil, msg, r, s)
70 | return result, nil
71 | } else {
72 | type Sign struct {
73 | R *big.Int
74 | S *big.Int
75 | }
76 | var sign Sign
77 | if _, err := std.Unmarshal(signature, &sign); err != nil {
78 | return false, err
79 | } else {
80 | ff, _ := new(big.Int).SetString(MAX, 16)
81 | if sign.R.Sign() == -1 {
82 | sign.R.And(sign.R, ff)
83 | }
84 | if sign.S.Sign() == -1 {
85 | sign.S.And(sign.S, ff)
86 | }
87 | result := sm2.VerifyWithSM2(pk, nil, msg, sign.R, sign.S)
88 | return result, nil
89 | }
90 | }
91 | }
92 |
93 | ```
94 |
95 | ## 小通知
96 | ofd-go项目发起有段时间,由于工作中涉及ofd的相关开发,我们团队曾经使用ofd-fw这个java库来完成我们的开发任务,使用过程中发现不少问题,期间我们也fork该库,形成自己的分支,问题的根源在于尽管ofd标准是统一的,但是各家软件集成商对于标准的理解不太一样,特别是关键字段的赋值上差别很大,所以需要针对不同的ofd软件集成商做适配,才能实现通用,由于ofd-rw的解决方案足有优秀,我们决定开辟新天地,ofd-go正是在这个背景下产生的,依托于go语言优秀的跨平台能力,成熟的生态,可以高度定制,开发HTTP服务,SDK,动态链接库,都是不错的选择.
97 |
98 | 目前的ofd-go每天的独立clone在50左右,反映出在市面上有一定的需求,但是除了项目初期有热心人士关注,提出issue,提交一些bug,后续的clone者反馈问题较少,作为发起人,我意识到ofd-go本身还不完善,为了持续推进开源项目,本着"我为人人,人人为我"的精神,现阶段希望征集大家对于ofd-go的需求提案,无论是提交bug、提供ofd测试样本或者是提供产品思路,都希望广泛交流,为此我建了讨论组,感兴趣的可以加群
99 |
100 | QQ:628529123
101 | 
102 |
103 |
--------------------------------------------------------------------------------
/attachments.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "archive/zip"
5 | "encoding/xml"
6 | )
7 |
8 | type Attachments struct {
9 | AttachmentsXml
10 | pwd string
11 | rc *zip.ReadCloser
12 | }
13 | type AttachmentsXml struct {
14 | XMLName xml.Name `xml:"Attachments"`
15 | Text string `xml:",chardata"`
16 | Ofd string `xml:"ofd,attr"`
17 | Attachment []struct {
18 | Text string `xml:",chardata"`
19 | ID string `xml:"ID,attr"`
20 | Name string `xml:"Name,attr"`
21 | Format string `xml:"Format,attr"`
22 | Size string `xml:"Size,attr"`
23 | Visible string `xml:"Visible,attr"`
24 | CreationDate string `xml:"CreationDate,attr"`
25 | } `xml:"Attachment"`
26 | }
27 |
28 | func (attachments *Attachments) GetFileContent(path string) ([]byte, error) {
29 | return LoadZipFileContent(attachments.rc, path)
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/attachments_test.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "encoding/xml"
5 | "testing"
6 | )
7 |
8 | const attachment_content = `
9 |
10 |
12 | bker_issuer_20191231_C10303110004552019030390296600243000000000019444.xml
13 |
14 | `
15 |
16 | func Test_Attachments(t *testing.T) {
17 | var res Attachments
18 | if err := xml.Unmarshal([]byte(attachment_content), &res); err != nil {
19 | t.Logf("%s", err)
20 | } else {
21 | t.Logf("%v", res)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/common_data.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "archive/zip"
5 | "encoding/xml"
6 | "fmt"
7 | "strings"
8 | )
9 |
10 | type CommonDataXml struct {
11 | Text string `xml:",chardata"`
12 | MaxUnitID struct {
13 | Text string `xml:",chardata"`
14 | } `xml:"MaxUnitID"`
15 | PageArea struct {
16 | Text string `xml:",chardata"`
17 | PhysicalBox struct {
18 | Text string `xml:",chardata"`
19 | } `xml:"PhysicalBox"`
20 | } `xml:"PageArea"`
21 | PublicRes struct {
22 | Text string `xml:",chardata"`
23 | } `xml:"PublicRes"`
24 | DocumentRes struct {
25 | Text string `xml:",chardata"`
26 | } `xml:"DocumentRes"`
27 | TemplatePage struct {
28 | Text string `xml:",chardata"`
29 | ID string `xml:"ID,attr"`
30 | BaseLoc string `xml:"BaseLoc,attr"`
31 | } `xml:"TemplatePage"`
32 | }
33 | type CommonData struct {
34 | CommonDataXml
35 | pwd string
36 | rc *zip.ReadCloser
37 | }
38 |
39 | func (commonData *CommonData) GetPublicRes() (*PublicRes, error) {
40 | res, err := commonData.getResource(PUBLICRES)
41 | if err != nil {
42 | return nil, err
43 | }
44 | return res.(*PublicRes), nil
45 | }
46 | func (commonData *CommonData) GetDocumentRes() (*DocumentRes, error) {
47 | res, err := commonData.getResource(DOCUMENTRES)
48 | if err != nil {
49 | return nil, err
50 | }
51 | return res.(*DocumentRes), nil
52 | }
53 | func (commonData *CommonData) GetTemplatePage() (*Page, error) {
54 |
55 | res, err := commonData.getResource(TEMPLATEPAGE)
56 | if err != nil {
57 | return nil, err
58 | }
59 | return res.(*Page), nil
60 | }
61 |
62 | type CommonDataResource int
63 |
64 | const (
65 | PUBLICRES CommonDataResource = iota
66 |
67 | DOCUMENTRES
68 |
69 | TEMPLATEPAGE
70 | )
71 |
72 | func (commonData *CommonData) getFileContent(path string) ([]byte, error) {
73 | return LoadZipFileContent(commonData.rc, path)
74 | }
75 | func (commonData *CommonData) getResource(resType CommonDataResource) (interface{}, error) {
76 | var path string
77 | switch resType {
78 | case PUBLICRES:
79 | path = commonData.PublicRes.Text
80 | case DOCUMENTRES:
81 | path = commonData.DocumentRes.Text
82 | case TEMPLATEPAGE:
83 | path = commonData.TemplatePage.BaseLoc
84 | default:
85 | return nil, fmt.Errorf("resType[%v] is invalid", resType)
86 | }
87 | if strings.EqualFold(path, "") {
88 | return nil, fmt.Errorf("Resource is null")
89 | }
90 | if path[0] == '/' {
91 | path = commonData.pwd + path
92 | } else {
93 | path = commonData.pwd + "/" + path
94 | }
95 | pos := strings.LastIndexByte(path, '/')
96 | pwd := path[0:pos]
97 | content, err := commonData.getFileContent(path)
98 | if err != nil {
99 | return nil, err
100 | }
101 | switch resType {
102 | case PUBLICRES:
103 | var publicRes PublicRes
104 | err := xml.Unmarshal(content, &publicRes)
105 | if err != nil {
106 | return nil, err
107 | } else {
108 | publicRes.pwd = pwd
109 | publicRes.rc = commonData.rc
110 | }
111 | return &publicRes, nil
112 | case DOCUMENTRES:
113 | var documentRes DocumentRes
114 | err := xml.Unmarshal(content, &documentRes)
115 | if err != nil {
116 | return nil, err
117 | } else {
118 | documentRes.pwd = pwd
119 | documentRes.rc = commonData.rc
120 | }
121 | return &documentRes, nil
122 |
123 | case TEMPLATEPAGE:
124 | var page Page
125 | err := xml.Unmarshal(content, &page)
126 | if err != nil {
127 | return nil, err
128 | } else {
129 | page.pwd = pwd
130 | }
131 | return &page, nil
132 | default:
133 | return nil, fmt.Errorf("resType[%v] is invalid", resType)
134 |
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/common_data_test.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "testing"
7 | )
8 |
9 | func TestCommonData(t *testing.T) {
10 | pwd, _ := os.Getwd()
11 | file := filepath.Join(pwd, "samples", "DZHD_1605281110201000001_202205250016050102202100000069141950_20220525_000413.ofd")
12 | ofdReader, err := NewOFDReader(file)
13 | if err != nil {
14 | t.Logf("%s", err)
15 | }
16 | defer ofdReader.Close()
17 |
18 | ofd, err := ofdReader.OFD()
19 | if err != nil {
20 | t.Logf("%v", err)
21 | } else {
22 | t.Logf("%v", ofd)
23 | }
24 |
25 | index_0 := ofd.DocBody[0].DocInfo.DocID.Text
26 | doc, err := ofdReader.GetDocumentById(index_0)
27 | if err != nil {
28 | t.Logf("%v", err)
29 | } else {
30 | t.Logf("%v", doc)
31 | }
32 | commonData, err := doc.GetCommonData()
33 | if err != nil {
34 | t.Logf("%v", err)
35 | } else {
36 | t.Logf("%v", commonData)
37 | }
38 | pr, err := commonData.GetPublicRes()
39 | if err != nil {
40 | t.Logf("%v", err)
41 | } else {
42 | t.Logf("%v", pr)
43 | }
44 | dr, err := commonData.GetDocumentRes()
45 | if err != nil {
46 | t.Logf("%v", err)
47 | } else {
48 | t.Logf("%v", dr)
49 | }
50 | tp, err := commonData.GetTemplatePage()
51 | if err != nil {
52 | t.Logf("%v", err)
53 | } else {
54 | t.Logf("%v", tp)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/content.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import "encoding/xml"
4 |
5 | type Page struct {
6 | PageXml
7 | pwd string
8 | }
9 | type PageXml struct {
10 | XMLName xml.Name `xml:"Page"`
11 | Text string `xml:",chardata"`
12 | Ofd string `xml:"ofd,attr"`
13 | Area struct {
14 | Text string `xml:",chardata"`
15 | PhysicalBox struct {
16 | Text string `xml:",chardata"`
17 | } `xml:"PhysicalBox"`
18 | } `xml:"Area"`
19 | Content struct {
20 | Text string `xml:",chardata"`
21 | Layer struct {
22 | Text string `xml:",chardata"`
23 | ID string `xml:"ID,attr"`
24 | TextObject []struct {
25 | Text string `xml:",chardata"`
26 | ID string `xml:"ID,attr"`
27 | Boundary string `xml:"Boundary,attr"`
28 | Font string `xml:"Font,attr"`
29 | Size string `xml:"Size,attr"`
30 | Weight string `xml:"Weight,attr"`
31 | TextCode struct {
32 | Text string `xml:",chardata"`
33 | X string `xml:"X,attr"`
34 | Y string `xml:"Y,attr"`
35 | DeltaX string `xml:"DeltaX,attr"`
36 | } `xml:"TextCode"`
37 | } `xml:"TextObject"`
38 | PathObject []struct {
39 | Text string `xml:",chardata"`
40 | ID string `xml:"ID,attr"`
41 | Boundary string `xml:"Boundary,attr"`
42 | LineWidth string `xml:"LineWidth,attr"`
43 | AbbreviatedData struct {
44 | Text string `xml:",chardata"`
45 | } `xml:"AbbreviatedData"`
46 | } `xml:"PathObject"`
47 | ImageObject []struct {
48 | Text string `xml:",chardata"`
49 | ID string `xml:"ID,attr"`
50 | Boundary string `xml:"Boundary,attr"`
51 | CTM string `xml:"CTM,attr"`
52 | ResourceID string `xml:"ResourceID,attr"`
53 | } `xml:"ImageObject"`
54 | } `xml:"Layer"`
55 | } `xml:"Content"`
56 | }
57 |
--------------------------------------------------------------------------------
/content_info.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | std "encoding/asn1"
5 |
6 | "golang.org/x/crypto/cryptobyte"
7 | "golang.org/x/crypto/cryptobyte/asn1"
8 | )
9 |
10 | type ContentInfo struct {
11 | contentType std.ObjectIdentifier
12 | content cryptobyte.String
13 | }
14 |
15 | func NewContentInfo(input cryptobyte.String) (*ContentInfo, error) {
16 |
17 | var contentInfo cryptobyte.String
18 | input.ReadASN1(&contentInfo, asn1.SEQUENCE)
19 |
20 | var contentType std.ObjectIdentifier
21 | contentInfo.ReadASN1ObjectIdentifier(&contentType)
22 |
23 | var content cryptobyte.String
24 | var tag asn1.Tag
25 | contentInfo.ReadAnyASN1(&content, &tag)
26 | return &ContentInfo{
27 | contentType: contentType,
28 | content: content,
29 | }, nil
30 | }
31 |
32 | func (contentInfo *ContentInfo) GetContent() []byte {
33 | return contentInfo.content
34 | }
35 |
36 | type SignedData struct {
37 | version int64
38 | digestAlgorithms cryptobyte.String
39 | contentInfo cryptobyte.String
40 | certificates cryptobyte.String
41 | extendedCertificatesAndCertificates cryptobyte.String
42 | crls cryptobyte.String
43 | signerInfos cryptobyte.String
44 | }
45 |
46 | func NewSignedData(content cryptobyte.String) (*SignedData, error) {
47 |
48 | var signedData cryptobyte.String
49 | content.ReadASN1(&signedData, asn1.SEQUENCE)
50 | var version int64
51 | signedData.ReadASN1Integer(&version)
52 |
53 | var digestAlgorithms cryptobyte.String
54 | signedData.ReadASN1(&digestAlgorithms, asn1.SET)
55 |
56 | var contentInfo cryptobyte.String
57 | signedData.ReadASN1(&contentInfo, asn1.SEQUENCE)
58 |
59 | var certificates cryptobyte.String
60 | signedData.ReadASN1(&certificates, asn1.SEQUENCE)
61 |
62 | var extendedCertificatesAndCertificates cryptobyte.String
63 | var exit bool
64 | signedData.ReadOptionalASN1(&extendedCertificatesAndCertificates, &exit, asn1.SEQUENCE)
65 |
66 | var crls cryptobyte.String
67 | signedData.ReadOptionalASN1(&crls, &exit, asn1.SEQUENCE)
68 |
69 | var signerInfos cryptobyte.String
70 | signedData.ReadASN1(&signerInfos, asn1.SET)
71 |
72 | return &SignedData{
73 | version: version,
74 | digestAlgorithms: digestAlgorithms,
75 | contentInfo: content,
76 | certificates: certificates,
77 | extendedCertificatesAndCertificates: extendedCertificatesAndCertificates,
78 | crls: crls,
79 | signerInfos: signerInfos,
80 | }, nil
81 | }
82 | func (signedData *SignedData) GetCertificates() []byte {
83 | return signedData.certificates
84 | }
85 | func (signedData *SignedData) GetSignerInfos() ([]SignerInfo, error) {
86 | var signerInfoSet []SignerInfo
87 | signerInfos := signedData.signerInfos
88 |
89 | exist := true
90 | for exist {
91 | var signerInfo cryptobyte.String
92 | exist = signerInfos.ReadASN1(&signerInfo, asn1.SEQUENCE)
93 | if exist {
94 | item, err := NewSignerInfo(signerInfo)
95 | if err != nil {
96 | break
97 | }
98 | signerInfoSet = append(signerInfoSet, *item)
99 | }
100 | }
101 | return signerInfoSet, nil
102 | }
103 |
104 | type SignerInfo struct {
105 | version int64
106 | issuerAndSerialNumber cryptobyte.String
107 | digestAlgorithm cryptobyte.String
108 | authenticatedAttributes cryptobyte.String
109 | digestEncryptionAlgorithm cryptobyte.String
110 | encryptedDigest cryptobyte.String
111 | unauthenticatedAttributes cryptobyte.String
112 | }
113 |
114 | func NewSignerInfo(signInfo cryptobyte.String) (*SignerInfo, error) {
115 | var version int64
116 | signInfo.ReadASN1Integer(&version)
117 | var issuerAndSerialNumber cryptobyte.String
118 | signInfo.ReadASN1(&issuerAndSerialNumber, asn1.SEQUENCE)
119 | var digestAlgorithm cryptobyte.String
120 | signInfo.ReadASN1(&digestAlgorithm, asn1.SEQUENCE)
121 | var authenticatedAttributes cryptobyte.String
122 | var tag asn1.Tag
123 | signInfo.ReadAnyASN1Element(&authenticatedAttributes, &tag)
124 |
125 | var digestEncryptionAlgorithm cryptobyte.String
126 | signInfo.ReadASN1(&digestEncryptionAlgorithm, asn1.SEQUENCE)
127 |
128 | var encryptedDigest cryptobyte.String
129 | signInfo.ReadASN1(&encryptedDigest, asn1.SEQUENCE)
130 |
131 | var unauthenticatedAttributes cryptobyte.String
132 | var exit bool
133 | signInfo.ReadOptionalASN1(&unauthenticatedAttributes, &exit, asn1.SEQUENCE)
134 |
135 | return &SignerInfo{
136 | version: version,
137 | issuerAndSerialNumber: issuerAndSerialNumber,
138 | digestAlgorithm: digestAlgorithm,
139 | authenticatedAttributes: authenticatedAttributes,
140 | digestEncryptionAlgorithm: digestEncryptionAlgorithm,
141 | encryptedDigest: encryptedDigest,
142 | unauthenticatedAttributes: unauthenticatedAttributes,
143 | }, nil
144 | }
145 |
146 | func (signerInfo *SignerInfo) GetVersion() int64 {
147 | return signerInfo.version
148 | }
149 | func (signerInfo *SignerInfo) GetIssuerAndSerialNumber() cryptobyte.String {
150 | return signerInfo.issuerAndSerialNumber
151 | }
152 | func (signerInfo *SignerInfo) GetDigestAlgorithm() string {
153 | digestAlgorithm := signerInfo.digestAlgorithm
154 | var digestAlgorithmOid std.ObjectIdentifier
155 | digestAlgorithm.ReadASN1ObjectIdentifier(&digestAlgorithmOid)
156 | return digestAlgorithmOid.String()
157 | }
158 |
159 | func (signerInfo *SignerInfo) GetAuthenticatedAttributes() cryptobyte.String {
160 | authenticatedAttributes := signerInfo.authenticatedAttributes
161 | tag := authenticatedAttributes[0]
162 | if tag == 0xA0 {
163 | authenticatedAttributes[0] = 0x31
164 | }
165 | return authenticatedAttributes
166 | }
167 |
168 | func (signerInfo *SignerInfo) GetDigestEncryptionAlgorithm() string {
169 | digestEncryptionAlgorithm := signerInfo.digestEncryptionAlgorithm
170 | var digestEncryptionAlgorithmOid std.ObjectIdentifier
171 | digestEncryptionAlgorithm.ReadASN1ObjectIdentifier(&digestEncryptionAlgorithmOid)
172 | return digestEncryptionAlgorithmOid.String()
173 | }
174 |
175 | func (signerInfo *SignerInfo) GetEncryptedDigest() cryptobyte.String {
176 | return signerInfo.encryptedDigest
177 | }
178 |
179 | func (signerInfo *SignerInfo) GetUnauthenticatedAttributes() cryptobyte.String {
180 | unauthenticatedAttributes := signerInfo.unauthenticatedAttributes
181 | tag := unauthenticatedAttributes[0]
182 | if tag == 0xA1 {
183 | unauthenticatedAttributes[0] = 0x31
184 | }
185 | return unauthenticatedAttributes
186 | }
187 |
--------------------------------------------------------------------------------
/custom_tags.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "archive/zip"
5 | "encoding/xml"
6 | )
7 |
8 | type CustomTags struct {
9 | CustomTagsXml
10 | pwd string
11 | rc *zip.ReadCloser
12 | }
13 |
14 | type CustomTagsXml struct {
15 | XMLName xml.Name `xml:"CustomTags"`
16 | Text string `xml:",chardata"`
17 | Ofd string `xml:"ofd,attr"`
18 | CustomTag struct {
19 | Text string `xml:",chardata"`
20 | TypeID string `xml:"TypeID,attr"`
21 | FileLoc struct {
22 | Text string `xml:",chardata"`
23 | } `xml:"FileLoc"`
24 | } `xml:"CustomTag"`
25 | }
26 | func (attachments *CustomTags) GetFileContent(path string) ([]byte, error) {
27 | return LoadZipFileContent(attachments.rc, path)
28 |
29 | }
--------------------------------------------------------------------------------
/document.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "archive/zip"
5 | "encoding/xml"
6 | "fmt"
7 | "strings"
8 | )
9 |
10 | type DocumentXml struct {
11 | XMLName xml.Name `xml:"Document"`
12 | Text string `xml:",chardata"`
13 | Ofd string `xml:"ofd,attr"`
14 | CommonData CommonData `xml:"CommonData"`
15 | Pages Pages `xml:"Pages"`
16 | Attachments struct {
17 | Text string `xml:",chardata"`
18 | } `xml:"Attachments"`
19 | CustomTags struct {
20 | Text string `xml:",chardata"`
21 | } `xml:"CustomTags"`
22 | }
23 | type Document struct {
24 | DocumentXml
25 | pwd string
26 | rc *zip.ReadCloser
27 | }
28 |
29 | func (doc *Document) GetCommonData() (*CommonData, error) {
30 | commonData := doc.CommonData
31 | commonData.rc = doc.rc
32 | commonData.pwd = doc.pwd
33 | return &commonData, nil
34 | }
35 | func (doc *Document) GetPages() (*Pages, error) {
36 | pages := doc.Pages
37 | pages.rc = doc.rc
38 | pages.pwd = doc.pwd
39 | return &pages, nil
40 | }
41 |
42 | func (doc *Document) GetAttachments() (*Attachments, error) {
43 | res, err := doc.getResource(ATTACHMENTS)
44 | if err != nil {
45 | return nil, err
46 | }
47 | return res.(*Attachments), nil
48 | }
49 |
50 | func (doc *Document) GetCustomTags() (*CustomTags, error) {
51 | res, err := doc.getResource(CUSTOMTAGS)
52 | if err != nil {
53 | return nil, err
54 | }
55 | return res.(*CustomTags), nil
56 | }
57 |
58 | type DocResource int
59 |
60 | const (
61 | ATTACHMENTS DocResource = iota
62 | CUSTOMTAGS
63 | )
64 |
65 | func (document *Document) GetFileContent(path string) ([]byte, error) {
66 | return LoadZipFileContent(document.rc, path)
67 | }
68 | func (document *Document) getResource(resType DocResource) (interface{}, error) {
69 | var path string
70 | switch resType {
71 | case ATTACHMENTS:
72 | path = document.Attachments.Text
73 | case CUSTOMTAGS:
74 | path = document.CustomTags.Text
75 | default:
76 | return nil, fmt.Errorf("resType[%v] is invalid", resType)
77 | }
78 | if strings.EqualFold(path, "") {
79 | return nil, fmt.Errorf("Resource is null")
80 | }
81 | pos := strings.LastIndexByte(path, '/')
82 | pwd := path[0:pos]
83 | content, err := document.GetFileContent(path)
84 | if err != nil {
85 | if path[0] == '/' {
86 | path = document.pwd + path
87 | } else {
88 | path = document.pwd + "/" + path
89 | }
90 |
91 | pos := strings.LastIndexByte(path, '/')
92 | pwd = path[0:pos]
93 | content, err = document.GetFileContent(path)
94 | if err != nil {
95 | return nil, err
96 | }
97 | }
98 |
99 | switch resType {
100 | case ATTACHMENTS:
101 | var attachments Attachments
102 | if err := xml.Unmarshal(content, &attachments); err != nil {
103 | return nil, err
104 | } else {
105 |
106 | attachments.pwd = pwd
107 | attachments.rc = document.rc
108 | }
109 | return &attachments, nil
110 | case CUSTOMTAGS:
111 | var customTag CustomTags
112 | if err := xml.Unmarshal(content, &customTag); err != nil {
113 | return nil, err
114 | } else {
115 | customTag.pwd = pwd
116 | customTag.rc = document.rc
117 | }
118 | return &customTag, nil
119 | default:
120 | return nil, fmt.Errorf("resType[%v] is invalid", resType)
121 |
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/document_res.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "archive/zip"
5 | "encoding/xml"
6 | )
7 |
8 | type DocumentRes struct {
9 | DocumentResXml
10 | pwd string
11 | rc *zip.ReadCloser
12 | }
13 | type DocumentResXml struct {
14 | XMLName xml.Name `xml:"Res"`
15 | Text string `xml:",chardata"`
16 | Ofd string `xml:"ofd,attr"`
17 | BaseLoc string `xml:"BaseLoc,attr"`
18 | MultiMedias struct {
19 | Text string `xml:",chardata"`
20 | MultiMedia []struct {
21 | Text string `xml:",chardata"`
22 | ID string `xml:"ID,attr"`
23 | Type string `xml:"Type,attr"`
24 | MediaFile struct {
25 | Text string `xml:",chardata"`
26 | } `xml:"MediaFile"`
27 | } `xml:"MultiMedia"`
28 | } `xml:"MultiMedias"`
29 | }
30 |
31 | func (docRes *DocumentRes) GetFileContent(path string) ([]byte, error) {
32 | return LoadZipFileContent(docRes.rc, path)
33 | }
34 |
--------------------------------------------------------------------------------
/document_res_test.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "encoding/xml"
5 | "testing"
6 | )
7 |
8 | const doc_res_content = `
9 |
10 |
12 |
13 |
14 | Image_0.png
15 |
16 |
17 | Image_1.png
18 |
19 |
20 | `
21 |
22 | func Test_DocumentRes(t *testing.T) {
23 | var res DocumentRes
24 | if err := xml.Unmarshal([]byte(doc_res_content), &res); err != nil {
25 | t.Logf("%s", err)
26 | } else {
27 | t.Logf("%v", res)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/document_test.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "encoding/xml"
5 | "os"
6 | "path/filepath"
7 | "testing"
8 | )
9 |
10 | const document_content = `
11 |
12 |
14 |
15 | 66
16 |
17 | 0 0 210 297
18 |
19 | PublicRes_0.xml
20 | DocumentRes_0.xml
21 |
22 |
23 |
24 |
25 |
26 | Attachs/Attachments.xml
27 | Tags/CustomTags.xml
28 |
29 | `
30 |
31 | func TestDocumentXml(t *testing.T) {
32 | var doc Document
33 | if err := xml.Unmarshal([]byte(document_content), &doc); err != nil {
34 | t.Logf("%v", err)
35 | } else {
36 | t.Logf("%v", doc)
37 | }
38 | }
39 | func TestDocument(t *testing.T) {
40 | pwd, _ := os.Getwd()
41 | file := filepath.Join(pwd, "samples", "DZHD_1605281110201000001_202205250016050102202100000069141950_20220525_000413.ofd")
42 | ofdReader, err := NewOFDReader(file)
43 | if err != nil {
44 | t.Logf("%s", err)
45 | }
46 | defer ofdReader.Close()
47 |
48 | ofd, err := ofdReader.OFD()
49 | if err != nil {
50 | t.Logf("%v", err)
51 | } else {
52 | t.Logf("%v", ofd)
53 | }
54 |
55 | index_0 := ofd.DocBody[0].DocInfo.DocID.Text
56 | doc, err := ofdReader.GetDocumentById(index_0)
57 | if err != nil {
58 | t.Logf("%v", err)
59 | } else {
60 | t.Logf("%v", doc)
61 | }
62 | commonData, err := doc.GetCommonData()
63 | if err != nil {
64 | t.Logf("%v", err)
65 | } else {
66 | t.Logf("%v", commonData)
67 | }
68 |
69 | pages, err := doc.GetPages()
70 | if err != nil {
71 | t.Logf("%v", err)
72 | } else {
73 | t.Logf("%v", pages)
74 | }
75 |
76 | attachments, err := doc.GetAttachments()
77 | if err != nil {
78 | t.Logf("%v", err)
79 | } else {
80 | t.Logf("%v", attachments)
81 | }
82 |
83 | ct, err := doc.GetCustomTags()
84 | if err != nil {
85 | t.Logf("%v", err)
86 | } else {
87 | t.Logf("%v", ct)
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/itlabers/ofd-go
2 |
3 | go 1.17
4 |
5 | require (
6 | github.com/itlabers/crypto v1.0.2-0.20210519065553-86f4e4883143
7 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
8 | )
9 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/itlabers/crypto v1.0.2-0.20210519065553-86f4e4883143 h1:kddMWVSosTURjTTzJgiKKGvhPUhCPLArv4G4ixe2vYQ=
2 | github.com/itlabers/crypto v1.0.2-0.20210519065553-86f4e4883143/go.mod h1:UNoUMsPVFm+nYzzbtRY+QWm0MSXzSdldZdmDE0LdQy8=
3 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
4 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
5 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
6 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
7 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
8 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
9 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
10 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
11 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
12 |
--------------------------------------------------------------------------------
/ofd.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import "encoding/xml"
4 |
5 | type OFD struct {
6 | XMLName xml.Name `xml:"OFD"`
7 | Text string `xml:",chardata"`
8 | Ofd string `xml:"ofd,attr"`
9 | Version string `xml:"Version,attr"`
10 | DocBody []struct {
11 | Text string `xml:",chardata"`
12 | DocInfo struct {
13 | Text string `xml:",chardata"`
14 | DocID struct {
15 | Text string `xml:",chardata"`
16 | } `xml:"DocID"`
17 | } `xml:"DocInfo"`
18 | DocRoot struct {
19 | Text string `xml:",chardata"`
20 | } `xml:"DocRoot"`
21 | Signatures struct {
22 | Text string `xml:",chardata"`
23 | } `xml:"Signatures"`
24 | } `xml:"DocBody"`
25 | }
26 |
--------------------------------------------------------------------------------
/ofd_reader.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "archive/zip"
5 | "encoding/xml"
6 | "errors"
7 | "fmt"
8 | "strings"
9 | )
10 |
11 | const (
12 | OFD_PATH = "OFD.xml"
13 | )
14 |
15 | type OFDReader struct {
16 | validator Validator
17 | filepath string
18 | rc *zip.ReadCloser
19 | ofd *OFD
20 | }
21 |
22 | type Option func(opt *Options)
23 |
24 | type Options struct {
25 | validator Validator
26 | }
27 |
28 | var defaultOption = Options{
29 | validator: &CommonValidator{},
30 | }
31 |
32 | func WithValidator(validator Validator) Option {
33 | return func(opt *Options) {
34 | opt.validator = validator
35 | }
36 | }
37 |
38 | func NewOFDReader(path string, opts ...Option) (*OFDReader, error) {
39 | opt := defaultOption
40 | for _, o := range opts {
41 | o(&opt)
42 | }
43 | rc, err := zip.OpenReader(path)
44 | if err != nil {
45 | return nil, err
46 | }
47 | return &OFDReader{
48 | rc: rc,
49 | validator: opt.validator,
50 | filepath: path,
51 | }, nil
52 | }
53 | func (ofdReader *OFDReader) Close() error {
54 | if ofdReader.rc != nil {
55 | return ofdReader.rc.Close()
56 | }
57 | return nil
58 | }
59 |
60 | func (ofdReader *OFDReader) OFD() (*OFD, error) {
61 | if ofdReader.ofd == nil {
62 | content, err := ofdReader.GetFileContent(OFD_PATH)
63 | if err != nil {
64 | return nil, err
65 | }
66 | var ofd OFD
67 | if err := xml.Unmarshal(content, &ofd); err != nil {
68 | return nil, err
69 | } else {
70 | ofdReader.ofd = &ofd
71 | return &ofd, nil
72 | }
73 | } else {
74 | return ofdReader.ofd, nil
75 | }
76 | }
77 |
78 | type Resource int
79 |
80 | const (
81 | DOCUMENT Resource = iota
82 | SIGNATURES
83 | )
84 |
85 | func (ofdReader *OFDReader) getResourceById(docId string, resType Resource) (interface{}, error) {
86 | if docId == "" {
87 | return nil, errors.New("docId is empty")
88 | }
89 | ofd, err := ofdReader.OFD()
90 | if err != nil {
91 | return nil, err
92 | }
93 | docs := ofd.DocBody
94 | var path string
95 | for _, doc := range docs {
96 | if !strings.EqualFold(doc.DocInfo.DocID.Text, docId) {
97 | continue
98 | }
99 | switch resType {
100 | case DOCUMENT:
101 | path = doc.DocRoot.Text
102 | case SIGNATURES:
103 | path = doc.Signatures.Text
104 | default:
105 | return nil, fmt.Errorf("resType[%v] is invalid", resType)
106 | }
107 | if path[0] == '/' {
108 | path = path[1:]
109 | }
110 | pos := strings.LastIndexByte(path, '/')
111 | pwd := path[0:pos]
112 | content, err := ofdReader.GetFileContent(path)
113 | if err != nil {
114 | return nil, err
115 | }
116 | switch resType {
117 | case DOCUMENT:
118 | var doc Document
119 | if err := xml.Unmarshal(content, &doc); err != nil {
120 | return nil, err
121 | } else {
122 | doc.pwd = pwd
123 | doc.rc = ofdReader.rc
124 | }
125 | return &doc, nil
126 | case SIGNATURES:
127 | var signatures Signatures
128 | if err := xml.Unmarshal(content, &signatures); err != nil {
129 | return nil, err
130 | } else {
131 | signatures.pwd = pwd
132 | signatures.rc = ofdReader.rc
133 | }
134 | return &signatures, nil
135 | default:
136 | return nil, fmt.Errorf("resType[%v] is invalid", resType)
137 |
138 | }
139 | }
140 | return nil, fmt.Errorf("docId [%v] not found", docId)
141 | }
142 | func (ofdReader *OFDReader) GetDocumentById(docId string) (*Document, error) {
143 | res, err := ofdReader.getResourceById(docId, DOCUMENT)
144 | if err != nil {
145 | return nil, err
146 | }
147 | return res.(*Document), nil
148 | }
149 |
150 | func (ofdReader *OFDReader) GetSignaturesById(docId string) (*Signatures, error) {
151 | res, err := ofdReader.getResourceById(docId, SIGNATURES)
152 | if err != nil {
153 | return nil, err
154 | }
155 | signatures := res.(*Signatures)
156 | signatures.Validator = ofdReader.validator
157 | return signatures, nil
158 | }
159 | func (ofdReader *OFDReader) GetFileContent(path string) ([]byte, error) {
160 | return LoadZipFileContent(ofdReader.rc, path)
161 | }
162 |
--------------------------------------------------------------------------------
/ofd_reader_test.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "testing"
7 | )
8 |
9 | func TestNewOFDReader(t *testing.T) {
10 | pwd, _ := os.Getwd()
11 | // 替换成目标文件
12 | file := filepath.Join(pwd, "sample.ofd")
13 |
14 | ofdReader, err := NewOFDReader(file)
15 | if err != nil {
16 | t.Logf("%s", err)
17 | }
18 | defer ofdReader.Close()
19 |
20 | ofd, err := ofdReader.OFD()
21 | if err != nil {
22 | t.Logf("%v", err)
23 | } else {
24 | t.Logf("%v", ofd)
25 | }
26 |
27 | doc_0 := ofd.DocBody[0].DocInfo.DocID.Text
28 | doc, err := ofdReader.GetDocumentById(doc_0)
29 | if err != nil {
30 | t.Logf("%v", err)
31 | } else {
32 | t.Logf("%v", doc)
33 | }
34 |
35 | if attachments, err := doc.GetAttachments(); err != nil {
36 | t.Logf("%v", err)
37 | } else {
38 | t.Logf("%v", attachments)
39 | }
40 |
41 | signs, err := ofdReader.GetSignaturesById(doc_0)
42 | if err != nil {
43 | t.Logf("%v", err)
44 | } else {
45 | t.Logf("%v", signs)
46 | }
47 |
48 | sign_1 := signs.Signature[0].ID
49 | sign, err := signs.GetSignatureById(sign_1)
50 | if err != nil {
51 | t.Logf("%v", err)
52 | } else {
53 | t.Logf("%v", sign)
54 | }
55 | if result, err := sign.VerifyDigest(); err != nil {
56 | t.Logf("%v", err)
57 | } else {
58 | t.Logf("%v", result)
59 | }
60 | if result, err := sign.Verify(); err != nil {
61 | t.Logf("%v", err)
62 | } else {
63 | t.Logf("%v", result)
64 | }
65 |
66 | }
67 |
68 | func TestBatch(t *testing.T) {
69 | pwd, _ := os.Getwd()
70 | testcases := []struct {
71 | name string
72 | path string
73 | wanted bool
74 | }{
75 | // Add test cases.
76 | {"光大", filepath.Join(pwd, "samples", "2020062290131000005100000000013540122236009.ofd"), true},
77 | {"工行", filepath.Join(pwd, "samples", "0200216819200056225_001_21253000001_20220101_acc.ofd"), true},
78 | {"中电财", filepath.Join(pwd, "samples", "DZHD_1605281110201000001_202205250016050102202100000069141950_20220525_000413.ofd"), true},
79 | {"浙商银行", filepath.Join(pwd, "samples", "bkrs_issuer_20220309_C1030311000455.ofd"), true},
80 | {"中国铁路-退票", filepath.Join(pwd, "samples", "退票样例.ofd"), true},
81 | {"中国铁路-售票", filepath.Join(pwd, "samples", "售票换开样例.ofd"), true},
82 | }
83 | for _, test := range testcases {
84 | t.Run(test.name, func(t *testing.T) {
85 | ofdReader, err := NewOFDReader(test.path, WithValidator(&CommonValidator{}))
86 | if err != nil {
87 | t.Logf("%s", err)
88 | }
89 | defer ofdReader.Close()
90 |
91 | ofd, err := ofdReader.OFD()
92 | if err != nil {
93 | t.Logf("%v", err)
94 | } else {
95 | t.Logf("%v", ofd)
96 | }
97 |
98 | for _, doc := range ofd.DocBody {
99 | doc_0 := doc.DocInfo.DocID.Text
100 | doc, err := ofdReader.GetDocumentById(doc_0)
101 | if err != nil {
102 | t.Logf("%v", err)
103 | } else {
104 | t.Logf("%v", doc)
105 | }
106 |
107 | signs, err := ofdReader.GetSignaturesById(doc_0)
108 | if err != nil {
109 | t.Logf("%v", err)
110 | }
111 |
112 | sign_1 := signs.Signature[0].ID
113 | sign, _ := signs.GetSignatureById(sign_1)
114 | if err != nil {
115 | t.Logf("%v", err)
116 | }
117 | if result, err := sign.VerifyDigest(); err != nil {
118 | t.Logf("%v", err)
119 | } else {
120 | t.Logf("%v", result)
121 | }
122 | if result, err := sign.Verify(); err != nil {
123 | t.Logf("%v", err)
124 | } else {
125 | t.Logf("%v", result)
126 | }
127 | }
128 |
129 | })
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/ofd_test.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "encoding/xml"
5 |
6 | "testing"
7 | )
8 |
9 | const ofd_content = `
10 |
11 |
13 |
14 |
15 | 8132e5b821b249d8bde7a18642cca902
16 |
17 | Doc_0/Document.xml
18 | Doc_0/Signs/Signatures.xml
19 |
20 |
21 | `
22 |
23 | func TestOFD(t *testing.T) {
24 | var ofd OFD
25 | if err := xml.Unmarshal([]byte(ofd_content), &ofd); err != nil {
26 | t.Logf("%v", err)
27 | } else {
28 | t.Logf("%v", ofd)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/pages.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "archive/zip"
5 | "encoding/xml"
6 | "fmt"
7 | "strings"
8 | )
9 |
10 | type Pages struct {
11 | PagesXml
12 | pwd string
13 | rc *zip.ReadCloser
14 | }
15 | type PagesXml struct {
16 | Text string `xml:",chardata"`
17 | Page []struct {
18 | Text string `xml:",chardata"`
19 | ID string `xml:"ID,attr"`
20 | BaseLoc string `xml:"BaseLoc,attr"`
21 | } `xml:"Page"`
22 | }
23 |
24 | func (pages *Pages) GetFileContent(path string) ([]byte, error) {
25 | return LoadZipFileContent(pages.rc, path)
26 | }
27 | func (pages *Pages) GetPageById(pageId string) (*Page, error) {
28 | if strings.EqualFold(pageId, "") {
29 | return nil, fmt.Errorf("pageId is empty")
30 | }
31 | for _, item := range pages.Page {
32 | if !strings.EqualFold(item.ID, pageId) {
33 | continue
34 | }
35 | path := item.BaseLoc
36 | pos := strings.LastIndexByte(path, '/')
37 | pwd := path[0:pos]
38 | content, err := pages.GetFileContent(path)
39 | if err != nil {
40 | path = pages.pwd + "/" + item.BaseLoc
41 | pos := strings.LastIndexByte(path, '/')
42 | pwd = path[0:pos]
43 | content, err = pages.GetFileContent(path)
44 | if err != nil {
45 | return nil, err
46 | }
47 | }
48 | var page Page
49 | if err := xml.Unmarshal(content, &page); err != nil {
50 | return nil, err
51 | } else {
52 | page.pwd = pwd
53 | return &page, nil
54 | }
55 | }
56 | return nil, fmt.Errorf("pageId [%v] is invalid", pageId)
57 |
58 | }
59 |
60 | func (pages *Pages) GetPages() ([]Page, error) {
61 | var items []Page
62 | for _, item := range pages.Page {
63 | path := item.BaseLoc
64 | pos := strings.LastIndexByte(path, '/')
65 | pwd := path[0:pos]
66 | content, err := pages.GetFileContent(path)
67 | if err != nil {
68 | path = pages.pwd + "/" + item.BaseLoc
69 | pos := strings.LastIndexByte(path, '/')
70 | pwd = path[0:pos]
71 | content, err = pages.GetFileContent(path)
72 | if err != nil {
73 | return nil, err
74 | }
75 | }
76 |
77 | var page Page
78 | if err := xml.Unmarshal(content, &page); err != nil {
79 | return nil, err
80 | } else {
81 | page.pwd = pwd
82 | }
83 | items = append(items, page)
84 | }
85 | return items, nil
86 | }
87 |
--------------------------------------------------------------------------------
/pages_test.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "testing"
7 | )
8 |
9 | func TestPages(t *testing.T) {
10 | pwd, _ := os.Getwd()
11 | file := filepath.Join(pwd, "samples", "DZHD_1605281110201000001_202205250016050102202100000069141950_20220525_000413.ofd")
12 | ofdReader, err := NewOFDReader(file)
13 | if err != nil {
14 | t.Logf("%s", err)
15 | }
16 | defer ofdReader.Close()
17 |
18 | ofd, err := ofdReader.OFD()
19 | if err != nil {
20 | t.Logf("%v", err)
21 | } else {
22 | t.Logf("%v", ofd)
23 | }
24 |
25 | index_0 := ofd.DocBody[0].DocInfo.DocID.Text
26 | doc, err := ofdReader.GetDocumentById(index_0)
27 | if err != nil {
28 | t.Logf("%v", err)
29 | } else {
30 | t.Logf("%v", doc)
31 | }
32 | commonData, err := doc.GetCommonData()
33 | if err != nil {
34 | t.Logf("%v", err)
35 | } else {
36 | t.Logf("%v", commonData)
37 | }
38 |
39 | pages, err := doc.GetPages()
40 | if err != nil {
41 | t.Logf("%v", err)
42 | } else {
43 | t.Logf("%v", pages)
44 | }
45 | allPages, err := pages.GetPages()
46 | if err != nil {
47 | t.Logf("%v", err)
48 | } else {
49 | t.Logf("%v", allPages)
50 | }
51 |
52 | page_0 := pages.Page[0].ID
53 | page, err := pages.GetPageById(page_0)
54 | if err != nil {
55 | t.Logf("%v", err)
56 | } else {
57 | t.Logf("%v", page)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/public_res.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "archive/zip"
5 | "encoding/xml"
6 | )
7 |
8 | type PublicRes struct {
9 | PublicResXml
10 | pwd string
11 | rc *zip.ReadCloser
12 | }
13 | type PublicResXml struct {
14 | XMLName xml.Name `xml:"Res"`
15 | Text string `xml:",chardata"`
16 | Ofd string `xml:"ofd,attr"`
17 | BaseLoc string `xml:"BaseLoc,attr"`
18 | Fonts struct {
19 | Text string `xml:",chardata"`
20 | Font []struct {
21 | Text string `xml:",chardata"`
22 | ID string `xml:"ID,attr"`
23 | FontName string `xml:"FontName,attr"`
24 | Bold string `xml:"Bold,attr"`
25 | } `xml:"Font"`
26 | } `xml:"Fonts"`
27 | }
28 |
29 | func (publicRes *PublicRes) GetFileContent(path string) ([]byte, error) {
30 | return LoadZipFileContent(publicRes.rc, path)
31 | }
32 |
--------------------------------------------------------------------------------
/public_res_test.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "encoding/xml"
5 | "testing"
6 | )
7 |
8 | const public_res_content = `
9 |
10 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | `
19 |
20 | func Test_Res(t *testing.T) {
21 | var res PublicRes
22 | if err := xml.Unmarshal([]byte(public_res_content), &res); err != nil {
23 | t.Logf("%s", err)
24 | } else {
25 | t.Logf("%v", res)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ses_seal.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "errors"
5 | "time"
6 |
7 | std "encoding/asn1"
8 |
9 | "golang.org/x/crypto/cryptobyte"
10 | "golang.org/x/crypto/cryptobyte/asn1"
11 | )
12 |
13 | type SES_Signature struct {
14 | to_sign cryptobyte.String
15 | cert []byte
16 | signatureAlgId std.ObjectIdentifier
17 | signature std.BitString
18 | timestamp std.BitString
19 | }
20 |
21 | func New_SES_Signature(input cryptobyte.String) (*SES_Signature, error) {
22 | var ses_signature cryptobyte.String
23 | input.ReadASN1(&ses_signature, asn1.SEQUENCE)
24 |
25 | var to_sign cryptobyte.String
26 | ses_signature.ReadASN1Element(&to_sign, asn1.SEQUENCE)
27 |
28 | var cert cryptobyte.String
29 | ses_signature.ReadASN1(&cert, asn1.OCTET_STRING)
30 |
31 | var signatureAlgId std.ObjectIdentifier
32 | ses_signature.ReadASN1ObjectIdentifier(&signatureAlgId)
33 |
34 | var signature std.BitString
35 | ses_signature.ReadASN1BitString(&signature)
36 |
37 | var signTime std.BitString
38 | ses_signature.ReadASN1BitString(&signTime)
39 |
40 | return &SES_Signature{
41 | to_sign: to_sign,
42 | cert: cert,
43 | signatureAlgId: signatureAlgId,
44 | signature: signature,
45 | timestamp: signTime,
46 | }, nil
47 | }
48 |
49 | func (ses *SES_Signature) Get_TBS_Sign() (*TBS_Sign, error) {
50 | to_sign, err := New_TBS_Sign(ses.to_sign)
51 | if err != nil {
52 | return nil, err
53 | }
54 | return to_sign, nil
55 | }
56 |
57 | func (ses *SES_Signature) Get_Cert() ([]byte, error) {
58 | return ses.cert, nil
59 | }
60 |
61 | func (ses *SES_Signature) Get_SignatureAlgId() string {
62 | return ses.signatureAlgId.String()
63 | }
64 |
65 | func (ses *SES_Signature) Get_Signature() []byte {
66 | return ses.signature.Bytes
67 |
68 | }
69 |
70 | type TBS_Sign struct {
71 | version int64
72 | eSeal cryptobyte.String // SESeal
73 | timeInfo time.Time // timeInfo
74 | dataHash std.BitString // dataHash
75 | propertyInfo string // propertyInfo
76 | extDatas cryptobyte.String // ExtensionDatas
77 | }
78 |
79 | func (tbs *TBS_Sign) Get_Version() int64 {
80 | return tbs.version
81 | }
82 | func (tbs *TBS_Sign) Get_DataHash() []byte {
83 | return tbs.dataHash.Bytes
84 | }
85 | func (tbs *TBS_Sign) Get_Seal() (*SESeal, error) {
86 | eSeal, err := New_SESeal(tbs.eSeal)
87 | if err != nil {
88 | return nil, err
89 | }
90 | return eSeal, nil
91 | }
92 |
93 | type ExtensionDatas struct {
94 | ExtnId std.ObjectIdentifier
95 | Critical bool
96 | ExtnValue []byte
97 | }
98 |
99 | func New_TBS_Sign(input cryptobyte.String) (*TBS_Sign, error) {
100 | var tbs_sign cryptobyte.String
101 | input.ReadASN1(&tbs_sign, asn1.SEQUENCE)
102 |
103 | var version int64
104 | tbs_sign.ReadASN1Int64WithTag(&version, asn1.INTEGER)
105 |
106 | var eSeal cryptobyte.String
107 | tbs_sign.ReadASN1(&eSeal, asn1.SEQUENCE)
108 |
109 | var timeInfo time.Time
110 | tbs_sign.ReadASN1GeneralizedTime(&timeInfo)
111 |
112 | var dataHash std.BitString
113 | tbs_sign.ReadASN1BitString(&dataHash)
114 |
115 | var propertyInfo cryptobyte.String
116 | tbs_sign.ReadASN1(&propertyInfo, asn1.IA5String)
117 |
118 | var extDatas cryptobyte.String
119 | tbs_sign.ReadASN1(&extDatas, asn1.SEQUENCE)
120 |
121 | return &TBS_Sign{
122 | version: version,
123 | eSeal: eSeal,
124 | timeInfo: timeInfo,
125 | dataHash: dataHash,
126 | propertyInfo: string(propertyInfo),
127 | extDatas: extDatas,
128 | }, nil
129 | }
130 |
131 | type SESeal struct {
132 | eSealInfo cryptobyte.String //SES_Seal_Info
133 | cert []byte //cert
134 | signatureAlgId std.ObjectIdentifier //signatureAlgId
135 | signature std.BitString //signature
136 | }
137 |
138 | func New_SESeal(seSeal cryptobyte.String) (*SESeal, error) {
139 | var eSealInfo cryptobyte.String
140 | seSeal.ReadASN1Element(&eSealInfo, asn1.SEQUENCE)
141 |
142 | var cert cryptobyte.String
143 | seSeal.ReadASN1(&cert, asn1.OCTET_STRING)
144 |
145 | var signatureAlgId std.ObjectIdentifier
146 | seSeal.ReadASN1ObjectIdentifier(&signatureAlgId)
147 |
148 | var signature std.BitString
149 | seSeal.ReadASN1BitString(&signature)
150 |
151 | return &SESeal{
152 | eSealInfo: eSealInfo,
153 | cert: cert,
154 | signatureAlgId: signatureAlgId,
155 | signature: signature,
156 | }, nil
157 | }
158 |
159 | func (seal *SESeal) Get_SES_SealInfo() (*SES_Seal_Info, error) {
160 | eSeal, err := New_SES_Seal_Info(seal.eSealInfo)
161 | if err != nil {
162 | return nil, err
163 | }
164 | return eSeal, nil
165 | }
166 | func (seal *SESeal) Get_Cert() ([]byte, error) {
167 | return seal.cert, nil
168 | }
169 |
170 | func (seal *SESeal) Get_SignatureAlgId() string {
171 | return seal.signatureAlgId.String()
172 | }
173 |
174 | func (seal *SESeal) Get_Signature() []byte {
175 | return seal.signature.Bytes
176 |
177 | }
178 |
179 | func New_SES_Seal_Info(input cryptobyte.String) (*SES_Seal_Info, error) {
180 | var eSealInfo cryptobyte.String
181 | input.ReadASN1(&eSealInfo, asn1.SEQUENCE)
182 | var header cryptobyte.String
183 | eSealInfo.ReadASN1(&header, asn1.SEQUENCE)
184 | var esID cryptobyte.String
185 | eSealInfo.ReadASN1(&esID, asn1.IA5String)
186 |
187 | var property cryptobyte.String
188 | eSealInfo.ReadASN1(&property, asn1.SEQUENCE)
189 |
190 | var pictrue cryptobyte.String
191 | eSealInfo.ReadASN1(&pictrue, asn1.SEQUENCE)
192 |
193 | var extDatas cryptobyte.String
194 | eSealInfo.ReadASN1(&extDatas, asn1.SEQUENCE)
195 |
196 | return &SES_Seal_Info{
197 | header: header,
198 | esID: string(esID),
199 | property: property,
200 | pictrue: pictrue,
201 | extDatas: extDatas,
202 | }, nil
203 | }
204 |
205 | type SES_Seal_Info struct {
206 | header cryptobyte.String //SES_Header
207 | esID string //esID
208 | property cryptobyte.String //SES_ESPropertyInfo
209 | pictrue cryptobyte.String //SES_ESPictrueInfo
210 | extDatas cryptobyte.String //ExtensionDatas
211 | }
212 |
213 | func (ses_seal_info *SES_Seal_Info) Get_ESID() string {
214 | return ses_seal_info.esID
215 | }
216 |
217 | func (ses_seal_info *SES_Seal_Info) Get_Header() (*SES_Header, error) {
218 | ses_hender, err := New_SES_Header(ses_seal_info.header)
219 | if err != nil {
220 | return nil, err
221 | }
222 | return ses_hender, nil
223 | }
224 | func (ses_seal_info *SES_Seal_Info) Get_Property() (*SES_ESPropertyInfo, error) {
225 | ses_property, err := New_SES_ESPropertyInfo(ses_seal_info.property)
226 | if err != nil {
227 | return nil, err
228 | }
229 | return ses_property, nil
230 | }
231 | func (ses_seal_info *SES_Seal_Info) Get_Pictrue() (*SES_ESPictrueInfo, error) {
232 | ses_picture, err := New_SES_ESPictrueInfo(ses_seal_info.pictrue)
233 | if err != nil {
234 | return nil, err
235 | }
236 | return ses_picture, nil
237 | }
238 | func (ses_seal_info *SES_Seal_Info) Get_ExtDatas() (*ExtensionDatas, error) {
239 | extDatas, err := New_ExtensionDatas(ses_seal_info.extDatas)
240 | if err != nil {
241 | return nil, err
242 | }
243 | return extDatas, nil
244 | }
245 |
246 | func New_ExtensionDatas(extDatas cryptobyte.String) (*ExtensionDatas, error) {
247 | var extnId std.ObjectIdentifier
248 | extDatas.ReadASN1ObjectIdentifier(&extnId)
249 |
250 | var critical bool
251 | extDatas.ReadASN1Boolean(&critical)
252 |
253 | var extnValue cryptobyte.String
254 |
255 | extDatas.ReadASN1(&extnValue, asn1.OCTET_STRING)
256 | return &ExtensionDatas{
257 | ExtnId: extnId,
258 | Critical: critical,
259 | ExtnValue: extnValue,
260 | }, nil
261 | }
262 |
263 | func New_SES_ESPropertyInfo(ses_property cryptobyte.String) (*SES_ESPropertyInfo, error) {
264 | var seal_type int64
265 | ses_property.ReadASN1Integer(&seal_type)
266 |
267 | var name cryptobyte.String
268 | ses_property.ReadASN1(&name, asn1.UTF8String)
269 |
270 | var certListType int64
271 | ses_property.ReadASN1Integer(&certListType)
272 |
273 | var certList cryptobyte.String
274 | ses_property.ReadASN1(&certList, asn1.SEQUENCE)
275 |
276 | var createDate time.Time
277 | ses_property.ReadASN1GeneralizedTime(&createDate)
278 |
279 | var validStart time.Time
280 | ses_property.ReadASN1GeneralizedTime(&validStart)
281 |
282 | var validEnd time.Time
283 | ses_property.ReadASN1GeneralizedTime(&validEnd)
284 |
285 | return &SES_ESPropertyInfo{
286 | Type: seal_type,
287 | Name: string(name),
288 | CertListType: certListType,
289 | CertList: certList,
290 | CreateDate: createDate,
291 | ValidStart: validStart,
292 | ValidEnd: validEnd,
293 | }, nil
294 | }
295 | func New_SES_ESPictrueInfo(pictrue cryptobyte.String) (*SES_ESPictrueInfo, error) {
296 | var pic_type cryptobyte.String
297 | pictrue.ReadASN1(&pic_type, asn1.IA5String)
298 |
299 | var data cryptobyte.String
300 | pictrue.ReadASN1(&data, asn1.OCTET_STRING)
301 |
302 | var width int64
303 | pictrue.ReadASN1Integer(&width)
304 |
305 | var height int64
306 | pictrue.ReadASN1Integer(&height)
307 |
308 | return &SES_ESPictrueInfo{
309 | Type: string(pic_type),
310 | Data: data,
311 | Width: width,
312 | Height: height,
313 | }, nil
314 | }
315 |
316 | type SES_Header struct {
317 | ID string //id
318 | Version int64 //version
319 | Vid string //vid
320 | }
321 |
322 | func New_SES_Header(ses_header cryptobyte.String) (*SES_Header, error) {
323 | var id cryptobyte.String
324 | ses_header.ReadASN1(&id, asn1.IA5String)
325 |
326 | var verison int64
327 | ses_header.ReadASN1Integer(&verison)
328 |
329 | var vID cryptobyte.String
330 | ses_header.ReadASN1(&vID, asn1.IA5String)
331 | return &SES_Header{
332 | ID: string(id),
333 | Version: verison,
334 | Vid: string(vID),
335 | }, nil
336 | }
337 |
338 | type SES_ESPropertyInfo struct {
339 | Type int64
340 | Name string
341 | CertListType int64
342 | CertList cryptobyte.String
343 | CreateDate time.Time
344 | ValidStart time.Time
345 | ValidEnd time.Time
346 | }
347 |
348 | func (ses_property_info *SES_ESPropertyInfo) Get_CertList() (*SES_CertList, error) {
349 | ses_certlist, err := New_SES_CertList(ses_property_info.CertListType, ses_property_info.CertList)
350 | if err != nil {
351 | return nil, err
352 | } else {
353 | return ses_certlist, nil
354 | }
355 |
356 | }
357 |
358 | type SES_CertList struct {
359 | certs cryptobyte.String
360 | certDigestList cryptobyte.String
361 | }
362 |
363 | func New_SES_CertList(cert_type int64, cert_list cryptobyte.String) (*SES_CertList, error) {
364 | if cert_type == 1 {
365 | var certs cryptobyte.String
366 | cert_list.ReadASN1(&certs, asn1.SEQUENCE)
367 | return &SES_CertList{
368 | certs: certs,
369 | certDigestList: nil,
370 | }, nil
371 | } else if cert_type == 2 {
372 | var certDigestList cryptobyte.String
373 | cert_list.ReadASN1(&certDigestList, asn1.SEQUENCE)
374 | return &SES_CertList{
375 | certs: nil,
376 | certDigestList: certDigestList,
377 | }, nil
378 | } else {
379 | return nil, errors.New("type is not valid")
380 | }
381 | }
382 |
383 | type Cert []byte
384 |
385 | type CertInfoList struct {
386 | Certs []Cert
387 | }
388 |
389 | func (ses_certlist *SES_CertList) Get_Certs() []byte {
390 | return ses_certlist.certs
391 | }
392 |
393 | func (ses_certlist *SES_CertList) Get_CertDigestList() []byte {
394 | return ses_certlist.certDigestList
395 | }
396 |
397 | type CertDigestList struct {
398 | CertDigestObjs []CertDigestObj
399 | }
400 |
401 | type ObjType string
402 | type CertDigestValue []byte
403 |
404 | type CertDigestObj struct {
405 | Type ObjType
406 | Value CertDigestValue
407 | }
408 |
409 | type SES_ESPictrueInfo struct {
410 | Type string
411 | Data []byte
412 | Width int64
413 | Height int64
414 | }
415 |
--------------------------------------------------------------------------------
/ses_seal_test.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "archive/zip"
5 | "bytes"
6 | _ "encoding/hex"
7 | "io"
8 | "os"
9 | "path/filepath"
10 | "strings"
11 | "testing"
12 | )
13 |
14 | func Test_New(t *testing.T) {
15 | pwd, _ := os.Getwd()
16 | testcases := []struct {
17 | name string // 来源
18 | path string // 文件路径
19 | wanted bool // 用bool方便判断是否返回error,如果类型改为error反而不好判断
20 | }{
21 | // Add test cases.
22 | {"光大", filepath.Join(pwd, "samples", "2020062290131000005100000000013540122236009.ofd"), true},
23 | {"工行", filepath.Join(pwd, "samples", "0200216819200056225_001_21253000001_20220101_acc.ofd"), true},
24 | {"工行1", filepath.Join(pwd, "samples", "gonghang_1.ofd"), true},
25 |
26 | {"工行2", filepath.Join(pwd, "samples", "0402021509300142766_001_22206000039_20220725_acc.ofd"), true},
27 | {"中电财", filepath.Join(pwd, "samples", "DZHD_1605281110201000001_202205250016050102202100000069141950_20220525_000413.ofd"), true},
28 | {"浙商银行", filepath.Join(pwd, "samples", "bkrs_issuer_20220309_C1030311000455.ofd"), true},
29 | }
30 | for _, test := range testcases {
31 | t.Run(test.name, func(t *testing.T) {
32 | file := test.path
33 | r, err := zip.OpenReader(file)
34 | if err != nil {
35 | t.Logf("%v", err.Error())
36 | }
37 | defer r.Close()
38 | for _, f := range r.File {
39 | t.Logf("文件名: %s\n", f.Name)
40 | if strings.HasSuffix(f.Name, ".dat") {
41 | rc, err := f.Open()
42 | if err != nil {
43 | t.Logf("%v", err.Error())
44 | }
45 | defer rc.Close()
46 | var buf bytes.Buffer
47 | _, err = io.CopyN(&buf, rc, int64(f.UncompressedSize64))
48 | if err != nil {
49 | t.Logf("%v", err.Error())
50 | }
51 | content := buf.Bytes()
52 |
53 | if ses, err := New_SES_Signature(content); err != nil {
54 | t.Logf("%v", err.Error())
55 | } else {
56 | tbs_sign, err := ses.Get_TBS_Sign()
57 | if err != nil {
58 | t.Logf("%v", err.Error())
59 | } else {
60 | seal, err := tbs_sign.Get_Seal()
61 | if err != nil {
62 | t.Logf("%v", err.Error())
63 | } else {
64 | t.Logf("%v", seal)
65 | }
66 | }
67 | }
68 | }
69 |
70 | }
71 | })
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/signature.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "archive/zip"
5 | "bytes"
6 | "encoding/base64"
7 | "encoding/xml"
8 | "fmt"
9 | "strings"
10 |
11 | "golang.org/x/crypto/cryptobyte"
12 | )
13 |
14 | type Signature struct {
15 | SignatureXml
16 | pwd string
17 | rc *zip.ReadCloser
18 | Validator
19 | Category
20 | Content []byte
21 | }
22 | type SignatureXml struct {
23 | XMLName xml.Name `xml:"Signature"`
24 | Text string `xml:",chardata"`
25 | Ofd string `xml:"ofd,attr"`
26 | SignedInfo struct {
27 | Text string `xml:",chardata"`
28 | Provider struct {
29 | Text string `xml:",chardata"`
30 | ProviderName string `xml:"ProviderName,attr"`
31 | Version string `xml:"Version,attr"`
32 | Company string `xml:"Company,attr"`
33 | } `xml:"Provider"`
34 | SignatureMethod struct {
35 | Text string `xml:",chardata"`
36 | } `xml:"SignatureMethod"`
37 | SignatureDateTime struct {
38 | Text string `xml:",chardata"`
39 | } `xml:"SignatureDateTime"`
40 | Parameters struct {
41 | Text string `xml:",chardata"`
42 | Parameter struct {
43 | Text string `xml:",chardata"`
44 | Name string `xml:"Name,attr"`
45 | } `xml:"Parameter"`
46 | } `xml:"Parameters"`
47 | References struct {
48 | Text string `xml:",chardata"`
49 | CheckMethod string `xml:"CheckMethod,attr"`
50 | Reference []struct {
51 | Text string `xml:",chardata"`
52 | FileRef string `xml:"FileRef,attr"`
53 | CheckValue struct {
54 | Text string `xml:",chardata"`
55 | } `xml:"CheckValue"`
56 | } `xml:"Reference"`
57 | } `xml:"References"`
58 | StampAnnot struct {
59 | Text string `xml:",chardata"`
60 | ID string `xml:"ID,attr"`
61 | PageRef string `xml:"PageRef,attr"`
62 | Boundary string `xml:"Boundary,attr"`
63 | } `xml:"StampAnnot"`
64 | } `xml:"SignedInfo"`
65 | SignedValue struct {
66 | Text string `xml:",chardata"`
67 | } `xml:"SignedValue"`
68 | }
69 |
70 | func (signature Signature) GetFileContent(path string) ([]byte, error) {
71 | return LoadZipFileContent(signature.rc, path)
72 | }
73 | func (signature Signature) VerifyDigest() (bool, error) {
74 | checkMethod := signature.SignedInfo.References.CheckMethod
75 | if !(strings.EqualFold(checkMethod, SM3_OID) || strings.EqualFold(checkMethod, "sm3")) {
76 | return false, fmt.Errorf("oid(%s) not support ", checkMethod)
77 | }
78 | ref := signature.SignedInfo.References.Reference
79 | for _, value := range ref {
80 | filePath := value.FileRef
81 | if filePath[0] == '/' {
82 | filePath = filePath[1:]
83 | }
84 | content, err := signature.GetFileContent(filePath)
85 | if err != nil {
86 | break
87 | } else {
88 | validator := signature.Validator
89 | hashed := validator.Digest(content)
90 | encodeString := base64.StdEncoding.EncodeToString(hashed)
91 | if !strings.EqualFold(encodeString, value.CheckValue.Text) {
92 | return false, fmt.Errorf("file %s checkValue mistake", filePath)
93 | } else {
94 | continue
95 | }
96 | }
97 | }
98 | return true, nil
99 |
100 | }
101 | func (signature Signature) Verify() (bool, error) {
102 | switch signature.Category {
103 | case SIGN:
104 | return signature.verifySign()
105 | case SEAL:
106 | return signature.verifySeal()
107 | default:
108 | return signature.verifySeal()
109 | }
110 | }
111 |
112 | func (signature Signature) verifySign() (bool, error) {
113 |
114 | signatureMethod := signature.SignedInfo.SignatureMethod.Text
115 | if !strings.EqualFold(SM3WITHSM2_OID, signatureMethod) {
116 | return false, fmt.Errorf("oid(%s) not support ", signatureMethod)
117 | }
118 |
119 | signDatPath := signature.pwd + "/" + signature.SignedValue.Text
120 | content, err := signature.GetFileContent(signDatPath)
121 |
122 | if err != nil {
123 | return false, err
124 | }
125 |
126 | if contentInfo, err := NewContentInfo(cryptobyte.String(content)); err != nil {
127 | return false, err
128 | } else {
129 | content := contentInfo.GetContent()
130 | signedData, err := NewSignedData(content)
131 | if err != nil {
132 | return false, err
133 | }
134 |
135 | signerInfoes, err := signedData.GetSignerInfos()
136 | if err != nil {
137 | return false, err
138 | }
139 | cert := signedData.GetCertificates()
140 | for _, sign := range signerInfoes {
141 | msg := sign.GetAuthenticatedAttributes()
142 | encryptedDigest := sign.GetEncryptedDigest()
143 | validator := signature.Validator
144 | result, err := validator.Verify(cert, msg, encryptedDigest)
145 | if !result || err != nil {
146 | return false, err
147 | } else {
148 | continue
149 | }
150 | }
151 |
152 | }
153 | return true, nil
154 | }
155 | func (signature Signature) verifySeal() (bool, error) {
156 | signatureMethod := signature.SignedInfo.SignatureMethod.Text
157 | if !strings.EqualFold(SM3WITHSM2_OID, signatureMethod) {
158 | return false, fmt.Errorf("oid(%s) not support ", signatureMethod)
159 | }
160 |
161 | signDatPath := signature.SignedValue.Text
162 | content, err := signature.GetFileContent(signDatPath)
163 | if err != nil {
164 | signDatPath = signature.pwd + "/" + signature.SignedValue.Text
165 | content, err = signature.GetFileContent(signDatPath)
166 | if err != nil {
167 | return false, err
168 | }
169 |
170 | }
171 | validator := signature.Validator
172 | if ses, err := New_SES_Signature(content); err != nil {
173 | return false, err
174 | } else {
175 | tbs_sign, err := ses.Get_TBS_Sign()
176 | if err != nil {
177 | return false, err
178 | } else {
179 | dataDash := validator.Digest(signature.Content)
180 | if !bytes.Equal(tbs_sign.Get_DataHash(), dataDash) {
181 | return false, fmt.Errorf("%s", "Signatures.xml Hash Value Mistake")
182 | }
183 | seal, err := tbs_sign.Get_Seal()
184 | if err != nil {
185 | return false, err
186 | }
187 |
188 | if _, err := validator.Verify(seal.cert, seal.eSealInfo, seal.signature.Bytes); err != nil {
189 | return false, err
190 | }
191 |
192 | }
193 | if _, err := validator.Verify(ses.cert, ses.to_sign, ses.signature.Bytes); err != nil {
194 | return false, err
195 | } else {
196 | return true, nil
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/signature_test.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "encoding/xml"
5 | "testing"
6 | )
7 |
8 | const signature_content = `
9 |
10 |
12 |
13 |
14 | 1.2.156.10197.1.501
15 | 20220328071354Z
16 |
17 | none
18 |
19 |
20 |
21 | e70+E5QyBH4wRV4xET9joIjuIIFQUGE35sF1y8a3mSA=
22 |
23 |
24 | Xkbf6Pfn02s8RdWAfYiE9VmVpw3+MgFuEEEEt5EE+Vs=
25 |
26 |
27 | Sds4LG5RrkFWRR9909cHfWF+mAiyQclIGAjJUgHOu24=
28 |
29 |
30 | KXDYVC+KVyIOox46/9u174/Y0a1dHEnqWDIqxIDn7cI=
31 |
32 |
33 | VrM59lM2wAZ5KYm3sgvjo3wQCZ3UdE99Spj2MHDIM0w=
34 |
35 |
36 | x9wTJfjt1nnSZGKtNOKF8ipYFiI+YR2jQ0DM29axKv4=
37 |
38 |
39 | qEGum3RvTwYlzJPGFkkolHEsV6FsNUjb2dLAtyeEkr0=
40 |
41 |
42 | AROs4wgy8qPc/RYElgh6q9wC74PZx+eeTlHhqTztch8=
43 |
44 |
45 |
46 |
47 | /Doc_0/Signs/Sign_0/SignedValue.dat
48 |
49 | `
50 |
51 | func TestSignature(t *testing.T) {
52 | var signature Signature
53 | if err := xml.Unmarshal([]byte(signature_content), &signature); err != nil {
54 | t.Logf("%v", err)
55 | } else {
56 | t.Logf("%v", signature.SignedInfo.References)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/signatures.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "archive/zip"
5 | "encoding/xml"
6 | "fmt"
7 | "strings"
8 | )
9 |
10 | type SignaturesXml struct {
11 | XMLName xml.Name `xml:"Signatures"`
12 | Text string `xml:",chardata"`
13 | Ofd string `xml:"ofd,attr"`
14 | MaxSignId struct {
15 | Text string `xml:",chardata"`
16 | } `xml:"MaxSignId"`
17 | Signature []struct {
18 | Text string `xml:",chardata"`
19 | ID string `xml:"ID,attr"`
20 | Type string `xml:"Type,attr"`
21 | BaseLoc string `xml:"BaseLoc,attr"`
22 | } `xml:"Signature"`
23 | }
24 |
25 | type Signatures struct {
26 | SignaturesXml
27 | pwd string
28 | rc *zip.ReadCloser
29 | Validator
30 | }
31 |
32 | func (signatures Signatures) GetFileContent(path string) ([]byte, error) {
33 | return LoadZipFileContent(signatures.rc, path)
34 | }
35 | func (signatures Signatures) GetSignatureById(signId string) (*Signature, error) {
36 | if strings.EqualFold(signId, "") {
37 | return nil, fmt.Errorf("signId is empty")
38 | }
39 | for _, sign := range signatures.Signature {
40 | if !strings.EqualFold(signId, sign.ID) {
41 | continue
42 | }
43 | path := sign.BaseLoc
44 | pos := strings.LastIndexByte(path, '/')
45 | pwd := path[0:pos]
46 | content, err := signatures.GetFileContent(path)
47 | if err != nil {
48 | if path[0] == '/' {
49 | path = signatures.pwd + path
50 | } else {
51 | path = signatures.pwd + "/" + path
52 | }
53 | pos = strings.LastIndexByte(path, '/')
54 | pwd = path[0:pos]
55 | content, err = signatures.GetFileContent(path)
56 | if err != nil {
57 | return nil, err
58 | }
59 | }
60 |
61 | var signature Signature
62 | if err := xml.Unmarshal(content, &signature); err != nil {
63 | return nil, err
64 | } else {
65 | signature.pwd = pwd
66 | signature.rc = signatures.rc
67 | signature.Validator = signatures.Validator
68 | signature.Content = content
69 | switch sign.Type {
70 | case "Seal":
71 | signature.Category = SEAL
72 | case "Sign":
73 | signature.Category = SIGN
74 | default:
75 | signature.Category = SEAL
76 | }
77 | }
78 | return &signature, nil
79 | }
80 | return nil, fmt.Errorf("signId [%v] not found", signId)
81 | }
82 |
--------------------------------------------------------------------------------
/signatures_test.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "encoding/xml"
5 | "testing"
6 | )
7 |
8 | const signatures_content = `
9 |
10 |
12 | 2
13 |
14 |
15 | `
16 |
17 | func TestSignatures(t *testing.T) {
18 | var signature Signatures
19 | if err := xml.Unmarshal([]byte(signatures_content), &signature); err != nil {
20 | t.Logf("%v", err)
21 | } else {
22 | t.Logf("%v", signature.Signature)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/validator.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "math/big"
5 |
6 | "github.com/itlabers/crypto/sm/sm2"
7 | "github.com/itlabers/crypto/sm/sm3"
8 | smx509 "github.com/itlabers/crypto/x509"
9 | "golang.org/x/crypto/cryptobyte"
10 | "golang.org/x/crypto/cryptobyte/asn1"
11 | )
12 |
13 | const (
14 | SM3_OID = "1.2.156.10197.1.401"
15 | SM3WITHSM2_OID = "1.2.156.10197.1.501"
16 | MAX = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
17 | )
18 |
19 | type Category int
20 |
21 | const (
22 | SIGN Category = iota
23 | SEAL
24 | )
25 |
26 | type Hash interface {
27 | Digest([]byte) []byte
28 | }
29 | type Validator interface {
30 | Hash
31 |
32 | Verify([]byte, []byte, []byte) (bool, error)
33 | }
34 |
35 | type CommonValidator struct {
36 | }
37 |
38 | func (common *CommonValidator) Digest(msg []byte) []byte {
39 | h := sm3.New()
40 | h.Write(msg)
41 | dataDash := h.Sum(nil)
42 | return dataDash
43 |
44 | }
45 | func (common *CommonValidator) Verify(cert []byte, msg []byte, signature []byte) (bool, error) {
46 | certificate, err := smx509.ParseCertificate(cert)
47 | if err != nil {
48 | return false, err
49 | }
50 | pk := certificate.PublicKey.(*sm2.PublicKey)
51 | hashed := sm3.New()
52 | if len(signature) == 64 {
53 | r := new(big.Int).SetBytes(signature[0:32])
54 | s := new(big.Int).SetBytes(signature[32:64])
55 | result := sm2.Verify(pk, "", msg, hashed, r, s)
56 | return result, nil
57 | } else {
58 | type Sign struct {
59 | R *big.Int
60 | S *big.Int
61 | }
62 | var sign Sign
63 | sig := cryptobyte.String(signature)
64 | var ses_signature cryptobyte.String
65 | sig.ReadASN1(&ses_signature, asn1.SEQUENCE)
66 |
67 | var r cryptobyte.String
68 | ses_signature.ReadASN1(&r, asn1.INTEGER)
69 |
70 | var s cryptobyte.String
71 | ses_signature.ReadASN1(&s, asn1.INTEGER)
72 |
73 | sign.R = new(big.Int).SetBytes(r)
74 | sign.S = new(big.Int).SetBytes(s)
75 |
76 | ff, _ := new(big.Int).SetString(MAX, 16)
77 | if sign.R.Sign() == -1 {
78 | sign.R.And(sign.R, ff)
79 | }
80 | if sign.S.Sign() == -1 {
81 | sign.S.And(sign.S, ff)
82 | }
83 | result := sm2.Verify(pk, "", msg, hashed, sign.R, sign.S)
84 | return result, nil
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/zip.go:
--------------------------------------------------------------------------------
1 | package ofd
2 |
3 | import (
4 | "archive/zip"
5 | "bytes"
6 | "io"
7 | )
8 |
9 | func LoadZipFileContent(rc *zip.ReadCloser, path string) ([]byte, error) {
10 | filePath := path
11 | if filePath[0] == '/' {
12 | filePath = filePath[1:]
13 | }
14 | fs, err := rc.Open(filePath)
15 | if err != nil {
16 | return nil, err
17 | }
18 | defer fs.Close()
19 | var buf bytes.Buffer
20 | _, err = io.Copy(&buf, fs)
21 | if err != nil {
22 | return nil, err
23 | }
24 | content := buf.Bytes()
25 | return content, nil
26 | }
27 |
--------------------------------------------------------------------------------