# coding: utf-8
# PyGOST -- Pure Python GOST cryptographic functions library
-# Copyright (C) 2015-2020 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
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:
JSg6M8RBUYpw/8ym5ou1o2nDa09M5zF3BCCpzyCQBI+rzfISeKvPV1ROfcXiYU93
mwcl1xQV2G5/fgICB9A=
""")
- password = u'Пароль для PFX'
+ password = u"Пароль для PFX"
def test_shrouded_key_bag(self):
private_key_info_expected = b64decode(b"""
_, 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,
).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 = "Пароль для 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"""
+ MIIFKwIBAzCCBMQGCSqGSIb3DQEHAaCCBLUEggSxMIIErTCCAswGCSqGSIb3DQEH
+ AaCCAr0EggK5MIICtTCCArEGCyqGSIb3DQEMCgEDoIICSjCCAkYGCiqGSIb3DQEJ
+ FgGgggI2BIICMjCCAi4wggHboAMCAQICBAGMuoQwCgYIKoUDBwEBAwIwODENMAsG
+ A1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYt
+ Yml0MB4XDTAxMDEwMTAwMDAwMFoXDTQ5MTIzMTAwMDAwMFowOzENMAsGA1UEChME
+ VEsyNjEqMCgGA1UEAxMhT1JJR0lOQVRPUjogR09TVCAzNC4xMC0xMiA1MTItYml0
+ MIGgMBcGCCqFAwcBAQECMAsGCSqFAwcBAgECAQOBhAAEgYC0i7davCkOGGVcYqFP
+ tS1fUIROzB0fYARIe0tclTRpare/qzRuVRapqzzO+K21LDpYVfDPs2Sqa13ZN+Ts
+ /JUlv59qCFB2cYpFyB/0kh4+K79yvz7r8+4WE0EmZf8T3ae/J1Jo6xGunecH1/G4
+ hMts9HYLnxbwJDMNVGuIHV6gzqOBhzCBhDBjBgNVHSMEXDBagBSsbA5MRGaiQpbi
+ nwk7JWbzy6RTKqE8pDowODENMAsGA1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsy
+ NjogR09TVCAzNC4xMC0xMiAyNTYtYml0ggQBjLqBMB0GA1UdDgQWBBR+BlcJmAyt
+ awioV+55AFg6ydegpDAKBggqhQMHAQEDAgNBAApeqfHQG6YvR5Pt5oDMiNFiIdey
+ K5a0qf5gdBe2czLfF1A9Q8M9ya658Xl53zLzgOQXVCfYQsg4DFQBrPyHBBAxVDAj
+ BgkqhkiG9w0BCRUxFgQUeVV0+dS25MICJChpmGc/8AoUwE0wLQYJKoZIhvcNAQkU
+ MSAeHgBwADEAMgBGAHIAaQBlAG4AZABsAHkATgBhAG0AZTCCAdkGCSqGSIb3DQEH
+ AaCCAcoEggHGMIIBwjCCAb4GCyqGSIb3DQEMCgECoIIBVzCCAVMwWQYJKoZIhvcN
+ AQUNMEwwKQYJKoZIhvcNAQUMMBwECKf4N7NMwugqAgIIADAMBggqhQMHAQEEAgUA
+ MB8GCSqFAwcBAQUCAjASBBAlmt2WDfaPJlsAs0mLKglzBIH1DMvEacbbWRNDVSnX
+ JLWygYrKoipdOjDA/2HEnBZ34uFOLNheUqiKpCPoFpbR2GBiVYVTVK9ibiczgaca
+ EQYzDXtcS0QCZOxpKWfteAlbdJLC/SqPurPYyKi0MVRUPROhbisFASDT38HDH1Dh
+ 0dL5f6ga4aPWLrWbbgWERFOoOPyh4DotlPF37AQOwiEjsbyyRHq3HgbWiaxQRuAh
+ eqHOn4QVGY92/HFvJ7u3TcnQdLWhTe/lh1RHLNF3RnXtN9if9zC23laDZOiWZplU
+ yLrUiTCbHrtn1RppPDmLFNMt9dJ7KKgCkOi7Zm5nhqPChbywX13wcfYxVDAjBgkq
+ hkiG9w0BCRUxFgQUeVV0+dS25MICJChpmGc/8AoUwE0wLQYJKoZIhvcNAQkUMSAe
+ HgBwADEAMgBGAHIAaQBlAG4AZABsAHkATgBhAG0AZTBeME4wCgYIKoUDBwEBAgME
+ QAkBKw4ihn7pSIYTEhu0bcvTPZjI3WgVxCkUVlOsc80G69EKFEOTnObGJGSKJ51U
+ KkOsXF0a7+VBZf3BcVVQh9UECIVEtO+VpuskAgIIAA==
+ """)
+ 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"""
+ MIIFjAIBAzCCBSUGCSqGSIb3DQEHAaCCBRYEggUSMIIFDjCCA0EGCSqGSIb3DQEH
+ BqCCAzIwggMuAgEAMIIDJwYJKoZIhvcNAQcBMFUGCSqGSIb3DQEFDTBIMCkGCSqG
+ SIb3DQEFDDAcBAgUuSVGsSwGjQICCAAwDAYIKoUDBwEBBAIFADAbBgkqhQMHAQEF
+ AQIwDgQM9Hk3dagtS48+G/x+gIICwWGPqxxN+sTrKbruRf9R5Ya9cf5AtO1frqMn
+ f1eULfmZmTg/BdE51QQ+Vbnh3v1kmspr6h2+e4Wli+ndEeCWG6A6X/G22h/RAHW2
+ YrVmf6cCWxW+YrqzT4h/8RQL/9haunD5LmHPLVsYrEai0OwbgXayDSwARVJQLQYq
+ sLNmZK5ViN+fRiS5wszVJ3AtVq8EuPt41aQEKwPy2gmH4S6WmnQRC6W7aoqmIifF
+ PJENJNn5K2M1J6zNESs6bFtYNKMArNqtvv3rioY6eAaaLy6AV6ljsekmqodHmQjv
+ Y4eEioJs0xhpXhZY69PXT+ZBeHv6MSheBhwXqxAd1DqtPTafMjNK8rqKCap9TtPG
+ vONvo5W9dgwegxRRQzlum8dzV4m1W9Aq4W7t8/UcxDWRz3k6ijFPlGaA9+8ZMTEO
+ RHhBRvM6OY2/VNNxbgxWfGYuPxpSi3YnCZIPmBEe5lU/Xv7KjzFusGM38F8YR61k
+ 4/QNpKI1QUv714YKfaUQznshGGzILv1NGID62pl1+JI3vuawi2mDMrmkuM9QFU9v
+ /kRP+c2uBHDuOGEUUSNhF08p7+w3vxplatGWXH9fmIsPBdk2f3wkn+rwoqrEuijM
+ I/bCAylU/M0DMKhAo9j31UYSZdi4fsfRWYDJMq/8FPn96tuo+oCpbqv3NUwpZM/8
+ Li4xqgTHtYw/+fRG0/P6XadNEiII/TYjenLfVHXjAHOVJsVeCu/t3EsMYHQddNCh
+ rFk/Ic2PdIQOyB4/enpW0qrKegSbyZNuF1WI4zl4mI89L8dTQBUkhy45yQXZlDD8
+ k1ErYdtdEsPtz/4zuSpbnmwCEIRoOuSXtGuJP+tbcWEXRKM2UBgi3qBjpn7DU18M
+ tsrRM9pDdadl8mT/Vfh9+B8dZBZVxgQu70lMPEGexbUkYHuFCCnyi9J0V92StbIz
+ Elxla1VebjCCAcUGCSqGSIb3DQEHAaCCAbYEggGyMIIBrjCCAaoGCyqGSIb3DQEM
+ CgECoIIBQzCCAT8wVQYJKoZIhvcNAQUNMEgwKQYJKoZIhvcNAQUMMBwECP0EQk0O
+ 1twvAgIIADAMBggqhQMHAQEEAgUAMBsGCSqFAwcBAQUBATAOBAzwxSqgAAAAAAAA
+ AAAEgeUqj9mI3RDfK5hMd0EeYws7foZK/5ANr2wUhP5qnDjAZgn76lExJ+wuvlnS
+ 9PChfWVugvdl/9XJgQvvr9Cu4pOh4ICXplchcy0dGk/MzItHRVC5wK2nTxwQ4kKT
+ kG9xhLFzoD16dhtqX0+/dQg9G8pE5EzCBIYRXLm1Arcz9k7KVsTJuNMjFrr7EQuu
+ Tr80ATSQOtsq50zpFyrpznVPGCrOdIjpymZxNdvw48bZxqTtRVDxCYATOGqz0pwH
+ ClWULHD9LIajLMB2GhBKyQw6ujIlltJs0T+WNdX/AT2FLi1LFSS3+Cj9MVQwIwYJ
+ KoZIhvcNAQkVMRYEFHlVdPnUtuTCAiQoaZhnP/AKFMBNMC0GCSqGSIb3DQEJFDEg
+ Hh4AcAAxADIARgByAGkAZQBuAGQAbAB5AE4AYQBtAGUwXjBOMAoGCCqFAwcBAQID
+ BEDp4e22JmXdnvR0xA99yQuzQuJ8pxBeOpsLm2dZQqt3Fje5zqW1uk/7VOcfV5r2
+ bKm8nsLOs2rPT8hBOoeAZvOIBAjGIUHw6IjG2QICCAA=
+ """)
+ 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("""
+ MIIB6zCCAZigAwIBAgIEAYy6gjAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2MS
+ cwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEwMTAx
+ MDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRUSzI2MSowKAYDVQQDEy
+ FPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwXjAXBggqhQMHAQEBATAL
+ BgkqhQMHAQIBAQEDQwAEQJYpDRNiWWqDgaZje0EmLLOldQ35o5X1ZuZNSKequYQc/s
+ oI3OgDMWD7ThJJCk01IelCeb6MsBmG4lol+pnpVtOjgYcwgYQwYwYDVR0jBFwwWoAU
+ rGwOTERmokKW4p8JOyVm88ukUyqhPKQ6MDgxDTALBgNVBAoTBFRLMjYxJzAlBgNVBA
+ MTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdIIEAYy6gTAdBgNVHQ4EFgQU
+ Px5RgcjkifhlJm4/jQdkbm30rVQwCgYIKoUDBwEBAwIDQQA68x7Vk6PvP/8xOGHhf8
+ PuqaXAYskSyJPuBu+3Bo/PEj10devwc1J9uYWIDCGdKKPybSlnQHqUPBBPM30YX1YN
+ """))
+ recipient_prv_raw = hexdec("0DC8DC1FF2BC114BABC3F1CA8C51E4F58610427E197B1C2FBDBA4AE58CBFB7CE")[::-1]
+ recipient_prv = gost3410.prv_unmarshal(recipient_prv_raw)
+ recipient_cert = Certificate().decod(b64decode("""
+ MIIB6jCCAZegAwIBAgIEAYy6gzAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2MS
+ cwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEwMTAx
+ MDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA6MQ0wCwYDVQQKEwRUSzI2MSkwJwYDVQQDEy
+ BSRUNJUElFTlQ6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdDBeMBcGCCqFAwcBAQEBMAsG
+ CSqFAwcBAgEBAQNDAARAvyeCGXMsYwpYe5aE0w8w3m4vpKQapGInqpnFlv7h08psFP
+ 0s1W80q3BR534F4TmR+o5+iU+AW6ycvWuc73JEQ6OBhzCBhDBjBgNVHSMEXDBagBSs
+ bA5MRGaiQpbinwk7JWbzy6RTKqE8pDowODENMAsGA1UEChMEVEsyNjEnMCUGA1UEAx
+ MeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYtYml0ggQBjLqBMB0GA1UdDgQWBBQ3
+ 5gHPN1bx8l2eEMTbrtIg+5MU0TAKBggqhQMHAQEDAgNBABF2RHDaRqQuBS2yu7yGIG
+ FgA6c/LG4GKjSOwYsRVmXJNNkQ4TB7PB8j3q7gx2koPsVBm90WfMWSL6SNSh3muuM=
+ """))
+ 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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
+ e6qgRVHn9b+Mq3sp57LxHk4Sny0zV8TRwCr8
+ """)
+ 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)