From f56f450fdf6ecfb9a95be2bfa1d08ec7ae673008 Mon Sep 17 00:00:00 2001 From: vadzik Date: Sun, 14 Apr 2024 16:15:41 +0300 Subject: [PATCH] [DEL] Delete pygost --- app/utils_drive.py | 20 +- pygost-5.13/AUTHORS | 1 - pygost-5.13/COPYING | 674 ----------- pygost-5.13/FAQ | 24 - pygost-5.13/INSTALL | 43 - pygost-5.13/MANIFEST.in | 8 - pygost-5.13/NEWS | 271 ----- pygost-5.13/PKG-INFO | 96 -- pygost-5.13/README | 73 -- pygost-5.13/THANKS | 8 - pygost-5.13/VERSION | 1 - pygost-5.13/build/lib/pygost/__init__.py | 6 - .../build/lib/pygost/asn1schemas/__init__.py | 0 .../lib/pygost/asn1schemas/cert-dane-hash.py | 18 - .../asn1schemas/cert-selfsigned-example.py | 348 ------ .../build/lib/pygost/asn1schemas/cms.py | 431 ------- .../build/lib/pygost/asn1schemas/oids.py | 60 - .../build/lib/pygost/asn1schemas/pfx.py | 250 ---- .../build/lib/pygost/asn1schemas/pkcs10.py | 49 - .../build/lib/pygost/asn1schemas/prvkey.py | 100 -- .../build/lib/pygost/asn1schemas/x509.py | 262 ---- pygost-5.13/build/lib/pygost/gost28147.py | 457 ------- pygost-5.13/build/lib/pygost/gost28147_mac.py | 99 -- pygost-5.13/build/lib/pygost/gost3410.py | 412 ------- pygost-5.13/build/lib/pygost/gost3410_vko.py | 97 -- pygost-5.13/build/lib/pygost/gost34112012.py | 299 ----- .../build/lib/pygost/gost34112012256.py | 16 - .../build/lib/pygost/gost34112012512.py | 21 - pygost-5.13/build/lib/pygost/gost341194.py | 192 --- pygost-5.13/build/lib/pygost/gost3412.py | 186 --- pygost-5.13/build/lib/pygost/gost3413.py | 392 ------ pygost-5.13/build/lib/pygost/iface.py | 50 - pygost-5.13/build/lib/pygost/kdf.py | 81 -- pygost-5.13/build/lib/pygost/mgm.py | 168 --- pygost-5.13/build/lib/pygost/pbkdf2.py | 41 - .../lib/pygost/stubs/pygost/__init__.pyi | 0 .../lib/pygost/stubs/pygost/gost28147.pyi | 101 -- .../lib/pygost/stubs/pygost/gost28147_mac.pyi | 25 - .../lib/pygost/stubs/pygost/gost3410.pyi | 72 -- .../lib/pygost/stubs/pygost/gost3410_vko.pyi | 17 - .../lib/pygost/stubs/pygost/gost34112012.pyi | 18 - .../pygost/stubs/pygost/gost34112012256.pyi | 21 - .../pygost/stubs/pygost/gost34112012512.pyi | 24 - .../lib/pygost/stubs/pygost/gost341194.pyi | 25 - .../lib/pygost/stubs/pygost/gost3412.pyi | 18 - .../lib/pygost/stubs/pygost/gost3413.pyi | 81 -- .../build/lib/pygost/stubs/pygost/iface.pyi | 19 - .../build/lib/pygost/stubs/pygost/kdf.pyi | 22 - .../build/lib/pygost/stubs/pygost/mgm.pyi | 17 - .../build/lib/pygost/stubs/pygost/utils.pyi | 19 - .../build/lib/pygost/stubs/pygost/wrap.pyi | 31 - pygost-5.13/build/lib/pygost/test_cms.py | 1078 ----------------- .../build/lib/pygost/test_gost28147.py | 384 ------ .../build/lib/pygost/test_gost28147_mac.py | 63 - pygost-5.13/build/lib/pygost/test_gost3410.py | 495 -------- .../build/lib/pygost/test_gost3410_vko.py | 125 -- .../build/lib/pygost/test_gost34112012.py | 159 --- .../build/lib/pygost/test_gost341194.py | 200 --- pygost-5.13/build/lib/pygost/test_gost3412.py | 137 --- pygost-5.13/build/lib/pygost/test_gost3413.py | 766 ------------ pygost-5.13/build/lib/pygost/test_kdf.py | 58 - pygost-5.13/build/lib/pygost/test_mgm.py | 75 -- pygost-5.13/build/lib/pygost/test_pfx.py | 680 ----------- pygost-5.13/build/lib/pygost/test_wrap.py | 111 -- pygost-5.13/build/lib/pygost/test_x509.py | 433 ------- pygost-5.13/build/lib/pygost/utils.py | 101 -- pygost-5.13/build/lib/pygost/wrap.py | 152 --- pygost-5.13/dist/pygost-5.13-py3.12.egg | Bin 317057 -> 0 bytes pygost-5.13/pygost.egg-info/PKG-INFO | 93 -- pygost-5.13/pygost.egg-info/SOURCES.txt | 71 -- .../pygost.egg-info/dependency_links.txt | 1 - pygost-5.13/pygost.egg-info/top_level.txt | 1 - pygost-5.13/pygost/__init__.py | 6 - pygost-5.13/pygost/asn1schemas/__init__.py | 0 .../pygost/asn1schemas/cert-dane-hash.py | 18 - .../asn1schemas/cert-selfsigned-example.py | 348 ------ pygost-5.13/pygost/asn1schemas/cms.py | 431 ------- pygost-5.13/pygost/asn1schemas/oids.py | 60 - pygost-5.13/pygost/asn1schemas/pfx.py | 250 ---- pygost-5.13/pygost/asn1schemas/pkcs10.py | 49 - pygost-5.13/pygost/asn1schemas/prvkey.py | 100 -- pygost-5.13/pygost/asn1schemas/x509.py | 262 ---- pygost-5.13/pygost/gost28147.py | 457 ------- pygost-5.13/pygost/gost28147_mac.py | 99 -- pygost-5.13/pygost/gost3410.py | 412 ------- pygost-5.13/pygost/gost3410_vko.py | 97 -- pygost-5.13/pygost/gost34112012.py | 299 ----- pygost-5.13/pygost/gost34112012256.py | 16 - pygost-5.13/pygost/gost34112012512.py | 21 - pygost-5.13/pygost/gost341194.py | 192 --- pygost-5.13/pygost/gost3412.py | 186 --- pygost-5.13/pygost/gost3413.py | 392 ------ pygost-5.13/pygost/iface.py | 50 - pygost-5.13/pygost/kdf.py | 81 -- pygost-5.13/pygost/mgm.py | 168 --- pygost-5.13/pygost/pbkdf2.py | 41 - pygost-5.13/pygost/stubs/pygost/__init__.pyi | 0 pygost-5.13/pygost/stubs/pygost/gost28147.pyi | 101 -- .../pygost/stubs/pygost/gost28147_mac.pyi | 25 - pygost-5.13/pygost/stubs/pygost/gost3410.pyi | 72 -- .../pygost/stubs/pygost/gost3410_vko.pyi | 17 - .../pygost/stubs/pygost/gost34112012.pyi | 18 - .../pygost/stubs/pygost/gost34112012256.pyi | 21 - .../pygost/stubs/pygost/gost34112012512.pyi | 24 - .../pygost/stubs/pygost/gost341194.pyi | 25 - pygost-5.13/pygost/stubs/pygost/gost3412.pyi | 18 - pygost-5.13/pygost/stubs/pygost/gost3413.pyi | 81 -- pygost-5.13/pygost/stubs/pygost/iface.pyi | 19 - pygost-5.13/pygost/stubs/pygost/kdf.pyi | 22 - pygost-5.13/pygost/stubs/pygost/mgm.pyi | 17 - pygost-5.13/pygost/stubs/pygost/utils.pyi | 19 - pygost-5.13/pygost/stubs/pygost/wrap.pyi | 31 - pygost-5.13/pygost/test_cms.py | 1078 ----------------- pygost-5.13/pygost/test_gost28147.py | 384 ------ pygost-5.13/pygost/test_gost28147_mac.py | 63 - pygost-5.13/pygost/test_gost3410.py | 495 -------- pygost-5.13/pygost/test_gost3410_vko.py | 125 -- pygost-5.13/pygost/test_gost34112012.py | 159 --- pygost-5.13/pygost/test_gost341194.py | 200 --- pygost-5.13/pygost/test_gost3412.py | 137 --- pygost-5.13/pygost/test_gost3413.py | 766 ------------ pygost-5.13/pygost/test_kdf.py | 58 - pygost-5.13/pygost/test_mgm.py | 75 -- pygost-5.13/pygost/test_pfx.py | 680 ----------- pygost-5.13/pygost/test_wrap.py | 111 -- pygost-5.13/pygost/test_x509.py | 433 ------- pygost-5.13/pygost/utils.py | 101 -- pygost-5.13/pygost/wrap.py | 152 --- pygost-5.13/setup.cfg | 4 - pygost-5.13/setup.py | 42 - 130 files changed, 7 insertions(+), 20548 deletions(-) delete mode 100644 pygost-5.13/AUTHORS delete mode 100644 pygost-5.13/COPYING delete mode 100644 pygost-5.13/FAQ delete mode 100644 pygost-5.13/INSTALL delete mode 100644 pygost-5.13/MANIFEST.in delete mode 100644 pygost-5.13/NEWS delete mode 100644 pygost-5.13/PKG-INFO delete mode 100644 pygost-5.13/README delete mode 100644 pygost-5.13/THANKS delete mode 100644 pygost-5.13/VERSION delete mode 100644 pygost-5.13/build/lib/pygost/__init__.py delete mode 100644 pygost-5.13/build/lib/pygost/asn1schemas/__init__.py delete mode 100644 pygost-5.13/build/lib/pygost/asn1schemas/cert-dane-hash.py delete mode 100644 pygost-5.13/build/lib/pygost/asn1schemas/cert-selfsigned-example.py delete mode 100644 pygost-5.13/build/lib/pygost/asn1schemas/cms.py delete mode 100644 pygost-5.13/build/lib/pygost/asn1schemas/oids.py delete mode 100644 pygost-5.13/build/lib/pygost/asn1schemas/pfx.py delete mode 100644 pygost-5.13/build/lib/pygost/asn1schemas/pkcs10.py delete mode 100644 pygost-5.13/build/lib/pygost/asn1schemas/prvkey.py delete mode 100644 pygost-5.13/build/lib/pygost/asn1schemas/x509.py delete mode 100644 pygost-5.13/build/lib/pygost/gost28147.py delete mode 100644 pygost-5.13/build/lib/pygost/gost28147_mac.py delete mode 100644 pygost-5.13/build/lib/pygost/gost3410.py delete mode 100644 pygost-5.13/build/lib/pygost/gost3410_vko.py delete mode 100644 pygost-5.13/build/lib/pygost/gost34112012.py delete mode 100644 pygost-5.13/build/lib/pygost/gost34112012256.py delete mode 100644 pygost-5.13/build/lib/pygost/gost34112012512.py delete mode 100644 pygost-5.13/build/lib/pygost/gost341194.py delete mode 100644 pygost-5.13/build/lib/pygost/gost3412.py delete mode 100644 pygost-5.13/build/lib/pygost/gost3413.py delete mode 100644 pygost-5.13/build/lib/pygost/iface.py delete mode 100644 pygost-5.13/build/lib/pygost/kdf.py delete mode 100644 pygost-5.13/build/lib/pygost/mgm.py delete mode 100644 pygost-5.13/build/lib/pygost/pbkdf2.py delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/__init__.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/gost28147.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/gost28147_mac.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/gost3410.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/gost3410_vko.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/gost34112012.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/gost34112012256.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/gost34112012512.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/gost341194.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/gost3412.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/gost3413.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/iface.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/kdf.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/mgm.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/utils.pyi delete mode 100644 pygost-5.13/build/lib/pygost/stubs/pygost/wrap.pyi delete mode 100644 pygost-5.13/build/lib/pygost/test_cms.py delete mode 100644 pygost-5.13/build/lib/pygost/test_gost28147.py delete mode 100644 pygost-5.13/build/lib/pygost/test_gost28147_mac.py delete mode 100644 pygost-5.13/build/lib/pygost/test_gost3410.py delete mode 100644 pygost-5.13/build/lib/pygost/test_gost3410_vko.py delete mode 100644 pygost-5.13/build/lib/pygost/test_gost34112012.py delete mode 100644 pygost-5.13/build/lib/pygost/test_gost341194.py delete mode 100644 pygost-5.13/build/lib/pygost/test_gost3412.py delete mode 100644 pygost-5.13/build/lib/pygost/test_gost3413.py delete mode 100644 pygost-5.13/build/lib/pygost/test_kdf.py delete mode 100644 pygost-5.13/build/lib/pygost/test_mgm.py delete mode 100644 pygost-5.13/build/lib/pygost/test_pfx.py delete mode 100644 pygost-5.13/build/lib/pygost/test_wrap.py delete mode 100644 pygost-5.13/build/lib/pygost/test_x509.py delete mode 100644 pygost-5.13/build/lib/pygost/utils.py delete mode 100644 pygost-5.13/build/lib/pygost/wrap.py delete mode 100644 pygost-5.13/dist/pygost-5.13-py3.12.egg delete mode 100644 pygost-5.13/pygost.egg-info/PKG-INFO delete mode 100644 pygost-5.13/pygost.egg-info/SOURCES.txt delete mode 100644 pygost-5.13/pygost.egg-info/dependency_links.txt delete mode 100644 pygost-5.13/pygost.egg-info/top_level.txt delete mode 100644 pygost-5.13/pygost/__init__.py delete mode 100644 pygost-5.13/pygost/asn1schemas/__init__.py delete mode 100755 pygost-5.13/pygost/asn1schemas/cert-dane-hash.py delete mode 100755 pygost-5.13/pygost/asn1schemas/cert-selfsigned-example.py delete mode 100644 pygost-5.13/pygost/asn1schemas/cms.py delete mode 100644 pygost-5.13/pygost/asn1schemas/oids.py delete mode 100644 pygost-5.13/pygost/asn1schemas/pfx.py delete mode 100644 pygost-5.13/pygost/asn1schemas/pkcs10.py delete mode 100644 pygost-5.13/pygost/asn1schemas/prvkey.py delete mode 100644 pygost-5.13/pygost/asn1schemas/x509.py delete mode 100644 pygost-5.13/pygost/gost28147.py delete mode 100644 pygost-5.13/pygost/gost28147_mac.py delete mode 100644 pygost-5.13/pygost/gost3410.py delete mode 100644 pygost-5.13/pygost/gost3410_vko.py delete mode 100644 pygost-5.13/pygost/gost34112012.py delete mode 100644 pygost-5.13/pygost/gost34112012256.py delete mode 100644 pygost-5.13/pygost/gost34112012512.py delete mode 100644 pygost-5.13/pygost/gost341194.py delete mode 100644 pygost-5.13/pygost/gost3412.py delete mode 100644 pygost-5.13/pygost/gost3413.py delete mode 100644 pygost-5.13/pygost/iface.py delete mode 100644 pygost-5.13/pygost/kdf.py delete mode 100644 pygost-5.13/pygost/mgm.py delete mode 100644 pygost-5.13/pygost/pbkdf2.py delete mode 100644 pygost-5.13/pygost/stubs/pygost/__init__.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/gost28147.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/gost28147_mac.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/gost3410.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/gost3410_vko.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/gost34112012.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/gost34112012256.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/gost34112012512.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/gost341194.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/gost3412.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/gost3413.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/iface.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/kdf.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/mgm.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/utils.pyi delete mode 100644 pygost-5.13/pygost/stubs/pygost/wrap.pyi delete mode 100644 pygost-5.13/pygost/test_cms.py delete mode 100644 pygost-5.13/pygost/test_gost28147.py delete mode 100644 pygost-5.13/pygost/test_gost28147_mac.py delete mode 100644 pygost-5.13/pygost/test_gost3410.py delete mode 100644 pygost-5.13/pygost/test_gost3410_vko.py delete mode 100644 pygost-5.13/pygost/test_gost34112012.py delete mode 100644 pygost-5.13/pygost/test_gost341194.py delete mode 100644 pygost-5.13/pygost/test_gost3412.py delete mode 100644 pygost-5.13/pygost/test_gost3413.py delete mode 100644 pygost-5.13/pygost/test_kdf.py delete mode 100644 pygost-5.13/pygost/test_mgm.py delete mode 100644 pygost-5.13/pygost/test_pfx.py delete mode 100644 pygost-5.13/pygost/test_wrap.py delete mode 100644 pygost-5.13/pygost/test_x509.py delete mode 100644 pygost-5.13/pygost/utils.py delete mode 100644 pygost-5.13/pygost/wrap.py delete mode 100644 pygost-5.13/setup.cfg delete mode 100644 pygost-5.13/setup.py diff --git a/app/utils_drive.py b/app/utils_drive.py index 510a030..edee52a 100644 --- a/app/utils_drive.py +++ b/app/utils_drive.py @@ -1,9 +1,8 @@ import threading import os -import pygost.gost3412 as gost -# from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -# from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.backends import default_backend erasing_methods = { "2 прохода": { @@ -32,17 +31,12 @@ erasing_methods = { def get_random_bytes(size): seed=os.urandom(32) - # backend = default_backend() - # cipher = Cipher(algorithms.AES(seed), modes.CTR(b'\0'*16), backend=backend) - # encryptor = cipher.encryptor() - + backend = default_backend() + cipher = Cipher(algorithms.AES(seed), modes.CTR(b'\0'*16), backend=backend) + encryptor = cipher.encryptor() nulls=b'\0'*(size) - enc = gost.GOST3412Kuznechik(seed) - rounds = int(size / 32) - data = b'' - for i in range(512): - data += enc.encrypt(nulls) - return data + + return encryptor.update(nulls) class Drive(): diff --git a/pygost-5.13/AUTHORS b/pygost-5.13/AUTHORS deleted file mode 100644 index f047789..0000000 --- a/pygost-5.13/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -* Sergey Matveev diff --git a/pygost-5.13/COPYING b/pygost-5.13/COPYING deleted file mode 100644 index 9a2708d..0000000 --- a/pygost-5.13/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - 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 . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/pygost-5.13/FAQ b/pygost-5.13/FAQ deleted file mode 100644 index 63e62d0..0000000 --- a/pygost-5.13/FAQ +++ /dev/null @@ -1,24 +0,0 @@ -Frequently asked questions -************************** - -My signature is not validated by other implementations. What is wrong? - - Try to reverse it ('sign[::-1]'). Try to swap its halves - ('sign[len(sign)/2:] + sign[:len(sign)/2]'). Try to reverse its - swapped halves too. - - It is GOST: do you expect serialization unification?! - -My signature is *still* not validated by other implementations! - - Try to reverse digest you are signing/verifying ('dgst[::-1]'). - - It is GOST: do you expect serialization unification?! - -Everything above did not help me. Does PyGOST sucks? - - No way! You still have not tried to reverse your binary private - key, public key and swap its halves. - - It is GOST: do you expect serialization unification?! - diff --git a/pygost-5.13/INSTALL b/pygost-5.13/INSTALL deleted file mode 100644 index e94b639..0000000 --- a/pygost-5.13/INSTALL +++ /dev/null @@ -1,43 +0,0 @@ -Download -******** - -No additional dependencies except Python 2.7/3.x interpreter are -required. - - Preferable way is to download tarball with the signature: - - $ [fetch|wget] http://www.pygost.cypherpunks.ru/pygost-5.13.tar.zst - $ [fetch|wget] http://www.pygost.cypherpunks.ru/pygost-5.13.tar.zst.{asc,sig} - [verify signature] - $ zstd -d < pygost-5.13.tar.zst | tar xf - - $ cd pygost-5.13 - $ python setup.py install - - You can obtain releases source code prepared tarballs on -. You *have to* verify downloaded -tarballs authenticity to be sure that you retrieved trusted and -untampered software. There are two options: - -OpenPGP (https://www.openpgp.org/) '.asc' signature - Use GNU Privacy Guard (https://www.gnupg.org/) free software - implementation. For the very first time it is necessary to get - signing public key and import it. It is provided here - (PUBKEY-PGP.asc), but you should check alternate resources. - - pub rsa2048/0xE6FD1269CD0C009E 2016-09-13 - F55A 7619 3A0C 323A A031 0E6B E6FD 1269 CD0C 009E - uid PyGOST releases - - $ gpg --auto-key-locate dane --locate-keys pygost at cypherpunks dot ru - $ gpg --auto-key-locate wkd --locate-keys pygost at cypherpunks dot ru - -OpenSSH (https://www.openssh.com/) '.sig' signature - Public key (PUBKEY-SSH.pub) and its OpenPGP signature - (PUBKEY-SSH.pub.asc) made with the key above. Its fingerprint: - 'SHA256:/Z3T/T2sXaaunefAL6tz3ZykHTDYIMh5TLd9Hh9mxlU'. - - $ ssh-keygen -Y verify -f PUBKEY-SSH.pub -I pygost@cypherpunks.ru -n file \ - -s pygost-5.13.tar.zst.sig < pygost-5.13.tar.zst - - You can obtain development source code with 'git clone -git://git.cypherpunks.ru/pygost.git'. diff --git a/pygost-5.13/MANIFEST.in b/pygost-5.13/MANIFEST.in deleted file mode 100644 index f7ccb5c..0000000 --- a/pygost-5.13/MANIFEST.in +++ /dev/null @@ -1,8 +0,0 @@ -include AUTHORS -include COPYING -include FAQ -include INSTALL -include NEWS -include README -include THANKS -include VERSION diff --git a/pygost-5.13/NEWS b/pygost-5.13/NEWS deleted file mode 100644 index 781db0d..0000000 --- a/pygost-5.13/NEWS +++ /dev/null @@ -1,271 +0,0 @@ -News -**** - -*5.13* - Ability to use masked 34.10 private keys. - -*5.12* - Fixed incorrect digest calculation when using - 'GOST34112012*.update()' method. - -*5.11* - 'gost34112012''s 'update()'/'digest()' methods are streaming now - - they do not store the whole data in memory. - -*5.10* - Added ISO 10126 'pygost.gost3413.(un)pad_iso10126' padding support. - -*5.9* - Fixed 'wrap.wrap_cryptopro', that ignored Sbox for key - diversification. - -*5.8* - Added human-readable name of the curve in 'GOST3410Curve.name'. - -*5.7* - Fixed MGM ignoring of the set tag size. - -*5.6* - Fixed lint errors for previous release. - -*5.5* - More 34.10 curve parameters aliases: - id-tc26-gost-3410-2012-256-paramSetA -> id-tc26-gost-3410-12-256-paramSetA - id-tc26-gost-3410-2012-256-paramSetB -> id-tc26-gost-3410-12-256-paramSetB - id-tc26-gost-3410-2012-256-paramSetC -> id-tc26-gost-3410-12-256-paramSetC - id-tc26-gost-3410-2012-256-paramSetD -> id-tc26-gost-3410-12-256-paramSetD - id-tc26-gost-3410-2012-512-paramSetTest -> id-tc26-gost-3410-12-512-paramSetTest - id-tc26-gost-3410-2012-512-paramSetA -> id-tc26-gost-3410-12-512-paramSetA - id-tc26-gost-3410-2012-512-paramSetB -> id-tc26-gost-3410-12-512-paramSetB - id-tc26-gost-3410-2012-512-paramSetC -> id-tc26-gost-3410-12-512-paramSetC - -*5.4* - 'gost3410.prv_marshal' helper can make private keys that are in - curve's Q field, for better compatibility with some - implementations. - -*5.3* - * More than 4 times speed increase of 'gost34112012'. - * 'asn1schemas/cert-selfsigned-example.py' optionally can issue - CA signed child certificate. - -*5.2* - * 'GOST3410Curve' has '.contains(point)' method for checking if - point is on the curve. - * 'gost3410_vko' functions check if remote peer's public key is - on the curve. - * Small typing stubs fixes. - -*5.1* - Small typing stubs fixes. - -*5.0* - * Backward incompatible removing of misleading and excess 'mode' - keyword argument from all 'gost3410*' related functions. - Point/key sizes are determined by looking at curve's - parameters size. - * 'asn1schemas/cert-selfsigned-example.py' optionally can create - CA certificate. - -*4.9* - * *Fixed* nasty bug with Edwards curves using in 34.10-VKO - functions: curve's cofactor has not been used - * CTR-ACPKM mode of operation - * OMAC-ACPKM-Master moder of operation - * KExp15/KImp15 key export/import functions - * KDF_GOSTR3411_2012_256, KDF_TREE_GOSTR3411_2012_256 - * KEG export key generation function - -*4.8* - MGM AEAD mode for 64 and 128 bit ciphers. - -*4.7* - Removed 'gost28147.addmod' for simplicity. - -*4.6* - Fix invalid 'gost28147.addmod''s behaviour with much bigger values - than the modulo. - -*4.5* - Fixed digest endianness and more RFC4491bis conformance in - 'asn1schemas/cert-selfsigned-example.py' certificate's. - -*4.4* - * 'id-tc26-gost-3410-2012-512-paramSetTest' curve - * Simple FAQ - * More test vectors for 34.10-2012 - * More X.509, PKCS #10 and corresponding ASN.1 helper structures - -*4.3* - Dummy release with fixed 'pygost.__version__'. - -*4.2* - * 'pygost.gost3410.sign' accepts predefined random data used for - k/r generation - * More test vectors for 34.10-2012 - -*4.1* - * PEP-396 compatible module's '__version__' - * Curve parameters aliases: - id-GostR3410-2001-CryptoPro-XchA-ParamSet -> id-GostR3410-2001-CryptoPro-A-ParamSet - id-GostR3410-2001-CryptoPro-XchB-ParamSet -> id-GostR3410-2001-CryptoPro-C-ParamSet - id-tc26-gost-3410-2012-256-paramSetB -> id-GostR3410-2001-CryptoPro-A-ParamSet - id-tc26-gost-3410-2012-256-paramSetC -> id-GostR3410-2001-CryptoPro-B-ParamSet - id-tc26-gost-3410-2012-256-paramSetD -> id-GostR3410-2001-CryptoPro-C-ParamSet - * Forbid any later GNU GPL version autousage (project's licence - now is GNU GPLv3 only) - -*4.0* - * 34.10-2012 TC26 twisted Edwards curve related parameters - * Coordinates conversion from twisted Edwards to Weierstrass - form and vice versa - * More test vectors - * Backward incompatible Sbox and curves parameters renaming, to - comply with OIDs identifying them: - Gost2814789_TestParamSet -> id-Gost28147-89-TestParamSet - Gost28147_CryptoProParamSetA -> id-Gost28147-89-CryptoPro-A-ParamSet - Gost28147_CryptoProParamSetB -> id-Gost28147-89-CryptoPro-B-ParamSet - Gost28147_CryptoProParamSetC -> id-Gost28147-89-CryptoPro-C-ParamSet - Gost28147_CryptoProParamSetD -> id-Gost28147-89-CryptoPro-D-ParamSet - Gost28147_tc26_ParamZ -> id-tc26-gost-28147-param-Z - GostR3411_94_TestParamSet -> id-GostR3411-94-TestParamSet - GostR3411_94_CryptoProParamSet -> id-GostR3411-94-CryptoProParamSet - - GostR3410_2001_TestParamSet -> id-GostR3410-2001-TestParamSet - GostR3410_2001_CryptoPro_A_ParamSet -> id-GostR3410-2001-CryptoPro-A-ParamSet - GostR3410_2001_CryptoPro_B_ParamSet -> id-GostR3410-2001-CryptoPro-B-ParamSet - GostR3410_2001_CryptoPro_C_ParamSet -> id-GostR3410-2001-CryptoPro-C-ParamSet - GostR3410_2001_CryptoPro_XchA_ParamSet -> id-GostR3410-2001-CryptoPro-XchA-ParamSet - GostR3410_2001_CryptoPro_XchB_ParamSet -> id-GostR3410-2001-CryptoPro-XchB-ParamSet - GostR3410_2012_TC26_256_ParamSetA -> id-tc26-gost-3410-2012-256-paramSetA - GostR3410_2012_TC26_ParamSetA -> id-tc26-gost-3410-12-512-paramSetA - GostR3410_2012_TC26_ParamSetB -> id-tc26-gost-3410-12-512-paramSetB - GostR3410_2012_TC26_ParamSetC -> id-tc26-gost-3410-2012-512-paramSetC - * Backward incompatible 'GOST3410Curve' initialization: all - parameters are passed not as big-endian encoded binaries, but - as integers - * Backward incompatible change: 'gost3410.CURVE_PARAMS' is - disappeared. 'gost3410.CURVES' dictionary holds already - initialized 'GOST3410Curve'. Just use - 'CURVES["id-tc26-gost-3410-12-512-paramSetA"]' instead of - 'GOST3410Curve(*CURVE_PARAMS["id-tc26-gost-3410-12-512-paramSetA"])' - -*3.15* - * Licence changed back to GNU GPLv3+. GNU LGPLv3+ licenced - versions are not available anymore - * More ASN.1-based test vectors (PyDERASN - (http://www.pyderasn.cypherpunks.ru/) dependency required) - -*3.14* - Add missing typing stubs related to previous release. - -*3.13* - * Ability to explicitly specify used 28147-89 Sbox in - 'pygost.wrap.*' functions - * Ability to use key meshing in 28147-89 CBC mode - -*3.12* - * Added mode argument to 'pygost.gost3410_vko.kek_34102012256', - because 256-bit private keys can be used with that algorithm - too. - * Fix incorrect degree sanitizing in - 'pygost.gost3410.GOST3410Curve.exp' preventing using of - 'UKM=1' in 'pygost.gost3410_vko.kek_*' functions. - -*3.11* - Fixed PEP247 typing stub with invalid hexdigest method. - -*3.10* - Additional missing 34.11-* typing stubs. - -*3.9* - Add missing 34.11-2012 PBKDF2 typing stub. - -*3.8* - * 34.11-2012 based PBKDF2 function added - * 34.13-2015 does not require double blocksized IVs - -*3.7* - Fixed 34.13-2015 OFB bug with IVs longer than 2 blocks. - -*3.6* - Fixed source files installation during 'setup.py install' - invocation. - -*3.5* - Dummy release: added long description in package metadata. - -*3.4* - * Small mypy stubs related fixes - * Licence changed from GNU GPLv3+ to GNU LGPLv3+ - -*3.3* - * 'GOST3412Kuz' renamed to 'GOST3412Kuznechik' - * 'GOST3412Magma' implements GOST R 34.12-2015 Magma 64-bit - block cipher - -*3.2* - 34.13-2015 block cipher modes of operation implementations. - -*3.1* - Fixed mypy stubs related to PEP247-successors. - -*3.0* - * 'gost3411_94' renamed to 'gost341194' - * 'gost3411_2012' renamed and split to 'gost34112012256', - 'gost34112012512' - * 'GOST34112012' split to 'GOST34112012256', 'GOST34112012512' - * 'gost3410.kek' moved to separate 'gost3410_vko.kek_34102001' - * VKO GOST R 34.10-2012 appeared in 'gost3410_vko', with test - vectors - * 34.11-94 digest is reversed, to be compatible with HMAC and - PBKDF2 test vectors describe in TC26 documents - * 34.11-94 PBKDF2 test vectors added - * 'gost3410.prv_unmarshal', 'gost3410.pub_marshal', - 'gost3410.pub_unmarshal' helpers added, removing the need of - 'x509' module at all - * 'gost3410.verify' requires '(pubX, pubY)' tuple, instead of - two separate 'pubX', 'pubY' arguments - * 34.11-94 based PBKDF2 function added - -*2.4* - Fixed 34.13 mypy stub. - -*2.3* - Typo and pylint fixes. - -*2.2* - GOST R 34.13-2015 padding methods. - -*2.1* - Documentation and supplementary files refactoring. - -*2.0* - PEP-0247 compatible hashers and MAC. - -*1.0* - * Ability to specify curve in pygost.x509 module - * Ability to use 34.10-2012 in pygost.x509 functions - * Renamed classes and modules: - pygost.gost3410.SIZE_34100 -> pygost.gost3410.SIZE_3410_2001 - pygost.gost3410.SIZE_34112 -> pygost.gost3410.SIZE_3410_2012 - pygost.gost3411_12.GOST341112 -> pygost.gost3411_2012.GOST34112012 - -*0.16* - 34.10-2012 TC26 curve parameters. - -*0.15* - PEP-0484 static typing hints. - -*0.14* - 34.10-2012 workability fix. - -*0.13* - Python3 compatibility. - -*0.11* - GOST R 34.12-2015 Кузнечик (Kuznechik) implementation. - -*0.10* - CryptoPro and GOST key wrapping, CryptoPro key meshing. - diff --git a/pygost-5.13/PKG-INFO b/pygost-5.13/PKG-INFO deleted file mode 100644 index 601c6f7..0000000 --- a/pygost-5.13/PKG-INFO +++ /dev/null @@ -1,96 +0,0 @@ -Metadata-Version: 2.1 -Name: pygost -Version: 5.13 -Summary: Pure Python GOST cryptographic functions library -Home-page: http://www.pygost.cypherpunks.ru/ -Author: Sergey Matveev -Author-email: stargrave@stargrave.org -License: GPLv3 -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) -Classifier: Natural Language :: English -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 3 -Classifier: Topic :: Security :: Cryptography -Classifier: Topic :: Software Development :: Libraries :: Python Modules -License-File: COPYING -License-File: AUTHORS - -Pure Python 2.7/3.x GOST cryptographic functions library. - -GOST is GOvernment STandard of Russian Federation (and Soviet Union). - -* GOST 28147-89 (RFC 5830) block cipher with ECB, CNT (CTR), CFB, MAC, - CBC (RFC 4357) modes of operation -* various 28147-89-related S-boxes included -* GOST R 34.11-94 hash function (RFC 5831) -* GOST R 34.11-94 based PBKDF2 function -* GOST R 34.11-2012 Стрибог (Streebog) hash function (RFC 6986) -* GOST R 34.11-2012 based PBKDF2 function (Р 50.1.111-2016) -* GOST R 34.10-2001 (RFC 5832) public key signature function -* GOST R 34.10-2012 (RFC 7091) public key signature function -* various 34.10 curve parameters included -* Coordinates conversion from twisted Edwards to Weierstrass form and - vice versa -* VKO GOST R 34.10-2001 key agreement function (RFC 4357) -* VKO GOST R 34.10-2012 key agreement function (RFC 7836) -* 28147-89 and CryptoPro key wrapping (RFC 4357) -* 28147-89 CryptoPro key meshing for CFB and CBC modes (RFC 4357) -* RFC 4491 (using GOST algorithms with X.509) compatibility helpers -* GOST R 34.12-2015 128-bit block cipher Кузнечик (Kuznechik) (RFC 7801) -* GOST R 34.12-2015 64-bit block cipher Магма (Magma) -* GOST R 34.13-2015 padding methods and block cipher modes of operation - (ECB, CTR, OFB, CBC, CFB, MAC), ISO 10126 padding -* MGM AEAD mode for 64 and 128 bit ciphers (RFC 9058) -* CTR-ACPKM, OMAC-ACPKM-Master modes of operation (Р 1323565.1.017-2018) -* KExp15/KImp15 key export/import functions (Р 1323565.1.017-2018) -* KDF_GOSTR3411_2012_256, KDF_TREE_GOSTR3411_2012_256 (Р 50.1.113-2016) -* KEG export key generation function (Р 1323565.1.020-2018) -* PEP247-compatible hash/MAC functions - -Known problems: low performance and non time-constant calculations. - -Example 34.10-2012 keypair generation, signing and verifying: - - >>> from pygost.gost3410 import CURVES - >>> curve = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - >>> from os import urandom - >>> prv_raw = urandom(64) - >>> from pygost.gost3410 import prv_unmarshal - >>> from pygost.gost3410 import prv_marshal - >>> prv = prv_unmarshal(prv_raw) - >>> prv_raw = prv_marshal(curve, prv) - >>> from pygost.gost3410 import public_key - >>> pub = public_key(curve, prv) - >>> from pygost.gost3410 import pub_marshal - >>> from pygost.utils import hexenc - >>> print "Public key is:", hexenc(pub_marshal(pub)) - >>> from pygost import gost34112012512 - >>> data_for_signing = b"some data" - >>> dgst = gost34112012512.new(data_for_signing).digest() - >>> from pygost.gost3410 import sign - >>> signature = sign(curve, prv, dgst) - >>> from pygost.gost3410 import verify - >>> verify(curve, pub, dgst, signature) - True - -Other examples can be found in docstrings and unittests. -Example self-signed X.509 certificate creation can be found in -pygost/asn1schemas/cert-selfsigned-example.py. - -PyGOST is free software: see the file COPYING for copying conditions. - -PyGOST'es home page is: http://www.pygost.cypherpunks.ru/ -You can read about GOST algorithms more: http://www.gost.cypherpunks.ru/ - -Please send questions, bug reports and patches to -http://lists.cypherpunks.ru/gost.html mailing list. -Announcements also go to this mailing list. - -Development Git source code repository currently is located here: -http://www.git.cypherpunks.ru/?p=pygost.git;a=summary - - diff --git a/pygost-5.13/README b/pygost-5.13/README deleted file mode 100644 index 2034715..0000000 --- a/pygost-5.13/README +++ /dev/null @@ -1,73 +0,0 @@ -Pure Python 2.7/3.x GOST cryptographic functions library. - -GOST is GOvernment STandard of Russian Federation (and Soviet Union). - -* GOST 28147-89 (RFC 5830) block cipher with ECB, CNT (CTR), CFB, MAC, - CBC (RFC 4357) modes of operation -* various 28147-89-related S-boxes included -* GOST R 34.11-94 hash function (RFC 5831) -* GOST R 34.11-94 based PBKDF2 function -* GOST R 34.11-2012 Стрибог (Streebog) hash function (RFC 6986) -* GOST R 34.11-2012 based PBKDF2 function (Р 50.1.111-2016) -* GOST R 34.10-2001 (RFC 5832) public key signature function -* GOST R 34.10-2012 (RFC 7091) public key signature function -* various 34.10 curve parameters included -* Coordinates conversion from twisted Edwards to Weierstrass form and - vice versa -* VKO GOST R 34.10-2001 key agreement function (RFC 4357) -* VKO GOST R 34.10-2012 key agreement function (RFC 7836) -* 28147-89 and CryptoPro key wrapping (RFC 4357) -* 28147-89 CryptoPro key meshing for CFB and CBC modes (RFC 4357) -* RFC 4491 (using GOST algorithms with X.509) compatibility helpers -* GOST R 34.12-2015 128-bit block cipher Кузнечик (Kuznechik) (RFC 7801) -* GOST R 34.12-2015 64-bit block cipher Магма (Magma) -* GOST R 34.13-2015 padding methods and block cipher modes of operation - (ECB, CTR, OFB, CBC, CFB, MAC), ISO 10126 padding -* MGM AEAD mode for 64 and 128 bit ciphers (RFC 9058) -* CTR-ACPKM, OMAC-ACPKM-Master modes of operation (Р 1323565.1.017-2018) -* KExp15/KImp15 key export/import functions (Р 1323565.1.017-2018) -* KDF_GOSTR3411_2012_256, KDF_TREE_GOSTR3411_2012_256 (Р 50.1.113-2016) -* KEG export key generation function (Р 1323565.1.020-2018) -* PEP247-compatible hash/MAC functions - -Known problems: low performance and non time-constant calculations. - -Example 34.10-2012 keypair generation, signing and verifying: - - >>> from pygost.gost3410 import CURVES - >>> curve = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - >>> from os import urandom - >>> prv_raw = urandom(64) - >>> from pygost.gost3410 import prv_unmarshal - >>> from pygost.gost3410 import prv_marshal - >>> prv = prv_unmarshal(prv_raw) - >>> prv_raw = prv_marshal(curve, prv) - >>> from pygost.gost3410 import public_key - >>> pub = public_key(curve, prv) - >>> from pygost.gost3410 import pub_marshal - >>> from pygost.utils import hexenc - >>> print "Public key is:", hexenc(pub_marshal(pub)) - >>> from pygost import gost34112012512 - >>> data_for_signing = b"some data" - >>> dgst = gost34112012512.new(data_for_signing).digest() - >>> from pygost.gost3410 import sign - >>> signature = sign(curve, prv, dgst) - >>> from pygost.gost3410 import verify - >>> verify(curve, pub, dgst, signature) - True - -Other examples can be found in docstrings and unittests. -Example self-signed X.509 certificate creation can be found in -pygost/asn1schemas/cert-selfsigned-example.py. - -PyGOST is free software: see the file COPYING for copying conditions. - -PyGOST'es home page is: http://www.pygost.cypherpunks.ru/ -You can read about GOST algorithms more: http://www.gost.cypherpunks.ru/ - -Please send questions, bug reports and patches to -http://lists.cypherpunks.ru/gost.html mailing list. -Announcements also go to this mailing list. - -Development Git source code repository currently is located here: -http://www.git.cypherpunks.ru/?p=pygost.git;a=summary diff --git a/pygost-5.13/THANKS b/pygost-5.13/THANKS deleted file mode 100644 index cb2ec9d..0000000 --- a/pygost-5.13/THANKS +++ /dev/null @@ -1,8 +0,0 @@ -There are people deserving to be thanked for helping this project: - -* Dmitry Eremin-Solenikov for his - suggestions of TK26 standards usage as a base point for serialized - structures representation -* Alexander Lodin for finding bug in 34.13-2015 - OFB mode with IVs longer than 2 blocks -* Efimov Vasiliy for adding and testing of CBC-mode key meshing diff --git a/pygost-5.13/VERSION b/pygost-5.13/VERSION deleted file mode 100644 index 3fe7217..0000000 --- a/pygost-5.13/VERSION +++ /dev/null @@ -1 +0,0 @@ -5.13 diff --git a/pygost-5.13/build/lib/pygost/__init__.py b/pygost-5.13/build/lib/pygost/__init__.py deleted file mode 100644 index fba7932..0000000 --- a/pygost-5.13/build/lib/pygost/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Pure Python GOST cryptographic functions library. - -PyGOST is free software: see the file COPYING for copying conditions. -""" - -__version__ = "5.13" diff --git a/pygost-5.13/build/lib/pygost/asn1schemas/__init__.py b/pygost-5.13/build/lib/pygost/asn1schemas/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pygost-5.13/build/lib/pygost/asn1schemas/cert-dane-hash.py b/pygost-5.13/build/lib/pygost/asn1schemas/cert-dane-hash.py deleted file mode 100644 index 0292b9e..0000000 --- a/pygost-5.13/build/lib/pygost/asn1schemas/cert-dane-hash.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 -"""DANE's SPKI hash calculator -""" - -from base64 import standard_b64decode -from hashlib import sha256 -import sys - -from pygost.asn1schemas.x509 import Certificate - - -lines = sys.stdin.read().split("-----") -idx = lines.index("BEGIN CERTIFICATE") -if idx == -1: - raise ValueError("PEM has no CERTIFICATE") -cert_raw = standard_b64decode(lines[idx + 1]) -cert = Certificate().decod(cert_raw) -print(sha256(cert["tbsCertificate"]["subjectPublicKeyInfo"].encode()).hexdigest()) diff --git a/pygost-5.13/build/lib/pygost/asn1schemas/cert-selfsigned-example.py b/pygost-5.13/build/lib/pygost/asn1schemas/cert-selfsigned-example.py deleted file mode 100644 index bd562b1..0000000 --- a/pygost-5.13/build/lib/pygost/asn1schemas/cert-selfsigned-example.py +++ /dev/null @@ -1,348 +0,0 @@ -#!/usr/bin/env python3 -"""Create example self-signed X.509 certificate -""" - -from argparse import ArgumentParser -from base64 import standard_b64decode -from base64 import standard_b64encode -from datetime import datetime -from datetime import timedelta -from os import urandom -from sys import exit as sys_exit -from sys import stdout -from textwrap import fill - -from pyderasn import Any -from pyderasn import BitString -from pyderasn import Boolean -from pyderasn import IA5String -from pyderasn import Integer -from pyderasn import OctetString -from pyderasn import PrintableString -from pyderasn import UTCTime - -from pygost.asn1schemas.oids import id_at_commonName -from pygost.asn1schemas.oids import id_at_countryName -from pygost.asn1schemas.oids import id_ce_authorityKeyIdentifier -from pygost.asn1schemas.oids import id_ce_basicConstraints -from pygost.asn1schemas.oids import id_ce_keyUsage -from pygost.asn1schemas.oids import id_ce_subjectAltName -from pygost.asn1schemas.oids import id_ce_subjectKeyIdentifier -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256 -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetA -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetB -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetC -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetD -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512 -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetA -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetB -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetC -from pygost.asn1schemas.oids import id_tc26_signwithdigest_gost3410_2012_256 -from pygost.asn1schemas.oids import id_tc26_signwithdigest_gost3410_2012_512 -from pygost.asn1schemas.prvkey import PrivateKey -from pygost.asn1schemas.prvkey import PrivateKeyAlgorithmIdentifier -from pygost.asn1schemas.prvkey import PrivateKeyInfo -from pygost.asn1schemas.x509 import AlgorithmIdentifier -from pygost.asn1schemas.x509 import AttributeType -from pygost.asn1schemas.x509 import AttributeTypeAndValue -from pygost.asn1schemas.x509 import AttributeValue -from pygost.asn1schemas.x509 import AuthorityKeyIdentifier -from pygost.asn1schemas.x509 import BasicConstraints -from pygost.asn1schemas.x509 import Certificate -from pygost.asn1schemas.x509 import CertificateSerialNumber -from pygost.asn1schemas.x509 import Extension -from pygost.asn1schemas.x509 import Extensions -from pygost.asn1schemas.x509 import GeneralName -from pygost.asn1schemas.x509 import GostR34102012PublicKeyParameters -from pygost.asn1schemas.x509 import KeyIdentifier -from pygost.asn1schemas.x509 import KeyUsage -from pygost.asn1schemas.x509 import Name -from pygost.asn1schemas.x509 import RDNSequence -from pygost.asn1schemas.x509 import RelativeDistinguishedName -from pygost.asn1schemas.x509 import SubjectAltName -from pygost.asn1schemas.x509 import SubjectKeyIdentifier -from pygost.asn1schemas.x509 import SubjectPublicKeyInfo -from pygost.asn1schemas.x509 import TBSCertificate -from pygost.asn1schemas.x509 import Time -from pygost.asn1schemas.x509 import Validity -from pygost.asn1schemas.x509 import Version -from pygost.gost3410 import CURVES -from pygost.gost3410 import prv_unmarshal -from pygost.gost3410 import pub_marshal -from pygost.gost3410 import public_key -from pygost.gost3410 import sign -from pygost.gost34112012256 import GOST34112012256 -from pygost.gost34112012512 import GOST34112012512 -from pygost.utils import bytes2long - -parser = ArgumentParser(description="Self-signed X.509 certificate creator") -parser.add_argument( - "--ca", - action="store_true", - help="Enable BasicConstraints.cA", -) -parser.add_argument( - "--cn", - required=True, - help="Subject's CommonName", -) -parser.add_argument( - "--country", - help="Subject's Country", -) -parser.add_argument( - "--serial", - help="Serial number", -) -parser.add_argument( - "--ai", - required=True, - help="Signing algorithm: {256[ABCD],512[ABC]}", -) -parser.add_argument( - "--issue-with", - help="Path to PEM with CA to issue the child", -) -parser.add_argument( - "--reuse-key", - help="Path to PEM with the key to reuse", -) -parser.add_argument( - "--out-key", - help="Path to PEM with the resulting key", -) -parser.add_argument( - "--only-key", - action="store_true", - help="Only generate the key", -) -parser.add_argument( - "--out-cert", - help="Path to PEM with the resulting certificate", -) -args = parser.parse_args() -AIs = { - "256A": { - "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetA, - "key_algorithm": id_tc26_gost3410_2012_256, - "prv_len": 32, - "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetA"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256, - "hasher": GOST34112012256, - }, - "256B": { - "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetB, - "key_algorithm": id_tc26_gost3410_2012_256, - "prv_len": 32, - "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetB"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256, - "hasher": GOST34112012256, - }, - "256C": { - "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetC, - "key_algorithm": id_tc26_gost3410_2012_256, - "prv_len": 32, - "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetC"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256, - "hasher": GOST34112012256, - }, - "256D": { - "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetD, - "key_algorithm": id_tc26_gost3410_2012_256, - "prv_len": 32, - "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetD"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256, - "hasher": GOST34112012256, - }, - "512A": { - "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetA, - "key_algorithm": id_tc26_gost3410_2012_512, - "prv_len": 64, - "curve": CURVES["id-tc26-gost-3410-12-512-paramSetA"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512, - "hasher": GOST34112012512, - }, - "512B": { - "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetB, - "key_algorithm": id_tc26_gost3410_2012_512, - "prv_len": 64, - "curve": CURVES["id-tc26-gost-3410-12-512-paramSetB"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512, - "hasher": GOST34112012512, - }, - "512C": { - "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetC, - "key_algorithm": id_tc26_gost3410_2012_512, - "prv_len": 64, - "curve": CURVES["id-tc26-gost-3410-2012-512-paramSetC"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512, - "hasher": GOST34112012512, - }, -} -ai = AIs[args.ai] - -ca_prv = None -ca_cert = None -ca_subj = None -ca_ai = None -if args.issue_with is not None: - with open(args.issue_with, "rb") as fd: - lines = fd.read().decode("ascii").split("-----") - idx = lines.index("BEGIN PRIVATE KEY") - if idx == -1: - raise ValueError("PEM has no PRIVATE KEY") - prv_raw = standard_b64decode(lines[idx + 1]) - idx = lines.index("BEGIN CERTIFICATE") - if idx == -1: - raise ValueError("PEM has no CERTIFICATE") - cert_raw = standard_b64decode(lines[idx + 1]) - pki = PrivateKeyInfo().decod(prv_raw) - ca_prv = prv_unmarshal(bytes(OctetString().decod(bytes(pki["privateKey"])))) - ca_cert = Certificate().decod(cert_raw) - tbs = ca_cert["tbsCertificate"] - ca_subj = tbs["subject"] - curve_oid = GostR34102012PublicKeyParameters().decod(bytes( - tbs["subjectPublicKeyInfo"]["algorithm"]["parameters"] - ))["publicKeyParamSet"] - ca_ai = next(iter([ - params for params in AIs.values() - if params["publicKeyParamSet"] == curve_oid - ])) - -key_params = GostR34102012PublicKeyParameters(( - ("publicKeyParamSet", ai["publicKeyParamSet"]), -)) - - -def pem(obj): - return fill(standard_b64encode(obj.encode()).decode("ascii"), 64) - - -if args.reuse_key is not None: - with open(args.reuse_key, "rb") as fd: - lines = fd.read().decode("ascii").split("-----") - idx = lines.index("BEGIN PRIVATE KEY") - if idx == -1: - raise ValueError("PEM has no PRIVATE KEY") - prv_raw = standard_b64decode(lines[idx + 1]) - pki = PrivateKeyInfo().decod(prv_raw) - prv = prv_unmarshal(bytes(OctetString().decod(bytes(pki["privateKey"])))) -else: - prv_raw = urandom(ai["prv_len"]) - out = stdout if args.out_key is None else open(args.out_key, "w") - print("-----BEGIN PRIVATE KEY-----", file=out) - print(pem(PrivateKeyInfo(( - ("version", Integer(0)), - ("privateKeyAlgorithm", PrivateKeyAlgorithmIdentifier(( - ("algorithm", ai["key_algorithm"]), - ("parameters", Any(key_params)), - ))), - ("privateKey", PrivateKey(OctetString(prv_raw).encode())), - ))), file=out) - print("-----END PRIVATE KEY-----", file=out) - if args.only_key: - sys_exit() - prv = prv_unmarshal(prv_raw) - -curve = ai["curve"] -pub_raw = pub_marshal(public_key(curve, prv)) -rdn = [RelativeDistinguishedName(( - AttributeTypeAndValue(( - ("type", AttributeType(id_at_commonName)), - ("value", AttributeValue(PrintableString(args.cn))), - )), -))] -if args.country: - rdn.append(RelativeDistinguishedName(( - AttributeTypeAndValue(( - ("type", AttributeType(id_at_countryName)), - ("value", AttributeValue(PrintableString(args.country))), - )), - ))) -subj = Name(("rdnSequence", RDNSequence(rdn))) -not_before = datetime.utcnow() -not_after = not_before + timedelta(days=365 * (10 if args.ca else 1)) -ai_sign = AlgorithmIdentifier(( - ("algorithm", (ai if ca_ai is None else ca_ai)["sign_algorithm"]), -)) -exts = [ - Extension(( - ("extnID", id_ce_subjectKeyIdentifier), - ("extnValue", OctetString( - SubjectKeyIdentifier(GOST34112012256(pub_raw).digest()[:20]).encode() - )), - )), - Extension(( - ("extnID", id_ce_keyUsage), - ("critical", Boolean(True)), - ("extnValue", OctetString(KeyUsage( - ("keyCertSign" if args.ca else "digitalSignature",), - ).encode())), - )), -] -if args.ca: - exts.append(Extension(( - ("extnID", id_ce_basicConstraints), - ("critical", Boolean(True)), - ("extnValue", OctetString(BasicConstraints(( - ("cA", Boolean(True)), - )).encode())), - ))) -else: - exts.append(Extension(( - ("extnID", id_ce_subjectAltName), - ("extnValue", OctetString( - SubjectAltName(( - GeneralName(("dNSName", IA5String(args.cn))), - )).encode() - )), - ))) -if ca_ai is not None: - caKeyId = [ - bytes(SubjectKeyIdentifier().decod(bytes(ext["extnValue"]))) - for ext in ca_cert["tbsCertificate"]["extensions"] - if ext["extnID"] == id_ce_subjectKeyIdentifier - ][0] - exts.append(Extension(( - ("extnID", id_ce_authorityKeyIdentifier), - ("extnValue", OctetString(AuthorityKeyIdentifier(( - ("keyIdentifier", KeyIdentifier(caKeyId)), - )).encode())), - ))) - -serial = ( - bytes2long(GOST34112012256(urandom(16)).digest()[:20]) - if args.serial is None else int(args.serial) -) -tbs = TBSCertificate(( - ("version", Version("v3")), - ("serialNumber", CertificateSerialNumber(serial)), - ("signature", ai_sign), - ("issuer", subj if ca_ai is None else ca_subj), - ("validity", Validity(( - ("notBefore", Time(("utcTime", UTCTime(not_before)))), - ("notAfter", Time(("utcTime", UTCTime(not_after)))), - ))), - ("subject", subj), - ("subjectPublicKeyInfo", SubjectPublicKeyInfo(( - ("algorithm", AlgorithmIdentifier(( - ("algorithm", ai["key_algorithm"]), - ("parameters", Any(key_params)), - ))), - ("subjectPublicKey", BitString(OctetString(pub_raw).encode())), - ))), - ("extensions", Extensions(exts)), -)) -cert = Certificate(( - ("tbsCertificate", tbs), - ("signatureAlgorithm", ai_sign), - ("signatureValue", BitString( - sign(curve, prv, ai["hasher"](tbs.encode()).digest()[::-1]) - if ca_ai is None else - sign(ca_ai["curve"], ca_prv, ca_ai["hasher"](tbs.encode()).digest()[::-1]) - )), -)) -out = stdout if args.out_cert is None else open(args.out_cert, "w") -print("-----BEGIN CERTIFICATE-----", file=out) -print(pem(cert), file=out) -print("-----END CERTIFICATE-----", file=out) diff --git a/pygost-5.13/build/lib/pygost/asn1schemas/cms.py b/pygost-5.13/build/lib/pygost/asn1schemas/cms.py deleted file mode 100644 index 8028d2b..0000000 --- a/pygost-5.13/build/lib/pygost/asn1schemas/cms.py +++ /dev/null @@ -1,431 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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.oids import id_cms_mac_attr -from pygost.asn1schemas.oids import id_contentType -from pygost.asn1schemas.oids import id_digestedData -from pygost.asn1schemas.oids import id_encryptedData -from pygost.asn1schemas.oids import id_envelopedData -from pygost.asn1schemas.oids import id_Gost28147_89 -from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm -from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm_omac -from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_wrap_kexp15 -from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm -from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm_omac -from pygost.asn1schemas.oids import id_gostr3412_2015_magma_wrap_kexp15 -from pygost.asn1schemas.oids import id_messageDigest -from pygost.asn1schemas.oids import id_signedData -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256 -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512 -from pygost.asn1schemas.x509 import AlgorithmIdentifier -from pygost.asn1schemas.x509 import Certificate -from pygost.asn1schemas.x509 import CertificateSerialNumber -from pygost.asn1schemas.x509 import Name -from pygost.asn1schemas.x509 import SubjectPublicKeyInfo - - -class CMSVersion(Integer): - pass - - -class ContentType(ObjectIdentifier): - pass - - -class IssuerAndSerialNumber(Sequence): - schema = ( - ("issuer", Name()), - ("serialNumber", CertificateSerialNumber()), - ) - - -class KeyIdentifier(OctetString): - pass - - -class SubjectKeyIdentifier(KeyIdentifier): - pass - - -class RecipientIdentifier(Choice): - schema = ( - ("issuerAndSerialNumber", IssuerAndSerialNumber()), - ("subjectKeyIdentifier", SubjectKeyIdentifier(impl=tag_ctxp(0))), - ) - - -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 GostR3410KeyTransport2019(Sequence): - schema = ( - ("encryptedKey", OctetString()), - ("ephemeralPublicKey", SubjectPublicKeyInfo()), - ("ukm", OctetString()), - ) - - -class GostR341012KEGParameters(Sequence): - schema = ( - ("algorithm", ObjectIdentifier()), - ) - - -class KeyEncryptionAlgorithmIdentifier(AlgorithmIdentifier): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), { - id_gostr3412_2015_magma_wrap_kexp15: GostR341012KEGParameters(), - id_gostr3412_2015_kuznyechik_wrap_kexp15: GostR341012KEGParameters(), - }), - (("..", "encryptedKey"), { - id_tc26_gost3410_2012_256: GostR3410KeyTransport(), - id_tc26_gost3410_2012_512: GostR3410KeyTransport(), - id_gostr3412_2015_magma_wrap_kexp15: GostR3410KeyTransport2019(), - id_gostr3412_2015_kuznyechik_wrap_kexp15: GostR3410KeyTransport2019(), - }), - (("..", "recipientEncryptedKeys", any, "encryptedKey"), { - id_tc26_gost3410_2012_256: Gost2814789EncryptedKey(), - id_tc26_gost3410_2012_512: Gost2814789EncryptedKey(), - }), - ))), - ("parameters", Any(optional=True)), - ) - - -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", IssuerAndSerialNumber()), - # ("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 Gost2814789IV(OctetString): - bounds = (8, 8) - - -class Gost2814789Parameters(Sequence): - schema = ( - ("iv", Gost2814789IV()), - ("encryptionParamSet", ObjectIdentifier()), - ) - - -class Gost341215EncryptionParameters(Sequence): - schema = ( - ("ukm", OctetString()), - ) - - -class ContentEncryptionAlgorithmIdentifier(AlgorithmIdentifier): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), { - id_Gost28147_89: Gost2814789Parameters(), - id_gostr3412_2015_magma_ctracpkm: Gost341215EncryptionParameters(), - id_gostr3412_2015_kuznyechik_ctracpkm: Gost341215EncryptionParameters(), - id_gostr3412_2015_magma_ctracpkm_omac: Gost341215EncryptionParameters(), - id_gostr3412_2015_kuznyechik_ctracpkm_omac: Gost341215EncryptionParameters(), - }), - ))), - ("parameters", Any(optional=True)), - ) - - -class EncryptedContent(OctetString): - pass - - -class EncryptedContentInfo(Sequence): - schema = ( - ("contentType", ContentType()), - ("contentEncryptionAlgorithm", ContentEncryptionAlgorithmIdentifier()), - ("encryptedContent", EncryptedContent(impl=tag_ctxp(0), optional=True)), - ) - - -class Digest(OctetString): - pass - - -class AttributeValue(Any): - pass - - -class AttributeValues(SetOf): - schema = AttributeValue() - - -class EncryptedMac(OctetString): - pass - - -class Attribute(Sequence): - schema = ( - ("attrType", ObjectIdentifier(defines=( - (("attrValues",), { - id_contentType: ObjectIdentifier(), - id_messageDigest: Digest(), - id_cms_mac_attr: EncryptedMac(), - },), - ))), - ("attrValues", AttributeValues()), - ) - - -class UnprotectedAttributes(SetOf): - schema = Attribute() - bounds = (1, float("+inf")) - - -class CertificateChoices(Choice): - schema = ( - ("certificate", Certificate()), - # ("extendedCertificate", OctetString(impl=tag_ctxp(0))), - # ("v1AttrCert", AttributeCertificateV1(impl=tag_ctxc(1))), # V1 is osbolete - # ("v2AttrCert", AttributeCertificateV2(impl=tag_ctxc(2))), - # ("other", OtherCertificateFormat(impl=tag_ctxc(3))), - ) - - -class CertificateSet(SetOf): - schema = CertificateChoices() - - -class OriginatorInfo(Sequence): - schema = ( - ("certs", CertificateSet(impl=tag_ctxc(0), optional=True)), - # ("crls", RevocationInfoChoices(impl=tag_ctxc(1), optional=True)), - ) - - -class EnvelopedData(Sequence): - schema = ( - ("version", CMSVersion()), - ("originatorInfo", OriginatorInfo(impl=tag_ctxc(0), optional=True)), - ("recipientInfos", RecipientInfos()), - ("encryptedContentInfo", EncryptedContentInfo()), - ("unprotectedAttrs", UnprotectedAttributes(impl=tag_ctxc(1), optional=True)), - ) - - -class EncapsulatedContentInfo(Sequence): - schema = ( - ("eContentType", ContentType()), - ("eContent", OctetString(expl=tag_ctxc(0), optional=True)), - ) - - -class SignerIdentifier(Choice): - schema = ( - ("issuerAndSerialNumber", IssuerAndSerialNumber()), - ("subjectKeyIdentifier", SubjectKeyIdentifier(impl=tag_ctxp(0))), - ) - - -class DigestAlgorithmIdentifiers(SetOf): - schema = AlgorithmIdentifier() - - -class DigestAlgorithmIdentifier(AlgorithmIdentifier): - pass - - -class SignatureAlgorithmIdentifier(AlgorithmIdentifier): - pass - - -class SignatureValue(OctetString): - pass - - -class SignedAttributes(SetOf): - schema = Attribute() - bounds = (1, float("+inf")) - - -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 DigestedData(Sequence): - schema = ( - ("version", CMSVersion()), - ("digestAlgorithm", DigestAlgorithmIdentifier()), - ("encapContentInfo", EncapsulatedContentInfo()), - ("digest", Digest()), - ) - - -class EncryptedData(Sequence): - schema = ( - ("version", CMSVersion()), - ("encryptedContentInfo", EncryptedContentInfo()), - ("unprotectedAttrs", UnprotectedAttributes(impl=tag_ctxc(1), optional=True)), - ) - - -class ContentInfo(Sequence): - schema = ( - ("contentType", ContentType(defines=( - (("content",), { - id_digestedData: DigestedData(), - id_encryptedData: EncryptedData(), - id_envelopedData: EnvelopedData(), - id_signedData: SignedData(), - }), - ))), - ("content", Any(expl=tag_ctxc(0))), - ) diff --git a/pygost-5.13/build/lib/pygost/asn1schemas/oids.py b/pygost-5.13/build/lib/pygost/asn1schemas/oids.py deleted file mode 100644 index 4638900..0000000 --- a/pygost-5.13/build/lib/pygost/asn1schemas/oids.py +++ /dev/null @@ -1,60 +0,0 @@ -from pyderasn import ObjectIdentifier - - -id_at_commonName = ObjectIdentifier("2.5.4.3") -id_at_countryName = ObjectIdentifier("2.5.4.6") -id_at_localityName = ObjectIdentifier("2.5.4.7") -id_at_stateOrProvinceName = ObjectIdentifier("2.5.4.8") -id_at_organizationName = ObjectIdentifier("2.5.4.10") - -id_pkcs7 = ObjectIdentifier("1.2.840.113549.1.7") -id_data = id_pkcs7 + (1,) -id_signedData = id_pkcs7 + (2,) -id_envelopedData = id_pkcs7 + (3,) -id_digestedData = id_pkcs7 + (5,) -id_encryptedData = id_pkcs7 + (6,) - -id_pkcs9 = ObjectIdentifier("1.2.840.113549.1.9") -id_contentType = id_pkcs9 + (3,) -id_messageDigest = id_pkcs9 + (4,) -id_pkcs9_certTypes_x509Certificate = ObjectIdentifier("1.2.840.113549.1.9.22.1") -id_pkcs12_bagtypes_keyBag = ObjectIdentifier("1.2.840.113549.1.12.10.1.1") -id_pkcs12_bagtypes_pkcs8ShroudedKeyBag = ObjectIdentifier("1.2.840.113549.1.12.10.1.2") -id_pkcs12_bagtypes_certBag = ObjectIdentifier("1.2.840.113549.1.12.10.1.3") - -id_Gost28147_89 = ObjectIdentifier("1.2.643.2.2.21") -id_GostR3410_2001_TestParamSet = ObjectIdentifier("1.2.643.2.2.35.0") -id_cms_mac_attr = ObjectIdentifier("1.2.643.7.1.0.6.1.1") -id_tc26_gost3410_2012_256 = ObjectIdentifier("1.2.643.7.1.1.1.1") -id_tc26_gost3410_2012_512 = ObjectIdentifier("1.2.643.7.1.1.1.2") -id_tc26_gost3411_2012_256 = ObjectIdentifier("1.2.643.7.1.1.2.2") -id_tc26_gost3411_2012_512 = ObjectIdentifier("1.2.643.7.1.1.2.3") -id_tc26_signwithdigest_gost3410_2012_256 = ObjectIdentifier("1.2.643.7.1.1.3.2") -id_tc26_signwithdigest_gost3410_2012_512 = ObjectIdentifier("1.2.643.7.1.1.3.3") -id_gostr3412_2015_magma_ctracpkm = ObjectIdentifier("1.2.643.7.1.1.5.1.1") -id_gostr3412_2015_magma_ctracpkm_omac = ObjectIdentifier("1.2.643.7.1.1.5.1.2") -id_gostr3412_2015_kuznyechik_ctracpkm = ObjectIdentifier("1.2.643.7.1.1.5.2.1") -id_gostr3412_2015_kuznyechik_ctracpkm_omac = ObjectIdentifier("1.2.643.7.1.1.5.2.2") -id_tc26_agreement_gost3410_2012_256 = ObjectIdentifier("1.2.643.7.1.1.6.1") -id_tc26_agreement_gost3410_2012_512 = ObjectIdentifier("1.2.643.7.1.1.6.2") -id_gostr3412_2015_magma_wrap_kexp15 = ObjectIdentifier("1.2.643.7.1.1.7.1.1") -id_gostr3412_2015_kuznyechik_wrap_kexp15 = ObjectIdentifier("1.2.643.7.1.1.7.2.1") -id_tc26_gost3410_2012_256_paramSetA = ObjectIdentifier("1.2.643.7.1.2.1.1.1") -id_tc26_gost3410_2012_256_paramSetB = ObjectIdentifier("1.2.643.7.1.2.1.1.2") -id_tc26_gost3410_2012_256_paramSetC = ObjectIdentifier("1.2.643.7.1.2.1.1.3") -id_tc26_gost3410_2012_256_paramSetD = ObjectIdentifier("1.2.643.7.1.2.1.1.4") -id_tc26_gost3410_2012_512_paramSetTest = ObjectIdentifier("1.2.643.7.1.2.1.2.0") -id_tc26_gost3410_2012_512_paramSetA = ObjectIdentifier("1.2.643.7.1.2.1.2.1") -id_tc26_gost3410_2012_512_paramSetB = ObjectIdentifier("1.2.643.7.1.2.1.2.2") -id_tc26_gost3410_2012_512_paramSetC = ObjectIdentifier("1.2.643.7.1.2.1.2.3") -id_tc26_gost_28147_param_Z = ObjectIdentifier("1.2.643.7.1.2.5.1.1") - -id_pbes2 = ObjectIdentifier("1.2.840.113549.1.5.13") -id_pbkdf2 = ObjectIdentifier("1.2.840.113549.1.5.12") - -id_at_commonName = ObjectIdentifier("2.5.4.3") -id_ce_basicConstraints = ObjectIdentifier("2.5.29.19") -id_ce_subjectKeyIdentifier = ObjectIdentifier("2.5.29.14") -id_ce_keyUsage = ObjectIdentifier("2.5.29.15") -id_ce_subjectAltName = ObjectIdentifier("2.5.29.17") -id_ce_authorityKeyIdentifier = ObjectIdentifier("2.5.29.35") diff --git a/pygost-5.13/build/lib/pygost/asn1schemas/pfx.py b/pygost-5.13/build/lib/pygost/asn1schemas/pfx.py deleted file mode 100644 index 27a87d0..0000000 --- a/pygost-5.13/build/lib/pygost/asn1schemas/pfx.py +++ /dev/null @@ -1,250 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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.cms import Gost2814789Parameters -from pygost.asn1schemas.cms import Gost341215EncryptionParameters -from pygost.asn1schemas.oids import id_data -from pygost.asn1schemas.oids import id_encryptedData -from pygost.asn1schemas.oids import id_Gost28147_89 -from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm -from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm_omac -from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm -from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm_omac -from pygost.asn1schemas.oids import id_pbes2 -from pygost.asn1schemas.oids import id_pbkdf2 -from pygost.asn1schemas.oids import id_pkcs9_certTypes_x509Certificate -from pygost.asn1schemas.prvkey import PrivateKeyInfo -from pygost.asn1schemas.x509 import AlgorithmIdentifier -from pygost.asn1schemas.x509 import Certificate - - -class PBKDF2Salt(Choice): - 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()), - ) - - -class PBES2KDFs(AlgorithmIdentifier): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), {id_pbkdf2: PBKDF2Params()}), - ))), - ("parameters", Any(optional=True)), - ) - - -class PBES2Encs(AlgorithmIdentifier): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), { - id_Gost28147_89: Gost2814789Parameters(), - id_gostr3412_2015_magma_ctracpkm: Gost341215EncryptionParameters(), - id_gostr3412_2015_magma_ctracpkm_omac: Gost341215EncryptionParameters(), - id_gostr3412_2015_kuznyechik_ctracpkm: Gost341215EncryptionParameters(), - id_gostr3412_2015_kuznyechik_ctracpkm_omac: Gost341215EncryptionParameters(), - }), - ))), - ("parameters", Any(optional=True)), - ) - - -class PBES2Params(Sequence): - schema = ( - ("keyDerivationFunc", PBES2KDFs()), - ("encryptionScheme", PBES2Encs()), - ) - - -class EncryptionAlgorithmIdentifier(AlgorithmIdentifier): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), {id_pbes2: PBES2Params()}), - ))), - ("parameters", Any(optional=True)), - ) - - -class ContentEncryptionAlgorithmIdentifier(EncryptionAlgorithmIdentifier): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), {id_pbes2: PBES2Params()}), - ))), - ("parameters", Any(optional=True)), - ) - - -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(defines=( - (("bagValue",), {id_encryptedData: EncryptedData()}), - ))), - ("bagValue", PKCS12BagSet(expl=tag_ctxc(0))), - ("bagAttributes", PKCS12Attributes(optional=True)), - ) - - -class SafeContents(SequenceOf): - schema = SafeBag() - - -OctetStringSafeContents = SafeContents(expl=OctetString.tag_default) - - -class AuthSafe(Sequence): - schema = ( - ("contentType", ContentType(defines=( - (("content",), {id_data: OctetStringSafeContents()}), - ))), - ("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 - - -OctetStringX509Certificate = Certificate(expl=OctetString.tag_default) - - -class CertTypes(Any): - pass - - -class CertBag(Sequence): - schema = ( - ("certId", ObjectIdentifier(defines=( - (("certValue",), { - id_pkcs9_certTypes_x509Certificate: OctetStringX509Certificate(), - }), - ))), - ("certValue", CertTypes(expl=tag_ctxc(0))), - ) - - -class KeyBag(PrivateKeyInfo): - pass diff --git a/pygost-5.13/build/lib/pygost/asn1schemas/pkcs10.py b/pygost-5.13/build/lib/pygost/asn1schemas/pkcs10.py deleted file mode 100644 index dce45dd..0000000 --- a/pygost-5.13/build/lib/pygost/asn1schemas/pkcs10.py +++ /dev/null @@ -1,49 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 #10 related structures (**NOT COMPLETE**) -""" - -from pyderasn import BitString -from pyderasn import Integer -from pyderasn import Sequence -from pyderasn import SetOf -from pyderasn import tag_ctxc - -from pygost.asn1schemas.cms import Attribute -from pygost.asn1schemas.x509 import AlgorithmIdentifier -from pygost.asn1schemas.x509 import Name -from pygost.asn1schemas.x509 import SubjectPublicKeyInfo - - -class Attributes(SetOf): - schema = Attribute() - - -class CertificationRequestInfo(Sequence): - schema = ( - ("version", Integer(0)), - ("subject", Name()), - ("subjectPKInfo", SubjectPublicKeyInfo()), - ("attributes", Attributes(impl=tag_ctxc(0))), - ) - - -class CertificationRequest(Sequence): - schema = ( - ("certificationRequestInfo", CertificationRequestInfo()), - ("signatureAlgorithm", AlgorithmIdentifier()), - ("signature", BitString()), - ) diff --git a/pygost-5.13/build/lib/pygost/asn1schemas/prvkey.py b/pygost-5.13/build/lib/pygost/asn1schemas/prvkey.py deleted file mode 100644 index 7da2533..0000000 --- a/pygost-5.13/build/lib/pygost/asn1schemas/prvkey.py +++ /dev/null @@ -1,100 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 pyderasn import Any -from pyderasn import BitString -from pyderasn import Choice -from pyderasn import Integer -from pyderasn import Null -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 - -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256 -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512 -from pygost.asn1schemas.x509 import GostR34102012PublicKeyParameters - - -class ECParameters(Choice): - schema = ( - ("namedCurve", ObjectIdentifier()), - ("implicitCurve", Null()), - # ("specifiedCurve", SpecifiedECDomain()), - ) - - -ecPrivkeyVer1 = Integer(1) - - -class ECPrivateKey(Sequence): - schema = ( - ("version", Integer(ecPrivkeyVer1)), - ("privateKey", OctetString()), - ("parameters", ECParameters(expl=tag_ctxc(0), optional=True)), - ("publicKey", BitString(expl=tag_ctxc(1), optional=True)), - ) - - -class PrivateKeyAlgorithmIdentifier(Sequence): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), { - id_tc26_gost3410_2012_256: GostR34102012PublicKeyParameters(), - id_tc26_gost3410_2012_512: GostR34102012PublicKeyParameters(), - }), - ))), - ("parameters", Any(optional=True)), - ) - - -class PrivateKey(OctetString): - pass - - -class AttributeValue(Any): - pass - - -class AttributeValues(SetOf): - schema = AttributeValue() - - -class Attribute(Sequence): - schema = ( - ("attrType", ObjectIdentifier()), - ("attrValues", AttributeValues()), - ) - - -class Attributes(SetOf): - schema = Attribute() - - -class PublicKey(BitString): - pass - - -class PrivateKeyInfo(Sequence): - schema = ( - ("version", Integer(0)), - ("privateKeyAlgorithm", PrivateKeyAlgorithmIdentifier()), - ("privateKey", PrivateKey()), - ("attributes", Attributes(impl=tag_ctxc(0), optional=True)), - ("publicKey", PublicKey(impl=tag_ctxp(1), optional=True)), - ) diff --git a/pygost-5.13/build/lib/pygost/asn1schemas/x509.py b/pygost-5.13/build/lib/pygost/asn1schemas/x509.py deleted file mode 100644 index 86ad7da..0000000 --- a/pygost-5.13/build/lib/pygost/asn1schemas/x509.py +++ /dev/null @@ -1,262 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST 28147-89 block cipher - -This is implementation of :rfc:`5830` ECB, CNT, CFB and :rfc:`4357` -CBC modes of operation. N1, N2, K names are taken according to -specification's terminology. CNT and CFB modes can work with arbitrary -data lengths. -""" - -from functools import partial - -from pygost.gost3413 import pad2 -from pygost.gost3413 import pad_size -from pygost.gost3413 import unpad2 -from pygost.utils import hexdec -from pygost.utils import strxor -from pygost.utils import xrange - - -KEYSIZE = 32 -BLOCKSIZE = 8 -C1 = 0x01010104 -C2 = 0x01010101 - -# Sequence of K_i S-box applying for encryption and decryption -SEQ_ENCRYPT = ( - 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, - 7, 6, 5, 4, 3, 2, 1, 0, -) -SEQ_DECRYPT = ( - 0, 1, 2, 3, 4, 5, 6, 7, - 7, 6, 5, 4, 3, 2, 1, 0, - 7, 6, 5, 4, 3, 2, 1, 0, - 7, 6, 5, 4, 3, 2, 1, 0, -) - -# S-box parameters -DEFAULT_SBOX = "id-Gost28147-89-CryptoPro-A-ParamSet" -SBOXES = { - "id-Gost28147-89-TestParamSet": ( - (4, 2, 15, 5, 9, 1, 0, 8, 14, 3, 11, 12, 13, 7, 10, 6), - (12, 9, 15, 14, 8, 1, 3, 10, 2, 7, 4, 13, 6, 0, 11, 5), - (13, 8, 14, 12, 7, 3, 9, 10, 1, 5, 2, 4, 6, 15, 0, 11), - (14, 9, 11, 2, 5, 15, 7, 1, 0, 13, 12, 6, 10, 4, 3, 8), - (3, 14, 5, 9, 6, 8, 0, 13, 10, 11, 7, 12, 2, 1, 15, 4), - (8, 15, 6, 11, 1, 9, 12, 5, 13, 3, 7, 10, 0, 14, 2, 4), - (9, 11, 12, 0, 3, 6, 7, 5, 4, 8, 14, 15, 1, 10, 2, 13), - (12, 6, 5, 2, 11, 0, 9, 13, 3, 14, 7, 10, 15, 4, 1, 8), - ), - "id-Gost28147-89-CryptoPro-A-ParamSet": ( - (9, 6, 3, 2, 8, 11, 1, 7, 10, 4, 14, 15, 12, 0, 13, 5), - (3, 7, 14, 9, 8, 10, 15, 0, 5, 2, 6, 12, 11, 4, 13, 1), - (14, 4, 6, 2, 11, 3, 13, 8, 12, 15, 5, 10, 0, 7, 1, 9), - (14, 7, 10, 12, 13, 1, 3, 9, 0, 2, 11, 4, 15, 8, 5, 6), - (11, 5, 1, 9, 8, 13, 15, 0, 14, 4, 2, 3, 12, 7, 10, 6), - (3, 10, 13, 12, 1, 2, 0, 11, 7, 5, 9, 4, 8, 15, 14, 6), - (1, 13, 2, 9, 7, 10, 6, 0, 8, 12, 4, 5, 15, 3, 11, 14), - (11, 10, 15, 5, 0, 12, 14, 8, 6, 2, 3, 9, 1, 7, 13, 4), - ), - "id-Gost28147-89-CryptoPro-B-ParamSet": ( - (8, 4, 11, 1, 3, 5, 0, 9, 2, 14, 10, 12, 13, 6, 7, 15), - (0, 1, 2, 10, 4, 13, 5, 12, 9, 7, 3, 15, 11, 8, 6, 14), - (14, 12, 0, 10, 9, 2, 13, 11, 7, 5, 8, 15, 3, 6, 1, 4), - (7, 5, 0, 13, 11, 6, 1, 2, 3, 10, 12, 15, 4, 14, 9, 8), - (2, 7, 12, 15, 9, 5, 10, 11, 1, 4, 0, 13, 6, 8, 14, 3), - (8, 3, 2, 6, 4, 13, 14, 11, 12, 1, 7, 15, 10, 0, 9, 5), - (5, 2, 10, 11, 9, 1, 12, 3, 7, 4, 13, 0, 6, 15, 8, 14), - (0, 4, 11, 14, 8, 3, 7, 1, 10, 2, 9, 6, 15, 13, 5, 12), - ), - "id-Gost28147-89-CryptoPro-C-ParamSet": ( - (1, 11, 12, 2, 9, 13, 0, 15, 4, 5, 8, 14, 10, 7, 6, 3), - (0, 1, 7, 13, 11, 4, 5, 2, 8, 14, 15, 12, 9, 10, 6, 3), - (8, 2, 5, 0, 4, 9, 15, 10, 3, 7, 12, 13, 6, 14, 1, 11), - (3, 6, 0, 1, 5, 13, 10, 8, 11, 2, 9, 7, 14, 15, 12, 4), - (8, 13, 11, 0, 4, 5, 1, 2, 9, 3, 12, 14, 6, 15, 10, 7), - (12, 9, 11, 1, 8, 14, 2, 4, 7, 3, 6, 5, 10, 0, 15, 13), - (10, 9, 6, 8, 13, 14, 2, 0, 15, 3, 5, 11, 4, 1, 12, 7), - (7, 4, 0, 5, 10, 2, 15, 14, 12, 6, 1, 11, 13, 9, 3, 8), - ), - "id-Gost28147-89-CryptoPro-D-ParamSet": ( - (15, 12, 2, 10, 6, 4, 5, 0, 7, 9, 14, 13, 1, 11, 8, 3), - (11, 6, 3, 4, 12, 15, 14, 2, 7, 13, 8, 0, 5, 10, 9, 1), - (1, 12, 11, 0, 15, 14, 6, 5, 10, 13, 4, 8, 9, 3, 7, 2), - (1, 5, 14, 12, 10, 7, 0, 13, 6, 2, 11, 4, 9, 3, 15, 8), - (0, 12, 8, 9, 13, 2, 10, 11, 7, 3, 6, 5, 4, 14, 15, 1), - (8, 0, 15, 3, 2, 5, 14, 11, 1, 10, 4, 7, 12, 9, 13, 6), - (3, 0, 6, 15, 1, 14, 9, 2, 13, 8, 12, 4, 11, 10, 5, 7), - (1, 10, 6, 8, 15, 11, 0, 4, 12, 3, 5, 9, 7, 13, 2, 14), - ), - "id-tc26-gost-28147-param-Z": ( - (12, 4, 6, 2, 10, 5, 11, 9, 14, 8, 13, 7, 0, 3, 15, 1), - (6, 8, 2, 3, 9, 10, 5, 12, 1, 14, 4, 7, 11, 13, 0, 15), - (11, 3, 5, 8, 2, 15, 10, 13, 14, 1, 7, 4, 12, 9, 6, 0), - (12, 8, 2, 1, 13, 4, 15, 6, 7, 0, 10, 5, 3, 14, 9, 11), - (7, 15, 5, 10, 8, 1, 6, 13, 0, 9, 3, 14, 11, 4, 2, 12), - (5, 13, 15, 6, 9, 2, 12, 10, 11, 7, 8, 1, 4, 3, 14, 0), - (8, 14, 2, 5, 6, 9, 1, 12, 15, 4, 11, 0, 13, 10, 3, 7), - (1, 7, 14, 13, 0, 5, 8, 3, 4, 15, 10, 6, 9, 12, 11, 2), - ), - "id-GostR3411-94-TestParamSet": ( - (4, 10, 9, 2, 13, 8, 0, 14, 6, 11, 1, 12, 7, 15, 5, 3), - (14, 11, 4, 12, 6, 13, 15, 10, 2, 3, 8, 1, 0, 7, 5, 9), - (5, 8, 1, 13, 10, 3, 4, 2, 14, 15, 12, 7, 6, 0, 9, 11), - (7, 13, 10, 1, 0, 8, 9, 15, 14, 4, 6, 12, 11, 2, 5, 3), - (6, 12, 7, 1, 5, 15, 13, 8, 4, 10, 9, 14, 0, 3, 11, 2), - (4, 11, 10, 0, 7, 2, 1, 13, 3, 6, 8, 5, 9, 12, 15, 14), - (13, 11, 4, 1, 3, 15, 5, 9, 0, 10, 14, 7, 6, 8, 2, 12), - (1, 15, 13, 0, 5, 7, 10, 4, 9, 2, 3, 14, 6, 11, 8, 12), - ), - "id-GostR3411-94-CryptoProParamSet": ( - (10, 4, 5, 6, 8, 1, 3, 7, 13, 12, 14, 0, 9, 2, 11, 15), - (5, 15, 4, 0, 2, 13, 11, 9, 1, 7, 6, 3, 12, 14, 10, 8), - (7, 15, 12, 14, 9, 4, 1, 0, 3, 11, 5, 2, 6, 10, 8, 13), - (4, 10, 7, 12, 0, 15, 2, 8, 14, 1, 6, 5, 13, 11, 9, 3), - (7, 6, 4, 11, 9, 12, 2, 10, 1, 8, 0, 14, 15, 13, 3, 5), - (7, 6, 2, 4, 13, 9, 15, 0, 10, 1, 5, 11, 8, 14, 12, 3), - (13, 14, 4, 1, 7, 0, 5, 10, 3, 12, 8, 15, 6, 2, 9, 11), - (1, 3, 10, 9, 5, 11, 4, 15, 8, 6, 7, 14, 13, 0, 2, 12), - ), - "EACParamSet": ( - (11, 4, 8, 10, 9, 7, 0, 3, 1, 6, 2, 15, 14, 5, 12, 13), - (1, 7, 14, 9, 11, 3, 15, 12, 0, 5, 4, 6, 13, 10, 8, 2), - (7, 3, 1, 9, 2, 4, 13, 15, 8, 10, 12, 6, 5, 0, 11, 14), - (10, 5, 15, 7, 14, 11, 3, 9, 2, 8, 1, 12, 0, 4, 6, 13), - (0, 14, 6, 11, 9, 3, 8, 4, 12, 15, 10, 5, 13, 7, 1, 2), - (9, 2, 11, 12, 0, 4, 5, 6, 3, 15, 13, 8, 1, 7, 14, 10), - (4, 0, 14, 1, 5, 11, 8, 3, 12, 2, 9, 7, 6, 10, 13, 15), - (7, 14, 12, 13, 9, 4, 8, 15, 10, 2, 6, 0, 3, 11, 5, 1), - ), -} -SBOXES["AppliedCryptography"] = SBOXES["id-GostR3411-94-TestParamSet"] - - -def _K(s, _in): - """S-box substitution - - :param s: S-box - :param _in: 32-bit word - :returns: substituted 32-bit word - """ - return ( - (s[0][(_in >> 0) & 0x0F] << 0) + - (s[1][(_in >> 4) & 0x0F] << 4) + - (s[2][(_in >> 8) & 0x0F] << 8) + - (s[3][(_in >> 12) & 0x0F] << 12) + - (s[4][(_in >> 16) & 0x0F] << 16) + - (s[5][(_in >> 20) & 0x0F] << 20) + - (s[6][(_in >> 24) & 0x0F] << 24) + - (s[7][(_in >> 28) & 0x0F] << 28) - ) - - -def block2ns(data): - """Convert block to N1 and N2 integers - """ - data = bytearray(data) - return ( - data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24, - data[4] | data[5] << 8 | data[6] << 16 | data[7] << 24, - ) - - -def ns2block(ns): - """Convert N1 and N2 integers to 8-byte block - """ - n1, n2 = ns - return bytes(bytearray(( - (n2 >> 0) & 0xFF, (n2 >> 8) & 0xFF, (n2 >> 16) & 0xFF, (n2 >> 24) & 0xFF, - (n1 >> 0) & 0xFF, (n1 >> 8) & 0xFF, (n1 >> 16) & 0xFF, (n1 >> 24) & 0xFF, - ))) - - -def _shift11(x): - """11-bit cyclic shift - """ - return ((x << 11) & (2 ** 32 - 1)) | ((x >> (32 - 11)) & (2 ** 32 - 1)) - - -def validate_key(key): - if len(key) != KEYSIZE: - raise ValueError("Invalid key size") - - -def validate_iv(iv): - if len(iv) != BLOCKSIZE: - raise ValueError("Invalid IV size") - - -def validate_sbox(sbox): - if sbox not in SBOXES: - raise ValueError("Unknown sbox supplied") - - -def xcrypt(seq, sbox, key, ns): - """Perform full-round single-block operation - - :param seq: sequence of K_i S-box applying (either encrypt or decrypt) - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :param bytes key: 256-bit encryption key - :param ns: N1 and N2 integers - :type ns: (int, int) - :returns: resulting N1 and N2 - :rtype: (int, int) - """ - s = SBOXES[sbox] - w = bytearray(key) - x = [ - w[0 + i * 4] | - w[1 + i * 4] << 8 | - w[2 + i * 4] << 16 | - w[3 + i * 4] << 24 for i in range(8) - ] - n1, n2 = ns - for i in seq: - n1, n2 = _shift11(_K(s, (n1 + x[i]) % (2 ** 32))) ^ n2, n1 - return n1, n2 - - -def encrypt(sbox, key, ns): - """Encrypt single block - """ - return xcrypt(SEQ_ENCRYPT, sbox, key, ns) - - -def decrypt(sbox, key, ns): - """Decrypt single block - """ - return xcrypt(SEQ_DECRYPT, sbox, key, ns) - - -def ecb(key, data, action, sbox=DEFAULT_SBOX): - """ECB mode of operation - - :param bytes key: encryption key - :param data: plaintext - :type data: bytes, multiple of BLOCKSIZE - :param func action: "encrypt"/"decrypt" - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :returns: ciphertext - :rtype: bytes - """ - validate_key(key) - validate_sbox(sbox) - if not data or len(data) % BLOCKSIZE != 0: - raise ValueError("Data is not blocksize aligned") - result = [] - for i in xrange(0, len(data), BLOCKSIZE): - result.append(ns2block(action( - sbox, key, block2ns(data[i:i + BLOCKSIZE]) - ))) - return b"".join(result) - - -ecb_encrypt = partial(ecb, action=encrypt) -ecb_decrypt = partial(ecb, action=decrypt) - - -def cbc_encrypt(key, data, iv=8 * b"\x00", pad=True, sbox=DEFAULT_SBOX, mesh=False): - """CBC encryption mode of operation - - :param bytes key: encryption key - :param bytes data: plaintext - :param iv: initialization vector - :type iv: bytes, BLOCKSIZE length - :type bool pad: perform ISO/IEC 7816-4 padding - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :param bool mesh: enable key meshing - :returns: ciphertext - :rtype: bytes - - 34.13-2015 padding method 2 is used. - """ - validate_key(key) - validate_iv(iv) - validate_sbox(sbox) - if not data: - raise ValueError("No data supplied") - if pad: - data = pad2(data, BLOCKSIZE) - if len(data) % BLOCKSIZE != 0: - raise ValueError("Data is not blocksize aligned") - ciphertext = [iv] - for i in xrange(0, len(data), BLOCKSIZE): - if mesh and i >= MESH_MAX_DATA and i % MESH_MAX_DATA == 0: - key, _ = meshing(key, iv, sbox=sbox) - ciphertext.append(ns2block(encrypt(sbox, key, block2ns( - strxor(ciphertext[-1], data[i:i + BLOCKSIZE]) - )))) - return b"".join(ciphertext) - - -def cbc_decrypt(key, data, pad=True, sbox=DEFAULT_SBOX, mesh=False): - """CBC decryption mode of operation - - :param bytes key: encryption key - :param bytes data: ciphertext - :type bool pad: perform ISO/IEC 7816-4 unpadding after decryption - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :param bool mesh: enable key meshing - :returns: plaintext - :rtype: bytes - """ - validate_key(key) - validate_sbox(sbox) - if not data or len(data) % BLOCKSIZE != 0: - raise ValueError("Data is not blocksize aligned") - if len(data) < 2 * BLOCKSIZE: - raise ValueError("There is no either data, or IV in ciphertext") - iv = data[:BLOCKSIZE] - plaintext = [] - for i in xrange(BLOCKSIZE, len(data), BLOCKSIZE): - if ( - mesh and - (i - BLOCKSIZE) >= MESH_MAX_DATA and - (i - BLOCKSIZE) % MESH_MAX_DATA == 0 - ): - key, _ = meshing(key, iv, sbox=sbox) - plaintext.append(strxor( - ns2block(decrypt(sbox, key, block2ns(data[i:i + BLOCKSIZE]))), - data[i - BLOCKSIZE:i], - )) - if pad: - plaintext[-1] = unpad2(plaintext[-1], BLOCKSIZE) - return b"".join(plaintext) - - -def cnt(key, data, iv=8 * b"\x00", sbox=DEFAULT_SBOX): - """Counter mode of operation - - :param bytes key: encryption key - :param bytes data: plaintext - :param iv: initialization vector - :type iv: bytes, BLOCKSIZE length - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :returns: ciphertext - :rtype: bytes - - For decryption you use the same function again. - """ - validate_key(key) - validate_iv(iv) - validate_sbox(sbox) - if not data: - raise ValueError("No data supplied") - n2, n1 = encrypt(sbox, key, block2ns(iv)) - gamma = [] - for _ in xrange(0, len(data) + pad_size(len(data), BLOCKSIZE), BLOCKSIZE): - n1 = (n1 + C2) % (2 ** 32) - n2 = (n2 + C1) % (2 ** 32 - 1) - gamma.append(ns2block(encrypt(sbox, key, (n1, n2)))) - return strxor(b"".join(gamma), data) - - -MESH_CONST = hexdec("6900722264C904238D3ADB9646E92AC418FEAC9400ED0712C086DCC2EF4CA92B") -MESH_MAX_DATA = 1024 - - -def meshing(key, iv, sbox=DEFAULT_SBOX): - """:rfc:`4357` key meshing - """ - key = ecb_decrypt(key, MESH_CONST, sbox=sbox) - iv = ecb_encrypt(key, iv, sbox=sbox) - return key, iv - - -def cfb_encrypt(key, data, iv=8 * b"\x00", sbox=DEFAULT_SBOX, mesh=False): - """CFB encryption mode of operation - - :param bytes key: encryption key - :param bytes data: plaintext - :param iv: initialization vector - :type iv: bytes, BLOCKSIZE length - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :param bool mesh: enable key meshing - :returns: ciphertext - :rtype: bytes - """ - validate_key(key) - validate_iv(iv) - validate_sbox(sbox) - if not data: - raise ValueError("No data supplied") - ciphertext = [iv] - for i in xrange(0, len(data) + pad_size(len(data), BLOCKSIZE), BLOCKSIZE): - if mesh and i >= MESH_MAX_DATA and i % MESH_MAX_DATA == 0: - key, iv = meshing(key, ciphertext[-1], sbox=sbox) - ciphertext.append(strxor( - data[i:i + BLOCKSIZE], - ns2block(encrypt(sbox, key, block2ns(iv))), - )) - continue - ciphertext.append(strxor( - data[i:i + BLOCKSIZE], - ns2block(encrypt(sbox, key, block2ns(ciphertext[-1]))), - )) - return b"".join(ciphertext[1:]) - - -def cfb_decrypt(key, data, iv=8 * b"\x00", sbox=DEFAULT_SBOX, mesh=False): - """CFB decryption mode of operation - - :param bytes key: encryption key - :param bytes data: plaintext - :param iv: initialization vector - :type iv: bytes, BLOCKSIZE length - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :param bool mesh: enable key meshing - :returns: ciphertext - :rtype: bytes - """ - validate_key(key) - validate_iv(iv) - validate_sbox(sbox) - if not data: - raise ValueError("No data supplied") - plaintext = [] - data = iv + data - for i in xrange(BLOCKSIZE, len(data) + pad_size(len(data), BLOCKSIZE), BLOCKSIZE): - if ( - mesh and - (i - BLOCKSIZE) >= MESH_MAX_DATA and - (i - BLOCKSIZE) % MESH_MAX_DATA == 0 - ): - key, iv = meshing(key, data[i - BLOCKSIZE:i], sbox=sbox) - plaintext.append(strxor( - data[i:i + BLOCKSIZE], - ns2block(encrypt(sbox, key, block2ns(iv))), - )) - continue - plaintext.append(strxor( - data[i:i + BLOCKSIZE], - ns2block(encrypt(sbox, key, block2ns(data[i - BLOCKSIZE:i]))), - )) - return b"".join(plaintext) diff --git a/pygost-5.13/build/lib/pygost/gost28147_mac.py b/pygost-5.13/build/lib/pygost/gost28147_mac.py deleted file mode 100644 index aab2805..0000000 --- a/pygost-5.13/build/lib/pygost/gost28147_mac.py +++ /dev/null @@ -1,99 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST 28147-89 MAC -""" - -from copy import copy - -from pygost.gost28147 import block2ns -from pygost.gost28147 import BLOCKSIZE -from pygost.gost28147 import DEFAULT_SBOX -from pygost.gost28147 import ns2block -from pygost.gost28147 import validate_iv -from pygost.gost28147 import validate_key -from pygost.gost28147 import validate_sbox -from pygost.gost28147 import xcrypt -from pygost.gost3413 import pad1 -from pygost.iface import PEP247 -from pygost.utils import strxor -from pygost.utils import xrange - -digest_size = 8 -SEQ_MAC = ( - 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, -) - - -class MAC(PEP247): - """GOST 28147-89 MAC mode of operation - - >>> m = MAC(key=key) - >>> m.update("some data") - >>> m.update("another data") - >>> m.hexdigest()[:8] - 'a687a08b' - """ - digest_size = digest_size - - def __init__(self, key, data=b"", iv=8 * b"\x00", sbox=DEFAULT_SBOX): - """ - :param key: authentication key - :type key: bytes, 32 bytes - :param iv: initialization vector - :type iv: bytes, BLOCKSIZE length - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - """ - validate_key(key) - validate_iv(iv) - validate_sbox(sbox) - self.key = key - self.data = data - self.iv = iv - self.sbox = sbox - - def copy(self): - return MAC(self.key, copy(self.data), self.iv, self.sbox) - - def update(self, data): - """Append data that has to be authenticated - """ - self.data += data - - def digest(self): - """Get MAC tag of supplied data - - You have to provide at least single byte of data. - If you want to produce tag length of 3 bytes, then - ``digest()[:3]``. - """ - if not self.data: - raise ValueError("No data processed") - data = pad1(self.data, BLOCKSIZE) - prev = block2ns(self.iv)[::-1] - for i in xrange(0, len(data), BLOCKSIZE): - prev = xcrypt( - SEQ_MAC, self.sbox, self.key, block2ns(strxor( - data[i:i + BLOCKSIZE], - ns2block(prev), - )), - )[::-1] - return ns2block(prev) - - -def new(key, data=b"", iv=8 * b"\x00", sbox=DEFAULT_SBOX): - return MAC(key, data, iv, sbox) diff --git a/pygost-5.13/build/lib/pygost/gost3410.py b/pygost-5.13/build/lib/pygost/gost3410.py deleted file mode 100644 index c12b170..0000000 --- a/pygost-5.13/build/lib/pygost/gost3410.py +++ /dev/null @@ -1,412 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST R 34.10 public-key signature function. - -This is implementation of GOST R 34.10-2001 (:rfc:`5832`), GOST R -34.10-2012 (:rfc:`7091`). The difference between 2001 and 2012 is the -key, digest and signature lengths. -""" - -from os import urandom - -from pygost.utils import bytes2long -from pygost.utils import hexdec -from pygost.utils import long2bytes -from pygost.utils import modinvert - - -def point_size(point): - """Determine is it either 256 or 512 bit point - """ - return (512 // 8) if point.bit_length() > 256 else (256 // 8) - - -class GOST3410Curve(object): - """GOST 34.10 validated curve - - >>> curve = CURVES["id-GostR3410-2001-TestParamSet"] - >>> prv = prv_unmarshal(urandom(32)) - >>> signature = sign(curve, prv, GOST341194(data).digest()) - >>> pub = public_key(curve, prv) - >>> verify(curve, pub, GOST341194(data).digest(), signature) - True - - :param long p: characteristic of the underlying prime field - :param long q: elliptic curve subgroup order - :param long a, b: coefficients of the equation of the elliptic curve in - the canonical form - :param long x, y: the coordinate of the point P (generator of the - subgroup of order q) of the elliptic curve in - the canonical form - :param long e, d: coefficients of the equation of the elliptic curve in - the twisted Edwards form - :param str name: human-readable curve name - """ - - def __init__(self, p, q, a, b, x, y, cofactor=1, e=None, d=None, name=None): - self.p = p - self.q = q - self.a = a - self.b = b - self.x = x - self.y = y - self.cofactor = cofactor - self.e = e - self.d = d - if not self.contains((x, y)): - raise ValueError("Invalid parameters") - self._st = None - self.name = name - - @property - def point_size(self): - return point_size(self.p) - - def __repr__(self): - return "<%s: %s>" % (self.__class__.__name__, self.name) - - def pos(self, v): - """Make positive number - """ - if v < 0: - return v + self.p - return v - - def contains(self, point): - """Is point on the curve? - - :type point: (long, long) - """ - x, y = point - r1 = y * y % self.p - r2 = ((x * x + self.a) * x + self.b) % self.p - return r1 == self.pos(r2) - - def _add(self, p1x, p1y, p2x, p2y): - if p1x == p2x and p1y == p2y: - # double - t = ((3 * p1x * p1x + self.a) * modinvert(2 * p1y, self.p)) % self.p - else: - tx = self.pos(p2x - p1x) % self.p - ty = self.pos(p2y - p1y) % self.p - t = (ty * modinvert(tx, self.p)) % self.p - tx = self.pos(t * t - p1x - p2x) % self.p - ty = self.pos(t * (p1x - tx) - p1y) % self.p - return tx, ty - - def exp(self, degree, x=None, y=None): - x = x or self.x - y = y or self.y - tx = x - ty = y - if degree == 0: - raise ValueError("Bad degree value") - degree -= 1 - while degree != 0: - if degree & 1 == 1: - tx, ty = self._add(tx, ty, x, y) - degree = degree >> 1 - x, y = self._add(x, y, x, y) - return tx, ty - - def st(self): - """Compute s/t parameters for twisted Edwards curve points conversion - """ - if self.e is None or self.d is None: - raise ValueError("Non twisted Edwards curve") - if self._st is not None: - return self._st - self._st = ( - self.pos(self.e - self.d) * modinvert(4, self.p) % self.p, - (self.e + self.d) * modinvert(6, self.p) % self.p, - ) - return self._st - - -CURVES = { - "GostR3410_2001_ParamSet_cc": GOST3410Curve( - p=bytes2long(hexdec("C0000000000000000000000000000000000000000000000000000000000003C7")), - q=bytes2long(hexdec("5fffffffffffffffffffffffffffffff606117a2f4bde428b7458a54b6e87b85")), - a=bytes2long(hexdec("C0000000000000000000000000000000000000000000000000000000000003c4")), - b=bytes2long(hexdec("2d06B4265ebc749ff7d0f1f1f88232e81632e9088fd44b7787d5e407e955080c")), - x=bytes2long(hexdec("0000000000000000000000000000000000000000000000000000000000000002")), - y=bytes2long(hexdec("a20e034bf8813ef5c18d01105e726a17eb248b264ae9706f440bedc8ccb6b22c")), - ), - "id-GostR3410-2001-TestParamSet": GOST3410Curve( - p=bytes2long(hexdec("8000000000000000000000000000000000000000000000000000000000000431")), - q=bytes2long(hexdec("8000000000000000000000000000000150FE8A1892976154C59CFC193ACCF5B3")), - a=bytes2long(hexdec("0000000000000000000000000000000000000000000000000000000000000007")), - b=bytes2long(hexdec("5FBFF498AA938CE739B8E022FBAFEF40563F6E6A3472FC2A514C0CE9DAE23B7E")), - x=bytes2long(hexdec("0000000000000000000000000000000000000000000000000000000000000002")), - y=bytes2long(hexdec("08E2A8A0E65147D4BD6316030E16D19C85C97F0A9CA267122B96ABBCEA7E8FC8")), - ), - "id-tc26-gost-3410-12-256-paramSetA": GOST3410Curve( - p=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97")), - q=bytes2long(hexdec("400000000000000000000000000000000FD8CDDFC87B6635C115AF556C360C67")), - a=bytes2long(hexdec("C2173F1513981673AF4892C23035A27CE25E2013BF95AA33B22C656F277E7335")), - b=bytes2long(hexdec("295F9BAE7428ED9CCC20E7C359A9D41A22FCCD9108E17BF7BA9337A6F8AE9513")), - x=bytes2long(hexdec("91E38443A5E82C0D880923425712B2BB658B9196932E02C78B2582FE742DAA28")), - y=bytes2long(hexdec("32879423AB1A0375895786C4BB46E9565FDE0B5344766740AF268ADB32322E5C")), - cofactor=4, - e=0x01, - d=bytes2long(hexdec("0605F6B7C183FA81578BC39CFAD518132B9DF62897009AF7E522C32D6DC7BFFB")), - ), - "id-tc26-gost-3410-12-256-paramSetB": GOST3410Curve( - p=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97")), - q=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893")), - a=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94")), - b=bytes2long(hexdec("00000000000000000000000000000000000000000000000000000000000000a6")), - x=bytes2long(hexdec("0000000000000000000000000000000000000000000000000000000000000001")), - y=bytes2long(hexdec("8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14")), - ), - "id-tc26-gost-3410-12-256-paramSetC": GOST3410Curve( - p=bytes2long(hexdec("8000000000000000000000000000000000000000000000000000000000000C99")), - q=bytes2long(hexdec("800000000000000000000000000000015F700CFFF1A624E5E497161BCC8A198F")), - a=bytes2long(hexdec("8000000000000000000000000000000000000000000000000000000000000C96")), - b=bytes2long(hexdec("3E1AF419A269A5F866A7D3C25C3DF80AE979259373FF2B182F49D4CE7E1BBC8B")), - x=bytes2long(hexdec("0000000000000000000000000000000000000000000000000000000000000001")), - y=bytes2long(hexdec("3FA8124359F96680B83D1C3EB2C070E5C545C9858D03ECFB744BF8D717717EFC")), - ), - "id-tc26-gost-3410-12-256-paramSetD": GOST3410Curve( - p=bytes2long(hexdec("9B9F605F5A858107AB1EC85E6B41C8AACF846E86789051D37998F7B9022D759B")), - q=bytes2long(hexdec("9B9F605F5A858107AB1EC85E6B41C8AA582CA3511EDDFB74F02F3A6598980BB9")), - a=bytes2long(hexdec("9B9F605F5A858107AB1EC85E6B41C8AACF846E86789051D37998F7B9022D7598")), - b=bytes2long(hexdec("000000000000000000000000000000000000000000000000000000000000805a")), - x=bytes2long(hexdec("0000000000000000000000000000000000000000000000000000000000000000")), - y=bytes2long(hexdec("41ECE55743711A8C3CBF3783CD08C0EE4D4DC440D4641A8F366E550DFDB3BB67")), - ), - "id-tc26-gost-3410-12-512-paramSetTest": GOST3410Curve( - p=bytes2long(hexdec("4531ACD1FE0023C7550D267B6B2FEE80922B14B2FFB90F04D4EB7C09B5D2D15DF1D852741AF4704A0458047E80E4546D35B8336FAC224DD81664BBF528BE6373")), - q=bytes2long(hexdec("4531ACD1FE0023C7550D267B6B2FEE80922B14B2FFB90F04D4EB7C09B5D2D15DA82F2D7ECB1DBAC719905C5EECC423F1D86E25EDBE23C595D644AAF187E6E6DF")), - a=7, - b=bytes2long(hexdec("1CFF0806A31116DA29D8CFA54E57EB748BC5F377E49400FDD788B649ECA1AC4361834013B2AD7322480A89CA58E0CF74BC9E540C2ADD6897FAD0A3084F302ADC")), - x=bytes2long(hexdec("24D19CC64572EE30F396BF6EBBFD7A6C5213B3B3D7057CC825F91093A68CD762FD60611262CD838DC6B60AA7EEE804E28BC849977FAC33B4B530F1B120248A9A")), - y=bytes2long(hexdec("2BB312A43BD2CE6E0D020613C857ACDDCFBF061E91E5F2C3F32447C259F39B2C83AB156D77F1496BF7EB3351E1EE4E43DC1A18B91B24640B6DBB92CB1ADD371E")), - ), - "id-tc26-gost-3410-12-512-paramSetA": GOST3410Curve( - p=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC7")), - q=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27E69532F48D89116FF22B8D4E0560609B4B38ABFAD2B85DCACDB1411F10B275")), - a=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC4")), - b=bytes2long(hexdec("E8C2505DEDFC86DDC1BD0B2B6667F1DA34B82574761CB0E879BD081CFD0B6265EE3CB090F30D27614CB4574010DA90DD862EF9D4EBEE4761503190785A71C760")), - x=bytes2long(hexdec("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003")), - y=bytes2long(hexdec("7503CFE87A836AE3A61B8816E25450E6CE5E1C93ACF1ABC1778064FDCBEFA921DF1626BE4FD036E93D75E6A50E3A41E98028FE5FC235F5B889A589CB5215F2A4")), - ), - "id-tc26-gost-3410-12-512-paramSetB": GOST3410Curve( - p=bytes2long(hexdec("8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006F")), - q=bytes2long(hexdec("800000000000000000000000000000000000000000000000000000000000000149A1EC142565A545ACFDB77BD9D40CFA8B996712101BEA0EC6346C54374F25BD")), - a=bytes2long(hexdec("8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006C")), - b=bytes2long(hexdec("687D1B459DC841457E3E06CF6F5E2517B97C7D614AF138BCBF85DC806C4B289F3E965D2DB1416D217F8B276FAD1AB69C50F78BEE1FA3106EFB8CCBC7C5140116")), - x=bytes2long(hexdec("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002")), - y=bytes2long(hexdec("1A8F7EDA389B094C2C071E3647A8940F3C123B697578C213BE6DD9E6C8EC7335DCB228FD1EDF4A39152CBCAAF8C0398828041055F94CEEEC7E21340780FE41BD")), - ), - "id-tc26-gost-3410-12-512-paramSetC": GOST3410Curve( - p=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC7")), - q=bytes2long(hexdec("3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC98CDBA46506AB004C33A9FF5147502CC8EDA9E7A769A12694623CEF47F023ED")), - a=bytes2long(hexdec("DC9203E514A721875485A529D2C722FB187BC8980EB866644DE41C68E143064546E861C0E2C9EDD92ADE71F46FCF50FF2AD97F951FDA9F2A2EB6546F39689BD3")), - b=bytes2long(hexdec("B4C4EE28CEBC6C2C8AC12952CF37F16AC7EFB6A9F69F4B57FFDA2E4F0DE5ADE038CBC2FFF719D2C18DE0284B8BFEF3B52B8CC7A5F5BF0A3C8D2319A5312557E1")), - x=bytes2long(hexdec("E2E31EDFC23DE7BDEBE241CE593EF5DE2295B7A9CBAEF021D385F7074CEA043AA27272A7AE602BF2A7B9033DB9ED3610C6FB85487EAE97AAC5BC7928C1950148")), - y=bytes2long(hexdec("F5CE40D95B5EB899ABBCCFF5911CB8577939804D6527378B8C108C3D2090FF9BE18E2D33E3021ED2EF32D85822423B6304F726AA854BAE07D0396E9A9ADDC40F")), - cofactor=4, - e=0x01, - d=bytes2long(hexdec("9E4F5D8C017D8D9F13A5CF3CDF5BFE4DAB402D54198E31EBDE28A0621050439CA6B39E0A515C06B304E2CE43E79E369E91A0CFC2BC2A22B4CA302DBB33EE7550")), - ), -} -CURVES["id-GostR3410-2001-CryptoPro-A-ParamSet"] = CURVES["id-tc26-gost-3410-12-256-paramSetB"] -CURVES["id-GostR3410-2001-CryptoPro-B-ParamSet"] = CURVES["id-tc26-gost-3410-12-256-paramSetC"] -CURVES["id-GostR3410-2001-CryptoPro-C-ParamSet"] = CURVES["id-tc26-gost-3410-12-256-paramSetD"] -CURVES["id-GostR3410-2001-CryptoPro-XchA-ParamSet"] = CURVES["id-GostR3410-2001-CryptoPro-A-ParamSet"] -CURVES["id-GostR3410-2001-CryptoPro-XchB-ParamSet"] = CURVES["id-GostR3410-2001-CryptoPro-C-ParamSet"] -CURVES["id-tc26-gost-3410-2012-256-paramSetA"] = CURVES["id-tc26-gost-3410-12-256-paramSetA"] -CURVES["id-tc26-gost-3410-2012-256-paramSetB"] = CURVES["id-tc26-gost-3410-12-256-paramSetB"] -CURVES["id-tc26-gost-3410-2012-256-paramSetC"] = CURVES["id-tc26-gost-3410-12-256-paramSetC"] -CURVES["id-tc26-gost-3410-2012-256-paramSetD"] = CURVES["id-tc26-gost-3410-12-256-paramSetD"] -CURVES["id-tc26-gost-3410-2012-512-paramSetTest"] = CURVES["id-tc26-gost-3410-12-512-paramSetTest"] -CURVES["id-tc26-gost-3410-2012-512-paramSetA"] = CURVES["id-tc26-gost-3410-12-512-paramSetA"] -CURVES["id-tc26-gost-3410-2012-512-paramSetB"] = CURVES["id-tc26-gost-3410-12-512-paramSetB"] -CURVES["id-tc26-gost-3410-2012-512-paramSetC"] = CURVES["id-tc26-gost-3410-12-512-paramSetC"] -for _name, _curve in CURVES.items(): - _curve.name = _name -DEFAULT_CURVE = CURVES["id-tc26-gost-3410-12-256-paramSetB"] - - -def public_key(curve, prv, mask=None): - """Generate public key from the private one - - :param GOST3410Curve curve: curve to use - :param long prv: private key - :returns: public key's parts, X and Y - :rtype: (long, long) - """ - pub = curve.exp(prv) - if mask is not None: - pub = curve.exp(mask, pub[0], pub[1]) - return pub - - -def sign(curve, prv, digest, rand=None, mask=None): - """Calculate signature for provided digest - - :param GOST3410Curve curve: curve to use - :param long prv: private key - :param digest: digest for signing - :type digest: bytes, 32 or 64 bytes - :param rand: optional predefined random data used for k/r generation - :type rand: bytes, 32 or 64 bytes - :returns: signature, BE(S) || BE(R) - :rtype: bytes, 64 or 128 bytes - """ - size = curve.point_size - q = curve.q - e = bytes2long(digest) % q - if e == 0: - e = 1 - while True: - if rand is None: - rand = urandom(size) - elif len(rand) != size: - raise ValueError("rand length != %d" % size) - k = bytes2long(rand) % q - if k == 0: - continue - r, y = curve.exp(k) - if mask is not None: - r, y = curve.exp(mask, x=r, y=y) - r %= q - if r == 0: - continue - d = prv * r - k *= e - s = d + k - if mask is not None: - s *= mask - s %= q - if s == 0: - continue - break - return long2bytes(s, size) + long2bytes(r, size) - - -def verify(curve, pub, digest, signature): - """Verify provided digest with the signature - - :param GOST3410Curve curve: curve to use - :type pub: (long, long) - :param digest: digest needed to check - :type digest: bytes, 32 or 64 bytes - :param signature: signature to verify with - :type signature: bytes, 64 or 128 bytes - :rtype: bool - """ - size = curve.point_size - if len(signature) != size * 2: - raise ValueError("Invalid signature length") - q = curve.q - p = curve.p - s = bytes2long(signature[:size]) - r = bytes2long(signature[size:]) - if r <= 0 or r >= q or s <= 0 or s >= q: - return False - e = bytes2long(digest) % curve.q - if e == 0: - e = 1 - v = modinvert(e, q) - z1 = s * v % q - z2 = q - r * v % q - p1x, p1y = curve.exp(z1) - q1x, q1y = curve.exp(z2, pub[0], pub[1]) - lm = q1x - p1x - if lm < 0: - lm += p - lm = modinvert(lm, p) - z1 = q1y - p1y - lm = lm * z1 % p - lm = lm * lm % p - lm = lm - p1x - q1x - lm = lm % p - if lm < 0: - lm += p - lm %= q - # This is not constant time comparison! - return lm == r - - -def prv_unmarshal(prv): - """Unmarshal little-endian private key - - :param bytes prv: serialized private key - :rtype: long - - It is advisable to use :py:func:`pygost.gost3410.prv_marshal` to - assure that key i in curve's Q field for better compatibility with - some implementations. - """ - return bytes2long(prv[::-1]) - - -def prv_marshal(curve, prv): - """Marshal little-endian private key - - :param GOST3410Curve curve: curve to use - :param long prv: serialized private key - :rtype: bytes - - Key is in curve's Q field. - """ - return long2bytes(prv % curve.q, point_size(prv))[::-1] - - -def pub_marshal(pub): - """Marshal public key - - :type pub: (long, long) - :rtype: bytes - :returns: LE(X) || LE(Y) - """ - size = point_size(pub[0]) - return (long2bytes(pub[1], size) + long2bytes(pub[0], size))[::-1] - - -def pub_unmarshal(pub): - """Unmarshal public key - - :param pub: LE(X) || LE(Y) - :type pub: bytes - :rtype: (long, long) - """ - size = len(pub) // 2 - pub = pub[::-1] - return (bytes2long(pub[size:]), bytes2long(pub[:size])) - - -def uv2xy(curve, u, v): - """Convert twisted Edwards curve U,V coordinates to Weierstrass X,Y - """ - s, t = curve.st() - k1 = (s * (1 + v)) % curve.p - k2 = curve.pos(1 - v) - x = t + k1 * modinvert(k2, curve.p) - y = k1 * modinvert(u * k2, curve.p) - return x % curve.p, y % curve.p - - -def xy2uv(curve, x, y): - """Convert Weierstrass X,Y coordinates to twisted Edwards curve U,V - """ - s, t = curve.st() - xmt = curve.pos(x - t) - u = xmt * modinvert(y, curve.p) - v = curve.pos(xmt - s) * modinvert(xmt + s, curve.p) - return u % curve.p, v % curve.p diff --git a/pygost-5.13/build/lib/pygost/gost3410_vko.py b/pygost-5.13/build/lib/pygost/gost3410_vko.py deleted file mode 100644 index 3f169dc..0000000 --- a/pygost-5.13/build/lib/pygost/gost3410_vko.py +++ /dev/null @@ -1,97 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""Key agreement functions, VKO GOST R 34.10-2001/2012 -""" - -from pygost.gost3410 import pub_marshal -from pygost.gost34112012256 import GOST34112012256 -from pygost.gost34112012512 import GOST34112012512 -from pygost.gost341194 import GOST341194 -from pygost.utils import bytes2long - - -def ukm_unmarshal(ukm): - """Unmarshal UKM value - - :type ukm: little-endian bytes - :rtype: long - """ - return bytes2long(ukm[::-1]) - - -def kek(curve, prv, pub, ukm, mask=None): - if not curve.contains(pub): - raise ValueError("pub is not on the curve") - key = curve.exp(prv, pub[0], pub[1]) - key = curve.exp(curve.cofactor * ukm, key[0], key[1]) - if mask is not None: - key = curve.exp(mask, key[0], key[1]) - return pub_marshal(key) - - -def kek_34102001(curve, prv, pub, ukm): - """Key agreement (34.10-2001, 34.11-94) - - :param GOST3410Curve curve: curve to use - :param long prv: private key - :param pub: public key - :type pub: (long, long) - :param long ukm: user keying material, VKO-factor - :returns: Key Encryption Key (shared key) - :rtype: bytes, 32 bytes - - Shared Key Encryption Key computation is based on - :rfc:`4357` VKO GOST R 34.10-2001 with little-endian - hash output. - """ - return GOST341194( - kek(curve, prv, pub, ukm), - sbox="id-GostR3411-94-CryptoProParamSet", - ).digest() - - -def kek_34102012256(curve, prv, pub, ukm=1): - """Key agreement (34.10-2012, 34.11-2012 256 bit) - - :param GOST3410Curve curve: curve to use - :param long prv: private key - :param pub: public key - :type pub: (long, long) - :param long ukm: user keying material, VKO-factor - :returns: Key Encryption Key (shared key) - :rtype: bytes, 32 bytes - - Shared Key Encryption Key computation is based on - :rfc:`7836` VKO GOST R 34.10-2012. - """ - return GOST34112012256(kek(curve, prv, pub, ukm)).digest() - - -def kek_34102012512(curve, prv, pub, ukm=1): - """Key agreement (34.10-2012, 34.11-2012 512 bit) - - :param GOST3410Curve curve: curve to use - :param long prv: private key - :param pub: public key - :type pub: (long, long) - :param long ukm: user keying material, VKO-factor - :returns: Key Encryption Key (shared key) - :rtype: bytes, 32 bytes - - Shared Key Encryption Key computation is based on - :rfc:`7836` VKO GOST R 34.10-2012. - """ - return GOST34112012512(kek(curve, prv, pub, ukm)).digest() diff --git a/pygost-5.13/build/lib/pygost/gost34112012.py b/pygost-5.13/build/lib/pygost/gost34112012.py deleted file mode 100644 index 91782de..0000000 --- a/pygost-5.13/build/lib/pygost/gost34112012.py +++ /dev/null @@ -1,299 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST R 34.11-2012 (Streebog) hash function common files - -This is implementation of :rfc:`6986`. Most function and variable names are -taken according to specification's terminology. -""" - -from copy import copy -from struct import pack -from struct import unpack - -from pygost.iface import PEP247 -from pygost.utils import hexdec -from pygost.utils import strxor -from pygost.utils import xrange - - -BLOCKSIZE = 64 -Pi = bytearray(( - 252, 238, 221, 17, 207, 110, 49, 22, 251, 196, 250, - 218, 35, 197, 4, 77, 233, 119, 240, 219, 147, 46, - 153, 186, 23, 54, 241, 187, 20, 205, 95, 193, 249, - 24, 101, 90, 226, 92, 239, 33, 129, 28, 60, 66, - 139, 1, 142, 79, 5, 132, 2, 174, 227, 106, 143, - 160, 6, 11, 237, 152, 127, 212, 211, 31, 235, 52, - 44, 81, 234, 200, 72, 171, 242, 42, 104, 162, 253, - 58, 206, 204, 181, 112, 14, 86, 8, 12, 118, 18, - 191, 114, 19, 71, 156, 183, 93, 135, 21, 161, 150, - 41, 16, 123, 154, 199, 243, 145, 120, 111, 157, 158, - 178, 177, 50, 117, 25, 61, 255, 53, 138, 126, 109, - 84, 198, 128, 195, 189, 13, 87, 223, 245, 36, 169, - 62, 168, 67, 201, 215, 121, 214, 246, 124, 34, 185, - 3, 224, 15, 236, 222, 122, 148, 176, 188, 220, 232, - 40, 80, 78, 51, 10, 74, 167, 151, 96, 115, 30, - 0, 98, 68, 26, 184, 56, 130, 100, 159, 38, 65, - 173, 69, 70, 146, 39, 94, 85, 47, 140, 163, 165, - 125, 105, 213, 149, 59, 7, 88, 179, 64, 134, 172, - 29, 247, 48, 55, 107, 228, 136, 217, 231, 137, 225, - 27, 131, 73, 76, 63, 248, 254, 141, 83, 170, 144, - 202, 216, 133, 97, 32, 113, 103, 164, 45, 43, 9, - 91, 203, 155, 37, 208, 190, 229, 108, 82, 89, 166, - 116, 210, 230, 244, 180, 192, 209, 102, 175, 194, 57, - 75, 99, 182, -)) - -A = [unpack(">Q", hexdec(s))[0] for s in ( - "8e20faa72ba0b470", "47107ddd9b505a38", "ad08b0e0c3282d1c", "d8045870ef14980e", - "6c022c38f90a4c07", "3601161cf205268d", "1b8e0b0e798c13c8", "83478b07b2468764", - "a011d380818e8f40", "5086e740ce47c920", "2843fd2067adea10", "14aff010bdd87508", - "0ad97808d06cb404", "05e23c0468365a02", "8c711e02341b2d01", "46b60f011a83988e", - "90dab52a387ae76f", "486dd4151c3dfdb9", "24b86a840e90f0d2", "125c354207487869", - "092e94218d243cba", "8a174a9ec8121e5d", "4585254f64090fa0", "accc9ca9328a8950", - "9d4df05d5f661451", "c0a878a0a1330aa6", "60543c50de970553", "302a1e286fc58ca7", - "18150f14b9ec46dd", "0c84890ad27623e0", "0642ca05693b9f70", "0321658cba93c138", - "86275df09ce8aaa8", "439da0784e745554", "afc0503c273aa42a", "d960281e9d1d5215", - "e230140fc0802984", "71180a8960409a42", "b60c05ca30204d21", "5b068c651810a89e", - "456c34887a3805b9", "ac361a443d1c8cd2", "561b0d22900e4669", "2b838811480723ba", - "9bcf4486248d9f5d", "c3e9224312c8c1a0", "effa11af0964ee50", "f97d86d98a327728", - "e4fa2054a80b329c", "727d102a548b194e", "39b008152acb8227", "9258048415eb419d", - "492c024284fbaec0", "aa16012142f35760", "550b8e9e21f7a530", "a48b474f9ef5dc18", - "70a6a56e2440598e", "3853dc371220a247", "1ca76e95091051ad", "0edd37c48a08a6d8", - "07e095624504536c", "8d70c431ac02a736", "c83862965601dd1b", "641c314b2b8ee083", -)] - -Tau = ( - 0, 8, 16, 24, 32, 40, 48, 56, - 1, 9, 17, 25, 33, 41, 49, 57, - 2, 10, 18, 26, 34, 42, 50, 58, - 3, 11, 19, 27, 35, 43, 51, 59, - 4, 12, 20, 28, 36, 44, 52, 60, - 5, 13, 21, 29, 37, 45, 53, 61, - 6, 14, 22, 30, 38, 46, 54, 62, - 7, 15, 23, 31, 39, 47, 55, 63, -) - -C = [hexdec("".join(s))[::-1] for s in ( - ( - "b1085bda1ecadae9ebcb2f81c0657c1f", - "2f6a76432e45d016714eb88d7585c4fc", - "4b7ce09192676901a2422a08a460d315", - "05767436cc744d23dd806559f2a64507", - ), - ( - "6fa3b58aa99d2f1a4fe39d460f70b5d7", - "f3feea720a232b9861d55e0f16b50131", - "9ab5176b12d699585cb561c2db0aa7ca", - "55dda21bd7cbcd56e679047021b19bb7", - ), - ( - "f574dcac2bce2fc70a39fc286a3d8435", - "06f15e5f529c1f8bf2ea7514b1297b7b", - "d3e20fe490359eb1c1c93a376062db09", - "c2b6f443867adb31991e96f50aba0ab2", - ), - ( - "ef1fdfb3e81566d2f948e1a05d71e4dd", - "488e857e335c3c7d9d721cad685e353f", - "a9d72c82ed03d675d8b71333935203be", - "3453eaa193e837f1220cbebc84e3d12e", - ), - ( - "4bea6bacad4747999a3f410c6ca92363", - "7f151c1f1686104a359e35d7800fffbd", - "bfcd1747253af5a3dfff00b723271a16", - "7a56a27ea9ea63f5601758fd7c6cfe57", - ), - ( - "ae4faeae1d3ad3d96fa4c33b7a3039c0", - "2d66c4f95142a46c187f9ab49af08ec6", - "cffaa6b71c9ab7b40af21f66c2bec6b6", - "bf71c57236904f35fa68407a46647d6e", - ), - ( - "f4c70e16eeaac5ec51ac86febf240954", - "399ec6c7e6bf87c9d3473e33197a93c9", - "0992abc52d822c3706476983284a0504", - "3517454ca23c4af38886564d3a14d493", - ), - ( - "9b1f5b424d93c9a703e7aa020c6e4141", - "4eb7f8719c36de1e89b4443b4ddbc49a", - "f4892bcb929b069069d18d2bd1a5c42f", - "36acc2355951a8d9a47f0dd4bf02e71e", - ), - ( - "378f5a541631229b944c9ad8ec165fde", - "3a7d3a1b258942243cd955b7e00d0984", - "800a440bdbb2ceb17b2b8a9aa6079c54", - "0e38dc92cb1f2a607261445183235adb", - ), - ( - "abbedea680056f52382ae548b2e4f3f3", - "8941e71cff8a78db1fffe18a1b336103", - "9fe76702af69334b7a1e6c303b7652f4", - "3698fad1153bb6c374b4c7fb98459ced", - ), - ( - "7bcd9ed0efc889fb3002c6cd635afe94", - "d8fa6bbbebab07612001802114846679", - "8a1d71efea48b9caefbacd1d7d476e98", - "dea2594ac06fd85d6bcaa4cd81f32d1b", - ), - ( - "378ee767f11631bad21380b00449b17a", - "cda43c32bcdf1d77f82012d430219f9b", - "5d80ef9d1891cc86e71da4aa88e12852", - "faf417d5d9b21b9948bc924af11bd720", - ), -)] - - -def _lcache(): - cache = [] - for byteN in xrange(8): - cache.append([0 for _ in xrange(256)]) - for byteN in xrange(8): - for byteVal in xrange(256): - res64 = 0 - val = byteVal - for bitN in xrange(8): - if val & 0x80 > 0: - res64 ^= A[(7 - byteN) * 8 + bitN] - val <<= 1 - cache[byteN][byteVal] = res64 - return cache - - -# Trade memory for CPU for part of L() calculations -LCache = _lcache() - - -def add512bit(a, b): - a = int.from_bytes(a, "little") - b = int.from_bytes(b, "little") - r = (a + b) % (1 << 512) - return r.to_bytes(512 // 8, "little") - - -def g(n, hsh, msg): - res = E(LPS(strxor(hsh[:8], pack(">> m = GOST34112012(digest_size=32) - >>> m.update("foo") - >>> m.update("bar") - >>> m.hexdigest() - 'e3c9fd89226d93b489a9fe27d686806e24a514e3787bca053c698ec4616ceb78' - """ - block_size = BLOCKSIZE - - def __init__(self, data=b"", digest_size=64): - """ - :param digest_size: hash digest size to compute - :type digest_size: 32 or 64 bytes - """ - self._digest_size = digest_size - self.hsh = BLOCKSIZE * (b"\x01" if digest_size == 32 else b"\x00") - self.chk = bytearray(BLOCKSIZE * b"\x00") - self.n = 0 - self.buf = b"" - self.update(data) - - def copy(self): - obj = GOST34112012() - obj._digest_size = self._digest_size - obj.hsh = self.hsh - obj.chk = copy(self.chk) - obj.n = self.n - obj.buf = self.buf - return obj - - @property - def digest_size(self): - return self._digest_size - - def _update_block(self, block): - self.hsh = g(self.n, self.hsh, block) - self.chk = add512bit(self.chk, block) - self.n += 512 - - def update(self, data): - """Update state with the new data - """ - if len(self.buf) > 0: - chunk_len = BLOCKSIZE - len(self.buf) - self.buf += data[:chunk_len] - data = data[chunk_len:] - if len(self.buf) == BLOCKSIZE: - self._update_block(self.buf) - self.buf = b"" - while len(data) >= BLOCKSIZE: - self._update_block(data[:BLOCKSIZE]) - data = data[BLOCKSIZE:] - self.buf += data - - def digest(self): - """Get hash of the provided data - """ - data = self.buf - - # Padding - padblock_size = len(data) * 8 - data += b"\x01" - padlen = BLOCKSIZE - len(data) - if padlen != BLOCKSIZE: - data += b"\x00" * padlen - - hsh = g(self.n, self.hsh, data) - n = self.n + padblock_size - chk = add512bit(self.chk, data) - hsh = g(0, hsh, pack(" -# -# 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST R 34.11-94 hash function - -This is implementation of :rfc:`5831`. Most function and variable names are -taken according to specification's terminology. -""" - -from copy import copy -from functools import partial -from struct import pack - -from pygost.gost28147 import block2ns -from pygost.gost28147 import encrypt -from pygost.gost28147 import ns2block -from pygost.gost28147 import validate_sbox -from pygost.iface import PEP247 -from pygost.pbkdf2 import pbkdf2 as pbkdf2_base -from pygost.utils import hexdec -from pygost.utils import hexenc -from pygost.utils import strxor -from pygost.utils import xrange - - -DEFAULT_SBOX = "id-GostR3411-94-CryptoProParamSet" -BLOCKSIZE = 32 -C2 = 32 * b"\x00" -C3 = hexdec(b"ff00ffff000000ffff0000ff00ffff0000ff00ff00ff00ffff00ff00ff00ff00") -C4 = 32 * b"\x00" -digest_size = 32 - - -def A(x): - x4, x3, x2, x1 = x[0:8], x[8:16], x[16:24], x[24:32] - return b"".join((strxor(x1, x2), x4, x3, x2)) - - -def P(x): - return bytearray(( - x[0], x[8], x[16], x[24], x[1], x[9], x[17], x[25], x[2], - x[10], x[18], x[26], x[3], x[11], x[19], x[27], x[4], x[12], - x[20], x[28], x[5], x[13], x[21], x[29], x[6], x[14], x[22], - x[30], x[7], x[15], x[23], x[31], - )) - - -def _chi(Y): - """Chi function - - This is some kind of LFSR. - """ - (y16, y15, y14, y13, y12, y11, y10, y9, y8, y7, y6, y5, y4, y3, y2, y1) = ( - Y[0:2], Y[2:4], Y[4:6], Y[6:8], Y[8:10], Y[10:12], Y[12:14], - Y[14:16], Y[16:18], Y[18:20], Y[20:22], Y[22:24], Y[24:26], - Y[26:28], Y[28:30], Y[30:32], - ) - by1, by2, by3, by4, by13, by16, byx = ( - bytearray(y1), bytearray(y2), bytearray(y3), bytearray(y4), - bytearray(y13), bytearray(y16), bytearray(2), - ) - byx[0] = by1[0] ^ by2[0] ^ by3[0] ^ by4[0] ^ by13[0] ^ by16[0] - byx[1] = by1[1] ^ by2[1] ^ by3[1] ^ by4[1] ^ by13[1] ^ by16[1] - return b"".join(( - bytes(byx), y16, y15, y14, y13, y12, y11, y10, y9, y8, y7, y6, y5, y4, y3, y2 - )) - - -def _step(hin, m, sbox): - """Step function - - H_out = f(H_in, m) - """ - # Generate keys - u = hin - v = m - w = strxor(hin, m) - k1 = P(w) - - u = strxor(A(u), C2) - v = A(A(v)) - w = strxor(u, v) - k2 = P(w) - - u = strxor(A(u), C3) - v = A(A(v)) - w = strxor(u, v) - k3 = P(w) - - u = strxor(A(u), C4) - v = A(A(v)) - w = strxor(u, v) - k4 = P(w) - - # Encipher - h4, h3, h2, h1 = hin[0:8], hin[8:16], hin[16:24], hin[24:32] - s1 = ns2block(encrypt(sbox, k1[::-1], block2ns(h1[::-1])))[::-1] - s2 = ns2block(encrypt(sbox, k2[::-1], block2ns(h2[::-1])))[::-1] - s3 = ns2block(encrypt(sbox, k3[::-1], block2ns(h3[::-1])))[::-1] - s4 = ns2block(encrypt(sbox, k4[::-1], block2ns(h4[::-1])))[::-1] - s = b"".join((s4, s3, s2, s1)) - - # Permute - # H_out = chi^61(H_in XOR chi(m XOR chi^12(S))) - x = s - for _ in xrange(12): - x = _chi(x) - x = strxor(x, m) - x = _chi(x) - x = strxor(hin, x) - for _ in xrange(61): - x = _chi(x) - return x - - -class GOST341194(PEP247): - """GOST 34.11-94 big-endian hash - - >>> m = GOST341194() - >>> m.update("foo") - >>> m.update("bar") - >>> m.hexdigest() - '3bd8a3a35917871dfa0d49f9e73e7c57eea028dc061133eb560849ea20c133af' - >>> GOST341194("foobar").hexdigest() - '3bd8a3a35917871dfa0d49f9e73e7c57eea028dc061133eb560849ea20c133af' - """ - block_size = BLOCKSIZE - digest_size = digest_size - - def __init__(self, data=b"", sbox=DEFAULT_SBOX): - """ - :param bytes data: provide initial data - :param bytes sbox: S-box to use - """ - validate_sbox(sbox) - self.data = data - self.sbox = sbox - - def copy(self): - return GOST341194(copy(self.data), self.sbox) - - def update(self, data): - """Append data that has to be hashed - """ - self.data += data - - def digest(self): - """Get hash of the provided data - """ - _len = 0 - checksum = 0 - h = 32 * b"\x00" - m = self.data - for i in xrange(0, len(m), BLOCKSIZE): - part = m[i:i + BLOCKSIZE][::-1] - _len += len(part) * 8 - checksum = (checksum + int(hexenc(part), 16)) % (2 ** 256) - if len(part) < BLOCKSIZE: - part = b"\x00" * (BLOCKSIZE - len(part)) + part - h = _step(h, part, self.sbox) - h = _step(h, 24 * b"\x00" + pack(">Q", _len), self.sbox) - - checksum = hex(checksum)[2:].rstrip("L") - if len(checksum) % 2 != 0: - checksum = "0" + checksum - checksum = hexdec(checksum) - checksum = b"\x00" * (BLOCKSIZE - len(checksum)) + checksum - h = _step(h, checksum, self.sbox) - return h[::-1] - - -def new(data=b"", sbox=DEFAULT_SBOX): - return GOST341194(data, sbox) - - -PBKDF2_HASHER = partial(GOST341194, sbox="id-GostR3411-94-CryptoProParamSet") - - -def pbkdf2(password, salt, iterations, dklen): - return pbkdf2_base(PBKDF2_HASHER, password, salt, iterations, dklen) diff --git a/pygost-5.13/build/lib/pygost/gost3412.py b/pygost-5.13/build/lib/pygost/gost3412.py deleted file mode 100644 index b9472ee..0000000 --- a/pygost-5.13/build/lib/pygost/gost3412.py +++ /dev/null @@ -1,186 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST 34.12-2015 64 and 128 bit block ciphers (:rfc:`7801`) - -Several precalculations are performed during this module importing. -""" - -from pygost.gost28147 import block2ns as gost28147_block2ns -from pygost.gost28147 import decrypt as gost28147_decrypt -from pygost.gost28147 import encrypt as gost28147_encrypt -from pygost.gost28147 import ns2block as gost28147_ns2block -from pygost.utils import strxor -from pygost.utils import xrange - - -KEYSIZE = 32 - -LC = bytearray(( - 148, 32, 133, 16, 194, 192, 1, 251, 1, 192, 194, 16, 133, 32, 148, 1, -)) -PI = bytearray(( - 252, 238, 221, 17, 207, 110, 49, 22, 251, 196, 250, 218, 35, 197, 4, 77, - 233, 119, 240, 219, 147, 46, 153, 186, 23, 54, 241, 187, 20, 205, 95, 193, - 249, 24, 101, 90, 226, 92, 239, 33, 129, 28, 60, 66, 139, 1, 142, 79, 5, - 132, 2, 174, 227, 106, 143, 160, 6, 11, 237, 152, 127, 212, 211, 31, 235, - 52, 44, 81, 234, 200, 72, 171, 242, 42, 104, 162, 253, 58, 206, 204, 181, - 112, 14, 86, 8, 12, 118, 18, 191, 114, 19, 71, 156, 183, 93, 135, 21, 161, - 150, 41, 16, 123, 154, 199, 243, 145, 120, 111, 157, 158, 178, 177, 50, 117, - 25, 61, 255, 53, 138, 126, 109, 84, 198, 128, 195, 189, 13, 87, 223, 245, - 36, 169, 62, 168, 67, 201, 215, 121, 214, 246, 124, 34, 185, 3, 224, 15, - 236, 222, 122, 148, 176, 188, 220, 232, 40, 80, 78, 51, 10, 74, 167, 151, - 96, 115, 30, 0, 98, 68, 26, 184, 56, 130, 100, 159, 38, 65, 173, 69, 70, - 146, 39, 94, 85, 47, 140, 163, 165, 125, 105, 213, 149, 59, 7, 88, 179, 64, - 134, 172, 29, 247, 48, 55, 107, 228, 136, 217, 231, 137, 225, 27, 131, 73, - 76, 63, 248, 254, 141, 83, 170, 144, 202, 216, 133, 97, 32, 113, 103, 164, - 45, 43, 9, 91, 203, 155, 37, 208, 190, 229, 108, 82, 89, 166, 116, 210, 230, - 244, 180, 192, 209, 102, 175, 194, 57, 75, 99, 182, -)) - -######################################################################## -# Precalculate inverted PI value as a performance optimization. -# Actually it can be computed only once and saved on the disk. -######################################################################## -PIinv = bytearray(256) -for x in xrange(256): - PIinv[PI[x]] = x - - -def gf(a, b): - c = 0 - while b: - if b & 1: - c ^= a - if a & 0x80: - a = (a << 1) ^ 0x1C3 - else: - a <<= 1 - b >>= 1 - return c - -######################################################################## -# Precalculate all possible gf(byte, byte) values as a performance -# optimization. -# Actually it can be computed only once and saved on the disk. -######################################################################## - - -GF = [bytearray(256) for _ in xrange(256)] - -for x in xrange(256): - for y in xrange(256): - GF[x][y] = gf(x, y) - - -def L(blk, rounds=16): - for _ in range(rounds): - t = blk[15] - for i in range(14, -1, -1): - blk[i + 1] = blk[i] - t ^= GF[blk[i]][LC[i]] - blk[0] = t - return blk - - -def Linv(blk): - for _ in range(16): - t = blk[0] - for i in range(15): - blk[i] = blk[i + 1] - t ^= GF[blk[i]][LC[i]] - blk[15] = t - return blk - -######################################################################## -# Precalculate values of the C -- it does not depend on key. -# Actually it can be computed only once and saved on the disk. -######################################################################## - - -C = [] - -for x in range(1, 33): - y = bytearray(16) - y[15] = x - C.append(L(y)) - - -def lp(blk): - return L([PI[v] for v in blk]) - - -class GOST3412Kuznechik(object): - """GOST 34.12-2015 128-bit block cipher Кузнечик (Kuznechik) - """ - blocksize = 16 - - def __init__(self, key): - """ - :param key: encryption/decryption key - :type key: bytes, 32 bytes - - Key scheduling (roundkeys precomputation) is performed here. - """ - kr0 = bytearray(key[:16]) - kr1 = bytearray(key[16:]) - self.ks = [kr0, kr1] - for i in range(4): - for j in range(8): - k = lp(bytearray(strxor(C[8 * i + j], kr0))) - kr0, kr1 = [strxor(k, kr1), kr0] - self.ks.append(kr0) - self.ks.append(kr1) - - def encrypt(self, blk): - blk = bytearray(blk) - for i in range(9): - blk = lp(bytearray(strxor(self.ks[i], blk))) - return bytes(strxor(self.ks[9], blk)) - - def decrypt(self, blk): - blk = bytearray(blk) - for i in range(9, 0, -1): - blk = [PIinv[v] for v in Linv(bytearray(strxor(self.ks[i], blk)))] - return bytes(strxor(self.ks[0], blk)) - - -class GOST3412Magma(object): - """GOST 34.12-2015 64-bit block cipher Магма (Magma) - """ - blocksize = 8 - - def __init__(self, key): - """ - :param key: encryption/decryption key - :type key: bytes, 32 bytes - """ - # Backward compatibility key preparation for 28147-89 key schedule - self.key = b"".join(key[i * 4:i * 4 + 4][::-1] for i in range(8)) - self.sbox = "id-tc26-gost-28147-param-Z" - - def encrypt(self, blk): - return gost28147_ns2block(gost28147_encrypt( - self.sbox, - self.key, - gost28147_block2ns(blk[::-1]), - ))[::-1] - - def decrypt(self, blk): - return gost28147_ns2block(gost28147_decrypt( - self.sbox, - self.key, - gost28147_block2ns(blk[::-1]), - ))[::-1] diff --git a/pygost-5.13/build/lib/pygost/gost3413.py b/pygost-5.13/build/lib/pygost/gost3413.py deleted file mode 100644 index f3cb5da..0000000 --- a/pygost-5.13/build/lib/pygost/gost3413.py +++ /dev/null @@ -1,392 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST R 34.13-2015: Modes of operation for block ciphers - -This module currently includes only padding methods. -""" - -from os import urandom - -from pygost.utils import bytes2long -from pygost.utils import long2bytes -from pygost.utils import strxor -from pygost.utils import xrange - - -KEYSIZE = 32 - - -def pad_size(data_size, blocksize): - """Calculate required pad size to full up blocksize - """ - if data_size < blocksize: - return blocksize - data_size - if data_size % blocksize == 0: - return 0 - return blocksize - data_size % blocksize - - -def pad1(data, blocksize): - """Padding method 1 - - Just fill up with zeros if necessary. - """ - return data + b"\x00" * pad_size(len(data), blocksize) - - -def pad2(data, blocksize): - """Padding method 2 (also known as ISO/IEC 7816-4) - - Add one bit and then fill up with zeros. - """ - return data + b"\x80" + b"\x00" * pad_size(len(data) + 1, blocksize) - - -def unpad2(data, blocksize): - """Unpad method 2 - """ - last_block = bytearray(data[-blocksize:]) - pad_index = last_block.rfind(b"\x80") - if pad_index == -1: - raise ValueError("Invalid padding") - for c in last_block[pad_index + 1:]: - if c != 0: - raise ValueError("Invalid padding") - return data[:-(blocksize - pad_index)] - - -def pad3(data, blocksize): - """Padding method 3 - """ - if pad_size(len(data), blocksize) == 0: - return data - return pad2(data, blocksize) - - -def ecb_encrypt(encrypter, bs, pt): - """ECB encryption mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes pt: already padded plaintext - """ - if not pt or len(pt) % bs != 0: - raise ValueError("Plaintext is not blocksize aligned") - ct = [] - for i in xrange(0, len(pt), bs): - ct.append(encrypter(pt[i:i + bs])) - return b"".join(ct) - - -def ecb_decrypt(decrypter, bs, ct): - """ECB decryption mode of operation - - :param decrypter: Decrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes ct: ciphertext - """ - if not ct or len(ct) % bs != 0: - raise ValueError("Ciphertext is not blocksize aligned") - pt = [] - for i in xrange(0, len(ct), bs): - pt.append(decrypter(ct[i:i + bs])) - return b"".join(pt) - - -def acpkm(encrypter, bs): - """Perform ACPKM key derivation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - """ - return b"".join([ - encrypter(bytes(bytearray(range(d, d + bs)))) - for d in range(0x80, 0x80 + bs * (KEYSIZE // bs), bs) - ]) - - -def ctr(encrypter, bs, data, iv, _acpkm=None): - """Counter mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes data: plaintext/ciphertext - :param bytes iv: half blocksize-sized initialization vector - - For decryption you use the same function again. - """ - if len(iv) != bs // 2: - raise ValueError("Invalid IV size") - if len(data) > bs * (1 << (8 * (bs // 2 - 1))): - raise ValueError("Too big data") - stream = [] - ctr_value = 0 - ctr_max_value = 1 << (8 * (bs // 2)) - if _acpkm is not None: - acpkm_algo_class, acpkm_section_size_in_bs = _acpkm - acpkm_section_size_in_bs //= bs - for _ in xrange(0, len(data) + pad_size(len(data), bs), bs): - if ( - _acpkm is not None and - ctr_value != 0 and - ctr_value % acpkm_section_size_in_bs == 0 - ): - encrypter = acpkm_algo_class(acpkm(encrypter, bs)).encrypt - stream.append(encrypter(iv + long2bytes(ctr_value, bs // 2))) - ctr_value = (ctr_value + 1) % ctr_max_value - return strxor(b"".join(stream), data) - - -def ctr_acpkm(algo_class, encrypter, section_size, bs, data, iv): - """CTR-ACPKM mode of operation - - :param algo_class: pygost.gost3412's algorithm class - :param encrypter: encrypting function, that takes block as an input - :param int section_size: ACPKM'es section size (N), in bytes - :param int bs: cipher's blocksize, bytes - :param bytes data: plaintext/ciphertext - :param bytes iv: half blocksize-sized initialization vector - - For decryption you use the same function again. - """ - if section_size % bs != 0: - raise ValueError("section_size must be multiple of bs") - return ctr(encrypter, bs, data, iv, _acpkm=(algo_class, section_size)) - - -def ofb(encrypter, bs, data, iv): - """OFB mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes data: plaintext/ciphertext - :param bytes iv: blocksize-sized initialization vector - - For decryption you use the same function again. - """ - if len(iv) < bs or len(iv) % bs != 0: - raise ValueError("Invalid IV size") - r = [iv[i:i + bs] for i in range(0, len(iv), bs)] - result = [] - for i in xrange(0, len(data) + pad_size(len(data), bs), bs): - r = r[1:] + [encrypter(r[0])] - result.append(strxor(r[-1], data[i:i + bs])) - return b"".join(result) - - -def cbc_encrypt(encrypter, bs, pt, iv): - """CBC encryption mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes pt: already padded plaintext - :param bytes iv: blocksize-sized initialization vector - """ - if not pt or len(pt) % bs != 0: - raise ValueError("Plaintext is not blocksize aligned") - if len(iv) < bs or len(iv) % bs != 0: - raise ValueError("Invalid IV size") - r = [iv[i:i + bs] for i in range(0, len(iv), bs)] - ct = [] - for i in xrange(0, len(pt), bs): - ct.append(encrypter(strxor(r[0], pt[i:i + bs]))) - r = r[1:] + [ct[-1]] - return b"".join(ct) - - -def cbc_decrypt(decrypter, bs, ct, iv): - """CBC decryption mode of operation - - :param decrypter: Decrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes ct: ciphertext - :param bytes iv: blocksize-sized initialization vector - """ - if not ct or len(ct) % bs != 0: - raise ValueError("Ciphertext is not blocksize aligned") - if len(iv) < bs or len(iv) % bs != 0: - raise ValueError("Invalid IV size") - r = [iv[i:i + bs] for i in range(0, len(iv), bs)] - pt = [] - for i in xrange(0, len(ct), bs): - blk = ct[i:i + bs] - pt.append(strxor(r[0], decrypter(blk))) - r = r[1:] + [blk] - return b"".join(pt) - - -def cfb_encrypt(encrypter, bs, pt, iv): - """CFB encryption mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes pt: plaintext - :param bytes iv: blocksize-sized initialization vector - """ - if len(iv) < bs or len(iv) % bs != 0: - raise ValueError("Invalid IV size") - r = [iv[i:i + bs] for i in range(0, len(iv), bs)] - ct = [] - for i in xrange(0, len(pt) + pad_size(len(pt), bs), bs): - ct.append(strxor(encrypter(r[0]), pt[i:i + bs])) - r = r[1:] + [ct[-1]] - return b"".join(ct) - - -def cfb_decrypt(encrypter, bs, ct, iv): - """CFB decryption mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes ct: ciphertext - :param bytes iv: blocksize-sized initialization vector - """ - if len(iv) < bs or len(iv) % bs != 0: - raise ValueError("Invalid IV size") - r = [iv[i:i + bs] for i in range(0, len(iv), bs)] - pt = [] - for i in xrange(0, len(ct) + pad_size(len(ct), bs), bs): - blk = ct[i:i + bs] - pt.append(strxor(encrypter(r[0]), blk)) - r = r[1:] + [blk] - return b"".join(pt) - - -def _mac_shift(bs, data, xor_lsb=0): - num = (bytes2long(data) << 1) ^ xor_lsb - return long2bytes(num, bs)[-bs:] - - -Rb64 = 0b11011 -Rb128 = 0b10000111 - - -def _mac_ks(encrypter, bs): - Rb = Rb128 if bs == 16 else Rb64 - _l = encrypter(bs * b"\x00") - k1 = _mac_shift(bs, _l, Rb) if bytearray(_l)[0] & 0x80 > 0 else _mac_shift(bs, _l) - k2 = _mac_shift(bs, k1, Rb) if bytearray(k1)[0] & 0x80 > 0 else _mac_shift(bs, k1) - return k1, k2 - - -def mac(encrypter, bs, data): - """MAC (known here as CMAC, OMAC1) mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes data: data to authenticate - - Implementation is based on PyCrypto's CMAC one, that is in public domain. - """ - k1, k2 = _mac_ks(encrypter, bs) - if len(data) % bs == 0: - tail_offset = len(data) - bs - else: - tail_offset = len(data) - (len(data) % bs) - prev = bs * b"\x00" - for i in xrange(0, tail_offset, bs): - prev = encrypter(strxor(data[i:i + bs], prev)) - tail = data[tail_offset:] - return encrypter(strxor( - strxor(pad3(tail, bs), prev), - k1 if len(tail) == bs else k2, - )) - - -def acpkm_master(algo_class, encrypter, key_section_size, bs, keymat_len): - """ACPKM-Master key derivation - - :param algo_class: pygost.gost3412's algorithm class - :param encrypter: encrypting function, that takes block as an input - :param int key_section_size: ACPKM'es key section size (T*), in bytes - :param int bs: cipher's blocksize, bytes - :param int keymat_len: length of key material to produce - """ - return ctr_acpkm( - algo_class, - encrypter, - key_section_size, - bs, - data=b"\x00" * keymat_len, - iv=b"\xFF" * (bs // 2), - ) - - -def mac_acpkm_master(algo_class, encrypter, key_section_size, section_size, bs, data): - """OMAC-ACPKM-Master - - :param algo_class: pygost.gost3412's algorithm class - :param encrypter: encrypting function, that takes block as an input - :param int key_section_size: ACPKM'es key section size (T*), in bytes - :param int section_size: ACPKM'es section size (N), in bytes - :param int bs: cipher's blocksize, bytes - :param bytes data: data to authenticate - """ - if len(data) % bs == 0: - tail_offset = len(data) - bs - else: - tail_offset = len(data) - (len(data) % bs) - prev = bs * b"\x00" - sections = len(data) // section_size - if len(data) % section_size != 0: - sections += 1 - keymats = acpkm_master( - algo_class, - encrypter, - key_section_size, - bs, - (KEYSIZE + bs) * sections, - ) - for i in xrange(0, tail_offset, bs): - if i % section_size == 0: - keymat, keymats = keymats[:KEYSIZE + bs], keymats[KEYSIZE + bs:] - key, k1 = keymat[:KEYSIZE], keymat[KEYSIZE:] - encrypter = algo_class(key).encrypt - prev = encrypter(strxor(data[i:i + bs], prev)) - tail = data[tail_offset:] - if len(tail) == bs: - key, k1 = keymats[:KEYSIZE], keymats[KEYSIZE:] - encrypter = algo_class(key).encrypt - k2 = long2bytes(bytes2long(k1) << 1, size=bs) - if bytearray(k1)[0] & 0x80 != 0: - k2 = strxor(k2, long2bytes(Rb128 if bs == 16 else Rb64, size=bs)) - return encrypter(strxor( - strxor(pad3(tail, bs), prev), - k1 if len(tail) == bs else k2, - )) - - -def pad_iso10126(data, blocksize): - """ISO 10126 padding - - Does not exist in 34.13, but added for convenience. - It uses urandom call for getting the randomness. - """ - pad_len = blocksize - len(data) % blocksize - if pad_len == 0: - pad_len = blocksize - return b"".join((data, urandom(pad_len - 1), bytes((pad_len,)))) - - -def unpad_iso10126(data, blocksize): - """Unpad :py:func:`pygost.gost3413.pad_iso10126` - """ - if len(data) % blocksize != 0: - raise ValueError("Data length is not multiple of blocksize") - pad_len = bytearray(data)[-1] - if pad_len > blocksize: - raise ValueError("Padding length is bigger than blocksize") - return data[:-pad_len] diff --git a/pygost-5.13/build/lib/pygost/iface.py b/pygost-5.13/build/lib/pygost/iface.py deleted file mode 100644 index e2d6a4c..0000000 --- a/pygost-5.13/build/lib/pygost/iface.py +++ /dev/null @@ -1,50 +0,0 @@ -from abc import ABCMeta -from abc import abstractmethod - -from pygost.utils import hexenc - - -# This function is taken from six package as is -def add_metaclass(metaclass): - """Class decorator for creating a class with a metaclass.""" - def wrapper(cls): - orig_vars = cls.__dict__.copy() - slots = orig_vars.get("__slots__") - if slots is not None: - if isinstance(slots, str): - slots = [slots] - for slots_var in slots: - orig_vars.pop(slots_var) - orig_vars.pop("__dict__", None) - orig_vars.pop("__weakref__", None) - return metaclass(cls.__name__, cls.__bases__, orig_vars) - return wrapper - - -@add_metaclass(ABCMeta) -class PEP247(object): - @property - @abstractmethod - def digest_size(self): - """The size of the digest produced by the hashing objects. - """ - - @abstractmethod - def copy(self): - """Return a separate copy of this hashing object. - """ - - @abstractmethod - def update(self, data): - """Hash data into the current state of the hashing object. - """ - - @abstractmethod - def digest(self): - """Return the hash value as a string containing 8-bit data. - """ - - def hexdigest(self): - """Return the hash value as a string containing hexadecimal digits. - """ - return hexenc(self.digest()) diff --git a/pygost-5.13/build/lib/pygost/kdf.py b/pygost-5.13/build/lib/pygost/kdf.py deleted file mode 100644 index 4e404c6..0000000 --- a/pygost-5.13/build/lib/pygost/kdf.py +++ /dev/null @@ -1,81 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""Key derivation functions, Р 50.1.113-2016, Р 1323565.1.020-2018 -""" - -import hmac - -from pygost.gost3410_vko import kek_34102012256 -from pygost.gost3410_vko import kek_34102012512 -from pygost.gost34112012256 import GOST34112012256 -from pygost.utils import bytes2long -from pygost.utils import long2bytes - - -def kdf_gostr3411_2012_256(key, label, seed): - """KDF_GOSTR3411_2012_256 - - :param bytes key: initial key - :param bytes label: label - :param bytes seed: seed - :returns: 32 bytes - """ - return hmac.new( - key=key, - msg=b"".join((b"\x01", label, b"\x00", seed, b"\x01\x00")), - digestmod=GOST34112012256, - ).digest() - - -def kdf_tree_gostr3411_2012_256(key, label, seed, keys, i_len=1): - """KDF_TREE_GOSTR3411_2012_256 - - :param bytes key: initial key - :param bytes label: label - :param bytes seed: seed - :param int keys: number of generated keys - :param int i_len: length of iterations value (called "R") - :returns: list of 256-bit keys - """ - keymat = [] - _len = long2bytes(keys * 32 * 8, size=1) - for i in range(keys): - keymat.append(hmac.new( - key=key, - msg=b"".join((long2bytes(i + 1, size=i_len), label, b"\x00", seed, _len)), - digestmod=GOST34112012256, - ).digest()) - return keymat - - -def keg(curve, prv, pub, h): - """Export key generation (Р 1323565.1.020-2018) - - :param GOST3410Curve curve: curve to use - :param long prv: private key - :param pub: public key - :type pub: (long, long) - :param bytes h: "h"-value, 32 bytes - """ - if len(h) != 32: - raise ValueError("h must be 32 bytes long") - ukm = bytes2long(h[:16]) - if ukm == 0: - ukm = 1 - if curve.point_size == 64: - return kek_34102012512(curve, prv, pub, ukm) - k_exp = kek_34102012256(curve, prv, pub, ukm) - return b"".join(kdf_tree_gostr3411_2012_256(k_exp, b"kdf tree", h[16:24], 2)) diff --git a/pygost-5.13/build/lib/pygost/mgm.py b/pygost-5.13/build/lib/pygost/mgm.py deleted file mode 100644 index fb51343..0000000 --- a/pygost-5.13/build/lib/pygost/mgm.py +++ /dev/null @@ -1,168 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""Multilinear Galois Mode (MGM) block cipher mode. -""" - -from hmac import compare_digest - -from pygost.gost3413 import pad1 -from pygost.utils import bytes2long -from pygost.utils import long2bytes -from pygost.utils import strxor - - -def _incr(data, bs): - return long2bytes(bytes2long(data) + 1, size=bs // 2) - - -def incr_r(data, bs): - return data[:bs // 2] + _incr(data[bs // 2:], bs) - - -def incr_l(data, bs): - return _incr(data[:bs // 2], bs) + data[bs // 2:] - - -def nonce_prepare(nonce): - """Prepare nonce for MGM usage - - It just clears MSB. - """ - n = bytearray(nonce) - n[0] &= 0x7F - return bytes(n) - - -class MGM(object): - # Implementation is fully based on go.cypherpunks.ru/gogost/mgm - def __init__(self, encrypter, bs, tag_size=None): - """Multilinear Galois Mode (MGM) block cipher mode - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize - :param int tag_size: authentication tag size - (defaults to blocksize if not specified) - """ - if bs not in (8, 16): - raise ValueError("Only 64/128-bit blocksizes allowed") - self.tag_size = bs if tag_size is None else tag_size - if self.tag_size < 4 or self.tag_size > bs: - raise ValueError("Invalid tag_size") - self.encrypter = encrypter - self.bs = bs - self.max_size = (1 << (bs * 8 // 2)) - 1 - self.r = 0x1B if bs == 8 else 0x87 - - def _validate_nonce(self, nonce): - if len(nonce) != self.bs: - raise ValueError("nonce length must be equal to cipher's blocksize") - if bytearray(nonce)[0] & 0x80 > 0: - raise ValueError("nonce must not have higher bit set") - - def _validate_sizes(self, plaintext, additional_data): - if len(plaintext) == 0 and len(additional_data) == 0: - raise ValueError("At least one of plaintext or additional_data required") - if len(plaintext) + len(additional_data) > self.max_size: - raise ValueError("plaintext+additional_data are too big") - - def _mul(self, x, y): - x = bytes2long(x) - y = bytes2long(y) - z = 0 - max_bit = 1 << (self.bs * 8 - 1) - while y > 0: - if y & 1 == 1: - z ^= x - if x & max_bit > 0: - x = ((x ^ max_bit) << 1) ^ self.r - else: - x <<= 1 - y >>= 1 - return long2bytes(z, size=self.bs) - - def _crypt(self, icn, data): - icn[0] &= 0x7F - enc = self.encrypter(bytes(icn)) - res = [] - while len(data) > 0: - res.append(strxor(self.encrypter(enc), data)) - enc = incr_r(enc, self.bs) - data = data[self.bs:] - return b"".join(res) - - def _auth(self, icn, text, ad): - icn[0] |= 0x80 - enc = self.encrypter(bytes(icn)) - _sum = self.bs * b"\x00" - ad_len = len(ad) - text_len = len(text) - while len(ad) > 0: - _sum = strxor(_sum, self._mul( - self.encrypter(enc), - pad1(ad[:self.bs], self.bs), - )) - enc = incr_l(enc, self.bs) - ad = ad[self.bs:] - while len(text) > 0: - _sum = strxor(_sum, self._mul( - self.encrypter(enc), - pad1(text[:self.bs], self.bs), - )) - enc = incr_l(enc, self.bs) - text = text[self.bs:] - _sum = strxor(_sum, self._mul(self.encrypter(enc), ( - long2bytes(ad_len * 8, size=self.bs // 2) + - long2bytes(text_len * 8, size=self.bs // 2) - ))) - return self.encrypter(_sum)[:self.tag_size] - - def seal(self, nonce, plaintext, additional_data): - """Seal plaintext - - :param bytes nonce: blocksize-sized nonce. - Assure that it does not have MSB bit set - (:py:func:`pygost.mgm.nonce_prepare` helps) - :param bytes plaintext: plaintext to be encrypted and authenticated - :param bytes additional_data: additional data to be authenticated - """ - self._validate_nonce(nonce) - self._validate_sizes(plaintext, additional_data) - icn = bytearray(nonce) - ciphertext = self._crypt(icn, plaintext) - tag = self._auth(icn, ciphertext, additional_data) - return ciphertext + tag - - def open(self, nonce, ciphertext, additional_data): - """Open ciphertext - - :param bytes nonce: blocksize-sized nonce. - Assure that it does not have MSB bit set - (:py:func:`pygost.mgm.nonce_prepare` helps) - :param bytes ciphertext: ciphertext to be decrypted and authenticated - :param bytes additional_data: additional data to be authenticated - :raises ValueError: if ciphertext authentication fails - """ - self._validate_nonce(nonce) - self._validate_sizes(ciphertext, additional_data) - icn = bytearray(nonce) - ciphertext, tag_expected = ( - ciphertext[:-self.tag_size], - ciphertext[-self.tag_size:], - ) - tag = self._auth(icn, ciphertext, additional_data) - if not compare_digest(tag_expected, tag): - raise ValueError("Invalid authentication tag") - return self._crypt(icn, ciphertext) diff --git a/pygost-5.13/build/lib/pygost/pbkdf2.py b/pygost-5.13/build/lib/pygost/pbkdf2.py deleted file mode 100644 index 0fd6ddc..0000000 --- a/pygost-5.13/build/lib/pygost/pbkdf2.py +++ /dev/null @@ -1,41 +0,0 @@ -# coding: utf-8 -"""PBKDF2 implementation suitable for GOST R 34.11-94/34.11-2012. - -This implementation is based on Python 3.5.2 source code's one. -PyGOST does not register itself in hashlib anyway, so use it instead. -""" - - -from pygost.utils import bytes2long -from pygost.utils import long2bytes -from pygost.utils import strxor -from pygost.utils import xrange - - -def pbkdf2(hasher, password, salt, iterations, dklen): - """PBKDF2 implementation suitable for GOST R 34.11-94/34.11-2012 - """ - inner = hasher() - outer = hasher() - password = password + b"\x00" * (inner.block_size - len(password)) - inner.update(strxor(password, len(password) * b"\x36")) - outer.update(strxor(password, len(password) * b"\x5C")) - - def prf(msg): - icpy = inner.copy() - ocpy = outer.copy() - icpy.update(msg) - ocpy.update(icpy.digest()) - return ocpy.digest() - - dkey = b"" - loop = 1 - while len(dkey) < dklen: - prev = prf(salt + long2bytes(loop, 4)) - rkey = bytes2long(prev) - for _ in xrange(iterations - 1): - prev = prf(prev) - rkey ^= bytes2long(prev) - loop += 1 - dkey += long2bytes(rkey, inner.digest_size) - return dkey[:dklen] diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/__init__.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/__init__.pyi deleted file mode 100644 index e69de29..0000000 diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/gost28147.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/gost28147.pyi deleted file mode 100644 index be31261..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/gost28147.pyi +++ /dev/null @@ -1,101 +0,0 @@ -from typing import Callable -from typing import Dict -from typing import Sequence -from typing import Tuple - - -SBOXES = ... # type: Dict[str, Tuple[Tuple[int, ...], ...]] -BLOCKSIZE = ... # type: int - -Words = Tuple[int, int] - - -def block2ns(data: bytes) -> Words: ... - - -def ns2block(ns: Words) -> bytes: ... - - -def validate_key(key: bytes) -> None: ... - - -def validate_iv(iv: bytes) -> None: ... - - -def validate_sbox(sbox: str) -> None: ... - - -def xcrypt(seq: Sequence[int], sbox: str, key: bytes, ns: Words) -> Words: ... - - -def encrypt(sbox: str, key: bytes, ns: Words) -> Words: ... - - -def decrypt(sbox: str, key: bytes, ns: Words) -> Words: ... - - -def ecb( - key: bytes, - data: bytes, - action: Callable[[str, bytes, Words], Words], - sbox: str = ..., -) -> bytes: ... - - -def ecb_encrypt( - key: bytes, - data: bytes, - sbox: str = ..., -) -> bytes: ... - - -def ecb_decrypt( - key: bytes, - data: bytes, - sbox: str = ..., -) -> bytes: ... - - -def cbc_encrypt( - key: bytes, - data: bytes, - iv: bytes = ..., - pad: bool = ..., - sbox: str = ..., - mesh: bool = ..., -) -> bytes: ... - - -def cbc_decrypt( - key: bytes, - data: bytes, - pad: bool = ..., - sbox: str = ..., - mesh: bool = ..., -) -> bytes: ... - - -def cnt( - key: bytes, - data: bytes, - iv: bytes = ..., - sbox: str = ..., -) -> bytes: ... - - -def cfb_encrypt( - key: bytes, - data: bytes, - iv: bytes = ..., - sbox: str = ..., - mesh: bool = ..., -) -> bytes: ... - - -def cfb_decrypt( - key: bytes, - data: bytes, - iv: bytes = ..., - sbox: str = ..., - mesh: bool = ..., -) -> bytes: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/gost28147_mac.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/gost28147_mac.pyi deleted file mode 100644 index 70d90d6..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/gost28147_mac.pyi +++ /dev/null @@ -1,25 +0,0 @@ -from pygost.iface import PEP247 - - -class MAC(PEP247): - def __init__( - self, - key: bytes, - data: bytes = ..., - iv: bytes = ..., - sbox: str = ..., - ) -> None: ... - - @property - def digest_size(self) -> int: ... - - def copy(self) -> "MAC": ... - - def update(self, data: bytes) -> None: ... - - def digest(self) -> bytes: ... - - def hexdigest(self) -> str: ... - - -def new(key: bytes, data: bytes = ..., iv: bytes = ..., sbox: str = ...) -> MAC: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/gost3410.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/gost3410.pyi deleted file mode 100644 index 8f0dcb8..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/gost3410.pyi +++ /dev/null @@ -1,72 +0,0 @@ -from typing import Dict -from typing import Tuple - - -DEFAULT_CURVE = ... # type: GOST3410Curve -CURVES = ... # type: Dict[str, GOST3410Curve] -PublicKey = Tuple[int, int] - - -class GOST3410Curve(object): - p = ... # type: int - q = ... # type: int - a = ... # type: int - b = ... # type: int - x = ... # type: int - y = ... # type: int - cofactor = ... # type: int - e = ... # type: int - d = ... # type: int - name = ... # type: str - - def __init__( - self, - p: int, - q: int, - a: int, - b: int, - x: int, - y: int, - cofactor: int = 1, - e: int = None, - d: int = None, - name: str = None, - ) -> None: ... - - def pos(self, v: int) -> int: ... - - def exp(self, degree: int, x: int = ..., y: int = ...) -> int: ... - - def st(self) -> Tuple[int, int]: ... - - @property - def point_size(self) -> int: ... - - def contains(self, point: Tuple[int, int]) -> bool: ... - - -def public_key(curve: GOST3410Curve, prv: int) -> PublicKey: ... - - -def sign(curve: GOST3410Curve, prv: int, digest: bytes, rand: bytes = None) -> bytes: ... - - -def verify(curve: GOST3410Curve, pub: PublicKey, digest: bytes, signature: bytes) -> bool: ... - - -def prv_unmarshal(prv: bytes) -> int: ... - - -def prv_marshal(curve: GOST3410Curve, prv: int) -> bytes: ... - - -def pub_marshal(pub: PublicKey) -> bytes: ... - - -def pub_unmarshal(pub: bytes) -> PublicKey: ... - - -def uv2xy(curve: GOST3410Curve, u: int, v: int) -> Tuple[int, int]: ... - - -def xy2uv(curve: GOST3410Curve, x: int, y: int) -> Tuple[int, int]: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/gost3410_vko.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/gost3410_vko.pyi deleted file mode 100644 index 6ea9b82..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/gost3410_vko.pyi +++ /dev/null @@ -1,17 +0,0 @@ -from pygost.gost3410 import GOST3410Curve -from pygost.gost3410 import PublicKey - - -def ukm_unmarshal(ukm: bytes) -> int: ... - - -def kek(curve: GOST3410Curve, prv: int, pub: PublicKey, ukm: int) -> bytes: ... - - -def kek_34102001(curve: GOST3410Curve, prv: int, pub: PublicKey, ukm: int) -> bytes: ... - - -def kek_34102012256(curve: GOST3410Curve, prv: int, pub: PublicKey, ukm: int = ...) -> bytes: ... - - -def kek_34102012512(curve: GOST3410Curve, prv: int, pub: PublicKey, ukm: int = ...) -> bytes: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/gost34112012.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/gost34112012.pyi deleted file mode 100644 index 3d5cc41..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/gost34112012.pyi +++ /dev/null @@ -1,18 +0,0 @@ -from pygost.iface import PEP247 - - -class GOST34112012(PEP247): - block_size = ... # type: int - - def __init__(self, data: bytes = ..., digest_size: int = ...) -> None: ... - - @property - def digest_size(self) -> int: ... - - def copy(self) -> "GOST34112012": ... - - def update(self, data: bytes) -> None: ... - - def digest(self) -> bytes: ... - - def hexdigest(self) -> str: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/gost34112012256.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/gost34112012256.pyi deleted file mode 100644 index a1d2a01..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/gost34112012256.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from pygost.iface import PEP247 - - -class GOST34112012256(PEP247): - block_size = ... # type: int - - def __init__(self, data: bytes = ...) -> None: ... - - @property - def digest_size(self) -> int: ... - - def copy(self) -> "GOST34112012256": ... - - def update(self, data: bytes) -> None: ... - - def digest(self) -> bytes: ... - - def hexdigest(self) -> str: ... - - -def new(data: bytes = ...) -> GOST34112012256: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/gost34112012512.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/gost34112012512.pyi deleted file mode 100644 index 349bddd..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/gost34112012512.pyi +++ /dev/null @@ -1,24 +0,0 @@ -from pygost.iface import PEP247 - - -class GOST34112012512(PEP247): - block_size = ... # type: int - - def __init__(self, data: bytes = ...) -> None: ... - - @property - def digest_size(self) -> int: ... - - def copy(self) -> "GOST34112012512": ... - - def update(self, data: bytes) -> None: ... - - def digest(self) -> bytes: ... - - def hexdigest(self) -> str: ... - - -def new(data: bytes = ...) -> GOST34112012512: ... - - -def pbkdf2(password: bytes, salt: bytes, iterations: int, dklen: int) -> bytes: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/gost341194.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/gost341194.pyi deleted file mode 100644 index 24de2e4..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/gost341194.pyi +++ /dev/null @@ -1,25 +0,0 @@ -from pygost.iface import PEP247 - - -class GOST341194(PEP247): - sbox = ... # type: str - block_size = ... # type: int - - def __init__(self, data: bytes = ..., sbox: str = ...) -> None: ... - - @property - def digest_size(self) -> int: ... - - def copy(self) -> "GOST341194": ... - - def update(self, data: bytes) -> None: ... - - def digest(self) -> bytes: ... - - def hexdigest(self) -> str: ... - - -def new(data: bytes = ..., sbox: str = ...) -> GOST341194: ... - - -def pbkdf2(password: bytes, salt: bytes, iterations: int, dklen: int) -> bytes: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/gost3412.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/gost3412.pyi deleted file mode 100644 index ef278b7..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/gost3412.pyi +++ /dev/null @@ -1,18 +0,0 @@ -class GOST3412Kuznechik(object): - blocksize = ... # type: int - - def __init__(self, key: bytes) -> None: ... - - def encrypt(self, blk: bytes) -> bytes: ... - - def decrypt(self, blk: bytes) -> bytes: ... - - -class GOST3412Magma(object): - blocksize = ... # type: int - - def __init__(self, key: bytes) -> None: ... - - def encrypt(self, blk: bytes) -> bytes: ... - - def decrypt(self, blk: bytes) -> bytes: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/gost3413.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/gost3413.pyi deleted file mode 100644 index 4cfd694..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/gost3413.pyi +++ /dev/null @@ -1,81 +0,0 @@ -from typing import Callable - - -def pad_size(data_size: int, blocksize: int) -> int: ... - - -def pad1(data: bytes, blocksize: int) -> bytes: ... - - -def pad2(data: bytes, blocksize: int) -> bytes: ... - - -def unpad2(data: bytes, blocksize: int) -> bytes: ... - - -def pad3(data: bytes, blocksize: int) -> bytes: ... - - -def ecb_encrypt(encrypter: Callable[[bytes], bytes], bs: int, pt: bytes) -> bytes: ... - - -def ecb_decrypt(decrypter: Callable[[bytes], bytes], bs: int, ct: bytes) -> bytes: ... - - -def acpkm(encrypter: Callable[[bytes], bytes], bs: int) -> bytes: ... - - -def ctr(encrypter: Callable[[bytes], bytes], bs: int, data: bytes, iv: bytes) -> bytes: ... - - -def ctr_acpkm( - algo_class: object, - encrypter: Callable[[bytes], bytes], - section_size: int, - bs: int, - data: bytes, - iv: bytes, -) -> bytes: ... - - -def ofb(encrypter: Callable[[bytes], bytes], bs: int, data: bytes, iv: bytes) -> bytes: ... - - -def cbc_encrypt(encrypter: Callable[[bytes], bytes], bs: int, pt: bytes, iv: bytes) -> bytes: ... - - -def cbc_decrypt(decrypter: Callable[[bytes], bytes], bs: int, ct: bytes, iv: bytes) -> bytes: ... - - -def cfb_encrypt(encrypter: Callable[[bytes], bytes], bs: int, pt: bytes, iv: bytes) -> bytes: ... - - -def cfb_decrypt(encrypter: Callable[[bytes], bytes], bs: int, ct: bytes, iv: bytes) -> bytes: ... - - -def mac(encrypter: Callable[[bytes], bytes], bs: int, data: bytes) -> bytes: ... - - -def acpkm_master( - algo_class: object, - encrypter: Callable[[bytes], bytes], - key_section_size: int, - bs: int, - keymat_len: int, -) -> bytes: ... - - -def mac_acpkm_master( - algo_class: object, - encrypter: Callable[[bytes], bytes], - key_section_size: int, - section_size: int, - bs: int, - data: bytes, -) -> bytes: ... - - -def pad_iso10126(data: bytes, blocksize: int) -> bytes: ... - - -def unpad_iso10126(data: bytes, blocksize: int) -> bytes: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/iface.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/iface.pyi deleted file mode 100644 index a5c2a85..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/iface.pyi +++ /dev/null @@ -1,19 +0,0 @@ -from abc import ABCMeta -from abc import abstractmethod - - -class PEP247(metaclass=ABCMeta): - @abstractmethod - @property - def digest_size(self) -> int: ... - - @abstractmethod - def copy(self) -> "PEP247": ... - - @abstractmethod - def update(self, data: bytes) -> None: ... - - @abstractmethod - def digest(self) -> bytes: ... - - def hexdigest(self) -> str: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/kdf.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/kdf.pyi deleted file mode 100644 index ccab8af..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/kdf.pyi +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Sequence -from typing import Tuple - -from pygost.gost3410 import GOST3410Curve - - -PublicKey = Tuple[int, int] - - -def kdf_gostr3411_2012_256(key: bytes, label: bytes, seed: bytes) -> bytes: ... - - -def kdf_tree_gostr3411_2012_256( - key: bytes, - label: bytes, - seed: bytes, - keys: int, - i_len: int = 1, -) -> Sequence[bytes]: ... - - -def keg(curve: GOST3410Curve, prv: int, pub: PublicKey, h: bytes) -> bytes: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/mgm.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/mgm.pyi deleted file mode 100644 index 81906b7..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/mgm.pyi +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Callable - - -def nonce_prepare(nonce: bytes) -> bytes: ... - - -class MGM(object): - def __init__( - self, - encrypter: Callable[[bytes], bytes], - bs: int, - tag_size: int = None, - ) -> None: ... - - def seal(self, nonce: bytes, plaintext: bytes, additional_data: bytes) -> bytes: ... - - def open(self, nonce: bytes, ciphertext: bytes, additional_data: bytes) -> bytes: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/utils.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/utils.pyi deleted file mode 100644 index 76460e5..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/utils.pyi +++ /dev/null @@ -1,19 +0,0 @@ -from typing import AnyStr - - -def strxor(a: bytes, b: bytes) -> bytes: ... - - -def hexdec(data: AnyStr) -> bytes: ... - - -def hexenc(data: bytes) -> str: ... - - -def bytes2long(raw: bytes) -> int: ... - - -def long2bytes(n: int, size: int = ...) -> bytes: ... - - -def modinvert(a: int, n: int) -> int: ... diff --git a/pygost-5.13/build/lib/pygost/stubs/pygost/wrap.pyi b/pygost-5.13/build/lib/pygost/stubs/pygost/wrap.pyi deleted file mode 100644 index 776a6e7..0000000 --- a/pygost-5.13/build/lib/pygost/stubs/pygost/wrap.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Callable - - -def wrap_gost(ukm: bytes, kek: bytes, cek: bytes, sbox: str = ...) -> bytes: ... - - -def unwrap_gost(kek: bytes, data: bytes, sbox: str = ...) -> bytes: ... - - -def wrap_cryptopro(ukm: bytes, kek: bytes, cek: bytes, sbox: str = ...) -> bytes: ... - - -def unwrap_cryptopro(kek: bytes, data: bytes, sbox: str = ...) -> bytes: ... - - -def kexp15( - encrypter_key: Callable[[bytes], bytes], - encrypter_mac: Callable[[bytes], bytes], - bs: int, - key: bytes, - iv: bytes, -) -> bytes: ... - - -def kimp15( - encrypter_key: Callable[[bytes], bytes], - encrypter_mac: Callable[[bytes], bytes], - bs: int, - kexp: bytes, - iv: bytes, -) -> bytes: ... diff --git a/pygost-5.13/build/lib/pygost/test_cms.py b/pygost-5.13/build/lib/pygost/test_cms.py deleted file mode 100644 index 7e5781a..0000000 --- a/pygost-5.13/build/lib/pygost/test_cms.py +++ /dev/null @@ -1,1078 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 six import text_type - -from pygost.gost28147 import cfb_decrypt -from pygost.gost3410 import CURVES -from pygost.gost3410 import prv_unmarshal -from pygost.gost3410 import pub_marshal -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.gost3412 import GOST3412Kuznechik -from pygost.gost3412 import GOST3412Magma -from pygost.gost3413 import ctr_acpkm -from pygost.gost3413 import KEYSIZE -from pygost.gost3413 import mac as omac -from pygost.kdf import kdf_tree_gostr3411_2012_256 -from pygost.kdf import keg -from pygost.utils import hexdec -from pygost.wrap import kimp15 -from pygost.wrap import unwrap_cryptopro -from pygost.wrap import unwrap_gost - -try: - from pyderasn import DecodePathDefBy - from pyderasn import OctetString - - from pygost.asn1schemas.cms import ContentInfo - from pygost.asn1schemas.cms import SignedAttributes - from pygost.asn1schemas.oids import id_cms_mac_attr - from pygost.asn1schemas.oids import id_envelopedData - from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm - from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm_omac - from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_wrap_kexp15 - from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm - from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm_omac - from pygost.asn1schemas.oids import id_gostr3412_2015_magma_wrap_kexp15 - from pygost.asn1schemas.oids import id_messageDigest - from pygost.asn1schemas.oids import id_tc26_agreement_gost3410_2012_256 - from pygost.asn1schemas.oids import id_tc26_agreement_gost3410_2012_512 - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256 - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512 - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetA - from pygost.asn1schemas.oids import id_tc26_gost3411_2012_256 - from pygost.asn1schemas.oids import id_tc26_gost3411_2012_512 - from pygost.asn1schemas.x509 import Certificate - from pygost.asn1schemas.x509 import GostR34102012PublicKeyParameters -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, - ): - content_info, tail = ContentInfo().decode(content_info_raw) - self.assertSequenceEqual(tail, b"") - self.assertIsNotNone(content_info["content"].defined) - _, signed_data = content_info["content"].defined - self.assertEqual(len(signed_data["signerInfos"]), 1) - curve = CURVES[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"]), - )) - - 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, - "id-GostR3410-2001-CryptoPro-XchA-ParamSet", - GOST34112012256, - ) - - 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, - "id-tc26-gost-3410-12-512-paramSetB", - GOST34112012512, - ) - - -@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"") - self.assertIsNotNone(content_info["content"].defined) - _, digested_data = content_info["content"].defined - 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 = "id-tc26-gost-28147-param-Z" - content_info, tail = ContentInfo().decode(content_info_raw, ctx={ - "defines_by_path": [ - ( - ( - "content", - DecodePathDefBy(id_envelopedData), - "recipientInfos", - any, - "ktri", - "encryptedKey", - DecodePathDefBy(spki_algorithm), - "transportParameters", - "ephemeralPublicKey", - "algorithm", - "algorithm", - ), - ( - ( - ("..", "subjectPublicKey"), - { - id_tc26_gost3410_2012_256: OctetString(), - id_tc26_gost3410_2012_512: OctetString(), - }, - ), - ), - ) for spki_algorithm in ( - id_tc26_gost3410_2012_256, - id_tc26_gost3410_2012_512, - ) - ], - }) - self.assertSequenceEqual(tail, b"") - self.assertIsNotNone(content_info["content"].defined) - _, enveloped_data = content_info["content"].defined - eci = enveloped_data["encryptedContentInfo"] - ri = enveloped_data["recipientInfos"][0] - self.assertIsNotNone(ri["ktri"]["encryptedKey"].defined) - _, encrypted_key = ri["ktri"]["encryptedKey"].defined - ukm = bytes(encrypted_key["transportParameters"]["ukm"]) - spk = encrypted_key["transportParameters"]["ephemeralPublicKey"]["subjectPublicKey"] - self.assertIsNotNone(spk.defined) - _, pub_key_their = spk.defined - curve = CURVES[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"]) - self.assertIsNotNone(eci["contentEncryptionAlgorithm"]["parameters"].defined) - _, encryption_params = eci["contentEncryptionAlgorithm"]["parameters"].defined - 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), - ) - - self.process_cms( - content_info_raw, - prv_key_our, - "id-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), - ukm_unmarshal(ukm), - ) - - self.process_cms( - content_info_raw, - prv_key_our, - "id-tc26-gost-3410-12-512-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 = "id-tc26-gost-28147-param-Z" - content_info, tail = ContentInfo().decode(content_info_raw, ctx={ - "defines_by_path": [ - ( - ( - "content", - DecodePathDefBy(id_envelopedData), - "recipientInfos", - any, - "kari", - "originator", - "originatorKey", - "algorithm", - "algorithm", - ), - ( - ( - ("..", "publicKey"), - { - id_tc26_gost3410_2012_256: OctetString(), - id_tc26_gost3410_2012_512: OctetString(), - }, - ), - ), - ) for _ in ( - id_tc26_gost3410_2012_256, - id_tc26_gost3410_2012_512, - ) - ], - }) - self.assertSequenceEqual(tail, b"") - self.assertIsNotNone(content_info["content"].defined) - _, enveloped_data = content_info["content"].defined - eci = enveloped_data["encryptedContentInfo"] - kari = enveloped_data["recipientInfos"][0]["kari"] - self.assertIsNotNone(kari["originator"]["originatorKey"]["publicKey"].defined) - _, pub_key_their = kari["originator"]["originatorKey"]["publicKey"].defined - ukm = bytes(kari["ukm"]) - rek = kari["recipientEncryptedKeys"][0] - curve = CURVES[curve_name] - kek = keker(curve, prv_key_our, bytes(pub_key_their), ukm) - self.assertIsNotNone(rek["encryptedKey"].defined) - _, encrypted_key = rek["encryptedKey"].defined - 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"]) - self.assertIsNotNone(eci["contentEncryptionAlgorithm"]["parameters"].defined) - _, encryption_params = eci["contentEncryptionAlgorithm"]["parameters"].defined - 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), - ) - - self.process_cms( - content_info_raw, - prv_key_our, - "id-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), - ukm_unmarshal(ukm), - ) - - self.process_cms( - content_info_raw, - prv_key_our, - "id-tc26-gost-3410-12-512-paramSetB", - keker, - b"Test message", - ) - - -@skipIf(not pyderasn_exists, "PyDERASN dependency is required") -class TestR132356510252019(TestCase): - """Test vectors from Р 1323565.1.025-2019 - """ - def setUp(self): - self.curve256 = CURVES["id-tc26-gost-3410-2012-256-paramSetA"] - self.curve512 = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - self.psk = hexdec("8F5EEF8814D228FB2BBC5612323730CFA33DB7263CC2C0A01A6C6953F33D61D5")[::-1] - - self.ca_prv = prv_unmarshal(hexdec("092F8D059E97E22B90B1AE99F0087FC4D26620B91550CBB437C191005A290810")[::-1]) - self.ca_pub = public_key(self.curve256, self.ca_prv) - self.ca_cert = Certificate().decod(b64decode(""" -MIIB8DCCAZ2gAwIBAgIEAYy6gTAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 -MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw -MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA4MQ0wCwYDVQQKEwRUSzI2MScwJQYD -VQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwaDAhBggqhQMHAQEB -ATAVBgkqhQMHAQIBAQEGCCqFAwcBAQICA0MABEAaSoKcjw54UACci6svELNF0IYM -RIW8urUsqamIpoG46XCqrVOuI6Q13N4dwcRsbZdqByf+GC2f5ZfO3baN5bTKo4GF -MIGCMGEGA1UdAQRaMFiAFIDZDPeZ+GZNk1OJjsCecS2npzESoTowODENMAsGA1UE -ChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYtYml0 -ggQBjLqBMB0GA1UdDgQWBBSA2Qz3mfhmTZNTiY7AnnEtp6cxEjAKBggqhQMHAQED -AgNBAAgv248F4OeNCkhlzJWec0evHYnMBlSzk1lDm0F875B7CqMrKh2MtJHXenbj -Gc2uRn2IwgmSf/LZDrYsKKqZSxk= -""")) - - self.sender256_prv = prv_unmarshal(hexdec("0B20810E449978C7C3B76C6FF77A16C532421139344A058EF56310B6B6F377E8")[::-1]) - self.sender256_pub = public_key(self.curve256, self.sender256_prv) - self.sender256_cert = Certificate().decod(b64decode(""" -MIIB8zCCAaCgAwIBAgIEAYy6gjAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 -MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw -MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRUSzI2MSowKAYD -VQQDEyFPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwaDAhBggqhQMH -AQEBATAVBgkqhQMHAQIBAQEGCCqFAwcBAQICA0MABECWKQ0TYllqg4GmY3tBJiyz -pXUN+aOV9WbmTUinqrmEHP7KCNzoAzFg+04SSQpNNSHpQnm+jLAZhuJaJfqZ6VbT -o4GFMIGCMGEGA1UdAQRaMFiAFIDZDPeZ+GZNk1OJjsCecS2npzESoTowODENMAsG -A1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYt -Yml0ggQBjLqBMB0GA1UdDgQWBBTRnChHSWbQYwnJC62n2zu5Njd03zAKBggqhQMH -AQEDAgNBAB41oijaXSEn58l78y2rhxY35/lKEq4XWZ70FtsNlVxWATyzgO5Wliwn -t1O4GoZsxx8r6T/i7VG65UNmQlwdOKQ= -""")) - - self.recipient256_prv = prv_unmarshal(hexdec("0DC8DC1FF2BC114BABC3F1CA8C51E4F58610427E197B1C2FBDBA4AE58CBFB7CE")[::-1]) - self.recipient256_pub = public_key(self.curve256, self.recipient256_prv) - self.recipient256_cert = Certificate().decod(b64decode(""" -MIIB8jCCAZ+gAwIBAgIEAYy6gzAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 -MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw -MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA6MQ0wCwYDVQQKEwRUSzI2MSkwJwYD -VQQDEyBSRUNJUElFTlQ6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdDBoMCEGCCqFAwcB -AQEBMBUGCSqFAwcBAgEBAQYIKoUDBwEBAgIDQwAEQL8nghlzLGMKWHuWhNMPMN5u -L6SkGqRiJ6qZxZb+4dPKbBT9LNVvNKtwUed+BeE5kfqOfolPgFusnL1rnO9yREOj -gYUwgYIwYQYDVR0BBFowWIAUgNkM95n4Zk2TU4mOwJ5xLaenMRKhOjA4MQ0wCwYD -VQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1i -aXSCBAGMuoEwHQYDVR0OBBYEFLue+PUb9Oe+pziBU+MvNejjgrzFMAoGCCqFAwcB -AQMCA0EAPP9Oad1/5jwokSjPpccsQ0xCdVYM+mGQ0IbpiZxQj8gnkt8sq4jR6Ya+ -I/BDkbZNDNE27TU1p3t5rE9NMEeViA== -""")) - - self.sender512_prv = prv_unmarshal(hexdec("F95A5D44C5245F63F2E7DF8E782C1924EADCB8D06C52D91023179786154CBDB1561B4DF759D69F67EE1FBD5B68800E134BAA12818DA4F3AC75B0E5E6F9256911")[::-1]) - self.sender512_pub = public_key(self.curve512, self.sender512_prv) - self.sender512_cert = Certificate().decod(b64decode(""" -MIICNjCCAeOgAwIBAgIEAYy6hDAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 -MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw -MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRUSzI2MSowKAYD -VQQDEyFPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDUxMi1iaXQwgaowIQYIKoUD -BwEBAQIwFQYJKoUDBwECAQIBBggqhQMHAQECAwOBhAAEgYC0i7davCkOGGVcYqFP -tS1fUIROzB0fYARIe0tclTRpare/qzRuVRapqzzO+K21LDpYVfDPs2Sqa13ZN+Ts -/JUlv59qCFB2cYpFyB/0kh4+K79yvz7r8+4WE0EmZf8T3ae/J1Jo6xGunecH1/G4 -hMts9HYLnxbwJDMNVGuIHV6gzqOBhTCBgjBhBgNVHQEEWjBYgBSA2Qz3mfhmTZNT -iY7AnnEtp6cxEqE6MDgxDTALBgNVBAoTBFRLMjYxJzAlBgNVBAMTHkNBIFRLMjY6 -IEdPU1QgMzQuMTAtMTIgMjU2LWJpdIIEAYy6gTAdBgNVHQ4EFgQUK+l9HAscONGx -zCcRpxRAmFHvlXowCgYIKoUDBwEBAwIDQQAbjA0Q41/rIKOOvjHKsAsoEJM+WJf6 -/PKXg2JaStthmw99bdtwwkU/qDbcje2tF6mt+XWyQBXwvfeES1GFY9fJ -""")) - - self.recipient512_prv = prv_unmarshal(hexdec("A50315981F0A7C7FC05B4EB9591A62B1F84BD6FD518ACFCEDF0A7C9CF388D1F18757C056ADA5B38CBF24CDDB0F1519EF72DB1712CEF1920952E94AF1F9C575DC")[::-1]) - self.recipient512_pub = public_key(self.curve512, self.recipient512_prv) - self.recipient512_cert = Certificate().decod(b64decode(""" -MIICNTCCAeKgAwIBAgIEAYy6hTAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 -MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw -MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA6MQ0wCwYDVQQKEwRUSzI2MSkwJwYD -VQQDEyBSRUNJUElFTlQ6IEdPU1QgMzQuMTAtMTIgNTEyLWJpdDCBqjAhBggqhQMH -AQEBAjAVBgkqhQMHAQIBAgEGCCqFAwcBAQIDA4GEAASBgKauwGYvUkzz19g0LP/p -zeRdmwy1m+QSy9W5ZrL/AGuJofm2ARjz40ozNbW6bp9hkHu8x66LX7u5zz+QeS2+ -X5om18UXriComgO0+qhZbc+Hzu0eQ8FjOd8LpLk3TzzfBltfLOX5IiPLjeum+pSP -0QjoXAVcrop//B4yvZIukvROo4GFMIGCMGEGA1UdAQRaMFiAFIDZDPeZ+GZNk1OJ -jsCecS2npzESoTowODENMAsGA1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjog -R09TVCAzNC4xMC0xMiAyNTYtYml0ggQBjLqBMB0GA1UdDgQWBBSrXT5VKhm/5uff -kwW0XpG19k6AajAKBggqhQMHAQEDAgNBAAJBpsHRrQKZGb22LOzaReEB8rl2MbIR -ja64NaM5h+cAFoHm6t/k+ziLh2A11rTakR+5of4NQ3EjEhuPtomP2tc= -""")) - - def test_certs(self): - """Certificates signatures - """ - for prv, pub, curve, cert in ( - (self.ca_prv, self.ca_pub, self.curve256, self.ca_cert), - (self.sender256_prv, self.sender256_pub, self.curve256, self.sender256_cert), - (self.recipient256_prv, self.recipient256_pub, self.curve256, self.recipient256_cert), - (self.sender512_prv, self.sender512_pub, self.curve512, self.sender512_cert), - (self.recipient512_prv, self.recipient512_pub, self.curve512, self.recipient512_cert), - ): - pub_our = public_key(curve, prv) - self.assertEqual(pub_our, pub) - self.assertSequenceEqual( - pub_marshal(pub_our), - bytes(OctetString().decod(bytes( - cert["tbsCertificate"]["subjectPublicKeyInfo"]["subjectPublicKey"] - ))), - ) - - for cert in ( - self.ca_cert, - self.sender256_cert, - self.recipient256_cert, - self.sender512_cert, - self.recipient512_cert, - ): - self.assertTrue(verify( - self.curve256, - self.ca_pub, - GOST34112012256(cert["tbsCertificate"].encode()).digest()[::-1], - bytes(cert["signatureValue"]), - )) - - def test_signed_with_attrs(self): - ci = ContentInfo().decod(b64decode(""" -MIIENwYJKoZIhvcNAQcCoIIEKDCCBCQCAQExDDAKBggqhQMHAQECAzA7BgkqhkiG -9w0BBwGgLgQsyu7t8vDu6/zt++kg7/Do7OXwIOTr/yDx8vDz6vLz8PsgU2lnbmVk -RGF0YS6gggI6MIICNjCCAeOgAwIBAgIEAYy6hDAKBggqhQMHAQEDAjA4MQ0wCwYD -VQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1i -aXQwHhcNMDEwMTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRU -SzI2MSowKAYDVQQDEyFPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDUxMi1iaXQw -gaowIQYIKoUDBwEBAQIwFQYJKoUDBwECAQIBBggqhQMHAQECAwOBhAAEgYC0i7da -vCkOGGVcYqFPtS1fUIROzB0fYARIe0tclTRpare/qzRuVRapqzzO+K21LDpYVfDP -s2Sqa13ZN+Ts/JUlv59qCFB2cYpFyB/0kh4+K79yvz7r8+4WE0EmZf8T3ae/J1Jo -6xGunecH1/G4hMts9HYLnxbwJDMNVGuIHV6gzqOBhTCBgjBhBgNVHQEEWjBYgBSA -2Qz3mfhmTZNTiY7AnnEtp6cxEqE6MDgxDTALBgNVBAoTBFRLMjYxJzAlBgNVBAMT -HkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdIIEAYy6gTAdBgNVHQ4EFgQU -K+l9HAscONGxzCcRpxRAmFHvlXowCgYIKoUDBwEBAwIDQQAbjA0Q41/rIKOOvjHK -sAsoEJM+WJf6/PKXg2JaStthmw99bdtwwkU/qDbcje2tF6mt+XWyQBXwvfeES1GF -Y9fJMYIBlDCCAZACAQEwQDA4MQ0wCwYDVQQKEwRUSzI2MScwJQYDVQQDEx5DQSBU -SzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQCBAGMuoQwCgYIKoUDBwEBAgOgga0w -GAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTkwMzIw -MTk1NTIyWjAiBgkqhkiG9w0BCWIxFQQTU2lnbmVkIGF0dHIncyB2YWx1ZTBPBgkq -hkiG9w0BCQQxQgRAUdPHEukF5BIfo9DoQIMdnB0ZLkzq0RueEUZSNv07A7C+GKWi -G62fueArg8uPCHPTUN6d/42p33fgMkEwH7f7cDAKBggqhQMHAQEBAgSBgGUnVka8 -FvTlClmOtj/FUUacBdE/nEBeMLOO/535VDYrXlftPE6zQf/4ghS7TQG2VRGQ3GWD -+L3+W09A7d5uyyTEbvgtdllUG0OyqFwKmJEaYsMin87SFVs0cn1PGV1fOKeLluZa -bLx5whxd+mzlpekL5i6ImRX+TpERxrA/xSe5 -""")) - _, sd = ci["content"].defined - content = bytes(sd["encapContentInfo"]["eContent"]) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры SignedData."), - ) - si = sd["signerInfos"][0] - self.assertEqual( - si["digestAlgorithm"]["algorithm"], - id_tc26_gost3411_2012_512, - ) - digest = [ - bytes(attr["attrValues"][0].defined[1]) for attr in si["signedAttrs"] - if attr["attrType"] == id_messageDigest - ][0] - self.assertSequenceEqual(digest, GOST34112012512(content).digest()) - self.assertTrue(verify( - self.curve512, - self.sender512_pub, - GOST34112012512( - SignedAttributes(si["signedAttrs"]).encode() - ).digest()[::-1], - bytes(si["signature"]), - )) - - def test_signed_without_attrs(self): - ci = ContentInfo().decod(b64decode(""" -MIIDAQYJKoZIhvcNAQcCoIIC8jCCAu4CAQExDDAKBggqhQMHAQECAjA7BgkqhkiG -9w0BBwGgLgQsyu7t8vDu6/zt++kg7/Do7OXwIOTr/yDx8vDz6vLz8PsgU2lnbmVk -RGF0YS6gggH3MIIB8zCCAaCgAwIBAgIEAYy6gjAKBggqhQMHAQEDAjA4MQ0wCwYD -VQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1i -aXQwHhcNMDEwMTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRU -SzI2MSowKAYDVQQDEyFPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQw -aDAhBggqhQMHAQEBATAVBgkqhQMHAQIBAQEGCCqFAwcBAQICA0MABECWKQ0TYllq -g4GmY3tBJiyzpXUN+aOV9WbmTUinqrmEHP7KCNzoAzFg+04SSQpNNSHpQnm+jLAZ -huJaJfqZ6VbTo4GFMIGCMGEGA1UdAQRaMFiAFIDZDPeZ+GZNk1OJjsCecS2npzES -oTowODENMAsGA1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4x -MC0xMiAyNTYtYml0ggQBjLqBMB0GA1UdDgQWBBTRnChHSWbQYwnJC62n2zu5Njd0 -3zAKBggqhQMHAQEDAgNBAB41oijaXSEn58l78y2rhxY35/lKEq4XWZ70FtsNlVxW -ATyzgO5Wliwnt1O4GoZsxx8r6T/i7VG65UNmQlwdOKQxgaIwgZ8CAQEwQDA4MQ0w -CwYDVQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1 -Ni1iaXQCBAGMuoIwCgYIKoUDBwEBAgIwCgYIKoUDBwEBAQEEQC6jZPA59szL9FiA -0wC71EBE42ap6gKxklT800cu2FvbLu972GJYNSI7+UeanVU37OVWyenEXi2E5HkU -94kBe8Q= -""")) - _, sd = ci["content"].defined - content = bytes(sd["encapContentInfo"]["eContent"]) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры SignedData."), - ) - si = sd["signerInfos"][0] - self.assertEqual( - si["digestAlgorithm"]["algorithm"], - id_tc26_gost3411_2012_256, - ) - self.assertTrue(verify( - self.curve256, - self.sender256_pub, - GOST34112012256(content).digest()[::-1], - bytes(si["signature"]), - )) - - def test_kari_ephemeral(self): - ci = ContentInfo().decod(b64decode(""" -MIIB/gYJKoZIhvcNAQcDoIIB7zCCAesCAQIxggFioYIBXgIBA6CBo6GBoDAXBggq -hQMHAQEBAjALBgkqhQMHAQIBAgEDgYQABIGAe+itJVNbHM35RHfzuwFJPYdPXqtW -8hNEF7Z/XFEE2T71SRkhFX7ozYKQNh/TkVY9D4vG0LnD9Znr/pJyOjpsNb+dPcKX -Kbk/0JQxoPGHxFzASVAFq0ov/yBe2XGFWMeKUqtaAr7SvoYS0oEhT5EuT8BXmecd -nRe7NqOzESpb15ahIgQgsqHxOcdOp03l11S7k3OH1k1HNa5F8m9ctrOzH2846FMw -FwYJKoUDBwEBBwIBMAoGCCqFAwcBAQYCMHYwdDBAMDgxDTALBgNVBAoTBFRLMjYx -JzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdAIEAYy6hQQw -SxLc18zMwzLwXbcKqYhV/VzsdBgVArOHsSBIbaThJWE7zI37VGPMQJM5VXJ7GVcL -MF0GCSqGSIb3DQEHATAfBgkqhQMHAQEFAgIwEgQQ6EeVlADDCz2cdEWKy+tM94Av -yIFl/Ie4VeFFuczTsMsIaOUEe3Jn9GeVp8hZSj3O2q4hslQ/u/+Gj4QkSHm/M0ih -ITAfBgkqhQMHAQAGAQExEgQQs1t6D3J3WCEvxunnEE15NQ== -""")) - _, ed = ci["content"].defined - kari = ed["recipientInfos"][0]["kari"] - orig_key = kari["originator"]["originatorKey"] - self.assertEqual(orig_key["algorithm"]["algorithm"], id_tc26_gost3410_2012_512) - self.assertEqual( - GostR34102012PublicKeyParameters().decod( - bytes(orig_key["algorithm"]["parameters"]) - )["publicKeyParamSet"], - id_tc26_gost3410_2012_512_paramSetA, - ) - orig_pub = pub_unmarshal( - bytes(OctetString().decod(bytes(orig_key["publicKey"]))), - ) - ukm = bytes(kari["ukm"]) - self.assertEqual( - kari["keyEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_wrap_kexp15, - ) - self.assertEqual( - kari["keyEncryptionAlgorithm"]["parameters"].defined[1]["algorithm"], - id_tc26_agreement_gost3410_2012_512, - ) - kexp = bytes(kari["recipientEncryptedKeys"][0]["encryptedKey"]) - keymat = keg(self.curve512, self.recipient512_prv, orig_pub, ukm) - kim, kek = keymat[:KEYSIZE], keymat[KEYSIZE:] - cek = kimp15( - GOST3412Kuznechik(kek).encrypt, - GOST3412Kuznechik(kim).encrypt, - GOST3412Kuznechik.blocksize, - kexp, - ukm[24:24 + GOST3412Kuznechik.blocksize // 2], - ) - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_ctracpkm_omac, - ) - eci_ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - self.assertEqual(ed["unprotectedAttrs"][0]["attrType"], id_cms_mac_attr) - encrypted_mac = bytes(ed["unprotectedAttrs"][0]["attrValues"][0].defined[1]) - encrypted_content = bytes(eci["encryptedContent"]) - cek_enc, cek_mac = kdf_tree_gostr3411_2012_256( - cek, b"kdf tree", eci_ukm[GOST3412Kuznechik.blocksize // 2:], 2, - ) - content_and_tag = ctr_acpkm( - GOST3412Kuznechik, - GOST3412Kuznechik(cek_enc).encrypt, - 256 * 1024, - GOST3412Kuznechik.blocksize, - encrypted_content + encrypted_mac, - eci_ukm[:GOST3412Kuznechik.blocksize // 2], - ) - content = content_and_tag[:-GOST3412Kuznechik.blocksize] - tag_expected = content_and_tag[-GOST3412Kuznechik.blocksize:] - self.assertSequenceEqual( - omac( - GOST3412Kuznechik(cek_mac).encrypt, - GOST3412Kuznechik.blocksize, - content, - ), - tag_expected, - ) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры EnvelopedData."), - ) - - def test_kari_static(self): - ci = ContentInfo().decod(b64decode(""" -MIIBawYJKoZIhvcNAQcDoIIBXDCCAVgCAQIxgfehgfQCAQOgQjBAMDgxDTALBgNV -BAoTBFRLMjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJp -dAIEAYy6gqEiBCBvcfyuSF57y8vVyaw8Z0ch3wjC4lPKTrpVRXty4Rhk5DAXBgkq -hQMHAQEHAQEwCgYIKoUDBwEBBgEwbjBsMEAwODENMAsGA1UEChMEVEsyNjEnMCUG -A1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYtYml0AgQBjLqDBChPbi6B -krXuLPexPAL2oUGCFWDGQHqINL5ExuMBG7/5XQRqriKARVa0MFkGCSqGSIb3DQEH -ATAbBgkqhQMHAQEFAQEwDgQMdNdCKnYAAAAwqTEDgC9O2bYyTGQJ8WUQGq0zHwzX -L0jFhWHTF1tcAxYmd9pX5i89UwIxhtYqyjX1QHju2g== -""")) - _, ed = ci["content"].defined - kari = ed["recipientInfos"][0]["kari"] - ukm = bytes(kari["ukm"]) - self.assertEqual( - kari["keyEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_magma_wrap_kexp15, - ) - self.assertEqual( - kari["keyEncryptionAlgorithm"]["parameters"].defined[1]["algorithm"], - id_tc26_agreement_gost3410_2012_256, - ) - kexp = bytes(kari["recipientEncryptedKeys"][0]["encryptedKey"]) - keymat = keg( - self.curve256, - self.recipient256_prv, - self.sender256_pub, - ukm, - ) - kim, kek = keymat[:KEYSIZE], keymat[KEYSIZE:] - cek = kimp15( - GOST3412Magma(kek).encrypt, - GOST3412Magma(kim).encrypt, - GOST3412Magma.blocksize, - kexp, - ukm[24:24 + GOST3412Magma.blocksize // 2], - ) - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_magma_ctracpkm, - ) - eci_ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - content = ctr_acpkm( - GOST3412Magma, - GOST3412Magma(cek).encrypt, - 8 * 1024, - GOST3412Magma.blocksize, - bytes(eci["encryptedContent"]), - eci_ukm[:GOST3412Magma.blocksize // 2], - ) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры EnvelopedData."), - ) - - def test_ktri_256(self): - ci = ContentInfo().decod(b64decode(""" -MIIBlQYJKoZIhvcNAQcDoIIBhjCCAYICAQAxggEcMIIBGAIBADBAMDgxDTALBgNV -BAoTBFRLMjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJp -dAIEAYy6gzAXBgkqhQMHAQEHAgEwCgYIKoUDBwEBBgEEgbcwgbQEMFiMredFR3Mv -3g2wqyVXRnrhYEBMNFaqqgBpHwPQh3bF98tt9HZPxRDCww0OPfxeuTBeMBcGCCqF -AwcBAQEBMAsGCSqFAwcBAgEBAQNDAARAdFJ9ww+3ptvQiaQpizCldNYhl4DB1rl8 -Fx/2FIgnwssCbYRQ+UuRsTk9dfLLTGJG3JIEXKFxXWBgOrK965A5pAQg9f2/EHxG -DfetwCe1a6uUDCWD+wp5dYOpfkry8YRDEJgwXQYJKoZIhvcNAQcBMB8GCSqFAwcB -AQUCATASBBDUHNxmVclO/v3OaY9P7jxOgC+sD9CHGlEMRUpfGn6yfFDMExmYeby8 -LzdPJe1MkYV0qQgdC1zI3nQ7/4taf+4zRA== -""")) - _, ed = ci["content"].defined - ktri = ed["recipientInfos"][0]["ktri"] - self.assertEqual( - ktri["keyEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_wrap_kexp15, - ) - self.assertEqual( - ktri["keyEncryptionAlgorithm"]["parameters"].defined[1]["algorithm"], - id_tc26_agreement_gost3410_2012_256, - ) - _, encrypted_key = ktri["encryptedKey"].defined - self.assertEqual( - encrypted_key["ephemeralPublicKey"]["algorithm"]["algorithm"], - id_tc26_gost3410_2012_256, - ) - pub = pub_unmarshal(bytes(OctetString().decod( - bytes(encrypted_key["ephemeralPublicKey"]["subjectPublicKey"]) - ))) - ukm = bytes(encrypted_key["ukm"]) - kexp = bytes(encrypted_key["encryptedKey"]) - keymat = keg(self.curve256, self.recipient256_prv, pub, ukm) - kim, kek = keymat[:KEYSIZE], keymat[KEYSIZE:] - cek = kimp15( - GOST3412Kuznechik(kek).encrypt, - GOST3412Kuznechik(kim).encrypt, - GOST3412Kuznechik.blocksize, - kexp, - ukm[24:24 + GOST3412Kuznechik.blocksize // 2], - ) - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_ctracpkm, - ) - eci_ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - content = ctr_acpkm( - GOST3412Kuznechik, - GOST3412Kuznechik(cek).encrypt, - 256 * 1024, - GOST3412Kuznechik.blocksize, - bytes(eci["encryptedContent"]), - eci_ukm[:GOST3412Kuznechik.blocksize // 2], - ) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры EnvelopedData."), - ) - - def test_ktri_512(self): - ci = ContentInfo().decod(b64decode(""" -MIIB5wYJKoZIhvcNAQcDoIIB2DCCAdQCAQAxggFXMIIBUwIBADBAMDgxDTALBgNVBAoTBFRL -MjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdAIEAYy6hTAXBgkq -hQMHAQEHAQEwCgYIKoUDBwEBBgIEgfIwge8EKDof9JLTJVuIfP+c+imDCGyOLtAYENkoXpeU -CdiGn0Lt65t3TN9G0bUwgaAwFwYIKoUDBwEBAQIwCwYJKoUDBwECAQIBA4GEAASBgDD9XXHn -0j4EwY3DGB1wzHeThPRDlCwIvpmqWy00QDhS3fLRWiETSe9uMLeg27zI/EiserKMasNZum/i -d09cmP8aTNIDNRtI5H9M0mH7LpEtY8L901MszvOKHLDYdemvz0JUqOvBtvoeQ6sV4Gl45zXx -HTzBWlBw1FLX/ITWLapaBCAa09foTeA+PObBznGuCOPoKy+xz/9IIVmZidI6EYkIrzBZBgkq -hkiG9w0BBwEwGwYJKoUDBwEBBQECMA4EDA4z1UwRL4WYzKFX/oAv8eEX3fWt6hxDpjO0rI7/ -CiJ/CwYGCKODJ9h63vAwlsWwcPwAjxcsLvCNlv6i4NqhGTAXBgkqhQMHAQAGAQExCgQIs2DT -LuZ22Yw= -""")) - _, ed = ci["content"].defined - ktri = ed["recipientInfos"][0]["ktri"] - self.assertEqual( - ktri["keyEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_magma_wrap_kexp15, - ) - self.assertEqual( - ktri["keyEncryptionAlgorithm"]["parameters"].defined[1]["algorithm"], - id_tc26_agreement_gost3410_2012_512, - ) - _, encrypted_key = ktri["encryptedKey"].defined - self.assertEqual( - encrypted_key["ephemeralPublicKey"]["algorithm"]["algorithm"], - id_tc26_gost3410_2012_512, - ) - pub = pub_unmarshal( - bytes(OctetString().decod( - bytes(encrypted_key["ephemeralPublicKey"]["subjectPublicKey"]) - )), - ) - ukm = bytes(encrypted_key["ukm"]) - kexp = bytes(encrypted_key["encryptedKey"]) - keymat = keg(self.curve512, self.recipient512_prv, pub, ukm) - kim, kek = keymat[:KEYSIZE], keymat[KEYSIZE:] - cek = kimp15( - GOST3412Magma(kek).encrypt, - GOST3412Magma(kim).encrypt, - GOST3412Magma.blocksize, - kexp, - ukm[24:24 + GOST3412Magma.blocksize // 2], - ) - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_magma_ctracpkm_omac, - ) - eci_ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - self.assertEqual(ed["unprotectedAttrs"][0]["attrType"], id_cms_mac_attr) - encrypted_mac = bytes(ed["unprotectedAttrs"][0]["attrValues"][0].defined[1]) - encrypted_content = bytes(eci["encryptedContent"]) - cek_enc, cek_mac = kdf_tree_gostr3411_2012_256( - cek, b"kdf tree", eci_ukm[GOST3412Magma.blocksize // 2:], 2, - ) - content_and_tag = ctr_acpkm( - GOST3412Magma, - GOST3412Magma(cek_enc).encrypt, - 8 * 1024, - GOST3412Magma.blocksize, - encrypted_content + encrypted_mac, - eci_ukm[:GOST3412Magma.blocksize // 2], - ) - content = content_and_tag[:-GOST3412Magma.blocksize] - tag_expected = content_and_tag[-GOST3412Magma.blocksize:] - self.assertSequenceEqual( - omac( - GOST3412Magma(cek_mac).encrypt, - GOST3412Magma.blocksize, - content, - ), - tag_expected, - ) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры EnvelopedData."), - ) - - def test_digested256(self): - ci = ContentInfo().decod(b64decode(""" -MH0GCSqGSIb3DQEHBaBwMG4CAQAwCgYIKoUDBwEBAgIwOwYJKoZIhvcNAQcBoC4ELMru7fLw -7uv87fvpIO/w6Ozl8CDk6/8g8fLw8+ry8/D7IERpZ2VzdERhdGEuBCD/esPQYsGkzxZV8uUM -IAWt6SI8KtxBP8NyG8AGbJ8i/Q== -""")) - _, dd = ci["content"].defined - eci = dd["encapContentInfo"] - self.assertSequenceEqual( - GOST34112012256(bytes(eci["eContent"])).digest(), - bytes(dd["digest"]), - ) - - def test_digested512(self): - ci = ContentInfo().decod(b64decode(""" -MIGfBgkqhkiG9w0BBwWggZEwgY4CAQAwCgYIKoUDBwEBAgMwOwYJKoZIhvcNAQcBoC4ELMru -7fLw7uv87fvpIO/w6Ozl8CDk6/8g8fLw8+ry8/D7IERpZ2VzdERhdGEuBEDe4VUvcKSRvU7R -FVhFjajXY+nJSUkUsoi3oOeJBnru4PErt8RusPrCJs614ciHCM+ehrC4a+M1Nbq77F/Wsa/v -""")) - _, dd = ci["content"].defined - eci = dd["encapContentInfo"] - self.assertSequenceEqual( - GOST34112012512(bytes(eci["eContent"])).digest(), - bytes(dd["digest"]), - ) - - def test_encrypted_kuznechik(self): - ci = ContentInfo().decod(b64decode(""" -MHEGCSqGSIb3DQEHBqBkMGICAQAwXQYJKoZIhvcNAQcBMB8GCSqFAwcBAQUCATASBBBSwX+z -yOEPPuGyfpsRG4AigC/P8ftTdQMStfIThVkE/vpJlwaHgGv83m2bsPayeyuqpoTeEMOaqGcO -0MxHWsC9hQ== -""")) - _, ed = ci["content"].defined - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_ctracpkm, - ) - ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - content = ctr_acpkm( - GOST3412Kuznechik, - GOST3412Kuznechik(self.psk).encrypt, - 256 * 1024, - GOST3412Kuznechik.blocksize, - bytes(eci["encryptedContent"]), - ukm[:GOST3412Kuznechik.blocksize // 2], - ) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры EncryptedData."), - ) - - def test_encrypted_magma(self): - ci = ContentInfo().decod(b64decode(""" -MIGIBgkqhkiG9w0BBwagezB5AgEAMFkGCSqGSIb3DQEHATAbBgkqhQMHAQEFAQIwDgQMuncO -u3uYPbI30vFCgC9Nsws4R09yLp6jUtadncWUPZGmCGpPKnXGgNHvEmUArgKJvu4FPHtLkHuL -eQXZg6EZMBcGCSqFAwcBAAYBATEKBAjCbQoH632oGA== -""")) - _, ed = ci["content"].defined - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_magma_ctracpkm_omac, - ) - ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - self.assertEqual(ed["unprotectedAttrs"][0]["attrType"], id_cms_mac_attr) - encrypted_mac = bytes(ed["unprotectedAttrs"][0]["attrValues"][0].defined[1]) - cek_enc, cek_mac = kdf_tree_gostr3411_2012_256( - self.psk, b"kdf tree", ukm[GOST3412Magma.blocksize // 2:], 2, - ) - content_and_tag = ctr_acpkm( - GOST3412Magma, - GOST3412Magma(cek_enc).encrypt, - 8 * 1024, - GOST3412Magma.blocksize, - bytes(eci["encryptedContent"]) + encrypted_mac, - ukm[:GOST3412Magma.blocksize // 2], - ) - content = content_and_tag[:-GOST3412Magma.blocksize] - tag_expected = content_and_tag[-GOST3412Magma.blocksize:] - self.assertSequenceEqual( - omac( - GOST3412Magma(cek_mac).encrypt, - GOST3412Magma.blocksize, - content, - ), - tag_expected, - ) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры EncryptedData."), - ) diff --git a/pygost-5.13/build/lib/pygost/test_gost28147.py b/pygost-5.13/build/lib/pygost/test_gost28147.py deleted file mode 100644 index a83e930..0000000 --- a/pygost-5.13/build/lib/pygost/test_gost28147.py +++ /dev/null @@ -1,384 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from unittest import TestCase - -from pygost.gost28147 import block2ns -from pygost.gost28147 import BLOCKSIZE -from pygost.gost28147 import cbc_decrypt -from pygost.gost28147 import cbc_encrypt -from pygost.gost28147 import cfb_decrypt -from pygost.gost28147 import cfb_encrypt -from pygost.gost28147 import cnt -from pygost.gost28147 import DEFAULT_SBOX -from pygost.gost28147 import ecb_decrypt -from pygost.gost28147 import ecb_encrypt -from pygost.gost28147 import encrypt -from pygost.gost28147 import KEYSIZE -from pygost.gost28147 import MESH_MAX_DATA -from pygost.gost28147 import ns2block -from pygost.utils import hexdec -from pygost.utils import strxor - - -class ECBTest(TestCase): - def test_gcl(self): - """Test vectors from libgcl3 - """ - sbox = "id-Gost28147-89-TestParamSet" - key = hexdec(b"0475f6e05038fbfad2c7c390edb3ca3d1547124291ae1e8a2f79cd9ed2bcefbd") - plaintext = bytes(bytearray(( - 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, - 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, - 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, - 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, - 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, - 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, - 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, - 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, - 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, 0x40, - 0x4f, 0x4e, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x48, - 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, - 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58, - 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x60, - 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, - 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70, - 0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x7a, 0x79, 0x78, - 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, - 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, - 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, - 0x9f, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, 0x99, 0x98, - 0xa7, 0xa6, 0xa5, 0xa4, 0xa3, 0xa2, 0xa1, 0xa0, - 0xaf, 0xae, 0xad, 0xac, 0xab, 0xaa, 0xa9, 0xa8, - 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0, - 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8, - 0xc7, 0xc6, 0xc5, 0xc4, 0xc3, 0xc2, 0xc1, 0xc0, - 0xcf, 0xce, 0xcd, 0xcc, 0xcb, 0xca, 0xc9, 0xc8, - 0xd7, 0xd6, 0xd5, 0xd4, 0xd3, 0xd2, 0xd1, 0xd0, - 0xdf, 0xde, 0xdd, 0xdc, 0xdb, 0xda, 0xd9, 0xd8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, - ))) - ciphertext = bytes(bytearray(( - 0x4b, 0x8c, 0x4c, 0x98, 0x15, 0xf2, 0x4a, 0xea, - 0x1e, 0xc3, 0x57, 0x09, 0xb3, 0xbc, 0x2e, 0xd1, - 0xe0, 0xd1, 0xf2, 0x22, 0x65, 0x2d, 0x59, 0x18, - 0xf7, 0xdf, 0xfc, 0x80, 0x4b, 0xde, 0x5c, 0x68, - 0x46, 0x53, 0x75, 0x53, 0xa7, 0x46, 0x0d, 0xec, - 0x05, 0x1f, 0x1b, 0xd3, 0x0a, 0x63, 0x1a, 0xb7, - 0x78, 0xc4, 0x43, 0xe0, 0x5d, 0x3e, 0xa4, 0x0e, - 0x2d, 0x7e, 0x23, 0xa9, 0x1b, 0xc9, 0x02, 0xbc, - 0x21, 0x0c, 0x84, 0xcb, 0x0d, 0x0a, 0x07, 0xc8, - 0x7b, 0xd0, 0xfb, 0xb5, 0x1a, 0x14, 0x04, 0x5c, - 0xa2, 0x53, 0x97, 0x71, 0x2e, 0x5c, 0xc2, 0x8f, - 0x39, 0x3f, 0x6f, 0x52, 0xf2, 0x30, 0x26, 0x4e, - 0x8c, 0xe0, 0xd1, 0x01, 0x75, 0x6d, 0xdc, 0xd3, - 0x03, 0x79, 0x1e, 0xca, 0xd5, 0xc1, 0x0e, 0x12, - 0x53, 0x0a, 0x78, 0xe2, 0x0a, 0xb1, 0x1c, 0xea, - 0x3a, 0xf8, 0x55, 0xb9, 0x7c, 0xe1, 0x0b, 0xba, - 0xa0, 0xc8, 0x96, 0xeb, 0x50, 0x5a, 0xd3, 0x60, - 0x43, 0xa3, 0x0f, 0x98, 0xdb, 0xd9, 0x50, 0x6d, - 0x63, 0x91, 0xaf, 0x01, 0x40, 0xe9, 0x75, 0x5a, - 0x46, 0x5c, 0x1f, 0x19, 0x4a, 0x0b, 0x89, 0x9b, - 0xc4, 0xf6, 0xf8, 0xf5, 0x2f, 0x87, 0x3f, 0xfa, - 0x26, 0xd4, 0xf8, 0x25, 0xba, 0x1f, 0x98, 0x82, - 0xfc, 0x26, 0xaf, 0x2d, 0xc0, 0xf9, 0xc4, 0x58, - 0x49, 0xfa, 0x09, 0x80, 0x02, 0x62, 0xa4, 0x34, - 0x2d, 0xcb, 0x5a, 0x6b, 0xab, 0x61, 0x5d, 0x08, - 0xd4, 0x26, 0xe0, 0x08, 0x13, 0xd6, 0x2e, 0x02, - 0x2a, 0x37, 0xe8, 0xd0, 0xcf, 0x36, 0xf1, 0xc7, - 0xc0, 0x3f, 0x9b, 0x21, 0x60, 0xbd, 0x29, 0x2d, - 0x2e, 0x01, 0x48, 0x4e, 0xf8, 0x8f, 0x20, 0x16, - 0x8a, 0xbf, 0x82, 0xdc, 0x32, 0x7a, 0xa3, 0x18, - 0x69, 0xd1, 0x50, 0x59, 0x31, 0x91, 0xf2, 0x6c, - 0x5a, 0x5f, 0xca, 0x58, 0x9a, 0xb2, 0x2d, 0xb2, - ))) - encrypted = ecb_encrypt(key, plaintext, sbox=sbox) - self.assertSequenceEqual(encrypted, ciphertext) - decrypted = ecb_decrypt(key, encrypted, sbox=sbox) - self.assertSequenceEqual(decrypted, plaintext) - - def test_cryptopp(self): - """Test vectors from Crypto++ 5.6.2 - """ - sbox = "AppliedCryptography" - data = ( - (b"BE5EC2006CFF9DCF52354959F1FF0CBFE95061B5A648C10387069C25997C0672", b"0DF82802B741A292", b"07F9027DF7F7DF89"), - (b"B385272AC8D72A5A8B344BC80363AC4D09BF58F41F540624CBCB8FDCF55307D7", b"1354EE9C0A11CD4C", b"4FB50536F960A7B1"), - (b"AEE02F609A35660E4097E546FD3026B032CD107C7D459977ADF489BEF2652262", b"6693D492C4B0CC39", b"670034AC0FA811B5"), - (b"320E9D8422165D58911DFC7D8BBB1F81B0ECD924023BF94D9DF7DCF7801240E0", b"99E2D13080928D79", b"8118FF9D3B3CFE7D"), - (b"C9F703BBBFC63691BFA3B7B87EA8FD5E8E8EF384EF733F1A61AEF68C8FFA265F", b"D1E787749C72814C", b"A083826A790D3E0C"), - (b"728FEE32F04B4C654AD7F607D71C660C2C2670D7C999713233149A1C0C17A1F0", b"D4C05323A4F7A7B5", b"4D1F2E6B0D9DE2CE"), - (b"35FC96402209500FCFDEF5352D1ABB038FE33FC0D9D58512E56370B22BAA133B", b"8742D9A05F6A3AF6", b"2F3BB84879D11E52"), - (b"D416F630BE65B7FE150656183370E07018234EE5DA3D89C4CE9152A03E5BFB77", b"F86506DA04E41CB8", b"96F0A5C77A04F5CE"), - ) - for key, pt, ct in data: - key = hexdec(key) - pt = hexdec(pt) - ct = hexdec(ct) - self.assertSequenceEqual(ecb_encrypt(key, pt, sbox=sbox), ct) - - def test_cryptomanager(self): - """Test vector from http://cryptomanager.com/tv.html - """ - sbox = "id-GostR3411-94-TestParamSet" - key = hexdec(b"75713134B60FEC45A607BB83AA3746AF4FF99DA6D1B53B5B1B402A1BAA030D1B") - self.assertSequenceEqual( - ecb_encrypt(key, hexdec(b"1122334455667788"), sbox=sbox), - hexdec(b"03251E14F9D28ACB"), - ) - - -class CFBTest(TestCase): - def test_cryptomanager(self): - """Test vector from http://cryptomanager.com/tv.html - """ - key = hexdec(b"75713134B60FEC45A607BB83AA3746AF4FF99DA6D1B53B5B1B402A1BAA030D1B") - sbox = "id-GostR3411-94-TestParamSet" - self.assertSequenceEqual( - cfb_encrypt( - key, - hexdec(b"112233445566778899AABBCCDD800000"), - iv=hexdec(b"0102030405060708"), - sbox=sbox, - ), - hexdec(b"6EE84586DD2BCA0CAD3616940E164242"), - ) - self.assertSequenceEqual( - cfb_decrypt( - key, - hexdec(b"6EE84586DD2BCA0CAD3616940E164242"), - iv=hexdec(b"0102030405060708"), - sbox=sbox, - ), - hexdec(b"112233445566778899AABBCCDD800000"), - ) - - def test_steps(self): - """Check step-by-step operation manually - """ - key = urandom(KEYSIZE) - iv = urandom(BLOCKSIZE) - plaintext = urandom(20) - ciphertext = cfb_encrypt(key, plaintext, iv) - - # First full block - step = encrypt(DEFAULT_SBOX, key, block2ns(iv)) - step = strxor(plaintext[:8], ns2block(step)) - self.assertSequenceEqual(step, ciphertext[:8]) - - # Second full block - step = encrypt(DEFAULT_SBOX, key, block2ns(step)) - step = strxor(plaintext[8:16], ns2block(step)) - self.assertSequenceEqual(step, ciphertext[8:16]) - - # Third non-full block - step = encrypt(DEFAULT_SBOX, key, block2ns(step)) - step = strxor(plaintext[16:] + 4 * b"\x00", ns2block(step)) - self.assertSequenceEqual(step[:4], ciphertext[16:]) - - def test_random(self): - """Random data with various sizes - """ - key = urandom(KEYSIZE) - iv = urandom(BLOCKSIZE) - for size in (5, 8, 16, 120): - pt = urandom(size) - self.assertSequenceEqual( - cfb_decrypt(key, cfb_encrypt(key, pt, iv), iv), - pt, - ) - - -class CTRTest(TestCase): - def test_gcl(self): - """Test vectors from libgcl3 - """ - sbox = "id-Gost28147-89-TestParamSet" - key = hexdec(b"0475f6e05038fbfad2c7c390edb3ca3d1547124291ae1e8a2f79cd9ed2bcefbd") - plaintext = bytes(bytearray(( - 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, - 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, - 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, - 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, - 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, - 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, - 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, - 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, - 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, 0x40, - 0x4f, 0x4e, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x48, - 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, - 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58, - 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x60, - 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, - 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70, - 0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x7a, 0x79, 0x78, - 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, - 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, - 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, - 0x9f, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, 0x99, 0x98, - 0xa7, 0xa6, 0xa5, 0xa4, 0xa3, 0xa2, 0xa1, 0xa0, - 0xaf, 0xae, 0xad, 0xac, 0xab, 0xaa, 0xa9, 0xa8, - 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0, - 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8, - 0xc7, 0xc6, 0xc5, 0xc4, 0xc3, 0xc2, 0xc1, 0xc0, - 0xcf, 0xce, 0xcd, 0xcc, 0xcb, 0xca, 0xc9, 0xc8, - 0xd7, 0xd6, 0xd5, 0xd4, 0xd3, 0xd2, 0xd1, 0xd0, - 0xdf, 0xde, 0xdd, 0xdc, 0xdb, 0xda, 0xd9, 0xd8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, - ))) - ciphertext = bytes(bytearray(( - 0x4a, 0x5e, 0x37, 0x6c, 0xa1, 0x12, 0xd3, 0x55, - 0x09, 0x13, 0x1a, 0x21, 0xac, 0xfb, 0xb2, 0x1e, - 0x8c, 0x24, 0x9b, 0x57, 0x20, 0x68, 0x46, 0xd5, - 0x23, 0x2a, 0x26, 0x35, 0x12, 0x56, 0x5c, 0x69, - 0x2a, 0x2f, 0xd1, 0xab, 0xbd, 0x45, 0xdc, 0x3a, - 0x1a, 0xa4, 0x57, 0x64, 0xd5, 0xe4, 0x69, 0x6d, - 0xb4, 0x8b, 0xf1, 0x54, 0x78, 0x3b, 0x10, 0x8f, - 0x7a, 0x4b, 0x32, 0xe0, 0xe8, 0x4c, 0xbf, 0x03, - 0x24, 0x37, 0x95, 0x6a, 0x55, 0xa8, 0xce, 0x6f, - 0x95, 0x62, 0x12, 0xf6, 0x79, 0xe6, 0xf0, 0x1b, - 0x86, 0xef, 0x36, 0x36, 0x05, 0xd8, 0x6f, 0x10, - 0xa1, 0x41, 0x05, 0x07, 0xf8, 0xfa, 0xa4, 0x0b, - 0x17, 0x2c, 0x71, 0xbc, 0x8b, 0xcb, 0xcf, 0x3d, - 0x74, 0x18, 0x32, 0x0b, 0x1c, 0xd2, 0x9e, 0x75, - 0xba, 0x3e, 0x61, 0xe1, 0x61, 0x96, 0xd0, 0xee, - 0x8f, 0xf2, 0x9a, 0x5e, 0xb7, 0x7a, 0x15, 0xaa, - 0x4e, 0x1e, 0x77, 0x7c, 0x99, 0xe1, 0x41, 0x13, - 0xf4, 0x60, 0x39, 0x46, 0x4c, 0x35, 0xde, 0x95, - 0xcc, 0x4f, 0xd5, 0xaf, 0xd1, 0x4d, 0x84, 0x1a, - 0x45, 0xc7, 0x2a, 0xf2, 0x2c, 0xc0, 0xb7, 0x94, - 0xa3, 0x08, 0xb9, 0x12, 0x96, 0xb5, 0x97, 0x99, - 0x3a, 0xb7, 0x0c, 0x14, 0x56, 0xb9, 0xcb, 0x49, - 0x44, 0xa9, 0x93, 0xa9, 0xfb, 0x19, 0x10, 0x8c, - 0x6a, 0x68, 0xe8, 0x7b, 0x06, 0x57, 0xf0, 0xef, - 0x88, 0x44, 0xa6, 0xd2, 0x98, 0xbe, 0xd4, 0x07, - 0x41, 0x37, 0x45, 0xa6, 0x71, 0x36, 0x76, 0x69, - 0x4b, 0x75, 0x15, 0x33, 0x90, 0x29, 0x6e, 0x33, - 0xcb, 0x96, 0x39, 0x78, 0x19, 0x2e, 0x96, 0xf3, - 0x49, 0x4c, 0x89, 0x3d, 0xa1, 0x86, 0x82, 0x00, - 0xce, 0xbd, 0x54, 0x29, 0x65, 0x00, 0x1d, 0x16, - 0x13, 0xc3, 0xfe, 0x1f, 0x8c, 0x55, 0x63, 0x09, - 0x1f, 0xcd, 0xd4, 0x28, 0xca, - ))) - iv = b"\x02\x01\x01\x01\x01\x01\x01\x01" - encrypted = cnt(key, plaintext, iv=iv, sbox=sbox) - self.assertSequenceEqual(encrypted, ciphertext) - decrypted = cnt(key, encrypted, iv=iv, sbox=sbox) - self.assertSequenceEqual(decrypted, plaintext) - - def test_gcl2(self): - """Test vectors 2 from libgcl3 - """ - sbox = "id-Gost28147-89-TestParamSet" - key = hexdec(b"fc7ad2886f455b50d29008fa622b57d5c65b3c637202025799cadf0768519e8a") - plaintext = bytes(bytearray(( - 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, - 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, - 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, - 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, - 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, - 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, - ))) - ciphertext = bytes(bytearray(( - 0xd0, 0xbe, 0x60, 0x1a, 0x2c, 0xf1, 0x90, 0x26, - 0x9b, 0x7b, 0x23, 0xb4, 0xd2, 0xcc, 0xe1, 0x15, - 0xf6, 0x05, 0x57, 0x28, 0x88, 0x75, 0xeb, 0x1e, - 0xd3, 0x62, 0xdc, 0xda, 0x9b, 0x62, 0xee, 0x9a, - 0x57, 0x87, 0x8a, 0xf1, 0x82, 0x37, 0x9c, 0x7f, - 0x13, 0xcc, 0x55, 0x38, 0xb5, 0x63, 0x32, 0xc5, - 0x23, 0xa4, 0xcb, 0x7d, 0x51, - ))) - iv = BLOCKSIZE * b"\x00" - encrypted = cnt(key, plaintext, iv=iv, sbox=sbox) - self.assertSequenceEqual(encrypted, ciphertext) - decrypted = cnt(key, encrypted, iv=iv, sbox=sbox) - self.assertSequenceEqual(decrypted, plaintext) - - -class CBCTest(TestCase): - def test_pad_requirement(self): - key = KEYSIZE * b"x" - for s in (b"", b"foo", b"foobarbaz"): - with self.assertRaises(ValueError): - cbc_encrypt(key, s, pad=False) - with self.assertRaises(ValueError): - cbc_decrypt(key, s, pad=False) - - def test_passes(self): - iv = urandom(BLOCKSIZE) - key = KEYSIZE * b"x" - for pt in (b"foo", b"foobarba", b"foobarbaz", 16 * b"x"): - ct = cbc_encrypt(key, pt, iv) - dt = cbc_decrypt(key, ct) - self.assertSequenceEqual(pt, dt) - - def test_iv_existence_check(self): - key = KEYSIZE * b"x" - with self.assertRaises(ValueError): - cbc_decrypt(key, BLOCKSIZE * b"x") - iv = urandom(BLOCKSIZE) - cbc_decrypt(key, cbc_encrypt(key, BLOCKSIZE * b"x", iv)) - - def test_meshing(self): - pt = urandom(MESH_MAX_DATA * 3) - key = urandom(KEYSIZE) - ct = cbc_encrypt(key, pt) - dt = cbc_decrypt(key, ct) - self.assertSequenceEqual(pt, dt) - - -class CFBMeshingTest(TestCase): - def setUp(self): - self.key = urandom(KEYSIZE) - self.iv = urandom(BLOCKSIZE) - - def test_single(self): - pt = b"\x00" - ct = cfb_encrypt(self.key, pt, mesh=True) - dec = cfb_decrypt(self.key, ct, mesh=True) - self.assertSequenceEqual(pt, dec) - - def test_short(self): - pt = urandom(MESH_MAX_DATA - 1) - ct = cfb_encrypt(self.key, pt, mesh=True) - dec = cfb_decrypt(self.key, ct, mesh=True) - dec_plain = cfb_decrypt(self.key, ct) - self.assertSequenceEqual(pt, dec) - self.assertSequenceEqual(pt, dec_plain) - - def test_short_iv(self): - pt = urandom(MESH_MAX_DATA - 1) - ct = cfb_encrypt(self.key, pt, iv=self.iv, mesh=True) - dec = cfb_decrypt(self.key, ct, iv=self.iv, mesh=True) - dec_plain = cfb_decrypt(self.key, ct, iv=self.iv) - self.assertSequenceEqual(pt, dec) - self.assertSequenceEqual(pt, dec_plain) - - def test_longer_iv(self): - pt = urandom(MESH_MAX_DATA * 3) - ct = cfb_encrypt(self.key, pt, iv=self.iv, mesh=True) - dec = cfb_decrypt(self.key, ct, iv=self.iv, mesh=True) - dec_plain = cfb_decrypt(self.key, ct, iv=self.iv) - self.assertSequenceEqual(pt, dec) - self.assertNotEqual(pt, dec_plain) diff --git a/pygost-5.13/build/lib/pygost/test_gost28147_mac.py b/pygost-5.13/build/lib/pygost/test_gost28147_mac.py deleted file mode 100644 index ec1af3f..0000000 --- a/pygost-5.13/build/lib/pygost/test_gost28147_mac.py +++ /dev/null @@ -1,63 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 unittest import TestCase - -from pygost.gost28147_mac import MAC - - -class TestMAC(TestCase): - """Test vectors generated with libgcl3 library - """ - k = b"This is message\xFF length\x0032 bytes" - - def test_a(self): - self.assertSequenceEqual( - MAC(self.k, b"a").hexdigest(), - "bd5d3b5b2b7b57af", - ) - - def test_abc(self): - self.assertSequenceEqual( - MAC(self.k, b"abc").hexdigest(), - "28661e40805b1ff9", - ) - - def test_128U(self): - self.assertSequenceEqual( - MAC(self.k, 128 * b"U").hexdigest(), - "1a06d1bad74580ef", - ) - - def test_13x(self): - self.assertSequenceEqual( - MAC(self.k, 13 * b"x").hexdigest(), - "917ee1f1a668fbd3", - ) - - def test_parts(self): - m = MAC(self.k) - m.update(b"foo") - m.update(b"bar") - self.assertSequenceEqual(m.digest(), MAC(self.k, b"foobar").digest()) - - def test_copy(self): - m = MAC(self.k, b"foo") - c = m.copy() - m.update(b"barbaz") - c.update(b"bar") - c.update(b"baz") - self.assertSequenceEqual(m.digest(), c.digest()) diff --git a/pygost-5.13/build/lib/pygost/test_gost3410.py b/pygost-5.13/build/lib/pygost/test_gost3410.py deleted file mode 100644 index cd71535..0000000 --- a/pygost-5.13/build/lib/pygost/test_gost3410.py +++ /dev/null @@ -1,495 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from unittest import TestCase - -from pygost.gost3410 import CURVES -from pygost.gost3410 import GOST3410Curve -from pygost.gost3410 import prv_marshal -from pygost.gost3410 import prv_unmarshal -from pygost.gost3410 import public_key -from pygost.gost3410 import sign -from pygost.gost3410 import uv2xy -from pygost.gost3410 import verify -from pygost.gost3410 import xy2uv -from pygost.utils import bytes2long -from pygost.utils import hexdec -from pygost.utils import hexenc -from pygost.utils import long2bytes - - -class Test341001(TestCase): - def test_rfc(self): - """Test vector from :rfc:`5832` - """ - prv = bytes(bytearray(( - 0x7A, 0x92, 0x9A, 0xDE, 0x78, 0x9B, 0xB9, 0xBE, - 0x10, 0xED, 0x35, 0x9D, 0xD3, 0x9A, 0x72, 0xC1, - 0x1B, 0x60, 0x96, 0x1F, 0x49, 0x39, 0x7E, 0xEE, - 0x1D, 0x19, 0xCE, 0x98, 0x91, 0xEC, 0x3B, 0x28 - ))) - pub_x = bytes(bytearray(( - 0x7F, 0x2B, 0x49, 0xE2, 0x70, 0xDB, 0x6D, 0x90, - 0xD8, 0x59, 0x5B, 0xEC, 0x45, 0x8B, 0x50, 0xC5, - 0x85, 0x85, 0xBA, 0x1D, 0x4E, 0x9B, 0x78, 0x8F, - 0x66, 0x89, 0xDB, 0xD8, 0xE5, 0x6F, 0xD8, 0x0B - ))) - pub_y = bytes(bytearray(( - 0x26, 0xF1, 0xB4, 0x89, 0xD6, 0x70, 0x1D, 0xD1, - 0x85, 0xC8, 0x41, 0x3A, 0x97, 0x7B, 0x3C, 0xBB, - 0xAF, 0x64, 0xD1, 0xC5, 0x93, 0xD2, 0x66, 0x27, - 0xDF, 0xFB, 0x10, 0x1A, 0x87, 0xFF, 0x77, 0xDA - ))) - digest = bytes(bytearray(( - 0x2D, 0xFB, 0xC1, 0xB3, 0x72, 0xD8, 0x9A, 0x11, - 0x88, 0xC0, 0x9C, 0x52, 0xE0, 0xEE, 0xC6, 0x1F, - 0xCE, 0x52, 0x03, 0x2A, 0xB1, 0x02, 0x2E, 0x8E, - 0x67, 0xEC, 0xE6, 0x67, 0x2B, 0x04, 0x3E, 0xE5 - ))) - signature = bytes(bytearray(( - 0x41, 0xAA, 0x28, 0xD2, 0xF1, 0xAB, 0x14, 0x82, - 0x80, 0xCD, 0x9E, 0xD5, 0x6F, 0xED, 0xA4, 0x19, - 0x74, 0x05, 0x35, 0x54, 0xA4, 0x27, 0x67, 0xB8, - 0x3A, 0xD0, 0x43, 0xFD, 0x39, 0xDC, 0x04, 0x93, - 0x01, 0x45, 0x6C, 0x64, 0xBA, 0x46, 0x42, 0xA1, - 0x65, 0x3C, 0x23, 0x5A, 0x98, 0xA6, 0x02, 0x49, - 0xBC, 0xD6, 0xD3, 0xF7, 0x46, 0xB6, 0x31, 0xDF, - 0x92, 0x80, 0x14, 0xF6, 0xC5, 0xBF, 0x9C, 0x40 - ))) - prv = bytes2long(prv) - signature = signature[32:] + signature[:32] - - c = CURVES["id-GostR3410-2001-TestParamSet"] - pubX, pubY = public_key(c, prv) - self.assertSequenceEqual(long2bytes(pubX), pub_x) - self.assertSequenceEqual(long2bytes(pubY), pub_y) - s = sign(c, prv, digest) - self.assertTrue(verify(c, (pubX, pubY), digest, s)) - self.assertTrue(verify(c, (pubX, pubY), digest, signature)) - - def test_sequence(self): - c = CURVES["id-GostR3410-2001-TestParamSet"] - prv = prv_unmarshal(urandom(32)) - pubX, pubY = public_key(c, prv_unmarshal(prv_marshal(c, prv))) - for _ in range(20): - digest = urandom(32) - s = sign(c, prv, digest) - self.assertTrue(verify(c, (pubX, pubY), digest, s)) - - -class Test34102012(TestCase): - def test_1(self): - """Test vector from 34.10-2012 standard itself - """ - curve = CURVES["id-GostR3410-2001-TestParamSet"] - prv = bytes2long(hexdec("7A929ADE789BB9BE10ED359DD39A72C11B60961F49397EEE1D19CE9891EC3B28")) - digest = hexdec("2DFBC1B372D89A1188C09C52E0EEC61FCE52032AB1022E8E67ECE6672B043EE5") - rand = hexdec("77105C9B20BCD3122823C8CF6FCC7B956DE33814E95B7FE64FED924594DCEAB3") - signature = sign(curve, prv, digest, rand) - r = "41aa28d2f1ab148280cd9ed56feda41974053554a42767b83ad043fd39dc0493" - s = "01456c64ba4642a1653c235a98a60249bcd6d3f746b631df928014f6c5bf9c40" - self.assertSequenceEqual(hexenc(signature), s + r) - - def test_2(self): - """Test vector from 34.10-2012 standard itself - """ - curve = GOST3410Curve( - p=3623986102229003635907788753683874306021320925534678605086546150450856166624002482588482022271496854025090823603058735163734263822371964987228582907372403, - q=3623986102229003635907788753683874306021320925534678605086546150450856166623969164898305032863068499961404079437936585455865192212970734808812618120619743, - a=7, - b=1518655069210828534508950034714043154928747527740206436194018823352809982443793732829756914785974674866041605397883677596626326413990136959047435811826396, - x=1928356944067022849399309401243137598997786635459507974357075491307766592685835441065557681003184874819658004903212332884252335830250729527632383493573274, - y=2288728693371972859970012155529478416353562327329506180314497425931102860301572814141997072271708807066593850650334152381857347798885864807605098724013854, - ) - prv = bytes2long(hexdec("0BA6048AADAE241BA40936D47756D7C93091A0E8514669700EE7508E508B102072E8123B2200A0563322DAD2827E2714A2636B7BFD18AADFC62967821FA18DD4")) - digest = hexdec("3754F3CFACC9E0615C4F4A7C4D8DAB531B09B6F9C170C533A71D147035B0C5917184EE536593F4414339976C647C5D5A407ADEDB1D560C4FC6777D2972075B8C") - rand = hexdec("0359E7F4B1410FEACC570456C6801496946312120B39D019D455986E364F365886748ED7A44B3E794434006011842286212273A6D14CF70EA3AF71BB1AE679F1") - signature = sign(curve, prv, digest, rand) - r = "2f86fa60a081091a23dd795e1e3c689ee512a3c82ee0dcc2643c78eea8fcacd35492558486b20f1c9ec197c90699850260c93bcbcd9c5c3317e19344e173ae36" - s = "1081b394696ffe8e6585e7a9362d26b6325f56778aadbc081c0bfbe933d52ff5823ce288e8c4f362526080df7f70ce406a6eeb1f56919cb92a9853bde73e5b4a" - self.assertSequenceEqual(hexenc(signature), s + r) - - def test_gcl3(self): - """Test vector from libgcl3 - """ - p = bytes2long(bytes(bytearray(( - 0x45, 0x31, 0xAC, 0xD1, 0xFE, 0x00, 0x23, 0xC7, - 0x55, 0x0D, 0x26, 0x7B, 0x6B, 0x2F, 0xEE, 0x80, - 0x92, 0x2B, 0x14, 0xB2, 0xFF, 0xB9, 0x0F, 0x04, - 0xD4, 0xEB, 0x7C, 0x09, 0xB5, 0xD2, 0xD1, 0x5D, - 0xF1, 0xD8, 0x52, 0x74, 0x1A, 0xF4, 0x70, 0x4A, - 0x04, 0x58, 0x04, 0x7E, 0x80, 0xE4, 0x54, 0x6D, - 0x35, 0xB8, 0x33, 0x6F, 0xAC, 0x22, 0x4D, 0xD8, - 0x16, 0x64, 0xBB, 0xF5, 0x28, 0xBE, 0x63, 0x73, - )))) - q = bytes2long(bytes(bytearray(( - 0x45, 0x31, 0xAC, 0xD1, 0xFE, 0x00, 0x23, 0xC7, - 0x55, 0x0D, 0x26, 0x7B, 0x6B, 0x2F, 0xEE, 0x80, - 0x92, 0x2B, 0x14, 0xB2, 0xFF, 0xB9, 0x0F, 0x04, - 0xD4, 0xEB, 0x7C, 0x09, 0xB5, 0xD2, 0xD1, 0x5D, - 0xA8, 0x2F, 0x2D, 0x7E, 0xCB, 0x1D, 0xBA, 0xC7, - 0x19, 0x90, 0x5C, 0x5E, 0xEC, 0xC4, 0x23, 0xF1, - 0xD8, 0x6E, 0x25, 0xED, 0xBE, 0x23, 0xC5, 0x95, - 0xD6, 0x44, 0xAA, 0xF1, 0x87, 0xE6, 0xE6, 0xDF, - )))) - a = bytes2long(bytes(bytearray(( - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, - )))) - b = bytes2long(bytes(bytearray(( - 0x1C, 0xFF, 0x08, 0x06, 0xA3, 0x11, 0x16, 0xDA, - 0x29, 0xD8, 0xCF, 0xA5, 0x4E, 0x57, 0xEB, 0x74, - 0x8B, 0xC5, 0xF3, 0x77, 0xE4, 0x94, 0x00, 0xFD, - 0xD7, 0x88, 0xB6, 0x49, 0xEC, 0xA1, 0xAC, 0x43, - 0x61, 0x83, 0x40, 0x13, 0xB2, 0xAD, 0x73, 0x22, - 0x48, 0x0A, 0x89, 0xCA, 0x58, 0xE0, 0xCF, 0x74, - 0xBC, 0x9E, 0x54, 0x0C, 0x2A, 0xDD, 0x68, 0x97, - 0xFA, 0xD0, 0xA3, 0x08, 0x4F, 0x30, 0x2A, 0xDC, - )))) - x = bytes2long(bytes(bytearray(( - 0x24, 0xD1, 0x9C, 0xC6, 0x45, 0x72, 0xEE, 0x30, - 0xF3, 0x96, 0xBF, 0x6E, 0xBB, 0xFD, 0x7A, 0x6C, - 0x52, 0x13, 0xB3, 0xB3, 0xD7, 0x05, 0x7C, 0xC8, - 0x25, 0xF9, 0x10, 0x93, 0xA6, 0x8C, 0xD7, 0x62, - 0xFD, 0x60, 0x61, 0x12, 0x62, 0xCD, 0x83, 0x8D, - 0xC6, 0xB6, 0x0A, 0xA7, 0xEE, 0xE8, 0x04, 0xE2, - 0x8B, 0xC8, 0x49, 0x97, 0x7F, 0xAC, 0x33, 0xB4, - 0xB5, 0x30, 0xF1, 0xB1, 0x20, 0x24, 0x8A, 0x9A, - )))) - y = bytes2long(bytes(bytearray(( - 0x2B, 0xB3, 0x12, 0xA4, 0x3B, 0xD2, 0xCE, 0x6E, - 0x0D, 0x02, 0x06, 0x13, 0xC8, 0x57, 0xAC, 0xDD, - 0xCF, 0xBF, 0x06, 0x1E, 0x91, 0xE5, 0xF2, 0xC3, - 0xF3, 0x24, 0x47, 0xC2, 0x59, 0xF3, 0x9B, 0x2C, - 0x83, 0xAB, 0x15, 0x6D, 0x77, 0xF1, 0x49, 0x6B, - 0xF7, 0xEB, 0x33, 0x51, 0xE1, 0xEE, 0x4E, 0x43, - 0xDC, 0x1A, 0x18, 0xB9, 0x1B, 0x24, 0x64, 0x0B, - 0x6D, 0xBB, 0x92, 0xCB, 0x1A, 0xDD, 0x37, 0x1E, - )))) - prv = bytes(bytearray(( - 0x0B, 0xA6, 0x04, 0x8A, 0xAD, 0xAE, 0x24, 0x1B, - 0xA4, 0x09, 0x36, 0xD4, 0x77, 0x56, 0xD7, 0xC9, - 0x30, 0x91, 0xA0, 0xE8, 0x51, 0x46, 0x69, 0x70, - 0x0E, 0xE7, 0x50, 0x8E, 0x50, 0x8B, 0x10, 0x20, - 0x72, 0xE8, 0x12, 0x3B, 0x22, 0x00, 0xA0, 0x56, - 0x33, 0x22, 0xDA, 0xD2, 0x82, 0x7E, 0x27, 0x14, - 0xA2, 0x63, 0x6B, 0x7B, 0xFD, 0x18, 0xAA, 0xDF, - 0xC6, 0x29, 0x67, 0x82, 0x1F, 0xA1, 0x8D, 0xD4, - ))) - pub_x = bytes(bytearray(( - 0x11, 0x5D, 0xC5, 0xBC, 0x96, 0x76, 0x0C, 0x7B, - 0x48, 0x59, 0x8D, 0x8A, 0xB9, 0xE7, 0x40, 0xD4, - 0xC4, 0xA8, 0x5A, 0x65, 0xBE, 0x33, 0xC1, 0x81, - 0x5B, 0x5C, 0x32, 0x0C, 0x85, 0x46, 0x21, 0xDD, - 0x5A, 0x51, 0x58, 0x56, 0xD1, 0x33, 0x14, 0xAF, - 0x69, 0xBC, 0x5B, 0x92, 0x4C, 0x8B, 0x4D, 0xDF, - 0xF7, 0x5C, 0x45, 0x41, 0x5C, 0x1D, 0x9D, 0xD9, - 0xDD, 0x33, 0x61, 0x2C, 0xD5, 0x30, 0xEF, 0xE1, - ))) - pub_y = bytes(bytearray(( - 0x37, 0xC7, 0xC9, 0x0C, 0xD4, 0x0B, 0x0F, 0x56, - 0x21, 0xDC, 0x3A, 0xC1, 0xB7, 0x51, 0xCF, 0xA0, - 0xE2, 0x63, 0x4F, 0xA0, 0x50, 0x3B, 0x3D, 0x52, - 0x63, 0x9F, 0x5D, 0x7F, 0xB7, 0x2A, 0xFD, 0x61, - 0xEA, 0x19, 0x94, 0x41, 0xD9, 0x43, 0xFF, 0xE7, - 0xF0, 0xC7, 0x0A, 0x27, 0x59, 0xA3, 0xCD, 0xB8, - 0x4C, 0x11, 0x4E, 0x1F, 0x93, 0x39, 0xFD, 0xF2, - 0x7F, 0x35, 0xEC, 0xA9, 0x36, 0x77, 0xBE, 0xEC, - ))) - digest = bytes(bytearray(( - 0x37, 0x54, 0xF3, 0xCF, 0xAC, 0xC9, 0xE0, 0x61, - 0x5C, 0x4F, 0x4A, 0x7C, 0x4D, 0x8D, 0xAB, 0x53, - 0x1B, 0x09, 0xB6, 0xF9, 0xC1, 0x70, 0xC5, 0x33, - 0xA7, 0x1D, 0x14, 0x70, 0x35, 0xB0, 0xC5, 0x91, - 0x71, 0x84, 0xEE, 0x53, 0x65, 0x93, 0xF4, 0x41, - 0x43, 0x39, 0x97, 0x6C, 0x64, 0x7C, 0x5D, 0x5A, - 0x40, 0x7A, 0xDE, 0xDB, 0x1D, 0x56, 0x0C, 0x4F, - 0xC6, 0x77, 0x7D, 0x29, 0x72, 0x07, 0x5B, 0x8C, - ))) - signature = bytes(bytearray(( - 0x2F, 0x86, 0xFA, 0x60, 0xA0, 0x81, 0x09, 0x1A, - 0x23, 0xDD, 0x79, 0x5E, 0x1E, 0x3C, 0x68, 0x9E, - 0xE5, 0x12, 0xA3, 0xC8, 0x2E, 0xE0, 0xDC, 0xC2, - 0x64, 0x3C, 0x78, 0xEE, 0xA8, 0xFC, 0xAC, 0xD3, - 0x54, 0x92, 0x55, 0x84, 0x86, 0xB2, 0x0F, 0x1C, - 0x9E, 0xC1, 0x97, 0xC9, 0x06, 0x99, 0x85, 0x02, - 0x60, 0xC9, 0x3B, 0xCB, 0xCD, 0x9C, 0x5C, 0x33, - 0x17, 0xE1, 0x93, 0x44, 0xE1, 0x73, 0xAE, 0x36, - 0x10, 0x81, 0xB3, 0x94, 0x69, 0x6F, 0xFE, 0x8E, - 0x65, 0x85, 0xE7, 0xA9, 0x36, 0x2D, 0x26, 0xB6, - 0x32, 0x5F, 0x56, 0x77, 0x8A, 0xAD, 0xBC, 0x08, - 0x1C, 0x0B, 0xFB, 0xE9, 0x33, 0xD5, 0x2F, 0xF5, - 0x82, 0x3C, 0xE2, 0x88, 0xE8, 0xC4, 0xF3, 0x62, - 0x52, 0x60, 0x80, 0xDF, 0x7F, 0x70, 0xCE, 0x40, - 0x6A, 0x6E, 0xEB, 0x1F, 0x56, 0x91, 0x9C, 0xB9, - 0x2A, 0x98, 0x53, 0xBD, 0xE7, 0x3E, 0x5B, 0x4A, - ))) - prv = bytes2long(prv) - signature = signature[64:] + signature[:64] - c = GOST3410Curve(p, q, a, b, x, y) - pubX, pubY = public_key(c, prv) - self.assertSequenceEqual(long2bytes(pubX), pub_x) - self.assertSequenceEqual(long2bytes(pubY), pub_y) - s = sign(c, prv, digest) - self.assertTrue(verify(c, (pubX, pubY), digest, s)) - self.assertTrue(verify(c, (pubX, pubY), digest, signature)) - - def test_sequence(self): - c = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - prv = bytes2long(urandom(64)) - pubX, pubY = public_key(c, prv_unmarshal(prv_marshal(c, prv))) - for _ in range(20): - digest = urandom(64) - s = sign(c, prv, digest) - self.assertTrue(verify(c, (pubX, pubY), digest, s)) - self.assertNotIn(b"\x00" * 8, s) - - -class TestUVXYConversion(TestCase): - """Twisted Edwards to Weierstrass coordinates conversion and vice versa - """ - def test_curve1(self): - c = CURVES["id-tc26-gost-3410-2012-256-paramSetA"] - u, v = (0x0D, bytes2long(hexdec("60CA1E32AA475B348488C38FAB07649CE7EF8DBE87F22E81F92B2592DBA300E7"))) - self.assertEqual(uv2xy(c, u, v), (c.x, c.y)) - self.assertEqual(xy2uv(c, c.x, c.y), (u, v)) - - def test_curve2(self): - c = CURVES["id-tc26-gost-3410-2012-512-paramSetC"] - u, v = (0x12, bytes2long(hexdec("469AF79D1FB1F5E16B99592B77A01E2A0FDFB0D01794368D9A56117F7B38669522DD4B650CF789EEBF068C5D139732F0905622C04B2BAAE7600303EE73001A3D"))) - self.assertEqual(uv2xy(c, u, v), (c.x, c.y)) - self.assertEqual(xy2uv(c, c.x, c.y), (u, v)) - - -class Test34102012SESPAKE(TestCase): - """Test vectors for multiplication from :rfc:`8133` - """ - def test_curve1(self): - c = CURVES["id-GostR3410-2001-CryptoPro-A-ParamSet"] - q_ind = ( - 0xA69D51CAF1A309FA9E9B66187759B0174C274E080356F23CFCBFE84D396AD7BB, - 0x5D26F29ECC2E9AC0404DCF7986FA55FE94986362170F54B9616426A659786DAC, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F18127067" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0x59495655D1E7C7424C622485F575CCF121F3122D274101E8AB734CC9C9A9B45E, - 0x48D1C311D33C9B701F3B03618562A4A07A044E3AF31E3999E67B487778B53C62, - ) - ) - self.assertEqual( - c.exp(0x1F2538097D5A031FA68BBB43C84D12B3DE47B7061C0D5E24993E0C873CDBA6B3), - ( - 0xBBC77CF42DC1E62D06227935379B4AA4D14FEA4F565DDF4CB4FA4D31579F9676, - 0x8E16604A4AFDF28246684D4996274781F6CB80ABBBA1414C1513EC988509DABF, - ) - ) - self.assertEqual( - c.exp(0xDC497D9EF6324912FD367840EE509A2032AEDB1C0A890D133B45F596FCCBD45D), - ( - 0x6097341C1BE388E83E7CA2DF47FAB86E2271FD942E5B7B2EB2409E49F742BC29, - 0xC81AA48BDB4CA6FA0EF18B9788AE25FE30857AA681B3942217F9FED151BAB7D0, - ), - ) - - def test_curve2(self): - c = CURVES["id-GostR3410-2001-CryptoPro-B-ParamSet"] - q_ind = ( - 0x3D715A874A4B17CB3B517893A9794A2B36C89D2FFC693F01EE4CC27E7F49E399, - 0x1C5A641FCF7CE7E87CDF8CEA38F3DB3096EACE2FAD158384B53953365F4FE7FE, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F18127067" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0x6DC2AE26BC691FCA5A73D9C452790D15E34BA5404D92955B914C8D2662ABB985, - 0x3B02AAA9DD65AE30C335CED12F3154BBAC059F66B088306747453EDF6E5DB077, - ) - ) - self.assertEqual( - c.exp(0x499D72B90299CAB0DA1F8BE19D9122F622A13B32B730C46BD0664044F2144FAD), - ( - 0x61D6F916DB717222D74877F179F7EBEF7CD4D24D8C1F523C048E34A1DF30F8DD, - 0x3EC48863049CFCFE662904082E78503F4973A4E105E2F1B18C69A5E7FB209000, - ) - ) - self.assertEqual( - c.exp(0x0F69FF614957EF83668EDC2D7ED614BE76F7B253DB23C5CC9C52BF7DF8F4669D), - ( - 0x33BC6F7E9C0BA10CFB2B72546C327171295508EA97F8C8BA9F890F2478AB4D6C, - 0x75D57B396C396F492F057E9222CCC686437A2AAD464E452EF426FC8EEED1A4A6, - ), - ) - - def test_curve3(self): - c = CURVES["id-GostR3410-2001-CryptoPro-C-ParamSet"] - q_ind = ( - 0x1E36383E43BB6CFA2917167D71B7B5DD3D6D462B43D7C64282AE67DFBEC2559D, - 0x137478A9F721C73932EA06B45CF72E37EB78A63F29A542E563C614650C8B6399, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F18127067" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0x945821DAF91E158B839939630655A3B21FF3E146D27041E86C05650EB3B46B59, - 0x3A0C2816AC97421FA0E879605F17F0C9C3EB734CFF196937F6284438D70BDC48, - ) - ) - self.assertEqual( - c.exp(0x3A54AC3F19AD9D0B1EAC8ACDCEA70E581F1DAC33D13FEAFD81E762378639C1A8), - ( - 0x96B7F09C94D297C257A7DA48364C0076E59E48D221CBA604AE111CA3933B446A, - 0x54E4953D86B77ECCEB578500931E822300F7E091F79592CA202A020D762C34A6, - ) - ) - self.assertEqual( - c.exp(0x448781782BF7C0E52A1DD9E6758FD3482D90D3CFCCF42232CF357E59A4D49FD4), - ( - 0x4B9C0AB55A938121F282F48A2CC4396EB16E7E0068B495B0C1DD4667786A3EB7, - 0x223460AA8E09383E9DF9844C5A0F2766484738E5B30128A171B69A77D9509B96, - ), - ) - - def test_curve4(self): - c = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - q_ind = ( - 0x2A17F8833A32795327478871B5C5E88AEFB91126C64B4B8327289BEA62559425D18198F133F400874328B220C74497CD240586CB249E158532CB8090776CD61C, - 0x728F0C4A73B48DA41CE928358FAD26B47A6E094E9362BAE82559F83CDDC4EC3A4676BD3707EDEAF4CD85E99695C64C241EDC622BE87DC0CF87F51F4367F723C5, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F181270671C6213E3930EFDDA26451792C6208122EE60D200520D695DFD9F5F0FD5ABA702" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0x0C0AB53D0E0A9C607CAD758F558915A0A7DC5DC87B45E9A58FDDF30EC3385960283E030CD322D9E46B070637785FD49D2CD711F46807A24C40AF9A42C8E2D740, - 0xDF93A8012B86D3A3D4F8A4D487DA15FC739EB31B20B3B0E8C8C032AAF8072C6337CF7D5B404719E5B4407C41D9A3216A08CA69C271484E9ED72B8AAA52E28B8B, - ) - ) - self.assertEqual( - c.exp(0x3CE54325DB52FE798824AEAD11BB16FA766857D04A4AF7D468672F16D90E7396046A46F815693E85B1CE5464DA9270181F82333B0715057BBE8D61D400505F0E), - ( - 0xB93093EB0FCC463239B7DF276E09E592FCFC9B635504EA4531655D76A0A3078E2B4E51CFE2FA400CC5DE9FBE369DB204B3E8ED7EDD85EE5CCA654C1AED70E396, - 0x809770B8D910EA30BD2FA89736E91DC31815D2D9B31128077EEDC371E9F69466F497DC64DD5B1FADC587F860EE256109138C4A9CD96B628E65A8F590520FC882, - ) - ) - self.assertEqual( - c.exp(0xB5C286A79AA8E97EC0E19BC1959A1D15F12F8C97870BA9D68CC12811A56A3BB11440610825796A49D468CDC9C2D02D76598A27973D5960C5F50BCE28D8D345F4), - ( - 0x238B38644E440452A99FA6B93D9FD7DA0CB83C32D3C1E3CFE5DF5C3EB0F9DB91E588DAEDC849EA2FB867AE855A21B4077353C0794716A6480995113D8C20C7AF, - 0xB2273D5734C1897F8D15A7008B862938C8C74CA7E877423D95243EB7EBD02FD2C456CF9FC956F078A59AA86F19DD1075E5167E4ED35208718EA93161C530ED14, - ), - ) - - def test_curve5(self): - c = CURVES["id-tc26-gost-3410-12-512-paramSetB"] - q_ind = ( - 0x7E1FAE8285E035BEC244BEF2D0E5EBF436633CF50E55231DEA9C9CF21D4C8C33DF85D4305DE92971F0A4B4C07E00D87BDBC720EB66E49079285AAF12E0171149, - 0x2CC89998B875D4463805BA0D858A196592DB20AB161558FF2F4EF7A85725D20953967AE621AFDEAE89BB77C83A2528EF6FCE02F68BDA4679D7F2704947DBC408, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F181270671C6213E3930EFDDA26451792C6208122EE60D200520D695DFD9F5F0FD5ABA702" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0x7D03E65B8050D1E12CBB601A17B9273B0E728F5021CD47C8A4DD822E4627BA5F9C696286A2CDDA9A065509866B4DEDEDC4A118409604AD549F87A60AFA621161, - 0x16037DAD45421EC50B00D50BDC6AC3B85348BC1D3A2F85DB27C3373580FEF87C2C743B7ED30F22BE22958044E716F93A61CA3213A361A2797A16A3AE62957377, - ) - ) - self.assertEqual( - c.exp(0x715E893FA639BF341296E0623E6D29DADF26B163C278767A7982A989462A3863FE12AEF8BD403D59C4DC4720570D4163DB0805C7C10C4E818F9CB785B04B9997), - ( - 0x10C479EA1C04D3C2C02B0576A9C42D96226FF033C1191436777F66916030D87D02FB93738ED7669D07619FFCE7C1F3C4DB5E5DF49E2186D6FA1E2EB5767602B9, - 0x039F6044191404E707F26D59D979136A831CCE43E1C5F0600D1DDF8F39D0CA3D52FBD943BF04DDCED1AA2CE8F5EBD7487ACDEF239C07D015084D796784F35436, - ) - ) - self.assertEqual( - c.exp(0x30FA8C2B4146C2DBBE82BED04D7378877E8C06753BD0A0FF71EBF2BEFE8DA8F3DC0836468E2CE7C5C961281B6505140F8407413F03C2CB1D201EA1286CE30E6D), - ( - 0x34C0149E7BB91AE377B02573FCC48AF7BFB7B16DEB8F9CE870F384688E3241A3A868588CC0EF4364CCA67D17E3260CD82485C202ADC76F895D5DF673B1788E67, - 0x608E944929BD643569ED5189DB871453F13333A1EAF82B2FE1BE8100E775F13DD9925BD317B63BFAF05024D4A738852332B64501195C1B2EF789E34F23DDAFC5, - ), - ) - - def test_curve6(self): - c = CURVES["id-tc26-gost-3410-2012-256-paramSetA"] - q_ind = ( - 0xB51ADF93A40AB15792164FAD3352F95B66369EB2A4EF5EFAE32829320363350E, - 0x74A358CC08593612F5955D249C96AFB7E8B0BB6D8BD2BBE491046650D822BE18, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F18127067" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0xDBF99827078956812FA48C6E695DF589DEF1D18A2D4D35A96D75BF6854237629, - 0x9FDDD48BFBC57BEE1DA0CFF282884F284D471B388893C48F5ECB02FC18D67589, - ) - ) - self.assertEqual( - c.exp(0x147B72F6684FB8FD1B418A899F7DBECAF5FCE60B13685BAA95328654A7F0707F), - ( - 0x33FBAC14EAE538275A769417829C431BD9FA622B6F02427EF55BD60EE6BC2888, - 0x22F2EBCF960A82E6CDB4042D3DDDA511B2FBA925383C2273D952EA2D406EAE46, - ) - ) - self.assertEqual( - c.exp(0x30D5CFADAA0E31B405E6734C03EC4C5DF0F02F4BA25C9A3B320EE6453567B4CB), - ( - 0x2B2D89FAB735433970564F2F28CFA1B57D640CB902BC6334A538F44155022CB2, - 0x10EF6A82EEF1E70F942AA81D6B4CE5DEC0DDB9447512962874870E6F2849A96F, - ), - ) - - def test_curve7(self): - c = CURVES["id-tc26-gost-3410-2012-512-paramSetC"] - q_ind = ( - 0x489C91784E02E98F19A803ABCA319917F37689E5A18965251CE2FF4E8D8B298F5BA7470F9E0E713487F96F4A8397B3D09A270C9D367EB5E0E6561ADEEB51581D, - 0x684EA885ACA64EAF1B3FEE36C0852A3BE3BD8011B0EF18E203FF87028D6EB5DB2C144A0DCC71276542BFD72CA2A43FA4F4939DA66D9A60793C704A8C94E16F18, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F181270671C6213E3930EFDDA26451792C6208122EE60D200520D695DFD9F5F0FD5ABA702" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0x0185AE6271A81BB7F236A955F7CAA26FB63849813C0287D96C83A15AE6B6A86467AB13B6D88CE8CD7DC2E5B97FF5F28FAC2C108F2A3CF3DB5515C9E6D7D210E8, - 0xED0220F92EF771A71C64ECC77986DB7C03D37B3E2AB3E83F32CE5E074A762EC08253C9E2102B87532661275C4B1D16D2789CDABC58ACFDF7318DE70AB64F09B8, - ) - ) - self.assertEqual( - c.exp(0x332F930421D14CFE260042159F18E49FD5A54167E94108AD80B1DE60B13DE7999A34D611E63F3F870E5110247DF8EC7466E648ACF385E52CCB889ABF491EDFF0), - ( - 0x561655966D52952E805574F4281F1ED3A2D498932B00CBA9DECB42837F09835BFFBFE2D84D6B6B242FE7B57F92E1A6F2413E12DDD6383E4437E13D72693469AD, - 0xF6B18328B2715BD7F4178615273A36135BC0BF62F7D8BB9F080164AD36470AD03660F51806C64C6691BADEF30F793720F8E3FEAED631D6A54A4C372DCBF80E82, - ) - ) - self.assertEqual( - c.exp(0x38481771E7D054F96212686B613881880BD8A6C89DDBC656178F014D2C093432A033EE10415F13A160D44C2AD61E6E2E05A7F7EC286BCEA3EA4D4D53F8634FA2), - ( - 0xB7C5818687083433BC1AFF61CB5CA79E38232025E0C1F123B8651E62173CE6873F3E6FFE7281C2E45F4F524F66B0C263616ED08FD210AC4355CA3292B51D71C3, - 0x497F14205DBDC89BDDAF50520ED3B1429AD30777310186BE5E68070F016A44E0C766DB08E8AC23FBDFDE6D675AA4DF591EB18BA0D348DF7AA40973A2F1DCFA55, - ), - ) diff --git a/pygost-5.13/build/lib/pygost/test_gost3410_vko.py b/pygost-5.13/build/lib/pygost/test_gost3410_vko.py deleted file mode 100644 index a8e298e..0000000 --- a/pygost-5.13/build/lib/pygost/test_gost3410_vko.py +++ /dev/null @@ -1,125 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from unittest import TestCase - -from pygost.gost3410 import CURVES -from pygost.gost3410 import prv_unmarshal -from pygost.gost3410 import pub_unmarshal -from pygost.gost3410 import public_key -from pygost.gost3410_vko import kek_34102001 -from pygost.gost3410_vko import kek_34102012256 -from pygost.gost3410_vko import kek_34102012512 -from pygost.gost3410_vko import ukm_unmarshal -from pygost.utils import bytes2long -from pygost.utils import hexdec - - -class TestVKO34102001(TestCase): - def test_vector(self): - curve = CURVES["id-GostR3410-2001-TestParamSet"] - ukm = ukm_unmarshal(hexdec("5172be25f852a233")) - prv1 = prv_unmarshal(hexdec("1df129e43dab345b68f6a852f4162dc69f36b2f84717d08755cc5c44150bf928")) - prv2 = prv_unmarshal(hexdec("5b9356c6474f913f1e83885ea0edd5df1a43fd9d799d219093241157ac9ed473")) - kek = hexdec("ee4618a0dbb10cb31777b4b86a53d9e7ef6cb3e400101410f0c0f2af46c494a6") - pub1 = public_key(curve, prv1) - pub2 = public_key(curve, prv2) - self.assertSequenceEqual(kek_34102001(curve, prv1, pub2, ukm), kek) - self.assertSequenceEqual(kek_34102001(curve, prv2, pub1, ukm), kek) - - def test_sequence(self): - curve = CURVES["id-GostR3410-2001-TestParamSet"] - for _ in range(10): - ukm = ukm_unmarshal(urandom(8)) - prv1 = bytes2long(urandom(32)) - prv2 = bytes2long(urandom(32)) - pub1 = public_key(curve, prv1) - pub2 = public_key(curve, prv2) - kek1 = kek_34102001(curve, prv1, pub2, ukm) - kek2 = kek_34102001(curve, prv2, pub1, ukm) - self.assertSequenceEqual(kek1, kek2) - kek1 = kek_34102001(curve, prv1, pub1, ukm) - kek2 = kek_34102001(curve, prv2, pub2, ukm) - self.assertNotEqual(kek1, kek2) - - -class TestVKO34102012256(TestCase): - """RFC 7836 - """ - def test_vector(self): - curve = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - ukm = ukm_unmarshal(hexdec("1d80603c8544c727")) - prvA = prv_unmarshal(hexdec("c990ecd972fce84ec4db022778f50fcac726f46708384b8d458304962d7147f8c2db41cef22c90b102f2968404f9b9be6d47c79692d81826b32b8daca43cb667")) - pubA = pub_unmarshal(hexdec("aab0eda4abff21208d18799fb9a8556654ba783070eba10cb9abb253ec56dcf5d3ccba6192e464e6e5bcb6dea137792f2431f6c897eb1b3c0cc14327b1adc0a7914613a3074e363aedb204d38d3563971bd8758e878c9db11403721b48002d38461f92472d40ea92f9958c0ffa4c93756401b97f89fdbe0b5e46e4a4631cdb5a")) - prvB = prv_unmarshal(hexdec("48c859f7b6f11585887cc05ec6ef1390cfea739b1a18c0d4662293ef63b79e3b8014070b44918590b4b996acfea4edfbbbcccc8c06edd8bf5bda92a51392d0db")) - pubB = pub_unmarshal(hexdec("192fe183b9713a077253c72c8735de2ea42a3dbc66ea317838b65fa32523cd5efca974eda7c863f4954d1147f1f2b25c395fce1c129175e876d132e94ed5a65104883b414c9b592ec4dc84826f07d0b6d9006dda176ce48c391e3f97d102e03bb598bf132a228a45f7201aba08fc524a2d77e43a362ab022ad4028f75bde3b79")) - vko = hexdec("c9a9a77320e2cc559ed72dce6f47e2192ccea95fa648670582c054c0ef36c221") - self.assertSequenceEqual(kek_34102012256(curve, prvA, pubB, ukm), vko) - self.assertSequenceEqual(kek_34102012256(curve, prvB, pubA, ukm), vko) - - def test_sequence(self): - curve = CURVES["id-tc26-gost-3410-2012-256-paramSetA"] - for _ in range(10): - ukm = ukm_unmarshal(urandom(8)) - prv1 = bytes2long(urandom(32)) - prv2 = bytes2long(urandom(32)) - pub1 = public_key(curve, prv1) - pub2 = public_key(curve, prv2) - kek1 = kek_34102012256(curve, prv1, pub2, ukm) - kek2 = kek_34102012256(curve, prv2, pub1, ukm) - self.assertSequenceEqual(kek1, kek2) - kek1 = kek_34102012256(curve, prv1, pub1, ukm) - kek2 = kek_34102012256(curve, prv2, pub2, ukm) - self.assertNotEqual(kek1, kek2) - - def test_pub_is_not_on_curve(self): - with self.assertRaises(ValueError): - kek_34102012256( - CURVES["id-tc26-gost-3410-2012-256-paramSetA"], - bytes2long(urandom(32)), - pub_unmarshal(urandom(64)), - ) - - -class TestVKO34102012512(TestCase): - """RFC 7836 - """ - def test_vector(self): - curve = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - ukm = ukm_unmarshal(hexdec("1d80603c8544c727")) - prvA = prv_unmarshal(hexdec("c990ecd972fce84ec4db022778f50fcac726f46708384b8d458304962d7147f8c2db41cef22c90b102f2968404f9b9be6d47c79692d81826b32b8daca43cb667")) - pubA = pub_unmarshal(hexdec("aab0eda4abff21208d18799fb9a8556654ba783070eba10cb9abb253ec56dcf5d3ccba6192e464e6e5bcb6dea137792f2431f6c897eb1b3c0cc14327b1adc0a7914613a3074e363aedb204d38d3563971bd8758e878c9db11403721b48002d38461f92472d40ea92f9958c0ffa4c93756401b97f89fdbe0b5e46e4a4631cdb5a")) - prvB = prv_unmarshal(hexdec("48c859f7b6f11585887cc05ec6ef1390cfea739b1a18c0d4662293ef63b79e3b8014070b44918590b4b996acfea4edfbbbcccc8c06edd8bf5bda92a51392d0db")) - pubB = pub_unmarshal(hexdec("192fe183b9713a077253c72c8735de2ea42a3dbc66ea317838b65fa32523cd5efca974eda7c863f4954d1147f1f2b25c395fce1c129175e876d132e94ed5a65104883b414c9b592ec4dc84826f07d0b6d9006dda176ce48c391e3f97d102e03bb598bf132a228a45f7201aba08fc524a2d77e43a362ab022ad4028f75bde3b79")) - vko = hexdec("79f002a96940ce7bde3259a52e015297adaad84597a0d205b50e3e1719f97bfa7ee1d2661fa9979a5aa235b558a7e6d9f88f982dd63fc35a8ec0dd5e242d3bdf") - self.assertSequenceEqual(kek_34102012512(curve, prvA, pubB, ukm), vko) - self.assertSequenceEqual(kek_34102012512(curve, prvB, pubA, ukm), vko) - - def test_sequence(self): - curve = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - for _ in range(10): - ukm = ukm_unmarshal(urandom(8)) - prv1 = bytes2long(urandom(32)) - prv2 = bytes2long(urandom(32)) - pub1 = public_key(curve, prv1) - pub2 = public_key(curve, prv2) - kek1 = kek_34102012512(curve, prv1, pub2, ukm) - kek2 = kek_34102012512(curve, prv2, pub1, ukm) - self.assertSequenceEqual(kek1, kek2) - kek1 = kek_34102012512(curve, prv1, pub1, ukm) - kek2 = kek_34102012512(curve, prv2, pub2, ukm) - self.assertNotEqual(kek1, kek2) diff --git a/pygost-5.13/build/lib/pygost/test_gost34112012.py b/pygost-5.13/build/lib/pygost/test_gost34112012.py deleted file mode 100644 index c7c2df9..0000000 --- a/pygost-5.13/build/lib/pygost/test_gost34112012.py +++ /dev/null @@ -1,159 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from random import randint -from unittest import skip -from unittest import TestCase -import hmac - -from pygost import gost34112012256 -from pygost import gost34112012512 -from pygost.gost34112012256 import GOST34112012256 -from pygost.gost34112012512 import GOST34112012512 -from pygost.gost34112012512 import pbkdf2 -from pygost.utils import hexdec -from pygost.utils import hexenc - - -class TestCopy(TestCase): - def runTest(self): - m = GOST34112012256() - c = m.copy() - m.update(b"foobar") - c.update(b"foo") - c.update(b"bar") - self.assertSequenceEqual(m.digest(), c.digest()) - - -class TestSymmetric(TestCase): - def runTest(self): - chunks = [] - for _ in range(randint(1, 10)): - chunks.append(urandom(randint(20, 80))) - m = GOST34112012256() - for chunk in chunks: - m.update(chunk) - self.assertSequenceEqual( - m.hexdigest(), - GOST34112012256(b"".join(chunks)).hexdigest(), - ) - - -class TestHMAC(TestCase): - """RFC 7836 - """ - def test_256(self): - for digestmod in (GOST34112012256, gost34112012256): - self.assertSequenceEqual( - hmac.new( - key=hexdec("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), - msg=hexdec("0126bdb87800af214341456563780100"), - digestmod=digestmod, - ).hexdigest(), - "a1aa5f7de402d7b3d323f2991c8d4534013137010a83754fd0af6d7cd4922ed9", - ) - - def test_512(self): - for digestmod in (GOST34112012512, gost34112012512): - self.assertSequenceEqual( - hmac.new( - key=hexdec("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), - msg=hexdec("0126bdb87800af214341456563780100"), - digestmod=digestmod, - ).hexdigest(), - "a59bab22ecae19c65fbde6e5f4e9f5d8549d31f037f9df9b905500e171923a773d5f1530f2ed7e964cb2eedc29e9ad2f3afe93b2814f79f5000ffc0366c251e6", - ) - - -class TestVectors(TestCase): - def test_m1(self): - m = hexdec("323130393837363534333231303938373635343332313039383736353433323130393837363534333231303938373635343332313039383736353433323130")[::-1] - self.assertSequenceEqual( - GOST34112012512(m).digest(), - hexdec("486f64c1917879417fef082b3381a4e211c324f074654c38823a7b76f830ad00fa1fbae42b1285c0352f227524bc9ab16254288dd6863dccd5b9f54a1ad0541b")[::-1] - ) - self.assertSequenceEqual( - GOST34112012256(m).digest(), - hexdec("00557be5e584fd52a449b16b0251d05d27f94ab76cbaa6da890b59d8ef1e159d")[::-1] - ) - - def test_m2(self): - m = u"Се ветри, Стрибожи внуци, веютъ с моря стрелами на храбрыя плъкы Игоревы".encode("cp1251") - self.assertSequenceEqual(m, hexdec("fbe2e5f0eee3c820fbeafaebef20fffbf0e1e0f0f520e0ed20e8ece0ebe5f0f2f120fff0eeec20f120faf2fee5e2202ce8f6f3ede220e8e6eee1e8f0f2d1202ce8f0f2e5e220e5d1")[::-1]) - self.assertSequenceEqual( - GOST34112012512(m).digest(), - hexdec("28fbc9bada033b1460642bdcddb90c3fb3e56c497ccd0f62b8a2ad4935e85f037613966de4ee00531ae60f3b5a47f8dae06915d5f2f194996fcabf2622e6881e")[::-1] - ) - self.assertSequenceEqual( - GOST34112012256(m).digest(), - hexdec("508f7e553c06501d749a66fc28c6cac0b005746d97537fa85d9e40904efed29d")[::-1] - ) - - def test_habr144(self): - """Test vector from https://habr.com/ru/post/452200/ - """ - m = hexdec("d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff0900060000000000000000000000010000000100000000000000001000002400000001000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - self.assertSequenceEqual( - GOST34112012256(m).hexdigest(), - "c766085540caaa8953bfcf7a1ba220619cee50d65dc242f82f23ba4b180b18e0", - ) - - -class TestPBKDF2(TestCase): - """http://tc26.ru/.../R_50.1.111-2016.pdf - """ - def test_1(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 1, 64)), - "64770af7f748c3b1c9ac831dbcfd85c26111b30a8a657ddc3056b80ca73e040d2854fd36811f6d825cc4ab66ec0a68a490a9e5cf5156b3a2b7eecddbf9a16b47", - ) - - def test_2(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 2, 64)), - "5a585bafdfbb6e8830d6d68aa3b43ac00d2e4aebce01c9b31c2caed56f0236d4d34b2b8fbd2c4e89d54d46f50e47d45bbac301571743119e8d3c42ba66d348de", - ) - - def test_3(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 4096, 64)), - "e52deb9a2d2aaff4e2ac9d47a41f34c20376591c67807f0477e32549dc341bc7867c09841b6d58e29d0347c996301d55df0d34e47cf68f4e3c2cdaf1d9ab86c3", - ) - - @skip("it takes too long") - def test_4(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 1677216, 64)), - "49e4843bba76e300afe24c4d23dc7392def12f2c0e244172367cd70a8982ac361adb601c7e2a314e8cb7b1e9df840e36ab5615be5d742b6cf203fb55fdc48071", - ) - - def test_5(self): - self.assertSequenceEqual( - hexenc(pbkdf2( - b"passwordPASSWORDpassword", - b"saltSALTsaltSALTsaltSALTsaltSALTsalt", - 4096, - 100, - )), - "b2d8f1245fc4d29274802057e4b54e0a0753aa22fc53760b301cf008679e58fe4bee9addcae99ba2b0b20f431a9c5e50f395c89387d0945aedeca6eb4015dfc2bd2421ee9bb71183ba882ceebfef259f33f9e27dc6178cb89dc37428cf9cc52a2baa2d3a", - ) - - def test_6(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"pass\x00word", b"sa\x00lt", 4096, 64)), - "50df062885b69801a3c10248eb0a27ab6e522ffeb20c991c660f001475d73a4e167f782c18e97e92976d9c1d970831ea78ccb879f67068cdac1910740844e830", - ) diff --git a/pygost-5.13/build/lib/pygost/test_gost341194.py b/pygost-5.13/build/lib/pygost/test_gost341194.py deleted file mode 100644 index 4ffa6d6..0000000 --- a/pygost-5.13/build/lib/pygost/test_gost341194.py +++ /dev/null @@ -1,200 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 unittest import skip -from unittest import TestCase -import hmac - -from pygost import gost341194 -from pygost.gost341194 import GOST341194 -from pygost.gost341194 import pbkdf2 -from pygost.utils import hexenc - - -class TestCopy(TestCase): - def runTest(self): - m = GOST341194() - c = m.copy() - m.update(b"foobar") - c.update(b"foo") - c.update(b"bar") - self.assertSequenceEqual(m.digest(), c.digest()) - - -class TestHMACPEP247(TestCase): - def runTest(self): - h = hmac.new(b"foo", digestmod=gost341194) - h.update(b"foobar") - h.digest() - - -class TestVectors(TestCase): - def test_empty(self): - self.assertSequenceEqual( - GOST341194(b"", "id-GostR3411-94-TestParamSet").hexdigest(), - "ce85b99cc46752fffee35cab9a7b0278abb4c2d2055cff685af4912c49490f8d", - ) - - def test_a(self): - self.assertSequenceEqual( - GOST341194(b"a", "id-GostR3411-94-TestParamSet").hexdigest(), - "d42c539e367c66e9c88a801f6649349c21871b4344c6a573f849fdce62f314dd", - ) - - def test_abc(self): - self.assertSequenceEqual( - GOST341194(b"abc", "id-GostR3411-94-TestParamSet").hexdigest(), - "f3134348c44fb1b2a277729e2285ebb5cb5e0f29c975bc753b70497c06a4d51d", - ) - - def test_message_digest(self): - self.assertSequenceEqual( - GOST341194(b"message digest", "id-GostR3411-94-TestParamSet").hexdigest(), - "ad4434ecb18f2c99b60cbe59ec3d2469582b65273f48de72db2fde16a4889a4d", - ) - - def test_Us(self): - self.assertSequenceEqual( - GOST341194(128 * b"U", "id-GostR3411-94-TestParamSet").hexdigest(), - "53a3a3ed25180cef0c1d85a074273e551c25660a87062a52d926a9e8fe5733a4", - ) - - def test_dog(self): - self.assertSequenceEqual( - GOST341194(b"The quick brown fox jumps over the lazy dog", "id-GostR3411-94-TestParamSet",).hexdigest(), - "77b7fa410c9ac58a25f49bca7d0468c9296529315eaca76bd1a10f376d1f4294", - ) - - def test_cog(self): - self.assertSequenceEqual( - GOST341194(b"The quick brown fox jumps over the lazy cog", "id-GostR3411-94-TestParamSet",).hexdigest(), - "a3ebc4daaab78b0be131dab5737a7f67e602670d543521319150d2e14eeec445", - ) - - def test_rfc32(self): - self.assertSequenceEqual( - GOST341194(b"This is message, length=32 bytes", "id-GostR3411-94-TestParamSet",).hexdigest(), - "b1c466d37519b82e8319819ff32595e047a28cb6f83eff1c6916a815a637fffa", - ) - - def test_rfc50(self): - self.assertSequenceEqual( - GOST341194(b"Suppose the original message has length = 50 bytes", "id-GostR3411-94-TestParamSet",).hexdigest(), - "471aba57a60a770d3a76130635c1fbea4ef14de51f78b4ae57dd893b62f55208", - ) - - -class TestVectorsCryptoPro(TestCase): - """CryptoPro S-box test vectors - """ - def test_empty(self): - self.assertSequenceEqual( - GOST341194(b"", "id-GostR3411-94-CryptoProParamSet").hexdigest(), - "981e5f3ca30c841487830f84fb433e13ac1101569b9c13584ac483234cd656c0", - ) - - def test_a(self): - self.assertSequenceEqual( - GOST341194(b"a", "id-GostR3411-94-CryptoProParamSet").hexdigest(), - "e74c52dd282183bf37af0079c9f78055715a103f17e3133ceff1aacf2f403011", - ) - - def test_abc(self): - self.assertSequenceEqual( - GOST341194(b"abc", "id-GostR3411-94-CryptoProParamSet").hexdigest(), - "b285056dbf18d7392d7677369524dd14747459ed8143997e163b2986f92fd42c", - ) - - def test_message_digest(self): - self.assertSequenceEqual( - GOST341194(b"message digest", "id-GostR3411-94-CryptoProParamSet",).hexdigest(), - "bc6041dd2aa401ebfa6e9886734174febdb4729aa972d60f549ac39b29721ba0", - ) - - def test_dog(self): - self.assertSequenceEqual( - GOST341194(b"The quick brown fox jumps over the lazy dog", "id-GostR3411-94-CryptoProParamSet",).hexdigest(), - "9004294a361a508c586fe53d1f1b02746765e71b765472786e4770d565830a76", - ) - - def test_32(self): - self.assertSequenceEqual( - GOST341194(b"This is message, length=32 bytes", "id-GostR3411-94-CryptoProParamSet",).hexdigest(), - "2cefc2f7b7bdc514e18ea57fa74ff357e7fa17d652c75f69cb1be7893ede48eb", - ) - - def test_50(self): - self.assertSequenceEqual( - GOST341194(b"Suppose the original message has length = 50 bytes", "id-GostR3411-94-CryptoProParamSet",).hexdigest(), - "c3730c5cbccacf915ac292676f21e8bd4ef75331d9405e5f1a61dc3130a65011", - ) - - def test_Us(self): - self.assertSequenceEqual( - GOST341194(128 * b"U", "id-GostR3411-94-CryptoProParamSet").hexdigest(), - "1c4ac7614691bbf427fa2316216be8f10d92edfd37cd1027514c1008f649c4e8", - ) - - -class TestPBKDF2(TestCase): - """http://tc26.ru/methods/containers_v1/Addition_to_PKCS5_v1_0.pdf test vectors - """ - def test_1(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 1, 32)), - "7314e7c04fb2e662c543674253f68bd0b73445d07f241bed872882da21662d58", - ) - - def test_2(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 2, 32)), - "990dfa2bd965639ba48b07b792775df79f2db34fef25f274378872fed7ed1bb3", - ) - - def test_3(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 4096, 32)), - "1f1829a94bdff5be10d0aeb36af498e7a97467f3b31116a5a7c1afff9deadafe", - ) - - @skip("it takes too long") - def test_4(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 16777216, 32)), - "a57ae5a6088396d120850c5c09de0a525100938a59b1b5c3f7810910d05fcd97", - ) - - def test_5(self): - self.assertSequenceEqual( - hexenc(pbkdf2( - b"passwordPASSWORDpassword", - b"saltSALTsaltSALTsaltSALTsaltSALTsalt", - 4096, - 40, - )), - "788358c69cb2dbe251a7bb17d5f4241f265a792a35becde8d56f326b49c85047b7638acb4764b1fd", - ) - - def test_6(self): - self.assertSequenceEqual( - hexenc(pbkdf2( - b"pass\x00word", - b"sa\x00lt", - 4096, - 20, - )), - "43e06c5590b08c0225242373127edf9c8e9c3291", - ) diff --git a/pygost-5.13/build/lib/pygost/test_gost3412.py b/pygost-5.13/build/lib/pygost/test_gost3412.py deleted file mode 100644 index 5dbb200..0000000 --- a/pygost-5.13/build/lib/pygost/test_gost3412.py +++ /dev/null @@ -1,137 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 unittest import TestCase - -from pygost.gost3412 import C -from pygost.gost3412 import GOST3412Kuznechik -from pygost.gost3412 import GOST3412Magma -from pygost.gost3412 import L -from pygost.gost3412 import PI -from pygost.utils import hexdec - - -def S(blk): - return bytearray(PI[v] for v in blk) - - -def R(blk): - return L(blk, rounds=1) - - -class STest(TestCase): - def test_vec1(self): - blk = bytearray(hexdec("ffeeddccbbaa99881122334455667700")) - self.assertSequenceEqual(S(blk), hexdec("b66cd8887d38e8d77765aeea0c9a7efc")) - - def test_vec2(self): - blk = bytearray(hexdec("b66cd8887d38e8d77765aeea0c9a7efc")) - self.assertSequenceEqual(S(blk), hexdec("559d8dd7bd06cbfe7e7b262523280d39")) - - def test_vec3(self): - blk = bytearray(hexdec("559d8dd7bd06cbfe7e7b262523280d39")) - self.assertSequenceEqual(S(blk), hexdec("0c3322fed531e4630d80ef5c5a81c50b")) - - def test_vec4(self): - blk = bytearray(hexdec("0c3322fed531e4630d80ef5c5a81c50b")) - self.assertSequenceEqual(S(blk), hexdec("23ae65633f842d29c5df529c13f5acda")) - - -class RTest(TestCase): - def test_vec1(self): - blk = bytearray(hexdec("00000000000000000000000000000100")) - self.assertSequenceEqual(R(blk), hexdec("94000000000000000000000000000001")) - - def test_vec2(self): - blk = bytearray(hexdec("94000000000000000000000000000001")) - self.assertSequenceEqual(R(blk), hexdec("a5940000000000000000000000000000")) - - def test_vec3(self): - blk = bytearray(hexdec("a5940000000000000000000000000000")) - self.assertSequenceEqual(R(blk), hexdec("64a59400000000000000000000000000")) - - def test_vec4(self): - blk = bytearray(hexdec("64a59400000000000000000000000000")) - self.assertSequenceEqual(R(blk), hexdec("0d64a594000000000000000000000000")) - - -class LTest(TestCase): - def test_vec1(self): - blk = bytearray(hexdec("64a59400000000000000000000000000")) - self.assertSequenceEqual(L(blk), hexdec("d456584dd0e3e84cc3166e4b7fa2890d")) - - def test_vec2(self): - blk = bytearray(hexdec("d456584dd0e3e84cc3166e4b7fa2890d")) - self.assertSequenceEqual(L(blk), hexdec("79d26221b87b584cd42fbc4ffea5de9a")) - - def test_vec3(self): - blk = bytearray(hexdec("79d26221b87b584cd42fbc4ffea5de9a")) - self.assertSequenceEqual(L(blk), hexdec("0e93691a0cfc60408b7b68f66b513c13")) - - def test_vec4(self): - blk = bytearray(hexdec("0e93691a0cfc60408b7b68f66b513c13")) - self.assertSequenceEqual(L(blk), hexdec("e6a8094fee0aa204fd97bcb0b44b8580")) - - -class KuznechikTest(TestCase): - key = hexdec("8899aabbccddeeff0011223344556677fedcba98765432100123456789abcdef") - plaintext = hexdec("1122334455667700ffeeddccbbaa9988") - ciphertext = hexdec("7f679d90bebc24305a468d42b9d4edcd") - - def test_c(self): - self.assertSequenceEqual(C[0], hexdec("6ea276726c487ab85d27bd10dd849401")) - self.assertSequenceEqual(C[1], hexdec("dc87ece4d890f4b3ba4eb92079cbeb02")) - self.assertSequenceEqual(C[2], hexdec("b2259a96b4d88e0be7690430a44f7f03")) - self.assertSequenceEqual(C[3], hexdec("7bcd1b0b73e32ba5b79cb140f2551504")) - self.assertSequenceEqual(C[4], hexdec("156f6d791fab511deabb0c502fd18105")) - self.assertSequenceEqual(C[5], hexdec("a74af7efab73df160dd208608b9efe06")) - self.assertSequenceEqual(C[6], hexdec("c9e8819dc73ba5ae50f5b570561a6a07")) - self.assertSequenceEqual(C[7], hexdec("f6593616e6055689adfba18027aa2a08")) - - def test_roundkeys(self): - ciph = GOST3412Kuznechik(self.key) - self.assertSequenceEqual(ciph.ks[0], hexdec("8899aabbccddeeff0011223344556677")) - self.assertSequenceEqual(ciph.ks[1], hexdec("fedcba98765432100123456789abcdef")) - self.assertSequenceEqual(ciph.ks[2], hexdec("db31485315694343228d6aef8cc78c44")) - self.assertSequenceEqual(ciph.ks[3], hexdec("3d4553d8e9cfec6815ebadc40a9ffd04")) - self.assertSequenceEqual(ciph.ks[4], hexdec("57646468c44a5e28d3e59246f429f1ac")) - self.assertSequenceEqual(ciph.ks[5], hexdec("bd079435165c6432b532e82834da581b")) - self.assertSequenceEqual(ciph.ks[6], hexdec("51e640757e8745de705727265a0098b1")) - self.assertSequenceEqual(ciph.ks[7], hexdec("5a7925017b9fdd3ed72a91a22286f984")) - self.assertSequenceEqual(ciph.ks[8], hexdec("bb44e25378c73123a5f32f73cdb6e517")) - self.assertSequenceEqual(ciph.ks[9], hexdec("72e9dd7416bcf45b755dbaa88e4a4043")) - - def test_encrypt(self): - ciph = GOST3412Kuznechik(self.key) - self.assertSequenceEqual(ciph.encrypt(self.plaintext), self.ciphertext) - - def test_decrypt(self): - ciph = GOST3412Kuznechik(self.key) - self.assertSequenceEqual(ciph.decrypt(self.ciphertext), self.plaintext) - - -class MagmaTest(TestCase): - key = hexdec("ffeeddccbbaa99887766554433221100f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") - plaintext = hexdec("fedcba9876543210") - ciphertext = hexdec("4ee901e5c2d8ca3d") - - def test_encrypt(self): - ciph = GOST3412Magma(self.key) - self.assertSequenceEqual(ciph.encrypt(self.plaintext), self.ciphertext) - - def test_decrypt(self): - ciph = GOST3412Magma(self.key) - self.assertSequenceEqual(ciph.decrypt(self.ciphertext), self.plaintext) diff --git a/pygost-5.13/build/lib/pygost/test_gost3413.py b/pygost-5.13/build/lib/pygost/test_gost3413.py deleted file mode 100644 index 0098513..0000000 --- a/pygost-5.13/build/lib/pygost/test_gost3413.py +++ /dev/null @@ -1,766 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from random import randint -from unittest import TestCase - -from pygost.gost3412 import GOST3412Kuznechik -from pygost.gost3412 import GOST3412Magma -from pygost.gost3413 import _mac_ks -from pygost.gost3413 import acpkm -from pygost.gost3413 import acpkm_master -from pygost.gost3413 import cbc_decrypt -from pygost.gost3413 import cbc_encrypt -from pygost.gost3413 import cfb_decrypt -from pygost.gost3413 import cfb_encrypt -from pygost.gost3413 import ctr -from pygost.gost3413 import ctr_acpkm -from pygost.gost3413 import ecb_decrypt -from pygost.gost3413 import ecb_encrypt -from pygost.gost3413 import KEYSIZE -from pygost.gost3413 import mac -from pygost.gost3413 import mac_acpkm_master -from pygost.gost3413 import ofb -from pygost.gost3413 import pad2 -from pygost.gost3413 import pad_iso10126 -from pygost.gost3413 import unpad2 -from pygost.gost3413 import unpad_iso10126 -from pygost.utils import hexdec -from pygost.utils import hexenc -from pygost.utils import strxor - - -class Pad2Test(TestCase): - def test_symmetric(self): - for _ in range(100): - for blocksize in (GOST3412Magma.blocksize, GOST3412Kuznechik.blocksize): - data = urandom(randint(0, blocksize * 3)) - self.assertSequenceEqual( - unpad2(pad2(data, blocksize), blocksize), - data, - ) - - -class GOST3412KuznechikModesTest(TestCase): - key = hexdec("8899aabbccddeeff0011223344556677fedcba98765432100123456789abcdef") - ciph = GOST3412Kuznechik(key) - plaintext = "" - plaintext += "1122334455667700ffeeddccbbaa9988" - plaintext += "00112233445566778899aabbcceeff0a" - plaintext += "112233445566778899aabbcceeff0a00" - plaintext += "2233445566778899aabbcceeff0a0011" - iv = hexdec("1234567890abcef0a1b2c3d4e5f0011223344556677889901213141516171819") - - def test_ecb_vectors(self): - ciphtext = "" - ciphtext += "7f679d90bebc24305a468d42b9d4edcd" - ciphtext += "b429912c6e0032f9285452d76718d08b" - ciphtext += "f0ca33549d247ceef3f5a5313bd4b157" - ciphtext += "d0b09ccde830b9eb3a02c4c5aa8ada98" - self.assertSequenceEqual( - hexenc(ecb_encrypt( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(self.plaintext), - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(ecb_decrypt( - self.ciph.decrypt, - GOST3412Kuznechik.blocksize, - hexdec(ciphtext), - )), - self.plaintext, - ) - - def test_ecb_symmetric(self): - for _ in range(100): - pt = pad2(urandom(randint(0, 16 * 2)), GOST3412Kuznechik.blocksize) - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - ct = ecb_encrypt(ciph.encrypt, GOST3412Kuznechik.blocksize, pt) - self.assertSequenceEqual(ecb_decrypt( - ciph.decrypt, - GOST3412Kuznechik.blocksize, - ct, - ), pt) - - def test_ctr_vectors(self): - ciphtext = "" - ciphtext += "f195d8bec10ed1dbd57b5fa240bda1b8" - ciphtext += "85eee733f6a13e5df33ce4b33c45dee4" - ciphtext += "a5eae88be6356ed3d5e877f13564a3a5" - ciphtext += "cb91fab1f20cbab6d1c6d15820bdba73" - iv = self.iv[:GOST3412Kuznechik.blocksize // 2] - self.assertSequenceEqual( - hexenc(ctr( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(self.plaintext), - iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(ctr( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(ciphtext), - iv, - )), - self.plaintext, - ) - - def test_ctr_symmetric(self): - for _ in range(100): - pt = urandom(randint(0, 16 * 2)) - iv = urandom(GOST3412Kuznechik.blocksize // 2) - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - ct = ctr(ciph.encrypt, GOST3412Kuznechik.blocksize, pt, iv) - self.assertSequenceEqual(ctr( - ciph.encrypt, - GOST3412Kuznechik.blocksize, - ct, - iv, - ), pt) - - def test_ofb_vectors(self): - ciphtext = "" - ciphtext += "81800a59b1842b24ff1f795e897abd95" - ciphtext += "ed5b47a7048cfab48fb521369d9326bf" - ciphtext += "66a257ac3ca0b8b1c80fe7fc10288a13" - ciphtext += "203ebbc066138660a0292243f6903150" - self.assertSequenceEqual( - hexenc(ofb( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(self.plaintext), - self.iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(ofb( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(ciphtext), - self.iv, - )), - self.plaintext, - ) - - def test_ofb_symmetric(self): - for _ in range(100): - pt = urandom(randint(0, 16 * 2)) - iv = urandom(GOST3412Kuznechik.blocksize * 2) - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - ct = ofb(ciph.encrypt, GOST3412Kuznechik.blocksize, pt, iv) - self.assertSequenceEqual(ofb( - ciph.encrypt, - GOST3412Kuznechik.blocksize, - ct, - iv, - ), pt) - - def test_ofb_manual(self): - iv = [urandom(GOST3412Kuznechik.blocksize) for _ in range(randint(2, 10))] - pt = [ - urandom(GOST3412Kuznechik.blocksize) - for _ in range(len(iv), len(iv) + randint(1, 10)) - ] - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - r = [ciph.encrypt(i) for i in iv] - for i in range(len(pt) - len(iv)): - r.append(ciph.encrypt(r[i])) - ct = [strxor(g, r) for g, r in zip(pt, r)] - self.assertSequenceEqual( - ofb(ciph.encrypt, GOST3412Kuznechik.blocksize, b"".join(pt), b"".join(iv)), - b"".join(ct), - ) - - def test_cbc_vectors(self): - ciphtext = "" - ciphtext += "689972d4a085fa4d90e52e3d6d7dcc27" - ciphtext += "2826e661b478eca6af1e8e448d5ea5ac" - ciphtext += "fe7babf1e91999e85640e8b0f49d90d0" - ciphtext += "167688065a895c631a2d9a1560b63970" - self.assertSequenceEqual( - hexenc(cbc_encrypt( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(self.plaintext), - self.iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(cbc_decrypt( - self.ciph.decrypt, - GOST3412Kuznechik.blocksize, - hexdec(ciphtext), - self.iv, - )), - self.plaintext, - ) - - def test_cbc_symmetric(self): - for _ in range(100): - pt = pad2(urandom(randint(0, 16 * 2)), GOST3412Kuznechik.blocksize) - iv = urandom(GOST3412Kuznechik.blocksize * 2) - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - ct = cbc_encrypt(ciph.encrypt, GOST3412Kuznechik.blocksize, pt, iv) - self.assertSequenceEqual(cbc_decrypt( - ciph.decrypt, - GOST3412Kuznechik.blocksize, - ct, - iv, - ), pt) - - def test_cfb_vectors(self): - ciphtext = "" - ciphtext += "81800a59b1842b24ff1f795e897abd95" - ciphtext += "ed5b47a7048cfab48fb521369d9326bf" - ciphtext += "79f2a8eb5cc68d38842d264e97a238b5" - ciphtext += "4ffebecd4e922de6c75bd9dd44fbf4d1" - self.assertSequenceEqual( - hexenc(cfb_encrypt( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(self.plaintext), - self.iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(cfb_decrypt( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(ciphtext), - self.iv, - )), - self.plaintext, - ) - - def test_cfb_symmetric(self): - for _ in range(100): - pt = urandom(randint(0, 16 * 2)) - iv = urandom(GOST3412Kuznechik.blocksize * 2) - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - ct = cfb_encrypt(ciph.encrypt, GOST3412Kuznechik.blocksize, pt, iv) - self.assertSequenceEqual(cfb_decrypt( - ciph.encrypt, - GOST3412Kuznechik.blocksize, - ct, - iv, - ), pt) - - def test_mac_vectors(self): - k1, k2 = _mac_ks(self.ciph.encrypt, GOST3412Kuznechik.blocksize) - self.assertSequenceEqual(hexenc(k1), "297d82bc4d39e3ca0de0573298151dc7") - self.assertSequenceEqual(hexenc(k2), "52fb05789a73c7941bc0ae65302a3b8e") - self.assertSequenceEqual( - hexenc(mac( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(self.plaintext), - )[:8]), - "336f4d296059fbe3", - ) - - def test_mac_applies(self): - for _ in range(100): - data = urandom(randint(0, 16 * 2)) - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - mac(ciph.encrypt, GOST3412Kuznechik.blocksize, data) - - -class GOST3412MagmaModesTest(TestCase): - key = hexdec("ffeeddccbbaa99887766554433221100f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") - ciph = GOST3412Magma(key) - plaintext = "" - plaintext += "92def06b3c130a59" - plaintext += "db54c704f8189d20" - plaintext += "4a98fb2e67a8024c" - plaintext += "8912409b17b57e41" - iv = hexdec("1234567890abcdef234567890abcdef134567890abcdef12") - - def test_ecb_vectors(self): - ciphtext = "" - ciphtext += "2b073f0494f372a0" - ciphtext += "de70e715d3556e48" - ciphtext += "11d8d9e9eacfbc1e" - ciphtext += "7c68260996c67efb" - self.assertSequenceEqual( - hexenc(ecb_encrypt( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(self.plaintext), - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(ecb_decrypt( - self.ciph.decrypt, - GOST3412Magma.blocksize, - hexdec(ciphtext), - )), - self.plaintext, - ) - - def test_ecb_symmetric(self): - for _ in range(100): - pt = pad2(urandom(randint(0, 16 * 2)), 16) - ciph = GOST3412Magma(urandom(KEYSIZE)) - ct = ecb_encrypt(ciph.encrypt, GOST3412Magma.blocksize, pt) - self.assertSequenceEqual(ecb_decrypt( - ciph.decrypt, - GOST3412Magma.blocksize, - ct, - ), pt) - - def test_ctr_vectors(self): - ciphtext = "" - ciphtext += "4e98110c97b7b93c" - ciphtext += "3e250d93d6e85d69" - ciphtext += "136d868807b2dbef" - ciphtext += "568eb680ab52a12d" - iv = self.iv[:4] - self.assertSequenceEqual( - hexenc(ctr( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(self.plaintext), - iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(ctr( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(ciphtext), - iv, - )), - self.plaintext, - ) - - def test_ctr_symmetric(self): - for _ in range(100): - pt = urandom(randint(0, 16 * 2)) - iv = urandom(GOST3412Magma.blocksize // 2) - ciph = GOST3412Magma(urandom(KEYSIZE)) - ct = ctr(ciph.encrypt, GOST3412Magma.blocksize, pt, iv) - self.assertSequenceEqual(ctr( - ciph.encrypt, - GOST3412Magma.blocksize, - ct, - iv, - ), pt) - - def test_ofb_vectors(self): - iv = self.iv[:16] - ciphtext = "" - ciphtext += "db37e0e266903c83" - ciphtext += "0d46644c1f9a089c" - ciphtext += "a0f83062430e327e" - ciphtext += "c824efb8bd4fdb05" - self.assertSequenceEqual( - hexenc(ofb( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(self.plaintext), - iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(ofb( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(ciphtext), - iv, - )), - self.plaintext, - ) - - def test_ofb_symmetric(self): - for _ in range(100): - pt = urandom(randint(0, 16 * 2)) - iv = urandom(GOST3412Magma.blocksize * 2) - ciph = GOST3412Magma(urandom(KEYSIZE)) - ct = ofb(ciph.encrypt, GOST3412Magma.blocksize, pt, iv) - self.assertSequenceEqual(ofb( - ciph.encrypt, - GOST3412Magma.blocksize, - ct, - iv, - ), pt) - - def test_cbc_vectors(self): - ciphtext = "" - ciphtext += "96d1b05eea683919" - ciphtext += "aff76129abb937b9" - ciphtext += "5058b4a1c4bc0019" - ciphtext += "20b78b1a7cd7e667" - self.assertSequenceEqual( - hexenc(cbc_encrypt( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(self.plaintext), - self.iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(cbc_decrypt( - self.ciph.decrypt, - GOST3412Magma.blocksize, - hexdec(ciphtext), - self.iv, - )), - self.plaintext, - ) - - def test_cbc_symmetric(self): - for _ in range(100): - pt = pad2(urandom(randint(0, 16 * 2)), 16) - iv = urandom(GOST3412Magma.blocksize * 2) - ciph = GOST3412Magma(urandom(KEYSIZE)) - ct = cbc_encrypt(ciph.encrypt, GOST3412Magma.blocksize, pt, iv) - self.assertSequenceEqual(cbc_decrypt( - ciph.decrypt, - GOST3412Magma.blocksize, - ct, - iv, - ), pt) - - def test_cfb_vectors(self): - iv = self.iv[:16] - ciphtext = "" - ciphtext += "db37e0e266903c83" - ciphtext += "0d46644c1f9a089c" - ciphtext += "24bdd2035315d38b" - ciphtext += "bcc0321421075505" - self.assertSequenceEqual( - hexenc(cfb_encrypt( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(self.plaintext), - iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(cfb_decrypt( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(ciphtext), - iv, - )), - self.plaintext, - ) - - def test_cfb_symmetric(self): - for _ in range(100): - pt = urandom(randint(0, 16 * 2)) - iv = urandom(GOST3412Magma.blocksize * 2) - ciph = GOST3412Magma(urandom(KEYSIZE)) - ct = cfb_encrypt(ciph.encrypt, GOST3412Magma.blocksize, pt, iv) - self.assertSequenceEqual(cfb_decrypt( - ciph.encrypt, - GOST3412Magma.blocksize, - ct, - iv, - ), pt) - - def test_mac_vectors(self): - k1, k2 = _mac_ks(self.ciph.encrypt, GOST3412Magma.blocksize) - self.assertSequenceEqual(hexenc(k1), "5f459b3342521424") - self.assertSequenceEqual(hexenc(k2), "be8b366684a42848") - self.assertSequenceEqual( - hexenc(mac( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(self.plaintext), - )[:4]), - "154e7210", - ) - - def test_mac_applies(self): - for _ in range(100): - data = urandom(randint(0, 16 * 2)) - ciph = GOST3412Magma(urandom(KEYSIZE)) - mac(ciph.encrypt, GOST3412Magma.blocksize, data) - - -class TestVectorACPKM(TestCase): - """Test vectors from Р 1323565.1.017-2018 - """ - key = hexdec("8899AABBCCDDEEFF0011223344556677FEDCBA98765432100123456789ABCDEF") - - def test_magma_ctr_acpkm(self): - key = acpkm(GOST3412Magma(self.key).encrypt, GOST3412Magma.blocksize) - self.assertSequenceEqual(key, hexdec("863EA017842C3D372B18A85A28E2317D74BEFC107720DE0C9E8AB974ABD00CA0")) - key = acpkm(GOST3412Magma(key).encrypt, GOST3412Magma.blocksize) - self.assertSequenceEqual(key, hexdec("49A5E2677DE555982B8AD5E826652D17EEC847BF5B3997A81CF7FE7F1187BD27")) - key = acpkm(GOST3412Magma(key).encrypt, GOST3412Magma.blocksize) - self.assertSequenceEqual(key, hexdec("3256BF3F97B5667426A9FB1C5EAABE41893CCDD5A868F9B63B0AA90720FA43C4")) - - def test_magma_ctr(self): - encrypter = GOST3412Magma(self.key).encrypt - plaintext = hexdec(""" -11 22 33 44 55 66 77 00 FF EE DD CC BB AA 99 88 -00 11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A -11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 -22 33 44 55 66 77 88 99 - """.replace("\n", "").replace(" ", "")) - iv = hexdec("12345678") - ciphertext = hexdec(""" -2A B8 1D EE EB 1E 4C AB 68 E1 04 C4 BD 6B 94 EA -C7 2C 67 AF 6C 2E 5B 6B 0E AF B6 17 70 F1 B3 2E -A1 AE 71 14 9E ED 13 82 AB D4 67 18 06 72 EC 6F -84 A2 F1 5B 3F CA 72 C1 - """.replace("\n", "").replace(" ", "")) - self.assertSequenceEqual( - ctr_acpkm( - GOST3412Magma, - encrypter, - bs=GOST3412Magma.blocksize, - section_size=GOST3412Magma.blocksize * 2, - data=plaintext, - iv=iv - ), - ciphertext, - ) - self.assertSequenceEqual( - ctr_acpkm( - GOST3412Magma, - encrypter, - bs=GOST3412Magma.blocksize, - section_size=GOST3412Magma.blocksize * 2, - data=ciphertext, - iv=iv - ), - plaintext, - ) - - def test_kuznechik_ctr_acpkm(self): - key = acpkm(GOST3412Kuznechik(self.key).encrypt, GOST3412Kuznechik.blocksize) - self.assertSequenceEqual(key, hexdec("2666ED40AE687811745CA0B448F57A7B390ADB5780307E8E9659AC403AE60C60")) - key = acpkm(GOST3412Kuznechik(key).encrypt, GOST3412Kuznechik.blocksize) - self.assertSequenceEqual(key, hexdec("BB3DD5402E999B7A3DEBB0DB45448EC530F07365DFEE3ABA8415F77AC8F34CE8")) - key = acpkm(GOST3412Kuznechik(key).encrypt, GOST3412Kuznechik.blocksize) - self.assertSequenceEqual(key, hexdec("23362FD553CAD2178299A5B5A2D4722E3BB83C730A8BF57CE2DD004017F8C565")) - - def test_kuznechik_ctr(self): - encrypter = GOST3412Kuznechik(self.key).encrypt - iv = hexdec("1234567890ABCEF0") - plaintext = hexdec(""" -11 22 33 44 55 66 77 00 FF EE DD CC BB AA 99 88 -00 11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A -11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 -22 33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 11 -33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 11 22 -44 55 66 77 88 99 AA BB CC EE FF 0A 00 11 22 33 -55 66 77 88 99 AA BB CC EE FF 0A 00 11 22 33 44 - """.replace("\n", "").replace(" ", "")) - ciphertext = hexdec(""" -F1 95 D8 BE C1 0E D1 DB D5 7B 5F A2 40 BD A1 B8 -85 EE E7 33 F6 A1 3E 5D F3 3C E4 B3 3C 45 DE E4 -4B CE EB 8F 64 6F 4C 55 00 17 06 27 5E 85 E8 00 -58 7C 4D F5 68 D0 94 39 3E 48 34 AF D0 80 50 46 -CF 30 F5 76 86 AE EC E1 1C FC 6C 31 6B 8A 89 6E -DF FD 07 EC 81 36 36 46 0C 4F 3B 74 34 23 16 3E -64 09 A9 C2 82 FA C8 D4 69 D2 21 E7 FB D6 DE 5D - """.replace("\n", "").replace(" ", "")) - self.assertSequenceEqual( - ctr_acpkm( - GOST3412Kuznechik, - encrypter, - bs=GOST3412Kuznechik.blocksize, - section_size=GOST3412Kuznechik.blocksize * 2, - data=plaintext, - iv=iv, - ), - ciphertext, - ) - self.assertSequenceEqual( - ctr_acpkm( - GOST3412Kuznechik, - encrypter, - bs=GOST3412Kuznechik.blocksize, - section_size=GOST3412Kuznechik.blocksize * 2, - data=ciphertext, - iv=iv, - ), - plaintext, - ) - - def test_magma_omac_1_5_blocks(self): - encrypter = GOST3412Magma(self.key).encrypt - key_section_size = 640 // 8 - self.assertSequenceEqual( - acpkm_master( - GOST3412Magma, - encrypter, - key_section_size=key_section_size, - bs=GOST3412Magma.blocksize, - keymat_len=KEYSIZE + GOST3412Magma.blocksize, - ), - hexdec("0DF2F5273DA328932AC49D81D36B2558A50DBF9BBCAC74A614B2CCB2F1CBCD8A70638E3DE8B3571E"), - ) - text = hexdec("1122334455667700FFEEDDCC") - self.assertSequenceEqual( - mac_acpkm_master( - GOST3412Magma, - encrypter, - key_section_size, - section_size=GOST3412Magma.blocksize * 2, - bs=GOST3412Magma.blocksize, - data=text, - ), - hexdec("A0540E3730ACBCF3"), - ) - - def test_magma_omac_5_blocks(self): - encrypter = GOST3412Magma(self.key).encrypt - key_section_size = 640 // 8 - self.assertSequenceEqual( - acpkm_master( - GOST3412Magma, - encrypter, - key_section_size=key_section_size, - bs=GOST3412Magma.blocksize, - keymat_len=3 * (KEYSIZE + GOST3412Magma.blocksize), - ), - hexdec(""" -0D F2 F5 27 3D A3 28 93 2A C4 9D 81 D3 6B 25 58 -A5 0D BF 9B BC AC 74 A6 14 B2 CC B2 F1 CB CD 8A -70 63 8E 3D E8 B3 57 1E 8D 38 26 D5 5E 63 A1 67 -E2 40 66 40 54 7B 9F 1F 5F 2B 43 61 2A AE AF DA -18 0B AC 86 04 DF A6 FE 53 C2 CE 27 0E 9C 9F 52 -68 D0 FD BF E1 A3 BD D9 BE 5B 96 D0 A1 20 23 48 -6E F1 71 0F 92 4A E0 31 30 52 CB 5F CA 0B 79 1E -1B AB E8 57 6D 0F E3 A8 - """.replace("\n", "").replace(" ", "")), - ) - text = hexdec(""" -11 22 33 44 55 66 77 00 FF EE DD CC BB AA 99 88 -00 11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A -11 22 33 44 55 66 77 88 - """.replace("\n", "").replace(" ", "")) - self.assertSequenceEqual( - mac_acpkm_master( - GOST3412Magma, - encrypter, - key_section_size, - section_size=GOST3412Magma.blocksize * 2, - bs=GOST3412Magma.blocksize, - data=text, - ), - hexdec("34008DAD5496BB8E"), - ) - - def test_kuznechik_omac_1_5_blocks(self): - encrypter = GOST3412Kuznechik(self.key).encrypt - key_section_size = 768 // 8 - self.assertSequenceEqual( - acpkm_master( - GOST3412Kuznechik, - encrypter, - key_section_size=key_section_size, - bs=GOST3412Kuznechik.blocksize, - keymat_len=KEYSIZE + GOST3412Kuznechik.blocksize, - ), - hexdec(""" -0C AB F1 F2 EF BC 4A C1 60 48 DF 1A 24 C6 05 B2 -C0 D1 67 3D 75 86 A8 EC 0D D4 2C 45 A4 F9 5B AE -0F 2E 26 17 E4 71 48 68 0F C3 E6 17 8D F2 C1 37 - """.replace("\n", "").replace(" ", "")) - ) - text = hexdec(""" -11 22 33 44 55 66 77 00 FF EE DD CC BB AA 99 88 -00 11 22 33 44 55 66 77 - """.replace("\n", "").replace(" ", "")) - self.assertSequenceEqual( - mac_acpkm_master( - GOST3412Kuznechik, - encrypter, - key_section_size, - section_size=GOST3412Kuznechik.blocksize * 2, - bs=GOST3412Kuznechik.blocksize, - data=text, - ), - hexdec("B5367F47B62B995EEB2A648C5843145E"), - ) - - def test_kuznechik_omac_5_blocks(self): - encrypter = GOST3412Kuznechik(self.key).encrypt - key_section_size = 768 // 8 - self.assertSequenceEqual( - acpkm_master( - GOST3412Kuznechik, - encrypter, - key_section_size=key_section_size, - bs=GOST3412Kuznechik.blocksize, - keymat_len=3 * (KEYSIZE + GOST3412Kuznechik.blocksize), - ), - hexdec(""" -0C AB F1 F2 EF BC 4A C1 60 48 DF 1A 24 C6 05 B2 -C0 D1 67 3D 75 86 A8 EC 0D D4 2C 45 A4 F9 5B AE -0F 2E 26 17 E4 71 48 68 0F C3 E6 17 8D F2 C1 37 -C9 DD A8 9C FF A4 91 FE AD D9 B3 EA B7 03 BB 31 -BC 7E 92 7F 04 94 72 9F 51 B4 9D 3D F9 C9 46 08 -00 FB BC F5 ED EE 61 0E A0 2F 01 09 3C 7B C7 42 -D7 D6 27 15 01 B1 77 77 52 63 C2 A3 49 5A 83 18 -A8 1C 79 A0 4F 29 66 0E A3 FD A8 74 C6 30 79 9E -14 2C 57 79 14 FE A9 0D 3B C2 50 2E 83 36 85 D9 - """.replace("\n", "").replace(" ", "")), - ) - text = hexdec(""" -11 22 33 44 55 66 77 00 FF EE DD CC BB AA 99 88 -00 11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A -11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 -22 33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 11 -33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 11 22 - """.replace("\n", "").replace(" ", "")) - self.assertSequenceEqual( - mac_acpkm_master( - GOST3412Kuznechik, - encrypter, - key_section_size, - section_size=GOST3412Kuznechik.blocksize * 2, - bs=GOST3412Kuznechik.blocksize, - data=text, - ), - hexdec("FBB8DCEE45BEA67C35F58C5700898E5D"), - ) - - -class ISO10126Test(TestCase): - def test_symmetric(self): - for _ in range(100): - for blocksize in (GOST3412Magma.blocksize, GOST3412Kuznechik.blocksize): - data = urandom(randint(0, blocksize * 3)) - padded = pad_iso10126(data, blocksize) - self.assertSequenceEqual(unpad_iso10126(padded, blocksize), data) - with self.assertRaises(ValueError): - unpad_iso10126(padded[1:], blocksize) - - def test_small(self): - with self.assertRaises(ValueError): - unpad_iso10126(b"foobar\x00\x09", 8) diff --git a/pygost-5.13/build/lib/pygost/test_kdf.py b/pygost-5.13/build/lib/pygost/test_kdf.py deleted file mode 100644 index 6921cfc..0000000 --- a/pygost-5.13/build/lib/pygost/test_kdf.py +++ /dev/null @@ -1,58 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 unittest import TestCase - -from pygost.kdf import kdf_gostr3411_2012_256 -from pygost.kdf import kdf_tree_gostr3411_2012_256 -from pygost.utils import hexdec - - -class TestKDFGOSTR34112012256(TestCase): - def runTest(self): - self.assertEqual( - kdf_gostr3411_2012_256( - hexdec("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), - hexdec("26bdb878"), - hexdec("af21434145656378"), - ), - hexdec("a1aa5f7de402d7b3d323f2991c8d4534013137010a83754fd0af6d7cd4922ed9"), - ) - - -class TestKDFTREEGOSTR34112012256(TestCase): - def runTest(self): - self.assertSequenceEqual( - kdf_tree_gostr3411_2012_256( - hexdec("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), - hexdec("26bdb878"), - hexdec("af21434145656378"), - 1, - ), - (hexdec("a1aa5f7de402d7b3d323f2991c8d4534013137010a83754fd0af6d7cd4922ed9"),), - ) - self.assertSequenceEqual( - kdf_tree_gostr3411_2012_256( - hexdec("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), - hexdec("26bdb878"), - hexdec("af21434145656378"), - 2, - ), - ( - hexdec("22b6837845c6bef65ea71672b265831086d3c76aebe6dae91cad51d83f79d16b"), - hexdec("074c9330599d7f8d712fca54392f4ddde93751206b3584c8f43f9e6dc51531f9"), - ), - ) diff --git a/pygost-5.13/build/lib/pygost/test_mgm.py b/pygost-5.13/build/lib/pygost/test_mgm.py deleted file mode 100644 index e70a7f5..0000000 --- a/pygost-5.13/build/lib/pygost/test_mgm.py +++ /dev/null @@ -1,75 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from random import randint -from unittest import TestCase - -from pygost.gost3412 import GOST3412Kuznechik -from pygost.gost3412 import GOST3412Magma -from pygost.gost3412 import KEYSIZE -from pygost.mgm import MGM -from pygost.mgm import nonce_prepare -from pygost.utils import hexdec - - -class TestVector(TestCase): - def runTest(self): - key = hexdec("8899AABBCCDDEEFF0011223344556677FEDCBA98765432100123456789ABCDEF") - ad = hexdec("0202020202020202010101010101010104040404040404040303030303030303EA0505050505050505") - plaintext = hexdec("1122334455667700FFEEDDCCBBAA998800112233445566778899AABBCCEEFF0A112233445566778899AABBCCEEFF0A002233445566778899AABBCCEEFF0A0011AABBCC") - mgm = MGM(GOST3412Kuznechik(key).encrypt, GOST3412Kuznechik.blocksize) - ciphertext = mgm.seal(plaintext[:16], plaintext, ad) - self.assertSequenceEqual(ciphertext[:len(plaintext)], hexdec("A9757B8147956E9055B8A33DE89F42FC8075D2212BF9FD5BD3F7069AADC16B39497AB15915A6BA85936B5D0EA9F6851CC60C14D4D3F883D0AB94420695C76DEB2C7552")) - self.assertSequenceEqual(ciphertext[len(plaintext):], hexdec("CF5D656F40C34F5C46E8BB0E29FCDB4C")) - self.assertSequenceEqual(mgm.open(plaintext[:16], ciphertext, ad), plaintext) - - -class TestSymmetric(TestCase): - def _itself(self, mgm, bs, tag_size): - for _ in range(1000): - nonce = nonce_prepare(urandom(bs)) - ad = urandom(randint(0, 20)) - pt = urandom(randint(0, 20)) - if len(ad) + len(pt) == 0: - continue - ct = mgm.seal(nonce, pt, ad) - self.assertEqual(len(ct) - tag_size, len(pt)) - self.assertSequenceEqual(mgm.open(nonce, ct, ad), pt) - - def test_magma(self): - for tag_size in ( - GOST3412Magma.blocksize, - GOST3412Magma.blocksize - 2, - ): - mgm = MGM( - GOST3412Magma(urandom(KEYSIZE)).encrypt, - GOST3412Magma.blocksize, - tag_size, - ) - self._itself(mgm, GOST3412Magma.blocksize, tag_size) - - def test_kuznechik(self): - for tag_size in ( - GOST3412Kuznechik.blocksize, - GOST3412Kuznechik.blocksize - 2, - ): - mgm = MGM( - GOST3412Kuznechik(urandom(KEYSIZE)).encrypt, - GOST3412Kuznechik.blocksize, - tag_size, - ) - self._itself(mgm, GOST3412Kuznechik.blocksize, tag_size) diff --git a/pygost-5.13/build/lib/pygost/test_pfx.py b/pygost-5.13/build/lib/pygost/test_pfx.py deleted file mode 100644 index a2760bf..0000000 --- a/pygost-5.13/build/lib/pygost/test_pfx.py +++ /dev/null @@ -1,680 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 import gost3410 -from pygost.gost28147 import cfb_decrypt -from pygost.gost34112012256 import GOST34112012256 -from pygost.gost34112012512 import GOST34112012512 -from pygost.gost34112012512 import pbkdf2 as gost34112012_pbkdf2 -from pygost.gost3412 import GOST3412Kuznechik -from pygost.gost3412 import GOST3412Magma -from pygost.gost3412 import KEYSIZE -from pygost.gost3413 import ctr_acpkm -from pygost.gost3413 import mac as omac -from pygost.kdf import kdf_tree_gostr3411_2012_256 -from pygost.kdf import keg -from pygost.utils import hexdec -from pygost.wrap import kimp15 - - -try: - from pyderasn import OctetString - - from pygost.asn1schemas.cms import EncryptedData - from pygost.asn1schemas.cms import EnvelopedData - from pygost.asn1schemas.cms import SignedAttributes - from pygost.asn1schemas.cms import SignedData - from pygost.asn1schemas.oids import id_data - from pygost.asn1schemas.oids import id_envelopedData - from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm - from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_wrap_kexp15 - from pygost.asn1schemas.oids import id_messageDigest - from pygost.asn1schemas.oids import id_pbes2 - from pygost.asn1schemas.oids import id_pkcs12_bagtypes_certBag - from pygost.asn1schemas.oids import id_pkcs12_bagtypes_keyBag - from pygost.asn1schemas.oids import id_pkcs12_bagtypes_pkcs8ShroudedKeyBag - from pygost.asn1schemas.oids import id_pkcs9_certTypes_x509Certificate - from pygost.asn1schemas.oids import id_signedData - from pygost.asn1schemas.oids import id_tc26_agreement_gost3410_2012_256 - from pygost.asn1schemas.oids import id_tc26_gost3411_2012_256 - from pygost.asn1schemas.pfx import CertBag - from pygost.asn1schemas.pfx import KeyBag - from pygost.asn1schemas.pfx import OctetStringSafeContents - from pygost.asn1schemas.pfx import PBES2Params - from pygost.asn1schemas.pfx import PFX - from pygost.asn1schemas.pfx import PKCS8ShroudedKeyBag - from pygost.asn1schemas.pfx import SafeContents - from pygost.asn1schemas.x509 import Certificate -except ImportError: - pyderasn_exists = False -else: - 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"") - _, outer_safe_contents = pfx["authSafe"]["content"].defined - safe_contents, tail = OctetStringSafeContents().decode( - bytes(outer_safe_contents[0]["bagValue"]), - ) - self.assertSequenceEqual(tail, b"") - safe_bag = safe_contents[0] - shrouded_key_bag, tail = PKCS8ShroudedKeyBag().decode( - bytes(safe_bag["bagValue"]), - ) - self.assertSequenceEqual(tail, b"") - _, pbes2_params = shrouded_key_bag["encryptionAlgorithm"]["parameters"].defined - _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined - _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined - - 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="id-tc26-gost-28147-param-Z", - ), - 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"") - _, outer_safe_contents = pfx["authSafe"]["content"].defined - _, encrypted_data = outer_safe_contents[1]["bagValue"].defined - _, pbes2_params = encrypted_data["encryptedContentInfo"]["contentEncryptionAlgorithm"]["parameters"].defined - _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined - _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined - 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="id-tc26-gost-28147-param-Z", - ), - cert_bag_expected, - ) - - def test_mac(self): - pfx, tail = PFX().decode(self.pfx_raw) - self.assertSequenceEqual(tail, b"") - _, outer_safe_contents = pfx["authSafe"]["content"].defined - 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=SafeContents(outer_safe_contents).encode(), - digestmod=GOST34112012512, - ).digest(), - bytes(mac_data["mac"]["digest"]), - ) - - -@skipIf(not pyderasn_exists, "PyDERASN dependency is required") -class TestPFX2020(TestCase): - """PFX test vectors from newer PKCS#12 update - """ - ca_prv_raw = hexdec("092F8D059E97E22B90B1AE99F0087FC4D26620B91550CBB437C191005A290810") - ca_curve = gost3410.CURVES["id-tc26-gost-3410-12-256-paramSetA"] - ca_cert = Certificate().decod(b64decode(b""" - MIIB+TCCAaagAwIBAgIEAYy6gTAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2MS - cwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEwMTAx - MDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA4MQ0wCwYDVQQKEwRUSzI2MScwJQYDVQQDEx - 5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwXjAXBggqhQMHAQEBATALBgkq - hQMHAQIBAQEDQwAEQBpKgpyPDnhQAJyLqy8Qs0XQhgxEhby6tSypqYimgbjpcKqtU6 - 4jpDXc3h3BxGxtl2oHJ/4YLZ/ll87dto3ltMqjgZgwgZUwYwYDVR0jBFwwWoAUrGwO - TERmokKW4p8JOyVm88ukUyqhPKQ6MDgxDTALBgNVBAoTBFRLMjYxJzAlBgNVBAMTHk - NBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdIIEAYy6gTAdBgNVHQ4EFgQUrGwO - TERmokKW4p8JOyVm88ukUyowDwYDVR0TAQH/BAUwAwEB/zAKBggqhQMHAQEDAgNBAB - Gg3nhgQ5oCKbqlEdVaRxH+1WX4wVkawGXuTYkr1AC2OWw3ZC14Vvg3nazm8UMWUZtk - vu1kJcHQ4jFKkjUeg2E= - """)) - ca_pub = gost3410.pub_unmarshal(bytes(OctetString().decod(bytes( - ca_cert["tbsCertificate"]["subjectPublicKeyInfo"]["subjectPublicKey"] - )))) - password = u"Пароль для PFX".encode("utf-8") - cert_test = Certificate().decod(b64decode(b""" - MIICLjCCAdugAwIBAgIEAYy6hDAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2MS - cwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEwMTAx - MDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRUSzI2MSowKAYDVQQDEy - FPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDUxMi1iaXQwgaAwFwYIKoUDBwEBAQIw - CwYJKoUDBwECAQIBA4GEAASBgLSLt1q8KQ4YZVxioU+1LV9QhE7MHR9gBEh7S1yVNG - lqt7+rNG5VFqmrPM74rbUsOlhV8M+zZKprXdk35Oz8lSW/n2oIUHZxikXIH/SSHj4r - v3K/Puvz7hYTQSZl/xPdp78nUmjrEa6d5wfX8biEy2z0dgufFvAkMw1Ua4gdXqDOo4 - GHMIGEMGMGA1UdIwRcMFqAFKxsDkxEZqJCluKfCTslZvPLpFMqoTykOjA4MQ0wCwYD - VQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaX - SCBAGMuoEwHQYDVR0OBBYEFH4GVwmYDK1rCKhX7nkAWDrJ16CkMAoGCCqFAwcBAQMC - A0EACl6p8dAbpi9Hk+3mgMyI0WIh17IrlrSp/mB0F7ZzMt8XUD1Dwz3JrrnxeXnfMv - OA5BdUJ9hCyDgMVAGs/IcEEA== - """)) - prv_test_raw = b64decode(""" - MIHiAgEBMBcGCCqFAwcBAQECMAsGCSqFAwcBAgECAQRAEWkl+eblsHWs86SNgRKq - SxMOgGhbvR/uZ5/WWfdNG1axvUwVhpcXIxDZUmzQuNzqJBkseI7f5/JjXyTFRF1a - +YGBgQG0i7davCkOGGVcYqFPtS1fUIROzB0fYARIe0tclTRpare/qzRuVRapqzzO - +K21LDpYVfDPs2Sqa13ZN+Ts/JUlv59qCFB2cYpFyB/0kh4+K79yvz7r8+4WE0Em - Zf8T3ae/J1Jo6xGunecH1/G4hMts9HYLnxbwJDMNVGuIHV6gzg== - """) - - def test_cert_and_encrypted_key(self): - pfx_raw = b64decode(b""" - MIIFKwIBAzCCBMQGCSqGSIb3DQEHAaCCBLUEggSxMIIErTCCAswGCSqGSIb3DQEH - AaCCAr0EggK5MIICtTCCArEGCyqGSIb3DQEMCgEDoIICSjCCAkYGCiqGSIb3DQEJ - FgGgggI2BIICMjCCAi4wggHboAMCAQICBAGMuoQwCgYIKoUDBwEBAwIwODENMAsG - A1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYt - Yml0MB4XDTAxMDEwMTAwMDAwMFoXDTQ5MTIzMTAwMDAwMFowOzENMAsGA1UEChME - VEsyNjEqMCgGA1UEAxMhT1JJR0lOQVRPUjogR09TVCAzNC4xMC0xMiA1MTItYml0 - MIGgMBcGCCqFAwcBAQECMAsGCSqFAwcBAgECAQOBhAAEgYC0i7davCkOGGVcYqFP - tS1fUIROzB0fYARIe0tclTRpare/qzRuVRapqzzO+K21LDpYVfDPs2Sqa13ZN+Ts - /JUlv59qCFB2cYpFyB/0kh4+K79yvz7r8+4WE0EmZf8T3ae/J1Jo6xGunecH1/G4 - hMts9HYLnxbwJDMNVGuIHV6gzqOBhzCBhDBjBgNVHSMEXDBagBSsbA5MRGaiQpbi - nwk7JWbzy6RTKqE8pDowODENMAsGA1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsy - NjogR09TVCAzNC4xMC0xMiAyNTYtYml0ggQBjLqBMB0GA1UdDgQWBBR+BlcJmAyt - awioV+55AFg6ydegpDAKBggqhQMHAQEDAgNBAApeqfHQG6YvR5Pt5oDMiNFiIdey - K5a0qf5gdBe2czLfF1A9Q8M9ya658Xl53zLzgOQXVCfYQsg4DFQBrPyHBBAxVDAj - BgkqhkiG9w0BCRUxFgQUeVV0+dS25MICJChpmGc/8AoUwE0wLQYJKoZIhvcNAQkU - MSAeHgBwADEAMgBGAHIAaQBlAG4AZABsAHkATgBhAG0AZTCCAdkGCSqGSIb3DQEH - AaCCAcoEggHGMIIBwjCCAb4GCyqGSIb3DQEMCgECoIIBVzCCAVMwWQYJKoZIhvcN - AQUNMEwwKQYJKoZIhvcNAQUMMBwECKf4N7NMwugqAgIIADAMBggqhQMHAQEEAgUA - MB8GCSqFAwcBAQUCAjASBBAlmt2WDfaPJlsAs0mLKglzBIH1DMvEacbbWRNDVSnX - JLWygYrKoipdOjDA/2HEnBZ34uFOLNheUqiKpCPoFpbR2GBiVYVTVK9ibiczgaca - EQYzDXtcS0QCZOxpKWfteAlbdJLC/SqPurPYyKi0MVRUPROhbisFASDT38HDH1Dh - 0dL5f6ga4aPWLrWbbgWERFOoOPyh4DotlPF37AQOwiEjsbyyRHq3HgbWiaxQRuAh - eqHOn4QVGY92/HFvJ7u3TcnQdLWhTe/lh1RHLNF3RnXtN9if9zC23laDZOiWZplU - yLrUiTCbHrtn1RppPDmLFNMt9dJ7KKgCkOi7Zm5nhqPChbywX13wcfYxVDAjBgkq - hkiG9w0BCRUxFgQUeVV0+dS25MICJChpmGc/8AoUwE0wLQYJKoZIhvcNAQkUMSAe - HgBwADEAMgBGAHIAaQBlAG4AZABsAHkATgBhAG0AZTBeME4wCgYIKoUDBwEBAgME - QAkBKw4ihn7pSIYTEhu0bcvTPZjI3WgVxCkUVlOsc80G69EKFEOTnObGJGSKJ51U - KkOsXF0a7+VBZf3BcVVQh9UECIVEtO+VpuskAgIIAA== - """) - pfx = PFX().decod(pfx_raw) - _, outer_safe_contents = pfx["authSafe"]["content"].defined - - safe_contents = OctetStringSafeContents().decod(bytes( - outer_safe_contents[0]["bagValue"] - )) - safe_bag = safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_certBag) - cert_bag = CertBag().decod(bytes(safe_bag["bagValue"])) - self.assertEqual(cert_bag["certId"], id_pkcs9_certTypes_x509Certificate) - _, cert = cert_bag["certValue"].defined - self.assertEqual(Certificate(cert), self.cert_test) - - safe_contents = OctetStringSafeContents().decod(bytes( - outer_safe_contents[1]["bagValue"] - )) - safe_bag = safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_pkcs8ShroudedKeyBag) - shrouded_key_bag = PKCS8ShroudedKeyBag().decod(bytes(safe_bag["bagValue"])) - _, pbes2_params = shrouded_key_bag["encryptionAlgorithm"]["parameters"].defined - _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined - _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined - ukm = bytes(enc_scheme_params["ukm"]) - key = gost34112012_pbkdf2( - password=self.password, - salt=bytes(pbkdf2_params["salt"]["specified"]), - iterations=int(pbkdf2_params["iterationCount"]), - dklen=32, - ) - # key = hexdec("4b7ae649ca31dd5fe3243a91a5188c03f1d7049bec8e0d241c0e1e8c39ea4c1f") - key_enc, key_mac = kdf_tree_gostr3411_2012_256( - key, b"kdf tree", ukm[GOST3412Kuznechik.blocksize // 2:], 2, - ) - ciphertext = bytes(shrouded_key_bag["encryptedData"]) - plaintext = ctr_acpkm( - GOST3412Kuznechik, - GOST3412Kuznechik(key_enc).encrypt, - section_size=256 * 1024, - bs=GOST3412Kuznechik.blocksize, - data=ciphertext, - iv=ukm[:GOST3412Kuznechik.blocksize // 2], - ) - mac_expected = plaintext[-GOST3412Kuznechik.blocksize:] - plaintext = plaintext[:-GOST3412Kuznechik.blocksize] - mac = omac( - GOST3412Kuznechik(key_mac).encrypt, - GOST3412Kuznechik.blocksize, - plaintext, - ) - self.assertSequenceEqual(mac, mac_expected) - self.assertSequenceEqual(plaintext, self.prv_test_raw) - - mac_data = pfx["macData"] - mac_key = gost34112012_pbkdf2( - password=self.password, - salt=bytes(mac_data["macSalt"]), - iterations=int(mac_data["iterations"]), - dklen=96, - )[-32:] - # mac_key = hexdec("a81d1bc91a4a5cf1fd7320f92dda7e5b285816c3b20826a382d7ed0cbf3a9bf4") - self.assertSequenceEqual( - hmac_new( - key=mac_key, - msg=SafeContents(outer_safe_contents).encode(), - digestmod=GOST34112012512, - ).digest(), - bytes(mac_data["mac"]["digest"]), - ) - self.assertTrue(gost3410.verify( - self.ca_curve, - self.ca_pub, - GOST34112012256(cert["tbsCertificate"].encode()).digest()[::-1], - bytes(cert["signatureValue"]), - )) - - def test_encrypted_cert_and_key(self): - pfx_raw = b64decode(b""" - MIIFjAIBAzCCBSUGCSqGSIb3DQEHAaCCBRYEggUSMIIFDjCCA0EGCSqGSIb3DQEH - BqCCAzIwggMuAgEAMIIDJwYJKoZIhvcNAQcBMFUGCSqGSIb3DQEFDTBIMCkGCSqG - SIb3DQEFDDAcBAgUuSVGsSwGjQICCAAwDAYIKoUDBwEBBAIFADAbBgkqhQMHAQEF - AQIwDgQM9Hk3dagtS48+G/x+gIICwWGPqxxN+sTrKbruRf9R5Ya9cf5AtO1frqMn - f1eULfmZmTg/BdE51QQ+Vbnh3v1kmspr6h2+e4Wli+ndEeCWG6A6X/G22h/RAHW2 - YrVmf6cCWxW+YrqzT4h/8RQL/9haunD5LmHPLVsYrEai0OwbgXayDSwARVJQLQYq - sLNmZK5ViN+fRiS5wszVJ3AtVq8EuPt41aQEKwPy2gmH4S6WmnQRC6W7aoqmIifF - PJENJNn5K2M1J6zNESs6bFtYNKMArNqtvv3rioY6eAaaLy6AV6ljsekmqodHmQjv - Y4eEioJs0xhpXhZY69PXT+ZBeHv6MSheBhwXqxAd1DqtPTafMjNK8rqKCap9TtPG - vONvo5W9dgwegxRRQzlum8dzV4m1W9Aq4W7t8/UcxDWRz3k6ijFPlGaA9+8ZMTEO - RHhBRvM6OY2/VNNxbgxWfGYuPxpSi3YnCZIPmBEe5lU/Xv7KjzFusGM38F8YR61k - 4/QNpKI1QUv714YKfaUQznshGGzILv1NGID62pl1+JI3vuawi2mDMrmkuM9QFU9v - /kRP+c2uBHDuOGEUUSNhF08p7+w3vxplatGWXH9fmIsPBdk2f3wkn+rwoqrEuijM - I/bCAylU/M0DMKhAo9j31UYSZdi4fsfRWYDJMq/8FPn96tuo+oCpbqv3NUwpZM/8 - Li4xqgTHtYw/+fRG0/P6XadNEiII/TYjenLfVHXjAHOVJsVeCu/t3EsMYHQddNCh - rFk/Ic2PdIQOyB4/enpW0qrKegSbyZNuF1WI4zl4mI89L8dTQBUkhy45yQXZlDD8 - k1ErYdtdEsPtz/4zuSpbnmwCEIRoOuSXtGuJP+tbcWEXRKM2UBgi3qBjpn7DU18M - tsrRM9pDdadl8mT/Vfh9+B8dZBZVxgQu70lMPEGexbUkYHuFCCnyi9J0V92StbIz - Elxla1VebjCCAcUGCSqGSIb3DQEHAaCCAbYEggGyMIIBrjCCAaoGCyqGSIb3DQEM - CgECoIIBQzCCAT8wVQYJKoZIhvcNAQUNMEgwKQYJKoZIhvcNAQUMMBwECP0EQk0O - 1twvAgIIADAMBggqhQMHAQEEAgUAMBsGCSqFAwcBAQUBATAOBAzwxSqgAAAAAAAA - AAAEgeUqj9mI3RDfK5hMd0EeYws7foZK/5ANr2wUhP5qnDjAZgn76lExJ+wuvlnS - 9PChfWVugvdl/9XJgQvvr9Cu4pOh4ICXplchcy0dGk/MzItHRVC5wK2nTxwQ4kKT - kG9xhLFzoD16dhtqX0+/dQg9G8pE5EzCBIYRXLm1Arcz9k7KVsTJuNMjFrr7EQuu - Tr80ATSQOtsq50zpFyrpznVPGCrOdIjpymZxNdvw48bZxqTtRVDxCYATOGqz0pwH - ClWULHD9LIajLMB2GhBKyQw6ujIlltJs0T+WNdX/AT2FLi1LFSS3+Cj9MVQwIwYJ - KoZIhvcNAQkVMRYEFHlVdPnUtuTCAiQoaZhnP/AKFMBNMC0GCSqGSIb3DQEJFDEg - Hh4AcAAxADIARgByAGkAZQBuAGQAbAB5AE4AYQBtAGUwXjBOMAoGCCqFAwcBAQID - BEDp4e22JmXdnvR0xA99yQuzQuJ8pxBeOpsLm2dZQqt3Fje5zqW1uk/7VOcfV5r2 - bKm8nsLOs2rPT8hBOoeAZvOIBAjGIUHw6IjG2QICCAA= - """) - pfx = PFX().decod(pfx_raw) - _, outer_safe_contents = pfx["authSafe"]["content"].defined - - encrypted_data = EncryptedData().decod(bytes( - outer_safe_contents[0]["bagValue"] - )) - eci = encrypted_data["encryptedContentInfo"] - self.assertEqual(eci["contentEncryptionAlgorithm"]["algorithm"], id_pbes2) - pbes2_params = PBES2Params().decod(bytes( - eci["contentEncryptionAlgorithm"]["parameters"] - )) - _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined - _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined - ukm = bytes(enc_scheme_params["ukm"]) - key = gost34112012_pbkdf2( - password=self.password, - salt=bytes(pbkdf2_params["salt"]["specified"]), - iterations=int(pbkdf2_params["iterationCount"]), - dklen=32, - ) - # key = hexdec("d066a96fb326ba896a2352d3f40240a4ded6e7e7bd5b4db6b5241d631c8c381c") - key_enc, key_mac = kdf_tree_gostr3411_2012_256( - key, b"kdf tree", ukm[GOST3412Magma.blocksize // 2:], 2, - ) - ciphertext = bytes(eci["encryptedContent"]) - plaintext = ctr_acpkm( - GOST3412Magma, - GOST3412Magma(key_enc).encrypt, - section_size=8 * 1024, - bs=GOST3412Magma.blocksize, - data=ciphertext, - iv=ukm[:GOST3412Magma.blocksize // 2], - ) - mac_expected = plaintext[-GOST3412Magma.blocksize:] - plaintext = plaintext[:-GOST3412Magma.blocksize] - mac = omac( - GOST3412Magma(key_mac).encrypt, - GOST3412Magma.blocksize, - plaintext, - ) - self.assertSequenceEqual(mac, mac_expected) - - safe_contents = SafeContents().decod(plaintext) - safe_bag = safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_certBag) - cert_bag = CertBag().decod(bytes(safe_bag["bagValue"])) - self.assertEqual(cert_bag["certId"], id_pkcs9_certTypes_x509Certificate) - _, cert = cert_bag["certValue"].defined - self.assertEqual(Certificate(cert), self.cert_test) - - safe_contents = OctetStringSafeContents().decod(bytes( - outer_safe_contents[1]["bagValue"] - )) - safe_bag = safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_pkcs8ShroudedKeyBag) - shrouded_key_bag = PKCS8ShroudedKeyBag().decod(bytes(safe_bag["bagValue"])) - _, pbes2_params = shrouded_key_bag["encryptionAlgorithm"]["parameters"].defined - _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined - _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined - ukm = bytes(enc_scheme_params["ukm"]) - key = gost34112012_pbkdf2( - password=self.password, - salt=bytes(pbkdf2_params["salt"]["specified"]), - iterations=int(pbkdf2_params["iterationCount"]), - dklen=32, - ) - # key = hexdec("f840d001fd11441e0fb7ccf48f471915e5bf35275309dbe7ade9da4fe460ba7e") - ciphertext = bytes(shrouded_key_bag["encryptedData"]) - plaintext = ctr_acpkm( - GOST3412Magma, - GOST3412Magma(key).encrypt, - section_size=8 * 1024, - bs=GOST3412Magma.blocksize, - data=ciphertext, - iv=ukm[:GOST3412Magma.blocksize // 2], - ) - self.assertSequenceEqual(plaintext, self.prv_test_raw) - - mac_data = pfx["macData"] - mac_key = gost34112012_pbkdf2( - password=self.password, - salt=bytes(mac_data["macSalt"]), - iterations=int(mac_data["iterations"]), - dklen=96, - )[-32:] - # mac_key = hexdec("084f81782af1534ffd67e3c579c14cb45d7a6f659f46fdbb51a552e874e66fb2") - self.assertSequenceEqual( - hmac_new( - key=mac_key, - msg=SafeContents(outer_safe_contents).encode(), - digestmod=GOST34112012512, - ).digest(), - bytes(mac_data["mac"]["digest"]), - ) - - def test_dh(self): - curve = gost3410.CURVES["id-tc26-gost-3410-12-256-paramSetA"] - # sender_prv_raw = hexdec("0B20810E449978C7C3B76C6FF77A16C532421139344A058EF56310B6B6F377E8") - sender_cert = Certificate().decod(b64decode(""" - MIIB6zCCAZigAwIBAgIEAYy6gjAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 - MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw - MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRUSzI2MSowKAYD - VQQDEyFPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwXjAXBggqhQMH - AQEBATALBgkqhQMHAQIBAQEDQwAEQJYpDRNiWWqDgaZje0EmLLOldQ35o5X1ZuZN - SKequYQc/soI3OgDMWD7ThJJCk01IelCeb6MsBmG4lol+pnpVtOjgYcwgYQwYwYD - VR0jBFwwWoAUrGwOTERmokKW4p8JOyVm88ukUyqhPKQ6MDgxDTALBgNVBAoTBFRL - MjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdIIEAYy6 - gTAdBgNVHQ4EFgQUPx5RgcjkifhlJm4/jQdkbm30rVQwCgYIKoUDBwEBAwIDQQA6 - 8x7Vk6PvP/8xOGHhf8PuqaXAYskSyJPuBu+3Bo/PEj10devwc1J9uYWIDCGdKKPy - bSlnQHqUPBBPM30YX1YN - """)) - recipient_prv_raw = hexdec("0DC8DC1FF2BC114BABC3F1CA8C51E4F58610427E197B1C2FBDBA4AE58CBFB7CE")[::-1] - recipient_prv = gost3410.prv_unmarshal(recipient_prv_raw) - recipient_cert = Certificate().decod(b64decode(""" - MIIB6jCCAZegAwIBAgIEAYy6gzAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 - MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw - MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA6MQ0wCwYDVQQKEwRUSzI2MSkwJwYD - VQQDEyBSRUNJUElFTlQ6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdDBeMBcGCCqFAwcB - AQEBMAsGCSqFAwcBAgEBAQNDAARAvyeCGXMsYwpYe5aE0w8w3m4vpKQapGInqpnF - lv7h08psFP0s1W80q3BR534F4TmR+o5+iU+AW6ycvWuc73JEQ6OBhzCBhDBjBgNV - HSMEXDBagBSsbA5MRGaiQpbinwk7JWbzy6RTKqE8pDowODENMAsGA1UEChMEVEsy - NjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYtYml0ggQBjLqB - MB0GA1UdDgQWBBQ35gHPN1bx8l2eEMTbrtIg+5MU0TAKBggqhQMHAQEDAgNBABF2 - RHDaRqQuBS2yu7yGIGFgA6c/LG4GKjSOwYsRVmXJNNkQ4TB7PB8j3q7gx2koPsVB - m90WfMWSL6SNSh3muuM= - """)) - self.assertTrue(gost3410.verify( - self.ca_curve, - self.ca_pub, - GOST34112012256(sender_cert["tbsCertificate"].encode()).digest()[::-1], - bytes(sender_cert["signatureValue"]), - )) - self.assertTrue(gost3410.verify( - self.ca_curve, - self.ca_pub, - GOST34112012256(recipient_cert["tbsCertificate"].encode()).digest()[::-1], - bytes(recipient_cert["signatureValue"]), - )) - - pfx_raw = b64decode(""" - MIIKVwIBAzCCClAGCSqGSIb3DQEHAqCCCkEwggo9AgEBMQwwCgYIKoUDBwEBAgIw - ggcjBgkqhkiG9w0BBwGgggcUBIIHEDCCBwwwggKdBgkqhkiG9w0BBwGgggKOBIIC - ijCCAoYwggKCBgsqhkiG9w0BDAoBA6CCAkowggJGBgoqhkiG9w0BCRYBoIICNgSC - AjIwggIuMIIB26ADAgECAgQBjLqEMAoGCCqFAwcBAQMCMDgxDTALBgNVBAoTBFRL - MjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdDAeFw0w - MTAxMDEwMDAwMDBaFw00OTEyMzEwMDAwMDBaMDsxDTALBgNVBAoTBFRLMjYxKjAo - BgNVBAMTIU9SSUdJTkFUT1I6IEdPU1QgMzQuMTAtMTIgNTEyLWJpdDCBoDAXBggq - hQMHAQEBAjALBgkqhQMHAQIBAgEDgYQABIGAtIu3WrwpDhhlXGKhT7UtX1CETswd - H2AESHtLXJU0aWq3v6s0blUWqas8zvittSw6WFXwz7Nkqmtd2Tfk7PyVJb+faghQ - dnGKRcgf9JIePiu/cr8+6/PuFhNBJmX/E92nvydSaOsRrp3nB9fxuITLbPR2C58W - 8CQzDVRriB1eoM6jgYcwgYQwYwYDVR0jBFwwWoAUrGwOTERmokKW4p8JOyVm88uk - UyqhPKQ6MDgxDTALBgNVBAoTBFRLMjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1Qg - MzQuMTAtMTIgMjU2LWJpdIIEAYy6gTAdBgNVHQ4EFgQUfgZXCZgMrWsIqFfueQBY - OsnXoKQwCgYIKoUDBwEBAwIDQQAKXqnx0BumL0eT7eaAzIjRYiHXsiuWtKn+YHQX - tnMy3xdQPUPDPcmuufF5ed8y84DkF1Qn2ELIOAxUAaz8hwQQMSUwIwYJKoZIhvcN - AQkVMRYEFHlVdPnUtuTCAiQoaZhnP/AKFMBNMIIEZwYJKoZIhvcNAQcDoIIEWDCC - BFQCAQKgggHzoIIB7zCCAeswggGYoAMCAQICBAGMuoIwCgYIKoUDBwEBAwIwODEN - MAsGA1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAy - NTYtYml0MB4XDTAxMDEwMTAwMDAwMFoXDTQ5MTIzMTAwMDAwMFowOzENMAsGA1UE - ChMEVEsyNjEqMCgGA1UEAxMhT1JJR0lOQVRPUjogR09TVCAzNC4xMC0xMiAyNTYt - Yml0MF4wFwYIKoUDBwEBAQEwCwYJKoUDBwECAQEBA0MABECWKQ0TYllqg4GmY3tB - JiyzpXUN+aOV9WbmTUinqrmEHP7KCNzoAzFg+04SSQpNNSHpQnm+jLAZhuJaJfqZ - 6VbTo4GHMIGEMGMGA1UdIwRcMFqAFKxsDkxEZqJCluKfCTslZvPLpFMqoTykOjA4 - MQ0wCwYDVQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEy - IDI1Ni1iaXSCBAGMuoEwHQYDVR0OBBYEFD8eUYHI5In4ZSZuP40HZG5t9K1UMAoG - CCqFAwcBAQMCA0EAOvMe1ZOj7z//MThh4X/D7qmlwGLJEsiT7gbvtwaPzxI9dHXr - 8HNSfbmFiAwhnSij8m0pZ0B6lDwQTzN9GF9WDTGB/6GB/AIBA6BCMEAwODENMAsG - A1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYt - Yml0AgQBjLqCoSIEIBt4fjey+k8C1D3OaMca8wl6h3j3C6OAbrx8rmxXktsQMBcG - CSqFAwcBAQcCATAKBggqhQMHAQEGATB2MHQwQDA4MQ0wCwYDVQQKEwRUSzI2MScw - JQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQCBAGMuoMEMJkp - Wae6IVfaY3mP0izRY7ifc41fATXdJ2tmTl+1vitkSE2vLCKXDLl90KfHA6gNmDCC - AVQGCSqGSIb3DQEHATAfBgkqhQMHAQEFAgEwEgQQFhEshEBO2LkAAAAAAAAAAICC - ASQYvLpT/8azEXJfekyGuyvE9UkVX+Ao8sfu9My/c4WAVRNMhZkCqD+BbPwBsIzN - sXZIi9rXGAfsPz7xaO9EUFZPjNOWtF/E01oJgG+gYLFn7qAiEFcmRLptSHuanNHn - 7Yol6IHushX4UaW9hEa/L6eFQx/hoDhrNZnWTXNZtNuHuMGC9dzhHhTxfkdjZYXD - v+M7psVj58JutE3U2d4pgxKcBPdMO4vl4+27cIKxQZFZU2zuCVJLYLqmPT5pCBkM - mJqy7bZwHOJ9kBq/TGUf8iJGYSCNre3RTNLbcTTk7rZrbiMkFsG3borzenpouS5E - BcCkBt8Mj0nvsMCu9ipHTuWww7LltlkXCjlNXFUi6ZI3VyHW5CDpghujQWiZxiAc - JuGl6GwZoIIB7zCCAeswggGYoAMCAQICBAGMuoIwCgYIKoUDBwEBAwIwODENMAsG - A1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYt - Yml0MB4XDTAxMDEwMTAwMDAwMFoXDTQ5MTIzMTAwMDAwMFowOzENMAsGA1UEChME - VEsyNjEqMCgGA1UEAxMhT1JJR0lOQVRPUjogR09TVCAzNC4xMC0xMiAyNTYtYml0 - MF4wFwYIKoUDBwEBAQEwCwYJKoUDBwECAQEBA0MABECWKQ0TYllqg4GmY3tBJiyz - pXUN+aOV9WbmTUinqrmEHP7KCNzoAzFg+04SSQpNNSHpQnm+jLAZhuJaJfqZ6VbT - o4GHMIGEMGMGA1UdIwRcMFqAFKxsDkxEZqJCluKfCTslZvPLpFMqoTykOjA4MQ0w - CwYDVQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1 - Ni1iaXSCBAGMuoEwHQYDVR0OBBYEFD8eUYHI5In4ZSZuP40HZG5t9K1UMAoGCCqF - AwcBAQMCA0EAOvMe1ZOj7z//MThh4X/D7qmlwGLJEsiT7gbvtwaPzxI9dHXr8HNS - fbmFiAwhnSij8m0pZ0B6lDwQTzN9GF9WDTGCAQ4wggEKAgEBMEAwODENMAsGA1UE - ChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYtYml0 - AgQBjLqCMAoGCCqFAwcBAQICoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc - BgkqhkiG9w0BCQUxDxcNMjEwNDE0MTkyMTEyWjAvBgkqhkiG9w0BCQQxIgQg1XOA - zNa710QuXsn5+yIf3cNTiFOQMgTiBRJBz8Tr4I0wCgYIKoUDBwEBAQEEQALINal9 - 7wHXYiG+w0yzSkKOs0jRZew0S73r/cfk/sUoM3HKKIEbKruvlAdiOqX/HLFSEx/s - kxFG6QUFH8uuoX8= - """) - pfx = PFX().decod(pfx_raw) - self.assertEqual(pfx["authSafe"]["contentType"], id_signedData) - - sd = SignedData().decod(bytes(pfx["authSafe"]["content"])) - self.assertEqual(sd["certificates"][0]["certificate"], sender_cert) - si = sd["signerInfos"][0] - self.assertEqual( - si["digestAlgorithm"]["algorithm"], - id_tc26_gost3411_2012_256, - ) - digest = [ - bytes(attr["attrValues"][0].defined[1]) for attr in si["signedAttrs"] - if attr["attrType"] == id_messageDigest - ][0] - sender_pub = gost3410.pub_unmarshal(bytes(OctetString().decod(bytes( - sender_cert["tbsCertificate"]["subjectPublicKeyInfo"]["subjectPublicKey"] - )))) - content = bytes(sd["encapContentInfo"]["eContent"]) - self.assertSequenceEqual(digest, GOST34112012256(content).digest()) - self.assertTrue(gost3410.verify( - curve, - sender_pub, - GOST34112012256( - SignedAttributes(si["signedAttrs"]).encode() - ).digest()[::-1], - bytes(si["signature"]), - )) - - outer_safe_contents = SafeContents().decod(content) - - safe_bag = outer_safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_data) - safe_contents = OctetStringSafeContents().decod(bytes(safe_bag["bagValue"])) - safe_bag = safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_certBag) - cert_bag = CertBag().decod(bytes(safe_bag["bagValue"])) - self.assertEqual(cert_bag["certId"], id_pkcs9_certTypes_x509Certificate) - _, cert = cert_bag["certValue"].defined - self.assertEqual(Certificate(cert), self.cert_test) - - safe_bag = outer_safe_contents[1] - self.assertEqual(safe_bag["bagId"], id_envelopedData) - ed = EnvelopedData().decod(bytes(safe_bag["bagValue"])) - kari = ed["recipientInfos"][0]["kari"] - ukm = bytes(kari["ukm"]) - self.assertEqual( - kari["keyEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_wrap_kexp15, - ) - self.assertEqual( - kari["keyEncryptionAlgorithm"]["parameters"].defined[1]["algorithm"], - id_tc26_agreement_gost3410_2012_256, - ) - kexp = bytes(kari["recipientEncryptedKeys"][0]["encryptedKey"]) - keymat = keg(curve, recipient_prv, sender_pub, ukm) - kim, kek = keymat[:KEYSIZE], keymat[KEYSIZE:] - cek = kimp15( - GOST3412Kuznechik(kek).encrypt, - GOST3412Kuznechik(kim).encrypt, - GOST3412Kuznechik.blocksize, - kexp, - ukm[24:24 + GOST3412Kuznechik.blocksize // 2], - ) - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_ctracpkm, - ) - eci_ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - content = ctr_acpkm( - GOST3412Kuznechik, - GOST3412Kuznechik(cek).encrypt, - 256 * 1024, - GOST3412Kuznechik.blocksize, - bytes(eci["encryptedContent"]), - eci_ukm[:GOST3412Kuznechik.blocksize // 2], - ) - - safe_contents = SafeContents().decod(content) - safe_bag = safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_keyBag) - KeyBag().decod(bytes(safe_bag["bagValue"])) - self.assertSequenceEqual(bytes(safe_bag["bagValue"]), self.prv_test_raw) diff --git a/pygost-5.13/build/lib/pygost/test_wrap.py b/pygost-5.13/build/lib/pygost/test_wrap.py deleted file mode 100644 index 7ecf8ee..0000000 --- a/pygost-5.13/build/lib/pygost/test_wrap.py +++ /dev/null @@ -1,111 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from unittest import TestCase - -from pygost.gost28147 import DEFAULT_SBOX -from pygost.gost3412 import GOST3412Kuznechik -from pygost.gost3412 import GOST3412Magma -from pygost.utils import hexdec -from pygost.wrap import kexp15 -from pygost.wrap import kimp15 -from pygost.wrap import unwrap_cryptopro -from pygost.wrap import unwrap_gost -from pygost.wrap import wrap_cryptopro -from pygost.wrap import wrap_gost - - -class WrapGostTest(TestCase): - def test_symmetric(self): - for sbox in (DEFAULT_SBOX, "id-tc26-gost-28147-param-Z"): - for _ in range(1 << 8): - kek = urandom(32) - cek = urandom(32) - ukm = urandom(8) - wrapped = wrap_gost(ukm, kek, cek, sbox=sbox) - unwrapped = unwrap_gost(kek, wrapped, sbox=sbox) - self.assertSequenceEqual(unwrapped, cek) - - def test_invalid_length(self): - with self.assertRaises(ValueError): - unwrap_gost(urandom(32), urandom(41)) - with self.assertRaises(ValueError): - unwrap_gost(urandom(32), urandom(45)) - - -class WrapCryptoproTest(TestCase): - def test_symmetric(self): - for sbox in (DEFAULT_SBOX, "id-tc26-gost-28147-param-Z"): - for _ in range(1 << 8): - kek = urandom(32) - cek = urandom(32) - ukm = urandom(8) - wrapped = wrap_cryptopro(ukm, kek, cek, sbox=sbox) - unwrapped = unwrap_cryptopro(kek, wrapped, sbox=sbox) - self.assertSequenceEqual(unwrapped, cek) - - -class TestVectorKExp15(TestCase): - """Test vectors from Р 1323565.1.017-2018 - """ - key = hexdec("8899AABBCCDDEEFF0011223344556677FEDCBA98765432100123456789ABCDEF") - key_enc = hexdec("202122232425262728292A2B2C2D2E2F38393A3B3C3D3E3F3031323334353637") - key_mac = hexdec("08090A0B0C0D0E0F0001020304050607101112131415161718191A1B1C1D1E1F") - - def test_magma(self): - iv = hexdec("67BED654") - kexp = kexp15( - GOST3412Magma(self.key_enc).encrypt, - GOST3412Magma(self.key_mac).encrypt, - GOST3412Magma.blocksize, - self.key, - iv, - ) - self.assertSequenceEqual(kexp, hexdec(""" -CF D5 A1 2D 5B 81 B6 E1 E9 9C 91 6D 07 90 0C 6A -C1 27 03 FB 3A BD ED 55 56 7B F3 74 2C 89 9C 75 -5D AF E7 B4 2E 3A 8B D9 - """.replace("\n", "").replace(" ", ""))) - self.assertSequenceEqual(kimp15( - GOST3412Magma(self.key_enc).encrypt, - GOST3412Magma(self.key_mac).encrypt, - GOST3412Magma.blocksize, - kexp, - iv, - ), self.key) - - def test_kuznechik(self): - iv = hexdec("0909472DD9F26BE8") - kexp = kexp15( - GOST3412Kuznechik(self.key_enc).encrypt, - GOST3412Kuznechik(self.key_mac).encrypt, - GOST3412Kuznechik.blocksize, - self.key, - iv, - ) - self.assertSequenceEqual(kexp, hexdec(""" -E3 61 84 E8 4E 8D 73 6F F3 6C C2 E5 AE 06 5D C6 -56 B2 3C 20 F5 49 B0 2F DF F8 8E 1F 3F 30 D8 C2 -9A 53 F3 CA 55 4D BA D8 0D E1 52 B9 A4 62 5B 32 - """.replace("\n", "").replace(" ", ""))) - self.assertSequenceEqual(kimp15( - GOST3412Kuznechik(self.key_enc).encrypt, - GOST3412Kuznechik(self.key_mac).encrypt, - GOST3412Kuznechik.blocksize, - kexp, - iv, - ), self.key) diff --git a/pygost-5.13/build/lib/pygost/test_x509.py b/pygost-5.13/build/lib/pygost/test_x509.py deleted file mode 100644 index e9fccb7..0000000 --- a/pygost-5.13/build/lib/pygost/test_x509.py +++ /dev/null @@ -1,433 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 CURVES -from pygost.gost3410 import prv_unmarshal -from pygost.gost3410 import pub_marshal -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 Any - from pyderasn import BitString - from pyderasn import Boolean - from pyderasn import GeneralizedTime - from pyderasn import Integer - from pyderasn import OctetString - from pyderasn import PrintableString - from pyderasn import UTCTime - - from pygost.asn1schemas.oids import id_at_commonName - from pygost.asn1schemas.oids import id_ce_basicConstraints - from pygost.asn1schemas.oids import id_GostR3410_2001_TestParamSet - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256 - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetA - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512 - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetTest - from pygost.asn1schemas.oids import id_tc26_gost3411_2012_256 - from pygost.asn1schemas.oids import id_tc26_signwithdigest_gost3410_2012_256 - from pygost.asn1schemas.oids import id_tc26_signwithdigest_gost3410_2012_512 - from pygost.asn1schemas.pkcs10 import Attributes - from pygost.asn1schemas.pkcs10 import CertificationRequest - from pygost.asn1schemas.pkcs10 import CertificationRequestInfo - from pygost.asn1schemas.x509 import AlgorithmIdentifier - from pygost.asn1schemas.x509 import AttributeType - from pygost.asn1schemas.x509 import AttributeTypeAndValue - from pygost.asn1schemas.x509 import AttributeValue - from pygost.asn1schemas.x509 import BasicConstraints - from pygost.asn1schemas.x509 import Certificate - from pygost.asn1schemas.x509 import CertificateList - from pygost.asn1schemas.x509 import CertificateSerialNumber - from pygost.asn1schemas.x509 import Extension - from pygost.asn1schemas.x509 import Extensions - from pygost.asn1schemas.x509 import GostR34102012PublicKeyParameters - from pygost.asn1schemas.x509 import Name - from pygost.asn1schemas.x509 import RDNSequence - from pygost.asn1schemas.x509 import RelativeDistinguishedName - from pygost.asn1schemas.x509 import SubjectPublicKeyInfo - from pygost.asn1schemas.x509 import TBSCertificate - from pygost.asn1schemas.x509 import TBSCertList - from pygost.asn1schemas.x509 import Time - from pygost.asn1schemas.x509 import Validity - from pygost.asn1schemas.x509 import Version - -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, hasher, prv_key_raw, cert_raw): - cert, tail = Certificate().decode(cert_raw, ctx={ - "defines_by_path": ( - ( - ( - "tbsCertificate", - "subjectPublicKeyInfo", - "algorithm", - "algorithm", - ), - ( - ( - ("..", "subjectPublicKey"), - { - id_tc26_gost3410_2012_256: OctetString(), - id_tc26_gost3410_2012_512: OctetString(), - }, - ), - ), - ), - ), - }) - self.assertSequenceEqual(tail, b"") - curve = CURVES[curve_name] - prv_key = prv_unmarshal(prv_key_raw) - spk = cert["tbsCertificate"]["subjectPublicKeyInfo"]["subjectPublicKey"] - self.assertIsNotNone(spk.defined) - _, pub_key_raw = spk.defined - pub_key = pub_unmarshal(bytes(pub_key_raw)) - self.assertSequenceEqual(pub_key, public_key(curve, prv_key)) - self.assertTrue(verify( - curve, - pub_key, - hasher(cert["tbsCertificate"].encode()).digest()[::-1], - bytes(cert["signatureValue"]), - )) - - 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( - "id-GostR3410-2001-CryptoPro-XchA-ParamSet", - 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( - "id-tc26-gost-3410-12-512-paramSetB", - GOST34112012512, - prv_key_raw, - cert_raw, - ) - - -@skipIf(not pyderasn_exists, "PyDERASN dependency is required") -class TestRFC4491bis(TestCase): - """Test vectors from https://tools.ietf.org/html/draft-deremin-rfc4491-bis-02 - """ - - def _test_vector( - self, - curve_name, - hsh, - ai_spki, - ai_sign, - cert_serial, - prv_hex, - cr_sign_hex, - cr_b64, - c_sign_hex, - c_b64, - crl_sign_hex, - crl_b64, - ): - prv_raw = hexdec(prv_hex)[::-1] - prv = prv_unmarshal(prv_raw) - curve = CURVES[curve_name] - pub = public_key(curve, prv) - pub_raw = pub_marshal(pub) - subj = Name(("rdnSequence", RDNSequence([ - RelativeDistinguishedName(( - AttributeTypeAndValue(( - ("type", AttributeType(id_at_commonName)), - ("value", AttributeValue(PrintableString("Example"))), - )), - )) - ]))) - spki = SubjectPublicKeyInfo(( - ("algorithm", ai_spki), - ("subjectPublicKey", BitString(OctetString(pub_raw).encode())), - )) - - # Certification request - cri = CertificationRequestInfo(( - ("version", Integer(0)), - ("subject", subj), - ("subjectPKInfo", spki), - ("attributes", Attributes()), - )) - sign = hexdec(cr_sign_hex) - self.assertTrue(verify( - curve, - pub, - hsh(cri.encode()).digest()[::-1], - sign, - )) - cr = CertificationRequest(( - ("certificationRequestInfo", cri), - ("signatureAlgorithm", ai_sign), - ("signature", BitString(sign)), - )) - self.assertSequenceEqual(cr.encode(), b64decode(cr_b64)) - - # Certificate - tbs = TBSCertificate(( - ("version", Version("v3")), - ("serialNumber", CertificateSerialNumber(cert_serial)), - ("signature", ai_sign), - ("issuer", subj), - ("validity", Validity(( - ("notBefore", Time(("utcTime", UTCTime(b"010101000000Z")))), - ("notAfter", Time(("generalTime", GeneralizedTime(b"20501231000000Z")))), - ))), - ("subject", subj), - ("subjectPublicKeyInfo", spki), - ("extensions", Extensions(( - Extension(( - ("extnID", id_ce_basicConstraints), - ("critical", Boolean(True)), - ("extnValue", OctetString( - BasicConstraints((("cA", Boolean(True)),)).encode() - )), - )), - ))), - )) - sign = hexdec(c_sign_hex) - self.assertTrue(verify( - curve, - pub, - hsh(tbs.encode()).digest()[::-1], - sign, - )) - cert = Certificate(( - ("tbsCertificate", tbs), - ("signatureAlgorithm", ai_sign), - ("signatureValue", BitString(sign)), - )) - self.assertSequenceEqual(cert.encode(), b64decode(c_b64)) - - # CRL - tbs = TBSCertList(( - ("version", Version("v2")), - ("signature", ai_sign), - ("issuer", subj), - ("thisUpdate", Time(("utcTime", UTCTime(b"140101000000Z")))), - ("nextUpdate", Time(("utcTime", UTCTime(b"140102000000Z")))), - )) - sign = hexdec(crl_sign_hex) - self.assertTrue(verify( - curve, - pub, - hsh(tbs.encode()).digest()[::-1], - sign, - )) - crl = CertificateList(( - ("tbsCertList", tbs), - ("signatureAlgorithm", ai_sign), - ("signatureValue", BitString(sign)), - )) - self.assertSequenceEqual(crl.encode(), b64decode(crl_b64)) - - def test_256_test_paramset(self): - self._test_vector( - "id-GostR3410-2001-TestParamSet", - GOST34112012256, - AlgorithmIdentifier(( - ("algorithm", id_tc26_gost3410_2012_256), - ("parameters", Any( - GostR34102012PublicKeyParameters(( - ("publicKeyParamSet", id_GostR3410_2001_TestParamSet), - ("digestParamSet", id_tc26_gost3411_2012_256), - )) - )), - )), - AlgorithmIdentifier(( - ("algorithm", id_tc26_signwithdigest_gost3410_2012_256), - )), - 10, - "7A929ADE789BB9BE10ED359DD39A72C11B60961F49397EEE1D19CE9891EC3B28", - "6AAAB38E35D4AAA517940301799122D855484F579F4CBB96D63CDFDF3ACC432A41AA28D2F1AB148280CD9ED56FEDA41974053554A42767B83AD043FD39DC0493", - """ -MIHTMIGBAgEAMBIxEDAOBgNVBAMTB0V4YW1wbGUwZjAfBggqhQMHAQEBATATBgcq -hQMCAiMABggqhQMHAQECAgNDAARAC9hv5djbiWaPeJtOHbqFhcVQi0XsW1nYkG3b -cOJJK3/ad/+HGhD73ydm0pPF0WSvuzx7lzpByIXRHXDWibTxJqAAMAoGCCqFAwcB -AQMCA0EAaqqzjjXUqqUXlAMBeZEi2FVIT1efTLuW1jzf3zrMQypBqijS8asUgoDN -ntVv7aQZdAU1VKQnZ7g60EP9OdwEkw== - """, - "4D53F012FE081776507D4D9BB81F00EFDB4EEFD4AB83BAC4BACF735173CFA81C41AA28D2F1AB148280CD9ED56FEDA41974053554A42767B83AD043FD39DC0493", - """ -MIIBLTCB26ADAgECAgEKMAoGCCqFAwcBAQMCMBIxEDAOBgNVBAMTB0V4YW1wbGUw -IBcNMDEwMTAxMDAwMDAwWhgPMjA1MDEyMzEwMDAwMDBaMBIxEDAOBgNVBAMTB0V4 -YW1wbGUwZjAfBggqhQMHAQEBATATBgcqhQMCAiMABggqhQMHAQECAgNDAARAC9hv -5djbiWaPeJtOHbqFhcVQi0XsW1nYkG3bcOJJK3/ad/+HGhD73ydm0pPF0WSvuzx7 -lzpByIXRHXDWibTxJqMTMBEwDwYDVR0TAQH/BAUwAwEB/zAKBggqhQMHAQEDAgNB -AE1T8BL+CBd2UH1Nm7gfAO/bTu/Uq4O6xLrPc1Fzz6gcQaoo0vGrFIKAzZ7Vb+2k -GXQFNVSkJ2e4OtBD/TncBJM= - """, - "42BF392A14D3EBE957AF3E46CB50BF5F4221A003AD3D172753C94A9C37A31D2041AA28D2F1AB148280CD9ED56FEDA41974053554A42767B83AD043FD39DC0493", - """ -MIGSMEECAQEwCgYIKoUDBwEBAwIwEjEQMA4GA1UEAxMHRXhhbXBsZRcNMTQwMTAx -MDAwMDAwWhcNMTQwMTAyMDAwMDAwWjAKBggqhQMHAQEDAgNBAEK/OSoU0+vpV68+ -RstQv19CIaADrT0XJ1PJSpw3ox0gQaoo0vGrFIKAzZ7Vb+2kGXQFNVSkJ2e4OtBD -/TncBJM= - """, - ) - - def test_256a_paramset(self): - self._test_vector( - "id-tc26-gost-3410-2012-256-paramSetA", - GOST34112012256, - AlgorithmIdentifier(( - ("algorithm", id_tc26_gost3410_2012_256), - ("parameters", Any( - GostR34102012PublicKeyParameters(( - ("publicKeyParamSet", id_tc26_gost3410_2012_256_paramSetA), - )) - )), - )), - AlgorithmIdentifier(( - ("algorithm", id_tc26_signwithdigest_gost3410_2012_256), - )), - 10, - "7A929ADE789BB9BE10ED359DD39A72C11B60961F49397EEE1D19CE9891EC3B28", - "1BDC2A1317679B66232F63EA16FF7C64CCAAB9AD855FC6E18091661DB79D48121D0E1DA5BE347C6F1B5256C7AEAC200AD64AC77A6F5B3A0E097318E7AE6EE769", - """ -MIHKMHkCAQAwEjEQMA4GA1UEAxMHRXhhbXBsZTBeMBcGCCqFAwcBAQEBMAsGCSqF -AwcBAgEBAQNDAARAdCeV1L7ohN3yhQ/sA+o/rxhE4B2dpgtkUJOlXibfw5l49ZbP -TU0MbPHRiUPZRJPRa57AoW1RLS4SfMRpGmMY4qAAMAoGCCqFAwcBAQMCA0EAG9wq -Exdnm2YjL2PqFv98ZMyqua2FX8bhgJFmHbedSBIdDh2lvjR8bxtSVseurCAK1krH -em9bOg4Jcxjnrm7naQ== - """, - "140B4DA9124B09CB0D5CE928EE874273A310129492EC0E29369E3B791248578C1D0E1DA5BE347C6F1B5256C7AEAC200AD64AC77A6F5B3A0E097318E7AE6EE769", - """ -MIIBJTCB06ADAgECAgEKMAoGCCqFAwcBAQMCMBIxEDAOBgNVBAMTB0V4YW1wbGUw -IBcNMDEwMTAxMDAwMDAwWhgPMjA1MDEyMzEwMDAwMDBaMBIxEDAOBgNVBAMTB0V4 -YW1wbGUwXjAXBggqhQMHAQEBATALBgkqhQMHAQIBAQEDQwAEQHQnldS+6ITd8oUP -7APqP68YROAdnaYLZFCTpV4m38OZePWWz01NDGzx0YlD2UST0WuewKFtUS0uEnzE -aRpjGOKjEzARMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoUDBwEBAwIDQQAUC02pEksJ -yw1c6Sjuh0JzoxASlJLsDik2njt5EkhXjB0OHaW+NHxvG1JWx66sIArWSsd6b1s6 -DglzGOeubudp - """, - "14BD68087C3B903C7AA28B07FEB2E7BD6FE0963F563267359F5CD8EAB45059AD1D0E1DA5BE347C6F1B5256C7AEAC200AD64AC77A6F5B3A0E097318E7AE6EE769", - """ -MIGSMEECAQEwCgYIKoUDBwEBAwIwEjEQMA4GA1UEAxMHRXhhbXBsZRcNMTQwMTAx -MDAwMDAwWhcNMTQwMTAyMDAwMDAwWjAKBggqhQMHAQEDAgNBABS9aAh8O5A8eqKL -B/6y571v4JY/VjJnNZ9c2Oq0UFmtHQ4dpb40fG8bUlbHrqwgCtZKx3pvWzoOCXMY -565u52k= - """, - ) - - def test_512_test_paramset(self): - self._test_vector( - "id-tc26-gost-3410-2012-512-paramSetTest", - GOST34112012512, - AlgorithmIdentifier(( - ("algorithm", id_tc26_gost3410_2012_512), - ("parameters", Any( - GostR34102012PublicKeyParameters(( - ("publicKeyParamSet", id_tc26_gost3410_2012_512_paramSetTest), - )) - )), - )), - AlgorithmIdentifier(( - ("algorithm", id_tc26_signwithdigest_gost3410_2012_512), - )), - 11, - "0BA6048AADAE241BA40936D47756D7C93091A0E8514669700EE7508E508B102072E8123B2200A0563322DAD2827E2714A2636B7BFD18AADFC62967821FA18DD4", - "433B1D6CE40A51F1E5737EB16AA2C683829A405B9D9127E21260FC9D6AC05D87BF24E26C45278A5C2192A75BA94993ABD6074E7FF1BF03FD2F5397AFA1D945582F86FA60A081091A23DD795E1E3C689EE512A3C82EE0DCC2643C78EEA8FCACD35492558486B20F1C9EC197C90699850260C93BCBCD9C5C3317E19344E173AE36", - """ -MIIBTzCBvAIBADASMRAwDgYDVQQDEwdFeGFtcGxlMIGgMBcGCCqFAwcBAQECMAsG -CSqFAwcBAgECAAOBhAAEgYDh7zDVLGEz3dmdHVxBRVz3302LTJJbvGmvFDPRVlhR -Wt0hRoUMMlxbgcEzvmVaqMTUQOe5io1ZSHsMdpa8xV0R7L53NqnsNX/y/TmTH04R -TLjNo1knCsfw5/9D2UGUGeph/Sq3f12fY1I9O1CgT2PioM9Rt8E63CFWDwvUDMnH -N6AAMAoGCCqFAwcBAQMDA4GBAEM7HWzkClHx5XN+sWqixoOCmkBbnZEn4hJg/J1q -wF2HvyTibEUnilwhkqdbqUmTq9YHTn/xvwP9L1OXr6HZRVgvhvpgoIEJGiPdeV4e -PGie5RKjyC7g3MJkPHjuqPys01SSVYSGsg8cnsGXyQaZhQJgyTvLzZxcMxfhk0Th -c642 - """, - "415703D892F1A5F3F68C4353189A7EE207B80B5631EF9D49529A4D6B542C2CFA15AA2EACF11F470FDE7D954856903C35FD8F955EF300D95C77534A724A0EEE702F86FA60A081091A23DD795E1E3C689EE512A3C82EE0DCC2643C78EEA8FCACD35492558486B20F1C9EC197C90699850260C93BCBCD9C5C3317E19344E173AE36", - """ -MIIBqjCCARagAwIBAgIBCzAKBggqhQMHAQEDAzASMRAwDgYDVQQDEwdFeGFtcGxl -MCAXDTAxMDEwMTAwMDAwMFoYDzIwNTAxMjMxMDAwMDAwWjASMRAwDgYDVQQDEwdF -eGFtcGxlMIGgMBcGCCqFAwcBAQECMAsGCSqFAwcBAgECAAOBhAAEgYDh7zDVLGEz -3dmdHVxBRVz3302LTJJbvGmvFDPRVlhRWt0hRoUMMlxbgcEzvmVaqMTUQOe5io1Z -SHsMdpa8xV0R7L53NqnsNX/y/TmTH04RTLjNo1knCsfw5/9D2UGUGeph/Sq3f12f -Y1I9O1CgT2PioM9Rt8E63CFWDwvUDMnHN6MTMBEwDwYDVR0TAQH/BAUwAwEB/zAK -BggqhQMHAQEDAwOBgQBBVwPYkvGl8/aMQ1MYmn7iB7gLVjHvnUlSmk1rVCws+hWq -LqzxH0cP3n2VSFaQPDX9j5Ve8wDZXHdTSnJKDu5wL4b6YKCBCRoj3XleHjxonuUS -o8gu4NzCZDx47qj8rNNUklWEhrIPHJ7Bl8kGmYUCYMk7y82cXDMX4ZNE4XOuNg== - """, - "3A13FB7AECDB5560EEF6137CFC5DD64691732EBFB3690A1FC0C7E8A4EEEA08307D648D4DC0986C46A87B3FBE4C7AF42EA34359C795954CA39FF3ABBED9051F4D2F86FA60A081091A23DD795E1E3C689EE512A3C82EE0DCC2643C78EEA8FCACD35492558486B20F1C9EC197C90699850260C93BCBCD9C5C3317E19344E173AE36", - """ -MIHTMEECAQEwCgYIKoUDBwEBAwMwEjEQMA4GA1UEAxMHRXhhbXBsZRcNMTQwMTAx -MDAwMDAwWhcNMTQwMTAyMDAwMDAwWjAKBggqhQMHAQEDAwOBgQA6E/t67NtVYO72 -E3z8XdZGkXMuv7NpCh/Ax+ik7uoIMH1kjU3AmGxGqHs/vkx69C6jQ1nHlZVMo5/z -q77ZBR9NL4b6YKCBCRoj3XleHjxonuUSo8gu4NzCZDx47qj8rNNUklWEhrIPHJ7B -l8kGmYUCYMk7y82cXDMX4ZNE4XOuNg== - """, - ) diff --git a/pygost-5.13/build/lib/pygost/utils.py b/pygost-5.13/build/lib/pygost/utils.py deleted file mode 100644 index 21c31b0..0000000 --- a/pygost-5.13/build/lib/pygost/utils.py +++ /dev/null @@ -1,101 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 codecs import getdecoder -from codecs import getencoder -from sys import version_info - - -xrange = range if version_info[0] == 3 else xrange - - -def strxor(a, b): - """XOR of two strings - - This function will process only shortest length of both strings, - ignoring remaining one. - """ - mlen = min(len(a), len(b)) - a, b, xor = bytearray(a), bytearray(b), bytearray(mlen) - for i in xrange(mlen): - xor[i] = a[i] ^ b[i] - return bytes(xor) - - -_hexdecoder = getdecoder("hex") -_hexencoder = getencoder("hex") - - -def hexdec(data): - """Decode hexadecimal - """ - return _hexdecoder(data)[0] - - -def hexenc(data): - """Encode hexadecimal - """ - return _hexencoder(data)[0].decode("ascii") - - -def bytes2long(raw): - """Deserialize big-endian bytes into long number - - :param bytes raw: binary string - :returns: deserialized long number - :rtype: int - """ - return int(hexenc(raw), 16) - - -def long2bytes(n, size=32): - """Serialize long number into big-endian bytestring - - :param long n: long number - :returns: serialized bytestring - :rtype: bytes - """ - res = hex(int(n))[2:].rstrip("L") - if len(res) % 2 != 0: - res = "0" + res - s = hexdec(res) - if len(s) != size: - s = (size - len(s)) * b"\x00" + s - return s - - -def modinvert(a, n): - """Modular multiplicative inverse - - :returns: inverse number. -1 if it does not exist - - Realization is taken from: - https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm - """ - if a < 0: - # k^-1 = p - (-k)^-1 mod p - return n - modinvert(-a, n) - t, newt = 0, 1 - r, newr = n, a - while newr != 0: - quotinent = r // newr - t, newt = newt, t - quotinent * newt - r, newr = newr, r - quotinent * newr - if r > 1: - return -1 - if t < 0: - t = t + n - return t diff --git a/pygost-5.13/build/lib/pygost/wrap.py b/pygost-5.13/build/lib/pygost/wrap.py deleted file mode 100644 index 3fa49e7..0000000 --- a/pygost-5.13/build/lib/pygost/wrap.py +++ /dev/null @@ -1,152 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""Key wrap. - -:rfc:`4357` key wrapping (28147-89 and CryptoPro). -""" - -from hmac import compare_digest -from struct import pack -from struct import unpack - -from pygost.gost28147 import cfb_encrypt -from pygost.gost28147 import DEFAULT_SBOX -from pygost.gost28147 import ecb_decrypt -from pygost.gost28147 import ecb_encrypt -from pygost.gost28147_mac import MAC -from pygost.gost3413 import ctr -from pygost.gost3413 import mac - - -def wrap_gost(ukm, kek, cek, sbox=DEFAULT_SBOX): - """28147-89 key wrapping - - :param ukm: UKM - :type ukm: bytes, 8 bytes - :param kek: key encryption key - :type kek: bytes, 32 bytes - :param cek: content encryption key - :type cek: bytes, 32 bytes - :returns: wrapped key - :rtype: bytes, 44 bytes - """ - cek_mac = MAC(kek, data=cek, iv=ukm, sbox=sbox).digest()[:4] - cek_enc = ecb_encrypt(kek, cek, sbox=sbox) - return ukm + cek_enc + cek_mac - - -def unwrap_gost(kek, data, sbox=DEFAULT_SBOX): - """28147-89 key unwrapping - - :param kek: key encryption key - :type kek: bytes, 32 bytes - :param data: wrapped key - :type data: bytes, 44 bytes - :returns: unwrapped CEK - :rtype: 32 bytes - """ - if len(data) != 44: - raise ValueError("Invalid data length") - ukm, cek_enc, cek_mac = data[:8], data[8:8 + 32], data[-4:] - cek = ecb_decrypt(kek, cek_enc, sbox=sbox) - if MAC(kek, data=cek, iv=ukm, sbox=sbox).digest()[:4] != cek_mac: - raise ValueError("Invalid MAC") - return cek - - -def wrap_cryptopro(ukm, kek, cek, sbox=DEFAULT_SBOX): - """CryptoPro key wrapping - - :param ukm: UKM - :type ukm: bytes, 8 bytes - :param kek: key encryption key - :type kek: bytes, 32 bytes - :param cek: content encryption key - :type cek: bytes, 32 bytes - :returns: wrapped key - :rtype: bytes, 44 bytes - """ - return wrap_gost( - ukm, - diversify(kek, bytearray(ukm), sbox=sbox), - cek, - sbox=sbox, - ) - - -def unwrap_cryptopro(kek, data, sbox=DEFAULT_SBOX): - """CryptoPro key unwrapping - - :param kek: key encryption key - :type kek: bytes, 32 bytes - :param data: wrapped key - :type data: bytes, 44 bytes - :returns: unwrapped CEK - :rtype: 32 bytes - """ - if len(data) < 8: - raise ValueError("Invalid data length") - return unwrap_gost( - diversify(kek, bytearray(data[:8]), sbox=sbox), - data, - sbox=sbox, - ) - - -def diversify(kek, ukm, sbox=DEFAULT_SBOX): - out = kek - for i in range(8): - s1, s2 = 0, 0 - for j in range(8): - k, = unpack("> j) & 1: - s1 += k - else: - s2 += k - iv = pack(">1CFHQ@+|f-Ib&NG?fPqqoyQ z7l>Xpy0LvBjs5i}zs#&lHvrnPFK6SN7>nqx%F4?7t^e{DOaJ%l^=JF*{Ca*jyRx^R z-(4*(E&c!b_5IxZg&&~*`^`0NQCIfIsr}&}_W$&ivp;h-~E?4r{XH7=`cHc=MAzJ5&$Z2@}Q(<^i{ zjoW4gJ%!~W4uKggQgp5DTUv=g|3a7t#HXo@p2QR8g);?|6I3{b-|;PA$#%Pv2#a=Z zYn&^q_IevYAAwrBtOYVabU^)b&k$yPx2YM%uXS%%S;Z^iH>|?g((|`;gt}%*__A@8 zeI`WgT=os%00RM@DSpEF8n<>$12QJeDbA-LX0vPPOtj|>Er0y9L#nX@)OVY#!$q1+ z_9P^dkWj*O!fOZc*)zTF=26I>R@mBNjV0}K(QeMCVGBCL$`wy|xo=ijGIy5l0f>5Z z9rGu@Xr76_=%2}}cqSp^usyww+a}mn0v#5qY~`K}!x`}nIKy(^s`@#so7Y&AX@>Bt z{8{7G&aFD3a+Yu{-$vyGhr7l-k&qF}0O1nOa#!tfM>vG6DK7%fto^1xF?aS&1D0y| zJo3~9%LTgW8W)cTaVciynvQsb(AVyWk&0ze79B1_(R}olQY=leeX#}w64B?y8YKDi z;d%rX{)R)Rga8)dGcsmA&|2>IY`ZII-IpL}`_(3LJfUvmu!^C7bp>ODx!bOt9(LOk zKkl-YeyacrBgU-?TCgG%@`IMplOS4G|{e;k&-d`vpxisWQN^3VOgDpZ^^=mR8hzS~_ zlCG^;%0Q>)zzxr?ua=F14@dtNLD}s7=OP+NqQ5Bo;V_zMV8riPo%u1 zHCKjKCcdenXjpeZ@mU|`5*WQPed>%3oeVj7Y4L?oQ|q>h1edJDp&pe0}}LPE5T zz<(CHVk3`LdtU*oyDp>i3`Og+6Ll9a0|-$Jdjx}|Sj^WO5|JV!)EaY2uQjok2bFeM z=*a>RWetHwe_PU(R$ZZ3R$GE_2c}?X@Ow@KoaG=ZVi=fP0u?RxRCs3PE&ZEVYTe^@ z196xLJJCYRy2@)S5jP{jE=Cn|cu@hqsdcc$z1RjayA1(G0MSBZGxAYNws`^x?(j3@ z-hD^^)k0qOWgbm!|A1;3;wFMbMG44{MF~^GI@*3UL^GQ+uXla3!@)tH#D+~PmOT?q z-KvDoYgW%NI+!(|>LM4CB#lP6w>gNkk%v7%V(Kx5#HtSmU*QObwFq6lBiTwf{{%v# zC&=blEtGap(Sc0dZM6P;=Ev}lF zqGNdgQiKpE)=cC$2nMoJQT)$sDEN-svvFrC2 zq`=liC3`R+lZQmBOvE7N8})qiW8j>VcDF1PT*ir**jty;If90JT^5A&GJ94@E=DwM zH1wtn`!Z7%CPZ{@TL3M@j8I=>ZV}v80C(i!F>X`1Vb+IDGZuO<@6|0Mg4ByCTI0dD zcAn90`iMA#c}}<7vR1JZ(y|kJYN9W=cEYzXiFV9{K-+@=Qn5w=lP8>2#s6dx;Y3*z z(ZaNdz$TdTeleSzu%Ys7$DD?L?6Riw7lC&SaS4%j)^2b|q9Y;2qd^hvfvKXH&|awq zVVf1MTV=@VTZti}!ky0Dm#h*PxB}!|mcWDpp6IQK!en3)6yB9$1Cak~vSgPE4`So= zDMrT3PF6LoMbF@G6Geq96kuYgD(tD3oh;9*8#SHu=YV7|boNLbdWh$o$%GvPQHiVw zGICAuuevABPO$gPVvl8pn8kGMkd*{S>p+{N5a^#}!PEwKsi@Yt4aZVY2#!V4il2yg zMBG4<`FE%@`X9IIJB?_7&5f6fHrQ?W6}ETrl!;M}JG<3$f)GgAmp$pfAUe!Ibe!>F zX*oTnP2~vrbV?Z6Q&yA=s@Ush$LuaPDG^=^^M@p9MJvNA;LCyveG-p@SmxQ4Ww;5# z(UTjKJNOh#uZBT50t*LMVS8QLfcVvu;ah+arB0_fCWM7gY$jBMu8i$fxz~(UA#@$g zCzAzTLprnrFmmHCT01gXTbSA$(GdGt14{xwVMb`Siu*h{*?|8*Mwx@CCbM`?LSwcQ zm4d6fViWzkhLzDyjJiQ=5D4XohpU&D$&OXwl_)K=Eh@(v8fF7xT5HnY!4HB<5q6dc zMc6x%TgCea*TQskN#cPimR`2wRg#`5!76Q}htL-awTjuuAxp(a3%r1BSy&2Ic@17S z3TCfY1XA|WNFNpMSuZAp2BQF9tU2Bl?}3zSE4Bb_D-j_+BoP1^(76cb9gF3tY%)U} z+fT*66^zrxAoL+3hF97;$9^jWV?)0-v<4H#n+tLb*xS5>x9QdVBym9}>K&)zNGt)w zfk?*D&)b2V=a81LWy-ALRCG{EH_QKWEWxpS5*a=Y+Ii?1h$ z6n+$eT<~w?krpt$#JbJ3~gW3yyc@#2?=cXZDl z4H49`$sp7wq8(K#M>9fsG^h{NttRMJehQ>So=aUirTnw(Tmr8AcJ7f-(Z!Hl(EA}d zxB8Ynije=5{TCtOlEhVAg`juvk06zzNkAL$<0g5Wd3VKHAIHQsJDlN!xtvNkwp=nRh`rxC=@FSMRZAw8Uhap(wNBziiwg9Sgxi` zeVxNikfLiAs@FmSz+SOMFOi$2!FQBuJsevlckxSy@b{F zMafoGTd$zeb75D#P5DuS^on+gin}p$N%ANsHKu5j9NfOjw#Y@5ds7w_B57^E45;x| zLGT7VvoJ!_z?VkqrpBvNBv6Pcx3EI;tYhZb_LcWr*~1l5SR?+D z319@n%wu#B-3qhNW_htkzc6sUA6K8Ju| z1=1;=YH?b;Ehlt^fKYCda0MRUy3R)-{r-p%ldS-|H6yD5aSo1*_f=VJ3#>5=TYK4Z z==G^{N=^&e?{X{jaK*~fq&_4&torZeeuxg%f`we5tTeTcWW+<#wk(%e<#9Z$==x4= z@woW-e5A0e`k9>LM<*vUJ~Ol?=e&?K7A#FBAn?aiR*HUZBTAS1p-;#c%qb5tleZiV z2V&F{5T|wkR+2O!*B0qRF|LC@n?{yM^_8%XGQA6*=4Ke55s=cXEn*BrG@OR?(5TTq z+Pn_wGNrM$2FZvVM)Y!%Jqgp1&}J8IXv>mEQTDCG;jje3#S&dHB-umAJd%t)t+kd= zdKJa8Mvf9n^=U@Z$p|IW3dVClJ9Z+#l)=&CunXi?Q;2=08bUB^)G1`q6>g4cL*kZw z8)7UekfTd8dQ{0qaTI@m31SJpprzYn<$9|8&3}>@1mVtT*btyhnUVQy5)Dma+*@H8 z@8C;}Oj3H1!Vbr9c|15#j?Y3M4dzO@uT8NN6+~|`tzHJaX|N-t)>lQd>bLL%VBrty zKtLm~*ztxDVm9!ge5(BX!&&msgBQ*>JX>=P8jwB&@&@#_4<-QM(7u-UMV|X*MrN0DkTzGLQj*en+LTIi^)fo z=`vQEsxIqCC=>E6GtOK_93?>^Vtc#pOVZ{g(+wT?QMp5(hciS**qb+A1$|lE-Xwyf zLp-0>%*n0gn4A6Kd;6ZyuwByYZ~pO*pTJY5CBX}qiX*X}L_Dc#6rUrB12kxQ$Dg5% zMGnGXEER^bQqsTA84!WPYMPQRjXeq?{$)WrWW3D)XV&j$hanTTFb1!}2NcAMKqa%a zSrtq$Dw2<=M<(Ks9IwroMMV{AE__uYX@-+<-Atz{ixru79N4NEM(0yEi~T~e$?4Co zf^=mkl0-U;@6^O)+hi*2E@{+8sEoMwl_FD57O;76Y34XK`S$*Lk?`$rzWVA?0&1xS z(%T&sX!(Pwety*sOd3%x#)s)p^?`^rA&;1)=(oqkbk*+2;g>utr-Wk|3mFrUn5fco-q0t2Mr`j7tP`r8a6w)-g`tR9lRX5%g zR}u90nAH$c%3lCL@s};hb6q)+9s3Y-d&^)vGV_r8mQ+j}kAcxswU1_xqv7EqrJx7f zaCs5vs;c$Bp^PG0(@TLNVLT&X9Vtm@``xaF&}(9#FxCsiG7tvgZz-{T^0u8?#=oVx z^%2#jk^TciRjrM2U@Z9Dg<6V(mo)a`gJLPwdJ*AFFcJLK{`DBeYM#q!nuag5O%>VC zIFYs4IUBDM^@Fls5yJLKknt!{hQb~~6)#h0Tt{m^GOk&BLRA%cSAu72m;bB1IKj^E zhdQ`p?u2kgs;Pw^u_HZ=1U=Iq;1GF4;{vh5kiCy}@$^N08Sp(JOBSRy?KTT$F_;)}X%UF(YnvX~5im{p zair>lv=G|J1(hV8ndqiodN9K6d#g#UWVY8deT>iuaaQEo6d*(`mxzmMJsB5gw2lB5dG71Ge3;AH z{2bL5VdG8KzL07mOtHKY>sgW?yvoVM%Uj?^<4XhOHQq@N6e81FiNJQJ?sR$=(b>EN zpCZH{*zsXoTq<@X4le=2U@Hu5D4`$0yvLko;c~Y_jX~A`=kJ_*tJDI7*PyGTtx)n&gGtwxfjZioo++aMk3 zO@SoET$xGs_y~eV-nQ_NMilYkyEe&EN0qOT8y`Xy(~Om@6k-to1H-94SpSt)B%7rp zkpPrv^eg7|)#(*S2C1itUAAM_$60#%fBr-NOcFs!l)9<($x^!$8TAfN{`(B8*JbfGtU8ApdVC6=9Ol+Y)0E?Yu_q6U)4#5wX!` zW0OYcsd7=VlJH4X>Sfq(6ijEsfZQI^Hf*bLLT|Hg*#_3iE?(p;#;21fj?n1&lT zC}d6`y@MQvI+5-MC_o`nJL+WcaeAB{O8fwqP=vOd{nW|8Q94lGXJ$Q-w zVRC1_!a_2TOdVCNE=aWCl%5mmI`l5ntJ8{(s0AY+e+t z4=PEZxL&QYmMt@JN0CxT1_OZwDXN5zidB4EIjveh4!-Enz&yc^l#|F4OK%|lw2XGq z3C3TlH^%HNcckc-%nZlrnMz9}mCnXx`f?xVVp<9YW0X3%^%pN(Ju1muWB{@W)yn)w z*_EDIq}SpsE6T~9E#HRfOe-RNl6$_ zD*uGN3*eALR6_P=5*ZSC!14}gkI|g{Ij_OOLxo+NUV#>JC6Vt$|B6En7pV}hi)-^^ zbQq<=zjwi)NKV;iysA`J!=Ni9Wq1<1gu=Ulo57Zm9&$qhrhW1CsqzM(^}$Us(nvOj zGN$N-CW%^B8$o5nymbpomTYr7@FrX38h4UwdX5Y3oNHcp^=tgElz)YiKaXKv2ap$) z#0w-5=(wmvaCamVKFkeb38m0MERbGaI+3<(Rv3CsnyvOV5|L{(e51a0A|`TmCB#Eu z?LuUf1RoOs;U^$Tk%m1A&A{NmAzpHv#lRecQxbl7>bvxo9 zFIZCXfBW*Fl*N(?!7UxD&f6(yW94!r2BzBjr4{S;o7)R|Mer4IZ7*>1q`QfO2vRTP zj&y1rMmKyt-T**IAd?-uFKwXhX^YMcz(Z8Xv^`1{-B`_Nvau_w9>SonSdOaU8w1K< zaTZ#}OG)&g)>68t-60|dBvd5llF}SbZ7vmu;p+qKow)iB=tm$}|+~ zg}!=x*iO=VX;scz8)(H1OtBklgA!|BnzhI`3rS2-hne3h;Y6(%S*Ov_*yqDGz~DAf z)}ocwiH-}Voz_?FT-O)fKG>y3JZLUeIXfEO975Q#a-@HeCHy~HaO})c6(;8%#FIKk=hZ9sRIEFEgVhJeQ@*d6yox zBbTH5>=6@mze5OFY9loF!#o?=Cg7n-j-s~|=xVB@WDd|1sJZx~MWHBy1C$x>6R5eL zI}VFHZ(Mbdgo2=Q~h|inSGs`EkydSe}Zy&!fyEok{$1h>3_TOMKvmco61z z#(?HW?5Jk=biuG07tX2g%Da2dk%Y8w4@BqrGXN$;kv3zR=qsc}IPs1Er4~P^r^VR% z`-Ppwyv&310o6(J*O=fyJ6Pk=(i_mY)=M9RH+|a~XjbcSq@zOm{|o@#R%=+l5mn( z-$Bn4s@ho7Wk+E*68ZL3>S+ya$lGd%XVZBOWK%xolZ+SWaBq^eC0%s{3w}7sBTN!$ zUoZ#dAt;^`1>}!?`~)WpVkB5~tTYD4$aGYIw4#vs+}2tyb9@`0&(1n+QG%G`fDe-T zf;PixiL(<_qR8`6vskyQ`J(z+!Jzlx#Oas{@jssZ0Y{bI7w0kAz#$fM0({5`6^c{M z9cx^KJ*Ht!YFKy2=|ex=f#bbL>ptRcTDR5V8o3k2m%jNKJiY~coOh5pGQJ27bu0ww z5dgz2a+iP_&QTdfnBY-59w;E(;@3WZQBVxal=!EZ{CKYgN$gGf@(bj*kn`d;nZg?p zAtvR^nSG#UT;$N&H-BSc`sSxG3~ym$`Xt0%Xg6iIrFjbKE4J_?;b%#?g|Zh(jS9NL z5C-1ey92TQu<^0QgKR4=q8V48lNHoUdJdLHCeC)zMd(-OVE<6_MwAkKuYnGxn|v&) z;M_)M`i}WUfkM04E8@Y3o5tf2XBwIjiqpG@woEaQX>KN7i;55Qk zX$?;Z6w~;)IG1V(GJe{)?>eP4B)qrPpsX&QB$-*_uS_Ge9{KxWjR&N2J2^9tE&fwawrpR{O{pU3*+t`}8^0oKiA$bluQwOa@ZT73>-=#y^fC zAanR+f|~XnU&WEfZ(a8=RRva*!ynRnN)=IPN-wZE`ScF2HBPf;L%@ zEek0GfcF<`l%=(0P!pY+eu@4+rdT3T4o{vW<c#wZwEF>?=qe!(~3^BS211?wOb;e`UWw3%tjB_o6az5xv?91!ksh=eeNzRs?r zk`pqEE2b!Okn9*3%E88;9Ta3G{8NuPYU&LtEm_yZaBR6v3ZA^pRfddk<6q0##0hw7 zBnAKyP4gOhT#X};(qMT1IXrW^yj(-~6oHleb3{ZwG8PkE14$A;} zjuuQ;x}jZ{^o@v__TU~MG2RKhnb7x zRwySC76i63`faxvh^;pg`uLa>g|YFD`bU{^$nN)16R}fExG`Z&q~u+bQ1#dVv>YSi z_<}gV=O!4Tl2F#jFbCAgSt*a&k%O+PDmQDH@e`3aZ0w1>z@XYp#t2MfZIv!=bn!KL z-pt-$pta^K<=Kc19uu^6sSU$%WsEJ9hT)&Sw>eui>rQWi`!NEEej?wtW~>jIkp2`B zO@hSaOb18uZ}3S9*CuB%e|(my+;|~&!n$Zs*-~s>9N`y~o5~tm`Xt5pep<0l&!Zn_ z#^A=LgH^8y#<*(0?~f^djaGaRIJ@9D3s4f=i}C@#9`M*OkQ2gskt>0*z+?#i>O>A$ zQ|y-oRQU3ZumK+wr#a;23$Itg!@L$p^C77UP2i424W`MJQTyXSn$E{27G}IW?^x;S8XdAUl_}a-dIU?iAhQz>ylDU+t zctA;Da+lwrA}Mx1&Z-s3%}h$U!?cvcl0%N>v&pPkh|H(B2qxcY@CONSlYpi0V0se^ z`9K{$Y>)*kB@Ns#FF0E|7(J90AVX3MM-I!6k7*F8IUluQI_<<3&rt^MB)sp3SW36v zkxLj3k3%pn%-E6ELqYOEgXvTY0@$ulg%xpd!1pq4NXva;DbKv-(?`^peqTQmMPp(y z5t=W2;g@(gRggRCSX_iTGfMX>!f zKIW=lD^D>v9?TeAuL&f4VexZn^ks#C?1@rnE#oG#h+$`Le5F{kycF^ zc$JRi5)*7N$q#xT(>bN;mRv^v3|FX$;c>Xe)-{hpIqAGFmT#xn)#%t^)G_<2t;!yr zPRraSNz}<)OOu`zLO8kNRD4LK35s*W1E|{pi$;0w`wb-5BFXmsD;~m_a~GwAVK~W2 zUIYp0TpR?9(&I>6G`GUM%wTbm`CRsZOS=@ueHAAYb?FfTxt@Rt=8PHt&PDy*Xcn=y z`~_9-)@zhp9hlAPZ$t7NaiLnDdDAw-koz39-C^Hj!HG^VdPAt8aHb}XDu1Mv7bJ7Pbr9DC= zNS{4=C*CB6x;`8Qb{?}JQ_)H!R-|fX8Y>%l0((~K$m19SVniV&Ri^mArX# zVQ>8;OFwe1lBi?dOnl+Z$Ycahg@`Wq1RQbWM@l*wOgESoa~p?lavPf{9;5u$w{bRM zY=rP73JNB1V+wT`zZZH)22aLeI4RmSO9lBh__HC!(=*0k6dDN}=}j2eVK zGc89w2@)rscmUYQQ1ZNgtflwJ-onu)4ZQM!;xLz~USwTP;=s39SmDE~I$9KZC&+2$ zF+rMy>{@;#6ub^X1)ORaY0tT|cSfiaBP`>%Mi@VUsfSIOW2#n=IfxGoEKsf;ew?#$)F;&JSYOr?0iB$3_q;HL;f0$INM6P4Z~M0h@|S63W; znhXyqLZaIH@Q9&Dokpi5{a$nuf7AXl6bIDmzD&iHs@3js?A>zW^el{>tNh9bwy+K+ z_V37kPp-X>z@RX=gsgL8S( zNSoPt-D6cwBl8twR0wMB3M|D2uMR+!F13@zsj0!>heQm^h&csd!gzF^42-%--xr<# zVM~(rwe6YlH4Vuoqn3xDq`ArfPjWd)5km~73kg*ON0ZW5-ynA4taJv%95E1CH;;v? zNCrNNp=IC!kL(hT1e?GqfNlBI%&UDbq4F;t`y_UnWI?`l%)gs-Jc>t-4%-$PnNPF*X(G%(mPY;}D zZeZsO;xS3YhCx~%>{@kSkB!g?CR!LSJGTWhhC@Ln3R&wSmg73 z(nF~*?8O@hwNOT@BVssmS@+BMhvm6u{;=n8)U=2XY=lpk^>xTuW0(wgn>@#mi~! zWC*;cxlVsj9c}gIhYO6ix)0wjqWiwVitQ`@99Ld%y#+U#_$NV%Tql*;Gw&htK)ar2 z!$?9Su^=O`=T__3Q2LM;t5BHlw>Xw`+xrQ3gh(h{>>Hv(r{BGZ2B>_i!3&z9l5cAr zaNwKE+G5+6UFJUyBei-5v$+OA0Y4xI2@bi)MqZ!Z1d(kMUzG@beYq13kO2(m_3)30hF$C#Fk`D35@Od<0b z5;_2!{+CA_5{?J!;C!gj_5=GcBWYJOMd;_%y2TLpV)q76N*N(u$k9_$@`rQegC@L1 z_>z&>#6WkFY4YSrxRKeYY&?LPdOAAp#XnK?7%doTzf34E*D6qN4Td6Od12VoHNuq{ z;cB9RU9>^OxM%p7BTvDPT*_cbEj35)k|@uK@9e0{?MtdWgeE{JAA}e~;3*-7N(Xdf zBHbL8#s!;Q@C0jjh*59qohG4JK(^Y{K2PBn;RsZK&m4me>m}qum(aJYCD8Se8W*+b8~*qF-w*Ta2YWle`?Pp?n7_C0zM9$X?bYIP_U>wKuV!EH?WBKS&Tk*= zmyh#ndxPb_Ebiy_{$Yj}7T5O6-QrqZt?5K2lMgf>iztVKT`D_?anXS z+u7a2Vtx-eep|esr!d*c?4H1$*e{ERk2jwm0`TVM11<8^{%djlero3nUTFUB+q?Pw zJpn*#FFp~(bNX>{eR=iyJ;5@y?`VbVn+JQfpp(&K4>wbT-Sk<%I)I_|Kh5thKhi&E z?-o}J!WX`*^6DVT;^Ybh$ zMA)S@KFzK#=h!Pbv!QFkaqZWe&rlI`?yL6$qySNK`+ojme)+KYZ9c`5w9WnJPjkih z{R1s(W>;7CdVWdp&hEb2`}y5(i%Z7C-TZd8xC53hZ|?4}%FVTq(N7mrULH~&SH5mp zB*rx;X#N}MCH<3D%$edN*f8Cp2NcEC?{%ULXuBgfZn;=!Zxc zUx~zS?5CUeix0RKC7jEf>)+;I?@fvo!gD0q*}EGc<{bgDV0Z`uU-;{U z4D4z2v)NO7e>=Zi;6LbhqDLZtD?!!uJ)IR-OMlQs?2PUhD}XrV9?fTB1Q6i0Cmh-v ze@lTqi+djurMn_rEQe9d#c9a-le()A0(5N1?vIU&z-*@KKCFf%gY(Dyj0y2SP9O#Nfel zK*+HGKaBa1THE*;KQVmD8siS6eMM#qYlX8);f@=oq)6DnBy`Ub0PQivMf`s;P<5s3 zS$%d4WLlNA^n#+b<#+x{LTeDyTDEnG4tgG)EanGn^x~-~6WEW$OcEO`mXZ#-gJNsJ z5D~HAS#qEU1!$JPR7~1_IM`LcI7pPpH+%H6gZ#)x`H0huu_3iv55A84E414+*`>Ny zo1}~MBR*{QKgX{Q``QKlq_n0(6y!&4YHUeK*P3kU+fgIwRMQ}BCj*O6rMKbmd8ql+ zBv{dz;is|1@^Svt_VV$F;fHqpms3MB$-BY+i)dUBHbn(>rg6V$sro*tVC4G(zy*h# z+iZ=a0pjrX);~ zCc%7}z%SMah~3rfzey;;kIGs1VUR0-supIbj_dg+(8=%nfo{q9U$xvjq)RWG!mW`m z$-*QejfG$Qq9cgBL3MU{ZvTl)wgehZSCO zxqY@mJYqj03aN*bu0t8~u#E2X!a77Q89WwoDV`q@JIWlK{pY6Zc0Ygj-ShMF#bec9 z)Xn2}zP97LUkIQX>h#cIlg=^0#1N649O{-&i}Qecg!G!a!iXq*7$n=FOoh&t>cm~@ za%xF+nZ{M7Uds6|t7O1O(;4Ma{%9*7id%m%1-9cMjWXFZoz9CXHQJy0Pu=&IKh22; zbSQI}8K<*%_cvFcALdtIQ>W!E->F`w?e;{V|Chh${`~49E;r`qs5)4dyAu0I406EU zyn=9Z=ztDEgSY8+t5+!?gnR^%Hv1hSi<}v>;gf?NK87K@t@P)=MxVAd52wGL2yI$> zvu2wkWU-^n^jn)PUjfEeco+7qD)L`HFXE_U{bmCL)N@$DPRKSQx>(k~PePHTg3N`b zsH@~%9r1O&NBM$=rsF3M{O9{zbIt`^aAiqw$hXxv!G&>0GVaThOEl*uF=roUxMQ43 z_`t`9kU$?Yj(oW}`G%wzWB7wQ(1E$}Wo=?o^M_0-LBba#O^EJDSz9_8jD$pbKQbn4 z7r#60e`u4&h(s=Ds0M3lq%56&!)vddbu#0Zn>fLvj0_RaTt+02AZ9I1+i^$D3UwNB z)DPt7;y!dGeDv%K51zG>0H5aeMJhnZ6>7ekQ-M5`1i36fl9<8#9B!{csWA6mpQ>4ilcSR^VTgnDq2X)>y72ir+8zn-yE zO4%f&`ZTe}-@NynOM@>hDknV%yTTIq%R17^CX+u`lH7Q(R4L(FgFqIPcF+k)rEtv1 zJ!n^>0a`Z_*NhhtnZjc&TbC#W)E{5ZD*J3xK z;d2YMkR96o`nFl38iis!Jh58lL8-y( z7Vx#f!huCYZ2B#_gI3X!idfGEKO>rOtWr+?8P(wTY)Y#5d7=iv*f4-pHRVHEdagK( zva)WM++h|jnCo2~xFXkG7+*&HHOdCon8LZ{tnPgvgKdkI{dm(`4IAMH08czCI_G4W z5NoJg%5^T0H)NwLBWrhA2nl_hIgc^h@zyAXVT6_mkO9KQH0000804REU zSBT%%jBWz}02K!S00#g707*_$R6$HktyNu*+Bg(_&#$--mFSF`5MZIpXjCnoZ*8|l z8b;b_w+|-1Bo@K;`h$>e_rLFT0>jXv{g5^f1|0kN+;fgEm#UJbVmvzCK2h{u0pnaa zp(HDTqejV5W&$nXCPVFDTDw9?=qIncgJc6jIyBlKjlc{>27mX$AWxzQ0Hy{PXxO|& zs8|icf>Xe^$AV*K&dL%h;Ryb_2YCPYZNe@wLj)eTKq0eb(((Rh8&E zSJow2tqVHe%cV_>@3yn?FzF2vLQlR}_rC!pe=wW9B8q>uke}9Q#G>Al{=7Si7ITO> zoWl_q_!{bl%>v@x`iwtcxSQ3j&=#H7#FCV@j?`+Dc~l-UCPB>|6A}!T$Siqel{Xne zrZ@tbqREjbnJp+8o&RIyZ;}@Nv|wu_yG~(S=gv0d(Gw!`WSev%6Rr-{&dEbkg3M$N zYC@r47=i1+!g%ZGlSv+V>DWqRAZOLWRYa3coe>QTAp%z^P&$OrQW8E;2&Xqwcp13c zEqO(gI@gIZdG`c533=1mThVIxU=eQKKTrzAnq@WIcxLz^OfJ37w;&6H_{6705G!5c ziYjD*3~{_xrb#MkQo}+R^3#bGf^Z?+5;L@xnMMl=S_=|b37LcT^HOB6#5#x&u$7^0 zj$rSa?b@idAblYS(e8aYeR%ux)niQchZ0X-!Q8jX+Cuq~Lsnq61Xj}hqtcNCZ!=(% z?G>U^X*8Dg(;t59rkktL^<}R=8ed$d7il`af_~Z?#pyWSC9i$1hrWT}{>ic6J+x;T?a)9tVD!ouHS{tHB?mi)gUHaWb%zx_Uk))q;oR!t7c?C)|o z-5bC2lW-Dp=A=!6v#<;KH(S41-(&z*bP4xOv&qw3t+C0GrBup1v}EecXDvwQ{&vzI zj?TIt2ea<1Z+~UXOI%Fejog>P$9j1?yL`O+r5MiM@$qdjUTw;UPI71pl^7(-Q9}H< z>j?1z4(UOBx7F~c!>$6cBwq9{@Z0l@(G%NaJu;|xyn7z&ew_MCsdTue2Mw@MsFTyx z%t|FOqHkmxjmMjPLT{+-zW`860|XQR000O8D0+KWq|94BL<|4`)FuD`1ONa4PDNK! zrCRN7+q@P2->V?r{ZNucie)=#QeXpY=VM)(#&6PgU5CM-CEDg9iJB$ro%zLIIC*5D*{+Pe3;jF#>oL)&L-}$W}&yPPs8*2rGPh ze@lGO;Dk8o3RUuqUOjKGNcK}2PPi0G!XfZ2#OY;`rXtHV;8|_QoMlE+5C5A4*(%LN znhEC+bfP)o^91w@$^BG3k(tPF-}Ts#Ke9~n84uLfYT(nFqKhJ?i3=ML(kU!0p)n(3 zrWP9%*(1YsmKNxY@QH^qj>dXjQ}X8OMk|AhF(8uVB&V=8{*~!5C$%vVPjbStOk`5& zo@VTki$ao&MT|-_=Gct6K|^RK)7qsp18s6xC7}@qdHF_5^3ctL?um=r?xOB4I)vLj zK5^BUdzKG~``Z?|rqs@O!=3PkJL8RZ!W-?3cfJ$e`HOhR@UN8ofG2koWK(cw)Xi3@ zN$rfc*(x=uo$)qXr6#4^w%;J7j+bVSlbB|5K_iDOSd_91$PcK5KCl{Z>Z6VH#1jpo z93BYmXENg~3J=wBnX)`b2oa}n#0@7edA=Z0#LO%zP9qkxBv&M07`fze_#EhC1rsKu zPjVhJNo2}2PiJ@t@i5nk)w4!BR3^R*77T*GQNXg?l`NV`U;-Ap?1|#^Ub=FKNKpYA zMJu&tE@imh@~QWwO62F z@PO3BXV$Dr5Og6BdMehdEw41*5 z(I))+7tBN@^ z2?=`&n1tPnMaUdWV_3pc01nOO1^N$}Wg;f1N3|yh4iYt3C|qxhXF-f{caLx{FaYRy z8zNsryoE8-6^VpUMgea(9&Q_FP47B@{Qq8u9-TMbuHk9@D)_3I4pgK%fY>PEa7~N3 zK42FiZnD%`OP#S`v{d2A{d9e6%~9>mo9Y+`Vnzcfooy1ozIaOgPJaBi&028Eu2?UKp_58!##YM}@+Kg9b4mI7JB}S`iF171rW`cRd;9_vD7mY{v zq^}&+@1Bw=2S?%Q0(?T-s?0&|(7`cPLf`D3`u*1) zJ+uu4LrvL&VoH)}v&TiS043&g*kBk{Fj*#yR3C5{RzyNia$L)GOtQ<8kkcf=BZrHM zk$mqiNB#bpKjr8c3Fr%1S+M5*ot@qCWXJ4Jzs8z7#&t*gsZCQVjF8L0&(8t}F6xn? zGwU2qp8&*dihlNvJ7zH3wW(oQpT+F zB92#Of~c)B(^;~~wI>sm_ljgPar7qCLq|O&?|8V84xvH7(p+K+8nT(K<;>r+FQ}7m>omR?yS=j$VllRIB%}(*UXRTVFQ>rj1NOdxW!@Q6cYJrG z>W6VA+|R*c;Es*eO2crCd*`fz9T(FXMGIk)=!pzhl<@LkY{KZt(56MDtVl=)aqBE)EKyAXD6)m60S=B#ihcX`T!QT&Gvc!q zk{8f_Y#+X>GMt>AO^{Wq)6lfM5^_(^TwAIQXi_=GxT=?0fwnmR3xLCIfUW-i0^n#H zVC&(30q}eq;Q7mdcrYhQ;>-GVv#Qgu$|2k@4H%v1ob_9bT0hOcd)9x+rWMe$HMRjg z6SB}+fIAbUk~YU{(bVwH7F`WwrO0G3v7vv2hBrbFzXy7wPqspjz6W}vceX-fOr2~n z;>!VeNA%(Mpl{t4NY#}U(@^RCd= zP}A&J@Py|a61J}@cYK3ob$j89C36a45aJ`2iBK&<_2KFw0&0msU>kP|ku~E?9zuO; z+Zv>y0B3WyLof`Wf|;{7R<~;O@$S>bWIVVV+}u0$Zbrx@O;ZNSg&t`R0pXCVx{hWm zvJeq=+9PZ-uj~}5+yTto>$?Mxzb_z;L(Xjta5VUze%kInKm8eZ7?KJaLK$ph)F#?H zu$Qt<1$jOEr@cX^SgUKT;m4 zwmc+ySSB`e9aSv3Q;NP(t2Xw=tMiLHcx}P&E%H2lb9A&^E}>Kmp=gnb7p$<7PK)G0 zdRcL_PePW0Yk_K5VEwh=VBdYc-v0XD02@fE-BIgDmFXWWyRD1gTVEcj6<9Y&>`9mA zkXXr-1&~eYYO4(GI#1(?ZAVkXse6$Fr?qL(xTC_>L(JsD^l2*a(Qu?X4O+)qrKs;n zR2!mdf&p5yfW*$A_rM+|_yxHE7}7bkw4Jhmq6)~I*ubcDda)}tWqQRbuhgyIXf87N z7F#g65Oxbpw=SxyG&au|Ba-5=`l=V%vSHOptzYVZ2^?iM%w}P%ZfDhx&d2MU-}sKY z5!$o?wk3?w{aOzM>fCPswe2{1&1DN_!JbT;WPQEYtKF_~-Oj6g5Ni|PJ*fGj9(C5@ zv07SHGUMU(`DNE0r-z+3D<31T&zTuju98r6k;Z@?4mc*EV7j$uP7*vQ@H9^&5j;r6 zdv8BUHD~=I&BD079IjiAAWb5XVCzY>o4N){>s`NClcLCgkY+q$Qc)oU7~OLV3w1%~ zNR}5VP(k_Dv=+b;VtrrOt9n?quFD&}Zlw))o(wXs+N|gtDR3WFo?t+Vof8dHYeVU7 zcD#bg>)fGw+Ar{`Di_zJT2icGRW(ts?UtZ>U3_(PO{W>kevz3Q`7)v}4OJblbu zSp?YH7nvDaE9$Eo9VkHRdsDuE=dJSU3aA=^^|J&5&ue4bXt4GY!fM-=>-!}O(0;A} z>yMf+6~6%;IYhUF0Y)+m;Xt|02GZ{MHq58%Tiep6_fcj%iUMobLWhRAtD8>sdESQ5 zbaK`&d%+y7hJl}j*l55wU}a^lt74eT4*B{JPSu6I>d}NEPywrT`Zk0n zlhto6i=@1_Jha~w)9OC6&C*(8J=Sz~&18q`TPf_ECwS*r4>?cAoioRDLWzosQLBcq zg^)EkCDTgc?7_sJ5AinckNXY*ri<8NdrFuuZTG-oxI`T2R8hZm6JH7luMWBuyThWH)j)pzT-;J0bvuN>>9nOvUHIb+@TBK~fW00mX z*Dc)Ev~AnAZDZOt@3uM9wr$(CZQHipXP!^*b54CfQg!{fb|sZmWv{HPy+W2Ds9p^! zGW|R46Q?K!%GPOjyO=DUgYep^4SeCYa4+^WOr)NsGe*@BWYiBLUxHMKV)iN$rM1s3Km$vitsJ z(q}B+l?$|&hI#>ErgD~uWH8v1^r(g?b1U$^R+Ic*V350y{{f&F2af7gd@ZzkB6dZy1cJ z_lMYM6rZ(F*I7AZCn9C04W*oSmXVO;KD|{d{US#SJPo-aSfSf^VW2VP(CM-Is$b1YvHeP<}9~g`|;S1acu9J8wnAS1rDkz)k*loI3Y2L_M+T*1ir_&XQ%Bl zm69dA55D&yCy@&QUL%IqKg zk%1mApQvYxbuSiv_Ur_hgJQO-iJRLva!=@Wj3DYIQ1?6ScF0oayHrkm7ZGSxkv_5$( zOf*CtZ%`-$u;o0S9I}n6oKGMCHG;~BOc}|BFGtiDJx!HVkK)SHzHZGJed!~d{yYxOlebr7@{jRO#bcllVgvNoBFXmk5mr2ZB6v%p8zqAeP{Ndl#m$}K9fgbl-jG-aYO!oX%U}Gsx*}<7N{iY znewG0N6od`%bG{i1hcWM%Fs%yu7flxkY6zV?z}1r^dG4ge_V5;K{N+K_uoUqNKo`m zM~kPcVD;D~9DMS*Ryf<^FuNQyaYjQej}dNVBNc~`2EjOf(f-a*tBfEOs`hDMATl83 zI@h$frxrziv8|G6&qY$0vU7>zs5(sR#pzwasGF8z9vYI4(F-7tyO!&u_ih+BW`3Mp zuS(~mdS7_-rqL_rCpuw{s7upW7Wkj7{y$zhXQ@T3SpS&yq<`%{TfM5Jki3ivUu>WJ z03&ShjaT?jWFs{Du0J^#j)*WJz+j!vc5|6Es)7?Qv1Eh}Ib<|0!Tb9g7e^Y*yn^Bk zv@b!K+o>`R9Gr-QzWGo|N?0T>M*&;?QHsB8(k)^LJezyka*#-A1n@=_K-}Uuyo^B~ zwJz(GqU070+G&zFzx6|;iQy0LBA2e2FHDWAztS>$neR461L4xdMU4wnVn&Vo@VTzK z2L4SXAmoTW&P%FHdS0bCX=jwaYn*F8pwyq?9=#e|R+-sEU{A5PRsLTDxw@FLij;yp%MuIgG01;0 zcdkCszxiecHb4K(@DBnI5YoT@#Uv!?q~ygF7!+mx?~g2vt?2!6r0y4$#A9+q!(PTo zggKl3`pL{WT2O2=4zBQgC1Z}(rsj>2tzaSzCC50oP{+8=zOd+gQKfiugB?o!mViFm z6O;G|taaBV4K;lYH4TI|GvI~EuTd5* zf?d3bymxI%8~y<~@t4D$ahP&;WhupJI)Z3394`c?!4$m4NKl#LgjO_N7AiyU+H?V1 z0}>=(*<=s6hggY1%rSppWE@Fa`x+&WSv9Gq4*I!2;oYrx6`ZAT=K^au*UN1I2GJ;yV5MuPB@X4baa+<2%JqmRFeAS+p@kYX-!SBFl;)#RsHIZv+3AqYF zaQABeRC^e>fk05dhR31@TEEBc%Aqj|&|fyucP==+?8j3htoWSkG-jx!i_iavb~6?U z%H)Y)Kkh7-`u#{IeIJORO<40HczvadEc$IQBr0rVJx~uZ1MxYC7UO&{HUU{8b9FZz z|2vmpw{ZeW?y>HP?+S1|x?C;-)=OMwoEQ|SOsF@J>YNrT@R72Z!6)xFI^0&Ovc~gt zD$bfLzD{O(zCJUac2@i}f4si%)X2FHys1x?jE|@38K%1#%?+cm3&-CT7A=?gLN(h& zj>z@cyL2h5sEfqle1#*FjIvX;4t7uk8)|KmW-?N?{WP}Y?VF%YYj+cwRgqRKus#-F ziY7sT!7f7?62#^JmdRtOkIIw{#z8=TktpZmeW#V!q|GS_)5mDZ53!Jq0um_-CV<}V z?3_uR4`?!i95cF2n{Bc2Rzx=i3O#)1npJNc*AIP^E>CgPUBddMpaH6A#Hix%zm9$xS6G)kQa0(9b#b|3*`LjPNY+&;)M)g!iY0k3JT}N;EsA zu(z28Ud67oJCq2g{BryXnd#B~vHhxf(iix_hyFw;-#qG(ZzTv? z`#5;tcyhb&tL`ZpkDW4h&0VKgRY3v<^ zBx%cv93AM+w!Jg-a%U;r&V89C*1DgOui}~&8vg3vHG`zj$oh4_WYgO9{b~m#(@Vd6 z9ZO^x^JW&YXx*}&#WIQVRP1Kg-ze!8bh3ECdTnR?xhlFlQ#R==GI(rWL-hkR+;KrM zuqX1d_>e7o%-_Uq&i3(rXUNET59qONbhACMb7)|)=S$lV*?>^A;_%XIOy^-34g&lN z>ym2?-MC%`;$slvoYo~yfi~py7p!bY9U}`MSDBlX#SDH2b8YP1{nPAE1X*;_FginW z5#A4Ab~gjSfF3E!rHwv&SFb^s){h$Gi-oSiDCk0_2|pfBW#li}m@z#dz_M8mc>N3v zjP#jMXtCB;OkL<_hxRV{UQDuCRnIxaJ>h+>>2d6}yWLmR(sy*jUwR)W@F1j3m>m{S~|HgM7p5Oh8pk#i70lu zEys#bf_Y6Vk}sVT4j2ebsGMna59g(ES`tHwL_j8Bkcqz^z<4JI&mLvZmVWHypMgi$ zDt-aMWVY+b9^2|1@CM3jO|l3OJyx~?VK;IqNg>@`Z+SolA|Z>LG4os?lBAvB{#ePv z$!wH9#*zox!5ti?AZwgdFMcvZ43o9pZ@s;uL@;+>S&Px~MtW3OsIa|<`9g6m3fJy@ z_^;dk|D?);8qcr7e|RMFU%LE{tKa{ZDpeHJltsi;=$+l2qY|ea0~t~OzIb4$ep+6{ z*Zs!QuFb!4PReuWgGFQ}*Yf#j4$mS5y9y>c;o*o#$mG0OqWv9gflSNK`vWbPrE&PS zH}2sp&JcYgx8<-zg!*>v6`EZA@VcMBGhw&O3v|1;hY4$|GL+m<9P2Y}* zA`Lbi|M+nxusYzC5IjM-{Ma{pjYZlyQ&DX5;^o>Sb{%uAH9E@4g3m4%WKSqX8f6r_ z>=7?r!Aw(x_VJl_?+en}3m9`5LrsNgT>Q>O&&s!c8jhZO0y$QcmEK zqUAfCUtu?Dcg1`fj&DXOj0IJd;Epv#&$C?8oy?(~$$@}Fgt#~wg!@-?iA@U(!3vB` z01&d6jv`lL(&@#A33DZpx5c;5jchGLW{x(bsU*!euqEzpGuu}z%rrqo{spfz2J^2z z)J>WkO=>+hy+`;ymF7h{V8aJ~1icgw9iw9=Cu~?oC=#L#f& zb9*Oe27P@?J4BEyvRD3enwQW7w@4XA3?!EF$YIVdiLB1mD0N+AI13lcWG*ZH9D%G(vgX9+Y^-8Adt2|UuQ}MC7Z-^t7 zad8(T(KM`;?Jm0)cqSTQz4SuB#$(*qG&V8w@CSqS+MkzJfB9zz(ue<2Yb=FjsCj1R zlb-wom(u(|Kq&t+yH-5I$d_YM}hXL3pnbdWpAPo%-EN<76MtKaQlvomXjWWq^ zg>%*3Lenb@+d6u2;z5}VDlh~On#d@wwu#jk9?WtBL$ug&W6bzRO33CW_;3r1x&ynu z^}kEzoRd*1^hv6(SaGVaJP+fGRocmG2E4`2!g(j|8NvBz-Zh>!gyQ9MG`fMvG#V&L zB2X#XNp3GlixN%7NP7@e*2ykRq{IznmARwqd82|SGUF*J40(Oi3lHI!S%kEA=b7;r z5)WCV9n-f>Nz-Z(7^b)5F<6T&IYY$YPij*&=0b{i&n3VG*>Yw+|6aB6dX6!f6(~>mUPJTd;S!Dv>*!vw`TBiE@Py=$DD;as zjTsN}aI!3crA1yZp0hKk11f{p8zBxxqJVTLlEg!K4BZ)3E*}Z!No`mWduWU|ts22W znVn#I;zV$9hvLlZI*k!eoNIyy$oL;f&pvL5r0Y;lD;&d{nI%nYU?aDbM>kBD*wfk6 zt1OX9CfMbeapR?9brm3W$Pg1)kocQ?7o2U1Z>eJTXecYuaU+t?OL}(wf#82fRi#SJ z0C_{N;D;G2vfq|epke>cFf`&?bkmFQ(dWAA=Ua4>o|6SbcoqNMSa=s|^r^Qkept^W zi!a&w&xsI|>&De?rQhB4Ck(1K8i>AYmf$7Pqh8jl%Y0=* zuDsThzDvyzKkFJ?40J%LZp~sU_(;$f-D=U9@y${yLfQ!1A>ib`l?~^c5 z7G3HThdI6l(XiDA z(&5P1wnL3q^*=Uv0`RB`$H|1n(byHj4ICElJc0+O;O*h7tQl-<68EwQ(((;*un~oJ z)Z5G!J=;gE}R|c6AAR*<)oa=#By1^!DKi`V!!Pw8k0fEd`|C2 zJ8P?Wog%~7oB}PRa;-;iSJ8$y#0?W;osw!p8%Sh+k#s80zs9eaej!JaU)0x5C&;Y# z@Z#No$f*#C4=$!K*#O)|Kb36u{)ts$`Erd@FR3Y;ro{4wnfWfPDlv7;J1N$>NC1b) z!#MoNqsxc66DksMdL6Ky8s`{)`%leO7pJ6kL9(K2Dxv_Y%^xtIM!}9A3%vW3?9LZv zamg@kINGK^YcS4oDBpp#>C}M1^X9}7??VW<%}-WN=idoiuX$i-E0?4;;3v^WTywAM z?cJ%xjyNt|jcRhp7wFlJu*cLQQwhA}apcyK3}X`--QvttCvMtoCD~S5Ab+v!;!5%r z4=I$G)661BdJN6VeHCEM~^8Kh3NaEXr7X&J0|VB!88q$$uy;+#Pw?JpsC~e z`O@NTliKCL3!~-7@WjqQTzBOQ4g|V2s9oW0m+sHL^`E^rE5!8Xq7x5KENQqxTU%_F zAd@q<)C17BZ(V*CRTChsaB7ik)+$Er=2ko2{&sI{H44mDsC*SU1i3~XRG0vyJIxAt z^>c%(th~~>)u%kmK$)ys3+C&nI$S0GS-vu}L>bOMLVDYQv z?5m@L%=ob(Qa*3Na-2>)&Rcd+Y7o}*3vbn75q~E6NOrXdCVo^FOW2<0DGWlvF^neV z%sP6n&qb#D$jL<(VPbhl9hWSp-NvLq*L{1RhJ+i2cpTGGs!e8YY&q`IyjiCi1mh$# z@JoEUR4SdCgq018te@UcJAY;{u0(M?4yzc(5i6ry>|vb8Rl8V?XaP4W>L=9Pb!BNg zFpKZ;33#sEQv^P&^T)!cy-bzbn474Dp{i2euAp`KsJ24=tzgK8Yx15_zdoe;Qay!D znDpX4B<7e6bB8Q@19RU)IzIZXEAe=O6~dz^BATP{c8u%$u%CRo!TRHP9c1b7&582+ zaOM^B?J$_vD>x@RH82|e@mHX))a+8=RiHr!>8YjAsH_UI0J^A;2rFec1le3Mx=RF& zP*+N&ek*IJ{=V48^_Po-gYv9?jFV48VXG%#>j`2cLOIsWt2VI z@Pz3|+C;M1(Asf6$4WT91&4#nCduEXk=Qtt(JP;&SAVft7BJY>N8 zq8hx^0A<19hC_E@5UZSWg(o(P8dxwQFMEaEBw|&ih1w!Qj#3x*%({8tg1y`6j6X5m zXYom|f_r@U$ZTdILB^i#C%^o)F+a7oW4*=$Z!IVjO@Y8tnkZfh%qT06ul#~k{C?P> z_qOESC$4N%iPQS7M|HG(#G}05`F_iY{3$K=5lgxJ*{!#B=Ijzoxq+hFK_N_ob(#`= zY7K_!sHuk*hTU7ZSu3sz#sov>Dw!RNK`QGsluZ9`Cs)OP?t$R6t$HIFP0CHumd%+b zt@1@8lDn%>0n8}Ox}E?bj>~%X?6rZuPpJp5`$W`%J*vTng72*2`u; zpNZf>u}16J`=u%XrM^dtGR6DtJ7acQwi{vqlE}0Wt3<7N_^bEj6CIO(d1!*oIxq zvl!nyMJRQPOpQtZ$x@B8{{1XLmyF2IP*j`2F-B7lYFvDbQg4ZsRudCCm5mX|$;4;5 z1IYQw8deeXawPQ)Q$hl3VNfv;&A&HtA(e@B1STG;_)U(f?3D+#HB2dC?k+Jd}qBvyfyiXv3viY?#)Ijj2iu6_F z4Lt%}gDf6lXbT$!Ep?Tsd>nKpa?XL&B?Ak&3Vx$kYv6o`z@|fMBt;LtW}a*-p2c4; zeF|Qo-$Ohq`?}P=c!)xhjJ}aAuT`+|T=+MM4&af+FT+wy!Xq9#RRSx{3ET8@i%7&O zmDEXxS(cRg=WV_}nV>71cMzjB6=d^6l!r9mGpl_Dfl@4AZtH^BS zb>lX~mC;eM@%=YCFbPv&WlY%UgUG$7!`sx=#r8|fNLNL6>!<12xuGHO>rnh7x-62z zt|m^EsM$Fts_l+J4IP<2lX~3zYK29zy33Y>9j5~CwAF^C+yxVrBs&-cxDV>l>B<5> z+fm|!jX=J^4RC$pFN$8s;|r%8V7Gq;Z!zj?zbp}^*ib9GTq89QCC8Ic>iqU&L9Goj zprxvU=t{JNhM7j)*w)wZjs~{yg0brmBv2C&dPuEpHfxDj>#f}3Fe zHjLRYWq0QnZm;QS<0r17he44=yR++f)&kH0X;A#Abf7vbCej~`Z;8+lhRIxIvw_9ZTD)!I1BfSsKq7c4*$x@ zs93*yAncy!g-HlqaGsVyn|8oBB$})UmH&OG-F-%z3V*8@BD;WTPNalT% z-7X`~3$raHK`7*z=i`{hot%u~`%Jig!xU-6WGvp=GXUFfqc$fe10;LYpNw@#B z;)}n4fH3}lcmaJ|L*swkK$g0#)4Dj~ceQ?<2)`&R$j0tq;&3)H28AqM28i8xGro5& z_?D<-dPf4|+tU^wG#GnPniT};))jvX!_4(Lnyj(D_yc>PnYuMsO>WP-I}ff<6~vb+ zQCB))bZ@oX5;fDEP8eydN@~&E;wa}l;&>RcmDqR+pkCP#AJc)MVwRKNL)#jcBb`x{ zZ3N95om^ycw~JjENj_M>KKr~bU7xcC$^=ysE^I#tT`-2_FBErnT9M*vuv%uns%Y+P z$~q;N6kH%HA@L8TdM%-!nawY{qPQ3sS$_jXI@Up!kNdl0cz&Zl(ti#QNlDGCk5EaX z2v1G)=78TB4KdYQjeZ6H1$RJ0sWw>Q8&$OQ^gqV#04l$EDr8-S#L|2tlpk#u#B6q> z8bBn6DjU|s_WUDQe_O=y?(D@8I6cMZeFh7Wl`;3FKq?cKr#s0g5g$RC2qw!;jg*F_ zDGHR+5j3GtIQKT&aUFFXKG7XSL){BwuL45V>jD_=XxRdW>gUS zh%jhnRv-s_HwT4vzIoy$9Ya1LOaLNHQ)C&=Ows|SM5p=X1`j3j2f*5bA8&&t%v{M4 zM9GeC!F=muAUn^`two>mxnYr&nZ2YX(0?*b#=t!-I~1{r z4Wm^V=|i8Ku*ADuJng3rpkszUI9m}fM3C`m?e6+?S^a46`hA<#)$l$%^mW`gKri zQi$_>IG~&cMW%#2!u%oMSJV0~FU1#@10TV^Hhe2w?BO}D6G7T9u!a=N!Zh?`*Rx@t znSFIx5{%ijT~h85zSMIgfV6jV%GsS+z-~;}LzF2pf#vFlqcVTC=Hg#%j3Ccz;XeDg zyBO4h-}$gR$eLKe4$yk=#1{lhM@ zdE1f4C$rC;mc(+{Si_j}!C&_2GVUZndI&Tvt7R85RrTAKo4?rYc;t;eFXGgOU|fR- zX%hmv3XYgefS9C&UAR*{JPpl7l!i?cYcVla{QD7C%FNb4gpBD)8$NMw5mCvt?sv8S zdLqRi%qh8B+;|_$fm(5nS{lh9k)W_PGJ&8e)39{>+Lf(6>qVYy8=f9rw`=kj9v)^V z(`NIm8Ngf3=0WEo^k3vwpIqHzQGn4|a|aQNR`GXj!)R-xjkR}z-*qtii*}l-g)*Av zd$q!gw2+t8k~YC%((V#+vjIS!x}sJjQ9j<_a3ji_5Tai^`*;sscXkJ7Xa0%f?@fkr zM8Eqn!U{dMcAfZSn}JLEA!wuONyJGSo{0&}RGzG|M*@HdMlLFhKTEjr(g+M4N8Ij^IVz_(e-&!2p>>Fw@adE=tBZut9M&(oZRoD}YCM=aGy zqwIHEQU+%Xy(Y6*)m%+tBDKYUPy6JldS7CEn6TNrCw{xw^w_-X)uAEDus&pbkK_IE zrLrGc%~l{K*>&|CxI4enW)@v{cyRX+1A;VnGWnXvowCo>seV1(o}A@iP>Y5$pO3$I(gjCpJ6uO9a~eoI*C!i+tEtf zmCf~xnRovOX}F8S(l@TU^9JI`C0)-;ALED+GV9`zA;YcY;d6gz4ukOinVF_j?3%s( z{?;|Vq)cOy;%G!DJKLk;Z5{{COfNgSA%C)AuU@vR@)4rpuEFD4caIlkLF#EQ<7UTWr?7#QKo&5%hZ^$6j#Jb?`~Av8w zOXf6-*IMw>b-~JTJA7ch4wtu(MX|=r`&_Td_T$(MU&jLpklrIwnFz+G;3T zetQ5h;?jtnGRvo?>h3IXtljhKv zEx6_xNDlmU^@^Gd#AKQvm0r@I1cR4CpmJK34?gfz&bGP%jR*V8^hQiaU0}z=BWI3r zWU7RBb!rhmQ`6}&bi*4`CvtJ{rpD6!TX!ls&Ee(Bg?D?B3<E-Quxc66Nu*_-FCRyDvgh}M+_BVMF ziz;*)ERzO{J&eBEwmvdYb>mr|T`N$qmBm&O#hM!{Jw zC3O`*C`|Ks#nPVt^Tv5`ziQsnmTM)*5+Wu9DQ0uAz-l}KgW)`8+i9lL>#M-$VIslC z`yOuaZe6Jg_UMqV@zo0Gz$YPD!FP*R=WykBaL&oZ+7clHJ>-I3K`*}f$Vf(*_K`r% z_Deb4hn=OL4-j@J{wfY`&a(P~bydh*M-=sfK!|D6vOR^6M(^ZQ!O<;B;fHpz6^Q+V zDM?(zUmgon;_dxO9Nql`C>V(IzAl`R8b*)y@y$%$jzCDWGiPt}uMn0}+jP3kajo$o zNB6jylwZFgC^t13PrXDgxfCc8Y!dstI2>|(f7|i4y>Av}Oi^pO>D$qROnE0q0VxZ1 zRPbbMKXxr?@&VwRnMcWV?N!nWUdAtURT(w@MMi;GCSS!ESqR)6AcN zxvstNKhp5M(Dc0k!l7YpvIRx5xJ~+p5ymxgxj;$y}u-sVQ?T5WAR%qnpXpH*!M!Q8< zJv@9TFQFd|by`Fow~Jv6KW*)HJxmhQu$a3}R2ZhW`Qwl|E+GL zWFEbp^7`-yKLI;qAm;Abz(2d&`eo_fX{_d>DDmp?0NX#Usa+Fv=k88g63b7gEZAB$g+U5%}+iF?DD zJb~2il|0?X0qKmJ-Ad&Rtsr}2hMoqozx{sdI zjy-fc^QUJiywXS(_(ekH4&g(77If4vd&?!mYMC>9G$M`;@B$!6mL9Fv`&M`L=G&g~ zO*@H8ZbXe!CC*DL>Wz=yx-f0YjjV4DPrMq5=dyFJ5RZ=E7H2kHQ4tD+7sR{p^G%$~ zC~jiSdggt@Wa%WCteI{^A~uISAB8Lg+wm|b z4f%geN7%#M>DXEoh?$7XowxSc(|KU^%V4ZTXX~6obW97yYppdT*W05El2{pjZ5U>> zO5bN5`aFRolWJK_NcZI>!I)h`cJ<@`s`C#==x_ zWoA}_F5D*l1k7X*mbqc+P(s0H?uRkrB5Wo$UctvTK5AD3m$QD8XW%6tR}M4qs_}#G zL{FgT7Ggz=%#2}qzz9)NCW9{DFRP9dL#h6Y7p zE}}A4%I&9QW?x)Zi0qkwpFmQfxW*|-7{{dIF;vc?#a4kOu!tBl)&n>Ik%#*@${Otq zGRaqv6t%J@jx)w7V^xPLy#vXbs6PCW9tX`$I5s3USPKU9n~pZt$!dvH#>5N$Q&u1u zi%TUjRvD`6;QNOK$Ock2VyD_3^kNQ#E(K2z!Ws#Q{4Qbw2X5;oODh7#xX7RF22NRhT7YEjWirZOId00slzC#4tG3?G_;R-=n%#^CZc zW)8CO^Fn1+3*nwTkl#^xMT0?o`hP3?v+)wjqdovnkV2u@k|hplRvLl!{WGKGBYrP= z2=x(76iOCkg*K>W0zZ_|pA7{=s^z{90-b$}Gv_3famRIrUo@96Z{H#DcCSQB*Ft^` z2m?j~5QoA3J%kRbZ8Hgw#H{phd=vFbe3$6i#@BBJG~{6(o&ngEzqEhKa2o z(t?=t)!u9Y1<8hPD_ra+BMSIa`zJ)Y-;1c0a@9T}hBxTP129M7c)MmBciAEaocEim zX*Yd-mSa;CdWD~Z=?<<=eUO-)GR-J2_Tv>=17<-glTcHzI3J-wEKI;!+j(dabP9=3 zP6TCPkQhY*aE&Plk5>kz8RxQwqNBSj0`f!##;a|D2bIrk#*_pQHr8jR!*#YG7RE3Q z6I_?8j1?Zz=?8C<79L0qH6~4lDN`m+BhUn=~+L+n^6)QxyQ+6Ib?I(}9GdBkdQXf+cC;(j2;t zzDkaUF(At`nmudtKl-PSQ4+B=Bq#iVq!xyQY8sSmjxt%r>oJlm6!$lhT&EIJs!%kr z7>aLw|Ncv5W|Z?$rxZs_Bu!TWXPBpX5J5O=FqJEfC>bmzjPwExj{>phJ0p}oP4x>r z#>gxe92q5;j*C%z0M!V{EpP_iBSbcKg*=mJOS}-q0bQ``rrxQ{s8;YV4=!m82>2L8 z=QIpHPIx)qU%|m$FH&VDgP1-+LKV&mpVpHvk&B^`?tPK&Ag@bu*lQHYXwtrzFQ96F zBJ{Ugd16C>TLmP5K%_wrW^!GC(cVZRd7`K+!7bi)4pF$kCy3)sjM2YG;gGSkHIivo z>7tBq;h)+9O^1mvkUC`wlncp;(f^WVe+VK@D$4>FAhJLyMuFZlN~J={|8gb-U?S$0 z8L>m-?)Fu0aVY*-@VC$pPRFhqJ&6g7@xJ@|oeQ8$5M2fe5_33i)}Pf; zf7cca6|DIWq&kHyt-D3hXX)hk977Usd;8i82t>%}-%`PCS|n&PXEX^d2__p1Av!Qr z$ZIbiX$un(qo{ol08|*txj5EPP8+x9qfFYrv3cXEwAv$*a4Wag9ijh1Ns>%u7IUqP zirzbru9+KJSgkw8gI!#GMD94v5} zXAw+0AY)NOe}})s;MWy`I^fXr=5R{}7^vrUNe00V|D4GJDTJ7URFnveCncDX$k_Lw zbf;fjPWLqf_j_NNM2rygV~t8B=(b|OlQpk9!IY3fVvI~MVTJ3NciDx62YT%aMFz)a zg&9ikd|uE}f_HMk4h=_HegASdhSx#~J1z;F0KmfX2Z_Xib8<0lw^pPLEl6{z{Y5PZ zkr?1#@%iJ1^IaiukUe2RUT*j(yGWJgKtI}jsJsbU7oWX4&TAOO!pE;5WB__O&52Gt ziZIUsInUu2>+4PUl_Q?=5^+jchTlH*70-QoEAc|G^@OSWxZQmUPQ_2!-FKZ+T)TEAV{6d$`Pgzmb1fajZ{7F)g+ z%)(CAqsrye$|(qEsw>a~G+Rvb6Ks}WcBj*EJdQS-f|@8+zR=#D)`D@LODPs##=;wx zkAKu#GyF{FUVO`|SHCwWo!uhbHmUXWA1Xe?tqj{Yax#45Y?QM+U;cJ(RBTR`=N)3) zTYqtUHIX-T<9Fk-BX=)sDsyB$@~xmP=&)}Q*|DTXC#z8WQCGl-G>x07X#jBBje4V7 zuArR5O)ukV+c}L$vv-u5tEIHAB$S)v-vEy7WT}<>>{Oge_#Tdbt%NeA8qJ{gkC|~w zZs8ra+2M{Hw633hHEdeH%Pgm`UBFp$=d9WpQ{%Kfb&Wo6<9hl1ba*|yw;EHzk<9{; zjenw-HI22(eB)f%-+XDz@Y~=o`k>nx(bSa>9b8NM{>E9m!s)RS^U-eZZoXKL=GGY7 z3cKm>;dZ)0z4)dcS$u{u;~rYKxUzKP<&SfTfL*yMBH?pD9#^Vv3~7x>c# z*cv|eIXkLK@LM}G2XJI})pWjuIO%f2*hxo>+CGPitl}J>?fT$2G=D{OE1W> zrMg`h+NUQSvGPfoAJ{Nn6jIn?RMQqu8^E~JW$wB8_iruMxsI3fbYgpGvgyJOuV^Z{ zSS>4pl~=H=u=+h~FT7v*az1jq@N!gs1+SsS2`Q`Pg(CJt}za*Y27MM*34`BybW1G@?cXnZ-QQaI=Z^K~K&f@g>z7W=Z z_mSQMp)E>k!hMyXXx53C@gVGk8_|>6JLK)0^^U~FSohgjXGH1Tepd@N4-L+JOD=MW z^3_kX_B3_nls_Wd`(7vGz&*CSRT37c&_1q1R>qo`gXss!J)K@FY1=LFUoKZ&gg>+si9)iH8(gzmRLWl@{IAHpR74)b9b_yM$WmOv>tSC zFwNC}8mew98**tdnz(3cEnn{7*>5NNyCK}Jkq#Mpaa?{GT41$WNkFGtaHzJ%b#lIp zRMQbrqmy7UXDjCvq~EsXHfFY#Jmn(8{_|gQUTaq`4P|;m3g&;;cC!E6p8g|v{(lt6 z|1Tr0?`m!TA4RX)gxv-=;?@TZ#I$&K?2i1#Df`e9Tm0ha#BG6?A}SS~5;{^Es^oat zPd7`V4o_A3zjrz?p8DfQ(*{^r%!4Hm%OywEfZC!mYOEv}*Yq~0k7sTANmpfO=luypKs zgVscuVguMedur_IiZtx4B%qiT{shP_2wC^8{C0vDA9{|n#P3xZVlaY`B1G0likgZOaX!N8rlD)o;< zEh(lWCj~@9Ja8t>pebrQ0!}qtx1e8$E=UtK$r?(uprmPAd>w7qc*g5+n=B?p*IoBLvQub&e>Wd(uz#0_m zhTJV*Z*Q9_6L}D;67XQl9?PZkr+2dO=&D*zYx*m_I{wa%PC8b;zu&n_SGlr0TkJ5+ z5_U~ml}p(5SJGG;`90sc8+ouawo-&I*2QxD^VA*md5_Whmy>6yK}Q7bxNcr*^sPE) zj?Ayi?4XQzl>U7BPOg@Ygtd& zS}<1W!qIF&Z9J@1whx(+SHt1E80SoaKAe@qPO{TCbS6OYosUBZEnEfIrKJ%*`+hlf z?eU{~AE;#ea8HG>u^^OiNZ-d7I#2_dZ^9&rzDXVA`Lx}@n}D#rN3Z5sRyAsj_*ezjg%WXBV)8tm zT|0yE2CNe4;{X_*E#9#Mp7LZ6o%9)>{9M02el+c% z13=aJB~E5no@g@gS29+ZFy;nsW`02d2AjdJl)q`4cMyGU^f5Q~*${X9GCA&k28Iqp z&?y}&wrI`dsOJkNKza7FMV&+D#*3w4>w{h(+TU^Nr7Fc$XGD1ytgUa$ z_4>Y!VDN}hLOB_)A;T9aA{D}>EZc+lj7HwP`Ne| z3#`KET{NJyXf8dq)=`&U+B!@srZt+3FD`p>#I#-vlvtsiyuBHY*BHo==+rrL&6HvPLe$NO%`)FJGWlm{KOt;3^Z+=szFcCt z7p!kho<_fb)Dq~&uUWasT>Q0#`h2)La`ch_@Z>&mtIq1>;Narpv#s@*N#;TT5<`uP z;;%rMYHP>l@BFh2GbDHOiQ#kMq^c||MB{jem!(-X>9M^dJ(f#l$^PEb+C^!hNml6>3sKSU#Hlxhmg**R zQ&`xct^EQQ1Qm7M;d7%FkiXfqQ?ncVcwGOq>9Kj&o`Vb)eZhqdQ_>4PN z5|%Wv2U3Q3<5?_(0Lmj>8%iD0Xi{VU!(yH;i=3+1}2-%?Z!;>f_6AHSbaEpu~p*R`}QBDyY5tD^X zll@+y+8UcCA<+v{A_+|T(b)cx;`~xk8T6*>o9aFoNI~%)1XS!TO~zx{xB&_Q!AAK> zMD@~4f)0w%BDp5B1dK#{$y;#*DJN9+-ey||X-V~>SX39$GAM;~TYLjxKpHuxWvke@ z#XyTY+iN2A{F3!9R$B(g5!^Dj1*;flz4e7NKaw7WmiD<2n^VTBW+=km5^W8N_-cVf zfy{%Zmvw-(uLXVaLxDy26Fm{*E=oz<2}DC~GX#YrZohCE*tw`#3m&Q68k%CkpHB6` zX1n-53`v%OO~@(U-CU9Z2uZ4nP5cYxqyY+P+KtJ0H0QfpOExyiG@mrT>dY7=Xvl0! zU904vJv!yhHP1E@fW&_X6>;FLOp3+M=gwa@&cne{)^;J6N^s`*z^x<8X5 z`=IQc+GTCEV4=cfsgCXlSx^m$Yk3?vnxzvaq?N;Myc=M0!YVS&GViEVB#ASS%n~qJ zqVzAA9K)_ToY86C+qm#ppE6Ksr#<^B=M*6(c(p^GQwzJgk;97EOtJmsEHj6b6Fci< zI7r6UF`ixmGsd+{=d^0~{M_UY>x!6kRrl19?Hk>U=ZJpjBfH%#AR+7UQC^z?wsHrlfOh%uZnyL!A0 zrGrN95qdBn1frQ)+V)#omTT|NUqJ{PLu9$7KX@|L$Hpjo`N2G2QoKIJJ4UJ|us;Nu zhEAk~)v_CA-2*N8x{KrKY=IR&)W1D<<%L%6YvWV}!@zmkg1#g)BYciTSaI2T1O9v# zI#|XXz;P1G{f8M>x`aEday29J@=={W2w-zdjpTFd+}cp0L!X2K*Z#5`I7XM!N;-P_ zi0m9(cvcK8dARnwn$>a48a0=*w&8KrGmV1TuxlZ14)7g>j?av0|h!a#l7%ur2pG`$h#Drfd{ede;keJS z4J;*=GWAVk6**9Ip1AEYfr357rD0mU1yhkh(fJ&zAp1z-L^XkYPIbmU`~|@_ zkvk#6f&{UU4}gDd)uG@plmruG$)og&PB9gE?i$diURO|1Bw4hPGadk-%(iA71t`Dt z*oN6`VS(|L^nz$Ce(E_>#|Gn?1`#>OdZAopUCb$;dYlEyq|`vDv;2^Ml*^SU0ea5V zk3bqZ%DSUS)-dvbEcFjN;3~~~;{`#7HF+Ti!)fyuECnpQX%SXL8h z=$u=Rg2Upe0{tGstqIxu#dx#z+}7_@;Z$&r5+%78>zmko15W%70u1^ctm8U!Dz z)4r1;%_FqmhQJ{+t3?bw|A-LY$-j0&SzE=?o=9SmC(Z5bCBvCR@+Y0-7Xyw~Ijfri(I?w%hqRrC<{qFCJ@gyHM6mh1%ncl# z{BEYyK-fcp_7EyzPh0{7Nr<7ua7Y4kDYujX->9EwP%urvC2?Y0-aJ;2Oi%}(m|7X_ z7G&>I@RU(_SrY{8t&E=R9=VvEuS#%%z}_nlji&02L=?`B z1#l7}n+G#;@>P6;v*|C)NzTZ6C`-?h+T+w(({?z-Sh5YTLtGj0-%LkMbb^^m3{^c+ zW$WXXE-=)BhIMQ6Mu`q7<7*dE8LT=1^OlMqv5MAfF*R$5fqd|qkX0txynJG|hI%W5 z0+Jm91#Sh~B5hw053}_@OajgeJjrkj!Rq7t63$`)^V+Y(XQn`LWe@hh7Lj!BM`4f2 z1n&qR`m&722V8ES1YlVW1R z!h968T+9As=;d3t_$s?97qR`FxiWH?i}5q)@SJ!TM(lO|^Ay<4iSKf$VK<-l#pHA; zCo?r1WHj^~hObM{ux63NWj9JU!{({i7fiZWhThzxGvg%)a>4cNfaz$kdhA)>Wo6pU z`?2)B6MUSpgV2Q?Bfu-w+W7qN{qJo1LWbQ3p;N06skgv** zG+DLDi#xMswb5})jDfc(;>9Ax85r^?j^RU$6{!x-4UmJk$k>-ju{oUNU{Bqr|a@WE^S6GlpqOUE>;X`%1aM_@hl)ufaKL!d~s z3?Qzy;ej&-?3r4$zeH=ES^lZvwGt#xrY|uvTA{>g&#vYw*_U-f4>OO!0NkH$lj<<< zoSD*#*<7(o#!6&S19w2VgUx^t`zKz1t(nIglw&Gw{DqunJMjD5qH zD4*`!#>q83+&Llo@2?SWf3P=Jgs&}@sp&q$Wn0Irs;8VPyTSxGS?WN@8AD_S8OxxQiCZD!vHh^(&u)C{k z;H}qz+g|og!5(2FH(!8h!TsAVZ8GFsqqOcv4SQ~yH-|^dPcP-@%H*z>H-=ntRR&?+ zms;jo&YT{8s8LwycCoGs&%iCsOn5im-9GK-B-C0T=yC;^0lpje>-!^c8S{NB`%1a+ z1`Wbl*F#Ok965_T^7f*DS@2?weWdY)Z}Jm?MZzc^uPiyKWGtN+yxxk2#EPE4ZqDdN zYtCYZtZe7a4ZR>nF>%N8B!9*0N^k+^E>v zH1Ss6yOrqo;4r_>K{t~I1{dQFOX#G*33lYa2mj{ah3}0b?BcIe{Wz1t6<=Mzm<9&; zd`M0Gwcs>vMjdApI@jL@PAw#{!iR85Wgsm zvfmHp|7Se>zXmsqEUdq?8xy&H%U_gR_Xkxlom3vI)J8Jx+=Hx9-E$6SY*q$|Yh6;z z^@Uk#wRJW<5MTGpBXO~}=Q{o#s_I1nb?UN@hCs`y|Jplm*gu0tYnnlcdB~|tX04lG z_AF`v&qL%&08=H(7*x^*K$e2fJJzBB|u_{cMO z4ERakp9f9;DmYl?7?+exQX-rwAkJaSRmrz$agm2$g3fv^{)5wDm7HJ&TQ6eUG2a7Q zm173!js07qP6oI!C;J~kQdQcX|Dv(|=lHH?+MT(6k4_or|M&P<{@3U$M1InCfF7ai zgG#lBIpL3`zbFmTX-S~FPZV%F7Pw~knY8exx(OEuj3sYbe5~*k@h9)5*=58fEYM*F zQ)%ak9;ah1Pkhu_HAU=ZKH&jyLH@KA`*zM+%OxP2QlM088$xg-jXCg4lo%{QXZ0;h zK%0iL%*w{4>>_%u46%tYTx9SKG6C%h>~TGN3j&Fkj`mSF+IgEGB;ygDwn77Mx(E;L zWj(uYwC>@#-&inP!%xUW2*(tEF$OmTl4y=$)~E?((*|}!Yq*jekLRa$*!TaiA^mmc z3U`A501*6LX8Z^0^S|#i7&w@J?`pHUm(yk&;*VY~kV*;iJs|~<_#|Vdd&Sx1nv-mm zEe9p{t!5zyM#6)?1ZzuJ%-5F#B;!}#EJ-BYCW zGrpafThS)TUzv&gbTJ8ObJ08+L%;_FFi_eBpiDM^?a~0NBCA}+lZ!AAhNx>; zWo~nSC-(fq)$hf|v8tlBaISpNCQ0tt*NwTisJZ+Bx{g*aIx9En+>9UJay~gTWW{oC zu7AL~=o#oITD1uxmOUu|Fs!Aw*>{6P0B+9>IeUO2=EE*BFFL(|5IeehKD-$+6DBt) zFcZejJ>7V|9hou`$5YRbTwUjh==ID|z~6}Kp-h?x{s7ME)K4YNzR>evs<*(1gDgTV z9y!&6c4~_^KYfon0bpD+MxZYU?NtjxgzAL-vPj^5StM=>IknlXcDcE^UQzBrkdLnA z^bM`2-Kw42DHdELDcgljUZz1J8p@=$l_aO&KLpO+!-v{4lo@A5`8S`wj7?`ZJogmi z_li&t(bn6_Z99p7*L3cp`Bj@7Um!^bp1z2OK}GI^jjgVu{pvqDCEK@XaEbNatgnY_^O2RR&JeQ!lZq2%UX&>ttw2r+96mYr9MT7#G`Bgg`4P zI?aaQlt0)xDu_@VbozgYjrNb<#0lxUNxXmE;Z{|H+BIwP>1oIIY;8@O;oJHV-AHJG zY{&KSG+U=zZr5*7lUXo25V)9e+qLp;ZN>Vf>-;h$i~#?YK8 z;9=-;!F+tt50QPNDtD{RKlQxV+O&*3OQ8m+-%{tA9qejr%v*wT6B8Mkanbr*+FV-{2)jIoMAvWL5k4$C+KU4(HZ;`C@lBTKvIiE9- zObmdl4lXb194#8etTLb%7bh7HjV&gw<}E3KEA#gBvR@Wb<9-|$I_1WMlnWi*yupG~ z9fzoK`zNzvtvOv{;|gckrC%5%nwc)6#cm*2un}9oc1+|l%4JAtJzBqpFs%~8l%;^u z`(2w~G8aZ1%?x;{E-OP+7gOvC&Z?B&s?$l!9pot5IAFk30qRSYzM0wCaVX^_`6y8r7 zOiO{7smQa9RBkgAu&P(%rycoy5dDip#dv5JGr_^PtF=7~eJG#Kg*^?pX^CGmBzavmq)_zu36S~OTV3sYNT3<1_9g(h{kNQG@O{W$@bFsLB((lb~F z@8IeB-Szn(#NyddH;rtxY}Q}|1f-Lg`s)mW3QvL+7VP>8w&Y)gNmdEuE7Ff@hX8hc z2``QHS@nh07w>8qD=a*xy>apCM$4{)x1okWh~mx1jO<-3mJ$kMh`GjwFD+?_iL+F$ z9{luvZpVDwC`1rl$w@{(M_`g{J|FaIAR^PNe97FUuRYS4qT(vl$e!I_F7ILE<3%7f53Kr0ay>2jPea4I&Uk;2wU1Pcoa(LWkOmbc|S|KXJU{ zsk~xw+V(mT8?H-#saS)tswqZ?Q%-J2PgkK>@U&6$+x@R>mi`?XN&^|+S$2?2^e2#ciF(%Sxouy-GUcB^iMdCzr)`CwqEBtWk=?1U1 z!hqS+=F%~~vL1brd4S(-D0NWWDss@k@kY2q%7UF}6_R`bm`;|kmQRY=1Cpa(pDy|= z_5l+JI?A-4Uo0t_g@m&siUMb1db|_fxd0fKgO_amg(ID5c3Gfmd_RF8V$_r0Cpch` z0YUo&;*9;M4Nj46QCo~bPrf=l8@$4H+W42{tNZ8q(0<}8#)CFe#-Z;^UsFkSBi z!dhxKTi55~dk_`3tFz<=j+Pc~M@Q!;#ipI1YS}4U#(XEXrQ=m^>^2C6?qEEjo@6rD0YHEz4` z+mP)$&Qmr;ifmXN+xS{GQeWm+BP42QXC+wyhn-Bj<7;qG(k?^myAG;B8bga@LE}rA zZwuk{lIoNQFh&?5Zp<=b23g8?My)E{$yz1|*1<973LaYGde6A#=?ND#?YAa!xYl=5 zA#`3~_1P*mzxl*9Z!<`wBKxC5eh`JHPw-Rg39K-O)ALWq=PzQ9k(Mn6S-AHid@{`9 z*(s7>cVf$%CMzit%a)&uM|P#L_~8@AY)PN9*CLBkHp7rR71n7b4!#{E9ozauog<BZ7^pX)GX=6Euis zGgQ$yKF`!qoTG#pp}vP`p=k8r8W$EFbA#Ao^=+KEUu7HpXXyG)`%!?5FBX-BI)|EG*V_bUb zc4BR1REcURBX=W1=>w4T9Z5nELf)##ObkncAW1SqP2^{^G+iWI)0D^AV-*RU_K&h^ z4k;Qi)fBTOdg->u#sdu2W&Fe=14x#PDN@203-+o2QwPK9w_;!~SZa7+`{P*;6@j=; zfq?$0peXvQeP<*AUd-@^+ORInIFNA}8Vs0@oir=eBgC3GP#F_X5k7t7PgO+(&>aK2 zaJ9E z5PF-96AnSdlqN}Cv6d>sNAam@Pt$q|`vi>D5f!d`6gGmE19z3<$hF$L{_oTk<35OX zkBs=4u`6~4*N=3Qtkj*IGoX?pi*zUZMFKCoi!TqDEuEt$*RVB62FK2I>(-W%tAJkL z1A>mb(8!FIah>LN#6t1Q(<3XF@v*sh*OplZ7&`;pda5BT0isjU!=a6XuiNA7Y3(K; zR|%M>Xuj?!sXQ0Ey{i7`pYP=zZ(eT1-CgKXsa}GnB;)T8G@;S*s4n4Yu4Uy42Tt43 z+%Gptg0C)svx*1HoNp!b(GFlJKG zf{94tGnfSDAaxvhke%aXW4G!-n6;-BoBJG-ZVo)dxe_e*uTjae43wcmH{L{221oCV zq>rCs2?TiTYqBOR*`e7y^ZP2g#UjmXb3 zdozR1Jw-xpB%H&w=>)uB%AG)1>#GtjL^m1d7l{na<)odeL~bjH*IXCX`3BTv_jHj* z1pd)=-9PCBgY&AS4A85ub10E}(yZjV&l#_Q01hzYzj)l*yAehnzs3`)kLgRLyzP26 zijc3k4Vim#t(m|srb=r(Ad9=vT)*Dei-X*#-p|jl;axPp(gh>_MhIhLZG+mik0K7# zWoBWp%hp!iJ(AYeV)c(f#*UVO`2oz_>!p4zK-M#~Q3l>{OX%)%lD zR^0XEJM#z8Qj|`k=`eX5iAI+I9Yc-*C<$X9mODyAkHF(57Z<7_3FGSKc%yA+l}57~ z^)fQFiglfOe$0x$7}optChpu^xfj@rnE&FWT)c#60NClz?!*6ZnWezlZ9G~HAl=r7 z3D7Ju=W?F<1JJ%?hWDYQJ~b1`741<8_Ef2zB%*o*IVn<)XERtds0x|qR?45A2fj!H zIgCY=}}wh&z5+U((4({V!q`Xr9KR{fW!SdIqOtU-JZ){eN{8j5_~^dtUn3-uj^9WMzJ9a`T0nP;?QqkXBCRy8-CQg1h3Mi#`VKMGZzkww2R&~}Hml2aNNF}s zMhp>O2EpN)4`C@Itk|gB)s)8(#04mKZ@kxsCeCjkX;jX~xoQ^{%V98%X_&N2FBVGa zuIQl~w5N;9EldmOGS&2ex^I7W1%Cc3y5K)m4#j!{n9eT|29@#uR^>4LRyo^RKW-cB zNI%)VhO|E21GET#x^;8u&O4Jxb!;<9=_K6r;tth~KwjxvW>X~2^!%6-t;hAm&1=t1 zC*eUR3+KJx%$cz$YWQ0J>^H7VZXma4ETuvyyEDbEk>9 zw%OyRk@O61_r>))ktN{T)H(4%uqANm*_XXw&s{kGEc|ov1h*e9;MnX-pI(iw-lsot z4fbBSyjibxMVQuG&iV8AA_Q9qW1gR?FSjwAAv_4Px1PwfpH#fB-Y0x*DaPYS7HOvM zSpQ@3>rWu+KPDoLI*PkeYM>tdK8>bGg0zUR)*HS)t&_&FXZi7nu!$}w+>=olYn*h= z1JKNY;5GR$rNli+34l_-`(}wJ(kcyWPg+J%Orp(G z>?8=xtZeOPzX80_TJRgNReV4iXU;ZXKSvkP($iFX81&K9)u8V%x6xGlyZ!rv&woX* zXLLxB20vWZEXXUOLDHD5I&>+Ve!?$9-`eKk zO&h`FYx-cmeVGV_{$bEFLRNl1CnqI5FUUZ1J!Bq3AGI6J-Q7HZn^NrVJJ)zk5V2@k zph~3i{-JTf=pls2boMEGZTmnxnw5~V1W8D70e^)NAKF_5ayfJfJ3d}fHk2|r>kQ8# zEU3Y%mQ5Av0{@V@fglPZ_%Oh#R`6_11*Nqj$DT-1kYdjq;8}EKhweOs?7LzGuh<~i z`1wU8PH9xDe!LwWF7M@%*@J-dcxN?WmNUj~%T>j;wG7vmG(mh>tF!dMTdub9klk6! z>VDZB9KaOi@C)|=M?*mD5-;&A`X1DB1x0h=SQ3tB^CBKf99^)@E$~cG=U@ekiOo^m zoiAM*S>Dg3oIz13W9<>1I-aczMfnjkgr!p@qnCXR5e5PTqR!)wV&H6YXp!feZ z%X634cOYN5$Pdk+$QZz~C}Gb7_L~5Fa~fgnVrbHILB3g!k4Xrf>z_g3B$qwxRM6zX zjF?JlT-cT5JdqxmLSGlv`7%LV7?y!(M|&vx!CJD4Jiw^dpskTI*UFcvv>O?nc+U&9 zfLlzGqHj&b*dj@@uh382e>WJA;jM{7lp$dj`}CUdUNW2MZ0Y|IW|puo+NC`#j>M8WX(@*nlta z?y!s9YjHMRp_hoquIOmB@!Xnb*hKY=mC?E(-sBov_L#zr7OB#+lT=>Z@_+DekwJmlNFGB1LP8Vk&eGED zLGMb>B5*mLk_HJJIgHB`%D~8@7!Bn^MgHD;}ZS#~>pMXb4S96V3UG(DpB)vjv z?7K2QHWWcx{Fs=MgJU{nPW}@0L`^PffW-)hX&{-=Wm_;jH_5EY8;zN8@!mk97TRf) ziZ>wXR`=*~+qF3eAZ$mp6}q z?8u|q#F8_o7uT6jmcp&OF6Uqr8HaH^=TNx&6 z)C`p~U$=}ak++>GDFvpXpUuAc(iU?2*74(W>C}tR4@%Gbi1x;?|5a(T%GBj53PO)u z74t8`KT~MvxeRGS7H&k#?b!t5N5Jhx555gv6T@A4?5luqc&L!DMkA%HOz$YKDhhMW zjVk3%hb7*{V=#SN!HF8+?J@2*dNv&3)11CV%bEHF(4ub(iv_h0DxIIf`dd%IspKLb zv@W^ev{n(Lj8s<2xdxA-s*SVD z7(3UskWS9afisGm=KI=8u+uN1oM!`rkge~4I~`n~%o%E0XY@o`l3hInPs<=P8a)kG z<}z)5oJHIE{lIa7shkSelu981>ePa~HcdK6#lnv&CbOd!q{nglr5q?1(c|oUl09wO zP*gzhZ_7d?Ifn=Oy{9jR%N}w)3r+y!iH*Y8o;Ei?qO_z|;O9;=#hJ1*f$5rvJVtc2 zb}$74b7kfu^-M;2XJn9}zo}>lvZ6++`OrB7B_-)_1KBuMI}^FHlJuYrg*ffHsWz>o z;l;#>5sv6AvcjfSHl(dvwgyjRgRuflA+tom>Ez#4KJS?KA}rPQX+r$zw?IPhnDA+H z+RegpEYW^uhx=6@X9qddc&0e)*vXA1U>5qw*rlk_`Gh`?BD*Ye50;2dG?3C#uM>fY zz!bQOLpmWf7r-rY4nua!boq~*`lk<+St>Ocq{64!m%g#c-8_Q>e=~7@pUz?VVkk;^r$thB<8a_)2E!kbymW_g#^vU`Q|*d*l!3-U@|SQ@1Q z1yKg(%xZ-M4W*KZME1i37dbC<&J#G0Cxt)H6uF;1Ps?pxp_gC2T<9r%VNZG zqR)2LSoCqITA?Lr?_#2y%``+ahZ=RNCH>ZYILEQs{&+O0B7T$7BHHayz*|?3y4Jns zY;Bz20*O1vf2{^H{l^9IJ6()ql|;1lZ6i1L8tQ0m(ZIel-e@a^jYYZkoGXw^-D3Oe zdF140FwrN0vtP#Cc%z_OIV231Vt|qzT_lerX4&`7L#u0++y zl)Gw6V`LmYM%pMlf^3eH6{xnrCxzwVja9&ifcjh>Y8i-XYrn9|D zq8GfhH{{!`Dq$%JP;EXd?gyOTl7w`&mlUP9-n2iFgRfGF$96GQgkmiJJ8$zp#W<&m zmnrJ+CffWznVbK!8?`VsFf#eA#H*Eeqn79qd~fs^mQvAHc*FA`2F?jy3ve9m2n|@X zZ4X`lCQ94ceO+V|Z||N47#bKPdO!I5O}AV0Xa&@^q|co92!+SD>qRh{*SS1otP`8Y zKGajE>`1?8KSJ&I@SK>-mx9c2}3G+l^YDJ1H%9SlPL z!b6T;{%mHiHPBpo4OvE()5Q~8FfrZSMo^6ARGErFK6D(wjyyH~Ff^pdcKV&xt%>e& z>6Uw~+wH5=ek`}Z{c>yiDC_8vs)kOx<5j>y1yvRJMq-qg&xPr)e%eD8F1E+1AHjqb z+@8(cT(?zxh4%|pOnVY?l2qWP%H?*g{-DvrCzuO2bd&7U4srjdeM5ItQjgj|+y(%7 zr~TY`TVhj>)alH^;~|$aoLL{)4*OG;`|)xLeKWof7)H?zhw+1|AjLIlZVj{Bqm3o& z!b^Z_azyaXCn*?we~<ICSb^9lApKA!*FAleI~UH@OVpg9x( zz;9^l|GQncGB*9aKf@||P6etcey2)ViRf#Dh|#j4HC)Z*(RqkR0W6R2I>RS z741DJGVUS$vg4;gs_Js$9R(E9!KD48F~vz$L5=e{wYRqhQKiyvk*OVX^chyY`xgQO z;u0hjhp7EI+6jWBCiEMo0ce>XFO<&Fs?y1CK{>g0wZxt)lbIpG`;_g(KEKeNH(QLj zWWg!^JthsEw`JSbMjfblSGQz%S#2TXrzIhY)dSB^jYFk&yxG85=zuOU)94dW2ic3# z3lNy17=;Q%lT=cRe{x7Ee*iBo0{AT*>(QAdExh*eD_PRLM0`&0rD%GHd((2r;=+$Y z9Pd{C#5SX>D9vh;rBRD$y3aXS4bc)k=~*o=T_)MUkQCvIUMsQ82C5)Fop#v%B-}00 zpS<75*@*eks%CF`>O4K4ktbNN@&e)23m!b5_om6hGF)Nlr+Mk5w5ff|RMJRdN=%wT zN`1^2oR%K$-~L$mRUeispL@ioRB~KveGU*knJi`yO-nUV0vFaX576Q4Ya?lHlUVUl zU}8Y?Nz7^DUz|iK9NMNB2a&X#XtM&k8j1Boty`d9|pg%c1a?}Kv&z7gB~NP>K;HD^RKdaD{b^BhG)*GyZF_lyNBLg zJd4>7+4qd7AieXJW5=yG;aomk8p9K&63HXn?P=z3WT%N$yEegL#L6OPONvnlI@(e;>K1|=jgONa z!kZy=5Ws-==DWhhhpon1v^Mc>zyCveWtFaL8TuRJH2(`D{;#LX#?0nFLnn0`r%hI* z|4@iar?wKBtI)|-J>64IlcX;#NqsRlEyxa?RKkOaiBZOY@@P!o5A6U0iNz&cITtU& z7Qr(?+|XxvKLSbg1k!|~>43+U4sP2XplTi-DmS^PDa1Pr z38)$IC{yeq88V>>9hH4ki`w-klYwrs;l&2$`QYP1#cKCcB1xwA1IxrD@%M!(3Fl^r zzT8}v<%aH?9-v~jeyzyLXE7`Osn7xxW5)S8;dy{Z5?}=Tf6Rx9kgRhkprir!=l>wq ziTa^oLDQOW7XU3q1{N675NM=jK1{~h%W|L+i;|r(a6SZtBuPPmxUEn*iV&q)F=_5g z6SXx%>`@DJ3U}@ViBS^k_FXKruq^GKOTe-Njl_IXN+51WlE=J63^Pno7$?#;w1uI` zqxt6Se7Qf}!I7rs?wyWLPaoZ1FZRl#0SC#Q6VXuDyI?UfM`M8QQ;G?j?;U(icj7A; z-MhXUc)mc!=Qo~jAAf9Wv3GU^Kx)g`UYFeP`lfC5J&k5wRBpcb^toHeqdevATN)$>;F6rG%lg<61wI1uY=v};WrE5^HPjiq2$tqN7a!C+I>56)Q#y@dD@(qYD zrn0;b#mbg7BS>VaS|MJ_A{8XKb%{*K6|t5mZUD0AkuLgo!B@K*t()3F5g`g20E0Mo zf6lF@jA!xkJb6|sCTiacb23kgm@P|I0G-Bw2L-Q`_^40&l?VKatZWj8fz54PL*J3Vw_z!OxdO1h&&JH5m}Qxk2JV=7{( zqc7QH0p2M)#A2j(tY-{tQh3z6PPZlo7TKgIvV48&FSG8WzvE(T`JD9=f5P!p@IB)` z3BgJ|pE^jkBpW|%y@LuD=rIW%hRVkc>b6t@owBCUns7ZIQ;uncO_W0wAm-ZFw*d?D ztxjUG5@0+`CYTyEHTe+uoczYyL;P<>kCePbHzNI_5Pu1?G4y3xkCduQjAELnCIVNp z+g8y03tTlRFvKA3D{=(WyKcC!`!-dq!q@k-zO%D8`>3z(RYdTR!@Y2!ZFf4HQSYOC zY4(zrDl`TOu4EpAtzu2bS;jlrt?0}1$wB$aJLr_sx40piidGY%>bzL^vWpT(g4OHv zFw1&Ujt3Dt2O#`#Ptak({Hr}gVM*YQej8?Y^Hkg@xm6avZLHtfl8OEvl89LerwvP+ zPv1SVk}WjGH!E#G&yeUmySz(;*{r4;6nE^o9cGSVn$V`kMP0E$D`(g_Ce{yv5W{Cn z_jC#8)zo?WfB{tb>Ns)vz_#nHKGM{E{0IninC)#ivBHpZeZ0Hr#upk#a#ig(-ao*J zs#etL#ne8z+pPkS{EMt%rXI$c#s(8auTwirpHWu60lsh+@q(zwVdUZT(Fv9jxZu3Ny~+kKo|L{UYhl3 z1?$yS0vV3dXU+T{#{IVL-r&i>j6s0#kUidQi>v)+Zn1#OOi^#V% zm4Y1b(xG+*xGSs|b+0w$Yi_80N|alX9Mq&+f*+G&JGa4CQo}e2W%j=UqH`gMBjll` z?ZE4wo6j8mvRgfSf0BDk=HsS&ae4V}7G|(4IsY-kWE!k7WM&s)Q0?jG>L&YpF;nsM z&J#)xHzQbrdrum3u<$H}kW)l#mmgunn`H6WDKw((WDLd-g-qw=@WVajmZ@L!aly+K zE$KN=#K9^En(&zKWy*7ya9a&QFL2K_`rTIg&<(pK`5qQus;SSjyD%07^xo6in}YD< zE?Ra)Pmv!)e=={_R1TZ&QKNJ>)pc$ra$TaYuVqkYxAx*P4lg}Pn{U^2-c&38MCLE3 z%GXOcJf_p?!VA054X5TRE-?ELsb_kCMar0)O#<80$nJp1wVK}Znzbf$tCjr||H&<} zyVBoVP$$+D{+R%Qn`)xUc3_)Z%n7P^TIrh8JOAGL{3)sWH~}K(HJXF%Nw-28tv1R- z>w<^NylQQgtD`_?5A>51Shs_mh7ZoU2)R2SrUO6LdVO|_y}mviyFz49?0wMt-+K%6 z8(LPV-|lqkx4%I6pWedW@VC48j~u30sXum+9-;gH;p!iQEBU_ne>j=g6HJWB#I|kQ zwr!nwVoq$^)`@NN#L2|AlmC2vRd?0*eb=k2yVkxMPxgb}SnE2X5#JST$P8t__*J&^ z4Hg-{%+LX$D$6`UTkc}i-LlbOW>r3;rA#thchA#&tMkeH7IC>@r$AfSW_+yjC*r^o zaE6HAyw^i6h6f`QhaT!U1utaKNfGu#15Z#ZyYa>&Iv zccGQ~9li;dDljN%JaQ2pE+|jc8AeV+%5Hx78iblSF1UrWs6{8{#y29)lIqDPf#()~ z>iwRVRu-Rzp(&i`5XBrt>!S+CDtp$Y3Uy=AEEUdAZILRUx4KNZX9jlvekBY2{pnCR zKG~r!@!yz`O6TtAKrrqyZbWBv;418d^*3HPzG}vbbC>VpJDkxsQEuFXeNWr87P&q| z@vRz5RX=sXJWRh&=4b`a50#5eADy$v?EQ*p_m0yTSJ!1ZiAdhcX4?+^YMUdI?lZB; zWh7bJJ*~O#j#5yyikXyzDi|5+aJgKfKCwDY1b2R%%}GeU(;Fjx16EDyMN^TxF$R%J z(uLQl1jtrR2%-Zx9PAB>aJ2GT_g%4>L|Oq%_Nm8npL%{4g-!%kb&co<4Bokg_&gu) zGBl-k?%%yxgY4xoG?R*ml02sD(j`vln$1vTjwa%EIR4vjh~DH|#o55XiaP(Z#OP|~ z;%Z=G=kl-TTy$wQ7LKM;JWrR#i*<=c|56V4E+cQD(}*Ay5I-AaJYPdB7!VIu4;#_K zURiV5(o#>@=$DpS?}dGquAr#GXQrZ}H=z;!F1|16@yFGzHT>DaLcMsNxd!qQ#$fuE zXC#itU%m0yI?5)IjaSRFsIb5K*_D`E*$TgJ#lzhoxBv?SyiJ&mdP7{}sNCf0T{Y+z zO7&!1e^bltQAGN=kQsaAQ^b|G)&<{NerUhs89IF76lhjT*TNui%Jz(N9n>Sq!UFEv zGTqbh^(d0m_J#J!A}IRw-h@f96uiuK?e>RLo-PJc%ZPkBuGu$fg_fm?EcT*QYj5Um zviwDB3h_cbX>mDEAgY3HN0|^LuROt9n!+{4T`d+hfTD!6Bue3m<^sjdWe_-h514>c zDK!tG(9lqxskga{lvNWd(`M|n6Os!D(`!6^9RV#%DPI>acNY4}9EVl%d0E!!-3^F% zF+>WT@v>Bcbe=2K>@F8?7q$LY#sz$%)X9|e)*(1y(`K?p_h!udvH46Sq{YazQ`6`c zoAmD1c?)hdJ3NTk$Y+WA^XDBz3zGG0^kSlGA+)$;ylV&%vP}?o*<3R}-z=H?Vj)cP zr{%0h*gFavqk&o>zT#JUy*9o$9%A@~X-SWCoG*LteNmnn4e&6~3TnZW>l3qS03HazY#Lnm#Nzb$KI!E+zdU1T6`B}q)v4TFC` zo)}uSjSGvLV-;>T<(Rb#ej`Ee?evW~;j7kR(c0z(>$QoM(?nt2u2upj1QozRVC^WC!ScZpnBUHiH?Fcp*nvGe!C^ zxpYT%_cCQO(UtKF`!KV2O+W`RT%0U?C33?y__#)M2a;onE$?53Z%B_OKg#k1~T#RQ&+ z)v$n$xt(^hepK&vqjO+aTP~!JQzAH-hsvg8Ma{OVZ%;dZ?E-bIk2}pw0DK$pgo~&5 z*y0Jnkp1#3s7LZ_Y~4%qqk}60@8=uCkE$9G#gudZ-_|=WI=E$iEJ#<8&M@Uyndu{3 z2$S5!e@r$=v&FP=p;Z#h9}JBgi=zq|Yq9CGi0wcG(b9Oq=R<7pOT!gnlGbI09viCqzDhqC$+fbnwbU#=Sh8$$E z%A(i|Uo(Bkd~2Dyv8tsXx|5e%TMvC&!4=ygBHD1$Wj605N4ZE7>T< z-a{PH)9+5Xf31bKKr53hkIWi9(v zz#YQp(ZRgmslxG%fgc8aE}^z`!Hixv%#MJ7pE>(5_ln?*Z0-Rf-?iW1qbR1yp8`>C zE+I^-Ct3M+fQ9OQ{xA>x_(boQo51}TX@_OxG{-Ydt)!w6Vy~wI|F?r}zn2k)uj_-J zkJ}!=i+|sC&+Bcf!RP11HsIw<#P@o?$M>by;Bz-s;Omy7=Y1%(rxJd#`~6>29K+X9 z>Q|+EKzOd?>Ha`5!&mY0pr2qoD7DAS6}86qrIKW+r~26UMMCg~sQX>x`h&s$t(_~8d&Z`g-Xb?fK<}HtO0#0s2xc<)87Y51kGHeUnVm5NBE|`k3%s3L{O6d=E7Ie;JbZf@;dt?0=IYD14d zFAG+q=M`9|z!sl}#8&R)Z5YbR;oS!SrRcbm!tj0S*%W^J1F@#wL*x!z_P9^6>+AYu zgh+r`AUCl~_{!Fle?Wk!Hccysb4hYj{e6H?`Iyta6Es6w8Nq%*+0`qNBhtR;9zWUdlfM6i zc6Rw=(=p}&{t`JonFKza>{30fbRlH40WXlAHguUZW$yURUMS4Zs#9lCt) zOU5XO$~j|!!qBgEDcGt6SM?-CE~>d8wDrtqVUE+9`y3gU2CDRrJf)ZF60ZeJf|Y zck>ftNctj}?%(z}jdGby{y z=+qinU&fZ_`xq?+)Xj?w|9IRVRpD*tyTy9|r(+eXHL4fgy|}R>(0QcTUqYUXSz^UY zyeNk7LbF~>^h2<_SIr`79vWVx)->k_Rl_8@^5Y^yBVL>@m$bu%KDtN23-=`m;kD)Y z`7Y)Pe;9S~FuHTMKU5{1$dU8V*L`10fK>^ga!3RZ9a_og$XlB~p?$GzD)PDMK%e?^ zqeEIuRma%xOdED%E0YJCWWk6=1{*!}op6LJ^m_*BhOm_U4izCqfGZ>GMu8XMfKt`k zuU>z-v=ML^bjXQT$)J4LEV7qKx-cEkn!@j~vj+F9vPV4Te)6_s@|V*aeUCLOSatJEh3T4XsZ#ztWg4E5jE&jo4jx0 zRo$(~=ZQD8_-p*)s!(B1O=WlOaf157o3N-a&VHyAcksR(w7TMJPf>0r|0tEJdk)U!1r3`|Kn>Pi688+B>$v9N6r$#8?H+AuP2Q3VrM*Qx{i)Q}RuKT=jRnRT7$_YG# zK(^f|a@|Omv$V*TptN?ErIN>*?4?hcioIs?cJ(t2iQi)PTV;(3F^LM#pT3=z3k$^LW=&d#3yi@zPrgkwN1!NZ z_@-ttd%aGIOgercrvH?Ax1iPtt-tLLfaHGTxF?N)_!`!2SK71KdTsYJO&(Ndnhje$ zZoG)#v(T$f*&b2NrSaVka-g~wJ%yd?&cHCL@PhMtjugx z^D7L0`t#~d0S6gtOCcBEGATbYOC1E1?#2&L!rmiY{3;T}Yfx?zMI1l86dv!Sd8TW! z1~)&a{3+@HwNzV?DS>-n#WR)ue&6p|FaQQr?dnIuFlxI)M{lXmxpBBhM>9tm2j%oz znB|iwo}Uv-S2jm|9YO9e06+1=JGB@H*dSc6Mu8>)WMn=+xg{Fu^2kPg_-BM*h#XHh zjEd-_@pto_eTqCeii0h*ztG}17@TQ`3yu%jMm+?TjbXmenlbxf=xA>P)HFK*rO+Hu z;RA8PpSJGw7H5IR$%!}Y(s{#BNN2P46Q&VQd8J{8E2kD0H5xPTo?v(aYF}BiH+`(+ zKg7K(6AlDYqFLzM+5d!Tx88cJ17=ZQwttnmccI-A3zP0i|paHODz2j_t6z zCNe2zdX>(n6_m8PA>L-%#sMN6sZjguX}Gp|JTtLN&k@+Xv_CeW)YzP&anOs>q6#+z z%Vi0n#Y!35>PyrJb?`Dcet1G=ach;bc){@s<#ror*FCCXs7FLg(PUZeoN#w>!jWtA zxP?1apMH~h!}Z0qau1X3ub&hzCE0O};{pD$odE_XTr_orGoMHZ3rC1ulzvv;b5PFlw}qzZb|Yy8Ky6`)NeeF9sY<>9P%X zTRTa?je?+Tj_BTUX!I^Th`OupIHg8c{Uwppe%1S!f*lQK7F)$>wX#je=tWpqMbwt@ zd$RtO6w`}R&obnUzSw0uivbiutp3S)A#IY5b7Q-%*h>oc$LLA{2p!lxS96A^BNWb~ zS1y6@=xVjrl~X^IMVgz^C7v(u^Eb!3l}tVdWrFF~xMSm*6^<6ka31>>6Mj*cr(wV| zw8#Q9I%w{jTv#E&x~=4(y;d$d^VnoY!QA$&w*a0Yd8LzHXHonTGCWsk&iPJDHZn_B zapi<~XM#%AxnoIJGI!UIGaWQe)nE}LVi^01Mm2IZdy4vQr5ul_BiPjBY z@duMRUeV?R&ukpZ<#|*OEM(GTbLt3lA;_7Iwgr=Hr%@G@=A%44H42V&KA0yFy28 zDkNW@PXEU1{4BU^lV~l4BJ~?+R3;nTtps8N zQH+Nm*Vxm@8HNmWwXl%rBmWbeL$jMm1qr07oikM#PdQ}_i^TJ4 z^O)+KaK2VgEhtHc>P*4hunicxjV0BCc3I)+PaPbzo-CnaU2h^2o@1BiFo~q2Zk%t} z!al)U-(h!Qxo__$L>c2(2+=N!W^4s&+Gb?8>oY)4Ok<`J(+g#e>e>KZsgL(B0f*vz zlX}J`##;;GXK>Lg6tIOc+?KxBXi;taZp_x!9CS=7=X&Kr2k;6*8yXrNd%bsJtqPV2 zlW6jFx>eqjv4{xhKP-L;L&^QfQ6h+HOcqz%b78uqv9cCyDuRA(DW&sBkR97HB(ZZE z2}~@TrO@t4bAKJ|mK#xuma|yD!FZz75Y=nknB=mEsQE=%SUnVQNsrehZ$hZ(=2k4b zbdq)v3Ka*ruo*3rERPTll^I6A-YIWh1<8EC7igDs z;%Nej$V^z$SIBA&6d6A76lrqZdoz{ zv`62pnX{y)@vbmy1F>(+W9uh@F7o==n(VpJbOl{ucE}+ed&bLAIxpUhP=(TICM7*v zhd^354|NMR<57G&3BUDzJnu`gdYqxqQBNSD8cG)P6;d9Za46M9f}NXP{kUcL?zpNB zyD87spTyUno2gZpt|{Gx9Ec<0yKDBrN!gavNV^1fv7mVSyE4ADXYQJ)fO234Dl5<@ zzxMY=$9a{}r058GdANx!k-`)3H*pEj(RK&doN*YCBHX_PikO%-Z6s9>Q8Q>~na3_n zL{9nl*C9^DC2OUMLB1^cIO3g&@G{N**!Bp@EJDC2&3^&ZQBC%#YI)1F4W--o#%mV? z^=L1CpuwzGm|SBE*oJ=6Clb(#m6&qsPH1`nSL50NP;R5TaR{d5zvrY|u>dFz%mYy5 zh|l5Mt~mXthxkTA#7vI$hK3W4!6#FL0Ia67ddgC?!O5{4lNjY8!ny^VmSiKA85#3)1lzd~W)b_;#<3#O%@5AA-ZN&AwxX$V z9@^!9yFBf`LOI9 zeUcKZfLkNLxUMYN+~~VWcEn>!FFSR~Icr`G&aDqKOP;-3ojySxx~!!Gm^Z+CdudUi zj2=K@(kAGL7LRHmHw5|`(bxb8UlXMkRdIoWtL#c;fi3Q)7~%91)+uv(KE+q}p(2Jy ze?uEhzT4&{q%o-kYqf{|nYM-sD_J_eF<*-rPV|E9Gg+SxU)@=XAz@JwkoT3r!E+^f zDqtKgOlvTNX&{kS4DJvnsVy4QrM1_fggiQG7ITu zRiAgMPQ<(${!};^js-EWpI7C8TLX>$swA4? z(o9IQqu#(W*mI_ID_*3T^L(!hd)8Q(4od)l++iEq((F- z->D~_S>I@t_tKq4g(pp@Y|7=H>`FlKR?Q!raUl;)ZDTP) z3RJ)IJc+ITW5x_TsKFk)U$z5^TC_;rutkZisb68@mY8N?%@OixI!&8u)+KUvT(r)y z&*?Z6^cwsO9%_N`k*Y=H_FB&&EjCSBXv-CTpkuo@_g-(`d?>$!waTnSVk5mMM~0*> zZi^+18;-gBJsTucVMCyB#TW>Uo;X(*+&=kz-gr{$JQPAqdsHR}ZzogVA&?cOesP6| zdZ`R3JuMlOJd${0yhze8O-s`3D#T^Xq;r23U;mUsD|KZt!M>aaZC1;tdx*CYy;(q& z3e{yXRpK zxqJ%6?!jmF$nF>o+;0zGZW)|TGzh-+MAZ<>nk24ul>2V$L$7p{*>;Do7%j56<)ChE z|Fd!9xp%$hMGc-ow#H+6%-ex4jPA-06gJ4v9{*UZcTT}xjDPb1F1PH0KjYQ7j#l8m zZ_&b_ zV2g&{3N^u65XDjMX7Jfjl4mBJkhE@075cUk4KL-R2?w*)tO^?@+CBjF7?0_df>Fv$ zD0f#QgxAe~>9(J5IlpcecEq~c2%I&a)#MhEDP(2MX>cP`{AfNzb)S5F=x1K9>>h%| zxUAk+{L#X<5En$nHJq9mt<|i{6JUynym*p=_;2%D>U*0Ehj|x)+~s7QhKCz+F5Q$L z_qY+hpuQ?Jw1aW8Pa(6%q@N4@siMjD^2kH_8VH={(z9e;lPbDWN~fjwb`CzLK{G$+ z4vqw36_9Rn41v?m=RFO@@>wqOk|Qza zF|44oL~|>Tay!NEsVTH`6L^0qOZ+Ky%W%AMU@*IcSt4-D@HyJ^O!BpNc_;iC@&fo2 zOM0#$$`G{G*Esda`i}bb2kz$lz=V8oif9a_$NN@$}09%WGl%e=qbeVtxNJ??_aCT_Z|+PHoIoeY;ppU7R0|7;&P8ijWmi5TR6^oO_`} z``mB2O`ijE|MCegPh+Wnw(uxAY}_pM$`XxBH&u@5a7x~E?}~ng2|cXX@zVni#Ifv> zKQS=fq-5)o1rMnuQdk0-6VcKm8!ftcLUOAv<6OU0MKKOE;j4B?w&Y`h(5EJS`g@XR z7UZ+d9CCQP!5HR4#2}}v{SLuw1j*RS+8iKSez%T)6R52 z6322NUp1_)N$|^GQ9_A2lGKOTQrRPs7HMcvr}Q zNu}O9Dq9!j9S_hXw_JLXwccTIyyjnH(K%F|w3{Az9eZr&- zjXOqzAFHN_>5srUvl$NO;$zH~vkm)lFq!H6LcG@Gbbcd^Jw8G@@X*H@*X>0K%Nc?~MmT4A5t{I)Z86gyGx&a(?GHOg?x;A-$G=t= z@sQU~kqRvuZ}&DjrcnobZBjW=O+?--=h~e(QKZT4_!Wuq zPQ+mG{5%)jY%+Un(&43H358{PGbxH+I1cK9MIKL}4F2L#xUj8#1QXeAXzYp<7YUQ= z-LRy1i*}HQDOL?BQ4%z?BVq?xxobV^qN%ll#p@1H8!_fPogQGfhJN#%pl!AV0fXAnpu4Dllq zz6F0Xo9{=(xKFX{r|m_O`}v!4%Ty~V#0ndn{9CEE9JuZXYR!XYKr9`b16XUoQC8#g zriu~#4@qQuC0?;>%5g=;;8|WVI7V5tZwR2EeacBi$x5h3uys~A%>3`zu>8_}q~Esr z9tQSH5AROPQ7k&MV!lQemicOI!Q=9A*TWJEFMkzW5v{8wK(M=pb_gn{AI6u)@Lis| zKAC<*Lztr{vb8beEp-^BGC&s^QRT<;Hbus|1Jl}UoIvqh8;ZUq)g34DG zRT+VG&?f+MhqR9Z`iA2IHl4Dm_VT7fZ;pP^e9-;G_%DMOaQ12>S*>d;p*?fm?ewLg zbhMw=7U>PKY@!2gf5ZL|{-7SAVa_H5z=S`T`c0Pln2m)o^K`gS;v*ey?mvtY^d|9} zpC}vEk7I8a2U;vqGyLtqZ9KONnEH)Uy`yW_&nSYsG=Av4Tm6t;@CGduMw~m;*1Vs3 z^bS5((?8n;6bmfxSCue=a8Pq6kA>n{Z$TZOwAO?kjno0*|s701yMr)%HI~Gg~$albFdVltN%uiFh>58Ofk$v zWUOra`5iI_%%U#Bm@{=^R!w;)f%a=9DRtU>O8yjkw-kMa5#4!Ow7>F#ZEJYto z{HKStRD|F54^7ZUVMHrSM)h#TghSnlsd%q&3U%d;z3|!FyqZp8vcYe~(6Th_srk)V zS;wwjbQ4sEn55Yz0{4jor|dciTYkn;-CO#+91QcbXyfc%?wi=*f=;kGzmjQ zGSAAKL+_-ULFO4hQ~Z8rl ze-=H^8Z;!XCtBG}^Ve!sBtfzG5kg;2xQ6Llcb+Qtk|45SC~AT6uQ?J8IMa$B#*JW0 z606#D+|_zBuEJik4~vO7fi-~%t&^~jMX_dOL~Dm31iiK#eYLph_!Me zn&?n2qg|H-+(J^s^9mJ!!4+aFxX=pbF)|KO!o)LyL}3lfoQgu~1+!4Wc5$N7!!d_; zlLF9rzlBsLz=5bJ?62CssjV3Ix=H@JwnR(VYRA80FSY(czU9aJEFp z%!V}(H(*OaG@I^(2%(U|OjjlIgIyLZtR@8mcp3NQv??CxBk$W*u^HO@B9S@B=d-BX z_(E{A=d9NY*l8Ux=}0giWg(DxtG3AgIlhvjHN?t!rWwp&7HzR+i^H6VKFd#vj98N( zp$7AE$&|OuA$Z&s42+FyP8L66A73F^woEf$CfrmgYU=F2j3qE_AP6F56Q3f*O=JQ0 z@2#8jBXh;LNf5nn=U3gNz1^@>Y+&F?2x|9d8#~lHJPyuHXc+Y!o0VF-3qY>c<&kZ{ z(#yDmK=3Y1(E1W9_3DCK{I_HMm>dQx&4GCFUv6q{KwA$L?wIFT-CS2p&c$PoQhE?m znATx6LS)9emJ(#)aA>BE49pZHAbY7av1?d1>Y{%)ESwU1vEYuE(Nn5&maZ+Li4qOf zFvBpg5UCYA+M)~-YQsB82olYNQ+G!jR;uyagiDw+1`VrJf(RsJ+fT@{;063mARsC2QuaGIUk^}3JGTtN;h3~GiO zCpnd=+X*UPz5yZMv>hQa&wZ=D`uIFC&W>tFUV-NN;lu72No2z9j3qKFt(t{N!;!(2U7A0*I@9;ASgnRL4wfC5u=)tfbm9Cce;W zuW=5fw);AqIaTcqoHUXk2`RQt>}tbeeQ3~SthAj|DWw~`dPI0hfT|aC@2gJbWdE3H%2E=r>i^) zYA`Z->4i{%o5dc!n2)s;GJXzm;t%SXV(=zbCt0S06pdC(V(Z$QD%3@po}+x$Akq#T z#M5faKq~D-gIoQrnF2SWsVbI1=bmd-I7M%qWk@ImQtIkWB899R%=Rz+13^B>`G5PM zVa7T$upT~1WuI>WyuAQ5iiEv!~J{?mk^L(0{iVCLO zHpuNE0%K9b-VIUNsGRx35o>zMwzsN#a8%kt)ZtTZ?l9wDyM^cO$uUim>@ynJnNa)M;OZVB5Ra5dkU8R%?oJA?g3p;Vpf ztK?WTmd|ea_k5m8>4gn|z04W( z&xuw&Y)QByp<8%A#~_o(%;Fi5iezS$5NrQ+v_d?IARrv+0 zhhW^fU*-%%{*Yu;_}(wMgp2q|zl-r$X0iN^ef5V#$m<)O>LFj0KHzsT8brdt;4 zzP?2}?hqpT<}SPA;08?*P)6A_7$w|hV?QKzIT@uSS5Il&o7Ef5M6hFIqv~J8M_FPw zRF=dm*Kl3Lnf2%z_~TMR1%d5b@w05G1Sq1_RPvl?KFzoh zbsgKVVji2jawN$g@uZ>soFUcUPohjr^WC$zsB_b2J6mmjY)#kHPNr9P#I~HyiL`y=mKDG-rWKLV$EaS5`JDaJ9~k`g-)_GG zc)r>nCwi(QKW_gu(mlUj^|pSg`gqFBp_HOM9Lk9_iC6P4Fou76%%fg|GU_;MuY{>hEY6s>sskx~gt-|8`9=n@S1 zEO-=@hk^LIJOU{Q==oDb(7n>;kE?nW3_sFci~&>(~opJ zb>VaCG5Q;I0gf`1cc({LT%o$4_4!6(X1O{O=y#Te9KZluAPG#}36 z^)}g87Ye%PYO~9M3^Ewwc`8@zEyh8Uu*jgt1HzBb(6Ms;do+H)+KhdpLRpGaDOuz z8bs;VDI<1+bOkAn62Vjqviz9H}!Mk0B zuuvAp5R#n_^yCJ-EIJFgV(FA74kN}|vjqaW~?XgB)=tjy7vSkX5E$O#dFoO><-4fcLwX8c{hu4fAqFF+Q%Z($VO zZB}nDeFEy>Gv3B`7cTB+Y7BV(ebK&Ri8U86(6LuEw&yHYE8WYJ6%be^Z<94or5wJ& zHC0pRbyJ(ZYRLT~KKvcL{@Vuro!iU?s2}epsT`s6(O)n7o5|}40y(w=0_xW*hTK~Q zEYFr$vYSSS6$FPc8m*_JYL1TD%LY9y=O}k-rcmVu{M=>cSh)t^A0N@i0tK4Ypa1F& z6)K7NceC5H!tky#5T0K1Vs#svysnH5ukl>v9`{a_tM)W&DU6Q@F?EXLSN2T|a7Hu? zY(1=h9}OHK`XvT4IV=@yQ`{9rWF{b__xrB;cFbU#fn2=;#!Xn*yYJZ99WOJslCX} zC7|@!EiiB*S(uyzoZ$f^8$HiOV58sv0{(tD~j!xmQs#tzTamc&K>CzA5UXB zb9BzsO)hJn-{sH#di?9&$${Vut)t(l*|1u%y_D$5n`Z7~y8~4njD@%1Q@`|O!9+TI z5cQHEWtQW|?g`W7mW%6)xaEX+_He{MEhxl1<>PCyQtD&Lil$}MAEE6-Le+)YGhrr9 zid$XIzDnwsN*FDti5bKaRt%PwLf=LI*U*I zLvFv*pqHdl2Ezd5Fux2%+IXB@-%{=gx|On4mE>VyVShWom;+7VyZI9wowWv!u9N`T z{T&S7BW71}A)|Y!93LsUuH@?3dLcA`4-~@p&TJ30f_JS4`l7dC&fB4T43h8D-`|x| zE_>e`zP0B5EIPW`H~O2K>3=50Pn;_jJ>xudxAP8F_H1qbve}=C*qdr&;on6FtZEur z9jZ+6KsvcBHdj}Z=grajEbXU1K#kfEL488S*xhVbYjcT7g))%G*TH5zpj&jEGj1eO zfHB&`;U%F7YF}%U-JpNXSjj@bNH8tCY@sx4?pft(pXOvXAt+d!y_n;cg|eS+ zWgy&^GDje&pgf)8T(|BnAMzWku^qZr#~%4P7Wwfn)4%+#oY&?i+{&ARcLdC~| zm4%_$)pN=BCYtjS^kKC``jFORby*wwWx zpLuLYzzAIlXkWzXQX63o6b=PuIm)_ zeRpbk6G7kyv=QR>`~(D!&Fmb|pxgm|)x6!8JR}a}e!`x7q9dMsb}R1c6h!)0gzs?} z4RYMfUg1*c^VHouaMrt#4Tq>R(dz(xTBn+*Ij9Nu2YJ$9ed**o4K7y<5VfQ_f${Jw z#4q$%bY=1iSJ5t3bG15d{!{gP`WI$ReKbO>G1;htplt-q({niNmNek$;LPJsc ziE5DD6X$^Y@;cgOH2Z1FSj(Tn-4kqd4b94|%e)qS=ENT=KZPAY@%K_Q(Q={1a%tM4 z9U?1lr&&}YOcqOii<>JRX2A(XH{OTqXw!IsNlBCEvjMNBz`^=LRh zf(hA~#%mbuMDJ5vpRHV&#vC0+hGc%olNNy2`;~%2E1LqTSMqr~+l!;rxT&iAgcv;3 zLF*0U)=_V14L716izJ-pqN*bwC6uKiU4YO6@f!t%B)>Sp_1aj8T+N5i?7cY(?>nS+ z>^OCuZgfFD*6ShNSOcUQ{?yRxv%V#Py=4D+&jX?{=r7R(q#CA!CmB{xxxD9e$Y2Lp zOn)2ezRHjXHY+Hs!L1x+N1+{_QgmL~p)+N;9D?UWrWcEJaYtD@k8=muq1G9dc$`Ek z(|VARm;mS~KL=jsW%LMff-S457N;CL3AmOvDZxxS2iAo`){uFT#$SFemVkLgFz=u%C(vRr zyeWZTVX*mdBfi|dVd7Y!M$~jNkP>Wj6I4Mw@^-|7l#>Q>xXBqBdXeEKui@E8SZQPL zx=ma_P`i6mTbOV`W+$|(zI3c3K!uRR_UWJ3m5=dW#fc44!u@C2;l?eS_E$4kf!R5} z^yBwM5oeH=UuN=IY-(MaRw}AlH!*BJlhoAkW9uOF()&By+72Y)c34xl`uvNF)HY)v zEg|N5j^3e{=7CY@P?Nr}Px951i&IbdLSXn@_XPXv-(L%Ma)jmxQcuI++pELbYDO)3 zPBQE($GjJrJ{GwSdy*cWT5KEz#lm%+HVVoqx83e0eGX{AZsw*}9+b*0g8VO?@aoTn zh{`SZdoiFtJUtEP!xFSGH^=vqvn1Ilcdu9Gg$t*GOjk+Q36Yd|kTj?^8}J`=SPu6yo0>-gxe7gbnIcZ7c}7t*8d z8V9IfvOn{YLYH6YMHtrFF`IA5aU%?XjPIF?*tPfWU)QcgULD(eHCtA72)Pfr!9A~? z&c0h-9|KPRJ8&T$|Nm>rRN{_!bNqAk^$`OD`&YsG|5pj*|7BhM&pe!&lZln%f4Ns# zCoUW8DZM>!wT4s%MKPrNq~KSc)?VZ_H);u;ENg2?Q81Kl#Y>w5C8jo?uTa}29BZOy z<25QeZ0giavfrM;QLg&`yzeAWShutR4mO(}BD`;cpKdNoQI4>-#v-0>N|+vNFP5Bl z`+yDYI?WRk+xL!E`*H*SeWRx9Trnq;@jZ zb^;G#G!7AJnU-uf*&Fvt*tV)3Zp9v)*F0C;dEHj9-cHSx(ue;o)vRP3dSB4lJWnWG z33v&lhgjXV6PPl!27B?)Oc~pCZX^IX&S+G;#8zZAh|jzox1M?@Qd%}U@m}jaY%-cZ zhW0aC-a_S{Voi@752i2`Kdsvvx`5;BH9ZFAckLS3SLSx7nJ@KreJKy0uZMd_D7o~V z|1vhV3G`7kTiE)Kr`9>J zdOX#I*D|A?PF~tmeL3%huS`7MZ^%FI*?I5y{kY$rcX|09&H--O%q^b~4HZHl^`FUB zcWZzclg}af8PKDxzL$o(_62LQxx39(2+RVP!BR3Ql_iDd6+U@ zZZY4q=ht7|-rtzNxjVgdZnswe!lMBMMGKN&(mxs`GnxJx*_xApLlgbFm-O>l}$ML^k>?V`|jE_2k6izo1;XlYe4sOal zQ**xFU-X_^XQVt)`E_0E&GVpmitV*+z%tP^4l4zJ3W=A?;b!h`(+E= z12OP~UEaHEx#OBL^|ky4DR%?S=_?;^mq$B0c{FyL3TyP4(_-35ZUZfpL|CBDZu%61 zu9dC6s(*EiSn|+|6*|=U50_h}<#>`lr(RE2J{XasAbx$*dJC28SJPYc8XC|0n~_$r zV>V4gEhd<5m~j}2o7~7fYgQ~b6gj^r=H}RB=fyT-5w#4kvwIuLCfG81I_F*9+}&+a zeG=(T*lF{5y-vEuxc+->dR=r)ITN{)k!VYFH9sd6f0{*FvZDfuz zJ1M<%fjZw>A?@Kd=6C;3fc8H%Ob5L6n?2M9eA^F4njQ7tYW)8j1pHH-+o1o$@GkDo ze+bk0XGds{w1L~k=4i9C+*?iXe}lk(Dxp(cY-qUNw7N!#m+`3hQ&dSL@>)hjD_r5K zn(MUt=)$ZO^nnmo40wlz38FIYd6$yUq7Fme!M zxG48A4Yr-@GU+ugToVd2ff!*G>$``5pbT|jKZXnYG@K{H*UBf4Yeqnq&$9797i?qc zDf>aGvuNkaa7=9_z(ZJ+w7_ao1jo5@tbdKf?IKI7$J1X_8LFMOl?O4>8-pBayYZ$v zU6jk?fG#*EB(+!rm{sNw6XF{*3_IZf`CVy0%*H0Kvv&2R5AA58IAbaj5|}s`$mjat z(Ml};jxgYl#!!L68!R<~B`6n=L?oasSxD>6J6uG@a)TB@Ssp4P2qq_x66Csz@`Kjt zy(v&ju_W-Wv*(BzY-%B-GnzHXpEAacXmMD0?rJfJO;D5l$u!mb7u`1;NZbF%ICt(B z?5jH>@Y67E5Ql+Y&@ta=aR-JGskay=v~K>!jS@WzN_JqCTAvC$s#Byv$OMoSs_FB*aWP(z;v2F~%W}kyeRr1Ct1&pjbM1^}_ zW20T5z70UzQLTQ)Z#qP!jf5u2@Efb;8T$`+7cy98AR&4SQSas{Khx^-qI+@sH z+!JxUygRNR-cXWYpFX^C>zICdh{m4s!?fGEES?l%0ewi?$hx%otb+f|rV4x>kEwhm z@wF+S)flp{5xRF?g$k=}9k$Hc-@0hxiyoDDImsSxhCz9Hp#;F(*40 zUgT7e{xe!49W$cExM?35BmdD%I-t;P9Bn+}SB4hcuMIRcj%40FrxfThLeHwk07sc| zR1r_Tb|*i z?WXe`7k~HBvFEFI7X7uz0{^MtQuv)XCD*g$+4h)kQ;_KqV_ z-H{SKPOgaseZ?bb&>9?02p@uPHi%UrC&b@48z|0Hl&Ct9phZfYvz`tvAHl-?=sMhf z164Ymm4LAL(N3T+Sv9KB%UuhTeU>AcrDV7Rp=^kAbGdX1LokcS2mEGCc8MMJqe*q& z5{Z>-c;j~-?sVPV?M~JTS34JVM50*ondy~tYwq^xm7yY)fm=+l>aSs6{F1t7HF{hA z`7|uAOj`1o93tu{amaAM_uLAru#7I#@a=bgk+f-IGMBRK-1!VHdC8f~?&@*1TC?hX zP2S6NVz2l5T++^c@$-~Mz0^L1mF!N+S75;5_#30zbwXk;6asLC{D9b2z zyWxjz{l$`*zw=^zd|Md;qOoCw}Yv*_PCtk~+5EEF?4R}ghoP7b{|f64sZrSTdIQzjJwUpa^5)c}y0OreH7`SlQ_lKY z*|1LMNThl=TzSuL=h;n|&cX5Jo3H=%6JYNS*ryNv{@m&Wn%>f2fgV0EIt-6Q1{*d) zaq(IU>2W%R5T;TM&S6{`euB?M3QO%@kaTIz5HSP_umkZQ69_viA8ifwascu&iN`>3 zcwvm3*-3U;+DiW;i^Q;gQ3y{Vd3g@jNx+jl0#Yx3JULJ{KVxOyeSXOpD*Qbs&K7!s zQwFts!wi;X&~W(k10M#@6r;;se(Rgvk1!u5GP(CC{7PkW>UgbsSn*uc7s zaIiemf;RLAZ>L|7evKY{-Y<9QfUi|Kc*f{5bc*PS#6oO;0@3XqO#PQe9@l)6=YZVbR?|1-GX%H@6Sp*l%be=S)-0rBszzC>zm~p zWWN^8ovq4050Jh@QEeAwB*iEYJ=S~j8|klY&|GJy0Ah?X-VDo!ObACPUeYp;k;K$A zk(3JlM?l|Jz5D`0qY&5644?EpcYB7k{Jl5|bT2o=y?Hx+&0T=nhZVDT1bin}{idAe z@i)m0_T9=#>4JKUrC(YSni5KkvJq9z`HezgsWiTt_lswM#4q@m7P_aZvOxNhRk4$O zcPU?eyOi%`8!TcTPm>txpWPUU*`U;LK3TS?!X^TQ2D1@Ay*N7{C2}lv;#Ny%3m)21 zuzEP@ly+o#v2Ao8p(u2T(FeSAUM35eNHi!#QM>i z8lH1d1o%5f?WAt$QVq!fyc0V+aW#hk|3>!<~#9<2(vyU5_S*!l1f3cIKZNanD>zjRg^Ku}+c2C{qz}>R*rL1>YIS?0(#qr{y99LeHa-bZDW^OA=>iIDt zb%Y&BR`RW{Q)<{q-Qz{_yj)AIo1BQ=u3wtpxwQZ2cFdW&3w_dvdfj$&?(DHX)`QFQ z$2pNQq8_%`B3!5dteN?;YU{{}_c({O{?)JB9DP#}zU5W!vUd~rJ6X{5^$`n)rn_@s zdF702wdCh~M)bJ^D`S5SG!Lk1c;L7oE;o95t5NX>throbW@tJ+g*Fq}vJ~90sXGhA6t?qgJ zrhDMk`&Lwp!+t*+-u%A9l=;ik@ei){C++CfZ}S%Y_-|cd`_fTRbie=re*b)iIR9JN zqHk+x{4a2+O|iD$ltKvW`%5i0i(U~ifIuhfT_vn2k?167pdwnOaJb~K7fQ+4&33V(LiK!LirO2QQWL8G8* zXi}q*7y(ek1Rs;o5vJlWq9Qt=tf?Y392t1p0@_o@&T;4J?W<83)fl90#ukC?Q4gzJ z(mdb~Ppw|{CVUhl@+D$Hk_apqByNgV1e8pGp^^!Px$>_)lvT$#E~+$zM4mDe8_}Q+5o5^eMmPRp{29nG-0S;Bk{b5p;ocT4T3XLU|vebhJWdt3;sp-t*SK zQ~#5p!#iLu`NYZTjK7;$-wH<4F*sg|h+0W-aJWLlj*5sVaI5;{30YKzZ`S_w2UsE0 z%=!1$sx@fi8;PtTgXa6)#g|!g(5OdeyS`4Rz)zE+e5PCP>4mQab~rm|6%P3HA6JKaMOQ?<9AVV?f#J5uRv zaQM%45UV+R+s-UbTJ`~53Zo>Ow#~kFE*_i^U%q`_zQbU|=$zu7=cF)j|Il!7@<^|v zgZZK8+jj1KJ(M0^!Q&i!W(OBW4EG0>!tS42dWC1}iXF*{ zKf4eM(L8K8am08p@&c$bnBPd$#Roikn_PKarh>n|>&J2=rGK@7%`Y<#RzR3v`+tlO zBsx|(IJ!mJ{7K3qUzz4OfSSzhaNn_5&H3}dyVDlv!|;FESN1j4`2SmZFes{PR0{Dw zjg%<>z&~y1{}*a9voJFJC)4z?cE%BRx^e%Bg1kkaC#&@0x0pKcszcSYMkP9FHL4fl z#(}-SA$y*z5s-jdV&@8sGJ`zDDUc|`g=T)th(|YK5}_|e*AROJhF1~ zkbJ)nA8lXv`*wClk0zf-)7!zNe>69xZeAYkXyfYlefoHP^8PqeoxF1RcIo2k>E!0s z=;7<+(aX~}I&yM7^ZY(BrJf!g{Af`a(juRt;Pv3+`{U%>^A>+{vi1CNbC^$0mwsk( zcJraX=kM+1_VcKZ_XQZ+?cwUw=;18K{$UwdKX!KYe0X^M_LzS1{;_fRac$M8l}9(b zCQgo?enuXyo_;klabo1@^)#~aen=j3mhllJtX?jD^>^f?`uWhw_h)J3@Y>_+VXVGS z`^(X{lTW{QlmK4en8BY*vsRvdHJbERMx0KsHXbc}8Af-$5`2!GSL2DfrK_ZnU$0v? zUw?l}Xs@Nm^88{qQ3jV!D<>!JuYPWi;GG^VePv%?PPbnVhYo!m-RhWnJ$^B0PS@L% zI{nYh++L4HNqwLG7pd>3?Z?N+>u-qj0mfISU)O1xoS)a<4-nYm__L{{DeET)*e~(V zwlC~DiT7%L-{-?mP@3gFuLoD}AD@NauNxxTUdg@Qw<@34@~4}_?7QCY)492=CD1R? zT&<%I`1MN<4Qwz@Eqyw9J^lNPzHo24`Q|n8Xmtnb?G~;4ZqdH9fX(cy_)h>^8vXp- zUL4}#j%3J%#`-w)a`W_fboBFm{=Uv`pTF19^nULr>E-nD^$-I3-KT7Hy>IE|@OU`+ z`QI%ZT?A7(xxlatQwtmNK5v&#$B$0Fj-G#ypnd42w{!jdoWCEh4<9LgEr>^EG*7zh zf17!K_bE{*pWHjR{JVbkGwo;he%_WIzYbR(pT+08xO&zyhZ6XIUOzs9%XM?QdA|1{ z)$Lch;MoDUl~a7rQ{2}sFK=p@>*xZLvh(5V4gEypdOvg2uiVA2R>1xB^ZoYSt#o6t zc|Gn8p1nLB8NXzDP#*V3PvY}K!O^eQ#Ji@BGXC4|CT` zGnGeMeOuHZs;2 zjH$}n90vOJL}un%bK<@EFy2;->OP(aV~nkEkWU{@zC)JTw#Wl?QT@!_+W?@Dgg%R7kRM-_}LsDkKN?wmXanN0hXsybeQS-xc zhwoTpmbh;3z#P4BEvV8aX9A4oT8HKo?WIBv;_R7aY{5X6U=rly0tv-_Zn7-sIVD@q z*mQTu2u=)df4hwF@I9Yv=LSYD0cmIZF8pgR?~n&zHR4Z~;j}2(1jbp1Pk-I}ty*cu zNBeDe-Op6x=kf4gfFf^(`M4TNE++RES^Q2zq6+Cvkf)ILYYtKK?(Ow?8)7T}UNipq z!c&*0Ix%?maO@J7=#oz?TRTgiTjSR!?5N+G`$yk^E0ggTwBhld;yg~M`}K7^dRM)*tfroURoTrQFW}xSgB`yarwjOY$K(C zV|)4(tlXG90gl<~_K=#{cXzJ9q(bp4O$2*)>)=x~vIa0tVr%VP4%Z^{U6$xyRp{zQ zWU1~{!kqZJ6tc;OKbLA1w2PK{#2MWs(QQ*BtE-TIdEwiQuvMP~hx3V|D%!YEgvW&N#XIqRMrJYT&Ysc&t6{$z#{If|>I%KYpSVO6*FL9=-^II($h{ z7hl7zhff_)0_WHn42lL_n=z0`&en13UfOY)SC!Kfsm&nOp zwfm3hXf}PTG8dS*g@Pi)O@PROFT}1Dy34&7G={JhX;G6J_E|8p8i;vpvKod@s~p!^ z3L=A+0vm{rlDA&gc@V3RCL-wmVF((Vhi&PS9`=cgf|NIAZsY*4B3l76-af05IKkH! zr*~Cx^kOCg@JFx(1Y9~8GV$On1hEgxruYpFmpv_3f+A&Qk@%hr0-?kS310~D^v#pY z`$=34-784?*Dt zKy@He2*M}E@qizUJ7Wk8A^{Sf!kBw#?1SUlJU@Z+orbD$NIz)GmF1K~PE5dk)op-~ zOXy5JvW1}3QBEuaYrJ*Kw|okm9$NsSMIa?bq}73}sxqj6H!;ABeY14mTh&EbFGngO zhLmf`Fs&KzWXNvYPCDLHB$DZFz&2=wE9!z+#|@mxoe79P5i{<|2P_NNTo52Z zGttCRVjkEGZ2hE7ajN4Qh+_otWr=ANb-^Z70+5YwQ3N? zJ&%SxdSOQ^POc@9i~@2HU&5quCscLn5y7e;Qz8OtoQ+W*4xuDy>Tad2Pu7^NjMf*= zPPIF7OdyOC!mwSU?mpdc)gzFEr< z&6+HA0xPRF0%}Vtu<|ldy_H?=)J=_vwLskqC0CfLm4ma z$~RJZKK4D3tf))*H|vu9ez>XlLZVR0dLKaWz1tk*FIn=L@#*E&#|)9DlD&9R*?Q5L zH>&1N{V~Gra_YtSJ>#N%I~y6~gJw^YJWt2&an!xO@V0nPCYF4@I(UDbp;4#gg+V{R z^ZxaXzP$R?_5q`Y=Z8UQmO-FS#{#=r7O2C?Fa@t*7FWH!`J$kl4h6dD!8iI*7nwiI zQq$t#=5EB#oq1|8$F|HJ_QWxJa^;yzu~!Qi(^y8oxXN07aw4%@G`0zz3fAN zY<#sDe%>#F&^yQ86q}fIattEH`@@m^)$Ik z=AEqu&cU6enPUQeJwVqJIV>g53x_wg8k^yn>Nzx(7mHBIW=3bH#)`>g5H{Y(YBaYY z;om5?y@+MgXPn4x>>j%}tF)EYZP*=m7TK8i%s%OTsKkKp78|-kyns{D@h75sJVtDT zub;gyqC2zDJ9laprBOra#u|hf(mQQrR<~)H+#d8DTs+m%b36$(ec|5oMZ1d;YYW44 zr#zC;1Kc%8ZJmLMy?&UZ(d*5)(}_H>$6>^2(7Qe&E8 ztoqA>wwi3@rhiK~hIQ2#3$=NOdBe`O9Ut|1qk!NEMb`+*RyMy}RyX;s#ps^*isInJ zm~2>^Ez~-VHj8n8{XtTOVEp&-G4se5>hIGe;e&j^gdFU?TB1vsHYLpB6bF?9SEl~+E6g)u}@(_W#keIecmIkr#v|M zqyqcPUZ0Z394+RfSQh;*y|B~Z5mJz%5?jw+wi+iv2YU<&7_WD1dfd{JJ2dh zWGMEL<7=%l)UH<3Q1t6}h;**(9lqxA4G@0fNM%suTQwp}o&AZf1&4fI3K@nIvYr|F^`p=0&>_*P$g*H^`)6<;Shna_}aMWuW)W52Y@*Jlm)32tU zt%;D#B&=mta{FeFQn=!5+DIQ@GhKM7$+a4M49E^V4co?mOnYzr5eMr>CWDDHgW-Sp z9r8bIx_P+r*4H7@X4y;c^;ovOmtcniRq4&Qn@a652yVo@Z@{!Nb1Xfsv@(H0 zE`pd_a!(Xz)3ZH6cF|DD1>UoD^>9$STCU0ilC=LyWoH)DS|8QSTf;SItt5P z=lB69>!#av#I^3f)S=*U4=v4JGLlL5wdyw5&HQuiBC)xbK)H3DZQ#ol$;4a_ENHa@ z!{s+kMVwgyeTG?OGH`;T)KQR$@&dv|7n`fP2vWbCf2UyFKiEt_x>M%2z9kG?hSreDb=Cx+O zYRa53e>Y-nUoRN3?5JRNEet`a$z_~uQ1K12GEMRD5<7OBh}WTKpm9DwY+LqOcWtfG zNz46g4VSc;k^R-NXbiPin(`L0msXbRHsX_?8q?Hyn4NELoIWIzWU~A8F|WvK7?6xG zC)c1$qdm=D%hUPWRPI5S^+%XV&|V{oI#hGe8HYPYFYdXvajf=(ckVb~bF+H%E9J_> zc~@Z^(S8xvX zaVphG-U9nOx$#q?iX82?Z{wO*3V+kbrP*;Rb8q05(dBRz^4Gd+?6&>qs(Gqj4jvBw zhd(769{slg!sVs5)@<+l%->gkR*pR${>F}@cw|^#j_#bX(CumJ@$1~k@t+HO>I0-) zKTjFEy1DspF|voZ?4JE*p?LwCy}a7hgU;e(S?!hv#?)AIt_{8z&<&(lQO#C#X!RI0 z&=BC<_w`2bzKCR#Wops`n>DfwglKqP)BWu5qR&;~DVk3?uW^%jYHDoNX-TNX&q49G z-~b~Ro*E&$BL9AV@QU*N*yY7@I2{&z`Ml_z{Ag%#;LbeeX_jq%XLv`OaQQVW`b0AC zDHC1Ut#K;vlwGz9rPUxqfB&Yxel*F4;exvjtUi+z859T{nx^zV@^ z)a&WxcLOv>kv+HKPeCf5_xyxmu5E^BjxNURXXo-t$i3T)f4+_;)&-lY1CpaxCX={i zsEyuTQC5tC&_gR7TwvTB!=a!+;B}CLFG_94TXc$Fptm95h z+GZ)3E+`fKB{id;7eT|q6ICHi1}R$3C_~n=;~HJhnFG6hA=K7&Gd(EQzkovun7kw? zs%L|w^>!jWvvC3RD4FbJJ6hl+kFIE=L@u@~0;z}gSTXg5|9hg-O}|s2W#U`~r7h8Si6&A!v$w$>3hB4`De}tFyu5OuvEa9isA@|D`uCIV<7lTcS4W^ zhIApVgQ#}7b{L^1g7xI5V_H2r@WSXyZir3kxhfZ4tR)P)v@tx>Eeb-I5wb1tT=0iuR1Liu`zQ!a9KxX+q@Tv z1P0$}ri>({+ae-QMw>}l8N#17=owNYL9^I&ULd|lRf7?D+qJyGqh$uHkLYekGVxuR zLP?$877DLmd@;R-8G=Y8mxxIRC`A{tv1&Iux|3KA+epQ8GasFD_!Klxc6J<2i^LYu zr;;}%jeyf-#=vk*$+C((gY?`0A$`H>#s)mc*Z#MhVsQniggV_&Cxd3=*fn-ULxju0 zKAVkGbCO9>)Pb)hJM_X7V*5J0>-do@;|<5|_0E_0s~!+bCzuvJ2@$ z;w|b?Qzu?&^@enPf)tOVZ#dgk?+F-)nsRDop}-Vj4>YpD0*Un+P|=@TR()!PaXDxU z_1m+oiy6`4Ah~Wd-Fs+d;U4y%WZ&)G?Z>|CHB0M zZ#73oAxoUS0~X})d5-RdnPAR1jzn)KSByB}x4J&TTToH# za0RlK;_B!|Q`&NhjiFq<$pe?wN|J+w2N zEdgaBIDT$JK^z@|R3|L$W>?I(7oG`f%tE18BBAc!S(GE}Z&{IJx{5KXCJ}3x%6TFc zeO4&mQ2)T5fEaEa8Mar%QK$G(}cPO#*GZ(VDJ;6SAtT& z@I!-;l~R!b5QQQC(IVep%@8^#H&~0UVl=A_B=#tX1=+bT2tXx735z@_^9ni=5|uPm zUwSXRqdvZ&fE(aps0Y2ko&*Ie(g}NDkW$DhqDUo0aVjlK)lM+aw`XNjNz4_pFi)~JVsepXD%QDJ&voU@%VFFvuv(`H%jUD0?3ZX}U;E<#%X^x_nV z^H$onM3&om+9ZuZli)YdEXDC`9zgOBh zrxaHRyH&bYvJ+T2kwh*MhiL1mh-h64ggQ)nk8Z3U`Md8+>)D=rbImwUK;4eoq|{> z)tbs9yoQohiY?z(OsmzY2mv;*Bc>)X5s0QZhzdO8Lh^PXgLMfqa0$0@LIVEdximeg zW(cxzuWAMD!Wi{^cND#+8stfJ2iVJ%$-jVj-KaU?Wv&G?VT?rB93aBr!wDeT4HV-G z1R(^u$C6@u-3^{9P8Wna2g@`vNIIB(~>;{SjQ58VHC`Woioh zq(V;u5m{*p6%ttrj}3;4gG1pPEYyTciVBClpcFtcXSz=Y_ z1)-S$fELjE=rD*m7P6Y+^1&AXZc(K~?7|u>DVF`-B^kj;yS@iw_3r#{>V&ZG5|v~` ziqI(f9TFs_BvJsFq=o@NP#83mqUcfs9wjnp9ncVoPdOC4i&llPNJfwVfoy;d)dJTX z*ool0*GiNK(Kl))Dldg#B>E|TUmf}u15amQK|q5*A$dmga0CE1DnXo2bP=}`({4LmaV3D`+i5F7Af;#_J;2IstO6`$WDI9wv}&SDm%AWxJidamuT zwgi&!;A)<5ED{5y@H3#6$q_OsKv_k**hv?b!G?*wtWpbA_WEb4f*sB!NALo*uecbp zXgC2-Vxn}Q>;u)$vJ2OM0-SqFfS?=FMuJu^Z&!!oOWi-S7rlGMycj zX}vypXh-flkI-?SqtTL?Hor>b&RJgp)+XX~1~m zSduHQkQ+H9IHF#{M8)a_8-kEltI(SG26PL@g?FYnxff%f<91Q-A-rAQIz zrlcqd<^Z3GA%vp?I}@SQqNn23*K79Mj-W%b?gYWyi-pxqN>oD#_`|<0rQ%J~L^JF= zsD?~h>;)3RFj1(qTlP1f zC`kdlQ$yB*$p$MFI-wxp{n^z1f{x{Zbzi_Gq$2ohNDk8{7QcM0G>=3|OMx4+9SY2* zu=G5Cq3O)gCM11=6&Mi5sG6lIDv$<-LJT|)LIUM6PfXAay&4Q^9G6TdBLD5uB zyP)_{-f9g!Gt~A7Zubox^3n@;7CJo%x?k&p@bUI)HyT~Bq4EU)jAZvl-4Vc9I zdL1~w$z%u(sp^6|Up&t;Ni!@{z%DD09xwwlqao1yPQ~jNwhy3~0T4@!!jcv)2U-XS zvp+{;7;yTcwkH>$rl?VsQO*3s6{ALr)QYUX(JTQE12P(i~z)dJ}T5Ak(>u zfl8qnJb=THz?C6e$RW^y3Dk2YWSUS1P+%(b1=5p^%D3}Jt*}H}9l~RU-O}$N9I5t{h8EkxU)?7Qq&a9wF)t7Z zwTYI6yTXl-w*P?EB!E^yfCsvaHEvU3b{sBXL*>5)`O-{2p2gks?;k118`n0ds|-b5 z>~#zlh6>Te4PI4{fNd1qisS9mA#Q2UR-X*yjgDe1Q72Rtlh+tZh)a28H2{)Q%i$HN zi@U&!0Ks4-sN)8qJM~QN$lQoR(rSNU3C?X4M18_B2IMeylR__{;*j%N=a@dSj2BH)$E5-~Y(NKX%y^1`5n!@2E1w6pemP%Ad%ppaH z@2PSXak#*susFzyPNm8OFjRw;&^v&VK#$1PuYp(Ju~aE36-wv4?S0mSfOTYK+M5JWAk{B#KSD-{Pgp?IMk5DEz;|>CJP1f;Zg+Ga?Nwox;#W1xLJ24Mw zAkaPNkllMY2TVYFUEVN!LJ2?2c5q$M1hN3GfXZO5g2kDW8A0=u!m#5J_*_JYB=*RQ z5zYH>T6n> zPR-xr=aNEAk`m!>tB6xQtTz#kgf~vYlknpLp4ajg1eb6-R0^5gERcfH0gM$HQV#~K zAUTs2ZI(rRnB$VndUEQ?&a6Vx-U%R=_!hAiStL|nyvR>?bwEf+aX~MyNP+=U$U*>* zYn!*wVFz3)AOZL#VQgWCv2j$2lIbQQh)q2J+H8Y?{K&v=Wd+Bp{$%y7 z3MZV$hQh}4fUaRwq{OCWF>eXAMGhTvWE|qmX^4#`7bE7dsHvb%u|*?2VytGNuZ3j_ zA!u1)!gQnYS!g#wVpVsTM^)n+{la2!R|-q3$#*6|mO_QaF~Vkts8+?kL;KxMic4y% zJ=!>2lYM4p)9xs+uV}^6haU};Aqkn=efwZS(@`n za)n;B!L=6hpgnzX{Xu?r5~1hF7Wu0jvW4zyY1_aE?;B(sh#Z1I{jjcStotgYsn89{ zA#I6(yLE)IH`1sV(0TWT#dwtz!iKoDNL2C_#D*4hD1zW-3}ppb4gw&T5`e4=zQvl$ zGTa$hv_L8;5EOZY2`j#*e-t$eso~b4+Dm6e$u?IMn`kM? z?Ix`yiUEj3SogSRk|c(CWvkt)r`LOx&u{D(2{Q_75uzcZ#xqS1F$}o@r%&5PnnAF(dG%*NTkC!5m?cRI|IWe78dmN7ZzYs zh;ODqG{$o&gJmTOmkHmKdtK$0PoQW*+a`Yt1pVUw*G{;e!}oumx&Q!**Z=_kuKy9_ z(RZ`9{}0Tgz9omPiRODo9XAf0$iVq3T~^KtM`X!z0RkvVjTJlJaSf~%6HS*sasS#p zsdKZ>!m#r<1i!{N3;v@$#3({Z$*K3ez3P#<@f+Z z*u?2i#j=SMjp2#!+zUw5G_Q=^16%xo0acJ|Tj1^W^4^Kr%G|@CO6zOtf?99p4%qgF z1rs~}#T>%UT__s#@kHsdd0 zpW$6w`MdD18@uJ-J@Me&+A!aj!9;Jr1!}H(wkRbE=;B8grnCCQ*A)Ic~=oTP1-Mw37BY(&jcDJ>qjX84V{<-?g&#!0R>A2II zUcZLH7uwIX&Bax|&3ZGMGWvX}_&d>zHvRRf%Kh8QM-}YO;n)|B{p1mhXHOsG48C0Z z`#FjTIbcgz8#^`5wi@fel*qDzjajO6MX$g67Kj2A2ff|yh*AoLDN&7-yhx_0Zwn%^ zGB}xJTQNzd+2b~ddYjA0nO)P$z_UW|y(s8(B1i^m`j70pzzV4f>VDi+I97=gL{Y5P z5^^L&p#*}LvLcL$=2Td)BP3P;JkudYwxsqsCM6wkRvP3?cX?#PQU&6yLUWfaZqX?i zPc_#wt=Tlgb!oYBXUZt`thA%(W4v2E?GA%Bb4tzZX1&7h107V@!kxck&NA72_YKS1 z_LaZQYv2^$z4rccYMy(%&~S<)MQcFY;Oq%{UwYe+epKYwB5)mRA$-G~SN-Zq8iDmM z?{eqx{&d>apnC7A^r^TQ+Wd?_({F4P_mxmi|30?r3k9Ql`;LGz+Px$P@z@iDhrI6{ zz6E(Jyy%U;(ZkJ!4>YCJ)&EDyb$FOPCNQZh&H&JPb-_r+`RJBdp}q=)$<9g{3d|lFwrx zgK%DESfvU%>4$`qtf4TV#!I@1|~*~gIHm@MQF!NCl8ocqRdfuF>z=#&IRg=rx@m>`f{RMaAMkRYdX zeVCbww}L|pxlX9XP}+C9b2rir`CZzYji)vGwDrAUKbqG*-P7|pxTH=BP zI3x-+G`cUTSV>VH0jo#9pDVLg669J0&_bK-D2eaYTvKQ%N_vFWC~Dl|bb@x8-lb5I zIOfDumDtdVPRk)MXet*ciB;*q1m*(HP%N0*tF8>36L~2`Q7ydJ1Ob+dj$#q!2pZcU zN+|_IN3KHD@xrYEJ#%u4IyLW%j--l0|L!`q{W;}}KEQcg%3RWoX^ z^k>U>AmbW>56CJ&v^H!Wt+4174RARR%B7T|T*YI}r31NF=MQ!G^>*aVL)`cWc{7J& z)AjRu?$cMA`fmb^?Nq1F8T*Th<1xOc>;6t2`1@zTXZ^m0zn7pI1ZMU)#FmM_Ir}cl z#GSrPk>vj3_XT(2i@%lkJaERDw=A{Eb`5u)6rkh1#aZ^=eMiV z4t}S4ufQ+AcvfpI#EaXiM%Ve@sJZHAz;9RGLjR@IUh)5bXxnwK7&Y?JtyOd=0Du&% z|8+<+GBGgzr~CItW6JKIYk2318tZ5Vp&;!0_TQFUMjEMR1Pvq!s;~o0G5*T2$lu3l z+==aft9+rbBwIG$;BCO*VmRD=3}3}Lr_#p`B8riRHco(%zFl1(YWY=Ms;u@6EVJJW zx3FyXQ?C9uurCh3v%xpGZr@zq9QjCkTRjgExU;1fKP9+#aziHd`5Kw=N?Z8xW;XNj zOAI^V_cISB{+oL{)|#)USjjpWS4q+%ZI+$VpVz_je578JyE(0fQOn0DX@wxBN!#79vXw{iH_#=^bnGELn)qZ%(x5 z>acC`tOkF^IM|n~>zm72at-^JCpN~wpVfAcI|ehK#lt^eN0BZ5pl?>)HUAj&6E(ia zO!_p$AvdJ^dbpo`gU7qApMH;B56#rp&U|vm`v>+gz^GkN;^r>n%Ypggz~Gh}F5e0h z>^$v-B~lW?Q#z$$!xd)Q$vUmFuYw{P#G zTxwbnA|WZGy*AN^kJo9tNpF!7u#@OHo7!AloWx9#@=lz6&bx!#Nd3AFMJ*_@#x0@GMgO?5JzE{)7 z4ea>@VwKq3raWo#9y3!wQ<>22eb+Xxr3&rQs=^SfS#j73V< zSe!o<(L2}Q1kEm@!RM)P^3{5LT;Ct(atnMqJ-|Pt7nefh9C_MWtCVhBJ$o^q#l`S5 ztE+0!dF=h4_%^#bqrFQPHaoo&|0dw^_c9slAb+L4Hj`B7!MXN0BWl!blGoLgW3NeS z^?+i&6iE*vZ&$lgK6%UUs^YRMcaHM_sk)By5Uu<6-!O1I&%;b)`XJ~E;*|M82G0#e1BmHZ0Dz0XS^sBEcNA4tZD{g4*vzra)TaMuFm!cP%<7* zF2N9ksHu{UX}YSK5~?Okkw;DJQqJ2loMi|-&SL4XD7LN?S7OqWGUp$e_EApm;B&p1 zSp*Gzva8>%ch>a{7DkZ_8c-zJrm|s~G?|5El;oZTS+ydE6RBirWNbT_vyf~yCD1b! zHc^Zb=$PAzMlMmwlw_LXA{6K7pRt-DrNx!X$K+5ueK zvz}`f2T-WDrYe)s1(HySDa)bUwy|=PpH^+>HV;wZFxDW7Mg+9Q3A_DFVrZ1PiCkr5 zB8H_4i=R2Qf2hh+WJ54n-w2(HMY|p5)MtdJCQeJ)PaOo&ClVg^afyRhVV`1f0tCxx z4yw5JqbS1+obO@d%O>)mh>(I&vFJNiK<%pRSnp_hwS30V_UL-K-*yJhqVLu=4y%h7 z4zow(qx)!k>!bPczJB)O!=qh~@*57I-R`y9-o_5ggXiFXVjc>o-J|i*^o^02`00Ah z!2SET*P`tkPNZ$LzMLP12GFDX@O>uG{58K?ql6B(g6X+=3pi$%Z%QOx6R1_Cugd3B zC6F3tA|*)aId_F2Q7R2|q=usasvnF<)?=A|UM)yC+ytt|iQryU-$N9AfsIjv7)iKh zMS9nZDAw2|w62jef=$rIu2L!C+><8`hf#hb=X5BgO6VQVFr4vaGZCay3h%1CGz+n=65%2Z{H8#dcq%S{+(gXVQit>ph)|tqm}UW>CIKoJ>EX@`_%~n(>s@=uR`V?*PQHP z%qT+?C>I+^jtpP`o;Fip3jpwF2e;IlK1dKyf#HUpy@_}KEkZju=<4&iYtvaE!TBAY zC$sdvPt~jc)A$^Dz`G#a;fgD@wM&w|LOUFqwQm)H^E|av+=h)i2ZA&=y0oon-pC9c zj0Vi&t5#1tWZddZv905{NuB_`_>UPn&Vafk*8$`bC(*{QapWW)iH*h6;A*ct#k55C zl+mOIk+Ptdm6Fh3RXQB_C{T1@Gf`p$a-teP@9!n3TLdxTy_p^IhKq)dynZ$$R5vmV zdLRo+l8#B^v5K)#%6E1OC(kyaup#sdJ=z@~TQKon`Xg=>m^x<;^Sld(~RUB%~Nf28W;}b;( zC3JPjN^UC5qK2WuJRZ)p8PW+pv6qpQ90oZ|`eUpIPK1utaE9=2sP6vU53G965o$Mf z2(|Zk+MhM`QG>waJ`K_&Z7RLC+Qd;bgYR#qSOtp(9n8aGi*sScx=btbfRF?xDTrvm zr6EI3AlgBXK7wvlv;|@^?iPF`C5orSK&2jJ_T|YlNc?m4iY!`JgEwG(G2TK95hL?{AyNmnjR_4k&5&kNeG|>Kne(M+2a1en^xEoaS7VC4hiGR&-exXuc1$` ziCtbH1ONc{zZFT$|2gzwm;ugcvMlgwu zq0xU{tabbk1ZEbdoUGqt=c?5(tQnURg4}PYuYB$7yj1lca8fRdJ{q%GS;PH3hjw## zxn6mTxVdf!R+WC+kk10;wx7D$XtIynYX3iUol}q~L6@!Dwr$())3$Bfwr$()K5d`2 zZQHi(+y9OGaAzi_9_pT%GkN|>(RT{BgA>noqGdvw-Drtpye=75~zSUpFhEe>PymtyvIrkiRy4-#$jG&v(v(ZEi?Am_rNMbp#j%VAexgP(3(2Cu`x zOY2now9TH%lCXoJv*~2V<*`VqvqyaC-<&zK8AYsC6t!pC7=cEc@bU7!e=l7S|9iQI z{jj*XwZA!<%knd#VFT-O7DT6(Ihj~PfgTuUqkCso4+i!1c5MCOHNEut_#Arp4Yz~g z_3?eYA9J6~?dCG}y41h9J$?JWK=%r}+9Pot|MLe$EtH^DH6@+?n;JTn= z=;?cQuCrEM1=)zS8E4@Q&_S&E$MbQ(+l%F}OcOhUXm|-5!f@$f5>>imiVO4UIR7#+ z($P^?R#tQ{WNDwjwKjycb-nWI5VG8u+yh?dE~~+RwOG$}xhb9K@QS_d#5d?Yx*6j+ zo)Aej8=0Ew-P5oj4D{`Bb6)UfwdY{PXSL7RPNA}c)qKi-tnd@;nFSi=8E;&5K~D{w zoT5%@!Q(T0MX9LLp$%Q0iZy*|@OYhEQ@hD%Y;)O4m}A6;)q2o9@NUxVAF^P!SuB;P z-*UI>ukP;Pww=L{#_Y4T{9yd*1VZw>f1sLXJ1Jt~#f#orAHJ+Ssn+7?`EjuO*&Pd5 zyv8#p2Vk|{F&8LHbG2=ZdP2nSZeR1qCMLHckgKujFj@Dbc#+xIGFjPGv$N>Fobk0< zze#l9HGJ)CQLzC2XD2-|&8+Uj!Ez1K#oYNourD87zO#zeCs0$~**q&sW~T_jX>}1@ z)r>@$WCS%wN|zHyc}5d;IDDvC&0c>_V7x2YdL0Yq9T~qysP+)a;xZzz@J{O-#w$bLXsFg z&q)p}aRysr?8r1&3B$>e)WA%|Sras|DXJ5#4sI{2gC2Kwb(Do4`{dX+;q*+fT5q0T znjKur6c$f26Cs^n6}iZUL7&8mgk9l7NG_Gw91mcA0~}u)@YU|cpiId7fi8tziyu7q zH=K`Z9%6=BC0C^H>_8S69h10$W%>RTw}lByBe01vz7|AGRiU;|J@Y`WE>BE+fv>jo z4Ohy>shz+3C*;eHRZmbXM|-?N^@7hiFibT~a*jweCBa4`6HFI44X)X@T$ zUX+@64n25JBym$CF;VX`HZS=m`Yl8xjJeHg%$udu2p^Vd3Bm z2Ur}QB9b}e$^|4m;j|^bia@+GLuRX6H{T2iHnFUc|S;U z)L~fs+D_@dztV$r*UIV-Rd>JLsB%it z{+d-m*8@}_n1?cTNDyix0)@PdI3cu2XRL-7Y0C5vR@U1Nq4#xIeD?Gxw20>^P6`5| z(b1*=OfPEw3C?uLZUvNBJR%i5IoYtb$&$7M;Wyor6JIilTB5F4Ws9An@6x2C;_etTs|WhxM5Iv{c&xJ1yC z?m~j;OkX`19Pf_-H@PERq}}_(>Y4f2^`-0VcL}BTtxcMD4juW8E?&eAuM+7n=(!f3 zhv(g}uX=$4tv{Nn?}&*4pPYg8*Mn^IHPgH9Qj;7unv1^q?NN<{pOH4Dhs)qkV3vM@ zF))+_W11O+WwhgTMHL~9x51|sGJ7?WF#}4SN!t@h0u3ocudLhmrb_Wr00-dLm&jm#ss^TfBDyW3Tk9c;KsbZmc#H*vi^WdY7`?ev$12&0s zKyxIk!Jgphc@!00TT|{2<4+I{D(QK8DSLkbTB!su{pPwfa7Mrbcu%~IAMo>{DS6I;@ zS(_QX_Ve5GhAk3$;Z;xa8_ZN4v|%H=1yL9(32sUiFBc?|RF)Saq{g%@8~g95%^xq2 z;uJ6d0G(e;`v2le|BpsCrEX(~C4uz)Th&N*uOy06YcwT>mWS6<44q!0}pm` zBbMje--dyK6N0))gt`)C5zsqY|Jdjm6OFLDbs_MhrtR=O6EzVvM;?gh>BQ*y%9YtuLmzLKJCCOiGS^DcpsC3Ylc13ax-7A>Vrvw=F2kuwjwcn7&=~BvmhMii zMSqr>?-wW{?i&5f8?a1xpaeBqyq4&=yd#)+#b`qJo9yOj2fE;7Nedg8k{MaqYeuY5 zyl%X4PY6F61GK#-d?`NO?&jBIYk{C;Mhz<&Bs6QLVe=zEo|tLu(Rx51;S9HuP0%m> zamhAgB_=`zTp=)EGW#i1^v!vaO?Jwtn&ggreCZ5PLXMgEWPO6>{4vE*Dk?h-Wft$G zhD*sd==Z+lw%hy^G$O4rWH*J9s@tfDDsO3DpQ*@qe}XZT zfJQN)w}MEwlnx$ucRPu8j_qsr+`SwA&e^4&f%x4KVkEojh? zo4)rnlUTvF=Ku&DDgWLpYQgx}OAryGoXdLe+uDDZ8GYoP-BeqG=VIHm#?E8KcHA$A7ua56bn@>__CEd)itmu)(cV8lT>RfQU$CV%HA zt>k2Wi?_f@O@m=(*v!IJMpi|*T#a$q(u%TY2hO!@dIsc5oi(;Ke~;VK&R=Nj04nY% zD@GUn5^#yQ*zMFO@K39nH$QKOQ4dgYHGfC#bDFk^NjGj%n}7`se>04r zKl0L*f9H?|AmLKA2$R%OUAQr<12)?7G0Y`mnDk=?uTUC$?P)^CmCmhCRlolAUPa4q zbJS2U_ZRbJ=7U7@mwTju&WH}8uu!Opf8R4@{FHuwBWR8EyhoH;R-&mLE*o+(?w^X4Pr)?zSw$?xC1W30UJ{NIj`Jxc zH#4*TUdbxQ3c3P=n*nw|0G3huhK+Y$Jj&`8MkzdLw{#2r(mZaT2gDvfIX&GNZ*N_l z*KZ2mH922h|D5N+T)tV92``D%;<&tb5uUq72H<)JO-uZIZsa>TatU4?A}B}?Iz>_n z;eYN9`U%-xp3Z+rJ)Z21!tqa+{M=lqiGB5r>1pl+#K)E85OU!M2TUV$R)K978T4TJ zGz0%2`CRDb&p*%hc1+u|oT8u;$8ixGdtHn8`A_%eQk?>&(Roh8&z>$8CE#bnD)dzB zeBM6LpMvi^0B?X2N6}$CD3v{S6^NmU!l{LpZ5I%QL*m}&_k~fz%5Q_Wv88{*=)CtK z=n}9c_>i8Omy@zXaW9@NUCDi%=G}9*rb&8Xs)IL4sm|H?VzxWB7ZsE`9FRP7#M{{XD>W5Ab{B1h4_gnfCwCL- zhO2Atby=jA8S8D&-0^|9Q_W@`FCk5E3hYE7k88K?nbpSIHrQ2#A+yJw6s7$i>#)#8ea172DkT$mK%V%1=_YHL!!W+mHJJ!&9Z<(U;T3X*<|RjzONj;L&&} zdRKG9IoU*sPBZ`p*_G^<-R@(`OW)em3m}i*1-xdp>RR&vp(nU!Nb_1Q&FnxLEePK< z@L+Gr!Cp%vGrYgnzp!pCoh?{Cz_wUmpn-P2JDb3}#VLuP!VUT!819~r**e}v{kWPN zUM=kxcJ`Ct<@U7%ky?1X%3*qFa5{9p9dhEqeeg7Ra>tD zlUGc!YMmb_BiS*&Y>%!cw=JW+>AmI?bZg%QH{VtoG(Ccgo^~kj4}L47D%!{f=jo4% z{&WOM%mZx*%9M&uYG|vb#6V^!1Hxnn!W6cD6oggaMlH!X4@&Xv`6Ae&*ex74n)a1H zHXu`QaL05$@GGVxL#-8^ZZ$Mf{hCHiy31y|8`xHsT9@w*$GgdrFa;%#7|fcO6`<&gl#pCpRZ*d~sC2YOf!Jbg zP`-vKb%BVg9J&YvSnZtJ`oPR{R-w=b(58&E9+Xu}%Y7uBXaCmbv5VBas7z?+rZg32 z_fIRv_NlZ2$fIIv_w}N>a)q6u-tDiy1|iwi;N4mtsUm6`9xKLtX>urfhKDu6q_x^P zrrc(bP7Gid5jsF-B0vbr@>j=U>Jc(ZTG4zdv*knn(j2wg17x=sWT9<)4XKmT8tAr0 z*3Ulx`8olvMYB6M_G{G|aEt{%#&Yhl8gl{Nw3?{0I8#FlQEA0T8j44RI2Q;11efL; z8=!WasgehB+2%~l(5rD^cew|g)^=38+`b<0H`Wim1lzw@xBxfU7~Yhcqo+>M&e?*_ zPO;RG9~IxVbGGrJzyNMQedPv}ViWDokJXKMFvNKqvD^BG)}eQTVp$e4{_95(1{Nsl ztPn4O@lRIxXI~1L*NF_OrfN_l)k|4ZDF{%W%>RLrBC<3qBPkWqKuS$Z=PV!@LtRVd zZ&<;)YbnGYnE{RzT|y!)1<8|+1vS}_2*ylw8>T5 zOC2H(cIUfE&K+8ygW6q-ix3aGGiObA)`70r1>uh!Ldd||sa(GlVdv}ZZ79l8((XgF zV%Df02Tvwe<%O3`ct5lA6Oa!dr>$+$-R(-3-Q%i3Doo(<0-%-Ku&Y}jUCxA_S*oC! zs(?MW=bt%yTLYNb%eS_hwv6%>Jn!pIE>=%oKE61)({=KhS$!T?cT~~$E~jm{1qb_9 zTEH#GX>@RKv7e2bMKqZ%Xw|9d<_oPO7CKt_(!x#=+mv+-5!@E_0k9zMpsxH}&a-4{ zsdZ;20oD{eMO+f~D=BIlk+ ziW1fJD5}^BlA<&&!$^bwzy{vT*w{ev!5O8m zSx2mj(<$7H&am#*AI zQ1SUODpks%Nri*UQhf}~RkOax0TXDv?gtLLTYD#I?<;Lq;L#Gj&Xy!-rBpoJ=DK@N z$k-=)<&`0;wK_Xf@IW*=d)ze(yXUm2rt+5c<9d9*j-U{~?%0~-WI_9Bj1rJec?0p= zCPUO(VNz<9U&b=pytzi(xEd_#w`LG+#Rph}bBIZwWIs@c`tmSpU>ZLGRX)M+TuF+% zEi41{c}WA7k}jQ95o`etaDYOHd~R*^bdaf&-Pop9`-vqFZJig_sAh|P)+p+! zIv995Pf-=*0ZP^*erxAF_aptHM}F4RvM{L^lRMtsc_Fpz69h!Ie-+6ji>p8ssIwJhSivE5t)!&%lF`^D3fdGp2$hm$ zoyq3`QO;h#1!Wrf8^A=oUHhA#p_jgZmOfWksh?-hE-l<3Rpfm|6!h6e>5yn}DOh~n z3dy9V$@Y?UNQP#3&9#>63kOOcRE4}mx=*vsjU7SN&8&@D{mWMuCT`$bUKJ@*l&v;7 z(#qQDfsC>u{J7mNI#svr`ihcMv}KtWVHH7FC(_*6f2US@!*_K7@%YBqSIDfY+pe4I zI!&!~3tFFAJR?QNI`DTU(L3Pa;QCRWS)&xb6psA9@QI?C2!{|k`-OSS(oZEG-hZO< z0%Hm}WD{AY8hG-4f=zQvByeayP^?kNEmG-|!=?jeMpp8?_0+YVP7kyiWvk3YDJrjw z5(2W#*x4jgMRuX5Ecz8o&83}-MTA{MF%`;xq2c85cc?=ap~y*R6l{@V4v`K>dqL&? zY!`my6;8+z(SkVh(hvqLF)qoG(!kV6ETmKuNK-1=QR$dPziFH}f~AgvO_53!c=9fy zA<<5$*r;I{gXPiEcuB&>3_DD+O$G+@5#mxgS?UcKVhMc+K|K33dje0P<*a<=HOenR zAt`MPmd*15kt@3XvE>*~EnL$>Nm3jN?wpCKNIXiTNC&3c=9=nk~D_^K_bF zV6!k-Lg=T^NcK(IzCuPAElabn>oBu#=DU6A^DXe-bKL&P_V(^hpfU8DgSHm5?o<{a z`GKu;uOWesSP3i?X+z=0;p7yN35yiO5KhVVC}rZqqxQ)n)xw0C1m*I{#2Eycgot4s z9MEcp5#fSRo09UAySCIs6yxzk;r7P*;VAkr7FeO;9`Qmf{rY?&nBh}enML8D*956Y z6cPQ^!Izh5Q;A2gRn@Y=QD--vk#yTsp|xz&g+h8i8AS*7E#+#Xno+z$mLAQ)SKHFj zYB>{}>rL@*!^}o6GW|*~J52c2m%Xt6u=L2_`>oUX?Q(lH5`^8nDy+cf2`@%zjyT4P zRWlKWK#NDxopPX2DMtl3B#jDKOw~=em=)70_RGMJgJr zi@ZRNc4!m~hd`ubD2DkIFuXrHjigA^zgKPnO?l%Y#_ z+^o2O8v+ForGx_k(PXeft7tU%Iv_f$C=fB7mP9NDTuh3vCfGgTAkbeJ3He-*@qY1L zYA(Tbf{f;QoFmG9Dwc)(PMDuoVGJ-(1x7`lXhDrt_J%4@E+dF20Z3RLvN-udD=H&- zNkq~=(VY$j3Sv4Rd6f_0_keSh(t04o7usZrl#9L~4GH|*p(ITaJXR?Dbqe2&_xt!uq~qGJ^pk5CrGSGl&5;c(|nlKB_an7YappU>5Nca%=A*T z%^pI|4S;7n+c2jQ=?8d>+)r)%V5evotdc-%NUaBJ)L-|(pqsvQ*5!B_Kto`Pibgl4`G`4-MQX@NV`Ipar0Zeh`hrY8cRX0)#>=5&$n?{D!3cD#rxY zK+4~iLas|jz)FJ_@e5psK?Pf&aS;t@`v_A&UjuZQ4%1$C1Zc$I3rHaiADxwQKrjro4#u8{YMQjh@`bk)TObCse5{Y@)^*(!^sfL@Z{(Q8w}C@Y(HN*_H(H z6S2v8RGOq@{NSS?>)_%*g)orC3|LCMr$8*=_MxEG2F0LIl{B;5Dd0#J?i9da-Qx1v zN&2(M$$<5He``bwI!k+cvl`peDSvq`5bDI@1bdUQqye2|P!8*oDjWsCWRk+I*h&75 zx9}LnRUTBc@^HhV;>^PS#uxVf5Wz?wj&)lg$p9)EH;M}%5^AUcV(3ERuR{Ug5u)qgPzSs9!DN0d^QvD@H4 z==xBD$&7*}VWT`});wrDVFkFR-%m$Lg(fqYhNGUwO@7&DF&R>abO3{P5${yMu zPi1)!=Ll^7ZP3*bGzo2&EzGO%beOyuIv}|V*-e~+wC{X^(W0lz1|CI(i)`gw5etZZXDXtlN}hY+J2gb zAfX*cV;e@*1jcRtjoq4aC5P4R?>-|)W5rN5v69JfK`Uq?uwu1@sVc|6YK^a~M8&ED z4HRj#Etm_Nc)H@%Hi{8dT{KozU}iUob%`PBTgsrD8cJX#IQ4t88ZT{PFo{d0(_Gfzj#qEVM+75sB zz`Y)0rxdX&J_vQVnA0GGUMXtm{yWkgu=Agq{i@L<|wsmcARb#?;4^@l4D@y|(I)4+BSdT?1G zZS_c;B9|1hr!3Vieuziy$^`Y=KX6IEFOQ?{`Vb*|faD#&Qm)oZzzjjy(#o8c9mA5m z&FM`Sae4F0M%R1sgC>45b}a-UNa)8qRe3qmp= z4Ef@k>74~l?)||7FAfL!3}!$6n?}rWOpdVt0sxQ+^FL>zjhW5wOe|5gu-gzt_^Q!k z6z>-QOY?wMJ}lzFCAbfqxY-K}6;zl?WMDZ)ljfSNx1({MPl4}(jL49hy~b{LJT?8! zEBr=OGJ&Yf^M{f9X3x{dmo=wv3{qPydY4+x5$dTWyc#-}UPGP40>nXtIQO_Im?oz& zzS^NN3*9P}4jE3T1w4}Ef;_VLF9WHy#Iil&J-8s=uF#lJCM=4j-D`OvbiY42 z2!}x>(tWEhXHRE9V+j4h+DK&<=4x8Y z?NF}t<;2?6&1qDBehN=KtYSM=14=*wF*KzfF)>K%E%~+BrUCcC%)uJ1en{Zm<$Rtj zVQZ#8?8>t-o2%WQ4dmAD3~fD`FTi}=K$O0(+C{3G${0`$1iVs>wy`&~BgzDgfBqmQ zA{j)9e+X(3w5rvATEM~)O*wUzF$?XK1WDLp1CBf4Ns&I|?RBGNXkRAjr|ET>=CVZ(YxIkI zYiu}GaE{j7NDn^(g>z)d$NL+q8o#21dCazS_PZ3oZ(9f$>mnm-w-Sx-o*Q4c4)*?R z5V`DRl$xylJ7}#rdGkdOPo$xatgy_i!5+59c+I_7P32^9mQgWgp{^*!7FuY)U3my{ z>*BR<<>*aGE)x6W*;FKE!`DrFc6CCam=LlGc*xBMv06$iA+YA*{Wo^Wv+=d;KgL z?%n}-i`kiYmPBm{Xln^(#~Ei#t*ZA4XUa#3gb;8W{4oLqHbgTBSjMwDh+C&t6{I%( zw5`!A%@MKfPlpeo8~(8gzI$tDT@}I;Svm*Mt?I!qdmft*o{m+)lIx^o ztraP!`wu&p!NcU?H}MZsh%UGmyRa+NWvk(Z8RYGiP7{yP+YZ~>k5AIq(zm#C-yZx* zKAjIUvYQDHzJt$7EEe_wxzk&+`<~JvCqlT#*Z;NzVgo4!9nt~-Kt%q}CD7i~{XfNs z&u{P1sN<@aAE?pvL=`9ZU{0@Q5yCZyL_7%K?Z0})0B(MNfN*Gmdx_;J{L4DNKE!XJ zkO9wl+(bWvY5X?+=MeoyFE3No6C8<&@C-5q6^;KK85$WOd`(q@aE3Q!sv50)iEe)8 zGI0CqOPYM&tC`6ez`kg{NG-B6RhRK-5ows^`0BJR(bYua`<j?_o|so zxHg)*m06s!)N-4JOiU+b%Y>9#sfaC1BXIXb(31`JngVl7h8;=9)vy?GS_eRQDfL&z z)%|XoAxSp(s{6UV;ly2}T=hwqh#19~0evvQ6A?~jCjPk>|*!9bz4yTNM`ez>+yRPN68)^ArI7fUy~f=-~c{00a+8|Gyz-Tqu?wwXmj=F?|T0O_t>5gx|6 z5bEud6Cg?-4z1nM*wyUw~<2Uhk3N*)EEOF zcnAefud)zNc8X7p#Z9i+9P*rq(-vPweNYp;inh(1LQQ%i&l z{1@2Hj)-Pk+oiWcZ$JoLW``#|v!0Ift5<(|+i|{-G)MB4MyjFl(+}Y*FNb#)|6#c- zRbQBY%D&f+erdJ=>6FmT&uLk08#w2zeLiftV=UmHtceQ$gDP+ur6yhucl>!m013 z_fDGM_rcEh-A(Vy&;1d;mfrVF+Rw+x&-2L7=MjGQXYFqSur2-qJQNVPSAH11>Ll%tb&0-pVK2D<%yjkXmwvr^FLXmUIp0 zs>-CuM=0H5*t=^@`@C{`%TFM&CKf7|i*1$Eb4r-X`{emZ13>&uh7p&0^@)?7JXzwr za;cvGHV^0lA>6WugPC@`M;%xO>u|btOyWx=I2#*zdDmQH1zG>BhxzW;M!>Pb!=)Z= zRa1J-Dv+cWoJJO<%@fRVmhNMQrh;S^=?rVx`iPiX=+%OVB*;@P)+?=8avCsY7Pl)mTQq2#KXXV#b%uOC=25aS5be*_tmzHwWDn1;6H26zHp+s*)Nth^7TFv0G&B~x`B7<4hTY&};A zt}_S<(uCeftdI(7bY;!iJ1p=79PsKo`FsS?_fb;oz;Tw&^_Pg z$pMkGrZOJbl2tH`qlf|<*RgbFwm(a5skqu2_}6G~vJHmb>JTHK=cgp~Q}u0DGd8HW zxM>p(;<#~*5X3}bR2_@0%+V*KSWyXsSC0#I%$-^_jc3Q<4rqm_6hKT>6`5s8*B5*QCIDxo!3TX& zxU#N*_@dE>=uCw~5CTDj4{&uvbB9rf#mf(7@9^)Ep2Rz1!yPj#!@!Q;-HO5%oUcb!EqLUrB7T zm%9w72xXFQ9L$+_;HAuYv*fEc@A@RoWC5vbS+Av3n3nQ~X?0P)k3^ zH-<9QiSaICME{Ec_w6m_zh|dtKUfG(2 z@u&`-WT1>b*mUkJT4cj@48>y!*WZYpX-?pt!<#(lkx1Vc{epn@JLEl`oJc{ z0tYvvVK|t^0`?@Ig8Ro@*|0LHUK${8g8vLEvX6l`S`T`NrwRu|85F>`gYa-vSgjbY zjv!`~KpJ%yyNwZcFsrjyAyY3dfUD-_vqWs{IDR_ObRHiDo11JOIG7Enz~qY}PoTeW z5TJrlo`b6+;R4n%29q1BV}(fL0@byltO|0If=RTGyTT2*DhI#u?%^jF(4at?0n7G$ zzm@iLcXShgb~nRs@LF3<|AYPccGFDsa&3LJ9_yvZ^XKHHUDJkdv9$MNGh)*V^az9* z+LgbJ3l+RR3*d?zv^~;@y$SrG_)oZ0-Nu4St!LtUQ@@&b^W{>Hg`DQXxJU;19aIlX z8L0%$hP@t!{c6AqimtCGPHzO4+ep?bE8Y`SfDS!V%`p>$=(Xqb%&m> zubbZUZ-iJIHh|PgdCx>?TzGf>9UtM(YWH_KI79u7UH&~Y$UB%44|5^X?%hDxi0Ka7 zF0wrUyuSO^j8YEuvnSEFlhEGM9gp*KRxE(@+L-fCcJ5_?CxP1Q_vbVxw@)BRDKY(6 zVE@jdle^^}0M-e6=^aSR^EEGE_vOin8vxfywkn!Gb#na^T0B6~3_13w2jnGk&3XBD zlstYPkT5_pw(r<64v%=M+=+?f&js=6jWKuj^q*}V>5&ZH!u#zi5Kfwg8&AYG<#JrsFo1Fp#lMRGO zBQ#llbMz7h2p2?@AzY|YH=Hnqc~Y6pLh&SiLix1-2>DZtIx9DsAnzBjVJiZ+y#iab z&6(jaGx(dgAJSo-4~bmFJ>j7(-mg3dxct3q)1*!5=e%vu=eTCz!;wsSUmng?Kc40f zho_pgL#Y0!tf3=imY2K{h3wAwl@7TpnF_Iw^%~$REEkxvkJ-3cQF&Sa2S@!C$iNnTN9<&yM2)xS_2 z4Vt_gT>??gcC+LuNYMrVE65K#Pn8iMTV>#s%VdTzlo$6!LHS;YZnzrYcTY`cRy$@72tn=wpFM`3*CDL}huBLgl5AkO#p zc-rs^KWu8N=UpRuPp@Zfek|InZ0?5R`A_5`d9@Kq2KvSVL8h7J>i@zM=2gE=puZN#M{7NZ*=6`%QuhY3$3Ecyv zK0kygP6O{>I28~Y&{{~Z;z#vTB?k9qU0O1#*7}1PuJU6o;qZx9>oTbn;?VoZ;$AG3 z4FkL4&ARB{Hz;vS;SX$7pv=321?bb2$m;nMj4HOW?#BuqLQJ;_9A)zJdMP4Jhv*LS z_Do0(&9UEhc{gRPI{rC@Jy)l?2!M$gpRqSMU+G_smbl*(C36H}i2EK(Q#) zt^|Okthl2o3$DU`l@-1tH=21@{l9hD$-%^@>QKNN~HRD@DzNx=T~_ zWjRP*PxgLuzaQ^j-3=-UlhV85*KI(AchACMsB(X(NYgV4*)Re~Ltsw67E|#w4y{orG3edLwd#OO z)mKWKJ0?39PgwF4^^-$%2H&g6aTLAj43=nEuMuc9;d%g}7rKKJm2?O);CftpT z+u(oUBe^=Q0ePyc<%brW*=YweH&Dzy>ha`Lbu26V&)G&I;M02o9c{k8J$1hnys%mh z<<@QKnW75T}Icge1p z7&z;FX0}`bHNgX6Q1vtTeEztv51?lfQig;wi{@swnuV(MC7=Rsqa)VZxdK|I-(b2-hv-O ztm&RXadRb8tFtrA=vucq1oBo7jcy2Y2OmH$|Gs?gspSmL}z@Lz+%he{q10uJ0|TqGAkcmi7mn@fXB3gX^3Fv~m(}tY(M3P7&v)NLl{fbAGqy zBYZZ*q_c0L-#fy)f`tiauxuag971Cz#duUny++i*5W#tH8s4Fi`=+=bUN`FJp*6F zia$~1>nJquwWa{je{vGR9ztO=JU@aMb!GB@$U3K3QGlgOZ=G%1wr$(CZQHhO+qP}v zY}>Xq=Vmfnl{C#1!7>W|IYg)-B=r^*GqfypUgs*1YdCNZ90r z!;vOSZ*HR}e_`M?9#Jj+bg*b|n48x!d1h79r3havDs*SX<#6qp(zEXmLTn~pna7<( zpH^hVLxRPbFf3GPB8@6QbB?8E@v7>|e8*@lafw%lyX>Zn}gVe_g zsQQ^p;u&4&!Lzl6**&pwdm0+k8*UUQc%qSzNwcs$p@nYE_ya5fx?*XnSFFrfgEm?t z{Zr;%H2Ai|Rl|yrdJYOK%e6xhLp_r$R5s2%q(z*#X}qGvUx_#5)5B32JVlr|wxKL$ zfVrszpDI(yUEin($0hy2FCtp5)mIXShHZmWB&VxkIdUdezJR(&=|%7+yj5T@&n_Fa zFXe&Juw29(RXnxwT_u@U*&X*TiNa3kF&->%C^?~=6;a^XHju}&onj>TCeN`qpuu#V zjf-)oC%AU%rJZ9uB?*Zm=<`#C*BO_=@fCm+UXAf-Z(U0HU@f;zO=osBoX2{sklk{Gk7;er4X z&6Io?aXwAsp_(Tx$kz`6a(#i(jo}>9?x3gBr^D8_n0q+*P(~-*9q$-uDGDR}T?3#i zfqZka!iJjLM7(l2;G%mNY0gRM%{H;d(ixG`kQdJ(x_sM&S*TR6_P18EWsioIcI=7s zm)sSi%lk`WsRKnp+c3c%RWMoJujr?r_>E?CRfq4GWyrQn$)k?~8$Y)P`hgAU&uUrS zuk6UE>Eo*YM60HDaXa`8&DQem4t=u>vHADpd}4^5G@PWK>e!G&;xGn5LVdMoRW2$X zej_s0sV0Y+%;% z_2Gg&A3=Rh&X@k7R#(a0~!{N=k;Nk%BeMQMJe4S<=U_~gSee3>t=*ES5 zWkr2+!y0=5Xn|&Hk$74J#3l!Q@~r;WITihdlrv^%vSJ4j)c}RuWWC`e4=e!vgT*eL zh#K5kza2t9=}f&CU=0#PGai7mC`d8ZPrc@|imzjn@5}B}k>tnMle)WEZ04i5%(%Pa z(d+WXfV+|DkWd`FY@Q;m0F>V*wQf&Hv2;6|+j^l}r#`}=^Lp2TyI#f#@y*w)4?a*UTl*ka2Y(d#r$3&T zM`1s*!!^(H;^)cg==&Je;xefzD!fZf^ZVd|xdDDJYhY8T>hvRgNd;Qk(OD_GOrF;QX~Oa>+I**k-NSKYtK<%keT^ z3pqd=vcab4a*tH-fLL0+Jc6HC5S=_?O=m2Wdg;&NjB~@TTvuL>>u$bS&c(;i{hkF_ ztJCYayFzk? z6A{GT@!o;h?ZxYx^6Hb2ZM82Eo2$m@Xr&9!4RY*s-_<<3i7D=7x>1Ms`1F;UT1G~o zZ%ziq#MQdgB7kc`iHD>P%}PdkRZ6=m@%+>bvGn87B?lKvFx73ivO_MRQV)Df0B@C{ zvDVeqW5#IK|KB*kjpx6d1Yh0kK^&>178bqeLvoOd#mr^I2SSIX-b*lqJRLV23|?jdg{@XM8H7kAYA1H;5{pkG|5SpdPd`Lw-xbb405|nrp(7zIbY;GAmNbD z0sFy9HhqX82j;jl>jr8UPUfG9M0@igbX;&xQQ^xi2)51P8=K_kS`O*y{h;DJAum3i zw<;0=8B|TL5%wM&f~wx3w-v*#GMn^GRHPaE<7abz(@j9D;$&6n%<2ZLI9CYXGV40W z?r-Nx>>@{@jWLYYfK%k2k(2GftFynkS#oja;viNAh!LPNEfXo#_lKbM7IS8$Tp8$e z3A5JB8(BNPH<440jdCjhI)4#blVFCp+>6~%yKeM-kSDd16%_H)lz?Ed`s-sECBYD@ z&$?)%X~1d=sK(e|MefOA-0sAa*?}a8+PBl@bu5$t8~2Z_ky05;z&TfnHOR7MH&7KB zd1|qvx@)V5^!&%0H*4p&ajiQ;*!7;s-H48nMqv@qY6jbLR3=ytr}Syq{3URbO5pUN z8HXo;^JL3eATOm{Ou*8!x|ZG(FQ+zPDWnY}$tR0*>MJ_73y69o)f~gD0kA;Vd@n6} z3^iN;$|B^=1gW9DhQSh-7gDXra66lJ3}s$Nn|yhl)*6-ay=m#ZB5mT!9XwB9XP{U*Zc34AL}twfVk7@je>266EPE8OX)q&@aVttsZ|liSQwxTc(pb{ zW=@k1)&^orGBfW>4LvL}qorIF!cF`ZX{D`Z=}Lhy7BJ}e+om~Vy0T%DWHM1aSW_yJ zn_##I*)+{gK9sWvu2GsYuX#$Y1QWRTMa=9j_8Zk8sj={YD&c0xg=wVE*PgURKga#e zVl|``E<1`#W;7Qg!#0qzS6rft_aSTnyzHT8I$xp126e=8P=#Pdpe)bKIN1a|BkW5_Ub5MLF72z0KA%D9psp#xNRG=ob5f4 z3sG+8?QN!OEm6_kAR_eLwpBTzjaUMsvl#fe@ly5V;JB2F>?*l-MH@;{<9wzM(=Lub z)Bqbt8Hn*pir8@)-COz(Z}ckG+vl@r2o=Bl$g`7TORxDh=LeV~EGQQD6$8FObvShu#8x_qkF zxE6b<9TazJ*|i*T9=%>Csfii6Mv$yJ>75XD@>x49FEFkSV+;!CWnw2sQK&5;r}Up+ z-gr#tIh)5Mga&peFQ?ovT}|dzqJ+4rlVLkcdUYk0yFC0fH=`IbeZXlm@9ZU#Lq%`w)q@ z7@2heI9HM$g#SG=80)5FE~m6CKoKXoPPW8z7`^1(E^w97NF#4>y*GA^O*QxU|pEN%Os2K*@6WuV@f0GSw~ph5}vIR!q?RHs$nU3*jux^GjXXt zkfnsC>k0ux-j}+n&;9*&=dQE-$0w_{D+J}BEZ^6gy&jppXn;OmTO{-VoRbAJW zZppgcNv)TqbBo0`x4Y-n%d67h(&@i)n|-h=2jQni^pm&@o;Hk+6{x=&Y5*PdFEihj zdTdx6#p z4@oskAg+)GKd(te;581va~cb&HYBV%+4S5->^u^-moy4CoIj86sgKbs8v$=K_Uy6N ze@2kJ0qZ7Jn9|j7{7u@Gt7UV=&(;o)I96A_eDh!k)Ex=`GC**cu@a(^C~(CrX)x$| zNmV_hPErgsa7pC&3a~E-B*~^4D~-3{eWIpqi`{^ktfgO zmdM&i3bF+xeBMwKlXgjjZN-h`-X!p^o0^^uB)JKl}#RN zAE+Fl3HNq#~r#PW5&1vG#6j5H%n^eJ88G zLziit`F5F?%aN(dDlu!H=KPREf*!P*gIE1Du{a^ZM02i)`Q*fb&?xGf*~L20CTZkG zgjP+X;$zOD@m<5hQqdlHYm`WVvALHMQaJ}*F9sME7b4p&wR=!vU3c;dmv+zxIJ(OU zY9(g`UhDNY&ot-kUGv*5IUVRmKT2obe4{1G?qr%R0=&?4NTzv^&a7J# z=Kye=Ka&!zZ6zTb+%=j4E$vtxHp1$h{>^*H2_ems@yce{rX(pM3~Tg)CsDXFAH0AT zk;blGacG$?YnmzK!wi8eW!Q!gs$H;I-6S$#oG_D_W<_RpJMQ58_bqTyl+~Eb*MpDw zbF@H?mi6NXTEdLqgPZkWhGnCa>~rd07tlk1TlUlKeEMYq#%~I1`sxv7EzBn8+qS1M zu9ll7U)HQ#(kYn{pJ_M?jCEupiKMhr5tceDt{8W!o?21)G0XSqku%UPD`%}q)j}+n z>H?g4|1buMokP#8K*mFwUrooT>zE+aVti3No>0abuyD!wAVzsf14Lkclics7yZPmk zd)JqpJFpNdFVNNs`jdSGK8#Zi0LXHNkC&G_q3!$V88(n9i`kD$pXe;9;?h+Y5?DL1 zC{rz(pTwj}on&t|(_o>*vlZ9SyNJz`3p|NEBTlFzPQp1?nf&-|P5|p&Q-ubVe%#XC zn6-#q;L)N6jpl>Hc%tPjb`+0s{#cWcG^QhuY$-AwBI0rtJ9u4Mk1zw%l{8ds*Qzu0 zm{O{M85LA<+@Y-JNGh=2z}P6`Oj@!Rm;PhAuunm}gJZpde!4_0VifB5T2c%%Gl6X2 z;JC9qqkcR-Q>xM5FH497qw`F2VFA3hsxy9{ZIfAjkCNutzGFF1%gvE>x~OU|C1#Kc zqkqV)OB|Hdd7(ialcoq+2OW&l8ct;)1nH|KkLEU(Jz}ZS^p+avQpEe6)ZgD31^aP}v!R48Rrm!M#s|tBH^L`7!HDF=Y zl)EoJj7DEgP4`aau@veKI?*E3>=gLVVyZ&(GrdETa8xPeA{^|Pb3xq3sJrOQOiU@* zJ?rLOkAz!)Y%GKiayM&mMP*{Fs3o1sfs7YVv6}BX+sBRT3V`UeG|fQow_xOn_0NTg zV4c-FjJ_P@FBF=ReQ^<;7b5M*nxsw&67)rlaQDAFj;b7m6B=*ZY_k3q=2Nk7}8Wpe z8-Oeb(r;mKrB&YW_vH@{TSBO zhv;0LZIXf&wl`i8#Gsk>10E3sZ{AlA(`aa+k9#5=V5O8}+jL8uMfQiN-pgRH2JS0& zNjS48)|~hbEyafdBLgQ-R-B0+O5--|ECw=^B&o4d`)M>Piu}e9@virywQVe;$kmV) zV;;;osDdFO`^DGc);4TN2RA9OZhf25p=Non5`;L?#$=x-uijQSA+H`y+Dy=gS{k<{@7B=?UyZx+*fsTw zYcN!%hGT}-sCw4Lmf`_j4G1b6j?}AaK5f{xtccJuY9!Vo47Ts<0;}4}op!m9>DHo% zPAO0yhl<9cX@Rm0b92p1RM#dIQ&30pBSK5k^@WI~V&(hNHB)LNHQ^4%L$ZOT2m2Bnv49!ePs2n$4}g??I*xiPi6~@|%cJDD(H_te!pt=8ZfX&)T zU-a_Q_HAIozh~`4SE-_RLS-5ID~w__fwEtvcxcTOyt{5e*y4D2dFfiMXOszkE7h63^3VW#*x#iTB>_vc8O~HJS4W*PqNdRqDLlu|GhajY;{ou<9wpK-w7j^(GLREBeS-9af;ur z9-#WgHGCf-DloKHA8(SZ#XEtxG2NMTqU;w7SA1O3@^PrK6Z6}2?pzc zey@nk9enBTmd(ltp5BFS#a_Z(%FvPjrp(vb@t>E`-C&3FQ+HTz^I zjBVl67jREUJKbMVE;<@5YvXe6d^Yvl;_msiC$B$8>o&u%y0i+qTj~V8zn1~_DVL!9 zPKAY$y~5ER3|Bh^=>wDQt=eUP?|KdmdjZ^g*%g&XYf&aDa13ftwAN&+1 z`@Brb!gTjGDP7uB3sNyIL!U#l|Ml(&?n5m?LmQHS+&a6Hn0l|N4QbFd&QG%q@L+1{ z&c%A%&s5Lr`;kmXb9-HNi50E#%edz1hRpa)jekR1SzOiS^q{(U`JWKL9zEZ`0e=7h zXyE|>kp4$N&CSuk{y(O0wM*L#b@-n-rRX!fc>nCEy7gvFy?+>CL_iC+suV&aSbYQQ z3n&VoOrK((;v6GGS_`KN)tV2Zn(;L!(_W^>8Mf9($U$dpk|iVZD2Y|`?!c<&^J9JR zZCdS=jQ&l-R8sl%LF*psMGI7FGX?M@Mu;eK8OQJ<1yo}zXxGJK&ZyERAWc~;^a9H? zgi!Sch}|}|9_FlGi*CXAedUgY0W8<8piX}A;^VW#|d(j*~-vX?I{rx z{xG0&L^h;9k|Rsd;`is2kmN-$L=#6t-q#UA=9Kf1ox*Kp%RZ^G70*>>h5|(A*A}a`m%Nd8jtf~>AD92zNPup?@eC7&1GLY zRl%vPepgKetO8%9NlWT~A!*A{wkE_kkOz{_va#&ydpS;1;|@Xp!x%$~G!J$IC2u%> zd&9vKg)bRvs)YA)K{7NnK@eLk1Yjd^XP}Hcyq%rh+|;tkNK)h%xd$cS^_;U{CQ<9~ zXG)naLa+EYM=y5cA^ zrA`U&Y}vbM^4bF68U<_!EZOwxQiI4U zu1$(?cj%>rB^&cf@=cvvUcXiX&zNt~2$s4WyKYxjY@7I3Ui_?Y36(3@l7K*(Qfea0 z%J#eO5yf?+4_#`i(#&q?c?8dSCO?kkhTXRW6`MyKK6;9&LIrU;zQ%6lE2(^G=Jo>S zT^+oB{H+YUP0Iy|2YIb$Afy64P@+hbch8%|x3x*ABHoA@f)$yd3@^I3p-!}5@LDUi zYoZoR->0MMXb~5sp)BI(Cs%h|gdui!rUxKpiF%f|JlLW9w zAv^Su`-2cR$1+>Us(EAkw$ej6tFd=B6-+t)FsLd}B%mXILtnn0<&NB)G#xe_nHen{ zmK5;7-;@RWX*t0D&SnpFjn*B#Jg_prHfT;j&7VVGdB1Z*UZ>vs&bfZ!W&(Z)bal%# zlH-Z7Av#FDIn$3s#+&}%Bwi?M=$x{xjXtim!;mj;qBD=!RslY>16;6}zyg0W|AT=2z*tnhNEN4kgS~wKcYbF6 z22M@j5^JVC*dH~BUdTuYR=`lFKmt&7TzNe99|U_CXuEXOa_vHn)NGKEKbwhC!zn?< zjP)}0$n)vg^s~N=Pkse2zGLWLQ2%eF82pE0@;@BJ@puKzrj6h*_ z&s#VB3TF8n44e#!7o5~ z%Q!((?KnV>EuoN|aanCN7lj;BpYcHvaRKl`ozm&%^1ciF&Q<6EAX)8&J~4-rWzOHN zL1Bl%e(N?j1D`VWyqr3Zy`1%*cN!qKUwa(Q+X`>$vYmrHf&5*HG6^W{Sakv7#AJ6q z0!fO0@!F)T@J^BC$VFN+QaAR3j_PpZibdmWhG80`+3!DJyiS+l?hhv^=a%5d;=OA* zEZJRzA_LzKILY(iWB71(B&h>(vsm3+g!`fpXR9w#-znk3U9t(w4U@Iw?fnDGFSW{~ z+9}^+o@}@1qD))Vz4ArJ4}}HXGdsBM;7GRA02M7T+W%6>A`SvAtvG6d9?(4PGite* zPe5dJEKwqe6~Y*EMo6iJq1_{9(y2~^28Hqv5&9h&;;8?{)p=pl0t0!@nQ$FDw4Iq% z&qE;2db0?;rn*)ddq~SqizRK-+1m})KZN_K>(?ci@1sHho_fBJi?_W<0RCncM4ss@ zOPV%yT-M7L2Sd)Wa}S>6_Y+a*8NgSt>3SwESdGIMe%4Ctj6?5Xw#xCkMeaCw*1?6{ zu_S%#ocCuAJ)P%1r}onq-6zuT9%zbNY2UI#l=3!FI%s5QD{D=Fy?*RgX?7D1ujG?n zOnzZ#T%%74)D(wn0Rj;?1tb$Lbef31*laMMDR8E+oLt?n zlpK>+u6S?7^^7Npz|N;v$*BnPEqm23Z1{C7_Bw~my-~^S(3!^VAe(I3Jz>c$@DU_L z_DDOq417Cb8<#5$yb5(^vPrICg>b&!z-&+Y>MrTlRS)^X74Pb-;82dVH?0j$&Cx=C z%mYBpkqaH357HW*;VW~W;*4&L^rfEx`V*c@X#q5^mVIHhK7up*>CUwlY0WG>QbD^*pK^ju&&e^ylde2bw|#1g^sJ~qm*Y4H}~Q5mzzelu&57$=hiE*`Y-X4OLzJTfp|CHi^srU z&yU`*T%3?@+Zo(Bzu~hwtWqBs{wp|dmAf&rO)RHd-L+Y!LwstNYX@Im3%?QHcomR; zN;0?__tq|`yT|wvuj8HU^jB-x89()#y(o&lPfq*qW6D}*&#YRs!{{C=lvo&#|B_`d zOirKW-^p$sXU>Hw%D%}a>Y4|m=u?X|HH_K&^

B_Sj9AqUSNND@KS&M^zj%$cEw8 z43Gdk?;S=D-fz5Y6mguzzG4_J=>2lwzF7rzKzBNk?-ANs8U8bGX=f=tlvPfOB1znX zw+dj4q*aZv2}yv|J;6j57a5mPSM(d8JvAM&T;PMt?SI`rHLsp0R%hM*-VcFxrT&$} zUBCCNF+Z=bp~Kt0uh%iZ(BI#=Lp$8x=ld|a-=xs6UA~{)F+T5itGAD;F~10|^ReIO zH@LXpr?%M{ce@DB<@cb9-EYi=~^nNhe|FLvGwh!(0 z-r1fG;ht23@%CPC{h>v_F7EUCzBp5jxke@&8mJ+(d;3D~<91JIyWzhwPQG_GrFUe!RJAJxRtC$|S{eB*{ zcix;>W52ue42fx{opOt@~ZFgyz1%Pnp%^>q5UM}Gj&F#}O}@KlUh+r}c+ z=Izd3R#vF4|5_Oy?w%a&_HZt|(hGkl@}MZ944vp2B)d;ouV z6ZUO)F^XHWyajTXbqtHi8oK$}pW$>3fXU1`VKd*{(Ku+%8zG$Hx-!GLHe9W|db$3gau?Ws>yG3xk7^gA|0sS;n<^5HUNt2R$`XoJ z_$`e~#ejL<9~0!L+0BEIG{sB_j}6t_u8vM!H(|?xR^JV zh7vh}ubEC@byXBymZ%whP5LUdNvMe6q8L_%czT)2?mWlLkkKWZ@}g=gmZn;oJI7$X zwXEGHmtm^NoUd-0a;@x?hU4^DVpQwTW~o_R#Lo2QvE9sXw?S!B*c^2e?j|O1kr;j@ zwuVuN+fY)i=9ap2JGe#fAZcC&lbpQ-Pg&rWOiiuU$f@W!uwIr?O4lL?nw$9BopRx@Jc9M~*s>LhRdlQdqJ& zj8-F+k<`+%yi6b7XZ_L+f`w*V!!_#+M(HAj3U}G;xYUAKjRVR<(~lV|dMmlpm|2_f zdAYpn?Cv_cuS}I@uLZaxAv`kb^t727>rnz*W{ z<=fB=J_bdOf`A^eh{&wkaB(t}jE1gr@dv-j!}!6VUa$Mw3a{xcAf5Zx4Jf@s3C5bf zOT=MBxJ!;iQND#@AQB@*g8Fuyp6)bWgJ+Ji?Pd=}&KEO$Dtne0=mdsr7cBQj|)TrjEWU ztEm9eCDV)KBZ}62L271qfb29-8vq20c7#+aidJhZB9r80_7V&ep-gMElXIY~8Fgu` zftyu>jKyBpl<}5QJs3BNA%5n(Z5B<_)QkGrE`LL(R ziI)SE;8MU+S+CZA(?6YN-4cf^2V^BokX5Rp9!%rgN|h?JC1cjH5-l8~W=c8RRthxH z$v(&R$Zj7y6ieB-VmwD=H!0Z~X(pw_hW2BMP>x&KZ`4bfCcFE0#N^{P!g|bV?J`bg z3vr)uuY5qB%YD|Sw2XV;yv!8fTATD1A+IB^l$INNdAyQ~lvzIG*#M_o(Xys9eG*_~=CGr|hC5ehyHzo%Mud2JM z&79ZiLrrz&(ll)>wy=qt*dhmN953$$k$R@vw=z-4^V}*ujFR=rnx+tO9bEe8Y3mnF zGcX+#*wRoPk#w|w9;)t=+31H1aV~)d3JWH&7eGRiOaP&%6Nq(^U~~g8q!md+f|>XfUHwUkVEb@}sNbCx)_! zqBi3NjX7jGqLop&X~~;*Z~=^Ton|>+kG)Qw_@eC^EB*ir4Rp^ zy>nv3c>iJlI^tBY(>lTyPI73(TI)H34R#={bwhjNWH{v7^B%LeyG6!rEgO%0FR*Mw zgSG>H-ZmLB(8q;^#j<_{T!eqMArjsqd&0D})7rfOK|e-tsW)+G1di=2YISNR6A!xA z7(G;b=wV_-z3q3(J>p^2Gw00^#4V1)lMp+-)_HkhJ?z{e^F*|)@ zGDYc6?#tOVzIBru%Uc2QS>oK*mu7Hao~rG(EBe( z2r+y19W%>i#tMcE?AWbx`-lU|$;fWbC+Dqip|!{q$?FET457>H*p+5{rT>;K!bw-7 z;H4GBUOwM#8r_Lozq`K!?YaLD#&wxal0e# zap3mk3wd{pj&OkcgEMP~$$NDW#`aLgv+Ysg4m4u*{uo@T%Q=^e(?2zJ)_?jWMQ#QM zb5{g@WrXcDa08Pk9F1@)m*HNxu_VzHH)U4W8aK5Kv6cbr$+i1TRh-ug^*e9zOc4K- z!GOs9k;Q;`#7-%WBQ)rcLJpdk2acHc`ikX%peajq2%%5Cn+H1F{_;h@d?Z`2&qk#2 zWoY8Eu}$%Y6K)vtN&vYBfqgoZ-M*B)!|^ia2gHhly`nC6bDg9y=HnO5iW5DD{c`A2 zv_qG2l{jewip#lh|9#|I`si^QLG|}L4XE!#_j!0zg$(Za+x*-5&+`!kw~xm?X{>h7 z^EuQ0q7;`|Y{%OT``ykF7t8wa%4{s0M~*Moop!*>wZ`*{^vC(j{vuTvTB&Kq z(5)JyA_vO3u&T0*63ZZ?@TrJO+!PRFn1*T+x{4u;z_CgO{NP1(WJF~-^+5M1h1iQ5 z2|+NkErq@riiK!yy1(LC4 z1`0jp@M}9JBTWnkb#qQ-lR}qZmPg}!=*ocL73`+%HZA9RdA#jhv_P4vV}_`@SXx3bX^ycsU~x}rwPMu_0hd)Wern|V`stYc5BPzEIeaE-k`f_ zzKK|oJT;(DJWrNQ=#Z!jYZdEgJ$jjl_H4Nc_1|-i*GEpSt*18`{nWooHYc!DkvOWK zy}4!G<*o78id9jS)--AlnvQyg;tDj*X~xZd6d%RSL^P0{<5>Czpa#gQsU!&rE&^at z3hF@y;RRMe0lPqfFbnXgdt>n)Yu>n6;s*C~ja3-eJA;^H1URm!tieYwE~r&kJ=>*> znq+nt#vL^e7v>nh%V6Jy9KzP#Znn1Lj;ponW#|fNS0{<<^8dTqU5_?P$521(+z$wr zI4cURkZ2;(ba-er6~i0Qh;;Q0Xr@(_u73&Nyvh@-x1LN~NRx|Kedezj#LS3ZxJkC& zGDRut8#|gWC0bo$*$S6nk`U=tMK@*>Ars2TY3hniWuje2EJ5oY!&9AcFN9N0j_*a_ z8^{+Osqj;tm<%^@j(t(TBv+;fTSqar_o*#wnx-0yOOhhy zRvDi^IH?q4ubxS2ArPuSUl_^&4Vp zw5Hccr*-!kev~EqReN1DKFEQPy|jUlinWBk!FK}Z(yf3#9%oo^QWt#*3+3A8g-m z^^$U+F0XM-XsnE0r!AebZCRrz4qe23Y8oZpcI`x=Sy}ixH2$|*XCm)LqWImQ& zb}gVY=Bg?{lQy2C)qy6*A6o&z5s)S~kB(#!MKd_$2!R&?ojw6P88}fEI0iQbFMCLo zbYBzAR})ZKei6p8kFHFJuso`F9y#t(#rnx#5?hwAAqAMH0KMnk{jRs;ajY zLXx6`YI2GhM?e>zE{|QtdX^dpBwdQ;Y{Q;jgSGD5jq3Ch_0vj}sEWB9liBb}6jRx5 zn1ZVZ=XA=WP~)J80#6f7tiZmu44 zp-fteXhLAsa%i|LIEH}HanJ>JLLofis>)%k!eJ*w;i5_cLmdKfBw6LWh^Thd_)<30biW@5qTsYII zKG-deRKA_%c3(iY=`xbZ!yV%>y@cW6zwozOcI>i$5I^f^hTp$dyF2cdMiBcy;`mb8 zf3(K=0_+cJba#W1t`dS6FcZ-Q1pMcMk06yJvf-2SLh+wF%lmcfetnC-h z=4Xe1A|#^{AI65FNT9X^pXEsr^Vg52hF=FTW<(#?Wfux&hX`Qqqzo?{#E3NZmxL!i zzF*|fT{Tf5rrtO3Oo5+gIA|C|7(7FFN(8`%5TjOvj;0qjwC@JO5=dZ`}COqhvhfMz-y2Q<7qZm21}SMU`bkraWGeqsRm!2;@`=-^A#KT8JhGxVfS{9Sqq11Ap0hDoIhuUzl`uEgO}{H% zoAh^Bl1VV5@g~e8566Q&7@ewOP#~kSH2WN1BjcVY^^#>hT2tR)_aL&?uJTx%05LA6 zvsYW$Wl2iO<6NfxkAwvzBCnF{DjN~EY~8Tv4{7z>7E1n&adArz>V2?4YuQtfU94cl&vTcgh_lQ2j^(RK6OvgyA5)yW0 zx+6|QHdbxJ*6GYuc{=W_*@(+{eTrm_hD3-@$ze#vXs2--XoB>u$3_(B4t*hQ$12I~|EOxZ zp1-kCocEEHs#>U7MIx5f#5PUCnKKb&U71^bbudd%b)2=*qSo?5Q+KBFc?z#hGn6yV zk=55W3t<_miUUI&g_)SeGtq}J(+3z8;sI=dX9t-W;VC-vjYL)U z?h@d$h5O^d;Xg2e-iYCRs}1c*@5G|rSLpZX7sG2A?I$5V@JdcgYL*<@-kFBCwTc80 zB}5am<1A+arxk*C$zyD(SS-^YL$dP@1Q2U%w-UHtnM^ZrV5+z?i&ZN$IHhStsU$`g z>xVth9jZ(}3>}4Lj5|tFR=zl@I*pZ=_{+wf~73k#F7Bt{l} zfAA0=i_dnV`6P^YdA)5pl^r73C=(j9mQ9ux;dzNUEA@5h)>tqXW=T3x+td=j_r%mn zRWZ&?YFm@n9F0Pfz@QNRm0ktsRWYd%N!5y!1!K;__2bKwF{`r}aT&Gx=)WR5je8pX zhthZ)_w)V1q{`{J#sM_Wff6cl1+#(3t2oJymb{L&;4RZsX*Nh?mJ4ps98NhVre?^H zPA;W+kibE+R$<)Fw4R-C>|rTZ>~nW>k-BX^%0v-oq>Z00S+lobXUM*Cm7Yb4#EFJt zcY{`i0p?WeH=%td;BmCM6R3M#gPDJ;%4iNoLR|bjtpw@H));dmiwrcBP1(0lferxB z`*3gY_4DZ}9a+Kg(WXv|s(Rj*yAv_cKaS(ZkFe1I2M8)MUHq#L;tyvwwe%XU{^jW4 z(a=jo?(Eyp%c)g!_T?!7)u}*=d-ON;al*K5 zGlof{12hvEa7cP5)~iRks^-$Jmq8Y%gvMmcFoNbAPL-BRRf#VvQ*ak5qf)o-6(oX5 zmJE4^kjMUw`CFQd%p6jeziclmKe#Ly)LoI&P$&}Y5qJOm^|P-4Q8q2d(e*2~^i-zT z@8wtH3|72hAGC%#YU8|`hAkD6mMQ#CwEF(GKQvGHc(J3Dvf)&)vz}z3KvvVY7Zd<- z6AI&?+AydP7{*9Y02^1U5Rl#5aLaM+kmc=4?LklqFmyw$RPf~szEFMe^fvIk9FQe~ zQym@h;c-mnrlb^{1))0({+`clUgiTqVU5XhSOMSNOR4|B4)x26d>{bgKANcwgzvNX z?5IAnmMS**a{W0x_|7*3c`AukgaL2ic^hw060IP<2ZWGftuM~{qC{GC}E;2C}) z*Sr<7k~q<}qQ0LZvY_PFzH%tdyx<{PX& z{bzEYPdT42pD>k|(r7CpfS?CC>@>knaa*V{n0bIbh3&teOUpx}J-ev&|B2%C|->F!% zXR0S{t{vp=Ydq8scqxs7j^_Z(GNe1F>l=YxtMSz8|G0i%wo}bKsC5K;KPT0%oaUZV zV~=SPv9YhNUgb$f`U`Z8n{=(O{{!x*^__jv^v}qX{0}n?`(Ks&zaAbNrwvi~pV_*# z0}Cp{0GocKN=!M35s1T8z^&!Odw2p_djavxB?zte=WQN{9st+uQoRidykO_H&#fC? zuYXJ5L+NG~8oWfuCgo+1)YFd#Tc8D!tw8^c;xre^avCv#t|x>3o1?8CR!7Ql4yJz} z$siIur7IT?f;UTQz7wS!4;M%6GVB=?IwQRSoK~j8(E1Nq0P~-N>NUG_tr?`IrA6Gp z=o4-Ly?H^Yel;z6&;YY3F(rCnLOPHsXMuVOxZ=GedB#0NX*bV32=zif*7ZQ40xlDv zqwD}CRw!7R8>2z354u|;+C|d?Lef@(;X2j`rWKs|zNHgOB`UZem?qQY* z7aUb-kT;+h7FQtU&2tIUJLLANqto2DzcVrtFT9Rp0T4m-`mSeZ;r@+x4SXN=K3svb zD>&X)(4aY03)^+%QNw%GS`FwCbUtB3^c&>TYNF-{K-w>$+i>Lz9;ZR-2vl1mSwW2_izjmMU*2KQA?6v) zAf1X)EE>`E3Uo+IGx25e`b8=|Ea&8O8oGB8$2uG%y zB~q?X&Scb5U?{bq(WZ7WHY*4ciPJyeX?gTjjYiyqi;$3*a?((20$mLHglT|o=jn?c zGYbh z>FRj|5(!!PyJe4sePS7`9e}qHj`>-2VD4C5>X$A%?kVUlNcFm%a(-9A82n`Fs2gik z%PwtLbBazj;Z^huKla*d(i|Tl>hrB*gGupkJduQe_<0Nh8@XsxA5YqN-*Dq9djwQ6 z6huhp`yjjEDv4UcUfk<@TNa_y!gcT3W+i6>Wz&oPbz|8TXJ+sF^DnD&Wxw7ULDb z+U@AmD$nPBDf6(U01XpNJqKXMlFuohA6K2q8YYXQYu1zfo~cVcWuO~LUdH%rkX0ODP>hgvLF6>p zSH}+5A^~OHsF4mQ^(7#DA9E5N%zkq*5?XNnJY(D+l~Ev!9pTAAbVtQDExV`Q1Xs6Y z=1P^pBzqNcLf}5t7+}kI3f12H#d_7cw$jL;)$o|1P(1>a$VGu;;`ICl`tKvw)hAXR z-YEqM4iEr<{okhgue|H&*?Sln7@3>s>Cx%wS^O`;z9~o(U|F-xzir!?w(Xv_ZQHh| zZA{y?ZQHhO^UitOdtx{Ctt#p*BeF6wKG|A0>*>)O*?Tyf+u71FGyW&P*+?d~OLl+( zHRP(el(I+&aH-232L+5K^d}_G(1N5-D7AvrHGu}4yB@E!gXj)$3*dpZn@z@`JYR-Z za@hr(EPTz)b?F5ON_3{P({J1F3QL)+5}~Hr_e;%hi8(c%YckE%QusVB;)N zZoG|sI)W8Es3)0hwkl%bfaZ$8BC^^x_unTUFMLwUReLzw5HuxPakslhag8<^jdNaMyTn^jw7bT(k2S&Sc!}|zuJfLw@fPsS zai3%O6adfhW21r=5HWISl7s)~G+B zGkZ$#M2*+FgC&z0vQkTl0wBdGg;ttQEmGaz zK~=r(OWjQ$G0$qSn(#*w4%^V3-2%6p@;lnwnnO=NPvm+S93{=KtixSdTwI|Kl*(RJ zh_ch#XvgqpiYCGv{r!r~iVH2M$_A4}sHqCcgKa&Cx4+)krkjsfLIS$C#q#W4k*OiP zLev!(H4ALaxOHD1pPr!?s1$x5j3_cENIPeuhiYMm>@AD!X%Jy$h*dFaASbP=P*Dx; z-zCwKCnN?Y+!vY^x*HNBQOGS7fr}=Tpo}dlTRL8|Tqiw-eW-l=8A<5Jp?WL76%%TU za*m-u&K)ti3}TZfBgJ$8$Y zf{hTCl!FUo?O+Fm@_$Zn$h>HahlX5HPsj8@R zX5PwfoLAIBGE~KQ_KVm(f+T>}Oc?MCSVL2M)P2Y_1!;A)1okFr%)4ZWi*bB?Bxmg;(TL-Sphyj3?hyLW z=ml5ZP40tS71B*hp0vv&ms0$}jWn8ne2SVbGyTF3$z?e|} zAXF<%q}Uyvivp@AljF1(0!sen=%Lz`FO^n+zZ8J|i9p?yN;qsP<5LH9+iP^>!Vq4~ z&k>=}MumY&|1be{5HS#_fS{BN8-{xz*!{OsCaLARLfg?9q9Adho!x`DOF~*;zX09d z_k~X>9_07nwXhiARp2<1GIa6fo21H1C*l-AQ6~?QXsOk3D0e1oRU6^35T;@se zU62u=Z-0D3^bzO#Fqpc=AkjA*G zf&&s0q@eoZlcoVZ{n8+~3+nSL5W%#Gpi&lNCA}2Aa8ci&o|7jq%RwN2)E++nis=|% zqYGf3BFnp`*_|_l>I8)zpqmGl)qx=a(Ob9ZH=Qv&GQLkI*w5`h^&n)m&63=;2$ z>cgVzFu0|ILb;`c85tzmD~oG0s>Mh_D2+9lP%73c{y+%EvQ{$?qV=MaxSItA$3poQ z;J&2k%hFe%`*0Vi{A18WBr{M=B~1V(0GtH68fX9pU<26-aD^oS;ewijm->woAW32` zwyRs~)q?BYhYd;ujmd)?2te=cPt&KOrI7Cpo&exOZ}cEHprsT*k8+_4fD^c#VIi4g zP&OZ$=N(~+%ipc!M6zaVYX>uvaAJmN8AEFJ8?w3N$CdW*VhCNr?Zmp6Tl73^YUQi zC@|rNjF&ml(!;v-hQge{LM0U))j^^R^zK3@@q*$&9&QGd@H7sTE?2 zgmck$VckQDJ9gs)i`Zzz7#AHYOmMhttd*9DJ_t}V)=I}@8x$p;c&BOH34xVEe$X=d z3daO2Lv9`qea=KODsz%ZB6CoNBjoAIQ$?Dj@##5zfb`G0r!qh88vRdjoh@zrCHlH% z#xM$uuM|bOO?~0yxFo5Zi{w5%m9QfyJe<6cku`Ur*AJWMm(6%XbA|jJ-$F0is&ilH zq@+<(;H~JxtF_^89%g#-`SG(d5yxk-h3M($y600Y3&;D(NnF=?=EQcTob10e_C_<~ zE~k6^LOm9c=>?*u6oc-aQgjo0Mb2!^-bSqHu*xT!{hD&qKHdvgYb8g_I2Y9)&OfHy zC)FDc{>OJmZA;(U0;6qK%k;;aE;*bw*P(bTz#z8x(C+Yvx9jo#H$cEWpwM9Oh)`Vd z{!=z?JcD(pW-l<6DiDwdXVCDRjdtDLzmhxY$0DBLwI!hk=nk*$?JMFWsOJkwOW>-ZSpK*hus-$wi$FQX zD1)=^z}6vL-9n$Qr#JY5iH6q`PHqsOv#ZaEhNGiC{pauLv}yZ+jGOB~7PQq|)|B%| z#x-C@rqx+|{wE*YWin?D!}*14f9cA)zap=it@7fz=B!=yq9S~^KgML?Ueeo)#(s?D z-A1sd$^r)%(FZ#5Of_HLR=99<@Io6bL7D9;@!gU`r?-_l|f^K3MGuqkgW2nwmYv* z$G<48niEE`8CMS6#x;{D=2&5{DGGkQn0mV5!&hx@Y9x&LR1}m5E{8OSPvnY^rxw32 z8=MB2({Y8RlQkT20s@lQrpD2%(`UO|u%%gO7*nfMJ&mf#x0sriO)9US&8>>I#F@j+ z8Qa?#e1<@$kNyauhmR%r&e8~-9t;hgV}v!%#_oZKyu8DwRb?U6>fcTvZ{J~6`WXoc z;3o~BHPL`*cvPNWBgLjgz#8N`-?c=X2ff~hXKBRIc@S6|k1y`_OUCwEvNMVs8SJWD z*PJxXAV)ayN^b%Op%o4z!>!ak_j%w?d2n5o(bCt$*3AE(d9y z&eGu*j=##^$~+{h?7Hjb?PoOpo@a5U|4g@h@)^v20reizRcU(hn;7>?LJNa_C8 zl_}nqfU!unE9HS7Dl{s52~mR-`*U|g!ZUOw_VBgny-Ca;eg2JwkedL|UpUjTnBgQD zd_DHgH5ph6jINQ7K7kwVa5>n3W(8YdCN-nG6+129oa$ale`LPl*xw{YIq42r*6)k2 zi_{(t=%1=qq)8aMB(|$k8c00+)nUG~bk5Mo<<`{xxSX-MA$L0&i1K7b0(7luZx$!B z!xdwrklEf9kr5L%)5;`4vK;h8)PE+xP~nloS03esUo-%iZ{GFnh!I!AVxKN|h!O39 zjbTrNkh4aH?x7q*q4Lm7OUp{POi*foN?r&*JWN_FB-eTWVqi{As&+XrRQ)|n_Q_gr zHiAsuhH5y9b9}2dbY2wfsF46p8i>^$mjJFnoC-&-a!M4P22&`e*yzlVk)kYUVkP+g8xqt)P$M2-$7dCrSg)_^Cr3;Jaw=yh-Rpf>4m*rGAP*3eW$`Y_pHdWUXbj z;fQ)1|4UcD?!*bf>GRNYDt7uOY~$LM(3WQLk^@U5-YUg#E#1VT&0wwN%n`>&?7;`!Nikp|UGh$FL=WxW!WK zS0!4_(DItwkQ>Q`-C=Nyx2Rcn>4mLb_37rwrs54#i_yS-j^~%uz2e9I!_U($;(Xe$ zz%7N|4c*ZJD&_1*-T+KCKFK6#s~O@Tp~PG-l!<{WA5*SJD>etj_k3@B8=bp21bq}7 z;6=y#YDns4gErFim_~NW;3#qxrC8Wzkw&BY}er0^)dM?|*IGO)8R>XFMaLoWEWxeKg zkvWxPTqn7M?(IDKv%U|3xho;ImGqvXco1)poWH= zm*m|Xo3TCanG&#LNzHYG0BM0iV;NV%y<;s|h(Isjza4p;8a?Eb$zzT%xu=&`i_~`v$b-pNUhe|NOg04i@ zgkRng=(;JrF8TDHneFV0=qkK>E|#^g<-a^?iyvHyRRj zqka9ZyFxpA?ts@VUsG0P)OPWH>iv%H5o+w-h1Lpa*q2|MV69ec-MTtzi87~BoA?_Y z0#Aqrl0E!}>}_{q;CRCXwlTa*oZ6VV(_U0i~Q^^ffInROsqI$1E?Fm3Oa(}Ouj zcoox}LN%czw=q90e#sM&5KOHe#sy&4MSS1DCLz+*Mi8u9q$zQN;ABGJ%!W9KtV~1I3`8@BFQspkR_ED{RsGB0s;VtrKg=!_SeSWVvLN2>|xP2Q@7d@*+;9j%(eH##9IS;TJU2{SptqP}G(jgRt*o!fsU%Soo&vBV^6~ zEkhKAtRRl9hmY;CH#sYiGds67zl}O%^ZG;5&14w)k&`l?dgnbjj0AKb!&sf0#fJ@_ zP~LocmeO3je4NtIz9o_D$VmoMg^3yOv2Q{ADr(LlN}gtWuMG1+O%H+T%@)K*O&_d0 zIhdh{#sRIp|5%ex(WelY{e1!{HXX~m^Kh>2H&c`ysDu;xiJd|i7tw~$r-C&Q#S`(= z0jza|UpX9Vtu~iDf(?U@$+rimIhGXmzzgY{Gs9~C1q?JyGf|WL2k%rWcCHj6xS`I38dR) zehMB-%qyBPBn>Gac5&hlE6FKlhQO>%?M(~tF&&njU2_F7Ycb73bm}as1oa(SV*wTq zcl4{i>8VK)CENmw^sJ6=)?Bj#Twf4Y9qzyo2R&2-$||{hOiM6zk`cuk+>wKJ99-S# z>F9N_(LY2(F4s_xa^)pkO+dj!OWJJIlfP?`{g#-v!3wYRQ`VQiY>nH&6ro7=~p`y z2A5OGkuK%?jn}T{97XkgX2qxKS<0`@`|U2wj7CPI|bGyZhE`F=qk(^Yp0e0MfYLe2=Jrrk1T8n#O2|)kJuCN68`1|k# z%0Apd6)>(K6RlW&+ep7YXl*0#tjLKi_}8Y5kSqilhALCyPuS>X0(_8jc)VOdi2N&94Q8Nqm9we@J;Z5U^D2|MUv*Hn;@ z2@VrTwgP>a*m&`j0+9GkhDEh_%0|s^#=Jof_I4P-5GwGa*KOYh)}$(IZrYY3{SGHS zG8&E4^z|BPkCLizj`vwgp{mzq-StzYH4j0_~=Z{EjLepH)nruS3jG72&h@f=`|KP+2@wj z(5!Fr%+y*;!m5Fz6(Tr^$M-)&)Hi{>X?&G&t-5~t{>V-l>ZWt!;I7x~K<~P#7MGmF8FiGxiyY{110p(} zxB~jdIP=e}T#e=i7XBxcF1hV7vl6Ib?&Dqr>HGor=o*<&!U{WLA-An$%pC4Cxy&*M;)~T>oBC-7j9Mi;&$(S+VWaElF*=Q)~JMo{CJ3Vc+2a*vD;U-hBId5Q|A>Neb8JOnDg%OE@6Nomdmv@yRDf_cub#@Jjp2$PTYt zYLDLg;r+s&Vhp?Y-~4xY({J!Q>8pT{28{X@TbC{ERVch2nB*O5E!dhOyPO`UyP%W% z_-gM`9EFGT-jk>)f=vg;Z)A=RPC?4f_8|e(?a^17>t|ngQoSGL?m~*23sG!pR!6!sT`l9UEXj`##95wKeYeEz_=7Q9@6+cwJs}nU;o;y zm!~A)%?&ZP515V5l~MSk>1zW{;Kz$R*Q{#B7#9um*`|l+QVV{Sfcetzg{Hx9#^V(^ z}_sNH6(PCrMQ;nO=8?^fWxvcuxeglI0_Ci7RFF@R zD0J-iG4%?z8KWbqv^)Pqrj8bTfd=vZ;&rnCcs0E@6>*&t4H}HHr_$Q8OY??jBJZp1 zGJR9$&>n#FWDu2j8q?5}Onv{M0=dJwuTo8((=U1Ne*)`|S))43{DcGxi4)%gJ;uYC zsLz3hPx(>#V-Wiph~zts`@yO7;Z&^owI+x|{*&*taH8jIw;mLUOXZ6DOxSX|bywym zBwgw=*D}x%%kA2KUCbL%&`MZE;eh1ojegSI=h-ScG7T3~R<==!GYTCP83Sjg62tk~ zi)4t0Aj_eEjnp#P1Ha`kgc`$GZ&mB9f4G5mipJUtr&qyNV7Y*e@Ga7K`J4)Def!X&~A3uj}p zA}?1>Ev1&lZA5F~M&m7U91c+xgPvgyy!We0onDu^3W$+^>!Z@=7TXpyA`~Nm)&+pn z9e~ILpkMvWVu0TV3Cauj=OcI~(mf3c%uQd$i)-!&WPV&-UtUg6+L_(t=;#Q7%nbYW zNbrJ3`xCFmc>B9X0`cNhq z@$L0pnyCY*tD3}s`h!~f9}w>5VjprO!5#HmrXAQf(_ko&;JTR+St;;De z+C|&z^yZQAT4A@#usT7jY#ocE+PJpaSasI|@Vbv!XfSNFn@e#9Rv-EvAxVtPkTlq| z=ozYq9=$_t$65z$j{{ax`~yiIouq^UG&b(@Zi99`Dj=wK{vvg3CBPL%MGwanowq2#dD z37b!!BW${=v(Uh>prS54DEP@=KX{O9WcUQMAHfMEqs9L6OjO0MN}f>gan}=jje4dC@1) zPMKW}b&(a=nN17HZU-6t4lR{AY--yOyLxpAec^hTf8ma zzhYfYt|CquhejMp_tl5V+rwLN^75+Ft{BS*}zI~b8qbuxUY zDJ~qnulBRdb*^=ov&SKGhCWn_xbGVVt!`m>BFT1-PO5ewR{s%OCurThWtO+rC%7CJ zgr9kBq)FI)K1MPwgA_b|C?6rXCoO6nQ+Uu#75P#IDerS5K1ec;7iq+a0mZjoeD08E%+%?3u@!UWzuPYH_n6lt{0wmC&%NO+R<$+u4Q0r*;2K* zdWPD@2gxkjF|s97X>&CAot)QF6NGT>du42;QyOn*H3jpDfcTNieQj}Fan`bt!sX$1 zB3yQGsgZE6`t2IN{&`*h1klw{EmNONUo=pdH5s3QA=6(#7Ue?IFc=GtgojEgYB!-t z`8#7wRg-28Vg}#NTtiD~lJ;4b-GY-Ct(bm)lo=7BB$jc6u5m8gK7p>dlP+-zH z&#Ywc4*xvASYOK6@Wk)_N+C$y|5qE z^?d|8I55Yx1VHGAi7XwabBoYCyo zf*#s+tm@UTowtIvIj48{v|MXQ{Df^qDz^yK-?yJa6 zK$iZb*DB)xoqaH>T*nSp;ZD$@XL$&-q3tEH?9RH~B{9M~)w~KAh7j zmQ4mH7aPwwl@2Vs=M*@vjE0(*+8A3yim-t(*8ojsma|c4h8e$eL6~k=(hMh6ckeXP zXB_B?GU7%)A=C_-VBGfsiO^2Zm{!K&D!HT$F-KqDM#kYNxdPi5&h*WP#G|VD21zJS z%V)E4!O(k zk>btvWm5kZ6AHQVRZdJuZbv-F>$>g4`y}(s>rHQc0|k|D6DKCaH$DFwa_M$W@5`3~ zrKHr2GOS&Qvk?WmSX@X=h@}kyAxxZDjriLK>}8Hd;fF8cV>LO=k6^%OA{l+NrBhHR zJgl^YlHwcUsIk3@AUFuPnp7xB>YMmAk}DkHG`IU%$4-*X#=>fSe!H=#z(VTeJq5wd z2eINs-Vc{oV*)J{Rc8y z+2-r_#tSJ<8&^tSPINtnP zbbck8-?mNO-J0L7O`lzsZ{AIx+D+bhn!eqd)pI?qG2cZlk9wsk9~DP8Y7?myM;H}H zG)wmg zZ1;RRk^ot>O&)e0HcvFTI;(4$k*R%pK}(Jvq@v|`MhbSWGtUs(ty(5^n22Ci1k4C9 zTha5S;boVJli3xSh!5S1Yba~RP14Y-Kk`g6peI$4<14& zaPy_$(2**>Zk90gemUj3@}o(U1;B&$fhk11>pTQUE}ob{P&f`u{}rT2{gsJ79WA@b z^^>qICuB^i7tsl#A4?KmFmrq6VRSx0MX`#9Wmb5#dho@~)K4-8Lbm!H^ z&a;fYYtw7r_L;rw@^T>(=}OH(8$a&M1m^^|+?Su@26nipdiIZ{FuV`hC21CeGZV;1 zOs4|IbaEO>$afP5BXY+(k~K}ZZVhQBTM61%qAW;JbD4a5*SzdwD1(i(d!nr=h}5o? zVs1_1-@VL4s#aNQsdSX?-E8;$@Z2;{9+S>n?;e*s$k^jdB{_rV8I<4Mp4shSTORZW zCSvsMA>w}Oe)gLggGR0J(or$Sou5sUw|C_bcqeVn?b{A2NBA!{u^+VtM`HRfoNlXYx1Z77N!&b{I23Xaiqmoc z-KSyl;k@D8+P#3uu|>jRT&J*5$2D9A55y;%o<^${UP3tLppTiJ=?NKJb|bt+ZwdteIrK-IJzWt!o+*t?ZJ z7VPEc-jT99TqNM5Hgkn3J8_4>xqG)rjG4q%?|gkw!t^oe#58kqw^pl2CjImJq+tvq zBX+U37TKRjrx4;idI<5ii#_c>SN$a^q@?~QMbsjRE>vME&0kHqs1>RElue6LRg^;) zWamrS(ei-xx;%Nh^ic{}FH1Ujor*6dLNv-HGgax_MO7LZg|peWvuiUkpCrk*dt%IN zNZPprw0E(PY30*C7@r0qIhFmjIwH)aG_|}fn5#IZO(RGnQ3+QCOnL4bM5)I0vRv8{X9 zH2lItrkgOgp|4%Y>wPzqkHD@9Ds zH(dVq8qroe3u%+Od7ODk)y;-8$U0t~7pZkDBA|1obKz;XKRfxldHUs&*{bzAboh}d zbwzx(5)wu~qeqYM{aRE6u@J@fr`$GO0ta*kg`DZZxl((u#|!uP_lK2;UETUC&Ei)a z%DSZrf2n0Z`o(q4^=T7%1r7v7xC-q?pjag|_~{WFRpUPq*oi35xvc4;v{vL?xi^Zj zD|e{tyzk zP2~`JWucIa*NpMRu;ur~QGsEH;*OO7x^sxR^q-xi8>vJ*r?8z%8Uo$Zw+g$j>JBq{ zZof+F$sA3N`-g%XJe_mC?>*)hrXH@(xg|ZcZ{K@e#>ekj#STPuQg&|64g6Ju15L!t+bagy4_C}~$&-y!@?}>VWx&b?d!LHF6VxhB z=rGcWa3zOEsa6+?X7tJ|e)cf(PX1Rmlc%LGo+k$|@}-rfu!6qGYtT8ZcMam5I6$uD z9hKb=e#Y88#yxTU^>G6`@9M(vu=D1&_#aP=;;2=PcZH1eJlHD-zR`)bmZYuug+m4M z@Hg%0&TNQ?HU3+bIWM(Z|Mk?}6Ca97LE(*8fM#=3&)enZwcGRbO;*tg`a1XMd7@Ok z)pdVH?AzYP-y>Vox@e7Xw@Ex|GBYC@ypD;-j1@Jmm!z|K=ePb0zxsKv+pTTxF4)aL zx74l-jcb!*Bd0u0ACnp3Bb*2Rv?iqYu7c@`BUso+kd8QwYffDzv!*qioJPgqajli` z#8X@4(&4m~`bxU#!BB9w zDy0CzoqktpPCm?yE{qQKANhL;d>{H>W~}VGGpW_pUWA_pR1nX+NZpc6?x%X-HDkX89V*mPx5bTuWvE@Hf_elQ}Cs65oBmbwNjlH&Fo zwK%e9HYzK$SgIy}%MCDbjm98;m8V<6R6_j44lS#X!=Slnt`y$ytdwld*PtG*%2d*b zR09iG5s}&h3@f9G9k~af5V2-0xJW8HC##SppVW$kR-R^-+OuKJEwaET9CC`Pg2${l zs~Wj$C@U+dk_ykd2=)^um}j6DM0OI6CXm8LnK~up|K&pHIHaAs1?2C@y}wUMM43yq zTi3$aS;4^mZtoKYWmPgZP*xXTNZ|lK9w<$x(;^n>cPciNB}-H zG!`3zNu#xvPzF;qQ^p(4qoM9G5=C~kl*h6dA(zw#{%r`&WW`vXk4@e<(7BsZz^X$~>ag2_Ff9Us|50Sw5v%Su1P~Qf4EN7_Gui$f}yi(T7YYn5;n?WB^pF zJhaV-5JzfPS1Y6nZe5ygPLohLMs8=H-hh8ryrHZP)CAYdwfxk$=I_;nY%0yLQ#M)-E)85F}>qWlIP|xMDhmBye3*4H-sv;T8{#J-9>#z;Hd75(AvO zfl?e$O2m{DayxS|oHNMdSTfcre+3q~mDcSol&b^{PXzs0uRx?eS)+hJ-|(gl1Q=Z| z#;qU=XCj(R1}j>8B6z+E6Nx#P04A{PpE@vwIBDHr-`j*LEL@5-@=brR-9}N_IXY;~ zW+!--Q_AH*Cg$}}R)6a%0}Xg}mT=3@9A7_(pL354e9Q`Gg~1lH>>lB&9Y z4Gi4XF*r<8LSmOV(6*n1x7 zLL;U^r<(r3Ej{2@G8Mr}>+NH$2Y_He+p4E7qqvVvXATu>&7#uIDn$@f+9)+tD~4dAls5b^VL0>JZ9gHgfP zBR$n=5R}9y`eySMYvG3%l_4kz9a5^0g+q1YC^PpolH<`75;cTV(#9uc^BlqmdJ_P} zfI^u1;@bq%zHvkZ;BJ4iz(1|HtyXmqk~q87E}+|RyF>!M?Iu>#BOq8rFk3=s10=G+ zYyBPO?nx^^8@Ixlh67QEa09RbESrqCLSe}R%`BDuF_~X6{0Ut~@czKqxTul(TF3!~ z#KtD)^_3awUjF$Il=70#{Y;g%L~D$sxX2e^Is$?I9NWiqD#Y0ltF6YwMD_6oqxOHu7sV9e z4hu$sX(OBVJTS-VU4|H(h1ud>iCR*=Pn&!D*omFv=vKS(`zyhNL_8%bNP&le?aUt_ zB0E(SO$wPI?i6n;ieYdRXoC(N7+~PnZHaLkz@ybPkaF#Mp-ChJElcfhC#*Li0d!REd-LDz`NZR#M{f(U=2Z7oowO%DcdT zd;*zzsBsQ3kT`hnZnB1E!ZiqFBo1geN%njFWe5^V2XsZHMq()cMH#{bddxs@c3bs! zA_F*7I72m}-@#-+IKh1K_|OPa6j5?z$vW3X1GGqks*4!wAqzJ46u_I|C1dGijF~Z#3I6FPGI+>>Ktq_wjRb+obO~XEP#6YK z)TWc=#Iqs+cBpW$d$gfQL}n^vJsyPiD0}8`Jk%4^kI;#b_&U40&7xU!vRl`LpIb!2uG+w@q0 z!u}-H+8Y<3jy|6>S=LtRzBhYfZJe8U+94gsu%5N+QgpVoq>p1S-F_Q(=(ybE*tAqQ z@M-yZh_ZI-d;hiU4(L$#^N`qUtai-`w?SAnXRX3lYZN1hqKj*}) zHl9p!blIQBEeyNX5w?w^dCkF!qbun;>FpJXC(yIKFIv~*X3~%aQMZIKH1ysbXjs|o zR^0wUco=Nk)M)5pwhmL#AzM(<&iHPh_nqI0S-lYc&JDt~-02^Uyk1dLh%<~(gTt>I zr8ZKs? zRug1L7I|kl;oLHVa6JZlq-BFira8D&V>h590PN-hbEH}>&tDLTKesM*9J;=|w62e0 zz?J^SEIm33Frx?!hhfL%#(KzIHA>_haVaw~6go2v+4+H!4@ z{Z@!iq{=3tU?(M_WLG4Cgm_mpIxt>wHN`Y)kKsJY2NG8fg=LqioR^Cc`rqmo_EtDyuX`_Z0^MgN8=W2*zQ!bP z^I`4qmK|HFrb%;xk&ctXqt}4@kfK)-=JnrF-l`u9W690W8lGfxbqSZ5ydMrT0rj^B zBT~mBR~`Rs*`EyGM(x%K?sh(zUxJH*?j2j|SBt$jo878wH=pg_h84A75GXc|PuYu- zo407PI_%_kI6SE@6!c;w9kwD%w=`1IkBw6G{Re^bt#9I<;^cL zxs^R(RGylfpE=u&<7N5iP~B3q|1Pn4acqA(IL>GncY1NYjn2StE?uLiAV5n=8ZZ)D z*V$PyG=h7IVLu~Q=PM81xhcP6u2}mm7iWlgv&7gb5@?D3cAvLrbj*!OYSF~rU~p~C zizYNVui>ZiS z1qawrk$+Ax^RQ!Hbeyw136 zud7_rHh++~pmp@Jt~KJ(xdLZwNa}#zlvX@!t8A)V-sb$y=R14AeI<~ma;>8Ic#O1W1lu#L1(8U=KHBgM3j~ zA(QqOdrr^j`c?tzp0)?QM-J=@pz||?Gsf|>3+Q!;!^gpaC)6O-Z0m6aGWjAJ(XYwF z1G7+(|He?Z5l%v=_^vP~DEQJ*@Woxh^S_TBJHpeZu5#($&T5kTPSXBPFYl@)KLMrA zvq1OD6c^%>BWK;iG1j_*b1g^4#vdaQP>)=NtHpyIJou7Al||Wo2HD--0NNyIAvIm% z|4{IF0r4^WvD1Y2*{t)cOVztZL_ZMvAI8qHJ+r7=voR_aCl%XxV%xTD+qP{R729^j z72C#>itTi|zn%A7eY*d_{;>C6>zebv$G}@$Dd^JjD?F!t#MO%XUDxlm76{JsEOep1DP7U2-nP(NRQ zL@Q0E4F1d4wk3H<3dS6dT*RQ^%B=w`Tjk|H8FIn)3#d*n zDSL>lUWJ44JDC?pP(b}7{#;R&9?ORp*+hl=DTeIdWT<` z@^QUZkxY)}=~#K+@?U#(~iPGABO;D%AJv2x?u0fZuFsk=Q_J z6fRvmU7(-?m(mQ9zwVb*H#Z2M1r~hA?TP<}xvrf)vwy;nut{w_UGy@4IrOw|M7FCp z9AT8dA?t3H4u%k7vS7ah2T1Aj9ZWMmsM|+QxsLZ@Aw*{qa8D z0v?h0M#E3^0ue36f%Kp0Nbd@)G6?P*E{q~69?=J`EyCh+v9_NJ@ZSIIhsO$|!wAfc zWDyeB&Rw4TCoDAY&^WyoQ}Rd56P=n_PO@<2?Us-K$yls2z4u&(lH=^_bFA$+sPM}7 z-@VyQKionh`q8+)m|T17BAtuA!>?CC=e(cNZJiD2hUQ&^w-9>#BVjH42eVe|Tt(%R z_wLq0#}+Lov%&!my>ep}C7N$ZLOwBTt+5s)-!?_Xe?*rjk{ z`phot*-Erd$!+@8+-m_FnD9WZa}SMfuZ<3`#mK5jE(7U{Ll&#{VtWc85?L$JL!(&U zrP@7P+r>DIWGTg0^5U!uq_oP-GEcGB0lil2Qtbo zHK(J&h(;vE&a|NDANM2fTc^>zlIJ}_G7CV*Yo8n+Xdi3aXBUak-rA=my&KPc z|26%xwo}({_>FOc_|CG@|KGE$|2zGn?`CcP-x4rtQ}(FhXgd$J{MWC+VDPfSzz`A2 zjgiPcl!#EJNXmVXka#qN-i(J!a=k`^<#Ri1@d}6(DF@;l1|`Hju@wBl4u2tf!P-R& zQF&g+Vp3Hyx{K_aERNlK;mS&U%wc=;?QT!zyX~tFrk&9PMW}W6Hnazw7(+j^b*=s{ zJ5>Q51GLgH%4(EZO%jEl^}lC;IRhA;>n9Hd$kbsL{N>@X5{yD`M85gR0I@=X-YGOl zvfA^msb9uYXkw0Jg)j_K{p?UXCIDh`jJxkfQIc6hC3YpS0Ba=$Z;I(c(Byv69;9BnE7rb#!hi#fm!1gt*gt0Rj~#BRpAQc=2whaL?hc5|_4h z-0W1f_S~wINk@%k18bgjL01BhgA0f~4qsYZ+q1G=Zp<|#-c16|zvr;3)Ob2-=sRhj z3^`uk6TP1g5!x|qFp@8~(DI+x<<~y#tYCw!@6P?|SWUq_M?(()6)}zM*v4;C3jwPs zUkw^+Y5uV>ziniwTR+6*MpXdOSpi*wrR)EYKESKSup*4tyh*%D2kEOn(B^Z)-t}}n zdet+r_OwpDn&#?fb;D4AWXZ+^fYu~;qNYyL<*74LQl5#dz`Q4JH04S%Ti3g%9vCJa z`Sr-*S}0X&M!MF(K%N~*L@$x@cYT(ld&a#CziCY&AumF4nwPkl43;)tCr_-aq6_(9Ei8UQe8A**S{QX-}Jp&1qlGES%k!J4j-FXmY&ha;zaY@P6-KKiEBl zdxAIof41%N2jl#Pd0O}zbi+stlP2OOWpd*;zi|~RO*Wh)O-8@wLx<3k7kv-A?|QM^ zy1jt*ghw%+c9^p5Z7&mdL}lsCsDH(kWoHa#`U+vY9i_koK=L|YI`#w(>xs%?rR|@c zao~p#1vceE-vk+JK4Sc7e6J%4!+VY#pXWcl@A(&4V;WqYal) zc^=|C9JT$9rsAd!xTB6mB!LGLag|6Xn#65o4lFJ{mJk>5PI#=o^_Z(}BL3vqu*xl{LNv!{LkS{~f8A|t;*if~{0Kl^~nO9NvTb-+q9OGI# z8%_qD-COZ1oP~U5?~FI!72j?H>umi;^Li@Ylo}iL(CiZ-6s1}&#-D4gpDwKBFWTnc zl(!4@C>EAhvYlSNysnR2UAurHMpvJ;E>WKk_wHT1cJ?Dbupc}tVtY?)d^DKhFZLcP zykF-tU+Aj!(!3ULyJ)*MWFNG6?YK-|?1+u=TpZ^dUwxLBy697Z(j{-YWt0D$g7j8O zl(AN>fM*x~E;rmwe2ucqmZncg<{&!^A~K(dhLA_rHKP!BVcfOd;3(|Qw{JEE1p)OHCZEtJ>DqMA9+h;E z2#oXc-Zn%ZMP(2*5=%PUgHgN43PLlUh@V?dTqmC+%vc z(XFK!&~zr$DZ41nVi}j#{HP?udEttN4Fj|Xkw}OA5uwl@{VAvjuj|08$Ou0$wPG1Z zC>dwtQEEUHS8(#rL>5U@)`dq>fo#g)g=>yY2NVxjx);$HoCmKtH`ih(9c6Z!6=c3g zPL2~qgk8Hwg&9wcnN>v-bU@u{EX6^B6?M)iIqMSLkru&RWaODg_=C+jEDq<^HE37{ zZ;L_D*+x`gCg@D6kz`k}u~aK4q>Q_hvNMR3rU<|&_-B(<8+A6(0;|yyGb5dfVN#83 zOt4Ky_ilD%7E@V7)y3qPapR8%+QuxcFtQFRJM%rQR3%AO6zOSHArAIBOzB}(6e+fh zlF4Z6wTtv3`lJhZYfG=aO3 zKx#3XqT-POO5Ir}8hZCI*>gy#nt5n*mW4oHMhiAUdECKAln1 z9W5i%&@$#-+8t6k)I&uCOmnFFXDJ-QA7jIuG`b-KJQ&qv8QEbDEL2;KQn0$fIl=8( z8q8Y(i-pU_jbN93<+TIlvxka|>x#5ns`odvDW4y9e5M%r6ZrXO;Q6;f@|N7E!$&nauZ)XF~8 z<~~2>`AX#TQNrX~0_2A?ejRh1BKSq$_QwgUD)c;YuXK2i;?&|c`Wk_)WL zNQQ_>B3$Y$f#cT;DIm%V=#-mt=+6Z)0nwkT3(2(btpy7bEHg>A=zIMvnTG>UJQ$3i zx`bh?)C+f1@{c+glF(2T(rn8=VZbY+J$@4bBN{X0Gb1{SYS)VXuq^xrU3btHH)1o& zxzu<`sbn&fEc&KNxnas^Ey#jm{VWq%Xbn#EsH|lebt2txB%2AmnJhgaM9^XqMk#!; zz|WC+P25Zd zU_p$7`G@loXd5jo8$vS?KRYe6($1-hb}h!3ld8vG(CkFqPz&KH#T00>+F}B+gLXah zVjI@;h7x*QBvS#1qF_Uwg7wJUWV#|cLIf~OX}`N99MZ_W(ZCGM@KZ`S^QxfM8z?zC z)1pJ{8L>DAgn@*UY({N_*%6LZl={J@Sqc#lI3f@Wyc%If#HZM`!6zIM0~v7$B^nCp z#2Wa61eZxRI&G}jG<21i*gv%d%kOF7E40Mjvx=Aj36W*}Y&wd;idu1JToGOfNhNU3 z0=I$)(&OYPCC8_=X5LU|NMRGAVYMR4WjZi4NHWTeHsYxnf~g5{=dc7!3E#+e%>q7y zxu{AG1}|txIHtJ3)pCQ!qCqs}(!`9`(~4GNsu-^p7eO~yxTPQW2h>GiGXXPam_ZiA zb&TpR@C#}NK5Q{#ZiHA_v4(^Dw#q_A1J$g*H5c*NPyx>!5YkDkD!-C`_6^G)}dD+*5t%a;}z2>dh>huvjtWRosy*pN`8v@NeSEUjE0`w50h z8D3_}eLeGNiUu1^N&P_qGKfn!LBU6bS&7Ie zNEC`x{kAT$sXX%tDMy+zXsT3j+)t+F6!Mgu~fK?k{ ziCGf%4Yvs7W5EG$1uV8?QNn^vU`9&8Vdfxk50sCc@TMm*M{#4o;%)zNc_wMB4dm02 zR+5E`RJqG2WSVkQVCVdc#EbRN##@G$Zgv>?n*c`@XMz!JC8(%B#6=@;h}HsPZ^;nf z>zjikhN!H-8(C^j+(;B)KyHFtddi_<8r=>pic6dvqJ)-aLd0?^Gi(H6xFqtov5;o9 zR>BdZg`>^-HT`)usp!5?6$>Cvzk`2%esK`zgw^Yjcr(mfU$ZI9?zpMwm%xp5T>F}` zg`D-xTCuPj1q5*m)AC?*;ln*nOWAr{K2IfD^IqwBZSV8)yKvWrYCc<-F!-=|#l9tG z#*aW1(=r7()A}Hl1oxRRBt@oZi9^2xLTs@j9kmZ&0(vVvFhORWQ--0=9ME`s*#_ao zpgIN&>>U|oHrPh5A{<7qhCJ=$9oy*cF<0~&Xv$+sEr%)&ZWRx^KkWFd2*%sHZhg#qgW@jRI`0&BWQ}vt{m@2{yJ(tdWY46~`T7zr zgmt%Uf~Pv?Gat5ed%_WRoK|PKSWSbw?03RGN#4G1`0x<4nIcSO4ujohw4?bpnMcp3VSA@0-LE1}q@H%bmuu!p|MtVQ&0~rhX=yu8 z#$G@27$|-!wm5Nn>^M1qcA~RfuXkEidAFsrzjbGx;Y!%B7Qo0v=neC~yQvVY-jy?k9E+MRr|IA$|y+YLN>Arm&t zN-PfRYMfK1QW(FO4+WVDJG$aOD_eauwe$WO6EZ0O!WZ}#h>7)Ka4K+r#;s}cInAr% z?V`@Nyp=lc(iIph<6PKrk@)Dqx4D!(PQlYxSC$+@oEvZc`a`^ZOxtsN`p%C_r~b!R z@T_I;ypaq#Sw=K}RG%dxa}QwlMy2~qWJ*HzF;Qnj_Qc(8cH za)$!$7Qks-x=5{@1Zn~W+vb!Rl|(BP zTr%~x&n}tfSc5y>bUGVcm{dJ<_mTR5i%&c5(hK;FL)U&tFVuD^MtR(JG{k+ib+8~- zB3~+a;&r$80sMGzkoVH(`^V)j!G??Ch|QdU8v|>@ zThFJ|tH|ynbOf%8e$(A>%_EmFktIK!0>vI@ok>;I-^#X@@kEBD8h?XHamkHw{+K_K z4g=Gwn{up49mYveC3(F17dn8QKETNP&y1+HnNi7sf^_Dt^poT0(irP&n1paw&;z;% z20R<^BVMt+_~zsVV3H2~!5p~T`AehpYmV5Wn6;;eaN;bi+U0yelMYRn^{@O=!tXO@ zZMoX~a!pp;JgH@&zwtS`sT)3KQ{(HACRQQ&kwrjjM!K~G-PA2-n}uNsX~7J#Ku1%{ z3$;sGjU&}MpQs)0cJmiqmg3x(*&grP)I-m-EA0Uscr(*#7S^2X?a`G04!_r_nGBpt+46o}-H>o9Fz_#phupB>**^14XcVH7 zCe1u%U-%t=tlWC>r+E;e)9AFjjpth)9^S^g{OnGlch~J+&oU8d{meWIzZ@p`FRN#r zZ#_f8wg!@5(amC9*5rtKC*dO7_q!Scy)Ue;x*A6m?P4SnG z+0=O4(|>2fN1@m^I|R?$frJF5Qigy1OJUZZ+HPz#7 z^!ixd#>UMkE0r*}c`~c{UH$FuPJ=uCt{&gLFKkTP`+1FKKE=_mG30GBG{RF?q3H?I zNyd=MQFTa6=UV988NH8WO1W?(?^VtD~-CZCD!%Ac_DM!WhKBno3>?!UB|zi z;Pa}r=JEAajoxj$^&RpAh-W^lsl9j3g&{3gjVU8JU+>f0@Uyx8GO>PwwcevhmLHDB z62lsr)})>?6|dUU;!Az8o@*i2RgfLd$+2RYlOVzgsiVbtTjC@ZZ;*FYENn!mhvy8P z$)mQ(#dkaBv^{j+AKuJ=AoJHPgMy7OkFE1YEX zfWz}~;BIn69R@X)I5(r(zF+<4k?XZOk7tq9Z|bgx;b-CsMje(U0w=g+*}1A9RXtBr zB_(b13V@c#FWZI)@hJ=Q7NS5921l~()%LO4ab{@46%_GtPJ z53V24HqTo9y~~CL&aLLK%Fw*c@%`T15XH(uezL1@wR=irQslJZP?> z^p-NqWp(*1n(1W3(g^x2Nl(FBS%U)40o;igW|11}po(s#jb|lWt8O+|#=Oo>27oFB zMtdlfGdy!gm?fJ3S)<2q%}t33E3ev38HBrNUM#p5E9|%KOnlg=C5j)K_Z3OU;MN7h ziU#naI1%exzS9FvG-K-SzjV5p+gWBiXYD8Jh9$Dh2&XDWO3$|!p5nLjH{L3sr+FCM zEQ)T@IO}@$e{x!#M`}EVC7#9q*%r7?e$Phs0X|9j)UVvEzRQhf_awUs*IV?2c{I=t z1^t?AqzU1P$co(a@nn4*naL#3ObAmeb{$@-*=a}2vjW)pp_$GcV|Y$?ovO~V<^_9B zwb=l?!G0XSu(%)ntb0KFYrfVbcYH{kur~ncQt)1V|v?Cse99iTE@Jq zi0<6{hTM|8IJ9lx{HYg8dqp6yp-&{kKv67d`ck+WkZab`DE1F75;_2rro(hX|28aj zTSgQgsa`l7|Jh3%8nv5@Os}lXw$gL8n|v8d-=7)D?=bJy#NB4htmbxHL3JbO1l3U1o*TIALkL$l-Rcb5yeKCeojNrWU_agRjhI&STrH z;C^>_o4Phu!J+26#yZ+Lot192T(FX!L^GO9qiDVDdpQujV(&pjRj|@DtfwFCbjohQ zr`TjhKBB6YAHk12V}}>d?oaCHN(lf|g+E(|&)*#{GpChZJtM=;H!7*%BD(j@zpap{_xu=*8OX$ zzUozv^2&sDuKs+>s<__X`NsaSwv(OOFNk--@MGiW zuKeY|rX_&98Xr|#Mt>22iR$dTfUr05v|i~>LNJi*zFoqRYZK9XHJ;n%q}cAaN_CHFH_`&kRF zX1~MsDoB?CS5P#{?%t~^UwC}E3nvIftH8n!~ zF5ARP)a%QJyr*q$?cw)e!!x)ty{vK#sAIj@eQB+JWRC0A&+nm#g6`wuS8xyzrvEV)@P9TGekuxIB@xp55UN9YWYnzryMM1#L8WTjNhw=l9fc6h^ zr^nnkJ(xmehsA`AjAZUAvCOd9mN7HidE0(F)-E&vXN~*vy?rDC>FfUv$sRXV2I&e2 zfFnu7fV9Sjfd+#90%d#)&{*VPS*U_fj!BjwV5{ENVC;G^y&1m!;T36SEmxvcIjvH8 zI?#BS!bZ{;QHl7kggn_|3f;PV37&VnXtq^-IYZ#p+8u1Yw9w^&FG|ZIudVxK>9x4y zwK6zQTnOPy$WCI`I-R)!!gp7iB*6`~NW%&(Ea$Xq8!PtQvH0EhZ@50b^`05qEn7?8^BE`h)p_Lp_|Jd?I~rLGvcAAs;#lT&rZN znV1WAit|<Hc`<{ym!x5n-B#(CC&EVq|B1p0P$1M9W*-`n@50fcUQJg{?SCC@G^Y3?PQ22{MA*SkNoY z&Xw$RX~HolZ>0kDhFJ{`f-d=lwc_=-U+qelx@kNeSGJczD16TJjG|-f$!52SMw|{Py1K%t6MN^xjt?F!=Jo?9cbOhE(;KbM-niEhk zig1?`C9k-?nZ#qd?Uucsp)(lVxg>VzI^@Xbz-q3*HOe6O<;`=g5!_F^^C$)-kxWRT$6Q zpMo-_1q3HXHeh&w8F$z_oT-xpa|}j`F}nGNJI8b=1oqojrNO z`RmVU@2bjcPsCj)B}vsYArV6Yci<(F)vFb6_+ActmLw&QY3u>hIeTm58jGVMrZkMh zOUFzn21DS6aPJ3ofkk=j8cQ^h$ai!^k}{+`8)jQmbM^ZbCBw=}E}CgP zWj|`jvS=*gZ`k?YEL?wJ$3gVi?P33D(@Zg%=7VK_?5b;ugqawD9L>17SJF(c&+W*& z!%cCEw>Q7D`%F8Iw7vladNfw7fsBy)x-&Ia+~BnX1Fu^Jh9nC)3o5v~O8j=vA84|J z+3rLQzhvErG99Z9;=OPLqW21{*=JrTgHIgFPbq5${N8JS(1%Mcq`a#vq-|-L55)g^ z=5(_bx*Z{bfUJGfm;nFtnZwEQpU2IVrnL=@x?3I=!uZ}Bm7-sAQa9U$#xJTf^gnW> zFRA2?Hsg{#A}C>pcv{VrN_kkENm9SG`T^zCU4=*(h3rsi1Dzl}k3}q61^0EY<^i9H zYH%uKHiQBT1g~YQzM5#nb$c5$S6|2pkgNyg%&8qly<$eW6Fu>2;R`gwpIi)@b|s!bp>G9uXIzQ`GA@N{GA_-_o6v5z z3KVecd&;a^xdPm708~&Hy@KK&8O3q@0k}_3o5op3pv@RtW!O=JGNbiWTGe_qXuPv%{&Hu13?*pH#QrX3w3icyE zsJVk_`Pfo8Y1x5g7exDe(;Z}+Pm%4%{^#d>qDHs|N|Quiwp7Da1Da|9oQ$BlLE;{D zkFJDdSpiS>3Kb5e{7Ggv-7xirOUw`K890UZX;4Qn%OMev`4F2;lwT#aGF;re&NgNR^vf8W)A}7wK30!E^nwlq z46vX`bd{Uny(CuSH{1vQQpz6`0M^u$*}5R)y=g$u3N-v z+hIKqzr5@bE925JTK{^Z0EYu--vJ=1XUOxH$Ea;B&6(OiRgyLT4TGpQEC)E(s2_7l zsl@zF(Sfod}RawMGJyu%eM?F@Z21LCxz4iz;)q&miqVNcG3uqZQ3x>L) zB4Hp|w97pbV1~M~Cu|8AL@oma0tR0332BjNkapX8F<@v zZ&riB?k4XOkzVh6R6%`%zAf~p?Dqk9(@EZjS|c^ROih0#J$62CTeQ_4K8n;w z#N*V$ukceGDRtoZEfR0~4S25;Qg6o92^bMg`EZ~nOvrn%j#3sxU#&}hY)!pjsi-#1 z_m?fnmOK;g*Jbd0Bkwn4LcOgDq4mf@-`w~WJbPcUWjiS(<$&9yWe!iZkdl%uuTq2a z+)|ZmzRYCqU+};46uqjD>65qHKd!q{hX@S)B8ozT<9?E|5t2m4MvyRu%aF1WF-J~~ z2^qmPCap%`v*J$0LX+qWm>F(oJ%vWg# zoIWCV`0$Tb4$)9;R`rN;bs&i8)vFBjZDHx!Lz6XoN6Xhv)*c;!7`HmAvAf3To&?n*F3{ zvu)nsnLmt<$^;7z1Vyo|hc$p|Mr1M*QTWy%_%w2v8js&JJ75VXsC5gooQ0vs`4n)d zu%w(S6|kr5N&%Du;Zi^QR+ zFliwmgI!AhTXntt-*|NHC?m_?=TsClycv@Yu@&$u znXlI*$8*=|f7>*)+$((N+?-qYKbsvWbdCMEU*h@e;J&Uuji*)5G@5tVRJr)tcG#D| zo~m)YwiN$9%)U#^Da)jJO?;L5rh;QmPLm7}C(ahFtCtr<)=OnzxHp7QSnt1uOTx$QyVILfHJ<$9} zeXu^2H1LOhMOY3bS< zX5=jdzlcBW3T>()wp>hh-A;Ft*za^-p>KNHw3qxUfnuT}D=U9$NP(<7RMz%hRLhIU zt6mnpwwP`+Kl!EI94M~r-DH1pRYm zwYa)jla6S_K2!I_{u!Z0lID)*^InjuV;|WkUt0`s27%LuYy%-q3`PkGN{0kiPG)2d zsUczXHwXZ~<-xU4K!HQcqs(#&g!1hX^j|a}7veBrL(Q0iXstLvV!$8uJZP5*322D8 z`y&zjYaIQz;HXH#qqj^2ANhVHl1L2wKA{^!)OXnf(U6&)|30J1SeUKZViXByb+*Xe z@7a3#^QI-qTCtO}bD~8MZ((%xmd=e1`v_`9Dp2&F{5ps}h?aY*?vVnJNuz1P!n1W< zQFd{2iKNA(r-x6gt8?>q{n+%YVchjjp`&Tg2e~3fjac8|3GqN0zLJ^s-oW5tjHc=m zwW>53u|)i`v)PD?mH+*#egND2B}98y>Y_xXqbEnh4N|S+@rB6gWkA{ZE+q`3Se{Dj z3NRmEv${{8r(3}8!tv2!2E}Q8Q^Zs?A>=Q=P)ED`;@n8JVij@R@H;QC>IG|9SrJ=N z5o%5mZ$|8)o5)Jwr@f4!vO{p3Q$&xTsH{Xhm6}q9mRYP|tzlTY#EU2eU&IW+Geaex z3>8Q-`b;Y|+H@GI#MeS0pLZ|6uiUkE>)_B-VD0rI?*pN&%&Frs_ZHRBP@1nBw6yNJ zXz|dgoFWivpZ6wjdqCk!z#pb$e~XeS1N-pJ`}xzvo_}VvYY3cjZ42Ya!~J#ScJF22 z7$E_oJB1@x9@lTC6$zOAguJi1*NsbSU{+y=0Cr8K-nd13uL7eitQ-}b^whkd8J(Tl z{kK5idJ{;I4#A%{`8@eaMdZ#K>*3Fp9Y&#*)aC4}U{C)x8n*|d7f>duSL2SaFSM&# zHX!6ogb<||j!v8}2}=-I8G@@wQN^km>DYkjf)zdG$#&8%J^qfq7@l6>d8W;5Y6U3d zck*8DwiSIeMve5de9dd?k>1G!#MTK1uDou^_rNHr!*i#wnLEdHO6Mas4zm1&;jlvh zNWd{Krk#)|8BP~sVRwi6L!~|@81lNEcW!B>`7wK2Ki(ZIjqMeFtwqwSE zoY9>YnZ$dC^>*sYbR!Uq!#l%Hu?2hI%e6p=cS3m5GH~1Pp_+lPqi7~tFq@WpIJrg#`8|$v z9PuYxj?C>JyvOqShPiBJWYFb7-v~lZDI}9USI$@?a2oUq<0eT(-B}rS?`D5;a4GHL zB0<&lsQIqI64p87G}4a;&dX$y+1W{V=dsfx{G3PnZ>y$KyM=kA=iJyW*F|kVU77Et zxw)t0rzs$PFg#p+k!-z^!qgqL^u8OZq}3zu`m!mmh2hl7*Q>Ko+MEnjOiV|hC;)=DA_87{h%h24ArECY51%C| zc+jzEYnH;mvALK5Q1Ejr#Z0e&+rna=zJO6=y>77tKPJyG}0 zBYHEdrADGoGdI7x_nOO%U#FMJx2w$O;;xrFxT@Azo+=n9;Rv%rt4pif#oMl;@re5l zrkeRf>joE)HwbSZm$D|WUpFglU!wueQ8wjYb~61y{c79G*167DKhLJn_gsFkcZ9x^ zx!*C0;7--Z;q85m7&pzj^T|E%Hm-Hjb1&un6=h$5uX)p>WkkTO{XI-kUv8+H-W{9` zgO|Rk$!+YA4xIOPms9OgwQON-oJXlB^q1n7sMFG?nb-H$l4j1&D{rU1G?hQ;^NKS9 zts<$)yCi;EHKL!U&7%xgqxGh_C!h^v-MPFyD(U-k>*1D)Sm(uc&Ew&=Be!!^9=F5Y z+Sunm%&b75aTBxKs)liS@1%@vjU=Bo)Nu$1mEI1Gk8&#_oq?_~z}O34(Wo|z8zy(Q z-~wO8j@HeNGtVL$(fE0k)R5r-o;D%l#}FF{cw*Z*Zk zHEe?(@Q#w)bqiNyQ(ubCgb4SOK9#-P9y2*_<67yG_s+ldc$@ueMBN)vg=vPe*l$Id zz2?f>Y8L3u&)jzA%S1A0oc#e7ExpK#c@NNwIWDn?LHP_KPD9HufNKV_TL2R1CjChR zuxf-OgOmKzEvX7`D^#ToGU3PUi&!i(7uUUtiHG;DB#0^+kF(_mp6<0xjj(^cK%Zz3 z)7`>vgzmA+<-&g39%6I3d9h7!HdS=c_$Odtx_EA478%OO^a%T6qTzY7`pxA{vY}=2 ztfPfKvBc))aYg@437K|(HwO5)+O0U-{^nWRMY$PDYIcQ1>i9c%#8B2B=Ijsg<)$3o zU8VRNU4WtB!-&2f;m~w+C27;%%vDcm0abX$;jWt(>sgx)6({z$=9hER=G(;;sJ@ru zG!v5Tun>=h*Z2uffj%II%9Wm(X?08Ks=lIZu(X9Ba>3Va6M00+cNCZCo(uVCpK)MU zzh-7^Wv*GnO8ah_Sl;s0qU?NyF1&?aK7+ZQL(Y7_?#EEi`ccm~3a9*M<#_LwS*v`v zn@ioNn7>NfXtj6rmh>~sCA_9CbJ}=V8C;$#)&y|!JGU}$JtJFv-$1vd-=u1GlsMKi ziuT6YzG8}ikMIw|88kHVxj+B`fyMuSk*oh( z6J#m_&I4`5b@VECZ7sZ+#!?+ofU+JXuznRK35n47OalgqNn>@)^*~m~vxPYV(vr+f z4YNY2E6%Z7=(ip^O4_QeNESI#P8y4(ahc@7B9Pe^rtTsNV0Xc0+vNKLwr%8_clnjO zwRE<}_R)L)##OMh-Qle7Jl%1U$Gqd(@dHzLoWO@jqbsSQ&&)HO^^Ti;2#b40<=C8B zy+m)D;>9$sQN}b{AK4TxwZ<=-l18QV4X?=N0&q%mj}g@vJ0aJXJmJ5T z`98egOX#jodn$2KR@w-KULh(f-=<=p|gH;w=nU?Zl zmf$tEF|=4-;uIrAEVIvMoX47BCP|BZDNWgys;1yQ%4FHv^jNCdsp0zy%zaSuP^}a# z=jb1*CG$^fZs?t+Vu!j##MS(5&I5<339X`blRMqmW+}L#IaR!^kqo1yu3(M%qNy(@ zcTJ)xZQiDeY9|8-MN{r3s&|o9m?{_vN5wPklR7ll8k~#h*~YTO z%CgM1%x?2wXMGDbSlL$Cu(p90<H1;O%-+r**T``sr)fV3P9JCbaRP9jl?i{c zfS{ienY|%g(9_`u%b6ySM4RY2=o@97M9-YS9l&%LASEiS8r^(pw{*<^b}6{NOWy$pVrE`0t_z=mGoTd>}1A zzyRMOpX34fK^nT3$^-L3KLWx7_CSjUrUuvnG0Z;71NuRB!FJ(wz-|EP0BZ;E0sO&W z0PBH&p<)172ciMK1U_U1&cE;j`vJl<$8MI?1MLCW18#w_1Hl8}44?_H_P~AcIN*7} zFb1$<$fwJ(*I`wF=mBO1!~s#?SQ-E=0Q(@W0AGOm0ek@7EceL+X9h6$(*yMZ#R3uT zuRVSLa%&@|v>0Q*6|VCSK6fbanM zz(oSo1NuSj0QC(q*V6mIaKQXxO?eDI=nVJ~K>UCMkYUW^7##~?q_ct*D6RI37a`Wx z;uVe=WNckcMX(HqO}vwXyuWRj@FnC3(YMjIiOpCIi&s8!bDCww-zk9J=Q*xct4Yn-@{t z<1b@c4)~lqJl8KHz{sePwQEv6#xxe;cgkJ=Gv7=RsXTAH0{Qai_;Ll58?DiDM@c zx2HGI-l8>}T06N4DKYOyB4i$^i0U?E-D6q|uIC$hT{Wye!d)^y2KLiWQi$aCQkL7; zNpUTnDJeUOXiGX^-XS znnEdM87t2nb&L#|vhSci^eSR3B;R3emT{^f_N=dx;_)aXTE#lMPlGhQ;#*wel<#33 zB!7`p;cKUM{2I!$zR9wl&1y`;4DFiM$Sh|e8`qN4Hixo&!z6b`6Te-OHdx`@IJG`w zj&3Rb_s}yeErBk?rL0fQS%bF3^gkO%}PvPi0KhvPMVKdmlR^|X38r3 zKA5f=T27pop{yM;Ur;w#ss*A7TmczQQVgyHoeETV3+W4sa##@amqUy_iWfjMEhFN% z22@5gS2uN4=wmt{qfL>2D-3V!~4DIU~CWj=Jk%Vtib_{KpLJlJlF@ci!i#u;! z5kS16VI5UIpLy=Hqnf*K-gR{2c#W#=+SFk7N69%`N~o_~y6SqcN8Mn9b(DQq^qcNkmkK3qF?<^8sdTH+WGNW4XJuv1XM7Yi9jy$E-o( zEK+?5QMM>InOzt}u9vsi7%>scGiRkZwtUXS+KYdkf=zm2A+8MeO-kUVk>{TQa?8hI zdSSXTvBkn4lzLcAVPRk_3{$hTU}|3-q{<{Ay?Eyda+IR*W$%r_yiCZL*zZM_)X9!TtPd52@@T5!CfTFZ=D+Ex7;np^sgay~l&77> zz}(%wuB0BE;OuKo#5_%l4z9u{Iya_v8}$yjBAJ+O;yjfF3l#r zf3D?N+oLVUoGv8W(=bRkej`-V;$tL)CpWi3!+IJtfA!L^MavtfL&fe*5 zh30SA7$y$kyYes_Xma7RS~DeWIFR9_}px06n_ zlk?6P*D^D)e&{)FbcQ2$^Y6G7+BTgG`KBM6yys3HZc=(*`xmo+BDHt+>>>=EfI86r zs&^GHDb8}^o_K~^f}x*YaQvA(XibW9miz0!eD6J-&8=<9rA=GbHZw31>@bp6{>mEt z^drDMlpw}zrCH(A>m|8dbY#bgJkcTU!#g>a0xd(Jn9JefMmxPvK+dgk>%IG$+Dp&k zYyBC$rElfoI4&*LWa;m;6nOmcssHLC^WdzFeA7ImoEN8Y$tn*Hb41{8Kh?oY-MkpQ zEdcRuD=s+&UW=Q4?O=EykiWW;N_GC1iQiXoV`IhDhhaY3)9CWA(r2)WLj>_Q1gy1} z@9OgC)L+mJ)Wjr&fzM`vK5GJ4-cdU#oyLC6!^pNdttix6n^@7xY-gltKZ|eU2uU5y z%02w^3uqXkcD;{xhfIg#&dO?K{k)H{Wz}1;1!m+B1JS8+@i&S)7I7m_7JjU09Y(*5 zmjz1i-M&B2v~>-kX_A8yc5qN=8tQdx35U&apZ;Itc5Rc3Fu+`+?a7=WPWk(vlJIAi zf$?=A*%-w*TBby4lV3=cx}@7+y?4q+M}A59XDF;HL{@J_UmWD&k;t9GSImY~DR|PZ z&!<0~#=F5{q0c>+>l?+Pt;xV2yu*EIcSZYftB5|4xO<6#^sa#kgc z55Nb8$zY>JMT%ugJ-v9e0%Rllv7El)DFuDHlsk7x@~pm;LpR zc)S6Xljawoo375lT3!B)_p8RWR9VPZ#E$u_qg|y4MMUTt3a7&s} zsyh~^&7rydW*xn3oK3|39TxK@QcB_-`UD|H+_0k&lj31q?>FQ4I zKDS#jes*(Msj>olaxa4-M>%!L1cs+sw}XAG`&d~LADQ}=Xl zoYMF3SHVfAUj7cJozsh^&}>N^E+>5zlC&9bIARypN;y$xvWfobrrugqj64K$cR3QS z*c7lF@t?1_6<8Y+1V;f!SI5yR@PBgnO(=3UguqQ0es;L?uggKR2o%1Zp_$b6lZaGq zw{+go7~a^(+{k6W{oeIvwtuiki%txn{_N+!Tl0oE?1z{UMfJ-+{>LRGL@*8!fKrLb zDH@|dI&mf3LE#lU;(_%viy8C;d-!1FoxLQSi)&0UALj`44#!}I0F#IfLR3mN2wJAe zAZ&#|lc)_sRmwI9UZ!h-igTY}4BrpoE^h!_gcZUqaTVe%c@_dLffXVyi51G;59*>T zw5*Y|z_UhU6S@#91YJZkL@vn=nMtriYZK@a-6uRTzrK0tyh}tbmwPPsvERGN+ttlG z^Ll}~8z&MHI3z~)G2tTcIx!%bmKPD(MUQ%$d;Hmd+6bV)j4hu7BlV*qnhpW#@fSk@ zHsoZP0w4aFhq1#8>$a>X02xmHQW4<8Ml9JG6OSmfW15?d*lj?_460dRrv@jHr$EXR zaX()Rj^3A}pFBK?h;T|T95mTSgunhyF)MNj?I-`d6A_K1@WjiDmPv_0a%LDTci70u z3xMR@1C^i9MB-KclY(VuWjB|=ew+{Kc>+KWMmG&I8W6yjpC054s26vK%v^}T&s?hj zh>GReA@W4&Ug*0^;9+{p;g!Ii@>k-VaqhQmiyavTa)*zQVzOI!@NW7^X*)J)w=ZvZ=buJ_(~IRsuB`xnOMVe*65v@y&LQse&$OM~(24xU)E3XW zoM?P``+?a?B1zv^&6&w#UKuq{{$!5jW-iOM^g2T>O0u#cT>()di-tGVWke^vL6s^# z?iKpv1pA2qTIqYx+JJfF6_ZIzHz&!=hoC~~MoF1_U`UD>0VjWR7{E^;spsg%Uh@h1 z?3X@pJasJ;c%w@#N2C>H#%dK3SP05n6iC5q1Z>(`D5ixHj0xGmyX|p(nypmXdEL`s zGSLQOnhlJxB#?!VXk!KE)4}4viV>*g_Yq`oM?*a4)CWc6@kwVV5q*0+m?&kqO?^vL zChro5gT42DeSW(A>rBnUMFBxT0=SEw;+X)d$A=BER*)Gc16>5XC{97mrKqGHEri#I z*@X90p)6fk4UQy!lhaF2`@ECwJMZlNBgoKwZBce&G3LS|i(gxYqFl1fZy;6qBv@n& z)Ww=!!7MLY>C9F17pX!CR#%1myx}d=r0gk&j;~)xjIv^IzcU~&Jkovu;_QNg_IwWb zO!}Br_}YynPZ+@h7s(WPA?7qBck9jzL(<++i~%t|nogvDZ;;g-8&dx%EJz5kXoO5e zfyJ*K4g}+FoH}ZEgNIlSWLRj|V+=bB5(h~XfLN?X+!fIqNXL`Z6KBeMH^2mq3Y_jt zvVUaX@*tYII3Mz;(=t38kcHj;pz~cso`aq2BS}`$crj(SgZXm3_0DN__p2~>ZvEZF zc=bjPL-3H-&*Lo-M)a{ri7E<_OUsif3tIeej3FT2IW;%1)@nPo&)m++Z z>87Tc+xEt9dH{I{`_!w4L0&y)6M!%)}gWmajA`y18DK=*xFSSZMc@Dkm; zq!Ah11ea3j3K{`c)?4IWrNKx5m$EsaB0C-OUGZ0LfV_TsYWJ~V3so8*U>+H?;4wx1kMfdJ-oun9 zlLlp5S->88Z79R-_o7rrEj!zYcs)BAPc*IMcvCU#)Oob#Wgv??fjI*$ zOq~YX2>fmWQr=~HlFI60pH#v$9D?fXTi!vH;X*EkjE)3XQgJ&rQoUL34SdISysN^I zaq@GoulnmK_?|DJEc?%|$)3O16*UMS@h2J;(F-ozm*d77J$kh|gUzQxrHgMIDj*-^ zNP4Ma1h?Y7UCNb^B>Q2v*0iPt;fvrTJ3p{9-h`IElrufA_35$|qIV*ve^{n6!Ca!q zN~x(>U{n&5t`V?PoDwY!?Pag{d!?ku8vFLdslYT@dWHg<$}3Eb1Z9lIvT*8^#+PnM z-E~3XnWvPgApe_Z#W9jadFO2}8p0rC)*(Dx@5>*?A0GoW4mlXTuOdej<=+J$GknC% zQG-STamu+qr}J24V&m`!m^vCeqK34czzoQHwLBD(+#z`=c1FG5r+3PG&k1=#z6T>d zFB`IHHkPe^2bPhOk&EN~@7dCY?rtXBPp0e_UW+L%y)V|{>NS6xwl5`dj2!m1U8e1g zEFxuI%6mKiwwYwtXR};idBY(&aCXP>Z&Dbd1YC6k zLS|J%pT8u!0mi}u*X zPiuuXdD-kDumfHU+>ctrs`{7FEHXOS-qCX4ih(2g;kyU1_T9x^qcUjRMIY@j|KQ{6 zB-Vuy?HsBs|4=oJzs_Psf3wJora7fuImg^{ZK$8i>6HXOV^=cZ_sTgGXg=CJzgz7W zL#`+kLwVZ^KXez6b^_6=Feg1<1@0A0{*@W;<=09C@vZ`_MqSj;!<)JWqk+FP?Jb0^ z-O!~#JnR+Vg7Pwy(G2OpzMMo<#5_2NtXz+5Ov3ULfhHo4NpDpOSR{72A@d7365|_` zs66?I&F>DuUVQKzEpDdEi4pI_$?w;UQNs>KZEG6JQM7RhMe}H}#3%635sRa}QV1xV zvQ;_;`IPgZ^rkPz8Rz#~a|7I|?C+)y-$ak~PG~8(PkMDaT92ZRcIQn@w&CN_Qrsy? zl>p*l=@SCRS>ecVIOzzd_gAC^S#ez`;{@!`eoAsl+_3&a%%VK{d@A+O3jUi;XqMGl zvkGM$`{?7vekH*v_y$w1ZI3VMY1f~$@uXW?PTttpG~-p^ZRE7RmU7nRjJ7>+n7sj@ zudU--$U;aax7X|Hq*h*Qb)Xs7TC`ENn|s8ZO+MARFq4lvN-oB%W1hJe-Pv;YeVOh1 z6qbJKO(bR9;!Ci^@nFbk?P-y6kkOC%THj4*Yd^V?jZEHa9g^=Vr8mCy7NFaD{VlJ) z^z*esV_5$-T6NtQUOR}nbg&^A*W7E?X{7;6Ue0YK9R{A0$Kl`1-r-6)2ycQNhVK;a z{Fsp(KV(l*&g|N)4jYH_m?M`Px{7J{ueWZbUdVTCBMomRbI)?4?$WJKlo}z&$j~Q6 z8AkrHtBuqIYGA@pV`X}~Fu-A&kjW{yxx8T{9W3kpjR5(4I;RsIt)t(hQVvKtcy8Va zeP)KAAorpIJ84$tse1??0-?o+qev4ZZ=pt^mJZh)-{CHFVwLle6aXC_(dkY@`9jh9 zc`V#Zz{!C-uzZrVQ^FCc5TFzY3R$e3!(Pc8u}EaM?pA6P=7|8&&`)v{?P2tw2%BQ; z=#Tt+Mc8m~NYv5QK|kDu1fISjtF`4mY7#p`wuvO<LAf8$qs;AQzSzuxaM_`^y?m$AA41idN~?8zh_oLHnT8?_0S zJErBwm28af#~ch(U^a?l0D@$hV6-Lq#rr6u;y){*qU$tS5#T0+>}Q?XtznoG(9HpG zQ}mNl+f`rD_eZUKP|ds-;zS+?@9O~r+#d!J4ZZ+mX2P*Cl)~btJ4fuX7#1S-V;o9< z<=G~*Zu}u?XO)NY^w~%Nh#^7Y(dio}^DeSD;S=yn5+CX0W5bhF%RxRjdf_% zNIHNNgK|jGGok2FDgzKL!^{NtLb03Z`B1{}>VyAk0M4v5@sg>-`BdZP2j{(bC{oL< z3by=umWL7?se7wJ5PH9q0#TnBirQ#Z;>hvqyR>wxerS@v;EJE%{tCnAhji=2sehV> zvbSTb5t}92njNm(3yJ*hjWnk+xbkjkTQ_oLnhmtZo;SYJrxXPWzN|vD`Jp)TX<9YO zP`WHtAH}6TRFA>bd3kMHVVIK#B`wPD9sQjWw%*I(?jHi7f{9~tx^e|s81>jA zNX}XxRt=Q&+3^As%pVdIz!o?Sl$~&XD2?+BGXUsn-kXTmk^qgYj5fNb#aG zvz?iZ<*?ViX)bsE<1WSXkMOEs>Y$B(I~GE`rTRcJ2$@3aOl+)H8yG~}g(&lgO}3$suG~*oig$dKYhWO6+!ym} zR~3jC+_mqQlv};CB=W7ze9J3;fS@0rPE`lvtr@aWDi43@Dq2@2SHnr?S0k&lGZv^3 zWs%V!u}PecM|4E;Rn=d4`iCnnBfYwx%yI+=-iqI0`9}C3P^dS2p>K67Ka%Z&cU5ph z+8O#jKuOsd{zAK~V%Wz$1bq&Wn(9l|i1{B+*WOt+lfo$=2?Pp@LjqPIVYCeef`vA- zlq}}rH{KbnK02*M3&kN32&rjYSDd-zY$U}!59$e7>leTEDMUvelJ|$MyXh1WbTY9_ zl%uzlHGaBWSh&R!ZSEmZ;^h7C|lmp;$(fo~FBEcpHCh8mrE>@es zTp~rhdps?b)&VShRRp`C@_RB7=9#7J+>`lDre3po*D`Z(n>HCm2EpkYF-m4&umtMC z-*M-Zei*{nmmDLq==z#%f%bo z1I_8qtE3gTZr`@wxL$^L#KFurk`2-HCa^paqMx(N=db!YP9~e)=^-+o1Hs8t*Wh)m znX^{SGx;c2y>adUebGSh#`icdiWNSprE^ zun`^OZDOz`GZ_xfwdK~Yl`T{cI2Jsyc(F{TT-BJO*eT)t0bs@l{Xt;Bx=Mx7r_XCz z7AnvzNERG*L>^^g7_uqKltnM)?|yQ|j09E~s(xy$nSUZT*XiuZw{f^RM%*bG7Ymqx zBoJW*H{^amN_cPhb$QEa_KHz(VeV!NCTuy+U;hA}K#P}^2?tVAD7$DOd^|}3qFm4P zvvG&;u`X7|pn?XV^PyUS4u3tx(4#y(3YkJP`!KPl` zo+$4v3oS=82skWjXZei2oJH5Jhy#P9Py(!*D51Zde`P z8`!s<m?rzN4B}|$xLY08IS&raPPsE@#sU2RFuci4JO+>KYFP} zC52+MFpiU24>0&=gzezLwNI5K3wYu>x}N{}#OoW`jCqS`5#}D`j{7J@A!3@V(8wPt zYxCB3&&t-cP@ZmYmcRQ$Yggxmxl~`FNQ_#w_CncHHFRsz0FK^dg*Z+Yffb@KGJF67fnaqnE^l=vKI2V zq_KU}5W9-N@)4|-96gkN@x0ak(1XqRLN@}6UIbQ1HNW5hCe?IeE{lCSyNJxzx585C z)&X~_2UFYqR0#w}Tt)YhWSOuPx=-=RbhKUl-RtqH)qbHO4X=N=s<2Qs7m}wa?g241 z{=~9R;aPkoqNkmv>N#S@{ew@r3DnkVT34kuyjMdozE;N6uibLYcFAUA2}rvkD|eKs zA?tGa>~Ht7d)ks>vb73ji54W4=~`g{Vqf3pV#7)U3x_=lC~!d4PATXKJu{R)qo5>f zZK2bnsN>O&N#2-2OiK+1VB}T}1`C9}s0*GVqivrM$~88QYDBKoRgO&2F^wXN&lS~i zG&6-@q9qnLMNpEZ#Dh*Cm{OjqOF+rGpw|gqeX#zhO_up4**`k!noswV{Qg+GnA#oun&GGj69GUcb94(y>~N>{cnu zQGrn!mB5kl+I3yg!K#q5~Ze0%!}~2#J}ZNRr-$CT(|bVM85eCIeyu;YTqj=Y{|!7rE%$ zik`YHMVA-Y`Xj(-Z^Do3ZPna324m*bq!4-V?x6}Rz#&Hw4w<0_ysse- zeBGHcEN^Bty3yl~HLQD{rC83kF^ruBr0FYz0soj39ZU3L^! zS#rw4tI6A3+P z-_<&Wu5@clm)ln=Gpw=30CX8xpwAMehMJ=};@Ou>yQ0NgRnhtkPD(nM3si=#Y>w}6 z$%=r7oq#JINh09`P@gKfbxza+#t3!BZR7FSx>(5-R4lN9hdgaRKtQc%0{-FB|)DpBUDJ@G&yFxZQ~G8~=Yc8BX*z(|`VL5$8ez08sqzRYprQ zLu1qb?l7dP?pfoA;^c4OyL(I4ab~wzemK+eZdJboX>IbQENY2{Vnw0h!%cCub9Zi; z+`i;wT6>LyYC&WK8AM4OCZmEZ^u77NAxVxpH!2|D`$&8HCIs!fyS>t-li8jR%+C6l zzkGiC@bBS&N^|iB7}Wf{ChM*uen3CRihSLm7-$K>IW>e+bp$l(P&cSSNp-04wicnX zXjng16i`R}McEr&ID_&+=ltMtLSk`Lk2P^OUI{fJ&Sd^coWg-iLf`Fr14IQ$OooS>$nDAkN8#YxNZi znVY8cun~)Ns3uL$aP#1~jAF7GK!XH*hgAzeXCbQn$6Z|WfNY&-+2ko(nQ5xaN1AKt zd*8ljWGQb+$*zwwc`Yf0C@vR`+?BDVB&D(Mi>(7H;zVtMvs>L%>egcXFiH<=>pP*4 zlV|o@#clnK3$nA^|C~odTX>42E$P6vj|gSMETM_^?%Pj8#Yj8pXbV^=Fr86i8a_|? z2dzyD+6pxLY!xd=tAGI0xl!vRP|XITDiJAI{mT04k)lH31J4y5qCB6z2(4XV~JQ_=0i*j9aCX0%j65VyKe!e8H%jY!B<>C~SE{)VG zK6Yk2kVDKq1yplNOVo;OIu$(RSoNCCdG^Wn_|oCyOAj4IH~4r3vx<+S)$Q`VcwM-v zFWy*LD2A?dGxMSJ*uvxQ$kAEe0*B)l;>gv$ z(slQr!qHY1d>Zh$n0l&xI(*6{evK)<_E7J1KFM?Zia+3;BHp|CHT%W=8Qykx=I^c^ zK2wi*#KnxnFQi(x%arjZ;ltdLmt`Zc;5OGVcd0{G8vfR7U2Wz5G-z186-?t4KGE^_bfwi`%>oF={$A)r9{x zYH0rmkMSJu&zp3Is-$yD-sCKoZ!Ctt>IE%9ns1W|p8gtVv5|9HZFQl~~GFq8lMuE%Zo_SPvH~ zWI78Jnd63^DndI|y15*RF5*WEuCE{wlqtM!3-6DOix&FMBtq_aZfoAB@j087MMB5> z540!nuQ1RrYr7Cn=9-=VF3@#TZ=*C%}c$ zVg*S>GbSZM(}N1jFORMVUOYt3`WYCCdEy%EhyF%6v2PsXdg2fMka9hc$WPWWx5bU0 z=lgr-^Tfol;&@pSn0gtyqsH;6jybw-9l`scZz;U+r35M;{}n?+tfX~G!2kfRpa1~K z{&x(uHZl9}2s)}JErYF&l6yb>#(jEpH(9?B;uoUq*A}SIUX=xJSyEvcNCGXMX#ENM zvVSlBSSTPXoh$)bibS9a6-{>@mqMfAHi=Y;ttC!{g;=Sy0?Li0q#C7C`O3G2!JzwU z>9gnky8GIFo8$hSymo0;fb#oJm>9HrI~{05z2==CG}!l3T-7Ilpo;Iu;{>e|Se4GrXF1`eFk_m$KFlSd3V9>o!eF<) z_ZcKMZa^Kzh^?#)g%HSCs3va%8euXh_NHjq$`fo@uaOT`eS$i!(`u|f^$c;;E}RtwJ8C976)P#t{?VFFfNaHiRx7H?2~|C|S(GU3HY#A}Q(E+pVKuGfMO0SB z*kRi~;wXhdC5DuJ}mrL13^wmO+GQ*iT3Q7~0GuS9hj>JTh_pT{AdQj@Lpgs2o zyD+k7^1(6R1&D7yDEFTvFn7z&=nBiU-pV@#ZDQ%Epvl*tuF3H~y~yb7x_+j8;_Lbh zLJTR>beqy-zs-I0lg2AYQ`Uwh-}k=Kaq)9E^t33w|J<^knUmB1YV_P`Txc=0$?HFC{gAakf)fz^3rneBb9_@+m{wc0!}=+aF@^kv3R^%%zwz^9_P)nf|ZJEO{m$nJ|c zsAY;>r)vAGAyg4Zh`TF)P2`zD_aG|SGOUSRFC34#?%!Au!dWh!UH9*X9w6IC>et#m zgE02^nPQ_wILl~vQ!~!D1P$ykFz0d1H;X7|s%sKA$h7zYiye&gkt>#{2EazP{%o9F z@{e#onO=7uT^=)~3}n)&A|WQv?DCn%Y%WPz8yhBI@SYOq%wj(>M5#)u$cmZl(P?Tq z0>O2`@~8DwHmsywS(SWWapYLCDBn*|uBD_j25LWi=u4e-bq-Gg#%I2UiO7X-2xS*q zfV)dW*ySc)2-EW`A0@=yc6X>1l%2A~(yKd}6;@&@N=e1Ah80vM!a*9eZwJA`jt-^> zU^qg{ZsK6hZ47Zy$(y+3m=SamB$BZrv^dCns>jUo@QGPs#`a+7+waz2ujB6Yq|&H? zw&fDux+x1S1ofS4b0|OO<90TkI>?LN$zQLk&Od3xIjoOc&Bv{k9@NLFw_VE*mPyrs ztaTjy*p)TEZSyd*bl_|ms*OPz9hlxvD~D6wYA(BBHN1X&mcG1)-TQcs)o8p(E;6@B z#xJ`6w@YK-SAP;ACP{}FyXZc~r6^UQOM!s9aPLxw0v!{z;wuC4m#)ME2?U# zVevJ_Odm@WD~`Mry6B&v_JRMw%c%P5dw~n)1R+`hHA?@7R*{c1edpyKj`C;NnY)MP zGuI2h4b?Tde4TyQ*xL;ibIT}w{D%I|pY{BU*7%=){HuQh`v0#)V{2~v--%{N^U@x9 z12sSWI&#lZMra)%nN*q~K0&~v6M9pCqT3cyTC;2O%B>f9ZlB|xcshpgo(%e$XoYa= zu!6dFsV*duD$#}7c9Z`4cb*SnyERGkCTyC?% zxnypX#__Tt^@(=A9d=7%56agsBfRe#k9C{metxr*%mOzFSBqR&w}@n%SIX&69leI{ zq~U7{^}gJzuH0yg1x;3hsAvbtSdyY@M^1{#!N7@hI`EJL4JID?&_bl5CgLH(uCagA zGGNyg9QhG)5m9)Fg@_s}#sQf~C4AmXBBiWa{Af}nMBl+924)a@brof@BPa*igD!-|C#90L^U9;%Kp zB;`{QMdYdJh^j*p!MDr7L=_e?T-?N>s#Rbs30b^M9$zMxrr!#jNSxe^C=%6c3m&A~ z%nT}Rp#>2Q#jL;w(zp7Xuv5C$)Ajocja_}4o&NQnMK6bqcAv*|+v+%G(3@#4D7AJ~ zYuD=X$$t%@;|5f$_NJ+pdrM=LpnJ=%u1S`Sb0p@H^+~))nkeYdh1X(_1I4zY970jc z(DdH7dw|52<2#f08hUT%K3eCS$LROEo+gjyh3hYqKJ)hzqwz@8@kk(dq5E&(!WpBi z(fUYD`aZgrLvclS8)AEz4M_S=tA{$y9!-BYmg?iW*}d7D$F`N}~}r?a+7L!pLJvRb(N?2yzG#YqPHjj-c+JPjD=NW!oW*1XNKFc%|W> z%54Z~rd*2U=;)ZRvgcD&7Juwi%y9 zK2dNTQZ(0p6f&6a&8w^Y3qRS37uqdG6`2D^R8&8Fy|Q|zWSAj6!KU5E1Q4kPKSZEHj z-UKU%t=_l^CfP;Sd#EfXH-&_rz(8WjDv+Hxyojl!0fVIjJpULw6bzT^_a#NhLIU*p zj?3X=Mq07LHS}uGcxtDG=&20>nmHCkK$-PloS{L|7Fn zY(lzSSN7qKXW}bk6wmdfEam!ok1jNxUQ%4)QGCpz=bTAaF0(0+J9&+aOHPBAR$KD-|oQn%jzp?nVv)sCKl#;iiL608KDdJJC9T9m6PEWM5 zM)}>~WUk~qnDcA6CA5VcE->w0dhXr4@8djkFsDb)rS}K6hU4YtP%?SecmV1qQT zG3Bwqjs`fJVo9;Br?inIi$J;i0=xMjyV$KH^S&LiT})@OOFF}`?=Mm?c1N?T*hyGK zrR z74UQ0X}j`vrcaoi2^D6C{n-Yj=25~wNx^Z0N3if+m~ikGMkDot@s%P}T`{0=rd+Uq zJiv37aXt&?ZAtFr2g7zJ7;nB|avBnOH>B@q5B8#vv8)&JeP%sr$t!>kpJ{KyXUI zwq4JI%zXT_u$vXL{j`z=7aV3RfvE$CmHWwLE#o$M3f%3=>K6lonPScNPmIGh#GF2o zUrF7jmju{mQ{L1SWHv%!t@s8mEd{~ZB6v-!IT#7aNkCTw>~y`v24hK!BdhS6794~e zlkrIKOw|%sV)HCu6GKRB$@?Te@CQ1$q!TZ(Zzo`;HJf#tZ#bhw35#nNwxfu*Svjjg)mU=sU$%xKVZ|48SZ@~5 zDmtvA9f!Z!c?|u-oaO8bA&u$8fl3qW7+}_I-Q@k9+7riqh`S7b#JddJ7GRraZWP&n ze}1xK8LLMM&+G_eLzi1>R6Au|+WbT5WLX#2(3G&yO^q=3PBFU?Kx|PFu0qXn5teVX zJ_v2t1Bo~zCDp_b5RLT(J0(NK=_d|cp#oA!_UaWO(nyqzMIIqpYN2iRQZz_t*NJH3HT#x}}ui^$PJ~+SU49dz+2Cp}^I@?g=6Ao6hzBlZDJ7|q{ ztu1G)`1z6#5asmqq6ZB5`c@b7E`u&Lmx{}h)_ya+cXO4acYUq(3MTKpHuHVy_kQ1& zi_|WCro(kj_=dH!=b+$XU8WN!+cXu(ZjYquXCXl55Tku#++_Z zL=YRXK)p@L;X3qUNR)5*e`4P5Tf63K5I%2uU!0JC5Ogw{zu=(5`8VMBG37?h_c z0JCP_AYPaFaF}ZYO#@VwJ=g)iyTkucL*qG>r}Ev#B|o`oQxJ8!m~kzArK zjxZ>vgzDAv1c=Q;6MEf>-MbV&8yReM{n-de5JwG}xT-dcDpfiV%59k*biF$~;Q>xm zii@vm^4Yu3IP`WxSO4TS)x-ScI#+a#%HL~dC#@M}#~VP`t;PT4=BxWX&8MZKnB7?X z3o{!xI0HwUN6Q5q(SOq!U5&>Z`fJzrurT!}ejHe5GGMbSb2AW}xp{>>GVExHY1jsn zI)+nChLcxBR+Vf;Q5ybl)v(IZbPrmwMhbd*;64<|p3qFmZ@HlQmcjgTeMmUTFZ(cE zMT8=1#`51PL7Mxll1&AkK0-0$DnY)KT~*pWJ31y|r3S(hMpXjB+5+l}P&}B&E{J}a z710jlsh)@95FD`1RKlm`8OA)2{sm(k@BG6Od_|J6Z|%{yyTe`89~s)Q{-ESLxSXGE zbhoo@P;>Xm4Jh*(>%r7-3UBb;e9a2nTuknqkOZq`@ZT(Rce*wY!<8jzDIxQyK7&Eb zk(A@%4S47A$Csirg+Triea?%*0N~3RNJhGFAyaP(6nUvijF;R(gjI=WdMWs-g0ca1 zN@g-D^@c&J)g-@E(kKP4YS=OxDm8^0#mk&Er*ltq^G*SNcc-*^;me>v-X7Ce=U+ML z-$%c8Q4RF3v|%n{5_~9+KWz#UQelELb}V#Zk)wa|qK1u~d-`uVCPAlW|5!)?@6A(l zkT8=_62A|VbxJ0uApSn7wvmaC4diuo>=jZhq5b^nuev+3iMTg1{`QH04<5=H>o}-R` z?H)7;007GWU8;02`d6&{zkC=q0_y2u4vWjY{x{TEp9PylR&5s`WO3>k=WTIi5#o zSwyW8sh(%J;L0MMEW}^3?RWR4eQvM6aA5vGh#lS9EA<Q%Xn~vc@BJm+#11P$Qz(T2ZTZbHhAF5MtIt5P<^@FFL$ncs4SQ@cX3sl)r6E{ zVuqfwP$w>{nl3V=w(s`3XnN{n;zq~Bz~EwI+XbCh6E8&2=;tk-_ZJH$%P6E$1kj(> zQf7z|ixVk^VsSE;Qc(t11f$egiS%W-FChz;N{uz3D9c(eE6IvBS5(PUX^CY@%~R@1 zQ$@=BSP}g~nJ7A`fPymwSqF(6kH;i3oJ~yjlrC1-nJn6jo>o)`rAp8`&oW?UF(YTT zWFc)@r0&l#YoQXnh5d>dKxVWpOUa1weVGQ5S~I#ykBk_ofq#O6n1m>z$4t~t7Src zX|Q4cWZ(|e2&fUQr)|rf0$L-rse>P{V>hMnqhWASsb)s!s_Z~Py+t|_(?pxf75ZtbmY+qP}n zw%e_3+s41PZ5vzLZg0Qu;Xd5tCMTI>W-{k(bbjz&&s(-<B0JNsq?v4)gL^1 zy+mJH%hlswXkGFL$Wt`$!zUTSAL^%1hE|@{-fjQN&t9sZfv3~^vMicoQ@+Ns3??-B zpl>Q&-hsUQ)nue5qw9O^DrPjYdp&x9P2TgGSJ`5@#i<%o4aSMSQ=<`m_2rq@gQSJp z+et~NS2wQWZR>!3xW4K*^9efl7GPfJ)I=Mda*Ids@7nxoC+@k%wSeDDrRpguc4w$# z6GRWv3&)OIHcFZM3M}IiZA>FWHgjnZ8`adsMdGu44U2YTrs$v%V(jz`>b~xM325h? zzv}MK2v1+yfE*XX$~PpAXYkX2oy^67YscRA|FI?Z>2~s}Qw9RkS^j@1bhN@2iSH~MaTg>oW+Assg1B!pec)J-y)>z{e(RmG)VwXy?hQ@Qe z3p%#;)gibHC;LIMjJ%Xp{hcNV0J*+!sfM{@f<&x=S}A2f;dHn?pS8+9SS^8K!{9=n zJe0NH;5&CoN6gIy6vNT2905Ceh3?z$yL;Q~`}6yI?Sq>TlyBqvdPm}8+0CTkTZZ+9 zsq6VU-A|D8y(B8R-iSlPmzt9m;`Vma-ItJ)U=JB6H%eCW#n_gT4<|e2rhGH}L)uxi zztpMGgE&I`129&8t84jd{M^u^Fwq5rV0_+#uu0xI{{ zi=(qfKe{8;tJ0hjQg<3xHXkw%(VkRW-+zqO~UX*Uq zjS`Svq;A5^LYvy%`J4+BALz}*+ys&@s~d?Qi>FGzn$yZ%t*Bn|jT2B0ZWry|G4Pk> z1At+T`lCIrBYr2$R-6|d_{;Ob1xjy%;*PD5jpPHV*G|Mv;)SYh!^1?GV(%PCO+*FB z7jyTlOtZfh=udP3k}v9RMww`TEwDEBlH^Mz(29r)k}uBgTbXXZ9%w6pW!w!m5SwT; zQa7-@(jxI*E$Bz|2{uv<&`ajr&-$djI}zO^do4h|WS(OEcAy_lb2AdHjL)uL*(Y2` zH6ZM0LqIh#`v;7+fdhw(s`ncQTURW!OcUytbQ9`0Y_A*0IxRcf=bj38ke)nwY*Bfo zF?nnto(qtkZ;)?nK%bdJCsSgxJ>VzTQF$#*ylyJPh%3hZDCB%tLqrX=pN2xqSz)7C zM%Z@W2uLY<&ps~xA#OaUr^cuwKQ-pF7G zn#Gi`0$`m26Cw`rP0G=BF8rnkW(54e*#FIkPf@P~A}(EB+B+xJEIK)u1}(F zUooh43n;CJk&3Nxy|e0+5OY~1ft?BQr2P5N8>}50DeX5&^{SM?)A~uJ4I48rgi{U- zo|Lqa7oi6mPQ4|nL01*4XAumGWy)(JNb(`6OlIJQ!I@+vRj$KTl!#LY4Q`J&!ztAs zRYIJBMY+XLU{q@40cZ*I9|?3*=IeEZd63IE672WFUUiRS2WwrvfM4D_vf;V>Hebu`-$yxLfbskmU%j%g!}YGb ziN6ewJL2@a?ya(W=3b|!ceKT#EvxrZ=A0X^7d`4~=C)NUSGe$S9F?oxd|R;3@%mpl zcYZPW*4{q@edWJQ;D6~|e(;V?;(=VKEd$^2++1Hr=$(G>PR72toV-jZ%67w?_|@O< zaqd0nX!Pv06ngq^(bkW_Q9KPU@_&x?`LR3jy)Q7R`z?LGUC11yL#^fKR@d)r7rt|= zLte>lWp5{#l!(sCG#b524W+@(W?=|04aMXJolgFF+K@B3P^YATC%c;Ve>whf1wZzp zn5siQhA=cCd-tfMeJCT&=xD@mP9!qaI-Q$Rm4n|k^Llyl{rD7dv}*9Q#Yb1V3ChNu zocP+A3>mPvY5}9FkL@8mv?J#o3(1b(y^ya;9H@^S(`n@NugF5%UK+ zejbM>IJ`o_aIBy2?d;(ixb*qF`3B_!p(oh`P&5# ztB!yymTXnOd1bgVr|E!NSz{Qm!PDnB#*E?F;iQvGyCxh%1zDHu7GKk-SLOg>Ee!mm zVu0o;Go+*9#Z0lz2R4cnGgC-5rp`_RforJdR#phQOwKC74ifOixe0HhbE^oDM ziS?N2S)Fj;VNwomCPb2@usiSGu^8w^cvlPb(r~c=zE&Kq z_uW68p{cFpChzSPjbJvtlh`xDn-hOMSry524lm17#+{)czJorL9{J4oZ7-z#L-On* zsp)z&Kbk&ps^t}rJXYp9j@suqP(!EDV_x)gCBJ!ncNT3znhAHo1tU=A?PQjLT@px7KU$0kOrUylSv zvXf^Y*0P8i+-J|FDQ&P|GPEqS?=(1)$Z>#}>SLAKT{2%?iV0rLVlwQ@R&5lF2&-WZ z#H{#7jAYuc#`<3605>Mj3U)7QsJpmmYGJh@q6indvu^)pqt2WqIW*u-a<*WyUshr9 zYy_U=2JSF<+M;O9lYje`Wm6K)4y7_7p-4m3*P{Iei8pqv?24CjFzL)6xr#2(@t6()XsNS^txN4<49mROrLvus+E^Q1Im)<|AKhKnDowu_jwO?R~f zLU;k=(nC>yfWE>2q73ngWn5G|)$DQwjU{n;9cxY`R=OqEC1DmvHc8A@ThIl3@s^u? zM&42p?vgxbATbLiTm&X0Psm;?8dE-&U}b3&mSU~S9CL^Pv!pq>qEUYoa|uJbbeXC< zOw)d@UD28r&(}U$g<`oKBGU0WVnj=SIEw2;r|jYZaeXz4@u09x!SEy%C7tEAL$o^L$A}5XQfgNjaJ!! zsY@n4vf)JDyy z@#+17fpAJBOzFv{?+;N9n*8lRtib$*M9y|bb$_+!GfYMsO#xw`P*!SWOPOnzlVh3R zaf|pXlyF%!BQlEipARHoK1azvC zO*#CRw=P|b!<095%H-RsCr1Wh6tOJ?SB))RA71AGdek|jk-K+609SPi53?NwM8h$uA*j&0x2T?(`H(g_Vd z$L4w}@6CV^YJ-X3P1qh(KQ3-gZ#~p$;pZL)Xy~!Dn>%gL-m5Zc$Ai;>KNBZ5_Af;N zZ*Gn+lo-NO_z#d6v)5ZhBUKh=Q=J`-dag!_Ta3)ij?p92?$KpOgj73QK?t*%`0~2Mk*<#tzeRV zJSFCYfr#_*GJf_z>;110Kk@y!yk4e8zVrQD9qx=)E|0+~u2+!#xj~q4u@QM3x&q)m zyc~`l^VV~}+^cd2-fFw~3+Zd73jFL-irzjUR_mh66n?z@asqezr>o0wJ4MSC z=ogKf-2~<+*N!JW5v`r|gx>eu;vjhrP|dvJb+C#Pid>zGFkZFn)4F|K$gs|(A5@Ab zQ^(AV>)IN^j93&L?>x{1H*4Vc&_K`w3&XtONkj`c_Tg)x*p|b}6p7f-AG&;?# zm_6?T8kx}f#7>I&{v(CP$dx+l$G7YMjg{0%1lJ$Ngif8`R4Jn6m=ct}Kr3!T-YPBO1|HJ%d5CO-F}c z_0OAq55N0VGSa$a+2#f`I=w&0=dK;Ub;DVGt7zD1qz6unCpA@ZCr_9jcSwmGZw~*m z`yQLj^wXfhz&^jTGF{3SjS|7~p5!P%%&tS+3dM>yz##dT0(4yDYJ#NMf@j6aB#Vkb z#caz6qfR&a7hGN~a|NeJD28Z|f*RM^fI*L^NQn%qM49ENVPV^uM3Z$Li0#lkl!5pV z0L>}JP`(>pv5+X(=9+G=H1+4auOYgGM5jT~#*0WvuEHG0tZ`SNag4J)fTSdVZ?)kX zcdo(T;H5Ut4;sWm1DzLsZuQHJ&D{{sTgBBrASYdrm&3*ItlDe>$T^6=AI~X(7phV6 zvU2Itw-(S0W|$9wdU@_zbBJyj5|JuZBz4LIH(l*s)n6Z)YG2+QqcQB)FXis``L)$^ zLgMY2my-G%x_T*LLFIOF^$J+o`&WFu4BKvB=Hv42QOW$l&!t%7I)H&4?XmA-d{>4hGb+1?>oZ?>EGp5 z_`~Wir+1~GL5Rse#VeZ`CWk^*qvMbnJTgW{iv~Tws{b&4ZhNC!ZPgsEx=3P&K#A&b zt^#j4e1wOu0@+|^~c*w)wB{G+MI;3QZr*K;@@4J zSyeSp`=`)n6Y^G_0NAufUs}1Vk!C5qLqyh2vZQ`58-l~$xy z={f=zP2VZJ>Jyvmbq;2j#B>f;#K8q3fY3}%iXz7&AjRt^xlQ%&e@4kXTuHG`Vkeg? zo&?j@*;|~y(iWHCr$3)?g*sVSTK!3fM~dba;%t84nXj(|hybn~iT=~-lPg2DAf2_S z7FMZxOG!g3H}v4X@RkOy^lIo+9S*g)W`n+_DS)e5h5cVz)DTO*b1S!Ws^V5|vO&kL z==!ZS^%y$nQ%lMohOgs;7Q1U&70X-Lv|cab9n6hvaplZ;tVXYj)N%i(?#drCWRrVA z978J9-m8*Ga7%izh1XSG>%?UDdWuV6*r6sbUpbX-bXZ~k%VQgKipNNXoSJuFx<`_< zMdO^xB386t?C+Tfn}I;A;r1fqnpLw%v|};5@A=Y1y||S8w!R16xl-qq&yg$wbpom7 z)TxKTTZf9YDYW9IpxTXK6_G|An3`ot4X0DO#keSVXr5lnI!oVR=Hpuqv)=bzXa1*9 zHWVB9H8m>0lY%Tm#o&EJ9{$o7(*k0x$gqUS)8f-myHWn#@%vzDfUQLBTFQ=%YScY+ zV1vU>Yx~l@-5G!UM%OSrj3m=9-Y6=L-{}iK=X!DGGihi@#>gE&PGRkILr!+flar;s z{=;qweFZ(I!q`mDry?%94>)z>kdVA)w`(kh4wRGeDxO}jb z8LL(sxKnC&SUSVqMXbg2aoT>`VfQtcO}52{KM#ic(h?wf3pECLx2{%sm}YC{`Jp)E!*z$2y`c| zw3~>1c513#4Rv=pjZQ=OIgGx}n%s|1PyJl@XW$3`O>KTItj1RTQ}rFxpA93+i{i!i zV9T!S-Q4uxHbkoD_NZlhGq$p&P;r~7tUEc_DuV|y@>3H^$TtG0(m z*ZcTDYF}jL3G--sQ~#}UpTgTK04ND2^-2vF0UmXqWlIcsAHc@f{bb$;yD693Q+wK3 z00DCRWHH5Kj4zZu;Wh}E@o6Hd;c#94*w6T$lit0hQsY$HLDTa%K8W0>s>xOhBz(zk zbGNHva=RV-a5Oty2kmLSdwc3MtbNjN+mOD)#cJ4=g|MFIIvSGUy%CKsH z+zewCK=1~U6q0|f3dFJ{GaU*bMsb^u=fQDoj=G2x1``UIT>Fxp=%!6)_yvfMYe!`J zWr?_`?DEzCr$r>NT4s`zX) z28_e`@kGR)>;D@p#k7t9oa=f3JfU~Ow+hmDFRvQYzQLiRMP}966+mU&_!ov&2tTyR z2r8z~>Gsdx6qpyrsD*ibK#sm>A1ZSZUNNk4i18 zD1DL64r{rSOcDRSdRcix9je|^?R^K+xZw5HZmuGShkd-N<~VDaFsZhV8e1L9A9;WDcX*g3I(-@ z9M_uscL%^Ym}9-M-i%YjWTqG#Ga#V+SJ~2UB5F7-cd0T^8Iw*duQis6PLzOdv_S0^ICnTE9H=++5NY z7FC@?;c}XIx<<}Yo6MRdwf6I>_z!Z>xPV%4uTPoaV$_7adMCB+2-~+Aw=eoAVkvE6 z^}u{HKc5MX)Hue=@JgCJAfe=MKaKscyQ+-ECkB#Lv1YMT%m6mYZUKCQ=m`htBYisHXzY3h}NA_YH}pOt+h z7A}E`GhZtO-5)72W-JfwiQ~+_O-t0oNptq#;VCq#L6H7_!_B<8_Oe8B(ar>;rMAw*^etY z(j^uy8Ceu|Ynm%gS(4~8Q^2WErPl46aAD7tNVlWz>RmZx9Wj~Jge%ku zPlqlriw8=g%w(Y5rcqr(gMzze^A^|anIu)US#dDXgsXdzIeqxk#~ZN(FJ)f;d?6+C zpJZ_jhF;c4cji4a2-Z~D8t``L&{up^6bu2(l9!7Aj61a3gQe3N!M78DcUR> z%rYak_lbf2R!fjMGB*m5oC|Kyfl5KvP{brJT5oQiJ1Iv>&O%1E{wv5LJ7?+Cv42m} zH3q9o`!BAF4&OeB_RlgEQi-aBnZ)W%Q=`tj*uOGjg*w5Zki66oZY#N%EJbB>7N8 zaTul?3l6DBiq*;=-J%*+G)fG+rq+9?e$*R_#~S)X1MBB zG!>Cd6PFEDSS_ClQZyKK1U{*J>$0q;^H0;+@=Iyr%K8@b*h>DRjpj~CY@r|mt3u`Z zX30HOrCzu_Qv!@Rrgcc%a4{;#ElK7nOs%pkhJRR4A~CU8L;}pl3#!n{h%~BE-OztC zPUQ)ArGI^*HSn*U6{A>;l?o;kSB%8DZ)({Nb{eRWCM%qds&8Lc6R&&oq#HP$DXL~R zijdPBE}+a$RU|H`E*cp9c#K(IJ{nVrFFS@!so$q!ossL1AAMLSIG(Vy(v=r?DaTW8 zI0*IY62XNl_U#@5H0aKiDICNWwXIBPA`;d#>IvO9Pj-<4 z1X(gRW7iAKo8|4xnQ#ZZ#qyC>LFIXXeb$aC1_AVFWg1OT#eoBBp#wr?&dg+2EJxMo z3>B5dF;AXzcy~DMstdS)fC~|1^EpN6LvLH~!f}|(k^)_~_chr0D9ptSahA-1^J;#7 zMEP76U8qbc=lpmIa!P{^tWmFePYI1;Lm8tX)M_>loybaiXF_H98{z^~Mh)cbr}{Hd ziOh#YSR19xR!@zx>w!pPK{ zvY?lBf{CIqtkTWrHGVT0bkye!T#9NfsK_*(*B1$fH;lDsm^l6rar)DgH z%q-r-dYpec$=(v?OP>nDM)exh>=hOH>B4m-j8@v^&Nw1YMFJ);h=^EDE0UPJX^_N* z?o12TU|H(fEjJKaP1D8mYDp*J#OJhK3$GEe^NNE692yhPYfLBJ{u@vMFn z2+He15TxhTFPK{6b_0!3;|S0%)|$A_1kw@{B$>Rm6B*8?utjQmuIvex(JjW0FE+M` zihUiHt;w>BlKJ|KyY$L7a~1LB#4=fJh%%li;zh~h2%=S}Q|e8Ui7l6PFN}Z&Ue$eU zEVGXM@e^3{v=ugq&;?s!=C8m8TJ}wsD3;R$QnM6M=7_SPV@QjtPOdG*GA5S}Bo)ND z5~?Kmq}G)B8XL3uvm_N8adf3*XNsySk;`q<3W;;nJ&-dI5NQcLT2>nM;(yTh1fpi< zcMyVwyZ070gg7Y556mF4ZZjzcH5ZF6oe{vB6-ws zk;8h%NftIbWp`F^aHdBeBq-~Q@tBSA?nSMT_?RWIXw36HQHTg)l^T>t#3)UU@eX(WlJ@(0?81y31*vaL~{OR;NK!Cd; zXD=BTOBb{K=?6AVbaBa-o|U8H-oDs4ahATtmy+vE-Sb-5cB-K2>R0@Z-ox2#=26Sp z=@-DZ4i+#J?e&h{ggEcur}2)?bMT7zj$WR>6$FbW4DoO?YiPgPzHMp2QJvDBxJyw9 zK(ZJKiU|}&b}E92>N~bK_?rUa0O^66A(JdYh>51ntHgOJ9ZH4+o43r^(rk&w((XpPF9?ZOa{EfP&XYXyG?&K=(SyP?b}!KwKzU?^<-BV%l02 z!@yupAx*BW;l?>r1Zy2GPK6boMsh5%tC7f?OozJZyoD%*-e_PXpG%>3n-yc$7^YM? z-ag=B#46D6#fsWwG+tI1j+t2>DAe~Z;lqr4uMEPH+%4ZHPZl>K1Qkg%TjRhGOKST> zG@deqrz~vLPMWFTsvM@r`T z=~A8jh;c63*Jn4c+`YuY$gX`9DZ5yV1(=+?~IPcb7XabDX^ zQPU?~2trIonbM-5eKxmDbc4k7x$nA{vG=spEcN9H974XF8v7iqp*glu3(CNCyVEC4_ACVGx#*Z8r~I@+lS@! z>Lqkm5tO@L74p%i*clX)*ugvk zSPh@3h2X%_N-w{`5==JIbD$3LqV+|{g(7GmuLN)I5xg-2_56X>YhFY20-<;JJ{#@1!38J{PVxTF{sCX|^5BLcyk(PcKwwpW>kz3hd!i&u49p{8!8 zw|YE#?sj!xIhQd(Y_!(4V(IfLAo363iQ}P5@2vc~1YN=VhQKF5pFoxXeS^q7jY+@O4R!W+Mu(xXy;_uR`4L_jPrcFqW0vDkr!{lmhF{MD~#DDqX-pj zpD4yLZt!9Be(E~VqjtVj{E+YzRFOkeQ56}<-b51+CRkbxj+`D&)in#Y@H^K~KVde6KsbsadX(C0I z(Ebv&l9GysAdai~AKN5y3MK;pZx%E~HEy;(JQm0pXFRtvZUZx*YRM5LS(A-n2RZt?5$cjy7jXxPY>I!0bkRClf;nAF3v2@8b8jVe7q4%muF$>6 z7tH=l7f?WEYnVWYu4ug+Xu$H*)4=RAnZ<^m&@w>n+IwZ5=(eFQw6Oc;R@AXHfC47t<#dvnvx{ukm@6R6* zTn)`NvoB;T{hMnO(yfMGxPk6UrCOQ_mwUEhYE-NpCt047 zNCJGV%RQmPQfW~c3zR2DQY^1Hiq8)9%antdY<3>s>Wj}PjCI?U?+}a6LNGKcUXPQ` zCZ&?jj(!FX?~0`W^aaX1OZfV=XgZfxp|zTUYIWNnu%_1yBvqduFzIz$Kb}o71?Ghc z)g%gw;s@mFGcKMi(4}X^ zSQ99-!x=HjQHEqFwWAeCk*gIk;h2F5SI!ksZ57X*DM3a6SfKf=0Rt0hCYn zxkk(h{#;Q7+`B;dg<=NrDLKzZPXU)aNc6#)WQBpSh?~nzN^o|)E6oLA3#sO-XmgHZcEex>RB#cJPxSK?sE>No5z;EA_5X#$QCFM3SoJhY#q zv>FU|uO={(fm9x^?*u3_TRj>SP=jr;ROTpHX=^se->B{@G)J{0Kr;o9y37@6A;-2k zJM?swu`JxwAog37QcfzUsa`glP=cl20o7|3QbTKqM!y|+`9 zISYyq%2zKB%WBiRg8Z*y3hP!BHLJ&r>5(7=gRpyB^DKgZmCm8EA(jd(0mDUemJG?$ z;&Yr`qSgJ6tvSmWut^M00<|4NJ`zzFS6At|1y{BhTU!J(Jv&=kO=NLX8s5M-xJA<4 zsi$|N+wp_wCwIc5(`6s=+BK6_Q?Fqq5gYT>tcuXTv?J2E%%O+wzxdnb(e9FljOvhU zToJ7nIt7a#U(*sM4jYrw1a;#b^yieP&>Bf56nyBf#PoB&9oo4;I-X+}4iQ(6#oRo4 zTKJ2oyXCXOnW0$)W6@?^Wsb+|C}lc!#1P68dud~>mWG^3(Vi5*vl-0jo8<|XqRgGg zv$83z2J*`UMAWEiwCgbYKdM=;mqQ#+MS;sJwltj>G%Z-81Gj6Xu>(!JXm`h%+91xG znG{pWpy*jDEwF~50GcrKgv?DDy-~rkG8Puu3bm>h3ZTWTnD#6I^X%b9no;wZP+1V5 zQCKH;H!)*nhOHAVei7}RzRs!F8FLpd9-GH-t=7PlV((5-xR6sh{uz4#v)YNwK3!ratUEfi6i7iuix%^q#RW!9XAIe?e09HRb*op9WG->A;8` z+b)hCg7pFHBB8Vdl{9u()T)oQP1F2tvR*XcT%;;>B3~V|Y-OIr7}pffh@sHim8S_! z1l3zx)*Bp~;GZeLVhO7L`@u3h1!zkT?uA@iY91mxd@<9!E1+OeU;V_sYpzUD!4eKb zI1P)XBAU3Qm<0n^l@PVK@SzEc-jNi*$;vW^!x#+n8*`e6*;w(P++5tNL^YE9)S`^f z?2(mS<8Is(I`8^Xq`rkq6~wO>ylshPi~hAA1UmynE*q&>SfwhR5>_}H#b#Z!fXcj5 zm4cu4GLFCD3v$fl4t=&u4J#7D=pM}1dyOvyE0;dcCDASMw$}`%yovhBe(A!_6kk#u zefX5@kx~WI;=1)XTA!E%7_n&F(l)2aFavQFnKtgDgld`C6kZqZ*Ru4i=e8+7?jM3+ zXf>DzN<<49VoHmct4I!9x^i7%Lh}NzM4`x3LTn&;44XHCzb~UXs#H~a{X2$YRv9hI znn#^@EBsfevWZtuC0K|C0-o(mBId4q%6TxUDWE>=R2d5}ZLGXNImr(cFIYSPK(U=q z_ZJS_w_Gn9lHid!TNI7TgNjHxkp`<~!1~d@gs{#54xTe2E*W64R}ch%N$gW)Cy^9Qd8~OC7Ee=Q=uM!^i-3nBF|Ak+8RHIh zrZ=mqSa<9b_;eaen$WNqDb>0K!?EaeRO_?jRq*$>8|vD?28S>!Oy1ij7i2{YBSTAd zl=Wji#AijBoDy=+_(=Tws7T-8lyJh3xs)6|l`a)(8Xp$w?g6QD>*lGNBN6)tNw zU%B*x>-+BG2fQLYWueE!*;VpC& zk7Z0@*}zte2M078G{+)li)XyFbgj!!R(ld*z?_JbuAYi1>SYsAh@mB5H`b&JT8E+B zg#n9FsMs>clHppQGMsC4xa}(OH`BZnw0`mWfU~7?1I$h~xk|%UDNYG;{ZxW{Dz4Xw z@8W(Smanu#t(5rizYa2gu^$+8$35zcU=&b6aQ7-PU=EId zjA>yHl(Kw?r;WLgAKY=HzW$)JLg5#`HZT&aw0Dug8oIEnODVNk&?Xf;8Z^^0Xy37_IG%bpIdF0Om-4AX0;#d zTiK1Zk!E_|y${NyD{{PvV|KE@b)0Y?hq<~*I*Q(T*Lh1eA~IDiTvM*4+i0kIo4OnA zY_H}=ABlh!T-mSb54R(}sw<4Dg1+`H({oSl^_RzwX%c|D#HSZF{t*|wDO}WRvW2)k zu&~eT)333ZntGoOismcVZugdjcB<{K`KPxTp+ErHc`e`K*L3#v=IhD&G`8(6-1Ad= z>AAz}kzZT0lLnRU@|P}{@aHr}lotF!H{pFxSno$N0>twr1NNaQW1wG%yw(|X# zFYrjFR^0CV)@>@~G&l8s;=|X0R$>Sk@jR1W)C6XD8}}PY+Umbk#SVXRPh3lPDl;4x z8%_`WC%(oeiwCs2yPo|@ink(ZG>+;DZq2uT9L$a%3|5FcLA(YJ7kg1LPdPb{5Z{p$ zCt--nQ$^cjVmw~!DpBqs7x;EPO^eRY#8(hIek)F*iz(`PhNrEF4P|8!n&=@IH_XWO zPrY><+`QeME$NnpS8pdDxRvt%sf`Kqu(a|Xg?$DLFfT>(y4b}k&E0naVjmw?#t|JJ zPWD7j655KJs8~NF7xH_$vV5vJJ%sJBx0FO%?oQA+3tSDhjs&Lqo|IzA<4y5DPJCGn z|ERd|e0O<{V*FORTrs|8bX}NZz94>K13W7(>eImYaJZbfl+~h=nN*KOBVRrSsBKW5 zce&+Ie*PjxtGFEpn!73wT2Yilm+L<#)zNYJ7Nkqjr&c>)T@JnmG24qJcmaj%hVL6e z_z;5pLInF}0sF=X{Duhh1L^aF>BAGe0fqdc0Q<%P`-TNMjV9M_wo`{DORbS9H@1G` zffR=<`c=FOR?jtsx?8BSb$(ddQBvZE6Q~!;&w0W}QGHIfD-%1EfseTgr|x$}I4{dl zRNkbA*{1#sML!En`E4)F2$~&FllyuU@=UjB=125ieesp`^_l5s-`HsE>wWhyJyy9t zRtf*XRf{#K28WmZ_O*^z^{MYTa#v&S(bD+f5x(E$4NXGQoGKqiHLmD!ToyR)z}`lac6Jo(q%^dLwLFE@~_AhJ~VnwxQz$Skt$cGQ;b(9ZYuYq{hjK{ZlU)(^Zt5ffY0fAhDXEmu+R8dCA+m|N27ohUn+pc|VsX z5G+PR>U-)Xk`HaJ1??1D15W#sxB0nlAWTD+QQ+78d#}$ta$$~LoM|N@-GPlvjOT2= zo!IQo%%9bk>slD(AU7f@tFbcXl6JcJ`Y>knN7a7;?j@@nCt~B>yj?#GO42YpPpft> z?!xuV#zwYXdR+3$hwXsq^g_!ruT?|7>+4v*%VuX{6HmGsvS!h%O51D<`m@}Nsg~p4 zN>4}s`j#9AVSGVJzs0I=&WQvcpM&1dpLJ~>9a#)Nd7BjfBsy8hc{$h>pR8OcKc|g3 zTti;r$fVhhvdXp_?qr`?a+0eZ%z`Q;WfMbY>9@lR!&%`6ut#G4?#epaO_!`REyFrD zmZ`N?Lye%elTQ!{;@Yfh{|sypVGYpJ(sOHy&Vk0`|9NTfripZ#YNFnrdgQd6@kRcA zAj>bhqSED!{EdRC9O#RNL+*I9dp>c-A1^?1Vm22*7$iA6QvfOgk(`$+@+;y`+v$5C zr@XN|)SHHL-e?}uL*vQ+PL+o`i&DSMGX4|LH@BicdLV-c(ZP8K!a3aaiDB9*-kEu9 zQ~_a#%lL09LAHd^+{&`=L)~yTElVFn=BFMP!?$7ITrKaAuF~E_y9q_6LIYX3B(M;Nl_O{G zOh{$>1>I*o88++U7c+XewG@@`8_WO37U}e(D-ZRVUl-!7wSIW!@@JULyN@=zW4{X#(<*d`dx!uAb zRF#6fr?jB6qK#<|M}RqOI9EoyXvG-i-KBQ;1gEn_7t1vq)d$J=1`55&!pLbHC_Lk( zEScmD+&x~O8UmMJ*L~MCUStosug5jY6<*{I$*0fr_@8iz%_}PDEWo1au z-*}(SW0rR%%%&ZduigUhnHW7Vgm+%2HfS=#1%N#&*4-NX%|oA}Buxm5?u zFkw+1F>jFw3Z)+hh*h=yc#^nVx80<`FH9NWJH*TbdsiLwyuCYxkqO!y!**>&tjyWyZII!u@HmweM?})wNCd#0oC`{+$)qgyakU zMOtgrSGe{)6zqeuCM(%@BQo;D{d0-HZ%?whY44kHF|!u7x@@?}R)e+eI-ZPrcy9TQ z2bW{Dtb{=ai6lMGAMS#&#b@6gXsJ8qmhSuobr%6`SAEw58Uo!B`qyKBc`b2IhG&nb z-)q-du1xeytq&(0lj~uZ+m4#;L@jqFA241C4+GcpHE%koMgnn{pR<5WIcX?{&*+I{ z)K?G2J7!@|>hL}0XMCySFWfm_D8PCz#U5`^w`-bh*i<%l-_dyPyY^Ejm8);V#Ad?k z-ijR57-Mk>&eW#qmtmUL3&!n4X27S{#O6~Ab9_)qES(j^U)c2kA2cR<;tM==Qrow*MQf?>88atC%}1u(1P#dg>6m zsZQRNx4%q$Q*lBJ%5;N}=LA{9XX4E_44C5d7dTL;a?0`VJM1A%msq~GPr(m~*e|{y z>hW({?7jkOAv6=r2J#7{NXO6G1d#yPp?K;6A0}~qvELx%&4(@B1HSBEaj1t_FJNY| zEbK>9Z@64HeA3@04WI+kz1ski#COfFs{$IDmB!EmAgLW`KLxy23{(3yT*R7nBjOku zt#9H%U(UwDC+8SbvhzE@jbXhN3<%&>CFHii*fAmuLr;uVxJOA>o>er35bJYC0 z>>EeUsh{^R>v=(0n9^2;0-o^ZSGTe;_2YF-YaKk#g|8FR)~YpBRdsfrx#%`BF8%6v z->)`xifS{fo}A;gx)S*dr*C0r2}b61HbxlPME^OPo`ZQM@OW^ec5FhhtfV;h`0c*b z{E5m+-u4PA)U@dKDxTD(>g>MeZ9sjp$XzKI__W*$OCo!s%}Kw|<5RlK#==VfvJY$Y z`FO>;?T8dQRL8l~s(5;`Zuxi7Rvm&~Zm~+WdiaS;Jf%!_O{?bRRs6{g;n_EK;0f&Y z#qp_cVWrTHYaZYu8+&?$@DSAH+1Akjxa*{Ms!PpX*Yv-bmS2QzmX z-Zod7-eTr0VR#5U?xfovdQjq5$e=w{reTVC6|Gs+7IqfST9UOI7W4mM>>Oh>d!j%8 zv~AnAjcMDqZM&Z~r)}G|Z5z{=p60Y|?c~3&_Log|U*5{i&B>`sX*{LT7-UN~%4YOQEgn z2!15LFdiYCr&Z@H(|*Wcknj5W8jP*4fpI>CIag@RM&RP|B;1KR3?5Q%5c{GgPJ6Vj z%+Zg6bkYePgWi)fsO|wq*@v!qZbCyhf0No&daBTHl_Zm_TJ0ZRGiI-Bxr(VNCy94o zCZ_Jx&QPITU7m|zw$~+23_H|577ex}6Y8WxijX$aSgf`(q3qE$URBZ9>yHFEq1abO zR>;|w`?IYWC64OiNQ$-szKxE44vJ;azVgM%mj76BQzWI(eidM`dthd#pwDc_mJtF2 zwO^tR<=!zF;HtuI)<=_P{D6)%V@xjHvIs64cEKRNYQ1FQUSeMADnnO};KHsi+wzHF zIg0BFMXg{;mocN)U?wN-kMk3c!B#aDzqp15<2W{L@{QiRR5ewBWUUaLg$=#OI2yxU zUD3ua3;D-tcB{z0D5QRoTgdz=m(52vUsXHFz2}Ymz7Yga^&?X* zwJ1!69vDf1P@rBsYe;{hKSgP9P7P|Ah6`j{MWm@UN;Rn|cFG#yKEEuX_b_zk3+|Yw zDc7q($3Kw;VHsgigo4|(KfR{b3%9Tw9czA~^N?GMcICsRUdj@;lOz!GCtOhO^-u_l zqJpH=S5v3LGZD*D3curkI4ob~ZFE0+dUEE`Q8uNzWuxusQR;;Qt}I~}3S?9UuM{xv z8^sJ~Ho0Ae!#4>|v??1tEca3^ZW3n&?7k<%b|&f9=`R()^8ISM_!8w$y4!uE*S-8( zvx~)in$u>H1ovQ)E(LmF1fTObb_5dlnz9EvV}y%F1?p$Df_7@gUh0lD5gT@^i?x4W|TxmsZ!y#U|7pDs?4^p2{#vYo16-X~ttIdZg7y z#%K~5?e9NAxjWxJ^-hk{Szb?%rfL$Qk79iGX6Eg=1!73aoj3@66nS0EN_qyzTZjnM zQ{826-EHx?)KagDWJj)iGDTaWBN6Q5EI%#bCD}X%>UY3nRB0B9s6U=wtkB<8>Uo^8 zA{X`*zO?G{P1i?H*MDI?FWmj<-j}y~=k4p2vzwMien59|gap^&=kRLOG|bRF%h^Ug zsu%K8O~K2XX!SR~A|BK&+U_6=luYTrzm-;2@Uz1@%A;~AMc-S-!I^07cO}G7v=tl~ zS$;$6C(kQu$C`bn%+KL}%=^wZ^!kQFn#i@Kyv;f#I~eb~OkVR{j@SIB?cUmpS{|K+ z>_Htzq|H@#`3ibHu2)jIykJIrOvQb;Cp;9^6TI3MeQ+eSj^B+wkNp-Us&%-iu^CaH zjWBm!rLM!~b6IZgJQY9O9a)CJu_{ikkAmY0kM7;j?xLA;6qt#=CQwI*qq2+J-rEh3NF$KXt1QzGCuZ+{K*#q5jVEFw8aLn*Tl?6K}z-yPjML$>os|ksMjOrkB`Qr?ZN0hBAbVJfWh?Iruq4NoUhmOv1fDArQBoktM5g^ z@_0KL&PS^O?)g=69f5D1<(Bt}h0LIwZ+$jbHjPLRGP5IcKzBu<#}l*V0)n`!evo&+ z{b=3uusulaODn*lAZv|qCgcvF-R~;%{P*E8cbWFK1-31zj%-~hTgVxtv;Duig4VUufG7Zp(lz5VIr(h*d> zJyUOAb7-_I^~*19l}Op>aaqg!+>9JUujq;6d4JwO(1+^5_asCs!VY_Hl`iv3?At(# zyLy8Uq6G&c=4UQJGEsOYwN8pd$9SZ1&iG5k~rUhAAZrf zmM8J$CRh@Y?a+U5l1%olwow?k+C5|J+Rf`Oy5sFK{ePXb-Z>d2Ka1xEz(CqS^3+)X zl>+T5BV4%L0_l;}@>T11aPg2qPvR?zUzIK9FJu&*&TxdA%OzkH%JmzGeqO>TG>#b$ z(!kHM2K#9`{R4Xo!XT7v^C#mReh}NCnAxeXbPyw=ef19WH;bY;6K=>0*IqfDYR>6*3@XzACP8JU`iKey1cHv~RFiHJIh~%Z87?>5w z7`SUWg@x!q|M7bm;tf3564?)SkolS6L#nMEPC01#oBGMCa{fW1s?mmbG2oG>$2r~3 z7<8A7Q!tn%FD&+!-YnLVV(eulhII^zF0_=+MNd7T0#mbfKe~-qk`gC(CBb^(T6Rwd55d{KZbsOPw6k_F~#XGuUmM$t`8~^a~3Gj}Mh~9nrG^Y;i^} z3Rhz9UYtUFU;0|Z=GfvpZwSdr#It+&M*^5HmH+#7I`=Av$#uziw3IJZZ~gA>Bwa77 z!PV%}Z@yC&i_d%z-b1afM}Ks?l8goRi;7V*@^gS9FNx*6{I5v^Tic(#!X4hxB_%8Xw>jxcD@b@P4_G}!uu=15UmP;&UDGq2nYZ%f;E#!5WE)t;* zw93Dxuy9jhQR2OD#0!pVUz#a7Pn}+7Ih)5UN{2lDd6oX@(QU|n;FZ%H!ro00OHC=Y zmY5*dv`5b2Evtu+A{C>>r69m_PEXP`R?o*LsCn)xGZ`~Ce?N8a}H93F{6Mnhu7 zM$&_x&Vybusc=GXl)?i)dH7FX6e)#@cvc+yusAx_tWyLix&XShuNCaW0x3vUkzk21 z(qyeLlLQjaqp%2w7Bqzpx_r=+l9q-}iS?q#E}k=vi`0}XJXovn2R_30A9}y*&+q5& z-@bvDHp-vTM5Y2O>LG)F+dJ-VTWbm0*{j{qe&k9v=XypoijP$c(;pLTpFWn09EWxx zv*P4>2@;lY!s)CilM8UlT*X4Hn|0F}qb6h3PK`uMRLP(fimNYHW8KK8NtUvgjAhW@fiq@lRgaQ|j##VYT6;p|{9R4ZiU7f4a1Xyk=e?AA_ z5UdsawO*<61 zg{KIePN%-CymfRk*HSjIG{Fi_*_l!qcd2Ddh!D~#+5=u&n?`g@*BTV|HcF9hx?_ZN ztMv344fP@OwpC7}tN1(hRCK43*TABFx`SCRnwB_PWMG&kdA@f>*1LYM-gSnBES}dO zO@Fo)|FS zZNmjzK?r4GZ72R<-&|QPfXc3GmI9xj7uwfDf<%d*e4fL;7BsLkM&?)}Oh{1mE%#YU z_`$w5MqK4P5={6lrWyb37w@q!L(Dsh(Z&+lMW||t{J2oX78$BBWpz`I`^^7}&G7vP zTMRUZ@=k=HdSz4oP&QcWs3^v(KU_Tx0`w0oGBlN@jkq0{@O-}QI4ws-upq5QC7Q9H zz`-vKe*C86p%lz^ykwHx*+0#-<~1bq`)elb4kI%V?PRq``*m8Z2}K{&5)!j8nPjyF z3UvOAx!P@~^I6!WnX`ou=k1x@xCd2qBpEkgJ;Ta@}D$8EO< zUDoVq`??JhCN*Lc)y*gV=*RE(smr80^Hwy7sJ6)5Qj0EnPz`o)vN~I%QuKAettW>~9v$9RGH=L^!oe(otr!rM-`{ z>BOEW#xz`P=rf&|l#c_hoauTZ(+vIVHO*v9c~E{6kM~_xlFMMr*h+4}!n_x+oU_r7 zaTJrT#zh*daa(K{%#Mbkh0M(MRw}TGG6=(atSZ{hzvopuA1vBIPoR9%W(b2yDST|< zP2wtR+hP4xl~|;;LTOG^g45jnlT%-;cIM2LZ^p-AI(gagq#aul)AiD9RXNp`%jAip zSS5}QT!xzkwiwCXj1xWD=!y;?0Yv;gbX)ihIueLXsFigEpJoJ2#OgrICJ9Xs)A;Z7 z^+p^)cVPrFXFS~@tNF3le#6MsOLcyOA+4Ytq8vRpuN$)9d<||Nc(-a6T zGSl%-6QHS}WIt@mqZ%6m5?oUr=Hrn=59_mp_Z7mEWgc6~al_K|m~SZH9-@r@ID28Y zvn*m|#1BQrgB``tVb@)wZin8mTRW>)JF5r6o2ehWlDkzV9k!?p)WJ3FyKm zsEO0BOxQp8rHPZz6hAM-@(YfbL4+K}=XF^!DR;qC6(Rmj4+C>ODy(7Y347^DlgVY9 zyDNXVI(T=sp!x;ntmQ_!5ppQOHES?O$$x8xo$EIQD>)0smD3WA{6wNCM`n!%+EiJM zgSEmXHM| zm)r{Vd7WAH>b1N%JboR`!J5@iFaWf!Fg_j^AoNPSVcA${OVL&BN0{hnQUBbT=XEh~(+{Tn|?WpPmIXl97gA&I0(m z1FnAxluSK6>@KLSF5_1=oXJXc?k(Fds&{J)55(ndQIRWgrBq<}lj`l8W>;*wR4WQ@QMy1BSxY>pb!#uG;ue51E!ZWY% z>UCLB-?p4Zn&T4c4$oMIZw}PISNPeIY|MeBMPCHLb#-et6Q&e>o2C9{g`MovQrHr& z=Ui7Ns$pox5H8zHv!xK%5IU<}%UZW##xE8`Yv!R!k3W*@V6xtGBw;?+W1c(cmKfe? zT~Lgv1Ys;rmI_|Df|mW|G<17XCKT%tuFG5v?#nhJ#eDKW7aCEDYjqk+tn+5@X<-Bj zgN}S)oCVjA%~eJ)!a54xiV-yyEt@L|0rE%&bQ2yNk!+7Ef9xEedwl35nX!_wOAw0s z;C3O-td}aGPdZK8HtN^pm4$#@cZ_S0`lYF7+9+mqcKY+-j=t2%Err8Y5mHCW!K~a^ zRtH#%+IIoGV{Qut?~5oP&qb5jZHJ*^#ATbE zg!!G;Qd|bbfpBH&fP~ghN8u#tS)H>G$X=v&yoargW0|EQ4ca7&Pnt`-Sm>cW6Eb>e zvfzd7!3LucDH7xSmB-1sF<6Pmf<~Lrr-H`dMIo!20*sJ0S?X=7Q{w?6@Osl5L~?~K zQI8Xy3-ui6ahN(gXQpLHb;*snxsNYxR$E2R6mU|>gWv6b<97yjhThM)Qy-UiAD6!M zDV;pB%`d$>5ix|fv!@Y`jqT9aHm4D6sZn8fBSR5ycOxp&E(hB1ikM6{~ zko$PTACQrXK`&s)f#CbUg+H*MQGjBf!Z{$I5GLS}EX2^@)^$>!&hj5P#B2*wKg4Yo z2Nu8@4cVDrh!;J*jV^+IkP~4CAfxJ=mWsBJ;tCUmzO~fI#W*y&C>}y~bH0abd!t0h zfcdn4KGa8}MXfr=+xrY&wHGyN^AV1Ag z%sPxw&7^sRh~}ii?OHnDoM7)6oV=wbltMDGq*OyrvA79v8HdTw-HJY%!f(^ z^)+^{v7*ER`=cW*swUhpGGF1nPua{0qxEV@*Q+P^Q(SKhtueH#NMT=eg5M2mNt=}- zc3c~y_3B8OGzS}-pGTLAPOH2MY5SfswUjYz_hfZ3_^M4D)h{Ze8JxBdJ%9a?MKXLu zps02Ygys?Ylz(0%UgCPF>t<)y&+7Ci_PM|B%O-v3g&T){_t=hq*g@HW@{M~EtM$c_>_cmW#H^CtqdsK0)rF?%qF8)l?33 zopXk={XFV#2ad!04nT=w{PeJS({;GP(|bz-AvjoP!W${WIZV!ED|`J?JQl0@Ao@Sa zK`6ai&8heRK$JKD!190hef}TgQ%grzH&$*IcCP>0pI++v`{`_ ze$!MwEqbJ5P9If6!JFbG5GEmwJzf zyYl7KBFAp)J+;s3=9xb|PYN#Kq4;{88>KA;n{|pWWVrA3Y6NS~Mcl4ev-|MedDs5O zc+zg(?v+|s`p-DCd!c2~uzY_33dz^`A!Xld`5v#K_= zp#RuTj2xwIn<{5yGU)>52Y;nDd3YrW3{LFax1&w;d{&Dn2V`Q8GXu*hRn9;-=;F)m z<`1W!?6iIRIL}kYFJBMt_K-j^u06&3{1L6Q=s{jNe4kHXB3L^+3 z9wW3p)B%YB$bnJdBhVelDNI!Wwt(0H??ht+HvqQ*&kXVd2m%fG01)hfSplm-G(qaX z^*}oC8!(U|LjmgmR3JAoWCK817#uj6)sNFa;Doj(F@OL(2U!QHgV=`bz_`&EU>r~w zKpfBov;(?8JVCC&w?Vw{3g8;>ocN583Xl>YO8^bWuY|&=PFzOV%;4DI@PI3bZLq&k z7ZCh_4x$F61PC(#yc3ZT_zmJf2oMbDJg@~~3RE5l81M!3Lwa%Tp$v2buEF`?y`XP& zfxp3fkZ!2=xB&f#UQ9-yH$MiT0R3QI_&3;lpTOUc_~867F9HLS07XCm){XH%1Rxn0 z5B3GPVHsEk#)AeB-Jl!6-p~wCfdoQ(Vcx*+p#g`1qQH4DL8uqg0ZWj4kPno-WuWyy zI!GYo3+kRCh#|Ni$PE>c4A>9qhjEh*><4`C?9l;FK;}XE5MJ;G48i&!UMTnQfc0Q~ zkX|4+?Lai(0T5@v6EF^t1o?)3ff|Sb@(lb2vj7Q%;)Z#_8kh!NgY`iP16jWnf!Cl0 zpdV0x>cNkAz&GRz$sQDdzU}@S)(hdrbf6m83}gc?12qBV03nDEq&i;KMbbPCS{Krn=osT78@USID<77BV?0pJEzj7PYhuo zommSI;%l@g5CUb)!5tmftdTN6xroJ&5)#D1oITip6PqkRD{GhlwTG68hA@!I%FPOi zG|rp@kC-xujH5?1?8df`ZM?go-_+e;2B|nK%0oTbljohkzxiU z8@b`$%qDEgo+1u>@E`+=34sO&q_64xG)nNakvcc5<`iLqzOU9CW)7$>lQ*ocQ9)D3 zJ_^r{6WImbo%f%!OxanXSFj|(cXI270+=?{^}YIy6LR&mir0ur2qS+}qBOJyx!L%a z_(M@c5+bGaov_~+cKv!!@m7$U18LQ--Ns=?zUI?rHj2_~y`pyyl$!(7ca04>%r9b3 z$iR%tRQ+zfq@Xiowgw`ZaOHcg6&y^9<1z0Mtrxm*jBJhq2aPB12>dd8m!dt+&DJK{ zw4AVjNoiaX6P94`aIG-oM&sX!!(t<1djo>hRCyne8oWQuvs_)M+b4La;Jn)L5$I=v zr`XH1fE(@R>q@*ebG)nq!|(0B)z~Jj+LrTHoVl6q2XvEKE-55;+4c$+tk!8enSG8# zzg0^Po-bt)nL|T%ZFYry3gT<`+w)p~XA|bS$HSAqM>L&&Zf<7nekJyP@TTl^&Pwre zoX=fqEmgt?FZk`*K3tp=Nx46jys>S@1$`Czzi&N91n&$ATl&oB%_S}h@KawM_D*|T zPezOrkQFyGDzEY}uO)0bxgFXXW#Vv0Mx>~!%ZQGLn%31FS)E`0Yb>X_aAly&yU9~o zv#;SPuF-7`6}7z#eYcZSPpk7Gok7wI2CH^9vc42UKx}D~-z@h~yyo3)av$_Y6oR^(3ass6;FNLVCv~Q z;5?wcLG*Jg=1Cw-M{<#|TsJhzRDlf4W`@Agpg~iL&0=Ix@U%Xg9D|8T)g|V zYkm5e{hQMCHXMRMM12Qii)=D7GO*9n6z;wV3dyH9AAJK~NDtJXc+USWD+*GjLQOS> z7Ct42?LyJ|#6+5lqP#X1g8M2^U?4@(;vJpk7_i&i$nZHC7_824E=XInNJ}Ev1#BSw zPy}+S+0l&UxlCc$21(H8elHNQx~ax%6=F-0p+>)f1yx`yef$U(iZQ@N{FPZGjh^w0undc6K%q;`LSZra}6x(DxPvENGk&gB~Xy9r{Lm?w8zdf%Z_{(SmOJ3|lnGKiq07cM6f`8yO9=|YA=2q!B+i=2SBJ%s!^FJQ$U%TWAw&@fofSAKOL9DO zK0*Fy#KsasZoK?V%aEV2f56W5GsF-|eyO$~+rDfh)#37(rR=jLC`__qI>|BfvFu-G zK{`LR{Vt4oq{_sGp&x=KU1P5m6g6V>dCVV>L`705iX_*Ffvj+vI3&a6<39_2uM~9= z{uJ{I2vm#B!&xx>6KO#nj1owWQX(TT2*rpFo%ttY9*gT?8^ILBkQwE{cF}7gGApVh zUN;EkA?%}Pv%|usFX8G0p4|i`kRH7aRD=J5?IH3w9&b@>1)W|#H29-QOAKxVOmNPG zrExG6lZAecjUj=#hFf=%VEAd+wfD5buVY z)41O9-Q@8&V{XGSZo2$0LR}t|)Uf1M1f~@SZjJ6gr|t4wiqH6yguk(U>rTWj-RnCR zLAOd6kM)Su=5j4~>up;w^r?23Jk#QOn(28N^{;|k=GREqY4}fe?L5qEEz7aX1`m~7 z6tmnP5Rp7AuCF(;3Gk^Ep)PNr{Q91p3it`kaC2@21Br20P@U-p2+A#7!e7Zy`f;Eo zP(d69{%Db7A0Q4BaTN7Ym%ERQM^(LY7|?uVE1pQ8L1S;5W?E2-k)s%_*5mbcFb(tq z38S?ZFYVxj4qLG{MEEB+AcVkqvZA#Te0v&GF|d>P!h02tWlHZv7}A$If0-vgQ8@^7 z4d!U0?c-PcoL%K4uv;HlS~xAWi`-TB*MNGWMU zG38ov#qV8B?P@5-td6QV0((MMuM^cH?#}9h-7awMH>T{-Q5PJNdm<-;ncvycNnkE%k3nO{ zM2Ud(!=AP9YGTUGr>!hfi%>g_uqR<;er>vVdrsvkRi;gOEyddcHaa`C2`TAo|Ey@` z`_<=wU(xY`W}Imeuj@_z0wJ(3)vs2B_X+D9jVQk=)o+VLhn7_}yWOW|_*eE-Z#zS> ze0t6(UJqc;hdf=d(8ID9|Kc|sXjZ(HoVpuspCP-Npk zFI-5H;K5ZEFRQhZLpgS$@dRT75G^PzMPr06Rm{ni1n;H7#xm68Qr;4Gwh3ZbRsFY@ zyv(jvzwf`+-hR)qCq*z@dec>te@(gMI!<078@;OnyR6GN20UZ^D0H7*URLHTxfK8T zhH$Dn-kI0-JsN$d*jL@|?Jc(x$@o9iWxfd~_A0i@{gkiIf07sQ+Qy*o6TFcdm-FWi z?B#9!+PhhPFDD@KcPbO?xOsD5UM$X@IjCFMN7yL4fQl&F{ny$1wP_oF@iz06=~La3 znpgQ=mPYi8cZY4q_wN~6-Sl)X7&-ABqLkUU%PvXj9YcMik*e;-880_dX*snYtjEBn zA@Ye@UutSzNrf8uk$?^FfcpDJ83FlT8_9;fZ-G#E`{ft;Ini#q_4Gr0DO*yBA4CCo zwHb~1cLKZifPxO`ws)RSE?maM>-d&=x^b)1u{1SzN9YSB_)+6ADr&UM;WJx%UPu^~ zE~8*A_l`SSW9QgWGDCbV4Q%!au7lrf3bk2;@Ck2L8>%X7BV(_kkPf~)V0XLw5XOlUeg!m1z`)R>k;+@msL zfzV{*1?jBR<-1&BT?9T=Ms((@G>toDhs_CAKS~m4%H=D4WPYpern7FQI8l^|3S*o~ zCy7z3D@v=%go)B)j0y7?Ol6CzVQ4#{!ch+^cACo=hzMgyYfT}M?PA@5PLgk>_(`Oo z&ZO{^icuV@Ll&x=S<-aEZmJzx)3CrPtCNS4v_#L)l!{9u7VtB`;3KQ!{Ci6lRof>C zl{lLfx`LDqQ90Fkix%}dL2q*hNDct4k~$Jm! z?gTZdoY+^%!&Iq~(o)qS6si9A6qk7LOudj)$tG8=`~)x$CY1$Dnc8#vmj@DBVHXS? zY%|q={j@R|KA8VTU{#WG(McxCNb8Yk)GNTqbADK1z`|IICdh|10sbcj8))me65n3% z7e*jBj8r9j5p75hEj#kmW3q^35-PYFiBm#~I4-obOoComv@j^tX#zR_zkKe4Maljf z83Nx-`5|l$9f~cc4(Xlx@9V7CG`b~q8vqX=m@p^>jV}4mL{#xJ*r)<>#>BcqkQETA zapJ~Olm&1r$QFQ#CL&LW=XBIX3X*J_q{2@>tk)8I7X#_IlGi&~Mgtlc zF-$qZ%7U>at)wkdPqD8H32#|e&!#;XZy7bjXH5#2s{}It?2Fa(Lk6S?yk3%QA2F9$ zyRh&GJ=jgcSGKz`Gkvqn!{iC9Dwmn%qy(%<$2tS#u7HMgy zEQ-4qvrqQ#!*rN6DbQY3kEG+j4&Esmj7y_Z_r*a47*T1&I)Dj++=Te=CU)?<)5Gqe zJMU8+QI%XjuvhLTZXH;{q?}UJPMju4Qbq*Q#hd#iA53o`d8(ilbaHD=h4DdDHxP34D-lf8>30V)cF z0<_*WiW676luX4|z_d5tBXjnRmb%3(DZ-oNCNWC%B-M8oY4Ro<6M|Yy^vrFtR}p$1 zdc4n~9EQ8qL&iYzPUrT+Q-Zt0kR;=l@WjN2$;>jIA}in;bTsZR#koNm*pK@klw$El zlVFEYPg7C6M-$i8P)_vs=%^o5VGRp(HuPtf|ope zFj&vM9X2VuEwx+Ybk&(F^wY{!D<_0bwT}^v!v@jRL48 zrU;!kx-wNx1URwJ*~FE3`%q_o8$wLkEs9ryC5q#5?V)ru)iFd7Z908Csd(V@!u?C_ za@B{*CmG2qOkwsmN0-6dU6_J!ZE&LuYQGrOYzpnVncE7*`d8`t{bk-^mWtneX3b=f z!h-!lhX1m(q!6yPC>gR=oC}0!vigKb#P@Uk<#PY|%{JrI!IgQoJYzI*6SiDfvi>5} zPNdDQz%nu=B6w-MiDDCxcB?oYr?helvWcQ|HA%BHBpd@Pz{fDaUiCb&mCQu(TXGN+ z0U$2i;-Fe{o7Drgx_JaDS*!H%<-AFE=WyssQxwpT?{EgkL;s6&QAMOKW^I>e(^qF~ za(Sr_1TT-xjD}}iy>-D^8ByGaDXrpfhQ*GzVM#m(qnjcxz8++DhU*Iz)<-hj*QEGY zCCy2mo|7VD2NhQD?PAFaL$=;Odh8QrCw0de#3_bKVO@2CcGM9FUnKm4wwZ>5O73xH zw_SBk88=6pl@3Ij8{;F*(ViB^*@MQchOTlo{U~Yj_fUw%QBaCZfV})4fz&!zsS5Kb z!D9Bu!niG!r{Gwbz}3O;Tutk$Z3ku68^23Ip9oPLbw&+883})&BuTe7K|R7{`?Ut4 z()1Zy(eC-S!r{(!>+(kak9Ys!Vax4*+?Gk7BvY7=+A%5CQA=Fy!A+gp{gpVgK+4?A z+D;Ch1||Yay9}0_EMxZQuo7(!)vDq)C!4L~q6 z5LfrwcR+$8p?spiE0r>fh8doj^*TMvWV zC5RkX*w%WrdrrgVqrkqeEKy9h7gu2UIhk(GJlBe3@Z3o|H(9{*{SV7S8_%OsAC*oZ ze^buC!W?_C;U@?i)XyME3XUiu=aj#Asc8znljn-L#4+oAxjW#BS4La+3N%6|7Zx1l zDc6QKlP3|AwaQ6a3O-RP&SCKL2{;y)8D6?oA3tL#I!}*NreZmm_5if?;)-oCl}r_A zN#OEZ3UnTxe7n_J0-dflpK9}R#hPDP*4tVsEP212D^A5O&HsfCH+SnZij*?7`v>b0 zMqATbHY`fZYBVe*@K07zM_q@!qO$jocZi?@Y8~2V1dBPS4Hxzb%;i^1xphPip>p>5 zrw{&g=#h+lm1zTs`?bJ8PoKa*%9wWAZr{S3pZaR7jYm}5!DU^kD(_G1eS6xzGtEy? zjX3j|4vkj|w@o8gq736}EkyNUi(P#En#5t%uLX}IRh7B(KIN+8ZFT~vx!TT;FzapW zRsTGWoIcNS;BS%{Q0hOu>)DDl(~$=fsRklaM{78_f*`gSa#W>B7~2fge^vCd`=vd` ziQ8#1Am()L+`)*BWVVY@qhm0zl&|PfQo&11IH*sDW$9$44Z2x4elO%TrX@{It?q_T z(^EKxdn)uEX`NHB<;k1HsI+SfZt`!k9cYi+#!Q6`6k6Mi!>k|c?J4y#J8eJ={M>LD z*rE+Z<~jtqLwGd}MOL4iTSicyds;?NuoM$eJ#q6H<=~>nm9zJ>js&AY_XCl zZTXvbsJy9UnLy2#Yfhi0tjYTb?O5)96fI6t-&LMJA;d}l&MjNSgD<30ZQ*S4hl%Tx-L9t~v^#JEd%+Y!c|XN0D^#~O(^l+!fZ(SL|k zNS&V-m2ONl_i$Ln@I!At-%$2big-t^Yp$7k5u0ZH=L2$zTN34 zx%fL@MYGF`B0LD{mt%Hc_9YtbL#KV?NWP+65WiJb_XDFN)Lj1JtL_QTzrvO6eViBq zpPSM{`?2=_yrh^=^~O-FLIl(e%@BGpW@xX;^3e+R?mYbZ5^+rT{a2&NJRMhC!9B_C zhIEXr-Q*JC3DW!%>x6*FQ{afVC7`Ud*Xw%3^1EYN_rv!YM)QPcNnmS*D;}X|SKUC- zHBYf`BYKxl%WRIR0OOtMmyB_;XzX1|zVcYb)JY2KRHo^QadxDfuQ0y4*Zf)ZM^{m( zSxx;=(zB~rRb{=G0?r_E%B9H--i5)E*EYEtFKMmxiPq zE;iw>WDDY7Su$dD_+P(`epQ*)sV6VeJj1M~>A9WgVDGqtHMlhO0zn3x!jP}$`1gY0 z?`olM?dE=#>m#0|Ew`#GAPnYtgb$bQck!LZRa@8(}}yr2dad1aDXzQ!d>tuJTUi|HuTgdF*xjt9(*&h$c*60X{xSzJM$6Oe=2G^exDk{N|iNGWwbS|kW7{gi0RSBTKN_c@y|L+kb)H3dlbu&UC%bkcBtLH^Lra6@`Gp32zu; z)`PP)=9)-u*n9MLs31a}u2j;UO=c|&7j1Y`9@JKkfnamQNdAyP(1EMA+n}BHGjBDj zzNi$`JnK$;q1S zYxLy|3nYH7Zj_s!u{0BohF$OR)UJJTK+UlKOn$zZ^W{2@T`&PeD|%~Tg;4Rqb1Bpv z)m^y?TCPpuCS;a9sXdhZHROm=$1EK`EB+RLjj@gcd7?t;zL~M0K)=`!214dEm|;N}3I+BKR|uCwC6%e(~m&@3#AoY+-5$s@Eehd)4>P z*Oj`^cZ%TXYedw%>Gu`&$L2>cn!HqvzCd7~tm)zPNCaKOR@SM&TFg2n5r5;w@Q!Si2O5Rt znY2;ys&2ENxn<3NT2$D&C_+hTnDo&qHaSofcr-a^ zXXT%LFQ|v>QG~MvXZN}O$i4Ucy?Gr~88J0^aur|C=$SjG3kldL>$isnpo>LsP<&wakd@~dO2y8=C zCYt%y{zR%Nya02@CF~od$|v*?Au;GbSMb2){vgfoB#w;E-mdAnj)1HG!fLmX-Jm&X z0RSgW0D$@bv7fQAvoQa6E4%cA@={ZOdg-yw%#`zqNFYo~;H;PPrXg3ArY3<{B9W7= zccdhNfoX`w#r%khj)ujqX{d;?HD6tA{Z|5EB<2i`;fXQUBmn|f_vU*tHzUgovZ>#H z{r&!xe?ORcf7SiCy?oVadFLVwj2#c4?EB8;SeZNc%;mZyy9>xi9P{eQ_TtP#I6rhZ ze#D04?oa+rQGrFQw?K1&x*CJbXy)wPi~WZb^SSz2v?F7b;t@6Uy|JUkAleBgX8Oz= zI=!hQuvcivoSv~hooqXQ6zmKPneiI_0=5cC#0wAuwGKtbpD-$VCW=frvS0XEh#fz# zJJJl{xW6fK5&Q`{?{YS*WKFG$%UR2gpBmm=in1(2resa7i_1~V?n2E=HilpYd3<+n zIkVH2$B3C))4-SG(>y%4Z}K@cWn+pF13|B2&>CFg6xNrI1JwNLoe@?|&vb6d;`<}O z|KzENG2!#hkZZqIhWL*BPVUyy>el?b=nKRI8FmEBqpTK(d2tVUQ3$|5FRIWT^7rD@ zAqcki=|}d?+33X%aB`0zs7kxsk@a}%(2CHmDOWRo)9cX>uPsWGso_?e8XwgRTAx)5 zmtG`Dx6%5N;v$fE`V_*?>iTe(2SRWr{PH>NtxvL1`~22&Rv`=$nJU8+%4Dx1}toJ6P~CShvRXV}b=;YQ0_09lg7 zSEQ3zm!X%S|AHN3Ypa#TIY~eKsS~Y}+7NK(?7|o|D>JVyl)r21iG zGJpv<0^~tCLG-MGJV_2T0qDT&5CQC^wg7gp4R~{0fRzjp=-)qtp8{+-NH16b!eGr9 zfG-*|2iJhH!$Ln`0dxY)2@U8DfCca)?mYwS7EE{mYOg+k`r%@j26n(T$P3~WGKcZk zRW;xP+K2&=ZW+F*WQ_qdqxMH`ApgzEuM3TTLI`%l4$KA6g7hG%1HZ1Y03nH)_yBt) zNcw97z#a&I7|+4${EG;{u&v++j@92rC761f+><1_1s5 z7{MA)2Iv4DVBDaO!2S$wuO2jJt(q+uzyo}UmJ@{0fWKUbBk9#RKvb3=To`H3mwFm- zfo-$^7vcpw5)%Z|%?DBq0n{nD6z1!O0umV1fWs@}){~Zk zL7F>>Kmg>XxFBMn021>AwSZBU5mjI-s0KW75QO>3{x7@D7$5vO8D`2&a9hQuA1Eo= zIIfzhJDDV|Q3x@NBN_JYcj$&ruu4TCErEISx^`e(7v1>Z>p}Sy&30dKzn_C-`f} z80Esbau=+FU%AFLJ+!<3a$#uWj9@EouI&zF_EjpjQBm%EN=w_`EyVP2@4SG`kp~X4 zy81-up{fnpbo^J;ye7e+i)5SW}PtJ!j z2MfKK%uaSbW6$cPhain$&EEXL-IoCB<)9iVBH45)a6%L*EHUC}1>1w3Gyoruii&iJ zb9yY)w5=HRRW~VgI4P9x<;sQR=TYT>NB6dzjtwb!1KgRjjdHs%u_B>a)ELh^EKs|k9_0AyvqOUoX0yab8e1Xr zP0L5F4?joAvi8iTlZ_PlV;#lzYtXgvy^DvGa+AH-qf17V z^VznIL~@n_<3#AoWM(6UOg;cm!!!Z68xGe<9=-wtvLyn{=K%)R6{!bNnEvW!>dfgu zG4uB`wT@W{(6dOxcxQw&8oN~dA5CTG&2L*qfy ze-o5(ZrXPUQ!=`d@uVeo5jS;?Hzqe8Jlw8+fqYSs2d#j!OqdE~*z9+Ims{rSx83PE z>U9FLZ2Bu|BTVv8-0I-G1XmMkj>0hbO>w44n{kS7IhPhHZ>ut zd8EDz5f2Q6LJk$`UJ7aA3*B`;dQ@e5ur=bdTBTE#uYkOtmV|}&@_0%yX||P`9U58M z@rd;q8wERA4&Pi?f=L6N>J|Mtx6GU}8@^=H)Q>iBDpY)hCuR!^I~aR~h1VRSDDnaI znzbeHS`O@0T%Gb_cL_aX_5_(&Sqldp3Z(_=9Z^NnI`-_i&R`1XKU>1be)_0L zTIr334wft*@T$mTv$tT5bKv#{uhGF24_U1=GdxOA6cWSK#_gaDRj^d8Nc1X~s-W>g zh~xg0@j$fED_mLT6)9ZClgc4x2d`ZJD^HtjP`|b)lR7Asgqy>+aMl7xCqO0Gmp8K$g&si%@!(c5+YnDZXS^70JYydwFOT( zq9b7$o?z8iCPQ{rBd)(EI|up~+JsuT5MHzb0m6FZUxKX2T?}8UUS>#x;+Ji8qk+EY zs{9|;=!iHlQQXMt9LP!>6nvPt9V8}t%(|T}BjRk#B>a@L1~W+D86DA^6gg64h~_v$ z1k5;zhxqmxzcbhS8UCjYMx0&pdMMi~!F#>lubtq1FcVoPMa`rcYU@VnWHx`bm7{+; z9LxlidGErNnAZ%i!lQ^H<#W1HX`cd)DQ2R!n=EMm$jCT|^-j`JZ)Cn_G%0n7R{POn zozD7c8|PwxMY*0IeVMIU<;LG*) z)wi#=qfYIM_+G`t?!Cn2IP}-96l<4|g3fQ=No_3o zo$__Nu56=CyYMi2s7}p={eg-gX0KL_<;xeA-6ZW{o5WZu`60O-Z0Nqy`LTQXQyCnw znd#4X9`^tHOoG5!y1VYNT&R7qf?;a@X}wI+_!Y#pTt!y^ z12^=(kx-CG=s0f+K&V|A&OjMyX-mBEbPr`;t)IZVbT#y)pSl{LrMH<2f$LzWcF{@+ zTCivVuSv*v$VpY2E1nRjSgzhsxU^IfJk$zNEE=LpiBzM}C@EH>W)*wkrz8!@RF&lH zw_;woU!hK>P9%j4Ug@hOY)Mc!Oiooupw7`U|JMZ)E?Gxxk)kEMM^0DEgh{z7rc_fb zQ@m5Wd}&!E6hiZy9I;UOoGfTOcacn5t31b*60ve<6{07ZsKr(#Ow|&ka6(tQlheeoswX}M#Wt%R-~ki zT_jjjLWyUovWP0G1RWyPf8i9VIzrt_RY2ZC?Y7~Sq-D62sFj4hdhS41HD!r%vmwE1 ztukF*$V$V?Deb@_xM5?ZWjT6XtXfiV?M5b-E?E^?zlp|Pm7<_ntXk8sIh|=%wQfa{ zf+|ayf-P0lh!|oz0kSz2Ez(%bxc`<#OJu0QfMpYw|&MlMFR;sW= zb37BbDre5(3Y%tmie0JE*b24P%(Y3&utga!eH)69DxUA4};iPo~!?A+|>Mt zG%n5JxG607H+z7f8z1v88%G%s)IaPG3IEK#g%|mR?YaDmg!Q}-w5M@~J-0tYv^E$z zl;~WtDYM~`dOTcSOcd7#_Y3Q|?b1>CGbcy7S~9L$lUVc1mWnG;ZK(S7B_1p9ek>ON z-NOBpDJ^-fqFk31uSTqAlctr&JjGf6$Y08e`YSdcn@6nyzjko+h;|Me;OKA_yZdhnG3m20Hh596jGS*BGTW-0p#<`02Jt| z?87jZVI{mYkhmDp^=U|ysBc{0EkhH|e)jG!k?yNe?V=XJ2<@Hh*cU>d9|(}q4(0b) z{u)`NO`!o9G-nXYq23A3bj(WNdsXOY6OTSED?0u@TbkLOcsbc)^3A%e*puH3pL@#rNKTmI-s3vn?{d#t=RdLKWX~4k> zoROJcCt-yPl+f@6avX^3D71dD+7NA6H+Dc3cbKOnk!8@4p*ww#p~~CfecUZ}4Z~)@p~mE- zhN4?)LO_X3AOh;Vn;i-J2O}}*D79vo?ae|BE<-G^TMr&bhA2zH@LOqvB%NVGh9nl3 zJqV2LdS(K0+YVoQVmoM^t$`l!&c6h>1MJ%Y{b(p4ksy+-I0tU9o!-BI9R?#ZDt=wz z8d&I{Rzl?Li*<_bTledn7Z8FL@(2ZM1zSBzuy>{lE{x|6dBAq=1=5g%GXYM4Dt(^} zh=KZAV#Kutv!evn#Ub-Aqho;LhX%+nS)iH#=#1@bLTxy(op&1-*88mv+a!S?*H`dh zw1}Nyo7CXCOuEyuxVE7IzL!0t9Nw=~eenkhj@&vkw%7_$1?{rIN08uDgNIua2|&SH z-a%F0b6@trQ9`Is<+RN+gypfd?vOwd1ji0bLez-5-Py+$f5@F;arB}AZXe+Kyc`QG z7MqAkK2Up_x&JtdHi)wUB8*^LFfMRu$3;lNwfl4QMAiYA-c;gX#j*Q4izLtjU>u#$ z-2F_zN)}l@(T7Z;@$!4~GgLht9x|gwc7#L35>5UG)}dryC>c+RIu;; z`3qk<%_;UDY6rC^9w%gWn*k;_jvlZfqa8qV%jgFRCIb5ghkQ_R!$9wx z)!~QI&Bu~D3>IPYC&Z7Ds6x!iWJY{9f(*1<-w410YFN4eqEjGZ<0-1xyD?&7zC4I- z!m9bPd)Ce*2*zDXpQ%yOa<)R&jXprY z_Lw3X{b)uUa32_TunF_ZD0c3q_K_CqF9G*sP!7Z`Aur0Df16%_*#aL>iqstI_>b+7 z$il7~=dNSCV~%zwZzdr8p3u3(SuLufZWkZAhnl;xwR2zvf4DZ$K+#RdDpC9riH|ZwpYcYwl#%M|-v1dLdFx zhDqt-tQCkvumyKg{T{C2#JM2K&^g=`<~UQGTY1_9&{$FknjMyH~^6f-VxOHlzm@0mhu+l1-`8*oGhVG zNDC{1=iw$^E6rHX$@>AT8Ta*%L60xN(N5v!3y2PHF^s_9?QkuL0xBi~<08}EQAJl2 zaWGamlVz)pj6z+ZtLS2|3YB6AHrx*~C)ht~5yhAlR70K;xd~#Pp@)w^d;KwEnyw@} zi2{Qvu^G&a1XS_Ax;I8Q?23^I3BW6qTn=a7fJ1#i88PuwLf8qgaK<$6ZNRZ9GZ)v( zZDGan_c&gA+j&I_quDX2oR1c*^KJ2gj^K%~1np3;c`}L!H~dW|?Lx(i3F7f&j(3Q! z@1aHuYH;%il~F@9;2v$4)3!*Q@(-8@q={p4`T_VBFA_!hi||}ogm}FvS>Q_EsH?zS zl(hRAcEIuN5t~Xw_t}mQsxV+?|$rVmuV;M+dlo0&=j>wPTSRjS1uea4%N0N&~7{u^S zM1{w+kdT;zGk<>c{jSH65`<#IQJqAJDaA>*vS5fqbP%bIf+Q5_$3iA~e-kT>{r?5W z;eWiHMT7{&NkN@NgcM>WCT5Sqag#=`B(@ZpVq*>uPr8Xv9;SX)$B9`6n{^r?$6gpx zX$Qb|OSfxvr5YhIq_`P0O~yqNMN5OMS&~HQ@$tJX8X-5ijLfXF9H^7S?_24Gp~wmw zoh5BsZOT#$}eI`(DTI^4y$xQhb7(6a(^Mixd>XIA`9n)a2@)Q$vHJ{}KR=4#U83zE! zg$H00Xiy=$8arF%b{BaK%yDgbw+BmTR$6~&yJ-i_x2{aB;X=*i{=xRf`B)jR$4ND~ zJ~y(b!t0?ccJM#?gJgnYO_n)G}$dVkUjeBAqomJCkdhd|vo&|1>onnzR0-s6fBkk(^NoF_kMzsk-Fd6dR3W;5&U zc?R1U%6b|Y+iR+d0RuccVYqVjM2`Q;#)rZi;{uv31ZBg$Z-s=a6Tdobv z%VojAwpgWkrMMKXT{0{ySxl#-$TD@)#i~Uu5C^Jez=ZJ}6&$R#luD_DX-=yIdUTn* zC3dM?r8$uh7F?=KiTa7WC6pc8l{`eMNFCcDQ7!3@q##W)bWHnOD!4Pl#)WJX`d>FiPV94dSXCP9f;6#6BC*D(9BuDxW*K^`2qwAMV(pMO-1I7$Hz*7lFft|6>%#RVnT24OdDJ?VQRN`+Wi@fv_VoT1N*lW+l*dkuwVfq$ss@ap^wvl|I;T(h)lC+e zV^;F1*KvIGgvRQ=WOMb_ea2RAl@4`94UjY$c2ehrb_okDRYERnm0jrCJ|bc5P@-gR zaI7z@AjW2=Kxv^>MV8#2X_NwHNGK^(qMqRZCs&R}-SQ#+atKv7ATRlHUq*|_t(!h< zRY^@7^S5Nb%@L}mL1?0)pXfAbYJp2-T4`>_oo8P3Cf0NQUBm#} zpR-^IlPFi)8~YO8d-}H-pH5IEFXzvEfre3I^HL*0m(+*cLzl@#_7LG}<%*%TqiYf) zJKK(QN#I!F)@jA+qMjRH&ymF1dh;L^NBNHKy;6F~Cm=}gXFr7Dq)8J@ujZbdcx|o?J`q&0)r1Nv+|#NOHHTcptK89~l8U zmx#hCNvaLKr?tKr>9OW_(O%?DGm+sLg~%+j{|7>5@y*Wn+q}ewWdGkD`=LN*JFQ?Z zp5hyM_FC^6O2yIg5tiH~suc!H8Le%~AD;d0y9i+8V_db**9aalf2=c`K;L@ziO*XU znWme6M|F>fsq)XMo)6GE3DNbsJID` zS|&SoJN^2mcMFGOOJIKbI}w93_V4s-7$koBPu4gB^n1Ujzj!BEWQQZ&5#CbdqJIMv zS~&^LmHvH$ct}ztOp^-uuWuF3BB20MZk(;55>qRXeyBEbylwUH%C9-6&4ISGZ#LtI6M+Sa zZ@MoIYJ5w8x7Xwtjoi-#>LqyJIczD?Ve&AVOU%951UYwmV=Ky28@Pxj?N{wzq zVqRS3b{o$&dMHv5h3x$FE8VtgE~|kIHX=vN6;yfYExlc_dx}>WFQ0{;hpMUm#Ns#s zOtqoyH3_f%b2ckqiMtt&Q*1?R^j}M{Wsm-iz|F!`&7#v<`Z+7TGU=!hZv=AEWQ|Pw zDEY;M&{On6tFBD>XJ1~g;Ny}J!ADz<3KoD!5u^HG}TvK zZ3X7WS$?xYUC%6ZlP6yTZUsl(L4s5YE`X(cMINDIOPBJEM#^kC3V+?Fw56z-NP(V@ z>r`qYOGZ+`%~fA$PKfw>hN{3kxdZu(G7t@eATVMGrAeeGUa~&eC9>C>Jec&4a{oq) z)8g5?hqy@Rb=JOcBezOO6_=OZnZJUw?SifD4{U6l>agcA1GAZT2yrc!Im7d6er1sS zQp|QNw5g}un&h;;m^v!wAU(N;fz<_3*1(kQXkrQREMlu$Oe@F5PPri2Z zUDMu%L*1gn0%pqNs{vjQ5$&dyifjGP`T|M^$=c+5pP{d{RjGeF4}$7umFw7U$`J(K z^cUZC^yhTn+s0ZWk88`du@Az!VPu_N!YN++HY4MYe|p2tuyytmW=iAdAh6!kCR7~* zuH08?q7)sD59HO271mI7ZKW7tP6`fajoVg0qL{ z1w<#-c<{NqSjlMF(~hPt!$t?FYM0{;{y}$Z*sns93Tv+OoN=;gQ^y*Qg@xCR&josE zy^8!qdP1GWII5#rD6I9ns`xJ_p%F~pXt*@zhGlzqYd}nbYRl}OZV{eIdCgRs9E$c8 z3C>{8T?Raz_pC^=^W}xg(&^}$*JlN54ahIOp=-rg2pu+$iB@8Ivz_DM4w^XzqFnj! z(}#fhZqeN_0vIle!LCCkw=wIBv=e;Y&dxjT>zXPEnVF6H76MCqZ7LmCe_PAU<`0=m zx+RO1L{T?0(nDlWWYCySsTJ!OVr9#X~JPBf%8=zryq&FZAl7Gm1gcvqGMf6rZ?4HjL>R- z8&F%k!MKvOGJaVe{P7`t@1BP!-;Wg9tQ7T5kSJifvvcvrrD z+c)fA&#aQ59<)%!Eo6n=Ms8$stN=9Z;ZLqFqVMfK;ioU2$UW6gtX~+7w*`EI{I%M~ zS?gH6TU?R~Iky_!?1z?z3#1WFeXG6Q##gowm~(1gC-TB0YHTO88?Ny0(eQkB;lKS7 z*xzXecS>+fh)S2sARB&#r zsAqgD_4NT^qG2aF6pVrm8=ut7ijZ^6Wa`0E|Urdwa8pTKbIG3q*xInPX;4J0h2aG;if z+tc~iUgmVX4%5oEQK%gAr`L{d8s$0N#F`1k(!&r`I)Ikx4-Ua!HS=o5R@h156)!~L zWPXKqFX~=u81P`eIRIoWRZgi4qqnnPOSt}y7-wvdmuFeN01p05W&yBxhFl{x_ zyLRh%+>6OwzM{Ili!c~;5xac=f7?;sKpuaJ|Aukl5#Q7v^)8@jtCIRI1OZeQ1i#C2 z#2;M-<#j7P)a;o%aT!-kCcMZx zrPYezRuoizd!xd}5#=Pyk#WUc*La;ySGcz(zI0RT2BD*R~IzwPQY2CzZb-I{?aesB;lF##;e2e@dC6P zC@XZ@c&j%&jf~hEwUJWHrm*H#E5P~V-K%WzA1>x}Jq$hEg79ms^#9O4&TWXJx|5nr z%w~2+SwnpeJXmd?UUR_um9Ecn7f4scXm+-oqXJe-a%uq3%4*i{#QoCP!j0sV4m*;U zn#&*;aJ(4>1jDO$y%&B{F4Ye1ZzJq+?g<}ng+T$wV0kEM=9!U6*x@FcyE7AjbV~QZ z!rADXtILZO&7I6sir;Ln`flq3O~<4FK`qg%hQ_m$kab|!+t9So*+rwYLi-f{GW!$` zL4umz&cfbknof_4XtZ)4!Ot{Q+o>?>^A3Za69?OC*R)pl0O@PO=Q6ZlBVhu4AQ%JwAz0KubAUb z{2F%F{CIY_%iy|i8S0|fa~l|DdoO?Y7=Nt~7hQ^-*2VLyAoa4_%y4<>w!iAIrSi4Y z_P%U`<&~vWdy0p~_3*w*#n?P^y2|%5UK`|AzjMpa4J12fzdF3jM!_ty15#3YnBK$U zhXMM^5m2`-?5X-@{Odoa$dxb5&syFdQyY5_Np<>8mCRNSPRp?ygu`@47e*=P4=)_r zv6O!Yzlo&V1yt^hMvR=SWgTPFF;y=PZz(Z}MHK1I>rDLZ$7V#9y_jnm{DW4CbxbQ{ zGSwZcKhBpKMz>b=|0;2~6z)a%)I2=yNyif{&yL4m7+UcC%glGUy|YdJXcaR z@zuS&T)5&vg!e7m)9a_YnXb_t({%*Ggr?b0X6U?kCt!B5Gc#^GOzTCNCq*-xg zu`|3W-@sj$KIcX3&S^M(f?0INNxHk!hhHafXWB)DPhC^vR8P%JUQ8yjqY%7K{Lx>l zeCyS8WhJS59juP~oG}$0s;hdyxyt-#`|M4zZE0nzje2Jn)VSsSkYz^;_1m?x_|!f| z=?2T7iZwf(@2L~^BLURAHy&paNkXuN>3p{c9;Ki96AG#Og)WueFm%qm%c>RLSqdvI zR~Q&-C^xzD-&kAM_*Cd?6}JKCF4UzDTc()55}!X{{O8s?F9x|jayRbEJ-)}b?!vep zKOzorq};0+OKWhb(VL|;t@UnOgDGz- z4@siANvIy*L5o5ofu6|NxOW^6r>3~4B}AM*oDBP%4ELO< zjf?y_R;hn(>Dkn0zzAFBM|f zg8c{PKe=ILZE~f;|NQj#ctAj`{|67MzMHlE|L~z|N=KuPV%_Av(zA4dM0uu*`BnQX2 z$)!30$KYuf9itxiO@q@)OC&s~&(0XTo0;NLA<)>R_WrzI&M!az=zr-yf0iguZtF5N!_3VDogzsujD7g)1wo13!K8cVq<ETfG%Fp(m4vxGdDpY8EPZY8z^xl5JBeCrAudDdFULN|)<(F+@S1SeJHltn*DV#C zDO_6I{;0E^WsuYn@OwV7%|V+NIUjdqSmLY7KT6r8qtL(x8fFRJQfl4`d~ zR?84K-`Sf}Zu-$I{qpa!O}ns6yQq|Ht2y5gnCvuQAg!ZFz0N>;QQ6GZt@rDmx#+M8 zxlu%Kgo(JUICkn>UTwuMw6`K!?7jWVmYU&F?<^J5%c@<2$05ycx8k?;cA$!Xf4=Hc zqxgh4EM&|8%QMw*$qrA1Mm)vph=xIb|A*iYU)V-GApy;K)7&!{JctmIs+<6?yp%k0 z5RdpDScn+mT!Y$E={yBw=A%#v2s=cNOl~bIgwWF=I5g#CrjUP=lPD-5{NqGRQOYSn z>w}^IZYjtkiG5i)X1)~5FVOMwFa+TW_l`zfS=&aG!J(vDG6eBMm`7!{#U-0X%Uux z@@!59uY+4p1AS}mVk0|_?piA}4MZok``}~cZBV-f9U8~>mW(}G@Z@<(fvvj!-;UKP z``1h1CnqOMkB{Xfj@OW9yrk#-#WZE!@Md$5x}Cj=i^L#S>BnUPW4&y=GdG3LCs*3kZ$CqYd7*5zEHp3?|Bq+ z*7nmCaT8+}#CPL)@Nuw3dUYdRJMd@<+PgFOPSs;-pte4Gw9T33Vj*BzZW}1yd+nkF z-3bN+5B1f+_qw5}YDDkH*7)Z{o~lW@n4%C)p~txK9uH;S-7OFkCmd->Lf9^INtO^x zU-YDaDc)#GBuloafV`O8%$g~a4>MXuC_Fj}$L+V*5^5_}!*+O%SafD5DiHKAvikI) zdrRHeRDoi+ac^hz5)AjdP?2gkftSjUO%3?yyX>t<@3z;f7DxTh!+r@|(XDyidM`n> zy~o8vLP-LiIt9kCxA_H$yq;e{nTsex?paYBeJO_Zs@#^tB}sGJr)bZcr{8YPXU{Oo z>R6fQX~9O^&cweZ=*#ASvYriCl$e)3YnkX0oDL_?Q29%z4kitH`Ps^j$oa?PM$Hp5 z*}qgGhlG6f2mcZhqQj|-C6$gFWS4eEguYWfZ58o$d_=Xkd2_O^?n3q@&MBZ7YEhtFY>E{nbQ;E|NCB;RBHlq-n$=F7Ik zt$esxXL4ujw<6#R^`cMLFpRUZJZm5(`Q#1!cF1!cQ;>-G5HB2lZW#!;cBl!^xVw&~ zdq~7or7=;7OybhRl<+0+;i)1}5XMr$bIwBJ1t|y_XN zNpgR5cvOH2XtY(}9~wL=Bu6H+aGG<62}DNZv_X(?H{UdCvU=fU9Ljty`XUL+e4#*i z3&IR!NTkscOekbd_)xPsoH3*V4J2`A4?<%-pklHk5#}^6KF4^2dM#QNA?|@-ORIs1 zTjGj8qc|9nKzxQoXnv3;RChrEQs*98?_47E&L-u*U9n^^`}-o__(rC{N{^r?nrVI+ zNY(QMA%+?dDu5dEacqaH6(9-?fa&@gSO!A!78vAi1vfPOB1i86USU|GbTF)}$A&Md zAKLTBQdm{S9#ly0l9Je6#nr$O`E%DD-?cVatCAb)U|1g%sf_hzCy_3W%B&b91bdT6 z|429%4+*u&Ik!{5ZhGDBDV61b-x1> z>(f7v~Qim}#p5~6PtYQa4^V^-RX>xy~8kzLjEo#0l z@T|{;M*J)ppx#Z-jj@A&j9Il2w2F3OH1*<68vIW2umrcWp-&ue}?$7DYt2#!F!3-2~k~ zAlQF9+pI36y7EhHpjA$AU1NqqUekWvxFX^#M6G%2w}}w><`~B;s_knV^pygS#*%}+Eg(x z$02F)6?2aK>aD;w`9{zEmke;9`n12j4kf_`F*S+HI>7Wen>uNw=0<>Bn1>Y@eTIv_ zmFlrFwM2NH_^di0cY0F_zEHR3J`HuSGb7_wzCPM~`mb#l>5+=_pqftr2rnkQ zg<8g+ll}8G7A^t7OC$y|+CYrQc8G-#8-ofa@W5&Bb|@heyYLV12=AWu*kH8Na0hfa zL}pR_A=K=Atr#lPo+igj^4!e=^*SOLlDT8Nq+kgCAh03oDiK+3NkO5HdJ>@_+C zNtpn37&Jec0_HjbUWfxjPvk!a9P$gLRqF&l7#_&!9NmUm@Oe_7eW_ebpFe%6nG&C| zCX}(?{wc6633J54<~2R$p|^BcnV6H+6A20X-nsxsRV$tqm$qr*_P4dmE3fVeIZv zexUwSlzT~ZmF*({0WFaHpF}wmGb0o8e;4K3TG}=`qn>=;$B9}}#gyaCblNzhvD0JZ zr+zaHiz1DtnlZ)`^Sm@&L)je?$)1ll%#EPKsyHR_g{^9cAZl~JQnG+r@=;ZlI1w|+ zMR%c}Orgjb8&U#+Yzdyoqfmdn6 zv6r$K;|;^Nn~gdh<_yoIuL8TMH_((i8()q*4*STs$GXPVG5j;K(C2fYatu9nHss## z7}?952JUEVrQap)z~tWWot{6Fd()}9TH!5{{Vt#w#XB#bkARLpf3Pl6c9_OvJf3iFv3Y}F((Xoh2)g;ke6 zr^i%*a{AZkLdA)&vOTDgNIjAB)!N(=)hU@U8Ych31w(MXg}CLv z{Mnt0{9tr&RQ7D&)^YG$_OsmyLH0Di3m;S3t)KO4$Tr3+P{ySXjXRP1`M3TNzRjGr zU&5un+$sMUnwe7wO}GF2kKNBJfyZr?zw6OOg7vWeNswn*!G6`bmYs=-P;6F0g4XKT z*jsTfzO##;$xydjcbNCKUTb#N%ph+G^Iqp2ALcBmOXk{B6feg%@8hqs zkx|(a*2>h5Mi#}Z2?%7nJ0^#1GWS|mT@8x9dvw`dhdSMzs=Tf?8uiTBRc2|aGWD6p zqiX**k&P-=-8wAl3Kl4h3WV~fjHL+IDs`?Ty9{IJjo-fRI2B6~a8(^Ks;Mi>87SSh zVian8w^~hfFo{=pbR0*iG5xu6zvRsBTx~zRj8E3-dmeS!&%F5E#HO8@oVw2(bM!nfgrt2x-4pP9?~vSz z?h<7B27EHh+n43P>SNzG0R$@$k3Shh2+A>)n=|`3GwrBOi1_-F+-Xy=t8RpU z7>UD)LphPh#DSp`M39suNP=m_Fp;TB(+y+6UK|62X&wKdt*jYn(+DJ@CB**X7m&~j z!YbAq0mDG$#4M<)(7IrAu1v*dAgFR($-!_kunikha=Nr0M4a~%VIx~gWE`TNi&hRb zP`wyLWzOd_R~vj1`jBdhM=Tl)jP-KiWLqgB@%7QE)Uq1Lrai`U2Fk1&=M<9qmD#x1 zXT-SVV5?q#{#zINvQxN&oLsr#V>V;TAe z{LCdutx;vPhjLx#rtn0YfQBk9>?uImk=Xv(PtBNf!gD~kili&-fop*)LUfFc2S$K_ zO6iejYfD!)>0j!B0#im}5~D%RRIoCtXeKX4f?{mqn}UqBsS#3v1iPDsyn|1(lI{p~ zPyV&~r`D27{QUey5BSZoL>Mg73&SyWUW=sBcBvGa|ieN%+x!8Zf>b0N9LpYOic?4OYL~rRgNeu zx>6=}Vv=XgQ~#=eeaH#-?~y&%&X*6(F}1?gu&sh)$@#jpX)B(cE&64NtJ3v~=EH(_ z^(yU4o=5l7#^ti}WmfzwqbO@}d~bPv`~v0I?&0>k``hwNqksk^{@8}!UKs4hiUw+Y z_yUl$8RSSLvRT3`sCguKU{uc-27A7Q6(n_pEh!uc5;<@f(BTmBf%5<{+5{xb02b7* z8d1V>HK|9)!URF5)o>Qv4&=cO0xUZZAjxc$aSg~rVo*nD=1c)_1LRbdgk+&fLJ=ec zR|8s9Ou~OV0pmb8;x5AT4R`b=Y~h_~qL1J?PQ@NB`qIg9w~uYz7XK_%6@ACF@!Bs2 zk{UeU1dqq@+$uDtgRUgJ4{rlEeON5mLZIJA1=DexKO*?Y``eKJtl50u4{O2?H{rU0 z#FFYe4n{pkbvZZv&D3#dKXIG6yy|&XS?&pL0tmB&#i{kWHG93C$$Y1fu`co5ybL*( zp-^aftNjU9Gx5C^3d$`Mdja-i%MzI|mB}%<|-N zubZ&pP@FmaugW|pbF3C=c4km13g!_7G(^zX1t}CPnC2FwCj%hsF{fYo(-syWMFEjp zl+v`Z$6Fp?$z34jW^kZnMB$Vu8ip0~H}GQ6@q>NA3D^uTT|&6hIJJ(JX{S~^P_(3h z5>^qEF^nh@0=aWi)|97_8{ydWs4%_~Fk{fjV2Kg|_Q_cRT?-71a3MgT7O*BzktIlD z3*piyn*!3oBH_P*fIR}^LkjAp#8M#z(1;=HE3Wq_>wEUIsYy8pHp~l93B`VqC94D& zBZW~$#RIPr^-Uu(84F6sYd5x3XDB3XEGDy`CI{( zei4prSNu44lzXDHpKQZ`e3o8(FCc*qSz8hMzFV4y4y+n;quN+H{dM@OltInU%j%*d zRwBsun@=SZKhx<{I64@jsKA5&)X!f(0E|ruUHG%z7OtJ}N^|8~@erFb23&|5RBKBZ zv97)LjL^ojH*5AwO=?pfpuSn)L!{96TUbhpaxPm;O_KnXTrls~9d9vmF&Ko;OgJ16 z@ti~>Bn0F)7AsG@UB$km3S2W>jSsHqj7@sO;#bvyb;Ic&oYPUk*7)bnkL*!@-fNz3 zlTOBg>+zxwLwzbx&`R za72|_T`&kS-~_p@8Y}?M$>6#uKv_HeEiwcZ;LKDS9q7@&g@7%zn5X% zuJQBhnd|;bn#gd$ySM)S{sLbTT(|?lwIO~{MD$?gjat5`Gb_;Bp0|5KnN6UGlEwY_5Xad)TUJ zx^Gh7iWheTuHxx-+U{)~JwH;CR#8^RZSvBmKWd4uWOqnHzGt-yT-Lg`kjh+E72mb0 zJ?TCBNGw}ohz)=BeQ5z@rX`9+3&DYHV}l!j+<|J46>1=@En3sl602C0QbpxA4}RxT znguC1mu3Xubr|-T@kEkh!Z;IrL>u8|3Mhjy0$;F3!{$HWyUspHY3n8}carZq9Mn$v zO;A61Wm2ZGKQhVcmFyv%p;y1&l;!e6igx_y)$nGCS5qw%4P z!>oaD74pLMZ~}VLsK3~+0{X3eONcf>4S_-nf4x*D0G+<{+o1*{RE@>eL$$zRGOLV* z!%P87gY=LlC6omelt+9^zE z(%UaAUifjPrWN?eRZVhp%>}v&5jOt@siaaREv-*y1|;1FFPJ3^WH9#R>8m z0#qU`fT{+pg#o$1s}>MS$VKQ1fj$(?{ka(LC$#1k4=Gxo6j)6xJPaJ5f$fC$hxLvP z8Y_&{G#QoLDBh3N2AWWqDabIE@_`8+s1Ov`#!d!wEb4Af$x2w*rv~(;AL18Ap25Aq zx&oD)fuIMM?6KC%N!9frx?)Z;P0`}Rgv0;T1RT?m`<}THsu|w zE06m5_Q$VAZZ|vpg;HRFe<3n)Plip^YcC7-$2)P!_B?sdb=$TiM-IYi0NpwGm`f7e z(`sVU@td?Eo%Rnu`m=p_$9cqjr1$fCQ^)M%J;43nV+Fk*ivs~oTj?B@_5*&srQVIU z%cJzAwlR3J<%cNb#gzF5C%mIkMCxxJJpaj&|TKm-F_IAN_|F> zEG1LkcxJ|dy+|r%JyO^f&LFj^M$j{WGd2W+L>ymb%KevrYGG14v~Zf zg>;&mWev`cf;QR-21H1FF_xIt-UP2mqr~9SJ!D==HX)!jBR|) z(Z{4Uz6jVlbn@uU{PsL8ERrJNv{E=FK-Ega-c5Q=EpVR=)k}URKHKgpaJOISLhyRE zKif`wh8MgIgzNsmfF6V(SU+*7T~FJ}%?zU! ze69Oa-ChIRP$S7(XI{KDMzy-{!gyGmbD!-r@<{y}$fBn)hJdkwQ8`OLwiP<7+4J(P z);rF(smi)i%Q9*`?Z?IjoE_+{Uua#Lk!5#c;+FdlzAj66Qfg%-QeIK)Sjo=D!r|}4 z2U{7)Ur0eNW`ejGNE?>ClPA}Wws8+d6)$raUalZ7R_(QPY-)bt~ zq?=#!YMuJ-RiAuBDPCfBooa{P@o^tW+h_B-Uw^V4G|2{Toi>92bj_U!cPH%OfK#lW zTsQo)Zb606FLZ-!lrima9Ih22U%A$C32eYXfT9wvsn|cr%Q|e(&7Qa$VqQIZ*&wcZ zd~CwDr)0?iv>IBlbW6h|Sz+6bUkG$7{1#n)XNS@RdY!LHy;`5J3AM35uD=KQunAgD zTMV1}RcGC&5ZF5_6?m4oP5`;JUNuowD)=^e)B$vreDz zA5g2JGV9Amg}5Xc-|O4A=j$uQcrex*n?6h{NXg@+Ounv?y){zA$#9I>VBf3^|;$ZP+mRl zEva$5C`9P$@}Vcs1S4s$fn;BhwZ~W~(Cfgl1U3 z7=^E)RkhHxxqCzQPk?=`{DpZK|Jf!akmeCzZOJt3L{XQ%`mnl6dE7Zlk9?2Y7Ii@Q5Mv)odyK= zp<;Nd+x^*%vork9`$+(uP@Z`t_4R@)s=`NlS1=j5wqdKTB2hf+HfOV4HVAFa3hNX1 zGbJhSc4a;JDwm$7gpD3h;*|daZCPg}^yjAo9Wz7{At2%ag~E%CxCe{YhrdTB$w&4F z=dyxL;s+XI8%_Yn1xy@lW3(_US{I9Tnip69mdVCFd3l~6-y?r}gJzf*D>8RH?zc(i z^RTjUX~{XGnmxN)8d`>~a@tcRaa%mrbSoCzkPh+=Y5Qv*#3N$}XMr=Jd)Wd!AY&DD z)WL+4$utDuKXVjIlnjm`80YX_{tstVI_ zYarBYUwLYPIB6&yO5AiXJ~iMq&ILJwD_7lm#KU#`T^nW&7bb2H6Uvj~dkR05LYd?5 z)Cr=44ww0`yq>^{%!e#9LwGtw&e0G^OPfDN*4ci4$H{99>O5InugstmXeLJ#Y&nEf zH|}uI0h7cnB}0z;qlo9q6O<8(*`LHp;YbU^(0kc|?gnm3Hha+26L5cyTc3}}nN-+B zbvD7+nOMl?3P{}uE5+Y&xWEq?BsAO|PL4`{5)e%$4{Mk=fM@Q7Vz?Vc2*$aCL<|Lt z6i^vv*u+JhWsDkU11vRG3{FcPqANAuf$}uM=t(4EcLSt70H)1o@AIc&xZ~E4=1&+Q z*B@Xor1W=DXcF&oLXNonJ4kIIpZFkMkTLXuz}NI>R-AttevFmVa?{>ZttWK@%2Sic zQxo#{JuUz11!w1&OU>M|u0My15d1hGGBWO&o;BX>8z6MKsd_&cd?Wo_ua1=kKf&%m z;3m#ChS{e$aLQMdZZ|*4uB3tu=(diin-X&$w7ZV_sR%xUgkgRyyriOhGy77$z@IR7 zCR`KD%yfZRvP1q_Q1=hBy5tK&&D#$4RN#G5t$F_t3 z@l7xc7ru*-KgF3e1FCaglV?51--c-TTs^UWEWv37ra$CSAOK6qg;DP9`R(!Z4@PN` ze>E}6X^RKxWeDI&T({>ye2IOwbv8^F9J#dFlK5OadEwjR3!|vcJ{UW53meKHHc$9b zO53YdyC}DqR=&0}El+E6k=u%Jin3W8K23PlsC|}SZT{Bq43Ro)-Y;C3oSq-B-kt%f zTzSJ8ir9|2Wewj9?%8)}%;aYFxgI}58{dBz7cqEw8W$5t*i+_WWvs-EcS?#H`;-op znL1}H-yS41Ny@O4UN9|-_IO@TbTB~WsJt#!Gs+u+Z+>hb@lqZ6cKjJ?#oCofZa6*I!EAr{{7UV+e zOLhUJ>gG|VVH^knDH9MGh$Dvqz=jtZoN57j6}ab1)wkx^9u5Ov>T4-P{tm`|hslAR z5AZ?rdfcY$7lf(q!G#XUnvb*qwdjwsdj%6|4J7Pq3DW}X9)id&U;)9W5`;HQ zpi5>@HXk#lT^)qJY7;Fm&7b3UM3-|%KlE3cAwC&N7q_TwpWh_7YbCt%HH*8#pz_I! ze3DNtT+A{wx$jplTG(rIQM6dPOwsf)H5^^ju7iQz1RUQ;HUlB zaG225{@i|;5V#t6RM=|`=Wx$wAOFmb<;~@H3C;oE+R3ePe-ws&nb1=`&IRP|qAfl} zj5gqNh!n(9JqR%YY5d9`{l$bQ@!&Qh6l)TcuOyy@YsrhZp#rn61jEaG(XdzUcV^jt zR@#$T7N-?;_WN#@7dF)U&l%O4Le3K;FCo>xE~}J!gk^o*%DkzldA|$1Wm+`s-=lrz zJPlaEZDL?tjl;Rt?&TVpSs9jV4XrnWAB4UHMhhRy(l1VtCjl#2N|$}PM7WpZ7v9AP&wp5Lsc*GUvxDak{!Oe&@}(P^5?Yz2rxZ9d&)DN4f7? z94~e=xxbFFJ~!EKTkk)oGxww4z#V65k8wC_+`L<9T+X-Gqp;Svw?!QfJxKqzm^;0% z{hoB!0Gu_N#DVsL{k;(&<9hx3Ur^*joOJblC;)&olK&M%^#9X|um9=9IAR#NJZyP~ zU07tYNzE=&6udT;w^92M8 z5mL$}5bEwgxbOR;Hj^Mcg>&H%eT7^p1xSd0GMvUaE}ui1;cSN^9&Wc@Hs61~PqQXJ z6;%zbP6wXsCuAFVT!OyQun0F=|^*0gEF#c-N5v`20T#io^jc%$~ za3#AkC%cv}$dX@Ml3y#AY|C#f%5RiN-Uck-z?5u1_PpmzGG?q!tTX76IbO z4SK!%szFT`>5=FyqPFbQlbw@HL?Sh_1vbkO@^(%4s^^RGfFL>DNF643Q#0P&4 z;nthjFmHjb_8?k=r%RpH+4T6|t?Xm<0GG4|lq~gE7GvL@`XKP>0m$`bwda<5v2k=) ztL(UwX6IxXgw%sJ7F@Y1(TNJK2Z0+qsI{P7tBKaVY?*I%C2wH8jN>xHCT}3A;i>ZM znQI%Fa}(@7x+jwKm#QOLXO{Gvo-DpgbMR0G)kcVyiWZ|2C`#`@=4jS0)uOdPpDAP3 zyrXE{{1vOQ0hTR{O)7UtPlKg=g&yEA64?7p_RPJ+47Jci9SL4*Jv{U5*loM^@2I_` zpFE~@!X70}cB%x&b!mq{RVwiIFvxN~fz>3xt7o5>ow?8O=ksxmm90qYPA4ye@|Fzd zD#@c7(75-L71`y$MTgdzkP3PRI^SFVPH|gUe_fnf0M?_u_N`pDzuxeSXxrz0q6{lYF1gT5p(u-n7ky{UllS734l)Jb9CD1J<(-SGK$hE;rGt+WFi*@mm0Y5_G9Zenk(#p&ciE6!hT1tR>)c4#Bwq4n9u zaXX4^w^+DE+G+xcmNoSRGzbz9WdJD%EXwxpY`D8&=dYK0LrHc{dV0a4w_)KHCj$e| z&xF6MC?0eut-$aDW)Z)Spy#y0Q?qoxG$!f12K6;4;B^)c)E z+u`KF7;B1%Pf;F$$@~yjn?>^Q9f8HmNRQ>SDQc%^X2`7QCRaNgKE_tE^;Pg)g!;%k z`p8Lu&CHv`@Y)ELlWy4k5BkVT!M{ROWWx;WNjL0a0s6>W1?k98K_a8}q?)a>W#|}4 zh1x`6937ijYsr?f&NO{PL=?ti$FL4INUFgm_GfH<>E*d~O`)-okDfv@(RAbk^~TQp zOS5CZA%i+hM(8akZ#$v>8nUmyL4qXYAudcG<|I4b&>DB+GzMQ4`sG%W^{oGLGlVtH zW$o=-E0y&oSt#gaunYQaoPjmY_DOV}T6Bku&h}9K?HmP*3w8SGTR`0>`X{Z!vkN-h zF#U*;xswQoHq&fAsYrr|Or)YwJ!dE;%0E{AL#*A15~bcLd#>ucF;AX?=i=akeiJn? z>!{{Y!>BiF9+lft96E8rLz?#LBl+_;zJ9b5f&TtnGhh&k)1K072oGATw7NTbDJbK9 zJMJ^^h)4%tr$LnMnZ7(E)<{a(=N6o_^?9)2W=O)Z*B@Z+D7fS>B=H=+Y|H8zQ2qni zxv|A?Lmu%1-JeqQaR|{1TKWZq=qVljW?}k9w}6Bw{pKA|Q7w?7Z(4dzLi8NBfO(XG zvsj!po}q%N!Gmz6#Ri;3*!oL(^vS7UpV|VZ6GL@=^!=Vn3~22zaBQaZMUWSoJi{BW z(i?X`;c{DAwK?xaV%%qIJj_n)z|{sGoWe9iYU~27Nr~k@#X%VOxUs`Di)DY)=)qP( zB*F!pyR#Li{}@7NapV$^LsSj0M|0q?=9cP*TAdkl)MP4vEA88xAE`^Dvp#Jv>0l|Z z9|m5&IEu1BbKi7^jsk&u%8`?G;`2H<8)7Y$b~bGlW|c}u z4~n_4W%*Y*Ts&I(0a_1NWUVRCDJ z46fQ4i}8lAur(23NgE7}pAGC*fM`B$S|fJl=S)=7)LdxP=Ekiy)f|!#x!-TJx^z2( z(CMDG#;zg`M6Gt$93B(8-+#2Zbhkq2cB9(j)DZ=u)kSHIUPkT>n;-oKuOba}of;in zhwnX)Sia3Zwjsuf4-Z zb!2Zim+wx zB}3;<;2;e-?u1mf-tTbd(DER!*6om>&OcV)`y&}_0yPSiZs%mkKFP5>#n3UD9t5)q z2NF6S2@iJ3oW4gtOy709SdbZ>SoYqXi8T?i2+G1jO}exsFAJJvCd-`Nza(^<(F}qxEzSWK^#= zf#>IU!}EIj>b}J($=kxxwlk8qpreAM6gg+2HV(9cIMZK&Vgd zyWtTohoubmbW_m7&0LbGk5z3Z$@Ef3DW`Y=xtN1)x@~U5=h=Q*+=X_`sDJV-Xyv9P zcV6*wtz-z#`BwAdQF0rcmgiMKZg=4ZxFna1mZvXqqExIUWvBItYGWc-_@n%z)V}#c zMxb!mZv5C_oUDl?Z}v!Qg5z1CuMsrPHXUcQ6>>n`x1Leo5~-6un1VV06oK`MM|&TV z(i;JSR6mm1P0Vb_k?mybU*ZfxWxJnHZ2p5$M&mELmX(n0TL zii^!~c!C?>eqyiNf^oZo?^=uwzcf$%lA?T6cq)qE^3Eh=9UugfSle9n7Dun$TmAj(;SK~6%f2$wB*a{{!BC87w z%7!b(e|73TxsyeDUX$D$bhfskQk4Rlaw8jD&>irDXS8de}1QV7SUnJ#S2$K z2H1X|xITJretmvl`(E7+PjPSnp?n*EU-gK~{$+;gDX8$}9-LnL9p{&`Q6w?qAT1Re zE$#EKbL?k3$8?5YW!lSmit-4%$~DV)iuDM;_+Mc?BXrmd@?oIEz^_9)ewuS7_~3(! z1qr_dZ82;J9082w6y*rpf_p5VQ4)d+z{Z4zZvi^4nxiFf3EbdblRi+!dx?65V*_z; zW0E?ejrA1u2;YEp*fqzE@!-G4y&!S?dFVd_NbnNC#l|6a#2c#;Q$y;&J7Oua3g-rR z*qCn-#?400)Mo399jg-1KQgQ}XA{9&b?ww(C!x(cz*J^^hZ?w;h*5bNZr0ac1{v zHQN4_2&8QE!jtsJaD{WDXD_?tV@cNFyP-l6Y!aqRW^7u8!m7k+IMuBe&`dxY_j!3` z7!&;M`gI@av+0CK>0wSGc2RB%`9-tQh{gohK`ExM=|<*pY4+TNuK6&>A>FDuUEZX} zghi*R!AzeLx7hhsnNC?b5e}=FmP%@;g{}4S9~*Px$)xS~S;fZY!yx$Edm|5~^F)M>KB?i^bc!FuiQFTsj>3$=lP^O~ic>neNte zAz_TfAn?lm5F9p_hr6ZcNdQK>lo5Hr!(&%m&Xijs(IOccSxv^1yJKsgGo9P2DZPE> zxrDfGVXez^PR7s7g_d$wpBJs8&35d+8MjGRJ05Rl9GvkXfJzA5P9Zn;hUv%Z`*Nm- z-tTL0%;fu^_R5oFvo6;`4<^K^z$Rm^)oOqFG}z8L7ydPC z7r`}y7d}jufw-64OYGCJ8XLflky0s3%qw%Bnu4;*DhyEuv&^Jq33{jrN9-x605XY0 zs#0nOv$IM9iNYQSY9og(Jc)*a3z3Cp1$RoBl@+}-rHMvqhBRhqZB=FA(fNlel^MzD z(&c9PtXVquHg}%Mqt~R4QNA52wkZtlcvc_@>mO(=|AKmMs(M1I2F9v-OBl@-wAkwh_X))wI7t$Z0J>`_+#Kd8G?leUX5oF<+WRmt@(js&Z&S7GgTr|+-BM^+1 zD7lEvv8HYVC@`uCj}7BLq9Nyo6x>GTsRNgP(vTAiYpFURnM{g#dN4YZbfX|M$GW*V ze6KpX+sr4R@#Fk+bb~s6jF33%kuQfcm^ae8+r|KBITy9=y5OvgUYKDsOCghwg+O8w z2)q|1XQWHY%-pDXrrlQ2Vg-;E{Uiz8^{&rz9(A5WYJ*Fna{#7iP8W*=$&uFVn+r1R5F!tma?ZF<9tRLFAq8hV zG;=GeUW%qv7vQ9%PG0D#J~3XGXM)jknb`U2=Hm?~WV;R-pIcBfENU(L?lx|^rES!~ zN-l+l8WX4Qh@-qqgp4UyaKFS&MLyXX37SGx`fvh`i@+(oKdFj8 z$fo~Ww+T9$V$u;OW){A6cQ0n5SsEfC{5O*{3UB_qNby&rz zU@_Fw?!%G!Fy||^WICbAXh5!tEHzY@NNtc_VU|{4{hK@k8EpVm0?1hso@z4AotNmG z+9u32{3szskB=g(?;x1FA|lm>Y%Y8tGS29s)QpVpMD2~_tc&*Oydb}xY+Fwe3m9l7 zPMQ_`TR1qN@ki&xPJdSv$m8^lt*X>+xRJm-;awwr%zR?Oi=g9-a%!KP6dXT86s6Sa z8r8FhX0xVpkiz;lZE1U*rXE|a@8Ky$*P_7Asy1%=sUaPbS4^N7PNM&a<*ddVL4 zlgB)TTR$bxd{yUQWz0&4p$utsZPDuF_g)d_8ra)jm^LM0m~gyP9WytILJTmMM@GtJ=0GO{mt z+BdC|!7(5(0gHW=Z4d3{ zuc=;P%;rnc^e}D5skh9e>qRB2D~R=^Hvdfj9?1NDgGc{<16YP;=hH7LyOyNBuLI@t zVB|pEv@dNxpQ3t@zOx*&8b}v|tXR=nQgwj2qR}>;Qk`TH8Af2mL{vUeKL>=wBz&(# zYH#H}+qn3lnfN@L{8ECqf+^InULr(7!x1W)q>Nl2_K|t?F4ce-L&DHQBoX~?RvGqd zAi0>4n~ISrfRZWHyL}>h`NQ;YK1yi?SVRmkYl>zlu&T=2=GxY5h1{)wi*ePUou}Sw zZE#Pc#hGraM((b5R*G+=<$xfjHL;nu+LDs^6`5&>|{{-y1Y1uNNHbqR)Rp6pW14#bRpAp`vKdR3T>tp(zan%hNYVVaa33|POB+43v*>&2r z6JumKuy1}F_ku^x1S*`_-(S4v(QD~7g zUnuvs%0K!^AM;2bf3IFt`)lQ2;2NfZVFR7L2>CWYe(pZOeLK0jPRIMM*zMons=_97 zPr-AxQeuIfWW`Z8%X?W)-VP@v|6o>P>=88rbKGNxOBbVQo^9m zIQh8oJgk&j-7digY}j<-e|KAL`}%d*e_pI>gFbhr)(;Plz0Jy!0oBdBn;vY8eK zR2Y&}%(fuq8i{vPG7$|>;sMo(Q1OCTl4_j(ONs9XNL)!9rVR0elG)OQ zv?~m`&R*$>;JUil>S4Q|&GbOx`-o<9qr7IRncwrFwo@9@VaeEXYobs-^_h|a&DGn1+Ad}FbG;WjyqX5M|Xm}arqg7wrRdd{+g z*w!Ox8vmxQ+2rXEOvN1rX(N4Xt}bQQY52pCk&K+Ah8)1eAyKcaaq2z{|JWQasS%pO zypKMbk(!(=GNde{1g|J6Nxz=S5ArV)s6nBfxS`lXbH-3j#UOfGx~zh5zOPSExq5K| zN?~~OD_S)i$W5Jq&{HYEtPpoP-os;3m}PiCOncp=a26)&*COX(aN1cBhR+Scq(#e< zqE#>z3ENn$x2Hil1v8Z_Mk6X^+A0OdZ*kqT2Gd$EVZId;P(D&@!2fGm*+iiTOo8Wf z072eFX+OGH|DiJE6SQ%& zl?m^_0}>!bi9}RcDRg+Kdf<;1cMU)7$Pmj_)M?Ia(_LQECd#P&P3nD1>pdbwRd^j)845WpY_@>J&P=-z>BoApZ8wqnKm`O_2;+1+lUiz ztu0f01x+@erOMilL$>c5dp1$)XZ^n`A0&k>@V3y}p_V{DVX2TmuK2nutXXGOl_nxu zU3&8xSOB%cdVtluBKjn+%Jc<}^ib$#dNYWV=;Ltf+8+<-?ADUyjhA6to7zJl1+ zxq3ySMLjL@_J1)xU!v;!GIB3u-Bx*6mr_=D^MYunl1Lzf2Ap!lnqZI)7q20w9ViJ( zN=s01loT%|!*;V|Fe<51s;FNjcMdXF&+6HqV-N*bUnAEkSX[@ z*ryU-LJdKVdy6Y*$m%NxrNAh(g0Rjm35N2q3Iy>st10WfnHObzBoRvm1YFZ6;>bn8BI|X*Ctp;xln}RiSdj$jLE3 zx$-%^LA|5=vWtMYD#77ULqT(0@6<>sp~pheo`u@QsV5<)yN!J<43WT2J7zuwT!JDuDfkT^96yHlf`lDR>o4W3BgWr@Xyh5bD6u zG!0v+AJTK!j)dy?YQNyqYG}7?szGPDww!8N+*4|uvu&Hx)F5tMo=S<#{5kmOVN_To z7yxREHA$?r9t)iShS{T$HI`0cBauRofgA&#lqwDU=g2vk z!B!8xw}VjIXU=#bUk}w^2==~nIv~A!^(D*(T!1S@TdJ8{kZ3J{f zo7yo0wH0S#dwun4uB|RoWJ8!{gZ34Exw3`7^p`NMD6mhUrM1(|mYi+O7%XElVJhF; z4mXlP{}gY+#+$zBe!#8eHG93b+y_E^n4j9urcR$I^{)^39`9Q0V#w&kylDOrtyFH~ zl4|me4%WVTzuqa2AyKx>$68`czsYf70X>XvD|%ECJvMvA-8;My)W2LF3D%N}=)%DB zxs2pNCPZFg%lqsmZb~^!gz}&I-r05F`Avm*;qlt&~ zF=82vhxJ`JEdDX8#-|HMuDm0;gkus?9=P1mQ}7#ZxvX(H-lJ7hZg|D32c)-b;@5WV zOeuf#L08f>VL%?gLcs#R3%b~YELY2*l%u)%){PS`Xb*?RTIX@H6EF1Xy$?5^ZWU-i zf>Kl~G#go9q|{_|q3n0rDB9lE-$|EMPj{YW7Wn#&a|yZ70}V29HHSwp&}{;mSb|$*%KPJ1Gw8# zfYC`L5}pB-K}ddJhuD1@iVP$I9%30yQAYMDM{9hLRN}5P*q3rNs(~hcvs`ey77@f> zEf{s=%umgjw^FqVW3SuMy}hvVtsOipEI1f{zaEZNw%S`AjxGGgSHfWt-7C8Fz7CT& z#NF>4lG@26uL-pZ7`Du*LLqXoX}I5>9%sM>deMts6&QxA*Xe|-f8eEsHC4kmn23b+ zC`z&(oaq)}77l^UyclnWRTd4gnr>lf!Am%&r#_3NMZz{kAyr{hfrX;f!k-uTXs;nD zUsJh?>HJ&dWSJymKd0BGF%V9{RIEOAMYO)=E-n_YcgX- z4If4be`B#C-K#4{t@W@o7m66&3s3*CE&8W#UE#L#nM56wTT3*3<+%uF%{2e9A=Zx$Zc}J&n!mJWTW*DS!h&4M- zr#8PAM%Kp6Ffw!?tsF@SFP#t+j$)8z9z1%5vJ{SX%2#M;Cy%Tgw2&kw9YK*z6s8@S zxOZ1ZFs31}?5>S0E-Sutcn5NC=Aqk4w;i+fveIJhCPF)5GWdzhosQ5O57!%acKY=$o(!la>a&Mv_Sh=poKEh33g1?9wr6gJ6>T1b=@Lu^Ja z>0qa^OrQLFA5J=7Ej?+Ep{{vIzy@z{E}osLk%?lCB8vdrB~68$OGv8ERV~OgrxzP5 z)D+=QIEMgRCDh~{7%NIoHIX_hu%buT`6z3|iI)wAjG4#DDZ0ZihbzF33AWS(dn>Nr z$7X|*HJ79c_-4%esM({P!%hb?>)i_XqhyAm&|9%8Y+hzND?J7sdbgK79 zF=wAiZ^pGc&q?=E2iPSvbLHIV1c)t2#Y(zouR|LK1_){9*?RWLx{tM)hmF`c^v`8F$bawVmEvY)f{+kTD!0`^1C_l2w=dW z|7{c|eOo%dlXmthb4c6-HTqM-X@_xsT4jwdaaFFsl-lYxi8x1%givI3k5*RL!R zVlYb5`rV6m`ebkiC_yd~?kq%{8CRZqhh!ezKQ1M&tQP8FCAGo&i5KPjGE7 zj&@o0D}-C+O;}Yd*RfEFSZ04-EE)Q@mHn`<4n&HL!I^|UM@Ru#z%g=YE=Z~px~C$6 zw4Z1S>$xw8#J(YLpP~q7>6|9^7)oLry40wDVnlQGyde-rdO!X>@J}fPpa}7o{75Sj zBJNSZ+wd8P+~XW5;5aS>P&4Fkus?tfk+&jXDg-lKKsBPMzfW?)tpg*?h=sN4KokvS z^PlAc0fY>*Xo1%8DtAO@5nK`Kg`Vc-UkobdVR=walu?%=*aVJ3S;bIBqTz}dljW@wFjfTxTAtLg;>*l|lj zK{1p;H5Ja0dF-e(Lclbs!TZPtP1npH1`%=doNs*9Lo?;^@Fk?Vr@j)&$#5@*cQ@ss zBlqk|eym*+CLu3P!aSMFHeudL(TgscYc>`( z#nPgXzJJU~*9nIS4bTFa*PE=gq2WWR8MjC-z!V)~E=$#v%i8pA5>K5S9!=Rg=mf#( zNq?bjRYoWko`(%ZiQONpc3C=Zpr(OYJ?BPgsxk{Bcr5<(z9>GDj}7-18Fe-RNQB1M>K`bC<|rNYLd2Py3EG9rf} z88N5e7r(U1Kx3xVs4=MBa%z}*TB4kS-80_gg8)V`k7E=C;~;zob#4OV*`t$+Er8PS zfWX|6aSKQgV}?<-2aYSeQhl8hMcISjaIVtWR9~R2>(?^fvz?8og#&%XsSm<+wd?M~ zZ1OG7Eb@16wXjVnWp>k{kFzJNx8wb{nLJU~HaIy^ImrFuaVapRs9j$_{f6mK&pPKu zja6$LZYow*Q8giFnzIdCFUvauj3H=3#RJppd#NO(jvj~Z*HHD-DY;{%P8>=YWwRt{ z)6TA&A+!LS)O`udth-v{c2MBGDV9DG>=fV{)R!CYT7gOaFxkzUgA(?BoWPErz@+HE zMS1+Fe*t#);za-637=q z!-J)?FvZ>|G&&-I-UB)l{HNv+<)|F2vE5@JlBTvu7GXSx&34XWuFu`Uwe_|<4~{^eUxD4Y#`OuQUtNId?*jGv zCON)>^S#iBe1F}*GoAKglo#4kg@2j=t-8S-{FIT|x29jR^2jiB)FkdA-}g{0e?SrS z){*>u-Ng6pN4?*C-F*X70r_0N1xIfz1Jnm|$CJZ%2Tl(-q+&SJ1F~;K$V56L-z$e8 z#|>vZgO~bCJx2JCQTK!c_%i@~6kdjfa26cdj}ZY5aM8|A$m!m;2Lo&@*aXmK&pol| z;HXWo0wy~#DT8o7o+2pB6S(q%pJDODMI z|3hKW{fZ7NsuIj(j>JxxkHuWlIHpeostcv!KLhx3AMVDDHf)Y93OiaR-5{?FjM$4Fg5*ijsh~d75A6{Z3Xq8#HF6kOK1`H|eLNft zu_6tITSw`@kor@X+!LT_WoAEdVqr#gQlb1YJI}sd%p2oCxa4i{X?NT@%5*0+%4CJO zj7kZBO4*$pasiVcasde45~E(uS}#9PLai)@)gYfkWm@nO=e!~^e$r!PbfWBNT}`R| zvaA1an*IeY{R5Qr8$9kea#TnEj;CydeubBo_#0+W??v$^=zH$`?(Ij41ob7gYdXnw z3(DpJ#MWn6A-*l>O9ebabNkP@@2IETUhB!)3B^+evj$R|CSyq(c=<;A@;|9R?L2tN zXMyEl1{{y$jeW8{I~dCFw|kl`lgI86Y<;8JdYCQlwryO7qadZC(=8;W?R9Dp$tgFI zqux(VouoLzT502pp`h{1~dDBCRJ4wSgME87(FmtbRrGGnLbrnP<<4A8=iFtH; zscLsv{UHWfN`_0t-3>b0 zm4)~{pO?p`#0>WAaB;<%_b!>&VKB06QRzIPvhP&eJe>Ne6=zXqk@0euz7;2Non<*w zp+D+@h@%aG;4UsgC8WIV9WOgdTdS=I-m@2n2s|Hu=78P4`N0eCBzl=|0r$Z16T1`VLN)tqoCy*R{7NGxa_Pclt30n)A*s&-pUW zjM$OqF0?^DQ&VDxWLTSP(REcglM ziV5dRPS&)mby(DySskdFo<`GSU@yG!-pBiT+&%vId$99)7Y>(B8h4=^AGzGN5pX%a zg?4wvXd70^5o#_b-RC5}ClB1tjjQLHpup4)Un9wFb|z_Nww=`fv$_<{d+bIO9Wt>8 zGwW`CRT`1O$qnSw515fbA@y4&4Ws2Q5ERhnD-p{Bn3VT++>#^Y$Vw zn?gcbNn7v}LXpo5M%i`dz>bY$m5=?oT)Mbin)54j)hQFLRl=%M8d|NeS*@s1>AS2` z%945zeQ+U@uuyayazlKzAOXMp44HsqCblQ%Qgmi61N ztEmU!z+vG`*?VIwPjdT-6Nt)HPr>X@sN*vwh=@$Obq>qO^Fm@9Y;w2Ly&kvIx39ic z%F6w#cei>$yCwiXAWl{E+hTeS_elBvgRE!$q``u-ElxNU6f({cm+z*&g{PY#vJD!+ z_~5m`n(RQ$V|&09Fw4|3)g-nlr_~Sq6%}&9nn4lWqX#>qx>N1FFEB9M-(3!Lgq$O_ zq4g)0q2oMhA!TR`t_@x8VL0-rAdk=e)wxnhp6KR+P?;Q1ar{b{N0y3>w6#B1Npf|5 zzD~EH5EO(D{(#xgMR#(g6J0E_&6A2!l;c2LEJ}001AKQ!x2tE4FX-P^^vsJMqpp+dSFC!lm<%s=V{}iGqTsFcxlClajlF-KXlarw>Jw@Mj z6C2aXRqb>&bbbEncY*4$SaXO!&U!#+um%4LzNVNebmK=YfclL!O*ziSOIipUvWr3s zCU>|K&WNBCL^zGdmcEmQb=6=6{KI84r<)lpE_hEfTL&iCC?cwtj(VFa#yK>)FZC~x z797i4e`GhO;<5+h4>^>hY%C3GqPvI6V$&>9luYegAKY4e{H=fPhjl^Z)= zNfVO{P%8a{swSsu2ScTon`{@CW-P+f1(G~58LSR6dD+BN;iwu<8FeZJEh(8Q+Dtit zAe>3GhLy1f&+9CU-3vp5~)_=&4z=K+G}E` zrTi5=NkT+p5(-KhO|GErlH7WgBofh7nsHhYgVuc@E|8Egl=LVZLu%e~^#DpISg?&C zg{%5Qe;ia9SY!AX3#|IBg|!}J^!nQ(X~|X;x~{lj+`JvH-P_OCPh7lT!zK-YZp+^v zp$j)vfFD>*+J`rfcF1AicrXiU@CSA5q4?O-J8lFr<(x5$2jgsI7onMSwqcn{S;3r0 zrApi2P7YgD7p0lR+i-iQ=OB%PRzR}kIhp%%84kb#XaPnf?wr$(CZQHhO+qRv& z*fw5l8&P*$IhjecXB>l?#~7Wi%~ zT9!rUb~$Vn8ZEgDw~WoM>Ssyjf7L{prZ{FYaMrVNE~Q2_L2Va8U3jxbb2j z1*wmp-o1VO@Zq5soXL@4M}x^{8>ES`3xfdr;5xqo@Npo;&{O{v2})-?zRPX*D!JV* z;jnu^Rj2WKP~~M~2d!fL_R=#?JYbJDLL)_FE_kl{bbaUlAtoFHJg1)1QWcGvmREfXvK> zS!~on-!X7v778`wr@;XmG2=l)AjCdE`jY+?>%hNZl?p#f#DH+tL{3r{mza!dG|U~R zDL#%`M^Ruv9x;|ZlSPdxB$kW7OVN)e_f`%RB_u;d9vUoEx5rY@zRne#ikgJXm8Lo# z(MBvKb&yaE_a?1VS6m;_2nT=>Sz3+v^ts8PFmbY&mFCn+9(7z7v^bkC5hq80^clr z#?!>26)1|={@dNb@Qx}y{XNs`xveIzSBGz#%x%< z@TC7*e-v#MRL;flVTW)c;19M|@A;Wwn!0zAi723zoiGxui|80d@8}d*4!VuzT1{N@>Vp`({?c@;#ZI zQaT?EI|Y_)hk;BHM^5x$NRX$F5cVa|nIPEC6wkjhcP~aj{{R!9TRjJDB&(mA$jTmK4hLPt;~@mlz3E^|j>4Lo=bslEl#Tm)EoWtLJC& zH!d*mDnELYHSNL{0w5l~|^6(@s&Ykd`&+bZa5ulwOcksL%VN zO7#_2pf-yD^3@f8%WOZ*X>w?S2#CqNn|2``faWy$(VK5{qL`(9Vt0Y>&X^`NVc`@MAs}!yAwHlUW9l@6ia^Ng7 zilnS5qUCcxB4kA>%7Xv-uLmhNF2X!XaHVKbnU1J|Q4*iBgA|O(#o7mG6dC95*@}a< zaMl$#g~I!#>zi$+`c@#r4&A z-Mec|^m(^pi!Ozn569P=v&dF%aL4}nbWTQ@>a!LYW`<{{tTO+*M?LFI&~^IIrv0I| z9j1_VN7&HALxCn6%X;n0+F?(%8dsUzQudMG6^5!hX4hTX654AOAwTUmw!<9)?;SKq zrwk%ylQW=s9Z5>C`J*Boq5wHA0!*t%Kf_TZx%f#!$z3bI%5~%gaY`mGCW{t8xXe#I z+|pH%%ExJMsd8x5^Tn*hLE#YyeU^sx)fEw>v!4I=^b}gg#-950b;ah8ikG_G9|&i2 zMrXI$yIGq4=LMw;tzB25@*k&zK0@>CctL&=U`o^(vS4Ju zGKskg+o$g5uO`O|7zAg!s7$&*P>j%v=L`Ff7nYL5>J1)Fj9^j>{RIzhO{}}s%Ye== z(PdvDyM#Oap~Eu>R5 z;fN1j_kb)3STBbG!N#?MNkLqrDgz|jus}NG_4~@o)-BwW2Ld zp+7zptf@Q2H0_2Q&qI>c4QSzHkxy%yiA^1bMfuHVL);vXHx*eig-5Bw0irny-5?`Q ze}02|q5vyy0Pk@9{d@yiVEsM-v{im}*#4_=(WaNQt=F7?PCX7W2Y{`=u5JK4D_{RR zLmfb1bIL6U05F^Q|D}Pwsr&y(gB}ZeER9TjP+UR*cy3`u#EPWON?`%S>ni0+McsBJ z{sYibFLQtl@hvgaKM?xG8Tak0RgQ6ORa;Hhm@jKZt(*InZX)g0v!ajgZlf#+7q{%) z`vB55@}(u=4Ln$}-~Z&>EWMkD7<}mj0>yC?E2~a!gAopghP?EAZ@gY!dT+e+zT2Ff zC_!GWzxaL}RNw5#k$<8L51Cp%zV6*dQ6g_B%t3fmRh9oL8X3{^>f+W(7n7E}3cm@P zP_mGWkO_Emve1mkC8zw({Y4|^QC3klRcKJ~>Zqz6D>^A^Yvk5Z7o!%xLWk3p%1Sz@ zcvLiIYo;DF;rAwLCLCD8^QCZ-jGRoAQJE>46~A(a*OjgmzY4xtA8DUwBkKX*Jybj> zej2|C9vPk&BI{A=RaTd+j3P7Lt~$Os9!Z`@CO0YeYIdY{l8(p)ygn_tQ21gF?@IQN zjLiDKZdSCFyjs4Anh>=RjI8p1nx2;;^FbYCCW}Vq0o`d;XeoYjhwsX6ryij3f6AW6 zBKLtGY$U5j>iu!hkt7>g70}LGkh+`1w-v`Ieno+2D~(qCqzuneB&G0$8J?zSLhjRT zRj#I}LiU3iwxP&E_QM*!qUb{I13b7X-bpyX!uMr!QH$IKzH?K$k#v9w-<8@*Uo&0tSUH$kI*!=c5#a9Xz{>qtb zQu*gN71vq$j$STZcar=GQmbrI(q8f~>hdA`Z_JZ>AgIpBm&Ea^v-I57M@{s)6ZZfL zq|=Z&oI`e|7_$6}^($6lhpFi^_zyKijQe(4?6b?6%tBr-_;r`RovKR+5REn~>kPka zS}NnkKiFJO0Fih$byfHRW0=%g7O0Q80Xfp;(Tu^}&LNU4_CeR)QgDKFGKe|&#} zc20WyHh$E+eoSuSKJXrYpVGeVkD{j?zxj{RFTc5;j~>6Jk6BMYZt+*6_+O8EKhE=~ z`6cchPg|o8f$`${^v0jV3yZnwNH%B2rhH|9>?_G4L{Ox2I~MdpG=Vm>z+fjL1Z$?A z%V?NlFr-*OQ=C|xH8a>A_V)v$UJJA+kY79;m|qwkpa7jeSYCr{1>;Wwx|BXkTNKGQ zZKg9q5jZv_cVMcxU~c%oaM%%hC!&!vLIk$BR&!QOEAuW`hPe{1T+Lqd`OJe5S=PzQ zNY~-2k}Y&gF=1?p&Yl+X*ht_=LR+vvB93Gk)_dd#g17S1eDbz=oz=hRoNT)Zl?U7Ul2Whj@cS>R z4BZJ*l?OGR?L$xyK{!?hu(g;K2i~AEtvCq8fsG}iy~_3*B6GwA=2#{ae+uA%19}Q7 zB(6#~E?}Dh5wE((8V7&}5y4Lpw8Mybb~Gn)>`cD$&E|r50J!*FoZ<9y!$;E!1wnka zbjq~1Wf>AI)L9tj^XIXwIj4+mHyt^oBA}}b$Fg4dvnp1F8&Fm*Br3r4!ODwq#sA#9 zq*l)0F0C*_%eq_byZ5Nnf_dxiNP1Bpl(sdj;T$F z#;t~3498N;CNG#27B61RtFBbb0bf}|(5Wo2H0#D#5aHBuSujVLKp~DZ%m}xXlBw)q z7T!TR_nH{`EZ5c=gOi;qHkYq@4HN50GGoYu^E2thlaw=LZ|8eh3Z1YMlUXe!O zssuJ_HJGs^+dn0ZmR>ToxGi;$ja@V->dTS_m3+2kQ)Shdi0-jy0c-BH+>&QjfoaKJ zQKGZ(sWeW2Qt3R_Of2{VW7nVc3kmn^A2d%VG@~dxBb{#)6^1*4d4s}f{cCDilh|h& zjiQrakAKcwae!IGhdhPG)JbStXL2gLDfo9W)gPh7tWXE+Os>sZlT~B6h0wh#0pACw zW?Mc=S%VMFN-JAKWknB^=K|KreXS6p1tgdn-h9l;;zLThB!S-T6QXQ={Ed8KA6koL{d;BnG>{T(NYSTTU*Kuyk2%E(4)%ME2X-O#@7noWh9z(!H7U{ z+Z{hPj66wTl~yA<#gbt1j#C9Y&cpeNaGROq;P0PP0iXohU_u%)$XvJgYTjnsf{8O> zc52X1o2r5At6=v$9xMtrsTg1c#StC%jB2!`Ug_}+NC$OQy|ZBr!qL?%LE1~$cbtvP zIukmN&pT&Ub8FT+{@C%xzod31>OuetEmp_0X;UgOu4L&nus&NM#6o~Ir0P$`mm;us z@9=}A5U*b`g?$>aAHTUOYWTNkl0kFM;F5w$1_7xZCFfVj%*r9LH7*o4nJ{$?ypSv{ z9kHtFhSo<4ziP>pVfo~^RY4F!R3}xfzsbGvk($a^^>e0Hw2jsb!_5LttyYB`UXwzY z3(N>FWsMEM`utL!X)tgmPu}3%<5&}*MJ>lMVTLLLMH8zB%gHqadzeO)8+%BqTq$ub zMaUaZtO^5)$X{>GlPAEpn8QN}vv&l&uIWzMI`1ON-t-w~yzS`cC8C)8IMd42|&h$#YA zA4TlcCL2L_PF~H5Q=`5!7DQ^r$_^~alC^IU&JR&u$f>1EV+^z4GKIg~PM$zAr75Nf z7EN+du)0v5YANkCEkCfx$zgWG5v}iE>B5N?+{UcUatgz+qe5Q{`_o}v7$3b6QyG~~ zbFryBwa0AbRNdUalf-({#}4!wvV-@?3syaV2SmH)w&LXn-rL&*>mGi&2A);8VWOg| zo9%BMNk3II`i!0&W;BS)#KdiZEJqIOgG(N%6sGi2?+ui#ha1b64Is}X#?1wYUbu}k zlYTJ3&eH&DksfTI;DW9@$oh60O#ecf3;{LdC_6_+Jf&ns)I4sU?J0y=2AYm53q{sP z2(wuC+l3NZLUm?QfjhCBpTl|cppzG;&4Q-eK)nw?CDyUOGHyp zxU3lARCGm99dkGvcRUA`NAvCO2=7Fdk4%7Q_-nrsRd%BC#JQ?6xL7AyRYka;p!Qf+Lz_f*G`LRtvt4R0GZtBamvX6}?{Z5=KTWs?Y9wLd=eE0IgFXRbd#ZomWw<@sDm${{|vdB%h!k+1crdWh2c*pg-muFO5PcamM z-RIm1UZCw!dtu=M+YJTn*#ybhHSVOIcIUj$vy1TyCwZ5y?4+OU{TZ>)pIG9rZ(!1y zZDH$ZZf}B?1RZUsXD<#QL@%6|PID`@AXNh(Q?do(bs}5@JfXZsji$TF;Vcn4;rARM zKs3E&ALdp7h^Yjoz;ep>F3sN5sFJD~>2G>8Ey%303H-;&UKH@04q$vK4S6Q$VP@@Q z*0BPNvU@1-V}0!k*T+QmQ<3Z&c)g)<=muAgsGDAx8vB-|NaPO&wsiHy4RN}8SaQ8C zl?ecMZy%wJ_w_0GEA=LtS%uKt_hmceZCdTxY*J;SBqr;tTXzCv@-ipe$*qukPOasWeMz(R_H6Al-cZcala1C>d`daNo%SK_PFI|o z8lR`FwOo0yKJ<$Xf*k_@A*FR0+<$KFK`fhfqMYikVdVERt;)(VA-Qm_cv*pv-e19|*0kT{af%YH-nO8B(t)uPR z>R##gUNhRa%xAN$+03J*dsVajxwNFWXhU<6m%9)&dVH)`j^bqZH4^A3I)Wi!hck`g zZS?sQl^x(+<_B_!yf!ZOyf9$<2Kj1H?^m@0t_Xo|whQiPnimIHL+^)P>wa&KS%Z6R z`ti}=Sf4iWBD23F{vBxDruBJwXfLLM(^LfO_5fCykoc@A)9Mv_I6F7ln5fFjv+Qv`^C^F$=!_@OZ*j+BdYTH;UBT$d%# z%Si7B%ZU(@lnLwP4i_Gs)(V5CW|KJZr7pi!6Qus7=FE#ba3B;X&1#$QO3fj;=|01QF!?%JkAv8P{R_e-OA`xO|x7J-CZd zsN91rHhmo#rknx15__9YaTpuP&@@?0_8_Z+1j8Xry6~h0k6SRW${U!H6bv@7Es97N z@cbue<#Cimu^Q3g0ZoO2`< zHVL)}F=S?tkj7wuqJ}?sv=9RXZd_-7L}DNiQYtb=@*ye$wh&cvvMMkTg>YvMAZiKa zay3f=o0}v?Q@)48}W`Q8wH*tf;lR;5PkBg}gwzfJYIoTWmRP zLLdzcs(?O>zxElAF3~npqCZnNu!lweN(%m}5fWW==!?VKPR$T-U)FVEj9tk6up3(L z4fK8c`GgiZ(gx_)?hbexX7$S!fB)QHeAxKqQ=Y)&^)2%K>^ysj{qDM}0BFxUi%o2v zST`@TJ2wQomw8`LJEj$Wt=&GVr+Fy7s9-ObYO@1A%2zsln|xokH@f+Lob4^*IJ2WV z+@WznM*Ii=T>l<=Z^avB$z#6*$m-wQS4JCQd9lOhm&UofX5+5!;ziH@L(Id!vk^Hm zaEI6&9+Ci{aBzW>18*X7E{Ee&&}PpT%|pM(7t)2e^;a6TCnE#hnvCaC@MUcp&6%Ur zbod>g9wf428mC`>nmK-@$r2(^LtD4dnChL$|Zf&+#YvT^k$t=pl^F-Hge_osFj+d+Jzsa-l zj<+A73U&4;gX_(=fsCckhUdGO??h86p9{Lq&T5TN zho@swWT(qxX*=76mdoQ~eY8914@)FaYG`2iPjLoxzIq>yZQZ@VnhKDCHe|jmY_5XO zyyMw!`RY0fJ<1g{Q(pUC{!!#8Ha`0=@B4R%<=HPIEHY&}Bcn+&P+&=PW6irBqPfY> zwTtG1sU-!lf&s=v#XqMR`+r=|>9NQ1N`BuF>PnX8@=DxZ#Gl-+dhReY9-=&+)yZ^( zqdsM*7AAM+2rd~Ig48_PR&kwW^C-%?K9S7m#TyM{8yaHaqzBPLS;=F zIOz~<>eQe(maAFQoX{dNqEw}A7cQJkhV>WLp4)V_W5OPD2me@^+3B@F|Mbuk;eQgM z=L(rZXF{6e|I3wa+H)rzmrpPr9TbJHi*}(VBu!k1So7z5LNp}DycR5V38$p;1Qq)Q zxMUGLPyzx&mQxkJh;6f1JfJ|#qXCjJY}LG=J%%_3BI>+V<6#2}GX^%lR16}xSHu6q zUyMuJ*dt%UEya-a^?t5}lQZ7H{T{K-)tF||r9 z%n{{1Euki!X?3W;yd_tR%8!kQ%gy4^Dem^Njcu0{Qr-jL0jO_>;6z_mqFyh_%r^se zM+6aL4x+oRSiIhc$H;CaN7~n~U@L&kz8RiZQd1Td{U~9sU||4x`W!xTu7Y-7IS!-2e_xMg}=s5p1d6LfCn% znSF1oI}9@UbbY$N_{_u+5gbueLvM}j6pNM4M6%TIN|2^<{=?AK9D$E9Q= zL~-zHA#|v=Fk%yrQnZeB&Dy2r%{UmcY+}PTzE;a-*}P;7!2-$#)d@nYl;0Uz2WOhq zDHc_cP{fDX#_&uS2yRLG187a8GY9yLct0V}HL8I6%p4!qE(^?uzZes@dh7AH9$Zhh zC9tR@$SZf#s5O^j@F`j4cl>rULPE2<3`O?FUDY*Opu0=6Q5IO0 zOl`Pxi@s!iI=3Su8@ze4?Az{?oByl``nBx~NOAvbJ)Y8(W0;R%7lifk3>QywE~GwM zLaBD{3`zMTYet=A%^oPIo)^_A6hK=!Ns3i?du+Z6b?Vx2gvSl*()|{UA8D z92%j-u{E4w+MLHzZhFC$wMu%WWR+9KLvin_wzzI%CLA~WaWq?|jmp%Z>(W+f61MF& zpB%^br|Yb)rg+WI5F6V-46a6uobDO6W>8-LTk`1VJHXY^ehkMx0ql)K!iIqzcC0!5}Y<(x^OxkM#N?kgu9Lr#9=DQg3NZvX~K$itMEV0KObv zD0an}n%z@|oIMDfmWDQiJS}I zmAtwToQjm94Js~etUR+&Uh493aXmw^SGZm~Z?GPEq@yja!f}S$;^W?75QpX+sdJu+ zckBDV?Qi$?NBy(q*(u2<@AgmqGnX|pf~`dRkMK;I-b&NDiq(bO{_}lEbf66)d{tcP zxN0!5A?g6r6jj)`n5$vDvV@Fn6fcU1Zz*?0k7Q>3KfElk%*p5mR=8LGsVEl`n4%_I{=7EFElJyBAJC zF|3~0tq4Rah&2*Bl%u@&@09Nac+psK6+|{H_a4hoPyztnUU-r7YiRjYb+9pyirqLJ#bk*yUjogn%w{=IOKeri_QuXyu-z z_(G~EQmS&?i&yG`2zEjB%Xb_0&^_bz2?Sot=z%>WjmVmhEb`@NJ8I6qMP3{i75F%B z>{3x7n$kwBM6fFh1sJWq@}&SHWa2p%huX=DiPj-g0t4DZ6sSe{1U|LCb}qCrK!BS@vNej{A{VCl&SQIwDxr+@$9VSr* z;76U#^2?@(x@!*}8jontUC$gEMT(m;q`41AjrJ`CL#O^lxvdeO)S#R@=Z6z%21S(@ zQWRC3fxmuwkZzE>U#Qm~mzxQKl4*vM7$u~y>W=+pv{`dxCgTy58>7gAt<}X0D)_su zV@`lUDa_2{;1)x%21wDQUAsYWFrY+&3ix@I_He|}+zGZz1!aU|qb=J1s34&@ zkzRq;OwQk{XZ;m#m7MQ&=N&Yk^za!wdfHY0fljoC`EgqI1s3xr6RwqR`M255)&TM>YV!;HU)- zVuYro_vh4nfVXLCC5qvydZ;h)qhq}(oiCVdkOib95h*keePKvuA6DuuwAgWsc5Q{tx2YK z#9vp^ZY2u?RuY7D2AX*?F;hvA4vu9~8ygfNT&Tkgx`_$GNE`7Oo8INkD^kZREyu!q z9@>&V+#m9?yfMTRmwT)^Bqzg@L#JF)fkjyzZrZ9icVE9h$Up4wp9?WRP{}JNMI-U# zlB&n>7HIO8kLPgEn;9wqGC-*5pH-y9cdxT8sF4t;^R!5)NvF8dK5w;#`bV2%t`az@ zyzU+rzoYr#JI@^Ejyq+avpaP?pQAf2pQoHOq<^l$~l3E>Ar* zM=wXWZ0}Z?WNkol$pd9lW_%r+dY)ch5}SGpzU*J-mkAc&ZyzVPVy!E-zL7s<-*|6% z^XUiS6Az~@^1o~Tzei#gyE zmH5oFxEXJb5_X?jH`xaZ0QfGK^Btn)rWB~zrP~`MSE;X+)^}L*<}xd_Jeq1dx~)sP zEpN~Jt;LX%oyN2AiKi@40APv!?hg$7R#b;ldVSFbv^<1exE zGAcArv>RTHRlB;~tKUPXb5qOXWqzvZjl{!ZZ?*OPs`ve>>|BYijEn?{e7QH{>;B(8 zQi;Q^veA{2_s7a_ZJ(E1^3B(}K~r=ef>tzL0S%~mct*#~?>NEj#{{4HYfnho`N&N? zUgGWKN5MGs))@z?+ zDGl2lAFiV)X@c@3abo4FPI;1qY%Jp~A5)@Vcfil(*m9?a=UIRxy5=DAEFNu&F7AX{ zn#sM&(-4P-R_&N26P-5Z0?#m?CygCBnEw{S+ z6I0jy@(37I-4N}#8rn0-G7(Fbm&&xRQe8-a_Y*NX&}y7N`cr}LL_-ldLPH|%_U7gpowhmkA8M22m#pl9)9pCPX#pz;+A+w}F z`(>a4VS0h;X!gT#mrZwbrq|HM>p_(bM_^L^`JF}U>oI5%h~w2tt)j1sYL_>6@$s#$ zS$Fz7rdmy`4v>D^m0hQluLD1(jY+TI7ubXrXR*ixW^6og;UwaSCO}?lbDSg-nnWg9b#^WQ`EJDkms%B%CS|o zud6wL_%QH}Bb)`&;+w7scLs%t5FjG80sCW7jPH8d1 z5}dsxf}hxd*Q6#;EhVCYN|gEFKp{3IM{z4vGFvuR38HA-TV|pc%0Jw8y7uOdl_jEj z(Id*koAL#h@gD*G2@Rv_-krJa3H^7uNYVu|(b(cyfl>aCJ@5I2e2^K1ADtd5MrF zpjZIw;bZgn_Vtg>^!KEr1mgtbDLQGWm|+y78#f-GfbKy zKFSrP;uTV%W2*EG7}f*C#6r|)<}z8VX5xkBJo5JK4e`b?WuK08Dg|Nffb_6926ZJW zbckH(PV&rWBgc7$6LO1f4WW2fr2tsV1;%Ah?bW?*Xsj7$#qZMJByU!yiPDHo(}%Tl zuwYS%_o(r;e1tN_=uvXYBXC+VNakLUpqkY=>(G;Q3!z~!mogOd5X8XxBQT2~DHa(b z0i8v7q4mHTf7DYYO5C~4Ju8wst?SstLLfHh7TI)Q?(GTI-SdLaEK_|1LLs7v2N zDjH>V#(B|sT?Z^7_Zh31q~=)tL_)jiWkuZ@j5{Y#hD^9jES2u?;F4@_CqNwGKjkJwF5@p*inn?4qFM^0{bz@qo$_r8i6YD0dvK;^9J^VGTh=- zz<^6roIU`C2kZtbPDO_QDBbmLAmEaO{cgy>4dpX!qeN$z%Lu_V*h|B2ac5)Q>uVT& zyfUhz&B))4yNxhx>um^9Eq2xU{x4qWZyoecFZx#?bZ*?%A$gyHFE095UTETxk=|Iq z#-58&AJG=*56++h%zbm3p?QCma+BAvBq5q1l5&%FVhfDxI3Ey}p$)CwzUCg+vOd?7 zrelG(9@vSS*%{tUKZX+Z_PH+o%h@b|#sUDp4!40FJ+Biow-2iszutFY3{ThN2{%03 zypDA~rw677r6)nVJ5@cOk3~m4AGG`~kolS!UPU116T43v_|2c&=VuhJpU5vV<(~U@T%WQeII=-#;zFzw^--B9S zieB3{iJMfJ-;u|QqJAQ)T61xbS1%J+I-q_xk271_DeGE(>}@@^S9MxmY1KU_o3&%U z&E&UMTE7am_nk7~E3Z!XiU=mZO6xpo z^Stf-ysh^D0MqqlS2Sko`$|1y2AHyhBxgL$Qo@XJiaeGI*5{N#B9M_3y2M84ZBqel zFl;0KYqRP@GsR~en1>;`P(1brj5Ps{?D>gJOvlBK-urz2P_6W2n7~t(oBclU9ULY~ zY%ZUi7AM0qS=mC*mAUuA^OqyFL;$s*rX7f9Q-hGTcL(HV2d5GL&9d@1tY;x3*Iipm z#KYSyaA$9+SNT>sf3Gj;dhzcb0e-Q|jf}XX-rgkKMen!H<)_C{`+C(J?%!p?g5?o| zGr5=!K6G(E+#xLk+Vcb640e!-sXdc{J(K{tW@p@921a}HAj+t|LQ@rin6 zfKZjsmpH_5dlb==A`G(u5M*pOkG*B{U%a@VnH&lKpZ#l`n6M_IqKB3JvKOh1RqJkp zsGFIQ0h#cCzx(=Kc;{eGV-FJnzNYyR>wRc{Hq`yvx4@c+oQ$qzM0=+Ozp7?88oOV_V-}2RMgHVBLTFSU`J|-@%vg({GewA?Q*Kt+hScka(gb zTI#R+S_0!SP+!8GVV>dnTs2s8#h2}R?EBsWiIuzfKKM8`d)6FEM$W>62apM76Xw+^ z2%pM~M7>RLuMANp36mD@X&dNzc8t-QgznLx6lYI1rN8OXA&2vki^{brEKim^q`;BZ zj8u&hlK1_Db;p2i)OyQMQQbhW9ma2#x$4Gau$?9lt z+Lv=A9NC5N9E@6uXnk9cLY$>A`@o2f^^Nk zS!cK==O1c`Jz-(QEp`g_<(FXp>vLO=X zBAYA24#N`>qfW-$*UTV3hCGZ))``WMm2X+0Uf}kY z-sI5W1HqFA2(T0t7&n^v$7guD=#^K~nQUz;*_SeZ2u; z`DO(8bt%J4gbQ~b9a;8{_b?_3qGxpYEg?TW`aa2ji8)#vZC>lb#4+NQMH^M`PAuk9$Qj%bIYb|8KZg7 ztk`b6CuKy)7Fur4?IzqJN0m?e9ux*P2A=5K@z|Ki(_~54huS?np6~T4AhI zyf{b=j26Wgw4sS0YJqgj{{q`8B|6F9NR$gCU88L>Aw`4S>7i7!vWKSTT4Se!)37}& zE%Cjt0TyBj&uVGt6p=AvB|||*w_1FFhW^N3C2!rsy(8iu=&K~m0)T}OB%@JN)Y3AW zHwe0B2iToCksf^mZHc|Z#k=)m=>qfFG9}yycX3V9UGVYS?l;KIt3Bb_N2>vao%w&T z3MPBm09kM%3P4a_mpCUFcWmj)%t$VfkGT=b6)e_}75d6>!Z(dFrHD7n+t{NFqBMIl z__c*P<5Y{9L55Xy?&7&FS6GTDJrY!SFD5-7Vp3o*N>L+KWcYfVSl;h=*NBa)F?l&Y zv=N-RMJKUr{S{84ZClr2DH*IVohx=K$u=FhUKZOACAyhT*aqX&glZNS&4Wa)(Wyy| zK71$*Mr~uIG?_npfcGXaG-09Hz7KQCh%E9Cf3s|tZ&q=BN``Go zc1VbH{&FoF!W|`&Rki~BnNwU1G$aoAWbv3?+jefyT$5>67)YjK#E}^fJ##R^moZ&r zZLl84iV~edUdV%fVhOxSy0>(KbiT+L7bS+16qJcVfCm#N)-YjG#TcB)Lv)3 z>fuB?Jorupw#r28+Rr2f+>2+pONn9qmvBHanMm%|;|~nFH;M791D-HyY2t6&L19Zt zm+)YcktB{;(<0&JydrghfF&#MYI72f%?h%ZApu{8TuMD?tpJk%!qK+Q+rb!_;&ot- z6ng?T-lgW|nSV|=r{X{%A3PeireIl}hk9A4%saM<6%Yk{tToAD6_kM*lg?AuA?e(T zagDV@SEe_wL1gK_XYPcRck1XMOREPK?%6k`Wq0CzIf(DlDhW;ouI=fr`vlioVKk-j z;Lq%(h4>XtJgQ8a+){q@BG3(Gv&|i9%;ItX3Kdoh=#}ct%~Hi!<)t|rLxwoIGb`ml z1ScFJDaXmN$g=qcQ^i86y@s^8P}YO!V{4E&CXBzI*hbJIpS{woh`*&<$Y~VuBw#np zrk2`i6)RTL^%}j_MHd$1@^MNEstbaLc z+cAbNta~+z4?>i?{Ed%-TFedAiebP#sC0xh z9N5dQO~_Spz=zLEI({3Ks|wS=dMs9)*Vzmgamd(!k;X(I%!dM9t>9*k7l&1#BJjuu zP9KXVUEb@cIuXXdwwgySl3A#7UBdNqkPShcmAsoO3B(%DkRfE32kx{uw==iYX76Oja0 z)0_*~kU-na=gi>VGStUS${QT9J4CQ3aKJyeWQ$vn`I~Zm>HC)fxM*uXC{R3d*IoLg z+PL0s(T-8G-s-3;oIjzHtaPN&Xc=UA_|>6x>A zr{P?XinOy^E=$wH6S)Gu>45p$Ie-kW;sKG(8 zpd^ci{v8<@1JFImu+SkSnIu*Q?p1dB7~Qq;>o8KJLvxA#L8cJcOMCWn;-Nt+BG}#i zGXhbB7I5Ah2kj?KFs?C&D;kb+&y7`qiqFlHLYb~xCtPG{7T?_$!P z7>W~elX2JCWOOo4FYNZzGD3(>SGOc-ZO&$e8xqi@(MXDn#J|-Ux>2n$`RMfb&TisT zI9sLmt?I5nWs%cSQJCqOM$Jtd{ zAKyV&O&VWK8ac#(1eijO-~=3;Kc?S9Yb&kZx6jtA?#{nlrjnXx z%QCMY=|kBz-l)eVeAB_wGp){rUr+Ngr}&Ey+PsSp)bkbHmlr0NkCUA(o|vZ@1C5g~ z>LvpX>F1@xWR6)l5%)YX+CPw?{Xo}zq)3v%)J=9hzO*OE6DK-km$R-LH0U-^AyF+a zS5>^?U)x2pw2v#5KZpH@MGrG4)1Dug8xAS&4k_NQ`*zO@8N8)l+NV8EJ{H((>4LaX zH^1udu%4dcH>9(gt+z)#pQ~+I?}M=U4@c|0g0}C?Az6&Cwh6*n*`fJnpi-_c=8pZv5>}j(=9(kWDY%>u$&9Mf+!DB4@M!j{L{9-%eL}zzC1W zC)&(b^A>UiQ;<@ac51A3m>y{{cv`HrIye4he>5Dm;IO_=V1`_!^E&Xzr)Q5{7Wl;f ziZ(kvo=pFY`ooDYMOceWq=9Fkf#w&0B&DZ zSZ$co(Omo3)z=Jn-##z(nV45S0-(u9l<`}9*f#(c}}k3iS|1FTwLm~_C^Y3*`}Nom2n>2{qC*dj$x0u zh3$J44JNI?+^87-F28#a@mX5dX?|LKPHFhpaf1jPWqW`1{RyLH^l~kn_rBL%Iw=;`;k6iu71D-9@Urv{q*hwgCZ#vOLOL<(b|W3X@O*!<=(>U(||Wm4_B zDm5q5-Qpv$+D8I>V`%fAMsI3TEj9UaHJlC9V2kop4UWSgV$ngG{rATB&7=?8Psfxj zLQKRL)NG;WqS7p!EVs2!^?8q%%f)>MZ(hp|)Z-4+CzR3c&%2u=78MXzBjVe_!h50H zd{5mt^WOE6zu%vL=hy~0{u2EFY$bicR$h#HWP1@q6ATW50L}|uoO{%g;LVUhS3JRD zzIL`pV6@K=y4MrB(_Y=5EyBvC=0o2qvhMZ9LbOXK)85;-3u*aC-nwgFk7cND8jTt)m4gnpevGAMnH^nSL0%^FB?Q2^^ zzxXvVj-9C54L~r}lu8jlGZ7lmX;F$-$s6YyByTH(i}7fVK(zD8Bz$cX&=efnii2SC zLx1c;7n7keG3?`bY(W=`q23Nhbhvvpx2~TJ^xA%gp!UZy&VnbAyj8EA^TURyJdgTsNod%bLgLPo;0MtnR*)-|f)g>guYFrhKH9 zYqhFRHQS4(-JVYW?XW`-|9i)errk%I{z2RRxx>)YNq9@qV8aY@N;V};cWOJ6^YzPC3}VqhBiclmz|!{R5-* z@IDJje5|pO!1;rfjFi^JSa&Ll#R1;VfR+86M2r^-w0$}Zt**Y6l+1iN11$f1(z>$M ze;>lS9l+kRVn+me zAg2O0jkO?KB4k^ta4X}IH~>N<0HG@k+7*dYPB|=d)5wxDo>i&%#*sN^oU?4guO}n& zwYR@ss;#FHC8H4%)SveesgA_aRg(V7V$y@2S3@c<5$-4S_G>J5Cu6C|+y0TGHF8D| z9@enLzV`c*v@iiM^3Ibf^npozCbMa&2YJ*ZNF^i+S*>Pd&i?7Lk9_eS;#PYdhsTk?6l86ga`+B^htJfI>o~O2fn6v#O$Yp-40`IVHWk=V zC>9+I%~t9~jK@Ru?7ySDh$E@*Y+SeWH-}~IO@A@Y#X|NoIW?;B1kjNX2X-qAncQ?I z5UWkXN^IJ;J9`=%=qOC~4yM!@=_C^{V?%Ok;o!-zu)H%!oDVq&BZjCg@DfUy4i6(0 zNYDnuqxuFLmJ^TKC07t9qYW`lPz!F+!sPdquINm zFE|7r$T8V~Q*T-F#UYYuamlZ@^Cy?c-lq_Tb;~Mj;`-=wB}Cz|_3Gj)NQO}N2okLV z!fyJOp;1%$NIb2+@OfwGkYjPRwwd=8T7MGc4m1F zUMQDj8H)&)TP+$OP2QM*tcpIti9SJ{nQD+7Z^%&J+TrVub(fG&tBtLOh!UTdA;M%I ze*CAM?~7#2)#v_yqO>}*Fs3(xUElAvpF~er*#qS%@6UyzMV>ou+~y%ROqX__XOD`- zy2T5y2{NeRBJ45r88QcIYE7!5c3ifn*MJpp23Qc3il~2iv7* z&CC_6W0pe!!l8})V5|-{DO&|lQyVGI{2Y_!HccuZK}flGP$=NMw8uRpu5?)&avm{q zl`?$=qmJX(J7mB4Hnbv-7av z*>+CuLbDIlSCZYemXjc)7hjAKjofwOh3XR_L`f)6B`-DwVyGOZB1MG)#Z3C6g-Ot> zg;hW_cH0Mo%JAX%lL-Zu;z7{!qafkalnMyUf@q0_*O!Lc$s;4?lbq`-913w^EC!(= z6Vo3N%qTf}9u)Z3-{By!=tcADvaar#w(?kXaSGJ25PrSV9g*Cs zb8!b5rtWw>?{Dd@eJ`*wjRI!GRQy`)Jk#uT0%iN6Ey)e@*n=)@&uQNw+WeM;^Bm3L zOKrr!TdQmE==%4En zZKH$?nbfj3?VPK-%PehZ9%y~k?DfcBW7RoI>$56|#@MLn{Z%Kfe9kEQhtwokU~^ks zeg_$}$Zm4f^*`6t3r<|lR;Md9+oWB2Oa^;D%|*rFG+bp^ZU1%1+BOCE+bzeX`Pj?4 z?nq$);2!M04bXjwigUBtJB9SPeIBd|<6INgJDS}!_T`zj0_5nmp@V7Z@X&DqS=x?a zz@*|Eh_e};3E1X~k-XrAmns;N1$hYgEA^-AE>TQsmWh~Ltej?MTi+30f-8|wE=5l$ zq#n#bEMYJapIBGGmm?n^+a5tEF)kKOh^!o72n2~OHL^bxNC_4)v??|N+6enzoGc#~ zoEg21Q36Xu*t10l>p@;9z#J=s4Q4@2(YGAv0G67uB{@HuHj5=(tr7U1)&Ikq za2ErGQ1?V%sT3*`0W-j2m;85i;A0{nnPsA!T2~R))96C`l&NL*b9pX?=qcjj;m#t9 zC*PH|3tKUQAJN?L?DBb3rU^kh($6>mq10mmt-t4;Qs(V^W~y*tW4YvRr=x8-rATvh z-~6E}qahvC6QMXUL5=AWvUeT01rze|HoSEY!<=3B)TZ;kQ*n*UTPx#tE;BQ%8$j@9 z|4p9nQUe_P2crK%R~LI?;9@uSgR>j@PYp|hku(x=1q3G{Ep_T9 z^(y6-O;nc&d)Aq++g0n06A1+b@i+n}_O(iZd$~>>M^`chAt zC7-RI_n)3`inktUXgreJwJ)6?{O6rxsp22&^VdhLZyshfsp6Y-#RT)|bbI3=%ivbZ zjid%L6krX2W(s9sjfSl@oT8E5Er1t_F0gCB)#6p?O(@%(c@tgC-cWzER=8d7qMcr` z`54ML9k|%5QmdQ2jhRZkuZj$_9saXBGY)i1>>cDApki4hM~EjZ$Xdz z{bm5Usv|L7L3Sg?g@JRxT?NwUZpnI65+%WKCS;CjC{tiO0e8ub#(NI{bmXhiUI}_t zk}ANm0bJG7SZ;}Zb>y_^Zi#wV5-q^Gp>Lgz+WkrZy7M;aZjpU$C|F@UnLqTelkF#Z z7XWsYtubFgeN7V4fpsRj6El>IVLY*SQ<8pP0KCgNjt#s6@>*@b(n||;DTf8t4N6y@ z3)T%oSF8(`4Tz><3)}-jm-iPe8#Gl>47dlHu1F578#xFNzzxbxEHbYSMR*k$8M|@@-_6k3gDMT$?U_%OLfM+%{HCcRxY=Qo59qhNsXeE z(nNZhezw~eYpm(;=4r%yAw~@HB3tNVB12ie2{4`pnf82;*q9h{f1i;VM6O?cbCdS; z34uB}`j~&S6(Cn--%pPj}>A;wuVoAfb={o|W0*z=a}#pZU*iQ|1TFYfp5T<|Np zq+j&h-ip?y@BrKhQ^PUkkRgEs_5F-8fN+3iNrgv}fud zi*i8hDtyrua`OSX2d5m-`Kuh6twW(GW7I=VeDr{1k7zPAqyTI9$iX$NBc5NAlTZ74 z$eU|UOr&Q+jEoyjy);=0Fw1L68E6md0$kA>+HzWOE39}#mb9U-taI$?&Yy?!>kii)mmKCTM0X4zMT)6Eb_B_lj^90auaA<8(gu8rcAn&jt zo6)`QB^#fBCPs6sa1VD@0m8pLSOC+qpvVPWcsDWIt0%Mwss9~SYHU^O9}WPvrc4Ur zgLC|=Qw8?)=#fX~sE`F_wEdK9Y>6Me@jbfAkW6dfhB$?P9Gj|L6S;)B1Nd#uPIelJ z{+sbO>QSf%ZgfX=S7(Z5>cC$TW_HZWWr|bOMeb7|N#>h{sY(g+Md`>9>rc`N`byMz zm&#RmmuuJAXJ*jo{zs)gMtI`V*mq{VWbcnCXsY~_(l(|+n~zQJ9h^EfKVDxcPdPWA zYF{?JH$IgaDq>{>rEU$hrDOF!oA-+$7;aQ!zZ@}`9v8A&_@!f_3UiitfnE#F-Ve+Qpknx68Tr5@ zJPW>AId_7&>C@Sour4R8@LTDC$5(!=Pcm|gVw}6XA7J=iN1k&0c6h-~H>SqD!SKc2 zpQm%A0`_@#-dH(()PKyrY)Vva6v#ZLOb?w+aZ0$q-#Uby42H$RoLs0xcwYsmZxyfo zBl22MN~#d&Q5M2xyeiC&Mkdah8y*z2Ftsp^U!~J{8=ILHdVqRtoO+fV zb`r07i>7dzI)u$w&Yqpy-GBmZp3ViYkS4%G3S1|gdo-^6-pp&@*P+*WX8YUzsh>*5 zWJ*~r8SxPAoHE}l_wX#^+_-iOe=DCk$6T63PFKq|+TD0*SQLs9f*p~_CNGq5NUU{8 zy>zG(%II(#QH=~Idhc0cEY*tq3^LqZhPEpi<`)FRRn9D%vRGvpzk5aDGk3?0k4Rq3 zS>?(<9!56Nmx@YadoG1gK<747xw^0i9AOdQY{1#{3~%0?Jw*jaK1J+>OGBAwn|sN5;fmQjyetvckX30k zwp?blir=3$jZN#-2z7iE+N5PYTJw~cu>Fp#Hb~QIkV?mejbc)Rg`Kx^QUgX#1IC-3 z?FLzF)s7JwEa;5l)F1k(@BO8m9gq1>_S0r5E%wvvv{pymI>7L}JqJSYm6&y!i^tSg>AD6=; z@Gg%4!l#E77ciSeo9=eTSM{%K9~0XubOibThW*eZnvAN;-1eCc?Uo-axs<1@(Dj!$7}T!Ji!eTx<~M6> zoB++{$d5D@mAjnsgW~%)*Smg=&Yy*k`MN?^4&ayP{d>fM`>e8TH zw>0?>+L(IwwZ;{XDJ&cwK!*}G!{1u@f>RQNSrLdmP1&x5yYOXRirdOr2TTrV)MDDo z*Mc%_C^6#0wegSlYimL55&1K#%IZsw5sStJM*s15;>+r5Kk9ZP<%Cy)h2yL+v^D!H z}8=oIHMxuw*zTMk#kGk z0bEE#3Z!Q_fcTzAuTbYD!21* ztgYl!~32>_=F$8=&>w~UO1)eg5KsqI%T;4 zNnRyMf|z3xExAQWtxf-k0|W9@oM$2w*xBKxzoKf|#H*ymM4*NdLxmq-yU3r=cJ$TJ zwk8bh``L=1#&pNiY1Ls`Yj!fy1C4yFN6)bdIt_Yv;<0=ln!uE6d}_4~*Mt;MbVRev zSTcSjh{RrYkzC^F6Y=OF2){z&_`hZdNk(A@hMIUpak}g!lkij%`-Q^+Q)(IFWiIO{ zew??RuA;Ne5$<~Ol$&Z%#BH2c1qDh0@%)&9fJnm_ObOrX`P6)!e}H+B=8}umXP0yR z>(S?Nu;$HCV(!oT*aaMUc^f7YEWvgzqU)N=bv}1-Y z;$EkNqQJj`0kT%>q%YUfNT0{JKVGxF9$mb)yheNI(oC9h#<|o%7-%~jnsFRbL!Q*fc5FbF^1(JVoEOgK1f?n@ zvEbKx5s-w5AO`JF&UrnbL1A!qZmey{<@j#7x=;|UYAO~=x8>U^b!yG%TC?RGC!*H= zW`jOf+*?b_Y#KRKgE=`{CmH^GYwoPDDjOfgeNIK$Ye(N-LE;~Vqc_)h`gnA4-fXxuy_4I^rBQxwX6!a zQB`WS_0SpxYxhO`xd6tO^`w=JQQR zh}B|swY$(rs**0seA-;0vrF}Pde)1?yYo+H=ZKQ~Y)iSdM?z2noaZt0oo0_}gue{0 z5`8&O298zxp{Nah+x85#D3 z$T!Ky5h&-N|IToQC}a}FG07kx0e{1n3IW4U(fNZc0?#ls?k4JT1Dz!b`^O4L<{2vL z3WXhuDG1OjkO{shVDiRp$iQe`VsY*Z{P7>oRD)siU$PM2WV0TQCUsm3;w(>l}a&^XDb_1T(tSCPYYP;=!A$?4P6r3dP*599YRYQ zZuZB!jHP%Rkwc=t*-q&pZz13zp4w*rDtb z+lmnEq79g$Jz!tDWRg#mlvEv(e(j@DpmXOT@|+R3yxWiP$kiveiUB-klQBdlK95jk z@+w$$$b43NTT&hfX&{q zbZcab5YxA8y|wBS@wnjA`9|cinN#Sh%?8lw5r?AGNzZ0 z0|>!!m#!nEP1apo4X$U-EIWd)qaX1LRtSg3Ggox1Qm~h9n(k7+MKE>P#9whDmUk|!ABknaXz{a0Wpx&SGBPbgy1wt_X*^~1h?4%l{^ zkj0XImY*)vvUCnS0h-mLfO435>rU>{59a_zNVZsFFCX$!rhpV}Z4HqKZX9Q3NTcm5 zL)pQ6AkEJvfZ?hJ2bxyR|xR$Fmn_{ncw|h-gTZcMdK4L(((oD#Gc>vp4ATS z6Gr1h6c1=Fw)@GAqjQA-VN6MvK_?r;NH!#2JB=z_mK>5Qhv+S5eUHNQJq~B({vLBh zE;JWuhCc(0Ba>!{U6ogNnG~xZX}pxzr;Y40EWmLU*9GL$U`VG;E|}s3K4=E4r}?9& zLZurA7?^byZUzIT3P-Jr1h|y6OFaIVmUB!#*lmr)k{)btb+1PGZf$aGz^7fWwx@U4 zKw12NcoXe2a-}`Frs7hfT@karf!Xm%_p|qY(6q%_y+wKbUV4(dVQ+C* zHfDeQ=~wizy!-FpOYf{-5vpKzy1UIYZmRo8Z(OO1>#pBZgWkezRF_}G&35@l%@vI1 z*WO@Ajkn@jme)aonwyF8c9zpk_iow7311W|zuAN>FPQ-=%+@+)>uv2z+?$8kmN)jU zOO?~d;&$(mixMHv-EGoMmvYMv&*?=sOt=%=)2|_xzHVgNR-VQ0#YvkT`fHr$G5V-u zv`4txV5dpeOTVYipr_8l=Z^d9D^B!1`s;}Mqt>qL7QIs&;0fBldfWZSbIaSrW;aMw zYKsRd_}2C~?v-0~>wBKR=83~}kz@5I_X2>uT?_qfZqNgfH}#Y5snz7=8WVS8`?GR{ ziD?Nd^vMI*=_A4eI008S4%f@bCA?zet-uW3L$qdt_68=Bx3ksd`d;uU?qonB%kGL} z^XVdci_+m~@2$%r0ZuUH`fE`BM6koP)!WL=#k`286O!bbLlt16C=WQnNcXkZ7z2wp zv+^QFAh%`MkQ+UF6*J@gl|l)(=w-P2+xGKo4p(YP6x+jKfj863{_gqXQ1%z<_;xM- zp2vJbw|*h`855qF7M{5tdMin;zCeYQ(lR&z@8V9-o%7hu-}pT=0W4OzQ+IQex;!FL z!mbcB0`4xSL9|_e1`<4g|J@`CM%BvHM*S#d}5; z3^~q*`eTB~g$hiW%lL9zkp5JSvb+JSXX|g(;m5kW5cC|@a*$}j?XC=9+Wyp6uo@TT zl)m&M#W{dowBFpbUhrJb(j+Uq=)F`8>uI|hujdB+ZmuH3@y3Z8gpMK#Ox}zOz&wqd ztJGDxxDm*_8?iG_n#l^sm&_GHYr4sK@!4^UrA|672}g{qig2E9i^1=7by~6 zu2C+ODiLfrTO<+=kM(0Gl`KUxKuZ@uk3#~)WTQtHCRRHXhX^DBKh8V>LGUyR2Dp&? z!*DVfNZVzL%Yr&RKpO~xQ^F2Ug_G)F*6pCaTj{y&Pk_U7{1C1%)VhP*2KiY{gcz~S z6vDsK+(M$1e&%`D7#@34H~hc`?1p2(6~h>tP`L-==2k|)aswuy*?YCyW*bx?)>+_u z-;A(FM0I$GY7DA9G+;fMVt{~|xdRWAguN#{FROyuID|&BV(v(Q5{d+Qnk<>X{h%qW zmt4@`3T9UYX1H^&U<@0R;GKnvo#z>u^N_8Jo|yuHazdv-M5L`x9-IIXRtTCc;FcCI zjy1xD1WG9#iWb0#gkMWE0tqqzpBh@AlWb((1N6DCelHPkM@m4%ah*t!9iEsBq#bRk zrO*~oB^id-NdSdL<&SkI#%b4)mCSQQp_=aUngXfk=v>h%#c2|!D-LG~NPZe-n~x%( za#B0gR-_+9rZ_ifmf+!O9rusarC8R(l%q3Y$-1}KH3%dr-5C|^HnUR8576=RvuTYD zKQR}GovE0%k;X8?xgifm!mi8tAyv(b1Kl(ApFg+j#^PSB&s&(+eb^TT-7-&i|6)j% z$4N0xa&~0A2O_PHr%RsqTn8)t!(F9n4;kP`WH-QNL)~3T^7UC9@IThtoOUZ(z7I3q z(+2p3-bVQdOxXE^AeX#?-G z#*MwcND+ENi#1fDj)l}Bi|OM` zRO1}=*+}yF`W%pG;fZon=(B;Op`c0afao~b;z-#2^w`vkU4cp|5{>>$lKXk8$D^f3 zb-I>@pLAVAujXy8aWV{!ba_?^j!Sl?P__jYNu$#vAl#<6k{c@FcSlmw1r^4u%79@J zTQ~2{mWziS+BK!lGVZbaI5Q(}mCC8bE9FOskwv-}L|p4qbe4jgpaj6~C#TiQ2j?p-Yn-A7*l5sHC{Y zP>|eRDs!AhDnAt+DRfpsY;q;fZHjS?hlOiYCFp1Nr)XD-Qa^EmFVen)tMpWu zSwE$|Ytda-N40|2hoY6*D)eHHnk*Hmts^RA1>F+#+eTzWG&)e9o-BGATJgsP$=Rh% z!prm^=xC~y<)z9xE|s6ED-RAx;yV^$)l5#fyFyS)zNH5W`42Tt@a80&Yu=u|W0$T= z@I3SP+$*9FcFZ{GpF!sM<*}sL$K@#uZMA<>smLE0WPDO z%HbFphK&$|9RfZ?6+xusLu7&-qf`F#KTTxIE|i13e4?Eo@%fuHL0bn;bN2Wzld+q< z$M+W2Jwv~i+TMDqE;D=L1BoU0gLSSpb72cmfm%e|ao{L2xIw<9Sxx*;JA)2!xCUjD zzmDotH9nGb5;>QH)X{o&fjz0ly7I@%h~t%*WSB_w<0X{I=^9}?-D2Gid$qyc%tD>t z`w*U7id>U3|B`u7`4Xg(1+8LU7he;skJ{2X!_22ojk--%o;s(FZ0{3iv6hcP=e#%O z5jzHP7FKK=f{Z?wXg(iK1kr$8a3<2+#8Z%_gTlZCAo0D( zgaQyO;AAerJFHoHsy;w@e}=-rQ9hwjGP-uVG$0diSVw+C@o@Y7`h?^@F5BJKKO+%{ z`J=Uof#M*h*-1onDOmVycH#oU{JmxfVwAblY$a5-{$g@`Nq**Hq*Di_kr0(c01-O6 z^65rdNRaMOB=M-OwnTr)`~Z|A1M5JM65fSh` z>6LEl`M1dDb|+tH_kSk{k77mlWet`Nehsi|wI+aw@NsC4YB zticH+80b<~VXdea6U`?zw#1BQzYz!MC0>+6vW-QteP5m0RbZ7D#1W8y3sg2#vg#`6b5sX#A@G0J89*^h zy^qI&pbD?BOjnQQq|C2BR~D3fYjdlUQbHLe>tcaRB~TSZtXOO1)S^C|*ds37l}Z%X zk(L{z7_%(SN}oZUHdHArJXUj@q4!))vpZVu?@wnPu;jd3EkD&FM$QqcexK+btOE|I z>-l5LmguIt0YM-u&!p>~HvC5<7E}Tx7;g%QXOLL?xK9uGh)#_URK(t%XcbFW@-G>1 zXeI$UB-sT>c;KsDbTNZe+wNU^ofLuTdozEj`y-$ zuq;F4*#h(tRMkqM&LFvy|K-K3?zhVbjPdyc^s(HR{%>ucq_#j0Tn?z_(Ityv!3Ww6 zA^}N$ya6&6{rD=Ej_A)DNGVK*3}AzfME`(?<_YYJ(W?avC^#t}QwCs^wEf<7P;zLw zTFCBf)1dQtXP&TRy)V=EF2i(dHy6>kQBDwP5GWwUARjWDclDaBO?n{5p3Vl#nKzx) zw}xi^F}$(9m^qF4Hm`eQ0!lxcN8EnoC?7h z4uBYdFwdMP*|0RF)7hXmuhlcyPb?2lQ-zT7yNexgZL|BT6xqXNWqI3lCDrdlqu&n0 z;$HHtVM?Fjid?~RS=ZQGwLma^)n3d<^y?iaQGr`l`zekq3xSDkeTx8v5rxAWiUCz`Ni_7+KDWROlY|~lZ8Pm(czMvj*^kB@}U)i<7TynXn(Li>Z^s+ zLV=UbYCHZbdPcw#+nJ;}sw!g18o11O+3rkx*|yo<;5BI=2QfI=pp7u#`+^w?#_)pk zhFT~I#q-Bj9DYE9QU_E|Qz5G2Fjvc1q07hMKggACOu{7?L@Mt{#drXl;}v;;0F{@D z1ZQL7+yS>BZkCA$F)b3qoivLNWEZR_dZN#siD#;`Z<69^p;UZe6H6i^Z9Ih^&qsFh zsFb`1&YVJ6^f@BSsn4~4VzuJv^j5x^lec=*;S?o}X4Dsj$k86Snx)`~fjkylJ zOT@7e-0H;)C5@Iip6vj}Tfr#Ji<0ErYsm&ww=zxf?dC4T&*s{!cz)jY-CeY>>2ce7 zOqS|CPUA6Y*0}#ijyPPCVzr}L-YyKgN55h@LQgqKsj$P!y;7F;T|dJ1)OIGVFh~ z(hSszW@?mIQtd(2+f3_E6q&jsMXhWVf`k%w^vR3Nw~jj&z^Y-A!J3hRYZ9{Ika2TKSeawJcfBj;VQf(tz?qO^rZLmJxO^lV$&GC2 zBMa9U!fJwRUI0$8>(?%|5)pzHUQ2xEbWHBa-S?;OK(9LSJ&Zj}AF)yuz_i1XogrQd z!Y^AJKs@k~);1SDLZh3`pdR0@6Fz)RT=i3ojTeE#CU-K7Wk)>cyt64Q+qG1h4xjBi zKc)L5|EBD86i))$-u!D&>7}Ow{xn&q@v0NS9kFY%H*^yNam9O_cvcUwwiVA1air~0 z>3!4reY2}6%W*GHne9Al`@tI0>xYrF^`+EE!JAdoYus|8?qX51QSzir(sF{C*j;+# zDErLS*P}&UyO7FLqX9WW?MN(0&7z@pu1S^JohuKvpmSW)XPHPA7Vk%oQt_Y(G7A}e z?CILK*B_y@H&z}{v(#3I%aq*hHliQt1CWj;yf-S?Zz3$DcAdt$d*|&;1{(UV+!d|* z`Uk$6RF806{4qN^8rohpE>{BQ-@%`1%zD{QATI$@cfqod_OXI?O!LY9ye+U~4^fO) zHZ$yYbo%pOUSYyqFldqFJcAV$5fjQIb)ke2n}jBN%XZ?D9{qxtC3!hD%2k<|bFxCL z7ZdPX_*LRI2-xkTb6RMB)xqD4PA|V-Nb`_z#sbh8cq)Vt0Avxa_Qry&G7}zY@Jc&z zq!x(In;^G#K0tuOrDHXFQ*+%j&#DPkBdA8<*m=B@wKKuJ+wI7XV9EWg;OY4gJnDH$ zG_20ry4LF`c}oHJixS)O-%8M; z?{F!TE($s7M{XAd4E77(mi*DK2<$@zF@u7$c_*fp@6(>-pJRv+_I9tf9nWcr7})Tq&&tN+r+s_p{qxv+hM(^H=VN}YgOxU z1#OO(A)Tg(kCRM_T+<%Hc{Rx7T->L$>nM?W*{|vp_$wh*R`zN_^`1@nQ({KJTCWsR zKEnQ(ao5(RxG&Uzj5iaFKZD8X)H6TmJ=J^xyXai0W?j61xdhKxzvOtWx-@5CzVDh$ zO-OG~J+**mbh@==j5m?;j=6}6a1v^)sE=LGGS0fxY^^lQN?XN9H)(@OVH;f$%eCYB zX6{@>$Xq5 z;}a!wg>LGa@8D2jds8JUcw!y3?bBs;iAnxB$t#opeK&n7W1XM&Eon`a{|tki@qv!4 zvpPdm>qZwpveWO6k21NNWwE-}HXNaUA4vPA*_ApP(4?!Tf(ssFFs^_sLv()0+O zYt)}}SWH952 z?VPTI7m;SY!y&;O%JG2+fI%X0pBAfM51HLuO zG`FH5rC9@S;WRR8fo#D$(rv-EGoBi@6CQ_m1Xvey8c5s8&JH-Do^I!+5SDud|J%&5 z>oYE9_FL{}f-|j>dhlN!ZZw`9DdwW5-$4xqdGtTI z9Gsk~>Y38TP5i;m=5_mg#DUU};B-Uuuf?Q%R0*hCgB$5L)KVy<-_|bAF7_3 zOUTyVnSamc%@2?w=3G)H{Gj9macWgShQ_N|hKroURrjSWfdbek5bK7Er4gmQnz7)z zo-tmcZT7NNux_y~x|P_aE9wC+sxNgg`DTbx z>LRn3s2nXWs8yEX+O_(|n z|L9!yLX2j{oIb>LecrU((ZX0gbVB$)J3PWjos#V{(=-DpkJBnxCKr}JnP!dS@L%K9 zJKuUNlF)*7`Q}2Dup}O%B^Kt;ZEZ{m{!!5W4`c5boLdlPjmEZZ+jeqdoY=N)+qP}n z_DN1`pE!AAJ74DhnQx|M?!CLJ_rKm%UENRjdY-*{tv4|ITKQm_Fm0`hqEhE0LzPBy zN@byHM4VmWX62G|lx_TMx7D;>^4<4QqsGg7{j@TU3(>ij^`*NZL)T5Wh-IrqDW-RU za^aynJ(TIb**mXYb@P#nDK-gTf*VyGgn%Vc!xJcJL@R@CRfikRizL;b5nm}nlcy@C#LfNTT=vtJtXR?~jvu`4w zA&Gp>yDt0miCpQ>8{?xp?frK?b2RX`X;HQ5b-@dfs0rx*AS$GEER2u>Xr3n;{Y*NxSe&l+|BRS9Yy0g7 z);c@{Ry({~*SD;1Wu+5FAkz7>rr={vKMcw7)xI8p%lsR=-aaZrRR~#ttRA-BPd}OR z#mwW-)o4^nZXwY{=in=Te#zy~Se55>^p1^C<)twCGha3V{1}vQaJzTT-A^6WNM))5 zRHiXHshDwiUK+YIS8*kSp(Isx>B_E(SiFF=24;l-8C8=hOESbaE1PuXC~ls!U@f?j zQzMndS6^1@K!=@~e#oX`sl=A}#{yCi>+a0nzaeM-n5g0xDGNt7Wu!<2Y;bp!n6fhs zzoxGVPnPQJ#dBnhuu*+vv(@Bz+C_Wh4z#4RlTDd`t{@C}9_DgUf~o1cbP2DOn2Zt! z!J0d1Cc{uvcv+Y)`>xTW0d#p|lZHu(y4YO9x{A!Hz&uC&{@FZXqcg;YLUI*l2uLBJ zQ_tBL0==z80O%Hm{$5+l+tt-)cEJ_14z2K`o11 zZ`?jA+c!H6x}GBThI*WBSOKgKr>>k73@-1&tfNf9icM$Nct$?8RGBUTRS|^=NiiWJ z=!OWZUK%1Xw12&P1pY%r z3fvttwXGLiPE_jdANS10cXZ5e4-?Ext=#JWClp(b%vSwDUqB%L@%niPewu@)xxKRs zqoK1Mv$L^ww9wud2#42~k_A(UfAj8Zg)DI&SLqw&`wU9bG2-yPp5 zVrR3p|GNA7>Z3-#7+}8fFzB;TbsNgg8N&R;q)Ub=sh@tIf&l_c^NSjw7hTw^u2=JCO>SbIp&+=L+noQPFZ=DNmMlu>oP@`ysXN zoaWtKnm}3enKnv8j@x0#6NGz6+fj&k!F5p$vExiv1by;J0}T69yhht3hrCZH*S9>9 zYqR>`XA;5+WEZ)@ilwr0nq1vZ;rBMoyxqR*xoY5J475{h=4 zzk@E$2N{?R>Rzy?n_)hn|8)jj7CRmhV1R%gn1F!j|6gXn+0@3&+0xw3)P&yD!_d~j z#`I?@j&OACH@hBn`$ZNBC@web1U7(R6ClSY8Dfkqf0vI!3^Q3oMtagJ=Sh+?GraA( zBm9z1q1#llDgaMQpWq`r5SCQs^mNUd36 zb_I_md=KJx#OeGd@Zow5$6ZiqlE47L*yo)w&Z^N#5~!7;shCm&kfmNR`TkK$nexVy zboTiB!$7e9sY1+_z>`ibFN%$0(d~-S4Rmg z2?TUB%yOfJ{2`yLT6M@Sa0M&by4{&!r^3Flf_jG2A?%Z9gNkxvqBiu^$C%Zif6~!E3@) z*7Y}gPsd|9eOBL|(p!l?k3nSgnFf{p&k%j1@z@9zEoa&5-NgTE{Dae(Z``ss5|RyHE$sqw9n?m2Iwlos{*>q(yhWP2XTpTe zE)Ky-!IHv)@WVvV*(8D;K889f*rzbkZk)Z8B%1kmH}@NG8{dwG52D63<$znh%3aFR z#GKXHsiN+RooB&5qAYkvW1jfuaAXJ}*D$ll*R(lkFqT*PTRM)W`T~2(Hjv$Xk}4yL2cBB zLBg)ZD*-aGu^tl=AVZ3nTc0fsEfWxzvB-}n0M>G?L-``h!w^~C|;*Yg=K z>^t39zx8FwP5Yn`)OD_N$sw1#^xSX`<{HEXFFOaJbO+*Wr{DW(g{_=($ocHzW)N7s zIg5DUA15*JeF?i|7R0+|8w#-Rs9jE0F80x%s`kWnuFN5Rsj7Ucm1zj#2_FDcb|4tu z%?7_Q$FhoUj?;j#B9)G##k9c7GCChrh#t$v_S-N4=6ih5jRP*qpFza{ewi!Ol?yUr zwP{N&CbQ>wl*SAr1O%n$2|$&>J63D7-_gtKm(B)Eymx)bEo`BocH zAM@)(xp#ytS(?BHbuY^|)V{g8qnP2Yg+n>mkX9e&FOwaIxi#z^J>X{%(H68y&2d=2 zQGf0{=iM)=6Rju!e_g@XaAa^`X9`^ z9S7@D)i5;H^^H+VA+0c8<}3Pjj@sQh!_oC@7~(lQd`odN%1OTLwCQ4&73bmhDk^)R zvbjCXa<#Kuy)M%!m-<95h!q;<*!Basw>_f^IV*1W>x}SGfI7fAiuC=96}Q3B`)spv ztS>7i?T(4tAL4Ia)W8IM#i~iIMj%b#caUGhO{_@OVb`O|$p+IL-B6g!(Nt>N8o0*M zimW~fA}sAH1NvRfk3hlZiGimv(Ud5%!ac{?CQK%hF$E!$B`OiV>6do@P|Ev>aqORe z><7QQua48g7276B6ewqeFsT+8EA}q7Rx`jWB61(C_o2)$A)!IFstHjlRKPHi9|3%`kqZO^bRY{!;H1ZrfFVYB+29TaSn;J*I9VjJbb%FcSz9TG z*kqpZE7jyS>ht`IIqez1zAUah^o@KmZS|mZQNB;uSg!%a&s?hdx^>5?zHdaB0p-8y*}()utDi zUUqeok=n5O4*O5^er!a_q52^LctER|R?v5Vld5fLN`Q*Kcao*nu}PqVy^Ooy$Y-So>czcWY9g ztCax2f&maGV^Q^JuhhESXs_~1DKr5;02)1vJaM%^et15%aZ`)w{NLbY+ao-!Rb`f8 zph=J@we&hYP@L-AN!K!EAuC~7%Pb;{MDIm*nT9u@EpXLL$Yb8<;elCWcKHCGVkv5K z{fs8|%F-3N&U^b(V3h}U%dYzm0j4L|WS!>&Qp9{tRr2e<5om1(${XeCA(pg`_4BLPXg2Z;%Q=Bm-x}F z1opQ~*y(`TtPkhs+FzOXGMD&FnG8B{AQXsN#4kMNAw=>g`{=9-zi@F#VBaHxRB$3+oBW8*T-4PW|<6c4x5mS6_g)$lIe)-5F zg(V*e`Uu-RkgQ7vX;TlH&;oar4q(bGXIsc&g)^SA{OdJcVzEksu_0ZJe`2%%TA*)8 zxa;f63)h2!;m!=#L**LKdl+|kdq1^|@hhg)_7KpDw!k2OZ z>8T7U$Y7ZJuF`@80pzO(iR3gdzrU}q4Nouy!R2a{@8y-Tc4K{A^V<&u#6trFVJ+5saBI>R9eVj4)Z13|uY)hy5FSc%>0jPX%CnIpFY(ZW>Gvmj36m-U-`}EW& zIJn`owWvN)IMe<9>5={jifH!BP;Pf;H=QudY<_~E=>CaRLQUMUCxFCW%D~(#(q8SK zRfFhap-zi|Xtp+N(@@VZ-E7KkU2n767n{&pY>Xh)vO=H&##dghFM;k?ja2cv=Ba8oa|+Z;8|<&mT_o#&48Zk9d1ui$Ru1StSjpVhG$FE>N) zwD?bWN@~outJa=Nt?Vd`G!-dS5PYWOzh&Yo=CPALp2vzSYaYO`t|fx*#8ot+O3?Hs zx7RHZBwuKExZqr-M_S3!aXJ`=i=eH}IoYyu1zMSXuvlOnf+-chG)qf<*@%+*T!=#BfF#K9!2b$nJ|wM>K+vDy<~?a$cO{fQCZ$S$XF_j{=q7AX zG2uGwFF|OZ+zCMalH&B~^~0dD1BKof-0(WYJF->fBy5ji%*4*}o;%n1&VGfrYSjt| zx>EMs$n0*gljqAuXL&XXzvTit>ILG3pEk8|b~KZ6FQA~w3~%V?4#keKyfOIkbS|=5 zn_CRWp}N~Bw5ut#^spXJc`$*#u}mjlR8P%m?%hwiG}<^j4`N*nGgSsHzt+aNM@~qi zsFL+tLG5gGD`kq!0CWgoq+B{Z|LpZTWkS^G-PwgAmP%@*6sB*t+1nYY+m;Qr#SlJ^ zUb`GEQwmJc1M)y;1Y?NQZs3?#+%XB?&jmN~ULIfx=ZCKmnq#*HHqr*EA53v|B$vA= z_~M-lb|!4IQD$l-iUwtzrdd;bCyRWHxH7-pW1b>1M|`seL0y~NZn>PRY|^whoMe>^ zhM~^yG=8dEqfR(hBH~gxHtbmJjDM0VG?Pqa`CoLz*49bk^hmoTeqW`sbVs~6#b=1O zkTw$4MBG$SNgnxOgB1;Y8@I6Bdv-@26O0!5J#nt)=d;H&zuj~?jP+X<;gW6#E(Ue4 znDcS1WKh${PjpE(omuN6x*#l@RNx9>wiPYkfhM zz*ggL^kw;q=}lM~#5wAALU9R89rSx%tD;kARRngkuT|x*lq}MS|0pT)5Lr#gj14T| z*E*+o$$qXv)bHjITIaGlhD@rO%!`Gq?I&cS5kfb*=15<^3lOR1TaM5s)+;1$QX=^r zoYI?j30|gZ;Qfu<-Ao;MtPNtQt7bbn^y3^LGmh9G9yH7&o750(H@l1!;REM|J z7c*5(d4Z3*q&=m!VdAnUW-XZiF9-YDI2VEXOxi%adA^xxD7_CGv1)q;=JS@d)i1{9 ziF?%7rWGnVFVIKw0A=0g-|)D;U+dTQG6Z8xwad$O_C-7G9V|BBOllp|HKmZ7L^j5iCaqjjYJw5+E zI1Qs$idAZl5jl}{)XUS+=xfEAHb2LqDGdT$%*)s8zR49^Ah1`p$tHz@y-DU*MV+MJ zmptNjI_KJK(fTCY?mk&BL|@$-%A@B-XsTA-I8myxzrS;TIR7z|(KVKm7hG#H=VLut znfwb@@5X5Qqx_mmiz={3rLEUi(V!pz3z-p#D|r^GF51QNr3VQcXn9VH6M0*;1;oQx zoAc;0-R<#d){Lh#OHSh&K9thl3$rdcR;jlu19ds!WqTR7C<%3PPt1$hjqm?f!nBm8 z4(a%*fZo9Y0pa}5ILF@7cmIjDb{{kdeS80lC-WuKAdE-gk`w=NN{F4KB8Q_ z224G8P)IBUXUHUJ;sI0c9#yj7nm(r3BikNDU;z{p4d2&xsmnmtWe_EBbYOuMXT8VzH$rTm&*)`=b6zY=P!lo5N`wiX%WE#uQ#!eV!Y zvNad@Xg9W!j@{YEvf8M2M?QsoPVe}IxQzd_+q0P9*lTUQ)Fbxh zoR?g+J~v<>8w6je8rX)TP|0}NVxwVP2`r#U` z{VewX1=l;6dHgK*Z4F!HeMzLRnfi<&#zAGYC9R{CYEiIsE%d0ptG7s;q?sw~q^)VN z^zN>2KYrr+3?Y}^jj99GNtb87*+qVS)^*aX^3A$+;y@;ZS!-{To-XW4WhW2GFH=dfpaL3{eaz(`lv`rxG`#-T#!Gwz=eMoRmd^5~B=#u_UlpOb6{pf%6($q>980dz zXB6cVd`O^(R>o}ogv95P?Z238Tjk33tjpuqGsos-9&X;=fpEmfF^J$>lYvwa!?yXt z#?F#v?n(p*cs$3>Kt>tW%6BQ&L8OjAD!n!13TJLpU=z!_Mud4bKDCFN0OAyj=Ln{m zqrzaBm|9RHE&_DvPs~mB^H?^Zv`PAovWA^KTxfD)XYw76wlZAhw5j|gbNiFEf+L7t z5$2UT1!lwtX>MD`o6JNZXy;F*ty@)n+k=0I`$%j%yld1BgY=JCkbdYVZ_I7l z4T!o+Ybba27@h%IlMaW2*35AbXmr)Epf$Fd+w|W`$Ka3OsOkK9%skw=JI?bG6D45465;+}(= zen1^DjAnnBY4lauJqatt1mCuZxyjtS@Fh!Iz%4{aIpf0uqd9#8RUv*hSf*FRSM`76 zsCmUz)V13xN8tf#?@?JE2dWJPQBfje-y*p{DK}a@mBDnwe*>UPq|X&w2+n`WP%{$8 z22SiN2&E?enNW)GMzuVzR|5T=B)xXBL>H#F0X|1cg8Ue;_1NC5=)2h^j6ii`G-a6FR~&#I0V?_2Z2 zrdmlHmCNZn+(laLW|zr_-C@Ny-)sIsBnfS!P{DXML1(G+XoDGr(boyW1wueTI=pEY}EJVVVI6 z)4`N(ain=5zq>tc;Vjl&giMhF4s8RU4bn@FjRY%ojeKz}R62Da6D+ooNiAB_5;+8% z;-GDTiBd3fQKVlQyBH#ySYyc-Laa*ZZuB>){q4QrDJ#_ zBDx;1`QaDtpq*26J+DPed$is!B0Bi6s*VBn(M=lHCzf+MS=ig-2CaE0YD8;5EoYu4T zYy_fz?yy*JXk#ObH4+%atJPG#^MM^3bS7Pn5*ps2q%;x&RN8>U!1PR4Ft**iJ9XNW zG`qm9POK3Y`z$?MwQTR!5dR(4h|)M6E0ur9*jYBhzm%63JA!@QE}C#fK$MreuKx3k zRCA`H*}yX^kx{jTZ669_)tyx>V*FH7#jL)NxTIO>i9J3H5b}{O|!D)0~kB^krygPB})e^4( z&Z3xia+`zQylVB4`suX_>b1h>&|ClhsH;HOy7=QZZhua~zLNO1=^>c(Qmyd(()yRZ zmc1?cj`z{)0Dqw72xWl#0TF{T{)#UM@9wEE^b!x{D_7TDs=nRXm4rh#|NSFL!jFsy z#euCGQQ#L%W;b5gRARyqMTO$8kU^uy1 zn|l5PMyaZt^S(Gz?>%kT3Z`P{t5Z*E!lPQt$dl472>6}ds~EhJOqSTRg}JzaD$O@P ze?Z7UKrTF`<=f*TV0C*K{DozY*NI7{IE3RGFA) zWI62%#rhAIAv7#x7G0T}{Mj^%mS7Yj#}B{ja^}&reY-b^x(dqqWoR135t^{YFv=tC zMf-Irh2~A8n+S#R7)Qxeum#8q4xPjTI}2TTEm+5Fj};gOv@Ul4ewxSuR2GcB(m?nz zL=6_-ltc)eLY%1r1IMJpuai(jya&Sv=Q1}ZP29} z=!)!?McWHBt%8kzKy|Q}+=`&qgpv!0VdX`mtc1aA>|FPjERb8IjCnc;558x% zSORhDAF7WtC93-=|EIKu72Ge!NL-3ONmTif5=CZ1CnYg3eLKp}*_3~NIDBwDltkfI z5IZ++JfYq5U^-acsP=RIJ`R*jemOA`i<=a``)jC5K3Mfat=lIPfZz`zca}cHF6T?= zpoz-bl(KyeWfn#)mKHu936LOVN+MR!QQXXlp>acqN-P5?0ttWbZ%2jZ)hxvlZWXOf*;=XPbZ9u+jU*lyJy_xU>)+1f7)~ z1~Sa?WnlNe`Bw!uM)&D9T>6^!SF{|qL0lZ)6g;)Cr5bn>95`ofp&1{y0N@iUd$}~74l3agEuvch9~mdsb&3(yruB{$BB>2CjGIq7;dBnZ4`3q>^L*u)%nwc>#cn22V(p z$Mki30B>P0pYL#+_Mv!!>ygmDjI#8I^&9QNR2%mZ1Kz#)$+J+%9b|pLQ)R0=ZMK77 z{frJ?YNE&4{#aU7^hTrm8R9k$?`##?S*P~Fe#uh_Y+RpV zE^X~8)Td7U$4T=M1iZ0cZ4~O~1G$r!>4Vwk(q968WcQLl0#&X9Qg<^LjujvZa}IxP z_Qki<==PFcY{)Aj6g|*F8Vz=|kd2o}hoh$e@x#&o&^3c@h$!?cHb8798I=?Rz1nW# zKl_D=dh~hCD@L?fr9=whZK1wg!GRJ3tMw`av{4|D8>uNl@F-FZswII9qkdoIKnB?` z5vach)stKW5Lv;C1+Gb=bW9GPoUy*b;ekCePo%~V@-gFffQigE`H`;fC8VkqgA=qu zPA!4nVHN0AiC!nuLq+;esFv}nvQ@+$?lp*FDZH!-N9@#AE`0T|>{8MaC4zC@y;lHx zPF|w|+Sh-0EN7f|-JKd4%7D$?L`>LVm9-&x)Xfk9(b3c50-e!QWSSQiy%;}H=zG6A zw#Gi>>A8cEthjWUJ1%XcBit!fP69G$?S_Fg}actPFj+AS#02fS;3~5}y-9Tx|AH2r2{;PxlzDONTb^ z?^jn>1IJ0Xg-*WY!}-2l=g!=^+lN~1`}Gm+8TyJz1(qYnQN-GYcG5;(ZVY*vCoG;! zzu4Nw2n8N^@U3HGtEOsB5vOZtgWz8U;t9uKow@1P;EXRAXkG{6hS?*S_5}}x#7=hg z^%{0dk|JSq<%>v+S4?3-NGRd~(9g2;jZz)%A$VW{zx5RGYC&WA^U^4?KY`;$IV3VD zj$M;)q1E>SD=25wzCpC*@b;oQFR0RM3z!GElhAZu=SEsW{3QnBL$egkub8`d#Fdu3 z8<(@UCE)>;7%v5TwUgK8)+uCy#a@9bpe6d!-tf3IkigC+W79d9ReBc&&Co9?1kx+g zRQu8+*C}_PxVU1y>ToA+5conS7TwH?aff;H`@(J!F~B6^j4Dh)vo> z)OaH=K7#~qS~{P)ky>|A2wGBv$EA#qo}%Z#Q#0_{P!cC%riUVj@boYBH;aMwN~4uY zD`3zf3{uh7!;>VxNPj}XwJ46W)1t4gU1pR~4ex^$f0t%5gjU@-2XBRPv&e^7IhmjM zXMfP5&7IL5{sB&ROmGh~bb*w-v}^6Nt5^7H#$4|IW3sJwaK0+J^xz>g`N7wQMm@W4 z+Zxd76i&ZraXPEluTHnap!w;ViP-M zmCS7H!k$MrDgO~Q{tdesbNKY2pA|bTRPz-uah{s4G45npG@w$JU*kOn;P3c&b_Y@q zJd8i`PC0V_sb_ukOCw=ep1YSD|Csp2YG1HXI$e>Z!QFFcdv6=idlffZ0)f1HMr`P>p1pQx)AC2_ zE^_D43hLV(?e&Hx1mafxu$51ia3`%JlHRqVS!{&h_PPG4o5nzkXcJ-U57qNayaW;S zaUwOT2+y4JXwB+dLnZOZyX`IpLg~q(#T|3rdbgMJ(W&H0OdzL~t5BZu1;yoDl}s&P znb{^R9H`$?wy>CZmi6{)HHc3?DBEdUjqY-pnY=Zl6aPkQGABZSH45Le1bO<>q7Ak#FU)a}KEQ^D4TrR1N5gn&X4AH1f<^X3z;DC* zMukynSapA3WEFr)MifkqlEfYmELTUHO>a~BzAK?pl&o2=tdU-w=kckK?4Q=(VgEBP zb@PqW@RO#p_4~<3bAB4<|D2cV>pOTF8~)Iy_4WUgHErzR>0)7TN6*U4@&n9`dH+sL zjRJwCf7eMv%*SIl1kBP28S#jc2U8U9N{5k9Bp^wCq`N9vn#o7`DM_0-M(W9%=~;%_ zg@$VOWJdZ~d3E^-n|k>e7H0fm&X8XfjHx_n0$P-muxN|C^ zv^Z6|4L#m5ScegWn^n-Nuw@JL$Pq0dVh^e#nvcg8E@54LSWPy*imNKPsv$_t(kCV# zenu2X(X)(XSL8;hu(!SWOEf3P`-mFl0h%wMCS8)a>Y(V zI&@&&ktx!HL21&+%(X^n_>Eh1+D4#QUK{(GhhwI)GmPo1;5Ol^Hai1a7)Ppj46%BI z?O@`_4e+kXHyBF9@M-Yd6hVdAP^iM3#5Bn52r?t@8thS2cADVP6F8~qO-tw`c(y?| z7lBYyX?7c8VicPQwXw+3PapmM8W zbCCq8Lxjuw#uqfzWIVvWE0`ilK_vIXS-1Ue%=#LJ-eFO6n0J+9Q zTE`45w_R-hw{Ej*;eH4U-&xP_Y+K89=b*ZV>^qyLmTvV`9ml2OdfhSl7}$ENYp-6* zd+0IDF3O}&+rQ~=`y2A~{Z`#c{ZPnfE6M9xYGyx{3|95SgsP4Pz_Nb}>2@mN z_Nc1cai&amv7AvU$6?t=G`l*kAQSol-K#OCzWukLFI{%UQpN-VT44qP694}hA^!u_ z{=bFFww9#>-neTXzxk8i7H3BXa8UF+2~+|Y1`$*cq+|dZH%X`71zZ65bAUMn8Bo$n zQj(lVlIVam36qo2fHWPG(w~%Ent6Yvx8x{YsVXyWrG^X`8mCgc?AJt)UsH8X+^IcR z{0$wF6Ln;&z|8i2exGlTfYth4K;XkOMRHyM;hEs~!sirD(2F|BKT7g9KMYy&h<50( z0%-9;s$Q7X=vkx_Kcz7H*H4K1yy#BeDZaZcHPz159pEBenxgZd4<<15R#OBf0}mZk!{^ z9vlzofyqFo3&j}q4xK(nZmc8C9v%Q3&Yr1!2Ky-3@gyd;wzRp$d2gsAQx;y zY@1;Yd`AGo02ibKv2KJT!yZW&)B)eXs0;JhYkl$=RlGDfra!ZWUPzSst({8K-#{qUb$Ps?;^T9918n}I-7Hbt) zK!|XlgTMiIJIoPu&#eppKzso5EEtOuSU`+$r~~`P@QrDVK7jWWA?6wMs)U!>4*a43 zABzxZ>zBLX7DPw()mR7q4e}e*7$orX96uHzh);?@)UBa?{WX>^+yTOXuM6Q=Zb1Cv zII|m&fN=gm2ked18`;?Qj&h%07vTZv&SD?y19pse4``pT*Kc7eXthba&w^*)UxyL% z=IhPI{=aNq4da-7r*9GEd+MWF&_@uQC^MC1zah)@socY`Ukq-1k6=q?SWeuCA5o?~ z{V<*N3{G_|yfZsHU@K*&k8~xu2d(wjLsxaC=O}DF%*!fz!}xx0Y2$IzSd4d4%NNk73Ir%80Gb2!iz>DZH`-}+{k zFg9SW-5dM&x`In4m0=5hvhZR4>jaxMP58{NZEB9vPAB5&Z}MeCjqt2aEYPj7zOZ+H zT3y?cpkd^2~COldfzFx+E)Z0wmVLq*n^EUDXz|~MP0KnG;JbZ3S1nuFH411?d_GNi{ z&(ejvUOO>jMok%;Ff*y~$o*DkWsO6M3NAxeUNQlaS)GAre=q5b`@$ZYFgf z{MuBtZORz-3I3_Y4LDCqB5_9I&fnSqT?gJlFqiu*)`Wd!Yp?oXuTT5LmXaEWr4C1V z5YG18;RwSbt$z2pK554nG55>ue)ng53&#Jg{<;qCL3pC>IAhBre2=f~3YK0uREWEp zAI2_36>B7nCXFLjg1@_X-xXybE!_lHyq<8Rv6lWP(~iD znueu9-&r8=-i>$VkM*rtsZMJ{YpG=OH!jN`&xjM$yr^rtI+IP{7_;sr`a*ZRp|3b$ z=6B)FIpgJ_Ucf(D$y)0m)HZ|Wd6_|2;AiJH{8d+5z&)@HO?7-4bmtlz?TmY|o^A zE7Lbtluew@UZeQrpjBnreW2YVvx3?W7K}I(KS}z6h^Yx(>xN^Ne@DMCJ>E&1c7-%~ zVr{O0Ul9!7M<9UY`@Hy@0XLo9(j1zg+LtQlGpP~#PQ>FaF#UzGhjy$o>C5KF`_}^?p42-OrtO)%cJ98@Sq!OI9Ij9_aL@ zkaE8NLR<6!V|O}$qbc3qmtXBM(^IVQj;1KJ9fziVKP)J7G<7wMR#vJ7hD%Qc++b_# zz6xmRT1%%NJqF~YVpV^yb_>j&o#i>Ot-1c2fVVA|^S4i_Jw85N=sLu_rs!kTYw};x z{^`zP^ozf_W>NWWDbZbysXTvrl98HsA>?7-#X(LK)_H}#f70H~1YQPV058$imEGbwi2-vD*Hy zc>t%9tEST@-&cRPu}deUPdPmOPHpyoyeiU%2(7&%Y1&KPo4ZA8_5vqQO)ZI>M~oy^ z(0Yz?l|&3e+QQRTyC3aR%i#MshdAJUqxz(66Bauk8xYu|>_z*7E!%^Q41ukU$qc(4 z>8-al?rSsN;vaOzy!y);&AZ2D+=Hwaj=?Ek&`kv*#M!(ZiO?^7OjW}OD4j=|IO z9UWtwD=GsGw}Hd9r%gW<8Abyb1`k9v!gt^R=sI&mlw3%NFF;l6Qt?3c^o&e_&>>61 z1ofCZRS4f`IBRFf5MVP0gu6Y7?r!t=3kjJHL?)gxvSdjtDD!{|=Y zw_N3B5`h?(IG0wq09ICj9M@R6In5U1a_6nlP9M;kxklc`ne9k!H_DaP=6|uRuDpPN znQg2*swcc*)X{{0N6gG<->ZB>R0r6Go`%}9SMnT9So|X!s!*P5JQhc_?L0lCR951? zxNb{5408ZN1RzRNZ(-$dphjLc;)Y=511hBj4kn(DY3_9QpwScd736~w_Y1V9m6_T> zD~B3NU<)%cHb{+`gS)((8~A_oT~|_pkN0^%Jkf6+ptNU(pLCE-i4AR!0o5}^`o!vw z3UHqMb`7P3woqhZ2=L6d9C7E`f{f=CB*U3?$)FeNltldnW-Hh$02Ds;bp}Mnfyum^ zn_@cDRCYug0o_W_&HQRCK|2i>#_8Z(P~6_aI0|?eAAsRNF;#j_Ft}Jwfol%GbDcL( ztyxa@dCtu{k$GI#ZyMGZ1vu{2>^Da~%sK zM}QvS?+KU5f%~M6zp*TSKqq2hN835(IzTez%WmD9 zv#0QVY$1~BNEzsh&^cfxmt+NT5%ylgPLl&jQ2uNqYg1(}m z%~muZf3TL&br|T-{kzslOFdp!(v^oN4okm90~6Hn4w%{sot_CJm*%FC)P(17G1!Q# z1YKm%gb%{ca5FmRqNeR6);!qXCYWs*i}Nc>=IQqKQrLK#eaHh~U9>&A>U-Sj->~M! zwV{7l+(UA>6!99`Box0-8(b&qK|a_fDC~(sM5ZIG)4|Qc0J-R z$B#QRxx>&o(4yG2oW^sUHhtJ+2z>)GFZHMRB!mPk)+we3JmCek?*FzHR_%H}xTo+X44)5m z*E{OBO@ogS{r_R?9HKi5+btj4wr$(CZQC{~b|v}8wr$%^#ZJYx)pfgj(D$44ot?=U zoVCuop7rki?BCWa53F-N6^k9OiX&Z9Sch;&{gvk6s6r$Ak{@*Ep>H5 zvx@?rj@xlE&a0J>iO6Q@eJbpKDvBH*B<)o6uwNL=5mv1Lr}}9;rm1{t3^x62xIeuM zw#_{yP4i+u$o$(897#wC9_W}>E$Zke?v)h%ilzn$JUB*+OGKS-B_gfaI%qyd+TLBh zk3zwvq0z}}mc-2+wAfQ^d0G7Zygr!G zM}IL}JTn7vC{r!f+ZyNkhEOpU92C#9x7k~2fI6CY-k0h=2bxocD)(~gLg>Q$jp~2u zm~`ZWoy86-taA|8oGs`9IJdc#77 z3akDCkB3|jo8*WWn-kg-wH74mQoI-?(LZtxxk98EClN=?2Dw7Cm>>~C3F9 zL+qJ!F?t+WYy-(!@{oum0*N~ou2Bp=T7SI*VQD!k5;T!PtTWN@Qwtpv*%d{D4 z2vdZNNIFD0{oqBE`~qG>*BOWV?q}CVj2hrZo4}B1pDR4vciWc}?&N#5g|7tv)({Oe zMe;h7_j^feNd$n!2Y)GyqneM1@p|eb6i<-)61x#RuAIK#$SjY<9~m)uMddt!YeG7?2}Wqdcn@ z!W=Nr)jOt8jm&UC8^#ANJpt~tU>mS!MxJJ}Q z&UWIvQAD`6@WO>Ee|VgN$X@cgY2Sg^^U7JmU*K@U6;Dr@Swv0)#xL`I}_lX1}_^D`HhytpQGya+dkW>t+wwhW z%*oCdCQ5G^vtYu7oRAYx1+l{NTH-t0ZWl^YDSd^k+veBc79bD5ifIi<(K5eeC+peY z>|NshWZUdo!m3-t;3jk$>}n9Ss|W|LDvxT9S80BK$T@asa;s98YD&HL2ur3f?c#Ux zx0uYLHQA86=148qq}#(Th?6?SvmgnyeH6@%AZbRX7F|g>x5@(4tD4PO*9?WA0!oX8ct#Bi)3~`fW59-6GMrLa(bUvXV+z5=I?jMF*!W z4(m@`d2H+u$@3~#Vz(?n3XnduG<#N!W1;3;`%sfjPdZi^@@~DEe>wAH_?9V|oDWxR z9E3>ECo`kcuk^Aj|IrgarOPX-wG5@n05d84wXOIR+Cfu2kB^ue0@IhMu?dnWlFe9A=c6{RU^~nAmf!N=4BaF>)|BAWTnDxiiTaO>=RQT z)JwTnXN>#Z@>>J%CPUcF$Y~|@)&I@}lxc_*VLp+|#w#wf8!tMY9A9P80-oggtKD(| zkFEI!tTKUZ-lIrM)u(@sM==&t)l}XEq#Y5HXp8N|bI@0`kMD>1msM2aq?W_oS2`+3 z_z_hFCtsOFY1SCY+oG3%>)9c%dcyTotznEK*a0?i^S^DlN+p^H?CV;?mJf4G+Y-X) zj%d{k)&MM?(sVYz7jeW*Ro4JKCx&hUc%P?#5gBC2b5uG-**t<5`Nx5IK&iH2&)GHZ z(?TUg`Poqbch@B!{zf*}0t;4Y29AKJ1sN^gsZhCcoN zdPp=k?_^cHW|s)(8js$_aqa^40*Flw_iOWp?B7^2jWSGu$aW*I+c$9YLv(}pKK7QCR;(9QyD+hzg1 z%HMKD(^BH+iz`y}DrMl9y3+uZdp`jeyN9Y7W3Q*}>#RSunrVeU%($C)R% zJHN1Dj7s45w9OOuPr{2cN;!4r*{rQH-O?ehiaDY_L5>SohyvW8H>SiY`Of1m(QRix z!yr2&MhH{Z3GG8+BtP5gm`Fa{^bh#P9sFL8Te7n7&ZANe0n7M7k^oPm4FZ{ zJFqNW&-)085W5vJaS_C5RP%1vuV;5;mE!1mEenH=K>J|dlTNb=z1AiE)+OoG@CZ=E z2(hVe#ibhB*21vbm-pUH%5@v15eaAU(lGX8ZWn{ERB5%72zKg%W`Eg};${ftZ75@I zU5d)4A)G&#!XHBiW!TK3P&NMKuK3yM0`Zhlw(} z!+&Y{u024H}VJ;;0cD2IeD~tAK!m=fS(Mmmtc$&ff6ndE((fqn{^bwI7`>5 zExshz5+Z)J)2g}X6wV6KS=bO`>?0+r(O^j^({M>oh^cIBmO5sL*-q5#L&7626J`V$ z`3%dZ#OK?D%|~t&mJ}*R(FC<^dBzw%O?L{CH#$oWB%S9)`u7Eh!?T}K(`fa5bz@EL z)jh0krllaI^1TDTAoZWDYGv#caB7$YG}IF`D!xk!{!v-pF*`CBRPHg=GZ!%K`+)zu zSJw^Q`D+UOU(31x5D?4%K(_xEWd09{zDL*A4tF($pf`ds%175x=3ewslu?(l&qO;Z zufa^W@nHU%g^fE&TN;hO7?0M0L;k$fdP|#32pD6{b?z47CN{ja*K&|7FYd20!r9ZG-DI z`7q9rcu= z4%(gd)S?dC9rhHX4&0r;0et(srSKn&^0=kI0do7grRW-Hd*40(I@?VF-3wNU z5vXTgH`0grl%WpR9rILthh!;6i18Vt$NB2 zjhIuU{GsWlE@Xb_Q|i(!{>WSiH?jJOQv)$cVE!SkR>Xy-Ab}A;7vX|ykibm7BGEbc zBj3;=uoxrIJA3JtfY=X+5~1p#uvi}O(j4Dr!l}3r=r02aNF;a=8m1a7F*mq*=h9Ho zNCObI;Uy2Og*s540e})@(cp4uo5#_cK-@vJfNd5rL!{7~5pNA_)$tTez?y zkq`L1WyVH0QV+~2o$4l2;T*6}-2Mw$;V#glHw1s!sUgTa$k33rSReFhj9dq4WG~oh z8`hKrau333TZZKv(K}22u5m7Z%qeY9K$>u#$fLTDBhx9jSRdr6A@k{OOOU^<;c#gHV1qh0{0ihJg&FHz0Ahh%hHQHVlzz0lY})j+a;D{rgy? zQBHd`LV^(9KaohHVF_6>lZ3Z#rpzi{z`>;j-%fUJ93*Ch zFyW(NRBVp@gb%1yK)-Z`*CTPdNaPeG&0@QKym)XfPOW#BuP)BOtx$yyfTDOkTRdNR zHa2+uQCa80EW`Rs=E2s;-$4_jsJ)ynG6^2#L#}S!9z^p3G#(}WPzRT#d@1=Z48AS_ zfqs*7E49X@_t-kls=bgY?kFCmp;k+R3~)qgLEOMG;iE$=`YAxeAo%)PM1WO^$q*6V zuKS<0^&Mcr)JOSv#h8M%66%t&M_7^$=pfrcdvI4?l1Sp8!};kYalb{b!#t7%K>T$6 zP)>FS7d8wYrbR;dSScvpgJfDyz}cQuDVdW2zM1U1c8+9T>wpv&ZREndo0rwV?_>X7 zCbWx(5_^H&B{wIFq*H{voBP;*wxN0OM-tkz`szr=yjlH|2@_gei(L31tEQ*k)!DuK zl@0pnm@a8k@j}hFRXaaUJDJ>DQO#Z}eWJEAX?JX4OuATO`-R@*c#JM}uf^uo7k3>Y z3TNUAznGEqW~5bt38!-FlwfOl74xFa2HG;1yN?1BokbwtAIK*s$LZ~avz}7zkg;?I zUYxjDC=)}j@n@9{JQ1#|$bY%5F**kd4Ljn(j7SKQEuuw>D0+9%jq)|bpTZ}pjXtfI zW6$l&=m&LbTk*n1dhK=O6VKXoIPhakMCqH>ng|8TJN$Lb7U-*D6D4V@6CavFg0ArQ z&I+!|>N;Khys#BdBY1qMA35P6la*0OiAv*$OiOtJ*UrOcBq(taBmLQ7H*Kk=G}$7Y zE957oJrKA*^J-=;Rs)6C_Z%JG3M3vjY`tmGsKv&*jXBo}Y#x-jz6YPzfEIy;`8R{P z2>+WKZ5uAUlLRWBK2FEHb~QY2_$HqOVtpdW?_2|`7|9=SXX!hl$O7=9N?_}Uk+TdZ zrb>>^glOeg%$EEGYD;YP${Xico`qFJiNppP0t$(!FPv)p!oPUbDL6_Z@LYgZR|UZh zSGZwcmt0gf0VewMBg*OFy7WCGAj3wlS2yrKp~5_npaa$s{X5!3QVK!8a#=g`F<+1~ z-_E4qel-QM*oU9N1`ym=-`69qen>{+vfcr>@PBt(BZ zv9ZvPD!hxQCz>$%mc%s4fv84`;DBp^iBu51Qyx_)7iy3lP}`?xq~qM*+Av7l4oi?o z9iqJXh2|ELNI`8UlQ(lH?`BpzCOqI%7v6j}m)~m)&77-gn~$0~ZEA3ZEcXt&whnrK z$XjOD={oDUOy^EcT@H+yVGL!in@Xom$bsOpB%UQvk@58B4D!mFR7F`pE!_^8vM|T{ zHRqI=WX`W-meerpUn_K$q(i&|gWJPvZ^#|vsar0qO>_xEt_Y#KIx2l5Fj!`?`^cTi z$QKd?UZ5wSf=WbUiQ{O@9Cu%e=)ieZq?5yWaq*y3-7Jm1(I;(^UGN_GX-?`0ZHM9q z8Mtr4H(y;xwED}Pu$xG26SdX!9K7xsTVQE_u~FmSr?W|hYs4@p-H});YqyG*MBUF! z+#hq@XfvO>t{5um8Vc3?NWx!)_)Iid(}Ib|!k}_b*7SWu78TfCeU*67Wo~qnJL$r8AA}_Qij6FiRr!K361sfJtetI%)R)88Yj|t1pTcdJa;zDA> z#e-y9Ay@+ceOt{I0RPsxE;-^U#mUIBo$azxtjCL*Lz$E_M6Ic4@^W@~tvoJ%4r_GC z153PzZYS5l7iABbw28n1)6D|7qqNVT}89+}RmTmhXyIuKiSM@nD za@h>37CH;*wEI`+V&4jdMbnU~%oNX+b_@V#SB{JXSiQha)sAKB^5XTQa2Yo+(CE<0bGW|Bp2wW7C- z)y+qtlETg6vS6h}2H1niC}!pwiSdONowa^*_Bw>ce~)@Xro}#_xcMY3`D;I@$M|x} zEoGaCMj*JKAZ^Pv6Z}Poe&ZY+ z{8ioA`ZU|KtmM1fP6dT?A$tiL7u7~jn9-!>*UyaV7(a~;df?Y&^M}xP?x1-Ej$9#e zviNtSjatEgH|PXA&3BZk1bS$Kz4U54*Y02_oQImeDyN(xu11L!)+XI4{9nYQO!-Kg z$AK0<2|>k?v0*D*oUe)|=CduSD_;ZqJihZfTO`6I)`JbA1Yd79QJuVx?QHy)(){c! zus28z3u$vw<=`_p-Yk4!%gpW-?TE1`)W}IjV-RG@{R+m&gfjaMEh0yIC{5Djbg&rK z>}Insj1J<8NGuAHAP1vA+1gAfM%n!X$H@~*CU4+0-x>9MgA=aPHB+35Lh16_Qz+>6 zj)p!j_j8n~Gzg<_0ok57N8~lKI*W}Dh^IXhrToqTH_=>G;EQsDA7gK6}GFpYW5@At=GZQ39k!4DTT=y##BL}kL{La0a4dp)C z+b-N9%}#HSDECGt+ZY?te?@}}bCm72G)L_VZqp?$3`*@~vPLP&zL=ebiu~(TgayDN zHJlc;syg>SjtkU;_x;wlQnFf{E&1)|s?MMJZ0aT^?3VEOFBSrWX*+IQ-&!*biv9{l zxrlw=wRDSp8;a7KVm0ag`6n@!anDG_lpg6f%&6WO;-OmS?D~I?!aDZ~jM$k{kz93m z>R;r1og>In660UxgiONYT6+rXDAmFAdUF~F2!V4aMW&i<64zy}yPqH~wPLDlwiMNm zcB;k~fz-7((Ojb;bDELV1R5Eca8R!ju<#!1!@j95kz`6@2LDB+2o zXJ=mYp>)jAnytu0tqFlm;NI802S|+&LXR9|+blz_>u5F>P#)RHx@F6GWGi&=pl(|x zZ7VWQUJn~My4-+AHkt`rg`;N$Q_^zeRXRacvz z#hR3~>omf>84Vq`$_gdM%+i;oE$PYMzI~|1?QOGfA)BFJnb94{fdw^IP0OCZI=Y$F zN=0x`?sH658O-KmlQl;4tcD&ZXjC(fC;n~AITF~3rH!Wy{soa74bo>NN?Kz~Qb_Ku z6mod{^2@n}K*yD)gmYc|O(hMmA7s)>b7~{s#-~pE*g5`~LBhCM-lFVkCELT!sYMUz zKnKa~zyry}1~CAJ_F^QXb@FT;&%|YLEXNM$MytM*<04TQ3k7;?mCFJ|MR7t2R?U&` z{>FYKK-RHdBSF7X+D&7Fr|ok#;Dy&!YqRdr7G-`w4t*SvM-wqa_=6?)^!9!yPt)lp zH!38G`>0yWxTCh&^Sp16|7u5CYP^^A2KR}L4!O2qkmtss)D=+LD!Ze1IlqUhNM}9= zg<~Y0lojsw+c1e)z5fz65qex~O`YJmdXi~jzFRiYvmA?PrhMZ5GSjic^Ncd>MzNc> zUTq8W1d+yrYDbxKS^~E;rI3Gi!eqWs2O~K&ehj7BTVZ}&Wc|3IEGOg=Q?*@40aK3l z)Kn20R&0dipWvI@km$G?>yj-a%CCy2hY#@P9orOG;1GWnFh9R$ZMO5kG95MWbL;pdJjyaC@6Z(0;xXe+p`?g|EHN42c{G@L}pcXvxtTK z@-UMjzSbqJS$&i(Nh=$|2`Z3`_2hRC6t9-8mA}Y{I!p67^lfD_kLTht)=8GiN~M^* zWurv&=TQ{=3~bs?XE)c~e*a>4k@Gi}1M-#h%1z9}$Aqyamv%2U$=~SYxv)OPrLWd! zC6l?k3Upq9iPe5`n%ZOt9d2q5wXJp`d!5M(*}p$cjK(6yPiu|6LuRN9DVt)>88-=3 z6nqq4>g`E~CIIfMPhe6y1^W&>2WrBSzM{YAB4>%}0-)N$o{btjouK}jjjgn^p2K6mGVKyA*KhLmaGV6Y+Z1gSww znQl<%Mus!(&i<`nvR0$7&!WOep9`CzZ`7a9NJv)ov`c+)mZ%Xiqj1BSg$5fhs+T;~ zJI~OMGH&m$EEf%nHu`aj(fE&=`;zBpSI=Hx2kqo9wlSl1<0+?9nGYDt1|AK)>>9J) zpXZLQZ{CwM8E4S8;na(Qxd!Q-=8+Rv1xUYMHo^>em_k9ERJsvIy75(C671K-XE=%@ zGL{&!FVriS7^$05+TtJG`N7ZKU#VwGOf>cfXp?&A6L9JwytSCw1dXgW96S7?uF+eh{&K>BtZ0g^z^7`f{QtM{+wU;z>#d1rqyJ3A1`?^x+R z$&mWyCjG5L#{6ID!3h9=v8f624y4)sJsA@bWbgAJCHOg|5puq|espt@F&pg@kX}3Q zJ78Wv?P-l%L1n}5Bw6{4 z28>GlKpFIz%%8daT&{udpJf0iYvql`)}X9G1jk z_V1H8PTka9v&LD~v2)X$E*(#+I&$sOGRR_nL-U@_1n%wq5Or`nwyvM~nuC8bXN2SEdr!>}B%*5L<)FTk%Xt zkdlp*zp^J~8`X7R)mBfk$Zj~i2f7u-bF8uIn{`gs+IwW2$=&cb*2&XRaKQ{e*h<|$m3cm|C0>0u-Hld;e3ejZl%qp@^GR8!olq)6AK9&!#a-V=- zP_H3-MP6ZU)dw|$A)yd~5K#!!AkiJcB@NKvh1o(2jU&UA*+3Scq9urN!eC5`2h(|0 zoDzzx@Cu=C<&fscZpn-Z>l5k;*A%^AyrOT}ZrS#+_pk?>`kO*ef=-|eL3<^*fbfX; z5cw3nfW3m9khrA0ki8Ob-S;j+(}L2V7(p3P4S)@ZyJ5OzxWMp8{6JqJ_L-d0oxr$6 zUMX(n2W|UpLyP*A2QT_BLOFvtp>{!f#l29yl9}M;P~$stH?a1(oU)lfr;#jzwFmK- zB-0rml=tBgigMo%clKW@?XGETc9wE&D-E#Ctb>oOYq7~Pg&VPpX$&Zh{-O(tHn~M7 z{DB*S_RF+5HCuWZ1s&Oa)FjD#ZvuQ}^C`J=XVY7tJ1GeQz{Mw~O9SHlfk)+d(35=0n9?_sT{_oY#6G*PrCq zkhdZ_fn%neu+o;thp?Ko6PeD}h?6L%)R1zMg@)TqH0f+J(WzO9RHP^g+6VKpW1sT6QnS`{z(4A1Hi*?EU&xpxEvE7qN=W2{~7v>*? zZiMTyxb$1;YmMsdRrkxQ(ppnTPWz!vNx8y@Twx8rV?B(B%7K*#A|8$0CMV+w8Xt9d z`5lJC1rI5m9kzU=#W)ndqI}P;r;NW_>@>Yty(2HO{_jbiw6=pOm~LtnW(4SNZtez^ zcdSnsf#5x|1gXu{D75-(Nivbp0;Lu#4JO!%q~Q*BqrVfDvdxBJGEL@3 z?s;!=^n|I+m**bo)gS{Ou@vVy=XSpc5>6CpKIeeg{STIF`NegY%;3Wmug=oN-)va- zH8c59+o%HRy)Hg0@a1&W@-ZTF>o$Q03M~v1=(Gmf`3Kc{Fo(Vg5d$LCAsH#^a_-7d zmm6X#!dQ|0%rLj6tNc`EChZhH-Aqt5aCgz>3*!(Cn=*zhI{;vkSzTXrDas zaDvr5Bj0%)?rgGj%tx-)j~2}yx!E9Q%RJi9nUoQ1UVWyc@uS}PTzbE3=K^A^yI=M76p0u3?Y!sD-?O0j6MIgU#dRV^=>i#I6e2WzdnA8sWg9id?B>f+`iT{O9`;Upyqpd54yN*q;JIV?X z0t)e~gBv&rYj6bO7qK16wJ|Cj?l=XnZv);Okb&J${1w`b!*XpitvPxP%#>(yDWdk3 zsOZp;hI)y>V~a}3voYQ875rtDlFF1;MD!{z!cLWv?~~~fQ&uz1ERV$3lPt&CY}fCb z&y!Ef_#dmbO{mw+k2%I6ZA{_MxcN`*!wb}i459#;dN9#?h!rgM;n>)PpnB-~C}v|j z2s?{uEL|X7EZ9*pqavFYqc$3)U)7 zt?4;aEx2Dccf2$Ho=+*k!9kFIEbM_#KfE01XswlLb(HmBJMjjdt+5W11K6d-7Q~>G z9ID-)53tSY41z4Os0>tzUrqJ&3Gnw{(t4BdB??aTHWeDvxnQj+tW|cn7Mnr#tMymu z{NR_?vz3iyOui6W38g!K(yxG8Rflp1F13xi*x0?8HsS3OO6?#me1o4{@M$)qvV%A~%4VC~; z#?L7AMVXEM>G+@?*+putgrlW-q`Q8}T#s$M1@UX#pVwsFyd@RRNKJqG4OZ#{re}dZ z3d8XSFVT(aY}SmYXF+i1>ra?@Austn4a_C;%dQ54<#{og zeeu`QIw(?l0KNMiZys}>DR|G zutgAn4vSUrc!`};`YTc(zeI&vKwIF@63*e@eR@C#mX|i~*f0&Eszp5Cl)U`eA;5Wi z)d%);=t8sI2^sKV>BmGiaNIK)7>d*+ga@8<^#&o+N4-+?MK~!JW^A~a(coP9QW=rA zuuk!DB6zWynTA0CmFXv>ZNLWmi>!Ag>_(N6MN_tZWF0891co!d#`e>b`x1Q4s8qnt zV5UK2W@hk6$E9QUL*xfZ!OUfG1j$xegk3DmN4ikr$s{>iCxhkS9D5CsYD`!W`!EpV zx=dKQrmD)JQy~LwDh1__p=~1OBpFb3P7U)2cVNq3PaV1yQh=+D5ebxuYb&zcjnj?W zynp$MoBh-@-<3a7W4THs*C__FkJXI!ZgpjE7+%=8jGvU11Ko;4r8QN=^FWQL?>P%jK@gNo@x!hP!R4on$4# z<&p`i3g+ZOGSiNs|IN0vf7;%w0BM>eY2!W z_>dTJQl~8>!qbYd@EW9$q2Uvz7_=7sMMy5@e)Q;y0l~R}_Sdn)X&}6*C}Uz%DsJ=u zvpJhGaPXPBZ}dnFNU zGrRco!sxfsx4VZ~UO*=|GERCDVBaS6WOf!!zvz{ zvO;Fgceh?GUV!1J)ShC{W@9ApIelxG04J16-Q(Sxh!4IQ#N=Znb!G^@A)jy~6B(LZ zts=qW1|LIf3L?WHhEZj*b?i>t;r?jXwIV>7j6jqdHPFpvT1ml2lIZ*lBC~7Btf=_c zMxa(EBVP8>7p8z0O;qJ0iN3uc?#XebKU`SlxK8Xp#r`hxocee)mY5+VQB|d{soCuF zFizczfz7u6#$%Q}zQm_~Ydz8(4ra)J!FORbkV z8YE5D$KN*#K&X2D8j>cUcv7Pwz`)P$6xI>KPK5OuS5Y2QGk!$NQl6$!sg~BLz6&@U zpz%s8!M*Og-%}61M=OcQP!aseb8V@phhAEr<0J3&cs#jsUcMq7veIB5FI)8PYWQ>8 zE^S4CHu*KjrN|GlAj_3ziMZbfK#1POiRKFVjq2}mxw6CP zYlK;@s@=)&`aZdHrX38^e>ltL{x$itxnP+06XM!OO{C-sso_d&<_NI)=$^{(SNSNR z&~cRA4KHj`Ln$twRk6^-UwZg-AF(5u(>0r`YBkvErBR}6WUKxL^`(RWpA0lw@Opc1 z3U%auhj@7(6`8ud!rjAk??hIa<_D{-LKH2z!b_Q&-1aV)bh}mhkbky*{ zpuWn;gev5z4BQk8;;^lLhpv=|sYcILrfZS4MG1>4_`{)?VWkg6?~F84c%)(F9WGvm z1avDUElnH_w}`+oX5nDUPqR@VB#dj$X!!?!nMNcd%}WDN0dpqZR*7|JYob74Em?YQ zwsMV<+36AR`YI`MAYSl^QoP+nb)|v1SnGPxqR1h@>i0eyAfG4nWO;UgtvP0i`cX?9 zj1xYiN6ezp=W7NaZ8;>DVWiPWaEa5Lc7&QybKCJ3KSN_c5@ca{6d=qKWHH)N;VM!t zsyKwXkCTKi0OjBglxF^|VgmHSv{&Hn`iGgxBfjuFoPY321R3sDr&VB3>m!<}4~+<%9BmIGP)7)52W&WxF% z*h7WHZKYkptXi_8uONLFHIscd#rksH_7MiaC`-7>)At2p)=Ue^_b#z-9Uh7q zafso1_}8Sgo8D7MgyV#-?UlsQHfdOUH7S@aADeWuiPQ03qCCvLg) zvSlL7L^5PdNU_Swu&9X^;5Wc;1@(Y`@k>~en27Hr^(y8=qu*tWGm4&G5=+|Jb zE>A08K;MIKnotgPCOjd4=d0OYkA3p)I}AO5e#C{&3zS9!J2eeQ=+~Qp~xRND!`>57ZithW_FKqn9~fr8&rr}-OajjaON)| z1KDbV92_C_u^|;kY1H5~qpjmOk6*@j|Gy$Dvan;Aamds9(b&b1b?CDJxU&I?HJetF zye&Vp_s8Wgo#s@6wVJT>w^MD1L_eK$zv}cr%!Iu$tt{mQ9Jof?eIupfl|OXV*-`BP zl!a-)3OF5RF4`$-s$>?}PHA+=JL!r{Uyz$^0Pq>Y7|Nn2vcll&794&?lsk|4Dv2G) ziM6$9xZ(y(nayiEd|vZDX_Alo>$!WS&WU54i*@gQdFlL(s>+I}rOaXhi;iFM!{Oi^ zi+~V4p7Opcb~hiIfcqV!p)qPyAKL9V?hV;pF06gtFw5gq(PtM-V3ty+Yc%HfxpUMnK+37-JShEatQImg8c;Pa1cXj4j}`d=+ao{u41C`R@2B= z+=Dr!nJ!|o@lM9`kDi6Jl5QtZf$>-~$T{4Ff|8F%kr{Z%)1@uw5X z7N8y4u)=mkebq#jx?w8=Jv2WFw0EHUTDSp8bxc@WS)BJ^diQ998raFVt%8dERR)tS<89YMc4TDYH}eaRWk*y&>l%4}cM&oe-b>B+sDrZaP!7Qbh(+ zCN)PLq}o~4@-Xz4I{GZ?;kHBSs^D%=KD~Q|kOBu+tUGr@6|tc+NlgJk-dt|p-kVIM z$WA_J|Mn(gPjGclz1mq4^#Zq}S#0c~A>S|x>w%+@uV0;6{0SO@H@VYpir1X-(4n-7 zg$`MV4yr2tP7|vj&L?qoNC^jfegAzORqlccF7Y3^@#mlJ$o~J=9RDxg^gr&9?y7x` zn-XZf2ac1eS5)zTJK4#Wn2>m)%-R&rfvj;?JZhAZu#$I}S1B?GqgUt>6 z%b%x)&hJ_sA}6e*^?`l{(G`=z>Sl-NE1WPL@HrWrm@)uix)C3fI4NVAG61K!Q6D#w zc170?>p81&mXw*)f)M^0Gsy)Js7){(F6bJ^@eQwbCb_~%YOS=bfr37ht3+5MKhXP*TWMg7VYChhcYU*11y8VBi{}!gc zrybdFsR%ZOxCq(TA%u7DgBL4MDV50Oh7PkrJ2lAts=;xENa-|&pid^R1^=x9588V0 znW^fi#OrnHpfwD8nTa1LMy0NYk;@RVEIACh;RBUPm zHby*ngCFKr5Xa6S13&j9n~6mozCS=5j$8saWUo~)^xAIB2TiMhQUGHnndB%y=5S>B znB;%?6lY__tI=Sy9-!o8$7rW-Ut_>oTZbmyV_?^M0WV0U;pfOL{SY%VR@&FCX4Ptq zb8k96a8U&`Hy`8n+y%M2Q*Z*=fVx}}a$ZSU*~C;e_xITerPdh(k-%7qc@1RkZzTOM z`T<%(%P$)IlT|Q%>#0nXnQGL@4I>SZuKjJ(o=4D9AF@FCazueivIH~1GdX|&$!6AF zt(ti`%SF)@M@~}dBDBU1SqL=*2q=fPo1GG83U;>XZ(}4_mKh@6Aa9Dz8Lig7rRmx2 z6WcCBH-H}I>q(a<&t#XryG3BD6I0ica%$IkSWcN>Yrv48|HH`$BJbp60gZwbXh~C9 z3IW1H4J&|PA(6L)HBjXQr%u)$xOVkXn@586cynpy)nmJ=z5-W4tQf(cTwZ|QxEiL5 z5D=K<EI%B#Clo5tl{l*v*&#j!>ae`=mEMIfR zksUAHLVX9zAW%IpnN3Y0YspIlTLNK$J)lTcgiEHB0LZ8FeuEsS4TBuO)nIj9_*J1D zr^VN}b(6s4vr#K1GsmX4L=<0KfmzXr`MV@J}~!igRQ%mt^9N#^czOTn|a- zS7Q|obf9DU{?!j;S`Ryc=P)d;Zn(hC-Vx6`xOfG?5y#?YBzJF)XM^iFvg3>2e73bE zEGiF>7M9kJt8klkr>`tJe{cfdo+u>C2{sgdZ^0sd8u#Is8beF_^0zSw2MvbP+6&Ko^tJqt?vG&Q4BQ z3*DmtXDA06-Gh61D>bqrIkqnG z_lQ664=?(r?0y~$FT&ya|*!*kcT#cRAvHRPZpLSGYE7Tr1*r5)-0|RtaV{Y z{4qs%@$VH;IYwr+t^kv8I@j3KfPzBbv6T@>O;~-&2PYT9NBiyVZ#u*==+2koll4=D zA6NgmKnBFQn;3oF^P((4F@jy60Kd8qI|2rPST9a)L26=RmY9SyQ7s1n8;XbG2sFNF zZX^4`ffZilJ4Zw^!ErXQ6`V$kVPstyn|fbPdGA{|R>L?@Q5=X*0T()&YBH7RM^(C@ z*b@fsQ|a3cd1+2Mhsl}-h@zH7Jk+Oy_+IVy_0UId@Yi$y^-=N-C+Cz8rkmeHt+~4!Dau@-`Bfan6ga&oiZZVSA_T6cix%zEa*diV z@g$ypTTQ_Bv`dg9pKHDPrATUroC)Q^pP#>Hj9w7*N1}1KAAO!JR*tJd$Z(pj)@D** z+MAe}rKaV}*cS9nah`%d52UR~{LiqwOumh#TQDSt_5Ok5MN7-hMs_gK1@+g?>zQWvF0iphsh<$k{Y{*l_6 zVPiQEfrY33evJP!8p&K0Q>E?B?ARd9E90OmtXO50+d3^tmU$CwW30W_n;kLq@pt}u zJY#Tt)7vZcaIb9h<=!c_P%>*Seathj?)nY$!Kp$!&KY`Hj9BbBK^W(|lj+7p z=g#W`=+JeHcv-mr5o16J`4MwK2-%3w0E|?e zNej$>3E>-I7Hwj;qp_cL@QYhd7;8Vl!zup+xQX6>pBfgVR51nEVG^H&l+UjW&Pny! z80#(|5PPo_4%(pMZr)$)RtekXRa*<9o!S?>^rbi9wx_gD>(0|8FRKp7;k4o#>So-Z3G~og?|Rd|G@M9-z?$(m`pX=+IF~O*akaOx-b6ot#&asiOtO7G*cyXom4LUh&>=u%0XI8N^4Stg%pSg_rJT@5&fy=F=3WKyMlr+ z#Fep5L!#@25(|ZX-d^^SDD=CcjD9}m+L~B@*nd7*KSNe8O!gRG@^*)7n^~s~v4-;G z{R5tCVTb(sZ{fynDR%HUYtp%!qV6&F=(EASX>T=Vqr2g~a4ki55V~`kNN<342DpD) z23vAtcvIdg%tm#?dSRZ3?7(+txsu)h?+khWegU~T>K^|HdT|yw{ObSU7QAQgUv*+1 zUj;FoS|%}s^uyPT@bm8uC<(R|>IHt4s6~F0*vo3CILr!iJkCsXgy;>k{a=lp2Rzl? z8^CXN2pN%4*)w~U$lhC48QFVeXXRQ&8CluFMO0R@McI^{St^9eNXZ`ma~rt#f6M!O zyDxn{o$qHFKE*9hso(RyOBmT}-OBvD#^k#NK5F+ukx?L96ocWjnNx)nmhe1b+aau(x$-Yq3L zpW~v(W?f;razVFL(Y#x@9ZzRq87)OiJgwkA6$_ zi1#@6i!)jM0Y==*G?L47_xZ5gD0&x@VOpzE#%4UNS! zcSIs*1;l5LC8V|m2MsyZ3$SvwFge|cNsBelV^soz@oIOw zs{)NAJjsgqO}q~~r43Y=9dogomfuInGn}~_7f0zVn)L!hrIazQt)cO{PV_XRhCGKv z<2AvgJF(VidYe@rVMO!|>s-+o_>|<1Bw`$o)Z5OHP;&Y6$40jiABR3py5g1A1Us3| zz+j>w{!)}YR{k39uJ%>ftYqpek%-#3d@P3Qbl+N)jP=NLSTH`j%so0D8&S*my3o~7 zg;aU-I+m9|zMj)=ql89D=g4zZmBuK)ls*3Nb+MZyF&`T^wsFZEKQ4~HOhF=cLX)5N zO{8L=nEb86LKE-Db{r%np9HNO1rB3p{&w4;qlXQWcDlUTJU2AvpLB0jFg zZQDc4-#Pu%q*s|nYUHjQQv{8lbnghw!H5 zeVLT%mcS2l_$m-{WG%x5t*Vha@v+$ESpNjiyrG4KC}W4E@_m`_=ji&JZ$ay(!&syz zpL~kPr0ZJHYm#y+YmB4LoMK@VZoH-#NSSnBYD_({HeTBsqtl|NqOpWGDCMMH=;uxu zKBv&~_zGXs?O~Y7ZOg8N63lrQKbB{4q?k6>b10fh`3j(=b^>gHj~Sf}y|7Jrsua=D z`Ef@(-7`1K<;m-ZwIrU#QYOKe#O@Sqw~rK$xp52)q*w%9x0?@H{UEbVC+?{O2aQD|+o{IE*12+r{c1O!+Txgh| z%!!YWl=^)ezgo4Z8qfZDh}7?4U{-CC-Upk0Qihe7glt%|WM_VbL5pYLF=exjCe1sZ z>5*SJc~Y?_>q{7nB#)k|W4*#?jrqzr1FSDRT$sanXaotX_1a&1+c5j7;-s%mf^ zclD~U;uzw)yXt1o>}nWSHuR@}P|v&Cozz3bD(uRMjLub>8RKXCX=-5=Cn z?oBFqF04L5Z^q)Hvz!QLl=T9=%p{2yI`Vq;1kyex_#C+0~~{jpaGI zuxLI`i)1Isl%H*h)lewsYA9^Z>v&As*G8Bhf4N8T#`3AI>ptVjU7K4e5ma_p()67w za*ZAsh(5_R8Y3BK6j*pBDDZH;`-#Mrs-{fa$kf3$G_u~Da4(LT`Pae8oa7x@N6D_H z$3af0oS}TH{J661EnS9JU_k$CjM@ixW!SFPQQD}CE%=Oc_{=q!a>}+g7xXUO$Yo) z$X?JS*+=BXl4EW~$C}1?Tj6dSM{%aiurRyuw5CAFG=EIiu84LBrQrJGW{$BW&q1fq z3Zsrv8pe&6@n61kgg`%*yl4my<8eL`o@1A7)+j|U)kv?~k9UpMBh0+;^Tz;#n#*XC zpV&6NOpX>fKBhU5FTp;$SdhD0E9ckPf^l0|o% zp2^M_!9eIbzs_p)0$AAKcopWNQZT0-($*ZCW+eF4H zZa6P$v)oaP^Q|&yXO3qP7$QNN3NK*Uz5Th>sz z1>GxlHJ*1-{G}S_-$fP3DyoIZe|Xg*iAGW^U>$4MC95SlXTW>JYJ}y|W!4dDJe5Tj z3rT*k^Cr0N@n!xuJ#Cx(l*t3uUw7sP-!7-`vgDde5xrr#XKcKvMK*Qgn?yjxNj&DB zZnUvV(b?#65Yo_b; zdPt=V_7X^?==43xoagXaR#lr{H2a!N<#TrE}KL%p3FHQjaCKl{&bM)Ri_{jEJ8S%NR}WDxJS> zwYreG^d?hFf2FJx`^M3PMHy@Wu<8|Ctq^y7EEV;>zmGZ5Lo5}Wzah=n>=9}7>dGQNr zUN}PM7i@^*>+JE9A5mLSsOWjoG}J7co9;9@$TJai#V=vhm0nR{y7$4Jt*}8PN19BR z^?FdIo$lomnnSwRDd`I_ILzaV$WK>1nu`d#)))yr`KcA(aB3m^c5P)D*Qdc2Ohc_# zw4VImL_QOx_v-%G{%Ry;oG@Qh9!e#Y^H|^l#u4nal=v>g8mNX|#F1*sPiZReR2LgR zL~YiWEj&%@F73zmFQ>Ta+2ObCz@=XQJaK@&^ok*DHeDzm--(B8^_@tyz{A4E3_Av; zv*hNA-IZ!3_&1CSpBZY^_d&DV1-q35vt_jLWYgIa4ZHA#TarV-3i55nu zby$5&Uw9QPWZnQ|+ zxQF(N6Ar6nmX#?Br7O2<$gYg@3-aUzH$Uy@T^c3Hp7e{LCe5ByTTb_wiNDz!WxVZT zUo>x;kan(EIJ|esUprQ3UHc~Hr=q+pC5Bm;oT`x?G0fW6XiWa;>Fd5><`b0qD&NtJ zG|cWp=CCcgnN88Qm%uy!Iy&ey%xp4BMsIYCyxs@(#K_}dp4KPpId(>c%56|i5Yx4( z(6FW4a69@z_AADTBsl`Isjwq3I|m*7OT|;zlw_t?obPm1xK_>*H+EvrdkewHp7*E6qH zB+XqkWaIwIGLWd;RnyRz-EdiDrKS!O>lEf`$Vlj^ADGb*>!x`T0#9S{#47c!zSpqk zOF>(8fd7Mg?WAvJv$umV2hn0xNGZHXFX)7{I|y_|hIMLo7@UBhgpNT9z7xTLob(J;1;0SRf%HG0GvNQu z*R7mf-MC!c+)Z7%e*eYT*v8Su&Da>6rf-9O;6(ctM=cF8987WRBM`_%uo6rAm-28T zuP_h45FGGSQ$;OEoTzTGD6y$C`6{7k%xvaU?CcV3Wqv`Jeu9ipPEFif!H@51PM_6$ zj$zMeg7ajt?mC}{KI_o^hk>^V-+z%zpv-0HR*2z?d)mEl?zQ3iEr)XD9@VbSa&A+w zOK5$JBk&4Ege*k&M6+aGrFu2~1;-Ec}dXFvF@#5zUe)OGM`Ps1bx zX$57p5+q`jswwe8w6YX%6mpWPLsLYY%X&W3L`z1{P~gRSji%%I?d3SCuwnub`_K59=ml2NN@RSGlYAhQEMc_NU7srrjfU>kR1y z(yCUGtswQW$K(S{=0p8!KbZ0pHQnFw^3^0IwR|R_s2FOhvhBc?NV`*xKejk;|BOiT zQfmy3|6t0apq3EGMFGJxIksdNtZJ7q?CY=`BpoF*mrjJ+TITYS<<4sQ_*j81Lg4^&om*_J*_|;P* zJjdTAE=$!uxhtkBlsvG8$0!2K!5LL2N0w%HT;8(~{z zHqW0F^;F`fuC#@Od;>q}+!*oaXD(uGWOPfEbb7bsrKD--l8NrN zr&`Ds5E0Uvo~FYP;S4uOg*^;RWjad!hCv}%e%FTOn^$1muxof-;Z=J~CEa3gOgo)o zF=E&o)vi4LoTqHUBEZ3Z+0Y`%|2Ex;ou=t-Bpm*sneD5(==w(Td(fI(iBi;QaS=5eGx63-JSD4mH!i2u zoS;}O6kobv{i<5TtF$b!cf+)W?Q7e{j&{7>HFL#XOPOGQ>?NXQg)#PB`bW){sqeJ5 z2YmD{ejC)mAJ>U`p+oM@a`uaw>6G&}ooQZmRk8N{*c%^rMsJF2W9|n?ODmpBY+qQC zvVm1B0SjR`#5#;U?400^qr~3Pty-EV*z52a@DLaXRkhi6+ zp&{B*mWuLZQu1Ag>cF#Y>MuMJDJeBZ2#}(<#M^SbcF?%6!yC!N%l-3S6&r`B=eQs` zNH4C3%W#pE4YT23HsAK@4;+_zeC60669YR%{di))+1GXRb1ChxzHbB_MzBo1OEO_+ z78Gp*ePi>!W12mBglBfO<|4J#cLs$#1)G!{V_G947YR_7)pQyz6#3Ig%orYY=b{C$PgvHMcJ7RdRsi;yhs78?MBn9a(>QZtNu;)7Z8{!tkkLN+l#$4j@770@@ftlJ0+ieOPrz=x;~zA@6I<5LdQ2F z;yF55QawIaqR&?>t`rH~DRCsCZTPa;IH%EXnO<=b;!uV1;4x;eCrqDB1wN&xRS<|qSdr&@ zQn08f$tsY|7~G1~vwADMO0K^nBYUGJ5o;H_=7bFa&nC05*|V?At!V2cO_9ORrLJw0 zmsZ}R8(PtP9di51IXy15Q8Emko~oW;qv&!b7)_JV5AdTq?*3>DY<+)BJZMpj8x9pqxrAL;orEq z#r{h8#wETBCM#aarZ$~;3|+y^W-Hwnd1dR#)(RY!&wu|!9L32`uEzTyvJhK}g7l;n z{X3P>qWPY0oBW%E`xf{kc1PWavfC{N=B+Vh?#)OUzju z9yh=rvbOfJ_GQePSH#3XDnCug+<+o`2&^M!z*Gg(Pz1An>%x7k`;Nh(1GQ6G9f8CT zHejjt=0C2UNDhs=L?__PnC;am#3L$n&D!Vafw*gOSNc-@WK$I{XiEgRYx0ox@WI>D ztgYe{#ElPbnRS_C2$f}?Z!o#HeWxHim8jqjn99Wt7a7a;KS~KWt}ehCI=r|(GOXBd z6K3F4d7hRtVu9NG2yFmPf`$GoO7r_I2{(!jj}e4c;9mRaA|0ITu=Px8wcEH{Qbxm9 zs`H2rZJ&sx98b^;xot+Ix7HWuQ$G?TP2;ZSS`SfG;3c?g+rRz5eJ1i;_+V(^NpfNp zlA$?e>N!c%^J@45WxNVsgbCdZO`9(!cfPaI_V)1ANzVKlAZ(+IsdxH8J9{=~)H6m& z)8ZfMg|A=Z$PLNZU8o$SCd^*BK&4wm<U-z=TsVrP z&MzM|NJ_=mV~>a+LvWjjom3rdrfWM ze&Dkmqd>o##lUWSfzE0EVm~&PCT12t-7m5eKETPWu7_tnJW+;sJUDOy{hs2kc%OLz zwESp0&R~&{O0c4YwN7lOs8w!xHKx%~^~b#l@0~9gMhib=K37Do&-p;c+q#{C75E1-F=@oY>i!Tf!UauKqkSkejG(m3{hw0%Jn~Ht}jHHlfe3%2!ML;}pdu zk`-kP+nWv&RJTSOVc#vsQ<8MsSFk#81_v4(v*K|zxB7=kB)V_w%Cn5Ww4WD;dG_FsOud2v)%?r*lGQ2mSN}8*Tn3V3EI<_U^z1S)Uwx3IF;XK zPsVV`F3xV~KIf9*e(R&C{)^A9TZ}{)&!sgKnsiGs7M!vcOGc=pRHu7c-*&lGc-^v~ zPC4?VLN*S+Q*`bE!B)S$$|LfSC#*>|vJ!3n16Y~gF=}s;ayk+0V((i4L;9M!qQx1u zmp~;s|4@yCl>=NgX;CGSFF*)h6$@#lix(4TbgY6NHG*Wesk}8MO=VdUzwSLM?j!W( ztXJ6XyTNjOiproaqxR;WXYYgXf!u_r^ZS zo!I6f#g1K*7zw4JuIsT&mg@SJcC9@Y$1sV$VKIdbhkzODt-E{i7rha@^Zl6uraN%03WXrcnP%wG}vM zZ`a`1VcFYKRH48o+cVZsn>0c*ta8SroIfzlvK5UP*Xlz-0Y1U|5c1Z7{vK=zW2yRl znz<-_aQLldV^JkVElYoUa+}Nz37F6ttHF#S+sXM?y$>pV;_frDKEGAgt_{o952{d8 zCMGz(<<~NGCr6~TiBOB9wO+_*zcKJ}fmxW(z8W;97mTe3Zt6x@#qe}7afY|0IYt66 z=nQG=qJOxjeNP~b$MNz`G1)skr9^x(4xLW*mL@cUdiLC0V!Pk`7jNoOF+RWkss^ia zphLJax}34h0w**4wX={kc|yghS$wl zd`isWKMC3r4?c)xIOHIZOY#z`N(l8ZfaW++og9ZZ15}0792QbU`6n^ zSVg-V-)w;zcEQ01$M-~oKw!{#P)J5j?yQ2UtQuIT`SlBO@yR0ZZ5WX84nV;B{Ld2r zZBRQiruzpOaBT+sxS)aq3=5u#pWIUg+_-U=5FE2>L83+bjPkHAODW`NiyA05A$`TnGe2 z!o3Fu{a(IT_*b%rT+}Yf$Tk#sbuX}J#G>gndqsaGL&!zR7dZuSfyggG%|v^3!c9P} zaj&SeDX15f!cK3hR>*>V+pt<>h23o4EA3|C>Sk;P4h#Q(g*~Kf^%ep~>>~q#fIi!M zV9@Oi#PWMCiGuQ8DIAv*9Rl$ODu5>re;ydr7XQc}`}--y9tDMC!yN*X%so7g9<;>* zIXd(a{yzzYcf%od6YKN#Kokv-v4cN*4-A^ZhZ=&y#fO25Gv|yAk^^gqqeKRY19$uF zS9|RO()`WOpP4(RX9B+v0op-y&}bRdAe8R4>w^J{2LqWQFf+v1X|0M9gUVz!&h(RT^#=Z?tPX*2 z{Go!MFsK8VKL1w9LGWHMKt6W_nSB1mIpqN0(D@*?awCv=KK`5hGt40E2}Y(DxTf(v zrGr2UbdVLYJNH-m&(L!Sc;sDCg9+#*3?Mcag4o-HXn#}4UdC|D( zhhPN!i?;Vd)FJRbOk&p@Feq&R7w1KgoQ(TWI6Ox=gzrNw>&*q&-+==l@^SeN<-?Pg zL-@4_bNj74)Gela0X z6Wvf!;RC@T%#2S@qjrF^G@M3e9`QlRgbxmfF#V!yCo4hgPk@n%6+up^FqF*Sxz=IW zXRbcQm%vawh}>I}ktnfyN!TIa<8^vZ;z61aCIi0YK#*5t^6%jP97-e5E7<*jI@Cax z4S(P7&jW*=25S;1!p2^D^K`l*+BNFiyW;NG5%e}e+jC26{@*XK=>uF z^34B7dhinlbp;XSUk&)Lk=0wiChr3nlKbe9YrqjXw1!_9;30i{=V5UGd@MW3j2!+% zln$@q@EA2olc@Ir_~kuD*IxScgO zakMyVZQ^Q;Vus?@U-^Ro`0z4Fco9>WHVTx|{Uo|t*ju{VSUFmlpSAEZad5VWe+P#w ztoUR`MF|)VwE!A%HcCs68jW)8z($cZxDqJv8<@j^M0D?gL635v#QZ#z_W#AHU9yj~ z1tv2LAc*3ubE4#+p3)ulPZ4Wsg=JXivw7ggrtIx(4L~-_(#_6}MNypNDiXx`_qyC2F&bzU=a1<2=l?pMI!duRp! zVYop)?q#3v5PmrSmopvG2gs*Q>{p=?d3Y5l&z?Zm67oyK`&G=x9a_a(_S-8$(9P$q3eVl&|YT@>9zgJ`tBt*5b zcORLY`*-BeR31q{R2xC}X_XKDP5ZMg6j_7FyDs;MY5D&o{#mR=XfeD`kT);x6CV^D zPW-)<5m`*+ZGZdF#)pSPe{TXrh9d8V+lL-`{BP*b^<{*XquLp^&yy+qC-3Jzv4hf~ z+LyIYBP;qR4Zh-xARDS(NBgvpV$`%hU-TfLQ0?m3M>&_EM*UjFMgXDO1G5jhU5Xm? zYdISMglZqjJ}CMLYS5phVFVPaT^#!;!!p#U-wVZngb8vd2*d&WM@b95KY0o=0m%OV DCF1U^ diff --git a/pygost-5.13/pygost.egg-info/PKG-INFO b/pygost-5.13/pygost.egg-info/PKG-INFO deleted file mode 100644 index 2d9fd93..0000000 --- a/pygost-5.13/pygost.egg-info/PKG-INFO +++ /dev/null @@ -1,93 +0,0 @@ -Metadata-Version: 2.1 -Name: pygost -Version: 5.13 -Summary: Pure Python GOST cryptographic functions library -Home-page: http://www.pygost.cypherpunks.ru/ -Author: Sergey Matveev -Author-email: stargrave@stargrave.org -License: GPLv3 -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) -Classifier: Natural Language :: English -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 3 -Classifier: Topic :: Security :: Cryptography -Classifier: Topic :: Software Development :: Libraries :: Python Modules -License-File: COPYING -License-File: AUTHORS - -Pure Python 2.7/3.x GOST cryptographic functions library. - -GOST is GOvernment STandard of Russian Federation (and Soviet Union). - -* GOST 28147-89 (RFC 5830) block cipher with ECB, CNT (CTR), CFB, MAC, - CBC (RFC 4357) modes of operation -* various 28147-89-related S-boxes included -* GOST R 34.11-94 hash function (RFC 5831) -* GOST R 34.11-94 based PBKDF2 function -* GOST R 34.11-2012 Стрибог (Streebog) hash function (RFC 6986) -* GOST R 34.11-2012 based PBKDF2 function (Р 50.1.111-2016) -* GOST R 34.10-2001 (RFC 5832) public key signature function -* GOST R 34.10-2012 (RFC 7091) public key signature function -* various 34.10 curve parameters included -* Coordinates conversion from twisted Edwards to Weierstrass form and - vice versa -* VKO GOST R 34.10-2001 key agreement function (RFC 4357) -* VKO GOST R 34.10-2012 key agreement function (RFC 7836) -* 28147-89 and CryptoPro key wrapping (RFC 4357) -* 28147-89 CryptoPro key meshing for CFB and CBC modes (RFC 4357) -* RFC 4491 (using GOST algorithms with X.509) compatibility helpers -* GOST R 34.12-2015 128-bit block cipher Кузнечик (Kuznechik) (RFC 7801) -* GOST R 34.12-2015 64-bit block cipher Магма (Magma) -* GOST R 34.13-2015 padding methods and block cipher modes of operation - (ECB, CTR, OFB, CBC, CFB, MAC), ISO 10126 padding -* MGM AEAD mode for 64 and 128 bit ciphers (RFC 9058) -* CTR-ACPKM, OMAC-ACPKM-Master modes of operation (Р 1323565.1.017-2018) -* KExp15/KImp15 key export/import functions (Р 1323565.1.017-2018) -* KDF_GOSTR3411_2012_256, KDF_TREE_GOSTR3411_2012_256 (Р 50.1.113-2016) -* KEG export key generation function (Р 1323565.1.020-2018) -* PEP247-compatible hash/MAC functions - -Known problems: low performance and non time-constant calculations. - -Example 34.10-2012 keypair generation, signing and verifying: - - >>> from pygost.gost3410 import CURVES - >>> curve = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - >>> from os import urandom - >>> prv_raw = urandom(64) - >>> from pygost.gost3410 import prv_unmarshal - >>> from pygost.gost3410 import prv_marshal - >>> prv = prv_unmarshal(prv_raw) - >>> prv_raw = prv_marshal(curve, prv) - >>> from pygost.gost3410 import public_key - >>> pub = public_key(curve, prv) - >>> from pygost.gost3410 import pub_marshal - >>> from pygost.utils import hexenc - >>> print "Public key is:", hexenc(pub_marshal(pub)) - >>> from pygost import gost34112012512 - >>> data_for_signing = b"some data" - >>> dgst = gost34112012512.new(data_for_signing).digest() - >>> from pygost.gost3410 import sign - >>> signature = sign(curve, prv, dgst) - >>> from pygost.gost3410 import verify - >>> verify(curve, pub, dgst, signature) - True - -Other examples can be found in docstrings and unittests. -Example self-signed X.509 certificate creation can be found in -pygost/asn1schemas/cert-selfsigned-example.py. - -PyGOST is free software: see the file COPYING for copying conditions. - -PyGOST'es home page is: http://www.pygost.cypherpunks.ru/ -You can read about GOST algorithms more: http://www.gost.cypherpunks.ru/ - -Please send questions, bug reports and patches to -http://lists.cypherpunks.ru/gost.html mailing list. -Announcements also go to this mailing list. - -Development Git source code repository currently is located here: -http://www.git.cypherpunks.ru/?p=pygost.git;a=summary diff --git a/pygost-5.13/pygost.egg-info/SOURCES.txt b/pygost-5.13/pygost.egg-info/SOURCES.txt deleted file mode 100644 index 781d116..0000000 --- a/pygost-5.13/pygost.egg-info/SOURCES.txt +++ /dev/null @@ -1,71 +0,0 @@ -AUTHORS -COPYING -FAQ -INSTALL -MANIFEST.in -NEWS -README -THANKS -VERSION -setup.cfg -setup.py -pygost/__init__.py -pygost/gost28147.py -pygost/gost28147_mac.py -pygost/gost3410.py -pygost/gost3410_vko.py -pygost/gost34112012.py -pygost/gost34112012256.py -pygost/gost34112012512.py -pygost/gost341194.py -pygost/gost3412.py -pygost/gost3413.py -pygost/iface.py -pygost/kdf.py -pygost/mgm.py -pygost/pbkdf2.py -pygost/test_cms.py -pygost/test_gost28147.py -pygost/test_gost28147_mac.py -pygost/test_gost3410.py -pygost/test_gost3410_vko.py -pygost/test_gost34112012.py -pygost/test_gost341194.py -pygost/test_gost3412.py -pygost/test_gost3413.py -pygost/test_kdf.py -pygost/test_mgm.py -pygost/test_pfx.py -pygost/test_wrap.py -pygost/test_x509.py -pygost/utils.py -pygost/wrap.py -pygost.egg-info/PKG-INFO -pygost.egg-info/SOURCES.txt -pygost.egg-info/dependency_links.txt -pygost.egg-info/top_level.txt -pygost/asn1schemas/__init__.py -pygost/asn1schemas/cert-dane-hash.py -pygost/asn1schemas/cert-selfsigned-example.py -pygost/asn1schemas/cms.py -pygost/asn1schemas/oids.py -pygost/asn1schemas/pfx.py -pygost/asn1schemas/pkcs10.py -pygost/asn1schemas/prvkey.py -pygost/asn1schemas/x509.py -pygost/stubs/pygost/__init__.pyi -pygost/stubs/pygost/gost28147.pyi -pygost/stubs/pygost/gost28147_mac.pyi -pygost/stubs/pygost/gost3410.pyi -pygost/stubs/pygost/gost3410_vko.pyi -pygost/stubs/pygost/gost34112012.pyi -pygost/stubs/pygost/gost34112012256.pyi -pygost/stubs/pygost/gost34112012512.pyi -pygost/stubs/pygost/gost341194.pyi -pygost/stubs/pygost/gost3412.pyi -pygost/stubs/pygost/gost3413.pyi -pygost/stubs/pygost/iface.pyi -pygost/stubs/pygost/kdf.pyi -pygost/stubs/pygost/mgm.pyi -pygost/stubs/pygost/utils.pyi -pygost/stubs/pygost/wrap.pyi \ No newline at end of file diff --git a/pygost-5.13/pygost.egg-info/dependency_links.txt b/pygost-5.13/pygost.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/pygost-5.13/pygost.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/pygost-5.13/pygost.egg-info/top_level.txt b/pygost-5.13/pygost.egg-info/top_level.txt deleted file mode 100644 index d05bec6..0000000 --- a/pygost-5.13/pygost.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -pygost diff --git a/pygost-5.13/pygost/__init__.py b/pygost-5.13/pygost/__init__.py deleted file mode 100644 index fba7932..0000000 --- a/pygost-5.13/pygost/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Pure Python GOST cryptographic functions library. - -PyGOST is free software: see the file COPYING for copying conditions. -""" - -__version__ = "5.13" diff --git a/pygost-5.13/pygost/asn1schemas/__init__.py b/pygost-5.13/pygost/asn1schemas/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pygost-5.13/pygost/asn1schemas/cert-dane-hash.py b/pygost-5.13/pygost/asn1schemas/cert-dane-hash.py deleted file mode 100755 index 0292b9e..0000000 --- a/pygost-5.13/pygost/asn1schemas/cert-dane-hash.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 -"""DANE's SPKI hash calculator -""" - -from base64 import standard_b64decode -from hashlib import sha256 -import sys - -from pygost.asn1schemas.x509 import Certificate - - -lines = sys.stdin.read().split("-----") -idx = lines.index("BEGIN CERTIFICATE") -if idx == -1: - raise ValueError("PEM has no CERTIFICATE") -cert_raw = standard_b64decode(lines[idx + 1]) -cert = Certificate().decod(cert_raw) -print(sha256(cert["tbsCertificate"]["subjectPublicKeyInfo"].encode()).hexdigest()) diff --git a/pygost-5.13/pygost/asn1schemas/cert-selfsigned-example.py b/pygost-5.13/pygost/asn1schemas/cert-selfsigned-example.py deleted file mode 100755 index bd562b1..0000000 --- a/pygost-5.13/pygost/asn1schemas/cert-selfsigned-example.py +++ /dev/null @@ -1,348 +0,0 @@ -#!/usr/bin/env python3 -"""Create example self-signed X.509 certificate -""" - -from argparse import ArgumentParser -from base64 import standard_b64decode -from base64 import standard_b64encode -from datetime import datetime -from datetime import timedelta -from os import urandom -from sys import exit as sys_exit -from sys import stdout -from textwrap import fill - -from pyderasn import Any -from pyderasn import BitString -from pyderasn import Boolean -from pyderasn import IA5String -from pyderasn import Integer -from pyderasn import OctetString -from pyderasn import PrintableString -from pyderasn import UTCTime - -from pygost.asn1schemas.oids import id_at_commonName -from pygost.asn1schemas.oids import id_at_countryName -from pygost.asn1schemas.oids import id_ce_authorityKeyIdentifier -from pygost.asn1schemas.oids import id_ce_basicConstraints -from pygost.asn1schemas.oids import id_ce_keyUsage -from pygost.asn1schemas.oids import id_ce_subjectAltName -from pygost.asn1schemas.oids import id_ce_subjectKeyIdentifier -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256 -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetA -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetB -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetC -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetD -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512 -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetA -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetB -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetC -from pygost.asn1schemas.oids import id_tc26_signwithdigest_gost3410_2012_256 -from pygost.asn1schemas.oids import id_tc26_signwithdigest_gost3410_2012_512 -from pygost.asn1schemas.prvkey import PrivateKey -from pygost.asn1schemas.prvkey import PrivateKeyAlgorithmIdentifier -from pygost.asn1schemas.prvkey import PrivateKeyInfo -from pygost.asn1schemas.x509 import AlgorithmIdentifier -from pygost.asn1schemas.x509 import AttributeType -from pygost.asn1schemas.x509 import AttributeTypeAndValue -from pygost.asn1schemas.x509 import AttributeValue -from pygost.asn1schemas.x509 import AuthorityKeyIdentifier -from pygost.asn1schemas.x509 import BasicConstraints -from pygost.asn1schemas.x509 import Certificate -from pygost.asn1schemas.x509 import CertificateSerialNumber -from pygost.asn1schemas.x509 import Extension -from pygost.asn1schemas.x509 import Extensions -from pygost.asn1schemas.x509 import GeneralName -from pygost.asn1schemas.x509 import GostR34102012PublicKeyParameters -from pygost.asn1schemas.x509 import KeyIdentifier -from pygost.asn1schemas.x509 import KeyUsage -from pygost.asn1schemas.x509 import Name -from pygost.asn1schemas.x509 import RDNSequence -from pygost.asn1schemas.x509 import RelativeDistinguishedName -from pygost.asn1schemas.x509 import SubjectAltName -from pygost.asn1schemas.x509 import SubjectKeyIdentifier -from pygost.asn1schemas.x509 import SubjectPublicKeyInfo -from pygost.asn1schemas.x509 import TBSCertificate -from pygost.asn1schemas.x509 import Time -from pygost.asn1schemas.x509 import Validity -from pygost.asn1schemas.x509 import Version -from pygost.gost3410 import CURVES -from pygost.gost3410 import prv_unmarshal -from pygost.gost3410 import pub_marshal -from pygost.gost3410 import public_key -from pygost.gost3410 import sign -from pygost.gost34112012256 import GOST34112012256 -from pygost.gost34112012512 import GOST34112012512 -from pygost.utils import bytes2long - -parser = ArgumentParser(description="Self-signed X.509 certificate creator") -parser.add_argument( - "--ca", - action="store_true", - help="Enable BasicConstraints.cA", -) -parser.add_argument( - "--cn", - required=True, - help="Subject's CommonName", -) -parser.add_argument( - "--country", - help="Subject's Country", -) -parser.add_argument( - "--serial", - help="Serial number", -) -parser.add_argument( - "--ai", - required=True, - help="Signing algorithm: {256[ABCD],512[ABC]}", -) -parser.add_argument( - "--issue-with", - help="Path to PEM with CA to issue the child", -) -parser.add_argument( - "--reuse-key", - help="Path to PEM with the key to reuse", -) -parser.add_argument( - "--out-key", - help="Path to PEM with the resulting key", -) -parser.add_argument( - "--only-key", - action="store_true", - help="Only generate the key", -) -parser.add_argument( - "--out-cert", - help="Path to PEM with the resulting certificate", -) -args = parser.parse_args() -AIs = { - "256A": { - "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetA, - "key_algorithm": id_tc26_gost3410_2012_256, - "prv_len": 32, - "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetA"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256, - "hasher": GOST34112012256, - }, - "256B": { - "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetB, - "key_algorithm": id_tc26_gost3410_2012_256, - "prv_len": 32, - "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetB"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256, - "hasher": GOST34112012256, - }, - "256C": { - "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetC, - "key_algorithm": id_tc26_gost3410_2012_256, - "prv_len": 32, - "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetC"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256, - "hasher": GOST34112012256, - }, - "256D": { - "publicKeyParamSet": id_tc26_gost3410_2012_256_paramSetD, - "key_algorithm": id_tc26_gost3410_2012_256, - "prv_len": 32, - "curve": CURVES["id-tc26-gost-3410-2012-256-paramSetD"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_256, - "hasher": GOST34112012256, - }, - "512A": { - "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetA, - "key_algorithm": id_tc26_gost3410_2012_512, - "prv_len": 64, - "curve": CURVES["id-tc26-gost-3410-12-512-paramSetA"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512, - "hasher": GOST34112012512, - }, - "512B": { - "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetB, - "key_algorithm": id_tc26_gost3410_2012_512, - "prv_len": 64, - "curve": CURVES["id-tc26-gost-3410-12-512-paramSetB"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512, - "hasher": GOST34112012512, - }, - "512C": { - "publicKeyParamSet": id_tc26_gost3410_2012_512_paramSetC, - "key_algorithm": id_tc26_gost3410_2012_512, - "prv_len": 64, - "curve": CURVES["id-tc26-gost-3410-2012-512-paramSetC"], - "sign_algorithm": id_tc26_signwithdigest_gost3410_2012_512, - "hasher": GOST34112012512, - }, -} -ai = AIs[args.ai] - -ca_prv = None -ca_cert = None -ca_subj = None -ca_ai = None -if args.issue_with is not None: - with open(args.issue_with, "rb") as fd: - lines = fd.read().decode("ascii").split("-----") - idx = lines.index("BEGIN PRIVATE KEY") - if idx == -1: - raise ValueError("PEM has no PRIVATE KEY") - prv_raw = standard_b64decode(lines[idx + 1]) - idx = lines.index("BEGIN CERTIFICATE") - if idx == -1: - raise ValueError("PEM has no CERTIFICATE") - cert_raw = standard_b64decode(lines[idx + 1]) - pki = PrivateKeyInfo().decod(prv_raw) - ca_prv = prv_unmarshal(bytes(OctetString().decod(bytes(pki["privateKey"])))) - ca_cert = Certificate().decod(cert_raw) - tbs = ca_cert["tbsCertificate"] - ca_subj = tbs["subject"] - curve_oid = GostR34102012PublicKeyParameters().decod(bytes( - tbs["subjectPublicKeyInfo"]["algorithm"]["parameters"] - ))["publicKeyParamSet"] - ca_ai = next(iter([ - params for params in AIs.values() - if params["publicKeyParamSet"] == curve_oid - ])) - -key_params = GostR34102012PublicKeyParameters(( - ("publicKeyParamSet", ai["publicKeyParamSet"]), -)) - - -def pem(obj): - return fill(standard_b64encode(obj.encode()).decode("ascii"), 64) - - -if args.reuse_key is not None: - with open(args.reuse_key, "rb") as fd: - lines = fd.read().decode("ascii").split("-----") - idx = lines.index("BEGIN PRIVATE KEY") - if idx == -1: - raise ValueError("PEM has no PRIVATE KEY") - prv_raw = standard_b64decode(lines[idx + 1]) - pki = PrivateKeyInfo().decod(prv_raw) - prv = prv_unmarshal(bytes(OctetString().decod(bytes(pki["privateKey"])))) -else: - prv_raw = urandom(ai["prv_len"]) - out = stdout if args.out_key is None else open(args.out_key, "w") - print("-----BEGIN PRIVATE KEY-----", file=out) - print(pem(PrivateKeyInfo(( - ("version", Integer(0)), - ("privateKeyAlgorithm", PrivateKeyAlgorithmIdentifier(( - ("algorithm", ai["key_algorithm"]), - ("parameters", Any(key_params)), - ))), - ("privateKey", PrivateKey(OctetString(prv_raw).encode())), - ))), file=out) - print("-----END PRIVATE KEY-----", file=out) - if args.only_key: - sys_exit() - prv = prv_unmarshal(prv_raw) - -curve = ai["curve"] -pub_raw = pub_marshal(public_key(curve, prv)) -rdn = [RelativeDistinguishedName(( - AttributeTypeAndValue(( - ("type", AttributeType(id_at_commonName)), - ("value", AttributeValue(PrintableString(args.cn))), - )), -))] -if args.country: - rdn.append(RelativeDistinguishedName(( - AttributeTypeAndValue(( - ("type", AttributeType(id_at_countryName)), - ("value", AttributeValue(PrintableString(args.country))), - )), - ))) -subj = Name(("rdnSequence", RDNSequence(rdn))) -not_before = datetime.utcnow() -not_after = not_before + timedelta(days=365 * (10 if args.ca else 1)) -ai_sign = AlgorithmIdentifier(( - ("algorithm", (ai if ca_ai is None else ca_ai)["sign_algorithm"]), -)) -exts = [ - Extension(( - ("extnID", id_ce_subjectKeyIdentifier), - ("extnValue", OctetString( - SubjectKeyIdentifier(GOST34112012256(pub_raw).digest()[:20]).encode() - )), - )), - Extension(( - ("extnID", id_ce_keyUsage), - ("critical", Boolean(True)), - ("extnValue", OctetString(KeyUsage( - ("keyCertSign" if args.ca else "digitalSignature",), - ).encode())), - )), -] -if args.ca: - exts.append(Extension(( - ("extnID", id_ce_basicConstraints), - ("critical", Boolean(True)), - ("extnValue", OctetString(BasicConstraints(( - ("cA", Boolean(True)), - )).encode())), - ))) -else: - exts.append(Extension(( - ("extnID", id_ce_subjectAltName), - ("extnValue", OctetString( - SubjectAltName(( - GeneralName(("dNSName", IA5String(args.cn))), - )).encode() - )), - ))) -if ca_ai is not None: - caKeyId = [ - bytes(SubjectKeyIdentifier().decod(bytes(ext["extnValue"]))) - for ext in ca_cert["tbsCertificate"]["extensions"] - if ext["extnID"] == id_ce_subjectKeyIdentifier - ][0] - exts.append(Extension(( - ("extnID", id_ce_authorityKeyIdentifier), - ("extnValue", OctetString(AuthorityKeyIdentifier(( - ("keyIdentifier", KeyIdentifier(caKeyId)), - )).encode())), - ))) - -serial = ( - bytes2long(GOST34112012256(urandom(16)).digest()[:20]) - if args.serial is None else int(args.serial) -) -tbs = TBSCertificate(( - ("version", Version("v3")), - ("serialNumber", CertificateSerialNumber(serial)), - ("signature", ai_sign), - ("issuer", subj if ca_ai is None else ca_subj), - ("validity", Validity(( - ("notBefore", Time(("utcTime", UTCTime(not_before)))), - ("notAfter", Time(("utcTime", UTCTime(not_after)))), - ))), - ("subject", subj), - ("subjectPublicKeyInfo", SubjectPublicKeyInfo(( - ("algorithm", AlgorithmIdentifier(( - ("algorithm", ai["key_algorithm"]), - ("parameters", Any(key_params)), - ))), - ("subjectPublicKey", BitString(OctetString(pub_raw).encode())), - ))), - ("extensions", Extensions(exts)), -)) -cert = Certificate(( - ("tbsCertificate", tbs), - ("signatureAlgorithm", ai_sign), - ("signatureValue", BitString( - sign(curve, prv, ai["hasher"](tbs.encode()).digest()[::-1]) - if ca_ai is None else - sign(ca_ai["curve"], ca_prv, ca_ai["hasher"](tbs.encode()).digest()[::-1]) - )), -)) -out = stdout if args.out_cert is None else open(args.out_cert, "w") -print("-----BEGIN CERTIFICATE-----", file=out) -print(pem(cert), file=out) -print("-----END CERTIFICATE-----", file=out) diff --git a/pygost-5.13/pygost/asn1schemas/cms.py b/pygost-5.13/pygost/asn1schemas/cms.py deleted file mode 100644 index 8028d2b..0000000 --- a/pygost-5.13/pygost/asn1schemas/cms.py +++ /dev/null @@ -1,431 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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.oids import id_cms_mac_attr -from pygost.asn1schemas.oids import id_contentType -from pygost.asn1schemas.oids import id_digestedData -from pygost.asn1schemas.oids import id_encryptedData -from pygost.asn1schemas.oids import id_envelopedData -from pygost.asn1schemas.oids import id_Gost28147_89 -from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm -from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm_omac -from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_wrap_kexp15 -from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm -from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm_omac -from pygost.asn1schemas.oids import id_gostr3412_2015_magma_wrap_kexp15 -from pygost.asn1schemas.oids import id_messageDigest -from pygost.asn1schemas.oids import id_signedData -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256 -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512 -from pygost.asn1schemas.x509 import AlgorithmIdentifier -from pygost.asn1schemas.x509 import Certificate -from pygost.asn1schemas.x509 import CertificateSerialNumber -from pygost.asn1schemas.x509 import Name -from pygost.asn1schemas.x509 import SubjectPublicKeyInfo - - -class CMSVersion(Integer): - pass - - -class ContentType(ObjectIdentifier): - pass - - -class IssuerAndSerialNumber(Sequence): - schema = ( - ("issuer", Name()), - ("serialNumber", CertificateSerialNumber()), - ) - - -class KeyIdentifier(OctetString): - pass - - -class SubjectKeyIdentifier(KeyIdentifier): - pass - - -class RecipientIdentifier(Choice): - schema = ( - ("issuerAndSerialNumber", IssuerAndSerialNumber()), - ("subjectKeyIdentifier", SubjectKeyIdentifier(impl=tag_ctxp(0))), - ) - - -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 GostR3410KeyTransport2019(Sequence): - schema = ( - ("encryptedKey", OctetString()), - ("ephemeralPublicKey", SubjectPublicKeyInfo()), - ("ukm", OctetString()), - ) - - -class GostR341012KEGParameters(Sequence): - schema = ( - ("algorithm", ObjectIdentifier()), - ) - - -class KeyEncryptionAlgorithmIdentifier(AlgorithmIdentifier): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), { - id_gostr3412_2015_magma_wrap_kexp15: GostR341012KEGParameters(), - id_gostr3412_2015_kuznyechik_wrap_kexp15: GostR341012KEGParameters(), - }), - (("..", "encryptedKey"), { - id_tc26_gost3410_2012_256: GostR3410KeyTransport(), - id_tc26_gost3410_2012_512: GostR3410KeyTransport(), - id_gostr3412_2015_magma_wrap_kexp15: GostR3410KeyTransport2019(), - id_gostr3412_2015_kuznyechik_wrap_kexp15: GostR3410KeyTransport2019(), - }), - (("..", "recipientEncryptedKeys", any, "encryptedKey"), { - id_tc26_gost3410_2012_256: Gost2814789EncryptedKey(), - id_tc26_gost3410_2012_512: Gost2814789EncryptedKey(), - }), - ))), - ("parameters", Any(optional=True)), - ) - - -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", IssuerAndSerialNumber()), - # ("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 Gost2814789IV(OctetString): - bounds = (8, 8) - - -class Gost2814789Parameters(Sequence): - schema = ( - ("iv", Gost2814789IV()), - ("encryptionParamSet", ObjectIdentifier()), - ) - - -class Gost341215EncryptionParameters(Sequence): - schema = ( - ("ukm", OctetString()), - ) - - -class ContentEncryptionAlgorithmIdentifier(AlgorithmIdentifier): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), { - id_Gost28147_89: Gost2814789Parameters(), - id_gostr3412_2015_magma_ctracpkm: Gost341215EncryptionParameters(), - id_gostr3412_2015_kuznyechik_ctracpkm: Gost341215EncryptionParameters(), - id_gostr3412_2015_magma_ctracpkm_omac: Gost341215EncryptionParameters(), - id_gostr3412_2015_kuznyechik_ctracpkm_omac: Gost341215EncryptionParameters(), - }), - ))), - ("parameters", Any(optional=True)), - ) - - -class EncryptedContent(OctetString): - pass - - -class EncryptedContentInfo(Sequence): - schema = ( - ("contentType", ContentType()), - ("contentEncryptionAlgorithm", ContentEncryptionAlgorithmIdentifier()), - ("encryptedContent", EncryptedContent(impl=tag_ctxp(0), optional=True)), - ) - - -class Digest(OctetString): - pass - - -class AttributeValue(Any): - pass - - -class AttributeValues(SetOf): - schema = AttributeValue() - - -class EncryptedMac(OctetString): - pass - - -class Attribute(Sequence): - schema = ( - ("attrType", ObjectIdentifier(defines=( - (("attrValues",), { - id_contentType: ObjectIdentifier(), - id_messageDigest: Digest(), - id_cms_mac_attr: EncryptedMac(), - },), - ))), - ("attrValues", AttributeValues()), - ) - - -class UnprotectedAttributes(SetOf): - schema = Attribute() - bounds = (1, float("+inf")) - - -class CertificateChoices(Choice): - schema = ( - ("certificate", Certificate()), - # ("extendedCertificate", OctetString(impl=tag_ctxp(0))), - # ("v1AttrCert", AttributeCertificateV1(impl=tag_ctxc(1))), # V1 is osbolete - # ("v2AttrCert", AttributeCertificateV2(impl=tag_ctxc(2))), - # ("other", OtherCertificateFormat(impl=tag_ctxc(3))), - ) - - -class CertificateSet(SetOf): - schema = CertificateChoices() - - -class OriginatorInfo(Sequence): - schema = ( - ("certs", CertificateSet(impl=tag_ctxc(0), optional=True)), - # ("crls", RevocationInfoChoices(impl=tag_ctxc(1), optional=True)), - ) - - -class EnvelopedData(Sequence): - schema = ( - ("version", CMSVersion()), - ("originatorInfo", OriginatorInfo(impl=tag_ctxc(0), optional=True)), - ("recipientInfos", RecipientInfos()), - ("encryptedContentInfo", EncryptedContentInfo()), - ("unprotectedAttrs", UnprotectedAttributes(impl=tag_ctxc(1), optional=True)), - ) - - -class EncapsulatedContentInfo(Sequence): - schema = ( - ("eContentType", ContentType()), - ("eContent", OctetString(expl=tag_ctxc(0), optional=True)), - ) - - -class SignerIdentifier(Choice): - schema = ( - ("issuerAndSerialNumber", IssuerAndSerialNumber()), - ("subjectKeyIdentifier", SubjectKeyIdentifier(impl=tag_ctxp(0))), - ) - - -class DigestAlgorithmIdentifiers(SetOf): - schema = AlgorithmIdentifier() - - -class DigestAlgorithmIdentifier(AlgorithmIdentifier): - pass - - -class SignatureAlgorithmIdentifier(AlgorithmIdentifier): - pass - - -class SignatureValue(OctetString): - pass - - -class SignedAttributes(SetOf): - schema = Attribute() - bounds = (1, float("+inf")) - - -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 DigestedData(Sequence): - schema = ( - ("version", CMSVersion()), - ("digestAlgorithm", DigestAlgorithmIdentifier()), - ("encapContentInfo", EncapsulatedContentInfo()), - ("digest", Digest()), - ) - - -class EncryptedData(Sequence): - schema = ( - ("version", CMSVersion()), - ("encryptedContentInfo", EncryptedContentInfo()), - ("unprotectedAttrs", UnprotectedAttributes(impl=tag_ctxc(1), optional=True)), - ) - - -class ContentInfo(Sequence): - schema = ( - ("contentType", ContentType(defines=( - (("content",), { - id_digestedData: DigestedData(), - id_encryptedData: EncryptedData(), - id_envelopedData: EnvelopedData(), - id_signedData: SignedData(), - }), - ))), - ("content", Any(expl=tag_ctxc(0))), - ) diff --git a/pygost-5.13/pygost/asn1schemas/oids.py b/pygost-5.13/pygost/asn1schemas/oids.py deleted file mode 100644 index 4638900..0000000 --- a/pygost-5.13/pygost/asn1schemas/oids.py +++ /dev/null @@ -1,60 +0,0 @@ -from pyderasn import ObjectIdentifier - - -id_at_commonName = ObjectIdentifier("2.5.4.3") -id_at_countryName = ObjectIdentifier("2.5.4.6") -id_at_localityName = ObjectIdentifier("2.5.4.7") -id_at_stateOrProvinceName = ObjectIdentifier("2.5.4.8") -id_at_organizationName = ObjectIdentifier("2.5.4.10") - -id_pkcs7 = ObjectIdentifier("1.2.840.113549.1.7") -id_data = id_pkcs7 + (1,) -id_signedData = id_pkcs7 + (2,) -id_envelopedData = id_pkcs7 + (3,) -id_digestedData = id_pkcs7 + (5,) -id_encryptedData = id_pkcs7 + (6,) - -id_pkcs9 = ObjectIdentifier("1.2.840.113549.1.9") -id_contentType = id_pkcs9 + (3,) -id_messageDigest = id_pkcs9 + (4,) -id_pkcs9_certTypes_x509Certificate = ObjectIdentifier("1.2.840.113549.1.9.22.1") -id_pkcs12_bagtypes_keyBag = ObjectIdentifier("1.2.840.113549.1.12.10.1.1") -id_pkcs12_bagtypes_pkcs8ShroudedKeyBag = ObjectIdentifier("1.2.840.113549.1.12.10.1.2") -id_pkcs12_bagtypes_certBag = ObjectIdentifier("1.2.840.113549.1.12.10.1.3") - -id_Gost28147_89 = ObjectIdentifier("1.2.643.2.2.21") -id_GostR3410_2001_TestParamSet = ObjectIdentifier("1.2.643.2.2.35.0") -id_cms_mac_attr = ObjectIdentifier("1.2.643.7.1.0.6.1.1") -id_tc26_gost3410_2012_256 = ObjectIdentifier("1.2.643.7.1.1.1.1") -id_tc26_gost3410_2012_512 = ObjectIdentifier("1.2.643.7.1.1.1.2") -id_tc26_gost3411_2012_256 = ObjectIdentifier("1.2.643.7.1.1.2.2") -id_tc26_gost3411_2012_512 = ObjectIdentifier("1.2.643.7.1.1.2.3") -id_tc26_signwithdigest_gost3410_2012_256 = ObjectIdentifier("1.2.643.7.1.1.3.2") -id_tc26_signwithdigest_gost3410_2012_512 = ObjectIdentifier("1.2.643.7.1.1.3.3") -id_gostr3412_2015_magma_ctracpkm = ObjectIdentifier("1.2.643.7.1.1.5.1.1") -id_gostr3412_2015_magma_ctracpkm_omac = ObjectIdentifier("1.2.643.7.1.1.5.1.2") -id_gostr3412_2015_kuznyechik_ctracpkm = ObjectIdentifier("1.2.643.7.1.1.5.2.1") -id_gostr3412_2015_kuznyechik_ctracpkm_omac = ObjectIdentifier("1.2.643.7.1.1.5.2.2") -id_tc26_agreement_gost3410_2012_256 = ObjectIdentifier("1.2.643.7.1.1.6.1") -id_tc26_agreement_gost3410_2012_512 = ObjectIdentifier("1.2.643.7.1.1.6.2") -id_gostr3412_2015_magma_wrap_kexp15 = ObjectIdentifier("1.2.643.7.1.1.7.1.1") -id_gostr3412_2015_kuznyechik_wrap_kexp15 = ObjectIdentifier("1.2.643.7.1.1.7.2.1") -id_tc26_gost3410_2012_256_paramSetA = ObjectIdentifier("1.2.643.7.1.2.1.1.1") -id_tc26_gost3410_2012_256_paramSetB = ObjectIdentifier("1.2.643.7.1.2.1.1.2") -id_tc26_gost3410_2012_256_paramSetC = ObjectIdentifier("1.2.643.7.1.2.1.1.3") -id_tc26_gost3410_2012_256_paramSetD = ObjectIdentifier("1.2.643.7.1.2.1.1.4") -id_tc26_gost3410_2012_512_paramSetTest = ObjectIdentifier("1.2.643.7.1.2.1.2.0") -id_tc26_gost3410_2012_512_paramSetA = ObjectIdentifier("1.2.643.7.1.2.1.2.1") -id_tc26_gost3410_2012_512_paramSetB = ObjectIdentifier("1.2.643.7.1.2.1.2.2") -id_tc26_gost3410_2012_512_paramSetC = ObjectIdentifier("1.2.643.7.1.2.1.2.3") -id_tc26_gost_28147_param_Z = ObjectIdentifier("1.2.643.7.1.2.5.1.1") - -id_pbes2 = ObjectIdentifier("1.2.840.113549.1.5.13") -id_pbkdf2 = ObjectIdentifier("1.2.840.113549.1.5.12") - -id_at_commonName = ObjectIdentifier("2.5.4.3") -id_ce_basicConstraints = ObjectIdentifier("2.5.29.19") -id_ce_subjectKeyIdentifier = ObjectIdentifier("2.5.29.14") -id_ce_keyUsage = ObjectIdentifier("2.5.29.15") -id_ce_subjectAltName = ObjectIdentifier("2.5.29.17") -id_ce_authorityKeyIdentifier = ObjectIdentifier("2.5.29.35") diff --git a/pygost-5.13/pygost/asn1schemas/pfx.py b/pygost-5.13/pygost/asn1schemas/pfx.py deleted file mode 100644 index 27a87d0..0000000 --- a/pygost-5.13/pygost/asn1schemas/pfx.py +++ /dev/null @@ -1,250 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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.cms import Gost2814789Parameters -from pygost.asn1schemas.cms import Gost341215EncryptionParameters -from pygost.asn1schemas.oids import id_data -from pygost.asn1schemas.oids import id_encryptedData -from pygost.asn1schemas.oids import id_Gost28147_89 -from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm -from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm_omac -from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm -from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm_omac -from pygost.asn1schemas.oids import id_pbes2 -from pygost.asn1schemas.oids import id_pbkdf2 -from pygost.asn1schemas.oids import id_pkcs9_certTypes_x509Certificate -from pygost.asn1schemas.prvkey import PrivateKeyInfo -from pygost.asn1schemas.x509 import AlgorithmIdentifier -from pygost.asn1schemas.x509 import Certificate - - -class PBKDF2Salt(Choice): - 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()), - ) - - -class PBES2KDFs(AlgorithmIdentifier): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), {id_pbkdf2: PBKDF2Params()}), - ))), - ("parameters", Any(optional=True)), - ) - - -class PBES2Encs(AlgorithmIdentifier): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), { - id_Gost28147_89: Gost2814789Parameters(), - id_gostr3412_2015_magma_ctracpkm: Gost341215EncryptionParameters(), - id_gostr3412_2015_magma_ctracpkm_omac: Gost341215EncryptionParameters(), - id_gostr3412_2015_kuznyechik_ctracpkm: Gost341215EncryptionParameters(), - id_gostr3412_2015_kuznyechik_ctracpkm_omac: Gost341215EncryptionParameters(), - }), - ))), - ("parameters", Any(optional=True)), - ) - - -class PBES2Params(Sequence): - schema = ( - ("keyDerivationFunc", PBES2KDFs()), - ("encryptionScheme", PBES2Encs()), - ) - - -class EncryptionAlgorithmIdentifier(AlgorithmIdentifier): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), {id_pbes2: PBES2Params()}), - ))), - ("parameters", Any(optional=True)), - ) - - -class ContentEncryptionAlgorithmIdentifier(EncryptionAlgorithmIdentifier): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), {id_pbes2: PBES2Params()}), - ))), - ("parameters", Any(optional=True)), - ) - - -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(defines=( - (("bagValue",), {id_encryptedData: EncryptedData()}), - ))), - ("bagValue", PKCS12BagSet(expl=tag_ctxc(0))), - ("bagAttributes", PKCS12Attributes(optional=True)), - ) - - -class SafeContents(SequenceOf): - schema = SafeBag() - - -OctetStringSafeContents = SafeContents(expl=OctetString.tag_default) - - -class AuthSafe(Sequence): - schema = ( - ("contentType", ContentType(defines=( - (("content",), {id_data: OctetStringSafeContents()}), - ))), - ("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 - - -OctetStringX509Certificate = Certificate(expl=OctetString.tag_default) - - -class CertTypes(Any): - pass - - -class CertBag(Sequence): - schema = ( - ("certId", ObjectIdentifier(defines=( - (("certValue",), { - id_pkcs9_certTypes_x509Certificate: OctetStringX509Certificate(), - }), - ))), - ("certValue", CertTypes(expl=tag_ctxc(0))), - ) - - -class KeyBag(PrivateKeyInfo): - pass diff --git a/pygost-5.13/pygost/asn1schemas/pkcs10.py b/pygost-5.13/pygost/asn1schemas/pkcs10.py deleted file mode 100644 index dce45dd..0000000 --- a/pygost-5.13/pygost/asn1schemas/pkcs10.py +++ /dev/null @@ -1,49 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 #10 related structures (**NOT COMPLETE**) -""" - -from pyderasn import BitString -from pyderasn import Integer -from pyderasn import Sequence -from pyderasn import SetOf -from pyderasn import tag_ctxc - -from pygost.asn1schemas.cms import Attribute -from pygost.asn1schemas.x509 import AlgorithmIdentifier -from pygost.asn1schemas.x509 import Name -from pygost.asn1schemas.x509 import SubjectPublicKeyInfo - - -class Attributes(SetOf): - schema = Attribute() - - -class CertificationRequestInfo(Sequence): - schema = ( - ("version", Integer(0)), - ("subject", Name()), - ("subjectPKInfo", SubjectPublicKeyInfo()), - ("attributes", Attributes(impl=tag_ctxc(0))), - ) - - -class CertificationRequest(Sequence): - schema = ( - ("certificationRequestInfo", CertificationRequestInfo()), - ("signatureAlgorithm", AlgorithmIdentifier()), - ("signature", BitString()), - ) diff --git a/pygost-5.13/pygost/asn1schemas/prvkey.py b/pygost-5.13/pygost/asn1schemas/prvkey.py deleted file mode 100644 index 7da2533..0000000 --- a/pygost-5.13/pygost/asn1schemas/prvkey.py +++ /dev/null @@ -1,100 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 pyderasn import Any -from pyderasn import BitString -from pyderasn import Choice -from pyderasn import Integer -from pyderasn import Null -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 - -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256 -from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512 -from pygost.asn1schemas.x509 import GostR34102012PublicKeyParameters - - -class ECParameters(Choice): - schema = ( - ("namedCurve", ObjectIdentifier()), - ("implicitCurve", Null()), - # ("specifiedCurve", SpecifiedECDomain()), - ) - - -ecPrivkeyVer1 = Integer(1) - - -class ECPrivateKey(Sequence): - schema = ( - ("version", Integer(ecPrivkeyVer1)), - ("privateKey", OctetString()), - ("parameters", ECParameters(expl=tag_ctxc(0), optional=True)), - ("publicKey", BitString(expl=tag_ctxc(1), optional=True)), - ) - - -class PrivateKeyAlgorithmIdentifier(Sequence): - schema = ( - ("algorithm", ObjectIdentifier(defines=( - (("parameters",), { - id_tc26_gost3410_2012_256: GostR34102012PublicKeyParameters(), - id_tc26_gost3410_2012_512: GostR34102012PublicKeyParameters(), - }), - ))), - ("parameters", Any(optional=True)), - ) - - -class PrivateKey(OctetString): - pass - - -class AttributeValue(Any): - pass - - -class AttributeValues(SetOf): - schema = AttributeValue() - - -class Attribute(Sequence): - schema = ( - ("attrType", ObjectIdentifier()), - ("attrValues", AttributeValues()), - ) - - -class Attributes(SetOf): - schema = Attribute() - - -class PublicKey(BitString): - pass - - -class PrivateKeyInfo(Sequence): - schema = ( - ("version", Integer(0)), - ("privateKeyAlgorithm", PrivateKeyAlgorithmIdentifier()), - ("privateKey", PrivateKey()), - ("attributes", Attributes(impl=tag_ctxc(0), optional=True)), - ("publicKey", PublicKey(impl=tag_ctxp(1), optional=True)), - ) diff --git a/pygost-5.13/pygost/asn1schemas/x509.py b/pygost-5.13/pygost/asn1schemas/x509.py deleted file mode 100644 index 86ad7da..0000000 --- a/pygost-5.13/pygost/asn1schemas/x509.py +++ /dev/null @@ -1,262 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST 28147-89 block cipher - -This is implementation of :rfc:`5830` ECB, CNT, CFB and :rfc:`4357` -CBC modes of operation. N1, N2, K names are taken according to -specification's terminology. CNT and CFB modes can work with arbitrary -data lengths. -""" - -from functools import partial - -from pygost.gost3413 import pad2 -from pygost.gost3413 import pad_size -from pygost.gost3413 import unpad2 -from pygost.utils import hexdec -from pygost.utils import strxor -from pygost.utils import xrange - - -KEYSIZE = 32 -BLOCKSIZE = 8 -C1 = 0x01010104 -C2 = 0x01010101 - -# Sequence of K_i S-box applying for encryption and decryption -SEQ_ENCRYPT = ( - 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, - 7, 6, 5, 4, 3, 2, 1, 0, -) -SEQ_DECRYPT = ( - 0, 1, 2, 3, 4, 5, 6, 7, - 7, 6, 5, 4, 3, 2, 1, 0, - 7, 6, 5, 4, 3, 2, 1, 0, - 7, 6, 5, 4, 3, 2, 1, 0, -) - -# S-box parameters -DEFAULT_SBOX = "id-Gost28147-89-CryptoPro-A-ParamSet" -SBOXES = { - "id-Gost28147-89-TestParamSet": ( - (4, 2, 15, 5, 9, 1, 0, 8, 14, 3, 11, 12, 13, 7, 10, 6), - (12, 9, 15, 14, 8, 1, 3, 10, 2, 7, 4, 13, 6, 0, 11, 5), - (13, 8, 14, 12, 7, 3, 9, 10, 1, 5, 2, 4, 6, 15, 0, 11), - (14, 9, 11, 2, 5, 15, 7, 1, 0, 13, 12, 6, 10, 4, 3, 8), - (3, 14, 5, 9, 6, 8, 0, 13, 10, 11, 7, 12, 2, 1, 15, 4), - (8, 15, 6, 11, 1, 9, 12, 5, 13, 3, 7, 10, 0, 14, 2, 4), - (9, 11, 12, 0, 3, 6, 7, 5, 4, 8, 14, 15, 1, 10, 2, 13), - (12, 6, 5, 2, 11, 0, 9, 13, 3, 14, 7, 10, 15, 4, 1, 8), - ), - "id-Gost28147-89-CryptoPro-A-ParamSet": ( - (9, 6, 3, 2, 8, 11, 1, 7, 10, 4, 14, 15, 12, 0, 13, 5), - (3, 7, 14, 9, 8, 10, 15, 0, 5, 2, 6, 12, 11, 4, 13, 1), - (14, 4, 6, 2, 11, 3, 13, 8, 12, 15, 5, 10, 0, 7, 1, 9), - (14, 7, 10, 12, 13, 1, 3, 9, 0, 2, 11, 4, 15, 8, 5, 6), - (11, 5, 1, 9, 8, 13, 15, 0, 14, 4, 2, 3, 12, 7, 10, 6), - (3, 10, 13, 12, 1, 2, 0, 11, 7, 5, 9, 4, 8, 15, 14, 6), - (1, 13, 2, 9, 7, 10, 6, 0, 8, 12, 4, 5, 15, 3, 11, 14), - (11, 10, 15, 5, 0, 12, 14, 8, 6, 2, 3, 9, 1, 7, 13, 4), - ), - "id-Gost28147-89-CryptoPro-B-ParamSet": ( - (8, 4, 11, 1, 3, 5, 0, 9, 2, 14, 10, 12, 13, 6, 7, 15), - (0, 1, 2, 10, 4, 13, 5, 12, 9, 7, 3, 15, 11, 8, 6, 14), - (14, 12, 0, 10, 9, 2, 13, 11, 7, 5, 8, 15, 3, 6, 1, 4), - (7, 5, 0, 13, 11, 6, 1, 2, 3, 10, 12, 15, 4, 14, 9, 8), - (2, 7, 12, 15, 9, 5, 10, 11, 1, 4, 0, 13, 6, 8, 14, 3), - (8, 3, 2, 6, 4, 13, 14, 11, 12, 1, 7, 15, 10, 0, 9, 5), - (5, 2, 10, 11, 9, 1, 12, 3, 7, 4, 13, 0, 6, 15, 8, 14), - (0, 4, 11, 14, 8, 3, 7, 1, 10, 2, 9, 6, 15, 13, 5, 12), - ), - "id-Gost28147-89-CryptoPro-C-ParamSet": ( - (1, 11, 12, 2, 9, 13, 0, 15, 4, 5, 8, 14, 10, 7, 6, 3), - (0, 1, 7, 13, 11, 4, 5, 2, 8, 14, 15, 12, 9, 10, 6, 3), - (8, 2, 5, 0, 4, 9, 15, 10, 3, 7, 12, 13, 6, 14, 1, 11), - (3, 6, 0, 1, 5, 13, 10, 8, 11, 2, 9, 7, 14, 15, 12, 4), - (8, 13, 11, 0, 4, 5, 1, 2, 9, 3, 12, 14, 6, 15, 10, 7), - (12, 9, 11, 1, 8, 14, 2, 4, 7, 3, 6, 5, 10, 0, 15, 13), - (10, 9, 6, 8, 13, 14, 2, 0, 15, 3, 5, 11, 4, 1, 12, 7), - (7, 4, 0, 5, 10, 2, 15, 14, 12, 6, 1, 11, 13, 9, 3, 8), - ), - "id-Gost28147-89-CryptoPro-D-ParamSet": ( - (15, 12, 2, 10, 6, 4, 5, 0, 7, 9, 14, 13, 1, 11, 8, 3), - (11, 6, 3, 4, 12, 15, 14, 2, 7, 13, 8, 0, 5, 10, 9, 1), - (1, 12, 11, 0, 15, 14, 6, 5, 10, 13, 4, 8, 9, 3, 7, 2), - (1, 5, 14, 12, 10, 7, 0, 13, 6, 2, 11, 4, 9, 3, 15, 8), - (0, 12, 8, 9, 13, 2, 10, 11, 7, 3, 6, 5, 4, 14, 15, 1), - (8, 0, 15, 3, 2, 5, 14, 11, 1, 10, 4, 7, 12, 9, 13, 6), - (3, 0, 6, 15, 1, 14, 9, 2, 13, 8, 12, 4, 11, 10, 5, 7), - (1, 10, 6, 8, 15, 11, 0, 4, 12, 3, 5, 9, 7, 13, 2, 14), - ), - "id-tc26-gost-28147-param-Z": ( - (12, 4, 6, 2, 10, 5, 11, 9, 14, 8, 13, 7, 0, 3, 15, 1), - (6, 8, 2, 3, 9, 10, 5, 12, 1, 14, 4, 7, 11, 13, 0, 15), - (11, 3, 5, 8, 2, 15, 10, 13, 14, 1, 7, 4, 12, 9, 6, 0), - (12, 8, 2, 1, 13, 4, 15, 6, 7, 0, 10, 5, 3, 14, 9, 11), - (7, 15, 5, 10, 8, 1, 6, 13, 0, 9, 3, 14, 11, 4, 2, 12), - (5, 13, 15, 6, 9, 2, 12, 10, 11, 7, 8, 1, 4, 3, 14, 0), - (8, 14, 2, 5, 6, 9, 1, 12, 15, 4, 11, 0, 13, 10, 3, 7), - (1, 7, 14, 13, 0, 5, 8, 3, 4, 15, 10, 6, 9, 12, 11, 2), - ), - "id-GostR3411-94-TestParamSet": ( - (4, 10, 9, 2, 13, 8, 0, 14, 6, 11, 1, 12, 7, 15, 5, 3), - (14, 11, 4, 12, 6, 13, 15, 10, 2, 3, 8, 1, 0, 7, 5, 9), - (5, 8, 1, 13, 10, 3, 4, 2, 14, 15, 12, 7, 6, 0, 9, 11), - (7, 13, 10, 1, 0, 8, 9, 15, 14, 4, 6, 12, 11, 2, 5, 3), - (6, 12, 7, 1, 5, 15, 13, 8, 4, 10, 9, 14, 0, 3, 11, 2), - (4, 11, 10, 0, 7, 2, 1, 13, 3, 6, 8, 5, 9, 12, 15, 14), - (13, 11, 4, 1, 3, 15, 5, 9, 0, 10, 14, 7, 6, 8, 2, 12), - (1, 15, 13, 0, 5, 7, 10, 4, 9, 2, 3, 14, 6, 11, 8, 12), - ), - "id-GostR3411-94-CryptoProParamSet": ( - (10, 4, 5, 6, 8, 1, 3, 7, 13, 12, 14, 0, 9, 2, 11, 15), - (5, 15, 4, 0, 2, 13, 11, 9, 1, 7, 6, 3, 12, 14, 10, 8), - (7, 15, 12, 14, 9, 4, 1, 0, 3, 11, 5, 2, 6, 10, 8, 13), - (4, 10, 7, 12, 0, 15, 2, 8, 14, 1, 6, 5, 13, 11, 9, 3), - (7, 6, 4, 11, 9, 12, 2, 10, 1, 8, 0, 14, 15, 13, 3, 5), - (7, 6, 2, 4, 13, 9, 15, 0, 10, 1, 5, 11, 8, 14, 12, 3), - (13, 14, 4, 1, 7, 0, 5, 10, 3, 12, 8, 15, 6, 2, 9, 11), - (1, 3, 10, 9, 5, 11, 4, 15, 8, 6, 7, 14, 13, 0, 2, 12), - ), - "EACParamSet": ( - (11, 4, 8, 10, 9, 7, 0, 3, 1, 6, 2, 15, 14, 5, 12, 13), - (1, 7, 14, 9, 11, 3, 15, 12, 0, 5, 4, 6, 13, 10, 8, 2), - (7, 3, 1, 9, 2, 4, 13, 15, 8, 10, 12, 6, 5, 0, 11, 14), - (10, 5, 15, 7, 14, 11, 3, 9, 2, 8, 1, 12, 0, 4, 6, 13), - (0, 14, 6, 11, 9, 3, 8, 4, 12, 15, 10, 5, 13, 7, 1, 2), - (9, 2, 11, 12, 0, 4, 5, 6, 3, 15, 13, 8, 1, 7, 14, 10), - (4, 0, 14, 1, 5, 11, 8, 3, 12, 2, 9, 7, 6, 10, 13, 15), - (7, 14, 12, 13, 9, 4, 8, 15, 10, 2, 6, 0, 3, 11, 5, 1), - ), -} -SBOXES["AppliedCryptography"] = SBOXES["id-GostR3411-94-TestParamSet"] - - -def _K(s, _in): - """S-box substitution - - :param s: S-box - :param _in: 32-bit word - :returns: substituted 32-bit word - """ - return ( - (s[0][(_in >> 0) & 0x0F] << 0) + - (s[1][(_in >> 4) & 0x0F] << 4) + - (s[2][(_in >> 8) & 0x0F] << 8) + - (s[3][(_in >> 12) & 0x0F] << 12) + - (s[4][(_in >> 16) & 0x0F] << 16) + - (s[5][(_in >> 20) & 0x0F] << 20) + - (s[6][(_in >> 24) & 0x0F] << 24) + - (s[7][(_in >> 28) & 0x0F] << 28) - ) - - -def block2ns(data): - """Convert block to N1 and N2 integers - """ - data = bytearray(data) - return ( - data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24, - data[4] | data[5] << 8 | data[6] << 16 | data[7] << 24, - ) - - -def ns2block(ns): - """Convert N1 and N2 integers to 8-byte block - """ - n1, n2 = ns - return bytes(bytearray(( - (n2 >> 0) & 0xFF, (n2 >> 8) & 0xFF, (n2 >> 16) & 0xFF, (n2 >> 24) & 0xFF, - (n1 >> 0) & 0xFF, (n1 >> 8) & 0xFF, (n1 >> 16) & 0xFF, (n1 >> 24) & 0xFF, - ))) - - -def _shift11(x): - """11-bit cyclic shift - """ - return ((x << 11) & (2 ** 32 - 1)) | ((x >> (32 - 11)) & (2 ** 32 - 1)) - - -def validate_key(key): - if len(key) != KEYSIZE: - raise ValueError("Invalid key size") - - -def validate_iv(iv): - if len(iv) != BLOCKSIZE: - raise ValueError("Invalid IV size") - - -def validate_sbox(sbox): - if sbox not in SBOXES: - raise ValueError("Unknown sbox supplied") - - -def xcrypt(seq, sbox, key, ns): - """Perform full-round single-block operation - - :param seq: sequence of K_i S-box applying (either encrypt or decrypt) - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :param bytes key: 256-bit encryption key - :param ns: N1 and N2 integers - :type ns: (int, int) - :returns: resulting N1 and N2 - :rtype: (int, int) - """ - s = SBOXES[sbox] - w = bytearray(key) - x = [ - w[0 + i * 4] | - w[1 + i * 4] << 8 | - w[2 + i * 4] << 16 | - w[3 + i * 4] << 24 for i in range(8) - ] - n1, n2 = ns - for i in seq: - n1, n2 = _shift11(_K(s, (n1 + x[i]) % (2 ** 32))) ^ n2, n1 - return n1, n2 - - -def encrypt(sbox, key, ns): - """Encrypt single block - """ - return xcrypt(SEQ_ENCRYPT, sbox, key, ns) - - -def decrypt(sbox, key, ns): - """Decrypt single block - """ - return xcrypt(SEQ_DECRYPT, sbox, key, ns) - - -def ecb(key, data, action, sbox=DEFAULT_SBOX): - """ECB mode of operation - - :param bytes key: encryption key - :param data: plaintext - :type data: bytes, multiple of BLOCKSIZE - :param func action: "encrypt"/"decrypt" - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :returns: ciphertext - :rtype: bytes - """ - validate_key(key) - validate_sbox(sbox) - if not data or len(data) % BLOCKSIZE != 0: - raise ValueError("Data is not blocksize aligned") - result = [] - for i in xrange(0, len(data), BLOCKSIZE): - result.append(ns2block(action( - sbox, key, block2ns(data[i:i + BLOCKSIZE]) - ))) - return b"".join(result) - - -ecb_encrypt = partial(ecb, action=encrypt) -ecb_decrypt = partial(ecb, action=decrypt) - - -def cbc_encrypt(key, data, iv=8 * b"\x00", pad=True, sbox=DEFAULT_SBOX, mesh=False): - """CBC encryption mode of operation - - :param bytes key: encryption key - :param bytes data: plaintext - :param iv: initialization vector - :type iv: bytes, BLOCKSIZE length - :type bool pad: perform ISO/IEC 7816-4 padding - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :param bool mesh: enable key meshing - :returns: ciphertext - :rtype: bytes - - 34.13-2015 padding method 2 is used. - """ - validate_key(key) - validate_iv(iv) - validate_sbox(sbox) - if not data: - raise ValueError("No data supplied") - if pad: - data = pad2(data, BLOCKSIZE) - if len(data) % BLOCKSIZE != 0: - raise ValueError("Data is not blocksize aligned") - ciphertext = [iv] - for i in xrange(0, len(data), BLOCKSIZE): - if mesh and i >= MESH_MAX_DATA and i % MESH_MAX_DATA == 0: - key, _ = meshing(key, iv, sbox=sbox) - ciphertext.append(ns2block(encrypt(sbox, key, block2ns( - strxor(ciphertext[-1], data[i:i + BLOCKSIZE]) - )))) - return b"".join(ciphertext) - - -def cbc_decrypt(key, data, pad=True, sbox=DEFAULT_SBOX, mesh=False): - """CBC decryption mode of operation - - :param bytes key: encryption key - :param bytes data: ciphertext - :type bool pad: perform ISO/IEC 7816-4 unpadding after decryption - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :param bool mesh: enable key meshing - :returns: plaintext - :rtype: bytes - """ - validate_key(key) - validate_sbox(sbox) - if not data or len(data) % BLOCKSIZE != 0: - raise ValueError("Data is not blocksize aligned") - if len(data) < 2 * BLOCKSIZE: - raise ValueError("There is no either data, or IV in ciphertext") - iv = data[:BLOCKSIZE] - plaintext = [] - for i in xrange(BLOCKSIZE, len(data), BLOCKSIZE): - if ( - mesh and - (i - BLOCKSIZE) >= MESH_MAX_DATA and - (i - BLOCKSIZE) % MESH_MAX_DATA == 0 - ): - key, _ = meshing(key, iv, sbox=sbox) - plaintext.append(strxor( - ns2block(decrypt(sbox, key, block2ns(data[i:i + BLOCKSIZE]))), - data[i - BLOCKSIZE:i], - )) - if pad: - plaintext[-1] = unpad2(plaintext[-1], BLOCKSIZE) - return b"".join(plaintext) - - -def cnt(key, data, iv=8 * b"\x00", sbox=DEFAULT_SBOX): - """Counter mode of operation - - :param bytes key: encryption key - :param bytes data: plaintext - :param iv: initialization vector - :type iv: bytes, BLOCKSIZE length - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :returns: ciphertext - :rtype: bytes - - For decryption you use the same function again. - """ - validate_key(key) - validate_iv(iv) - validate_sbox(sbox) - if not data: - raise ValueError("No data supplied") - n2, n1 = encrypt(sbox, key, block2ns(iv)) - gamma = [] - for _ in xrange(0, len(data) + pad_size(len(data), BLOCKSIZE), BLOCKSIZE): - n1 = (n1 + C2) % (2 ** 32) - n2 = (n2 + C1) % (2 ** 32 - 1) - gamma.append(ns2block(encrypt(sbox, key, (n1, n2)))) - return strxor(b"".join(gamma), data) - - -MESH_CONST = hexdec("6900722264C904238D3ADB9646E92AC418FEAC9400ED0712C086DCC2EF4CA92B") -MESH_MAX_DATA = 1024 - - -def meshing(key, iv, sbox=DEFAULT_SBOX): - """:rfc:`4357` key meshing - """ - key = ecb_decrypt(key, MESH_CONST, sbox=sbox) - iv = ecb_encrypt(key, iv, sbox=sbox) - return key, iv - - -def cfb_encrypt(key, data, iv=8 * b"\x00", sbox=DEFAULT_SBOX, mesh=False): - """CFB encryption mode of operation - - :param bytes key: encryption key - :param bytes data: plaintext - :param iv: initialization vector - :type iv: bytes, BLOCKSIZE length - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :param bool mesh: enable key meshing - :returns: ciphertext - :rtype: bytes - """ - validate_key(key) - validate_iv(iv) - validate_sbox(sbox) - if not data: - raise ValueError("No data supplied") - ciphertext = [iv] - for i in xrange(0, len(data) + pad_size(len(data), BLOCKSIZE), BLOCKSIZE): - if mesh and i >= MESH_MAX_DATA and i % MESH_MAX_DATA == 0: - key, iv = meshing(key, ciphertext[-1], sbox=sbox) - ciphertext.append(strxor( - data[i:i + BLOCKSIZE], - ns2block(encrypt(sbox, key, block2ns(iv))), - )) - continue - ciphertext.append(strxor( - data[i:i + BLOCKSIZE], - ns2block(encrypt(sbox, key, block2ns(ciphertext[-1]))), - )) - return b"".join(ciphertext[1:]) - - -def cfb_decrypt(key, data, iv=8 * b"\x00", sbox=DEFAULT_SBOX, mesh=False): - """CFB decryption mode of operation - - :param bytes key: encryption key - :param bytes data: plaintext - :param iv: initialization vector - :type iv: bytes, BLOCKSIZE length - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - :param bool mesh: enable key meshing - :returns: ciphertext - :rtype: bytes - """ - validate_key(key) - validate_iv(iv) - validate_sbox(sbox) - if not data: - raise ValueError("No data supplied") - plaintext = [] - data = iv + data - for i in xrange(BLOCKSIZE, len(data) + pad_size(len(data), BLOCKSIZE), BLOCKSIZE): - if ( - mesh and - (i - BLOCKSIZE) >= MESH_MAX_DATA and - (i - BLOCKSIZE) % MESH_MAX_DATA == 0 - ): - key, iv = meshing(key, data[i - BLOCKSIZE:i], sbox=sbox) - plaintext.append(strxor( - data[i:i + BLOCKSIZE], - ns2block(encrypt(sbox, key, block2ns(iv))), - )) - continue - plaintext.append(strxor( - data[i:i + BLOCKSIZE], - ns2block(encrypt(sbox, key, block2ns(data[i - BLOCKSIZE:i]))), - )) - return b"".join(plaintext) diff --git a/pygost-5.13/pygost/gost28147_mac.py b/pygost-5.13/pygost/gost28147_mac.py deleted file mode 100644 index aab2805..0000000 --- a/pygost-5.13/pygost/gost28147_mac.py +++ /dev/null @@ -1,99 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST 28147-89 MAC -""" - -from copy import copy - -from pygost.gost28147 import block2ns -from pygost.gost28147 import BLOCKSIZE -from pygost.gost28147 import DEFAULT_SBOX -from pygost.gost28147 import ns2block -from pygost.gost28147 import validate_iv -from pygost.gost28147 import validate_key -from pygost.gost28147 import validate_sbox -from pygost.gost28147 import xcrypt -from pygost.gost3413 import pad1 -from pygost.iface import PEP247 -from pygost.utils import strxor -from pygost.utils import xrange - -digest_size = 8 -SEQ_MAC = ( - 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, -) - - -class MAC(PEP247): - """GOST 28147-89 MAC mode of operation - - >>> m = MAC(key=key) - >>> m.update("some data") - >>> m.update("another data") - >>> m.hexdigest()[:8] - 'a687a08b' - """ - digest_size = digest_size - - def __init__(self, key, data=b"", iv=8 * b"\x00", sbox=DEFAULT_SBOX): - """ - :param key: authentication key - :type key: bytes, 32 bytes - :param iv: initialization vector - :type iv: bytes, BLOCKSIZE length - :param sbox: S-box parameters to use - :type sbox: str, SBOXES'es key - """ - validate_key(key) - validate_iv(iv) - validate_sbox(sbox) - self.key = key - self.data = data - self.iv = iv - self.sbox = sbox - - def copy(self): - return MAC(self.key, copy(self.data), self.iv, self.sbox) - - def update(self, data): - """Append data that has to be authenticated - """ - self.data += data - - def digest(self): - """Get MAC tag of supplied data - - You have to provide at least single byte of data. - If you want to produce tag length of 3 bytes, then - ``digest()[:3]``. - """ - if not self.data: - raise ValueError("No data processed") - data = pad1(self.data, BLOCKSIZE) - prev = block2ns(self.iv)[::-1] - for i in xrange(0, len(data), BLOCKSIZE): - prev = xcrypt( - SEQ_MAC, self.sbox, self.key, block2ns(strxor( - data[i:i + BLOCKSIZE], - ns2block(prev), - )), - )[::-1] - return ns2block(prev) - - -def new(key, data=b"", iv=8 * b"\x00", sbox=DEFAULT_SBOX): - return MAC(key, data, iv, sbox) diff --git a/pygost-5.13/pygost/gost3410.py b/pygost-5.13/pygost/gost3410.py deleted file mode 100644 index c12b170..0000000 --- a/pygost-5.13/pygost/gost3410.py +++ /dev/null @@ -1,412 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST R 34.10 public-key signature function. - -This is implementation of GOST R 34.10-2001 (:rfc:`5832`), GOST R -34.10-2012 (:rfc:`7091`). The difference between 2001 and 2012 is the -key, digest and signature lengths. -""" - -from os import urandom - -from pygost.utils import bytes2long -from pygost.utils import hexdec -from pygost.utils import long2bytes -from pygost.utils import modinvert - - -def point_size(point): - """Determine is it either 256 or 512 bit point - """ - return (512 // 8) if point.bit_length() > 256 else (256 // 8) - - -class GOST3410Curve(object): - """GOST 34.10 validated curve - - >>> curve = CURVES["id-GostR3410-2001-TestParamSet"] - >>> prv = prv_unmarshal(urandom(32)) - >>> signature = sign(curve, prv, GOST341194(data).digest()) - >>> pub = public_key(curve, prv) - >>> verify(curve, pub, GOST341194(data).digest(), signature) - True - - :param long p: characteristic of the underlying prime field - :param long q: elliptic curve subgroup order - :param long a, b: coefficients of the equation of the elliptic curve in - the canonical form - :param long x, y: the coordinate of the point P (generator of the - subgroup of order q) of the elliptic curve in - the canonical form - :param long e, d: coefficients of the equation of the elliptic curve in - the twisted Edwards form - :param str name: human-readable curve name - """ - - def __init__(self, p, q, a, b, x, y, cofactor=1, e=None, d=None, name=None): - self.p = p - self.q = q - self.a = a - self.b = b - self.x = x - self.y = y - self.cofactor = cofactor - self.e = e - self.d = d - if not self.contains((x, y)): - raise ValueError("Invalid parameters") - self._st = None - self.name = name - - @property - def point_size(self): - return point_size(self.p) - - def __repr__(self): - return "<%s: %s>" % (self.__class__.__name__, self.name) - - def pos(self, v): - """Make positive number - """ - if v < 0: - return v + self.p - return v - - def contains(self, point): - """Is point on the curve? - - :type point: (long, long) - """ - x, y = point - r1 = y * y % self.p - r2 = ((x * x + self.a) * x + self.b) % self.p - return r1 == self.pos(r2) - - def _add(self, p1x, p1y, p2x, p2y): - if p1x == p2x and p1y == p2y: - # double - t = ((3 * p1x * p1x + self.a) * modinvert(2 * p1y, self.p)) % self.p - else: - tx = self.pos(p2x - p1x) % self.p - ty = self.pos(p2y - p1y) % self.p - t = (ty * modinvert(tx, self.p)) % self.p - tx = self.pos(t * t - p1x - p2x) % self.p - ty = self.pos(t * (p1x - tx) - p1y) % self.p - return tx, ty - - def exp(self, degree, x=None, y=None): - x = x or self.x - y = y or self.y - tx = x - ty = y - if degree == 0: - raise ValueError("Bad degree value") - degree -= 1 - while degree != 0: - if degree & 1 == 1: - tx, ty = self._add(tx, ty, x, y) - degree = degree >> 1 - x, y = self._add(x, y, x, y) - return tx, ty - - def st(self): - """Compute s/t parameters for twisted Edwards curve points conversion - """ - if self.e is None or self.d is None: - raise ValueError("Non twisted Edwards curve") - if self._st is not None: - return self._st - self._st = ( - self.pos(self.e - self.d) * modinvert(4, self.p) % self.p, - (self.e + self.d) * modinvert(6, self.p) % self.p, - ) - return self._st - - -CURVES = { - "GostR3410_2001_ParamSet_cc": GOST3410Curve( - p=bytes2long(hexdec("C0000000000000000000000000000000000000000000000000000000000003C7")), - q=bytes2long(hexdec("5fffffffffffffffffffffffffffffff606117a2f4bde428b7458a54b6e87b85")), - a=bytes2long(hexdec("C0000000000000000000000000000000000000000000000000000000000003c4")), - b=bytes2long(hexdec("2d06B4265ebc749ff7d0f1f1f88232e81632e9088fd44b7787d5e407e955080c")), - x=bytes2long(hexdec("0000000000000000000000000000000000000000000000000000000000000002")), - y=bytes2long(hexdec("a20e034bf8813ef5c18d01105e726a17eb248b264ae9706f440bedc8ccb6b22c")), - ), - "id-GostR3410-2001-TestParamSet": GOST3410Curve( - p=bytes2long(hexdec("8000000000000000000000000000000000000000000000000000000000000431")), - q=bytes2long(hexdec("8000000000000000000000000000000150FE8A1892976154C59CFC193ACCF5B3")), - a=bytes2long(hexdec("0000000000000000000000000000000000000000000000000000000000000007")), - b=bytes2long(hexdec("5FBFF498AA938CE739B8E022FBAFEF40563F6E6A3472FC2A514C0CE9DAE23B7E")), - x=bytes2long(hexdec("0000000000000000000000000000000000000000000000000000000000000002")), - y=bytes2long(hexdec("08E2A8A0E65147D4BD6316030E16D19C85C97F0A9CA267122B96ABBCEA7E8FC8")), - ), - "id-tc26-gost-3410-12-256-paramSetA": GOST3410Curve( - p=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97")), - q=bytes2long(hexdec("400000000000000000000000000000000FD8CDDFC87B6635C115AF556C360C67")), - a=bytes2long(hexdec("C2173F1513981673AF4892C23035A27CE25E2013BF95AA33B22C656F277E7335")), - b=bytes2long(hexdec("295F9BAE7428ED9CCC20E7C359A9D41A22FCCD9108E17BF7BA9337A6F8AE9513")), - x=bytes2long(hexdec("91E38443A5E82C0D880923425712B2BB658B9196932E02C78B2582FE742DAA28")), - y=bytes2long(hexdec("32879423AB1A0375895786C4BB46E9565FDE0B5344766740AF268ADB32322E5C")), - cofactor=4, - e=0x01, - d=bytes2long(hexdec("0605F6B7C183FA81578BC39CFAD518132B9DF62897009AF7E522C32D6DC7BFFB")), - ), - "id-tc26-gost-3410-12-256-paramSetB": GOST3410Curve( - p=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97")), - q=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893")), - a=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94")), - b=bytes2long(hexdec("00000000000000000000000000000000000000000000000000000000000000a6")), - x=bytes2long(hexdec("0000000000000000000000000000000000000000000000000000000000000001")), - y=bytes2long(hexdec("8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14")), - ), - "id-tc26-gost-3410-12-256-paramSetC": GOST3410Curve( - p=bytes2long(hexdec("8000000000000000000000000000000000000000000000000000000000000C99")), - q=bytes2long(hexdec("800000000000000000000000000000015F700CFFF1A624E5E497161BCC8A198F")), - a=bytes2long(hexdec("8000000000000000000000000000000000000000000000000000000000000C96")), - b=bytes2long(hexdec("3E1AF419A269A5F866A7D3C25C3DF80AE979259373FF2B182F49D4CE7E1BBC8B")), - x=bytes2long(hexdec("0000000000000000000000000000000000000000000000000000000000000001")), - y=bytes2long(hexdec("3FA8124359F96680B83D1C3EB2C070E5C545C9858D03ECFB744BF8D717717EFC")), - ), - "id-tc26-gost-3410-12-256-paramSetD": GOST3410Curve( - p=bytes2long(hexdec("9B9F605F5A858107AB1EC85E6B41C8AACF846E86789051D37998F7B9022D759B")), - q=bytes2long(hexdec("9B9F605F5A858107AB1EC85E6B41C8AA582CA3511EDDFB74F02F3A6598980BB9")), - a=bytes2long(hexdec("9B9F605F5A858107AB1EC85E6B41C8AACF846E86789051D37998F7B9022D7598")), - b=bytes2long(hexdec("000000000000000000000000000000000000000000000000000000000000805a")), - x=bytes2long(hexdec("0000000000000000000000000000000000000000000000000000000000000000")), - y=bytes2long(hexdec("41ECE55743711A8C3CBF3783CD08C0EE4D4DC440D4641A8F366E550DFDB3BB67")), - ), - "id-tc26-gost-3410-12-512-paramSetTest": GOST3410Curve( - p=bytes2long(hexdec("4531ACD1FE0023C7550D267B6B2FEE80922B14B2FFB90F04D4EB7C09B5D2D15DF1D852741AF4704A0458047E80E4546D35B8336FAC224DD81664BBF528BE6373")), - q=bytes2long(hexdec("4531ACD1FE0023C7550D267B6B2FEE80922B14B2FFB90F04D4EB7C09B5D2D15DA82F2D7ECB1DBAC719905C5EECC423F1D86E25EDBE23C595D644AAF187E6E6DF")), - a=7, - b=bytes2long(hexdec("1CFF0806A31116DA29D8CFA54E57EB748BC5F377E49400FDD788B649ECA1AC4361834013B2AD7322480A89CA58E0CF74BC9E540C2ADD6897FAD0A3084F302ADC")), - x=bytes2long(hexdec("24D19CC64572EE30F396BF6EBBFD7A6C5213B3B3D7057CC825F91093A68CD762FD60611262CD838DC6B60AA7EEE804E28BC849977FAC33B4B530F1B120248A9A")), - y=bytes2long(hexdec("2BB312A43BD2CE6E0D020613C857ACDDCFBF061E91E5F2C3F32447C259F39B2C83AB156D77F1496BF7EB3351E1EE4E43DC1A18B91B24640B6DBB92CB1ADD371E")), - ), - "id-tc26-gost-3410-12-512-paramSetA": GOST3410Curve( - p=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC7")), - q=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27E69532F48D89116FF22B8D4E0560609B4B38ABFAD2B85DCACDB1411F10B275")), - a=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC4")), - b=bytes2long(hexdec("E8C2505DEDFC86DDC1BD0B2B6667F1DA34B82574761CB0E879BD081CFD0B6265EE3CB090F30D27614CB4574010DA90DD862EF9D4EBEE4761503190785A71C760")), - x=bytes2long(hexdec("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003")), - y=bytes2long(hexdec("7503CFE87A836AE3A61B8816E25450E6CE5E1C93ACF1ABC1778064FDCBEFA921DF1626BE4FD036E93D75E6A50E3A41E98028FE5FC235F5B889A589CB5215F2A4")), - ), - "id-tc26-gost-3410-12-512-paramSetB": GOST3410Curve( - p=bytes2long(hexdec("8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006F")), - q=bytes2long(hexdec("800000000000000000000000000000000000000000000000000000000000000149A1EC142565A545ACFDB77BD9D40CFA8B996712101BEA0EC6346C54374F25BD")), - a=bytes2long(hexdec("8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006C")), - b=bytes2long(hexdec("687D1B459DC841457E3E06CF6F5E2517B97C7D614AF138BCBF85DC806C4B289F3E965D2DB1416D217F8B276FAD1AB69C50F78BEE1FA3106EFB8CCBC7C5140116")), - x=bytes2long(hexdec("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002")), - y=bytes2long(hexdec("1A8F7EDA389B094C2C071E3647A8940F3C123B697578C213BE6DD9E6C8EC7335DCB228FD1EDF4A39152CBCAAF8C0398828041055F94CEEEC7E21340780FE41BD")), - ), - "id-tc26-gost-3410-12-512-paramSetC": GOST3410Curve( - p=bytes2long(hexdec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC7")), - q=bytes2long(hexdec("3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC98CDBA46506AB004C33A9FF5147502CC8EDA9E7A769A12694623CEF47F023ED")), - a=bytes2long(hexdec("DC9203E514A721875485A529D2C722FB187BC8980EB866644DE41C68E143064546E861C0E2C9EDD92ADE71F46FCF50FF2AD97F951FDA9F2A2EB6546F39689BD3")), - b=bytes2long(hexdec("B4C4EE28CEBC6C2C8AC12952CF37F16AC7EFB6A9F69F4B57FFDA2E4F0DE5ADE038CBC2FFF719D2C18DE0284B8BFEF3B52B8CC7A5F5BF0A3C8D2319A5312557E1")), - x=bytes2long(hexdec("E2E31EDFC23DE7BDEBE241CE593EF5DE2295B7A9CBAEF021D385F7074CEA043AA27272A7AE602BF2A7B9033DB9ED3610C6FB85487EAE97AAC5BC7928C1950148")), - y=bytes2long(hexdec("F5CE40D95B5EB899ABBCCFF5911CB8577939804D6527378B8C108C3D2090FF9BE18E2D33E3021ED2EF32D85822423B6304F726AA854BAE07D0396E9A9ADDC40F")), - cofactor=4, - e=0x01, - d=bytes2long(hexdec("9E4F5D8C017D8D9F13A5CF3CDF5BFE4DAB402D54198E31EBDE28A0621050439CA6B39E0A515C06B304E2CE43E79E369E91A0CFC2BC2A22B4CA302DBB33EE7550")), - ), -} -CURVES["id-GostR3410-2001-CryptoPro-A-ParamSet"] = CURVES["id-tc26-gost-3410-12-256-paramSetB"] -CURVES["id-GostR3410-2001-CryptoPro-B-ParamSet"] = CURVES["id-tc26-gost-3410-12-256-paramSetC"] -CURVES["id-GostR3410-2001-CryptoPro-C-ParamSet"] = CURVES["id-tc26-gost-3410-12-256-paramSetD"] -CURVES["id-GostR3410-2001-CryptoPro-XchA-ParamSet"] = CURVES["id-GostR3410-2001-CryptoPro-A-ParamSet"] -CURVES["id-GostR3410-2001-CryptoPro-XchB-ParamSet"] = CURVES["id-GostR3410-2001-CryptoPro-C-ParamSet"] -CURVES["id-tc26-gost-3410-2012-256-paramSetA"] = CURVES["id-tc26-gost-3410-12-256-paramSetA"] -CURVES["id-tc26-gost-3410-2012-256-paramSetB"] = CURVES["id-tc26-gost-3410-12-256-paramSetB"] -CURVES["id-tc26-gost-3410-2012-256-paramSetC"] = CURVES["id-tc26-gost-3410-12-256-paramSetC"] -CURVES["id-tc26-gost-3410-2012-256-paramSetD"] = CURVES["id-tc26-gost-3410-12-256-paramSetD"] -CURVES["id-tc26-gost-3410-2012-512-paramSetTest"] = CURVES["id-tc26-gost-3410-12-512-paramSetTest"] -CURVES["id-tc26-gost-3410-2012-512-paramSetA"] = CURVES["id-tc26-gost-3410-12-512-paramSetA"] -CURVES["id-tc26-gost-3410-2012-512-paramSetB"] = CURVES["id-tc26-gost-3410-12-512-paramSetB"] -CURVES["id-tc26-gost-3410-2012-512-paramSetC"] = CURVES["id-tc26-gost-3410-12-512-paramSetC"] -for _name, _curve in CURVES.items(): - _curve.name = _name -DEFAULT_CURVE = CURVES["id-tc26-gost-3410-12-256-paramSetB"] - - -def public_key(curve, prv, mask=None): - """Generate public key from the private one - - :param GOST3410Curve curve: curve to use - :param long prv: private key - :returns: public key's parts, X and Y - :rtype: (long, long) - """ - pub = curve.exp(prv) - if mask is not None: - pub = curve.exp(mask, pub[0], pub[1]) - return pub - - -def sign(curve, prv, digest, rand=None, mask=None): - """Calculate signature for provided digest - - :param GOST3410Curve curve: curve to use - :param long prv: private key - :param digest: digest for signing - :type digest: bytes, 32 or 64 bytes - :param rand: optional predefined random data used for k/r generation - :type rand: bytes, 32 or 64 bytes - :returns: signature, BE(S) || BE(R) - :rtype: bytes, 64 or 128 bytes - """ - size = curve.point_size - q = curve.q - e = bytes2long(digest) % q - if e == 0: - e = 1 - while True: - if rand is None: - rand = urandom(size) - elif len(rand) != size: - raise ValueError("rand length != %d" % size) - k = bytes2long(rand) % q - if k == 0: - continue - r, y = curve.exp(k) - if mask is not None: - r, y = curve.exp(mask, x=r, y=y) - r %= q - if r == 0: - continue - d = prv * r - k *= e - s = d + k - if mask is not None: - s *= mask - s %= q - if s == 0: - continue - break - return long2bytes(s, size) + long2bytes(r, size) - - -def verify(curve, pub, digest, signature): - """Verify provided digest with the signature - - :param GOST3410Curve curve: curve to use - :type pub: (long, long) - :param digest: digest needed to check - :type digest: bytes, 32 or 64 bytes - :param signature: signature to verify with - :type signature: bytes, 64 or 128 bytes - :rtype: bool - """ - size = curve.point_size - if len(signature) != size * 2: - raise ValueError("Invalid signature length") - q = curve.q - p = curve.p - s = bytes2long(signature[:size]) - r = bytes2long(signature[size:]) - if r <= 0 or r >= q or s <= 0 or s >= q: - return False - e = bytes2long(digest) % curve.q - if e == 0: - e = 1 - v = modinvert(e, q) - z1 = s * v % q - z2 = q - r * v % q - p1x, p1y = curve.exp(z1) - q1x, q1y = curve.exp(z2, pub[0], pub[1]) - lm = q1x - p1x - if lm < 0: - lm += p - lm = modinvert(lm, p) - z1 = q1y - p1y - lm = lm * z1 % p - lm = lm * lm % p - lm = lm - p1x - q1x - lm = lm % p - if lm < 0: - lm += p - lm %= q - # This is not constant time comparison! - return lm == r - - -def prv_unmarshal(prv): - """Unmarshal little-endian private key - - :param bytes prv: serialized private key - :rtype: long - - It is advisable to use :py:func:`pygost.gost3410.prv_marshal` to - assure that key i in curve's Q field for better compatibility with - some implementations. - """ - return bytes2long(prv[::-1]) - - -def prv_marshal(curve, prv): - """Marshal little-endian private key - - :param GOST3410Curve curve: curve to use - :param long prv: serialized private key - :rtype: bytes - - Key is in curve's Q field. - """ - return long2bytes(prv % curve.q, point_size(prv))[::-1] - - -def pub_marshal(pub): - """Marshal public key - - :type pub: (long, long) - :rtype: bytes - :returns: LE(X) || LE(Y) - """ - size = point_size(pub[0]) - return (long2bytes(pub[1], size) + long2bytes(pub[0], size))[::-1] - - -def pub_unmarshal(pub): - """Unmarshal public key - - :param pub: LE(X) || LE(Y) - :type pub: bytes - :rtype: (long, long) - """ - size = len(pub) // 2 - pub = pub[::-1] - return (bytes2long(pub[size:]), bytes2long(pub[:size])) - - -def uv2xy(curve, u, v): - """Convert twisted Edwards curve U,V coordinates to Weierstrass X,Y - """ - s, t = curve.st() - k1 = (s * (1 + v)) % curve.p - k2 = curve.pos(1 - v) - x = t + k1 * modinvert(k2, curve.p) - y = k1 * modinvert(u * k2, curve.p) - return x % curve.p, y % curve.p - - -def xy2uv(curve, x, y): - """Convert Weierstrass X,Y coordinates to twisted Edwards curve U,V - """ - s, t = curve.st() - xmt = curve.pos(x - t) - u = xmt * modinvert(y, curve.p) - v = curve.pos(xmt - s) * modinvert(xmt + s, curve.p) - return u % curve.p, v % curve.p diff --git a/pygost-5.13/pygost/gost3410_vko.py b/pygost-5.13/pygost/gost3410_vko.py deleted file mode 100644 index 3f169dc..0000000 --- a/pygost-5.13/pygost/gost3410_vko.py +++ /dev/null @@ -1,97 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""Key agreement functions, VKO GOST R 34.10-2001/2012 -""" - -from pygost.gost3410 import pub_marshal -from pygost.gost34112012256 import GOST34112012256 -from pygost.gost34112012512 import GOST34112012512 -from pygost.gost341194 import GOST341194 -from pygost.utils import bytes2long - - -def ukm_unmarshal(ukm): - """Unmarshal UKM value - - :type ukm: little-endian bytes - :rtype: long - """ - return bytes2long(ukm[::-1]) - - -def kek(curve, prv, pub, ukm, mask=None): - if not curve.contains(pub): - raise ValueError("pub is not on the curve") - key = curve.exp(prv, pub[0], pub[1]) - key = curve.exp(curve.cofactor * ukm, key[0], key[1]) - if mask is not None: - key = curve.exp(mask, key[0], key[1]) - return pub_marshal(key) - - -def kek_34102001(curve, prv, pub, ukm): - """Key agreement (34.10-2001, 34.11-94) - - :param GOST3410Curve curve: curve to use - :param long prv: private key - :param pub: public key - :type pub: (long, long) - :param long ukm: user keying material, VKO-factor - :returns: Key Encryption Key (shared key) - :rtype: bytes, 32 bytes - - Shared Key Encryption Key computation is based on - :rfc:`4357` VKO GOST R 34.10-2001 with little-endian - hash output. - """ - return GOST341194( - kek(curve, prv, pub, ukm), - sbox="id-GostR3411-94-CryptoProParamSet", - ).digest() - - -def kek_34102012256(curve, prv, pub, ukm=1): - """Key agreement (34.10-2012, 34.11-2012 256 bit) - - :param GOST3410Curve curve: curve to use - :param long prv: private key - :param pub: public key - :type pub: (long, long) - :param long ukm: user keying material, VKO-factor - :returns: Key Encryption Key (shared key) - :rtype: bytes, 32 bytes - - Shared Key Encryption Key computation is based on - :rfc:`7836` VKO GOST R 34.10-2012. - """ - return GOST34112012256(kek(curve, prv, pub, ukm)).digest() - - -def kek_34102012512(curve, prv, pub, ukm=1): - """Key agreement (34.10-2012, 34.11-2012 512 bit) - - :param GOST3410Curve curve: curve to use - :param long prv: private key - :param pub: public key - :type pub: (long, long) - :param long ukm: user keying material, VKO-factor - :returns: Key Encryption Key (shared key) - :rtype: bytes, 32 bytes - - Shared Key Encryption Key computation is based on - :rfc:`7836` VKO GOST R 34.10-2012. - """ - return GOST34112012512(kek(curve, prv, pub, ukm)).digest() diff --git a/pygost-5.13/pygost/gost34112012.py b/pygost-5.13/pygost/gost34112012.py deleted file mode 100644 index 91782de..0000000 --- a/pygost-5.13/pygost/gost34112012.py +++ /dev/null @@ -1,299 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST R 34.11-2012 (Streebog) hash function common files - -This is implementation of :rfc:`6986`. Most function and variable names are -taken according to specification's terminology. -""" - -from copy import copy -from struct import pack -from struct import unpack - -from pygost.iface import PEP247 -from pygost.utils import hexdec -from pygost.utils import strxor -from pygost.utils import xrange - - -BLOCKSIZE = 64 -Pi = bytearray(( - 252, 238, 221, 17, 207, 110, 49, 22, 251, 196, 250, - 218, 35, 197, 4, 77, 233, 119, 240, 219, 147, 46, - 153, 186, 23, 54, 241, 187, 20, 205, 95, 193, 249, - 24, 101, 90, 226, 92, 239, 33, 129, 28, 60, 66, - 139, 1, 142, 79, 5, 132, 2, 174, 227, 106, 143, - 160, 6, 11, 237, 152, 127, 212, 211, 31, 235, 52, - 44, 81, 234, 200, 72, 171, 242, 42, 104, 162, 253, - 58, 206, 204, 181, 112, 14, 86, 8, 12, 118, 18, - 191, 114, 19, 71, 156, 183, 93, 135, 21, 161, 150, - 41, 16, 123, 154, 199, 243, 145, 120, 111, 157, 158, - 178, 177, 50, 117, 25, 61, 255, 53, 138, 126, 109, - 84, 198, 128, 195, 189, 13, 87, 223, 245, 36, 169, - 62, 168, 67, 201, 215, 121, 214, 246, 124, 34, 185, - 3, 224, 15, 236, 222, 122, 148, 176, 188, 220, 232, - 40, 80, 78, 51, 10, 74, 167, 151, 96, 115, 30, - 0, 98, 68, 26, 184, 56, 130, 100, 159, 38, 65, - 173, 69, 70, 146, 39, 94, 85, 47, 140, 163, 165, - 125, 105, 213, 149, 59, 7, 88, 179, 64, 134, 172, - 29, 247, 48, 55, 107, 228, 136, 217, 231, 137, 225, - 27, 131, 73, 76, 63, 248, 254, 141, 83, 170, 144, - 202, 216, 133, 97, 32, 113, 103, 164, 45, 43, 9, - 91, 203, 155, 37, 208, 190, 229, 108, 82, 89, 166, - 116, 210, 230, 244, 180, 192, 209, 102, 175, 194, 57, - 75, 99, 182, -)) - -A = [unpack(">Q", hexdec(s))[0] for s in ( - "8e20faa72ba0b470", "47107ddd9b505a38", "ad08b0e0c3282d1c", "d8045870ef14980e", - "6c022c38f90a4c07", "3601161cf205268d", "1b8e0b0e798c13c8", "83478b07b2468764", - "a011d380818e8f40", "5086e740ce47c920", "2843fd2067adea10", "14aff010bdd87508", - "0ad97808d06cb404", "05e23c0468365a02", "8c711e02341b2d01", "46b60f011a83988e", - "90dab52a387ae76f", "486dd4151c3dfdb9", "24b86a840e90f0d2", "125c354207487869", - "092e94218d243cba", "8a174a9ec8121e5d", "4585254f64090fa0", "accc9ca9328a8950", - "9d4df05d5f661451", "c0a878a0a1330aa6", "60543c50de970553", "302a1e286fc58ca7", - "18150f14b9ec46dd", "0c84890ad27623e0", "0642ca05693b9f70", "0321658cba93c138", - "86275df09ce8aaa8", "439da0784e745554", "afc0503c273aa42a", "d960281e9d1d5215", - "e230140fc0802984", "71180a8960409a42", "b60c05ca30204d21", "5b068c651810a89e", - "456c34887a3805b9", "ac361a443d1c8cd2", "561b0d22900e4669", "2b838811480723ba", - "9bcf4486248d9f5d", "c3e9224312c8c1a0", "effa11af0964ee50", "f97d86d98a327728", - "e4fa2054a80b329c", "727d102a548b194e", "39b008152acb8227", "9258048415eb419d", - "492c024284fbaec0", "aa16012142f35760", "550b8e9e21f7a530", "a48b474f9ef5dc18", - "70a6a56e2440598e", "3853dc371220a247", "1ca76e95091051ad", "0edd37c48a08a6d8", - "07e095624504536c", "8d70c431ac02a736", "c83862965601dd1b", "641c314b2b8ee083", -)] - -Tau = ( - 0, 8, 16, 24, 32, 40, 48, 56, - 1, 9, 17, 25, 33, 41, 49, 57, - 2, 10, 18, 26, 34, 42, 50, 58, - 3, 11, 19, 27, 35, 43, 51, 59, - 4, 12, 20, 28, 36, 44, 52, 60, - 5, 13, 21, 29, 37, 45, 53, 61, - 6, 14, 22, 30, 38, 46, 54, 62, - 7, 15, 23, 31, 39, 47, 55, 63, -) - -C = [hexdec("".join(s))[::-1] for s in ( - ( - "b1085bda1ecadae9ebcb2f81c0657c1f", - "2f6a76432e45d016714eb88d7585c4fc", - "4b7ce09192676901a2422a08a460d315", - "05767436cc744d23dd806559f2a64507", - ), - ( - "6fa3b58aa99d2f1a4fe39d460f70b5d7", - "f3feea720a232b9861d55e0f16b50131", - "9ab5176b12d699585cb561c2db0aa7ca", - "55dda21bd7cbcd56e679047021b19bb7", - ), - ( - "f574dcac2bce2fc70a39fc286a3d8435", - "06f15e5f529c1f8bf2ea7514b1297b7b", - "d3e20fe490359eb1c1c93a376062db09", - "c2b6f443867adb31991e96f50aba0ab2", - ), - ( - "ef1fdfb3e81566d2f948e1a05d71e4dd", - "488e857e335c3c7d9d721cad685e353f", - "a9d72c82ed03d675d8b71333935203be", - "3453eaa193e837f1220cbebc84e3d12e", - ), - ( - "4bea6bacad4747999a3f410c6ca92363", - "7f151c1f1686104a359e35d7800fffbd", - "bfcd1747253af5a3dfff00b723271a16", - "7a56a27ea9ea63f5601758fd7c6cfe57", - ), - ( - "ae4faeae1d3ad3d96fa4c33b7a3039c0", - "2d66c4f95142a46c187f9ab49af08ec6", - "cffaa6b71c9ab7b40af21f66c2bec6b6", - "bf71c57236904f35fa68407a46647d6e", - ), - ( - "f4c70e16eeaac5ec51ac86febf240954", - "399ec6c7e6bf87c9d3473e33197a93c9", - "0992abc52d822c3706476983284a0504", - "3517454ca23c4af38886564d3a14d493", - ), - ( - "9b1f5b424d93c9a703e7aa020c6e4141", - "4eb7f8719c36de1e89b4443b4ddbc49a", - "f4892bcb929b069069d18d2bd1a5c42f", - "36acc2355951a8d9a47f0dd4bf02e71e", - ), - ( - "378f5a541631229b944c9ad8ec165fde", - "3a7d3a1b258942243cd955b7e00d0984", - "800a440bdbb2ceb17b2b8a9aa6079c54", - "0e38dc92cb1f2a607261445183235adb", - ), - ( - "abbedea680056f52382ae548b2e4f3f3", - "8941e71cff8a78db1fffe18a1b336103", - "9fe76702af69334b7a1e6c303b7652f4", - "3698fad1153bb6c374b4c7fb98459ced", - ), - ( - "7bcd9ed0efc889fb3002c6cd635afe94", - "d8fa6bbbebab07612001802114846679", - "8a1d71efea48b9caefbacd1d7d476e98", - "dea2594ac06fd85d6bcaa4cd81f32d1b", - ), - ( - "378ee767f11631bad21380b00449b17a", - "cda43c32bcdf1d77f82012d430219f9b", - "5d80ef9d1891cc86e71da4aa88e12852", - "faf417d5d9b21b9948bc924af11bd720", - ), -)] - - -def _lcache(): - cache = [] - for byteN in xrange(8): - cache.append([0 for _ in xrange(256)]) - for byteN in xrange(8): - for byteVal in xrange(256): - res64 = 0 - val = byteVal - for bitN in xrange(8): - if val & 0x80 > 0: - res64 ^= A[(7 - byteN) * 8 + bitN] - val <<= 1 - cache[byteN][byteVal] = res64 - return cache - - -# Trade memory for CPU for part of L() calculations -LCache = _lcache() - - -def add512bit(a, b): - a = int.from_bytes(a, "little") - b = int.from_bytes(b, "little") - r = (a + b) % (1 << 512) - return r.to_bytes(512 // 8, "little") - - -def g(n, hsh, msg): - res = E(LPS(strxor(hsh[:8], pack(">> m = GOST34112012(digest_size=32) - >>> m.update("foo") - >>> m.update("bar") - >>> m.hexdigest() - 'e3c9fd89226d93b489a9fe27d686806e24a514e3787bca053c698ec4616ceb78' - """ - block_size = BLOCKSIZE - - def __init__(self, data=b"", digest_size=64): - """ - :param digest_size: hash digest size to compute - :type digest_size: 32 or 64 bytes - """ - self._digest_size = digest_size - self.hsh = BLOCKSIZE * (b"\x01" if digest_size == 32 else b"\x00") - self.chk = bytearray(BLOCKSIZE * b"\x00") - self.n = 0 - self.buf = b"" - self.update(data) - - def copy(self): - obj = GOST34112012() - obj._digest_size = self._digest_size - obj.hsh = self.hsh - obj.chk = copy(self.chk) - obj.n = self.n - obj.buf = self.buf - return obj - - @property - def digest_size(self): - return self._digest_size - - def _update_block(self, block): - self.hsh = g(self.n, self.hsh, block) - self.chk = add512bit(self.chk, block) - self.n += 512 - - def update(self, data): - """Update state with the new data - """ - if len(self.buf) > 0: - chunk_len = BLOCKSIZE - len(self.buf) - self.buf += data[:chunk_len] - data = data[chunk_len:] - if len(self.buf) == BLOCKSIZE: - self._update_block(self.buf) - self.buf = b"" - while len(data) >= BLOCKSIZE: - self._update_block(data[:BLOCKSIZE]) - data = data[BLOCKSIZE:] - self.buf += data - - def digest(self): - """Get hash of the provided data - """ - data = self.buf - - # Padding - padblock_size = len(data) * 8 - data += b"\x01" - padlen = BLOCKSIZE - len(data) - if padlen != BLOCKSIZE: - data += b"\x00" * padlen - - hsh = g(self.n, self.hsh, data) - n = self.n + padblock_size - chk = add512bit(self.chk, data) - hsh = g(0, hsh, pack(" -# -# 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST R 34.11-94 hash function - -This is implementation of :rfc:`5831`. Most function and variable names are -taken according to specification's terminology. -""" - -from copy import copy -from functools import partial -from struct import pack - -from pygost.gost28147 import block2ns -from pygost.gost28147 import encrypt -from pygost.gost28147 import ns2block -from pygost.gost28147 import validate_sbox -from pygost.iface import PEP247 -from pygost.pbkdf2 import pbkdf2 as pbkdf2_base -from pygost.utils import hexdec -from pygost.utils import hexenc -from pygost.utils import strxor -from pygost.utils import xrange - - -DEFAULT_SBOX = "id-GostR3411-94-CryptoProParamSet" -BLOCKSIZE = 32 -C2 = 32 * b"\x00" -C3 = hexdec(b"ff00ffff000000ffff0000ff00ffff0000ff00ff00ff00ffff00ff00ff00ff00") -C4 = 32 * b"\x00" -digest_size = 32 - - -def A(x): - x4, x3, x2, x1 = x[0:8], x[8:16], x[16:24], x[24:32] - return b"".join((strxor(x1, x2), x4, x3, x2)) - - -def P(x): - return bytearray(( - x[0], x[8], x[16], x[24], x[1], x[9], x[17], x[25], x[2], - x[10], x[18], x[26], x[3], x[11], x[19], x[27], x[4], x[12], - x[20], x[28], x[5], x[13], x[21], x[29], x[6], x[14], x[22], - x[30], x[7], x[15], x[23], x[31], - )) - - -def _chi(Y): - """Chi function - - This is some kind of LFSR. - """ - (y16, y15, y14, y13, y12, y11, y10, y9, y8, y7, y6, y5, y4, y3, y2, y1) = ( - Y[0:2], Y[2:4], Y[4:6], Y[6:8], Y[8:10], Y[10:12], Y[12:14], - Y[14:16], Y[16:18], Y[18:20], Y[20:22], Y[22:24], Y[24:26], - Y[26:28], Y[28:30], Y[30:32], - ) - by1, by2, by3, by4, by13, by16, byx = ( - bytearray(y1), bytearray(y2), bytearray(y3), bytearray(y4), - bytearray(y13), bytearray(y16), bytearray(2), - ) - byx[0] = by1[0] ^ by2[0] ^ by3[0] ^ by4[0] ^ by13[0] ^ by16[0] - byx[1] = by1[1] ^ by2[1] ^ by3[1] ^ by4[1] ^ by13[1] ^ by16[1] - return b"".join(( - bytes(byx), y16, y15, y14, y13, y12, y11, y10, y9, y8, y7, y6, y5, y4, y3, y2 - )) - - -def _step(hin, m, sbox): - """Step function - - H_out = f(H_in, m) - """ - # Generate keys - u = hin - v = m - w = strxor(hin, m) - k1 = P(w) - - u = strxor(A(u), C2) - v = A(A(v)) - w = strxor(u, v) - k2 = P(w) - - u = strxor(A(u), C3) - v = A(A(v)) - w = strxor(u, v) - k3 = P(w) - - u = strxor(A(u), C4) - v = A(A(v)) - w = strxor(u, v) - k4 = P(w) - - # Encipher - h4, h3, h2, h1 = hin[0:8], hin[8:16], hin[16:24], hin[24:32] - s1 = ns2block(encrypt(sbox, k1[::-1], block2ns(h1[::-1])))[::-1] - s2 = ns2block(encrypt(sbox, k2[::-1], block2ns(h2[::-1])))[::-1] - s3 = ns2block(encrypt(sbox, k3[::-1], block2ns(h3[::-1])))[::-1] - s4 = ns2block(encrypt(sbox, k4[::-1], block2ns(h4[::-1])))[::-1] - s = b"".join((s4, s3, s2, s1)) - - # Permute - # H_out = chi^61(H_in XOR chi(m XOR chi^12(S))) - x = s - for _ in xrange(12): - x = _chi(x) - x = strxor(x, m) - x = _chi(x) - x = strxor(hin, x) - for _ in xrange(61): - x = _chi(x) - return x - - -class GOST341194(PEP247): - """GOST 34.11-94 big-endian hash - - >>> m = GOST341194() - >>> m.update("foo") - >>> m.update("bar") - >>> m.hexdigest() - '3bd8a3a35917871dfa0d49f9e73e7c57eea028dc061133eb560849ea20c133af' - >>> GOST341194("foobar").hexdigest() - '3bd8a3a35917871dfa0d49f9e73e7c57eea028dc061133eb560849ea20c133af' - """ - block_size = BLOCKSIZE - digest_size = digest_size - - def __init__(self, data=b"", sbox=DEFAULT_SBOX): - """ - :param bytes data: provide initial data - :param bytes sbox: S-box to use - """ - validate_sbox(sbox) - self.data = data - self.sbox = sbox - - def copy(self): - return GOST341194(copy(self.data), self.sbox) - - def update(self, data): - """Append data that has to be hashed - """ - self.data += data - - def digest(self): - """Get hash of the provided data - """ - _len = 0 - checksum = 0 - h = 32 * b"\x00" - m = self.data - for i in xrange(0, len(m), BLOCKSIZE): - part = m[i:i + BLOCKSIZE][::-1] - _len += len(part) * 8 - checksum = (checksum + int(hexenc(part), 16)) % (2 ** 256) - if len(part) < BLOCKSIZE: - part = b"\x00" * (BLOCKSIZE - len(part)) + part - h = _step(h, part, self.sbox) - h = _step(h, 24 * b"\x00" + pack(">Q", _len), self.sbox) - - checksum = hex(checksum)[2:].rstrip("L") - if len(checksum) % 2 != 0: - checksum = "0" + checksum - checksum = hexdec(checksum) - checksum = b"\x00" * (BLOCKSIZE - len(checksum)) + checksum - h = _step(h, checksum, self.sbox) - return h[::-1] - - -def new(data=b"", sbox=DEFAULT_SBOX): - return GOST341194(data, sbox) - - -PBKDF2_HASHER = partial(GOST341194, sbox="id-GostR3411-94-CryptoProParamSet") - - -def pbkdf2(password, salt, iterations, dklen): - return pbkdf2_base(PBKDF2_HASHER, password, salt, iterations, dklen) diff --git a/pygost-5.13/pygost/gost3412.py b/pygost-5.13/pygost/gost3412.py deleted file mode 100644 index b9472ee..0000000 --- a/pygost-5.13/pygost/gost3412.py +++ /dev/null @@ -1,186 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST 34.12-2015 64 and 128 bit block ciphers (:rfc:`7801`) - -Several precalculations are performed during this module importing. -""" - -from pygost.gost28147 import block2ns as gost28147_block2ns -from pygost.gost28147 import decrypt as gost28147_decrypt -from pygost.gost28147 import encrypt as gost28147_encrypt -from pygost.gost28147 import ns2block as gost28147_ns2block -from pygost.utils import strxor -from pygost.utils import xrange - - -KEYSIZE = 32 - -LC = bytearray(( - 148, 32, 133, 16, 194, 192, 1, 251, 1, 192, 194, 16, 133, 32, 148, 1, -)) -PI = bytearray(( - 252, 238, 221, 17, 207, 110, 49, 22, 251, 196, 250, 218, 35, 197, 4, 77, - 233, 119, 240, 219, 147, 46, 153, 186, 23, 54, 241, 187, 20, 205, 95, 193, - 249, 24, 101, 90, 226, 92, 239, 33, 129, 28, 60, 66, 139, 1, 142, 79, 5, - 132, 2, 174, 227, 106, 143, 160, 6, 11, 237, 152, 127, 212, 211, 31, 235, - 52, 44, 81, 234, 200, 72, 171, 242, 42, 104, 162, 253, 58, 206, 204, 181, - 112, 14, 86, 8, 12, 118, 18, 191, 114, 19, 71, 156, 183, 93, 135, 21, 161, - 150, 41, 16, 123, 154, 199, 243, 145, 120, 111, 157, 158, 178, 177, 50, 117, - 25, 61, 255, 53, 138, 126, 109, 84, 198, 128, 195, 189, 13, 87, 223, 245, - 36, 169, 62, 168, 67, 201, 215, 121, 214, 246, 124, 34, 185, 3, 224, 15, - 236, 222, 122, 148, 176, 188, 220, 232, 40, 80, 78, 51, 10, 74, 167, 151, - 96, 115, 30, 0, 98, 68, 26, 184, 56, 130, 100, 159, 38, 65, 173, 69, 70, - 146, 39, 94, 85, 47, 140, 163, 165, 125, 105, 213, 149, 59, 7, 88, 179, 64, - 134, 172, 29, 247, 48, 55, 107, 228, 136, 217, 231, 137, 225, 27, 131, 73, - 76, 63, 248, 254, 141, 83, 170, 144, 202, 216, 133, 97, 32, 113, 103, 164, - 45, 43, 9, 91, 203, 155, 37, 208, 190, 229, 108, 82, 89, 166, 116, 210, 230, - 244, 180, 192, 209, 102, 175, 194, 57, 75, 99, 182, -)) - -######################################################################## -# Precalculate inverted PI value as a performance optimization. -# Actually it can be computed only once and saved on the disk. -######################################################################## -PIinv = bytearray(256) -for x in xrange(256): - PIinv[PI[x]] = x - - -def gf(a, b): - c = 0 - while b: - if b & 1: - c ^= a - if a & 0x80: - a = (a << 1) ^ 0x1C3 - else: - a <<= 1 - b >>= 1 - return c - -######################################################################## -# Precalculate all possible gf(byte, byte) values as a performance -# optimization. -# Actually it can be computed only once and saved on the disk. -######################################################################## - - -GF = [bytearray(256) for _ in xrange(256)] - -for x in xrange(256): - for y in xrange(256): - GF[x][y] = gf(x, y) - - -def L(blk, rounds=16): - for _ in range(rounds): - t = blk[15] - for i in range(14, -1, -1): - blk[i + 1] = blk[i] - t ^= GF[blk[i]][LC[i]] - blk[0] = t - return blk - - -def Linv(blk): - for _ in range(16): - t = blk[0] - for i in range(15): - blk[i] = blk[i + 1] - t ^= GF[blk[i]][LC[i]] - blk[15] = t - return blk - -######################################################################## -# Precalculate values of the C -- it does not depend on key. -# Actually it can be computed only once and saved on the disk. -######################################################################## - - -C = [] - -for x in range(1, 33): - y = bytearray(16) - y[15] = x - C.append(L(y)) - - -def lp(blk): - return L([PI[v] for v in blk]) - - -class GOST3412Kuznechik(object): - """GOST 34.12-2015 128-bit block cipher Кузнечик (Kuznechik) - """ - blocksize = 16 - - def __init__(self, key): - """ - :param key: encryption/decryption key - :type key: bytes, 32 bytes - - Key scheduling (roundkeys precomputation) is performed here. - """ - kr0 = bytearray(key[:16]) - kr1 = bytearray(key[16:]) - self.ks = [kr0, kr1] - for i in range(4): - for j in range(8): - k = lp(bytearray(strxor(C[8 * i + j], kr0))) - kr0, kr1 = [strxor(k, kr1), kr0] - self.ks.append(kr0) - self.ks.append(kr1) - - def encrypt(self, blk): - blk = bytearray(blk) - for i in range(9): - blk = lp(bytearray(strxor(self.ks[i], blk))) - return bytes(strxor(self.ks[9], blk)) - - def decrypt(self, blk): - blk = bytearray(blk) - for i in range(9, 0, -1): - blk = [PIinv[v] for v in Linv(bytearray(strxor(self.ks[i], blk)))] - return bytes(strxor(self.ks[0], blk)) - - -class GOST3412Magma(object): - """GOST 34.12-2015 64-bit block cipher Магма (Magma) - """ - blocksize = 8 - - def __init__(self, key): - """ - :param key: encryption/decryption key - :type key: bytes, 32 bytes - """ - # Backward compatibility key preparation for 28147-89 key schedule - self.key = b"".join(key[i * 4:i * 4 + 4][::-1] for i in range(8)) - self.sbox = "id-tc26-gost-28147-param-Z" - - def encrypt(self, blk): - return gost28147_ns2block(gost28147_encrypt( - self.sbox, - self.key, - gost28147_block2ns(blk[::-1]), - ))[::-1] - - def decrypt(self, blk): - return gost28147_ns2block(gost28147_decrypt( - self.sbox, - self.key, - gost28147_block2ns(blk[::-1]), - ))[::-1] diff --git a/pygost-5.13/pygost/gost3413.py b/pygost-5.13/pygost/gost3413.py deleted file mode 100644 index f3cb5da..0000000 --- a/pygost-5.13/pygost/gost3413.py +++ /dev/null @@ -1,392 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""GOST R 34.13-2015: Modes of operation for block ciphers - -This module currently includes only padding methods. -""" - -from os import urandom - -from pygost.utils import bytes2long -from pygost.utils import long2bytes -from pygost.utils import strxor -from pygost.utils import xrange - - -KEYSIZE = 32 - - -def pad_size(data_size, blocksize): - """Calculate required pad size to full up blocksize - """ - if data_size < blocksize: - return blocksize - data_size - if data_size % blocksize == 0: - return 0 - return blocksize - data_size % blocksize - - -def pad1(data, blocksize): - """Padding method 1 - - Just fill up with zeros if necessary. - """ - return data + b"\x00" * pad_size(len(data), blocksize) - - -def pad2(data, blocksize): - """Padding method 2 (also known as ISO/IEC 7816-4) - - Add one bit and then fill up with zeros. - """ - return data + b"\x80" + b"\x00" * pad_size(len(data) + 1, blocksize) - - -def unpad2(data, blocksize): - """Unpad method 2 - """ - last_block = bytearray(data[-blocksize:]) - pad_index = last_block.rfind(b"\x80") - if pad_index == -1: - raise ValueError("Invalid padding") - for c in last_block[pad_index + 1:]: - if c != 0: - raise ValueError("Invalid padding") - return data[:-(blocksize - pad_index)] - - -def pad3(data, blocksize): - """Padding method 3 - """ - if pad_size(len(data), blocksize) == 0: - return data - return pad2(data, blocksize) - - -def ecb_encrypt(encrypter, bs, pt): - """ECB encryption mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes pt: already padded plaintext - """ - if not pt or len(pt) % bs != 0: - raise ValueError("Plaintext is not blocksize aligned") - ct = [] - for i in xrange(0, len(pt), bs): - ct.append(encrypter(pt[i:i + bs])) - return b"".join(ct) - - -def ecb_decrypt(decrypter, bs, ct): - """ECB decryption mode of operation - - :param decrypter: Decrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes ct: ciphertext - """ - if not ct or len(ct) % bs != 0: - raise ValueError("Ciphertext is not blocksize aligned") - pt = [] - for i in xrange(0, len(ct), bs): - pt.append(decrypter(ct[i:i + bs])) - return b"".join(pt) - - -def acpkm(encrypter, bs): - """Perform ACPKM key derivation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - """ - return b"".join([ - encrypter(bytes(bytearray(range(d, d + bs)))) - for d in range(0x80, 0x80 + bs * (KEYSIZE // bs), bs) - ]) - - -def ctr(encrypter, bs, data, iv, _acpkm=None): - """Counter mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes data: plaintext/ciphertext - :param bytes iv: half blocksize-sized initialization vector - - For decryption you use the same function again. - """ - if len(iv) != bs // 2: - raise ValueError("Invalid IV size") - if len(data) > bs * (1 << (8 * (bs // 2 - 1))): - raise ValueError("Too big data") - stream = [] - ctr_value = 0 - ctr_max_value = 1 << (8 * (bs // 2)) - if _acpkm is not None: - acpkm_algo_class, acpkm_section_size_in_bs = _acpkm - acpkm_section_size_in_bs //= bs - for _ in xrange(0, len(data) + pad_size(len(data), bs), bs): - if ( - _acpkm is not None and - ctr_value != 0 and - ctr_value % acpkm_section_size_in_bs == 0 - ): - encrypter = acpkm_algo_class(acpkm(encrypter, bs)).encrypt - stream.append(encrypter(iv + long2bytes(ctr_value, bs // 2))) - ctr_value = (ctr_value + 1) % ctr_max_value - return strxor(b"".join(stream), data) - - -def ctr_acpkm(algo_class, encrypter, section_size, bs, data, iv): - """CTR-ACPKM mode of operation - - :param algo_class: pygost.gost3412's algorithm class - :param encrypter: encrypting function, that takes block as an input - :param int section_size: ACPKM'es section size (N), in bytes - :param int bs: cipher's blocksize, bytes - :param bytes data: plaintext/ciphertext - :param bytes iv: half blocksize-sized initialization vector - - For decryption you use the same function again. - """ - if section_size % bs != 0: - raise ValueError("section_size must be multiple of bs") - return ctr(encrypter, bs, data, iv, _acpkm=(algo_class, section_size)) - - -def ofb(encrypter, bs, data, iv): - """OFB mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes data: plaintext/ciphertext - :param bytes iv: blocksize-sized initialization vector - - For decryption you use the same function again. - """ - if len(iv) < bs or len(iv) % bs != 0: - raise ValueError("Invalid IV size") - r = [iv[i:i + bs] for i in range(0, len(iv), bs)] - result = [] - for i in xrange(0, len(data) + pad_size(len(data), bs), bs): - r = r[1:] + [encrypter(r[0])] - result.append(strxor(r[-1], data[i:i + bs])) - return b"".join(result) - - -def cbc_encrypt(encrypter, bs, pt, iv): - """CBC encryption mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes pt: already padded plaintext - :param bytes iv: blocksize-sized initialization vector - """ - if not pt or len(pt) % bs != 0: - raise ValueError("Plaintext is not blocksize aligned") - if len(iv) < bs or len(iv) % bs != 0: - raise ValueError("Invalid IV size") - r = [iv[i:i + bs] for i in range(0, len(iv), bs)] - ct = [] - for i in xrange(0, len(pt), bs): - ct.append(encrypter(strxor(r[0], pt[i:i + bs]))) - r = r[1:] + [ct[-1]] - return b"".join(ct) - - -def cbc_decrypt(decrypter, bs, ct, iv): - """CBC decryption mode of operation - - :param decrypter: Decrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes ct: ciphertext - :param bytes iv: blocksize-sized initialization vector - """ - if not ct or len(ct) % bs != 0: - raise ValueError("Ciphertext is not blocksize aligned") - if len(iv) < bs or len(iv) % bs != 0: - raise ValueError("Invalid IV size") - r = [iv[i:i + bs] for i in range(0, len(iv), bs)] - pt = [] - for i in xrange(0, len(ct), bs): - blk = ct[i:i + bs] - pt.append(strxor(r[0], decrypter(blk))) - r = r[1:] + [blk] - return b"".join(pt) - - -def cfb_encrypt(encrypter, bs, pt, iv): - """CFB encryption mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes pt: plaintext - :param bytes iv: blocksize-sized initialization vector - """ - if len(iv) < bs or len(iv) % bs != 0: - raise ValueError("Invalid IV size") - r = [iv[i:i + bs] for i in range(0, len(iv), bs)] - ct = [] - for i in xrange(0, len(pt) + pad_size(len(pt), bs), bs): - ct.append(strxor(encrypter(r[0]), pt[i:i + bs])) - r = r[1:] + [ct[-1]] - return b"".join(ct) - - -def cfb_decrypt(encrypter, bs, ct, iv): - """CFB decryption mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes ct: ciphertext - :param bytes iv: blocksize-sized initialization vector - """ - if len(iv) < bs or len(iv) % bs != 0: - raise ValueError("Invalid IV size") - r = [iv[i:i + bs] for i in range(0, len(iv), bs)] - pt = [] - for i in xrange(0, len(ct) + pad_size(len(ct), bs), bs): - blk = ct[i:i + bs] - pt.append(strxor(encrypter(r[0]), blk)) - r = r[1:] + [blk] - return b"".join(pt) - - -def _mac_shift(bs, data, xor_lsb=0): - num = (bytes2long(data) << 1) ^ xor_lsb - return long2bytes(num, bs)[-bs:] - - -Rb64 = 0b11011 -Rb128 = 0b10000111 - - -def _mac_ks(encrypter, bs): - Rb = Rb128 if bs == 16 else Rb64 - _l = encrypter(bs * b"\x00") - k1 = _mac_shift(bs, _l, Rb) if bytearray(_l)[0] & 0x80 > 0 else _mac_shift(bs, _l) - k2 = _mac_shift(bs, k1, Rb) if bytearray(k1)[0] & 0x80 > 0 else _mac_shift(bs, k1) - return k1, k2 - - -def mac(encrypter, bs, data): - """MAC (known here as CMAC, OMAC1) mode of operation - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize, bytes - :param bytes data: data to authenticate - - Implementation is based on PyCrypto's CMAC one, that is in public domain. - """ - k1, k2 = _mac_ks(encrypter, bs) - if len(data) % bs == 0: - tail_offset = len(data) - bs - else: - tail_offset = len(data) - (len(data) % bs) - prev = bs * b"\x00" - for i in xrange(0, tail_offset, bs): - prev = encrypter(strxor(data[i:i + bs], prev)) - tail = data[tail_offset:] - return encrypter(strxor( - strxor(pad3(tail, bs), prev), - k1 if len(tail) == bs else k2, - )) - - -def acpkm_master(algo_class, encrypter, key_section_size, bs, keymat_len): - """ACPKM-Master key derivation - - :param algo_class: pygost.gost3412's algorithm class - :param encrypter: encrypting function, that takes block as an input - :param int key_section_size: ACPKM'es key section size (T*), in bytes - :param int bs: cipher's blocksize, bytes - :param int keymat_len: length of key material to produce - """ - return ctr_acpkm( - algo_class, - encrypter, - key_section_size, - bs, - data=b"\x00" * keymat_len, - iv=b"\xFF" * (bs // 2), - ) - - -def mac_acpkm_master(algo_class, encrypter, key_section_size, section_size, bs, data): - """OMAC-ACPKM-Master - - :param algo_class: pygost.gost3412's algorithm class - :param encrypter: encrypting function, that takes block as an input - :param int key_section_size: ACPKM'es key section size (T*), in bytes - :param int section_size: ACPKM'es section size (N), in bytes - :param int bs: cipher's blocksize, bytes - :param bytes data: data to authenticate - """ - if len(data) % bs == 0: - tail_offset = len(data) - bs - else: - tail_offset = len(data) - (len(data) % bs) - prev = bs * b"\x00" - sections = len(data) // section_size - if len(data) % section_size != 0: - sections += 1 - keymats = acpkm_master( - algo_class, - encrypter, - key_section_size, - bs, - (KEYSIZE + bs) * sections, - ) - for i in xrange(0, tail_offset, bs): - if i % section_size == 0: - keymat, keymats = keymats[:KEYSIZE + bs], keymats[KEYSIZE + bs:] - key, k1 = keymat[:KEYSIZE], keymat[KEYSIZE:] - encrypter = algo_class(key).encrypt - prev = encrypter(strxor(data[i:i + bs], prev)) - tail = data[tail_offset:] - if len(tail) == bs: - key, k1 = keymats[:KEYSIZE], keymats[KEYSIZE:] - encrypter = algo_class(key).encrypt - k2 = long2bytes(bytes2long(k1) << 1, size=bs) - if bytearray(k1)[0] & 0x80 != 0: - k2 = strxor(k2, long2bytes(Rb128 if bs == 16 else Rb64, size=bs)) - return encrypter(strxor( - strxor(pad3(tail, bs), prev), - k1 if len(tail) == bs else k2, - )) - - -def pad_iso10126(data, blocksize): - """ISO 10126 padding - - Does not exist in 34.13, but added for convenience. - It uses urandom call for getting the randomness. - """ - pad_len = blocksize - len(data) % blocksize - if pad_len == 0: - pad_len = blocksize - return b"".join((data, urandom(pad_len - 1), bytes((pad_len,)))) - - -def unpad_iso10126(data, blocksize): - """Unpad :py:func:`pygost.gost3413.pad_iso10126` - """ - if len(data) % blocksize != 0: - raise ValueError("Data length is not multiple of blocksize") - pad_len = bytearray(data)[-1] - if pad_len > blocksize: - raise ValueError("Padding length is bigger than blocksize") - return data[:-pad_len] diff --git a/pygost-5.13/pygost/iface.py b/pygost-5.13/pygost/iface.py deleted file mode 100644 index e2d6a4c..0000000 --- a/pygost-5.13/pygost/iface.py +++ /dev/null @@ -1,50 +0,0 @@ -from abc import ABCMeta -from abc import abstractmethod - -from pygost.utils import hexenc - - -# This function is taken from six package as is -def add_metaclass(metaclass): - """Class decorator for creating a class with a metaclass.""" - def wrapper(cls): - orig_vars = cls.__dict__.copy() - slots = orig_vars.get("__slots__") - if slots is not None: - if isinstance(slots, str): - slots = [slots] - for slots_var in slots: - orig_vars.pop(slots_var) - orig_vars.pop("__dict__", None) - orig_vars.pop("__weakref__", None) - return metaclass(cls.__name__, cls.__bases__, orig_vars) - return wrapper - - -@add_metaclass(ABCMeta) -class PEP247(object): - @property - @abstractmethod - def digest_size(self): - """The size of the digest produced by the hashing objects. - """ - - @abstractmethod - def copy(self): - """Return a separate copy of this hashing object. - """ - - @abstractmethod - def update(self, data): - """Hash data into the current state of the hashing object. - """ - - @abstractmethod - def digest(self): - """Return the hash value as a string containing 8-bit data. - """ - - def hexdigest(self): - """Return the hash value as a string containing hexadecimal digits. - """ - return hexenc(self.digest()) diff --git a/pygost-5.13/pygost/kdf.py b/pygost-5.13/pygost/kdf.py deleted file mode 100644 index 4e404c6..0000000 --- a/pygost-5.13/pygost/kdf.py +++ /dev/null @@ -1,81 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""Key derivation functions, Р 50.1.113-2016, Р 1323565.1.020-2018 -""" - -import hmac - -from pygost.gost3410_vko import kek_34102012256 -from pygost.gost3410_vko import kek_34102012512 -from pygost.gost34112012256 import GOST34112012256 -from pygost.utils import bytes2long -from pygost.utils import long2bytes - - -def kdf_gostr3411_2012_256(key, label, seed): - """KDF_GOSTR3411_2012_256 - - :param bytes key: initial key - :param bytes label: label - :param bytes seed: seed - :returns: 32 bytes - """ - return hmac.new( - key=key, - msg=b"".join((b"\x01", label, b"\x00", seed, b"\x01\x00")), - digestmod=GOST34112012256, - ).digest() - - -def kdf_tree_gostr3411_2012_256(key, label, seed, keys, i_len=1): - """KDF_TREE_GOSTR3411_2012_256 - - :param bytes key: initial key - :param bytes label: label - :param bytes seed: seed - :param int keys: number of generated keys - :param int i_len: length of iterations value (called "R") - :returns: list of 256-bit keys - """ - keymat = [] - _len = long2bytes(keys * 32 * 8, size=1) - for i in range(keys): - keymat.append(hmac.new( - key=key, - msg=b"".join((long2bytes(i + 1, size=i_len), label, b"\x00", seed, _len)), - digestmod=GOST34112012256, - ).digest()) - return keymat - - -def keg(curve, prv, pub, h): - """Export key generation (Р 1323565.1.020-2018) - - :param GOST3410Curve curve: curve to use - :param long prv: private key - :param pub: public key - :type pub: (long, long) - :param bytes h: "h"-value, 32 bytes - """ - if len(h) != 32: - raise ValueError("h must be 32 bytes long") - ukm = bytes2long(h[:16]) - if ukm == 0: - ukm = 1 - if curve.point_size == 64: - return kek_34102012512(curve, prv, pub, ukm) - k_exp = kek_34102012256(curve, prv, pub, ukm) - return b"".join(kdf_tree_gostr3411_2012_256(k_exp, b"kdf tree", h[16:24], 2)) diff --git a/pygost-5.13/pygost/mgm.py b/pygost-5.13/pygost/mgm.py deleted file mode 100644 index fb51343..0000000 --- a/pygost-5.13/pygost/mgm.py +++ /dev/null @@ -1,168 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""Multilinear Galois Mode (MGM) block cipher mode. -""" - -from hmac import compare_digest - -from pygost.gost3413 import pad1 -from pygost.utils import bytes2long -from pygost.utils import long2bytes -from pygost.utils import strxor - - -def _incr(data, bs): - return long2bytes(bytes2long(data) + 1, size=bs // 2) - - -def incr_r(data, bs): - return data[:bs // 2] + _incr(data[bs // 2:], bs) - - -def incr_l(data, bs): - return _incr(data[:bs // 2], bs) + data[bs // 2:] - - -def nonce_prepare(nonce): - """Prepare nonce for MGM usage - - It just clears MSB. - """ - n = bytearray(nonce) - n[0] &= 0x7F - return bytes(n) - - -class MGM(object): - # Implementation is fully based on go.cypherpunks.ru/gogost/mgm - def __init__(self, encrypter, bs, tag_size=None): - """Multilinear Galois Mode (MGM) block cipher mode - - :param encrypter: encrypting function, that takes block as an input - :param int bs: cipher's blocksize - :param int tag_size: authentication tag size - (defaults to blocksize if not specified) - """ - if bs not in (8, 16): - raise ValueError("Only 64/128-bit blocksizes allowed") - self.tag_size = bs if tag_size is None else tag_size - if self.tag_size < 4 or self.tag_size > bs: - raise ValueError("Invalid tag_size") - self.encrypter = encrypter - self.bs = bs - self.max_size = (1 << (bs * 8 // 2)) - 1 - self.r = 0x1B if bs == 8 else 0x87 - - def _validate_nonce(self, nonce): - if len(nonce) != self.bs: - raise ValueError("nonce length must be equal to cipher's blocksize") - if bytearray(nonce)[0] & 0x80 > 0: - raise ValueError("nonce must not have higher bit set") - - def _validate_sizes(self, plaintext, additional_data): - if len(plaintext) == 0 and len(additional_data) == 0: - raise ValueError("At least one of plaintext or additional_data required") - if len(plaintext) + len(additional_data) > self.max_size: - raise ValueError("plaintext+additional_data are too big") - - def _mul(self, x, y): - x = bytes2long(x) - y = bytes2long(y) - z = 0 - max_bit = 1 << (self.bs * 8 - 1) - while y > 0: - if y & 1 == 1: - z ^= x - if x & max_bit > 0: - x = ((x ^ max_bit) << 1) ^ self.r - else: - x <<= 1 - y >>= 1 - return long2bytes(z, size=self.bs) - - def _crypt(self, icn, data): - icn[0] &= 0x7F - enc = self.encrypter(bytes(icn)) - res = [] - while len(data) > 0: - res.append(strxor(self.encrypter(enc), data)) - enc = incr_r(enc, self.bs) - data = data[self.bs:] - return b"".join(res) - - def _auth(self, icn, text, ad): - icn[0] |= 0x80 - enc = self.encrypter(bytes(icn)) - _sum = self.bs * b"\x00" - ad_len = len(ad) - text_len = len(text) - while len(ad) > 0: - _sum = strxor(_sum, self._mul( - self.encrypter(enc), - pad1(ad[:self.bs], self.bs), - )) - enc = incr_l(enc, self.bs) - ad = ad[self.bs:] - while len(text) > 0: - _sum = strxor(_sum, self._mul( - self.encrypter(enc), - pad1(text[:self.bs], self.bs), - )) - enc = incr_l(enc, self.bs) - text = text[self.bs:] - _sum = strxor(_sum, self._mul(self.encrypter(enc), ( - long2bytes(ad_len * 8, size=self.bs // 2) + - long2bytes(text_len * 8, size=self.bs // 2) - ))) - return self.encrypter(_sum)[:self.tag_size] - - def seal(self, nonce, plaintext, additional_data): - """Seal plaintext - - :param bytes nonce: blocksize-sized nonce. - Assure that it does not have MSB bit set - (:py:func:`pygost.mgm.nonce_prepare` helps) - :param bytes plaintext: plaintext to be encrypted and authenticated - :param bytes additional_data: additional data to be authenticated - """ - self._validate_nonce(nonce) - self._validate_sizes(plaintext, additional_data) - icn = bytearray(nonce) - ciphertext = self._crypt(icn, plaintext) - tag = self._auth(icn, ciphertext, additional_data) - return ciphertext + tag - - def open(self, nonce, ciphertext, additional_data): - """Open ciphertext - - :param bytes nonce: blocksize-sized nonce. - Assure that it does not have MSB bit set - (:py:func:`pygost.mgm.nonce_prepare` helps) - :param bytes ciphertext: ciphertext to be decrypted and authenticated - :param bytes additional_data: additional data to be authenticated - :raises ValueError: if ciphertext authentication fails - """ - self._validate_nonce(nonce) - self._validate_sizes(ciphertext, additional_data) - icn = bytearray(nonce) - ciphertext, tag_expected = ( - ciphertext[:-self.tag_size], - ciphertext[-self.tag_size:], - ) - tag = self._auth(icn, ciphertext, additional_data) - if not compare_digest(tag_expected, tag): - raise ValueError("Invalid authentication tag") - return self._crypt(icn, ciphertext) diff --git a/pygost-5.13/pygost/pbkdf2.py b/pygost-5.13/pygost/pbkdf2.py deleted file mode 100644 index 0fd6ddc..0000000 --- a/pygost-5.13/pygost/pbkdf2.py +++ /dev/null @@ -1,41 +0,0 @@ -# coding: utf-8 -"""PBKDF2 implementation suitable for GOST R 34.11-94/34.11-2012. - -This implementation is based on Python 3.5.2 source code's one. -PyGOST does not register itself in hashlib anyway, so use it instead. -""" - - -from pygost.utils import bytes2long -from pygost.utils import long2bytes -from pygost.utils import strxor -from pygost.utils import xrange - - -def pbkdf2(hasher, password, salt, iterations, dklen): - """PBKDF2 implementation suitable for GOST R 34.11-94/34.11-2012 - """ - inner = hasher() - outer = hasher() - password = password + b"\x00" * (inner.block_size - len(password)) - inner.update(strxor(password, len(password) * b"\x36")) - outer.update(strxor(password, len(password) * b"\x5C")) - - def prf(msg): - icpy = inner.copy() - ocpy = outer.copy() - icpy.update(msg) - ocpy.update(icpy.digest()) - return ocpy.digest() - - dkey = b"" - loop = 1 - while len(dkey) < dklen: - prev = prf(salt + long2bytes(loop, 4)) - rkey = bytes2long(prev) - for _ in xrange(iterations - 1): - prev = prf(prev) - rkey ^= bytes2long(prev) - loop += 1 - dkey += long2bytes(rkey, inner.digest_size) - return dkey[:dklen] diff --git a/pygost-5.13/pygost/stubs/pygost/__init__.pyi b/pygost-5.13/pygost/stubs/pygost/__init__.pyi deleted file mode 100644 index e69de29..0000000 diff --git a/pygost-5.13/pygost/stubs/pygost/gost28147.pyi b/pygost-5.13/pygost/stubs/pygost/gost28147.pyi deleted file mode 100644 index be31261..0000000 --- a/pygost-5.13/pygost/stubs/pygost/gost28147.pyi +++ /dev/null @@ -1,101 +0,0 @@ -from typing import Callable -from typing import Dict -from typing import Sequence -from typing import Tuple - - -SBOXES = ... # type: Dict[str, Tuple[Tuple[int, ...], ...]] -BLOCKSIZE = ... # type: int - -Words = Tuple[int, int] - - -def block2ns(data: bytes) -> Words: ... - - -def ns2block(ns: Words) -> bytes: ... - - -def validate_key(key: bytes) -> None: ... - - -def validate_iv(iv: bytes) -> None: ... - - -def validate_sbox(sbox: str) -> None: ... - - -def xcrypt(seq: Sequence[int], sbox: str, key: bytes, ns: Words) -> Words: ... - - -def encrypt(sbox: str, key: bytes, ns: Words) -> Words: ... - - -def decrypt(sbox: str, key: bytes, ns: Words) -> Words: ... - - -def ecb( - key: bytes, - data: bytes, - action: Callable[[str, bytes, Words], Words], - sbox: str = ..., -) -> bytes: ... - - -def ecb_encrypt( - key: bytes, - data: bytes, - sbox: str = ..., -) -> bytes: ... - - -def ecb_decrypt( - key: bytes, - data: bytes, - sbox: str = ..., -) -> bytes: ... - - -def cbc_encrypt( - key: bytes, - data: bytes, - iv: bytes = ..., - pad: bool = ..., - sbox: str = ..., - mesh: bool = ..., -) -> bytes: ... - - -def cbc_decrypt( - key: bytes, - data: bytes, - pad: bool = ..., - sbox: str = ..., - mesh: bool = ..., -) -> bytes: ... - - -def cnt( - key: bytes, - data: bytes, - iv: bytes = ..., - sbox: str = ..., -) -> bytes: ... - - -def cfb_encrypt( - key: bytes, - data: bytes, - iv: bytes = ..., - sbox: str = ..., - mesh: bool = ..., -) -> bytes: ... - - -def cfb_decrypt( - key: bytes, - data: bytes, - iv: bytes = ..., - sbox: str = ..., - mesh: bool = ..., -) -> bytes: ... diff --git a/pygost-5.13/pygost/stubs/pygost/gost28147_mac.pyi b/pygost-5.13/pygost/stubs/pygost/gost28147_mac.pyi deleted file mode 100644 index 70d90d6..0000000 --- a/pygost-5.13/pygost/stubs/pygost/gost28147_mac.pyi +++ /dev/null @@ -1,25 +0,0 @@ -from pygost.iface import PEP247 - - -class MAC(PEP247): - def __init__( - self, - key: bytes, - data: bytes = ..., - iv: bytes = ..., - sbox: str = ..., - ) -> None: ... - - @property - def digest_size(self) -> int: ... - - def copy(self) -> "MAC": ... - - def update(self, data: bytes) -> None: ... - - def digest(self) -> bytes: ... - - def hexdigest(self) -> str: ... - - -def new(key: bytes, data: bytes = ..., iv: bytes = ..., sbox: str = ...) -> MAC: ... diff --git a/pygost-5.13/pygost/stubs/pygost/gost3410.pyi b/pygost-5.13/pygost/stubs/pygost/gost3410.pyi deleted file mode 100644 index 8f0dcb8..0000000 --- a/pygost-5.13/pygost/stubs/pygost/gost3410.pyi +++ /dev/null @@ -1,72 +0,0 @@ -from typing import Dict -from typing import Tuple - - -DEFAULT_CURVE = ... # type: GOST3410Curve -CURVES = ... # type: Dict[str, GOST3410Curve] -PublicKey = Tuple[int, int] - - -class GOST3410Curve(object): - p = ... # type: int - q = ... # type: int - a = ... # type: int - b = ... # type: int - x = ... # type: int - y = ... # type: int - cofactor = ... # type: int - e = ... # type: int - d = ... # type: int - name = ... # type: str - - def __init__( - self, - p: int, - q: int, - a: int, - b: int, - x: int, - y: int, - cofactor: int = 1, - e: int = None, - d: int = None, - name: str = None, - ) -> None: ... - - def pos(self, v: int) -> int: ... - - def exp(self, degree: int, x: int = ..., y: int = ...) -> int: ... - - def st(self) -> Tuple[int, int]: ... - - @property - def point_size(self) -> int: ... - - def contains(self, point: Tuple[int, int]) -> bool: ... - - -def public_key(curve: GOST3410Curve, prv: int) -> PublicKey: ... - - -def sign(curve: GOST3410Curve, prv: int, digest: bytes, rand: bytes = None) -> bytes: ... - - -def verify(curve: GOST3410Curve, pub: PublicKey, digest: bytes, signature: bytes) -> bool: ... - - -def prv_unmarshal(prv: bytes) -> int: ... - - -def prv_marshal(curve: GOST3410Curve, prv: int) -> bytes: ... - - -def pub_marshal(pub: PublicKey) -> bytes: ... - - -def pub_unmarshal(pub: bytes) -> PublicKey: ... - - -def uv2xy(curve: GOST3410Curve, u: int, v: int) -> Tuple[int, int]: ... - - -def xy2uv(curve: GOST3410Curve, x: int, y: int) -> Tuple[int, int]: ... diff --git a/pygost-5.13/pygost/stubs/pygost/gost3410_vko.pyi b/pygost-5.13/pygost/stubs/pygost/gost3410_vko.pyi deleted file mode 100644 index 6ea9b82..0000000 --- a/pygost-5.13/pygost/stubs/pygost/gost3410_vko.pyi +++ /dev/null @@ -1,17 +0,0 @@ -from pygost.gost3410 import GOST3410Curve -from pygost.gost3410 import PublicKey - - -def ukm_unmarshal(ukm: bytes) -> int: ... - - -def kek(curve: GOST3410Curve, prv: int, pub: PublicKey, ukm: int) -> bytes: ... - - -def kek_34102001(curve: GOST3410Curve, prv: int, pub: PublicKey, ukm: int) -> bytes: ... - - -def kek_34102012256(curve: GOST3410Curve, prv: int, pub: PublicKey, ukm: int = ...) -> bytes: ... - - -def kek_34102012512(curve: GOST3410Curve, prv: int, pub: PublicKey, ukm: int = ...) -> bytes: ... diff --git a/pygost-5.13/pygost/stubs/pygost/gost34112012.pyi b/pygost-5.13/pygost/stubs/pygost/gost34112012.pyi deleted file mode 100644 index 3d5cc41..0000000 --- a/pygost-5.13/pygost/stubs/pygost/gost34112012.pyi +++ /dev/null @@ -1,18 +0,0 @@ -from pygost.iface import PEP247 - - -class GOST34112012(PEP247): - block_size = ... # type: int - - def __init__(self, data: bytes = ..., digest_size: int = ...) -> None: ... - - @property - def digest_size(self) -> int: ... - - def copy(self) -> "GOST34112012": ... - - def update(self, data: bytes) -> None: ... - - def digest(self) -> bytes: ... - - def hexdigest(self) -> str: ... diff --git a/pygost-5.13/pygost/stubs/pygost/gost34112012256.pyi b/pygost-5.13/pygost/stubs/pygost/gost34112012256.pyi deleted file mode 100644 index a1d2a01..0000000 --- a/pygost-5.13/pygost/stubs/pygost/gost34112012256.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from pygost.iface import PEP247 - - -class GOST34112012256(PEP247): - block_size = ... # type: int - - def __init__(self, data: bytes = ...) -> None: ... - - @property - def digest_size(self) -> int: ... - - def copy(self) -> "GOST34112012256": ... - - def update(self, data: bytes) -> None: ... - - def digest(self) -> bytes: ... - - def hexdigest(self) -> str: ... - - -def new(data: bytes = ...) -> GOST34112012256: ... diff --git a/pygost-5.13/pygost/stubs/pygost/gost34112012512.pyi b/pygost-5.13/pygost/stubs/pygost/gost34112012512.pyi deleted file mode 100644 index 349bddd..0000000 --- a/pygost-5.13/pygost/stubs/pygost/gost34112012512.pyi +++ /dev/null @@ -1,24 +0,0 @@ -from pygost.iface import PEP247 - - -class GOST34112012512(PEP247): - block_size = ... # type: int - - def __init__(self, data: bytes = ...) -> None: ... - - @property - def digest_size(self) -> int: ... - - def copy(self) -> "GOST34112012512": ... - - def update(self, data: bytes) -> None: ... - - def digest(self) -> bytes: ... - - def hexdigest(self) -> str: ... - - -def new(data: bytes = ...) -> GOST34112012512: ... - - -def pbkdf2(password: bytes, salt: bytes, iterations: int, dklen: int) -> bytes: ... diff --git a/pygost-5.13/pygost/stubs/pygost/gost341194.pyi b/pygost-5.13/pygost/stubs/pygost/gost341194.pyi deleted file mode 100644 index 24de2e4..0000000 --- a/pygost-5.13/pygost/stubs/pygost/gost341194.pyi +++ /dev/null @@ -1,25 +0,0 @@ -from pygost.iface import PEP247 - - -class GOST341194(PEP247): - sbox = ... # type: str - block_size = ... # type: int - - def __init__(self, data: bytes = ..., sbox: str = ...) -> None: ... - - @property - def digest_size(self) -> int: ... - - def copy(self) -> "GOST341194": ... - - def update(self, data: bytes) -> None: ... - - def digest(self) -> bytes: ... - - def hexdigest(self) -> str: ... - - -def new(data: bytes = ..., sbox: str = ...) -> GOST341194: ... - - -def pbkdf2(password: bytes, salt: bytes, iterations: int, dklen: int) -> bytes: ... diff --git a/pygost-5.13/pygost/stubs/pygost/gost3412.pyi b/pygost-5.13/pygost/stubs/pygost/gost3412.pyi deleted file mode 100644 index ef278b7..0000000 --- a/pygost-5.13/pygost/stubs/pygost/gost3412.pyi +++ /dev/null @@ -1,18 +0,0 @@ -class GOST3412Kuznechik(object): - blocksize = ... # type: int - - def __init__(self, key: bytes) -> None: ... - - def encrypt(self, blk: bytes) -> bytes: ... - - def decrypt(self, blk: bytes) -> bytes: ... - - -class GOST3412Magma(object): - blocksize = ... # type: int - - def __init__(self, key: bytes) -> None: ... - - def encrypt(self, blk: bytes) -> bytes: ... - - def decrypt(self, blk: bytes) -> bytes: ... diff --git a/pygost-5.13/pygost/stubs/pygost/gost3413.pyi b/pygost-5.13/pygost/stubs/pygost/gost3413.pyi deleted file mode 100644 index 4cfd694..0000000 --- a/pygost-5.13/pygost/stubs/pygost/gost3413.pyi +++ /dev/null @@ -1,81 +0,0 @@ -from typing import Callable - - -def pad_size(data_size: int, blocksize: int) -> int: ... - - -def pad1(data: bytes, blocksize: int) -> bytes: ... - - -def pad2(data: bytes, blocksize: int) -> bytes: ... - - -def unpad2(data: bytes, blocksize: int) -> bytes: ... - - -def pad3(data: bytes, blocksize: int) -> bytes: ... - - -def ecb_encrypt(encrypter: Callable[[bytes], bytes], bs: int, pt: bytes) -> bytes: ... - - -def ecb_decrypt(decrypter: Callable[[bytes], bytes], bs: int, ct: bytes) -> bytes: ... - - -def acpkm(encrypter: Callable[[bytes], bytes], bs: int) -> bytes: ... - - -def ctr(encrypter: Callable[[bytes], bytes], bs: int, data: bytes, iv: bytes) -> bytes: ... - - -def ctr_acpkm( - algo_class: object, - encrypter: Callable[[bytes], bytes], - section_size: int, - bs: int, - data: bytes, - iv: bytes, -) -> bytes: ... - - -def ofb(encrypter: Callable[[bytes], bytes], bs: int, data: bytes, iv: bytes) -> bytes: ... - - -def cbc_encrypt(encrypter: Callable[[bytes], bytes], bs: int, pt: bytes, iv: bytes) -> bytes: ... - - -def cbc_decrypt(decrypter: Callable[[bytes], bytes], bs: int, ct: bytes, iv: bytes) -> bytes: ... - - -def cfb_encrypt(encrypter: Callable[[bytes], bytes], bs: int, pt: bytes, iv: bytes) -> bytes: ... - - -def cfb_decrypt(encrypter: Callable[[bytes], bytes], bs: int, ct: bytes, iv: bytes) -> bytes: ... - - -def mac(encrypter: Callable[[bytes], bytes], bs: int, data: bytes) -> bytes: ... - - -def acpkm_master( - algo_class: object, - encrypter: Callable[[bytes], bytes], - key_section_size: int, - bs: int, - keymat_len: int, -) -> bytes: ... - - -def mac_acpkm_master( - algo_class: object, - encrypter: Callable[[bytes], bytes], - key_section_size: int, - section_size: int, - bs: int, - data: bytes, -) -> bytes: ... - - -def pad_iso10126(data: bytes, blocksize: int) -> bytes: ... - - -def unpad_iso10126(data: bytes, blocksize: int) -> bytes: ... diff --git a/pygost-5.13/pygost/stubs/pygost/iface.pyi b/pygost-5.13/pygost/stubs/pygost/iface.pyi deleted file mode 100644 index a5c2a85..0000000 --- a/pygost-5.13/pygost/stubs/pygost/iface.pyi +++ /dev/null @@ -1,19 +0,0 @@ -from abc import ABCMeta -from abc import abstractmethod - - -class PEP247(metaclass=ABCMeta): - @abstractmethod - @property - def digest_size(self) -> int: ... - - @abstractmethod - def copy(self) -> "PEP247": ... - - @abstractmethod - def update(self, data: bytes) -> None: ... - - @abstractmethod - def digest(self) -> bytes: ... - - def hexdigest(self) -> str: ... diff --git a/pygost-5.13/pygost/stubs/pygost/kdf.pyi b/pygost-5.13/pygost/stubs/pygost/kdf.pyi deleted file mode 100644 index ccab8af..0000000 --- a/pygost-5.13/pygost/stubs/pygost/kdf.pyi +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Sequence -from typing import Tuple - -from pygost.gost3410 import GOST3410Curve - - -PublicKey = Tuple[int, int] - - -def kdf_gostr3411_2012_256(key: bytes, label: bytes, seed: bytes) -> bytes: ... - - -def kdf_tree_gostr3411_2012_256( - key: bytes, - label: bytes, - seed: bytes, - keys: int, - i_len: int = 1, -) -> Sequence[bytes]: ... - - -def keg(curve: GOST3410Curve, prv: int, pub: PublicKey, h: bytes) -> bytes: ... diff --git a/pygost-5.13/pygost/stubs/pygost/mgm.pyi b/pygost-5.13/pygost/stubs/pygost/mgm.pyi deleted file mode 100644 index 81906b7..0000000 --- a/pygost-5.13/pygost/stubs/pygost/mgm.pyi +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Callable - - -def nonce_prepare(nonce: bytes) -> bytes: ... - - -class MGM(object): - def __init__( - self, - encrypter: Callable[[bytes], bytes], - bs: int, - tag_size: int = None, - ) -> None: ... - - def seal(self, nonce: bytes, plaintext: bytes, additional_data: bytes) -> bytes: ... - - def open(self, nonce: bytes, ciphertext: bytes, additional_data: bytes) -> bytes: ... diff --git a/pygost-5.13/pygost/stubs/pygost/utils.pyi b/pygost-5.13/pygost/stubs/pygost/utils.pyi deleted file mode 100644 index 76460e5..0000000 --- a/pygost-5.13/pygost/stubs/pygost/utils.pyi +++ /dev/null @@ -1,19 +0,0 @@ -from typing import AnyStr - - -def strxor(a: bytes, b: bytes) -> bytes: ... - - -def hexdec(data: AnyStr) -> bytes: ... - - -def hexenc(data: bytes) -> str: ... - - -def bytes2long(raw: bytes) -> int: ... - - -def long2bytes(n: int, size: int = ...) -> bytes: ... - - -def modinvert(a: int, n: int) -> int: ... diff --git a/pygost-5.13/pygost/stubs/pygost/wrap.pyi b/pygost-5.13/pygost/stubs/pygost/wrap.pyi deleted file mode 100644 index 776a6e7..0000000 --- a/pygost-5.13/pygost/stubs/pygost/wrap.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Callable - - -def wrap_gost(ukm: bytes, kek: bytes, cek: bytes, sbox: str = ...) -> bytes: ... - - -def unwrap_gost(kek: bytes, data: bytes, sbox: str = ...) -> bytes: ... - - -def wrap_cryptopro(ukm: bytes, kek: bytes, cek: bytes, sbox: str = ...) -> bytes: ... - - -def unwrap_cryptopro(kek: bytes, data: bytes, sbox: str = ...) -> bytes: ... - - -def kexp15( - encrypter_key: Callable[[bytes], bytes], - encrypter_mac: Callable[[bytes], bytes], - bs: int, - key: bytes, - iv: bytes, -) -> bytes: ... - - -def kimp15( - encrypter_key: Callable[[bytes], bytes], - encrypter_mac: Callable[[bytes], bytes], - bs: int, - kexp: bytes, - iv: bytes, -) -> bytes: ... diff --git a/pygost-5.13/pygost/test_cms.py b/pygost-5.13/pygost/test_cms.py deleted file mode 100644 index 7e5781a..0000000 --- a/pygost-5.13/pygost/test_cms.py +++ /dev/null @@ -1,1078 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 six import text_type - -from pygost.gost28147 import cfb_decrypt -from pygost.gost3410 import CURVES -from pygost.gost3410 import prv_unmarshal -from pygost.gost3410 import pub_marshal -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.gost3412 import GOST3412Kuznechik -from pygost.gost3412 import GOST3412Magma -from pygost.gost3413 import ctr_acpkm -from pygost.gost3413 import KEYSIZE -from pygost.gost3413 import mac as omac -from pygost.kdf import kdf_tree_gostr3411_2012_256 -from pygost.kdf import keg -from pygost.utils import hexdec -from pygost.wrap import kimp15 -from pygost.wrap import unwrap_cryptopro -from pygost.wrap import unwrap_gost - -try: - from pyderasn import DecodePathDefBy - from pyderasn import OctetString - - from pygost.asn1schemas.cms import ContentInfo - from pygost.asn1schemas.cms import SignedAttributes - from pygost.asn1schemas.oids import id_cms_mac_attr - from pygost.asn1schemas.oids import id_envelopedData - from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm - from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm_omac - from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_wrap_kexp15 - from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm - from pygost.asn1schemas.oids import id_gostr3412_2015_magma_ctracpkm_omac - from pygost.asn1schemas.oids import id_gostr3412_2015_magma_wrap_kexp15 - from pygost.asn1schemas.oids import id_messageDigest - from pygost.asn1schemas.oids import id_tc26_agreement_gost3410_2012_256 - from pygost.asn1schemas.oids import id_tc26_agreement_gost3410_2012_512 - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256 - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512 - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetA - from pygost.asn1schemas.oids import id_tc26_gost3411_2012_256 - from pygost.asn1schemas.oids import id_tc26_gost3411_2012_512 - from pygost.asn1schemas.x509 import Certificate - from pygost.asn1schemas.x509 import GostR34102012PublicKeyParameters -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, - ): - content_info, tail = ContentInfo().decode(content_info_raw) - self.assertSequenceEqual(tail, b"") - self.assertIsNotNone(content_info["content"].defined) - _, signed_data = content_info["content"].defined - self.assertEqual(len(signed_data["signerInfos"]), 1) - curve = CURVES[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"]), - )) - - 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, - "id-GostR3410-2001-CryptoPro-XchA-ParamSet", - GOST34112012256, - ) - - 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, - "id-tc26-gost-3410-12-512-paramSetB", - GOST34112012512, - ) - - -@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"") - self.assertIsNotNone(content_info["content"].defined) - _, digested_data = content_info["content"].defined - 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 = "id-tc26-gost-28147-param-Z" - content_info, tail = ContentInfo().decode(content_info_raw, ctx={ - "defines_by_path": [ - ( - ( - "content", - DecodePathDefBy(id_envelopedData), - "recipientInfos", - any, - "ktri", - "encryptedKey", - DecodePathDefBy(spki_algorithm), - "transportParameters", - "ephemeralPublicKey", - "algorithm", - "algorithm", - ), - ( - ( - ("..", "subjectPublicKey"), - { - id_tc26_gost3410_2012_256: OctetString(), - id_tc26_gost3410_2012_512: OctetString(), - }, - ), - ), - ) for spki_algorithm in ( - id_tc26_gost3410_2012_256, - id_tc26_gost3410_2012_512, - ) - ], - }) - self.assertSequenceEqual(tail, b"") - self.assertIsNotNone(content_info["content"].defined) - _, enveloped_data = content_info["content"].defined - eci = enveloped_data["encryptedContentInfo"] - ri = enveloped_data["recipientInfos"][0] - self.assertIsNotNone(ri["ktri"]["encryptedKey"].defined) - _, encrypted_key = ri["ktri"]["encryptedKey"].defined - ukm = bytes(encrypted_key["transportParameters"]["ukm"]) - spk = encrypted_key["transportParameters"]["ephemeralPublicKey"]["subjectPublicKey"] - self.assertIsNotNone(spk.defined) - _, pub_key_their = spk.defined - curve = CURVES[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"]) - self.assertIsNotNone(eci["contentEncryptionAlgorithm"]["parameters"].defined) - _, encryption_params = eci["contentEncryptionAlgorithm"]["parameters"].defined - 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), - ) - - self.process_cms( - content_info_raw, - prv_key_our, - "id-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), - ukm_unmarshal(ukm), - ) - - self.process_cms( - content_info_raw, - prv_key_our, - "id-tc26-gost-3410-12-512-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 = "id-tc26-gost-28147-param-Z" - content_info, tail = ContentInfo().decode(content_info_raw, ctx={ - "defines_by_path": [ - ( - ( - "content", - DecodePathDefBy(id_envelopedData), - "recipientInfos", - any, - "kari", - "originator", - "originatorKey", - "algorithm", - "algorithm", - ), - ( - ( - ("..", "publicKey"), - { - id_tc26_gost3410_2012_256: OctetString(), - id_tc26_gost3410_2012_512: OctetString(), - }, - ), - ), - ) for _ in ( - id_tc26_gost3410_2012_256, - id_tc26_gost3410_2012_512, - ) - ], - }) - self.assertSequenceEqual(tail, b"") - self.assertIsNotNone(content_info["content"].defined) - _, enveloped_data = content_info["content"].defined - eci = enveloped_data["encryptedContentInfo"] - kari = enveloped_data["recipientInfos"][0]["kari"] - self.assertIsNotNone(kari["originator"]["originatorKey"]["publicKey"].defined) - _, pub_key_their = kari["originator"]["originatorKey"]["publicKey"].defined - ukm = bytes(kari["ukm"]) - rek = kari["recipientEncryptedKeys"][0] - curve = CURVES[curve_name] - kek = keker(curve, prv_key_our, bytes(pub_key_their), ukm) - self.assertIsNotNone(rek["encryptedKey"].defined) - _, encrypted_key = rek["encryptedKey"].defined - 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"]) - self.assertIsNotNone(eci["contentEncryptionAlgorithm"]["parameters"].defined) - _, encryption_params = eci["contentEncryptionAlgorithm"]["parameters"].defined - 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), - ) - - self.process_cms( - content_info_raw, - prv_key_our, - "id-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), - ukm_unmarshal(ukm), - ) - - self.process_cms( - content_info_raw, - prv_key_our, - "id-tc26-gost-3410-12-512-paramSetB", - keker, - b"Test message", - ) - - -@skipIf(not pyderasn_exists, "PyDERASN dependency is required") -class TestR132356510252019(TestCase): - """Test vectors from Р 1323565.1.025-2019 - """ - def setUp(self): - self.curve256 = CURVES["id-tc26-gost-3410-2012-256-paramSetA"] - self.curve512 = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - self.psk = hexdec("8F5EEF8814D228FB2BBC5612323730CFA33DB7263CC2C0A01A6C6953F33D61D5")[::-1] - - self.ca_prv = prv_unmarshal(hexdec("092F8D059E97E22B90B1AE99F0087FC4D26620B91550CBB437C191005A290810")[::-1]) - self.ca_pub = public_key(self.curve256, self.ca_prv) - self.ca_cert = Certificate().decod(b64decode(""" -MIIB8DCCAZ2gAwIBAgIEAYy6gTAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 -MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw -MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA4MQ0wCwYDVQQKEwRUSzI2MScwJQYD -VQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwaDAhBggqhQMHAQEB -ATAVBgkqhQMHAQIBAQEGCCqFAwcBAQICA0MABEAaSoKcjw54UACci6svELNF0IYM -RIW8urUsqamIpoG46XCqrVOuI6Q13N4dwcRsbZdqByf+GC2f5ZfO3baN5bTKo4GF -MIGCMGEGA1UdAQRaMFiAFIDZDPeZ+GZNk1OJjsCecS2npzESoTowODENMAsGA1UE -ChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYtYml0 -ggQBjLqBMB0GA1UdDgQWBBSA2Qz3mfhmTZNTiY7AnnEtp6cxEjAKBggqhQMHAQED -AgNBAAgv248F4OeNCkhlzJWec0evHYnMBlSzk1lDm0F875B7CqMrKh2MtJHXenbj -Gc2uRn2IwgmSf/LZDrYsKKqZSxk= -""")) - - self.sender256_prv = prv_unmarshal(hexdec("0B20810E449978C7C3B76C6FF77A16C532421139344A058EF56310B6B6F377E8")[::-1]) - self.sender256_pub = public_key(self.curve256, self.sender256_prv) - self.sender256_cert = Certificate().decod(b64decode(""" -MIIB8zCCAaCgAwIBAgIEAYy6gjAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 -MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw -MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRUSzI2MSowKAYD -VQQDEyFPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwaDAhBggqhQMH -AQEBATAVBgkqhQMHAQIBAQEGCCqFAwcBAQICA0MABECWKQ0TYllqg4GmY3tBJiyz -pXUN+aOV9WbmTUinqrmEHP7KCNzoAzFg+04SSQpNNSHpQnm+jLAZhuJaJfqZ6VbT -o4GFMIGCMGEGA1UdAQRaMFiAFIDZDPeZ+GZNk1OJjsCecS2npzESoTowODENMAsG -A1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYt -Yml0ggQBjLqBMB0GA1UdDgQWBBTRnChHSWbQYwnJC62n2zu5Njd03zAKBggqhQMH -AQEDAgNBAB41oijaXSEn58l78y2rhxY35/lKEq4XWZ70FtsNlVxWATyzgO5Wliwn -t1O4GoZsxx8r6T/i7VG65UNmQlwdOKQ= -""")) - - self.recipient256_prv = prv_unmarshal(hexdec("0DC8DC1FF2BC114BABC3F1CA8C51E4F58610427E197B1C2FBDBA4AE58CBFB7CE")[::-1]) - self.recipient256_pub = public_key(self.curve256, self.recipient256_prv) - self.recipient256_cert = Certificate().decod(b64decode(""" -MIIB8jCCAZ+gAwIBAgIEAYy6gzAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 -MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw -MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA6MQ0wCwYDVQQKEwRUSzI2MSkwJwYD -VQQDEyBSRUNJUElFTlQ6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdDBoMCEGCCqFAwcB -AQEBMBUGCSqFAwcBAgEBAQYIKoUDBwEBAgIDQwAEQL8nghlzLGMKWHuWhNMPMN5u -L6SkGqRiJ6qZxZb+4dPKbBT9LNVvNKtwUed+BeE5kfqOfolPgFusnL1rnO9yREOj -gYUwgYIwYQYDVR0BBFowWIAUgNkM95n4Zk2TU4mOwJ5xLaenMRKhOjA4MQ0wCwYD -VQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1i -aXSCBAGMuoEwHQYDVR0OBBYEFLue+PUb9Oe+pziBU+MvNejjgrzFMAoGCCqFAwcB -AQMCA0EAPP9Oad1/5jwokSjPpccsQ0xCdVYM+mGQ0IbpiZxQj8gnkt8sq4jR6Ya+ -I/BDkbZNDNE27TU1p3t5rE9NMEeViA== -""")) - - self.sender512_prv = prv_unmarshal(hexdec("F95A5D44C5245F63F2E7DF8E782C1924EADCB8D06C52D91023179786154CBDB1561B4DF759D69F67EE1FBD5B68800E134BAA12818DA4F3AC75B0E5E6F9256911")[::-1]) - self.sender512_pub = public_key(self.curve512, self.sender512_prv) - self.sender512_cert = Certificate().decod(b64decode(""" -MIICNjCCAeOgAwIBAgIEAYy6hDAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 -MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw -MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRUSzI2MSowKAYD -VQQDEyFPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDUxMi1iaXQwgaowIQYIKoUD -BwEBAQIwFQYJKoUDBwECAQIBBggqhQMHAQECAwOBhAAEgYC0i7davCkOGGVcYqFP -tS1fUIROzB0fYARIe0tclTRpare/qzRuVRapqzzO+K21LDpYVfDPs2Sqa13ZN+Ts -/JUlv59qCFB2cYpFyB/0kh4+K79yvz7r8+4WE0EmZf8T3ae/J1Jo6xGunecH1/G4 -hMts9HYLnxbwJDMNVGuIHV6gzqOBhTCBgjBhBgNVHQEEWjBYgBSA2Qz3mfhmTZNT -iY7AnnEtp6cxEqE6MDgxDTALBgNVBAoTBFRLMjYxJzAlBgNVBAMTHkNBIFRLMjY6 -IEdPU1QgMzQuMTAtMTIgMjU2LWJpdIIEAYy6gTAdBgNVHQ4EFgQUK+l9HAscONGx -zCcRpxRAmFHvlXowCgYIKoUDBwEBAwIDQQAbjA0Q41/rIKOOvjHKsAsoEJM+WJf6 -/PKXg2JaStthmw99bdtwwkU/qDbcje2tF6mt+XWyQBXwvfeES1GFY9fJ -""")) - - self.recipient512_prv = prv_unmarshal(hexdec("A50315981F0A7C7FC05B4EB9591A62B1F84BD6FD518ACFCEDF0A7C9CF388D1F18757C056ADA5B38CBF24CDDB0F1519EF72DB1712CEF1920952E94AF1F9C575DC")[::-1]) - self.recipient512_pub = public_key(self.curve512, self.recipient512_prv) - self.recipient512_cert = Certificate().decod(b64decode(""" -MIICNTCCAeKgAwIBAgIEAYy6hTAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 -MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw -MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA6MQ0wCwYDVQQKEwRUSzI2MSkwJwYD -VQQDEyBSRUNJUElFTlQ6IEdPU1QgMzQuMTAtMTIgNTEyLWJpdDCBqjAhBggqhQMH -AQEBAjAVBgkqhQMHAQIBAgEGCCqFAwcBAQIDA4GEAASBgKauwGYvUkzz19g0LP/p -zeRdmwy1m+QSy9W5ZrL/AGuJofm2ARjz40ozNbW6bp9hkHu8x66LX7u5zz+QeS2+ -X5om18UXriComgO0+qhZbc+Hzu0eQ8FjOd8LpLk3TzzfBltfLOX5IiPLjeum+pSP -0QjoXAVcrop//B4yvZIukvROo4GFMIGCMGEGA1UdAQRaMFiAFIDZDPeZ+GZNk1OJ -jsCecS2npzESoTowODENMAsGA1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjog -R09TVCAzNC4xMC0xMiAyNTYtYml0ggQBjLqBMB0GA1UdDgQWBBSrXT5VKhm/5uff -kwW0XpG19k6AajAKBggqhQMHAQEDAgNBAAJBpsHRrQKZGb22LOzaReEB8rl2MbIR -ja64NaM5h+cAFoHm6t/k+ziLh2A11rTakR+5of4NQ3EjEhuPtomP2tc= -""")) - - def test_certs(self): - """Certificates signatures - """ - for prv, pub, curve, cert in ( - (self.ca_prv, self.ca_pub, self.curve256, self.ca_cert), - (self.sender256_prv, self.sender256_pub, self.curve256, self.sender256_cert), - (self.recipient256_prv, self.recipient256_pub, self.curve256, self.recipient256_cert), - (self.sender512_prv, self.sender512_pub, self.curve512, self.sender512_cert), - (self.recipient512_prv, self.recipient512_pub, self.curve512, self.recipient512_cert), - ): - pub_our = public_key(curve, prv) - self.assertEqual(pub_our, pub) - self.assertSequenceEqual( - pub_marshal(pub_our), - bytes(OctetString().decod(bytes( - cert["tbsCertificate"]["subjectPublicKeyInfo"]["subjectPublicKey"] - ))), - ) - - for cert in ( - self.ca_cert, - self.sender256_cert, - self.recipient256_cert, - self.sender512_cert, - self.recipient512_cert, - ): - self.assertTrue(verify( - self.curve256, - self.ca_pub, - GOST34112012256(cert["tbsCertificate"].encode()).digest()[::-1], - bytes(cert["signatureValue"]), - )) - - def test_signed_with_attrs(self): - ci = ContentInfo().decod(b64decode(""" -MIIENwYJKoZIhvcNAQcCoIIEKDCCBCQCAQExDDAKBggqhQMHAQECAzA7BgkqhkiG -9w0BBwGgLgQsyu7t8vDu6/zt++kg7/Do7OXwIOTr/yDx8vDz6vLz8PsgU2lnbmVk -RGF0YS6gggI6MIICNjCCAeOgAwIBAgIEAYy6hDAKBggqhQMHAQEDAjA4MQ0wCwYD -VQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1i -aXQwHhcNMDEwMTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRU -SzI2MSowKAYDVQQDEyFPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDUxMi1iaXQw -gaowIQYIKoUDBwEBAQIwFQYJKoUDBwECAQIBBggqhQMHAQECAwOBhAAEgYC0i7da -vCkOGGVcYqFPtS1fUIROzB0fYARIe0tclTRpare/qzRuVRapqzzO+K21LDpYVfDP -s2Sqa13ZN+Ts/JUlv59qCFB2cYpFyB/0kh4+K79yvz7r8+4WE0EmZf8T3ae/J1Jo -6xGunecH1/G4hMts9HYLnxbwJDMNVGuIHV6gzqOBhTCBgjBhBgNVHQEEWjBYgBSA -2Qz3mfhmTZNTiY7AnnEtp6cxEqE6MDgxDTALBgNVBAoTBFRLMjYxJzAlBgNVBAMT -HkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdIIEAYy6gTAdBgNVHQ4EFgQU -K+l9HAscONGxzCcRpxRAmFHvlXowCgYIKoUDBwEBAwIDQQAbjA0Q41/rIKOOvjHK -sAsoEJM+WJf6/PKXg2JaStthmw99bdtwwkU/qDbcje2tF6mt+XWyQBXwvfeES1GF -Y9fJMYIBlDCCAZACAQEwQDA4MQ0wCwYDVQQKEwRUSzI2MScwJQYDVQQDEx5DQSBU -SzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQCBAGMuoQwCgYIKoUDBwEBAgOgga0w -GAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTkwMzIw -MTk1NTIyWjAiBgkqhkiG9w0BCWIxFQQTU2lnbmVkIGF0dHIncyB2YWx1ZTBPBgkq -hkiG9w0BCQQxQgRAUdPHEukF5BIfo9DoQIMdnB0ZLkzq0RueEUZSNv07A7C+GKWi -G62fueArg8uPCHPTUN6d/42p33fgMkEwH7f7cDAKBggqhQMHAQEBAgSBgGUnVka8 -FvTlClmOtj/FUUacBdE/nEBeMLOO/535VDYrXlftPE6zQf/4ghS7TQG2VRGQ3GWD -+L3+W09A7d5uyyTEbvgtdllUG0OyqFwKmJEaYsMin87SFVs0cn1PGV1fOKeLluZa -bLx5whxd+mzlpekL5i6ImRX+TpERxrA/xSe5 -""")) - _, sd = ci["content"].defined - content = bytes(sd["encapContentInfo"]["eContent"]) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры SignedData."), - ) - si = sd["signerInfos"][0] - self.assertEqual( - si["digestAlgorithm"]["algorithm"], - id_tc26_gost3411_2012_512, - ) - digest = [ - bytes(attr["attrValues"][0].defined[1]) for attr in si["signedAttrs"] - if attr["attrType"] == id_messageDigest - ][0] - self.assertSequenceEqual(digest, GOST34112012512(content).digest()) - self.assertTrue(verify( - self.curve512, - self.sender512_pub, - GOST34112012512( - SignedAttributes(si["signedAttrs"]).encode() - ).digest()[::-1], - bytes(si["signature"]), - )) - - def test_signed_without_attrs(self): - ci = ContentInfo().decod(b64decode(""" -MIIDAQYJKoZIhvcNAQcCoIIC8jCCAu4CAQExDDAKBggqhQMHAQECAjA7BgkqhkiG -9w0BBwGgLgQsyu7t8vDu6/zt++kg7/Do7OXwIOTr/yDx8vDz6vLz8PsgU2lnbmVk -RGF0YS6gggH3MIIB8zCCAaCgAwIBAgIEAYy6gjAKBggqhQMHAQEDAjA4MQ0wCwYD -VQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1i -aXQwHhcNMDEwMTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRU -SzI2MSowKAYDVQQDEyFPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQw -aDAhBggqhQMHAQEBATAVBgkqhQMHAQIBAQEGCCqFAwcBAQICA0MABECWKQ0TYllq -g4GmY3tBJiyzpXUN+aOV9WbmTUinqrmEHP7KCNzoAzFg+04SSQpNNSHpQnm+jLAZ -huJaJfqZ6VbTo4GFMIGCMGEGA1UdAQRaMFiAFIDZDPeZ+GZNk1OJjsCecS2npzES -oTowODENMAsGA1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4x -MC0xMiAyNTYtYml0ggQBjLqBMB0GA1UdDgQWBBTRnChHSWbQYwnJC62n2zu5Njd0 -3zAKBggqhQMHAQEDAgNBAB41oijaXSEn58l78y2rhxY35/lKEq4XWZ70FtsNlVxW -ATyzgO5Wliwnt1O4GoZsxx8r6T/i7VG65UNmQlwdOKQxgaIwgZ8CAQEwQDA4MQ0w -CwYDVQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1 -Ni1iaXQCBAGMuoIwCgYIKoUDBwEBAgIwCgYIKoUDBwEBAQEEQC6jZPA59szL9FiA -0wC71EBE42ap6gKxklT800cu2FvbLu972GJYNSI7+UeanVU37OVWyenEXi2E5HkU -94kBe8Q= -""")) - _, sd = ci["content"].defined - content = bytes(sd["encapContentInfo"]["eContent"]) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры SignedData."), - ) - si = sd["signerInfos"][0] - self.assertEqual( - si["digestAlgorithm"]["algorithm"], - id_tc26_gost3411_2012_256, - ) - self.assertTrue(verify( - self.curve256, - self.sender256_pub, - GOST34112012256(content).digest()[::-1], - bytes(si["signature"]), - )) - - def test_kari_ephemeral(self): - ci = ContentInfo().decod(b64decode(""" -MIIB/gYJKoZIhvcNAQcDoIIB7zCCAesCAQIxggFioYIBXgIBA6CBo6GBoDAXBggq -hQMHAQEBAjALBgkqhQMHAQIBAgEDgYQABIGAe+itJVNbHM35RHfzuwFJPYdPXqtW -8hNEF7Z/XFEE2T71SRkhFX7ozYKQNh/TkVY9D4vG0LnD9Znr/pJyOjpsNb+dPcKX -Kbk/0JQxoPGHxFzASVAFq0ov/yBe2XGFWMeKUqtaAr7SvoYS0oEhT5EuT8BXmecd -nRe7NqOzESpb15ahIgQgsqHxOcdOp03l11S7k3OH1k1HNa5F8m9ctrOzH2846FMw -FwYJKoUDBwEBBwIBMAoGCCqFAwcBAQYCMHYwdDBAMDgxDTALBgNVBAoTBFRLMjYx -JzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdAIEAYy6hQQw -SxLc18zMwzLwXbcKqYhV/VzsdBgVArOHsSBIbaThJWE7zI37VGPMQJM5VXJ7GVcL -MF0GCSqGSIb3DQEHATAfBgkqhQMHAQEFAgIwEgQQ6EeVlADDCz2cdEWKy+tM94Av -yIFl/Ie4VeFFuczTsMsIaOUEe3Jn9GeVp8hZSj3O2q4hslQ/u/+Gj4QkSHm/M0ih -ITAfBgkqhQMHAQAGAQExEgQQs1t6D3J3WCEvxunnEE15NQ== -""")) - _, ed = ci["content"].defined - kari = ed["recipientInfos"][0]["kari"] - orig_key = kari["originator"]["originatorKey"] - self.assertEqual(orig_key["algorithm"]["algorithm"], id_tc26_gost3410_2012_512) - self.assertEqual( - GostR34102012PublicKeyParameters().decod( - bytes(orig_key["algorithm"]["parameters"]) - )["publicKeyParamSet"], - id_tc26_gost3410_2012_512_paramSetA, - ) - orig_pub = pub_unmarshal( - bytes(OctetString().decod(bytes(orig_key["publicKey"]))), - ) - ukm = bytes(kari["ukm"]) - self.assertEqual( - kari["keyEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_wrap_kexp15, - ) - self.assertEqual( - kari["keyEncryptionAlgorithm"]["parameters"].defined[1]["algorithm"], - id_tc26_agreement_gost3410_2012_512, - ) - kexp = bytes(kari["recipientEncryptedKeys"][0]["encryptedKey"]) - keymat = keg(self.curve512, self.recipient512_prv, orig_pub, ukm) - kim, kek = keymat[:KEYSIZE], keymat[KEYSIZE:] - cek = kimp15( - GOST3412Kuznechik(kek).encrypt, - GOST3412Kuznechik(kim).encrypt, - GOST3412Kuznechik.blocksize, - kexp, - ukm[24:24 + GOST3412Kuznechik.blocksize // 2], - ) - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_ctracpkm_omac, - ) - eci_ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - self.assertEqual(ed["unprotectedAttrs"][0]["attrType"], id_cms_mac_attr) - encrypted_mac = bytes(ed["unprotectedAttrs"][0]["attrValues"][0].defined[1]) - encrypted_content = bytes(eci["encryptedContent"]) - cek_enc, cek_mac = kdf_tree_gostr3411_2012_256( - cek, b"kdf tree", eci_ukm[GOST3412Kuznechik.blocksize // 2:], 2, - ) - content_and_tag = ctr_acpkm( - GOST3412Kuznechik, - GOST3412Kuznechik(cek_enc).encrypt, - 256 * 1024, - GOST3412Kuznechik.blocksize, - encrypted_content + encrypted_mac, - eci_ukm[:GOST3412Kuznechik.blocksize // 2], - ) - content = content_and_tag[:-GOST3412Kuznechik.blocksize] - tag_expected = content_and_tag[-GOST3412Kuznechik.blocksize:] - self.assertSequenceEqual( - omac( - GOST3412Kuznechik(cek_mac).encrypt, - GOST3412Kuznechik.blocksize, - content, - ), - tag_expected, - ) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры EnvelopedData."), - ) - - def test_kari_static(self): - ci = ContentInfo().decod(b64decode(""" -MIIBawYJKoZIhvcNAQcDoIIBXDCCAVgCAQIxgfehgfQCAQOgQjBAMDgxDTALBgNV -BAoTBFRLMjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJp -dAIEAYy6gqEiBCBvcfyuSF57y8vVyaw8Z0ch3wjC4lPKTrpVRXty4Rhk5DAXBgkq -hQMHAQEHAQEwCgYIKoUDBwEBBgEwbjBsMEAwODENMAsGA1UEChMEVEsyNjEnMCUG -A1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYtYml0AgQBjLqDBChPbi6B -krXuLPexPAL2oUGCFWDGQHqINL5ExuMBG7/5XQRqriKARVa0MFkGCSqGSIb3DQEH -ATAbBgkqhQMHAQEFAQEwDgQMdNdCKnYAAAAwqTEDgC9O2bYyTGQJ8WUQGq0zHwzX -L0jFhWHTF1tcAxYmd9pX5i89UwIxhtYqyjX1QHju2g== -""")) - _, ed = ci["content"].defined - kari = ed["recipientInfos"][0]["kari"] - ukm = bytes(kari["ukm"]) - self.assertEqual( - kari["keyEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_magma_wrap_kexp15, - ) - self.assertEqual( - kari["keyEncryptionAlgorithm"]["parameters"].defined[1]["algorithm"], - id_tc26_agreement_gost3410_2012_256, - ) - kexp = bytes(kari["recipientEncryptedKeys"][0]["encryptedKey"]) - keymat = keg( - self.curve256, - self.recipient256_prv, - self.sender256_pub, - ukm, - ) - kim, kek = keymat[:KEYSIZE], keymat[KEYSIZE:] - cek = kimp15( - GOST3412Magma(kek).encrypt, - GOST3412Magma(kim).encrypt, - GOST3412Magma.blocksize, - kexp, - ukm[24:24 + GOST3412Magma.blocksize // 2], - ) - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_magma_ctracpkm, - ) - eci_ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - content = ctr_acpkm( - GOST3412Magma, - GOST3412Magma(cek).encrypt, - 8 * 1024, - GOST3412Magma.blocksize, - bytes(eci["encryptedContent"]), - eci_ukm[:GOST3412Magma.blocksize // 2], - ) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры EnvelopedData."), - ) - - def test_ktri_256(self): - ci = ContentInfo().decod(b64decode(""" -MIIBlQYJKoZIhvcNAQcDoIIBhjCCAYICAQAxggEcMIIBGAIBADBAMDgxDTALBgNV -BAoTBFRLMjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJp -dAIEAYy6gzAXBgkqhQMHAQEHAgEwCgYIKoUDBwEBBgEEgbcwgbQEMFiMredFR3Mv -3g2wqyVXRnrhYEBMNFaqqgBpHwPQh3bF98tt9HZPxRDCww0OPfxeuTBeMBcGCCqF -AwcBAQEBMAsGCSqFAwcBAgEBAQNDAARAdFJ9ww+3ptvQiaQpizCldNYhl4DB1rl8 -Fx/2FIgnwssCbYRQ+UuRsTk9dfLLTGJG3JIEXKFxXWBgOrK965A5pAQg9f2/EHxG -DfetwCe1a6uUDCWD+wp5dYOpfkry8YRDEJgwXQYJKoZIhvcNAQcBMB8GCSqFAwcB -AQUCATASBBDUHNxmVclO/v3OaY9P7jxOgC+sD9CHGlEMRUpfGn6yfFDMExmYeby8 -LzdPJe1MkYV0qQgdC1zI3nQ7/4taf+4zRA== -""")) - _, ed = ci["content"].defined - ktri = ed["recipientInfos"][0]["ktri"] - self.assertEqual( - ktri["keyEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_wrap_kexp15, - ) - self.assertEqual( - ktri["keyEncryptionAlgorithm"]["parameters"].defined[1]["algorithm"], - id_tc26_agreement_gost3410_2012_256, - ) - _, encrypted_key = ktri["encryptedKey"].defined - self.assertEqual( - encrypted_key["ephemeralPublicKey"]["algorithm"]["algorithm"], - id_tc26_gost3410_2012_256, - ) - pub = pub_unmarshal(bytes(OctetString().decod( - bytes(encrypted_key["ephemeralPublicKey"]["subjectPublicKey"]) - ))) - ukm = bytes(encrypted_key["ukm"]) - kexp = bytes(encrypted_key["encryptedKey"]) - keymat = keg(self.curve256, self.recipient256_prv, pub, ukm) - kim, kek = keymat[:KEYSIZE], keymat[KEYSIZE:] - cek = kimp15( - GOST3412Kuznechik(kek).encrypt, - GOST3412Kuznechik(kim).encrypt, - GOST3412Kuznechik.blocksize, - kexp, - ukm[24:24 + GOST3412Kuznechik.blocksize // 2], - ) - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_ctracpkm, - ) - eci_ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - content = ctr_acpkm( - GOST3412Kuznechik, - GOST3412Kuznechik(cek).encrypt, - 256 * 1024, - GOST3412Kuznechik.blocksize, - bytes(eci["encryptedContent"]), - eci_ukm[:GOST3412Kuznechik.blocksize // 2], - ) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры EnvelopedData."), - ) - - def test_ktri_512(self): - ci = ContentInfo().decod(b64decode(""" -MIIB5wYJKoZIhvcNAQcDoIIB2DCCAdQCAQAxggFXMIIBUwIBADBAMDgxDTALBgNVBAoTBFRL -MjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdAIEAYy6hTAXBgkq -hQMHAQEHAQEwCgYIKoUDBwEBBgIEgfIwge8EKDof9JLTJVuIfP+c+imDCGyOLtAYENkoXpeU -CdiGn0Lt65t3TN9G0bUwgaAwFwYIKoUDBwEBAQIwCwYJKoUDBwECAQIBA4GEAASBgDD9XXHn -0j4EwY3DGB1wzHeThPRDlCwIvpmqWy00QDhS3fLRWiETSe9uMLeg27zI/EiserKMasNZum/i -d09cmP8aTNIDNRtI5H9M0mH7LpEtY8L901MszvOKHLDYdemvz0JUqOvBtvoeQ6sV4Gl45zXx -HTzBWlBw1FLX/ITWLapaBCAa09foTeA+PObBznGuCOPoKy+xz/9IIVmZidI6EYkIrzBZBgkq -hkiG9w0BBwEwGwYJKoUDBwEBBQECMA4EDA4z1UwRL4WYzKFX/oAv8eEX3fWt6hxDpjO0rI7/ -CiJ/CwYGCKODJ9h63vAwlsWwcPwAjxcsLvCNlv6i4NqhGTAXBgkqhQMHAQAGAQExCgQIs2DT -LuZ22Yw= -""")) - _, ed = ci["content"].defined - ktri = ed["recipientInfos"][0]["ktri"] - self.assertEqual( - ktri["keyEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_magma_wrap_kexp15, - ) - self.assertEqual( - ktri["keyEncryptionAlgorithm"]["parameters"].defined[1]["algorithm"], - id_tc26_agreement_gost3410_2012_512, - ) - _, encrypted_key = ktri["encryptedKey"].defined - self.assertEqual( - encrypted_key["ephemeralPublicKey"]["algorithm"]["algorithm"], - id_tc26_gost3410_2012_512, - ) - pub = pub_unmarshal( - bytes(OctetString().decod( - bytes(encrypted_key["ephemeralPublicKey"]["subjectPublicKey"]) - )), - ) - ukm = bytes(encrypted_key["ukm"]) - kexp = bytes(encrypted_key["encryptedKey"]) - keymat = keg(self.curve512, self.recipient512_prv, pub, ukm) - kim, kek = keymat[:KEYSIZE], keymat[KEYSIZE:] - cek = kimp15( - GOST3412Magma(kek).encrypt, - GOST3412Magma(kim).encrypt, - GOST3412Magma.blocksize, - kexp, - ukm[24:24 + GOST3412Magma.blocksize // 2], - ) - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_magma_ctracpkm_omac, - ) - eci_ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - self.assertEqual(ed["unprotectedAttrs"][0]["attrType"], id_cms_mac_attr) - encrypted_mac = bytes(ed["unprotectedAttrs"][0]["attrValues"][0].defined[1]) - encrypted_content = bytes(eci["encryptedContent"]) - cek_enc, cek_mac = kdf_tree_gostr3411_2012_256( - cek, b"kdf tree", eci_ukm[GOST3412Magma.blocksize // 2:], 2, - ) - content_and_tag = ctr_acpkm( - GOST3412Magma, - GOST3412Magma(cek_enc).encrypt, - 8 * 1024, - GOST3412Magma.blocksize, - encrypted_content + encrypted_mac, - eci_ukm[:GOST3412Magma.blocksize // 2], - ) - content = content_and_tag[:-GOST3412Magma.blocksize] - tag_expected = content_and_tag[-GOST3412Magma.blocksize:] - self.assertSequenceEqual( - omac( - GOST3412Magma(cek_mac).encrypt, - GOST3412Magma.blocksize, - content, - ), - tag_expected, - ) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры EnvelopedData."), - ) - - def test_digested256(self): - ci = ContentInfo().decod(b64decode(""" -MH0GCSqGSIb3DQEHBaBwMG4CAQAwCgYIKoUDBwEBAgIwOwYJKoZIhvcNAQcBoC4ELMru7fLw -7uv87fvpIO/w6Ozl8CDk6/8g8fLw8+ry8/D7IERpZ2VzdERhdGEuBCD/esPQYsGkzxZV8uUM -IAWt6SI8KtxBP8NyG8AGbJ8i/Q== -""")) - _, dd = ci["content"].defined - eci = dd["encapContentInfo"] - self.assertSequenceEqual( - GOST34112012256(bytes(eci["eContent"])).digest(), - bytes(dd["digest"]), - ) - - def test_digested512(self): - ci = ContentInfo().decod(b64decode(""" -MIGfBgkqhkiG9w0BBwWggZEwgY4CAQAwCgYIKoUDBwEBAgMwOwYJKoZIhvcNAQcBoC4ELMru -7fLw7uv87fvpIO/w6Ozl8CDk6/8g8fLw8+ry8/D7IERpZ2VzdERhdGEuBEDe4VUvcKSRvU7R -FVhFjajXY+nJSUkUsoi3oOeJBnru4PErt8RusPrCJs614ciHCM+ehrC4a+M1Nbq77F/Wsa/v -""")) - _, dd = ci["content"].defined - eci = dd["encapContentInfo"] - self.assertSequenceEqual( - GOST34112012512(bytes(eci["eContent"])).digest(), - bytes(dd["digest"]), - ) - - def test_encrypted_kuznechik(self): - ci = ContentInfo().decod(b64decode(""" -MHEGCSqGSIb3DQEHBqBkMGICAQAwXQYJKoZIhvcNAQcBMB8GCSqFAwcBAQUCATASBBBSwX+z -yOEPPuGyfpsRG4AigC/P8ftTdQMStfIThVkE/vpJlwaHgGv83m2bsPayeyuqpoTeEMOaqGcO -0MxHWsC9hQ== -""")) - _, ed = ci["content"].defined - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_ctracpkm, - ) - ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - content = ctr_acpkm( - GOST3412Kuznechik, - GOST3412Kuznechik(self.psk).encrypt, - 256 * 1024, - GOST3412Kuznechik.blocksize, - bytes(eci["encryptedContent"]), - ukm[:GOST3412Kuznechik.blocksize // 2], - ) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры EncryptedData."), - ) - - def test_encrypted_magma(self): - ci = ContentInfo().decod(b64decode(""" -MIGIBgkqhkiG9w0BBwagezB5AgEAMFkGCSqGSIb3DQEHATAbBgkqhQMHAQEFAQIwDgQMuncO -u3uYPbI30vFCgC9Nsws4R09yLp6jUtadncWUPZGmCGpPKnXGgNHvEmUArgKJvu4FPHtLkHuL -eQXZg6EZMBcGCSqFAwcBAAYBATEKBAjCbQoH632oGA== -""")) - _, ed = ci["content"].defined - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_magma_ctracpkm_omac, - ) - ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - self.assertEqual(ed["unprotectedAttrs"][0]["attrType"], id_cms_mac_attr) - encrypted_mac = bytes(ed["unprotectedAttrs"][0]["attrValues"][0].defined[1]) - cek_enc, cek_mac = kdf_tree_gostr3411_2012_256( - self.psk, b"kdf tree", ukm[GOST3412Magma.blocksize // 2:], 2, - ) - content_and_tag = ctr_acpkm( - GOST3412Magma, - GOST3412Magma(cek_enc).encrypt, - 8 * 1024, - GOST3412Magma.blocksize, - bytes(eci["encryptedContent"]) + encrypted_mac, - ukm[:GOST3412Magma.blocksize // 2], - ) - content = content_and_tag[:-GOST3412Magma.blocksize] - tag_expected = content_and_tag[-GOST3412Magma.blocksize:] - self.assertSequenceEqual( - omac( - GOST3412Magma(cek_mac).encrypt, - GOST3412Magma.blocksize, - content, - ), - tag_expected, - ) - self.assertEqual( - content.decode("cp1251"), - text_type(u"Контрольный пример для структуры EncryptedData."), - ) diff --git a/pygost-5.13/pygost/test_gost28147.py b/pygost-5.13/pygost/test_gost28147.py deleted file mode 100644 index a83e930..0000000 --- a/pygost-5.13/pygost/test_gost28147.py +++ /dev/null @@ -1,384 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from unittest import TestCase - -from pygost.gost28147 import block2ns -from pygost.gost28147 import BLOCKSIZE -from pygost.gost28147 import cbc_decrypt -from pygost.gost28147 import cbc_encrypt -from pygost.gost28147 import cfb_decrypt -from pygost.gost28147 import cfb_encrypt -from pygost.gost28147 import cnt -from pygost.gost28147 import DEFAULT_SBOX -from pygost.gost28147 import ecb_decrypt -from pygost.gost28147 import ecb_encrypt -from pygost.gost28147 import encrypt -from pygost.gost28147 import KEYSIZE -from pygost.gost28147 import MESH_MAX_DATA -from pygost.gost28147 import ns2block -from pygost.utils import hexdec -from pygost.utils import strxor - - -class ECBTest(TestCase): - def test_gcl(self): - """Test vectors from libgcl3 - """ - sbox = "id-Gost28147-89-TestParamSet" - key = hexdec(b"0475f6e05038fbfad2c7c390edb3ca3d1547124291ae1e8a2f79cd9ed2bcefbd") - plaintext = bytes(bytearray(( - 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, - 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, - 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, - 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, - 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, - 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, - 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, - 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, - 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, 0x40, - 0x4f, 0x4e, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x48, - 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, - 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58, - 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x60, - 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, - 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70, - 0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x7a, 0x79, 0x78, - 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, - 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, - 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, - 0x9f, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, 0x99, 0x98, - 0xa7, 0xa6, 0xa5, 0xa4, 0xa3, 0xa2, 0xa1, 0xa0, - 0xaf, 0xae, 0xad, 0xac, 0xab, 0xaa, 0xa9, 0xa8, - 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0, - 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8, - 0xc7, 0xc6, 0xc5, 0xc4, 0xc3, 0xc2, 0xc1, 0xc0, - 0xcf, 0xce, 0xcd, 0xcc, 0xcb, 0xca, 0xc9, 0xc8, - 0xd7, 0xd6, 0xd5, 0xd4, 0xd3, 0xd2, 0xd1, 0xd0, - 0xdf, 0xde, 0xdd, 0xdc, 0xdb, 0xda, 0xd9, 0xd8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, - ))) - ciphertext = bytes(bytearray(( - 0x4b, 0x8c, 0x4c, 0x98, 0x15, 0xf2, 0x4a, 0xea, - 0x1e, 0xc3, 0x57, 0x09, 0xb3, 0xbc, 0x2e, 0xd1, - 0xe0, 0xd1, 0xf2, 0x22, 0x65, 0x2d, 0x59, 0x18, - 0xf7, 0xdf, 0xfc, 0x80, 0x4b, 0xde, 0x5c, 0x68, - 0x46, 0x53, 0x75, 0x53, 0xa7, 0x46, 0x0d, 0xec, - 0x05, 0x1f, 0x1b, 0xd3, 0x0a, 0x63, 0x1a, 0xb7, - 0x78, 0xc4, 0x43, 0xe0, 0x5d, 0x3e, 0xa4, 0x0e, - 0x2d, 0x7e, 0x23, 0xa9, 0x1b, 0xc9, 0x02, 0xbc, - 0x21, 0x0c, 0x84, 0xcb, 0x0d, 0x0a, 0x07, 0xc8, - 0x7b, 0xd0, 0xfb, 0xb5, 0x1a, 0x14, 0x04, 0x5c, - 0xa2, 0x53, 0x97, 0x71, 0x2e, 0x5c, 0xc2, 0x8f, - 0x39, 0x3f, 0x6f, 0x52, 0xf2, 0x30, 0x26, 0x4e, - 0x8c, 0xe0, 0xd1, 0x01, 0x75, 0x6d, 0xdc, 0xd3, - 0x03, 0x79, 0x1e, 0xca, 0xd5, 0xc1, 0x0e, 0x12, - 0x53, 0x0a, 0x78, 0xe2, 0x0a, 0xb1, 0x1c, 0xea, - 0x3a, 0xf8, 0x55, 0xb9, 0x7c, 0xe1, 0x0b, 0xba, - 0xa0, 0xc8, 0x96, 0xeb, 0x50, 0x5a, 0xd3, 0x60, - 0x43, 0xa3, 0x0f, 0x98, 0xdb, 0xd9, 0x50, 0x6d, - 0x63, 0x91, 0xaf, 0x01, 0x40, 0xe9, 0x75, 0x5a, - 0x46, 0x5c, 0x1f, 0x19, 0x4a, 0x0b, 0x89, 0x9b, - 0xc4, 0xf6, 0xf8, 0xf5, 0x2f, 0x87, 0x3f, 0xfa, - 0x26, 0xd4, 0xf8, 0x25, 0xba, 0x1f, 0x98, 0x82, - 0xfc, 0x26, 0xaf, 0x2d, 0xc0, 0xf9, 0xc4, 0x58, - 0x49, 0xfa, 0x09, 0x80, 0x02, 0x62, 0xa4, 0x34, - 0x2d, 0xcb, 0x5a, 0x6b, 0xab, 0x61, 0x5d, 0x08, - 0xd4, 0x26, 0xe0, 0x08, 0x13, 0xd6, 0x2e, 0x02, - 0x2a, 0x37, 0xe8, 0xd0, 0xcf, 0x36, 0xf1, 0xc7, - 0xc0, 0x3f, 0x9b, 0x21, 0x60, 0xbd, 0x29, 0x2d, - 0x2e, 0x01, 0x48, 0x4e, 0xf8, 0x8f, 0x20, 0x16, - 0x8a, 0xbf, 0x82, 0xdc, 0x32, 0x7a, 0xa3, 0x18, - 0x69, 0xd1, 0x50, 0x59, 0x31, 0x91, 0xf2, 0x6c, - 0x5a, 0x5f, 0xca, 0x58, 0x9a, 0xb2, 0x2d, 0xb2, - ))) - encrypted = ecb_encrypt(key, plaintext, sbox=sbox) - self.assertSequenceEqual(encrypted, ciphertext) - decrypted = ecb_decrypt(key, encrypted, sbox=sbox) - self.assertSequenceEqual(decrypted, plaintext) - - def test_cryptopp(self): - """Test vectors from Crypto++ 5.6.2 - """ - sbox = "AppliedCryptography" - data = ( - (b"BE5EC2006CFF9DCF52354959F1FF0CBFE95061B5A648C10387069C25997C0672", b"0DF82802B741A292", b"07F9027DF7F7DF89"), - (b"B385272AC8D72A5A8B344BC80363AC4D09BF58F41F540624CBCB8FDCF55307D7", b"1354EE9C0A11CD4C", b"4FB50536F960A7B1"), - (b"AEE02F609A35660E4097E546FD3026B032CD107C7D459977ADF489BEF2652262", b"6693D492C4B0CC39", b"670034AC0FA811B5"), - (b"320E9D8422165D58911DFC7D8BBB1F81B0ECD924023BF94D9DF7DCF7801240E0", b"99E2D13080928D79", b"8118FF9D3B3CFE7D"), - (b"C9F703BBBFC63691BFA3B7B87EA8FD5E8E8EF384EF733F1A61AEF68C8FFA265F", b"D1E787749C72814C", b"A083826A790D3E0C"), - (b"728FEE32F04B4C654AD7F607D71C660C2C2670D7C999713233149A1C0C17A1F0", b"D4C05323A4F7A7B5", b"4D1F2E6B0D9DE2CE"), - (b"35FC96402209500FCFDEF5352D1ABB038FE33FC0D9D58512E56370B22BAA133B", b"8742D9A05F6A3AF6", b"2F3BB84879D11E52"), - (b"D416F630BE65B7FE150656183370E07018234EE5DA3D89C4CE9152A03E5BFB77", b"F86506DA04E41CB8", b"96F0A5C77A04F5CE"), - ) - for key, pt, ct in data: - key = hexdec(key) - pt = hexdec(pt) - ct = hexdec(ct) - self.assertSequenceEqual(ecb_encrypt(key, pt, sbox=sbox), ct) - - def test_cryptomanager(self): - """Test vector from http://cryptomanager.com/tv.html - """ - sbox = "id-GostR3411-94-TestParamSet" - key = hexdec(b"75713134B60FEC45A607BB83AA3746AF4FF99DA6D1B53B5B1B402A1BAA030D1B") - self.assertSequenceEqual( - ecb_encrypt(key, hexdec(b"1122334455667788"), sbox=sbox), - hexdec(b"03251E14F9D28ACB"), - ) - - -class CFBTest(TestCase): - def test_cryptomanager(self): - """Test vector from http://cryptomanager.com/tv.html - """ - key = hexdec(b"75713134B60FEC45A607BB83AA3746AF4FF99DA6D1B53B5B1B402A1BAA030D1B") - sbox = "id-GostR3411-94-TestParamSet" - self.assertSequenceEqual( - cfb_encrypt( - key, - hexdec(b"112233445566778899AABBCCDD800000"), - iv=hexdec(b"0102030405060708"), - sbox=sbox, - ), - hexdec(b"6EE84586DD2BCA0CAD3616940E164242"), - ) - self.assertSequenceEqual( - cfb_decrypt( - key, - hexdec(b"6EE84586DD2BCA0CAD3616940E164242"), - iv=hexdec(b"0102030405060708"), - sbox=sbox, - ), - hexdec(b"112233445566778899AABBCCDD800000"), - ) - - def test_steps(self): - """Check step-by-step operation manually - """ - key = urandom(KEYSIZE) - iv = urandom(BLOCKSIZE) - plaintext = urandom(20) - ciphertext = cfb_encrypt(key, plaintext, iv) - - # First full block - step = encrypt(DEFAULT_SBOX, key, block2ns(iv)) - step = strxor(plaintext[:8], ns2block(step)) - self.assertSequenceEqual(step, ciphertext[:8]) - - # Second full block - step = encrypt(DEFAULT_SBOX, key, block2ns(step)) - step = strxor(plaintext[8:16], ns2block(step)) - self.assertSequenceEqual(step, ciphertext[8:16]) - - # Third non-full block - step = encrypt(DEFAULT_SBOX, key, block2ns(step)) - step = strxor(plaintext[16:] + 4 * b"\x00", ns2block(step)) - self.assertSequenceEqual(step[:4], ciphertext[16:]) - - def test_random(self): - """Random data with various sizes - """ - key = urandom(KEYSIZE) - iv = urandom(BLOCKSIZE) - for size in (5, 8, 16, 120): - pt = urandom(size) - self.assertSequenceEqual( - cfb_decrypt(key, cfb_encrypt(key, pt, iv), iv), - pt, - ) - - -class CTRTest(TestCase): - def test_gcl(self): - """Test vectors from libgcl3 - """ - sbox = "id-Gost28147-89-TestParamSet" - key = hexdec(b"0475f6e05038fbfad2c7c390edb3ca3d1547124291ae1e8a2f79cd9ed2bcefbd") - plaintext = bytes(bytearray(( - 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, - 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, - 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, - 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, - 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, - 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, - 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, - 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, - 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, 0x40, - 0x4f, 0x4e, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x48, - 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, - 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58, - 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x60, - 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, - 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70, - 0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x7a, 0x79, 0x78, - 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, - 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, - 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, - 0x9f, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, 0x99, 0x98, - 0xa7, 0xa6, 0xa5, 0xa4, 0xa3, 0xa2, 0xa1, 0xa0, - 0xaf, 0xae, 0xad, 0xac, 0xab, 0xaa, 0xa9, 0xa8, - 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0, - 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8, - 0xc7, 0xc6, 0xc5, 0xc4, 0xc3, 0xc2, 0xc1, 0xc0, - 0xcf, 0xce, 0xcd, 0xcc, 0xcb, 0xca, 0xc9, 0xc8, - 0xd7, 0xd6, 0xd5, 0xd4, 0xd3, 0xd2, 0xd1, 0xd0, - 0xdf, 0xde, 0xdd, 0xdc, 0xdb, 0xda, 0xd9, 0xd8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, - ))) - ciphertext = bytes(bytearray(( - 0x4a, 0x5e, 0x37, 0x6c, 0xa1, 0x12, 0xd3, 0x55, - 0x09, 0x13, 0x1a, 0x21, 0xac, 0xfb, 0xb2, 0x1e, - 0x8c, 0x24, 0x9b, 0x57, 0x20, 0x68, 0x46, 0xd5, - 0x23, 0x2a, 0x26, 0x35, 0x12, 0x56, 0x5c, 0x69, - 0x2a, 0x2f, 0xd1, 0xab, 0xbd, 0x45, 0xdc, 0x3a, - 0x1a, 0xa4, 0x57, 0x64, 0xd5, 0xe4, 0x69, 0x6d, - 0xb4, 0x8b, 0xf1, 0x54, 0x78, 0x3b, 0x10, 0x8f, - 0x7a, 0x4b, 0x32, 0xe0, 0xe8, 0x4c, 0xbf, 0x03, - 0x24, 0x37, 0x95, 0x6a, 0x55, 0xa8, 0xce, 0x6f, - 0x95, 0x62, 0x12, 0xf6, 0x79, 0xe6, 0xf0, 0x1b, - 0x86, 0xef, 0x36, 0x36, 0x05, 0xd8, 0x6f, 0x10, - 0xa1, 0x41, 0x05, 0x07, 0xf8, 0xfa, 0xa4, 0x0b, - 0x17, 0x2c, 0x71, 0xbc, 0x8b, 0xcb, 0xcf, 0x3d, - 0x74, 0x18, 0x32, 0x0b, 0x1c, 0xd2, 0x9e, 0x75, - 0xba, 0x3e, 0x61, 0xe1, 0x61, 0x96, 0xd0, 0xee, - 0x8f, 0xf2, 0x9a, 0x5e, 0xb7, 0x7a, 0x15, 0xaa, - 0x4e, 0x1e, 0x77, 0x7c, 0x99, 0xe1, 0x41, 0x13, - 0xf4, 0x60, 0x39, 0x46, 0x4c, 0x35, 0xde, 0x95, - 0xcc, 0x4f, 0xd5, 0xaf, 0xd1, 0x4d, 0x84, 0x1a, - 0x45, 0xc7, 0x2a, 0xf2, 0x2c, 0xc0, 0xb7, 0x94, - 0xa3, 0x08, 0xb9, 0x12, 0x96, 0xb5, 0x97, 0x99, - 0x3a, 0xb7, 0x0c, 0x14, 0x56, 0xb9, 0xcb, 0x49, - 0x44, 0xa9, 0x93, 0xa9, 0xfb, 0x19, 0x10, 0x8c, - 0x6a, 0x68, 0xe8, 0x7b, 0x06, 0x57, 0xf0, 0xef, - 0x88, 0x44, 0xa6, 0xd2, 0x98, 0xbe, 0xd4, 0x07, - 0x41, 0x37, 0x45, 0xa6, 0x71, 0x36, 0x76, 0x69, - 0x4b, 0x75, 0x15, 0x33, 0x90, 0x29, 0x6e, 0x33, - 0xcb, 0x96, 0x39, 0x78, 0x19, 0x2e, 0x96, 0xf3, - 0x49, 0x4c, 0x89, 0x3d, 0xa1, 0x86, 0x82, 0x00, - 0xce, 0xbd, 0x54, 0x29, 0x65, 0x00, 0x1d, 0x16, - 0x13, 0xc3, 0xfe, 0x1f, 0x8c, 0x55, 0x63, 0x09, - 0x1f, 0xcd, 0xd4, 0x28, 0xca, - ))) - iv = b"\x02\x01\x01\x01\x01\x01\x01\x01" - encrypted = cnt(key, plaintext, iv=iv, sbox=sbox) - self.assertSequenceEqual(encrypted, ciphertext) - decrypted = cnt(key, encrypted, iv=iv, sbox=sbox) - self.assertSequenceEqual(decrypted, plaintext) - - def test_gcl2(self): - """Test vectors 2 from libgcl3 - """ - sbox = "id-Gost28147-89-TestParamSet" - key = hexdec(b"fc7ad2886f455b50d29008fa622b57d5c65b3c637202025799cadf0768519e8a") - plaintext = bytes(bytearray(( - 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, - 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, - 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, - 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, - 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, - 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, - ))) - ciphertext = bytes(bytearray(( - 0xd0, 0xbe, 0x60, 0x1a, 0x2c, 0xf1, 0x90, 0x26, - 0x9b, 0x7b, 0x23, 0xb4, 0xd2, 0xcc, 0xe1, 0x15, - 0xf6, 0x05, 0x57, 0x28, 0x88, 0x75, 0xeb, 0x1e, - 0xd3, 0x62, 0xdc, 0xda, 0x9b, 0x62, 0xee, 0x9a, - 0x57, 0x87, 0x8a, 0xf1, 0x82, 0x37, 0x9c, 0x7f, - 0x13, 0xcc, 0x55, 0x38, 0xb5, 0x63, 0x32, 0xc5, - 0x23, 0xa4, 0xcb, 0x7d, 0x51, - ))) - iv = BLOCKSIZE * b"\x00" - encrypted = cnt(key, plaintext, iv=iv, sbox=sbox) - self.assertSequenceEqual(encrypted, ciphertext) - decrypted = cnt(key, encrypted, iv=iv, sbox=sbox) - self.assertSequenceEqual(decrypted, plaintext) - - -class CBCTest(TestCase): - def test_pad_requirement(self): - key = KEYSIZE * b"x" - for s in (b"", b"foo", b"foobarbaz"): - with self.assertRaises(ValueError): - cbc_encrypt(key, s, pad=False) - with self.assertRaises(ValueError): - cbc_decrypt(key, s, pad=False) - - def test_passes(self): - iv = urandom(BLOCKSIZE) - key = KEYSIZE * b"x" - for pt in (b"foo", b"foobarba", b"foobarbaz", 16 * b"x"): - ct = cbc_encrypt(key, pt, iv) - dt = cbc_decrypt(key, ct) - self.assertSequenceEqual(pt, dt) - - def test_iv_existence_check(self): - key = KEYSIZE * b"x" - with self.assertRaises(ValueError): - cbc_decrypt(key, BLOCKSIZE * b"x") - iv = urandom(BLOCKSIZE) - cbc_decrypt(key, cbc_encrypt(key, BLOCKSIZE * b"x", iv)) - - def test_meshing(self): - pt = urandom(MESH_MAX_DATA * 3) - key = urandom(KEYSIZE) - ct = cbc_encrypt(key, pt) - dt = cbc_decrypt(key, ct) - self.assertSequenceEqual(pt, dt) - - -class CFBMeshingTest(TestCase): - def setUp(self): - self.key = urandom(KEYSIZE) - self.iv = urandom(BLOCKSIZE) - - def test_single(self): - pt = b"\x00" - ct = cfb_encrypt(self.key, pt, mesh=True) - dec = cfb_decrypt(self.key, ct, mesh=True) - self.assertSequenceEqual(pt, dec) - - def test_short(self): - pt = urandom(MESH_MAX_DATA - 1) - ct = cfb_encrypt(self.key, pt, mesh=True) - dec = cfb_decrypt(self.key, ct, mesh=True) - dec_plain = cfb_decrypt(self.key, ct) - self.assertSequenceEqual(pt, dec) - self.assertSequenceEqual(pt, dec_plain) - - def test_short_iv(self): - pt = urandom(MESH_MAX_DATA - 1) - ct = cfb_encrypt(self.key, pt, iv=self.iv, mesh=True) - dec = cfb_decrypt(self.key, ct, iv=self.iv, mesh=True) - dec_plain = cfb_decrypt(self.key, ct, iv=self.iv) - self.assertSequenceEqual(pt, dec) - self.assertSequenceEqual(pt, dec_plain) - - def test_longer_iv(self): - pt = urandom(MESH_MAX_DATA * 3) - ct = cfb_encrypt(self.key, pt, iv=self.iv, mesh=True) - dec = cfb_decrypt(self.key, ct, iv=self.iv, mesh=True) - dec_plain = cfb_decrypt(self.key, ct, iv=self.iv) - self.assertSequenceEqual(pt, dec) - self.assertNotEqual(pt, dec_plain) diff --git a/pygost-5.13/pygost/test_gost28147_mac.py b/pygost-5.13/pygost/test_gost28147_mac.py deleted file mode 100644 index ec1af3f..0000000 --- a/pygost-5.13/pygost/test_gost28147_mac.py +++ /dev/null @@ -1,63 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 unittest import TestCase - -from pygost.gost28147_mac import MAC - - -class TestMAC(TestCase): - """Test vectors generated with libgcl3 library - """ - k = b"This is message\xFF length\x0032 bytes" - - def test_a(self): - self.assertSequenceEqual( - MAC(self.k, b"a").hexdigest(), - "bd5d3b5b2b7b57af", - ) - - def test_abc(self): - self.assertSequenceEqual( - MAC(self.k, b"abc").hexdigest(), - "28661e40805b1ff9", - ) - - def test_128U(self): - self.assertSequenceEqual( - MAC(self.k, 128 * b"U").hexdigest(), - "1a06d1bad74580ef", - ) - - def test_13x(self): - self.assertSequenceEqual( - MAC(self.k, 13 * b"x").hexdigest(), - "917ee1f1a668fbd3", - ) - - def test_parts(self): - m = MAC(self.k) - m.update(b"foo") - m.update(b"bar") - self.assertSequenceEqual(m.digest(), MAC(self.k, b"foobar").digest()) - - def test_copy(self): - m = MAC(self.k, b"foo") - c = m.copy() - m.update(b"barbaz") - c.update(b"bar") - c.update(b"baz") - self.assertSequenceEqual(m.digest(), c.digest()) diff --git a/pygost-5.13/pygost/test_gost3410.py b/pygost-5.13/pygost/test_gost3410.py deleted file mode 100644 index cd71535..0000000 --- a/pygost-5.13/pygost/test_gost3410.py +++ /dev/null @@ -1,495 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from unittest import TestCase - -from pygost.gost3410 import CURVES -from pygost.gost3410 import GOST3410Curve -from pygost.gost3410 import prv_marshal -from pygost.gost3410 import prv_unmarshal -from pygost.gost3410 import public_key -from pygost.gost3410 import sign -from pygost.gost3410 import uv2xy -from pygost.gost3410 import verify -from pygost.gost3410 import xy2uv -from pygost.utils import bytes2long -from pygost.utils import hexdec -from pygost.utils import hexenc -from pygost.utils import long2bytes - - -class Test341001(TestCase): - def test_rfc(self): - """Test vector from :rfc:`5832` - """ - prv = bytes(bytearray(( - 0x7A, 0x92, 0x9A, 0xDE, 0x78, 0x9B, 0xB9, 0xBE, - 0x10, 0xED, 0x35, 0x9D, 0xD3, 0x9A, 0x72, 0xC1, - 0x1B, 0x60, 0x96, 0x1F, 0x49, 0x39, 0x7E, 0xEE, - 0x1D, 0x19, 0xCE, 0x98, 0x91, 0xEC, 0x3B, 0x28 - ))) - pub_x = bytes(bytearray(( - 0x7F, 0x2B, 0x49, 0xE2, 0x70, 0xDB, 0x6D, 0x90, - 0xD8, 0x59, 0x5B, 0xEC, 0x45, 0x8B, 0x50, 0xC5, - 0x85, 0x85, 0xBA, 0x1D, 0x4E, 0x9B, 0x78, 0x8F, - 0x66, 0x89, 0xDB, 0xD8, 0xE5, 0x6F, 0xD8, 0x0B - ))) - pub_y = bytes(bytearray(( - 0x26, 0xF1, 0xB4, 0x89, 0xD6, 0x70, 0x1D, 0xD1, - 0x85, 0xC8, 0x41, 0x3A, 0x97, 0x7B, 0x3C, 0xBB, - 0xAF, 0x64, 0xD1, 0xC5, 0x93, 0xD2, 0x66, 0x27, - 0xDF, 0xFB, 0x10, 0x1A, 0x87, 0xFF, 0x77, 0xDA - ))) - digest = bytes(bytearray(( - 0x2D, 0xFB, 0xC1, 0xB3, 0x72, 0xD8, 0x9A, 0x11, - 0x88, 0xC0, 0x9C, 0x52, 0xE0, 0xEE, 0xC6, 0x1F, - 0xCE, 0x52, 0x03, 0x2A, 0xB1, 0x02, 0x2E, 0x8E, - 0x67, 0xEC, 0xE6, 0x67, 0x2B, 0x04, 0x3E, 0xE5 - ))) - signature = bytes(bytearray(( - 0x41, 0xAA, 0x28, 0xD2, 0xF1, 0xAB, 0x14, 0x82, - 0x80, 0xCD, 0x9E, 0xD5, 0x6F, 0xED, 0xA4, 0x19, - 0x74, 0x05, 0x35, 0x54, 0xA4, 0x27, 0x67, 0xB8, - 0x3A, 0xD0, 0x43, 0xFD, 0x39, 0xDC, 0x04, 0x93, - 0x01, 0x45, 0x6C, 0x64, 0xBA, 0x46, 0x42, 0xA1, - 0x65, 0x3C, 0x23, 0x5A, 0x98, 0xA6, 0x02, 0x49, - 0xBC, 0xD6, 0xD3, 0xF7, 0x46, 0xB6, 0x31, 0xDF, - 0x92, 0x80, 0x14, 0xF6, 0xC5, 0xBF, 0x9C, 0x40 - ))) - prv = bytes2long(prv) - signature = signature[32:] + signature[:32] - - c = CURVES["id-GostR3410-2001-TestParamSet"] - pubX, pubY = public_key(c, prv) - self.assertSequenceEqual(long2bytes(pubX), pub_x) - self.assertSequenceEqual(long2bytes(pubY), pub_y) - s = sign(c, prv, digest) - self.assertTrue(verify(c, (pubX, pubY), digest, s)) - self.assertTrue(verify(c, (pubX, pubY), digest, signature)) - - def test_sequence(self): - c = CURVES["id-GostR3410-2001-TestParamSet"] - prv = prv_unmarshal(urandom(32)) - pubX, pubY = public_key(c, prv_unmarshal(prv_marshal(c, prv))) - for _ in range(20): - digest = urandom(32) - s = sign(c, prv, digest) - self.assertTrue(verify(c, (pubX, pubY), digest, s)) - - -class Test34102012(TestCase): - def test_1(self): - """Test vector from 34.10-2012 standard itself - """ - curve = CURVES["id-GostR3410-2001-TestParamSet"] - prv = bytes2long(hexdec("7A929ADE789BB9BE10ED359DD39A72C11B60961F49397EEE1D19CE9891EC3B28")) - digest = hexdec("2DFBC1B372D89A1188C09C52E0EEC61FCE52032AB1022E8E67ECE6672B043EE5") - rand = hexdec("77105C9B20BCD3122823C8CF6FCC7B956DE33814E95B7FE64FED924594DCEAB3") - signature = sign(curve, prv, digest, rand) - r = "41aa28d2f1ab148280cd9ed56feda41974053554a42767b83ad043fd39dc0493" - s = "01456c64ba4642a1653c235a98a60249bcd6d3f746b631df928014f6c5bf9c40" - self.assertSequenceEqual(hexenc(signature), s + r) - - def test_2(self): - """Test vector from 34.10-2012 standard itself - """ - curve = GOST3410Curve( - p=3623986102229003635907788753683874306021320925534678605086546150450856166624002482588482022271496854025090823603058735163734263822371964987228582907372403, - q=3623986102229003635907788753683874306021320925534678605086546150450856166623969164898305032863068499961404079437936585455865192212970734808812618120619743, - a=7, - b=1518655069210828534508950034714043154928747527740206436194018823352809982443793732829756914785974674866041605397883677596626326413990136959047435811826396, - x=1928356944067022849399309401243137598997786635459507974357075491307766592685835441065557681003184874819658004903212332884252335830250729527632383493573274, - y=2288728693371972859970012155529478416353562327329506180314497425931102860301572814141997072271708807066593850650334152381857347798885864807605098724013854, - ) - prv = bytes2long(hexdec("0BA6048AADAE241BA40936D47756D7C93091A0E8514669700EE7508E508B102072E8123B2200A0563322DAD2827E2714A2636B7BFD18AADFC62967821FA18DD4")) - digest = hexdec("3754F3CFACC9E0615C4F4A7C4D8DAB531B09B6F9C170C533A71D147035B0C5917184EE536593F4414339976C647C5D5A407ADEDB1D560C4FC6777D2972075B8C") - rand = hexdec("0359E7F4B1410FEACC570456C6801496946312120B39D019D455986E364F365886748ED7A44B3E794434006011842286212273A6D14CF70EA3AF71BB1AE679F1") - signature = sign(curve, prv, digest, rand) - r = "2f86fa60a081091a23dd795e1e3c689ee512a3c82ee0dcc2643c78eea8fcacd35492558486b20f1c9ec197c90699850260c93bcbcd9c5c3317e19344e173ae36" - s = "1081b394696ffe8e6585e7a9362d26b6325f56778aadbc081c0bfbe933d52ff5823ce288e8c4f362526080df7f70ce406a6eeb1f56919cb92a9853bde73e5b4a" - self.assertSequenceEqual(hexenc(signature), s + r) - - def test_gcl3(self): - """Test vector from libgcl3 - """ - p = bytes2long(bytes(bytearray(( - 0x45, 0x31, 0xAC, 0xD1, 0xFE, 0x00, 0x23, 0xC7, - 0x55, 0x0D, 0x26, 0x7B, 0x6B, 0x2F, 0xEE, 0x80, - 0x92, 0x2B, 0x14, 0xB2, 0xFF, 0xB9, 0x0F, 0x04, - 0xD4, 0xEB, 0x7C, 0x09, 0xB5, 0xD2, 0xD1, 0x5D, - 0xF1, 0xD8, 0x52, 0x74, 0x1A, 0xF4, 0x70, 0x4A, - 0x04, 0x58, 0x04, 0x7E, 0x80, 0xE4, 0x54, 0x6D, - 0x35, 0xB8, 0x33, 0x6F, 0xAC, 0x22, 0x4D, 0xD8, - 0x16, 0x64, 0xBB, 0xF5, 0x28, 0xBE, 0x63, 0x73, - )))) - q = bytes2long(bytes(bytearray(( - 0x45, 0x31, 0xAC, 0xD1, 0xFE, 0x00, 0x23, 0xC7, - 0x55, 0x0D, 0x26, 0x7B, 0x6B, 0x2F, 0xEE, 0x80, - 0x92, 0x2B, 0x14, 0xB2, 0xFF, 0xB9, 0x0F, 0x04, - 0xD4, 0xEB, 0x7C, 0x09, 0xB5, 0xD2, 0xD1, 0x5D, - 0xA8, 0x2F, 0x2D, 0x7E, 0xCB, 0x1D, 0xBA, 0xC7, - 0x19, 0x90, 0x5C, 0x5E, 0xEC, 0xC4, 0x23, 0xF1, - 0xD8, 0x6E, 0x25, 0xED, 0xBE, 0x23, 0xC5, 0x95, - 0xD6, 0x44, 0xAA, 0xF1, 0x87, 0xE6, 0xE6, 0xDF, - )))) - a = bytes2long(bytes(bytearray(( - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, - )))) - b = bytes2long(bytes(bytearray(( - 0x1C, 0xFF, 0x08, 0x06, 0xA3, 0x11, 0x16, 0xDA, - 0x29, 0xD8, 0xCF, 0xA5, 0x4E, 0x57, 0xEB, 0x74, - 0x8B, 0xC5, 0xF3, 0x77, 0xE4, 0x94, 0x00, 0xFD, - 0xD7, 0x88, 0xB6, 0x49, 0xEC, 0xA1, 0xAC, 0x43, - 0x61, 0x83, 0x40, 0x13, 0xB2, 0xAD, 0x73, 0x22, - 0x48, 0x0A, 0x89, 0xCA, 0x58, 0xE0, 0xCF, 0x74, - 0xBC, 0x9E, 0x54, 0x0C, 0x2A, 0xDD, 0x68, 0x97, - 0xFA, 0xD0, 0xA3, 0x08, 0x4F, 0x30, 0x2A, 0xDC, - )))) - x = bytes2long(bytes(bytearray(( - 0x24, 0xD1, 0x9C, 0xC6, 0x45, 0x72, 0xEE, 0x30, - 0xF3, 0x96, 0xBF, 0x6E, 0xBB, 0xFD, 0x7A, 0x6C, - 0x52, 0x13, 0xB3, 0xB3, 0xD7, 0x05, 0x7C, 0xC8, - 0x25, 0xF9, 0x10, 0x93, 0xA6, 0x8C, 0xD7, 0x62, - 0xFD, 0x60, 0x61, 0x12, 0x62, 0xCD, 0x83, 0x8D, - 0xC6, 0xB6, 0x0A, 0xA7, 0xEE, 0xE8, 0x04, 0xE2, - 0x8B, 0xC8, 0x49, 0x97, 0x7F, 0xAC, 0x33, 0xB4, - 0xB5, 0x30, 0xF1, 0xB1, 0x20, 0x24, 0x8A, 0x9A, - )))) - y = bytes2long(bytes(bytearray(( - 0x2B, 0xB3, 0x12, 0xA4, 0x3B, 0xD2, 0xCE, 0x6E, - 0x0D, 0x02, 0x06, 0x13, 0xC8, 0x57, 0xAC, 0xDD, - 0xCF, 0xBF, 0x06, 0x1E, 0x91, 0xE5, 0xF2, 0xC3, - 0xF3, 0x24, 0x47, 0xC2, 0x59, 0xF3, 0x9B, 0x2C, - 0x83, 0xAB, 0x15, 0x6D, 0x77, 0xF1, 0x49, 0x6B, - 0xF7, 0xEB, 0x33, 0x51, 0xE1, 0xEE, 0x4E, 0x43, - 0xDC, 0x1A, 0x18, 0xB9, 0x1B, 0x24, 0x64, 0x0B, - 0x6D, 0xBB, 0x92, 0xCB, 0x1A, 0xDD, 0x37, 0x1E, - )))) - prv = bytes(bytearray(( - 0x0B, 0xA6, 0x04, 0x8A, 0xAD, 0xAE, 0x24, 0x1B, - 0xA4, 0x09, 0x36, 0xD4, 0x77, 0x56, 0xD7, 0xC9, - 0x30, 0x91, 0xA0, 0xE8, 0x51, 0x46, 0x69, 0x70, - 0x0E, 0xE7, 0x50, 0x8E, 0x50, 0x8B, 0x10, 0x20, - 0x72, 0xE8, 0x12, 0x3B, 0x22, 0x00, 0xA0, 0x56, - 0x33, 0x22, 0xDA, 0xD2, 0x82, 0x7E, 0x27, 0x14, - 0xA2, 0x63, 0x6B, 0x7B, 0xFD, 0x18, 0xAA, 0xDF, - 0xC6, 0x29, 0x67, 0x82, 0x1F, 0xA1, 0x8D, 0xD4, - ))) - pub_x = bytes(bytearray(( - 0x11, 0x5D, 0xC5, 0xBC, 0x96, 0x76, 0x0C, 0x7B, - 0x48, 0x59, 0x8D, 0x8A, 0xB9, 0xE7, 0x40, 0xD4, - 0xC4, 0xA8, 0x5A, 0x65, 0xBE, 0x33, 0xC1, 0x81, - 0x5B, 0x5C, 0x32, 0x0C, 0x85, 0x46, 0x21, 0xDD, - 0x5A, 0x51, 0x58, 0x56, 0xD1, 0x33, 0x14, 0xAF, - 0x69, 0xBC, 0x5B, 0x92, 0x4C, 0x8B, 0x4D, 0xDF, - 0xF7, 0x5C, 0x45, 0x41, 0x5C, 0x1D, 0x9D, 0xD9, - 0xDD, 0x33, 0x61, 0x2C, 0xD5, 0x30, 0xEF, 0xE1, - ))) - pub_y = bytes(bytearray(( - 0x37, 0xC7, 0xC9, 0x0C, 0xD4, 0x0B, 0x0F, 0x56, - 0x21, 0xDC, 0x3A, 0xC1, 0xB7, 0x51, 0xCF, 0xA0, - 0xE2, 0x63, 0x4F, 0xA0, 0x50, 0x3B, 0x3D, 0x52, - 0x63, 0x9F, 0x5D, 0x7F, 0xB7, 0x2A, 0xFD, 0x61, - 0xEA, 0x19, 0x94, 0x41, 0xD9, 0x43, 0xFF, 0xE7, - 0xF0, 0xC7, 0x0A, 0x27, 0x59, 0xA3, 0xCD, 0xB8, - 0x4C, 0x11, 0x4E, 0x1F, 0x93, 0x39, 0xFD, 0xF2, - 0x7F, 0x35, 0xEC, 0xA9, 0x36, 0x77, 0xBE, 0xEC, - ))) - digest = bytes(bytearray(( - 0x37, 0x54, 0xF3, 0xCF, 0xAC, 0xC9, 0xE0, 0x61, - 0x5C, 0x4F, 0x4A, 0x7C, 0x4D, 0x8D, 0xAB, 0x53, - 0x1B, 0x09, 0xB6, 0xF9, 0xC1, 0x70, 0xC5, 0x33, - 0xA7, 0x1D, 0x14, 0x70, 0x35, 0xB0, 0xC5, 0x91, - 0x71, 0x84, 0xEE, 0x53, 0x65, 0x93, 0xF4, 0x41, - 0x43, 0x39, 0x97, 0x6C, 0x64, 0x7C, 0x5D, 0x5A, - 0x40, 0x7A, 0xDE, 0xDB, 0x1D, 0x56, 0x0C, 0x4F, - 0xC6, 0x77, 0x7D, 0x29, 0x72, 0x07, 0x5B, 0x8C, - ))) - signature = bytes(bytearray(( - 0x2F, 0x86, 0xFA, 0x60, 0xA0, 0x81, 0x09, 0x1A, - 0x23, 0xDD, 0x79, 0x5E, 0x1E, 0x3C, 0x68, 0x9E, - 0xE5, 0x12, 0xA3, 0xC8, 0x2E, 0xE0, 0xDC, 0xC2, - 0x64, 0x3C, 0x78, 0xEE, 0xA8, 0xFC, 0xAC, 0xD3, - 0x54, 0x92, 0x55, 0x84, 0x86, 0xB2, 0x0F, 0x1C, - 0x9E, 0xC1, 0x97, 0xC9, 0x06, 0x99, 0x85, 0x02, - 0x60, 0xC9, 0x3B, 0xCB, 0xCD, 0x9C, 0x5C, 0x33, - 0x17, 0xE1, 0x93, 0x44, 0xE1, 0x73, 0xAE, 0x36, - 0x10, 0x81, 0xB3, 0x94, 0x69, 0x6F, 0xFE, 0x8E, - 0x65, 0x85, 0xE7, 0xA9, 0x36, 0x2D, 0x26, 0xB6, - 0x32, 0x5F, 0x56, 0x77, 0x8A, 0xAD, 0xBC, 0x08, - 0x1C, 0x0B, 0xFB, 0xE9, 0x33, 0xD5, 0x2F, 0xF5, - 0x82, 0x3C, 0xE2, 0x88, 0xE8, 0xC4, 0xF3, 0x62, - 0x52, 0x60, 0x80, 0xDF, 0x7F, 0x70, 0xCE, 0x40, - 0x6A, 0x6E, 0xEB, 0x1F, 0x56, 0x91, 0x9C, 0xB9, - 0x2A, 0x98, 0x53, 0xBD, 0xE7, 0x3E, 0x5B, 0x4A, - ))) - prv = bytes2long(prv) - signature = signature[64:] + signature[:64] - c = GOST3410Curve(p, q, a, b, x, y) - pubX, pubY = public_key(c, prv) - self.assertSequenceEqual(long2bytes(pubX), pub_x) - self.assertSequenceEqual(long2bytes(pubY), pub_y) - s = sign(c, prv, digest) - self.assertTrue(verify(c, (pubX, pubY), digest, s)) - self.assertTrue(verify(c, (pubX, pubY), digest, signature)) - - def test_sequence(self): - c = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - prv = bytes2long(urandom(64)) - pubX, pubY = public_key(c, prv_unmarshal(prv_marshal(c, prv))) - for _ in range(20): - digest = urandom(64) - s = sign(c, prv, digest) - self.assertTrue(verify(c, (pubX, pubY), digest, s)) - self.assertNotIn(b"\x00" * 8, s) - - -class TestUVXYConversion(TestCase): - """Twisted Edwards to Weierstrass coordinates conversion and vice versa - """ - def test_curve1(self): - c = CURVES["id-tc26-gost-3410-2012-256-paramSetA"] - u, v = (0x0D, bytes2long(hexdec("60CA1E32AA475B348488C38FAB07649CE7EF8DBE87F22E81F92B2592DBA300E7"))) - self.assertEqual(uv2xy(c, u, v), (c.x, c.y)) - self.assertEqual(xy2uv(c, c.x, c.y), (u, v)) - - def test_curve2(self): - c = CURVES["id-tc26-gost-3410-2012-512-paramSetC"] - u, v = (0x12, bytes2long(hexdec("469AF79D1FB1F5E16B99592B77A01E2A0FDFB0D01794368D9A56117F7B38669522DD4B650CF789EEBF068C5D139732F0905622C04B2BAAE7600303EE73001A3D"))) - self.assertEqual(uv2xy(c, u, v), (c.x, c.y)) - self.assertEqual(xy2uv(c, c.x, c.y), (u, v)) - - -class Test34102012SESPAKE(TestCase): - """Test vectors for multiplication from :rfc:`8133` - """ - def test_curve1(self): - c = CURVES["id-GostR3410-2001-CryptoPro-A-ParamSet"] - q_ind = ( - 0xA69D51CAF1A309FA9E9B66187759B0174C274E080356F23CFCBFE84D396AD7BB, - 0x5D26F29ECC2E9AC0404DCF7986FA55FE94986362170F54B9616426A659786DAC, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F18127067" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0x59495655D1E7C7424C622485F575CCF121F3122D274101E8AB734CC9C9A9B45E, - 0x48D1C311D33C9B701F3B03618562A4A07A044E3AF31E3999E67B487778B53C62, - ) - ) - self.assertEqual( - c.exp(0x1F2538097D5A031FA68BBB43C84D12B3DE47B7061C0D5E24993E0C873CDBA6B3), - ( - 0xBBC77CF42DC1E62D06227935379B4AA4D14FEA4F565DDF4CB4FA4D31579F9676, - 0x8E16604A4AFDF28246684D4996274781F6CB80ABBBA1414C1513EC988509DABF, - ) - ) - self.assertEqual( - c.exp(0xDC497D9EF6324912FD367840EE509A2032AEDB1C0A890D133B45F596FCCBD45D), - ( - 0x6097341C1BE388E83E7CA2DF47FAB86E2271FD942E5B7B2EB2409E49F742BC29, - 0xC81AA48BDB4CA6FA0EF18B9788AE25FE30857AA681B3942217F9FED151BAB7D0, - ), - ) - - def test_curve2(self): - c = CURVES["id-GostR3410-2001-CryptoPro-B-ParamSet"] - q_ind = ( - 0x3D715A874A4B17CB3B517893A9794A2B36C89D2FFC693F01EE4CC27E7F49E399, - 0x1C5A641FCF7CE7E87CDF8CEA38F3DB3096EACE2FAD158384B53953365F4FE7FE, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F18127067" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0x6DC2AE26BC691FCA5A73D9C452790D15E34BA5404D92955B914C8D2662ABB985, - 0x3B02AAA9DD65AE30C335CED12F3154BBAC059F66B088306747453EDF6E5DB077, - ) - ) - self.assertEqual( - c.exp(0x499D72B90299CAB0DA1F8BE19D9122F622A13B32B730C46BD0664044F2144FAD), - ( - 0x61D6F916DB717222D74877F179F7EBEF7CD4D24D8C1F523C048E34A1DF30F8DD, - 0x3EC48863049CFCFE662904082E78503F4973A4E105E2F1B18C69A5E7FB209000, - ) - ) - self.assertEqual( - c.exp(0x0F69FF614957EF83668EDC2D7ED614BE76F7B253DB23C5CC9C52BF7DF8F4669D), - ( - 0x33BC6F7E9C0BA10CFB2B72546C327171295508EA97F8C8BA9F890F2478AB4D6C, - 0x75D57B396C396F492F057E9222CCC686437A2AAD464E452EF426FC8EEED1A4A6, - ), - ) - - def test_curve3(self): - c = CURVES["id-GostR3410-2001-CryptoPro-C-ParamSet"] - q_ind = ( - 0x1E36383E43BB6CFA2917167D71B7B5DD3D6D462B43D7C64282AE67DFBEC2559D, - 0x137478A9F721C73932EA06B45CF72E37EB78A63F29A542E563C614650C8B6399, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F18127067" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0x945821DAF91E158B839939630655A3B21FF3E146D27041E86C05650EB3B46B59, - 0x3A0C2816AC97421FA0E879605F17F0C9C3EB734CFF196937F6284438D70BDC48, - ) - ) - self.assertEqual( - c.exp(0x3A54AC3F19AD9D0B1EAC8ACDCEA70E581F1DAC33D13FEAFD81E762378639C1A8), - ( - 0x96B7F09C94D297C257A7DA48364C0076E59E48D221CBA604AE111CA3933B446A, - 0x54E4953D86B77ECCEB578500931E822300F7E091F79592CA202A020D762C34A6, - ) - ) - self.assertEqual( - c.exp(0x448781782BF7C0E52A1DD9E6758FD3482D90D3CFCCF42232CF357E59A4D49FD4), - ( - 0x4B9C0AB55A938121F282F48A2CC4396EB16E7E0068B495B0C1DD4667786A3EB7, - 0x223460AA8E09383E9DF9844C5A0F2766484738E5B30128A171B69A77D9509B96, - ), - ) - - def test_curve4(self): - c = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - q_ind = ( - 0x2A17F8833A32795327478871B5C5E88AEFB91126C64B4B8327289BEA62559425D18198F133F400874328B220C74497CD240586CB249E158532CB8090776CD61C, - 0x728F0C4A73B48DA41CE928358FAD26B47A6E094E9362BAE82559F83CDDC4EC3A4676BD3707EDEAF4CD85E99695C64C241EDC622BE87DC0CF87F51F4367F723C5, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F181270671C6213E3930EFDDA26451792C6208122EE60D200520D695DFD9F5F0FD5ABA702" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0x0C0AB53D0E0A9C607CAD758F558915A0A7DC5DC87B45E9A58FDDF30EC3385960283E030CD322D9E46B070637785FD49D2CD711F46807A24C40AF9A42C8E2D740, - 0xDF93A8012B86D3A3D4F8A4D487DA15FC739EB31B20B3B0E8C8C032AAF8072C6337CF7D5B404719E5B4407C41D9A3216A08CA69C271484E9ED72B8AAA52E28B8B, - ) - ) - self.assertEqual( - c.exp(0x3CE54325DB52FE798824AEAD11BB16FA766857D04A4AF7D468672F16D90E7396046A46F815693E85B1CE5464DA9270181F82333B0715057BBE8D61D400505F0E), - ( - 0xB93093EB0FCC463239B7DF276E09E592FCFC9B635504EA4531655D76A0A3078E2B4E51CFE2FA400CC5DE9FBE369DB204B3E8ED7EDD85EE5CCA654C1AED70E396, - 0x809770B8D910EA30BD2FA89736E91DC31815D2D9B31128077EEDC371E9F69466F497DC64DD5B1FADC587F860EE256109138C4A9CD96B628E65A8F590520FC882, - ) - ) - self.assertEqual( - c.exp(0xB5C286A79AA8E97EC0E19BC1959A1D15F12F8C97870BA9D68CC12811A56A3BB11440610825796A49D468CDC9C2D02D76598A27973D5960C5F50BCE28D8D345F4), - ( - 0x238B38644E440452A99FA6B93D9FD7DA0CB83C32D3C1E3CFE5DF5C3EB0F9DB91E588DAEDC849EA2FB867AE855A21B4077353C0794716A6480995113D8C20C7AF, - 0xB2273D5734C1897F8D15A7008B862938C8C74CA7E877423D95243EB7EBD02FD2C456CF9FC956F078A59AA86F19DD1075E5167E4ED35208718EA93161C530ED14, - ), - ) - - def test_curve5(self): - c = CURVES["id-tc26-gost-3410-12-512-paramSetB"] - q_ind = ( - 0x7E1FAE8285E035BEC244BEF2D0E5EBF436633CF50E55231DEA9C9CF21D4C8C33DF85D4305DE92971F0A4B4C07E00D87BDBC720EB66E49079285AAF12E0171149, - 0x2CC89998B875D4463805BA0D858A196592DB20AB161558FF2F4EF7A85725D20953967AE621AFDEAE89BB77C83A2528EF6FCE02F68BDA4679D7F2704947DBC408, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F181270671C6213E3930EFDDA26451792C6208122EE60D200520D695DFD9F5F0FD5ABA702" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0x7D03E65B8050D1E12CBB601A17B9273B0E728F5021CD47C8A4DD822E4627BA5F9C696286A2CDDA9A065509866B4DEDEDC4A118409604AD549F87A60AFA621161, - 0x16037DAD45421EC50B00D50BDC6AC3B85348BC1D3A2F85DB27C3373580FEF87C2C743B7ED30F22BE22958044E716F93A61CA3213A361A2797A16A3AE62957377, - ) - ) - self.assertEqual( - c.exp(0x715E893FA639BF341296E0623E6D29DADF26B163C278767A7982A989462A3863FE12AEF8BD403D59C4DC4720570D4163DB0805C7C10C4E818F9CB785B04B9997), - ( - 0x10C479EA1C04D3C2C02B0576A9C42D96226FF033C1191436777F66916030D87D02FB93738ED7669D07619FFCE7C1F3C4DB5E5DF49E2186D6FA1E2EB5767602B9, - 0x039F6044191404E707F26D59D979136A831CCE43E1C5F0600D1DDF8F39D0CA3D52FBD943BF04DDCED1AA2CE8F5EBD7487ACDEF239C07D015084D796784F35436, - ) - ) - self.assertEqual( - c.exp(0x30FA8C2B4146C2DBBE82BED04D7378877E8C06753BD0A0FF71EBF2BEFE8DA8F3DC0836468E2CE7C5C961281B6505140F8407413F03C2CB1D201EA1286CE30E6D), - ( - 0x34C0149E7BB91AE377B02573FCC48AF7BFB7B16DEB8F9CE870F384688E3241A3A868588CC0EF4364CCA67D17E3260CD82485C202ADC76F895D5DF673B1788E67, - 0x608E944929BD643569ED5189DB871453F13333A1EAF82B2FE1BE8100E775F13DD9925BD317B63BFAF05024D4A738852332B64501195C1B2EF789E34F23DDAFC5, - ), - ) - - def test_curve6(self): - c = CURVES["id-tc26-gost-3410-2012-256-paramSetA"] - q_ind = ( - 0xB51ADF93A40AB15792164FAD3352F95B66369EB2A4EF5EFAE32829320363350E, - 0x74A358CC08593612F5955D249C96AFB7E8B0BB6D8BD2BBE491046650D822BE18, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F18127067" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0xDBF99827078956812FA48C6E695DF589DEF1D18A2D4D35A96D75BF6854237629, - 0x9FDDD48BFBC57BEE1DA0CFF282884F284D471B388893C48F5ECB02FC18D67589, - ) - ) - self.assertEqual( - c.exp(0x147B72F6684FB8FD1B418A899F7DBECAF5FCE60B13685BAA95328654A7F0707F), - ( - 0x33FBAC14EAE538275A769417829C431BD9FA622B6F02427EF55BD60EE6BC2888, - 0x22F2EBCF960A82E6CDB4042D3DDDA511B2FBA925383C2273D952EA2D406EAE46, - ) - ) - self.assertEqual( - c.exp(0x30D5CFADAA0E31B405E6734C03EC4C5DF0F02F4BA25C9A3B320EE6453567B4CB), - ( - 0x2B2D89FAB735433970564F2F28CFA1B57D640CB902BC6334A538F44155022CB2, - 0x10EF6A82EEF1E70F942AA81D6B4CE5DEC0DDB9447512962874870E6F2849A96F, - ), - ) - - def test_curve7(self): - c = CURVES["id-tc26-gost-3410-2012-512-paramSetC"] - q_ind = ( - 0x489C91784E02E98F19A803ABCA319917F37689E5A18965251CE2FF4E8D8B298F5BA7470F9E0E713487F96F4A8397B3D09A270C9D367EB5E0E6561ADEEB51581D, - 0x684EA885ACA64EAF1B3FEE36C0852A3BE3BD8011B0EF18E203FF87028D6EB5DB2C144A0DCC71276542BFD72CA2A43FA4F4939DA66D9A60793C704A8C94E16F18, - ) - self.assertEqual( - c.exp(bytes2long(hexdec( - "BD04673F7149B18E98155BD1E2724E71D0099AA25174F792D3326C6F181270671C6213E3930EFDDA26451792C6208122EE60D200520D695DFD9F5F0FD5ABA702" - )[::-1]), x=q_ind[0], y=q_ind[1]), - ( - 0x0185AE6271A81BB7F236A955F7CAA26FB63849813C0287D96C83A15AE6B6A86467AB13B6D88CE8CD7DC2E5B97FF5F28FAC2C108F2A3CF3DB5515C9E6D7D210E8, - 0xED0220F92EF771A71C64ECC77986DB7C03D37B3E2AB3E83F32CE5E074A762EC08253C9E2102B87532661275C4B1D16D2789CDABC58ACFDF7318DE70AB64F09B8, - ) - ) - self.assertEqual( - c.exp(0x332F930421D14CFE260042159F18E49FD5A54167E94108AD80B1DE60B13DE7999A34D611E63F3F870E5110247DF8EC7466E648ACF385E52CCB889ABF491EDFF0), - ( - 0x561655966D52952E805574F4281F1ED3A2D498932B00CBA9DECB42837F09835BFFBFE2D84D6B6B242FE7B57F92E1A6F2413E12DDD6383E4437E13D72693469AD, - 0xF6B18328B2715BD7F4178615273A36135BC0BF62F7D8BB9F080164AD36470AD03660F51806C64C6691BADEF30F793720F8E3FEAED631D6A54A4C372DCBF80E82, - ) - ) - self.assertEqual( - c.exp(0x38481771E7D054F96212686B613881880BD8A6C89DDBC656178F014D2C093432A033EE10415F13A160D44C2AD61E6E2E05A7F7EC286BCEA3EA4D4D53F8634FA2), - ( - 0xB7C5818687083433BC1AFF61CB5CA79E38232025E0C1F123B8651E62173CE6873F3E6FFE7281C2E45F4F524F66B0C263616ED08FD210AC4355CA3292B51D71C3, - 0x497F14205DBDC89BDDAF50520ED3B1429AD30777310186BE5E68070F016A44E0C766DB08E8AC23FBDFDE6D675AA4DF591EB18BA0D348DF7AA40973A2F1DCFA55, - ), - ) diff --git a/pygost-5.13/pygost/test_gost3410_vko.py b/pygost-5.13/pygost/test_gost3410_vko.py deleted file mode 100644 index a8e298e..0000000 --- a/pygost-5.13/pygost/test_gost3410_vko.py +++ /dev/null @@ -1,125 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from unittest import TestCase - -from pygost.gost3410 import CURVES -from pygost.gost3410 import prv_unmarshal -from pygost.gost3410 import pub_unmarshal -from pygost.gost3410 import public_key -from pygost.gost3410_vko import kek_34102001 -from pygost.gost3410_vko import kek_34102012256 -from pygost.gost3410_vko import kek_34102012512 -from pygost.gost3410_vko import ukm_unmarshal -from pygost.utils import bytes2long -from pygost.utils import hexdec - - -class TestVKO34102001(TestCase): - def test_vector(self): - curve = CURVES["id-GostR3410-2001-TestParamSet"] - ukm = ukm_unmarshal(hexdec("5172be25f852a233")) - prv1 = prv_unmarshal(hexdec("1df129e43dab345b68f6a852f4162dc69f36b2f84717d08755cc5c44150bf928")) - prv2 = prv_unmarshal(hexdec("5b9356c6474f913f1e83885ea0edd5df1a43fd9d799d219093241157ac9ed473")) - kek = hexdec("ee4618a0dbb10cb31777b4b86a53d9e7ef6cb3e400101410f0c0f2af46c494a6") - pub1 = public_key(curve, prv1) - pub2 = public_key(curve, prv2) - self.assertSequenceEqual(kek_34102001(curve, prv1, pub2, ukm), kek) - self.assertSequenceEqual(kek_34102001(curve, prv2, pub1, ukm), kek) - - def test_sequence(self): - curve = CURVES["id-GostR3410-2001-TestParamSet"] - for _ in range(10): - ukm = ukm_unmarshal(urandom(8)) - prv1 = bytes2long(urandom(32)) - prv2 = bytes2long(urandom(32)) - pub1 = public_key(curve, prv1) - pub2 = public_key(curve, prv2) - kek1 = kek_34102001(curve, prv1, pub2, ukm) - kek2 = kek_34102001(curve, prv2, pub1, ukm) - self.assertSequenceEqual(kek1, kek2) - kek1 = kek_34102001(curve, prv1, pub1, ukm) - kek2 = kek_34102001(curve, prv2, pub2, ukm) - self.assertNotEqual(kek1, kek2) - - -class TestVKO34102012256(TestCase): - """RFC 7836 - """ - def test_vector(self): - curve = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - ukm = ukm_unmarshal(hexdec("1d80603c8544c727")) - prvA = prv_unmarshal(hexdec("c990ecd972fce84ec4db022778f50fcac726f46708384b8d458304962d7147f8c2db41cef22c90b102f2968404f9b9be6d47c79692d81826b32b8daca43cb667")) - pubA = pub_unmarshal(hexdec("aab0eda4abff21208d18799fb9a8556654ba783070eba10cb9abb253ec56dcf5d3ccba6192e464e6e5bcb6dea137792f2431f6c897eb1b3c0cc14327b1adc0a7914613a3074e363aedb204d38d3563971bd8758e878c9db11403721b48002d38461f92472d40ea92f9958c0ffa4c93756401b97f89fdbe0b5e46e4a4631cdb5a")) - prvB = prv_unmarshal(hexdec("48c859f7b6f11585887cc05ec6ef1390cfea739b1a18c0d4662293ef63b79e3b8014070b44918590b4b996acfea4edfbbbcccc8c06edd8bf5bda92a51392d0db")) - pubB = pub_unmarshal(hexdec("192fe183b9713a077253c72c8735de2ea42a3dbc66ea317838b65fa32523cd5efca974eda7c863f4954d1147f1f2b25c395fce1c129175e876d132e94ed5a65104883b414c9b592ec4dc84826f07d0b6d9006dda176ce48c391e3f97d102e03bb598bf132a228a45f7201aba08fc524a2d77e43a362ab022ad4028f75bde3b79")) - vko = hexdec("c9a9a77320e2cc559ed72dce6f47e2192ccea95fa648670582c054c0ef36c221") - self.assertSequenceEqual(kek_34102012256(curve, prvA, pubB, ukm), vko) - self.assertSequenceEqual(kek_34102012256(curve, prvB, pubA, ukm), vko) - - def test_sequence(self): - curve = CURVES["id-tc26-gost-3410-2012-256-paramSetA"] - for _ in range(10): - ukm = ukm_unmarshal(urandom(8)) - prv1 = bytes2long(urandom(32)) - prv2 = bytes2long(urandom(32)) - pub1 = public_key(curve, prv1) - pub2 = public_key(curve, prv2) - kek1 = kek_34102012256(curve, prv1, pub2, ukm) - kek2 = kek_34102012256(curve, prv2, pub1, ukm) - self.assertSequenceEqual(kek1, kek2) - kek1 = kek_34102012256(curve, prv1, pub1, ukm) - kek2 = kek_34102012256(curve, prv2, pub2, ukm) - self.assertNotEqual(kek1, kek2) - - def test_pub_is_not_on_curve(self): - with self.assertRaises(ValueError): - kek_34102012256( - CURVES["id-tc26-gost-3410-2012-256-paramSetA"], - bytes2long(urandom(32)), - pub_unmarshal(urandom(64)), - ) - - -class TestVKO34102012512(TestCase): - """RFC 7836 - """ - def test_vector(self): - curve = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - ukm = ukm_unmarshal(hexdec("1d80603c8544c727")) - prvA = prv_unmarshal(hexdec("c990ecd972fce84ec4db022778f50fcac726f46708384b8d458304962d7147f8c2db41cef22c90b102f2968404f9b9be6d47c79692d81826b32b8daca43cb667")) - pubA = pub_unmarshal(hexdec("aab0eda4abff21208d18799fb9a8556654ba783070eba10cb9abb253ec56dcf5d3ccba6192e464e6e5bcb6dea137792f2431f6c897eb1b3c0cc14327b1adc0a7914613a3074e363aedb204d38d3563971bd8758e878c9db11403721b48002d38461f92472d40ea92f9958c0ffa4c93756401b97f89fdbe0b5e46e4a4631cdb5a")) - prvB = prv_unmarshal(hexdec("48c859f7b6f11585887cc05ec6ef1390cfea739b1a18c0d4662293ef63b79e3b8014070b44918590b4b996acfea4edfbbbcccc8c06edd8bf5bda92a51392d0db")) - pubB = pub_unmarshal(hexdec("192fe183b9713a077253c72c8735de2ea42a3dbc66ea317838b65fa32523cd5efca974eda7c863f4954d1147f1f2b25c395fce1c129175e876d132e94ed5a65104883b414c9b592ec4dc84826f07d0b6d9006dda176ce48c391e3f97d102e03bb598bf132a228a45f7201aba08fc524a2d77e43a362ab022ad4028f75bde3b79")) - vko = hexdec("79f002a96940ce7bde3259a52e015297adaad84597a0d205b50e3e1719f97bfa7ee1d2661fa9979a5aa235b558a7e6d9f88f982dd63fc35a8ec0dd5e242d3bdf") - self.assertSequenceEqual(kek_34102012512(curve, prvA, pubB, ukm), vko) - self.assertSequenceEqual(kek_34102012512(curve, prvB, pubA, ukm), vko) - - def test_sequence(self): - curve = CURVES["id-tc26-gost-3410-12-512-paramSetA"] - for _ in range(10): - ukm = ukm_unmarshal(urandom(8)) - prv1 = bytes2long(urandom(32)) - prv2 = bytes2long(urandom(32)) - pub1 = public_key(curve, prv1) - pub2 = public_key(curve, prv2) - kek1 = kek_34102012512(curve, prv1, pub2, ukm) - kek2 = kek_34102012512(curve, prv2, pub1, ukm) - self.assertSequenceEqual(kek1, kek2) - kek1 = kek_34102012512(curve, prv1, pub1, ukm) - kek2 = kek_34102012512(curve, prv2, pub2, ukm) - self.assertNotEqual(kek1, kek2) diff --git a/pygost-5.13/pygost/test_gost34112012.py b/pygost-5.13/pygost/test_gost34112012.py deleted file mode 100644 index c7c2df9..0000000 --- a/pygost-5.13/pygost/test_gost34112012.py +++ /dev/null @@ -1,159 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from random import randint -from unittest import skip -from unittest import TestCase -import hmac - -from pygost import gost34112012256 -from pygost import gost34112012512 -from pygost.gost34112012256 import GOST34112012256 -from pygost.gost34112012512 import GOST34112012512 -from pygost.gost34112012512 import pbkdf2 -from pygost.utils import hexdec -from pygost.utils import hexenc - - -class TestCopy(TestCase): - def runTest(self): - m = GOST34112012256() - c = m.copy() - m.update(b"foobar") - c.update(b"foo") - c.update(b"bar") - self.assertSequenceEqual(m.digest(), c.digest()) - - -class TestSymmetric(TestCase): - def runTest(self): - chunks = [] - for _ in range(randint(1, 10)): - chunks.append(urandom(randint(20, 80))) - m = GOST34112012256() - for chunk in chunks: - m.update(chunk) - self.assertSequenceEqual( - m.hexdigest(), - GOST34112012256(b"".join(chunks)).hexdigest(), - ) - - -class TestHMAC(TestCase): - """RFC 7836 - """ - def test_256(self): - for digestmod in (GOST34112012256, gost34112012256): - self.assertSequenceEqual( - hmac.new( - key=hexdec("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), - msg=hexdec("0126bdb87800af214341456563780100"), - digestmod=digestmod, - ).hexdigest(), - "a1aa5f7de402d7b3d323f2991c8d4534013137010a83754fd0af6d7cd4922ed9", - ) - - def test_512(self): - for digestmod in (GOST34112012512, gost34112012512): - self.assertSequenceEqual( - hmac.new( - key=hexdec("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), - msg=hexdec("0126bdb87800af214341456563780100"), - digestmod=digestmod, - ).hexdigest(), - "a59bab22ecae19c65fbde6e5f4e9f5d8549d31f037f9df9b905500e171923a773d5f1530f2ed7e964cb2eedc29e9ad2f3afe93b2814f79f5000ffc0366c251e6", - ) - - -class TestVectors(TestCase): - def test_m1(self): - m = hexdec("323130393837363534333231303938373635343332313039383736353433323130393837363534333231303938373635343332313039383736353433323130")[::-1] - self.assertSequenceEqual( - GOST34112012512(m).digest(), - hexdec("486f64c1917879417fef082b3381a4e211c324f074654c38823a7b76f830ad00fa1fbae42b1285c0352f227524bc9ab16254288dd6863dccd5b9f54a1ad0541b")[::-1] - ) - self.assertSequenceEqual( - GOST34112012256(m).digest(), - hexdec("00557be5e584fd52a449b16b0251d05d27f94ab76cbaa6da890b59d8ef1e159d")[::-1] - ) - - def test_m2(self): - m = u"Се ветри, Стрибожи внуци, веютъ с моря стрелами на храбрыя плъкы Игоревы".encode("cp1251") - self.assertSequenceEqual(m, hexdec("fbe2e5f0eee3c820fbeafaebef20fffbf0e1e0f0f520e0ed20e8ece0ebe5f0f2f120fff0eeec20f120faf2fee5e2202ce8f6f3ede220e8e6eee1e8f0f2d1202ce8f0f2e5e220e5d1")[::-1]) - self.assertSequenceEqual( - GOST34112012512(m).digest(), - hexdec("28fbc9bada033b1460642bdcddb90c3fb3e56c497ccd0f62b8a2ad4935e85f037613966de4ee00531ae60f3b5a47f8dae06915d5f2f194996fcabf2622e6881e")[::-1] - ) - self.assertSequenceEqual( - GOST34112012256(m).digest(), - hexdec("508f7e553c06501d749a66fc28c6cac0b005746d97537fa85d9e40904efed29d")[::-1] - ) - - def test_habr144(self): - """Test vector from https://habr.com/ru/post/452200/ - """ - m = hexdec("d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff0900060000000000000000000000010000000100000000000000001000002400000001000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - self.assertSequenceEqual( - GOST34112012256(m).hexdigest(), - "c766085540caaa8953bfcf7a1ba220619cee50d65dc242f82f23ba4b180b18e0", - ) - - -class TestPBKDF2(TestCase): - """http://tc26.ru/.../R_50.1.111-2016.pdf - """ - def test_1(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 1, 64)), - "64770af7f748c3b1c9ac831dbcfd85c26111b30a8a657ddc3056b80ca73e040d2854fd36811f6d825cc4ab66ec0a68a490a9e5cf5156b3a2b7eecddbf9a16b47", - ) - - def test_2(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 2, 64)), - "5a585bafdfbb6e8830d6d68aa3b43ac00d2e4aebce01c9b31c2caed56f0236d4d34b2b8fbd2c4e89d54d46f50e47d45bbac301571743119e8d3c42ba66d348de", - ) - - def test_3(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 4096, 64)), - "e52deb9a2d2aaff4e2ac9d47a41f34c20376591c67807f0477e32549dc341bc7867c09841b6d58e29d0347c996301d55df0d34e47cf68f4e3c2cdaf1d9ab86c3", - ) - - @skip("it takes too long") - def test_4(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 1677216, 64)), - "49e4843bba76e300afe24c4d23dc7392def12f2c0e244172367cd70a8982ac361adb601c7e2a314e8cb7b1e9df840e36ab5615be5d742b6cf203fb55fdc48071", - ) - - def test_5(self): - self.assertSequenceEqual( - hexenc(pbkdf2( - b"passwordPASSWORDpassword", - b"saltSALTsaltSALTsaltSALTsaltSALTsalt", - 4096, - 100, - )), - "b2d8f1245fc4d29274802057e4b54e0a0753aa22fc53760b301cf008679e58fe4bee9addcae99ba2b0b20f431a9c5e50f395c89387d0945aedeca6eb4015dfc2bd2421ee9bb71183ba882ceebfef259f33f9e27dc6178cb89dc37428cf9cc52a2baa2d3a", - ) - - def test_6(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"pass\x00word", b"sa\x00lt", 4096, 64)), - "50df062885b69801a3c10248eb0a27ab6e522ffeb20c991c660f001475d73a4e167f782c18e97e92976d9c1d970831ea78ccb879f67068cdac1910740844e830", - ) diff --git a/pygost-5.13/pygost/test_gost341194.py b/pygost-5.13/pygost/test_gost341194.py deleted file mode 100644 index 4ffa6d6..0000000 --- a/pygost-5.13/pygost/test_gost341194.py +++ /dev/null @@ -1,200 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 unittest import skip -from unittest import TestCase -import hmac - -from pygost import gost341194 -from pygost.gost341194 import GOST341194 -from pygost.gost341194 import pbkdf2 -from pygost.utils import hexenc - - -class TestCopy(TestCase): - def runTest(self): - m = GOST341194() - c = m.copy() - m.update(b"foobar") - c.update(b"foo") - c.update(b"bar") - self.assertSequenceEqual(m.digest(), c.digest()) - - -class TestHMACPEP247(TestCase): - def runTest(self): - h = hmac.new(b"foo", digestmod=gost341194) - h.update(b"foobar") - h.digest() - - -class TestVectors(TestCase): - def test_empty(self): - self.assertSequenceEqual( - GOST341194(b"", "id-GostR3411-94-TestParamSet").hexdigest(), - "ce85b99cc46752fffee35cab9a7b0278abb4c2d2055cff685af4912c49490f8d", - ) - - def test_a(self): - self.assertSequenceEqual( - GOST341194(b"a", "id-GostR3411-94-TestParamSet").hexdigest(), - "d42c539e367c66e9c88a801f6649349c21871b4344c6a573f849fdce62f314dd", - ) - - def test_abc(self): - self.assertSequenceEqual( - GOST341194(b"abc", "id-GostR3411-94-TestParamSet").hexdigest(), - "f3134348c44fb1b2a277729e2285ebb5cb5e0f29c975bc753b70497c06a4d51d", - ) - - def test_message_digest(self): - self.assertSequenceEqual( - GOST341194(b"message digest", "id-GostR3411-94-TestParamSet").hexdigest(), - "ad4434ecb18f2c99b60cbe59ec3d2469582b65273f48de72db2fde16a4889a4d", - ) - - def test_Us(self): - self.assertSequenceEqual( - GOST341194(128 * b"U", "id-GostR3411-94-TestParamSet").hexdigest(), - "53a3a3ed25180cef0c1d85a074273e551c25660a87062a52d926a9e8fe5733a4", - ) - - def test_dog(self): - self.assertSequenceEqual( - GOST341194(b"The quick brown fox jumps over the lazy dog", "id-GostR3411-94-TestParamSet",).hexdigest(), - "77b7fa410c9ac58a25f49bca7d0468c9296529315eaca76bd1a10f376d1f4294", - ) - - def test_cog(self): - self.assertSequenceEqual( - GOST341194(b"The quick brown fox jumps over the lazy cog", "id-GostR3411-94-TestParamSet",).hexdigest(), - "a3ebc4daaab78b0be131dab5737a7f67e602670d543521319150d2e14eeec445", - ) - - def test_rfc32(self): - self.assertSequenceEqual( - GOST341194(b"This is message, length=32 bytes", "id-GostR3411-94-TestParamSet",).hexdigest(), - "b1c466d37519b82e8319819ff32595e047a28cb6f83eff1c6916a815a637fffa", - ) - - def test_rfc50(self): - self.assertSequenceEqual( - GOST341194(b"Suppose the original message has length = 50 bytes", "id-GostR3411-94-TestParamSet",).hexdigest(), - "471aba57a60a770d3a76130635c1fbea4ef14de51f78b4ae57dd893b62f55208", - ) - - -class TestVectorsCryptoPro(TestCase): - """CryptoPro S-box test vectors - """ - def test_empty(self): - self.assertSequenceEqual( - GOST341194(b"", "id-GostR3411-94-CryptoProParamSet").hexdigest(), - "981e5f3ca30c841487830f84fb433e13ac1101569b9c13584ac483234cd656c0", - ) - - def test_a(self): - self.assertSequenceEqual( - GOST341194(b"a", "id-GostR3411-94-CryptoProParamSet").hexdigest(), - "e74c52dd282183bf37af0079c9f78055715a103f17e3133ceff1aacf2f403011", - ) - - def test_abc(self): - self.assertSequenceEqual( - GOST341194(b"abc", "id-GostR3411-94-CryptoProParamSet").hexdigest(), - "b285056dbf18d7392d7677369524dd14747459ed8143997e163b2986f92fd42c", - ) - - def test_message_digest(self): - self.assertSequenceEqual( - GOST341194(b"message digest", "id-GostR3411-94-CryptoProParamSet",).hexdigest(), - "bc6041dd2aa401ebfa6e9886734174febdb4729aa972d60f549ac39b29721ba0", - ) - - def test_dog(self): - self.assertSequenceEqual( - GOST341194(b"The quick brown fox jumps over the lazy dog", "id-GostR3411-94-CryptoProParamSet",).hexdigest(), - "9004294a361a508c586fe53d1f1b02746765e71b765472786e4770d565830a76", - ) - - def test_32(self): - self.assertSequenceEqual( - GOST341194(b"This is message, length=32 bytes", "id-GostR3411-94-CryptoProParamSet",).hexdigest(), - "2cefc2f7b7bdc514e18ea57fa74ff357e7fa17d652c75f69cb1be7893ede48eb", - ) - - def test_50(self): - self.assertSequenceEqual( - GOST341194(b"Suppose the original message has length = 50 bytes", "id-GostR3411-94-CryptoProParamSet",).hexdigest(), - "c3730c5cbccacf915ac292676f21e8bd4ef75331d9405e5f1a61dc3130a65011", - ) - - def test_Us(self): - self.assertSequenceEqual( - GOST341194(128 * b"U", "id-GostR3411-94-CryptoProParamSet").hexdigest(), - "1c4ac7614691bbf427fa2316216be8f10d92edfd37cd1027514c1008f649c4e8", - ) - - -class TestPBKDF2(TestCase): - """http://tc26.ru/methods/containers_v1/Addition_to_PKCS5_v1_0.pdf test vectors - """ - def test_1(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 1, 32)), - "7314e7c04fb2e662c543674253f68bd0b73445d07f241bed872882da21662d58", - ) - - def test_2(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 2, 32)), - "990dfa2bd965639ba48b07b792775df79f2db34fef25f274378872fed7ed1bb3", - ) - - def test_3(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 4096, 32)), - "1f1829a94bdff5be10d0aeb36af498e7a97467f3b31116a5a7c1afff9deadafe", - ) - - @skip("it takes too long") - def test_4(self): - self.assertSequenceEqual( - hexenc(pbkdf2(b"password", b"salt", 16777216, 32)), - "a57ae5a6088396d120850c5c09de0a525100938a59b1b5c3f7810910d05fcd97", - ) - - def test_5(self): - self.assertSequenceEqual( - hexenc(pbkdf2( - b"passwordPASSWORDpassword", - b"saltSALTsaltSALTsaltSALTsaltSALTsalt", - 4096, - 40, - )), - "788358c69cb2dbe251a7bb17d5f4241f265a792a35becde8d56f326b49c85047b7638acb4764b1fd", - ) - - def test_6(self): - self.assertSequenceEqual( - hexenc(pbkdf2( - b"pass\x00word", - b"sa\x00lt", - 4096, - 20, - )), - "43e06c5590b08c0225242373127edf9c8e9c3291", - ) diff --git a/pygost-5.13/pygost/test_gost3412.py b/pygost-5.13/pygost/test_gost3412.py deleted file mode 100644 index 5dbb200..0000000 --- a/pygost-5.13/pygost/test_gost3412.py +++ /dev/null @@ -1,137 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 unittest import TestCase - -from pygost.gost3412 import C -from pygost.gost3412 import GOST3412Kuznechik -from pygost.gost3412 import GOST3412Magma -from pygost.gost3412 import L -from pygost.gost3412 import PI -from pygost.utils import hexdec - - -def S(blk): - return bytearray(PI[v] for v in blk) - - -def R(blk): - return L(blk, rounds=1) - - -class STest(TestCase): - def test_vec1(self): - blk = bytearray(hexdec("ffeeddccbbaa99881122334455667700")) - self.assertSequenceEqual(S(blk), hexdec("b66cd8887d38e8d77765aeea0c9a7efc")) - - def test_vec2(self): - blk = bytearray(hexdec("b66cd8887d38e8d77765aeea0c9a7efc")) - self.assertSequenceEqual(S(blk), hexdec("559d8dd7bd06cbfe7e7b262523280d39")) - - def test_vec3(self): - blk = bytearray(hexdec("559d8dd7bd06cbfe7e7b262523280d39")) - self.assertSequenceEqual(S(blk), hexdec("0c3322fed531e4630d80ef5c5a81c50b")) - - def test_vec4(self): - blk = bytearray(hexdec("0c3322fed531e4630d80ef5c5a81c50b")) - self.assertSequenceEqual(S(blk), hexdec("23ae65633f842d29c5df529c13f5acda")) - - -class RTest(TestCase): - def test_vec1(self): - blk = bytearray(hexdec("00000000000000000000000000000100")) - self.assertSequenceEqual(R(blk), hexdec("94000000000000000000000000000001")) - - def test_vec2(self): - blk = bytearray(hexdec("94000000000000000000000000000001")) - self.assertSequenceEqual(R(blk), hexdec("a5940000000000000000000000000000")) - - def test_vec3(self): - blk = bytearray(hexdec("a5940000000000000000000000000000")) - self.assertSequenceEqual(R(blk), hexdec("64a59400000000000000000000000000")) - - def test_vec4(self): - blk = bytearray(hexdec("64a59400000000000000000000000000")) - self.assertSequenceEqual(R(blk), hexdec("0d64a594000000000000000000000000")) - - -class LTest(TestCase): - def test_vec1(self): - blk = bytearray(hexdec("64a59400000000000000000000000000")) - self.assertSequenceEqual(L(blk), hexdec("d456584dd0e3e84cc3166e4b7fa2890d")) - - def test_vec2(self): - blk = bytearray(hexdec("d456584dd0e3e84cc3166e4b7fa2890d")) - self.assertSequenceEqual(L(blk), hexdec("79d26221b87b584cd42fbc4ffea5de9a")) - - def test_vec3(self): - blk = bytearray(hexdec("79d26221b87b584cd42fbc4ffea5de9a")) - self.assertSequenceEqual(L(blk), hexdec("0e93691a0cfc60408b7b68f66b513c13")) - - def test_vec4(self): - blk = bytearray(hexdec("0e93691a0cfc60408b7b68f66b513c13")) - self.assertSequenceEqual(L(blk), hexdec("e6a8094fee0aa204fd97bcb0b44b8580")) - - -class KuznechikTest(TestCase): - key = hexdec("8899aabbccddeeff0011223344556677fedcba98765432100123456789abcdef") - plaintext = hexdec("1122334455667700ffeeddccbbaa9988") - ciphertext = hexdec("7f679d90bebc24305a468d42b9d4edcd") - - def test_c(self): - self.assertSequenceEqual(C[0], hexdec("6ea276726c487ab85d27bd10dd849401")) - self.assertSequenceEqual(C[1], hexdec("dc87ece4d890f4b3ba4eb92079cbeb02")) - self.assertSequenceEqual(C[2], hexdec("b2259a96b4d88e0be7690430a44f7f03")) - self.assertSequenceEqual(C[3], hexdec("7bcd1b0b73e32ba5b79cb140f2551504")) - self.assertSequenceEqual(C[4], hexdec("156f6d791fab511deabb0c502fd18105")) - self.assertSequenceEqual(C[5], hexdec("a74af7efab73df160dd208608b9efe06")) - self.assertSequenceEqual(C[6], hexdec("c9e8819dc73ba5ae50f5b570561a6a07")) - self.assertSequenceEqual(C[7], hexdec("f6593616e6055689adfba18027aa2a08")) - - def test_roundkeys(self): - ciph = GOST3412Kuznechik(self.key) - self.assertSequenceEqual(ciph.ks[0], hexdec("8899aabbccddeeff0011223344556677")) - self.assertSequenceEqual(ciph.ks[1], hexdec("fedcba98765432100123456789abcdef")) - self.assertSequenceEqual(ciph.ks[2], hexdec("db31485315694343228d6aef8cc78c44")) - self.assertSequenceEqual(ciph.ks[3], hexdec("3d4553d8e9cfec6815ebadc40a9ffd04")) - self.assertSequenceEqual(ciph.ks[4], hexdec("57646468c44a5e28d3e59246f429f1ac")) - self.assertSequenceEqual(ciph.ks[5], hexdec("bd079435165c6432b532e82834da581b")) - self.assertSequenceEqual(ciph.ks[6], hexdec("51e640757e8745de705727265a0098b1")) - self.assertSequenceEqual(ciph.ks[7], hexdec("5a7925017b9fdd3ed72a91a22286f984")) - self.assertSequenceEqual(ciph.ks[8], hexdec("bb44e25378c73123a5f32f73cdb6e517")) - self.assertSequenceEqual(ciph.ks[9], hexdec("72e9dd7416bcf45b755dbaa88e4a4043")) - - def test_encrypt(self): - ciph = GOST3412Kuznechik(self.key) - self.assertSequenceEqual(ciph.encrypt(self.plaintext), self.ciphertext) - - def test_decrypt(self): - ciph = GOST3412Kuznechik(self.key) - self.assertSequenceEqual(ciph.decrypt(self.ciphertext), self.plaintext) - - -class MagmaTest(TestCase): - key = hexdec("ffeeddccbbaa99887766554433221100f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") - plaintext = hexdec("fedcba9876543210") - ciphertext = hexdec("4ee901e5c2d8ca3d") - - def test_encrypt(self): - ciph = GOST3412Magma(self.key) - self.assertSequenceEqual(ciph.encrypt(self.plaintext), self.ciphertext) - - def test_decrypt(self): - ciph = GOST3412Magma(self.key) - self.assertSequenceEqual(ciph.decrypt(self.ciphertext), self.plaintext) diff --git a/pygost-5.13/pygost/test_gost3413.py b/pygost-5.13/pygost/test_gost3413.py deleted file mode 100644 index 0098513..0000000 --- a/pygost-5.13/pygost/test_gost3413.py +++ /dev/null @@ -1,766 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from random import randint -from unittest import TestCase - -from pygost.gost3412 import GOST3412Kuznechik -from pygost.gost3412 import GOST3412Magma -from pygost.gost3413 import _mac_ks -from pygost.gost3413 import acpkm -from pygost.gost3413 import acpkm_master -from pygost.gost3413 import cbc_decrypt -from pygost.gost3413 import cbc_encrypt -from pygost.gost3413 import cfb_decrypt -from pygost.gost3413 import cfb_encrypt -from pygost.gost3413 import ctr -from pygost.gost3413 import ctr_acpkm -from pygost.gost3413 import ecb_decrypt -from pygost.gost3413 import ecb_encrypt -from pygost.gost3413 import KEYSIZE -from pygost.gost3413 import mac -from pygost.gost3413 import mac_acpkm_master -from pygost.gost3413 import ofb -from pygost.gost3413 import pad2 -from pygost.gost3413 import pad_iso10126 -from pygost.gost3413 import unpad2 -from pygost.gost3413 import unpad_iso10126 -from pygost.utils import hexdec -from pygost.utils import hexenc -from pygost.utils import strxor - - -class Pad2Test(TestCase): - def test_symmetric(self): - for _ in range(100): - for blocksize in (GOST3412Magma.blocksize, GOST3412Kuznechik.blocksize): - data = urandom(randint(0, blocksize * 3)) - self.assertSequenceEqual( - unpad2(pad2(data, blocksize), blocksize), - data, - ) - - -class GOST3412KuznechikModesTest(TestCase): - key = hexdec("8899aabbccddeeff0011223344556677fedcba98765432100123456789abcdef") - ciph = GOST3412Kuznechik(key) - plaintext = "" - plaintext += "1122334455667700ffeeddccbbaa9988" - plaintext += "00112233445566778899aabbcceeff0a" - plaintext += "112233445566778899aabbcceeff0a00" - plaintext += "2233445566778899aabbcceeff0a0011" - iv = hexdec("1234567890abcef0a1b2c3d4e5f0011223344556677889901213141516171819") - - def test_ecb_vectors(self): - ciphtext = "" - ciphtext += "7f679d90bebc24305a468d42b9d4edcd" - ciphtext += "b429912c6e0032f9285452d76718d08b" - ciphtext += "f0ca33549d247ceef3f5a5313bd4b157" - ciphtext += "d0b09ccde830b9eb3a02c4c5aa8ada98" - self.assertSequenceEqual( - hexenc(ecb_encrypt( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(self.plaintext), - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(ecb_decrypt( - self.ciph.decrypt, - GOST3412Kuznechik.blocksize, - hexdec(ciphtext), - )), - self.plaintext, - ) - - def test_ecb_symmetric(self): - for _ in range(100): - pt = pad2(urandom(randint(0, 16 * 2)), GOST3412Kuznechik.blocksize) - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - ct = ecb_encrypt(ciph.encrypt, GOST3412Kuznechik.blocksize, pt) - self.assertSequenceEqual(ecb_decrypt( - ciph.decrypt, - GOST3412Kuznechik.blocksize, - ct, - ), pt) - - def test_ctr_vectors(self): - ciphtext = "" - ciphtext += "f195d8bec10ed1dbd57b5fa240bda1b8" - ciphtext += "85eee733f6a13e5df33ce4b33c45dee4" - ciphtext += "a5eae88be6356ed3d5e877f13564a3a5" - ciphtext += "cb91fab1f20cbab6d1c6d15820bdba73" - iv = self.iv[:GOST3412Kuznechik.blocksize // 2] - self.assertSequenceEqual( - hexenc(ctr( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(self.plaintext), - iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(ctr( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(ciphtext), - iv, - )), - self.plaintext, - ) - - def test_ctr_symmetric(self): - for _ in range(100): - pt = urandom(randint(0, 16 * 2)) - iv = urandom(GOST3412Kuznechik.blocksize // 2) - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - ct = ctr(ciph.encrypt, GOST3412Kuznechik.blocksize, pt, iv) - self.assertSequenceEqual(ctr( - ciph.encrypt, - GOST3412Kuznechik.blocksize, - ct, - iv, - ), pt) - - def test_ofb_vectors(self): - ciphtext = "" - ciphtext += "81800a59b1842b24ff1f795e897abd95" - ciphtext += "ed5b47a7048cfab48fb521369d9326bf" - ciphtext += "66a257ac3ca0b8b1c80fe7fc10288a13" - ciphtext += "203ebbc066138660a0292243f6903150" - self.assertSequenceEqual( - hexenc(ofb( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(self.plaintext), - self.iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(ofb( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(ciphtext), - self.iv, - )), - self.plaintext, - ) - - def test_ofb_symmetric(self): - for _ in range(100): - pt = urandom(randint(0, 16 * 2)) - iv = urandom(GOST3412Kuznechik.blocksize * 2) - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - ct = ofb(ciph.encrypt, GOST3412Kuznechik.blocksize, pt, iv) - self.assertSequenceEqual(ofb( - ciph.encrypt, - GOST3412Kuznechik.blocksize, - ct, - iv, - ), pt) - - def test_ofb_manual(self): - iv = [urandom(GOST3412Kuznechik.blocksize) for _ in range(randint(2, 10))] - pt = [ - urandom(GOST3412Kuznechik.blocksize) - for _ in range(len(iv), len(iv) + randint(1, 10)) - ] - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - r = [ciph.encrypt(i) for i in iv] - for i in range(len(pt) - len(iv)): - r.append(ciph.encrypt(r[i])) - ct = [strxor(g, r) for g, r in zip(pt, r)] - self.assertSequenceEqual( - ofb(ciph.encrypt, GOST3412Kuznechik.blocksize, b"".join(pt), b"".join(iv)), - b"".join(ct), - ) - - def test_cbc_vectors(self): - ciphtext = "" - ciphtext += "689972d4a085fa4d90e52e3d6d7dcc27" - ciphtext += "2826e661b478eca6af1e8e448d5ea5ac" - ciphtext += "fe7babf1e91999e85640e8b0f49d90d0" - ciphtext += "167688065a895c631a2d9a1560b63970" - self.assertSequenceEqual( - hexenc(cbc_encrypt( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(self.plaintext), - self.iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(cbc_decrypt( - self.ciph.decrypt, - GOST3412Kuznechik.blocksize, - hexdec(ciphtext), - self.iv, - )), - self.plaintext, - ) - - def test_cbc_symmetric(self): - for _ in range(100): - pt = pad2(urandom(randint(0, 16 * 2)), GOST3412Kuznechik.blocksize) - iv = urandom(GOST3412Kuznechik.blocksize * 2) - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - ct = cbc_encrypt(ciph.encrypt, GOST3412Kuznechik.blocksize, pt, iv) - self.assertSequenceEqual(cbc_decrypt( - ciph.decrypt, - GOST3412Kuznechik.blocksize, - ct, - iv, - ), pt) - - def test_cfb_vectors(self): - ciphtext = "" - ciphtext += "81800a59b1842b24ff1f795e897abd95" - ciphtext += "ed5b47a7048cfab48fb521369d9326bf" - ciphtext += "79f2a8eb5cc68d38842d264e97a238b5" - ciphtext += "4ffebecd4e922de6c75bd9dd44fbf4d1" - self.assertSequenceEqual( - hexenc(cfb_encrypt( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(self.plaintext), - self.iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(cfb_decrypt( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(ciphtext), - self.iv, - )), - self.plaintext, - ) - - def test_cfb_symmetric(self): - for _ in range(100): - pt = urandom(randint(0, 16 * 2)) - iv = urandom(GOST3412Kuznechik.blocksize * 2) - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - ct = cfb_encrypt(ciph.encrypt, GOST3412Kuznechik.blocksize, pt, iv) - self.assertSequenceEqual(cfb_decrypt( - ciph.encrypt, - GOST3412Kuznechik.blocksize, - ct, - iv, - ), pt) - - def test_mac_vectors(self): - k1, k2 = _mac_ks(self.ciph.encrypt, GOST3412Kuznechik.blocksize) - self.assertSequenceEqual(hexenc(k1), "297d82bc4d39e3ca0de0573298151dc7") - self.assertSequenceEqual(hexenc(k2), "52fb05789a73c7941bc0ae65302a3b8e") - self.assertSequenceEqual( - hexenc(mac( - self.ciph.encrypt, - GOST3412Kuznechik.blocksize, - hexdec(self.plaintext), - )[:8]), - "336f4d296059fbe3", - ) - - def test_mac_applies(self): - for _ in range(100): - data = urandom(randint(0, 16 * 2)) - ciph = GOST3412Kuznechik(urandom(KEYSIZE)) - mac(ciph.encrypt, GOST3412Kuznechik.blocksize, data) - - -class GOST3412MagmaModesTest(TestCase): - key = hexdec("ffeeddccbbaa99887766554433221100f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") - ciph = GOST3412Magma(key) - plaintext = "" - plaintext += "92def06b3c130a59" - plaintext += "db54c704f8189d20" - plaintext += "4a98fb2e67a8024c" - plaintext += "8912409b17b57e41" - iv = hexdec("1234567890abcdef234567890abcdef134567890abcdef12") - - def test_ecb_vectors(self): - ciphtext = "" - ciphtext += "2b073f0494f372a0" - ciphtext += "de70e715d3556e48" - ciphtext += "11d8d9e9eacfbc1e" - ciphtext += "7c68260996c67efb" - self.assertSequenceEqual( - hexenc(ecb_encrypt( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(self.plaintext), - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(ecb_decrypt( - self.ciph.decrypt, - GOST3412Magma.blocksize, - hexdec(ciphtext), - )), - self.plaintext, - ) - - def test_ecb_symmetric(self): - for _ in range(100): - pt = pad2(urandom(randint(0, 16 * 2)), 16) - ciph = GOST3412Magma(urandom(KEYSIZE)) - ct = ecb_encrypt(ciph.encrypt, GOST3412Magma.blocksize, pt) - self.assertSequenceEqual(ecb_decrypt( - ciph.decrypt, - GOST3412Magma.blocksize, - ct, - ), pt) - - def test_ctr_vectors(self): - ciphtext = "" - ciphtext += "4e98110c97b7b93c" - ciphtext += "3e250d93d6e85d69" - ciphtext += "136d868807b2dbef" - ciphtext += "568eb680ab52a12d" - iv = self.iv[:4] - self.assertSequenceEqual( - hexenc(ctr( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(self.plaintext), - iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(ctr( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(ciphtext), - iv, - )), - self.plaintext, - ) - - def test_ctr_symmetric(self): - for _ in range(100): - pt = urandom(randint(0, 16 * 2)) - iv = urandom(GOST3412Magma.blocksize // 2) - ciph = GOST3412Magma(urandom(KEYSIZE)) - ct = ctr(ciph.encrypt, GOST3412Magma.blocksize, pt, iv) - self.assertSequenceEqual(ctr( - ciph.encrypt, - GOST3412Magma.blocksize, - ct, - iv, - ), pt) - - def test_ofb_vectors(self): - iv = self.iv[:16] - ciphtext = "" - ciphtext += "db37e0e266903c83" - ciphtext += "0d46644c1f9a089c" - ciphtext += "a0f83062430e327e" - ciphtext += "c824efb8bd4fdb05" - self.assertSequenceEqual( - hexenc(ofb( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(self.plaintext), - iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(ofb( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(ciphtext), - iv, - )), - self.plaintext, - ) - - def test_ofb_symmetric(self): - for _ in range(100): - pt = urandom(randint(0, 16 * 2)) - iv = urandom(GOST3412Magma.blocksize * 2) - ciph = GOST3412Magma(urandom(KEYSIZE)) - ct = ofb(ciph.encrypt, GOST3412Magma.blocksize, pt, iv) - self.assertSequenceEqual(ofb( - ciph.encrypt, - GOST3412Magma.blocksize, - ct, - iv, - ), pt) - - def test_cbc_vectors(self): - ciphtext = "" - ciphtext += "96d1b05eea683919" - ciphtext += "aff76129abb937b9" - ciphtext += "5058b4a1c4bc0019" - ciphtext += "20b78b1a7cd7e667" - self.assertSequenceEqual( - hexenc(cbc_encrypt( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(self.plaintext), - self.iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(cbc_decrypt( - self.ciph.decrypt, - GOST3412Magma.blocksize, - hexdec(ciphtext), - self.iv, - )), - self.plaintext, - ) - - def test_cbc_symmetric(self): - for _ in range(100): - pt = pad2(urandom(randint(0, 16 * 2)), 16) - iv = urandom(GOST3412Magma.blocksize * 2) - ciph = GOST3412Magma(urandom(KEYSIZE)) - ct = cbc_encrypt(ciph.encrypt, GOST3412Magma.blocksize, pt, iv) - self.assertSequenceEqual(cbc_decrypt( - ciph.decrypt, - GOST3412Magma.blocksize, - ct, - iv, - ), pt) - - def test_cfb_vectors(self): - iv = self.iv[:16] - ciphtext = "" - ciphtext += "db37e0e266903c83" - ciphtext += "0d46644c1f9a089c" - ciphtext += "24bdd2035315d38b" - ciphtext += "bcc0321421075505" - self.assertSequenceEqual( - hexenc(cfb_encrypt( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(self.plaintext), - iv, - )), - ciphtext, - ) - self.assertSequenceEqual( - hexenc(cfb_decrypt( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(ciphtext), - iv, - )), - self.plaintext, - ) - - def test_cfb_symmetric(self): - for _ in range(100): - pt = urandom(randint(0, 16 * 2)) - iv = urandom(GOST3412Magma.blocksize * 2) - ciph = GOST3412Magma(urandom(KEYSIZE)) - ct = cfb_encrypt(ciph.encrypt, GOST3412Magma.blocksize, pt, iv) - self.assertSequenceEqual(cfb_decrypt( - ciph.encrypt, - GOST3412Magma.blocksize, - ct, - iv, - ), pt) - - def test_mac_vectors(self): - k1, k2 = _mac_ks(self.ciph.encrypt, GOST3412Magma.blocksize) - self.assertSequenceEqual(hexenc(k1), "5f459b3342521424") - self.assertSequenceEqual(hexenc(k2), "be8b366684a42848") - self.assertSequenceEqual( - hexenc(mac( - self.ciph.encrypt, - GOST3412Magma.blocksize, - hexdec(self.plaintext), - )[:4]), - "154e7210", - ) - - def test_mac_applies(self): - for _ in range(100): - data = urandom(randint(0, 16 * 2)) - ciph = GOST3412Magma(urandom(KEYSIZE)) - mac(ciph.encrypt, GOST3412Magma.blocksize, data) - - -class TestVectorACPKM(TestCase): - """Test vectors from Р 1323565.1.017-2018 - """ - key = hexdec("8899AABBCCDDEEFF0011223344556677FEDCBA98765432100123456789ABCDEF") - - def test_magma_ctr_acpkm(self): - key = acpkm(GOST3412Magma(self.key).encrypt, GOST3412Magma.blocksize) - self.assertSequenceEqual(key, hexdec("863EA017842C3D372B18A85A28E2317D74BEFC107720DE0C9E8AB974ABD00CA0")) - key = acpkm(GOST3412Magma(key).encrypt, GOST3412Magma.blocksize) - self.assertSequenceEqual(key, hexdec("49A5E2677DE555982B8AD5E826652D17EEC847BF5B3997A81CF7FE7F1187BD27")) - key = acpkm(GOST3412Magma(key).encrypt, GOST3412Magma.blocksize) - self.assertSequenceEqual(key, hexdec("3256BF3F97B5667426A9FB1C5EAABE41893CCDD5A868F9B63B0AA90720FA43C4")) - - def test_magma_ctr(self): - encrypter = GOST3412Magma(self.key).encrypt - plaintext = hexdec(""" -11 22 33 44 55 66 77 00 FF EE DD CC BB AA 99 88 -00 11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A -11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 -22 33 44 55 66 77 88 99 - """.replace("\n", "").replace(" ", "")) - iv = hexdec("12345678") - ciphertext = hexdec(""" -2A B8 1D EE EB 1E 4C AB 68 E1 04 C4 BD 6B 94 EA -C7 2C 67 AF 6C 2E 5B 6B 0E AF B6 17 70 F1 B3 2E -A1 AE 71 14 9E ED 13 82 AB D4 67 18 06 72 EC 6F -84 A2 F1 5B 3F CA 72 C1 - """.replace("\n", "").replace(" ", "")) - self.assertSequenceEqual( - ctr_acpkm( - GOST3412Magma, - encrypter, - bs=GOST3412Magma.blocksize, - section_size=GOST3412Magma.blocksize * 2, - data=plaintext, - iv=iv - ), - ciphertext, - ) - self.assertSequenceEqual( - ctr_acpkm( - GOST3412Magma, - encrypter, - bs=GOST3412Magma.blocksize, - section_size=GOST3412Magma.blocksize * 2, - data=ciphertext, - iv=iv - ), - plaintext, - ) - - def test_kuznechik_ctr_acpkm(self): - key = acpkm(GOST3412Kuznechik(self.key).encrypt, GOST3412Kuznechik.blocksize) - self.assertSequenceEqual(key, hexdec("2666ED40AE687811745CA0B448F57A7B390ADB5780307E8E9659AC403AE60C60")) - key = acpkm(GOST3412Kuznechik(key).encrypt, GOST3412Kuznechik.blocksize) - self.assertSequenceEqual(key, hexdec("BB3DD5402E999B7A3DEBB0DB45448EC530F07365DFEE3ABA8415F77AC8F34CE8")) - key = acpkm(GOST3412Kuznechik(key).encrypt, GOST3412Kuznechik.blocksize) - self.assertSequenceEqual(key, hexdec("23362FD553CAD2178299A5B5A2D4722E3BB83C730A8BF57CE2DD004017F8C565")) - - def test_kuznechik_ctr(self): - encrypter = GOST3412Kuznechik(self.key).encrypt - iv = hexdec("1234567890ABCEF0") - plaintext = hexdec(""" -11 22 33 44 55 66 77 00 FF EE DD CC BB AA 99 88 -00 11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A -11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 -22 33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 11 -33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 11 22 -44 55 66 77 88 99 AA BB CC EE FF 0A 00 11 22 33 -55 66 77 88 99 AA BB CC EE FF 0A 00 11 22 33 44 - """.replace("\n", "").replace(" ", "")) - ciphertext = hexdec(""" -F1 95 D8 BE C1 0E D1 DB D5 7B 5F A2 40 BD A1 B8 -85 EE E7 33 F6 A1 3E 5D F3 3C E4 B3 3C 45 DE E4 -4B CE EB 8F 64 6F 4C 55 00 17 06 27 5E 85 E8 00 -58 7C 4D F5 68 D0 94 39 3E 48 34 AF D0 80 50 46 -CF 30 F5 76 86 AE EC E1 1C FC 6C 31 6B 8A 89 6E -DF FD 07 EC 81 36 36 46 0C 4F 3B 74 34 23 16 3E -64 09 A9 C2 82 FA C8 D4 69 D2 21 E7 FB D6 DE 5D - """.replace("\n", "").replace(" ", "")) - self.assertSequenceEqual( - ctr_acpkm( - GOST3412Kuznechik, - encrypter, - bs=GOST3412Kuznechik.blocksize, - section_size=GOST3412Kuznechik.blocksize * 2, - data=plaintext, - iv=iv, - ), - ciphertext, - ) - self.assertSequenceEqual( - ctr_acpkm( - GOST3412Kuznechik, - encrypter, - bs=GOST3412Kuznechik.blocksize, - section_size=GOST3412Kuznechik.blocksize * 2, - data=ciphertext, - iv=iv, - ), - plaintext, - ) - - def test_magma_omac_1_5_blocks(self): - encrypter = GOST3412Magma(self.key).encrypt - key_section_size = 640 // 8 - self.assertSequenceEqual( - acpkm_master( - GOST3412Magma, - encrypter, - key_section_size=key_section_size, - bs=GOST3412Magma.blocksize, - keymat_len=KEYSIZE + GOST3412Magma.blocksize, - ), - hexdec("0DF2F5273DA328932AC49D81D36B2558A50DBF9BBCAC74A614B2CCB2F1CBCD8A70638E3DE8B3571E"), - ) - text = hexdec("1122334455667700FFEEDDCC") - self.assertSequenceEqual( - mac_acpkm_master( - GOST3412Magma, - encrypter, - key_section_size, - section_size=GOST3412Magma.blocksize * 2, - bs=GOST3412Magma.blocksize, - data=text, - ), - hexdec("A0540E3730ACBCF3"), - ) - - def test_magma_omac_5_blocks(self): - encrypter = GOST3412Magma(self.key).encrypt - key_section_size = 640 // 8 - self.assertSequenceEqual( - acpkm_master( - GOST3412Magma, - encrypter, - key_section_size=key_section_size, - bs=GOST3412Magma.blocksize, - keymat_len=3 * (KEYSIZE + GOST3412Magma.blocksize), - ), - hexdec(""" -0D F2 F5 27 3D A3 28 93 2A C4 9D 81 D3 6B 25 58 -A5 0D BF 9B BC AC 74 A6 14 B2 CC B2 F1 CB CD 8A -70 63 8E 3D E8 B3 57 1E 8D 38 26 D5 5E 63 A1 67 -E2 40 66 40 54 7B 9F 1F 5F 2B 43 61 2A AE AF DA -18 0B AC 86 04 DF A6 FE 53 C2 CE 27 0E 9C 9F 52 -68 D0 FD BF E1 A3 BD D9 BE 5B 96 D0 A1 20 23 48 -6E F1 71 0F 92 4A E0 31 30 52 CB 5F CA 0B 79 1E -1B AB E8 57 6D 0F E3 A8 - """.replace("\n", "").replace(" ", "")), - ) - text = hexdec(""" -11 22 33 44 55 66 77 00 FF EE DD CC BB AA 99 88 -00 11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A -11 22 33 44 55 66 77 88 - """.replace("\n", "").replace(" ", "")) - self.assertSequenceEqual( - mac_acpkm_master( - GOST3412Magma, - encrypter, - key_section_size, - section_size=GOST3412Magma.blocksize * 2, - bs=GOST3412Magma.blocksize, - data=text, - ), - hexdec("34008DAD5496BB8E"), - ) - - def test_kuznechik_omac_1_5_blocks(self): - encrypter = GOST3412Kuznechik(self.key).encrypt - key_section_size = 768 // 8 - self.assertSequenceEqual( - acpkm_master( - GOST3412Kuznechik, - encrypter, - key_section_size=key_section_size, - bs=GOST3412Kuznechik.blocksize, - keymat_len=KEYSIZE + GOST3412Kuznechik.blocksize, - ), - hexdec(""" -0C AB F1 F2 EF BC 4A C1 60 48 DF 1A 24 C6 05 B2 -C0 D1 67 3D 75 86 A8 EC 0D D4 2C 45 A4 F9 5B AE -0F 2E 26 17 E4 71 48 68 0F C3 E6 17 8D F2 C1 37 - """.replace("\n", "").replace(" ", "")) - ) - text = hexdec(""" -11 22 33 44 55 66 77 00 FF EE DD CC BB AA 99 88 -00 11 22 33 44 55 66 77 - """.replace("\n", "").replace(" ", "")) - self.assertSequenceEqual( - mac_acpkm_master( - GOST3412Kuznechik, - encrypter, - key_section_size, - section_size=GOST3412Kuznechik.blocksize * 2, - bs=GOST3412Kuznechik.blocksize, - data=text, - ), - hexdec("B5367F47B62B995EEB2A648C5843145E"), - ) - - def test_kuznechik_omac_5_blocks(self): - encrypter = GOST3412Kuznechik(self.key).encrypt - key_section_size = 768 // 8 - self.assertSequenceEqual( - acpkm_master( - GOST3412Kuznechik, - encrypter, - key_section_size=key_section_size, - bs=GOST3412Kuznechik.blocksize, - keymat_len=3 * (KEYSIZE + GOST3412Kuznechik.blocksize), - ), - hexdec(""" -0C AB F1 F2 EF BC 4A C1 60 48 DF 1A 24 C6 05 B2 -C0 D1 67 3D 75 86 A8 EC 0D D4 2C 45 A4 F9 5B AE -0F 2E 26 17 E4 71 48 68 0F C3 E6 17 8D F2 C1 37 -C9 DD A8 9C FF A4 91 FE AD D9 B3 EA B7 03 BB 31 -BC 7E 92 7F 04 94 72 9F 51 B4 9D 3D F9 C9 46 08 -00 FB BC F5 ED EE 61 0E A0 2F 01 09 3C 7B C7 42 -D7 D6 27 15 01 B1 77 77 52 63 C2 A3 49 5A 83 18 -A8 1C 79 A0 4F 29 66 0E A3 FD A8 74 C6 30 79 9E -14 2C 57 79 14 FE A9 0D 3B C2 50 2E 83 36 85 D9 - """.replace("\n", "").replace(" ", "")), - ) - text = hexdec(""" -11 22 33 44 55 66 77 00 FF EE DD CC BB AA 99 88 -00 11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A -11 22 33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 -22 33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 11 -33 44 55 66 77 88 99 AA BB CC EE FF 0A 00 11 22 - """.replace("\n", "").replace(" ", "")) - self.assertSequenceEqual( - mac_acpkm_master( - GOST3412Kuznechik, - encrypter, - key_section_size, - section_size=GOST3412Kuznechik.blocksize * 2, - bs=GOST3412Kuznechik.blocksize, - data=text, - ), - hexdec("FBB8DCEE45BEA67C35F58C5700898E5D"), - ) - - -class ISO10126Test(TestCase): - def test_symmetric(self): - for _ in range(100): - for blocksize in (GOST3412Magma.blocksize, GOST3412Kuznechik.blocksize): - data = urandom(randint(0, blocksize * 3)) - padded = pad_iso10126(data, blocksize) - self.assertSequenceEqual(unpad_iso10126(padded, blocksize), data) - with self.assertRaises(ValueError): - unpad_iso10126(padded[1:], blocksize) - - def test_small(self): - with self.assertRaises(ValueError): - unpad_iso10126(b"foobar\x00\x09", 8) diff --git a/pygost-5.13/pygost/test_kdf.py b/pygost-5.13/pygost/test_kdf.py deleted file mode 100644 index 6921cfc..0000000 --- a/pygost-5.13/pygost/test_kdf.py +++ /dev/null @@ -1,58 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 unittest import TestCase - -from pygost.kdf import kdf_gostr3411_2012_256 -from pygost.kdf import kdf_tree_gostr3411_2012_256 -from pygost.utils import hexdec - - -class TestKDFGOSTR34112012256(TestCase): - def runTest(self): - self.assertEqual( - kdf_gostr3411_2012_256( - hexdec("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), - hexdec("26bdb878"), - hexdec("af21434145656378"), - ), - hexdec("a1aa5f7de402d7b3d323f2991c8d4534013137010a83754fd0af6d7cd4922ed9"), - ) - - -class TestKDFTREEGOSTR34112012256(TestCase): - def runTest(self): - self.assertSequenceEqual( - kdf_tree_gostr3411_2012_256( - hexdec("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), - hexdec("26bdb878"), - hexdec("af21434145656378"), - 1, - ), - (hexdec("a1aa5f7de402d7b3d323f2991c8d4534013137010a83754fd0af6d7cd4922ed9"),), - ) - self.assertSequenceEqual( - kdf_tree_gostr3411_2012_256( - hexdec("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), - hexdec("26bdb878"), - hexdec("af21434145656378"), - 2, - ), - ( - hexdec("22b6837845c6bef65ea71672b265831086d3c76aebe6dae91cad51d83f79d16b"), - hexdec("074c9330599d7f8d712fca54392f4ddde93751206b3584c8f43f9e6dc51531f9"), - ), - ) diff --git a/pygost-5.13/pygost/test_mgm.py b/pygost-5.13/pygost/test_mgm.py deleted file mode 100644 index e70a7f5..0000000 --- a/pygost-5.13/pygost/test_mgm.py +++ /dev/null @@ -1,75 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from random import randint -from unittest import TestCase - -from pygost.gost3412 import GOST3412Kuznechik -from pygost.gost3412 import GOST3412Magma -from pygost.gost3412 import KEYSIZE -from pygost.mgm import MGM -from pygost.mgm import nonce_prepare -from pygost.utils import hexdec - - -class TestVector(TestCase): - def runTest(self): - key = hexdec("8899AABBCCDDEEFF0011223344556677FEDCBA98765432100123456789ABCDEF") - ad = hexdec("0202020202020202010101010101010104040404040404040303030303030303EA0505050505050505") - plaintext = hexdec("1122334455667700FFEEDDCCBBAA998800112233445566778899AABBCCEEFF0A112233445566778899AABBCCEEFF0A002233445566778899AABBCCEEFF0A0011AABBCC") - mgm = MGM(GOST3412Kuznechik(key).encrypt, GOST3412Kuznechik.blocksize) - ciphertext = mgm.seal(plaintext[:16], plaintext, ad) - self.assertSequenceEqual(ciphertext[:len(plaintext)], hexdec("A9757B8147956E9055B8A33DE89F42FC8075D2212BF9FD5BD3F7069AADC16B39497AB15915A6BA85936B5D0EA9F6851CC60C14D4D3F883D0AB94420695C76DEB2C7552")) - self.assertSequenceEqual(ciphertext[len(plaintext):], hexdec("CF5D656F40C34F5C46E8BB0E29FCDB4C")) - self.assertSequenceEqual(mgm.open(plaintext[:16], ciphertext, ad), plaintext) - - -class TestSymmetric(TestCase): - def _itself(self, mgm, bs, tag_size): - for _ in range(1000): - nonce = nonce_prepare(urandom(bs)) - ad = urandom(randint(0, 20)) - pt = urandom(randint(0, 20)) - if len(ad) + len(pt) == 0: - continue - ct = mgm.seal(nonce, pt, ad) - self.assertEqual(len(ct) - tag_size, len(pt)) - self.assertSequenceEqual(mgm.open(nonce, ct, ad), pt) - - def test_magma(self): - for tag_size in ( - GOST3412Magma.blocksize, - GOST3412Magma.blocksize - 2, - ): - mgm = MGM( - GOST3412Magma(urandom(KEYSIZE)).encrypt, - GOST3412Magma.blocksize, - tag_size, - ) - self._itself(mgm, GOST3412Magma.blocksize, tag_size) - - def test_kuznechik(self): - for tag_size in ( - GOST3412Kuznechik.blocksize, - GOST3412Kuznechik.blocksize - 2, - ): - mgm = MGM( - GOST3412Kuznechik(urandom(KEYSIZE)).encrypt, - GOST3412Kuznechik.blocksize, - tag_size, - ) - self._itself(mgm, GOST3412Kuznechik.blocksize, tag_size) diff --git a/pygost-5.13/pygost/test_pfx.py b/pygost-5.13/pygost/test_pfx.py deleted file mode 100644 index a2760bf..0000000 --- a/pygost-5.13/pygost/test_pfx.py +++ /dev/null @@ -1,680 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 import gost3410 -from pygost.gost28147 import cfb_decrypt -from pygost.gost34112012256 import GOST34112012256 -from pygost.gost34112012512 import GOST34112012512 -from pygost.gost34112012512 import pbkdf2 as gost34112012_pbkdf2 -from pygost.gost3412 import GOST3412Kuznechik -from pygost.gost3412 import GOST3412Magma -from pygost.gost3412 import KEYSIZE -from pygost.gost3413 import ctr_acpkm -from pygost.gost3413 import mac as omac -from pygost.kdf import kdf_tree_gostr3411_2012_256 -from pygost.kdf import keg -from pygost.utils import hexdec -from pygost.wrap import kimp15 - - -try: - from pyderasn import OctetString - - from pygost.asn1schemas.cms import EncryptedData - from pygost.asn1schemas.cms import EnvelopedData - from pygost.asn1schemas.cms import SignedAttributes - from pygost.asn1schemas.cms import SignedData - from pygost.asn1schemas.oids import id_data - from pygost.asn1schemas.oids import id_envelopedData - from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_ctracpkm - from pygost.asn1schemas.oids import id_gostr3412_2015_kuznyechik_wrap_kexp15 - from pygost.asn1schemas.oids import id_messageDigest - from pygost.asn1schemas.oids import id_pbes2 - from pygost.asn1schemas.oids import id_pkcs12_bagtypes_certBag - from pygost.asn1schemas.oids import id_pkcs12_bagtypes_keyBag - from pygost.asn1schemas.oids import id_pkcs12_bagtypes_pkcs8ShroudedKeyBag - from pygost.asn1schemas.oids import id_pkcs9_certTypes_x509Certificate - from pygost.asn1schemas.oids import id_signedData - from pygost.asn1schemas.oids import id_tc26_agreement_gost3410_2012_256 - from pygost.asn1schemas.oids import id_tc26_gost3411_2012_256 - from pygost.asn1schemas.pfx import CertBag - from pygost.asn1schemas.pfx import KeyBag - from pygost.asn1schemas.pfx import OctetStringSafeContents - from pygost.asn1schemas.pfx import PBES2Params - from pygost.asn1schemas.pfx import PFX - from pygost.asn1schemas.pfx import PKCS8ShroudedKeyBag - from pygost.asn1schemas.pfx import SafeContents - from pygost.asn1schemas.x509 import Certificate -except ImportError: - pyderasn_exists = False -else: - 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"") - _, outer_safe_contents = pfx["authSafe"]["content"].defined - safe_contents, tail = OctetStringSafeContents().decode( - bytes(outer_safe_contents[0]["bagValue"]), - ) - self.assertSequenceEqual(tail, b"") - safe_bag = safe_contents[0] - shrouded_key_bag, tail = PKCS8ShroudedKeyBag().decode( - bytes(safe_bag["bagValue"]), - ) - self.assertSequenceEqual(tail, b"") - _, pbes2_params = shrouded_key_bag["encryptionAlgorithm"]["parameters"].defined - _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined - _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined - - 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="id-tc26-gost-28147-param-Z", - ), - 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"") - _, outer_safe_contents = pfx["authSafe"]["content"].defined - _, encrypted_data = outer_safe_contents[1]["bagValue"].defined - _, pbes2_params = encrypted_data["encryptedContentInfo"]["contentEncryptionAlgorithm"]["parameters"].defined - _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined - _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined - 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="id-tc26-gost-28147-param-Z", - ), - cert_bag_expected, - ) - - def test_mac(self): - pfx, tail = PFX().decode(self.pfx_raw) - self.assertSequenceEqual(tail, b"") - _, outer_safe_contents = pfx["authSafe"]["content"].defined - 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=SafeContents(outer_safe_contents).encode(), - digestmod=GOST34112012512, - ).digest(), - bytes(mac_data["mac"]["digest"]), - ) - - -@skipIf(not pyderasn_exists, "PyDERASN dependency is required") -class TestPFX2020(TestCase): - """PFX test vectors from newer PKCS#12 update - """ - ca_prv_raw = hexdec("092F8D059E97E22B90B1AE99F0087FC4D26620B91550CBB437C191005A290810") - ca_curve = gost3410.CURVES["id-tc26-gost-3410-12-256-paramSetA"] - ca_cert = Certificate().decod(b64decode(b""" - MIIB+TCCAaagAwIBAgIEAYy6gTAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2MS - cwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEwMTAx - MDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA4MQ0wCwYDVQQKEwRUSzI2MScwJQYDVQQDEx - 5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwXjAXBggqhQMHAQEBATALBgkq - hQMHAQIBAQEDQwAEQBpKgpyPDnhQAJyLqy8Qs0XQhgxEhby6tSypqYimgbjpcKqtU6 - 4jpDXc3h3BxGxtl2oHJ/4YLZ/ll87dto3ltMqjgZgwgZUwYwYDVR0jBFwwWoAUrGwO - TERmokKW4p8JOyVm88ukUyqhPKQ6MDgxDTALBgNVBAoTBFRLMjYxJzAlBgNVBAMTHk - NBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdIIEAYy6gTAdBgNVHQ4EFgQUrGwO - TERmokKW4p8JOyVm88ukUyowDwYDVR0TAQH/BAUwAwEB/zAKBggqhQMHAQEDAgNBAB - Gg3nhgQ5oCKbqlEdVaRxH+1WX4wVkawGXuTYkr1AC2OWw3ZC14Vvg3nazm8UMWUZtk - vu1kJcHQ4jFKkjUeg2E= - """)) - ca_pub = gost3410.pub_unmarshal(bytes(OctetString().decod(bytes( - ca_cert["tbsCertificate"]["subjectPublicKeyInfo"]["subjectPublicKey"] - )))) - password = u"Пароль для PFX".encode("utf-8") - cert_test = Certificate().decod(b64decode(b""" - MIICLjCCAdugAwIBAgIEAYy6hDAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2MS - cwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEwMTAx - MDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRUSzI2MSowKAYDVQQDEy - FPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDUxMi1iaXQwgaAwFwYIKoUDBwEBAQIw - CwYJKoUDBwECAQIBA4GEAASBgLSLt1q8KQ4YZVxioU+1LV9QhE7MHR9gBEh7S1yVNG - lqt7+rNG5VFqmrPM74rbUsOlhV8M+zZKprXdk35Oz8lSW/n2oIUHZxikXIH/SSHj4r - v3K/Puvz7hYTQSZl/xPdp78nUmjrEa6d5wfX8biEy2z0dgufFvAkMw1Ua4gdXqDOo4 - GHMIGEMGMGA1UdIwRcMFqAFKxsDkxEZqJCluKfCTslZvPLpFMqoTykOjA4MQ0wCwYD - VQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaX - SCBAGMuoEwHQYDVR0OBBYEFH4GVwmYDK1rCKhX7nkAWDrJ16CkMAoGCCqFAwcBAQMC - A0EACl6p8dAbpi9Hk+3mgMyI0WIh17IrlrSp/mB0F7ZzMt8XUD1Dwz3JrrnxeXnfMv - OA5BdUJ9hCyDgMVAGs/IcEEA== - """)) - prv_test_raw = b64decode(""" - MIHiAgEBMBcGCCqFAwcBAQECMAsGCSqFAwcBAgECAQRAEWkl+eblsHWs86SNgRKq - SxMOgGhbvR/uZ5/WWfdNG1axvUwVhpcXIxDZUmzQuNzqJBkseI7f5/JjXyTFRF1a - +YGBgQG0i7davCkOGGVcYqFPtS1fUIROzB0fYARIe0tclTRpare/qzRuVRapqzzO - +K21LDpYVfDPs2Sqa13ZN+Ts/JUlv59qCFB2cYpFyB/0kh4+K79yvz7r8+4WE0Em - Zf8T3ae/J1Jo6xGunecH1/G4hMts9HYLnxbwJDMNVGuIHV6gzg== - """) - - def test_cert_and_encrypted_key(self): - pfx_raw = b64decode(b""" - MIIFKwIBAzCCBMQGCSqGSIb3DQEHAaCCBLUEggSxMIIErTCCAswGCSqGSIb3DQEH - AaCCAr0EggK5MIICtTCCArEGCyqGSIb3DQEMCgEDoIICSjCCAkYGCiqGSIb3DQEJ - FgGgggI2BIICMjCCAi4wggHboAMCAQICBAGMuoQwCgYIKoUDBwEBAwIwODENMAsG - A1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYt - Yml0MB4XDTAxMDEwMTAwMDAwMFoXDTQ5MTIzMTAwMDAwMFowOzENMAsGA1UEChME - VEsyNjEqMCgGA1UEAxMhT1JJR0lOQVRPUjogR09TVCAzNC4xMC0xMiA1MTItYml0 - MIGgMBcGCCqFAwcBAQECMAsGCSqFAwcBAgECAQOBhAAEgYC0i7davCkOGGVcYqFP - tS1fUIROzB0fYARIe0tclTRpare/qzRuVRapqzzO+K21LDpYVfDPs2Sqa13ZN+Ts - /JUlv59qCFB2cYpFyB/0kh4+K79yvz7r8+4WE0EmZf8T3ae/J1Jo6xGunecH1/G4 - hMts9HYLnxbwJDMNVGuIHV6gzqOBhzCBhDBjBgNVHSMEXDBagBSsbA5MRGaiQpbi - nwk7JWbzy6RTKqE8pDowODENMAsGA1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsy - NjogR09TVCAzNC4xMC0xMiAyNTYtYml0ggQBjLqBMB0GA1UdDgQWBBR+BlcJmAyt - awioV+55AFg6ydegpDAKBggqhQMHAQEDAgNBAApeqfHQG6YvR5Pt5oDMiNFiIdey - K5a0qf5gdBe2czLfF1A9Q8M9ya658Xl53zLzgOQXVCfYQsg4DFQBrPyHBBAxVDAj - BgkqhkiG9w0BCRUxFgQUeVV0+dS25MICJChpmGc/8AoUwE0wLQYJKoZIhvcNAQkU - MSAeHgBwADEAMgBGAHIAaQBlAG4AZABsAHkATgBhAG0AZTCCAdkGCSqGSIb3DQEH - AaCCAcoEggHGMIIBwjCCAb4GCyqGSIb3DQEMCgECoIIBVzCCAVMwWQYJKoZIhvcN - AQUNMEwwKQYJKoZIhvcNAQUMMBwECKf4N7NMwugqAgIIADAMBggqhQMHAQEEAgUA - MB8GCSqFAwcBAQUCAjASBBAlmt2WDfaPJlsAs0mLKglzBIH1DMvEacbbWRNDVSnX - JLWygYrKoipdOjDA/2HEnBZ34uFOLNheUqiKpCPoFpbR2GBiVYVTVK9ibiczgaca - EQYzDXtcS0QCZOxpKWfteAlbdJLC/SqPurPYyKi0MVRUPROhbisFASDT38HDH1Dh - 0dL5f6ga4aPWLrWbbgWERFOoOPyh4DotlPF37AQOwiEjsbyyRHq3HgbWiaxQRuAh - eqHOn4QVGY92/HFvJ7u3TcnQdLWhTe/lh1RHLNF3RnXtN9if9zC23laDZOiWZplU - yLrUiTCbHrtn1RppPDmLFNMt9dJ7KKgCkOi7Zm5nhqPChbywX13wcfYxVDAjBgkq - hkiG9w0BCRUxFgQUeVV0+dS25MICJChpmGc/8AoUwE0wLQYJKoZIhvcNAQkUMSAe - HgBwADEAMgBGAHIAaQBlAG4AZABsAHkATgBhAG0AZTBeME4wCgYIKoUDBwEBAgME - QAkBKw4ihn7pSIYTEhu0bcvTPZjI3WgVxCkUVlOsc80G69EKFEOTnObGJGSKJ51U - KkOsXF0a7+VBZf3BcVVQh9UECIVEtO+VpuskAgIIAA== - """) - pfx = PFX().decod(pfx_raw) - _, outer_safe_contents = pfx["authSafe"]["content"].defined - - safe_contents = OctetStringSafeContents().decod(bytes( - outer_safe_contents[0]["bagValue"] - )) - safe_bag = safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_certBag) - cert_bag = CertBag().decod(bytes(safe_bag["bagValue"])) - self.assertEqual(cert_bag["certId"], id_pkcs9_certTypes_x509Certificate) - _, cert = cert_bag["certValue"].defined - self.assertEqual(Certificate(cert), self.cert_test) - - safe_contents = OctetStringSafeContents().decod(bytes( - outer_safe_contents[1]["bagValue"] - )) - safe_bag = safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_pkcs8ShroudedKeyBag) - shrouded_key_bag = PKCS8ShroudedKeyBag().decod(bytes(safe_bag["bagValue"])) - _, pbes2_params = shrouded_key_bag["encryptionAlgorithm"]["parameters"].defined - _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined - _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined - ukm = bytes(enc_scheme_params["ukm"]) - key = gost34112012_pbkdf2( - password=self.password, - salt=bytes(pbkdf2_params["salt"]["specified"]), - iterations=int(pbkdf2_params["iterationCount"]), - dklen=32, - ) - # key = hexdec("4b7ae649ca31dd5fe3243a91a5188c03f1d7049bec8e0d241c0e1e8c39ea4c1f") - key_enc, key_mac = kdf_tree_gostr3411_2012_256( - key, b"kdf tree", ukm[GOST3412Kuznechik.blocksize // 2:], 2, - ) - ciphertext = bytes(shrouded_key_bag["encryptedData"]) - plaintext = ctr_acpkm( - GOST3412Kuznechik, - GOST3412Kuznechik(key_enc).encrypt, - section_size=256 * 1024, - bs=GOST3412Kuznechik.blocksize, - data=ciphertext, - iv=ukm[:GOST3412Kuznechik.blocksize // 2], - ) - mac_expected = plaintext[-GOST3412Kuznechik.blocksize:] - plaintext = plaintext[:-GOST3412Kuznechik.blocksize] - mac = omac( - GOST3412Kuznechik(key_mac).encrypt, - GOST3412Kuznechik.blocksize, - plaintext, - ) - self.assertSequenceEqual(mac, mac_expected) - self.assertSequenceEqual(plaintext, self.prv_test_raw) - - mac_data = pfx["macData"] - mac_key = gost34112012_pbkdf2( - password=self.password, - salt=bytes(mac_data["macSalt"]), - iterations=int(mac_data["iterations"]), - dklen=96, - )[-32:] - # mac_key = hexdec("a81d1bc91a4a5cf1fd7320f92dda7e5b285816c3b20826a382d7ed0cbf3a9bf4") - self.assertSequenceEqual( - hmac_new( - key=mac_key, - msg=SafeContents(outer_safe_contents).encode(), - digestmod=GOST34112012512, - ).digest(), - bytes(mac_data["mac"]["digest"]), - ) - self.assertTrue(gost3410.verify( - self.ca_curve, - self.ca_pub, - GOST34112012256(cert["tbsCertificate"].encode()).digest()[::-1], - bytes(cert["signatureValue"]), - )) - - def test_encrypted_cert_and_key(self): - pfx_raw = b64decode(b""" - MIIFjAIBAzCCBSUGCSqGSIb3DQEHAaCCBRYEggUSMIIFDjCCA0EGCSqGSIb3DQEH - BqCCAzIwggMuAgEAMIIDJwYJKoZIhvcNAQcBMFUGCSqGSIb3DQEFDTBIMCkGCSqG - SIb3DQEFDDAcBAgUuSVGsSwGjQICCAAwDAYIKoUDBwEBBAIFADAbBgkqhQMHAQEF - AQIwDgQM9Hk3dagtS48+G/x+gIICwWGPqxxN+sTrKbruRf9R5Ya9cf5AtO1frqMn - f1eULfmZmTg/BdE51QQ+Vbnh3v1kmspr6h2+e4Wli+ndEeCWG6A6X/G22h/RAHW2 - YrVmf6cCWxW+YrqzT4h/8RQL/9haunD5LmHPLVsYrEai0OwbgXayDSwARVJQLQYq - sLNmZK5ViN+fRiS5wszVJ3AtVq8EuPt41aQEKwPy2gmH4S6WmnQRC6W7aoqmIifF - PJENJNn5K2M1J6zNESs6bFtYNKMArNqtvv3rioY6eAaaLy6AV6ljsekmqodHmQjv - Y4eEioJs0xhpXhZY69PXT+ZBeHv6MSheBhwXqxAd1DqtPTafMjNK8rqKCap9TtPG - vONvo5W9dgwegxRRQzlum8dzV4m1W9Aq4W7t8/UcxDWRz3k6ijFPlGaA9+8ZMTEO - RHhBRvM6OY2/VNNxbgxWfGYuPxpSi3YnCZIPmBEe5lU/Xv7KjzFusGM38F8YR61k - 4/QNpKI1QUv714YKfaUQznshGGzILv1NGID62pl1+JI3vuawi2mDMrmkuM9QFU9v - /kRP+c2uBHDuOGEUUSNhF08p7+w3vxplatGWXH9fmIsPBdk2f3wkn+rwoqrEuijM - I/bCAylU/M0DMKhAo9j31UYSZdi4fsfRWYDJMq/8FPn96tuo+oCpbqv3NUwpZM/8 - Li4xqgTHtYw/+fRG0/P6XadNEiII/TYjenLfVHXjAHOVJsVeCu/t3EsMYHQddNCh - rFk/Ic2PdIQOyB4/enpW0qrKegSbyZNuF1WI4zl4mI89L8dTQBUkhy45yQXZlDD8 - k1ErYdtdEsPtz/4zuSpbnmwCEIRoOuSXtGuJP+tbcWEXRKM2UBgi3qBjpn7DU18M - tsrRM9pDdadl8mT/Vfh9+B8dZBZVxgQu70lMPEGexbUkYHuFCCnyi9J0V92StbIz - Elxla1VebjCCAcUGCSqGSIb3DQEHAaCCAbYEggGyMIIBrjCCAaoGCyqGSIb3DQEM - CgECoIIBQzCCAT8wVQYJKoZIhvcNAQUNMEgwKQYJKoZIhvcNAQUMMBwECP0EQk0O - 1twvAgIIADAMBggqhQMHAQEEAgUAMBsGCSqFAwcBAQUBATAOBAzwxSqgAAAAAAAA - AAAEgeUqj9mI3RDfK5hMd0EeYws7foZK/5ANr2wUhP5qnDjAZgn76lExJ+wuvlnS - 9PChfWVugvdl/9XJgQvvr9Cu4pOh4ICXplchcy0dGk/MzItHRVC5wK2nTxwQ4kKT - kG9xhLFzoD16dhtqX0+/dQg9G8pE5EzCBIYRXLm1Arcz9k7KVsTJuNMjFrr7EQuu - Tr80ATSQOtsq50zpFyrpznVPGCrOdIjpymZxNdvw48bZxqTtRVDxCYATOGqz0pwH - ClWULHD9LIajLMB2GhBKyQw6ujIlltJs0T+WNdX/AT2FLi1LFSS3+Cj9MVQwIwYJ - KoZIhvcNAQkVMRYEFHlVdPnUtuTCAiQoaZhnP/AKFMBNMC0GCSqGSIb3DQEJFDEg - Hh4AcAAxADIARgByAGkAZQBuAGQAbAB5AE4AYQBtAGUwXjBOMAoGCCqFAwcBAQID - BEDp4e22JmXdnvR0xA99yQuzQuJ8pxBeOpsLm2dZQqt3Fje5zqW1uk/7VOcfV5r2 - bKm8nsLOs2rPT8hBOoeAZvOIBAjGIUHw6IjG2QICCAA= - """) - pfx = PFX().decod(pfx_raw) - _, outer_safe_contents = pfx["authSafe"]["content"].defined - - encrypted_data = EncryptedData().decod(bytes( - outer_safe_contents[0]["bagValue"] - )) - eci = encrypted_data["encryptedContentInfo"] - self.assertEqual(eci["contentEncryptionAlgorithm"]["algorithm"], id_pbes2) - pbes2_params = PBES2Params().decod(bytes( - eci["contentEncryptionAlgorithm"]["parameters"] - )) - _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined - _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined - ukm = bytes(enc_scheme_params["ukm"]) - key = gost34112012_pbkdf2( - password=self.password, - salt=bytes(pbkdf2_params["salt"]["specified"]), - iterations=int(pbkdf2_params["iterationCount"]), - dklen=32, - ) - # key = hexdec("d066a96fb326ba896a2352d3f40240a4ded6e7e7bd5b4db6b5241d631c8c381c") - key_enc, key_mac = kdf_tree_gostr3411_2012_256( - key, b"kdf tree", ukm[GOST3412Magma.blocksize // 2:], 2, - ) - ciphertext = bytes(eci["encryptedContent"]) - plaintext = ctr_acpkm( - GOST3412Magma, - GOST3412Magma(key_enc).encrypt, - section_size=8 * 1024, - bs=GOST3412Magma.blocksize, - data=ciphertext, - iv=ukm[:GOST3412Magma.blocksize // 2], - ) - mac_expected = plaintext[-GOST3412Magma.blocksize:] - plaintext = plaintext[:-GOST3412Magma.blocksize] - mac = omac( - GOST3412Magma(key_mac).encrypt, - GOST3412Magma.blocksize, - plaintext, - ) - self.assertSequenceEqual(mac, mac_expected) - - safe_contents = SafeContents().decod(plaintext) - safe_bag = safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_certBag) - cert_bag = CertBag().decod(bytes(safe_bag["bagValue"])) - self.assertEqual(cert_bag["certId"], id_pkcs9_certTypes_x509Certificate) - _, cert = cert_bag["certValue"].defined - self.assertEqual(Certificate(cert), self.cert_test) - - safe_contents = OctetStringSafeContents().decod(bytes( - outer_safe_contents[1]["bagValue"] - )) - safe_bag = safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_pkcs8ShroudedKeyBag) - shrouded_key_bag = PKCS8ShroudedKeyBag().decod(bytes(safe_bag["bagValue"])) - _, pbes2_params = shrouded_key_bag["encryptionAlgorithm"]["parameters"].defined - _, pbkdf2_params = pbes2_params["keyDerivationFunc"]["parameters"].defined - _, enc_scheme_params = pbes2_params["encryptionScheme"]["parameters"].defined - ukm = bytes(enc_scheme_params["ukm"]) - key = gost34112012_pbkdf2( - password=self.password, - salt=bytes(pbkdf2_params["salt"]["specified"]), - iterations=int(pbkdf2_params["iterationCount"]), - dklen=32, - ) - # key = hexdec("f840d001fd11441e0fb7ccf48f471915e5bf35275309dbe7ade9da4fe460ba7e") - ciphertext = bytes(shrouded_key_bag["encryptedData"]) - plaintext = ctr_acpkm( - GOST3412Magma, - GOST3412Magma(key).encrypt, - section_size=8 * 1024, - bs=GOST3412Magma.blocksize, - data=ciphertext, - iv=ukm[:GOST3412Magma.blocksize // 2], - ) - self.assertSequenceEqual(plaintext, self.prv_test_raw) - - mac_data = pfx["macData"] - mac_key = gost34112012_pbkdf2( - password=self.password, - salt=bytes(mac_data["macSalt"]), - iterations=int(mac_data["iterations"]), - dklen=96, - )[-32:] - # mac_key = hexdec("084f81782af1534ffd67e3c579c14cb45d7a6f659f46fdbb51a552e874e66fb2") - self.assertSequenceEqual( - hmac_new( - key=mac_key, - msg=SafeContents(outer_safe_contents).encode(), - digestmod=GOST34112012512, - ).digest(), - bytes(mac_data["mac"]["digest"]), - ) - - def test_dh(self): - curve = gost3410.CURVES["id-tc26-gost-3410-12-256-paramSetA"] - # sender_prv_raw = hexdec("0B20810E449978C7C3B76C6FF77A16C532421139344A058EF56310B6B6F377E8") - sender_cert = Certificate().decod(b64decode(""" - MIIB6zCCAZigAwIBAgIEAYy6gjAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 - MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw - MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA7MQ0wCwYDVQQKEwRUSzI2MSowKAYD - VQQDEyFPUklHSU5BVE9SOiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwXjAXBggqhQMH - AQEBATALBgkqhQMHAQIBAQEDQwAEQJYpDRNiWWqDgaZje0EmLLOldQ35o5X1ZuZN - SKequYQc/soI3OgDMWD7ThJJCk01IelCeb6MsBmG4lol+pnpVtOjgYcwgYQwYwYD - VR0jBFwwWoAUrGwOTERmokKW4p8JOyVm88ukUyqhPKQ6MDgxDTALBgNVBAoTBFRL - MjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdIIEAYy6 - gTAdBgNVHQ4EFgQUPx5RgcjkifhlJm4/jQdkbm30rVQwCgYIKoUDBwEBAwIDQQA6 - 8x7Vk6PvP/8xOGHhf8PuqaXAYskSyJPuBu+3Bo/PEj10devwc1J9uYWIDCGdKKPy - bSlnQHqUPBBPM30YX1YN - """)) - recipient_prv_raw = hexdec("0DC8DC1FF2BC114BABC3F1CA8C51E4F58610427E197B1C2FBDBA4AE58CBFB7CE")[::-1] - recipient_prv = gost3410.prv_unmarshal(recipient_prv_raw) - recipient_cert = Certificate().decod(b64decode(""" - MIIB6jCCAZegAwIBAgIEAYy6gzAKBggqhQMHAQEDAjA4MQ0wCwYDVQQKEwRUSzI2 - MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQwHhcNMDEw - MTAxMDAwMDAwWhcNNDkxMjMxMDAwMDAwWjA6MQ0wCwYDVQQKEwRUSzI2MSkwJwYD - VQQDEyBSRUNJUElFTlQ6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdDBeMBcGCCqFAwcB - AQEBMAsGCSqFAwcBAgEBAQNDAARAvyeCGXMsYwpYe5aE0w8w3m4vpKQapGInqpnF - lv7h08psFP0s1W80q3BR534F4TmR+o5+iU+AW6ycvWuc73JEQ6OBhzCBhDBjBgNV - HSMEXDBagBSsbA5MRGaiQpbinwk7JWbzy6RTKqE8pDowODENMAsGA1UEChMEVEsy - NjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYtYml0ggQBjLqB - MB0GA1UdDgQWBBQ35gHPN1bx8l2eEMTbrtIg+5MU0TAKBggqhQMHAQEDAgNBABF2 - RHDaRqQuBS2yu7yGIGFgA6c/LG4GKjSOwYsRVmXJNNkQ4TB7PB8j3q7gx2koPsVB - m90WfMWSL6SNSh3muuM= - """)) - self.assertTrue(gost3410.verify( - self.ca_curve, - self.ca_pub, - GOST34112012256(sender_cert["tbsCertificate"].encode()).digest()[::-1], - bytes(sender_cert["signatureValue"]), - )) - self.assertTrue(gost3410.verify( - self.ca_curve, - self.ca_pub, - GOST34112012256(recipient_cert["tbsCertificate"].encode()).digest()[::-1], - bytes(recipient_cert["signatureValue"]), - )) - - pfx_raw = b64decode(""" - MIIKVwIBAzCCClAGCSqGSIb3DQEHAqCCCkEwggo9AgEBMQwwCgYIKoUDBwEBAgIw - ggcjBgkqhkiG9w0BBwGgggcUBIIHEDCCBwwwggKdBgkqhkiG9w0BBwGgggKOBIIC - ijCCAoYwggKCBgsqhkiG9w0BDAoBA6CCAkowggJGBgoqhkiG9w0BCRYBoIICNgSC - AjIwggIuMIIB26ADAgECAgQBjLqEMAoGCCqFAwcBAQMCMDgxDTALBgNVBAoTBFRL - MjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1QgMzQuMTAtMTIgMjU2LWJpdDAeFw0w - MTAxMDEwMDAwMDBaFw00OTEyMzEwMDAwMDBaMDsxDTALBgNVBAoTBFRLMjYxKjAo - BgNVBAMTIU9SSUdJTkFUT1I6IEdPU1QgMzQuMTAtMTIgNTEyLWJpdDCBoDAXBggq - hQMHAQEBAjALBgkqhQMHAQIBAgEDgYQABIGAtIu3WrwpDhhlXGKhT7UtX1CETswd - H2AESHtLXJU0aWq3v6s0blUWqas8zvittSw6WFXwz7Nkqmtd2Tfk7PyVJb+faghQ - dnGKRcgf9JIePiu/cr8+6/PuFhNBJmX/E92nvydSaOsRrp3nB9fxuITLbPR2C58W - 8CQzDVRriB1eoM6jgYcwgYQwYwYDVR0jBFwwWoAUrGwOTERmokKW4p8JOyVm88uk - UyqhPKQ6MDgxDTALBgNVBAoTBFRLMjYxJzAlBgNVBAMTHkNBIFRLMjY6IEdPU1Qg - MzQuMTAtMTIgMjU2LWJpdIIEAYy6gTAdBgNVHQ4EFgQUfgZXCZgMrWsIqFfueQBY - OsnXoKQwCgYIKoUDBwEBAwIDQQAKXqnx0BumL0eT7eaAzIjRYiHXsiuWtKn+YHQX - tnMy3xdQPUPDPcmuufF5ed8y84DkF1Qn2ELIOAxUAaz8hwQQMSUwIwYJKoZIhvcN - AQkVMRYEFHlVdPnUtuTCAiQoaZhnP/AKFMBNMIIEZwYJKoZIhvcNAQcDoIIEWDCC - BFQCAQKgggHzoIIB7zCCAeswggGYoAMCAQICBAGMuoIwCgYIKoUDBwEBAwIwODEN - MAsGA1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAy - NTYtYml0MB4XDTAxMDEwMTAwMDAwMFoXDTQ5MTIzMTAwMDAwMFowOzENMAsGA1UE - ChMEVEsyNjEqMCgGA1UEAxMhT1JJR0lOQVRPUjogR09TVCAzNC4xMC0xMiAyNTYt - Yml0MF4wFwYIKoUDBwEBAQEwCwYJKoUDBwECAQEBA0MABECWKQ0TYllqg4GmY3tB - JiyzpXUN+aOV9WbmTUinqrmEHP7KCNzoAzFg+04SSQpNNSHpQnm+jLAZhuJaJfqZ - 6VbTo4GHMIGEMGMGA1UdIwRcMFqAFKxsDkxEZqJCluKfCTslZvPLpFMqoTykOjA4 - MQ0wCwYDVQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEy - IDI1Ni1iaXSCBAGMuoEwHQYDVR0OBBYEFD8eUYHI5In4ZSZuP40HZG5t9K1UMAoG - CCqFAwcBAQMCA0EAOvMe1ZOj7z//MThh4X/D7qmlwGLJEsiT7gbvtwaPzxI9dHXr - 8HNSfbmFiAwhnSij8m0pZ0B6lDwQTzN9GF9WDTGB/6GB/AIBA6BCMEAwODENMAsG - A1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYt - Yml0AgQBjLqCoSIEIBt4fjey+k8C1D3OaMca8wl6h3j3C6OAbrx8rmxXktsQMBcG - CSqFAwcBAQcCATAKBggqhQMHAQEGATB2MHQwQDA4MQ0wCwYDVQQKEwRUSzI2MScw - JQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1Ni1iaXQCBAGMuoMEMJkp - Wae6IVfaY3mP0izRY7ifc41fATXdJ2tmTl+1vitkSE2vLCKXDLl90KfHA6gNmDCC - AVQGCSqGSIb3DQEHATAfBgkqhQMHAQEFAgEwEgQQFhEshEBO2LkAAAAAAAAAAICC - ASQYvLpT/8azEXJfekyGuyvE9UkVX+Ao8sfu9My/c4WAVRNMhZkCqD+BbPwBsIzN - sXZIi9rXGAfsPz7xaO9EUFZPjNOWtF/E01oJgG+gYLFn7qAiEFcmRLptSHuanNHn - 7Yol6IHushX4UaW9hEa/L6eFQx/hoDhrNZnWTXNZtNuHuMGC9dzhHhTxfkdjZYXD - v+M7psVj58JutE3U2d4pgxKcBPdMO4vl4+27cIKxQZFZU2zuCVJLYLqmPT5pCBkM - mJqy7bZwHOJ9kBq/TGUf8iJGYSCNre3RTNLbcTTk7rZrbiMkFsG3borzenpouS5E - BcCkBt8Mj0nvsMCu9ipHTuWww7LltlkXCjlNXFUi6ZI3VyHW5CDpghujQWiZxiAc - JuGl6GwZoIIB7zCCAeswggGYoAMCAQICBAGMuoIwCgYIKoUDBwEBAwIwODENMAsG - A1UEChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYt - Yml0MB4XDTAxMDEwMTAwMDAwMFoXDTQ5MTIzMTAwMDAwMFowOzENMAsGA1UEChME - VEsyNjEqMCgGA1UEAxMhT1JJR0lOQVRPUjogR09TVCAzNC4xMC0xMiAyNTYtYml0 - MF4wFwYIKoUDBwEBAQEwCwYJKoUDBwECAQEBA0MABECWKQ0TYllqg4GmY3tBJiyz - pXUN+aOV9WbmTUinqrmEHP7KCNzoAzFg+04SSQpNNSHpQnm+jLAZhuJaJfqZ6VbT - o4GHMIGEMGMGA1UdIwRcMFqAFKxsDkxEZqJCluKfCTslZvPLpFMqoTykOjA4MQ0w - CwYDVQQKEwRUSzI2MScwJQYDVQQDEx5DQSBUSzI2OiBHT1NUIDM0LjEwLTEyIDI1 - Ni1iaXSCBAGMuoEwHQYDVR0OBBYEFD8eUYHI5In4ZSZuP40HZG5t9K1UMAoGCCqF - AwcBAQMCA0EAOvMe1ZOj7z//MThh4X/D7qmlwGLJEsiT7gbvtwaPzxI9dHXr8HNS - fbmFiAwhnSij8m0pZ0B6lDwQTzN9GF9WDTGCAQ4wggEKAgEBMEAwODENMAsGA1UE - ChMEVEsyNjEnMCUGA1UEAxMeQ0EgVEsyNjogR09TVCAzNC4xMC0xMiAyNTYtYml0 - AgQBjLqCMAoGCCqFAwcBAQICoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc - BgkqhkiG9w0BCQUxDxcNMjEwNDE0MTkyMTEyWjAvBgkqhkiG9w0BCQQxIgQg1XOA - zNa710QuXsn5+yIf3cNTiFOQMgTiBRJBz8Tr4I0wCgYIKoUDBwEBAQEEQALINal9 - 7wHXYiG+w0yzSkKOs0jRZew0S73r/cfk/sUoM3HKKIEbKruvlAdiOqX/HLFSEx/s - kxFG6QUFH8uuoX8= - """) - pfx = PFX().decod(pfx_raw) - self.assertEqual(pfx["authSafe"]["contentType"], id_signedData) - - sd = SignedData().decod(bytes(pfx["authSafe"]["content"])) - self.assertEqual(sd["certificates"][0]["certificate"], sender_cert) - si = sd["signerInfos"][0] - self.assertEqual( - si["digestAlgorithm"]["algorithm"], - id_tc26_gost3411_2012_256, - ) - digest = [ - bytes(attr["attrValues"][0].defined[1]) for attr in si["signedAttrs"] - if attr["attrType"] == id_messageDigest - ][0] - sender_pub = gost3410.pub_unmarshal(bytes(OctetString().decod(bytes( - sender_cert["tbsCertificate"]["subjectPublicKeyInfo"]["subjectPublicKey"] - )))) - content = bytes(sd["encapContentInfo"]["eContent"]) - self.assertSequenceEqual(digest, GOST34112012256(content).digest()) - self.assertTrue(gost3410.verify( - curve, - sender_pub, - GOST34112012256( - SignedAttributes(si["signedAttrs"]).encode() - ).digest()[::-1], - bytes(si["signature"]), - )) - - outer_safe_contents = SafeContents().decod(content) - - safe_bag = outer_safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_data) - safe_contents = OctetStringSafeContents().decod(bytes(safe_bag["bagValue"])) - safe_bag = safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_certBag) - cert_bag = CertBag().decod(bytes(safe_bag["bagValue"])) - self.assertEqual(cert_bag["certId"], id_pkcs9_certTypes_x509Certificate) - _, cert = cert_bag["certValue"].defined - self.assertEqual(Certificate(cert), self.cert_test) - - safe_bag = outer_safe_contents[1] - self.assertEqual(safe_bag["bagId"], id_envelopedData) - ed = EnvelopedData().decod(bytes(safe_bag["bagValue"])) - kari = ed["recipientInfos"][0]["kari"] - ukm = bytes(kari["ukm"]) - self.assertEqual( - kari["keyEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_wrap_kexp15, - ) - self.assertEqual( - kari["keyEncryptionAlgorithm"]["parameters"].defined[1]["algorithm"], - id_tc26_agreement_gost3410_2012_256, - ) - kexp = bytes(kari["recipientEncryptedKeys"][0]["encryptedKey"]) - keymat = keg(curve, recipient_prv, sender_pub, ukm) - kim, kek = keymat[:KEYSIZE], keymat[KEYSIZE:] - cek = kimp15( - GOST3412Kuznechik(kek).encrypt, - GOST3412Kuznechik(kim).encrypt, - GOST3412Kuznechik.blocksize, - kexp, - ukm[24:24 + GOST3412Kuznechik.blocksize // 2], - ) - eci = ed["encryptedContentInfo"] - self.assertEqual( - eci["contentEncryptionAlgorithm"]["algorithm"], - id_gostr3412_2015_kuznyechik_ctracpkm, - ) - eci_ukm = bytes( - eci["contentEncryptionAlgorithm"]["parameters"].defined[1]["ukm"] - ) - content = ctr_acpkm( - GOST3412Kuznechik, - GOST3412Kuznechik(cek).encrypt, - 256 * 1024, - GOST3412Kuznechik.blocksize, - bytes(eci["encryptedContent"]), - eci_ukm[:GOST3412Kuznechik.blocksize // 2], - ) - - safe_contents = SafeContents().decod(content) - safe_bag = safe_contents[0] - self.assertEqual(safe_bag["bagId"], id_pkcs12_bagtypes_keyBag) - KeyBag().decod(bytes(safe_bag["bagValue"])) - self.assertSequenceEqual(bytes(safe_bag["bagValue"]), self.prv_test_raw) diff --git a/pygost-5.13/pygost/test_wrap.py b/pygost-5.13/pygost/test_wrap.py deleted file mode 100644 index 7ecf8ee..0000000 --- a/pygost-5.13/pygost/test_wrap.py +++ /dev/null @@ -1,111 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 os import urandom -from unittest import TestCase - -from pygost.gost28147 import DEFAULT_SBOX -from pygost.gost3412 import GOST3412Kuznechik -from pygost.gost3412 import GOST3412Magma -from pygost.utils import hexdec -from pygost.wrap import kexp15 -from pygost.wrap import kimp15 -from pygost.wrap import unwrap_cryptopro -from pygost.wrap import unwrap_gost -from pygost.wrap import wrap_cryptopro -from pygost.wrap import wrap_gost - - -class WrapGostTest(TestCase): - def test_symmetric(self): - for sbox in (DEFAULT_SBOX, "id-tc26-gost-28147-param-Z"): - for _ in range(1 << 8): - kek = urandom(32) - cek = urandom(32) - ukm = urandom(8) - wrapped = wrap_gost(ukm, kek, cek, sbox=sbox) - unwrapped = unwrap_gost(kek, wrapped, sbox=sbox) - self.assertSequenceEqual(unwrapped, cek) - - def test_invalid_length(self): - with self.assertRaises(ValueError): - unwrap_gost(urandom(32), urandom(41)) - with self.assertRaises(ValueError): - unwrap_gost(urandom(32), urandom(45)) - - -class WrapCryptoproTest(TestCase): - def test_symmetric(self): - for sbox in (DEFAULT_SBOX, "id-tc26-gost-28147-param-Z"): - for _ in range(1 << 8): - kek = urandom(32) - cek = urandom(32) - ukm = urandom(8) - wrapped = wrap_cryptopro(ukm, kek, cek, sbox=sbox) - unwrapped = unwrap_cryptopro(kek, wrapped, sbox=sbox) - self.assertSequenceEqual(unwrapped, cek) - - -class TestVectorKExp15(TestCase): - """Test vectors from Р 1323565.1.017-2018 - """ - key = hexdec("8899AABBCCDDEEFF0011223344556677FEDCBA98765432100123456789ABCDEF") - key_enc = hexdec("202122232425262728292A2B2C2D2E2F38393A3B3C3D3E3F3031323334353637") - key_mac = hexdec("08090A0B0C0D0E0F0001020304050607101112131415161718191A1B1C1D1E1F") - - def test_magma(self): - iv = hexdec("67BED654") - kexp = kexp15( - GOST3412Magma(self.key_enc).encrypt, - GOST3412Magma(self.key_mac).encrypt, - GOST3412Magma.blocksize, - self.key, - iv, - ) - self.assertSequenceEqual(kexp, hexdec(""" -CF D5 A1 2D 5B 81 B6 E1 E9 9C 91 6D 07 90 0C 6A -C1 27 03 FB 3A BD ED 55 56 7B F3 74 2C 89 9C 75 -5D AF E7 B4 2E 3A 8B D9 - """.replace("\n", "").replace(" ", ""))) - self.assertSequenceEqual(kimp15( - GOST3412Magma(self.key_enc).encrypt, - GOST3412Magma(self.key_mac).encrypt, - GOST3412Magma.blocksize, - kexp, - iv, - ), self.key) - - def test_kuznechik(self): - iv = hexdec("0909472DD9F26BE8") - kexp = kexp15( - GOST3412Kuznechik(self.key_enc).encrypt, - GOST3412Kuznechik(self.key_mac).encrypt, - GOST3412Kuznechik.blocksize, - self.key, - iv, - ) - self.assertSequenceEqual(kexp, hexdec(""" -E3 61 84 E8 4E 8D 73 6F F3 6C C2 E5 AE 06 5D C6 -56 B2 3C 20 F5 49 B0 2F DF F8 8E 1F 3F 30 D8 C2 -9A 53 F3 CA 55 4D BA D8 0D E1 52 B9 A4 62 5B 32 - """.replace("\n", "").replace(" ", ""))) - self.assertSequenceEqual(kimp15( - GOST3412Kuznechik(self.key_enc).encrypt, - GOST3412Kuznechik(self.key_mac).encrypt, - GOST3412Kuznechik.blocksize, - kexp, - iv, - ), self.key) diff --git a/pygost-5.13/pygost/test_x509.py b/pygost-5.13/pygost/test_x509.py deleted file mode 100644 index e9fccb7..0000000 --- a/pygost-5.13/pygost/test_x509.py +++ /dev/null @@ -1,433 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 CURVES -from pygost.gost3410 import prv_unmarshal -from pygost.gost3410 import pub_marshal -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 Any - from pyderasn import BitString - from pyderasn import Boolean - from pyderasn import GeneralizedTime - from pyderasn import Integer - from pyderasn import OctetString - from pyderasn import PrintableString - from pyderasn import UTCTime - - from pygost.asn1schemas.oids import id_at_commonName - from pygost.asn1schemas.oids import id_ce_basicConstraints - from pygost.asn1schemas.oids import id_GostR3410_2001_TestParamSet - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256 - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_256_paramSetA - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512 - from pygost.asn1schemas.oids import id_tc26_gost3410_2012_512_paramSetTest - from pygost.asn1schemas.oids import id_tc26_gost3411_2012_256 - from pygost.asn1schemas.oids import id_tc26_signwithdigest_gost3410_2012_256 - from pygost.asn1schemas.oids import id_tc26_signwithdigest_gost3410_2012_512 - from pygost.asn1schemas.pkcs10 import Attributes - from pygost.asn1schemas.pkcs10 import CertificationRequest - from pygost.asn1schemas.pkcs10 import CertificationRequestInfo - from pygost.asn1schemas.x509 import AlgorithmIdentifier - from pygost.asn1schemas.x509 import AttributeType - from pygost.asn1schemas.x509 import AttributeTypeAndValue - from pygost.asn1schemas.x509 import AttributeValue - from pygost.asn1schemas.x509 import BasicConstraints - from pygost.asn1schemas.x509 import Certificate - from pygost.asn1schemas.x509 import CertificateList - from pygost.asn1schemas.x509 import CertificateSerialNumber - from pygost.asn1schemas.x509 import Extension - from pygost.asn1schemas.x509 import Extensions - from pygost.asn1schemas.x509 import GostR34102012PublicKeyParameters - from pygost.asn1schemas.x509 import Name - from pygost.asn1schemas.x509 import RDNSequence - from pygost.asn1schemas.x509 import RelativeDistinguishedName - from pygost.asn1schemas.x509 import SubjectPublicKeyInfo - from pygost.asn1schemas.x509 import TBSCertificate - from pygost.asn1schemas.x509 import TBSCertList - from pygost.asn1schemas.x509 import Time - from pygost.asn1schemas.x509 import Validity - from pygost.asn1schemas.x509 import Version - -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, hasher, prv_key_raw, cert_raw): - cert, tail = Certificate().decode(cert_raw, ctx={ - "defines_by_path": ( - ( - ( - "tbsCertificate", - "subjectPublicKeyInfo", - "algorithm", - "algorithm", - ), - ( - ( - ("..", "subjectPublicKey"), - { - id_tc26_gost3410_2012_256: OctetString(), - id_tc26_gost3410_2012_512: OctetString(), - }, - ), - ), - ), - ), - }) - self.assertSequenceEqual(tail, b"") - curve = CURVES[curve_name] - prv_key = prv_unmarshal(prv_key_raw) - spk = cert["tbsCertificate"]["subjectPublicKeyInfo"]["subjectPublicKey"] - self.assertIsNotNone(spk.defined) - _, pub_key_raw = spk.defined - pub_key = pub_unmarshal(bytes(pub_key_raw)) - self.assertSequenceEqual(pub_key, public_key(curve, prv_key)) - self.assertTrue(verify( - curve, - pub_key, - hasher(cert["tbsCertificate"].encode()).digest()[::-1], - bytes(cert["signatureValue"]), - )) - - 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( - "id-GostR3410-2001-CryptoPro-XchA-ParamSet", - 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( - "id-tc26-gost-3410-12-512-paramSetB", - GOST34112012512, - prv_key_raw, - cert_raw, - ) - - -@skipIf(not pyderasn_exists, "PyDERASN dependency is required") -class TestRFC4491bis(TestCase): - """Test vectors from https://tools.ietf.org/html/draft-deremin-rfc4491-bis-02 - """ - - def _test_vector( - self, - curve_name, - hsh, - ai_spki, - ai_sign, - cert_serial, - prv_hex, - cr_sign_hex, - cr_b64, - c_sign_hex, - c_b64, - crl_sign_hex, - crl_b64, - ): - prv_raw = hexdec(prv_hex)[::-1] - prv = prv_unmarshal(prv_raw) - curve = CURVES[curve_name] - pub = public_key(curve, prv) - pub_raw = pub_marshal(pub) - subj = Name(("rdnSequence", RDNSequence([ - RelativeDistinguishedName(( - AttributeTypeAndValue(( - ("type", AttributeType(id_at_commonName)), - ("value", AttributeValue(PrintableString("Example"))), - )), - )) - ]))) - spki = SubjectPublicKeyInfo(( - ("algorithm", ai_spki), - ("subjectPublicKey", BitString(OctetString(pub_raw).encode())), - )) - - # Certification request - cri = CertificationRequestInfo(( - ("version", Integer(0)), - ("subject", subj), - ("subjectPKInfo", spki), - ("attributes", Attributes()), - )) - sign = hexdec(cr_sign_hex) - self.assertTrue(verify( - curve, - pub, - hsh(cri.encode()).digest()[::-1], - sign, - )) - cr = CertificationRequest(( - ("certificationRequestInfo", cri), - ("signatureAlgorithm", ai_sign), - ("signature", BitString(sign)), - )) - self.assertSequenceEqual(cr.encode(), b64decode(cr_b64)) - - # Certificate - tbs = TBSCertificate(( - ("version", Version("v3")), - ("serialNumber", CertificateSerialNumber(cert_serial)), - ("signature", ai_sign), - ("issuer", subj), - ("validity", Validity(( - ("notBefore", Time(("utcTime", UTCTime(b"010101000000Z")))), - ("notAfter", Time(("generalTime", GeneralizedTime(b"20501231000000Z")))), - ))), - ("subject", subj), - ("subjectPublicKeyInfo", spki), - ("extensions", Extensions(( - Extension(( - ("extnID", id_ce_basicConstraints), - ("critical", Boolean(True)), - ("extnValue", OctetString( - BasicConstraints((("cA", Boolean(True)),)).encode() - )), - )), - ))), - )) - sign = hexdec(c_sign_hex) - self.assertTrue(verify( - curve, - pub, - hsh(tbs.encode()).digest()[::-1], - sign, - )) - cert = Certificate(( - ("tbsCertificate", tbs), - ("signatureAlgorithm", ai_sign), - ("signatureValue", BitString(sign)), - )) - self.assertSequenceEqual(cert.encode(), b64decode(c_b64)) - - # CRL - tbs = TBSCertList(( - ("version", Version("v2")), - ("signature", ai_sign), - ("issuer", subj), - ("thisUpdate", Time(("utcTime", UTCTime(b"140101000000Z")))), - ("nextUpdate", Time(("utcTime", UTCTime(b"140102000000Z")))), - )) - sign = hexdec(crl_sign_hex) - self.assertTrue(verify( - curve, - pub, - hsh(tbs.encode()).digest()[::-1], - sign, - )) - crl = CertificateList(( - ("tbsCertList", tbs), - ("signatureAlgorithm", ai_sign), - ("signatureValue", BitString(sign)), - )) - self.assertSequenceEqual(crl.encode(), b64decode(crl_b64)) - - def test_256_test_paramset(self): - self._test_vector( - "id-GostR3410-2001-TestParamSet", - GOST34112012256, - AlgorithmIdentifier(( - ("algorithm", id_tc26_gost3410_2012_256), - ("parameters", Any( - GostR34102012PublicKeyParameters(( - ("publicKeyParamSet", id_GostR3410_2001_TestParamSet), - ("digestParamSet", id_tc26_gost3411_2012_256), - )) - )), - )), - AlgorithmIdentifier(( - ("algorithm", id_tc26_signwithdigest_gost3410_2012_256), - )), - 10, - "7A929ADE789BB9BE10ED359DD39A72C11B60961F49397EEE1D19CE9891EC3B28", - "6AAAB38E35D4AAA517940301799122D855484F579F4CBB96D63CDFDF3ACC432A41AA28D2F1AB148280CD9ED56FEDA41974053554A42767B83AD043FD39DC0493", - """ -MIHTMIGBAgEAMBIxEDAOBgNVBAMTB0V4YW1wbGUwZjAfBggqhQMHAQEBATATBgcq -hQMCAiMABggqhQMHAQECAgNDAARAC9hv5djbiWaPeJtOHbqFhcVQi0XsW1nYkG3b -cOJJK3/ad/+HGhD73ydm0pPF0WSvuzx7lzpByIXRHXDWibTxJqAAMAoGCCqFAwcB -AQMCA0EAaqqzjjXUqqUXlAMBeZEi2FVIT1efTLuW1jzf3zrMQypBqijS8asUgoDN -ntVv7aQZdAU1VKQnZ7g60EP9OdwEkw== - """, - "4D53F012FE081776507D4D9BB81F00EFDB4EEFD4AB83BAC4BACF735173CFA81C41AA28D2F1AB148280CD9ED56FEDA41974053554A42767B83AD043FD39DC0493", - """ -MIIBLTCB26ADAgECAgEKMAoGCCqFAwcBAQMCMBIxEDAOBgNVBAMTB0V4YW1wbGUw -IBcNMDEwMTAxMDAwMDAwWhgPMjA1MDEyMzEwMDAwMDBaMBIxEDAOBgNVBAMTB0V4 -YW1wbGUwZjAfBggqhQMHAQEBATATBgcqhQMCAiMABggqhQMHAQECAgNDAARAC9hv -5djbiWaPeJtOHbqFhcVQi0XsW1nYkG3bcOJJK3/ad/+HGhD73ydm0pPF0WSvuzx7 -lzpByIXRHXDWibTxJqMTMBEwDwYDVR0TAQH/BAUwAwEB/zAKBggqhQMHAQEDAgNB -AE1T8BL+CBd2UH1Nm7gfAO/bTu/Uq4O6xLrPc1Fzz6gcQaoo0vGrFIKAzZ7Vb+2k -GXQFNVSkJ2e4OtBD/TncBJM= - """, - "42BF392A14D3EBE957AF3E46CB50BF5F4221A003AD3D172753C94A9C37A31D2041AA28D2F1AB148280CD9ED56FEDA41974053554A42767B83AD043FD39DC0493", - """ -MIGSMEECAQEwCgYIKoUDBwEBAwIwEjEQMA4GA1UEAxMHRXhhbXBsZRcNMTQwMTAx -MDAwMDAwWhcNMTQwMTAyMDAwMDAwWjAKBggqhQMHAQEDAgNBAEK/OSoU0+vpV68+ -RstQv19CIaADrT0XJ1PJSpw3ox0gQaoo0vGrFIKAzZ7Vb+2kGXQFNVSkJ2e4OtBD -/TncBJM= - """, - ) - - def test_256a_paramset(self): - self._test_vector( - "id-tc26-gost-3410-2012-256-paramSetA", - GOST34112012256, - AlgorithmIdentifier(( - ("algorithm", id_tc26_gost3410_2012_256), - ("parameters", Any( - GostR34102012PublicKeyParameters(( - ("publicKeyParamSet", id_tc26_gost3410_2012_256_paramSetA), - )) - )), - )), - AlgorithmIdentifier(( - ("algorithm", id_tc26_signwithdigest_gost3410_2012_256), - )), - 10, - "7A929ADE789BB9BE10ED359DD39A72C11B60961F49397EEE1D19CE9891EC3B28", - "1BDC2A1317679B66232F63EA16FF7C64CCAAB9AD855FC6E18091661DB79D48121D0E1DA5BE347C6F1B5256C7AEAC200AD64AC77A6F5B3A0E097318E7AE6EE769", - """ -MIHKMHkCAQAwEjEQMA4GA1UEAxMHRXhhbXBsZTBeMBcGCCqFAwcBAQEBMAsGCSqF -AwcBAgEBAQNDAARAdCeV1L7ohN3yhQ/sA+o/rxhE4B2dpgtkUJOlXibfw5l49ZbP -TU0MbPHRiUPZRJPRa57AoW1RLS4SfMRpGmMY4qAAMAoGCCqFAwcBAQMCA0EAG9wq -Exdnm2YjL2PqFv98ZMyqua2FX8bhgJFmHbedSBIdDh2lvjR8bxtSVseurCAK1krH -em9bOg4Jcxjnrm7naQ== - """, - "140B4DA9124B09CB0D5CE928EE874273A310129492EC0E29369E3B791248578C1D0E1DA5BE347C6F1B5256C7AEAC200AD64AC77A6F5B3A0E097318E7AE6EE769", - """ -MIIBJTCB06ADAgECAgEKMAoGCCqFAwcBAQMCMBIxEDAOBgNVBAMTB0V4YW1wbGUw -IBcNMDEwMTAxMDAwMDAwWhgPMjA1MDEyMzEwMDAwMDBaMBIxEDAOBgNVBAMTB0V4 -YW1wbGUwXjAXBggqhQMHAQEBATALBgkqhQMHAQIBAQEDQwAEQHQnldS+6ITd8oUP -7APqP68YROAdnaYLZFCTpV4m38OZePWWz01NDGzx0YlD2UST0WuewKFtUS0uEnzE -aRpjGOKjEzARMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoUDBwEBAwIDQQAUC02pEksJ -yw1c6Sjuh0JzoxASlJLsDik2njt5EkhXjB0OHaW+NHxvG1JWx66sIArWSsd6b1s6 -DglzGOeubudp - """, - "14BD68087C3B903C7AA28B07FEB2E7BD6FE0963F563267359F5CD8EAB45059AD1D0E1DA5BE347C6F1B5256C7AEAC200AD64AC77A6F5B3A0E097318E7AE6EE769", - """ -MIGSMEECAQEwCgYIKoUDBwEBAwIwEjEQMA4GA1UEAxMHRXhhbXBsZRcNMTQwMTAx -MDAwMDAwWhcNMTQwMTAyMDAwMDAwWjAKBggqhQMHAQEDAgNBABS9aAh8O5A8eqKL -B/6y571v4JY/VjJnNZ9c2Oq0UFmtHQ4dpb40fG8bUlbHrqwgCtZKx3pvWzoOCXMY -565u52k= - """, - ) - - def test_512_test_paramset(self): - self._test_vector( - "id-tc26-gost-3410-2012-512-paramSetTest", - GOST34112012512, - AlgorithmIdentifier(( - ("algorithm", id_tc26_gost3410_2012_512), - ("parameters", Any( - GostR34102012PublicKeyParameters(( - ("publicKeyParamSet", id_tc26_gost3410_2012_512_paramSetTest), - )) - )), - )), - AlgorithmIdentifier(( - ("algorithm", id_tc26_signwithdigest_gost3410_2012_512), - )), - 11, - "0BA6048AADAE241BA40936D47756D7C93091A0E8514669700EE7508E508B102072E8123B2200A0563322DAD2827E2714A2636B7BFD18AADFC62967821FA18DD4", - "433B1D6CE40A51F1E5737EB16AA2C683829A405B9D9127E21260FC9D6AC05D87BF24E26C45278A5C2192A75BA94993ABD6074E7FF1BF03FD2F5397AFA1D945582F86FA60A081091A23DD795E1E3C689EE512A3C82EE0DCC2643C78EEA8FCACD35492558486B20F1C9EC197C90699850260C93BCBCD9C5C3317E19344E173AE36", - """ -MIIBTzCBvAIBADASMRAwDgYDVQQDEwdFeGFtcGxlMIGgMBcGCCqFAwcBAQECMAsG -CSqFAwcBAgECAAOBhAAEgYDh7zDVLGEz3dmdHVxBRVz3302LTJJbvGmvFDPRVlhR -Wt0hRoUMMlxbgcEzvmVaqMTUQOe5io1ZSHsMdpa8xV0R7L53NqnsNX/y/TmTH04R -TLjNo1knCsfw5/9D2UGUGeph/Sq3f12fY1I9O1CgT2PioM9Rt8E63CFWDwvUDMnH -N6AAMAoGCCqFAwcBAQMDA4GBAEM7HWzkClHx5XN+sWqixoOCmkBbnZEn4hJg/J1q -wF2HvyTibEUnilwhkqdbqUmTq9YHTn/xvwP9L1OXr6HZRVgvhvpgoIEJGiPdeV4e -PGie5RKjyC7g3MJkPHjuqPys01SSVYSGsg8cnsGXyQaZhQJgyTvLzZxcMxfhk0Th -c642 - """, - "415703D892F1A5F3F68C4353189A7EE207B80B5631EF9D49529A4D6B542C2CFA15AA2EACF11F470FDE7D954856903C35FD8F955EF300D95C77534A724A0EEE702F86FA60A081091A23DD795E1E3C689EE512A3C82EE0DCC2643C78EEA8FCACD35492558486B20F1C9EC197C90699850260C93BCBCD9C5C3317E19344E173AE36", - """ -MIIBqjCCARagAwIBAgIBCzAKBggqhQMHAQEDAzASMRAwDgYDVQQDEwdFeGFtcGxl -MCAXDTAxMDEwMTAwMDAwMFoYDzIwNTAxMjMxMDAwMDAwWjASMRAwDgYDVQQDEwdF -eGFtcGxlMIGgMBcGCCqFAwcBAQECMAsGCSqFAwcBAgECAAOBhAAEgYDh7zDVLGEz -3dmdHVxBRVz3302LTJJbvGmvFDPRVlhRWt0hRoUMMlxbgcEzvmVaqMTUQOe5io1Z -SHsMdpa8xV0R7L53NqnsNX/y/TmTH04RTLjNo1knCsfw5/9D2UGUGeph/Sq3f12f -Y1I9O1CgT2PioM9Rt8E63CFWDwvUDMnHN6MTMBEwDwYDVR0TAQH/BAUwAwEB/zAK -BggqhQMHAQEDAwOBgQBBVwPYkvGl8/aMQ1MYmn7iB7gLVjHvnUlSmk1rVCws+hWq -LqzxH0cP3n2VSFaQPDX9j5Ve8wDZXHdTSnJKDu5wL4b6YKCBCRoj3XleHjxonuUS -o8gu4NzCZDx47qj8rNNUklWEhrIPHJ7Bl8kGmYUCYMk7y82cXDMX4ZNE4XOuNg== - """, - "3A13FB7AECDB5560EEF6137CFC5DD64691732EBFB3690A1FC0C7E8A4EEEA08307D648D4DC0986C46A87B3FBE4C7AF42EA34359C795954CA39FF3ABBED9051F4D2F86FA60A081091A23DD795E1E3C689EE512A3C82EE0DCC2643C78EEA8FCACD35492558486B20F1C9EC197C90699850260C93BCBCD9C5C3317E19344E173AE36", - """ -MIHTMEECAQEwCgYIKoUDBwEBAwMwEjEQMA4GA1UEAxMHRXhhbXBsZRcNMTQwMTAx -MDAwMDAwWhcNMTQwMTAyMDAwMDAwWjAKBggqhQMHAQEDAwOBgQA6E/t67NtVYO72 -E3z8XdZGkXMuv7NpCh/Ax+ik7uoIMH1kjU3AmGxGqHs/vkx69C6jQ1nHlZVMo5/z -q77ZBR9NL4b6YKCBCRoj3XleHjxonuUSo8gu4NzCZDx47qj8rNNUklWEhrIPHJ7B -l8kGmYUCYMk7y82cXDMX4ZNE4XOuNg== - """, - ) diff --git a/pygost-5.13/pygost/utils.py b/pygost-5.13/pygost/utils.py deleted file mode 100644 index 21c31b0..0000000 --- a/pygost-5.13/pygost/utils.py +++ /dev/null @@ -1,101 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 codecs import getdecoder -from codecs import getencoder -from sys import version_info - - -xrange = range if version_info[0] == 3 else xrange - - -def strxor(a, b): - """XOR of two strings - - This function will process only shortest length of both strings, - ignoring remaining one. - """ - mlen = min(len(a), len(b)) - a, b, xor = bytearray(a), bytearray(b), bytearray(mlen) - for i in xrange(mlen): - xor[i] = a[i] ^ b[i] - return bytes(xor) - - -_hexdecoder = getdecoder("hex") -_hexencoder = getencoder("hex") - - -def hexdec(data): - """Decode hexadecimal - """ - return _hexdecoder(data)[0] - - -def hexenc(data): - """Encode hexadecimal - """ - return _hexencoder(data)[0].decode("ascii") - - -def bytes2long(raw): - """Deserialize big-endian bytes into long number - - :param bytes raw: binary string - :returns: deserialized long number - :rtype: int - """ - return int(hexenc(raw), 16) - - -def long2bytes(n, size=32): - """Serialize long number into big-endian bytestring - - :param long n: long number - :returns: serialized bytestring - :rtype: bytes - """ - res = hex(int(n))[2:].rstrip("L") - if len(res) % 2 != 0: - res = "0" + res - s = hexdec(res) - if len(s) != size: - s = (size - len(s)) * b"\x00" + s - return s - - -def modinvert(a, n): - """Modular multiplicative inverse - - :returns: inverse number. -1 if it does not exist - - Realization is taken from: - https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm - """ - if a < 0: - # k^-1 = p - (-k)^-1 mod p - return n - modinvert(-a, n) - t, newt = 0, 1 - r, newr = n, a - while newr != 0: - quotinent = r // newr - t, newt = newt, t - quotinent * newt - r, newr = newr, r - quotinent * newr - if r > 1: - return -1 - if t < 0: - t = t + n - return t diff --git a/pygost-5.13/pygost/wrap.py b/pygost-5.13/pygost/wrap.py deleted file mode 100644 index 3fa49e7..0000000 --- a/pygost-5.13/pygost/wrap.py +++ /dev/null @@ -1,152 +0,0 @@ -# coding: utf-8 -# PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2023 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, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# 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 . -"""Key wrap. - -:rfc:`4357` key wrapping (28147-89 and CryptoPro). -""" - -from hmac import compare_digest -from struct import pack -from struct import unpack - -from pygost.gost28147 import cfb_encrypt -from pygost.gost28147 import DEFAULT_SBOX -from pygost.gost28147 import ecb_decrypt -from pygost.gost28147 import ecb_encrypt -from pygost.gost28147_mac import MAC -from pygost.gost3413 import ctr -from pygost.gost3413 import mac - - -def wrap_gost(ukm, kek, cek, sbox=DEFAULT_SBOX): - """28147-89 key wrapping - - :param ukm: UKM - :type ukm: bytes, 8 bytes - :param kek: key encryption key - :type kek: bytes, 32 bytes - :param cek: content encryption key - :type cek: bytes, 32 bytes - :returns: wrapped key - :rtype: bytes, 44 bytes - """ - cek_mac = MAC(kek, data=cek, iv=ukm, sbox=sbox).digest()[:4] - cek_enc = ecb_encrypt(kek, cek, sbox=sbox) - return ukm + cek_enc + cek_mac - - -def unwrap_gost(kek, data, sbox=DEFAULT_SBOX): - """28147-89 key unwrapping - - :param kek: key encryption key - :type kek: bytes, 32 bytes - :param data: wrapped key - :type data: bytes, 44 bytes - :returns: unwrapped CEK - :rtype: 32 bytes - """ - if len(data) != 44: - raise ValueError("Invalid data length") - ukm, cek_enc, cek_mac = data[:8], data[8:8 + 32], data[-4:] - cek = ecb_decrypt(kek, cek_enc, sbox=sbox) - if MAC(kek, data=cek, iv=ukm, sbox=sbox).digest()[:4] != cek_mac: - raise ValueError("Invalid MAC") - return cek - - -def wrap_cryptopro(ukm, kek, cek, sbox=DEFAULT_SBOX): - """CryptoPro key wrapping - - :param ukm: UKM - :type ukm: bytes, 8 bytes - :param kek: key encryption key - :type kek: bytes, 32 bytes - :param cek: content encryption key - :type cek: bytes, 32 bytes - :returns: wrapped key - :rtype: bytes, 44 bytes - """ - return wrap_gost( - ukm, - diversify(kek, bytearray(ukm), sbox=sbox), - cek, - sbox=sbox, - ) - - -def unwrap_cryptopro(kek, data, sbox=DEFAULT_SBOX): - """CryptoPro key unwrapping - - :param kek: key encryption key - :type kek: bytes, 32 bytes - :param data: wrapped key - :type data: bytes, 44 bytes - :returns: unwrapped CEK - :rtype: 32 bytes - """ - if len(data) < 8: - raise ValueError("Invalid data length") - return unwrap_gost( - diversify(kek, bytearray(data[:8]), sbox=sbox), - data, - sbox=sbox, - ) - - -def diversify(kek, ukm, sbox=DEFAULT_SBOX): - out = kek - for i in range(8): - s1, s2 = 0, 0 - for j in range(8): - k, = unpack("> j) & 1: - s1 += k - else: - s2 += k - iv = pack("