]> Cypherpunks.ru repositories - pygost.git/commitdiff
More TC26 ASN.1 test vectors 3.15
authorSergey Matveev <stargrave@stargrave.org>
Sun, 9 Dec 2018 13:16:28 +0000 (16:16 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sun, 9 Dec 2018 18:17:31 +0000 (21:17 +0300)
12 files changed:
NEWS
news.texi
pygost/Makefile
pygost/asn1schemas/__init__.py [new file with mode: 0644]
pygost/asn1schemas/cms.py [new file with mode: 0644]
pygost/asn1schemas/pfx.py [new file with mode: 0644]
pygost/asn1schemas/x509.py [new file with mode: 0644]
pygost/test_cms.py [new file with mode: 0644]
pygost/test_cms_enveloped.py [deleted file]
pygost/test_pfx.py [new file with mode: 0644]
pygost/test_x509.py [new file with mode: 0644]
setup.py

diff --git a/NEWS b/NEWS
index d5195157d2e4ca6bf126d0bb50415c3f501bb3f6..92f8f820db68c1948e66143826fdb80c7bd7f186 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,7 @@
 3.15:
     * Licence changed back to GNU GPLv3+.
       GNU LGPLv3+ licenced versions are not available anymore
+    * More ASN.1-based test vectors (PyDERASN dependency required)
 
 3.14:
     Add missing typing stubs related to previous release.
index fb7b55b45c105a51853400ca8273967e611d88fb..9313b54d0f6edf1be0e0189ea6ee55eefbdd8a80 100644 (file)
--- a/news.texi
+++ b/news.texi
@@ -8,6 +8,8 @@
     @itemize
     @item Licence changed back to GNU GPLv3+. GNU LGPLv3+ licenced
         versions are not available anymore
+    @item More ASN.1-based test vectors
+        (`PyDERASN <http://pyderasn.cypherpunks.ru/>`__ dependency required)
     @end itemize
 
 @anchor{Release 3.14}
index 33ebd2a6cd85ddb343075770ff029a57b863a85f..6194911cb1638731163d361182afcaf8c2158a1f 100644 (file)
@@ -10,4 +10,6 @@ test:
        PYTHONPATH=$(PYTHONPATH):.. $(PYTHON) -m unittest test_wrap
        PYTHONPATH=$(PYTHONPATH):.. $(PYTHON) -m unittest test_gost3412
        PYTHONPATH=$(PYTHONPATH):.. $(PYTHON) -m unittest test_gost3413
-       PYTHONPATH=$(PYTHONPATH):.. $(PYTHON) -m unittest test_cms_enveloped
+       PYTHONPATH=$(PYTHONPATH):.. $(PYTHON) -m unittest test_x509
+       PYTHONPATH=$(PYTHONPATH):.. $(PYTHON) -m unittest test_cms
+       PYTHONPATH=$(PYTHONPATH):.. $(PYTHON) -m unittest test_pfx
diff --git a/pygost/asn1schemas/__init__.py b/pygost/asn1schemas/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/pygost/asn1schemas/cms.py b/pygost/asn1schemas/cms.py
new file mode 100644 (file)
index 0000000..187850b
--- /dev/null
@@ -0,0 +1,278 @@
+# coding: utf-8
+# PyGOST -- Pure Python GOST cryptographic functions library
+# Copyright (C) 2015-2018 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.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""CMS related structures (**NOT COMPLETE**)
+"""
+
+from pyderasn import Any
+from pyderasn import BitString
+from pyderasn import Choice
+from pyderasn import Integer
+from pyderasn import ObjectIdentifier
+from pyderasn import OctetString
+from pyderasn import Sequence
+from pyderasn import SequenceOf
+from pyderasn import SetOf
+from pyderasn import tag_ctxc
+from pyderasn import tag_ctxp
+
+from pygost.asn1schemas.x509 import AlgorithmIdentifier
+from pygost.asn1schemas.x509 import SubjectPublicKeyInfo
+
+
+class CMSVersion(Integer):
+    pass
+
+
+class ContentType(ObjectIdentifier):
+    pass
+
+
+class RecipientIdentifier(Choice):
+    schema = (
+        ("issuerAndSerialNumber", Any()),
+        # ("subjectKeyIdentifier", SubjectKeyIdentifier(impl=tag_ctxp(0))),
+    )
+
+
+class KeyEncryptionAlgorithmIdentifier(AlgorithmIdentifier):
+    pass
+
+
+class EncryptedKey(OctetString):
+    pass
+
+
+class KeyTransRecipientInfo(Sequence):
+    schema = (
+        ("version", CMSVersion()),
+        ("rid", RecipientIdentifier()),
+        ("keyEncryptionAlgorithm", KeyEncryptionAlgorithmIdentifier()),
+        ("encryptedKey", EncryptedKey()),
+    )
+
+
+class OriginatorPublicKey(Sequence):
+    schema = (
+        ("algorithm", AlgorithmIdentifier()),
+        ("publicKey", BitString()),
+    )
+
+
+class OriginatorIdentifierOrKey(Choice):
+    schema = (
+        # ("issuerAndSerialNumber", IssuerAndSerialNumber()),
+        # ("subjectKeyIdentifier", SubjectKeyIdentifier(impl=tag_ctxp(0))),
+        ("originatorKey", OriginatorPublicKey(impl=tag_ctxc(1))),
+    )
+
+
+class UserKeyingMaterial(OctetString):
+    pass
+
+
+class KeyAgreeRecipientIdentifier(Choice):
+    schema = (
+        ("issuerAndSerialNumber", Any()),
+        # ("rKeyId", RecipientKeyIdentifier(impl=tag_ctxc(0))),
+    )
+
+
+class RecipientEncryptedKey(Sequence):
+    schema = (
+        ("rid", KeyAgreeRecipientIdentifier()),
+        ("encryptedKey", EncryptedKey()),
+    )
+
+
+class RecipientEncryptedKeys(SequenceOf):
+    schema = RecipientEncryptedKey()
+
+
+class KeyAgreeRecipientInfo(Sequence):
+    schema = (
+        ("version", CMSVersion(3)),
+        ("originator", OriginatorIdentifierOrKey(expl=tag_ctxc(0))),
+        ("ukm", UserKeyingMaterial(expl=tag_ctxc(1), optional=True)),
+        ("keyEncryptionAlgorithm", KeyEncryptionAlgorithmIdentifier()),
+        ("recipientEncryptedKeys", RecipientEncryptedKeys()),
+    )
+
+
+class RecipientInfo(Choice):
+    schema = (
+        ("ktri", KeyTransRecipientInfo()),
+        ("kari", KeyAgreeRecipientInfo(impl=tag_ctxc(1))),
+        # ("kekri", KEKRecipientInfo(impl=tag_ctxc(2))),
+        # ("pwri", PasswordRecipientInfo(impl=tag_ctxc(3))),
+        # ("ori", OtherRecipientInfo(impl=tag_ctxc(4))),
+    )
+
+
+class RecipientInfos(SetOf):
+    schema = RecipientInfo()
+    bounds = (1, float("+inf"))
+
+
+class ContentEncryptionAlgorithmIdentifier(AlgorithmIdentifier):
+    pass
+
+
+class EncryptedContent(OctetString):
+    pass
+
+
+class EncryptedContentInfo(Sequence):
+    schema = (
+        ("contentType", ContentType()),
+        ("contentEncryptionAlgorithm", ContentEncryptionAlgorithmIdentifier()),
+        ("encryptedContent", EncryptedContent(impl=tag_ctxp(0), optional=True)),
+    )
+
+
+class EnvelopedData(Sequence):
+    schema = (
+        ("version", CMSVersion()),
+        # ("originatorInfo", OriginatorInfo(impl=tag_ctxc(0), optional=True)),
+        ("recipientInfos", RecipientInfos()),
+        ("encryptedContentInfo", EncryptedContentInfo()),
+        # ("unprotectedAttrs", UnprotectedAttributes(impl=tag_ctxc(1), optional=True)),
+    )
+
+
+class ContentInfo(Sequence):
+    schema = (
+        ("contentType", ContentType()),
+        ("content", Any(expl=tag_ctxc(0))),
+    )
+
+
+class Gost2814789IV(OctetString):
+    bounds = (8, 8)
+
+
+class Gost2814789Parameters(Sequence):
+    schema = (
+        ("iv", Gost2814789IV()),
+        ("encryptionParamSet", ObjectIdentifier()),
+    )
+
+
+class Gost2814789Key(OctetString):
+    bounds = (32, 32)
+
+
+class Gost2814789MAC(OctetString):
+    bounds = (4, 4)
+
+
+class Gost2814789EncryptedKey(Sequence):
+    schema = (
+        ("encryptedKey", Gost2814789Key()),
+        ("maskKey", Gost2814789Key(impl=tag_ctxp(0), optional=True)),
+        ("macKey", Gost2814789MAC()),
+    )
+
+
+class GostR34102001TransportParameters(Sequence):
+    schema = (
+        ("encryptionParamSet", ObjectIdentifier()),
+        ("ephemeralPublicKey", SubjectPublicKeyInfo(
+            impl=tag_ctxc(0),
+            optional=True,
+        )),
+        ("ukm", OctetString()),
+    )
+
+
+class GostR3410KeyTransport(Sequence):
+    schema = (
+        ("sessionEncryptedKey", Gost2814789EncryptedKey()),
+        ("transportParameters", GostR34102001TransportParameters(
+            impl=tag_ctxc(0),
+            optional=True,
+        )),
+    )
+
+
+class EncapsulatedContentInfo(Sequence):
+    schema = (
+        ("eContentType", ContentType()),
+        ("eContent", OctetString(expl=tag_ctxc(0), optional=True)),
+    )
+
+
+class SignerIdentifier(Choice):
+    schema = (
+        ("issuerAndSerialNumber", Any()),
+        # ("subjectKeyIdentifier", SubjectKeyIdentifier(impl=tag_ctxp(0))),
+    )
+
+
+class DigestAlgorithmIdentifiers(SetOf):
+    schema = AlgorithmIdentifier()
+
+
+class DigestAlgorithmIdentifier(AlgorithmIdentifier):
+    pass
+
+
+class SignatureAlgorithmIdentifier(AlgorithmIdentifier):
+    pass
+
+
+class SignatureValue(OctetString):
+    pass
+
+
+class SignerInfo(Sequence):
+    schema = (
+        ("version", CMSVersion()),
+        ("sid", SignerIdentifier()),
+        ("digestAlgorithm", DigestAlgorithmIdentifier()),
+        # ("signedAttrs", SignedAttributes(impl=tag_ctxc(0), optional=True)),
+        ("signatureAlgorithm", SignatureAlgorithmIdentifier()),
+        ("signature", SignatureValue()),
+        # ("unsignedAttrs", UnsignedAttributes(impl=tag_ctxc(1), optional=True)),
+    )
+
+
+class SignerInfos(SetOf):
+    schema = SignerInfo()
+
+
+class SignedData(Sequence):
+    schema = (
+        ("version", CMSVersion()),
+        ("digestAlgorithms", DigestAlgorithmIdentifiers()),
+        ("encapContentInfo", EncapsulatedContentInfo()),
+        # ("certificates", CertificateSet(impl=tag_ctxc(0), optional=True)),
+        # ("crls", RevocationInfoChoices(impl=tag_ctxc(1), optional=True)),
+        ("signerInfos", SignerInfos()),
+    )
+
+
+class Digest(OctetString):
+    pass
+
+
+class DigestedData(Sequence):
+    schema = (
+        ("version", CMSVersion()),
+        ("digestAlgorithm", DigestAlgorithmIdentifier()),
+        ("encapContentInfo", EncapsulatedContentInfo()),
+        ("digest", Digest()),
+    )
diff --git a/pygost/asn1schemas/pfx.py b/pygost/asn1schemas/pfx.py
new file mode 100644 (file)
index 0000000..790cc18
--- /dev/null
@@ -0,0 +1,196 @@
+# coding: utf-8
+# PyGOST -- Pure Python GOST cryptographic functions library
+# Copyright (C) 2015-2018 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.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""PKCS #12 related structures (**NOT COMPLETE**)
+"""
+
+from pyderasn import Any
+from pyderasn import Choice
+from pyderasn import Integer
+from pyderasn import ObjectIdentifier
+from pyderasn import OctetString
+from pyderasn import Sequence
+from pyderasn import SequenceOf
+from pyderasn import SetOf
+from pyderasn import tag_ctxc
+from pyderasn import tag_ctxp
+
+from pygost.asn1schemas.cms import CMSVersion
+from pygost.asn1schemas.cms import ContentType
+from pygost.asn1schemas.x509 import AlgorithmIdentifier
+
+
+class EncryptionAlgorithmIdentifier(AlgorithmIdentifier):
+    schema = (
+        ("algorithm", ObjectIdentifier()),
+        ("parameters", Any(optional=True)),
+    )
+
+
+class ContentEncryptionAlgorithmIdentifier(EncryptionAlgorithmIdentifier):
+    pass
+
+
+class PBES2KDFs(AlgorithmIdentifier):
+    schema = (
+        ("algorithm", ObjectIdentifier()),
+        ("parameters", Any(optional=True)),
+    )
+
+
+class PBES2Encs(AlgorithmIdentifier):
+    schema = (
+        ("algorithm", ObjectIdentifier()),
+        ("parameters", Any(optional=True)),
+    )
+
+
+class PBES2Params(Sequence):
+    schema = (
+        ("keyDerivationFunc", PBES2KDFs()),
+        ("encryptionScheme", PBES2Encs()),
+    )
+
+
+class EncryptedContent(OctetString):
+    pass
+
+
+class EncryptedContentInfo(Sequence):
+    schema = (
+        ("contentType", ContentType()),
+        ("contentEncryptionAlgorithm", ContentEncryptionAlgorithmIdentifier()),
+        ("encryptedContent", EncryptedContent(impl=tag_ctxp(0), optional=True)),
+    )
+
+
+class EncryptedData(Sequence):
+    schema = (
+        ("version", CMSVersion()),
+        ("encryptedContentInfo", EncryptedContentInfo()),
+        # ("unprotectedAttrs", UnprotectedAttributes(impl=tag_ctxc(1), optional=True)),
+    )
+
+
+class PKCS12BagSet(Any):
+    pass
+
+
+class AttrValue(SetOf):
+    schema = Any()
+
+
+class PKCS12Attribute(Sequence):
+    schema = (
+        ("attrId", ObjectIdentifier()),
+        ("attrValue", AttrValue()),
+    )
+
+
+class PKCS12Attributes(SetOf):
+    schema = PKCS12Attribute()
+
+
+class SafeBag(Sequence):
+    schema = (
+        ("bagId", ObjectIdentifier()),
+        ("bagValue", PKCS12BagSet(expl=tag_ctxc(0))),
+        ("bagAttributes", PKCS12Attributes(optional=True)),
+    )
+
+
+class SafeContents(SequenceOf):
+    schema = SafeBag()
+
+
+class OctetStringSafeContents(Sequence):
+    tag_default = OctetString.tag_default
+    schema = (("safeContents", SafeContents()),)
+
+
+class AuthSafe(Sequence):
+    schema = (
+        ("contentType", ContentType()),
+        ("content", Any(expl=tag_ctxc(0))),
+    )
+
+
+class DigestInfo(Sequence):
+    schema = (
+        ("digestAlgorithm", AlgorithmIdentifier()),
+        ("digest", OctetString()),
+    )
+
+
+class MacData(Sequence):
+    schema = (
+        ("mac", DigestInfo()),
+        ("macSalt", OctetString()),
+        ("iterations", Integer(default=1)),
+    )
+
+
+class PFX(Sequence):
+    schema = (
+        ("version", Integer(default=1)),
+        ("authSafe", AuthSafe()),
+        ("macData", MacData(optional=True)),
+    )
+
+
+class EncryptedPrivateKeyInfo(Sequence):
+    schema = (
+        ("encryptionAlgorithm", EncryptionAlgorithmIdentifier()),
+        ("encryptedData", OctetString()),
+    )
+
+
+class PKCS8ShroudedKeyBag(EncryptedPrivateKeyInfo):
+    pass
+
+
+class PBKDF2Salt(Choice):
+    schema = (
+        ("specified", OctetString()),
+        # ("otherSource", PBKDF2SaltSources()),
+    )
+
+
+id_hmacWithSHA1 = ObjectIdentifier("1.2.840.113549.2.7")
+
+
+class PBKDF2PRFs(AlgorithmIdentifier):
+    schema = (
+        ("algorithm", ObjectIdentifier(default=id_hmacWithSHA1)),
+        ("parameters", Any(optional=True)),
+    )
+
+
+class IterationCount(Integer):
+    bounds = (1, float("+inf"))
+
+
+class KeyLength(Integer):
+    bounds = (1, float("+inf"))
+
+
+class PBKDF2Params(Sequence):
+    schema = (
+        ("salt", PBKDF2Salt()),
+        ("iterationCount", IterationCount(optional=True)),
+        ("keyLength", KeyLength(optional=True)),
+        ("prf", PBKDF2PRFs()),
+    )
diff --git a/pygost/asn1schemas/x509.py b/pygost/asn1schemas/x509.py
new file mode 100644 (file)
index 0000000..5756592
--- /dev/null
@@ -0,0 +1,160 @@
+# coding: utf-8
+# PyGOST -- Pure Python GOST cryptographic functions library
+# Copyright (C) 2015-2018 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.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+""":rfc:`5280` related structures (**NOT COMPLETE**)
+
+They are taken from `PyDERASN <http://pyderasn.cypherpunks.ru/`__ tests.
+"""
+
+from pyderasn import Any
+from pyderasn import BitString
+from pyderasn import Boolean
+from pyderasn import Choice
+from pyderasn import GeneralizedTime
+from pyderasn import Integer
+from pyderasn import ObjectIdentifier
+from pyderasn import OctetString
+from pyderasn import PrintableString
+from pyderasn import Sequence
+from pyderasn import SequenceOf
+from pyderasn import SetOf
+from pyderasn import tag_ctxc
+from pyderasn import tag_ctxp
+from pyderasn import TeletexString
+from pyderasn import UTCTime
+
+
+class Version(Integer):
+    schema = (
+        ("v1", 0),
+        ("v2", 1),
+        ("v3", 2),
+    )
+
+
+class CertificateSerialNumber(Integer):
+    pass
+
+
+class AlgorithmIdentifier(Sequence):
+    schema = (
+        ("algorithm", ObjectIdentifier()),
+        ("parameters", Any(optional=True)),
+    )
+
+
+class AttributeType(ObjectIdentifier):
+    pass
+
+
+class AttributeValue(Any):
+    pass
+
+
+class OrganizationName(Choice):
+    schema = (
+        ("printableString", PrintableString()),
+        ("teletexString", TeletexString()),
+    )
+
+
+class AttributeTypeAndValue(Sequence):
+    schema = (
+        ("type", AttributeType(defines=(((".", "value"), {
+            ObjectIdentifier("2.5.4.6"): PrintableString(),
+            ObjectIdentifier("2.5.4.8"): PrintableString(),
+            ObjectIdentifier("2.5.4.7"): PrintableString(),
+            ObjectIdentifier("2.5.4.10"): OrganizationName(),
+            ObjectIdentifier("2.5.4.3"): PrintableString(),
+        }),))),
+        ("value", AttributeValue()),
+    )
+
+
+class RelativeDistinguishedName(SetOf):
+    schema = AttributeTypeAndValue()
+    bounds = (1, float("+inf"))
+
+
+class RDNSequence(SequenceOf):
+    schema = RelativeDistinguishedName()
+
+
+class Name(Choice):
+    schema = (
+        ("rdnSequence", RDNSequence()),
+    )
+
+
+class Time(Choice):
+    schema = (
+        ("utcTime", UTCTime()),
+        ("generalTime", GeneralizedTime()),
+    )
+
+
+class Validity(Sequence):
+    schema = (
+        ("notBefore", Time()),
+        ("notAfter", Time()),
+    )
+
+
+class SubjectPublicKeyInfo(Sequence):
+    schema = (
+        ("algorithm", AlgorithmIdentifier()),
+        ("subjectPublicKey", BitString()),
+    )
+
+
+class UniqueIdentifier(BitString):
+    pass
+
+
+class Extension(Sequence):
+    schema = (
+        ("extnID", ObjectIdentifier()),
+        ("critical", Boolean(default=False)),
+        ("extnValue", OctetString()),
+    )
+
+
+class Extensions(SequenceOf):
+    schema = Extension()
+    bounds = (1, float("+inf"))
+
+
+class TBSCertificate(Sequence):
+    schema = (
+        ("version", Version(expl=tag_ctxc(0), default="v1")),
+        ("serialNumber", CertificateSerialNumber()),
+        ("signature", AlgorithmIdentifier()),
+        ("issuer", Name()),
+        ("validity", Validity()),
+        ("subject", Name()),
+        ("subjectPublicKeyInfo", SubjectPublicKeyInfo()),
+        ("issuerUniqueID", UniqueIdentifier(impl=tag_ctxp(1), optional=True)),
+        ("subjectUniqueID", UniqueIdentifier(impl=tag_ctxp(2), optional=True)),
+        ("extensions", Extensions(expl=tag_ctxc(3), optional=True)),
+    )
+
+
+class Certificate(Sequence):
+    schema = (
+        ("tbsCertificate", TBSCertificate()),
+        ("signatureAlgorithm", AlgorithmIdentifier()),
+        ("signatureValue", BitString()),
+    )
diff --git a/pygost/test_cms.py b/pygost/test_cms.py
new file mode 100644 (file)
index 0000000..8eeea91
--- /dev/null
@@ -0,0 +1,418 @@
+# coding: utf-8
+# PyGOST -- Pure Python GOST cryptographic functions library
+# Copyright (C) 2015-2018 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.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from base64 import b64decode
+from unittest import skipIf
+from unittest import TestCase
+
+from pygost.gost28147 import cfb_decrypt
+from pygost.gost3410 import CURVE_PARAMS
+from pygost.gost3410 import GOST3410Curve
+from pygost.gost3410 import prv_unmarshal
+from pygost.gost3410 import pub_unmarshal
+from pygost.gost3410 import public_key
+from pygost.gost3410 import verify
+from pygost.gost3410_vko import kek_34102012256
+from pygost.gost3410_vko import ukm_unmarshal
+from pygost.gost34112012256 import GOST34112012256
+from pygost.gost34112012512 import GOST34112012512
+from pygost.utils import hexdec
+from pygost.wrap import unwrap_cryptopro
+from pygost.wrap import unwrap_gost
+
+try:
+    from pyderasn import OctetString
+
+    from pygost.asn1schemas.cms import ContentInfo
+    from pygost.asn1schemas.cms import DigestedData
+    from pygost.asn1schemas.cms import EnvelopedData
+    from pygost.asn1schemas.cms import Gost2814789EncryptedKey
+    from pygost.asn1schemas.cms import Gost2814789Parameters
+    from pygost.asn1schemas.cms import GostR3410KeyTransport
+    from pygost.asn1schemas.cms import SignedData
+except ImportError:
+    pyderasn_exists = False
+else:
+    pyderasn_exists = True
+
+
+@skipIf(not pyderasn_exists, "PyDERASN dependency is required")
+class TestSigned(TestCase):
+    """SignedData test vectors from "Использование
+    алгоритмов ГОСТ 28147-89, ГОСТ Р 34.11 и ГОСТ Р 34.10 в
+    криптографических сообщениях формата CMS" (TK26CMS.pdf)
+    """
+
+    def process_cms(
+            self,
+            content_info_raw,
+            prv_key_raw,
+            curve_name,
+            hasher,
+            mode,
+    ):
+        content_info, tail = ContentInfo().decode(content_info_raw)
+        self.assertSequenceEqual(tail, b"")
+        signed_data, tail = SignedData().decode(bytes(content_info["content"]))
+        self.assertSequenceEqual(tail, b"")
+        self.assertEqual(len(signed_data["signerInfos"]), 1)
+        curve = GOST3410Curve(*CURVE_PARAMS[curve_name])
+        self.assertTrue(verify(
+            curve,
+            public_key(curve, prv_unmarshal(prv_key_raw)),
+            hasher(bytes(signed_data["encapContentInfo"]["eContent"])).digest()[::-1],
+            bytes(signed_data["signerInfos"][0]["signature"]),
+            mode=mode,
+        ))
+
+    def test_256(self):
+        content_info_raw = b64decode("""
+MIIBBQYJKoZIhvcNAQcCoIH3MIH0AgEBMQ4wDAYIKoUDBwEBAgIFADAbBgkqhkiG
+9w0BBwGgDgQMVGVzdCBtZXNzYWdlMYHBMIG+AgEBMFswVjEpMCcGCSqGSIb3DQEJ
+ARYaR29zdFIzNDEwLTIwMTJAZXhhbXBsZS5jb20xKTAnBgNVBAMTIEdvc3RSMzQx
+MC0yMDEyICgyNTYgYml0KSBleGFtcGxlAgEBMAwGCCqFAwcBAQICBQAwDAYIKoUD
+BwEBAQEFAARAkptb2ekZbC94FaGDQeP70ExvTkXtOY9zgz3cCco/hxPhXUVo3eCx
+VNwDQ8enFItJZ8DEX4blZ8QtziNCMl5HbA==
+        """)
+        prv_key_raw = hexdec("BFCF1D623E5CDD3032A7C6EABB4A923C46E43D640FFEAAF2C3ED39A8FA399924")[::-1]
+        self.process_cms(
+            content_info_raw,
+            prv_key_raw,
+            "GostR3410_2001_CryptoPro_XchA_ParamSet",
+            GOST34112012256,
+            2001,
+        )
+
+    def test_512(self):
+        content_info_raw = b64decode("""
+MIIBSQYJKoZIhvcNAQcCoIIBOjCCATYCAQExDjAMBggqhQMHAQECAwUAMBsGCSqG
+SIb3DQEHAaAOBAxUZXN0IG1lc3NhZ2UxggECMIH/AgEBMFswVjEpMCcGCSqGSIb3
+DQEJARYaR29zdFIzNDEwLTIwMTJAZXhhbXBsZS5jb20xKTAnBgNVBAMTIEdvc3RS
+MzQxMC0yMDEyICg1MTIgYml0KSBleGFtcGxlAgEBMAwGCCqFAwcBAQIDBQAwDAYI
+KoUDBwEBAQIFAASBgFyVohNhMHUi/+RAF3Gh/cC7why6v+4jPWVlx1TYlXtV8Hje
+hI2Y+rP52/LO6EUHG/XcwCBbUxmRWsbUSRRBAexmaafkSdvv2FFwC8kHOcti+UPX
+PS+KRYxT8vhcsBLWWxDkc1McI7aF09hqtED36mQOfACzeJjEoUjALpmJob1V
+        """)
+        prv_key_raw = hexdec("3FC01CDCD4EC5F972EB482774C41E66DB7F380528DFE9E67992BA05AEE462435757530E641077CE587B976C8EEB48C48FD33FD175F0C7DE6A44E014E6BCB074B")[::-1]
+        self.process_cms(
+            content_info_raw,
+            prv_key_raw,
+            "GostR3410_2012_TC26_ParamSetB",
+            GOST34112012512,
+            2012,
+        )
+
+
+@skipIf(not pyderasn_exists, "PyDERASN dependency is required")
+class TestDigested(TestCase):
+    """DigestedData test vectors from "Использование
+    алгоритмов ГОСТ 28147-89, ГОСТ Р 34.11 и ГОСТ Р 34.10 в
+    криптографических сообщениях формата CMS" (TK26CMS.pdf)
+    """
+
+    def process_cms(self, content_info_raw, hasher):
+        content_info, tail = ContentInfo().decode(content_info_raw)
+        self.assertSequenceEqual(tail, b"")
+        digested_data, tail = DigestedData().decode(bytes(content_info["content"]))
+        self.assertSequenceEqual(tail, b"")
+        self.assertSequenceEqual(
+            hasher(bytes(digested_data["encapContentInfo"]["eContent"])).digest(),
+            bytes(digested_data["digest"]),
+        )
+
+    def test_256(self):
+        content_info_raw = b64decode("""
+MIGdBgkqhkiG9w0BBwWggY8wgYwCAQAwDAYIKoUDBwEBAgIFADBXBgkqhkiG9w0B
+BwGgSgRI0eUg4uXy8OgsINHy8Ojh7uboIOLt8/boLCDi5f7y+iDxIOzu8P8g8fLw
+5evg7Ogg7eAg9fDg4fD7/yDv6/rq+yDI4+7w5eL7BCCd0v5OkECeXah/U5dtdAWw
+wMrGKPxmmnQdUAY8VX6PUA==
+        """)
+        self.process_cms(content_info_raw, GOST34112012256)
+
+    def test_512(self):
+        content_info_raw = b64decode("""
+MIG0BgkqhkiG9w0BBwWggaYwgaMCAQAwDAYIKoUDBwEBAgMFADBOBgkqhkiG9w0B
+BwGgQQQ/MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAx
+MjM0NTY3ODkwMTIzNDU2Nzg5MDEyBEAbVNAaSvW51cw9htaNKFRisZq8JHUiLzXA
+hRIr5Lof+gCtMPh2ezqCOExldPAkwxHipIEzKwjvf0F5eJHBZG9I
+        """)
+        self.process_cms(content_info_raw, GOST34112012512)
+
+
+@skipIf(not pyderasn_exists, "PyDERASN dependency is required")
+class TestEnvelopedKTRI(TestCase):
+    """EnvelopedData KeyTransRecipientInfo-based test vectors from
+    "Использование алгоритмов ГОСТ 28147-89, ГОСТ Р 34.11 и ГОСТ Р 34.10
+    в криптографических сообщениях формата CMS" (TK26CMS.pdf)
+    """
+
+    def process_cms(
+            self,
+            content_info_raw,
+            prv_key_our,
+            curve_name,
+            keker,
+            plaintext_expected,
+    ):
+        sbox = "Gost28147_tc26_ParamZ"
+        content_info, tail = ContentInfo().decode(content_info_raw)
+        self.assertSequenceEqual(tail, b"")
+        enveloped_data, tail = EnvelopedData().decode(bytes(content_info["content"]))
+        self.assertSequenceEqual(tail, b"")
+        eci = enveloped_data["encryptedContentInfo"]
+        ri = enveloped_data["recipientInfos"][0]
+        encrypted_key, tail = GostR3410KeyTransport().decode(
+            bytes(ri["ktri"]["encryptedKey"])
+        )
+        self.assertSequenceEqual(tail, b"")
+        ukm = bytes(encrypted_key["transportParameters"]["ukm"])
+        spk = bytes(encrypted_key["transportParameters"]["ephemeralPublicKey"]["subjectPublicKey"])
+        pub_key_their, tail = OctetString().decode(spk)
+        self.assertSequenceEqual(tail, b"")
+        curve = GOST3410Curve(*CURVE_PARAMS[curve_name])
+        kek = keker(curve, prv_key_our, bytes(pub_key_their), ukm)
+        key_wrapped = bytes(encrypted_key["sessionEncryptedKey"]["encryptedKey"])
+        mac = bytes(encrypted_key["sessionEncryptedKey"]["macKey"])
+        cek = unwrap_cryptopro(kek, ukm + key_wrapped + mac, sbox=sbox)
+        ciphertext = bytes(eci["encryptedContent"])
+        encryption_params, tail = Gost2814789Parameters().decode(
+            bytes(eci["contentEncryptionAlgorithm"]["parameters"])
+        )
+        self.assertSequenceEqual(tail, b"")
+        iv = bytes(encryption_params["iv"])
+        self.assertSequenceEqual(
+            cfb_decrypt(cek, ciphertext, iv, sbox=sbox, mesh=True),
+            plaintext_expected,
+        )
+
+    def test_256(self):
+        content_info_raw = b64decode("""
+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
+        """)
+        prv_key_our = hexdec("BFCF1D623E5CDD3032A7C6EABB4A923C46E43D640FFEAAF2C3ED39A8FA399924")[::-1]
+
+        def keker(curve, prv, pub, ukm):
+            return kek_34102012256(
+                curve,
+                prv_unmarshal(prv),
+                pub_unmarshal(pub),
+                ukm_unmarshal(ukm),
+                mode=2001,
+            )
+
+        self.process_cms(
+            content_info_raw,
+            prv_key_our,
+            "GostR3410_2001_CryptoPro_XchA_ParamSet",
+            keker,
+            b"Test data to encrypt.\n" * 100,
+        )
+
+    def test_512(self):
+        content_info_raw = b64decode("""
+MIIB0gYJKoZIhvcNAQcDoIIBwzCCAb8CAQAxggF8MIIBeAIBADBbMFYxKTAnBgkq
+hkiG9w0BCQEWGkdvc3RSMzQxMC0yMDEyQGV4YW1wbGUuY29tMSkwJwYDVQQDEyBH
+b3N0UjM0MTAtMjAxMiA1MTIgYml0cyBleGNoYW5nZQIBATAhBggqhQMHAQEBAjAV
+BgkqhQMHAQIBAgIGCCqFAwcBAQIDBIHyMIHvMCgEIIsYzbVLn33aLinQ7SLNA7y+
+Lrm02khqDCfXrNS9iiMhBATerS8zoIHCBgkqhQMHAQIFAQGggaowIQYIKoUDBwEB
+AQIwFQYJKoUDBwECAQICBggqhQMHAQECAwOBhAAEgYAYiTVLKpSGaAvjJEDQ0hdK
+qR/jek5Q9Q2pXC+NkOimQh7dpCi+wcaHlPcBk96hmpnOFvLaiokX8V6jqtBl5gdk
+M40kOXv8kcDdTzEVKA/ZLxA8xanL+gTD6ZjaPsUu06nsA2MoMBWcHLUzueaP3bGT
+/yHTV+Za5xdcQehag/lNBgQIvCw4uUl0XC4wOgYJKoZIhvcNAQcBMB8GBiqFAwIC
+FTAVBAj+1QzaXaN9FwYJKoUDBwECBQEBgAyK54euw0sHhEVEkA0=
+        """)
+        prv_key_our = hexdec("3FC01CDCD4EC5F972EB482774C41E66DB7F380528DFE9E67992BA05AEE462435757530E641077CE587B976C8EEB48C48FD33FD175F0C7DE6A44E014E6BCB074B")[::-1]
+
+        def keker(curve, prv, pub, ukm):
+            return kek_34102012256(
+                curve,
+                prv_unmarshal(prv),
+                pub_unmarshal(pub, mode=2012),
+                ukm_unmarshal(ukm),
+            )
+
+        self.process_cms(
+            content_info_raw,
+            prv_key_our,
+            "GostR3410_2012_TC26_ParamSetB",
+            keker,
+            b"Test message",
+        )
+
+
+@skipIf(not pyderasn_exists, "PyDERASN dependency is required")
+class TestEnvelopedKARI(TestCase):
+    """EnvelopedData KeyAgreeRecipientInfo-based test vectors from
+    "Использование алгоритмов ГОСТ 28147-89, ГОСТ Р 34.11 и ГОСТ Р 34.10
+    в криптографических сообщениях формата CMS" (TK26CMS.pdf)
+    """
+
+    def process_cms(
+            self,
+            content_info_raw,
+            prv_key_our,
+            curve_name,
+            keker,
+            plaintext_expected,
+    ):
+        sbox = "Gost28147_tc26_ParamZ"
+        content_info, tail = ContentInfo().decode(content_info_raw)
+        self.assertSequenceEqual(tail, b"")
+        enveloped_data, tail = EnvelopedData().decode(bytes(content_info["content"]))
+        self.assertSequenceEqual(tail, b"")
+        eci = enveloped_data["encryptedContentInfo"]
+        kari = enveloped_data["recipientInfos"][0]["kari"]
+        pub_key_their, tail = OctetString().decode(
+            bytes(kari["originator"]["originatorKey"]["publicKey"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+        ukm = bytes(kari["ukm"])
+        rek = kari["recipientEncryptedKeys"][0]
+        curve = GOST3410Curve(*CURVE_PARAMS[curve_name])
+        kek = keker(curve, prv_key_our, bytes(pub_key_their), ukm)
+        encrypted_key, tail = Gost2814789EncryptedKey().decode(
+            bytes(rek["encryptedKey"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+        key_wrapped = bytes(encrypted_key["encryptedKey"])
+        mac = bytes(encrypted_key["macKey"])
+        cek = unwrap_gost(kek, ukm + key_wrapped + mac, sbox=sbox)
+        ciphertext = bytes(eci["encryptedContent"])
+        encryption_params, tail = Gost2814789Parameters().decode(
+            bytes(eci["contentEncryptionAlgorithm"]["parameters"])
+        )
+        self.assertSequenceEqual(tail, b"")
+        iv = bytes(encryption_params["iv"])
+        self.assertSequenceEqual(
+            cfb_decrypt(cek, ciphertext, iv, sbox=sbox, mesh=True),
+            plaintext_expected,
+        )
+
+    def test_256(self):
+        content_info_raw = b64decode("""
+MIIBhgYJKoZIhvcNAQcDoIIBdzCCAXMCAQIxggEwoYIBLAIBA6BooWYwHwYIKoUD
+BwEBAQEwEwYHKoUDAgIkAAYIKoUDBwEBAgIDQwAEQPAdWM4pO38iZ49UjaXQpq+a
+jhTa4KwY4B9TFMK7AiYmbFKE0eX/wvu69kFMQ2o3OJTnMOlr1WHiPYOmNO6C5hOh
+CgQIX+vNomZakEIwIgYIKoUDBwEBAQEwFgYHKoUDAgINADALBgkqhQMHAQIFAQEw
+gYwwgYkwWzBWMSkwJwYJKoZIhvcNAQkBFhpHb3N0UjM0MTAtMjAxMkBleGFtcGxl
+LmNvbTEpMCcGA1UEAxMgR29zdFIzNDEwLTIwMTIgMjU2IGJpdHMgZXhjaGFuZ2UC
+AQEEKjAoBCCNhrZOr7x2fsjjQAeDMv/tSoNRQSSQzzxgqdnYxJ3fIAQEgYLqVDA6
+BgkqhkiG9w0BBwEwHwYGKoUDAgIVMBUECHVmR/S+hlYiBgkqhQMHAQIFAQGADEI9
+UNjyuY+54uVcHw==
+        """)
+        prv_key_our = hexdec("BFCF1D623E5CDD3032A7C6EABB4A923C46E43D640FFEAAF2C3ED39A8FA399924")[::-1]
+
+        def keker(curve, prv, pub, ukm):
+            return kek_34102012256(
+                curve,
+                prv_unmarshal(prv),
+                pub_unmarshal(pub),
+                ukm_unmarshal(ukm),
+                mode=2001,
+            )
+
+        self.process_cms(
+            content_info_raw,
+            prv_key_our,
+            "GostR3410_2001_CryptoPro_XchA_ParamSet",
+            keker,
+            b"Test message",
+        )
+
+    def test_512(self):
+        content_info_raw = b64decode("""
+MIIBzAYJKoZIhvcNAQcDoIIBvTCCAbkCAQIxggF2oYIBcgIBA6CBraGBqjAhBggq
+hQMHAQEBAjAVBgkqhQMHAQIBAgIGCCqFAwcBAQIDA4GEAASBgCB0nQy/Ljva/mRj
+w6o+eDKIvnxwYIQB5XCHhZhCpHNZiWcFxFpYXZLWRPKifOxV7NStvqGE1+fkfhBe
+btkQu0tdC1XL3LO2Cp/jX16XhW/IP5rKV84qWr1Owy/6tnSsNRb+ez6IttwVvaVV
+pA6ONFy9p9gawoC8nitvAVJkWW0PoQoECDVfxzxgMTAHMCIGCCqFAwcBAQECMBYG
+ByqFAwICDQAwCwYJKoUDBwECBQEBMIGMMIGJMFswVjEpMCcGCSqGSIb3DQEJARYa
+R29zdFIzNDEwLTIwMTJAZXhhbXBsZS5jb20xKTAnBgNVBAMTIEdvc3RSMzQxMC0y
+MDEyIDUxMiBiaXRzIGV4Y2hhbmdlAgEBBCowKAQg8C/OcxRR0Uq8nDjHrQlayFb3
+WFUZEnEuAKcuG6dTOawEBLhi9hIwOgYJKoZIhvcNAQcBMB8GBiqFAwICFTAVBAiD
+1wH+CX6CwgYJKoUDBwECBQEBgAzUvQI4H2zRfgNgdlY=
+        """)
+        prv_key_our = hexdec("3FC01CDCD4EC5F972EB482774C41E66DB7F380528DFE9E67992BA05AEE462435757530E641077CE587B976C8EEB48C48FD33FD175F0C7DE6A44E014E6BCB074B")[::-1]
+
+        def keker(curve, prv, pub, ukm):
+            return kek_34102012256(
+                curve,
+                prv_unmarshal(prv),
+                pub_unmarshal(pub, mode=2012),
+                ukm_unmarshal(ukm),
+            )
+
+        self.process_cms(
+            content_info_raw,
+            prv_key_our,
+            "GostR3410_2012_TC26_ParamSetB",
+            keker,
+            b"Test message",
+        )
diff --git a/pygost/test_cms_enveloped.py b/pygost/test_cms_enveloped.py
deleted file mode 100644 (file)
index 9dc3100..0000000
+++ /dev/null
@@ -1,305 +0,0 @@
-# coding: utf-8
-
-from base64 import b64decode
-from unittest import skipIf
-from unittest import TestCase
-
-from pygost.gost28147 import cfb_decrypt
-from pygost.gost3410 import CURVE_PARAMS
-from pygost.gost3410 import GOST3410Curve
-from pygost.gost3410 import prv_unmarshal
-from pygost.gost3410 import pub_unmarshal
-from pygost.gost3410_vko import kek_34102012256
-from pygost.gost3410_vko import ukm_unmarshal
-from pygost.utils import hexdec
-from pygost.wrap import unwrap_cryptopro
-
-try:
-
-    from pyderasn import Any
-    from pyderasn import BitString
-    from pyderasn import Choice
-    from pyderasn import Integer
-    from pyderasn import ObjectIdentifier
-    from pyderasn import OctetString
-    from pyderasn import Sequence
-    from pyderasn import SetOf
-    from pyderasn import tag_ctxc
-    from pyderasn import tag_ctxp
-
-    class CMSVersion(Integer):
-        pass
-
-    class ContentType(ObjectIdentifier):
-        pass
-
-    class RecipientIdentifier(Choice):
-        schema = (
-            ("issuerAndSerialNumber", Any()),
-            # ("subjectKeyIdentifier", SubjectKeyIdentifier(impl=tag_ctxp(0))),
-        )
-
-    class AlgorithmIdentifier(Sequence):
-        schema = (
-            ("algorithm", ObjectIdentifier()),
-            ("parameters", Any(optional=True)),
-        )
-
-    class KeyEncryptionAlgorithmIdentifier(AlgorithmIdentifier):
-        pass
-
-    class EncryptedKey(OctetString):
-        pass
-
-    class KeyTransRecipientInfo(Sequence):
-        schema = (
-            ("version", CMSVersion()),
-            ("rid", RecipientIdentifier()),
-            ("keyEncryptionAlgorithm", KeyEncryptionAlgorithmIdentifier()),
-            ("encryptedKey", EncryptedKey()),
-        )
-
-    class RecipientInfo(Choice):
-        schema = (
-            ("ktri", KeyTransRecipientInfo()),
-            # ("kari", KeyAgreeRecipientInfo(impl=tag_ctxc(1))),
-            # ("kekri", KEKRecipientInfo(impl=tag_ctxc(2))),
-            # ("pwri", PasswordRecipientInfo(impl=tag_ctxc(3))),
-            # ("ori", OtherRecipientInfo(impl=tag_ctxc(4))),
-        )
-
-    class RecipientInfos(SetOf):
-        schema = RecipientInfo()
-        bounds = (1, float("+inf"))
-
-    class ContentEncryptionAlgorithmIdentifier(AlgorithmIdentifier):
-        pass
-
-    class EncryptedContent(OctetString):
-        pass
-
-    class EncryptedContentInfo(Sequence):
-        schema = (
-            ("contentType", ContentType()),
-            ("contentEncryptionAlgorithm", ContentEncryptionAlgorithmIdentifier()),
-            ("encryptedContent", EncryptedContent(impl=tag_ctxp(0), optional=True)),
-        )
-
-    class EnvelopedData(Sequence):
-        schema = (
-            ("version", CMSVersion()),
-            # ("originatorInfo", OriginatorInfo(impl=tag_ctxc(0), optional=True)),
-            ("recipientInfos", RecipientInfos()),
-            ("encryptedContentInfo", EncryptedContentInfo()),
-            # ("unprotectedAttrs", UnprotectedAttributes(impl=tag_ctxc(1), optional=True)),
-        )
-
-    class ContentInfo(Sequence):
-        schema = (
-            ("contentType", ContentType()),
-            ("content", Any(expl=tag_ctxc(0))),
-        )
-
-    class Gost2814789IV(OctetString):
-        bounds = (8, 8)
-
-    class Gost2814789Parameters(Sequence):
-        schema = (
-            ("iv", Gost2814789IV()),
-            ("encryptionParamSet", ObjectIdentifier()),
-        )
-
-    class Gost2814789Key(OctetString):
-        bounds = (32, 32)
-
-    class Gost2814789MAC(OctetString):
-        bounds = (4, 4)
-
-    class Gost2814789EncryptedKey(Sequence):
-        schema = (
-            ("encryptedKey", Gost2814789Key()),
-            ("maskKey", Gost2814789Key(impl=tag_ctxp(0), optional=True)),
-            ("macKey", Gost2814789MAC()),
-        )
-
-    class SubjectPublicKeyInfo(Sequence):
-        schema = (
-            ("algorithm", AlgorithmIdentifier()),
-            ("subjectPublicKey", BitString()),
-        )
-
-    class GostR34102001TransportParameters(Sequence):
-        schema = (
-            ("encryptionParamSet", ObjectIdentifier()),
-            ("ephemeralPublicKey", SubjectPublicKeyInfo(
-                impl=tag_ctxc(0),
-                optional=True,
-            )),
-            ("ukm", OctetString()),
-        )
-
-    class GostR3410KeyTransport(Sequence):
-        schema = (
-            ("sessionEncryptedKey", Gost2814789EncryptedKey()),
-            ("transportParameters", GostR34102001TransportParameters(
-                impl=tag_ctxc(0),
-                optional=True,
-            )),
-        )
-
-except ImportError:
-    pyderasn_exists = False
-else:
-    pyderasn_exists = True
-
-
-@skipIf(not pyderasn_exists, "PyDERASN dependency is required")
-class TestCMSEnveloped(TestCase):
-    """KeyTransRecipientInfo-based test vectors from "Использование
-    алгоритмов ГОСТ 28147-89, ГОСТ Р 34.11 и ГОСТ Р 34.10 в
-    криптографических сообщениях формата CMS" (TK26CMS.pdf)
-
-    `PyDERASN <http://pyderasn.cypherpunks.ru/>`__ library is required
-    for CMS parsing.
-    """
-
-    def process_cms(
-            self,
-            content_info_raw,
-            prv_key_our,
-            curve_name,
-            keker,
-            plaintext_expected,
-    ):
-        sbox = "Gost28147_tc26_ParamZ"
-        content_info, _ = ContentInfo().decode(content_info_raw)
-        enveloped_data, _ = EnvelopedData().decode(bytes(content_info["content"]))
-        eci = enveloped_data["encryptedContentInfo"]
-        ri = enveloped_data["recipientInfos"][0]
-        encrypted_key, _ = GostR3410KeyTransport().decode(
-            bytes(ri["ktri"]["encryptedKey"])
-        )
-        ukm = bytes(encrypted_key["transportParameters"]["ukm"])
-        spk = bytes(encrypted_key["transportParameters"]["ephemeralPublicKey"]["subjectPublicKey"])
-        pub_key_their, _ = OctetString().decode(spk)
-        curve = GOST3410Curve(*CURVE_PARAMS[curve_name])
-        kek = keker(curve, prv_key_our, bytes(pub_key_their), ukm)
-        key_wrapped = bytes(encrypted_key["sessionEncryptedKey"]["encryptedKey"])
-        mac = bytes(encrypted_key["sessionEncryptedKey"]["macKey"])
-        cek = unwrap_cryptopro(kek, ukm + key_wrapped + mac, sbox=sbox)
-        ciphertext = bytes(eci["encryptedContent"])
-        encryption_params, _ = Gost2814789Parameters().decode(
-            bytes(eci["contentEncryptionAlgorithm"]["parameters"])
-        )
-        iv = bytes(encryption_params["iv"])
-        self.assertSequenceEqual(
-            cfb_decrypt(cek, ciphertext, iv, sbox=sbox, mesh=True),
-            plaintext_expected,
-        )
-
-    def test_256(self):
-        content_info_raw = b64decode("""
-MIIKGgYJKoZIhvcNAQcDoIIKCzCCCgcCAQAxggE0MIIBMAIBADBbMFYxKTAnBgkq
-hkiG9w0BCQEWGkdvc3RSMzQxMC0yMDEyQGV4YW1wbGUuY29tMSkwJwYDVQQDEyBH
-b3N0UjM0MTAtMjAxMiAyNTYgYml0cyBleGNoYW5nZQIBATAfBggqhQMHAQEBATAT
-BgcqhQMCAiQABggqhQMHAQECAgSBrDCBqTAoBCCVJxUMdbKRzCJ5K1NWJIXnN7Ul
-zaceeFlblA2qH4wZrgQEsHnIG6B9BgkqhQMHAQIFAQGgZjAfBggqhQMHAQEBATAT
-BgcqhQMCAiQABggqhQMHAQECAgNDAARAFoqoLg1lV780co6GdwtjLtS4KCXv9VGR
-sd7PTPHCT/5iGbvOlKNW2I8UhayJ0dv7RV7Nb1lDIxPxf4Mbp2CikgQI1b4+WpGE
-sfQwggjIBgkqhkiG9w0BBwEwHwYGKoUDAgIVMBUECHYNkdvFoYdyBgkqhQMHAQIF
-AQGAggiYvFFpJKILAFdXjcdLLYv4eruXzL/wOXL8y9HHIDMbSzV1GM033J5Yt/p4
-H6JYe1L1hjAfE/BAAYBndof2sSUxC3/I7xj+b7M8BZ3GYPqATPtR4aCQDK6z91lx
-nDBAWx0HdsStT5TOj/plMs4zJDadvIJLfjmGkt0Np8FSnSdDPOcJAO/jcwiOPopg
-+Z8eIuZNmY4seegTLue+7DGqvqi1GdZdMnvXBFIKc9m5DUsC7LdyboqKImh6giZE
-YZnxb8a2naersPylhrf+zp4Piwwv808yOrD6LliXUiH0RojlmuaQP4wBkb7m073h
-MeAWEWSvyXzOvOOuFST/hxPEupiTRoHPUdfboJT3tNpizUhE384SrvXHpwpgivQ4
-J0zF2/uzTBEupXR6dFC9rTHAK3X79SltqBNnHyIXBwe+BMqTmKTfnlPVHBUfTXZg
-oakDItwKwa1MBOZeciwtUFza+7o9FZhKIandb848chGdgd5O9ksaXvPJDIPxQjZd
-EBVhnXLlje4TScImwTdvYB8GsI8ljKb2bL3FjwQWGbPaOjXc2D9w+Ore8bk1E4TA
-ayhypU7MH3Mq1EBZ4j0iROEFBQmYRZn8vAKZ0K7aPxcDeAnKAJxdokqrMkLgI6WX
-0glh/3Cs9dI+0D2GqMSygauKCD0vTIo3atkEQswDZR4pMx88gB4gmx7iIGrc/ZXs
-ZqHI7NQqeKtBwv2MCIj+/UTqdYDqbaniDwdVS8PE9nQnNU4gKffq3JbT+wRjJv6M
-Dr231bQHgAsFTVKbZgoL4gj4V7bLQUmW06+W1BQUJ2+Sn7fp+Xet9Xd3cGtNdxzQ
-zl6sGuiOlTNe0bfKP7QIMC7ekjflLBx8nwa2GZG19k3O0Z9JcDdN/kz6bGpPNssY
-AIOkTvLQjxIM9MhRqIv6ee0rowTWQPwXJP7yHApop4XZvVX6h9gG2gazqbDej2lo
-tAcfRAKj/LJ/bk9+OlNXOXVCKnwE1kXxZDsNJ51GdCungC56U/hmd3C1RhSLTpEc
-FlOWgXKNjbn6SQrlq1yASKKr80T0fL7PFoYwKZoQbKMAVZQC1VBWQltHkEzdL73x
-FwgZULNfdflF8sEhFC/zsVqckD/UnhzJz88PtCslMArJ7ntbEF1GzsSSfRfjBqnl
-kSUreE5XX6+c9yp5HcJBiMzp6ZqqWWaED5Y5xp1hZeYjuKbDMfY4tbWVc7Hy0dD2
-KGfZLp5umqvPNs7aVBPmvuxtrnxcJlUB8u2HoiHc6/TuhrpaopYGBhxL9+kezuLR
-v18nsAg8HOmcCNUS46NXQj/Mdpx8W+RsyzCQkJjieT/Yed20Zxq1zJoXIS0xAaUH
-TdE2dWqiT6TGlh/KQYk3KyFPNnDmzJm04a2VWIwpp4ypXyxrB7XxnVY6Q4YBYbZs
-FycxGjJWqj7lwc+lgZ8YV2WJ4snEo2os8SsA2GFWcUMiVTHDnEJvphDHmhWsf26A
-bbRqwaRXNjhj05DamTRsczgvfjdl1pk4lJYE4ES3nixtMe4s1X8nSmM4KvfyVDul
-J8uTpw1ZFnolTdfEL63BSf4FREoEqKB7cKuD7cpn7Rg4kRdM0/BLZGuxkH+pGMsI
-Bb8LecUWyjGsI6h74Wz/U2uBrfgdRqhR+UsfB2QLaRgM6kCXZ4vM0auuzBViFCwK
-tYMHzZWWz8gyVtJ0mzt1DrHCMx4pTS4yOhv4RkXBS/rub4VhVIsOGOGar5ZYtH47
-uBbdw3NC05JIFM7lI31d0s1fvvkTUR7eaqRW+SnR2c2oHpWlSO+Q0mrzx+vvOTdj
-xa713YtklBvyUUQr2SIbsXGpFnwjn+sXK1onAavp/tEax8sNZvxg5yeseFcWn+gD
-4rjk9FiSd1wp4fTDQFJ19evqruqKlq6k18l/ZAyUcEbIWSz2s3HfAAoAQyFPX1Q2
-95gVhRRw6lP4S6VPCfn/f+5jV4TcT6W/giRaHIk9Hty+g8bx1bFXaKVkQZ5R2Vmk
-qsZ65ZgCrYQJmcErPmYybvP7NBeDS4AOSgBQAGMQF4xywdNm6bniWWo3N/xkFv32
-/25x8okGgD8QcYKmhzieLSSzOvM/exB14RO84YZOkZzm01Jll0nac/LEazKoVWbn
-0VdcQ7pYEOqeMBXipsicNVYA/uhonp6op9cpIVYafPr0npCGwwhwcRuOrgSaZyCn
-VG2tPkEOv9LKmUbhnaDA2YUSzOOjcCpIVvTSBnUEiorYpfRYgQLrbcd2qhVvNCLX
-8ujZfMqXQXK8n5BK8JxNtczvaf+/2dfv1dQl0lHEAQhbNcsJ0t5GPhsSCC5oMBJl
-ZJuOEO/8PBWKEnMZOM+Dz7gEgsBhGyMFFrKpiwQRpyEshSD2QpnK6Lp0t5C8Za2G
-lhyZsEr+93AYOb5mm5+z02B4Yq9+RpepvjoqVeq/2uywZNq9MS98zVgNsmpryvTZ
-3HJHHB20u2jcVu0G3Nhiv22lD70JWCYFAOupjgVcUcaBxjxwUMAvgHg7JZqs6mC6
-tvTKwQ4NtDhoAhARlDeWSwCWb2vPH2H7Lmqokif1RfvJ0hrLzkJuHdWrzIYzXpPs
-+v9XJxLvbdKi9KU1Halq9S8dXT1fvs9DJTpUV/KW7QkRsTQJhTJBkQ07WUSJ4gBS
-Qp4efxSRNIfMj7DR6qLLf13RpIPTJO9/+gNuBIFcupWVfUL7tJZt8Qsf9eGwZfP+
-YyhjC8AyZjH4/9RzLHSjuq6apgw3Mzw0j572Xg6xDLMK8C3Tn/vrLOvAd96b9MkF
-3+ZHSLW3IgOiy+1jvK/20CZxNWc+pey8v4zji1hI17iohsipX/uZKRxhxF6+Xn2R
-UQp6qoxHAspNXgWQ57xg7C3+gmi4ciVr0fT9pg54ogcowrRH+I6wd0EpeWPbzfnQ
-pRmMVN+YtRsrEHwH3ToQ/i4vrtgA+eONuKT2uKZFikxA+VNmeeGdhkgqETMihQ==
-        """)
-        prv_key_our = hexdec("BFCF1D623E5CDD3032A7C6EABB4A923C46E43D640FFEAAF2C3ED39A8FA399924")[::-1]
-
-        def keker(curve, prv, pub, ukm):
-            return kek_34102012256(
-                curve,
-                prv_unmarshal(prv),
-                pub_unmarshal(pub),
-                ukm_unmarshal(ukm),
-                mode=2001,
-            )
-
-        self.process_cms(
-            content_info_raw,
-            prv_key_our,
-            "GostR3410_2001_CryptoPro_XchA_ParamSet",
-            keker,
-            b"Test data to encrypt.\n" * 100,
-        )
-
-    def test_512(self):
-        content_info_raw = b64decode("""
-MIIB0gYJKoZIhvcNAQcDoIIBwzCCAb8CAQAxggF8MIIBeAIBADBbMFYxKTAnBgkq
-hkiG9w0BCQEWGkdvc3RSMzQxMC0yMDEyQGV4YW1wbGUuY29tMSkwJwYDVQQDEyBH
-b3N0UjM0MTAtMjAxMiA1MTIgYml0cyBleGNoYW5nZQIBATAhBggqhQMHAQEBAjAV
-BgkqhQMHAQIBAgIGCCqFAwcBAQIDBIHyMIHvMCgEIIsYzbVLn33aLinQ7SLNA7y+
-Lrm02khqDCfXrNS9iiMhBATerS8zoIHCBgkqhQMHAQIFAQGggaowIQYIKoUDBwEB
-AQIwFQYJKoUDBwECAQICBggqhQMHAQECAwOBhAAEgYAYiTVLKpSGaAvjJEDQ0hdK
-qR/jek5Q9Q2pXC+NkOimQh7dpCi+wcaHlPcBk96hmpnOFvLaiokX8V6jqtBl5gdk
-M40kOXv8kcDdTzEVKA/ZLxA8xanL+gTD6ZjaPsUu06nsA2MoMBWcHLUzueaP3bGT
-/yHTV+Za5xdcQehag/lNBgQIvCw4uUl0XC4wOgYJKoZIhvcNAQcBMB8GBiqFAwIC
-FTAVBAj+1QzaXaN9FwYJKoUDBwECBQEBgAyK54euw0sHhEVEkA0=
-        """)
-        prv_key_our = hexdec("3FC01CDCD4EC5F972EB482774C41E66DB7F380528DFE9E67992BA05AEE462435757530E641077CE587B976C8EEB48C48FD33FD175F0C7DE6A44E014E6BCB074B")[::-1]
-
-        def keker(curve, prv, pub, ukm):
-            return kek_34102012256(
-                curve,
-                prv_unmarshal(prv),
-                pub_unmarshal(pub, mode=2012),
-                ukm_unmarshal(ukm),
-            )
-
-        self.process_cms(
-            content_info_raw,
-            prv_key_our,
-            "GostR3410_2012_TC26_ParamSetB",
-            keker,
-            b"Test message",
-        )
diff --git a/pygost/test_pfx.py b/pygost/test_pfx.py
new file mode 100644 (file)
index 0000000..39b477f
--- /dev/null
@@ -0,0 +1,218 @@
+# coding: utf-8
+# PyGOST -- Pure Python GOST cryptographic functions library
+# Copyright (C) 2015-2018 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.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from base64 import b64decode
+from hmac import new as hmac_new
+from unittest import skipIf
+from unittest import TestCase
+
+from pygost.gost28147 import cfb_decrypt
+from pygost.gost34112012512 import GOST34112012512
+from pygost.gost34112012512 import pbkdf2 as gost34112012_pbkdf2
+
+
+try:
+    from pygost.asn1schemas.cms import Gost2814789Parameters
+    from pygost.asn1schemas.pfx import EncryptedData
+    from pygost.asn1schemas.pfx import OctetStringSafeContents
+    from pygost.asn1schemas.pfx import PBES2Params
+    from pygost.asn1schemas.pfx import PBKDF2Params
+    from pygost.asn1schemas.pfx import PFX
+    from pygost.asn1schemas.pfx import PKCS8ShroudedKeyBag
+except ImportError:
+    pyderasn_exists = False
+else:
+    pyderasn_exists = True
+
+
+@skipIf(not pyderasn_exists, "PyDERASN dependency is required")
+class TestPFX(TestCase):
+    """PFX test vectors from "Транспортный ключевой контейнер" (R50.1.112-2016.pdf)
+    """
+    pfx_raw = b64decode("""
+MIIFqgIBAzCCBSsGCSqGSIb3DQEHAaCCBRwEggUYMIIFFDCCASIGCSqGSIb3DQEH
+AaCCARMEggEPMIIBCzCCAQcGCyqGSIb3DQEMCgECoIHgMIHdMHEGCSqGSIb3DQEF
+DTBkMEEGCSqGSIb3DQEFDDA0BCD5qZr0TTIsBvdgUoq/zFwOzdyJohj6/4Wiyccg
+j9AK/QICB9AwDAYIKoUDBwEBBAIFADAfBgYqhQMCAhUwFQQI3Ip/Vp0IsyIGCSqF
+AwcBAgUBAQRoSfLhgx9s/zn+BjnhT0ror07vS55Ys5hgvVpWDx4mXGWWyez/2sMc
+aFgSr4H4UTGGwoMynGLpF1IOVo+bGJ0ePqHB+gS5OL9oV+PUmZ/ELrRENKlCDqfY
+WvpSystX29CvCFrnTnDsbBYxFTATBgkqhkiG9w0BCRUxBgQEAQAAADCCA+oGCSqG
+SIb3DQEHBqCCA9swggPXAgEAMIID0AYJKoZIhvcNAQcBMHEGCSqGSIb3DQEFDTBk
+MEEGCSqGSIb3DQEFDDA0BCCJTJLZQRi1WIpQHzyjXbq7+Vw2+1280C45x8ff6kMS
+VAICB9AwDAYIKoUDBwEBBAIFADAfBgYqhQMCAhUwFQQIxepowwvS11MGCSqFAwcB
+AgUBAYCCA06n09P/o+eDEKoSWpvlpOLKs7dKmVquKzJ81nCngvLQ5fEWL1WkxwiI
+rEhm53JKLD0wy4hekalEk011Bvc51XP9gkDkmaoBpnV/TyKIY35wl6ATfeGXno1M
+KoA+Ktdhv4gLnz0k2SXdkUj11JwYskXue+REA0p4m2ZsoaTmvoODamh9JeY/5Qjy
+Xe58CGnyXFzX3eU86qs4WfdWdS3NzYYOk9zzVl46le9u79O/LnW2j4n2of/Jpk/L
+YjrRmz5oYeQOqKOKhEyhpO6e+ejr6laduEv7TwJQKRNiygogbVvkNn3VjHTSOUG4
+W+3NRPhjb0jD9obdyx6MWa6O3B9bUzFMNav8/gYn0vTDxqXMLy/92oTngNrVx6Gc
+cNl128ISrDS6+RxtAMiEBRK6xNkemqX5yNXG5GrLQQFGP6mbs2nNpjKlgj3pljmX
+Eky2/G78XiJrv02OgGs6CKnI9nMpa6N7PBHV34MJ6EZzWOWDRQ420xk63mnicrs0
+WDVJ0xjdu4FW3iEk02EaiRTvGBpa6GL7LBp6QlaXSSwONx725cyRsL9cTlukqXER
+WHDlMpjYLbkGZRrCc1myWgEfsputfSIPNF/oLv9kJNWacP3uuDOfecg3us7eg2OA
+xo5zrYfn39GcBMF1WHAYRO/+PnJb9jrDuLAE8+ONNqjNulWNK9CStEhb6Te+yE6q
+oeP6hJjFLi+nFLE9ymIo0A7gLQD5vzFvl+7v1ZNVnQkwRUsWoRiEVVGnv3Z1iZU6
+xStxgoHMl62V/P5cz4dr9vJM2adEWNZcVXl6mk1H8DRc1sRGnvs2l237oKWRVntJ
+hoWnZ8qtD+3ZUqsX79QhVzUQBzKuBt6jwNhaHLGl5B+Or/zA9FezsOh6+Uc+fZaV
+W7fFfeUyWwGy90XD3ybTrjzep9f3nt55Z2c+fu2iEwhoyImWLuC3+CVhf9Af59j9
+8/BophMJuATDJEtgi8rt4vLnfxKu250Mv2ZpbfF69EGTgFYbwc55zRfaUG9zlyCu
+1YwMJ6HC9FUVtJp9gObSrirbzTH7mVaMjQkBLotazWbegzI+be8V3yT06C+ehD+2
+GdLWAVs9hp8gPHEUShb/XrgPpDSJmFlOiyeOFBO/j4edDACKqVcwdjBOMAoGCCqF
+AwcBAQIDBEAIFX0fyZe20QKKhWm6WYX+S92Gt6zaXroXOvAmayzLfZ5Sd9C2t9zZ
+JSg6M8RBUYpw/8ym5ou1o2nDa09M5zF3BCCpzyCQBI+rzfISeKvPV1ROfcXiYU93
+mwcl1xQV2G5/fgICB9A=
+    """)
+    password = u'Пароль для PFX'
+
+    def test_shrouded_key_bag(self):
+        private_key_info_expected = b64decode(b"""
+MGYCAQAwHwYIKoUDBwEBAQEwEwYHKoUDAgIjAQYIKoUDBwEBAgIEQEYbRu86z+1JFKDcPDN9UbTG
+G2ki9enTqos4KpUU0j9IDpl1UXiaA1YDIwUjlAp+81GkLmyt8Fw6Gt/X5JZySAY=
+        """)
+
+        pfx, tail = PFX().decode(self.pfx_raw)
+        self.assertSequenceEqual(tail, b"")
+        octet_string_safe_contents, tail = OctetStringSafeContents().decode(
+            bytes(pfx["authSafe"]["content"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+        outer_safe_contents = octet_string_safe_contents["safeContents"]
+
+        octet_string_safe_contents, tail = OctetStringSafeContents().decode(
+            bytes(outer_safe_contents[0]["bagValue"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+        safe_bag = octet_string_safe_contents["safeContents"][0]
+        shrouded_key_bag, tail = PKCS8ShroudedKeyBag().decode(
+            bytes(safe_bag["bagValue"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+        pbes2_params, tail = PBES2Params().decode(
+            bytes(shrouded_key_bag["encryptionAlgorithm"]["parameters"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+        pbkdf2_params, tail = PBKDF2Params().decode(
+            bytes(pbes2_params["keyDerivationFunc"]["parameters"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+        enc_scheme_params, tail = Gost2814789Parameters().decode(
+            bytes(pbes2_params["encryptionScheme"]["parameters"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+
+        key = gost34112012_pbkdf2(
+            password=self.password.encode("utf-8"),
+            salt=bytes(pbkdf2_params["salt"]["specified"]),
+            iterations=int(pbkdf2_params["iterationCount"]),
+            dklen=32,
+        )
+        # key = hexdec("309dd0354c5603739403f2335e9e2055138f8b5c98b63009de0635eea1fd7ba8")
+        self.assertSequenceEqual(
+            cfb_decrypt(
+                key,
+                bytes(shrouded_key_bag["encryptedData"]),
+                iv=bytes(enc_scheme_params["iv"]),
+                sbox="Gost28147_tc26_ParamZ",
+            ),
+            private_key_info_expected,
+        )
+
+    def test_encrypted_data(self):
+        cert_bag_expected = b64decode(b"""
+MIIDSjCCA0YGCyqGSIb3DQEMCgEDoIIDHjCCAxoGCiqGSIb3DQEJFgGgggMKBIIDBjCCAwIwggKt
+oAMCAQICEAHQaF8xH5bAAAAACycJAAEwDAYIKoUDBwEBAwIFADBgMQswCQYDVQQGEwJSVTEVMBMG
+A1UEBwwM0JzQvtGB0LrQstCwMQ8wDQYDVQQKDAbQotCaMjYxKTAnBgNVBAMMIENBIGNlcnRpZmlj
+YXRlIChQS0NTIzEyIGV4YW1wbGUpMB4XDTE1MDMyNzA3MjUwMFoXDTIwMDMyNzA3MjMwMFowZDEL
+MAkGA1UEBhMCUlUxFTATBgNVBAcMDNCc0L7RgdC60LLQsDEPMA0GA1UECgwG0KLQmjI2MS0wKwYD
+VQQDDCRUZXN0IGNlcnRpZmljYXRlIDEgKFBLQ1MjMTIgZXhhbXBsZSkwZjAfBggqhQMHAQEBATAT
+BgcqhQMCAiMBBggqhQMHAQECAgNDAARA1xzymkpvr2dYJT8WTOX3Dt96/+hGsXNytUQpkWB5ImJM
+4tg9AsC4RIUwV5H41MhG0uBRFweTzN6AsAdBvhTClYEJADI3MDkwMDAxo4IBKTCCASUwKwYDVR0Q
+BCQwIoAPMjAxNTAzMjcwNzI1MDBagQ8yMDE2MDMyNzA3MjUwMFowDgYDVR0PAQH/BAQDAgTwMB0G
+A1UdDgQWBBQhWOsRQ68yYN2Utg/owHoWcqsVbTAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH
+AwQwDAYDVR0TAQH/BAIwADCBmQYDVR0jBIGRMIGOgBQmnc7Xh5ykb5t/BMwOkxA4drfEmqFkpGIw
+YDELMAkGA1UEBhMCUlUxFTATBgNVBAcMDNCc0L7RgdC60LLQsDEPMA0GA1UECgwG0KLQmjI2MSkw
+JwYDVQQDDCBDQSBjZXJ0aWZpY2F0ZSAoUEtDUyMxMiBleGFtcGxlKYIQAdBoXvL8TSAAAAALJwkA
+ATAMBggqhQMHAQEDAgUAA0EA9oq0Vvk8kkgIwkp0x0J5eKtia4MNTiwKAm7jgnCZIx3O98BThaTX
+3ZQhEo2RL9pTCPr6wFMheeJ+YdGMReXvsjEVMBMGCSqGSIb3DQEJFTEGBAQBAAAA
+        """)
+
+        pfx, tail = PFX().decode(self.pfx_raw)
+        self.assertSequenceEqual(tail, b"")
+        octet_string_safe_contents, tail = OctetStringSafeContents().decode(
+            bytes(pfx["authSafe"]["content"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+        outer_safe_contents = octet_string_safe_contents["safeContents"]
+
+        encrypted_data, tail = EncryptedData().decode(
+            bytes(outer_safe_contents[1]["bagValue"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+        pbes2_params, _ = PBES2Params().decode(
+            bytes(encrypted_data["encryptedContentInfo"]["contentEncryptionAlgorithm"]["parameters"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+        pbkdf2_params, tail = PBKDF2Params().decode(
+            bytes(pbes2_params["keyDerivationFunc"]["parameters"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+        enc_scheme_params, tail = Gost2814789Parameters().decode(
+            bytes(pbes2_params["encryptionScheme"]["parameters"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+        key = gost34112012_pbkdf2(
+            password=self.password.encode("utf-8"),
+            salt=bytes(pbkdf2_params["salt"]["specified"]),
+            iterations=int(pbkdf2_params["iterationCount"]),
+            dklen=32,
+        )
+        # key = hexdec("0e93d71339e7f53b79a0bc41f9109dd4fb60b30ae10736c1bb77b84c07681cfc")
+        self.assertSequenceEqual(
+            cfb_decrypt(
+                key,
+                bytes(encrypted_data["encryptedContentInfo"]["encryptedContent"]),
+                iv=bytes(enc_scheme_params["iv"]),
+                sbox="Gost28147_tc26_ParamZ",
+            ),
+            cert_bag_expected,
+        )
+
+    def test_mac(self):
+        pfx, tail = PFX().decode(self.pfx_raw)
+        self.assertSequenceEqual(tail, b"")
+        octet_string_safe_contents, tail = OctetStringSafeContents().decode(
+            bytes(pfx["authSafe"]["content"]),
+        )
+        self.assertSequenceEqual(tail, b"")
+        outer_safe_contents = octet_string_safe_contents["safeContents"]
+
+        mac_data = pfx["macData"]
+        mac_key = gost34112012_pbkdf2(
+            password=self.password.encode('utf-8'),
+            salt=bytes(mac_data["macSalt"]),
+            iterations=int(mac_data["iterations"]),
+            dklen=96,
+        )[-32:]
+        # mac_key = hexdec("cadbfbf3bceaa9b79f651508fac5abbeb4a13d0bd0e1876bd3c3efb2112128a5")
+        self.assertSequenceEqual(
+            hmac_new(
+                key=mac_key,
+                msg=outer_safe_contents.encode(),
+                digestmod=GOST34112012512,
+            ).digest(),
+            bytes(mac_data["mac"]["digest"]),
+        )
diff --git a/pygost/test_x509.py b/pygost/test_x509.py
new file mode 100644 (file)
index 0000000..cddee1c
--- /dev/null
@@ -0,0 +1,120 @@
+# coding: utf-8
+# PyGOST -- Pure Python GOST cryptographic functions library
+# Copyright (C) 2015-2018 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.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from base64 import b64decode
+from unittest import skipIf
+from unittest import TestCase
+
+from pygost.gost3410 import CURVE_PARAMS
+from pygost.gost3410 import GOST3410Curve
+from pygost.gost3410 import prv_unmarshal
+from pygost.gost3410 import pub_unmarshal
+from pygost.gost3410 import public_key
+from pygost.gost3410 import verify
+from pygost.gost34112012256 import GOST34112012256
+from pygost.gost34112012512 import GOST34112012512
+from pygost.utils import hexdec
+
+try:
+    from pyderasn import OctetString
+
+    from pygost.asn1schemas.x509 import Certificate
+except ImportError:
+    pyderasn_exists = False
+else:
+    pyderasn_exists = True
+
+
+@skipIf(not pyderasn_exists, "PyDERASN dependency is required")
+class TestCertificate(TestCase):
+    """Certificate test vectors from "Использования алгоритмов ГОСТ Р
+    34.10, ГОСТ Р 34.11 в профиле сертификата и списке отзыва
+    сертификатов (CRL) инфраструктуры открытых ключей X.509"
+    (TK26IOK.pdf)
+    """
+
+    def process_cert(self, curve_name, mode, hasher, prv_key_raw, cert_raw):
+        cert, tail = Certificate().decode(cert_raw)
+        self.assertSequenceEqual(tail, b"")
+        curve = GOST3410Curve(*CURVE_PARAMS[curve_name])
+        prv_key = prv_unmarshal(prv_key_raw)
+        pub_key_raw, tail = OctetString().decode(
+            bytes(cert["tbsCertificate"]["subjectPublicKeyInfo"]["subjectPublicKey"])
+        )
+        pub_key = pub_unmarshal(bytes(pub_key_raw), mode=mode)
+        self.assertSequenceEqual(tail, b"")
+        self.assertSequenceEqual(pub_key, public_key(curve, prv_key))
+        self.assertTrue(verify(
+            curve,
+            pub_key,
+            hasher(cert["tbsCertificate"].encode()).digest()[::-1],
+            bytes(cert["signatureValue"]),
+            mode=mode,
+        ))
+
+    def test_256(self):
+        cert_raw = b64decode("""
+MIICYjCCAg+gAwIBAgIBATAKBggqhQMHAQEDAjBWMSkwJwYJKoZIhvcNAQkBFhpH
+b3N0UjM0MTAtMjAxMkBleGFtcGxlLmNvbTEpMCcGA1UEAxMgR29zdFIzNDEwLTIw
+MTIgKDI1NiBiaXQpIGV4YW1wbGUwHhcNMTMxMTA1MTQwMjM3WhcNMzAxMTAxMTQw
+MjM3WjBWMSkwJwYJKoZIhvcNAQkBFhpHb3N0UjM0MTAtMjAxMkBleGFtcGxlLmNv
+bTEpMCcGA1UEAxMgR29zdFIzNDEwLTIwMTIgKDI1NiBiaXQpIGV4YW1wbGUwZjAf
+BggqhQMHAQEBATATBgcqhQMCAiQABggqhQMHAQECAgNDAARAut/Qw1MUq9KPqkdH
+C2xAF3K7TugHfo9n525D2s5mFZdD5pwf90/i4vF0mFmr9nfRwMYP4o0Pg1mOn5Rl
+aXNYraOBwDCBvTAdBgNVHQ4EFgQU1fIeN1HaPbw+XWUzbkJ+kHJUT0AwCwYDVR0P
+BAQDAgHGMA8GA1UdEwQIMAYBAf8CAQEwfgYDVR0BBHcwdYAU1fIeN1HaPbw+XWUz
+bkJ+kHJUT0ChWqRYMFYxKTAnBgkqhkiG9w0BCQEWGkdvc3RSMzQxMC0yMDEyQGV4
+YW1wbGUuY29tMSkwJwYDVQQDEyBHb3N0UjM0MTAtMjAxMiAoMjU2IGJpdCkgZXhh
+bXBsZYIBATAKBggqhQMHAQEDAgNBAF5bm4BbARR6hJLEoWJkOsYV3Hd7kXQQjz3C
+dqQfmHrz6TI6Xojdh/t8ckODv/587NS5/6KsM77vc6Wh90NAT2s=
+        """)
+        prv_key_raw = hexdec("BFCF1D623E5CDD3032A7C6EABB4A923C46E43D640FFEAAF2C3ED39A8FA399924")[::-1]
+        self.process_cert(
+            "GostR3410_2001_CryptoPro_XchA_ParamSet",
+            2001,
+            GOST34112012256,
+            prv_key_raw,
+            cert_raw,
+        )
+
+    def test_512(self):
+        cert_raw = b64decode("""
+MIIC6DCCAlSgAwIBAgIBATAKBggqhQMHAQEDAzBWMSkwJwYJKoZIhvcNAQkBFhpH
+b3N0UjM0MTAtMjAxMkBleGFtcGxlLmNvbTEpMCcGA1UEAxMgR29zdFIzNDEwLTIw
+MTIgKDUxMiBiaXQpIGV4YW1wbGUwHhcNMTMxMDA0MDczNjA0WhcNMzAxMDAxMDcz
+NjA0WjBWMSkwJwYJKoZIhvcNAQkBFhpHb3N0UjM0MTAtMjAxMkBleGFtcGxlLmNv
+bTEpMCcGA1UEAxMgR29zdFIzNDEwLTIwMTIgKDUxMiBiaXQpIGV4YW1wbGUwgaow
+IQYIKoUDBwEBAQIwFQYJKoUDBwECAQICBggqhQMHAQECAwOBhAAEgYATGQ9VCiM5
+FRGCQ8MEz2F1dANqhaEuywa8CbxOnTvaGJpFQVXQwkwvLFAKh7hk542vOEtxpKtT
+CXfGf84nRhMH/Q9bZeAc2eO/yhxrsQhTBufa1Fuou2oe/jUOaG6RAtUUvRzhNTpp
+RGGl1+EIY2vzzUua9j9Ol/gAoy/LNKQIfqOBwDCBvTAdBgNVHQ4EFgQUPcbTRXJZ
+nHtjj+eBP7b5lcTMekIwCwYDVR0PBAQDAgHGMA8GA1UdEwQIMAYBAf8CAQEwfgYD
+VR0BBHcwdYAUPcbTRXJZnHtjj+eBP7b5lcTMekKhWqRYMFYxKTAnBgkqhkiG9w0B
+CQEWGkdvc3RSMzQxMC0yMDEyQGV4YW1wbGUuY29tMSkwJwYDVQQDEyBHb3N0UjM0
+MTAtMjAxMiAoNTEyIGJpdCkgZXhhbXBsZYIBATAKBggqhQMHAQEDAwOBgQBObS7o
+ppPTXzHyVR1DtPa8b57nudJzI4czhsfeX5HDntOq45t9B/qSs8dC6eGxbhHZ9zCO
+SFtxWYdmg0au8XI9Xb8vTC1qdwWID7FFjMWDNQZb6lYh/J+8F2xKylvB5nIlRZqO
+o3eUNFkNyHJwQCk2WoOlO16zwGk2tdKH4KmD5w==
+        """)
+        prv_key_raw = hexdec("3FC01CDCD4EC5F972EB482774C41E66DB7F380528DFE9E67992BA05AEE462435757530E641077CE587B976C8EEB48C48FD33FD175F0C7DE6A44E014E6BCB074B")[::-1]
+        self.process_cert(
+            "GostR3410_2012_TC26_ParamSetB",
+            2012,
+            GOST34112012512,
+            prv_key_raw,
+            cert_raw,
+        )
index b17daef04437535e482ee66735ffd546016b79cf..0093ad01c6df98a8a8a971f1b00b6c8d61adef9c 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -22,7 +22,7 @@ setup(
         "Topic :: Security :: Cryptography",
         "Topic :: Software Development :: Libraries :: Python Modules",
     ],
-    packages=["pygost"],
+    packages=["pygost", "pygost.asn1schemas"],
     package_data={
         "pygost": ["stubs/**/*.pyi"],
     },