]> Cypherpunks.ru repositories - pygost.git/blobdiff - pygost/test_pfx.py
Updated fixed PFX test vectors
[pygost.git] / pygost / test_pfx.py
index 8dac161614b5da8537fda7d8709e9833fa633c9b..d9585241f3400bc3aa90c6af666dc4fd69430fa9 100644 (file)
@@ -1,11 +1,10 @@
 # coding: utf-8
 # PyGOST -- Pure Python GOST cryptographic functions library
-# Copyright (C) 2015-2018 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2015-2021 Sergey Matveev <stargrave@stargrave.org>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
+# the Free Software Foundation, version 3 of the License.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -20,16 +19,50 @@ from hmac import new as hmac_new
 from unittest import skipIf
 from unittest import TestCase
 
+from pygost import gost3410
 from pygost.gost28147 import cfb_decrypt
+from pygost.gost34112012256 import GOST34112012256
 from pygost.gost34112012512 import GOST34112012512
 from pygost.gost34112012512 import pbkdf2 as gost34112012_pbkdf2
+from pygost.gost3412 import GOST3412Kuznechik
+from pygost.gost3412 import GOST3412Magma
+from pygost.gost3412 import KEYSIZE
+from pygost.gost3413 import ctr_acpkm
+from pygost.gost3413 import mac as omac
+from pygost.kdf import kdf_tree_gostr3411_2012_256
+from pygost.kdf import keg
 from pygost.utils import hexdec
+from pygost.wrap import kimp15
 
 
 try:
+    from pyderasn import OctetString
+
+    from pygost.asn1schemas.cms import EncryptedData
+    from pygost.asn1schemas.cms import EnvelopedData
+    from pygost.asn1schemas.cms import SignedAttributes
+    from pygost.asn1schemas.cms import SignedData
+    from pygost.asn1schemas.oids import id_data
+    from pygost.asn1schemas.oids import id_envelopedData
+    from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm
+    from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_wrap_kexp15
+    from pygost.asn1schemas.oids import id_messageDigest
+    from pygost.asn1schemas.oids import id_pbes2
+    from pygost.asn1schemas.oids import id_pkcs12_bagtypes_certBag
+    from pygost.asn1schemas.oids import id_pkcs12_bagtypes_keyBag
+    from pygost.asn1schemas.oids import id_pkcs12_bagtypes_pkcs8ShroudedKeyBag
+    from pygost.asn1schemas.oids import id_pkcs9_certTypes_x509Certificate
+    from pygost.asn1schemas.oids import id_signedData
+    from pygost.asn1schemas.oids import id_tc26_agreement_gost3410_2012_256
+    from pygost.asn1schemas.oids import id_tc26_gost3411_2012_256
+    from pygost.asn1schemas.pfx import CertBag
+    from pygost.asn1schemas.pfx import KeyBag
     from pygost.asn1schemas.pfx import OctetStringSafeContents
+    from pygost.asn1schemas.pfx import PBES2Params
     from pygost.asn1schemas.pfx import PFX
     from pygost.asn1schemas.pfx import PKCS8ShroudedKeyBag
+    from pygost.asn1schemas.pfx import SafeContents
+    from pygost.asn1schemas.x509 import Certificate
 except ImportError:
     pyderasn_exists = False
 else:
@@ -73,7 +106,7 @@ AwcBAQIDBEAIFX0fyZe20QKKhWm6WYX+S92Gt6zaXroXOvAmayzLfZ5Sd9C2t9zZ
 JSg6M8RBUYpw/8ym5ou1o2nDa09M5zF3BCCpzyCQBI+rzfISeKvPV1ROfcXiYU93
 mwcl1xQV2G5/fgICB9A=
     """)
-    password = u'Пароль для PFX'
+    password = u"Пароль для PFX"
 
     def test_shrouded_key_bag(self):
         private_key_info_expected = b64decode(b"""
@@ -83,13 +116,12 @@ G2ki9enTqos4KpUU0j9IDpl1UXiaA1YDIwUjlAp+81GkLmyt8Fw6Gt/X5JZySAY=
 
         pfx, tail = PFX().decode(self.pfx_raw)
         self.assertSequenceEqual(tail, b"")
