├── .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 | ![QQ群](https://github.com/itlabers/ofd-go-reference/blob/master/images/qq_group.jpg) 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 | --------------------------------------------------------------------------------