From 69854a17b5ed9e7970acf34af1f478d8b27ef21e Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Thu, 25 Mar 2021 21:15:21 +0300 Subject: [PATCH] Draft update PKCS#12 test vectors --- pygost/asn1schemas/cms.py | 23 +- pygost/asn1schemas/oids.py | 4 + pygost/asn1schemas/pfx.py | 38 ++- pygost/asn1schemas/prvkey.py | 27 ++ pygost/test_pfx.py | 499 +++++++++++++++++++++++++++++++++++ 5 files changed, 589 insertions(+), 2 deletions(-) diff --git a/pygost/asn1schemas/cms.py b/pygost/asn1schemas/cms.py index 747b984..c57f6f0 100644 --- a/pygost/asn1schemas/cms.py +++ b/pygost/asn1schemas/cms.py @@ -307,10 +307,31 @@ class UnprotectedAttributes(SetOf): bounds = (1, float("+inf")) +class CertificateChoices(Choice): + schema = ( + ("certificate", Certificate()), + # ("extendedCertificate", OctetString(impl=tag_ctxp(0))), + # ("v1AttrCert", AttributeCertificateV1(impl=tag_ctxc(1))), # V1 is osbolete + # ("v2AttrCert", AttributeCertificateV2(impl=tag_ctxc(2))), + # ("other", OtherCertificateFormat(impl=tag_ctxc(3))), + ) + + +class CertificateSet(SetOf): + schema = CertificateChoices() + + +class OriginatorInfo(Sequence): + schema = ( + ("certs", CertificateSet(impl=tag_ctxc(0), optional=True)), + # ("crls", RevocationInfoChoices(impl=tag_ctxc(1), optional=True)), + ) + + class EnvelopedData(Sequence): schema = ( ("version", CMSVersion()), - # ("originatorInfo", OriginatorInfo(impl=tag_ctxc(0), optional=True)), + ("originatorInfo", OriginatorInfo(impl=tag_ctxc(0), optional=True)), ("recipientInfos", RecipientInfos()), ("encryptedContentInfo", EncryptedContentInfo()), ("unprotectedAttrs", UnprotectedAttributes(impl=tag_ctxc(1), optional=True)), diff --git a/pygost/asn1schemas/oids.py b/pygost/asn1schemas/oids.py index d5b9031..b1a178f 100644 --- a/pygost/asn1schemas/oids.py +++ b/pygost/asn1schemas/oids.py @@ -17,6 +17,10 @@ id_encryptedData = id_pkcs7 + (6,) id_pkcs9 = ObjectIdentifier("1.2.840.113549.1.9") id_contentType = id_pkcs9 + (3,) id_messageDigest = id_pkcs9 + (4,) +id_pkcs9_certTypes_x509Certificate = ObjectIdentifier("1.2.840.113549.1.9.22.1") +id_pkcs12_bagtypes_keyBag = ObjectIdentifier("1.2.840.113549.1.12.10.1.1") +id_pkcs12_bagtypes_pkcs8ShroudedKeyBag = ObjectIdentifier("1.2.840.113549.1.12.10.1.2") +id_pkcs12_bagtypes_certBag = ObjectIdentifier("1.2.840.113549.1.12.10.1.3") id_Gost28147_89 = ObjectIdentifier("1.2.643.2.2.21") id_GostR3410_2001_TestParamSet = ObjectIdentifier("1.2.643.2.2.35.0") diff --git a/pygost/asn1schemas/pfx.py b/pygost/asn1schemas/pfx.py index ecbf7b7..8b9a422 100644 --- a/pygost/asn1schemas/pfx.py +++ b/pygost/asn1schemas/pfx.py @@ -30,12 +30,20 @@ from pyderasn import tag_ctxp from pygost.asn1schemas.cms import CMSVersion from pygost.asn1schemas.cms import ContentType from pygost.asn1schemas.cms import Gost2814789Parameters +from pygost.asn1schemas.cms import Gost341215EncryptionParameters from pygost.asn1schemas.oids import id_data from pygost.asn1schemas.oids import id_encryptedData from pygost.asn1schemas.oids import id_Gost28147_89 +from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm +from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm_omac +from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm +from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm_omac from pygost.asn1schemas.oids import id_pbes2 from pygost.asn1schemas.oids import id_pbkdf2 +from pygost.asn1schemas.oids import id_pkcs9_certTypes_x509Certificate +from pygost.asn1schemas.prvkey import PrivateKeyInfo from pygost.asn1schemas.x509 import AlgorithmIdentifier +from pygost.asn1schemas.x509 import Certificate class PBKDF2Salt(Choice): @@ -84,7 +92,13 @@ class PBES2KDFs(AlgorithmIdentifier): class PBES2Encs(AlgorithmIdentifier): schema = ( ("algorithm", ObjectIdentifier(defines=( - (("parameters",), {id_Gost28147_89: Gost2814789Parameters()}), + (("parameters",), { + id_Gost28147_89: Gost2814789Parameters(), + id_gostr3412_2015_magma_ctracpkm: Gost341215EncryptionParameters(), + id_gostr3412_2015_magma_ctracpkm_omac: Gost341215EncryptionParameters(), + id_gostr3412_2015_kuznyechik_ctracpkm: Gost341215EncryptionParameters(), + id_gostr3412_2015_kuznyechik_ctracpkm_omac: Gost341215EncryptionParameters(), + }), ))), ("parameters", Any(optional=True)), ) @@ -212,3 +226,25 @@ class EncryptedPrivateKeyInfo(Sequence): class PKCS8ShroudedKeyBag(EncryptedPrivateKeyInfo): pass + + +OctetStringX509Certificate = Certificate(expl=OctetString.tag_default) + + +class CertTypes(Any): + pass + + +class CertBag(Sequence): + schema = ( + ("certId", ObjectIdentifier(defines=( + (("certValue",), { + id_pkcs9_certTypes_x509Certificate: OctetStringX509Certificate(), + }), + ))), + ("certValue", CertTypes(expl=tag_ctxc(0))), + ) + + +class KeyBag(PrivateKeyInfo): + pass diff --git a/pygost/asn1schemas/prvkey.py b/pygost/asn1schemas/prvkey.py index 60da749..6b9c493 100644 --- a/pygost/asn1schemas/prvkey.py +++ b/pygost/asn1schemas/prvkey.py @@ -23,6 +23,8 @@ from pyderasn import ObjectIdentifier from pyderasn import OctetString from pyderasn import Sequence from pyderasn import tag_ctxc +from pyderasn import tag_ctxp +from pyderasn import SetOf from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256 from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512 @@ -65,9 +67,34 @@ class PrivateKey(OctetString): pass +class AttributeValue(Any): + pass + + +class AttributeValues(SetOf): + schema = AttributeValue() + + +class Attribute(Sequence): + schema = ( + ("attrType", ObjectIdentifier()), + ("attrValues", AttributeValues()), + ) + + +class Attributes(SetOf): + schema = Attribute() + + +class PublicKey(BitString): + pass + + class PrivateKeyInfo(Sequence): schema = ( ("version", Integer(0)), ("privateKeyAlgorithm", PrivateKeyAlgorithmIdentifier()), ("privateKey", PrivateKey()), + ("attributes", Attributes(impl=tag_ctxc(0), optional=True)), + ("publicKey", PublicKey(impl=tag_ctxp(1), optional=True)), ) diff --git a/pygost/test_pfx.py b/pygost/test_pfx.py index 7702508..e9d8694 100644 --- a/pygost/test_pfx.py +++ b/pygost/test_pfx.py @@ -19,16 +19,49 @@ 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: @@ -176,3 +209,469 @@ ATAMBggqhQMHAQEDAgUAA0EA9oq0Vvk8kkgIwkp0x0J5eKtia4MNTiwKAm7jgnCZIx3O98BThaTX ).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) -- 2.44.0