-        _, octet_string_safe_contents = pfx["authSafe"]["content"].defined
-        outer_safe_contents = octet_string_safe_contents["safeContents"]
-        octet_string_safe_contents, tail = OctetStringSafeContents().decode(
+        _, outer_safe_contents = pfx["authSafe"]["content"].defined
+        safe_contents, tail = OctetStringSafeContents().decode(
             bytes(outer_safe_contents[0]["bagValue"]),
         )
         self.assertSequenceEqual(tail, b"")
-        safe_bag = octet_string_safe_contents["safeContents"][0]
+        safe_bag = safe_contents[0]
         shrouded_key_bag, tail = PKCS8ShroudedKeyBag().decode(
             bytes(safe_bag["bagValue"]),
         )
@@ -110,7 +142,7 @@ G2ki9enTqos4KpUU0j9IDpl1UXiaA1YDIwUjlAp+81GkLmyt8Fw6Gt/X5JZySAY=
                 key,
                 bytes(shrouded_key_bag["encryptedData"]),
                 iv=bytes(enc_scheme_params["iv"]),
-                sbox="Gost28147_tc26_ParamZ",
+                sbox="id-tc26-gost-28147-param-Z",
             ),
             private_key_info_expected,
         )
@@ -136,8 +168,7 @@ ATAMBggqhQMHAQEDAgUAA0EA9oq0Vvk8kkgIwkp0x0J5eKtia4MNTiwKAm7jgnCZIx3O98BThaTX
 
         pfx, tail = PFX().decode(self.pfx_raw)
         self.assertSequenceEqual(tail, b"")
-        _, octet_string_safe_contents = pfx["authSafe"]["content"].defined
-        outer_safe_contents = octet_string_safe_contents["safeContents"]
+        _, outer_safe_contents = pfx["authSafe"]["content"].defined
         _, encrypted_data = outer_safe_contents[1]["bagValue"].defined
         _, pbes2_params = encrypted_data["encryptedContentInfo"]["contentEncryptionAlgorithm"]["parameters"].defined
         _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined
@@ -154,7 +185,7 @@ ATAMBggqhQMHAQEDAgUAA0EA9oq0Vvk8kkgIwkp0x0J5eKtia4MNTiwKAm7jgnCZIx3O98BThaTX
                 key,
                 bytes(encrypted_data["encryptedContentInfo"]["encryptedContent"]),
                 iv=bytes(enc_scheme_params["iv"]),
-                sbox="Gost28147_tc26_ParamZ",
+                sbox="id-tc26-gost-28147-param-Z",
             ),
             cert_bag_expected,
         )
@@ -162,11 +193,10 @@ ATAMBggqhQMHAQEDAgUAA0EA9oq0Vvk8kkgIwkp0x0J5eKtia4MNTiwKAm7jgnCZIx3O98BThaTX
     def test_mac(self):
         pfx, tail = PFX().decode(self.pfx_raw)
         self.assertSequenceEqual(tail, b"")
