From 5013b980d53a9969649a59535b7566f73f11521e Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Sun, 9 Dec 2018 16:16:28 +0300 Subject: [PATCH] More TC26 ASN.1 test vectors --- NEWS | 1 + news.texi | 2 + pygost/Makefile | 4 +- pygost/asn1schemas/__init__.py | 0 pygost/asn1schemas/cms.py | 278 ++++++++++++++++++++++ pygost/asn1schemas/pfx.py | 196 ++++++++++++++++ pygost/asn1schemas/x509.py | 160 +++++++++++++ pygost/test_cms.py | 418 +++++++++++++++++++++++++++++++++ pygost/test_cms_enveloped.py | 305 ------------------------ pygost/test_pfx.py | 218 +++++++++++++++++ pygost/test_x509.py | 120 ++++++++++ setup.py | 2 +- 12 files changed, 1397 insertions(+), 307 deletions(-) create mode 100644 pygost/asn1schemas/__init__.py create mode 100644 pygost/asn1schemas/cms.py create mode 100644 pygost/asn1schemas/pfx.py create mode 100644 pygost/asn1schemas/x509.py create mode 100644 pygost/test_cms.py delete mode 100644 pygost/test_cms_enveloped.py create mode 100644 pygost/test_pfx.py create mode 100644 pygost/test_x509.py diff --git a/NEWS b/NEWS index d519515..92f8f82 100644 --- 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. diff --git a/news.texi b/news.texi index fb7b55b..9313b54 100644 --- 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 `__ dependency required) @end itemize @anchor{Release 3.14} diff --git a/pygost/Makefile b/pygost/Makefile index 33ebd2a..6194911 100644 --- a/pygost/Makefile +++ b/pygost/Makefile @@ -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 index 0000000..e69de29 diff --git a/pygost/asn1schemas/cms.py b/pygost/asn1schemas/cms.py new file mode 100644 index 0000000..187850b --- /dev/null +++ b/pygost/asn1schemas/cms.py @@ -0,0 +1,278 @@ +# coding: utf-8 +# PyGOST -- Pure Python GOST cryptographic functions library +# Copyright (C) 2015-2018 Sergey Matveev +# +# 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 . +"""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 index 0000000..790cc18 --- /dev/null +++ b/pygost/asn1schemas/pfx.py @@ -0,0 +1,196 @@ +# coding: utf-8 +# PyGOST -- Pure Python GOST cryptographic functions library +# Copyright (C) 2015-2018 Sergey Matveev +# +# 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 . +"""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 index 0000000..5756592 --- /dev/null +++ b/pygost/asn1schemas/x509.py @@ -0,0 +1,160 @@ +# coding: utf-8 +# PyGOST -- Pure Python GOST cryptographic functions library +# Copyright (C) 2015-2018 Sergey Matveev +# +# 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 . +""":rfc:`5280` related structures (**NOT COMPLETE**) + +They are taken from `PyDERASN +# +# 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 . + +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 index 9dc3100..0000000 --- a/pygost/test_cms_enveloped.py +++ /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 `__ 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(""" -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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", - ) diff --git a/pygost/test_pfx.py b/pygost/test_pfx.py new file mode 100644 index 0000000..39b477f --- /dev/null +++ b/pygost/test_pfx.py @@ -0,0 +1,218 @@ +# coding: utf-8 +# PyGOST -- Pure Python GOST cryptographic functions library +# Copyright (C) 2015-2018 Sergey Matveev +# +# 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 . + +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 index 0000000..cddee1c --- /dev/null +++ b/pygost/test_x509.py @@ -0,0 +1,120 @@ +# coding: utf-8 +# PyGOST -- Pure Python GOST cryptographic functions library +# Copyright (C) 2015-2018 Sergey Matveev +# +# 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 . + +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, + ) diff --git a/setup.py b/setup.py index b17daef..0093ad0 100644 --- 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"], }, -- 2.44.0