# coding: utf-8
# PyGOST -- Pure Python GOST cryptographic functions library
-# Copyright (C) 2015-2019 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2015-2023 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
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"""
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"]),
)
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,
)
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
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,
)
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,
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"""
+ 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("""
+ 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("""
+ MIIKVwIBAzCCClAGCSqGSIb3DQEHAqCCCkEwggo9AgEBMQwwCgYIKoUDBwEBAgIw
+ ggcjBgkqhkiG9w0BBwGgggcUBIIHEDCCBwwwggKdBgkqhkiG9w0BBwGgggKOBIIC
+ ijCCAoYwggKCBgsqhkiG9w0BDAoBA6CCAkowggJGBgoqhkiG9w0BCRYBoIICNgSC
+ AjIwggIuMIIB26ADAgECAgQBjLqEMAoGCCqFAwcBAQMCMDgxDTALBgNVBAoTBFRL
+ MjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdDAeFw0w
+ MTAxMDEwMDAwMDBaFw00OTEyMzEwMDAwMDBaMDsxDTALBgNVBAoTBFRLMjYxKjAo
+ BgNVBAMTIU9SSUdJTkFUT1I6IEdPU1QgMzQuMTAtMTIgNTEyLWJpdDCBoDAXBggq
+ hQMHAQEBAjALBgkqhQMHAQIBAgEDgYQABIGAtIu3WrwpDhhlXGKhT7UtX1CETswd
+ H2AESHtLXJU0aWq3v6s0blUWqas8zvittSw6WFXwz7Nkqmtd2Tfk7PyVJb+faghQ
+ dnGKRcgf9JIePiu/cr8+6/PuFhNBJmX/E92nvydSaOsRrp3nB9fxuITLbPR2C58W
+ 8CQzDVRriB1eoM6jgYcwgYQwYwYDVR0jBFwwWoAUrGwOTERmokKW4p8JOyVm88uk
+ UyqhPKQ6MDgxDTALBgNVBAoTBFRLMjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1Qg
+ MzQuMTAtMTIgMjU2LWJpdIIEAYy6gTAdBgNVHQ4EFgQUfgZXCZgMrWsIqFfueQBY
+ OsnXoKQwCgYIKoUDBwEBAwIDQQAKXqnx0BumL0eT7eaAzIjRYiHXsiuWtKn+YHQX
+ tnMy3xdQPUPDPcmuufF5ed8y84DkF1Qn2ELIOAxUAaz8hwQQMSUwIwYJKoZIhvcN
+ AQkVMRYEFHlVdPnUtuTCAiQoaZhnP/AKFMBNMIIEZwYJKoZIhvcNAQcDoIIEWDCC
+ BFQCAQKgggHzoIIB7zCCAeswggGYoAMCAQICBAGMuoIwCgYIKoUDBwEBAwIwODEN
+ MAsGA1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAy
+ NTYtYml0MB4XDTAxMDEwMTAwMDAwMFoXDTQ5MTIzMTAwMDAwMFowOzENMAsGA1UE
+ ChMEVEsyNjEqMCgGA1UEAxMhT1JJR0lOQVRPUjogR09TVCAzNC4xMC0xMiAyNTYt
+ Yml0MF4wFwYIKoUDBwEBAQEwCwYJKoUDBwECAQEBA0MABECWKQ0TYllqg4GmY3tB
+ JiyzpXUN+aOV9WbmTUinqrmEHP7KCNzoAzFg+04SSQpNNSHpQnm+jLAZhuJaJfqZ
+ 6VbTo4GHMIGEMGMGA1UdIwRcMFqAFKxsDkxEZqJCluKfCTslZvPLpFMqoTykOjA4
+ MQ0wCwYDVQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEy
+ IDI1Ni1iaXSCBAGMuoEwHQYDVR0OBBYEFD8eUYHI5In4ZSZuP40HZG5t9K1UMAoG
+ CCqFAwcBAQMCA0EAOvMe1ZOj7z//MThh4X/D7qmlwGLJEsiT7gbvtwaPzxI9dHXr
+ 8HNSfbmFiAwhnSij8m0pZ0B6lDwQTzN9GF9WDTGB/6GB/AIBA6BCMEAwODENMAsG
+ A1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYt
+ Yml0AgQBjLqCoSIEIBt4fjey+k8C1D3OaMca8wl6h3j3C6OAbrx8rmxXktsQMBcG
+ CSqFAwcBAQcCATAKBggqhQMHAQEGATB2MHQwQDA4MQ0wCwYDVQQKEwRUSzI2MScw
+ JQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQCBAGMuoMEMJkp
+ Wae6IVfaY3mP0izRY7ifc41fATXdJ2tmTl+1vitkSE2vLCKXDLl90KfHA6gNmDCC
+ AVQGCSqGSIb3DQEHATAfBgkqhQMHAQEFAgEwEgQQFhEshEBO2LkAAAAAAAAAAICC
+ ASQYvLpT/8azEXJfekyGuyvE9UkVX+Ao8sfu9My/c4WAVRNMhZkCqD+BbPwBsIzN
+ sXZIi9rXGAfsPz7xaO9EUFZPjNOWtF/E01oJgG+gYLFn7qAiEFcmRLptSHuanNHn
+ 7Yol6IHushX4UaW9hEa/L6eFQx/hoDhrNZnWTXNZtNuHuMGC9dzhHhTxfkdjZYXD
+ v+M7psVj58JutE3U2d4pgxKcBPdMO4vl4+27cIKxQZFZU2zuCVJLYLqmPT5pCBkM
+ mJqy7bZwHOJ9kBq/TGUf8iJGYSCNre3RTNLbcTTk7rZrbiMkFsG3borzenpouS5E
+ BcCkBt8Mj0nvsMCu9ipHTuWww7LltlkXCjlNXFUi6ZI3VyHW5CDpghujQWiZxiAc
+ JuGl6GwZoIIB7zCCAeswggGYoAMCAQICBAGMuoIwCgYIKoUDBwEBAwIwODENMAsG
+ A1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYt
+ Yml0MB4XDTAxMDEwMTAwMDAwMFoXDTQ5MTIzMTAwMDAwMFowOzENMAsGA1UEChME
+ VEsyNjEqMCgGA1UEAxMhT1JJR0lOQVRPUjogR09TVCAzNC4xMC0xMiAyNTYtYml0
+ MF4wFwYIKoUDBwEBAQEwCwYJKoUDBwECAQEBA0MABECWKQ0TYllqg4GmY3tBJiyz
+ pXUN+aOV9WbmTUinqrmEHP7KCNzoAzFg+04SSQpNNSHpQnm+jLAZhuJaJfqZ6VbT
+ o4GHMIGEMGMGA1UdIwRcMFqAFKxsDkxEZqJCluKfCTslZvPLpFMqoTykOjA4MQ0w
+ CwYDVQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1
+ Ni1iaXSCBAGMuoEwHQYDVR0OBBYEFD8eUYHI5In4ZSZuP40HZG5t9K1UMAoGCCqF
+ AwcBAQMCA0EAOvMe1ZOj7z//MThh4X/D7qmlwGLJEsiT7gbvtwaPzxI9dHXr8HNS
+ fbmFiAwhnSij8m0pZ0B6lDwQTzN9GF9WDTGCAQ4wggEKAgEBMEAwODENMAsGA1UE
+ ChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYtYml0
+ AgQBjLqCMAoGCCqFAwcBAQICoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc
+ BgkqhkiG9w0BCQUxDxcNMjEwNDE0MTkyMTEyWjAvBgkqhkiG9w0BCQQxIgQg1XOA
+ zNa710QuXsn5+yIf3cNTiFOQMgTiBRJBz8Tr4I0wCgYIKoUDBwEBAQEEQALINal9
+ 7wHXYiG+w0yzSkKOs0jRZew0S73r/cfk/sUoM3HKKIEbKruvlAdiOqX/HLFSEx/s
+ 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)