-        _, octet_string_safe_contents = pfx["authSafe"]["content"].defined
-        outer_safe_contents = octet_string_safe_contents["safeContents"]
+        _, outer_safe_contents = pfx["authSafe"]["content"].defined
         mac_data = pfx["macData"]
         mac_key = gost34112012_pbkdf2(
-            password=self.password.encode('utf-8'),
+            password=self.password.encode("utf-8"),
             salt=bytes(mac_data["macSalt"]),
             iterations=int(mac_data["iterations"]),
             dklen=96,
@@ -175,8 +205,476 @@ ATAMBggqhQMHAQEDAgUAA0EA9oq0Vvk8kkgIwkp0x0J5eKtia4MNTiwKAm7jgnCZIx3O98BThaTX
         self.assertSequenceEqual(
             hmac_new(
                 key=mac_key,
-                msg=outer_safe_contents.encode(),
+                msg=SafeContents(outer_safe_contents).encode(),
                 digestmod=GOST34112012512,
             ).digest(),
             bytes(mac_data["mac"]["digest"]),
         )
+
+
+@skipIf(not pyderasn_exists, "PyDERASN dependency is required")
+class TestPFX2020(TestCase):
+    """PFX test vectors from newer PKCS#12 update
+    """
+    ca_prv_raw = hexdec("092F8D059E97E22B90B1AE99F0087FC4D26620B91550CBB437C191005A290810")
+    ca_curve = gost3410.CURVES["id-tc26-gost-3410-12-256-paramSetA"]
+    ca_cert = Certificate().decod(b64decode(b"""
+        MIIB+TCCAaagAwIBAgIEAYy6gTAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2MS
+        cwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEwMTAx
+        MDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA4MQ0wCwYDVQQKEwRUSzI2MScwJQYDVQQDEx
+        5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwXjAXBggqhQMHAQEBATALBgkq
+        hQMHAQIBAQEDQwAEQBpKgpyPDnhQAJyLqy8Qs0XQhgxEhby6tSypqYimgbjpcKqtU6
+        4jpDXc3h3BxGxtl2oHJ/4YLZ/ll87dto3ltMqjgZgwgZUwYwYDVR0jBFwwWoAUrGwO
+        TERmokKW4p8JOyVm88ukUyqhPKQ6MDgxDTALBgNVBAoTBFRLMjYxJzAlBgNVBAMTHk
+        NBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdIIEAYy6gTAdBgNVHQ4EFgQUrGwO
+        TERmokKW4p8JOyVm88ukUyowDwYDVR0TAQH/BAUwAwEB/zAKBggqhQMHAQEDAgNBAB
+        Gg3nhgQ5oCKbqlEdVaRxH+1WX4wVkawGXuTYkr1AC2OWw3ZC14Vvg3nazm8UMWUZtk
+        vu1kJcHQ4jFKkjUeg2E=
+    """))
+    ca_pub = gost3410.pub_unmarshal(bytes(OctetString().decod(bytes(
+        ca_cert["tbsCertificate"]["subjectPublicKeyInfo"]["subjectPublicKey"]
+    ))))
+    password = u"Пароль для PFX".encode("utf-8")
+    cert_test = Certificate().decod(b64decode(b"""
+        MIICLjCCAdugAwIBAgIEAYy6hDAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2MS
+        cwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEwMTAx
+        MDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRUSzI2MSowKAYDVQQDEy
+        FPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDUxMi1iaXQwgaAwFwYIKoUDBwEBAQIw
+        CwYJKoUDBwECAQIBA4GEAASBgLSLt1q8KQ4YZVxioU+1LV9QhE7MHR9gBEh7S1yVNG
+        lqt7+rNG5VFqmrPM74rbUsOlhV8M+zZKprXdk35Oz8lSW/n2oIUHZxikXIH/SSHj4r
+        v3K/Puvz7hYTQSZl/xPdp78nUmjrEa6d5wfX8biEy2z0dgufFvAkMw1Ua4gdXqDOo4
+        GHMIGEMGMGA1UdIwRcMFqAFKxsDkxEZqJCluKfCTslZvPLpFMqoTykOjA4MQ0wCwYD
+        VQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaX
+        SCBAGMuoEwHQYDVR0OBBYEFH4GVwmYDK1rCKhX7nkAWDrJ16CkMAoGCCqFAwcBAQMC
+        A0EACl6p8dAbpi9Hk+3mgMyI0WIh17IrlrSp/mB0F7ZzMt8XUD1Dwz3JrrnxeXnfMv
+        OA5BdUJ9hCyDgMVAGs/IcEEA==
+    """))
+    prv_test_raw = b64decode("""
+        MIHiAgEBMBcGCCqFAwcBAQECMAsGCSqFAwcBAgECAQRAEWkl+eblsHWs86SNgRKq
+        SxMOgGhbvR/uZ5/WWfdNG1axvUwVhpcXIxDZUmzQuNzqJBkseI7f5/JjXyTFRF1a
+        +YGBgQG0i7davCkOGGVcYqFPtS1fUIROzB0fYARIe0tclTRpare/qzRuVRapqzzO
+        +K21LDpYVfDPs2Sqa13ZN+Ts/JUlv59qCFB2cYpFyB/0kh4+K79yvz7r8+4WE0Em
+        Zf8T3ae/J1Jo6xGunecH1/G4hMts9HYLnxbwJDMNVGuIHV6gzg==
+    """)
+
+    def test_cert_and_encrypted_key(self):
+        pfx_raw = b64decode(b"""
+            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
+        """)
+        pfx = PFX().decod(pfx_raw)
+        _, outer_safe_contents = pfx["authSafe"]["content"].defined
+
+        safe_contents = OctetStringSafeContents().decod(bytes(
+            outer_safe_contents[0]["bagValue"]
+        ))
+        safe_bag = safe_contents[0]
+        self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_certBag)
+        cert_bag = CertBag().decod(bytes(safe_bag["bagValue"]))
+        self.assertEqual(cert_bag["certId"], id_pkcs9_certTypes_x509Certificate)
+        _, cert = cert_bag["certValue"].defined
+        self.assertEqual(Certificate(cert), self.cert_test)
+
+        safe_contents = OctetStringSafeContents().decod(bytes(
+            outer_safe_contents[1]["bagValue"]
+        ))
+        safe_bag = safe_contents[0]
+        self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_pkcs8ShroudedKeyBag)
+        shrouded_key_bag = PKCS8ShroudedKeyBag().decod(bytes(safe_bag["bagValue"]))
+        _, pbes2_params = shrouded_key_bag["encryptionAlgorithm"]["parameters"].defined
+        _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined
+        _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined
+        ukm = bytes(enc_scheme_params["ukm"])
+        key = gost34112012_pbkdf2(
+            password=self.password,
+            salt=bytes(pbkdf2_params["salt"]["specified"]),
+            iterations=int(pbkdf2_params["iterationCount"]),
+            dklen=32,
+        )
+        # key = hexdec("4b7ae649ca31dd5fe3243a91a5188c03f1d7049bec8e0d241c0e1e8c39ea4c1f")
+        key_enc, key_mac = kdf_tree_gostr3411_2012_256(
+            key, b"kdf tree", ukm[GOST3412Kuznechik.blocksize // 2:], 2,
+        )
+        ciphertext = bytes(shrouded_key_bag["encryptedData"])
+        plaintext = ctr_acpkm(
+            GOST3412Kuznechik,
+            GOST3412Kuznechik(key_enc).encrypt,
+            section_size=256 * 1024,
+            bs=GOST3412Kuznechik.blocksize,
+            data=ciphertext,
+            iv=ukm[:GOST3412Kuznechik.blocksize // 2],
+        )
+        mac_expected = plaintext[-GOST3412Kuznechik.blocksize:]
+        plaintext = plaintext[:-GOST3412Kuznechik.blocksize]
+        mac = omac(
+            GOST3412Kuznechik(key_mac).encrypt,
+            GOST3412Kuznechik.blocksize,
+            plaintext,
+        )
+        self.assertSequenceEqual(mac, mac_expected)
+        self.assertSequenceEqual(plaintext, self.prv_test_raw)
+
+        mac_data = pfx["macData"]
+        mac_key = gost34112012_pbkdf2(
+            password=self.password,
+            salt=bytes(mac_data["macSalt"]),
+            iterations=int(mac_data["iterations"]),
+            dklen=96,
+        )[-32:]
+        # mac_key = hexdec("a81d1bc91a4a5cf1fd7320f92dda7e5b285816c3b20826a382d7ed0cbf3a9bf4")
+        self.assertSequenceEqual(
+            hmac_new(
+                key=mac_key,
+                msg=SafeContents(outer_safe_contents).encode(),
+                digestmod=GOST34112012512,
+            ).digest(),
+            bytes(mac_data["mac"]["digest"]),
+        )
+        self.assertTrue(gost3410.verify(
+            self.ca_curve,
+            self.ca_pub,
+            GOST34112012256(cert["tbsCertificate"].encode()).digest()[::-1],
+            bytes(cert["signatureValue"]),
+        ))
+
+    def test_encrypted_cert_and_key(self):
+        pfx_raw = b64decode(b"""
+            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
+        """)
+        pfx = PFX().decod(pfx_raw)
+        _, outer_safe_contents = pfx["authSafe"]["content"].defined
+
+        encrypted_data = EncryptedData().decod(bytes(
+            outer_safe_contents[0]["bagValue"]
+        ))
+        eci = encrypted_data["encryptedContentInfo"]
+        self.assertEqual(eci["contentEncryptionAlgorithm"]["algorithm"], id_pbes2)
+        pbes2_params = PBES2Params().decod(bytes(
+            eci["contentEncryptionAlgorithm"]["parameters"]
+        ))
+        _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined
+        _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined
+        ukm = bytes(enc_scheme_params["ukm"])
+        key = gost34112012_pbkdf2(
+            password=self.password,
+            salt=bytes(pbkdf2_params["salt"]["specified"]),
+            iterations=int(pbkdf2_params["iterationCount"]),
+            dklen=32,
+        )
+        # key = hexdec("d066a96fb326ba896a2352d3f40240a4ded6e7e7bd5b4db6b5241d631c8c381c")
+        key_enc, key_mac = kdf_tree_gostr3411_2012_256(
+            key, b"kdf tree", ukm[GOST3412Magma.blocksize // 2:], 2,
+        )
+        ciphertext = bytes(eci["encryptedContent"])
+        plaintext = ctr_acpkm(
+            GOST3412Magma,
+            GOST3412Magma(key_enc).encrypt,
+            section_size=8 * 1024,
+            bs=GOST3412Magma.blocksize,
+            data=ciphertext,
+            iv=ukm[:GOST3412Magma.blocksize // 2],
+        )
+        mac_expected = plaintext[-GOST3412Magma.blocksize:]
+        plaintext = plaintext[:-GOST3412Magma.blocksize]
+        mac = omac(
+            GOST3412Magma(key_mac).encrypt,
+            GOST3412Magma.blocksize,
+            plaintext,
+        )
+        self.assertSequenceEqual(mac, mac_expected)
+
+        safe_contents = SafeContents().decod(plaintext)
+        safe_bag = safe_contents[0]
+        self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_certBag)
+        cert_bag = CertBag().decod(bytes(safe_bag["bagValue"]))
+        self.assertEqual(cert_bag["certId"], id_pkcs9_certTypes_x509Certificate)
+        _, cert = cert_bag["certValue"].defined
+        self.assertEqual(Certificate(cert), self.cert_test)
+
+        safe_contents = OctetStringSafeContents().decod(bytes(
+            outer_safe_contents[1]["bagValue"]
+        ))
+        safe_bag = safe_contents[0]
+        self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_pkcs8ShroudedKeyBag)
+        shrouded_key_bag = PKCS8ShroudedKeyBag().decod(bytes(safe_bag["bagValue"]))
+        _, pbes2_params = shrouded_key_bag["encryptionAlgorithm"]["parameters"].defined
+        _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined
+        _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined
+        ukm = bytes(enc_scheme_params["ukm"])
+        key = gost34112012_pbkdf2(
+            password=self.password,
+            salt=bytes(pbkdf2_params["salt"]["specified"]),
+            iterations=int(pbkdf2_params["iterationCount"]),
+            dklen=32,
+        )
+        # key = hexdec("f840d001fd11441e0fb7ccf48f471915e5bf35275309dbe7ade9da4fe460ba7e")
+        ciphertext = bytes(shrouded_key_bag["encryptedData"])
+        plaintext = ctr_acpkm(
+            GOST3412Magma,
+            GOST3412Magma(key).encrypt,
+            section_size=8 * 1024,
+            bs=GOST3412Magma.blocksize,
+            data=ciphertext,
+            iv=ukm[:GOST3412Magma.blocksize // 2],
+        )
+        self.assertSequenceEqual(plaintext, self.prv_test_raw)
+
+        mac_data = pfx["macData"]
+        mac_key = gost34112012_pbkdf2(
+            password=self.password,
+            salt=bytes(mac_data["macSalt"]),
+            iterations=int(mac_data["iterations"]),
+            dklen=96,
+        )[-32:]
+        # mac_key = hexdec("084f81782af1534ffd67e3c579c14cb45d7a6f659f46fdbb51a552e874e66fb2")
+        self.assertSequenceEqual(
+            hmac_new(
+                key=mac_key,
+                msg=SafeContents(outer_safe_contents).encode(),
+                digestmod=GOST34112012512,
+            ).digest(),
+            bytes(mac_data["mac"]["digest"]),
+        )
+
+    def test_dh(self):
+        curve = gost3410.CURVES["id-tc26-gost-3410-12-256-paramSetA"]
+        # sender_prv_raw = hexdec("0B20810E449978C7C3B76C6FF77A16C532421139344A058EF56310B6B6F377E8")
+        sender_cert = Certificate().decod(b64decode("""
+            MIIB6zCCAZigAwIBAgIEAYy6gjAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2
+            MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw
+            MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRUSzI2MSowKAYD
+            VQQDEyFPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwXjAXBggqhQMH
+            AQEBATALBgkqhQMHAQIBAQEDQwAEQJYpDRNiWWqDgaZje0EmLLOldQ35o5X1ZuZN
+            SKequYQc/soI3OgDMWD7ThJJCk01IelCeb6MsBmG4lol+pnpVtOjgYcwgYQwYwYD
+            VR0jBFwwWoAUrGwOTERmokKW4p8JOyVm88ukUyqhPKQ6MDgxDTALBgNVBAoTBFRL
+            MjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdIIEAYy6
+            gTAdBgNVHQ4EFgQUPx5RgcjkifhlJm4/jQdkbm30rVQwCgYIKoUDBwEBAwIDQQA6
+            8x7Vk6PvP/8xOGHhf8PuqaXAYskSyJPuBu+3Bo/PEj10devwc1J9uYWIDCGdKKPy
+            bSlnQHqUPBBPM30YX1YN
+        """))
+        recipient_prv_raw = hexdec("0DC8DC1FF2BC114BABC3F1CA8C51E4F58610427E197B1C2FBDBA4AE58CBFB7CE")[::-1]
+        recipient_prv = gost3410.prv_unmarshal(recipient_prv_raw)
+        recipient_cert = Certificate().decod(b64decode("""
+            MIIB6jCCAZegAwIBAgIEAYy6gzAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2
+            MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw
+            MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA6MQ0wCwYDVQQKEwRUSzI2MSkwJwYD
+            VQQDEyBSRUNJUElFTlQ6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdDBeMBcGCCqFAwcB
+            AQEBMAsGCSqFAwcBAgEBAQNDAARAvyeCGXMsYwpYe5aE0w8w3m4vpKQapGInqpnF
+            lv7h08psFP0s1W80q3BR534F4TmR+o5+iU+AW6ycvWuc73JEQ6OBhzCBhDBjBgNV
+            HSMEXDBagBSsbA5MRGaiQpbinwk7JWbzy6RTKqE8pDowODENMAsGA1UEChMEVEsy
+            NjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYtYml0ggQBjLqB
+            MB0GA1UdDgQWBBQ35gHPN1bx8l2eEMTbrtIg+5MU0TAKBggqhQMHAQEDAgNBABF2
+            RHDaRqQuBS2yu7yGIGFgA6c/LG4GKjSOwYsRVmXJNNkQ4TB7PB8j3q7gx2koPsVB
+            m90WfMWSL6SNSh3muuM=
+        """))
+        self.assertTrue(gost3410.verify(
+            self.ca_curve,
+            self.ca_pub,
+            GOST34112012256(sender_cert["tbsCertificate"].encode()).digest()[::-1],
+            bytes(sender_cert["signatureValue"]),
+        ))
+        self.assertTrue(gost3410.verify(
+            self.ca_curve,
+            self.ca_pub,
+            GOST34112012256(recipient_cert["tbsCertificate"].encode()).digest()[::-1],
+            bytes(recipient_cert["signatureValue"]),
+        ))
+
+        pfx_raw = b64decode("""
+            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
+            kxFG6QUFH8uuoX8=
+        """)
+        pfx = PFX().decod(pfx_raw)
+        self.assertEqual(pfx["authSafe"]["contentType"], id_signedData)
+
+        sd = SignedData().decod(bytes(pfx["authSafe"]["content"]))
+        self.assertEqual(sd["certificates"][0]["certificate"], sender_cert)
+        si = sd["signerInfos"][0]
+        self.assertEqual(
+            si["digestAlgorithm"]["algorithm"],
+            id_tc26_gost3411_2012_256,
+        )
+        digest = [
+            bytes(attr["attrValues"][0].defined[1]) for attr in si["signedAttrs"]
+            if attr["attrType"] == id_messageDigest
+        ][0]
+        sender_pub = gost3410.pub_unmarshal(bytes(OctetString().decod(bytes(
+            sender_cert["tbsCertificate"]["subjectPublicKeyInfo"]["subjectPublicKey"]
+        ))))
+        content = bytes(sd["encapContentInfo"]["eContent"])
+        self.assertSequenceEqual(digest, GOST34112012256(content).digest())
+        self.assertTrue(gost3410.verify(
+            curve,
+            sender_pub,
+            GOST34112012256(
+                SignedAttributes(si["signedAttrs"]).encode()
+            ).digest()[::-1],
+            bytes(si["signature"]),
+        ))
+
+        outer_safe_contents = SafeContents().decod(content)
+
+        safe_bag = outer_safe_contents[0]
+        self.assertEqual(safe_bag["bagId"], id_data)
+        safe_contents = OctetStringSafeContents().decod(bytes(safe_bag["bagValue"]))
+        safe_bag = safe_contents[0]
+        self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_certBag)
+        cert_bag = CertBag().decod(bytes(safe_bag["bagValue"]))
+        self.assertEqual(cert_bag["certId"], id_pkcs9_certTypes_x509Certificate)
+        _, cert = cert_bag["certValue"].defined
+        self.assertEqual(Certificate(cert), self.cert_test)
+
+        safe_bag = outer_safe_contents[1]
+        self.assertEqual(safe_bag["bagId"], id_envelopedData)
+        ed = EnvelopedData().decod(bytes(safe_bag["bagValue"]))
+        kari = ed["recipientInfos"][0]["kari"]
+        ukm = bytes(kari["ukm"])
+        self.assertEqual(
+            kari["keyEncryptionAlgorithm"]["algorithm"],
+            id_gostr3412_2015_kuznyechik_wrap_kexp15,
+        )
+        self.assertEqual(
+            kari["keyEncryptionAlgorithm"]["parameters"].defined[1]["algorithm"],
+            id_tc26_agreement_gost3410_2012_256,
+        )
+        kexp = bytes(kari["recipientEncryptedKeys"][0]["encryptedKey"])
+        keymat = keg(curve, recipient_prv, sender_pub, ukm)
+        kim, kek = keymat[:KEYSIZE], keymat[KEYSIZE:]
+        cek = kimp15(
+            GOST3412Kuznechik(kek).encrypt,
+            GOST3412Kuznechik(kim).encrypt,
+            GOST3412Kuznechik.blocksize,
+            kexp,
+            ukm[24:24 + GOST3412Kuznechik.blocksize // 2],
+        )
+        eci = ed["encryptedContentInfo"]
+        self.assertEqual(
+            eci["contentEncryptionAlgorithm"]["algorithm"],
+            id_gostr3412_2015_kuznyechik_ctracpkm,
+        )
+        eci_ukm = bytes(
+            eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"]
+        )
+        content = ctr_acpkm(
+            GOST3412Kuznechik,
+            GOST3412Kuznechik(cek).encrypt,
+            256 * 1024,
+            GOST3412Kuznechik.blocksize,
+            bytes(eci["encryptedContent"]),
+            eci_ukm[:GOST3412Kuznechik.blocksize // 2],
+        )
+
+        safe_contents = SafeContents().decod(content)
+        safe_bag = safe_contents[0]
+        self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_keyBag)
+        KeyBag().decod(bytes(safe_bag["bagValue"]))
+        self.assertSequenceEqual(bytes(safe_bag["bagValue"]), self.prv_test_raw)