]> Cypherpunks.ru repositories - pygost.git/blob - pygost/test_pfx.py
Use DEFINES PyDERASN feature for less .decode() invocations
[pygost.git] / pygost / test_pfx.py
1 # coding: utf-8
2 # PyGOST -- Pure Python GOST cryptographic functions library
3 # Copyright (C) 2015-2018 Sergey Matveev <stargrave@stargrave.org>
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 from base64 import b64decode
19 from hmac import new as hmac_new
20 from unittest import skipIf
21 from unittest import TestCase
22
23 from pygost.gost28147 import cfb_decrypt
24 from pygost.gost34112012512 import GOST34112012512
25 from pygost.gost34112012512 import pbkdf2 as gost34112012_pbkdf2
26 from pygost.utils import hexdec
27
28
29 try:
30     from pygost.asn1schemas.pfx import OctetStringSafeContents
31     from pygost.asn1schemas.pfx import PFX
32     from pygost.asn1schemas.pfx import PKCS8ShroudedKeyBag
33 except ImportError:
34     pyderasn_exists = False
35 else:
36     pyderasn_exists = True
37
38
39 @skipIf(not pyderasn_exists, "PyDERASN dependency is required")
40 class TestPFX(TestCase):
41     """PFX test vectors from "Транспортный ключевой контейнер" (R50.1.112-2016.pdf)
42     """
43     pfx_raw = b64decode("""
44 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
74 mwcl1xQV2G5/fgICB9A=
75     """)
76     password = u'Пароль для PFX'
77
78     def test_shrouded_key_bag(self):
79         private_key_info_expected = b64decode(b"""
80 MGYCAQAwHwYIKoUDBwEBAQEwEwYHKoUDAgIjAQYIKoUDBwEBAgIEQEYbRu86z+1JFKDcPDN9UbTG
81 G2ki9enTqos4KpUU0j9IDpl1UXiaA1YDIwUjlAp+81GkLmyt8Fw6Gt/X5JZySAY=
82         """)
83
84         pfx, tail = PFX().decode(self.pfx_raw)
85         self.assertSequenceEqual(tail, b"")
86         _, octet_string_safe_contents = pfx["authSafe"]["content"].defined
87         outer_safe_contents = octet_string_safe_contents["safeContents"]
88         octet_string_safe_contents, tail = OctetStringSafeContents().decode(
89             bytes(outer_safe_contents[0]["bagValue"]),
90         )
91         self.assertSequenceEqual(tail, b"")
92         safe_bag = octet_string_safe_contents["safeContents"][0]
93         shrouded_key_bag, tail = PKCS8ShroudedKeyBag().decode(
94             bytes(safe_bag["bagValue"]),
95         )
96         self.assertSequenceEqual(tail, b"")
97         _, pbes2_params = shrouded_key_bag["encryptionAlgorithm"]["parameters"].defined
98         _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined
99         _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined
100
101         key = gost34112012_pbkdf2(
102             password=self.password.encode("utf-8"),
103             salt=bytes(pbkdf2_params["salt"]["specified"]),
104             iterations=int(pbkdf2_params["iterationCount"]),
105             dklen=32,
106         )
107         # key = hexdec("309dd0354c5603739403f2335e9e2055138f8b5c98b63009de0635eea1fd7ba8")
108         self.assertSequenceEqual(
109             cfb_decrypt(
110                 key,
111                 bytes(shrouded_key_bag["encryptedData"]),
112                 iv=bytes(enc_scheme_params["iv"]),
113                 sbox="Gost28147_tc26_ParamZ",
114             ),
115             private_key_info_expected,
116         )
117
118     def test_encrypted_data(self):
119         cert_bag_expected = b64decode(b"""
120 MIIDSjCCA0YGCyqGSIb3DQEMCgEDoIIDHjCCAxoGCiqGSIb3DQEJFgGgggMKBIIDBjCCAwIwggKt
121 oAMCAQICEAHQaF8xH5bAAAAACycJAAEwDAYIKoUDBwEBAwIFADBgMQswCQYDVQQGEwJSVTEVMBMG
122 A1UEBwwM0JzQvtGB0LrQstCwMQ8wDQYDVQQKDAbQotCaMjYxKTAnBgNVBAMMIENBIGNlcnRpZmlj
123 YXRlIChQS0NTIzEyIGV4YW1wbGUpMB4XDTE1MDMyNzA3MjUwMFoXDTIwMDMyNzA3MjMwMFowZDEL
124 MAkGA1UEBhMCUlUxFTATBgNVBAcMDNCc0L7RgdC60LLQsDEPMA0GA1UECgwG0KLQmjI2MS0wKwYD
125 VQQDDCRUZXN0IGNlcnRpZmljYXRlIDEgKFBLQ1MjMTIgZXhhbXBsZSkwZjAfBggqhQMHAQEBATAT
126 BgcqhQMCAiMBBggqhQMHAQECAgNDAARA1xzymkpvr2dYJT8WTOX3Dt96/+hGsXNytUQpkWB5ImJM
127 4tg9AsC4RIUwV5H41MhG0uBRFweTzN6AsAdBvhTClYEJADI3MDkwMDAxo4IBKTCCASUwKwYDVR0Q
128 BCQwIoAPMjAxNTAzMjcwNzI1MDBagQ8yMDE2MDMyNzA3MjUwMFowDgYDVR0PAQH/BAQDAgTwMB0G
129 A1UdDgQWBBQhWOsRQ68yYN2Utg/owHoWcqsVbTAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH
130 AwQwDAYDVR0TAQH/BAIwADCBmQYDVR0jBIGRMIGOgBQmnc7Xh5ykb5t/BMwOkxA4drfEmqFkpGIw
131 YDELMAkGA1UEBhMCUlUxFTATBgNVBAcMDNCc0L7RgdC60LLQsDEPMA0GA1UECgwG0KLQmjI2MSkw
132 JwYDVQQDDCBDQSBjZXJ0aWZpY2F0ZSAoUEtDUyMxMiBleGFtcGxlKYIQAdBoXvL8TSAAAAALJwkA
133 ATAMBggqhQMHAQEDAgUAA0EA9oq0Vvk8kkgIwkp0x0J5eKtia4MNTiwKAm7jgnCZIx3O98BThaTX
134 3ZQhEo2RL9pTCPr6wFMheeJ+YdGMReXvsjEVMBMGCSqGSIb3DQEJFTEGBAQBAAAA
135         """)
136
137         pfx, tail = PFX().decode(self.pfx_raw)
138         self.assertSequenceEqual(tail, b"")
139         _, octet_string_safe_contents = pfx["authSafe"]["content"].defined
140         outer_safe_contents = octet_string_safe_contents["safeContents"]
141         _, encrypted_data = outer_safe_contents[1]["bagValue"].defined
142         _, pbes2_params = encrypted_data["encryptedContentInfo"]["contentEncryptionAlgorithm"]["parameters"].defined
143         _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined
144         _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined
145         key = gost34112012_pbkdf2(
146             password=self.password.encode("utf-8"),
147             salt=bytes(pbkdf2_params["salt"]["specified"]),
148             iterations=int(pbkdf2_params["iterationCount"]),
149             dklen=32,
150         )
151         # key = hexdec("0e93d71339e7f53b79a0bc41f9109dd4fb60b30ae10736c1bb77b84c07681cfc")
152         self.assertSequenceEqual(
153             cfb_decrypt(
154                 key,
155                 bytes(encrypted_data["encryptedContentInfo"]["encryptedContent"]),
156                 iv=bytes(enc_scheme_params["iv"]),
157                 sbox="Gost28147_tc26_ParamZ",
158             ),
159             cert_bag_expected,
160         )
161
162     def test_mac(self):
163         pfx, tail = PFX().decode(self.pfx_raw)
164         self.assertSequenceEqual(tail, b"")
165         _, octet_string_safe_contents = pfx["authSafe"]["content"].defined
166         outer_safe_contents = octet_string_safe_contents["safeContents"]
167         mac_data = pfx["macData"]
168         mac_key = gost34112012_pbkdf2(
169             password=self.password.encode('utf-8'),
170             salt=bytes(mac_data["macSalt"]),
171             iterations=int(mac_data["iterations"]),
172             dklen=96,
173         )[-32:]
174         # mac_key = hexdec("cadbfbf3bceaa9b79f651508fac5abbeb4a13d0bd0e1876bd3c3efb2112128a5")
175         self.assertSequenceEqual(
176             hmac_new(
177                 key=mac_key,
178                 msg=outer_safe_contents.encode(),
179                 digestmod=GOST34112012512,
180             ).digest(),
181             bytes(mac_data["mac"]["digest"]),
182         )