From a440d30b1202b9767f02c7a16ff851d23b7ae3e2 Mon Sep 17 00:00:00 2001 From: Ivan Vazhenin Date: Wed, 30 Aug 2023 19:10:53 +0300 Subject: [PATCH] Add pygost deps --- deps/pygost-5.12/AUTHORS | 1 + deps/pygost-5.12/COPYING | 674 +++++++++++ deps/pygost-5.12/FAQ | 24 + deps/pygost-5.12/INSTALL | 64 + deps/pygost-5.12/MANIFEST.in | 8 + deps/pygost-5.12/NEWS | 264 ++++ deps/pygost-5.12/PKG-INFO | 96 ++ deps/pygost-5.12/README | 73 ++ deps/pygost-5.12/THANKS | 8 + deps/pygost-5.12/VERSION | 1 + deps/pygost-5.12/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.12/build/lib/pygost/gost28147.py | 457 +++++++ .../build/lib/pygost/gost28147_mac.py | 99 ++ deps/pygost-5.12/build/lib/pygost/gost3410.py | 404 ++++++ .../build/lib/pygost/gost3410_vko.py | 95 ++ .../build/lib/pygost/gost34112012.py | 299 +++++ .../build/lib/pygost/gost34112012256.py | 16 + .../build/lib/pygost/gost34112012512.py | 21 + .../build/lib/pygost/gost341194.py | 192 +++ deps/pygost-5.12/build/lib/pygost/gost3412.py | 186 +++ deps/pygost-5.12/build/lib/pygost/gost3413.py | 392 ++++++ deps/pygost-5.12/build/lib/pygost/iface.py | 50 + deps/pygost-5.12/build/lib/pygost/kdf.py | 81 ++ deps/pygost-5.12/build/lib/pygost/mgm.py | 168 +++ deps/pygost-5.12/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 + deps/pygost-5.12/build/lib/pygost/test_cms.py | 1078 +++++++++++++++++ .../build/lib/pygost/test_gost28147.py | 384 ++++++ .../build/lib/pygost/test_gost28147_mac.py | 63 + .../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 +++ .../build/lib/pygost/test_gost3412.py | 137 +++ .../build/lib/pygost/test_gost3413.py | 766 ++++++++++++ deps/pygost-5.12/build/lib/pygost/test_kdf.py | 58 + deps/pygost-5.12/build/lib/pygost/test_mgm.py | 75 ++ deps/pygost-5.12/build/lib/pygost/test_pfx.py | 680 +++++++++++ .../pygost-5.12/build/lib/pygost/test_wrap.py | 111 ++ .../pygost-5.12/build/lib/pygost/test_x509.py | 433 +++++++ deps/pygost-5.12/build/lib/pygost/utils.py | 101 ++ deps/pygost-5.12/build/lib/pygost/wrap.py | 152 +++ deps/pygost-5.12/dist/pygost-5.12-py3.11.egg | Bin 0 -> 328355 bytes deps/pygost-5.12/pygost.egg-info/PKG-INFO | 93 ++ deps/pygost-5.12/pygost.egg-info/SOURCES.txt | 71 ++ .../pygost.egg-info/dependency_links.txt | 1 + .../pygost-5.12/pygost.egg-info/top_level.txt | 1 + deps/pygost-5.12/pygost/__init__.py | 6 + .../pygost/asn1schemas/__init__.py | 0 .../pygost/asn1schemas/cert-dane-hash.py | 18 + .../asn1schemas/cert-selfsigned-example.py | 348 ++++++ deps/pygost-5.12/pygost/asn1schemas/cms.py | 431 +++++++ deps/pygost-5.12/pygost/asn1schemas/oids.py | 60 + deps/pygost-5.12/pygost/asn1schemas/pfx.py | 250 ++++ deps/pygost-5.12/pygost/asn1schemas/pkcs10.py | 49 + deps/pygost-5.12/pygost/asn1schemas/prvkey.py | 100 ++ deps/pygost-5.12/pygost/asn1schemas/x509.py | 262 ++++ deps/pygost-5.12/pygost/gost28147.py | 457 +++++++ deps/pygost-5.12/pygost/gost28147_mac.py | 99 ++ deps/pygost-5.12/pygost/gost3410.py | 404 ++++++ deps/pygost-5.12/pygost/gost3410_vko.py | 95 ++ deps/pygost-5.12/pygost/gost34112012.py | 299 +++++ deps/pygost-5.12/pygost/gost34112012256.py | 16 + deps/pygost-5.12/pygost/gost34112012512.py | 21 + deps/pygost-5.12/pygost/gost341194.py | 192 +++ deps/pygost-5.12/pygost/gost3412.py | 186 +++ deps/pygost-5.12/pygost/gost3413.py | 392 ++++++ deps/pygost-5.12/pygost/iface.py | 50 + deps/pygost-5.12/pygost/kdf.py | 81 ++ deps/pygost-5.12/pygost/mgm.py | 168 +++ deps/pygost-5.12/pygost/pbkdf2.py | 41 + .../pygost/stubs/pygost/__init__.pyi | 0 .../pygost/stubs/pygost/gost28147.pyi | 101 ++ .../pygost/stubs/pygost/gost28147_mac.pyi | 25 + .../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/stubs/pygost/gost3412.pyi | 18 + .../pygost/stubs/pygost/gost3413.pyi | 81 ++ .../pygost-5.12/pygost/stubs/pygost/iface.pyi | 19 + deps/pygost-5.12/pygost/stubs/pygost/kdf.pyi | 22 + deps/pygost-5.12/pygost/stubs/pygost/mgm.pyi | 17 + .../pygost-5.12/pygost/stubs/pygost/utils.pyi | 19 + deps/pygost-5.12/pygost/stubs/pygost/wrap.pyi | 31 + deps/pygost-5.12/pygost/test_cms.py | 1078 +++++++++++++++++ deps/pygost-5.12/pygost/test_gost28147.py | 384 ++++++ deps/pygost-5.12/pygost/test_gost28147_mac.py | 63 + deps/pygost-5.12/pygost/test_gost3410.py | 495 ++++++++ deps/pygost-5.12/pygost/test_gost3410_vko.py | 125 ++ deps/pygost-5.12/pygost/test_gost34112012.py | 159 +++ deps/pygost-5.12/pygost/test_gost341194.py | 200 +++ deps/pygost-5.12/pygost/test_gost3412.py | 137 +++ deps/pygost-5.12/pygost/test_gost3413.py | 766 ++++++++++++ deps/pygost-5.12/pygost/test_kdf.py | 58 + deps/pygost-5.12/pygost/test_mgm.py | 75 ++ deps/pygost-5.12/pygost/test_pfx.py | 680 +++++++++++ deps/pygost-5.12/pygost/test_wrap.py | 111 ++ deps/pygost-5.12/pygost/test_x509.py | 433 +++++++ deps/pygost-5.12/pygost/utils.py | 101 ++ deps/pygost-5.12/pygost/wrap.py | 152 +++ deps/pygost-5.12/setup.cfg | 4 + deps/pygost-5.12/setup.py | 42 + 129 files changed, 20529 insertions(+) create mode 100644 deps/pygost-5.12/AUTHORS create mode 100644 deps/pygost-5.12/COPYING create mode 100644 deps/pygost-5.12/FAQ create mode 100644 deps/pygost-5.12/INSTALL create mode 100644 deps/pygost-5.12/MANIFEST.in create mode 100644 deps/pygost-5.12/NEWS create mode 100644 deps/pygost-5.12/PKG-INFO create mode 100644 deps/pygost-5.12/README create mode 100644 deps/pygost-5.12/THANKS create mode 100644 deps/pygost-5.12/VERSION create mode 100644 deps/pygost-5.12/build/lib/pygost/__init__.py create mode 100644 deps/pygost-5.12/build/lib/pygost/asn1schemas/__init__.py create mode 100644 deps/pygost-5.12/build/lib/pygost/asn1schemas/cert-dane-hash.py create mode 100644 deps/pygost-5.12/build/lib/pygost/asn1schemas/cert-selfsigned-example.py create mode 100644 deps/pygost-5.12/build/lib/pygost/asn1schemas/cms.py create mode 100644 deps/pygost-5.12/build/lib/pygost/asn1schemas/oids.py create mode 100644 deps/pygost-5.12/build/lib/pygost/asn1schemas/pfx.py create mode 100644 deps/pygost-5.12/build/lib/pygost/asn1schemas/pkcs10.py create mode 100644 deps/pygost-5.12/build/lib/pygost/asn1schemas/prvkey.py create mode 100644 deps/pygost-5.12/build/lib/pygost/asn1schemas/x509.py create mode 100644 deps/pygost-5.12/build/lib/pygost/gost28147.py create mode 100644 deps/pygost-5.12/build/lib/pygost/gost28147_mac.py create mode 100644 deps/pygost-5.12/build/lib/pygost/gost3410.py create mode 100644 deps/pygost-5.12/build/lib/pygost/gost3410_vko.py create mode 100644 deps/pygost-5.12/build/lib/pygost/gost34112012.py create mode 100644 deps/pygost-5.12/build/lib/pygost/gost34112012256.py create mode 100644 deps/pygost-5.12/build/lib/pygost/gost34112012512.py create mode 100644 deps/pygost-5.12/build/lib/pygost/gost341194.py create mode 100644 deps/pygost-5.12/build/lib/pygost/gost3412.py create mode 100644 deps/pygost-5.12/build/lib/pygost/gost3413.py create mode 100644 deps/pygost-5.12/build/lib/pygost/iface.py create mode 100644 deps/pygost-5.12/build/lib/pygost/kdf.py create mode 100644 deps/pygost-5.12/build/lib/pygost/mgm.py create mode 100644 deps/pygost-5.12/build/lib/pygost/pbkdf2.py create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/__init__.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost28147.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost28147_mac.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3410.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3410_vko.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost34112012.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost34112012256.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost34112012512.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost341194.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3412.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3413.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/iface.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/kdf.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/mgm.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/utils.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/stubs/pygost/wrap.pyi create mode 100644 deps/pygost-5.12/build/lib/pygost/test_cms.py create mode 100644 deps/pygost-5.12/build/lib/pygost/test_gost28147.py create mode 100644 deps/pygost-5.12/build/lib/pygost/test_gost28147_mac.py create mode 100644 deps/pygost-5.12/build/lib/pygost/test_gost3410.py create mode 100644 deps/pygost-5.12/build/lib/pygost/test_gost3410_vko.py create mode 100644 deps/pygost-5.12/build/lib/pygost/test_gost34112012.py create mode 100644 deps/pygost-5.12/build/lib/pygost/test_gost341194.py create mode 100644 deps/pygost-5.12/build/lib/pygost/test_gost3412.py create mode 100644 deps/pygost-5.12/build/lib/pygost/test_gost3413.py create mode 100644 deps/pygost-5.12/build/lib/pygost/test_kdf.py create mode 100644 deps/pygost-5.12/build/lib/pygost/test_mgm.py create mode 100644 deps/pygost-5.12/build/lib/pygost/test_pfx.py create mode 100644 deps/pygost-5.12/build/lib/pygost/test_wrap.py create mode 100644 deps/pygost-5.12/build/lib/pygost/test_x509.py create mode 100644 deps/pygost-5.12/build/lib/pygost/utils.py create mode 100644 deps/pygost-5.12/build/lib/pygost/wrap.py create mode 100644 deps/pygost-5.12/dist/pygost-5.12-py3.11.egg create mode 100644 deps/pygost-5.12/pygost.egg-info/PKG-INFO create mode 100644 deps/pygost-5.12/pygost.egg-info/SOURCES.txt create mode 100644 deps/pygost-5.12/pygost.egg-info/dependency_links.txt create mode 100644 deps/pygost-5.12/pygost.egg-info/top_level.txt create mode 100644 deps/pygost-5.12/pygost/__init__.py create mode 100644 deps/pygost-5.12/pygost/asn1schemas/__init__.py create mode 100755 deps/pygost-5.12/pygost/asn1schemas/cert-dane-hash.py create mode 100755 deps/pygost-5.12/pygost/asn1schemas/cert-selfsigned-example.py create mode 100644 deps/pygost-5.12/pygost/asn1schemas/cms.py create mode 100644 deps/pygost-5.12/pygost/asn1schemas/oids.py create mode 100644 deps/pygost-5.12/pygost/asn1schemas/pfx.py create mode 100644 deps/pygost-5.12/pygost/asn1schemas/pkcs10.py create mode 100644 deps/pygost-5.12/pygost/asn1schemas/prvkey.py create mode 100644 deps/pygost-5.12/pygost/asn1schemas/x509.py create mode 100644 deps/pygost-5.12/pygost/gost28147.py create mode 100644 deps/pygost-5.12/pygost/gost28147_mac.py create mode 100644 deps/pygost-5.12/pygost/gost3410.py create mode 100644 deps/pygost-5.12/pygost/gost3410_vko.py create mode 100644 deps/pygost-5.12/pygost/gost34112012.py create mode 100644 deps/pygost-5.12/pygost/gost34112012256.py create mode 100644 deps/pygost-5.12/pygost/gost34112012512.py create mode 100644 deps/pygost-5.12/pygost/gost341194.py create mode 100644 deps/pygost-5.12/pygost/gost3412.py create mode 100644 deps/pygost-5.12/pygost/gost3413.py create mode 100644 deps/pygost-5.12/pygost/iface.py create mode 100644 deps/pygost-5.12/pygost/kdf.py create mode 100644 deps/pygost-5.12/pygost/mgm.py create mode 100644 deps/pygost-5.12/pygost/pbkdf2.py create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/__init__.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/gost28147.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/gost28147_mac.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/gost3410.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/gost3410_vko.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/gost34112012.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/gost34112012256.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/gost34112012512.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/gost341194.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/gost3412.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/gost3413.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/iface.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/kdf.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/mgm.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/utils.pyi create mode 100644 deps/pygost-5.12/pygost/stubs/pygost/wrap.pyi create mode 100644 deps/pygost-5.12/pygost/test_cms.py create mode 100644 deps/pygost-5.12/pygost/test_gost28147.py create mode 100644 deps/pygost-5.12/pygost/test_gost28147_mac.py create mode 100644 deps/pygost-5.12/pygost/test_gost3410.py create mode 100644 deps/pygost-5.12/pygost/test_gost3410_vko.py create mode 100644 deps/pygost-5.12/pygost/test_gost34112012.py create mode 100644 deps/pygost-5.12/pygost/test_gost341194.py create mode 100644 deps/pygost-5.12/pygost/test_gost3412.py create mode 100644 deps/pygost-5.12/pygost/test_gost3413.py create mode 100644 deps/pygost-5.12/pygost/test_kdf.py create mode 100644 deps/pygost-5.12/pygost/test_mgm.py create mode 100644 deps/pygost-5.12/pygost/test_pfx.py create mode 100644 deps/pygost-5.12/pygost/test_wrap.py create mode 100644 deps/pygost-5.12/pygost/test_x509.py create mode 100644 deps/pygost-5.12/pygost/utils.py create mode 100644 deps/pygost-5.12/pygost/wrap.py create mode 100644 deps/pygost-5.12/setup.cfg create mode 100644 deps/pygost-5.12/setup.py diff --git a/deps/pygost-5.12/AUTHORS b/deps/pygost-5.12/AUTHORS new file mode 100644 index 0000000..f047789 --- /dev/null +++ b/deps/pygost-5.12/AUTHORS @@ -0,0 +1 @@ +* Sergey Matveev diff --git a/deps/pygost-5.12/COPYING b/deps/pygost-5.12/COPYING new file mode 100644 index 0000000..9a2708d --- /dev/null +++ b/deps/pygost-5.12/COPYING @@ -0,0 +1,674 @@ + 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/deps/pygost-5.12/FAQ b/deps/pygost-5.12/FAQ new file mode 100644 index 0000000..16cdce0 --- /dev/null +++ b/deps/pygost-5.12/FAQ @@ -0,0 +1,24 @@ +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/deps/pygost-5.12/INSTALL b/deps/pygost-5.12/INSTALL new file mode 100644 index 0000000..02914c6 --- /dev/null +++ b/deps/pygost-5.12/INSTALL @@ -0,0 +1,64 @@ +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.10.tar.zst + $ [fetch|wget] http://www.pygost.cypherpunks.ru/pygost-5.10.tar.zst.sig + $ gpg --verify pygost-5.10.tar.zst.sig pygost-5.10.tar.zst + $ zstd -d < pygost-5.10.tar.zst | tar xf - + $ cd pygost-5.10 + $ python setup.py install + + You can obtain releases source code prepared tarballs on +. + + But also you can use pip (*no* OpenPGP authentication is performed!) +with PyPI: + + $ echo pygost==5.10 --hash=sha256:TARBALL-HASH > requirements.txt + $ pip install --requirement requirements.txt + + You *have to* verify downloaded tarballs integrity and authenticity +to be sure that you retrieved trusted and untampered software. GNU +Privacy Guard (https://www.gnupg.org/) is used for that purpose. + + For the very first time it is necessary to get signing public key and +import it. It is provided below, but you should check alternative +resources. + +pub rsa2048/0xE6FD1269CD0C009E 2016-09-13 + F55A 7619 3A0C 323A A031 0E6B E6FD 1269 CD0C 009E +uid PyGOST releases + + • gost (http://lists.cypherpunks.ru/gost.html) maillist + + • $ gpg --auto-key-locate dane --locate-keys pygost at cypherpunks dot ru + $ gpg --auto-key-locate wkd --locate-keys pygost at cypherpunks dot ru + + • -----BEGIN PGP PUBLIC KEY BLOCK----- + + mQENBFfXoPQBCACfxmT1M/oDKh+3DxiIHwA2YdyJ0joxe+QhT1cACApvD9GBOlbS + QqJU3kyO1+uOO5QzeTsSYdZbdcXF+Y7koEUsAVGY6aTKNKuuOrhVPTnhbG8Em++p + i6LPAvHs1/pD9xYWgSyGueu5OrcUu1bk7Ii16BePkGdoVqIo53OrteNH8fabJ5Ga + Rqvn2SxyTZ/HrgSfWqXOPmP62oiUKD6ztQPv1qP5GoSqPT3zXRF+c7yoJzAi09/D + trKpOH+eZqj/5M1v853i/TIQE975+AH9HNuIK3XYt67VQiDqU3CFeWC6wFUt/FOD + eAA9pKuJvY7eCyKVCOuNYJ5af1fGuxrEZPxJABEBAAG0J1B5R09TVCByZWxlYXNl + cyA8cHlnb3N0QGN5cGhlcnB1bmtzLnJ1PokBQAQTAQgAKgUCV9eg9AIbAwwLCgkN + CAwHCwMEAQIHFQoJCAsDAgUWAgEDAAIeAQIXgAAKCRDm/RJpzQwAnkiQB/wLD29x + 75urRIOCxLKrynERF2z/lxUv8aA6VB6Bp3/c08xbrtrNKpq970WvcxyNrsTFgcno + Sc2QBwGjSM4Oh5z1UxHt8wLvk+FTOYxlkUiOQv9uCwhU4ZtypV7Ps759dwneY2nS + Y0R5oGa3nFhi7JujBu7/9Xr2riBBczsGh3chFUe/WeQZxwfF4ZJFN/ykJpvlwkhe + txhAWSG2JTR9xDxbt6JBzdZ8hmS9YNZrzzyU3XUkdATi6zgkgv8BYPlc/QUCBVYp + xukpfqopwuT0QPKXZjPEBUNRAXGtPMo83OQyanMLm/BkSJXFBO2mVjaalEohc7Iq + jMcy/DjqMIpsOdVfiF4EEBEIAAYFAlfXoRkACgkQrhqBCeSYV+9zEgD/Weliq0bC + bQbT+AV0oPSsh4cl7/7yBWXuERUm0uIsDRsA/RSss+81tbyKTt8oObmDqi3gt8ka + 6j2AvJWj4I8J/fT9 + =pQ8y + -----END PGP PUBLIC KEY BLOCK----- + + You can obtain development source code with "git clone +git://git.cypherpunks.ru/pygost.git". diff --git a/deps/pygost-5.12/MANIFEST.in b/deps/pygost-5.12/MANIFEST.in new file mode 100644 index 0000000..f7ccb5c --- /dev/null +++ b/deps/pygost-5.12/MANIFEST.in @@ -0,0 +1,8 @@ +include AUTHORS +include COPYING +include FAQ +include INSTALL +include NEWS +include README +include THANKS +include VERSION diff --git a/deps/pygost-5.12/NEWS b/deps/pygost-5.12/NEWS new file mode 100644 index 0000000..840ca6d --- /dev/null +++ b/deps/pygost-5.12/NEWS @@ -0,0 +1,264 @@ +News +**** + +*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/deps/pygost-5.12/PKG-INFO b/deps/pygost-5.12/PKG-INFO new file mode 100644 index 0000000..d85054a --- /dev/null +++ b/deps/pygost-5.12/PKG-INFO @@ -0,0 +1,96 @@ +Metadata-Version: 2.1 +Name: pygost +Version: 5.12 +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/deps/pygost-5.12/README b/deps/pygost-5.12/README new file mode 100644 index 0000000..2034715 --- /dev/null +++ b/deps/pygost-5.12/README @@ -0,0 +1,73 @@ +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/deps/pygost-5.12/THANKS b/deps/pygost-5.12/THANKS new file mode 100644 index 0000000..cb2ec9d --- /dev/null +++ b/deps/pygost-5.12/THANKS @@ -0,0 +1,8 @@ +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/deps/pygost-5.12/VERSION b/deps/pygost-5.12/VERSION new file mode 100644 index 0000000..fa5f02c --- /dev/null +++ b/deps/pygost-5.12/VERSION @@ -0,0 +1 @@ +5.12 diff --git a/deps/pygost-5.12/build/lib/pygost/__init__.py b/deps/pygost-5.12/build/lib/pygost/__init__.py new file mode 100644 index 0000000..c0695ad --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/__init__.py @@ -0,0 +1,6 @@ +"""Pure Python GOST cryptographic functions library. + +PyGOST is free software: see the file COPYING for copying conditions. +""" + +__version__ = "5.12" diff --git a/deps/pygost-5.12/build/lib/pygost/asn1schemas/__init__.py b/deps/pygost-5.12/build/lib/pygost/asn1schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/deps/pygost-5.12/build/lib/pygost/asn1schemas/cert-dane-hash.py b/deps/pygost-5.12/build/lib/pygost/asn1schemas/cert-dane-hash.py new file mode 100644 index 0000000..0292b9e --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/asn1schemas/cert-dane-hash.py @@ -0,0 +1,18 @@ +#!/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/deps/pygost-5.12/build/lib/pygost/asn1schemas/cert-selfsigned-example.py b/deps/pygost-5.12/build/lib/pygost/asn1schemas/cert-selfsigned-example.py new file mode 100644 index 0000000..bd562b1 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/asn1schemas/cert-selfsigned-example.py @@ -0,0 +1,348 @@ +#!/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/deps/pygost-5.12/build/lib/pygost/asn1schemas/cms.py b/deps/pygost-5.12/build/lib/pygost/asn1schemas/cms.py new file mode 100644 index 0000000..8028d2b --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/asn1schemas/cms.py @@ -0,0 +1,431 @@ +# 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/deps/pygost-5.12/build/lib/pygost/asn1schemas/oids.py b/deps/pygost-5.12/build/lib/pygost/asn1schemas/oids.py new file mode 100644 index 0000000..4638900 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/asn1schemas/oids.py @@ -0,0 +1,60 @@ +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/deps/pygost-5.12/build/lib/pygost/asn1schemas/pfx.py b/deps/pygost-5.12/build/lib/pygost/asn1schemas/pfx.py new file mode 100644 index 0000000..27a87d0 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/asn1schemas/pfx.py @@ -0,0 +1,250 @@ +# 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/deps/pygost-5.12/build/lib/pygost/asn1schemas/pkcs10.py b/deps/pygost-5.12/build/lib/pygost/asn1schemas/pkcs10.py new file mode 100644 index 0000000..dce45dd --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/asn1schemas/pkcs10.py @@ -0,0 +1,49 @@ +# 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/deps/pygost-5.12/build/lib/pygost/asn1schemas/prvkey.py b/deps/pygost-5.12/build/lib/pygost/asn1schemas/prvkey.py new file mode 100644 index 0000000..7da2533 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/asn1schemas/prvkey.py @@ -0,0 +1,100 @@ +# 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/deps/pygost-5.12/build/lib/pygost/asn1schemas/x509.py b/deps/pygost-5.12/build/lib/pygost/asn1schemas/x509.py new file mode 100644 index 0000000..86ad7da --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/asn1schemas/x509.py @@ -0,0 +1,262 @@ +# 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/deps/pygost-5.12/build/lib/pygost/gost28147_mac.py b/deps/pygost-5.12/build/lib/pygost/gost28147_mac.py new file mode 100644 index 0000000..aab2805 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/gost28147_mac.py @@ -0,0 +1,99 @@ +# 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/deps/pygost-5.12/build/lib/pygost/gost3410.py b/deps/pygost-5.12/build/lib/pygost/gost3410.py new file mode 100644 index 0000000..cdb5b03 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/gost3410.py @@ -0,0 +1,404 @@ +# 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): + """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) + """ + return curve.exp(prv) + + +def sign(curve, prv, digest, rand=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, _ = curve.exp(k) + r %= q + if r == 0: + continue + d = prv * r + k *= e + s = (d + k) % 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/deps/pygost-5.12/build/lib/pygost/gost3410_vko.py b/deps/pygost-5.12/build/lib/pygost/gost3410_vko.py new file mode 100644 index 0000000..92f4a26 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/gost3410_vko.py @@ -0,0 +1,95 @@ +# 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): + 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]) + 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/deps/pygost-5.12/build/lib/pygost/gost34112012.py b/deps/pygost-5.12/build/lib/pygost/gost34112012.py new file mode 100644 index 0000000..91782de --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/gost34112012.py @@ -0,0 +1,299 @@ +# 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/deps/pygost-5.12/build/lib/pygost/gost3412.py b/deps/pygost-5.12/build/lib/pygost/gost3412.py new file mode 100644 index 0000000..b9472ee --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/gost3412.py @@ -0,0 +1,186 @@ +# 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/deps/pygost-5.12/build/lib/pygost/gost3413.py b/deps/pygost-5.12/build/lib/pygost/gost3413.py new file mode 100644 index 0000000..f3cb5da --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/gost3413.py @@ -0,0 +1,392 @@ +# 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/deps/pygost-5.12/build/lib/pygost/iface.py b/deps/pygost-5.12/build/lib/pygost/iface.py new file mode 100644 index 0000000..e2d6a4c --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/iface.py @@ -0,0 +1,50 @@ +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/deps/pygost-5.12/build/lib/pygost/kdf.py b/deps/pygost-5.12/build/lib/pygost/kdf.py new file mode 100644 index 0000000..4e404c6 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/kdf.py @@ -0,0 +1,81 @@ +# 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/deps/pygost-5.12/build/lib/pygost/mgm.py b/deps/pygost-5.12/build/lib/pygost/mgm.py new file mode 100644 index 0000000..fb51343 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/mgm.py @@ -0,0 +1,168 @@ +# 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/deps/pygost-5.12/build/lib/pygost/pbkdf2.py b/deps/pygost-5.12/build/lib/pygost/pbkdf2.py new file mode 100644 index 0000000..0fd6ddc --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/pbkdf2.py @@ -0,0 +1,41 @@ +# 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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/__init__.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/__init__.pyi new file mode 100644 index 0000000..e69de29 diff --git a/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost28147.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost28147.pyi new file mode 100644 index 0000000..be31261 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost28147.pyi @@ -0,0 +1,101 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost28147_mac.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost28147_mac.pyi new file mode 100644 index 0000000..70d90d6 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost28147_mac.pyi @@ -0,0 +1,25 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3410.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3410.pyi new file mode 100644 index 0000000..8f0dcb8 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3410.pyi @@ -0,0 +1,72 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3410_vko.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3410_vko.pyi new file mode 100644 index 0000000..6ea9b82 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3410_vko.pyi @@ -0,0 +1,17 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost34112012.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost34112012.pyi new file mode 100644 index 0000000..3d5cc41 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost34112012.pyi @@ -0,0 +1,18 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost34112012256.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost34112012256.pyi new file mode 100644 index 0000000..a1d2a01 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost34112012256.pyi @@ -0,0 +1,21 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost34112012512.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost34112012512.pyi new file mode 100644 index 0000000..349bddd --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost34112012512.pyi @@ -0,0 +1,24 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost341194.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost341194.pyi new file mode 100644 index 0000000..24de2e4 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost341194.pyi @@ -0,0 +1,25 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3412.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3412.pyi new file mode 100644 index 0000000..ef278b7 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3412.pyi @@ -0,0 +1,18 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3413.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3413.pyi new file mode 100644 index 0000000..4cfd694 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/gost3413.pyi @@ -0,0 +1,81 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/iface.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/iface.pyi new file mode 100644 index 0000000..a5c2a85 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/iface.pyi @@ -0,0 +1,19 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/kdf.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/kdf.pyi new file mode 100644 index 0000000..ccab8af --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/kdf.pyi @@ -0,0 +1,22 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/mgm.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/mgm.pyi new file mode 100644 index 0000000..81906b7 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/mgm.pyi @@ -0,0 +1,17 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/utils.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/utils.pyi new file mode 100644 index 0000000..76460e5 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/utils.pyi @@ -0,0 +1,19 @@ +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/deps/pygost-5.12/build/lib/pygost/stubs/pygost/wrap.pyi b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/wrap.pyi new file mode 100644 index 0000000..776a6e7 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/stubs/pygost/wrap.pyi @@ -0,0 +1,31 @@ +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/deps/pygost-5.12/build/lib/pygost/test_cms.py b/deps/pygost-5.12/build/lib/pygost/test_cms.py new file mode 100644 index 0000000..7e5781a --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_cms.py @@ -0,0 +1,1078 @@ +# 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/deps/pygost-5.12/build/lib/pygost/test_gost28147.py b/deps/pygost-5.12/build/lib/pygost/test_gost28147.py new file mode 100644 index 0000000..a83e930 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_gost28147.py @@ -0,0 +1,384 @@ +# 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/deps/pygost-5.12/build/lib/pygost/test_gost28147_mac.py b/deps/pygost-5.12/build/lib/pygost/test_gost28147_mac.py new file mode 100644 index 0000000..ec1af3f --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_gost28147_mac.py @@ -0,0 +1,63 @@ +# 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/deps/pygost-5.12/build/lib/pygost/test_gost3410.py b/deps/pygost-5.12/build/lib/pygost/test_gost3410.py new file mode 100644 index 0000000..cd71535 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_gost3410.py @@ -0,0 +1,495 @@ +# 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/deps/pygost-5.12/build/lib/pygost/test_gost3410_vko.py b/deps/pygost-5.12/build/lib/pygost/test_gost3410_vko.py new file mode 100644 index 0000000..a8e298e --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_gost3410_vko.py @@ -0,0 +1,125 @@ +# 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/deps/pygost-5.12/build/lib/pygost/test_gost34112012.py b/deps/pygost-5.12/build/lib/pygost/test_gost34112012.py new file mode 100644 index 0000000..c7c2df9 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_gost34112012.py @@ -0,0 +1,159 @@ +# 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/deps/pygost-5.12/build/lib/pygost/test_gost341194.py b/deps/pygost-5.12/build/lib/pygost/test_gost341194.py new file mode 100644 index 0000000..4ffa6d6 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_gost341194.py @@ -0,0 +1,200 @@ +# 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/deps/pygost-5.12/build/lib/pygost/test_gost3412.py b/deps/pygost-5.12/build/lib/pygost/test_gost3412.py new file mode 100644 index 0000000..5dbb200 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_gost3412.py @@ -0,0 +1,137 @@ +# 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/deps/pygost-5.12/build/lib/pygost/test_gost3413.py b/deps/pygost-5.12/build/lib/pygost/test_gost3413.py new file mode 100644 index 0000000..0098513 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_gost3413.py @@ -0,0 +1,766 @@ +# 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/deps/pygost-5.12/build/lib/pygost/test_kdf.py b/deps/pygost-5.12/build/lib/pygost/test_kdf.py new file mode 100644 index 0000000..6921cfc --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_kdf.py @@ -0,0 +1,58 @@ +# 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/deps/pygost-5.12/build/lib/pygost/test_mgm.py b/deps/pygost-5.12/build/lib/pygost/test_mgm.py new file mode 100644 index 0000000..e70a7f5 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_mgm.py @@ -0,0 +1,75 @@ +# 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/deps/pygost-5.12/build/lib/pygost/test_pfx.py b/deps/pygost-5.12/build/lib/pygost/test_pfx.py new file mode 100644 index 0000000..a2760bf --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_pfx.py @@ -0,0 +1,680 @@ +# 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/deps/pygost-5.12/build/lib/pygost/test_wrap.py b/deps/pygost-5.12/build/lib/pygost/test_wrap.py new file mode 100644 index 0000000..7ecf8ee --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_wrap.py @@ -0,0 +1,111 @@ +# 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/deps/pygost-5.12/build/lib/pygost/test_x509.py b/deps/pygost-5.12/build/lib/pygost/test_x509.py new file mode 100644 index 0000000..e9fccb7 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/test_x509.py @@ -0,0 +1,433 @@ +# 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/deps/pygost-5.12/build/lib/pygost/utils.py b/deps/pygost-5.12/build/lib/pygost/utils.py new file mode 100644 index 0000000..21c31b0 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/utils.py @@ -0,0 +1,101 @@ +# 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/deps/pygost-5.12/build/lib/pygost/wrap.py b/deps/pygost-5.12/build/lib/pygost/wrap.py new file mode 100644 index 0000000..3fa49e7 --- /dev/null +++ b/deps/pygost-5.12/build/lib/pygost/wrap.py @@ -0,0 +1,152 @@ +# 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("So+w%c2qx3=x>e|Mk0nViX-mpo)9lT1#cBnt|L z1_T5I1q7Z+po#V;$b|eqM*ANi|AVl)nv|l7>XHhS0=@#Hq0{V#j|B*x$m9(W5BuD?~y5dR#ZXTEGD+XdF zpd#C@u;=30_n{(#>2eCC7^Jm}+n0neBMsQQ8vhW-eLqw7|E-bb1lnw5v^>ngiaD#B znwt8o{e8fc{e8WBw*0*O^6}^FZfei}r%%s7|NZ;^{Js4ZMp*FsIm2$Rtobrt`Mpp0 z^81w9Ea><9{V%RD{n_CkT0-`0o_^jV;dR6qx36J+_i2sui|CG~_v*ryYIlVO=1YEd z-PZTlj_0MvA7##jf8QooUz6;gSs3&>-L5ljXFTa!b#1gdI~M8=k44v6CL5l!i_Nk+ zjxmI=YdE^~_U(n7((~AqkQ9=h$h{0}#ElN$PsBwTOiF@~;d7zwG<@Wd6fIeRwMS|9IctX27a!Z*Lc1(v-kO|K|4@is>Ak@30f7SNQTq?Q!Nw>EXfsGL(K>>Tk~Be$n5zd51MB4#`mxcJ`LV=j)R#1=}c{LoE7gCGQV~cnG|iV zjDgP}y5RSera~v*_@@y99A)8j6o>5nao-0F<0o9SJQN6HpOwWzU|#3u)vlY?dMBO8 z>T`v{g|E=zupzoIz#ZH)v~#O2m%w8SRq&=M&A%E5mUOHZMyG)U?IEhEmK8?RS4gYV zg@BQb#M#GpPLA_gF+21LOgD(eIu|=h7N}|msguc^wlFc;#(tXU1|AjlTXTOh^!_QY zqPWJB2p&Qx3;?ycRc(4C4!2})_bRu%(5e5~%mpfgE4`v$3XRY72!I> z9R*d$2t{G;|ER_1-9@`Y3Js~zKws^;(y;UdVcmJF#Y0mVsykli&9y?it0}%q8-SPA z?<&MM1H@A(VcViH3C%_^j1WrR+bKMA*fKqGZMpi$7?q~M&a3m7yj zTmB3mfhJ*g=P8w&Z!EI& zUL=Xt9y!^Z9C%sK;VsEtH|AeQb2A*FCx+sKM;V?WkQ;u?rR8K-OW(^2q%8ojOvcb2nwYP z=L;56qV;zT7bS^=bQ#B0rk=eq9WvEJ>ueGP*&IGdB0zUBRYSi}OG7u<39`({>dsdI zEa#Yr)&wg<7X+RDMa|3NnEpFk%Vy`$1JpSKig!U|x@*do7MYWDkU;|7)k_(;uhP9? zw09kXr5het0%$?RjP*fTrp+4!`sR-HX!nog?}~`Qi#dV3{coTR)&WJZm^k!Af;hD5 zFwdqxyBKRWYrW^6=HWr%{E={Z+BAMt`JJlJIa`f6CY}uT{0kCH09l+wM>}_9OHu^C zkjVve_(&BZq~ShPcq^2CAL)h+=P#H@G3CUGi~i~r$mu3o<(ADu(pZR6!DX34;+IJqwqQF4S>wcH5NG48Y9qv>8wvPU}| zn%-Dc#=36$0(Wq@uRccT6N|5gG=l`2J=;?m+$W1FI)#{rhw*BJJS&C|)9ng9Y4CCkwa(#SyGP!F!$%}nh_^!fc9TXU>S7bBk4+I{hsRJm zQ&yYZaxfUBmmN6a8k zIA`c2k5vaJ$s$tX(PClb>!8Y_jCq4f2;*$@I$iUL3*9KMoj;P{cv}i^q6*O0s>A7@s z)wt}rPp5%qOl~hycs`M*Ig_FIpvqA-P%JL;clBMAvkDEbe@zn1!!t(Rt`k$iBrk=s zOOVd;%?FnuJS!_!JK)Dvl%V3p7o+ndJ;k<#0Pmg^tRIB!7rb#+firRGF&yjmZ|H5F z_bE}z@x0wDuE9uB8|nP#{X{%}h1_!Pjjg%)%jA!t-rZ!dHdHlI;HuUyEyk}HIH-{g zU>_sMti@@EY4@gsVg8^bglL^?q?&g?4=p6)CiA|b$<>DobAiA@Y8w0MZw9`tB=746 zB9|(Zb5VqbDcVt}h1SJht9P!&suR26|D{X@*B9k!0cCPIWYXbf(bmn_#9Ki6)CQFW z5j2*tYK#8lAgkwnKtCj*6&GPoIoe z-%q8YZ=t6d$HB1xHPNwM+#N&=mPPhRgA!lo1?Y}`Laq(Dxk(=cQKuQS9jXKJs0V56 zOCshmVOpa%CWvK6C1HC4?X|UFG`!&rIAJX6HNjL~7NrQ$Z?qXHLLrKQ3~RV|(QkuN zY-!to>}ipS1;~(ru;)9WPVr-oE9_WA@wOC4`_W^UMv0z?Ny0TZdB*)|!AwQ_Ia(l4 z$FgBv;MTW#Lfg+*zQ~+~DKGHL52WaTM}nlr7Us5sa!wP|8qB9^4%FO~WH_gPUFZfI z{f_7}aGRR%V=O=ieGxC&Y+btVq+;qRBhmcPg7oejkCHH$dPn+3VYp*qg7a#|;cB!6 z0kg#cpO6!>)sOrj7^(?V>J;I|>lNrR@#MWkQ5*g%>Do{Y_CetQxIO`2v9Ds3J2(&z zF{@*Pt9m=#CD(<@W7B3c_4|YLNKdyp0(j)*rb(h@iXwb<8c9|WZ?+1=m0F534S$#t zu{>FQp3<-8D?O09FOQofrG?Q%z4=dK?)Hx~{^-Qtly6MpeZYvieprF$T>?>Rk+cx* z-64f{j<=t;0#228Fj4KuI?7;`hBuhjw}{B`V4u++5_Ylhk&5<+&|yr9fh2L{JoVgV z0xC3G7;!i0SR4>UsKty)sOTsFyjESAd;n!fidq!+9hDvX%c$1SqwL8QE_Yd7Xk@($ z3dun$jLn1qc^qbsLt#%Uc&AxS=&`Y@Mj+hF>HLI?5c0NY6;XO$ZnS>zb<86BE~=6u z%h>rb_d(G_+mJmOSl1iD>XR`}+5B`@(!2q}o!C6wh%~caa+FMxTABqKtgN4u^El7Q zHGvfyHiHStLeb=~@c1y^IuUio+Bkf&&msOWBHSLp{T zO7=3VWW4RZskRC6R9{sLbP{X(ez=Nz4dFWozGfzorGa#bi?Xq5RWhYW>h=bahZ@t9 z^_SFF-Hl;%6)n=72D0S8+|NJqFrol1(yKPmCm#szaLuTTayGQT{)D~uI`M_#YrggC73Z+AzlxXft108 z!v@$R4b{j$WjG>CcFWXe3+N8<)}jY#e{fCcs1u82S$)(dC7jU$0-;Ty`wB|8%#MAk zzGeadSYZ3JR_jOwa+yi*b|~=jCfmBk*tdJKv2rRtfFXo&CvQG3<@h-fBKQ-T&U*Pq zRCF;%mRbXd9_t3ul2uLd=8N__P@{ds3n3xWhV}b!|5@-tsxJh6jJwXk_=d&fSy3S`^xw^zy(tImjc!c=sYW_{qqMvSmF$` z+JKtZU@c@igoYeNTSPIrHXE`PF=kRS^_)c`yNcmbk_z>Ko^@7F0*_pn*u=N{Mt1Rp zdF_J({#JPmHh91}&d73Y5*tXFR_!P8?y#hz>oxNoBEr4jUp(-Y$=rZ<*0tF<384RK#|?q>`vA zVl;R=S)XumyubYTB;8#*_I+HafY?+*ENVpl1#sKLBBR0$ z7A}B2mT%&UDfM_ipe?d>e%x-@sB`Y3X(B(pW~?BoOc??R-KWz}y6ST$;eR5z`c511 zu{9Rj-Y6Hllmm4%3MaNoRXG2r;2v z@j>2~V?zE~`FX|~waJ^d&I)I2kw@Q{<4S7X;Kr>-c@AkZL>_vPPQ0g54j&>AL1$F8 z(v!3mWU15k6;Vg`)(p0^P5-U#3wtA?UymV`GvQFVSFgR+LvfUEZ5kBa;Xg>1Co z*mr3cjT2sZk9}u?8?9rkd68KZv*Uu7iGv77n10=d0b2(Ye+Y#tL zh<+U@(TxG~TPUU|*F6%~sM9^g$c+mYrpXd#l`hJSfIDfm3-fhHG&#%Rz-@v>4!de+ z5yxi74T#k#27@EIvGQcf`1!NO%!TpzGclXhE*Ah$G)VuOhbJPG z^{&h_&=TFal_@xpvAE4$kiJ6R%bTH07m6-xjZwOvEPo_ak^@WBk>1c`tODBXT12NVT z*dfNwA%C3yl>){*F^CDvmBdd#qjbL@)Fv*axOFWmTfri?6E>g<^LhYR7U zi4F{eX$nv0GbW=RKh30`8Bi1S%G8(SMhSugoT`M?)t$Y z(J=jNgCGnS@}yg_-)K<;%u1yC7~4lvH>}d3e$T-IDTNJ-o+>rX;R0GRb1#(c(4p?2 z?7_4o0vAz`vOo9Fl@AaF&x7*Ji&7lnEM;e56j3@FxWeWmJ?^kFX?Cu>cNDtEIGzAo zpLp1t99sj=XWOs(lz=dRV1ls$WFltj2rL=$Sx1GKlP4Lfz+ZS1=+b#%x)FhW$IeM%zfq;P(inSs3lVgr|)(E+@W zBVOsjfsX0KiP?ya6j0@i6k=j5L_s(nx1j{y^_lgAk8S6Ol@yt}`-`4D)5-wx@ zWG%PR)Kl&%B4rLJ%AnnlR9&?xPrnH|EyS?2Z+HY=cJCd=Zg)eMUNzii1UGi~cQP*n zOGWl{`ljO~{tt-kmf$H)HnHcCRvqam^zFRLTsS25ifWD>|M4~xdUZE3gTmOzNH=v0 z`)oIFIZ0#ae9jwD-eD_O+6W_v$&O+YjO|zgcB_DUr785zf5;=2Gh;_R$#Ouzv$^Cd z=Hr2dLsS__k_!UC+lCrV5Rh;H1SHgJ5US^%2zL4k5j1W)qP-V%#+f#tACXV%P7+I^ z^(>$`XC>7k3k6s#{_rpDjt75YcisacMlF^5fX|15lu-c8AU%Hd>=w0^>9GqLMA_y? zDf;D0_7<#<_#L{lJ)_fONq1BTlnUcfyqgWa1g9VWE|8EEQ8IoHqkX}(b<8aMfT-F; z3%?Bdj@FD-QzLB^!B9cHR(#byF6fkzGltHXtn^dCs!CVI4lxM?nHt$CU=dHjo&mr6 zeb^6)$});2-M&n`DCU_cN&`P~iVf{Dtk1`9U18D3T76ar*J>1At`XNZ1jW`ct961C zR$5LuBJ&^3zE~}mcKO1>`spyoz#fP4YEkM+R6J~1?yBas-WukPz(e;ndu-ws}zafvnE7A{Z8GAy!T8E2^xbq#WvV@_W zf3~o#G%lS>ibXztI(8de7N3^5>R(Kzu7~g-MLcHNKubF*3^|Cone%UqSOFzL%dt|) zx>rmT&DvSkizd%(iR=}lP}lzeP5`Zc!gHuqn_5felE$1U?=R{X*6{WdTQ(7vOWp`( zssbm;v6yF0j(}EjeRnWef38VVITUL{CdgxAsytNiql7XWLoYuY!KrUZfb!o zMTu|Qt2zv>ez^H_vpn|7!5N2vh=7k^j$!MlW>mGJiAUvTt@i5oq7UuCg4ex~^9-1K z!Mtx^$<$rUQ|65UVhmRhzX@pcXw?~fTfI>JGGhfBEzj|DQNf*{!(PctfddDf_LY5{ zPSnx#zISZCK5*Y0&qQvPJ}3ltIe`RWFz`k zoaXJN-Y^%mZFgk*meQSJO_@QP4qOK%-q={C?T{+k`QJf&=8J?$$6H4J%V znc4^Sa-_`!@1MqTkR`6XTy^}9`fbIw%Tw8|`y$x&qYID(;0@}nGkatjpA%U6zIvLU zPuwdxDlBepJM(sO5Gph1S2zq~g7G9oZjg~Fxaf^>)$&@Fq%A`c7(gd_-6aYMYd2vjn=xz2m4RFuX(?1zzEvmmsFn-3Ihp(} ziBt{hb5r14H#{NsE=;xz1C~R<9NLNWu+(6Iub8$erLE>lsJU7GQ9puZS|rK{ig{$m zOa7Z?EW1ps37ql5@cnVN>sxtPx6-g43WND$R9Je)^Ll zp($B>0M1ShhZ`%LQaC;KQV;-P8pt~yLr$}+P(S@CE;5?9v^v2Axjdmwov@h{QddR8X`6YE zn#5&y71cXXuqihNC1+}%%HZI3&+fhZch{((^@Q$tBNBlCwZ**>e%RSOmOdqXKfkY? zTbKP(!7=!g86?V|X1(oZBXNY8Pv~(7=o7SRXVuZ+e2IfFdCJ^$UqpNGo{)~NZQCH{a*nIwTIwr75e0ioJ{9%OUf`nH2$P{ zvPL`dZ+Qn4#XsviNNA_zz|uP-A5_?PA$$S5L~uHC_-!UeE?pih{uB%%Q59H8gz1C? zYotnU!d7FR>k*7OWr!QHp4$j5nfBczSuBJEQ6`4J>q)Dk!VkhB=W4JZ>vc+D=*~_9 zuPhEy(|?Sq=Xy?-B$qS%4FyrfO--1Qvl#vM)4jOV6Zt58YQwctgFK#Hs^sCeMSoei zP*v?mav^MmNoIX$S9m94GZC<} z)jfi#2y)6ZelsIS^DRO}W~E<}G~}_pQsa+MQ0 z*%L&#&wCit9Lv{N&en~WnLn#)rwH;CrnkwZmnZdPlk?C;xH=Ei{G>{uqg{tVE4F}R zB)o4v;mOuWCSSg335GIUF-t>@xhACzz@X3d@W5gh52f_j+J}1n8aXGu>%9cZcB75| zL8nA@KSu`X%nO7*iN*e%760C`!f0&!0$0CXwN25(&zQMz6n(&RF4W2QUg8)|{KRWL z5M$}d7LmCx@(xl0z_C4dL+LC{R3x*$WQQDS3(LyPMT z;;V5naY(PpX}wH~4-@gnWIx#>wa44l*zuc8R?wvmP+__w=eIvIVFsoc7Rm4mhUa`J zDZ_&;$C-iQiW}kJf}2f6Q2e#p?#y9q7kWny14H3#3geE|kLe=?p*UvDMZL)`3;xdW z)=WHl_+W-32~d>B$QvsvRguWDpk;SC^C*xSU49M7^^p(S_s+qPbI5^rU^$`5=q@DR z#!Hi{A0HXq7dcN_ZCS^nRt>3$#R(N)rI+M~Mxo5}0$QI?dguwLDSe&T9a^A(?|BG1 zU`nqvFw<8!7--kh+8?gw(LwW}oUlG&7S0O2((xxl>p>6!&#}R>p6hJ!l&C-@r9149 zz!D6sjF8VXt zSZH-H$>c@Ne?%S!HEs|ic+5!JoC#r7QV;^shx<&lYJO-8Qp#xb`r;teBN~!m$YpJm z${9?ZS6AZ=!)&QiCyG)f!z{r}C|ioz9Of4s;0aW1AVvrK{bM5skeJ~Uu@)*rP6V_| z`h{42P)1@p8s!CK%?6%lhR9gI%l4w$7!!i9ARuXNlE6XNEfDRi(HjxPUz z+8LNn0z=><+!dGcLZhC zG&7r6RYOxGvVqg4c5S#iUNsVX11gi91Khew{H1J$%Z*QDc|D9qx*QI*>babS?Ly8j zVqsE(zfocm!@(=dzY}8$x7alhgK6T98{Tp7jVsEe@`&LxQq*iX=B?VSp>b8FrA&uO zC8xeLIl%_B+=il^(y!a~&eg_Kf^V2IWDAF_{X*5p!EQ>~48foBal1ZFagCw7)T9vb+ z$3HmYhW&YD8`}wZL;R$+J!`v@XEQWzq5g8(AiKl#VfV_>TZ*8|=UJ9+MChKN3KF{( z&KN(bg>)ehDX?JD){|A0Y6B<59-X$nHiPOZ?^Fm-!nc1uiGYo5??U{?Q1sb17=2Cq zgQscW*bB~?^+qWitw)){_IV>eY+DJs&aH?^+uJn8dw)F|^ z87f7kcZFLdTK=y$5~9MddJs0N5a4Kw@3faay=k(Qu74tkK-%g9+1v>s3@k`C`Wj8F zF({c(U+KgBUwZ^50!JcbIJ~DkG)hX35~7c|Gtt<7U7LigHo68VCJ@u?CYzE{u^B^T zQ|@c~#DM+fP=2>ii>>@a^AX1r1p(1=-~Pv~^v*p7I4u};wldrulp^gz&ajljj!^a# zyZ8MbgnegWcBGuz;*R(-y-(@Bp9UO`;@C?{=2@N`CEN_pjH-tN$In-URxQC_`%wI@ zmLnx9&f>rZk5S~r%ulCGFhh8-l&3K2MrZ=ZQ2VmF77_kO(7CG; zy0V?Hlf>#6hk9Ff>>aY7mP=bjB2lQ?YXQdPydK5p{W7fA)omE;)sf+QZ;?s}ge~_2 zsBpRM0;)+ubE)2jcswI5_T4o9Lyq_n8-)ib=O6fEoT%%EPgbyb%@ityxVjoJy-nS_V(r*zS>9BmuAXOYp(3>)nqGy-91yIdJ z-Ck8pU&~hUg;EGn5r$%k?oktm%RsuBlI-B6je%$D-+|eBMk+gAvv3i=-@*r4A zRCO_k8&$}}l0M&FLq7kUgoD`zCl(Fq8Jti`-XXGX)(aP@61ww`LBzQuaS@Xq5fk~< z*`KpL#@F|cY5(7w8;dnW!|RA_-Y+A^TL=H%>_1tX5x76EU;hvvFCl$-`#)Q}yfXUR zZ~L+~uCG@PTwZQ$9O^9$@2*P%3}+n)uhJ79ZGA(I`{`~@Uw(|kF)`L&rgsjEoxf=r zy}VsNpB?Tm#~~l@PcFW{;ssTn@vmnYH@7x+hE4ntx%_o|^2?bvCa>}a^41w+A`&?B z2|?~oPC)4&R(@=qzx{1aVGXQ)ZnwJMzxjcMY#9ZRM_fOgo&R`0zw-~WwBA~UU1mf2 zwiYBWm?CnjhU`3P_wxi6J`2v=G6*gTTHa!4!VY2f)jS*DJIU)Z-B^IUAD>olvJ1@AF`ll|ayJ*+4mn@1WzQ)gyWLd3 zf|dX&yMFrr82i6Byx9wkCb!IdpDMbJzJ7z!+gNC7zWDe{-_73iZGOJ*IL6414exw; zv~_@{F*x4bG)&F3CN3yqsu&Qc5NP%}Xp@bxQ(67so*h0uqcb^k{?5(0X(&bEt3a>j zfBSG+iczu|_t8NyZ5t55(S|Y~Yo0|&VHrk^I9!!yzs4YTV36lbXJ7t1F}#(JMjvt| z+t}#FB|5e!2V-D1^JRern!O^$pVyQ(dTox#G><$F_&0O^ zwDXmPG|I=(-Sg!q`4vui2O;eGo5|(mOy3^#$P&YNvx_@tCf81A5mPtM9*T+YGAlVTx2!~JeSpku4h86%c|`mxfLY1C^h*1W2b`HsYU@*QQR}hikBo>d(vnV_dlZ5X zK`z}1q}|g&B^By=!iXFSE?pV`p)171b`S}ULMpwj5*6J!89B4^7kt{C{>?qyX_ZY;S)!)a7WMU|1z(i+mR zht1B$dXEL$m@7xPulh}$SchR(B|%km9yYZRV50lx*J+07u@!X5tdndiz^ErcSd zMk^7YxY_XD($ffqzoX7zBd+0y>h_7!clg{_UNOjuYt*Dx`#wI?a8pCNmX4yR^ke3C zIs&uU$ABlTxHvkI8*1EX3Ea!wsHF&W1DDP}Z6bUof+R4N5h-<|*pCgdH%~)6Vhq@P z7|zsrgvh*8csbvvWtSd;0k1ockB_6KD~8K6hn{_Hhuwxy1=tr)^Tx@u%!4CHNx66z zX^JtAfxe+nSzSYsRDM`wTZM9AvuPJ2`WG%X((ajK>SYE~?~K)E1CmSF$I3rew1m;R z0nFfShs1F#8*I6=G1Tmv`5%fqujzs_QIKv_t{K?n&7QBEn)!q$nue<7$MpXy4a%=y z#0q}X`_F#9E=U+SPfja%w9>j!KBeGX26lUbom|fX5rjk9&poO&sfj}Uz>+vV@kwHG zuv_+%LkI-IiF<0FPWvVD+t?Ay`}3geb$pvGvPsPvi~m09X44x2k7;;ezSPn@7z#2T zRG5BSfPyZ&Y7Ht(aFBFqt@bO5B$WjJ4J)mvKGMg(ckh*a4-PLoq(uBW{nT@v1=n*j zmv)+HU*Q@IbCzVenNF5vJEdT@B(&%-&5?N<7>`I4B4mm8>*Rh9FTpZ>3*ng$>by6% zH&J|&tV+$rUJVKjPR`V?IKZ`Mo*kNhZ@ilW)H!t3a11BR(ea0)9($n%amD#~b!SsA;Op!(0VKzon|m=o~6L z)L1xhNfkBW;-c%QaZ0Lt_Yp#~)1cn%#Q=Ke&CnamMZGdd!x-8uyQTXrD7Kw$&DD7J zuFX1>AduIA_5wHMbg0u7w#lh)omN6nPKr@;Tw#3jIW_Im_d0to03=Kp^`NGqtS3CqdVYHT$X{l)HmrRE7ZHnG43clR~2v8cikE_; z?kj16btSJcAW$8-U#AosZmB2GwMTZlbB{tL%Knw zgG|K(*Z&S77f^|545`aW$>4+G9gqx|$8=i!3zY(jvdRCYLq$bB(z05j~mMH)5PNz@UDBAPIu|d zvHmCX#Tez-+TE*Y+mW*Gw~IBrg5?LUIIRDMC!Ku!kBWiT8)XRCAM5!R`_`}bZ57@* zqA#$#=~m%8sc7+zV6+BEhCgyYSD3jg6^czB>z8q9F;W|I z7BoO^BFbDZi~?xWM3t4QPg?=FWx0sqO({BwZ{@Ct?IkPJXJ{NCq0kP?m9(PgEMp2N zkjYvgjh-?`+Y6YRJt`n;e4{mBa*UAN+K`OHgflq#9f575`Z4a=$2uN6D819~yF0rX;KK zBd=H{bC=op3r?;~mCR z9K*IF+%}=fla9YA>v!N*3c9zavTh>StcDrwQQ*uinAd$)TkAf-uT5SVm^m z&7;*Oixg4zyq@S%2&-eU9r=;8lgTh$HmyNJg%VmuTi3(crGe z&7`xW(}=Kxssv{98_o0R&dg*Cxcn;VT$EH;lgnmVj`iuvR`reNZLPe3MTnh0>_u^_ z|HQCSi2EZ&smzI^K|)Pr`ybMCPjb$f`9geteNUux{UYsLtE0moHvVaA!^D>s`9?b4 zSFuOlc)?8~-L2QnBg>{XE8oXP6HT`Bl?1 z;&POePMJc+oic!rg3&9JHVe?At@M3#Pd*N)yg$pl?fdp!y!{Fgs{1WzRcGnzcxWrv zri{v%!-jTjg4~rIU#8fAxBJqc`-TTD>aUY~#SByc)XE$FztZ>TQ^|IUfJfct7sUT< zU;fl*?JRIWKwT6-Kw$sP4hrI$sh4u>7+efi}?%h_acXj{%rlR)%Fyn?wF&G!_| zOV=1-!m=$hab#6RXydM}LX00ky*7-W2;wmyenWnFQMq1@Rwp z9&-slXxma85%!)>W(K9_uL*jH-yTjxFvhvdIiG#aoPgKX;Qb@41t~ zztYKSv?8TYvRMC{5*I?tT>;VL@8=QyHZ>-L8e}MU#=s;XVEd>|dXX8(LM5T)K*^DQ z=vL%B5>divjgdXR9i;m62Ps|~Fe@;z4&#FcnKzbfSx(d`p^|(0p)eCH(_#iz1Fj*S zqxrydx+iR~clFX4^-UsXop%VrlsZTPio*Sod~&0#nNc}>O{oUB_mpX}0*A!(9a8Lf znZ;*Ca#k0^$YO3Vy0X?okr|aU;pRUhPWijfJHx*7y{touASw53tC;bC_O*5$?Cn_K zq#7ddu-^Lwxq5E*)e$~=sA<rLL5muW}8))0u?#=3WmAUsEx}!;(*pB}Z>f^R=2xj+Sx_~S)wscq= zU$eo3*kiI1A@V2W9PU#^Aq}j4l?VYW3VKIPkqN4ZT z7<&1_??p~Ij7Zm8%&Y<|?l5(eJH5$_9fi0Ucp*lr zV~_a@UMXu34)aN`SKlRAv~=J@n98V`FV!~_D7cc^Fc<3#OMpcDKJ!_R9BLCOscxgb zadav+xmQ3NJDrQMr;zrw_cHY7~|zR*|-lGBn)UC0f~P;r`$r?gBZ9{q~+ z^skevC4Z?vVY`M?go%VG6;}$W=`*uA9XK8acLHH=|0`ti{S3V@Z6PwaQ&hC05e+4CMw*B?|)Bei^E`m zslt!1@9$&t>GZgAgY44^$yse1x9a?IJEau{Eo(RiNKBtctmX1Q$6Y(S*qJU)_<8gL zk}bzoG{*$`1ToZ4tGn9mzv4itm_lAKA|&z;NN=i)%`oT?f{VR$7`Y+UTijkOFjcriFyA2g{;eQsi`TT=J@LqT3q5M}7h97q4^Os__bd z;4!7nu*|G*6rZmlgQp#1?h5Qx0Q^=jXW6aWchUv#Sm#|oJSJ#> zUC%&@C~nvqpt~5+Q-aW5D4Wx?W5GoS>N6Oegw$ z@1xpr;`vS1#4l{VDexs`7JDOv;L+Df;#giN7H)lP5pB!7fTa2794k7@tF`{CMLDph zH=~D8SXPg&a|C8^GSz*j;YD`M8a#!74;)!MAhoLs%o+qE_$%Hyk;;YU`T|&5bzuV4 zPSQTpAz=s~hI++=)@G6VUvza&sZ9$|TvtQOz-S}&;_a@!R0FA+s$ZLkOvqE>dXSes zc~0{qG>Zd1wmhSf$j?Tnqiid%ybUGx8ows(JZ(?|xYHYsQT44OFkUajx(t{u5TgNT z&pJY+Fpo0IzuJBWk8m6f?$BR-Pzg-Y({GI8uKzT_xz8;-9nju&Q`~u2V%S>n4pw0u zYu6qOd3ea)qUxX;^h`Jkj;Fc8qqzCuxuD&%&hS&KA{Bl016w~;-bV6LVo7ZE1QxUcu98M#s% z3Dw2lF|zMpEUqQleY4Co?(Q zb*51IvpK!3Wd_BLH-dT!Vz4ecGccYTnp zi_Y`>&M~Eo?uF(G$aWx>vEcIhmft8GVUB&vw^dVgO)0FSNo`#$uc*h3ICrA^5i>{A zRg$eLKPg2KTz@8Zz1*F>5e;<_*LBgvUtZ3vI`u?kv4urRmx%O?=Wk?!%0zhsu~UW=2$w7&&s(0MJX-raO7P@rYnq zr8Au>iHA=8{ZdQG`P~xDWI(IIm;nzkz;Yg;fujK_H_MkfyP^|hwOL{6QY)>cwL z>3}rKyBYhy>Oxhw1hJRxsZ6;Zm2NdnfVYVTGs|VDIK;rDLp{h*;tm4^2ADxqJQ#AS zckZbYOWxj8ixsMlGL0zbm^mtNT(-SlXWL15Jloe6GY!4s8f|;Ax}O7DoN{HYJ$V7X zm^}oq6Usa`lq5p3pXjHozC#nB_ZJ#>LDLp-?`f8M+YpDlV2;YvzcZ zc4|#0uxY}I0VD14126_EHjppuY{7}Rtv*La=reQ>&-$_|@W9K$Wr+ooCs@hnLhcjx zx3m@*t8XOWuI(l)eEc>{U1;ictxN|`96~R(=&}Y%-FweV3QrSf+RaMJgpa4(gJSIC z%fpJ5DZOAsd+U<#FcRT%j4=MPYfw9(n-jtUH0rYJ)KpmfQi zv(B+-{pTmCR{A_#!I|2RQ~pq*P51(U&4mBykjbdOXsFn$^y@T?fNH8_xNJ$7iRc^I zc!{{%nNvbHvQpVz-c!ml@NAU!bbsA0E(n5c3Bl@B$^L|;&%36gU%8eg zOs0xe+lxN0|D=K-TJ$-eB(3-T_>A@Yd8(EB{eGIo`+Ydy&HWL2xp3~@6^{n1bnNov zFQBf8tH+~ZU;qIAF=GIVA_BrPBH5~Xc89|7 zey^qI&!FJL5?OvS5uRnKCCd~SERv4kWWfc^-QjA@-KK8Y%-jU-dtCUqdsx_<1n@w- zLLbp-I%Kv9qVo6lYNI*t(^GbSOk9J~^5`|nV@PFcvX#*jJleZ4WP6b+6>r*c%*MP^ z4MS>CWQc?(kc6rzlZ>X)1@D0o#3kW@_rb(~2!MAOQH&HA5_=I~%JA3FtiFF4^yn-0 zWCHCZAs!uYYj+$nOJz&{yqr~lSIZWv0?{4(;Ji`8l>3t?5j=U(P0o4o0CuurB;Vab z`R!^yO`!7Mn$Wq~vTBx+gn956%LbQbzM)$RfE=b%7jOiM3M))a+`sW`G!Df!_=9aW ziO!j;YOkKN5)d;|hmkKhONmMI5F$9eIof-u&}qY7U$C^oUgqNU_`I2Ze}1Gs?_R>` z$0p}39X>A9+h5z3eiLT}aA3k;Jn> zJ#t?&Lofd=X}<9)$qxy{S|wpsok)21EgLY{WiMe|YEjQr^@a_#oF(ZA)z8V);a-xq zL`!53Khf6z9AdmJn5C-LYz>|opvpeJ;Bk_DA z5A5|l&dAPS0~<$j3~@WK0hZ>($1)QVYwIX7+$940BeVJa{;xhSg~ckb5;bKT+&PcT zS9S$@#ij9^dpU!LtKv@DI@jDsyEAh>%@Zk|+9X1WW=JjI1qNAe+uJ6Xc$k~fD( zQzp^MJApJwzSJjtfimP;mZS+^EWx!3I`7WL>4`_^ZNvD!N16v8O3C{y5Y?k&)q_zR zwksi{rADjdtbK+X!_H5efT!o1=~@|efd8layixZ1-YjB?y@dSQ`{qb0a|Q<{rW=UU ze1P0vL#C5M&HyW#>CzRzy5vT&s*qJfH?0G zd%S!z^jx470H%=fC)?0l-R>UN)X-Ta9^)rLq`K?22?O2%NgvZC#?-s*lq9u=-Zf>SrVvhleF&bpuG_Z)Cb?y&ETt7@wv#sxx>f zJ{F(6sdP(t2~HU!>(kX%`2IJ2be>ksjPbAMl>Be~_o-JF7m$@w;)(13*LH#qx%G

_KNh%$sK@1&>PxSuz!N!sVF{>my2kuW)A|u^1vy7y-+ z0q!zRTG;+2&_MHtb&<)?C=jH?)?00zyDD%Srvh_n=AgueDmA6Ve*D@{UI+al;1h7f zoZu!_B)+JYpRzT~*fYwr8&nv`bdOmJDX+?E#xt#4?o}9`mm`SG582+*uB~C6d_G=| zj&}Fk$w!iVJJ{MgE`mU`DwORFW{JQzsbsyCcQ8wrljXQ$cgoJKHx1x6Y3>%;zI~ZY zUt0)^X{!G@K>Od9M@tDb`k(dIzf$@Cvfxq`QB;zUlVw_FVm<)+A2k*7pIw*g>G0s! zzZ?FA0{}qy_r8dj7>$Iis2rWV)c^Y-TWve$U;?4%RVnF&6yBhZehO~hdZ1w{Yn~by z)0B-PqCmljt*yCbb96g|Kuy6h-Yv{AzN+yV^dlnTR%&BO^mRI_wj)Tmv1%eJvV0PyHr^x2H zm^1|g61My8Tc8+Yv`2wOLHZ1e3j#(v^NewvRw~vTeYCs8E$citq&t5%H$3-!yTYb_ zKyJd-NLM_%jBR;pNxC*a$}HO}-dPA4w-EwFmMFd@Rkyj)@P`&n!1kaR(RU8XBlZzS zk^p0DFqD)dQCokL_zANH#q{9-=NGKICAXZj1onJjEjugJ=UgVtaK$s2x5{2Pq z2F%l=^jWgTs~)5AGtYu!Nh)-BbFZ9^Q297P|8mYW!3))aW#{Hib6DN+_x#)C=4WP5 zYd2i1UNkOgOk-|y^@m5Hj-Ri}%L>kD1SIG^=xhRE5UvJd9W_2jVJP-~U2u(uz8er2 zN`~mduW%22F~3y45a!eFN}1oU zc=FGo5Xz(#H=NgZhR~AVCS8);X7(fHFe3nuy>JQECw()31tLdJ^NA_9A!a+fL+Jy? z1L1ukwnw+iWxz(M%d8Wf9ECCE7DBz#VkIs@HY4cN{br}zYIXKRzINq#v-$VwZ14A1 z=JW2VpT@7(H;xJ^=b<;{>9Wzu3@zPE551W|3}(^9huo6I3Qw3uyU;PIE^D_Ac{OFR z2#l{_q=I2in&#my5`SZzb@FUx>W-hs zyn{u`c-oU9d81Jfz+VLNg#_Oj1r|v&a{P>OYSJSNM8kk2vcgG#_j_AsV&_Au%pk|i z9+MUuOq^BWExsZT-}x5hTgQzfABC$kELE5A0ZExh^7IXgq`yWrf<%#P(j#d0V=sim zQV^~_p5Zdn$qMx`jpnrTP6O&FDo>G~r5+lfjSdk5X%S;jg>hmnPN}TzCV|&+s~z^G zf~kKTze8txwSH~BYoGP_esLkc;3~F`du7}3BIK57;HC#3)&O#{ar-|HKQ^D;F8ykH zizniyja>6KXqA;vK}Z>mWytG)L!uC8;s?kokh2f(^F@gknfQ05UipdIv!liadva{< z4ZPf$igxl|X9zVPW@W26<^)E*2lh5{={99>(TxN-{0Z-tX$#xDSpndo z6JVdwAxwodVE5;*>OdYR2_RLPpOQuo`2cZk>e~x$@h1Q;K5ZPECAy3laA0&db$|js zR+LE}d-1MW2Qz6H(=QMSTZfX<0Z$ivI+@NaShhB0c!Yyyu^9CF9UL6(Hzw0$Zm68T z)XNF$TlT%2VzI25cZz?;`C8Xy+wX9DsBFL~Z<#~4N#GypoV93ipIAdY%cM(gT)e2v zV$SdgoLBx!?b$q%&%M*{3R&53U+y_H&Jz|;G5|xb5@iN8bx! zp~t&QxZ!~JU1oc z3rCLPsPfX(S<;2YwJx^uaC@g`kQ#wy@LO-H06r24V`kW}tqLTX)wUt{ z(l}v(06~SxnAG&LUm2w*)1`_9WH}5m@D4c8-^;+VM%%GuoH+Ss;?T5-UV<^0?m4o? zwK)g819MvuEjb9EC|Uxs8oHFG67OxaJ|Y4Tk;KoMdM*-(Q%`b!u4ZFpHA$Xe$O7zQ z4~>wKG)<|NJe$IYOWPc@-CdKznYpj7$7*^bJjpFq+B`sgBe@nwXmvgQ4;{>ZrSh=W z^SkID9*O!_F8}kp{Qs3oB{>yEArU27XLsl5q-n=MdgQ;a9%#y67MBV2>KIye1=r5W z`S$(L@QkFIKA$ZS*~B2%Aq1yfY>|ms?3c^b>LKQc)V$okkTTh7NALR+9=@VAmyDK(F82L;Ph_ooD&PO155Z(%`e^|D!}&v;yfu2Yn>9q34sAR`G+pXYpQ zgI2z%O159EuojFA!lz$%PG{hOS2lO#ov*oe#M z6?{medX)^=^no2kEyF=Y>zvIEACVG>0&53j!*;>j&~ag?!MX_1X3?o|+;sN!vHcYN zUAtZCuT07Gg4TpdWt)FHV6 zS~%VC(;~F1 zVAL#AkU4)JhUeLodn_5G6XVxVD36!xr?kRuR58?C=OYW7h>q9)fE_bUh`JaGr(>+{ zblbkdGEfQXW)wMWKE;2}U=lKpe9~F12fw!Y%f8qXKZ5_C`~C08o;d}?r~kmEBrgB} z(*HT~f9GaoXJBUgcjjv?E4R%Ngzp)>dJ|bpdQy`v2wuzgqyloW(UvMnM?^6dlsN5# zG75h{eo5)g-<@Pe_x0@-$e7Z4m46*wrsg(Dk`5JG5nN_+S zZQXd$pe#BiD7;4vL?l<6q#862Mw!83YRvd?M%-fsL^EStm_>S>!9Cvw_0oCg6r@T$ zqMB=FteR`jql6Nr4$|5|Z;|r|?#TywP#&rewdYNN1le4*9sm-xMslJ^WU>yTyG!EY zB;#@7UO1%f zTn{?82L*1f&o^}Ve*ywJokiM9&TJWC`5D&ILL7R)BKi9-`$c}S(C4{0eOx@BWBfy9 zWou`^Qd5tPra{l+A9seAEq<>ncR)G(yc~W`9PRn>bJ;?fF#O&}_m8p98B%Hyj9aD!8I&a>mVMwH@9Q&b{+$`RzJskMzBv3_(Pb^hy#8!Ri4Py zDyy5o-WAjdk;&~17Y`*?NIV=xWnN?fPnR^G9r&TJkFh7181+uiZ?TP%D=Qr zcJ6hPPLCtXG06pB^be%xoHRz!bSkD7jpNMD5vMn@kXp#18f1v=Yj5dRl}e=G?Xk_e zanmrn@)6r-itsIp{Y|+K$uYsTP%?cokQVE_6)NB+KEL^d^S`I4R;Hu_zonJ)Lyr?W zXiqLwv-_YM9(66g?SuR5cU|-IExygj&4$9iPWWjmx(_q_(%lg~YG9DY6>t0J(TPqJ z%)TYMBV?`5-e-AV@5u4CdA(QVcYpH*g{*}Fr01H=e?{=5oBigpP?eY`tNEJ`11);KFaO%7L5pwA0hk(ar7o$djfLJra!u z&z~L|&09%yYO!#}GUKpDGF^$1gsUC+)w`C2Raj%5LUmrFPVK9~+gdhGeFd>8j^(?5 z!fD?x011a9fZc|%bm7Upb7>FCUNFtjAEBVrNWY`h$f3P+2<1VZAssmz za)>Q0V$d}^?6H=iT|u%Mu%Tenhw?zuJbjOwn@Pv3-!kSfu!XADe;Bgipc>hO{& zvpXU?=p4Ci3jQG{_Aw{|NLBz{VUxKYu%g^mx~?5nE~s_Gz+1{gGCIv`N*VJFS8woB0P9|KS;EN5mR)CJT$$gEX9^Y@-ZxWRWf94x@QRVME!K zv)ah^YVwR~<*L66dnf8-BCU4?F?$Q4Om<%g3D&WQx@~1s3NVS!*~3^@T{X8;R5*)M zpt(ez)!5w{%E+duK~kJka$Q&>k<=feF2#kngjJJo#3<6shPs(Vsf}K4oLe9nB?8f* zrBnuM2e+{=1?&CfbA~vcJfpNLO7iAuk^B)xo=eLrbZxUPvh{8vhoh7cEMCO1m7}~# zC9!zjPH0cH3$(ujXQs+a(~>$s*)g@1(GJQjpHN?hA&wr4+y~^W&X=a~DNyZLS|-2i zP|h+)KY?`_lnz4|ElH)`M_@2pU(D>z>WSNL`5-8(SH#w!r!j^c^KTm+J!vJ5ST5a7 zDl&+ds5y?%CzL|diQJ^|q*hUMJY5gE5~f-*E-+-_H1r9@y%5#eHYsYxkevW8aqgKnHF*D<%LvRdS!5` zPJ5OEGg!72F4R+Wx{3uCkf_-BFVB{Sr^7#gL3ZhvKnV3VX4R7N!WSt75k6{7r$*4{_#2vTaUODQJ~KtQ(11( z(Y5&DFRl-(GzMq#F;<_z8cr!Pjo$;R{-;*^^wFU0h8cQuT&8I>|I>Q@-PuWE^wbz7 zo4;r=K_eRPEj=VL1nv2avu3}9JDYMWy;cmBFeZ&5Xvg&&4kqUqPL+CY6|>*(BGq&3 zxw5N@O_JMTZCt41zH>lD#0f<>fo>tuE;T>C5`SgZqTK?7cA6FVEjm*skwHns z%z{BOKx?2?Fgp}qD!&nrQ3B45IBd-^^+2xI5L$Dkn#v+beEAR>d%}Xg zOOmsRe&8XQ5cA%hbTY{d=209O!&Y=R&hc|JKswWC^?9-Zw0!jLME-L$`-c906vFKl zlADtj7=!xsC(u`7ZaMHeP`{J-%tByHS_zR4RoF*}nLGlFWWEH|C6dZ*x2=%=N3BRem7v>nRGq{(RdWQytV z`bh!XY6Px1o4v~x(ck9LxOj#fi=p&({iGS{YXsm?{AxgNN5xdN4JE0*(;O>0yc$I*J+rc{4UdL6M#gd9}HfNBdXo;rT^vQ5YFRXw4C zL@;KH-4YR;SJGZl0e?pqClG8pp_k@Vi zxvV0*%zc|LUfCNXUP>*;2DL}-I$#E>LcZm6VVqQ`F=hZ?*+q$jgYYBo9r61wZ0YDy zr;R<2ni$#0Ct1IXgVs^mGiuCZrV82fJ8!M5xn-ydeR;RTBB(~IbOqY9S~TS`6Aw)^ z+xH05Hf$xdNxH5z5?dzyH0BuysR4B-SNVUwnxNEex}%xRip`Q1Em@~+vc*E;du!1F zj7W?+o(=+RR|h@;qzHXi55U?zs`b6SE#pXt@wJc-Sq|&-sT}7RuUi8=#{7pRYHjBq zSJEtkTlGlVQb~U^tD{Pc5uhZ?6BMcYwZM&&$&U6pJIdqfR8)b>NUkCPc@{b(fi`HP9i`Sm*(qjD1!*0i2($pyh$D zM$_KW#l$cchvWlM{QII7(-@dXp%NfU-enld-?)(5!W9x%pSM_ybW8RD#8XF%q?+lu zu{*l;mkQ_A&hBaoZ9&G%n|7C$=&L@!NRhp-$x@_CqvdeMLSZ-R#TryCIvx0zu`XC>ltuFGi949?m^A+gd z0yZT5A?pJ_xpc|}bo*z07^}Me#{zDe1-Yu*HA)R$d?FdC-tQm|*vbG6QldJDrc_g4 zgkj8$Wn&%ZcyJph1hXDa3^@_5m)OdBtB!E3!HPwT<2Wd&g&}6Ig04nr1@Mm_;4K&ta8Pd#8-eL{@1bV&Oonvs~;2 z=i3;ANa5?B(edwhPLK|#gfydx9||B-z1t;In$EWe7-VVP!)#^mex?J!rV$Ie^xnea z-3={u!sK<#2r!~>PqwX%Nyc^6p!L2S+=uP{dH||JIt1^<8CMB}mwDMxTJ>iWCaT|; z{0$T!RO8wj%OvNvvucjhwa`gSIe#EwNb5`_Y&ln{+xrC z&#p!8zv>oaw;5#AdEDmHiG%(6d9yck`^tHPAY3dQFjTLjSM=aov6<5~vMXw>tOLp0 zU%HeX5)Nk3HoHcq4pDH>T&b>=4Y0>m+8@ZT{L`@H*QC6?bhqInl~qr_j7uc zbk%0}<*J~oo7H6Dy_e|l+T^s9u&bJOEA%zWXV>q4@Gk%H0%i7+$#?&h;Y)u20MP#b z@B(@^21ft5foxS9rwvi~pBlY-Azoo-pv}FZq>&s%G%{(NOd#8f7F_Q<&~0JyjLt;* z_vdXMND$WIbW1Se?Q7mvy4jlx6lo(p(MQ%IQ&lUD+PvNmcP?y!YOrr*g6<6bn7$gB zWlDy7?Qr5arL^Mtr7`ve_=#{tOOc6GhXzGQTy%T7$~ksk4=pQfwhVe3=P1lbTiyPS*q3_bQ*2xDY%nDB!jRQ_0|zYv@`>BaJEAu3q|%EEbbsT<@R5-@?x z_=LaYs&)8&rq+LGisNIUr2X~fX_$wYJ|FH+V0jILC4-NSh>6W=j**F?@z0EP=K(+H z4bWBF4F81u1+_;(s?lHN8I!m0^gqGu1gN-uE@EB-$Iy7kmmTZiM{jYX7=$N2xGQG+aQ0#goSEkFK8FU&&Yb_2BbEx!*O{UhONb;+0+D8={3kWj5C+KY44RZH zntz|`yotUEAaq{F+I9{VYU{w^2mi?KFLU#7+s5GkwVmt5alPb!H!O^Mf*Z0lEtCPg zpNGJ>*gEx+j3u2E!~>F~Dz=DVB*@Y-S^I4CQol;QQ8~TwWXAPv`#!8QF2Z^_8dOY& zAW=XZW&D)wuWkE~mEZ}_g^lE0AGs4O@$g*GjwBx7TStgvVi$L)bq(WADi-WHqAcCCCz*#BlY)Qd&4)ck!<=f|KPocb|LOTMBB$?Rwms z;%ZZK!8XH$s!r#%wY6xrQF%d#`h;T~FRw|qL5xZ6Zfo-g#TdXx{&f0KnT+<#u&>&V zh(S}98wi`|(Gl2!D=X}Vhq$A7-LC#ww&hvNUOmvcl*Vo)tz8}2__U2`*>U9Z$?A8f zCi&^@SFiEkNFobHpO(49 zPVli9tP^FcqY@7i3JPy0;R~8J3D3Z-TixEbTH?yF=IYgPyCHq$;$n0%X)(*5b$GAc zI_z44{EOJ;lc#ec>|l7_(n-LiS@KiYIM&u=ZRMTlcN4<;s+F#6u85-XQ6u*%Dd1(f ztc7=!ytjEM<4>lm0bFz=BGq;PiyYS5R_cqfga=`rrZj}~O zt6p@fUEd|+2)If4H1aeZ$JiKZI$v7RBhi5XN+vp-H(Rjj$`E3u%8uDW^}cl?A;cm7 zs&wh`tu4!^YC&5mk!P!nmpA2j%iG<%>efYb!$AE**VBxNlo;lGS0v3)tz5l5Ig>q> zR)bNbdcHO(iPC(~r(^0&wLd8#T+npE6SqTTW_-c*`pAH2L=QZn*YV-xO3{y`b~})q z>EHUeV|JOs|1vU&dl6s8!9G$4jTB;zP0I!}{6- zA|hk`Z@$AoRh0et&+JHtN(2_j$_z`z%>N~W2*?qzrTE7yvy1&NzV|=!l!=*v{@?d; zTKn@Kck1|~;|F?y-Ks1&zQ&d}as!GQshxYW{}G>=m4*EmPF_$Zo*7`m(DSqRbrVz^ z2+>4Q!Q1OxofNIJvGMWEG-P8OZO5E<#thADry(N?L`=`UR(1F5?xfny@t|816@ynb z^)dZ)M0D6vVVW+Y|9}zCBk|Z;`S?-eT;|b$fm~I+>NHX%UQb(9R}Rl>iR>WwBgwlU+jIlOLt|B&w zkn2(Pv6u~Ws*4co)I{ZI&?MQz;~2*D(Cl%bbHIZ%1r3_U?6B&tVsfgo;h^H_1nG4@ z^LE!`*SN6?UD{!6A35}$70;xR@eyooJtir2eGN;K(N(~b!+p_yuh?l0XRYwH`?8As ze#FRX115hln{1tt`{f}$(~m>XzjIQ+ z{*}+mFH*NRrpNE|vDb(eIjD5qMOA5{7IhIjxZj8&O@+!H1ZX#X^`llDVVJJh=l#VD z4pCCC=XxJ-E<>}*E>icH%toij~`%;asi=h~B&aQ9rI5Hl&ypBnTZjd0%l~F5bT+ z{L=5n2`KBfb zyB>f>p(~@0m8RbH9cih{=M8~|D@-AklvL%(*WXUGjhPOt9lz~5GC294r|M`~{Y({w zg@q2R_o$EFs@bXUw;HqEJiJ^FcLM|oQ=F!}lUZ8^Q}P_#DNq(LD*U8EQ5_ZGO4pVnfr8E(vOI)-=j`^VHBb>wdrM zZB&S4D_z`htN~xd#)TuoT`w22orpxEyNKO!lI!*QF7$YuO!W4CfEl{qP;?1#d_dLo zX7Ru88LuGgwMC_ku%aHEeLTLhlut?xvZz(ikDWg}oF1xv$nAXPX*JoUy{?7h7u*5% z3OZceu=K2cruXcOXZm(hSGD?fMRW$8+TzxP(9G| zsXyh;lzvcvz1{Bfc`<0eH-2rGV$0>wbklRC1)BCwi3L#P@2up?+rp2C8p+EZ==h?KGWh;+8^I`virWWcy_F&Kg1JA9Z~3G>>q zw*PzQ)82(SB^zN)j|*L@A+*W6!QSR&t({4-ADVm6WX)rv&~3xL5svOGi?)K-A%sW&l+VT7H-n1XLy%;oS`T z4tCqi)-hC(!re}(T<`BD?#G-Ix*T`8ZqMiUcA=X`r>;ip?A+JZsups(6B8rmRhka^ znp?2jYO@rNWUWRmU6{s#?=BjLqw*X-sSXyi)g85Hn(dQ!&&feix#;H>LcPZI$p*l1 zkL-R9y|>uH>7+)>W^?l+FsqGB{qr@#73^-h``2@v6pEbwh`T3K zpS*sD&&50U(Tb0}l)J|hj9{I)7G3be`;VogkstqB+A<$8H5!DqG#OiJ9B$`wSko0e zHJx_9{peE&1D&%f>`!QDEz0NF7Y0@f@z{>i$`Mu6QLY#hr{J5MWS5(1K$=i8*GL?z z?JE_xmx*cJZMMw@-D*y0RkqxN+DVze$tu&a-x6*P%g19iXUmIJQ$4ZmqMa#d?(d?7 z>(d1aXY8H)3?Klu`mQcmMmWS9SJhx+(`|#p({B}Cb(yrQl`Y{*T$+t-|0W3YXmO)f z7i+KW|D0v)C!XT2NBMM3m9Bq`_ip9)Zhz|Wm|YircAfO>A~rF7d6vN{jAnygCRXj@ zKN4m`M*p$1ST?AUI>$vJVCw`e1OVsh)m(dM^Hy!X+f}h`C33|H!|_Z)?aY{X;kCCu zKwWUH{EgNfy^Qy%_}DSrp+(i?)Uq==L>k+GXbWU{;JHLJT6Vyoj(qFFD&dx09gE0Iu*)vJlIM>I=xgp@MiXObglGXm)GIm zRrT!d$h+j3{~^+qt|S{B-9++UYYs~D{vh#@2*aF%#EW5)4az?tM2Fx_(#Q)5IJ=^M zdz_^|$i@ljbj>r;8_AN4hY^biO$^8UL<-~+;K!H-BD`mM3esmh@h(%&8zDUGnGhuS z>cew!ZCh_1G>Y*@yS}R1Xdd+cNS}R!=SE7A843e2%7xc6j+g*cSdV+q7bn3p&VM)# zcJfP$7V9@>P7;!AH8cNNS6k+tf(;tY%3*5Qg?+u^56;cNClnADA_?Eq4CY)WHjZB@ z4_o);@}5GtQ~bP^FLKCupbsmE1Kl7;9SOLnkU1o0p?LjkT+YmBs6DT|C=OraV>3X^ zm^3``#k7`>hmHvvZ%5pw1R0Ao9`=-O7bY!U2AnUH{Gm`rM2V8m3~Pcsx*nmoE{2EmQcNTMM6B!uHZmZ-OWe|BdhI}q&J{OmDkL%uM{Z+f)VdC}ay=-h;kGM${Go(5u zCdhrAVdE~q>DQ2L|Vd%dZg0e1{szbkvx2Qs2B-EG0k{*(el3xnncl!Us9Rtqzo50 z$o7QsSz-#3Kqm5NB(3=x*Y(;!NuiJhd4wR#62d}9#}>o@A5c*aM;1V)dGLU3G8;%J zAoWN2^9V8H>B6F;HYqZN!UQHq{JOy^?$rVS3f!Vw$XjB?Da45HIE=h}hQ57DCq~k@ zt=?yVEQu-F_ny%DKOg2s~dU*fC$t`kkm&u41rTN^5>R5;0##=!ucYG^2sYNB88#Z>#wZ(KOvL|S&eu-N8!>Z? zO_MQ(J$MMxr7$dURvU-sgXGzlIxavGVv9{09tU!3jXvmaxiqMBIzE@GCdr*c5iT?V zvI!(VLQ7TrCJ2NzX%cH;Jvs%Fq%tM|X}mfKy%&b%ekl}3G64l6oL&YqY*S40LLdPB z*y(S{UVSZa4|t1417qJbJ_xm=O(dsb+7SS_u@c2%Fe8KzBk(1U>vcK@F&o1&sIufMvdx>=x_${pxpfc9cK4 zMjL=0fL=$WWjzRIM6{reM{BPfjzi?qN}tm`cN7mY<$3p(;h)4mKo`TnSx>WCDj6=TCn0L|Ai6 zDPW)B=ZixVA}b<4ml1r{_sI`drQ%Z_+&Kh72MLOc22T}_tqX-fbBZr}Zd&A{I z*~oJR!2t4vAUoxf1}4(iK?M++!@ncOEAmf~|1p^oJQNFrxIXNz?!`wlRff=pRv-|8sLL48 zCM??3E>sqV7GN=ssxI0Ld+8N>)#F{MF*y7;+!!v~k{Z|fn*93xv$q;_HtHAG!yDK0 z6ZDh_eE!Sa(WNI0F7Z5PbCbFx5X#r_d)5-Vw^A#FM_hSkuyiP-j)V zwIT97gXZbQ*4BAI;Dw#HskMkF_zkeVlko$80e<$f@N(M=ksN2+OKSym4=!Ni@c?q@EPT|D*bdFmuMCD5EC%sYvUPo zs>25{8@jRQ%XLM2u=1n@yo#sYwt~E7mi4Gkj3>WGST(1a^|y6F_qB4O=QXiY?bC^A zQI~wjA^A;JONK_!>vYj2dZmc&Go@3bJLibdd)H`5*C?s$XRE|C*8OA}t%b{wemDn% zIAPX4uES)sk%lp4w0`sayK%rOgLj3E{1V2BGk49la3Xr^L)Y5VE~dNpce}^KXG_6Y z2$GpUd@fJKvRcvWapwhxJ=vD-u+t2W74G5$oU1-Gpw3!~mAL{(@Dr^U@RSjV>VhIVHU3}hq?u$UFF_4{nd1f7}dqn;Jv zuuozZ5?f-Hnm0^hGa^M7+8w%Eq&JglM?II0z`6d0dSAEwGH)N5z~qa*?XC4-9^X>e zw{54XWY~k}p-JziX*%2vq`D1U?WY!}{2iA?um#>fCOeL^_kH-c`6re6Ax(E3Mtsqv zLE%BA6%sPHSF^RY9YyP!=@gcv3w7p>i+|r%QH876D#ewEP6uv4-#a_C#-TohP+li{9rvMe zH$&F^bFg5w{J7oVnr+He7|i4xe(>dU6PuycqN)+b*!owcb)W;;Fik~8b!njd!62rw zd9OH2&!lx@X>w5~U3-$JZ-&myjBI7Ag;7V!SxxqHJ80+we6bZ+S)QmE>qCyJ!7+5o z6}Jav*jjyiPq23}E`|tiHE_M;1FL=STPMJJ@v87UzRW4i(>VLk$I6qheF)ah_lEQU z=Bf4FInH5=SFhHGKa`k@=nd0)tFTkdrd{Q;POG)UX-hNori&|PE@5OF-OcWJ9(!$s zc{0Risg$L!=5rNmWb@Qci;a|dzW8<<(@t=4<0f_W$uzN_x)rCOMrT6coTppi_Jglw zqf5$a)o=2lio9~*XBWix;&+Qjaku$>S=5Z{boekXhhsONzv!-?p z(sfLmt;Bvy+)eTc;6X=w1?PeNcjR+n@ikUg1?Q>uHs^th+kNtNI(28Zrt2M5=O^tt z^=3o6?fA+6Jxaac>eh<-ulx4#ulx3&Zu$QsB>w*r%X+R>cK-=;Rix}T+2MOW)u6K? zyW)0bH_bB*yG0W)JurO#AG+QtNR)8t(r(+fZQHhO+qSLU-MekuwyoW^ZJX0)Zf3so z&wo)-5p`LS@mAK#mCtH|h$1Q`oggCm3na<8)VqTz6}J`p_c}kEcM)8g$h_yx?D*go zLI@SI9g3jU@j>?rf^2yB$_8$lOvY*YZh=fHNE_NUL9=hq-gl2lyt|6w8tAv$nJ~Wxf)=nHVIUd_bUt zau(~BbvY|ovEJ7t16-jDM~F=DK?0+4n5h*f1eC0>Wxj62$7nxDx;>^1EYqP*rxvzs z)mLYeW;aN>Ml(v1PFdzyDnthh4u*IkO40*FI*2gb*@&SMqpw{^N38s#Zf|`T`muEt zRpG*bjC%kP;_MPRnXofNbN_>49wi0GY4CvO2@%kC@4@5y&W$mAZyMJJPp)(@^KC2F zB09ETw`Y$uQNz4kz~Hbfy$O^_DBz;;?3CCG<_Awd_QX8^l?(r${frqiM{o830@>i1 z5091(Y$=1L40&r~Bbq^P1d8~Nx)o}tm9Zd^UAly9`or&TGC?Ct5XeT9EmZp|0ezU# zWK`}80Ha35;e=^KOD7Uhy(5g0CO!<3Pp(EL=+*PVfw48IGm-)~Eu+kbaHU%93caF> zP3V@kv!#2~)84|7ZTT7HBY3qPxas;?i((zGQ{|FT^>REawW&??_j|BiI8{{>Y2apU zIvb=k{F*CdvHPc)wVdH|ioOfy^)p=i?!92@-~Qfec`$yAV>IuEW=*t|OuXb~wMtQ* zuuntZ+({}}_+BhQ*xT?m`j*MMwyrQ(X=goW??76GDhur0gjTjASvulC@wxm|vJ4xC zTq0GE>+<&L=FI~^#$FJ?#*bgg&TAK5FYN;MrG5`P_xy4&F5RIT+_J+5&B0ofdnjXo@3 zi+&|{giKRJ>aRAZbO4Gi=Cc|cth&cD!zYLH;L4qz^{k3Im~N>^J7+a4{65GfuF2}m z-J)1hW{Xl2P|WcDR>7awtJXo`0vu`n(;ZH-DUtwS%mnT5q+agg?Y}zuDqTM&Mk(hX zmbxO-&^!yuKz)4d;@4z37OeS1|Jtfk@nY3T)HXG4+Iu!{IWex9K9mz&y|okOxMy9o zp+2eHF!P&jIaI^ozhdI{Ys7e)7T3&;92H|7%1A4zD;A z`n$bAM*jagNf?02+XlxtH(cdGNW7M6q3<>w3eT*WHrGe{`Nm{v@^Z(oHwFz3^srZA-q_?{zGP z*Vw#uGHCQ^$!KO>OKW?p3O#kw!I@RIYI|nEt0S#;)dnlo&@+6n7MZ;m+z-yG-d|7cSDjWk>8lT||F$o;CJntlo-a5Ver*Z0TO*0!3XJ|m z14@h5;!|rKP3fhb%~BcHQMppo6&fUp**Q>oeTFr)A42X(CW#1 zuVJ)i-p|KL@@M7rRMWKnF^*uyo09S=^-xfWlH?`TaT+9Su3KWuTf;)Y4@bbBK$+aS zTGRFc^pch?_82+5W|LY6vjxw>Uaiev#lDWrw^!Dj84bEqH`$9jGXU6PZD-r>^P)DF zuR*={bL9K^{Od!#*XQ#}Um&{Apw)+#xAg33+6HQ5))Djgg4+TFjKA0O=e?HW?wsC! zr~B&_J2>3W=i@Gw9iOib-{1G2UVWqb4S$dC*V$I(%jM<5N25M}7pGwaI;E<^xBs20 z?RJ>3lVsdgO_zT*-V~h<&Zz_(w8HhY>|L-9Y@>OhdY5i2AP^PeAs z{5!EH90h4yFvuPUPsoASJQ_}d6s)^P>wEC`yR4701g81`AmVOr&a?QGCr%2MEU^bt zmSp`|Jd_a1Jd(4Au`n`)goEg>OH?r9Lu_Y(?hgJiJB{P~5^_2TSr4XQ2ishOrR=&U z(|}zu8q?Or-LP$(+_g2pZRRQB#hTL)Q5^@4bG7x5A(rA%9V17Khp1(#K;__*ajiwFB_ zBF)^Q%??&u2InE%5|1UDIA*=gg^K`^KBczKnJ~L^#)?)L!tNqn4T{7{fnLZjlpL7cml>G ztDq+26rXNxsX&AzHKitj`Es&A#WbD9WIWoloy|pC+hp2L+Fv7OtTHrYww0cBa_}y_ z%Eqc!8!2*iV+oh%-!(J|Cre;;V4OXrN4DR<;62*v3~4M2$sQ{Oa;@zc;!121h^)Ld z3DVa|u{nVim3>(CY6}I?0`X&tOb9b0byAVWlF$r3mqF;Wr3x5(aqaitER2F>+akAW zGepo|#__1Qc&&o-zmF6KF26VX(Z8~u_|+o^&VF-VzlPGp9vu8mB2U=-zl^tK+;PC8 z9l^EEGkjQfK~7C*OzsKjIY}-|(IVDsjd@{n&L)HLCQ zMJvi0=R0Jqi8==Qn;z&OlQVEiUBCr(MZnr;&T0Pz9B6W0hfvp?mz3_!q{uy}xTJR3 zSkGIkGFxe&J3$syL*iN;1&wCuh6`)w@R;lbnjW)>PP5KADHloM3?#D#PL?R2hfrYH zH%Blz&-s`X9vM&tDQ~xDU*()2#D=VNC~#?GS2uE6lb9>DAD?FCaB*R09S;Y~+BwBD zNMgpimFb>T?VO#N-eFzQ7ZR0VWXJ(l6Tc?nM9DEu=R$-5_Ej$7$Ot#$E=b%{dW6t& z*uTpCt8zXf%e11EvT3Be185Hf@5>E0u$*=RP7gcT=eCQnsy}3kAj+;DZ$s&zm4AdD z3=D;6W|6V`mXYJ$C77I%Qq7Rtv`PnJU#>j0{=-=l@3D)PPt{i!ozU38JiA!BF9ZQg zto4TDal#U0A7kkV&FVO)nwSkGIC)%nNt-H%k}LWPk=l<*6U}uafw+aAHrv6wT!C8=%5!a!s%R86M_167gl3G-nFuQ(H)kl2&q@!=v<)~; zYPI(;<3^uwhgGg_Oi@0n+Xn$`VWpXTW|LbRMttC#P~g^ImIKG+T3SiZzz~_8gA31w zp{)SdepjOEr}x?=oom!{-L-1?-xoOnZWD zL-*Pvh#Qy&BHJoW9wIz>8%ra;F@hGJr2&0btV(JR&eMUVr|*eyvtakRxvBX?65FJ# zU-=-fM8bB|#+o@8E4zW{H(W3*_xc#)yJb{52vU05(vnA{ex@L2>m`W-7)8Y2fwGE?IBN$Ijk zFyNE_dA{31`Nkrnr84W2P6kYwtunI60nc|U!blWNTD96n_D_c;sZ zS^_OZ*oC1OslP&S0kSfRYUD_*CDH|W^3P2TT73f4n!f@?OZF@&*AN;i<5>orrRyb< zL%_8a_eDEEVi8G^#bo2UY9tH zV-*EZbDo7D(1b2aJ2#4wDlI53Mw%Maeov&8DHK$>=+7{Tv&6-m4g-N0VdGOyRdgAJ zDBdR8{lHyy2!cFjmMXfOW zT9lJPg&-~B(lC`_lwiTG(&8{3-n^OUpx9gvb+AJeNus)7K9>elAO5^ho9LY|Q9*)u z=m)@gTXh&X3>D!7dGaWOl5=cDo`)v%iT4#06loS+)Ql$pD2ttWM*#|!{dvQ`Y!Sin z<@ACWECHHXGp7cVng&sMr+VRB6+O%;-+G*Rs-)B)sMGw=bE>6Elt6tKnnxhb92LD$ zBpVn7K-T()ZE)4*-SL9pgPJ_J&;WWeD8MuTMji0`@}T6n2Q6zH3@q!3G<2@bN1a_2qD2qs) zw;^!I%xY01F9K1bJB8OyC>!fPbjMOy6iKt&yUB1CkOE1^`31qdT`_qvZWa_{FqcAR z)-E|%G-g$BL{--fzUvRFz$c;_CPoq^B#((9fTaMl7!WadAuGmx_2txTYT@I|#%8Jj z3Aebi^uRdIH>MHeW0w|W*9;$d=NvJ{SZRmCJrcIIE<(5x7@Ca89FGLB?oipy>he@K zg=J(`<;;=rxa!5w7h%g{5uw->JBKl^VZ`F+2APF;;7!65cF6Ndp;v4t#4hp3Sx+YGHD_Fz{kqhbKlhCN3-Gc013Y{>C zENOv&y_GSL-vd_Hw(;btghvr-=9}ij8uf|fpH(HeLSXNfhs99$Mj?u1#{oEtlFxw| zJNqfU!P)i~<|JoiJ(Q(qN$+xLuj)7+U@Y22)FCd91Zs}5EjgZW6uk66d(x0snX#6muJPspi|Zd^XG*g(CNK>^7PfdaRJ zZIZPwON84693%nf1s!KNg<|#be~DzVf_d*%;!RNe$(^JK>g!Xe)#|s`&1D#QfgbtI~(9Khi8dZpfBT} zA6fRJ>w3sm#C`wQ!CCp((`5$x>jYTK^77T_^T()p>*s}`bJRdy_$S5Gl$GT$c&V1- z$;jKUZsApKMLu%tJ9ByDAQ$6j(D6C(E}X>s?B^+{n+xCdQqz7e?Tgv@QeJjyIM{gT zIUHY)fpOI`hueOXeumvkzb}Mrw+y|xM|Z|s2;_qM*%8ypaOKFWzRTLIo9|=sdpqPP zV;i9hJ64cSy0!87;rsk_>q3^pU2$84_s}LJq^FR<(D5J+VBpNhSBSsLpDbCe$(tv$ zW~I?-Q=E~nDDuTJ#RVAh@DJmMI2%$Oo;x5XUz3$*gPzTdaWt>L9D6cvrrzY_oX9x_oJAS%f9)9}q0V7HtuDciGH73r)4$jsm z-~yS2^lG#}^Scv8Iu9=?$`e75pm#(7losx36DQ}kqK{|Y&%2Koqn}JUJuvO50^CJi zV{bwawxQ<_T_IvjtN3-U2JE)?<#tpIKC^XiXDb}ejAMOBPsnWJ8K+}usi4T&m1^ys z@EQ2gp+E}uL|f{z$mhqwP_WFUpx^h0Xl;rxlN{p54m}-Qx_$H_pcq!TA;|u-Pz3M2 zSw`qmMR%~876jFN;rG>&So&3FPwJF8_5&_!M=HI3gS7_xZc=HgY4dki z#<#m?KHJ$GguI=-ffOe0%JBYc{S!t?uxrOOn^~dX(??J}^wp%aC1a3ij4U9oj?umg z2JESNOn_u-o_YR>(X}!pZ>Ap!GFqYJNzabf3i+2!LJteC;Q$;#w`p~_PtHu~#lKwf zN~TI=F+&eP`TdQ+P=_bJ0PUH_8jLBlVx{yQx6$Q zuW1;KriuG97NxtxhknX5ms^%1VM&fAj4`a?gP+gfXybEqU^03MkMwFNYm`q99+Tvn z9-f>~gZJ0SHv;T+rLO9s!yZm-ji#y%@zCv!mI$0zUqt`jKLdAr$G-n~ZkGev-2W{r z2Ul-zW&8!3C-e7lE*TCDX0k^&E zo`5~VMs2(R(}4%HUD{^IyG3i?j~eydG;a)#mY-b8)0fF#FRc%`=Bf_Dyf3!Qv7S0V z{LrAV(eGeg6`q1ynVa&hzq@}r%u1@aKG5e1vH*NH?$!54;xgs?RrZx~;|&@{u&sre zi92x>dFJg#1GD1AnfS`!i`?WV0*i)IK3-XIQOjC6GkU)j4T%>$f!&94Gb>)IVho*0Vmv6_#XV5gBP(oim-#fM*ZVL23LG_0b>>v==&i(_1BWi zq#1RbUHHsk6U5yZzw+os9+qmagk>)4{Zh~uNa6dxzzhLTJhBkK)Qhs;3-kXY0sn8| zhKZH!SGzHl@3;D;u62J^Sr&il!GXU{- zzdVu@`*^M4@1m+*6wstD`DzNboCK`C^M#)qHrmh*O3pz}U9xE3gm7fh2zni`>_)_? z4)kM}1KD2G&Kzlq7siRV?^mjt3H3J7J6C;%c1TA5YKg%B=6*5_dAf zjX68~2$8AL_55GpwEyg{T+g(-aR0tK6`=p$*T?$5rLRzhNxK0Cgsu;2wH}rP0;>Qq zTBMVl3aLY|n+elwTw064F3(u#dMYoqNNm`x>Es=WmvG?K;=bSg#ymY}=xmL;e|OI2oV>sodZ zGh2q(L=+)9_y(DPb_Mphmc0pq#79r}C=%ncMHrg#2v1j`i8oz@hxW3TT{l|y@Z4`A zl&$G6Y$}XnhQAPt8wyE0%lOZ@31!0;c3pe8k^+zSr*_!yzcz;cx^PFhLjV8>{SGYt z!{7OzGYdvemfu=!*6?=TXhZzb&jnH~LB1!V1d^C!%Jir>yAhG32;)7&o#E&kSl~8=E#J&}L%b|IrTp!_N6eBzE{#Z2mK1IZw zL-(5F$2LN;(u3U6hZqU1%Jig_8a%ISg=iXfZfORo2GEB7U z5JoO}Q37Dt$ZT=!1d9UPo*8lV07cG)Uu0f%dIKSLboG4rFlHu9Zct(-j9YlQ^L{%q zXC#iNo*lZm%@H%`TcCiy5!XYRHW3m4{?o0WO8WQ0z>BHT0wV#k0JU)FTo2l*Bhmcy zJ?adAam^HoJ}<5zK&LSMS5~2`>QZC&ZK!+>{_jsM)pe~VIY)r zmaNn(r3QX6FJPakiyO}f=q z{U!~$C6gnet2vK-E8pg3oPWCR&jZja?J^eg8yP=h?N_aC?~Yw8?WrOjh8{P}#}~s8 z`8TRcxBA>u&wH(H%gD1dYM{m~O|JR=j*jM>6(|o0v9UQ9Es#HVr?4B^C&nkm?Iqs( z%q=1oyl^-ii6A&!Bk8cssL>xKhNCyKSV6_k7jYt^L4|x4HCqQ4QX$WLCEiwI>wc~HTWFw#$yFcku6Ky#8{X@yV zZII5D=_8wUYLml_)dj)u%QHwm8y}5VgV(&*)4>B6KjGKx0r>U=OwR;Wr;K>GrLIk3 zqX7F`vpCwF7UuYyK$$denY11fi=F(aWG3(fCE)xPsY-7d>I#svSwpGBK)C9V@}kbs zqCw0mLk0;6(($mkVv=gUk`lNwA1`l*C1G`*$8q5k9!yC2u+fbhEI74sh#L2E*<~B8 z=@MHvIHN9u!eFt?bXjf2t9~2?*vMK74?zu3Lv#?@&QpLgq#!herhq(95LyBYNI_|c z_iWa!TWV&3(8$O;{^RB7qJK_3Y(#)ZOHguN9DEg^48i&6)Vo%3rnBBiSnAG{#%xSK z$#l$l5V-A&$)n&*ao#@qL-(x{{0(=Qdv}(vdU}KG*+}~8YHpA zKY!V5n)|bqMw8AmVLb^QR&%69ZvN=vAr(A(1^%q*F(Oavcb=Q&bp5=>aQgu)MH}r+ z9{fGacR+XXG3z$CLh3Os#3F9>?SDq?CY+ui`lP;G=v1bOxvWK1<76IYgZ$=WXX1nD z1|c%#3lCYvcJ}fV)yB3HYW3;7tN~LwC>+J@(Yg@@a19&)vx2?mora&xVX_>GzFo7M z=%XNhuuqs;q#5@c54sqDw~RUtcsLv~0xf;UyggRb%bMyku}oHXn0RVd3dBN9k!`Gc zo1utRy%Im|B;bn}P$VYKOSg~-4!%{b<5lQO^>ilUWynKEoKsm@*AsKW36a5AZkL#XBb>~9HKaH-&e4y@FGIGLa0!YepovMu;WK`X=1=;AfmBw zSHo0c={4H3UK&Z!u=<;A**;P#8%k>5omNsACErs610_ajDou}7n8b+^nma$fg;7} zn2Ul+mBfuj|RTQ#>!G#vIV{elInbe(lqWrL!X~Nbvn3P7%`L z@@IciNRhLWo+Msa^EvC6`qw5cft+Cc6`gBkxZydFZl9*{QDekXHNf(LsC4mPBhkf> zqxY8IMuisR|FyOEpK<84#)jR&7^3fqI*JVe0MsVoLtQTbk zsiVlRw_^Ob>zMEd;gA>&A_zqA9)6u)Dx1hsm&TiHj6}0PalGTHykcS6?m7`0uFGJt zSd*!$DOQ(DUVd9&PqA3&q*3C*mI)3RDIL3bfdof%#^th|B@5JUKU7erXXQ^8h3Uzl zLx$M4MV7+ZEXjsaY?SR9Yscc8MCs$QILl!UFOQ-nCdh`XB z0Ri`+)IkaBs6j)g8<7raOAg`{NQ!x2dO4z6erXm@NX~u(`k2$W2TUO7XtRC+@uV15 zQm&3@N}P%5@lJS`0$^NDKJv8}&UEH~OM+G7dkKV*qh12OA%TO82s+0QryPfEa7y$G zI^vA_3e^$W;1zb$CcoHSJ%Z;0hl$e>IiRkWoEv5R1)Al-bp2xp8|j^FJ>QS-K~&tX z&XOBAIy$&*UEQA)+jhpPCFg9>`|FaP(LhY&LlDV{2#&su!stQ!wS(Qvg~o+S>wPHP zT)l4Yj;^AE7jzea6L56((s2A4IJ(o%!Qr=$$wDf}UI3}aALwQ*spfV9;B1Z@d~;5H zXKy31V|a@x+atF2^-N~E@Nza;{vF`By;nnqM3^-!*qaY64=`i#$lmPFdd?_Z|2x$o6fQDcd3?cC3yq zd~I9lFAJ;@QgyV`lB~dkPUfBQRX8Xa*P*o?N3~$hp#}2b@x{!y`3MFn4Jrf}V~kLD z7Flt_ER{RsR@LrgZBqoBkk~UtPi+bPXIzW)go~Q?TT^*lo4ct{dT+4$Y*pLeknNhc z86+~%z0o0mh{BU6_^Gu7HkgCyxu>JE7xBj^s}{p7+;pbs4=s51d^ zV+#3HP+~1X*eI-u$8&kL&Uq~mh;#c0w2|&G6r0pjFE?$bm#86?f2xE0k{`*cS`wz#HZbIFMwiA*C_ zLtx2m!e!%`qfQY=st`57%u7YWz2<$oNoiUd#kN7=N7lnj|`irEvr_1fd& z0S4AkWudPVB#skr;iX+RYU^aF>;7h`?%#x$euwXp#%jm z=OtSKn&?{4KGLh9Ou1{Sf>e=e9-azNIzYb__!oe%2^v7sn}7wQx7j-55JpaEk=7M! zt1*5QpQ!aTt)*~Gz*rwr<9bA6BWOGFR5^`YtG^rks;(IK!F0RiB+pD;aWlC7WE?2wvE*k6Db9ZCIPxF| z=kfYZ)q@CIPb)Uh87BQKc!oe(qnzy7St-cxAL1a&c2 z+Ta0M-i_w^_r6~2=SKH_eufY4p!t{18w)f-n3(7o)~U})lA7rMw`dDTcDQ9CA7BRBnt|i}DJcyN|bQ({G zE8s{rx(4bRaSlL9n)tHbQ5ks#9W}YSQV&U*R5!;PZ@H*8n%8KQk)u_t>DCKiR{X`V z*>f=U;Ni}_z+S*S|0C_{Eldl*!EkyXLBMUE0%yPeXgz>*TOTe+yTFpmbrJwT_mUaW zhm!i#Oe|ltOD)t>rGA`<>KW{;L^GbvXxX49Y>`{3aB>#(A_M5e(bZ#mP+g=?V{P!S z#Iuw^-{>FaTdr~H!(a{dfIqq!iN88)?DUj}BoA=uWXcj{EP|71-17c3}3!hh7p9@B(xsg>e?%b6nZd zT_l~J(aSi>TJzf7tpZ<|K0dVX08`^;g8tv2*Ge)DV?fgPH48{>Hvrg&7d@21E19XGVbaA<*Spj{f zx;{|%?az+j&wr&1P_8wA>HgYBQJMZ>>< z4Sy@aUgPrQI&#};7jJJzm>M))V#FS?J_ug~8~J(&Pnx(}n*(kdY0uzR-=BVG@&sJl zI%j?e_5^Nyhq4#!*$bDS`Eyq6Mu3J%(eq5TE7Co3&auglYYy9D=_W zq1eJ0a{|**eeu19+pg;5T3^ z_<*!7Ty1{-POhM(C#eoF=%cAC!QbKTqp1#ed-waF=S8rm^hi;LKioE~$jf5EGMKHp z^eJ5aA}>P}>FlAP9jIG}4DPW`YY@#2&b{#Q;a8yFIu;R4>md}Y24H@DnFxge;m|X} z*8V@o$0fWk$Uw6_8@-MoMs(i|S!*LY13acEhf%4G2YVShraf+&RG z%LuDh!MiyXoYsmQcPvFohCQ>7XW5kQy)?;o8wrCF`^@pgEyw3|!r z00PeIlhuG(&J?#LUlrHZGF)5I1o35~!P*CJwbIHn9&pSbrgB2n!K1+Fbws>u9bw8VO3Pr7gwM%s3 zbhO_6Vb^NL93{nK&f^1KUv1^(wvC@PFH z46VMkF@m>jLl38K9^TFe%09UA;j3Q~|NFh+qu{BFkd-4x4sij7j45s&=82l6Lq0OK zY-sCz_rfkzD$>7I3IEi$L`e?nU)-C<%r-G&D#fQQNTs24u+1XEB1uAYq82Y^uq;G7+C$L~)}nRPK1Q`BU5&JbcD`(-{mAIVdtR6&+(MExLu)F=CTW^Og+b!p zyWxN=U(Fvx?v_QVJDTNrc2g4eNKxPko4qJBMsKPWn5c}L*qBvX`V2I5_M%eN44de6 zrHCl#Itt-^q9bulu)Eo7@vQs8E-!=-^eW=e%Q;$Eae-+lusHaT>15rVcOfQ+rSYOKW=P9KT(ni=;-gy;Xi}O zaz3){>-U$Ou+U_x1*^1lQDCjdiVIMYb?5bvat4LMe3~RJU6b31(>5!Xz#dRaI-e(& z(e<6uo&k-57Yf$W!9pXOzjL#??1nTkzK?jRCd6+8w&3%8+Z^Ke+FVUn=p_pZHnkchB&*IS`MO!DEcBbM z!DK}55V#8)rh@pNMDyC&D5omN$@i}4aPaxxiGEZgZO3ki^zL`Pa6nTn#{(8ooMg8! zuu0RlA!8o0x^?A$o2xE2s+xJ6i2o`ZQN)shkkZC^u(ou2GPp6Y3SN$0ID6sJ2D#O!#(MciR3n@CDo>kHJwphZVTT(!d)p( zE|ez7Pc@{6u~A_)>7mkM+dgI0C*aZ3*IZ*&7rppA$t;tZ_$|+k4Mma_KPIN+;FwKW zP`pGx(NIVkVllyC8cJn!*%b`WPO@n6#b72}yf=`lhjkjK;tfc-*FC!4c5Ms-h}aWv zMpX&K^Qg=+qc$7{J;QvIo3*c0_lh$Uc1Z``ene-KaJosGIPt1AvF6O`|LM#p&*G1K z&`!}!8Nm6RLd2p}3SKk8MvonOSXkNl_x^U3IwO4FD`j)$NzS8ETZFqsBR8^#48J~$%*TNy5A+zgd7SGR;KnYWcGB@L!& zkj=62(iVFA*74(e>D-IZ56Zyzi1x;~_f=`T!rbL12Eu?`6?+~TkSRR$T!u6u2REYa z{%nfzBk2C358no_h2bGH_EkVMJXAeq9?T6YY^?)8k3V z=XhS-brB`Uk`JJ?a6a9aJsdOdF=HL01i((Aq^zveH>8XPUf9YPK$}V;tPq!n!#x`z|Q%TJNjN zAIs1r+y+BBJ9RZD;B*vyVvkRGS4mvW$7M9BQZgtzb*4o6r7&u_g;P& zuDi(ftT=&?$F_=NyE;4oi84~!L7&^rl&2~#gl4Ov3K%ifIw6#dER~s$G&33Hol(I? z0cK*M$V!^27DH!@R8(Zc4dmlk?aUM|$})qtloE7nW;%3IMi&#u#yDdCkQFzqvmx!= zvo(368cY;v3t1!!PA31Z@cYER7h$QbO%vfyzXcJ2$3{$B&}|f!V~O>%INq=Lx;V0%m21id&2>olEHREV9qC@MMkbL<1=;^*$Di3`&8kIG`6+cLm&};56c} zN>}*Esek%F`A4k|gB<;b*@wDvh!ZbDb|FG_{{-st4Ti(`jrdrMl{MNyXlM;HIp}3j zX5?~JJ1ecQqnzi=r10jSR;OF4R8jv)8Bv|~XyDDOM?ITf3-&fHaKXgwqrcXJnE~TM z_?@oCa>}AQ26j;!yA5@8c4%PV8E|GLN~+1fb? zG%_?y^m*|8n{L10*$Sv*#gIAY83vDU--}>8r+az8R3|=-y}#Wo%7^bxjoy~N3S$U= zd6rN`7qt(%`9dbZt~ouT8K34Ti>747@Q0y5?;r|*loN&4HNdt~1n?< zETzapoy+4^{XwgbPdFQ4nWcy zoLL{$4*OG;`|)xDeKWoX7*5#@hw+1|D9t@-VFR<%qk|>q%14N6dPw-rFC`Rmzn=yR zSMTP`xdB~!0?rW0<_zen`w8|xD~RsGcqia@BhUg0008#?RFJia*>C*}tLi%!sG<0u zC}$<2uM#1~$c5E#H&!+N)TPk2x8nh^~iv1#$GfFI*M zasU3|cmYAv>{}QMRtl?jz5=0Cr$MUfa^jr?71P0F z0%9;F$kaeh^0{=jHV09q({GV!oN^2pSA6>Cg985~NGc7{1aP(!21`#EG)x1~F+W}? zpP^NyQ`~}baqno0KUXHRK!W!vhyH2AX^i%je6#aN?8_3=tgDluc?{5aWydqb5 zJqGN789C^me%vD;+BZ;`T(0_cxp)rDF&Xf*ZGW;_qz9g|Y2+QBa0G+dopNY>kA0$h`AVo@d}Y@PL}7 zfFK-bILbhetfZy>QJJi~`3KmOfZJDl1Ac_Q(EN%cwqs+ zfAL74-aKjkwNF6VivA_?bAmrb%TvOKj#Ca7eiY(pr}8JR8D&}dpB8x5iqS}9D)Npnc)j~T<0(u2L*A4~u0gL0KK&-j!| z&TH+@0pcgqg$&|p=_V@R!a9}#dYnBSB&{t{Yko>h3}}AISuOmF<7mYLyA+dP(w1W# zHb6IH@qVZkOMJZE3TDtjWnpJ4d%&9rmYZE1?x`P>RTa>Z>=8@$1E|iMp?n;;ZL-yq;X ztpKzk*vT;Y4N=uAFzLxf4_Cts@UD%Lr5=KtJR^@M{IQstcHcU!_q2d;`1RFGQgKH5 z+MXQrSSdA+K&sgD%Hqwm(Zg8YS>x{FSJUnu1`mlW79(W8Q{sa3&RfoH_uhmvg$Nl8 zFPKUs&j|OYnZHq;rq=B`ga;W%)pqAzjQh|GszgC7)FMN0qI)?SDik4O{08Hl+UmgG;A26Pl~g$ydBQQcjX&F0IJ? zFgGm851dsaLP$tZ#(?r@&E5~}0fR^+B;B|cF2WbUGeO+Z|M7hUk?ISkiNw$Yk1g)s zwmm@AJUmoxaC7j4T*xSNsaA#rYeI|WRG3gobQlrRFyT?9I6yLHLK8Wu_@x%L8%!nx z-DJay56YOv`+j z{No_UiAo|ye!|H05Ez;y4F%%9Ozk8}oMz3ewI@T|)(o*rBiJd@xg9J{MWWYtG2gM7Bw%PYInt4&V@#5R(VX1)fl)GnTxGE+@Q~?ofy7xd1^GC$Q5ri6sI*hXD z644P+rkTZ`6U-ub2vQ#lUa_SFAy5{*4|xeV5OKrBTh5#?k+o`>WHF0Oi15}m zDj`?YMzXj8$g)SK==_4eb|*$JwSh8H3^ou3aqRw#M_mQa^5uE*v{YQop%>Cho%gdhlVh}JUUh+9P?gR-8nB%@$(AK`?Ux8jX zeANgrR(Wf0Yh{11qI31IcNOPEvMh0ESSDFDYtC)_jFOQltM@jiXo(fSGl znbJ9Ix5;;U*u;PrsB)D|M~O~)iJ_Jj+9>B#&QeE#p;@dVb~{vkKd2$0|U~)vs?uR+d}c#A0Q@c$iEubs8FqA@Es+^|y!k z-#8s<1<7tih6Q1P5*8Ea%d{S8HP=|BG%qa#?q>JR;JFvLYBFGmLAqDuNalCF2oaAh z>Nv%(?`Z=U7axvMKfSBSkRiu=kwUxfbU5SQM}^YtMQ=4|3{qUFJVraEnvTd zwA`V1;?C?bbClAAH#9HmiVa)2!p|_Veh`EiKU=z|OSrD4&e{hIp~_drNy-Pd+-?n! zrtafMK%m3zZX-w(hg|C8J&^VK;YRB=;0Vk?jQKuJD`{Zvo3qbNOvWA&^m}(ju zOcA|L>@ksJtyV`|>aeFQnY;A{R55uhf@e=cr0N*!OoLZk(X^S^dB$b?ZLATVvPSHP_tu zvOesw*PLVIaOCViB-C)1Rq!Bdg*qXLi6t%;j6=eGmG!(X6m*{s*_I`qVUF2cNH>-ZasC}zpd>Y{jA@94zrX4SrZaKE6tmMzr< z0v}-B8R~7s!-vP(<(i+`bvTn@KlXszQ#+#;Cq`uhY=IZ7Eq#bYx@5VS7B@9ykMT<> zkCD`*c6ThXdmLzI`X(?#j>V{Y%m7Gv3Q$CO!wnxu6aS^x zHN{>jK7Xn^k@Cx|Pn%d$cu*s0}bm0}h7R4Am}&{7U^BuYcr65=hlnb*72 zQmDS7^(j_qL3dP_Y5sGc7z5mZT}lb%{w2Hf5fGIFPa3X(Fli54_t!FIeG7P5V zD9y3@kZ5Fh!bHegn2$r+R?BTd$+wu@@|(B(?Nl#4mHf&nw!bjgp3@-J68##3LYZi! z$#UeFna>U?e^~06(LecIfB!13d^?1oPX8J?8RqZ{z}vO{x1%|G(S<`-+|&;U7Dl_{T54{r|Xy zgV8^B@xOAIBIVwgdB$&@M>OI)g7q08?EfUrwjtn92ucm?5i2vz;q!*q5%%r-lo%x;mE>bLW?gssO$D}EsLF9N5D`OSLVWutj8LvZP#kCX9}m*I+3 zu205Wyq^*7#QyOWJqEpJ5%w%o%Wv!UtzDJH8JU)}{!Hj8+4@PKNn z2+P`20>Dqjpg0&zH647Fp6H==D0+kb`{F|`#@P$aly3-)cvJy_iDMB92=IZqDo(Jn z>Qc6IOV=Q@gfYQQ+yzZKF;~7}ah4PhJ_&r+xKpq9+|<&zR7?%w1p7$lNLp_dcvhLS zP8H}I^CqbW$du_NdHyn z3F{yHFankIWv5P`g?D&E2vKgl_&pDs)MnXUB=OB^3l(2A!CWlgPv$6jj}PUG4R7tU zh^)QxD7W_0Xcw0yS&0bViYA+Oy(;S?H3}J&M9P@yYVbK+ zqTVsujf8i;oK1U-lb_u?%ck4u?E`7VQM565+}M(+NMdI z&^4K&N*|5KZgc#1-4MUYwTQEUffaWAXN%Fr)Y(Pf*w*>q%DL#&Y$zB>p?IDui4*G- zjryhJ|4mxXT)P2L%0F%<&}gpur=WivSRGt=GkZn#Wpi^KQG;)4N}VUpS(?0pGM}lk zvhKKg*t__ir2AhN*Ostnb91$#Ip%7pOIZD>Tb|)q9zV5)Uu&ov#MYk8&!WP9YG+qs zuBFTTKIIR01K!4I{D>D|c0(U#L|Rv3-rrw@2Y==R&6J5l`V)UYZwt zZ+Rhol4lqQ1(To|DIIhDgh`t-&NWcCBnu0;OY>BB``4pLV(Si#pbjHh40n&c1P_?~WxLwfPuZZ=B zpw!Ng^wK6gVbfx=!ti3u{l4)`ETqZEv|Zic8k6|$+HnhRI5RYW)WBzf_T$GLWHYkW zOw>YxO970yWSmPdF^Y8{cj;^sKi>?Q+d=_s)2GFZdgwbU8>7Bz0fE9-TAdbwI6hL? zg-LPu?^qx9p8LXFQySo5fF<<23D+l9<2Y0$)|GxXq+q{}Sf2OsPs%sOKe>i;`B>ts z!&(S<&kP;3k$yI;5&6%($afI|coihkjW-N_{<&i4Ro2cdt`3!WS(Kwz&IApFJ-1Ug zW<;-=hlQ&f7p&LDmX06UC|wQyD|8zRTN@jrnYwNX7kfbMo6YFC^Uzb`L{Y}w>ddCp zE&mdArTbC?43TZ1)42uXVdykgc*2D&QS~J0!^Gkp+1<;e^>}CcFPy`So>c*Dq%d)^ zu;qw#o1o)rjcq87MYi039lpWc8vLkB(=8ZnMK0=jO1cy&IhAw5GuEm-2iB_eo@4VTL<9EAv%qf2v(YtAjgNM&bo?K0 z4BsoOMHG_H{r*^OJ8R>W`m!KjML5BhU1g*Vb0M02xd<(eW!k_kxEu?Y6rrHP9>gu& zW;UgL8dO~Y$Hff~|D<8k=P#?_zSq|oMh;flC2;+Uygf&f;Hz%M0ju{xPl}3!&Iydz>dM}3M_<00 zU3C`T0!@?rGqm0XNh!~}a&AF~T+n@IK^(M~!7hztGkDGLCiAIb>cX!2{m_-P)Y5Y3 z-2$%A8ZHZ_A0j92M@P4x@fOrkEda!l*{EQn7<~`6PfNQy<^I*m)!J|*VX*A__)-3a z0=7f7e@;s=Wi^+_nM11Rv;cDVdLCK@Pt08MEr&lu$fbjQzf*zd8wEcM{9Ht9?t~q= zuAdnO0Y9_%VDA;c8QI+YMZRghAw*J4kv|2X-dsYOR86q*Z37F`eEnb__z8&LFE@aD z(ZB7NP*NSvG&BN9i@C#xcP_WNS^NX7cqPlEe-ez#(`40 zJzdbMeO@X^7Q3sCeO@F4Z-~3z)vrGo{9c~?KmFcsmvfo?5W7A?@t$u{x4XaIQodfs z_0=z44g}P(zdrKF{9wnv{=KQP?)F;uqH|Ns@%kckMGw$=b~pRJI+fqIg*AhfwBza5 z>hUC%wYa;t`u)rT!5(0MXpi2&*{$8H=8sRjReNi4w*JRoYO2PY{CXgMuW4IVL$642RCGhU>=jDoF?QSPqe9^6Eh|1vh=QeZ?HcI#CjtT|@AEG*pdqTmnL5tKoZGX-vZC68QP>vautfeiTB5{I1kAk*1&ZN)xaff7)a%mK2Krb+p-vBS&XN0k}Mn9 zlpoS^<~={lX~lhxf=2^g@>h;hQqx@A=^Znp#;hwf+tv4iLPG)4%d zva<{SX16hvnjj~E?JZsMUb+KD5j@tBWk|0Df^9v{l(8(zuz}FFrIz3-`7|3~KkrXijmmgx~2_V5s}icZPQmMogE1 zg~4riL|$ktj+W5^+4x+0Z(#VQqqLTpj{Baa6aKr|i4hciA#B$lJKP4@jK;AUAew=h z2KwKmMU@U*ZcrQjG{lJo@8E*M;Nn)_|3(U#Xn*Y*L#xY}vOI6YMgQ74k)iL8dn3xc zZG5-*58!mHVl{?!qB|EiwuIV`6nl#(v(byJ_z4$9ke=vP3kkl6w)ZNT#7%=k3)C8B z{Gck>1Q&ih6d0t7^QGc8xR6J;NCe^Dcp?1OTwkAs9N`bcP98=#?zV@@#1mO^9{SpE ztMPEk{#5n};30#{>Fv3zb0@Sf7LA48H|-dce{Zx&i>PWD`aJKC!0D94vPUb-Xa;82bW3q5`hx= z7h>54fk3zsDwT|rsDvph6fJ5PH#q=4>g9p7N_ZfL8tm=lhaKE2yyS2RkH-`*iBax) zT9}W-f~QMw_3ji<+waHpeQrNLfA+h!%#F8r=;H;gA0$7o{r-IF*}0;lTWuj<5oBd^L%c^*%kf%(4Ci;H}@9W|BhwfhO$ z3vc{_o;drVV(h{DQsBz6j~zvssobMfiq1Vk>4A-|u&Zugo~~7I+e#n_p0`~jN4%`h zw3J2`rIuW*fyPou3mx0Grn!|@wBq=bjhE6tUH7n!PrFVOHg8e22&icvEni49uM^p zz#r@!P%Rl4LgXxhe1xs0^%#CzRhu&Ae}Cb)Oq!sZIpLK?FGf$06E z_9;F22}VNBWa77$`1dlc=X&C`D3b7!iy`-BT#29?_3kqZu|$fRNHr+AZ5_;L^VrZ5*&ql~y4;1;BJpdSaj@eaLkR@a?%i70%%{1EVY;GaP-G{7=*(Q+K zI%gU|CFd!&n%Cz+@P10q2T(sUXf*LQ-V}~1ukMqQ$?W+$Au{3cg_QO~`rVvbJ*4io z&mW5WjpLp)8uDvMr%iF!eDk%<*Cc5`jcF!y<+$M@oX=dhDtT*IC5OgmE6|?mUi2&{ z`=#WH;Iiv&C)8aKwfF@954Dp=JRPO<@UlF;S;a3u^y$Z|I|&?MtSNz7c*~&t$SAQF zP`n#EKn;D5aP}>a7q3RWO%QST@RWbN`^_^|ojI`aIq64H3#g&mj7SdH1uL4a@bmq4 z&w>fir)pC>5{6aX89aJRdCrN&J35*^NU%awD>TM5ng9Z4CAKs}( zL&62(fi(y;3ZS6y`N}TR{4R@V&_j4e41&z|aK$W-N*sGP%ig2Nm8Ce?MBj%I$HnAK zJ)C!V$TI9EtY`@JdDe*D3&lWx>!+sK_Ai0qfDY@A75=nwqc=YbFiJ|eVgH>w1dV() zQ#WoB{*+r1dboUQeo?JH{q6yVFQEFBIdjv?O8#Bk(<1&rAUTSKzK#8Fs8-9Z`x;;d z?R9g)UvGfo{f^))$ZJ?^-YV^i=43PBp(VxmL-6yC$L~#aINPyIp1EyPd(lH6YDeO_ zHNo&xLz}1UNx7-xcE@$3_7zaV7G8av9qiBsr(-OgY^qz~bXrbHs}t;Hs%7La!jS^K z$DWF3lgl$5v-lj2!%O>p9a@#mF$x!>Fg3DZU9e1s2u7@gv9+#Pl}H;uo#VR)R3^7( z35zE@uTV~xzE-bx~?1J;(#nc~$G<{;gk>pOBFxS3=5bUP-Y$DRnJ^&*+Psw=x+(!9UeL zIWK-2r{Uh%tSR)6!v8hAk_W;Bbj?|5U$xs4%lhtU@(tP zr036W&3Fmm8<1Bx>UI>yEutWBg=C*^H)o-+bQV>Ni+98;SDrf*cP4Rn4m#1n;8qS4 zG9rbtFRNFfRIw+k?NoeFy5+q_xfBA5E0Jkk3DmC+z>Cx%kc%GjHm2MEPMtUyHW~8CN z9gOT{kh04(&%5aiRCg~jmO`dLfJUUVz+HxX4O(sMi>OuWU*y7dxCr zb<8g7`k+<6?y<9I9nYsBikbCDY0lEgh?*_n&V-AxqyKJ@6HR2iYmdhTIir)%$v5f( zi+~FI#8#qHz zfG+0d620VqgR*IM5~!enG&Qp(%45l=jG+k+IR{y!aV>)G`HJK{U684-R@{ zE59qVl@$jalk&N4na}}({NTE}di!qAomh*!Mf?Q1T&+%}*F+2wBF1;~U&7F`-?J47 zBO8*$6?UDOE@>>S1RD!sUYkqk+~Z|NHw{Q^9ft!FN@pmvx>Ma=2fAd36{BR$*KRPM zDAh%E8`dYd%)_gHQ5IAU`d`xHcgh(PDY&{8$t<3vUW7o$Ld|bPNhir6hCye9(y#SN zAlut=Xqs#zdwWf2$JHuPTsBtF9{uEBh+l+Ro$t5f_-RO}cOS+-^Fz!X%kr*TCe|D; zGAb>+c?<-u58}&j3;NTS740?Fr&kHZe83lAn|R`346S2~>1A3%I_f)AOp_+ZEt)#% z363`I!l{FVC2fXLoom4Po1lv=Yu7arG+foZXb5PFx>+@2NlWEjX4V4Y+?d7GO#q$c z^l&uTbE4?-J40@TlsamkAejCdiPC)`n^1rVRaVjnuOBGD=CCSHOuMEVO zDfY+KM^Hu~B4$b63!s*2qE|)JOS*M1&DtkUs|ct|d+{9|cBS0-8b`n;p`@01jfNIa&4^58r9Jb|x+jn|MU^w`b$)V1`VB8_- zWO4w2-FQ|_w2*Y;gY&HCj9H|$aB_@?cIhAAd}vdJ9zR`I$C0e0 zsv^c>IT=+MjixX)agkqE>rg=g&s-Ou9)?^pVgy9KpY?Z`m^6TZ^7PlTScsvh$kSTh zc2&AOU8^czleRENmz*C7{NAt;E3OrYQsl|~-S{VMvLRoV8=61Mr0x2MhqFZSm<`Zb z+nd4_${Kx17o{)WR1p4iMmz#X`#6)_<=fb*T|8i3*Y+Chr+PGQC$Z-HKcP5jR@-K5 zR>E1l?If!Fyl;z6!w(adayI(Svyr}q->}G-)MlWHNJge(nbafvhVsNw{(qR0}CO1BnNjXTfE#&W%6?ACv z;_;2yYV=ToCtR=b+FaPm_F^;%i?V>6k2Eg63&~SH<4{3ry^*C&YNRhjC=(4$o@sI(e?V- zXpT*48+(MmFHX&I9LlyJvVN?2G9DhXhOw%)QVs?0Ix!nt=o%1jm8bLIZytvw^fx_j zlT|fRmaOlrXZzGO>?8avC%1XL$_L!)==4{`Q52V^LXz!u`W8VR(%2Fu)+u2d@g-$Y6#Ty9A& zgcNU8{6Xm#a=^)4hZy=QN0dm}!Mhg9_FcGE7DMC!wL6cKn5w^~%)owtaq&{*R!+W=LlCKf7XRTQ*=28XB^g>rU$$C|Wey?$P9h;RJ4#1hUz0HQj6P z+#vLFLXN@kESb8(-E6s|lgfBzwFaihR?IeZ-%<}^F+B2=>(=^yEk{I{%v4Kz81@6s zWnS-`(5w`AHO7sL>`a1KchY?34>wC4&bdmc-d4vPZlrC)^0f1-Eya+4^tSCh zbRYRHir$MUPi@|>96zswL_dme-mR;~ioGRDRGQzj@5=&k9Zyx5CFY!bo6ft&9kAB(1 zWKB=?3!7t=dwCXfYo?(`tSb$` z8M7G;ZXxLcR@Ur#S2BfWDH&0kO*%aO zCP*j?C&@_vir-S+TctV7ItgVjCu-H*T~TuACVjcb3<(7FRA6B2jheg*nB6CQo#{^% zjJKACAJSGq;5?Te#cLW=Q590!%{{lX2-)=-dD(aHB#^^@Zz_6|H6&+Fm{Mc8^OFu2 zR4DU2a8O}nlBBPIbQ7b9oVMQYsi+puvJsaY3E7XK`5na?o4J%*$-YmGAsrjQ`%9Uh zpHjCB$IA!$GmBWo0=EpGBi+v=U%Qug!k@t}fKRc+=W60~K^r~wQ}@hoXkUNfZ_W>l z$p9(!I^<>jH(SefT zk}72dGrm4KTS9F@+^#5L73+w%RpkE>D``}C{2-qV$iIJrY_k? z;l&fOYgH-d+N}zzQGhXDm3@*09}A=&HR;p-Nv>(2_ZD;T;qj&?SRliXWlDr)xCp6H z89PX7iaj*OKlqC=Z7&Er-K8yqyagb7E^cjhvZ_) zeHNDi;apsRyXQ!ULWfB)`2dP;;dqfaPiBX$NxvknMSq@JGLO4)=uq{%j%04U^*y_B z%urs6GVaiskg>*-T8JN-e;$`!e(vA;^&|1l-~;0d-FGy$PRctTco@J@>B(ZM;KNZy zP$Aie#(~WPwItqpIRSW;O3Z;8b*L%2vI+aRaVt7^v^qa_bs^JVfpcb4T+W5Z=uIbU z_N5>)m2Jh1o%T8Qb#S&~rYfqnM>8V)mZUU+x<6Up@aHw{nUCE+g4^*i#u(S^M2X56 z0)vM+XLu1C38rkY(jL?KzMJk1IYn$M+t0D0w;*r0wsd)qw*{W~oj1U(ImF-!#pm>Y2mxC=*2`*L?G_WOR2WPQS zaj~2vTMe4d#o_(a8eQ)D%kAwff z1xE#Pb&F^S_QoZklhBs!16gosv_@FhL=4aS;ABn>wph=_*>f-s%yL&LmJw&(DL`xQ2XBe&eLBl_Wj?Esd#s4U?;dw*Z5aGKdik%{_*fr&t0%On&uNXX| z3_1iNC~%K*LP4?ux&dsB6&@?^+dr$z-@T*|TYL}wdnJc=r)8+-9huQz!}Cjg)i&U< zd3bA~2?dwG@~?>3RO2DpT|(Lg<<$=3N}~BLPhFl&KB6GaFcR2Wnei9f4O19k3Jj_8 z;&>Y)V%&hKt=5j9IIeXCpW-T#G09z%)|Sc%v7#3;(Hpefm!|v+O@vw)Xi{}=Wv=$p z&m8|q1Z)*=RpM&Cn|&axh`mq!XjYAZaHB6cJnQGV$9KGB%aD>u!-b|~F+}{?%7eX= zuYbv1BkQMz7-kb^}uomV7U}m5CkxyTLoX@6RI@wm% zc<9B^Cz=PkA0PXr{{qflg)F0aZ7H;Crn8l{IGBe1!^%9ZK88)SzjZ(KFVT1EVH)Nv zA^>dIgNg4%iMQ!!C^JvHGbI7?;l|#>2w_hmzuAeBVci(cR#AZYA~nN)J6^-Nt^ed7 z)T(VA+df7Syv4CYubrxgwEQ<1q0pZ>gRM<_DM#<%v(W9RPedRywM%3ibMf!|d^7>wqM1W74J- z(KnD95>WOw|67P$5Gxy70j6p{YM3$Nmt?X*1`=aM>yK|x(O~Aa;YO5!tYzdO;Lb9) z!ie_27=u_^XGr0)0m6dvM4xzEeAZ_Pg`4C}Ne#C3M@qnVWGOmFT`L-YcZw$OJOKo? zi=EQJC{L?rtkGb*CzuXlr&THSAXnoiP-|){dphp?%@Y}>yu|MfmEi@Gt|bZIR;eWb zP~JteDC$_r;fxWc<48j>v_f;>J$+>}^g}J0VH`k3vXkD$eBGMvRO@=MIK3ntgO)Q!}+qQ-yBI`rGzJ zWZ!sQ59iN7e`eZ1gWNP5WkHQ}6K%Lycsg_m10*t!KfiGPh(U)KTSUQZr(omYaKjk64C1FEoBoKYICYW1O_)duya8yWfvi3qkvVm75a#RXuO%1K{M zi)*1jG@uptnGnFTHhJheap`^(CHIhcqjq98I8t{O)!!00D6T77(M7Xwxgrv;Q1l3? zrz>2|1kshNg0m=yVi1CsZ}e-HL>=Cw{JT*D*rLRW79Dq$?zD@r=gh-G0&YNcKzz#t z98_V9sfFScNtXq&b{=z7FXG~7`lp3lnH(RzkC{qJOGJZCyA;)kiwk!cvw|z{#+7op zB}w#BeOTa5J+Zc0c`wfr5x$cTomDMs?8mB{cDx;qiAj5JGUPhrS=@p_s{y|#s0TY6mh&l`C#w`IP%W4 zg1L;0gOspwOdwG>gHp%B;5xxfG_W1qD2yk1A-t8WAf2l-qkl`CH`Ue>JDT0T3i0~Q?#)}st03UB2m zS?NbJDSCa3tVgQ;G*;mzYnC|diRiQ3gvhWJ2{Kv`FPC&#^DLtKU4H-Qn8rlWBhK*^ zvPJV0^JV-^xq^oF-pgn_(>kIca@NmNu)btY-MX$ z1QNoUy_tq~wRZP|b7LAtJ%=X6macq|i&a@fYtZi{ya6D1Cl+XJk(GL7-Zc*5ST8!8 z!BV3?PW+dvsw>dOU70)jIYuYP1&edx*u8`v#1yJ|ScMpozNV=NRWKBip)CzN2?fYn zEJ^4bl8L9w_2q-{q!HF_2#e!btX809orWZEkhM7nv+~!cT6_z)LyK-g}TX_a@keAhJC|u4R?7*N~qU6&^#vw== z0|ZVRk0FA%+WK!c6Gya7HJ3AgYK0b8SwzPbSnf8=f>d{2hcYIsynqvi5+uPzRtcT0 z*sKrrI*b)Iv&toOqgPMKa$VKK{rphY;}*YTt4Jg8kzv;C&6S93U{9?-P>$Q;dad98 z-w6XP$fUewNY{SD;vfClIl<#W3OCaWLWr;$N{WIY^ z^>h(DvJOrtpxpBz0K)-SK`S^mI0^k zYZZ6}FWe<4Xn9iVstscK%xtW-FTDdnKB&2Wb}N1b&sOFHTkY& z%%7i*1Urs4J5=>YKSL+wk!~^e^x+rhnO&%HW8d6^dvhr7!$fk( z+=js!(7bgn0~Ph0+uuYuuKt{kDwcXY%}z!JQEutybQ6QIsN(DdE3a2f|K*4=xn$d2 z(K$FOX(n#>E;F;A_N&>%ck|$wB1!Td3FwHg`oI~w{(WDHvKTZsag#z{p|KH;Vi89a*Ala7RaaHfHj&?-_w{GRZjg_NQ)l` zwM+xL%SqQYH>+I|HrwE;KGD-KS=_ezdksP<+EtsU4MHVyYs=3zGiCFSZ2b5xr;Y!+ zg0BtX_ID04w=1Y?$UI0ctNGvkxymIM)&TZWCy~0@Lsll29K%h@pqH~ zTO1jltN4ocYIy88kK`{5hx+8~;?hRTtY$f9kcMsJo|~ZCeQum|+3jx#(b*!tZpKz+dG*2Y+uloqmjj`q1iJeuS*<3U=4(Dah1+hBB70`eJ7eJbjp5LSSu~i% z+@_=7C3ZL&r6gBQY22FB8caoSqGclMUc^UOV%C)x#Vc0voW+@S>FW7oQ$YCvZJTj3 zY-oh2qE%FKoTZ!AOB9S^7;ZFMSFXNYt}Nb7c;U6}TX16T8#}TjNgr{fA$^>|Ro_k` zjZO00vNox6(q=kZtiNwgRo9HGoGxcZ_HS3UI$xBoY4p02+5qD*m!uxG1x zI~_r_ig`X(twa|PcenoGt8_nDaXoK-pEqbZ{SdI%XZYGMY6*29a7jD8U3zdjcIkb$ zUK-DvhrX-PWC-mdw{yj74%EYL@$%{TO+80cm)akwF>po#Sj z&H`W#wxU-7=H!S1aZ<3}fH-YdU)z%(;iLNYo2f%dgbJdjCYkqkJc0VQ5~-VZ5@8O1 z!#TaY+6eR9>qL9oLd03(C3##0&Z3e$we6Z=F2vHXg-h&LK2FFp1Pe>9A(*UXptB`h zA#hP3H z#1q9>8bV5}4P`sdlu^oGGOlLa+9n+L6ODC|9sP&xpbh+fdVRep-5O>1PN0q;#Zdy7vVN8?ewGGF zP|}*Xp>vO)Z(n~%wicGpQh%|1KRaG+Y`^Zb@Q4!qm>yG>_D1whH2xklbk|d=+=3go zNnu-=cNV;Sq7nw>Sy!d&{*nji=Rn3GzO}COk&KL)^6z-$y=AVjQgI2MHT|Oiak@~2+wwq^&joB!;8&Z zYsghKE8pK6&42~Y#obd$tMkASeQO9kFXhWl7>{4#uc9RKEzx@Zj|sLjPr!<7_3>pr zLx8LhG03U=5>RjFD`v{y`RjV7AmIXJzVjAJ(ba1C_R=e$20ra&ba&zGcBaaJ@7EjU zBbHEo0SgmzMPqZ$a<$yGG*J$TZTvP-{Zzu?6I5L}d0soY;iHPuH|)*d&g-|O@7J-# ztdI8bZk)mqG8gsrvbT}6hA5D2(=VWQy==g}sn7Ckfi1IPcvwz&2&>+5I-=^}ptYpm z-F%LEr)mOSrq9n^YKEPo5B~8HWh9WVQT6#xZ>T^~#IK9px&@YZg@Newnispv$oO@6 zbZC|5D(ARovP`ABNmG7oScs`ZoS>q2yq`0?zJK#!?b}GeF!3)j*h%}UdHk7Zk;2_V z*e447%q;h7-bIQ&P4zH6FA|1q3oxi{nDsA@XD1sa0|qBPW4pRSG^i9o0P7WcZ`%b>TBgy>41mFxGAlcw?E+SU} z-CXB={}Vgrh`zkYZgDaBsQTM|I@0Ws&d=j1Y$uM6>Dq}Ut@FFQnO~3lZXFzmPB7Yf z4I1?;)v&XU*=4tLkE#B@lvMQzU&^bovt}}K1iF6NM{d6 z{8NHL%#+?e=F27C7Od!+hJE2$-Xv6=Sl#2M;-q+0W$Y`Yz9~devKm-{ETL7PcMgHO z6+!j#mhQEPNjH%om>i=5$db$=6&Jg6SH-iqgumo=+x5DM%B8Rj(Drjn(4-B=S#`~2 z9-vz(D-}r|1{U_W1I$^_IDxAl;n7)3(8zKzkloMT;5~e1IR`4Lo66yllIu#gj;#k$ z9r!>YeDB2eP$PKPa-b)A8|t(bvdbX(KK1QgG5NCR%^sp9=SShu&7R?YPKMu^6#vg0 zv8ZXM!Mp8u=+b8^vzLv&6r`RMYjeL&B4B0X@XBCCvODt0Ws#YhsvK{&=4VMC{Q+9! zx(M168s^SMn`*0bbPBY-9Dz0t>jB+@%dAlYu{^BdE-o(#O<>zmCeR1B9IMG# z(>Dp?1H37O*ZW2hmV{(@i5<`XRA4)fvoCm{k$TVJH~@y}ia~oKj+a`9v!F0&FpCk! z_0p>IC1dL%_GZ~d)>8hu^)h;Gd9DcCNE$t&sNG{^Th+g&Bg7S|GOexC>qK|8l%w02u`p7FfTz_s?nz7~b z0KLz;nG&84&_(hv5Bg{be)$~BMqBcsz?#*_W!87c<~I>Uen2Y`LH7?p!07b$0S)RM z;8*qAeepv=f6gb|$tMQV$!C|sj&^>8UwPOrhv5Ln&CC@Zg&t4s%>!qhE7?%68WX)X z(7R=_k(z^=Xm5Zg70!oFu0#KFSszJLssk8@u>A9d9-FRIPW~#&*>bi<+tqKfZddQZ zw6T{)h&4J3Z2;7T)7Ob)Zz`*sC+fjZt&>3iXBSX2a3T~LiWCc!Eg_JQExGs>n1Z8^ zBP(YTmv6(u(;U})BJ(o0S&up4yYdfVdr;iH)O3_=NRe!+mT0@k^4n=9l?aphqVK}Svb$+eeBq7P z;TrlBen4X4#5t{g7q_k&@8n~tTwx(oc0nCF?)M-fcBZjvMqAPQWS3`4XQojH`{6<9 z?{cL1;B~$w;4n%iK53R&cE*Mj}V!;WKM@mcr`}xeX^)O}h(2kdO6xP$xzoxtc#EEKD4)k8M-ISnev9uCXT+N!rA_<_w58hcHhLZ?f zO`VWnCY=TAL?x@wxJczMI~R+`IwG92*O3)y)*srCKs49ic(@T?>RLB;C|4zJJn2sk zvc3r{|2h12#Dkoj3bMb+9vpm;<|eP^*+X1zW$(OAm`7B-dsCescSd0+vaPyws3k;$ zl*IAwo70ht_FBP>2~@=UXW8M(Et9&hkt5IS6j$=`=c14^P}4Uf=`1FtHdQkP&9v($ zTpp9u^jip{K@yVaQ;kp0Ib))swSh zci4PD*lgE0`|JL%d0SZ`GeoJUA@Hr0p)6IyW?e^V_T^*Viwtk`9Q$2KcMnZAj{G9w z+74@ZrR3W#H{)J=bYK^A<0}tp#U^3imv&gy=X`j@rrW(3&<}y0hVx+&Mwpx9Tk%<< zOr)FVEA#w?V}6Eh$NwF8kdOcWS~3-Q!(JT! z9DTjTz`*_~SpR>0HvV7N)&C5_nK_wQIsVssm3iX4&Ys-U{Z?Z@rC%6LxzZ8_z>=O6ZCX*S%P|my*V2GbW_aqSaY%Hw9^Z$Z_{oXA1Bw>o%~(XrI~nP)288l zr#ZVnnN>x=pzc0-ep~*4vPTk&MlFwHxX!~dS=4s7d2mt1)UuhgWV^ZIW;f|)~JuNrSNIhR^JI6 zTdVHYH@j<7$GI}IJCMzqK!WX<)%T8HhP}wZdw zS%YPus~=Vn9(`<8VLZ3n=yiA~PTf6#r1whayZWOMh&sJ?R&&NQr0Z(<^^@=Vo6?p) z-Y$=}w{vN1H{@68Gp59}5?%Y7DT%Q`pI!9H`kl+0y_NrTj979pisakX_z#zwr(}5& zKPO*Lmp>R$A|Zc4XugF=_NnSFcn*%`?x&}gZ<|ihP>Tts8KfVE;3YM1&zKg;4o1u^ zh`Bm6+Iq50n@27IY;E5Lvj{g0pU!!gHgCNr&g# z**Z9`E4MZK8vM;s2s=|7m*Vcz}^LdOIC144S(|G>Q!;y>uy zLD>EeLTLj21Hx*k|G=e{-@#}5wdWz=400K^8dYa&=l=x6|I3ErgK>b_fvtnJf;&KN z!}K5%{NKR;U)G@&;XfGLq2B%vLMsCP1Hx*U|G=%4%fWg3uxBzL1Cj&Q4rP6P=l=u* z|I3DIgSCL!fv-cfLOURCWA)Gx{NKR;U)G_O%t3lPxrZ)58=?iu4t{-Z=l=xg|I3DI zgSSA~L9ZjUes`eRR_m!E_`iYwzpPU$%zrSngSY)3glhk@BeX+a$7^MCu-;zksUrNp zLBPK(kz;I3NSN-FntHLP(TMm{WN`$_YI=AJe8GyU%aq&5{EQ{cfe>~yc)PkWl6i5? zdYQ*42*t#$rQ(Fq~5EUlQvlP>92)%0UIHCs{n~CTQXY}(X zjqt*o?dP96oAqPjRb_rKP4?_#_=Evz`~Di`%>IIVbwdJv8pICZGSCY;FtMG9ZQFM8&v&w`_P6(6^_=wSQ(e{7SFd|ryO2!q zeWj4$4fA(yRH!-NvV*G>QqLSxGQG`Ck5xB_=fBSFe0dBqOj#^P8D7d-6I#_@SWoTf z@G(U*-ium0nZZ@=teYZlIp!eKmAui)eMeWGVj{h7u~4p%KL#P}s8_!ew;WoX@2E(z&YwO2I;KA! zqVebaknMIZi)Y1{oj#;(+R-GFVNl>LGr!3K9lUG}-|_f)Ns8z(mOe z*i;D-KdKX&1kHsvH8=DxAg)DsE;%<@Vd(?Wp<89sB(h&0na@&KqOOR%vMK{C`KO68 zJCv0~0LjwZrcl&liO?plLbAvZQP*Pl0Wwr+39Av`@r;C!N53i5LM=KHlu_M-$v`BA zfeh2aB6WA4-JB#zf8$eYB`n|WhJyZ*}2FPr=m=vScy!Gs8-|V0|d0fXEW)* zVz&vDi71jREohQW6gAFN-hHPuh;ahXnx;TUnF-_kk1TPmv0@@+Asp?q3Dva#mO9Za z@Ei-aN2?6;o@l$yLVRC{oY1l$7?8q)f{jd(#t2cT;ZLSnvOVgkmp;2QIyvsi$Vkyk z8Dxr}TyUOjw$=rtrVOn&a-o{qS!#6}#ciGA1nRWl5m9qCl$>J_q>CbB8AboE#x)ACLCsd%ny?L=wiH0gXuGGjUzkqNI* z6a+9CS4q?32;RTGp5rXEisY&VsM0Fq&wZ#uUw==?r2VkLwWMp!r1EL(k*T4f)M9ny z(xk>vOHGi=v!pRl@+PQaKo*~6-&(a%P?BJ*Yk|@GXC$dis*6QUojndi7S5;nXLGd$ zv|G2V@56ntyL2Qk~l%k!*b-H|MFDQ#&h=lm`1zM zN8M{NH5%>e{}MR$XfYY_WpuzK)U+|Ta@BtO4caKjEPlP_k8AVEmYKixY;xuAfBRd@ zgiqLSL60hMVWPI7v$1>o@G#=wV8aB?Dx39MWaDtInIKP(z`j2qpwyplJ1>gBy)-_r zAWyITPP)j(y!_{+OqZeR=Lo2*yWIln9R2A>0RkBQ3hrRvdRcYbxSi`wL|phwqt{!& zd*FjJd;If7W8TcO*G4lSfZTrzTYKexF~i%G_Ml@8tt%=LXcb7xDJrqU`$rEO-3|KY)A^Q1U$96Rr_*1pt4X6`kQyxTu$>X#=ZU3y!XHSQyMH z2U-Mq6GpS5l!7^^b}3BLbO2>IHY_$5_Kb!b$B%p4M0y$~2w1iv9EJTH6>T_db652- z2`R%;@1j49z#?0N&4mg$bH2CD9&E5fd&c__QK%3@#%QkIE1})a$50|PY9TqyOGA%{ zxhUbOeREQ-O&Ov_Ac6K^0i%N9C*>n8Vcw1)-lhpyXpYa!QBzyVt_vIKf8;KLhDfFJ)4H8M0VaJW#J1EsR+9rUkgAHC=)Fn#?A@k*D_dg|_k5hd()j4Y zLe2)KMexnCLl-d>BsQFm{O-Zq0xOYctCO%^IGOX*k%rgDOQ*J{(2r|ncn?EkNQ&9x zWAJW99Zy^eJ1 zFQ|?_!2Wr{<$QSZEa;<*))HuIv8%_@{%<_yb+zpfyDIgIhL@&qnd^H%i!GkRdmtdk z(-p9@<0;y@YXOCYsOmfYny-yg=yPQDHO7gO8CTtX|HW{}r~jp-6p#CMFtq-4gDv~O z+x`!(4j^yuF=+J>d;hPluw9vG7zR)vApd_pL%jbjY%#DiGWi#{)TY=ttV<(>_dd{y zPh(a@_9HRK`BaH0NhUc98LEm^sa~y{_^54MT-O1=tavOFCyK*MNYsWIdRQE9Suh;K zfnZ1T_Jq!ho*ax|(^Xx3q#~Z2G*F?>FO#uEywfSF8JX5-CPe}ju^`4Kwuh@a4y%gw zt7xf84@CtXH-mTAadO?b`S@uTMmGlQm~%wpde+0Mlr;4RAku17y^0*fihhb(Ql)fL zfohAxAUDVUO{4A#l}=i@>iWD)uFr6qYa}C;~|)!cxtIz+U>- z9?Gt35+7ZfLZ(2SiHmGlhm5sltKb?;>_}?@YMy4;LK&Dzrf06=FbI)a2L71e{33kq zz{(90L3F<^kOV$Q{#SFmR#Igbs&u46cB4eF=ho{wpu^yUsof`VCi%$O`9z?L)W8}} z%PAy5nuJzaXke&9)1HQeBxs}h=n+*+mVese_#0F)%-rSY+PWop?F)sXA%pJg&DD=p zYrwc$ceB1ux4>VEs^k^5CHmZ2MM#)Uw&b1p1kyTz;x}7p=(@BWjG}F1%v=TfQou_f zn2d<41Or{^W}*({)R>;1nyph`F9OwCxUCAins6Rn)AI zKFLzzI+fpFwP$Bjks-|io+8i0PdA*ekZmdnP<||nS;MbpoY_LrUbep&{~1|#iiyd* z%atNRC5MYIdVZIUu&uMMj@s+(i<+swb`SCPRo{@yWJ4l;tb$q3IM{V$anp15>rom1 zvTa@OZ3FP)h5GUD^6~G7AjjkscR&3KhYSddfTWD_KG>TbjJa;(+0{qu<`X)68o3aV zAVK#OF}z6DB)C8jNoaC(WybQjQ!VWJzGhT>vZ>gTs`#@FH5bFnffr9o@T?$+E{pwz zLR-AYtG^D&>ogPk@moEVCo6r>0kt^K+*<-;ed+r)Mv`n_=HluSZ4DqRk9uL5;R5+> zVUPcY!)_sv2icW2#~4od!@0Dpr7rMa<-y?SZqX^E|1?r&KtTVrq5n^)$;!se^q)-A z*Tw};!uiVMI~w{LbC#mgTflO1&$|v?-v*uJsKvNmm-X*K*ZrDsbhPpGezl*^$dGwmLoAPLjpv*Urg_ ze=x}H7P`@=XRPe)&FS**=F(-XV^|*5s3$B2&*^-f)L{I+n%U{rEUE7`_$2rHuzPg8!8GX#K>klYFZd@O#?-0H<5*^}ciS z`SzXr`Me^b?~&T+d9Ct&DSy1$&%WvTI-Z%?SOEVN%hf)3M_fJk)Wn70);3^J&^Nfv z=#B7Um~C2-h|zGQ-E7v*?-J`x3tZ2>NcaGqMLi-X$pwXHnw(ox@O?dh zJiK@IbMksPfc0gR*~|^_clo-%+`p&xvm_m!(mLvN_-W$%*`-FKe)MPu1ayAyW;#so ze7`Q-f9@~cKS|7V0=ic+2NMOpU*6wC%Jp)(c)xa`H5`^Y5jlZ3RZ{#;Q#@AA&#!7( z>llK5W#=Q-8~KaH_k84NTzE)aEI|esSnI{%@_F7GK6!gNF@MT-quuY093|xO zY3}aK;fxLaGcP1)Kw@63jAkD^-s}xVxN?MdcYvLb!sqXGSp+l*RrlKEzkKs-w!Z#~ z@Dj{NOIv}2ya^}|*w0l+xWit0#oMtdRv;WnlFjFipLzB?(WXB2tbVM#;tKGs_qLu& zNQ=qQ9c2J-ye5eZl5EmLKr#jknhH1(jzX!B2k!Pl$) zg8l(sZdVCcSA^TSYxw}jyb5mvD3|czCg5OYppm)8a8ymsc0b6!J1R5RhMVBkm-)J4 zMDPAI1Z#AOi}LUO#2a*(UGt0t7tl~c+i^y-Pp!yMPZ!zRvlD3JhRQxuuI^}{^d(7n zY8H0nK{%;{YlVGbHZ*l6ywv5$BNxM^sP?k#39SG;PsElDc8S~O7Tm!zU`~xbITL6k z*Cs5dXeSkV0B^@UV*?Jh1e+))7eqMjW1VeI-#OW4%C@UrR%m=^^UHOVm;dQhCpRc+ z0YoR;Z|+}vdAkA-yRksJEVpIJIw;;MV*1O@Pt{TrA;wRe+is@10Iz4j91LX>-225~ zaxrCq==@h23QcHFq5_qSe^aQsPfw5U>mWz@*NVyeCxM0n&5_}gr&FhdWT!$>*~&@! z%!+^kaeMv7%s=`DQiVdGpp}5{81H^u!@sxv-uq%=>Xb9rt7|W#TdtR@SDUcmxo`oo z%!#j~9bx@(&Y|sj33ue@TF9Xg@l>i^&?Z*u z8E<^^i(!)%RYR5X)0@zKn4|hAB!XWIUB!-nD96NJxw*=&Ms4&^Es2E(Iroy{{8Y#0 z_k^Z4oKkC16Aa?Z(MI-&+Ex2lO?MSB1+@qj2}X66Wo^OSd<}|rDV$0bM%F_u+ez2N z!0GbkV5Ob&lltVgjFHS==|klfRT?Ti;j_B*eV67sj@l45`t?g6m$E-Nsf}}s^UBMJ zHx#WQMu$3MUcC0bOV~N@vtvifVWe(!=@Ii_=tJjJbqO^*`h@gxvD;A9wd^v*T@MjL zj0sFt2c-B3>LACyo-wKfFjh4>+%9cw(-ozz>h&qDlUQl;RH%p=64#Q1J_iW7X%eHz zoGHhvwU>KNy4ZKO2bUx0v;CsDg6v}-zUh36KP1i$YF&TKMzR@OQ~(eXmWoPHSAn8? zeo))in69_tuvjA2k?OV00(-UL?!CVB8eSYBx1=UGX7A~zgLg0Z%6Sp{GZC&QQ|d@N>Z!D1umzu z%~)Y~t`hXMz#Uz8>Rts{^tf>&ae_d!we*RYCW_{dlf|Q_vBN~Mh!}c~+h8Z5Nmxvh zq+nNFe~?Aow7E&$Vuh_SjhI6aHh_%#6sYJNo*uW-u`FjTSF!eZ>F^^%i!c^Z!|bh_ zag0OP@Glc~TbVFgpcV|Flm|(t*D7{B*i3r!~BZt#PP7ky9SYdu(&K04T8FsTk5|aZg+(jz03=@oHn5$fHCF zWl3pNb)aIh)%@7WZX#W{@bAtbPDX)r5i-D8hfF#1-Ih}2*Jjf!#3myQ0wOFbqy6}# zb9CUwq!Xp{5)%CDExH$}uuk2r%Jfe(6;IlDwdznN-SKUBks)eelcIv^+>O!ij$vdN8t$bnk2csX%r<9F&b3?eEMUyzBJiDJ9==@&HYB|* z1|YHLl1d<|&#MChM(w-+-Mzz9E?$0J($DxB06&Dw#S#v4I|8jsYz!R$X=tud;MuqZ zBA0H<+m9+Cs%8J2iKaxW8Zlv6zy7LF%YVf5-T6s-hcds1pSwimWd>mzVI@KR!o3Pr zC9_Qby`m>9gJyvx+qLmPo0-Nek8K~CU{uA`FKZF1NsFycaB10AP<=raUO_gxr?S(7 z(r4r?&SJ8S<0ML!XNu#N%66+e&4QT2#gq5MqU@}QzvlrhU8+es;M01L4R_Z;T;hv) zAtxkzE8T+ZzD@nYN!! zG#ag}=MId}r`1v6oGqV~kWoQn)ChGl*_$_&qX(09t!m~t04u^ir(RsZD?Y}rqmfA= zc>4I4*YW5ro`&})!3OWq_=4|eJKv8BEc&E^2>8cW-oL)l=NJFlUQqOi{BRhpGAQ)F zaiDHi1sVvlEFnwS#Z|AberRaNgF){4h>iZVMHY9{wDfrRxoZhCCtljDam_RP-SMnm zfIJIn&T2suI;)r$_ZTu!T8Xu`;yr9tK4vxJ=UwRcwa-?gkK1`LMwhso92HOOz$+#~ z#+jG<8ZHa${`QcQx=}xTMuERvRSr`QO$Mtf=9@AxweA!6|E3A7Din1lxp|yGQ?w3;L zMIf44k52JUcJG@hh)1esvtn}6;>2b$iI}WqHCot`39OadoyBn&Fpp<9c8%VeSK7(w zHEfT$h^~!)WFPh1RbnA_i4R^NpCPE~29VI)A0oFR)=%FSF`QWHpE@^*(W#?#;S9hH z>K`|Tnx{csWlibr}z5sk2NnS3R&{EGHYg8(b5Q;#@Sw!L09N zUvYA5CPaT+DI$5nFf@X5l+7-d)lIx(n{s(t);;cs(r1VWak!x+#9=wVg6^3*8@KU=By|cwE4D<30Djkr$ z#osix1|~onr2>w6txjU4yF1=F=a|n&CChX~5xk?*x}CVZUxt)3Ix(_;nacLY_%XhZ z+sGX=*NRSie4O)gH+5GPfqqGc{-D;Vz;#Ua_r=VsB?+39jJ?cSe%Jh78ef7#2jv}N zs*?acxmJ^(3DuFeVbcVdW#_doa&Ps(bRcPJAmWdJWB!M27cU@hbrtG_p5L5enzQs) zpKa4;0e&z@jnQKBcc}vw(UrK*6@+$Xj+N(yb|y&ZSuksJ?vc`TdbStXHU>JS;9Ity zJ|0?U^F?{!FP#VJ?975%n}eE}Jm?5^gN;W!ClR^J9DmScy>$Eb_?F%0Iy3^F!G-B_ zW(uj^7QF`hsei6r6b_FP828STHA49!+1SheIqf!3g#6!=ktfz5AE8!0v_TNuZ*tSs z@^w&LqF#iABKg$$wZo~4vSGi~H!WA_zs7ayu|j0|%;b^Gt@AzJ8XdUb9$APxre}v% zEwWCqIm97#0&Ns1M7dQ8by-==4>(HWZAi=I@>;TAv}8|MzZ!8iFXxQewp6h@=LTWa zAF85@}`Xj<3 zThjg2xl9AOBR_I9mJ8H**-czFs}@nQ~$3vaL9V>@bh-ysKeL>SFGJo5{Lx zoVsMY)0aIOlTXN)mVg9l)|9NWc^v{Jo`3_cYkf(xM-PH6@jOytlx8ty0XiQ$0cEw-+RShLjBAD0(D|eC4t@ikR_Gd(etyX z2nHO)@>#JPg^{r0psiW#<1D-Uj)?YFk@8D+%<*KtV-|+8Ym-#IN&9S9ZgEXgpZri; znkaoA3^mDh@k8ObaR9a7ix%Wbky@2{axQx52a?XDUYnwAD!{w8BXR&x8qK72l#=Bk zY+0!Dz|<<+@%_W`l82u$v+>|C-}}eUiJAVl!7Zw#Mm?j#cA(Y>s@F!sF<9l(j=u=p zrR^Zy!P%(8^h{m}rBAEL_vgX*s?hK1z~q>viC+L&T4ROA;maJj<^Y<(h%zdNEGS`} zl~jiA!|mWoK!@tr=N+sL`gBY!+LCcFW>}@8D_k9=jZX}OL5s?KjJ;gEJfK+Dnd5!e zdVTrvp9y!FPeWnlW*hHskoQH2(XnorlfK7#M!K0Lv!sm+xMerX)Fh2)EH!nEQSoGz zb*-*b?sJFN-shl>9RK@8lZC<|V&Ft4V}#oejej81s&6@=eJ=kE;?gpOwnfm_aSZ1K z8qp5eM(v+c*Gkj!+%j~A#K_vj-z#M)n#R#CR zn2J_v)MEQ0uzFa}B{M(7ha=rC#;po%Q}>% zC5BW9i|9#aUDQ)oH27jhU-Ki@K_t7Ck6raD>iNWn`gUmXUd~IxH$GPyzJ8 zPtcl)TE%9wf(hMfn#`b^ZsiT0%~KG)BsW`9NpC7t${LJzFa!l-^XWCLP$Z(cBrLih zDSFV2RogK!9i;NO#;RWH`Iyv0$KZK#(_;wQWOm5Dm3%2_MBJ`ZhDIyOR#lW42uaswvah~4nO5o^GhHlwCP5=8FXuhZgIn!q5wySYz}U%2^J+WS9TH#P-mnuh7+ty z{={G;;%DWWr8O`~j9NQjD7EsqvB*mj+skP)v~P-S+fCkSdi7K_0DKC zBOonW$(O{O*t8(}ZVg($05+1ek8@nFBiylzuH<(~*XReoI|xdv*JSDwrFotFA~-I3 zjv&C)RZ=Sp1t*ESVNng|$ZS?XivC=)8_+6_$-`Qj1h(}rBYW{z9QOy~RJ;H(DgJHP zf3Dg(8H}+hn0p)c*7`>26+*xJ6YOahzyh84zQ0tLCx>C7n=NG6} z`Ru406aHD6zAGI%5MRV$7`gLQ_BzXTa(ZZI5;i7a60Soup4B#5vgj1Ktu2i{J8vEO zW=B!n0vl9fuel$nlN4oLNAV9ZPxS*?(7(9%xSD*;5TPXW?Fz+XYx1)n%BF8<(V;1h zIM73S<&m}DX^SbgM)LKhcL3|9 zUyhPayRu7c1u+J>;j^NbqypSjm{t{KP-|O7n?Imv*m3aE%b{EjV_4j?q_9bOXAK-@ zaL8+JA#OyZVL{QZ1)LdG#7RM_m)pE`$y79w=`eisMAS);gt-j`@eD}P9q{z)ov~xy z1g7Y*bA{eX#CiiK(N6F`WkpWuswU`KB<$g;r%5!7Sz!c&ef>L%f{EVU5L8y>Y}U~D zF|vF7$1d0+!DSrHPXwNi;~JJ&SF*?hA&4rZd?Fe4=)1%|c5Lbq z5k?WbldVy2e(|K^COMvMu>>X_6wz4#u^kX*aSGIF3w>)6+x0B{FUsp6aHW~Ab3>Qa+yB zRCm7upnwR?(H0dcF_qw7S*g&naSjf!CGsAXF3dbz*FkXLEz>ZR=`T|>GRSeUMn*y8 z1OPY~;rS4Zs>TAS6U1k3$yLhlBz-bII@rdof;bqpn#u!$hLUBf4Zjv_>*dKvK@Nxm zmfzx{P`~4$DhNypDce8{S0yPRCEdq}i3AR3()6X9peV+?s}*$$V>Na?(DWZ`P$x7T z;m?;Q9)JnD&~qZn+zO_`nMrWDz(gR262WvDsK(|BSlPt_p%c#^UrA&GVNhroNlB0r z(1l_JIxSx^4+|`I1yexC>`n{&A_1A2NXRf{>WaJM!jFQHS!s$Dl39xP4My_=gAr?N zw8RTaiu*m_npVgIG9ti(0*{}Ve9+4@!;T@c^3yma@_49r&yi#=QDtt4r=aI(-W+h$ zgmI8#{wqZ_VkWz-Ahv-(uh?v0SK7_#ZOnXG;#HUhVVOX{maw~+aL759a#|7!A!k7D z(WNAuBARR|R((Dt86ha!etVecMu8wOSacI& zn9}{8C9)Xpuuw^lIaGY})`f8>#?U}P9Kep%f|p#lNsxS(%G5|PSL&v!&xH_V1}P6O zj=l3i$5ZfN;KAU~yd!ydg23yQksQEfD;462!^8u7=1jlnK^bou(F)-WJu?J|ILVff z8wg_KU2Dk(W_;|Fp57w4T%!n1V&|lxkCdspFYR$Q1b-1A)I8!@CIv|oX22{`B4tv6 zvx{|dlFuzd43TnkggO|^*mxmHcJwCGMeR?FkU*L)C9}QiKEd-Pnuu}5zi7Z>qCVULLKu$PgaCAt_ zgzYd?@0R*hk?Q$Yt(yzRLcvn?$WDPg!GBiN{-xHy^4UQRy z$I=Ut+K}L!r9#5ThZa?nfg3;NO2ZmL@v(34;C$z83*hXIG2nnfcAJBT6BBR!Mn#Ec zCXG;hCTlSVpGdIY1uI#LMgj5Ceao?pAd8IGg!96)qEuR=KEO=tyz)D8_w}0u=VsFBP<<)dscziOQtn!wBb|0e=H!i5_c!ZPoB9!cjmM zak3ep?HiD8SBL9Iz^T&gbBcUBmEhqmDL9JE%OeEK5)sNp#u%o~aQ27Sz}{NJp=&jf zcA>+d1id#DV#Q+_My#jGi&%moj)?#T8iFdPNE7L$q$msJfS!mWMPP!ukf7CKrV`ZG zYxUU;V?wiU1;gEnhu2L=RznE}AigZ568xo$VcK<63!Sjs2_k`Gp;B$W&PC@`K`ElW#G zFbx8Y6m%Ah490Vol*Tm(SP&arg^w@ILD4^JOHa2~XbZaH7YHy}oQO2V6F5__AdO)7 zJ1lfnAW98HWx;Q-^1xar@d#-dHf#g1DG;tms^8`Gb4u^!EjF-|uA-@&%7c8jI%x&) z&=MpVGN7GpK5|UK$Ni@8^Gw1nz#m$Ia+WAQz`s~uE`w&*S&U$z)m-ssi)Yy;=!Rqq zIOPP>1E=7oGzEL!X!!iYcY%~LfZ|Be*wP~8!3%-mc4z2}0*{~7cjN=rl{Bj|s#zZa zvFh|Feq#JdNExaTMJdCYh4!qjO`%5QSE09xvK`A<=u}!E{di1?fDE}pF2QzeknR&< zv&1@}0z09o*%1yPJ0K_|%^kn3z0vv3k60bCYw6py2{vg05iL|>M0OatzJ+Wl?`Fxr ziVO7Bp}f|(&3&FCQRlj)1OFT$vyLTAB1u-g!2*4L{ z#%wFh4Jvit11AgEACHFAp#9ZY+D+04QCpw;nzr}(X*a_j4g({~`V8cq2N|%z;pmogJ-DZso+C)XA z&0Fe*Z$}>xH$3>A*ULJfTr@un%zx-~H9+~r3ha)cc%h+1;n0z!&v%7OVX<{zfRo4) zQ&)7~!Nk2HTAZ-L-?a#{#m`=>x|vocPUZ zkI)%IBnRXMtODUCRGc}H5j;yR0zVc>2p~Zwb3mPsY}!T8-Vt94lIEh#LA3P|NwXXy zK{=oI(8dj!kL16Q<{{{!N_rL)?A#@@D8#cFB%B{`Zu%NKl@k6fB^mL$j6B)Reii9N zeB~@MfjB1Ub*W%UbdIn^qnOFV1}zj5$Xt;jeP_rHmNQY&YE{IKJtoDfFRzj8!Y(Z1 zlL&TBXc=djMMm?4+A`~c%DhzbLvVIL4z6VeR3nVNF=Lp}Ai>FbN zN;efne$13mUQS^EMN_Wq!C3!|;wRCo#6w=eN-Z350DINY&?g%;Ytcg!y00G|BCQJJ zN#+*ppEM^6iepKtGerQW!k)OMTJ*-c#g0be7&R215(0!k)GM5Y+a>5=se)j&yYitcsR;uH55gho-ahR>O&-1~@w*|(P(g;5LrbKI zUJ{yw3=AAFh7I{81fSeoDL)ZzEU6O7CUXPN+8n*Dc*K2ZBw{iP>=sT#PHI*b`e-uUUh?gP&KyJ zCnAn;p}4S|d}9h?C0tk>D`LKnZe8p*xZCBdw4lD+t%C=c7+}KCQWP#SKTBcCZGKdy zgW}k1fwF`PW5X}lSLmUhwh4;#wno8)%q0ZU2k(}~zN<=}3fqtz+L{EsT}K>uC4+tjn|E7SOi)QB zVuW9dLZeVYYGlcPCIo5DR92AXCIM@h4g z7GV{pt#n$9Vtq;JH$BxDE*tE8tFU8XL8(Uf+y|7E{e+EVF%XF;`wq|4FUcW3xoY?7 zzpFher&kX1#2JOPNHNgSquN zaWpMhyW}sy;2*;OyA!T^|LxyZ7YIlR7YOLz_x}ZX4BTxT{sZ%9Y{=tkVfdZU#*e`! zF>$}hl$En1kXW&ufdNa=;>69iUxKQ~#xSIh-@Y_W=w9uzF>O7B64p2`ZC_kYR<-;w zXxL1dJA7r&c5~ZZ(QoLE@gPJ*STpQe*d^F?m6xctMy?Fu9L@v?CRPw zzHhH9a6tqGaTWLFXUJR@-Q~1Mx$Jen82WB=%&FKTE+hKXxn>&)t7T+dH#+%=>N2zP zvGV7}-DO+l3bQTf#`yIGYMb-V(N4ttRh}$G+)+uExTL=yN@e3InnUAXxo6PmY2F#z zdv=8V{c2#BcA%T9e4hh~g?~CD2;C77RyPtpY{#C%KO#Ce^0yIR*0#&Px)UIIbl|?u zLr7kK3e?^7?a)dTF(nSp%%%-y^QcL1G2Me(i{vuJPa}qXI8+8KY{<(M%5YV^iaN6oguV2C959?#u1OUo6*{?=YMxM@< ze#V1K6-`_I4}mgKrYt)e2gGN_uCQI#!ZfKEXO&rB(W{w zVwWmkFdA&X2BCq(!)~@Yp_Rg5OID*K&r@g_*nvqd4NUy9tC*nB>UJMMzs_am&aP=; z;$0&8niq0D5~2V%`$zVj;e=KNcinF*9;!wQp(#~s3p){`QG*~#TNB5|a4XK)6O$?e zpXidJTG9F*l9TtlC=YO_yWVr)sDW@-VR%RtH|rLRrCR8l)vTN2yEb2Vuw;~aRoYYa zGT$s8cZI{6JEvxLv0vc!f)6Ne;LqN$W|^+P`Gsd~`YBxJHE>I8U;2DHH_hCiX*$P~ zV>Do_ad!v5ExfMD+$#xa6S<8x6Tjlms(p6<8io%j@ATmE`EcIWqT>pr~ zFlcO)@RL+Y|2nkp4TE5K{fdM!-ae-U^V|_agud+=x(0hKJnKog(#Ow53^JqEGx$fz zbx!4IJiou7lDAIIBY2w;{`I0e1Y$LHbRXW|12t1eo1xcO!>w&fwwL+}r(I%<=%Y^G zHJv(SH`7B&pXWA!ujdB&E1ZZIK_%P~&7tpvPt-4Oso|GKZk1Fioe3H-Jx&wylqDjo zB56vTv<8lAdN&}GodPEP7ilS{Mi0(4Ej)d(hjJDR6^#2l!#Y*i*&sB6Yz2)8J+?)> zJks59lzLd-UPM+3M{I5NS2Z=w30f@_N>$DHFDa-3MYM_bz={@>SrbA$@+8$`ZrIuq z8IrWW#jqg8btDM0B{k8izYaU744C*-9ZAsPh!wWq@T5_MU5-j5*IgKlARJ=JuT1B1 zND$!qJx~4O0FXzqOS&b2(c#)Cccw@bXBD+5?PRFwfOm5~TU}}ZdSZIto0tx*nPP?*O}ikC*IyDN%49MqQjPf5I$qiN>eYJ!2w#YS+5bA(K6k)@RbW1^O!>i7^=K%NvJnt`60 zR(36qR;l{p`r7HqVJuh+t7xE<+1o&Vp=vN2INyp(Jm5jX3{}Cs`Cfi{d?N;W}&VEf_+#ca2fjeymlEY%>veeMz^ZdXG{Xb zBnX(_()E5O_5uPj5VL+>BHl{S4TCbf9plO*US0eaW#f-uC&_Yu2zx_12qj+2yYIN; zEt(fvJGYb>*f4YCKG$-x9;!l^JeV5?h$Pb#<=WAvrC~puZnA^@KEHG_F+)mw@2CVOF*=W5S<0R{5T>O$5 zZnf%=`M3<3uoL0taq(1tt$$dWPB42jU!a9n(OM!RFS9a@P^i8pvSm!gQmy2NKV2EJ!xE3(> zuoPWj>LWh@N^f6LV9P>=eRa;wyizi6Bv1jj*MM(LU^o0T%Eh@@UEfsBmTT0vIKDOt z`J}#m*glZ?BoXoPGJ&|!x$Y_Y=i9mELCB3i-f+)V(#VI5{Z)8H@PaHYg#sD)TW`#N zSwqZ2{yrP3XHYBsP(Q*rwv2yI+6A%>81k<3brC=FM<7LS(c7}wudKnr^t7+ECgbAb z&gAi3T*V(R1#v(~Sp4PKzSiqhFFPQ{ouOelpzZ>0IJ~>He7E%dt-AAG{A_8(fBuJl zYCU;)E&1E@X#!z{6I7Moe+M_*^>c0f`}*~51fZ@BB^sJC(qkKge1Dm?{p&SK5`F?R zXI%$ipE_n{z)i{$rQpon>#{wtNqOP(s8!~A28aWiY+-|HT0=H>484Z-wBf8{=FNo3 zo|bTH~8U0xn4iB9yo8%@VoeX*ua@jBwmTjW5yeAw9ZZHXm9s4;1yM+ zfH<+xyj7-f(uT10?n(f$WyJX6Q9WXFk;so-pWAu){Ou?S_3;S9DgW=6CSx-^&yAM5 zuy68LHUnF1m0mYax(@?TeXn9KUEcIFWGz!VN80f9;b9Tw@Zy;GerX7uRmU)~p!PCcfn)%{BO(q!Gpe4TB|`IOB+%SwxGJUK4? zMaV*;Nun5J(iYHnD#d@@^8-XAaa|s# zT?nGla5NT2aH^R|xdP^}%MH8T03B_2;1s-G0HILB=*g1yzYJA1B{aXSMDMk5OS!Mh z@D`!;xr=4Oqd7WL+(^lf%3Qu>+eWx~LQeIkrjaxaC@y|B-q=^y*qB8#=)h6rewPi& zrpeANq9u3F$*C7P9!V$5pyJxYorGp{D1)DeptUg2Vc2P; z8C{*YkOR|xwf*Mk4>e_cAIFAyLoapb&8;=Q6=B4QR^z?^)9|cO)MbYusHzEGpm}PJ69b?!lm6o2gF36iC4&r7VW=*u}|D zd|0=h+TKM+z}bK)857YLCvNw#h+|OaCIQMQM2$-4=09?3|In1D$c19FzY@C?i*-59 zXiSMrjvtqD9yC82w0%c0b{KkiUbp&BVs2K}_N$BM_Opi-VtVO&>SF{5KEHPpB4XSQ@*DPG z-EVbTUq|=LLuL@aWA6(8dc+W77?_~22r%@RLk9G1uEaPr9Ld;ff4bZa_G8BM68er~ z1ZaJ>L<{e4gfQ~(6>v>2UX@6>CDN+NT$InIN}@DQMM;t~a&L=3qg5K}N)JT?Ro@wt zt;Vr@zgUuSxeHc}ks!RNy@e|IftsKPGm`7wJ%XKLQFA6FH)%yJd(%v zhtPhaW^}2gOBfwbu$*_L#R}>F+`i^GLll#%6_J=E+vDNoVA2*NvJjqA50TJEE_pMao+O|Q`Z<}#N^jyY{9cf2 zP?UK$Rgsi5+6+W>K7ecE^GwD)d=IywY4|`dGG!d9ob~2bRh(Dr5RcJs&&GeIY%$3x zG^-)8#16+@cpCw0_Z7kA0@>lB?fNyrsvy?KYGV%mC>fnD5Pcp%#2MVgfwh5YizXA0 zj#0wjt3OjcbANp=;9n;U%IH##xeT3qSaYYN+qRwT*tTukww+A=dFEoyIWv9Hch&XuMOS^j>Raz} zfU=8_APMhh#@1|AV!Nnx0(mQdNET1Dt)ElZOo}eTeUHHa-bEgUEKNp$eXB$Y!;VY2lA^6f}zAb8p^aD)DAwjla`Pe z0y#+fZKwlIgpO5vg7CYkZhznQEql%oYSy(0w061Mp49hHgTUiH_R}P+E4(yW#ZWYW z@2)3V1d0Ug&B9`fa$-e0P0DkFkoYIa38}%QAVZEJ+CUHAgKqq5_Qz!0&U;UY7fp(S z*qS9-y!n@hmB`&rp6Xr*n+D+hulOJ00cI1F8K0y-vw@R0mo1=DmXuaF*dVq;{&-FQlXv$F+LPO zNIw1zYupyRNx2Onr+`h%pb*a7v>(v_D*ALA+2rIy0040PS|l<5-=YsA2g`3YIi+T9 zi!F-ub*(!m*DVGee*SSw#*Z@O62;z3FpiC()_Yp0aroN^%q&PbUbFLuvr0|Ba#ThD za<{go?4`Z^T-mqJL9rzAV8m)^1^4F^+Qshqa_Kef>asRaS@LyFHUpH)cH(NS-Zo~V zHA{-QqC)t(bgICx!Q8^oGi2;z>b^1?Pu8Qkt!Ib?-9+?P@=04ah{=Qk+F@;M4^aTt@Y1hcm@%|yd#kZ7B` z(FvJ~26h4lIR_3cik?ai!|>Je!uAYnL(nK=ULM}}@1+ak=9hce5A&N_yPKoAOkYD9Hn1)y0d#8VlZiDH z=z(E2x_4%^AW$DKht?k+lS}W9&!LCka61?tZ=c8eF}KN_ZcZc5OTC-h)3@&nbkESs z-KV|2in~O_0UuZq#N(R1@|*@(0jMW?&I>w*p1xy=-Jkj2L2 z9`HhUX*K?<`Ff7?P02)uXUuIUzJBk~%^3Ibgm8-K$kbHtp1L_0L6m?P>n>oAXxu93wug=7Y|GSEEM%kU6vUVu^U&mYZ#VRd)xM%?ySVW}l742jf>K5R%9J z1JyL!Ng)#tUewn5@MXnGl_q=7kG<{B?wJ4LHJ%wc0ISuG8GmW2i%mo16C!?h`?Gt<=Ocvzj+M%QZ+B zbLRuWzFbt<&MHjA=DfxU3M(x8BOHj@S#T4pSp7b zqh)?@9q+YPb@)@;y~tbuO)rGj0%JC+xOzHGNkUJ29`q#*+$1=4s2ibqi3_Vj`*IIl zi!mc(Wr5VDU4?IyTLm8kzO*vE?ir00l7y&v4su}eGuUDy2d2Ra7!LNtdS)t)>cELj z5$z~7a61`o^w_hjqfGpmCx^ZX$7h1oI~e2H za><0IH~_O7;J6z9uXaxcB|@GLbV=+Q{GhpiVZ2oH5HrlmIl_Hs2Qt9un8fug%lD_a zElgPI0ga4tH6WrY@-=;G83(epxuRkVyfr0nxRTb6?R?!o!C$tlx&orvTI1#FpHESH z*1lqc&zxjglGP%#Zg7m)_{IDX5*+RQO-~%7eqC@97!hWTWnmO<%F}4_O)wW}qrQ~L zZ~0Qa_zKIU!)aNv-0^Y+gF(M%p~@Gd4(7P@BGkll=s|nJ39~{$2>QzmbIX;7K9wjLOO;smm51zCuM?&5eDT@X!F=0F13F7oeLR!S`+*Xp_QPV=wu<-pUTeHH=Wy%CEu1EXh6nAj z(XL2Y-&%CJT9X}E4=d(5mX?31y8CTMm68kh*DUk99-soi+?A+<15q0gDCDfg2%(KT zW7IuKlc$HUGT(Lxy{<#!vZhC&g*{Gjk`WLMk2d*XdQtOEaHfNI%b`T$5UJqF$%eIz zm$d8&zv-SFd6OU&La{X!P`lYtjqH~>KoJS@cY|DB1DPmv%uoXYq2$EDY%TrB{QiEL zis!-C)x?IbIndS)F|A@%k8Wl|8~m+PzPxnwgJT zU%JkE7gucG+N61B*OuGp;z8{2ES3s|o@?=bc-{^Ds^dS<{Hu}jj+nsz$q_(*J;+91 zJ-zEDImvFVvFMZ69@#+n8DU*=xD5UTX5lLk4MRyVrjbrqN;^(hSRUMP8+2MOy;m&} zJ)qc`xIKZyU!Oem%DR0|9ymIp7w>8S>5tyZNA)kB28fqxcMuULz-c<^TtL{qGLDj} zoJvUShANFqkH!~ohwlS!AnOlZi0}Hyos>qXEp!urmJ;%U1JzMVY zv!y-PKWR4@+;$Axp7xE`A2H!4cn+c$SIzLTY-%ij;mzTFWcBFVlj7iE4T{l}HCmaa@K>OE{{{L~M|3@R6QnR+j5=Z*} zt!gB?R}%t1zDPUNv*AhAFR})K3Y1Z*WWE$&3-$C&!8$iMj3M1LWsjNb-O(nV3UXp4d(u$i z507Y;hrCqc%@HMZtL^Q5yVX*M+!PWIz9ILL%FweM_(SAk^rg4oJV0xScvYL|5lumV zT6@l?Chq7(XAq58D5>fxGSRh>J10p2%8uK*??^jIF5VJ^c!^1i3d$#lXeBM-pyaAfrO@n9#h{R=b7(Z-1%Mfd(|00KWeKiEAVxOI-z z>%>y!7FZ7^W_B#+bexob2B8I z{yG;Rafu(Z(#cJLg8b$#yBL~iXvoaW+;(fW0&!yN8u~ZwbB2q1Z0O%W0nXz_0^N|lg^)oqRTnub275xb2B;7Rj7&% zACk)Y?#0FC&x<@*SXc;>4ih`@I59Rs9FU82Zp7>&4@LM*Nr#s0pUxdkGA)OFUiZ(-SnDd#UHrL zsBS5Zgl5GwY<2|59X*XbS_kMYljj`}!k zNqOnBJ;z!WJp*!u_8QxopZo1;=P$H%02OzX6{CxO2{_xAeuhiE3)H6K>jQKjV7K{L z5kX4BDmd9ifq<<=+;fyo#IW#^Y?jte^9{(ApELm!omt2_Jz!TcX|W?qQS`{xf8$+M zMDo*e)E!hz)z3leoThDJ(v^$U+J8gc&lDr@ubfoHzd2-nNVpVD!bH^+XD$pY|Bbdh z3^Va)CcWsvE0l&_JDQMj#dE7um9OUBt0=i`_G${|zM~)?u@KKqL=G@{Q&aedI_nOg z7p`52Jdh~9GWTTA8IeI077CZN80`)`2Rk>dB&pCe(iFjBBpW3)o8nY8OZ*dteO6xh zZ)?oW;hQbnTJqACiEf+Bj?USvo@7<*9_fRaRn?N0uhC=Iyqg!I4%h%j4Qf(jM=BFF zb*3qr2b%gxD$C7RE0?vCI71ms%k>-`qZWy)3my``LKvmPF%f#a7pyIy9>(_{np59s z#TUOH^#A&Y{*mM(Ym5W{U~BRJ-N~5$bK0YQ=79aD`Fp06kJ9&FIIW?s*N9@vN))yI zWqo$~{ZoF9_`JVEf&)Wz3Q}CS!;0;jXNIHxM#nQ*Fd{HzJ zIMtBS?R=szNZk9pzEEmdxoz+^wzO{;?e{(e9Rjv^Z_-nPuj}PRJFQ;E0zd~p3H}txI-fnfzqoH_q z!-EYtNMH=`2DUeFG+%G#rT4w+DdJF#ugBLNG7$XH@Axe5SHJs~8~LDZ;Tzs=8J{;w zkJ#<6`|qs{_%*};H_&44N8P=O@@F>o!MFG;r%=O8UVgWbJoS})MF{Gkvd3)6*@=E7 zuAk<=U<3m$A6<#-0%o2E0&6A$#J7n*+m z2x)?nVJ8Z>UAlG7EH~b^!LG^;nB8Y>h}{cW-($f>F1Ge2rW&EH*yhehE*C;qeiEy! zfbF$hf7}ijo+=EEz8o)3+rdV%^>g(Ej>bdKyPE3H$tH@mqX00-u4KM!cOR2q`qrji z0J(iH;5DjL)|v(gJ-|JJo7Qq@W(QJfL3pQu2YZVT_F5X4;r%q5L%TJ#w_tez+hTx$ z2HJV=tOM>Ar^EvbHt2g`xOzTjYk3;G__v-{5c6)ZeNQRt|60K?C#HI^I>D% z%theh);h(MVh3vY(tGUtc>uyWYVg%rweie1e#I26(*A)mlo{jAa_?$%-7?&p-fKER zxAIwV^=YL+(ZR|n*J#7PeYKvJkWxmOfK)FhPG@>2w;ZNCrq*@ zOlE7QAgly8Y)Q&~P>gHO6UG+7ZehRCu&em72AP6`JErr7UojaOYAx?{t)_|W*D!3< zSvJ+#z_zr|ynMGm{%rxToH@q}iu!P(E5-4=B8Q2&pN&CjG>Y-8+dUva{FK1h6G9M2 znH%&oG)x z$uEAyVAjAa2Ss0`gyiI`j0~|srK2?rz!q(T@-axR4M0?3*Fng~YUj|>17@DH41qp? zHesZ7r>tCB?jz|uYhIhjE>!iTGNz%M(omS)KP?~Ir_%H%kBqL_*NyDT5ps-tx4Zrt zg#4ol@7nT66<%HcSU%=MlTFbxJggonrP&;c?N4nk0xw>l0}hmc;} z%7*AG%zRugh`E@SIeik!Z{s68`rrtS4JeluKk*z zcN0dPB^UgU=BU*UAget;6K&gbNR5W@=Hzh;d-`&iIp496a; z5hu`1tFa1;6E(CTm1bOofmnF3Q&G@QP)VMVK5ECA3V8shP4>hLy(&9)mz)1-O-GgU z?dt(wL*39zkll;9GjP4N!A*%7ddd{-oDJyg6iYSvQPEvHM;k8+4B!UTSB`%PHqq|< zSnY^AL#&tK9~-}rTJ%m(EQKZCPgL2kg3&B4T>EK9F#UxUaklbllP?Plu zV9Z3fF(>j9sI$%SbD8^^Y7nuoJKv46ZqWQ4)NY!bgm}=M*=stp z_H;$g2!Cx6f(PDCW&0%wJ6~^aLr|6ycORk@GDmgUxihdTEU804-gIU0eg`vL|#+Qv^&^`0cnn{?6Ii=)=TZzO~)7rI)SXd0l^U zvU>3F^2W-Zu9MHq>T$ccp^ChBIc~!(*xR+z0&Y1>ql1Hs{%qVVqDgl_t4vKdU1%P$ z(9z136m$yPB(Gx#`fCX{|cID-8oF!39t~)XDvnJyy;F73aNm5%2cRpuXu5zjA zPn38^tJW&|HHTmrc80)$J-7Fw!kaR}{c$2z5U;93QNfm%5TS7%Mk2H)OqcICUM>Ub z^R!Ihn>5;QrTq4G~8iMRn53zilUZQ#z;A;Z5 zNLpy5XfqrReLy{_lZ(QV2C*;ZTooXI1p=*tipz^uu2c$1EErst>|oUum-hj}q^7vLHb#q2lH;)7g7M#y;6As|a4L(cYPY2cps55JVq8lct)ky0!FGM3Ti&D7h*Rbf%TH3DhN zKfvmpf{ptm`hhysmWNRTQuzp|@(70Kij&=JU>Ts#i|erzb?7V$Ve@f-{pEw@a%!rk z15F%l$2K+FPb|1;YYkA3LsX^YHyYucbo6V&?4%&h+!ZmS5K6P}hBbSS6JE*6L17{# ztfFX&@0IU*mmc3nTwDesM|8w@7RO7~gvlL9iDC#1=!5TrMLVw>#JbsnUgQWuy<@;? z>7uEW^wk#Jksp-wR_!>>n(XFT0KpPvLXpXosK*DUvHhEY=V0B!%B%iMd%N$x!8o}zd z1yIKFBw9qAQ5^0A(l zhDyE|-|_6u3#w+F*qd*?O%K;DYB_h{(rgusd%4y+br9vo$MoL6Y)eeRm>2LjmnV@d zt^!e@&X$v51qIW!l9JL(L}3@pYf)$;R7jL|CY}37I(Y);m#XJ&02A?a?QedDT>1c7 zcwb$me4af!w{U?}lJ^x-&}S8qm8B zjgRI~9L;|B1{Eh%R84Nnn|(=g$2JGRY|x$D#c|u|gp? zPoYl=oeq#5S;_U%RnvSrJL-mg$%CgoHlEaWVL zDPQ&r4JVJgLme^?K~6lQV2cp7kFZDD3oP?vyYMA1cSH`462O_4g3xD)c20_r0;Wb{ zA*Gr?no`b+Ov5bvP2^?gvoq2Q zL(zjV#|ja1j}v6+*W(q&44cx-C=3g^CP+D=2=A{7y1Y!CN;rb8tda?eJiGCTpxdSj zsbQNg5Y+uiFFdeoDN`NQh~yEpaBm8_+Lnq^&7R;`Z;X2zW;T40?pJ)-VZyh%?1gQ{ z(j|lMw@T%+&FNK-7jpG1w*;Ffycnf9VjnM3$v_waEgDI4%!Wp#9OY-1FwAE$Q8VUb zR!F1ROXCO68f^-9dX6>S42QIzBsj5?Vm6ejBuX){k6K8Bt|gKL8Q@TnieQ;xLK})d z7CMv=4xh%9W%`1U0&pQS&w=VopeW|Eq>y7r1kM$2D_}9SuT(FuLG<#IsA`^9#tIR(}U(wpXSjwt)7SQhd+VSZYLFu*|N z85Ovr1k_jm)K`LX8bUC#mW^}QW?rgAd>c>aWAkJJJ@apH0D1Ot~#A!;7*Us^P>suaAJi(c#H*0klg1WTDf? zZ8PO8_@3J24m#-!s6L?Ej2QE;r{m(fdfzN9^&!9eG(5DnQOL;QmU2viD$_(`1YdE0 zUYjULE07sd1;v0Mkgk@Gh$m6tiT4|3kT6b(BaxTIprlBd_WpC43?f?V+pA|~OO-hO zc~yrSq?S%Jx(J+n<5hP{jo&;OHpq-yk#w=IV;f}d=W}94OCtcGFm1Rq(<;Mpy@)x6~cl8zC>`$rIs2rex)OIr^AOCH>=dX0C>i;33VKidVt5s`P9M>a*T4u zDh|Mg)O@f){dFG!tE;Jjo6tk7=}yTG*{l(zvI7gmS1 z3pWAu(MN~rFzNk+0F4-Q0V$~Nt-VqP2xe@7{@O14=)q#vm>b@H!?WJp;K5}S0h&m4 zLY2b8CSs&6MLgzA#9|s0X&rYCpVjV}WkC=(5tEclr9n!@2R;h24lV{%00UXXfThTD z3d90#7XoUfUjzzOK{Lyh431>(Mga`gEheXxs5gt81X#ECuUaI(v!tgtv!OkW@|WiV zp++o5us0b)>fcEQWxp<=%$^TSCLz>{o#^Lq3y)D$=}t8(2RAGt#w_G#bYbTQ5rhQd zP`d?^1bwfrS8b=dGz|g=WepQIy>8yi4h(+OR7}R6IKRG!Eo&gK| z)(c} z{#oOfu)X}-Kk_S3{TIWKrIE>hL@6a{+YNSvt`Ak1j7V4#Hp+8mO_Yl#&ayglgH@=A zEFcLSjb9sur2O;y&dsdM{FUgYBt58$%%R=!RHi#|Hvjg&dL3;6(4Dehn<>&;0?H;L89b!28B^Oy75xIfOaQTWyZwea zexwSM)4MRJ+G6Tbv+fUOFB(&pbY{RG;z_?Anv0HO4t#K6K__GsYY?4BwHO|Qeo$o@=RWLM&F=7JV)sqejBY{| zS@^0HwTSl;UV$IPPthIyb^e|(Tnyt>G;5j@!IU3F-k3_l9h-~~k&9@EC~wJxCckSr zR0OA-M9q80tu*F40n?2}t}Ru{E1E~@b6z`rhnlaWUZSf?Yx-H__m%r^7ai-H_bbr7 zwDiZrn;zPHwhsSLKZFxTPJ}zEa}qjG%?>S8;!vJ~aX?O-B)tf#H6l0$X=vaQ#TM<& zod^UvK}eWW1@Xm+4p^5Y=Q&v+Ew2ADKR6x2fH$^@-bujt-VZ$B;&6~xfA-_QX~gWu z<|MN?06A^q`D;O?x>&VQQ$ixBQm69t^Kh*o|=B=5qcvko#X9v*+RM!Auy6qo>o~Q2-K}S;`?tpDA}C zY&L-}EPg1Ji4Zys8l#R70j5L~4B;>gJ7V$UmQO7lTF!Mca$@c3<}|WDFPS?IR-v7$9>qVN z7@AU-m>8t>mi$_DQ=jW#=3tFhFF4@taz0mvuri6BB{I`hShW=4?sc#2 zN>{SG6oqa^WtO9?Brnsfy>;05)nTp5RQECP{n+xc@oC8{guCyucYg7tq8HoyZiv*1 zTjd$q?<=RzmYsWb+LuZCX?h)|xh#>xYQ3V~YHJQi7Pon7~?#F4L;Fz>csvOS0l@C3F`uf%DoKd@|? zW|Wh!#T1)E5uIMo-g=4JhqhODUVQamk^on^t1yLTleQdoYC&%1qKB{F!avA%=CbNg!2c0scc6L2#$CV& z!cc>GtA|ny)&4LgyP0t3J@~A^V)-*5dwNTD-%~Q= zNC@}%`rnp7Y#_zJLs|d;h=~8W1lpOn{ihi5{_Q;)by)TE1vQ+WsN~=p%7N2!rOo7hjIVzpUl$L;MB`9`K06P4Ly9#&6?$4%Tb%^fXa9!4aPb zODB_8R{zhHuAUyu+gLdWXK+)hqTb4z;Oc884Y#khq`~{Wnvs+a?1Scm)FLxec^QWm zo{Cw9uSVMvRYfGW--!xpVqye7nF{e~Dwd z@?O!&zyx4=@;Psi$Tz6$u)$ghioM?TovY+D;;RZjN|RKn@^Or&2tELaTnlSzZcC$xI$zc@Jcs7*|UI+AnXc71mWm}k-qQo5TIey=1otdk40Uk7WkJT zyEH#gGXv1MEdp`=3l1S5YpY_X{M+c@x2yXm9RIy|rlkBd7desP&pC%Fvi)`u z>*{KgVx>_M&-F`IBOi-M5!Y)5R@>%4f5#s1poqvW&-s3zGrj%N%8v>xu)E*SSHJ@+ zJKUyQ=ygSVblI`@xJ@YSf9Hzmn>Ah=dFKrZs=F4XonYk!{ycYn{RJU1j8>8D-f3nOnV5BmNoP(#<<7*~0=8bmU?z>M)_oFqDO#`HvJF?Tf`2L` zt9({*o)S|=UN-0#?;S5`JbecGlkVCU;$f@{qTW6^0)q6Ov=dZ>{=Bh#TL$Q|*>{S_ ztNR_@$(?d}G7LrELxnAlH1EoZ9dTh3|Cm|hc^)4in{u$3q6}|fUu8i+l+{=if$3bXI@!}--Pc&YRPd1cf31?# z@^0v=@3=q2Rb&0N15j`T_~rny@@Bt#GVQfCHTjUvTl&1^aC&AX_5$sVPCN-YvGWHf znOk9zk|r1JF6TEp0!))z3qGwg!9 z{1hJ6&`{^eJ+f8X{!if55i3L^Ru4&b{Tj1A^$%JKSj*b##

t~b34 z%i^Hx94hffZ|;*f-JdhO-&d(UKlie}pR&5&JE^|k2Rq+)H@z=E_ec1ey5BRYKOZAM z&m%vdNBG^JHNOeKKDRS@mp8qEzMuEMBlco_KdPU6A_VdGV)>>3hJmHLq^w^;L*Zry zoL_R83xNi_luou+jO$4tHDwJ>i6@vW=<3f^lt__}P`X92ch?&Cd1Q5$pFm=a&6O<{ z+bXE%6fu?d$@7o~fcP2>!Y}vg5+*&kGsSphQ#_hC59k3QT(gFQn0CBI?O6tEak{mQ z<4VLi8X9|=(cfMgVE z4{O?Z3!9kh)_@4d%TZoUDu|84m*zvbIvF!ImNGXoNh`UYdU{gb;2f|u#CXF3af3R$ zQpM0aZoarQI89&GO$lVpz)#l&9@aaXLgZxV^GK}-)kz!*(!ePKrVKX}Sx0z}rdxSR zZ7AN|#Yvc4g?Fnvzv{Cci3WxpHDBM`bW1J?>d^d}CF=yxEE!WXCmra`8i(EXmf?Zp z%K^ko-gE9ya#YMRs!a>Cvrb)J7;q=ixQp(`AIPBB56l^eafIkU0*y%DxGX_TLfUft zJ%FxlW&k!;-UpJIV0YV+sJagHyBJ!wo+|~`83Y7qLT5)y^{6DK$4!r>KLbZ?KGzq5;Tz3MY*rUf7Q3prwSc@+Y6eg8nn z4gaD{uw~rY(>M36H_E>R_>est_KFaoZ(_Q4YarlGh;9=VBz~YS$^}zI8lj5FZ&^)D zw-1tX0|vQ;htk|L(5degBp{@{n%^C==hHMfAbi$X$_-n*3Z{M(o^S0kmd4EXcgZyc zS4$oL8VydS-oQ%@Vg&U3l%#H|uFZ1B8Wk5eb;4c@H?{$Sm?)I0W3iPv>SPovGJf#t zaiNyEQ?t6^>^O{EQWVn`{J?((|I3~h*BMDyd1^;2y z9E*l6nyZ%^AE!a87RZ+sIH95JB%}+1q!LXj7~{E2+BVG4;orWRn1{QpOezeZD?y;G zLI32V*{!V1r~I#VI!65%Fg-BDZHSU}86!SV`&C>f%6y?=0p*H2vmIf(3x=uCq+(&? zR_)t`4)@r|L#HmBjQrLNlS)uqILBDmhejGL<&3Bx8KzBneP5P8m5>@Ko2$+=@U#!r z2ZV<2Ho3baR%gQ$qybAry$^U@SutE!;+ucUoQIPIGsrg%=FB?qlIOfw^3FV?iclmNfEZ5LZCu#V*2SUTwQN1SR#{aGtS?*)4@Ki$cDL` zK*6cATQ@3(wk16rga{g@L0V$cvgJFvqcGTb@vAD=h=|w-H;LQIKu;8BnM8>|uE+x4 zoY2s}xOme?s2w!zteuK&AB0mCKG)<`(+={CpbWI5y$b)J5NDakHcZEtfSjbodKXk_ zTDuxgULR|#+TngN)C>ZH(y~A5S**RE79~XvgT~Y#$khn}b>AljHm1W%qFFaE?-ZM% zmo6{juIVQ%rhbrC*?V{20x?n7ZZWvL1S0hjx`$ROETBR}ptH)VWh4h5jFefP5;;e~ zS-FGLQ}>ecq#!-2&&g8ST9vEErHfXrY)!(rR|QQnP(~eWI&~HdvsR6o0!JCCnhO=U=w12gPYPY986;Ydyr4TH8WS#uS}|y_{$mN zKZ6SIW8jU}fga+izyVPP`t$A}JRB8NDTJvZh#JO|M&8A2V}u^eYVVax*NO4ts=9hF z5gR#-pH4KM$A!Y?B-sTFW&z4G`Jl)V=q()hD`S*p<7$gLgLRC-P5|V1K^dG!eaA zTV1Wkcq(xJJ$Y%@u;yJX>HXLY-}D4M0%3-B;cMeW1+U8lxFQE_k1+hx2>wv?H%zj2 zV?nvbBjLTVU)8JWa;e8$R%2mYIGy|ss)wbNR2*l+P8Y*&IS5<6y)4|72nj3+*>Zzq z1gxxD&{AT59%32YpMOA7ru@-asMHz>ZD8+ME>1u=OxV=_MHM_*$HxPwH=I*;!2Sha zqp1ZpPq)H|*Q*nspXW06C1MUfc1cziP}x7F45$ju%nh!GHu}^Xc3*J9E6?le?K3|a zK$GVq3t0HM!MSEEzz1M4cL&pWZw2eRL)XX0RrmQfLaYrNK=P!lXQCuFtUK?Hm+)t` z`#TMsq3*^u@17at9ZZp%xd3VRZXk5TWQT1R*$x0+&uwc)F`N3?gXr5)aBu03+vzzo z20&_U%;_g9=Q7`eK=t+ebDD$8JAkBwn0_pve`nFr&0-G#>*P<#9Z1XbH4k9-<;jUF z0Ov`T3Ys5vQr#0;96;g>IrgYKP&)6{zw^)koiLt}a1@Y;P z5m(mq-)(KFk#wGd{RQ`XtijheZEc0Z6ku_ns6Tp?Uk*!b0IxY_iM~+#`|V$bl$l9S zOzjp{q4CJR0`Z!gccCAMx!GYiJNXF48we4GXfk|e=*0{W&WI>OxKN|6IHB_Mq|%!O zVu^f&a%=t&a;F%zmafu)UN2z7mISVQ`8H^qGsB^#@HcNiq{G}F;yH+WLPJ|TU%B>h zd3)C;iJMZ-x!a)6u}#2-BN_BQ+#IXE+)W?$Pt|LOQ2mjaLq|$1FS#S~S)Fk!9kN%_ z<)RklbQ^)%+-@`%&^f2>Iox`D9Ez!`NVN%IL)rqs10ZR! z+*^t;{+lm$iPgN9edh}-I}B!g#Co#~IhBW~6*O(>1z}!Gs-{@HiJ0o0h5Fg1ZqvI@#!{(pwubR&Vq68b01^ZyxyG2` z)C~2X)IzCm{IhT7Au;fAppRVA0aKUi9i3%)06LC}GH3o_(2ao$r7b?d5h$72?G=iO zCwxM~mi%ZD^1`6@Ed0-?MBvD51d;!waq7}5Iy%KG_n1WKSb&edmnLlxrg6O|KR1Lu zQ(zo-fGRm@7TI0;Mik3CX+?#VQ^N05??PcTaPn?+2}CK&)q*=eSqJ=|03Yx?Rl2`S zrM_bhlPShfZtO#uKwRh%#2j=@qEeTUjrBu%8Q^1Y(W6o}`f?-OXgnhxJwpU20N~%s zl)ScW;PNsBW|OT%muvBIvlwHx@v}87hIyrw!MYKO%++%`uTO*ww>kYo3!H0nJ(<=| zlixN*3T0QxUi=J@^nsC6g|VM?%AHiPSKFDkaXjw}1usdmXs6QN-YW zq?b88N=m)mF@rxOtWS;aAzxHQHvqn)=rn+4c?&Fq3rh9DerJ*)`Q*y_P*?eXuVEs3 zdk^E$lz=@Jv^^O+5}r@fh%l%AG!EgmD|r3oFA=0#S``;j3b^Ew>FH_|c0Z?ad3)~+ zqu(U(=06Z-h?I(+?~!@BuN2Z6)tvoIDS-HcGPH3nEof~a(6yR*?W_!G1&03b?g!1e z9Q@zUwldXdzFY@%A;poCHV$h?5@0$SSO~X0mZ(-vd=f7u+RIEfi_R{oxZhth;@5OWJZc128x;no&Ny4P<8ek!t(R$o(L_~k1JhZbGGT@>=;(Tw9hc%DT!={#6?lq#<^m^vz$D*C`=5821 zp2_a;e66hLZRX&&6RNG_Z-9IbwN79wqdjX+Hnr{>tj~eqUl4)3KlqP;{hVuKsQ2I) z=E-GWb~4{~3Z|I33s$baYA+OeltKg?5T$PA6T}dL{4i1vD5QJ{Yp7~bf)GId7*NCZ zVa6CRSM>~WT?jJ7C_??SJjej6D(P2QrKBL#%qO=3mdqAi*7JNIwn+ ztOOH`|KT@(8|5J|om{moFjzzq&SB_tR!(hv$H!bxX;z{C4FGbvZUZ8`dlm{smHkUa znwDO`hT%^d40H0en1ZK%XoW(FLGL!MSqo&M=4_60h6-ExGJO}Z(upX@9GfQuAMGvM zhwP$fL%f}Xx!_#`JAm9)+@sh1%<`~an0$H2DXPd02Xcz&;?iz*AenQXV( z0?==%k5KJABKS()^L3ygCHde$7?7Jg;bv&m2LB5m$_^7W-uto;>xWaG|~?>)X6@1i_n?A%e_8?Ykz8xYYf+>#wFLT085v?wr$(CZQHhO+qP}nw$azNZS?euiJ3puzMC$dY%pF9KbOp17*FHI4V7hs#1{O1JTvzExCL z6xUZ|uG^%>UJdguflZGSdI{z|_%XzW-USplR|>TTJHx!5O{ZfJZ|!g%O=J(vRBP~H zT2&<}w6+$;wKD_wx7C(;vD1E&7GAAevLV&PmI!yq5%iybx36j2kJ_3iNJ%pc?S$!| zdUs=-$t;7E{+Yp1%{FZtU~WCI|9p&)_pFIerr}_i^Y8;h(F|X36%U4uNNK5TmZiYU z5PQetQ84H<3EsVwW@@FS*^r>Hn`;WRbAdCXI?jTB`mnb-Lf7*R!b;{SruL08pVuLe zr+Bo7Sd&NIzYC4MRmzbG#tsyamv&fBz?+Ad`48wR5B2hgaSj*-xD9*hhbKlWF7uT4 zQx3^WB0Db6jlrcJo_CS`ZCS2UsQan&8eU{WlgLt;IbJ|4G$0%y(pOSh=m2#*xYP5(T6iycA=7mLv?hrCY-o1w&CSlnSaj zbFUn<=E}r7c=7UIBHjT5!gRWGI(A9KmuTlR)r#8F+`hb<2PpAP(luSS^MYVfxa8T9C!tJ-i!y_zqao-D}HmF&bl z#qNyg>SCLcT3C&~2Z-QRFR~-nsX_L!JKbEpK zf(mgp$IdcdHgM^lqBqaxDmo>P9#vR?NLhoy8akl5e>y#R5R`2jP9{^N@jTCIZI%IE zvEWymPduTT@$51;oBtlK=eK1}T-u)tC(p-FNtoHef+)`{$X3D044+cmwSN3JX{W42{Zke*A*f;{n^8>~(fp<*5 zhndNk3E$Xe>E+}{9h>xUx@V-LEQ$*j~$#J04!%bs#BN_E3ze7$}XS2@(Tq#{z#+#bW)qW|(;v zFrL*@8@XqZCEqrqh&c{w`q~*B05)PcuVeGLwkM}yh_42XQA_LQar7UatK;7r{$U+v z59rPP!W2JkJWV^(wWWZbk*fc2XS6nnlS65>m{1813Mdp620k{2P3#;lHcf z<#AQ<6SnvM!yNzc<`J0hlcH|xPl|B4iB-qnj|cv84E;T|P(}`Zx#748p_p{--tXf< zx_VYw4=(=Oo9crI@$#FxP@WyP&5Q>R0BAYb4n|aee8y6CyIZ~%^1S4@&kRBr`_pv* zk3Z*%hYQ629WBrJeU@{G9jSu;qX+ufg9rV_hW74`J^l*N3d7zi`Md;(Ljm^eRr8~J zCiWLf-h{E)ngc{k6AWsL?Usuos1OVkn?oiEEu^bqCzN5zg=Q(x1|*nvA`o{;h;n>@ zX5DvZlD&-KS0|I+g7Lo@D4oLQ=d0X|NydfAeY zKakbUIYlku)4-7H>(KGipI7465;_`2;Mw_TT;+_cZ=T`n-s|DZ`!ENt?NYB^W0X_( z?Y&Nk-Re>A5wA9o2g;%Hc z;rF$1g;+6f?Da{W{|Dl)lXZwWf~#GJVoIhnG35Tq{-OBY<=eZ;+Ox1-jUO?)o95YA zl`HQpO59BU^#X^f8QxWfai`D3%(c3DW@eCIZYJgA^@j8kfLmgzr<5-3YGy`tYKI!h z!t^YO%+v7|CpT*d^<9LDV;+%mFG6b|U$v2m_VxAF0FFs@C&$L^Td`Z6DKIm2IUVb0 zI+$rEhi;4^`__~ra0WE@=fX}6Th^MP4PH2EDKE1z{?6b5nLP85_$C}%e2|;X6C5`> zWR`}D2*?Asau>UJ7Tl&yn1nn`X46QIHOW>iN{4Kg?B{m{f7AmY(Xj6!$Kh%YL#Poa)`Sb&CR#Uc7U*P>gT*ig z9)y>e$kjF^`_{;r_9Vja7n(fH^1(CHL0L1nwIw{M=vg6b>Hy&s!?~jZN?TF z@~p$ji-myM7NB)WikeJTO(S-^8zf)3O}$gkk4qI!v6JxTIA&Ym8A|Wy=}yq~`JcRO z`FINn5NkuENHE#f$<&&MBd`X`dGj*vObq(OIUAPE>|MXxsA;Dr`BebjKZtF~u*2LQ zCGKe5w+6l_Q#vV%N(AZ3KycUt4RK6T;7B#+-E=Xu;B|%6;~a0I4-~NO_Yx@_K$65A zJLwC$mMVZv2gf$ZX-uUMT&pFTZ z#3#sOaER!&Lmj!QlWa%R2DIz}lDNsG@CGnUBa^`SauuvlSJJMg;2GK7%O6Qs(_3(q zGDcApQzf|#m7P0<#Jy7LPT@8H*kJ2^SC+j-nyvulkqYKQG%(&H;7KcsY1ZU;UCp~j zvTtL}etgdBP09s6boAa)w(;v_M>NPoaVCVw+Dc>KU{@TDT;XjS0}m=s4Opo_Jn0R_ zA+{t*SV?VV3|{B>4BrJbYKY^kjL?RB+MA)XXUT`_gK?!Dxn0$@*SFR|0vA>oirpT`%Wf^2$qp z##!U~bd|9obDeGh-b}F%b4^p-cT8B%_n#?*sdn=Bw=%Sssp)T#5c}`itDVqCt$;CD z4Sn7DsC#p9T`NTQl-;^xjHGFBzcNPXmL?u+flZ!is zO^P}Dt{+ttn$(0d1xN5Pb5Niv)|FCF1uU#=J|*^^FJKYD0DDkWP;Hv6rSK?IL0;F( zvY#ivxsl0V9er7t$s6@ju%^MKjA(p3lnBFvWMWRztjK`wd(>atyfAJSUGUCIKCc+V zwO}k!hf-Q97RqJ~TH+O_Ln$v5>Cw_XM&U0-WnTi$mu3VL{LBu;xhq@9E3XJr#!GFG zFY_M7EcQ+wQXbOvA)SVHxM5AaLgsz6eaR8%i(ynVgt*X z(MoyM6P2_^|_qr>z>Vy}A2@Akx=?*$iQ|1(+x1ij z`iDc`p+WX9`nrQ&QtP;@)9&52c^6&RcVFw3ZrGpJd0V-(TJG?8cwN7~DGx25{Y!T_ zhkNpn{_4cPNh=WPBLvt%25Vu4Fv0&k^WAADMkUd%hEQX;+b)56i=$WE%rfQH>u{#n z8CM1%CRn;>JNX9|x3t_J{eN`7-;_)CPEjC<7}G<#NsKxe;*9F7k#)ga@99hjGfa&- zc32_O2Pn~rb+%X>*9RO&2)i?c0ZOqxeo!tv=Z-7;!6*1E z6JV6gnI1936m^qsnkbe@HG~klbg{@Za6}nE!W5xK8L);V^#%evff+%BAuQUb+3Roq z?dL3`8N&`2f>HEFQx{}Tbo^;kDJ5&LpOW;HQnv!)4sG=Jo>Br{=k&jzwUlm0#;%vk z$ZNvMCuM(4r)0+kefmgyido$Ze4lmTh_eA5MfL%#pHgMcP{$20?NF(a%ab@?KRV`I zTmAOShb7c-BKpG!$!X3;ghr~!9lNZ_sOK$R{g^gIIoQZKQ*AR|FY9C@H_#k2UN7xp z1JFI^m2>@2Xc2$GQ&B9KRGMiT_=MW{t9+>!bOG84|Eg8NYKbm)!lfW|=f!V!zzsNripW#V1nM$q?w|At$7rk{NWPKhQGUlDmH+V885qXmE2{x+?nB zBgFC~lXd%po+P)MWxfRPO4})w?nySgVMCG&z=fVp1~_ ze%_>x-;-DPV3r)#03JhmZgZXTur84h!d}H4d4sRat z6wa&!k*)+O*L+pV(~kuqoKI~vI&{WKYfn@55>BCKt2zw2FD}!mwu|_20_Me2ZDR7c zt^$gc=uD`n>vi1FO<4otENpl3aE*PN?(kD;nIcwnaOFv-ioO$>;6@`;ldKC_>3)31 zui4@OCCM(X%__#(GL5KlnA2Nn3GD17ilL*^?#isj$;51#=D>g)5i+dq3+<&P@cNqW z#6ym4R?P!yx>Lul)nFYDXZG2WnuD~sVH&K#5sw~8aCXfk;G?e z(L|LW6qpAg_dV<&fWdmTS&Lro&&+(Fie(*uprNHHfQZzc26&;1wen)6{saMCHs<`& zY|PmyNDrgy3n^_;Wzcpt%3jvPHh^2;;+h#xe?mB|fx5chz3NjL^gT?HWtjOHFz8a6 zV#^DIW3xzf8PpOy+_+0&{N|X4*z9a<8TbR+)_t#}`#@YAq%TSjTS#S9Qk<9-z3QQ? zH*bl$-v;~Vt=lSq*o+MAVBe2W)TzzyrKwQ8^#`nhJk?)lbY+K69I96Istc*p+pih(KMB#;h92*c7^gFo=$+MY!TA6bjCA$eD0}zpIO_Aj4BUKjc$y z{d+DOBB>sclnm}SIGco%LpWS)CesAf5%-I%Q%J^}Gp{CX8}vg&>Q=yIlFj1-^wFi+ zQEd{|Fhp-DQU`i{LRFJ3`u68L^IbF3KMb}2+1PM|vpU9+c4P_;Idt8D+IyoC*3jMY zDA=@C!v^Efs(2f)8-qdEVkj&Q-Yf^PY-^7(dAd8Kg{$oEe4fTX*B(;v=Fx1q37y(XkA=pD&R%S|lfP6Z?K;_v z?rZzFB(I8cu6(%?M?wq?UD z3f`rN@m{kHC$~(I5S8kuzd*~eoF`9W<`Nu1O?U5*XXeGKJZoshs*F_Lr8G?`zR%u$ zZSKO}y;^ixV2^dQ?#n)HW0QWG_Z4yL8kaZVXv~c#jBU{kY)h>rgL;~f)VQ2!*ERe) zaP8TVVdXT)Y{i)DKR1Qebyd3^@}Vd#7 zYbbGhjkBvvb(I}H<81C;@zTNb|EGZ6#@RsZ>dNkYa5A8G{Zvo6vTss#1?TjK%^lTw zip)qOGWIPnG^gfxji|440lPhf;xlhcn;3KEPs9J0QB0%~_eTkxZsqGfC-s{T8c`Zmb7API`D zg?~ac&4fKX%r6>_}rT4KEkV zH|~>v@~MGx<=Op{v+?Nxe#O`Y@+3ZoLBF<}SCl2feW4B+mae{<>_yD`Je>UUs^I6J zK<63qc#`(c`Fk(eeG9&>?3T^Ruh;BN{r<*q=FOeyb2$5tCgSM4E9tuw@5VLtV*+O}#$qJp_hi%7Q!CuMIQ~XU` zsJ9omP=h(9TzOGvUsKo1^M#)E0#I-9%}E^J#%(C%nTc_JxTaciGG5Wa`Mu=yM{Qkbqn=4Yx?P|rb8}A~_R~R@Mt=XVR0g{H+nQ^fShauV4R;Sz z=HIl0cl6bzH9am*>dV*vh5+{J`~3+71puH!002P#FN2!9lcB?ZMB(aJcAFXqzw^p5 z=lBT$InnhSEn52jFv3WHmh9E3M8&|9< z%uloIZBI}`E;yvi#uU+#YZg60)h`z(1`s=RI;oihTSjSQ3L8Ty5ygsVCe|=+OD9~>Wz9fZa@ZJ!R_Taih6GKO8pB@&8jy`a$vqHEBbt$W?drWO z*?pEhLJ0>nQ-+z7v1kG5$ubZV%aV@hCWjb*EFldO1jxn|^-LBkXh1|jK_=fRW(@*g zWK8HRsFE8e3Nj!mMA{|@b5+>O(bpWPkP<-|(Kw?TGoC0=B5x1bXe&nxs8o6qP>amq^Xnp`kMGbWz3`kQ7dhl*VvH z1oYoM*nGS_c|ooY!T7X(`dk@%K45(t89S)djDk^U(;PzvGK|qkMzNTM0^aoYJfjiQ zeBJ!mdH_u(__6gof&t&teH-?tF5%~MuAHmk)z^Nirvul3uhV6u48DvweK+w11T+kw9xbUpxs{;3^g&jtJ%t zh_&Pv3BDGqvX}Vm0Pu_hHwBk%`}Al)J2Qr_wA5(lHuXJ2=Dkv$M)SfS zT7ye0B9ETD#MPjKxt!kOwhNS1zqRuE0Q0X8-#-6T1>L3Rfh2&uH82uUgB>bUCMkI2 zPZ8MJCRUSd#ty@YPEth_KiJYFSu%RBmsvHvC432nFmHV#jFjFt(aho{^yYuyW#-b_ zL)o_b6LdwLe*w8#8UC$b%6bw`wIl!AFX;;Y1pMr^C%@4PPeikHolB0N55Kix!&VBq z_q-(T7rS44o!=kmG^Q$hvxry7<#(b`>}GGHp>K?v4enNLiBV>$*>=NGcjeh94^bN9 zSVvJ+W!ZJ>U1zLaR%l%{ggI(DhN`*@`_DV1BGIfHylW)tJ@D=tDiFgQS}9}9#hfuo zW5yibIb+Pn-Z4pQ=N#U>eZ)tuLl(#FoNBGJ2doG3H84Uz))*m+bBGjgFcyL&#-SJ? z#ZshP6Tmc8DWqDZn2WVig4S9otb3`L`EbBOb6kCMKq6AzOUt_RszXxHc~#|8TsVF@ z`iINnb&$Gw=w@em`Sw7Ps{APlfaZ%7uvalB?1|@-2rkzuN7%Y$bLXzgQzg5pZ!Qg7 zCE+NzI!H9IvtZLep@H?D!h0e)U5< z@YtZj0P}#uz=NPTH2o-5=K;h00{{;J7J)`CE#Oic=6$$db;v%bC`dNIFy|mbFbq5e ze2!m42Ur;U473WJBF?lNkWtXBB=0zzouvZ!dJg>jBn`w*HR4e z%Q^KQ4*j0g|KG7aSQ$9~)3LoZVW@WW1pm%g>H{EK?}t6JM384K+^<99gv0&lwX^`AG55Znxs1P_4_tH^qIBGN z9xvF5Z0WIIfIox$S&lXhEbClz1>(Zua5)A^PI&d+qObHxmE+7qUN_b-@rH@+bmxvk z=W2mv9;ZDRxLCT$kmVVOAg$n$6u{=YZ#^pATY@HsI0!t=_vB~%ba5iB2XePu+ggJE zrj%fBDAw35<;GjK4bKagv*+srg%gloV^-@@XthYOUvgEUEACnSrWb(52JW33+HiCt zUv7ks5gZ$MEn<}bfss)fv&0B&neiR7S}GtUHa?Lo6~Yc>iajTy(#F*36*ukDphbs9 zeT)qIi3)Wx_~!1qv~7ijy5LH@i5uR@%5LB#lwiAE0$x{JFN-^(6QIMEvF+;X0UsE~ z`_l987Ao*nC4@-3Sj@xUSt5jZHxH)B@{=P=pFSz?V~>ZW;M{$HNcR7YEbJ+28OOgp59p27f7vyLdzf+#ug2ylYVh~qY9G=kZmj*M#XfQP2 zm$|-AzH`$@xpc$7J}*3yC+kaZhgWy9G#K{;PM3cus6g%$LXOIdTX>tjb3kwlnz-rAy{Xi7GkcQu>gC}%dii$O ztPv6OMfBQ!1J?LUvh3QEu}Ub>!~g0z_{ZzBZ#)k-w8w51Z{C08ydJyE7gpdJ-beL* zoO}!0`A%o);_QO#e z&CoBe^XDmby{mUlJ;rft9}QYOoHtgTt(?50Z-e)!!y@9?@!4Bnt)uTQCkL+=O@9*KGyZ-ODasRNtf9H?v@&3L% zgwy{`4h!Go|J@ts_xZ4X|EwPOkL11>|NHU|AOH9Hz5im|e|v5KI$z_|t>1HNWQp;n ze{kVnY4Z`|*{?f1`DxXpW`@e9va^nX*s4*ofj z`1`)w$M^Vt`MVw<*}kl}C5NUQmC6#nSZ-eH@?G1b54+^(=iIpDFoZ@>MIsk4-|Q~t zcr-ZR@jO;(O;q;{2XEM712}#O8`1>;S{{~{^+xhLF4E$dnHfTUe7k>Jqyv%WgAF`d zK2P-fVz#9~k~;E``C8TxVnu?h=Uom147wB8Y0tFVWs2Q8$R}5CJg4qKD0;;0`STn0 zgj{^PKE8wI-fX<<`Fy*&e7n=CnV)$4e;;>t-<{dwetPnaNa$vq^N>SK^hYo1=iKu# zJ&QgnEbp%Lb^Ce7egQDBf>3$!RZZC1$0OGl>@QwdSE+CQSQ{PfogVM?axK2mlgP_~ zh=6N)8qIgUp60~`FuZ2n#nFCz-N|WRN4{3y$fh;RlCWWY58^5B91)i@a`$&Q$L$^j zmz{UUVY$7hb<~>87Z||ksskKp>l8Ws?4HlRTfc+%yjJTmMm)oFV}W;Tyk33tb_1pM z5Zrn1iQ=`0?ht4AEO|9Bd6%CA;ku31? zWlFkP6UC=&-JBd$o+h5MJJBA@O;Gz%LtCSw`sJzBMUp_QU&YiLlYGAH#>m@qxk8=& zM)B1wWloJ2jP{|aa&1Z`aY;c^{KsWVM5-p7PBV>(%*v{w+yKFMHJ%2r+u#G%OZS0h(WkN5x zA1^mm$ShX=?NqT?CCm1G#^sz;uO!#HgqoPu`|vFRCS|UopgxJH=$!gUNeZ;Ark+d5 zr-13>#Nm*BpU3(tpV=KCy~p+~7=vRe=DLAvU6Au~*(R~*Q zLzo}}6UPj4u6m*ofu5?WT!)uXr0@V2LgZ-B@m9mwYlb`BZ8h}jc%o4?@l1+cGy7@gI2QBm_0O687 z5w)t4^*XEQ6h*m%B;#Zl^E%zsJQ!PMeR^BaR&|g$t<61uv5Iv{TJg!@u0UrX4@M+V zJ4J#8>#QhRD0Z)kJYvt|fDE~`Rrg5&+!;#J)gTpwG_Z8`o6Vn$FXuV;q+zQ;IZ0C# z)#~U+vxN3C<*FR1*bVF?OQ-1BGOqU3LM;sPuL*tfyQfa2GIs7*uTi-zD)uJYDQWTH zgVJX8k3o zo2YB$Wy?h?o24tQ%+=ze+7{7`Z1?8un1v0Pp}b{;@>iZOShG8SzE1Mc5)FNidjI1Y#l~kVs??#V~&!VG;px%`p%GJ!Ml! zYLe4n(9s3Y-33ie77Usz5uqRf3^jtJFjg_N7X09G$1ErGa!Pk?1+z|WfYI)=Y^NJs z0W56uh?biMWqKt9Eo4d*GKH(_upcJUBRPn>WBEgg0Aak!|v4 zEIWJcy;~5B6GYbrQ^zLYxUS+h=N58_;0MjIBlX8#W;V3D0q4A9UN(ITzDyy!l6ZVc z@v|G<*H^Zqu3d63B&&KpoHJ|h4~*lS1$_NsG9-1uHSf8bI=eS|PvkvZi-T)byTcpa z@yX92$gcO~j;H{LGCqtHjyf^D{BFNaEgRPw;!wQA04kBuACR}dywQ>#I_M6iB zJ$-$AlMlM{+m+hq#ZRO!bgB7W2Nvl=+h6}LtrWTIy+)p!s~77yZz9!L{(cS`3ERTc z&H_|_u0U^x6L*akujYtycMXBM@=wEA1^3F^+&zJ883RB2H$#Md|9pgybLT&?bKK@^;3&XOJ*sz4xL{mN92WfY zJ_eTBOUzMx?%*qsdMr-e=_c0(?>VAe^tFoK+QA$Z3q8iZ@~8+FhxKw8`+Bu-5%Ve! zNZ#bLka*r%oaMaiFlSqsa+@EAl{LuGU(X&u&q(SBR2Fjcdvhpju>hfGisXJlVdZaZ zNH^4BJHK0gT8uWi1GTiwE75I2ybqUmyAqysvsnQ&g*doR=_`F+rT7c5^06TUMUk$66{8Ig`Ts3dTOha6KWz>@MIkn-Q& zupJS#zX@56=F=~J(hrfk9RxE3FNj@`dOncF>GZx_Fdr;QbDPI?lvBc;w_|nO?;dlrZj7wX z#ld^#`f=au1is#AzP!qOUc4SGQHP_KnPm>&sWT~YqF#uosmLm`4l#+GiK@m=12Khb zswHEn8o>&lsAeJzUDiZJR#ngp_KZ=Azq*qWhOp=n_EbRMQ#3VEG4KHYtu#pNB8mzk z!YHc20t$m9)je#qI|qwrK=Xiuq!G#~at4cLQB&Xu9qf{FQDlmM%dMheAcB`k7?@0| zq2yV@3}hG3!DS{mZsghW{%tWeQ^LALHnGY?WuO{)>%d~7jpd|i$*pQu>=w%QY+49g z9Td8T+p^oCPb4 zt1zYcbD{b6#Kpb+{4Q&d_D9+F6plIySM94Wue_(CE#XF~I=ae+R{c@SN#969k=7;M zq{W}|v!sQX7OHCk+rSXa5JfGGG%?Xt5IkB@BiJya&>ARk4=4zB5dm#~Ji&9_2M=4q z@L|5G8uMm%2#cH$*A0y=efCo!x}f zTHQuDhGP2lY0`$m|IYR|V=XeVG%vajgF>Y)O2Vt8T1d2=p4!bN2!^zx-Ti}F>D6T$ z-y*kf3WOVNr<0d56cW{61?z^fvtpO-Qf+t4(JBTeP8Q2a);HL8BBfZQ#QN1SO*zCU zM6&W)dg9Yr=r@teFnT8l)aN{l5mZwX`;i2OvL-CTEFf{lh{FMx_gvJ@O%ukd0#v7_ zBh6gn-!!i&RT&{R(M%ov>ML4iX(kd?)iwQqTfxF|p4BMp&9{8MQL$J^e|NDN{7 zz+@p5!vjc$sA8t1k*lnltDvDL8o7a-2^ENX0A9hXA{wY^h=LeHVkOc*LrpOx!Ydve z_L!=tnxO*yA`vu|<-yT8nRtLU1y2lymT{G?uW@8WnG`YyQ!@o^usy?df zP5cv2SAvw7McDk9HDDqO9k(s`u~i#G9S3dR(vCD0wQh+`RWTcMWz%-8>s02^rq}~3 z_01<=Q3@BCCze)5Y@x*ghn#s5mD8A_YP$AAtl@7uj%G9}rKWX+%Lg%GOL))CVtY4!BASEz({g4lG$1H5f8PR%7)4_L z9mY@lcF%8qy(e$8CgW7&tO_-{az58|E}{z6Os)sE@cPjugX%cUBzUUco<`ulfd-RoB)g&< z2S3iMGNdZwpv(lttnwx4Mk4jVXTXI07fN+(<4eI^Jcm6-gb9l!3`<#J>m}RAt5u?L z<5MbW2nOmhwSl^IL!s#T*CUyVq`GY>|UV7+<8i zY6QDz#2HDXxQft7mrw#(L<}V5UDM{ApDSX4swx&tN>C(HACbgX(6`_v@Bg|bJdX>cP7mjr`3ttudBlT8^|t0Rw`wrb0W5nC?eu7 z!kxA~ha4!$M;%K=l$|1(!ggte;UWH_5^>R!(px7UI_AaVlX3C5{95)zqhkIKQfhS^|k`*htC*Q8B{%;vsGVCy*cj2wI~%GXONt_=Ut7 ziX2(a3{*t2hh#FVu=$ZBLCC&}q#DN`ftsSLp=_G4Ggh^921E>!CNOD{%=E}HDF6`- zB@fUbd^0kqw4IdP9zAC;$yq|bW~iIrIkG|WE(&wEuoP3l;zggiFxGe zN5p=~YUa053vNwHT6@tRLIm6LXPTI;EFCUJH=VXhE<2A-3|Ow(%vU2>m9s6UrR&h9 zi@fI1umV%;QmubR4H$2}Fq33EN9&Z4ajG($a2s>5>l(MuX0IzU@aD`%T_+k+rD`=L zLw!q+LMz9*OxnRpZTxL%Z`o^A$8}!7h{v!La3%NYO7CkZn0x9f9Fgd-dI%{s_C+}e z3ERj)dSsO`U~^(f!9@0|Ft~&1EQ|=s)?GvDR0ycpG^5A@DeGs$$YJR7~nSPyw8u|F#G;!Xcpn#mf`eO6i6;J54 zSiH;nCTpG^y%u(EMuY7#6w!6Ak>34RR@d|T8yC&>5LKn7jh0<3YE?sG+dPsr8%f@s zwcTG2y9`~=RVO26qcA*uZ>Es1_|`m2HR}@9Grmd{rb~4R>q_wl$F#B?>!MEep^4L% zj_xkBZDl4fH06hI4}<&SVCwC373zfgr!!%1KdlexctZj5@#E4$hdh`-S;pMdwkAd? z*$kC^HnB&oLr$0-TZmddM9eVnPJbrXV8v|*2dVQb>jkV!ECzz%qBh?xn2va7&YOm+W05g|upAOQm56AS2_1m3U4$bsx$JjP>{ zVV_|sqK?U73i1=b^sKaY*|GhDd1ObsSO`f{EGZ}6YBp#_F=US-){dIhD&r|MC;w0o zsn%{gk>`!sEDIO5nkTD7tx}UqhE9xHa&)O-#OuPb+U(QFNqE+zvov+}o3pyhL}ewu zy~_G>5AgpTn6wF!W_11|KVm=u0Kosxh}+f0(#HAUuq0O1)_#i(;op8cM`s3s+ix^< zY_NLDQ&UN70$HFfL39^e0nHQ<5i-`KsN$bbUXl}uxh{0y#EEY2_ig9$BSc#jA``aq zsj^~xZ*dpp{%*ZmOO~Q+DQ6nnI+Bmx*gENIrr9YS8;aWFF(^`4RH8pJYY=>@rnRDJ z+EH@gEZKPe{8_T*^_HWqW7eMo*TiS>&tv~k8c!4ce!p1MxxF{IfF`-n!liEDwvhRi zr#UfFH*uDH6F23-8sL zEg{HAOJ8SIAl*5d8H0jaRFFW%0qJ{=1 z@jL{Pwwe$C!Nq1ve+)nZ;LWF(-y$@=og6(I`-mxA{2KeXv}@15y(FQ#G-=k*%iGhV zNq4sC?CaFcjfHw~DC6G!7pY#XgcCxv$)J#(`h-1UGEgB;D5$4e4h|7}FtloG{&s^6 z7=$Ke&Li~Ih7hMul+OV4$3`;IF+lkM|={i}HnC((ESR!bATc~L{lo(4t7903}m zaj+8r!y7SC;v}tNG#%oiFI6O%-TdPX4M5V2$~2@t0wxTOIT{?u&fO*qWdALe1H2#)WQFKlPmgkR5}UOpB@J&$m>D^+;G$ zYq}C%$bbJ@7BIL=^ZKd~1b}paZe|PV_aZSjW`Lrth6AzEaDf2vP~j3CwX9c=H~BZZ z%e+e*H{7_0j~SaVuhW~;?Dj2!Wy|#fyAG6kBtOR}F}6bn#Cj$8T`lo%sYz?>;x?a^ z9us?75cI$)L<+`{>>Eh_z{1Q!JU@N{jsYmUb~?r^$GP=~A-C1BV@bHn_b?xRI14oE z7z?WazbCKtvKBA-DkszN6VtRnSGFcZrhn)SU!|NBZj7Cn-QHcA=onxbmQC)1X9}< zGw@FBSvkB1@Nynb{%W>Ri4J6Unu&&48$R9&C^_@zvg(J#UhhCgiRxot`&o%T=7Ef&(_p+mY1nkWV>e@ zn?c=c2{ao2xPISu(=EJc^@RJsr!{X}7GBchPwA3zac^$k70Jd1i}X!f^lfkd1$Wf` z$vJKQC*(=_hna@^pDy=*H9WS?n_>vRbM@v@j%byoVuoa5E z(BPf&EDzdh1}TxgHiU5nzsIgmuytS4{@w9VnL74M=6{LelIWrK9@zEHD1g ztgPfKpVN3CWH5t)+xdA!K+}CA|EGg5chKA_uFo|LSZ?*=PCZ5R$UcpBBSs{>Z#Xf- zCWVZ;m<1w`&MVjse8r;YTK{?cEda60AAH@HFky~vJOOY!u3*J}Z|_CSf!Uk=Q?K=E zz(-Lr#ua};wYDfWFq5g0$y|ch54a1+1txPS=Mq%QCJg;TU9z$)eBWH)5eD4(TuOLF zHfTbuKkb7rC2)XP9!%*=>~L^+`1sb$>?I&v4n{Hi`z>lz>CpAnhR^ODz-wc?svEOXDmU@*gpv zOnKs3!qRo5KFeCySn#^6$nqbHGtefncvAr=yLmC0)oq*8LH%!YTHyo?28%r>ok^XKo+wwED1#;x+XE52L$b+KP)jncS+hB4rIbI!HHYQ0D;88y z8f7zWP^9{;st&3W7mAgmkTt5ev8TdN0b;}gbcijmTq$+`9<-$eLw;4={cOYsY%a`h z%E@~3HZ^^t|BJA5it#OIyL{WWZR@mc+qP}n#%bHeY4^Wv+dOUC?m1uPV)DM3$z1GI zQrTBeDphOOv-bL3y|9XzUqUr4#GSZ@ag$Op!?|^KKY)mZEw5}j;^G}!hw26qZbagI zRPS3l*OUch$d7podkWLOY^R>xRDl2>|HBhW2}z#CA#+fPH}&$RkM#~Uu5w1f{)UAP1H26&dEk43`HHwnR=@rn5zFdCbHzk-kXuGqxHjfvU(72g|Z+e;}|L={$>iZ=Rl3?_*n_ zkBEDG7p{<`wU_XtwO&`y^DpTz1eV2{)tPz&SX9`!4J1lXfiTn`CRIAS zlE%5C-$U}o)N4ssg4I}UbxG}1r6O3_SPHThFy*Ehz5$=$|TlQ~FObz$Ua@~TAFnV^h) zpq%BLhq2~aIBuehJw!Xlf5#qn86P?!_wr6#kflePHn!@b$%7w7xEVEIZ-u67JVdqO zgD7SK+e!dA$XcC%eirC?Jn>88>d#Y^>Sj0W7ik&PIFvJW$7?Eyw$PWbBLiV>0fn|~ z6aMX$-cY?1g8#UA>L^|r#cIn-fTTaC5-hhMEUsHhYWe~C&$^N)wx6lv0|60g0|C+f z-|K4O;Nr@{$;`&_--W%>^^?VuNW00qxxv1LV+xcD>FtBa296DK>)KnSkiX>QlpnB< zhg$^A!!nngHN-_|Hl6i|AkBC8g~3$UmJyY9){MH*)KkCzLZ&WXl=_&JUR;qBA|)2% z-X`R~B0PvG);aoKtFFqc)L8Z%>T!D(;SMzbbn5qN)SoYZbZaah-fY`PD871Kmk@n+ z_I;x1^Z=+~27H3);9wi6a4CkQ1{;KiR!5)@O03pI>*P*1cx@?G&0X!VTN7<2L){kG zZAn+vUF`^4Q*3Yn?r=PFEq-g%JmGqcE^G9i;e3rgYqUP$evKaOqR->x&Xrv<&y&C% zvJC08hr6?vgQfGz9W0VO(brY-1wRwZt6_V3_x7S+_XMJ#&hSNMjiHtux-phvx)GL& zJ294}U5N8EUIInS^U2Ig{Em8fJ9bD#;iuP@%ao7;EKmcZ;YDeCd|_$la`eBPySI?& zlMv%F4+kZ~Rohu7;!f+->m(N7olyaGA}qG|NeOe2{&Wj3FB1|GFG&P!qD%nHULcxFCI4CSPJ?_US3X= zviMGw6J2JgbSPt~G*%3-!?`lQ@fp7KcXN^IDX%+lOfUg0ell#Ob;V@#ujAwMqjU;1 zSJqHHd07!ml9MPmuJ!MkRyJvi5>HQ*mhjYy-!KO8Bc+aH$%==XzPi%nc;l6pWb7C* ziERiJrK+l#vBRjSQFoc7n$+_VPuAQdd+n~j3U$-!MwHVNr{*=Kp=ntWxN=}*Q0HGg z&z0{Z4}b}3Aeb$3VQ4EgJwf!c$_AWa-|OMOJjk%KB;(SW0ze zs9w*rP~sCL2qh&~KygW!Q>dbu9~Ep9(Rj-dZ-;n^G{j|PGcuU}7#fR(-cmEjZe@5f z-5Ku>mVl)!Q0~!|w((Ew7uI(~7s2rPMx*4j|K<2Fc3!L$Wl?*aF4H$Nr%Clxl45h? z=*ChXND-M=4XN!fkLq&}DEJC6JW1ugV^vUE26c|nA#9J)^WLR;hLx)6TLqT=%=nEi zW{DD&BfW8?Y%QLcCH^}N(}RX6bYOAvw}j&A+3=#vd@vNTcN63qpGiiW+Gvs)(+nsnrY% zx}8{2`gdU_4)^dewqQm!6@CqNfoFC=1h}ezLu@FW>SOdxJ^H1n9ks^WWF4+?fTJ4GfQN|Kt z!A)iv*h-R!`tHIh(j#G8F=9<4$xKE|$FPDL6@?Oxvd|#wrp|ygVObF-ibev@5jw`h zR4G8}LR1l8m|(N6?(*#h6G>p|f_5?OP)14sJSTVQPvNFQ5-yU;AW@(bc1VqYe?O3H zb>Ts^2lmy7Qu_VFhsmrP!5Ct>!j{FlR|anEme1(~_DOH>^|@*a02rFEU|WR=iS2{& zB4gP?nazL004^bQu}9$UQ8br;@^_P5calUkCmJAvyF@)92T)`QJnah;b~_>T+p;Hu z96_qPbF9Pn3M$}5U}GTDNIeCq{d!}gLC;+E&`7xsf%U6^xS@c-i8L%w^g4&tKr8Hb z!_JO_gaausWEtqK8KfWYKp`$6i3{F6QfY=h%Q-v-|o%)FcKY_Ut4TZ`o zXk!Z>5=$3>)MGIKco^p$C_8mPjF4U1dY8_~CLz87Ab2S|^FsOQ8j20tm_~kGnwl>_ z(Yu;J1g>6~d?<;abfHnf4PZ^+(!?|EF$8C&wmP-Qk|S0lVH!aTgIC0`qQ^=Ehwa7!tg(Dy)1W=k z;75l@_vi`hvEE@9AuAK=4yhTRYfc3krV4b!fajOBf$jWP_lfmRh+pO0ctR7giewn5kgWeJJbf}ISc!LCT^JhIf_Ao3)Kv57uHPj#EG!Cc{? zLxY}VbSxP_dLv*eg_}Xavh6u{l2kt+zKpIqsDa#oKp6gi)IE|CJtEDBNroOBBq5;i zkOB21JZBfp_+8*c2_Y=-A1789(C)x6pe8VP5i>Y>FdgzROJFQG+(e{JzAg;Sf;oqt z$zfjQw&%BIy?iARLL$T>bVVlvgq2Qtjv+)W^~fOrR|T3OzOY2wy|JUX9v>uacD#p^ zdEbLk|LJo95E^ik>s`p9@AJC#m;8H}C5zgYkcK;P**ZS^rq8MQhrDig!tgSrZfYbg zx|Nj!jzo0^OiVEtV zikiE4+Pz8{R%GwnaR1vqIR4T%>mWSk8R$kR$Pkwzx|t1dA@->vbPx zqA9Ly{I;~#jw1D^p`~%yH||4qc7;-Ng%==#jlpA9QuPviXop;m+(O2I<6_QaExIQA zqbc=X=H<%GPE3fOYBIi*6s76=uD#e2|D5#PT0DPuSFFP7iHwKO$dE;1%F7b%3bJ>rXWh1e za7st;{xmN0h<};X?5c&9vGO?tu~ytiWTzE0WIGfKkB5nKI#)s?r33>!22G1k)o{%J@1 zeB~3^Ata+IVWyU4(tFruYSu`s;Boe56to(6``0-0ntGw#u;2q2}x}1a;Anqc-z<=Jg6QP=GUBX zf*#`6Geg_VH*xEpl!rHSXgcNz80DT1Q~Ph%mIDvP(*jeEA%7P#>!@cKVR+4_vQ-iG zcuZ!H6p8|fAM44ZMuB>31oLY_sRJ3Xm%h74A>u1+IzcR8cqgQ+|JIeLw0?A4 zX*2PV)%WJfw2QkJ!M&h)I`m&@GCq`UtVz%aA@vv*nJz~oQP zi=0wAIgcIXJ8nFMO<8o8^CbIYBN8(B#Wgy7JLKAIpfu(|Cq6WqbwT0~p)!xWRFET@ z@Qt4B8=28%yv0F#p^i z1HPEbV4Ko?^5)WQ(Qr2bZ5REInpQI}Z|!;+y%3W3ncUWKbXC1t&+u!jjpU#zaymkF zcCY5d@t-4mQ#X#_#?Ym+z{@t;s*8lAI-|Y0TP-O4ApIb!+KIZ-OjMg9W(8nYak5qY zB{EdTL*;znzCy#%0@FrSKG$Sm)cK6uxl%h*`W*W!(mzUUx{C7mQz%O-QY)04y<*eY zkn4ti^Qi#xo6Uz$j+hHv^Gx<@#^mO6yx%;3`2Lw2F2}zX7yllqr~pLfF&Amnb(+@& zp0!`M*G^jFzy~!?@jRPFChIJ7LoBNd8YvwWhzsHN9rsC16O_3+ z0%;m%@r0&u=I@a*N{X!CLu@e1wQtejOh3JQM^%QFj&mFvDUGbnU$77jG4+f z8MAUp^X-T$FXJeMvuA2IMMy{mO*mFa7m7M0UZgM z_BJN>ZefNm3}yBJ3;>5M`B^syF9#p&wil>?a)z%R>NBBst>4chWch=;>kpEEZz%M> z$GocL_SGb8QHRFTX;HfpN|#=nuYaj4c^8d8Q7@rxca`+$@)EZdwV7p$ zyc@e&t{k??qKBcZ!J|{`*#+eY6(&2bB2@N-&FOV-LlP$QI+*E>v4j9pFFii;+kJn9 zLoRA64aUf^t6*NEAZ|FJR*4imsHXSr#~;pkp`~%h$b{-6QnDYD4GXZ}v!thNwM=-o zDfa^{z`##A$feZeH3VSA<}(nFH;kD8EsY&VfCr7A=-x4Gqf;0*sCc!NN~S3cp?KR@ z_eS~7*6Lxroui!(@8B={bhcl}BZ}_y!$6rU!l7)^DOjy#6p`#%!i~-&We3RAc>Oeh z57A!@`-nI>5-Fz0Mqly1jxeqhWfE+Q5rih(O8{FTg` z^3R2(l8#Q%h^48j7pUGBM4LZx;;S-ohRi~zstS<}jHzr8yo!`}zBSEk)&y(Wy<0iv z=yqM{itD-hNoQ`G#gg7r_4bR$FEjUFNn0%v{)V6E)9)Vt#A zh!I1~Vq^)iMM?^NZfem`ugx5@uruJ#l8HQK`-tI56^=Ak;*LLB1oA45v^>M!zz zR7`dO4!D^4LIX7KSZZ&KquLN~HE9C5!vOKxf& z2i_@*$7MWu2a2H#ZuQ~)1ylL;Gbj_ZH{(OKv$eBCMx(#PigV3>uLrG*S5;B1U zBmazhEXuOle?=8O$GB23$Gyf^lL=J2tOhqv>hg6s+sJ#^&e)q)Y7ss?f5JU905|R- z=iBK|*E(!_Uj?mg7Im|9ZdEJi=yyFW>?XC#gNl^`R|cN;xI`@3+o426Bvn_1t}j>H zFJF)>-=Ninoe$@GsQEE3h#nRQ9u8nt=@IJZ>YFS<=D+50;Mx9SXoKoQq1jh%7@~%xR|2_A7Z6p_X_777URA1MXr}G#a9tc2lMD9+Ju9tNZad=DMM?Pv*%V)E&cmI(YS0}H}f_ILyp`D}R!u9E~j$)QjPL~chO zZxVoE+_d^vz!SpFIgN7cx&p(As+RecjY7~9krh7BBd|7P}86Imsq%k3o*XZGQ z|98vo@vXL z42Ryv2nm&?+lrOp(uYQrF%P0Ht1vdI<=XmpZNS7Ka6r5+2HqkvE0dKSaN_)oqiOa2 zd@FLiIP zcW+yJz4W?OU1K(0W8T(nu!a^yj~wV&cQE((D!Q!_3x#zKR(${gnmPjZy+>Of&#&&# zy;?kK?d@D^g|`<|w--wxdeY&26itAfC>&u(EYrA`!N;})=mzd}NZf~xyY;FBr z_2pe!*xZ`mS>4=R5SUxp++JTm9sC>bG>Yj-AvHCET9L561suMInv?+b;QN~b5-;0R z^fL#K3fH_k^G9Z)tR#Yr6n-R!6$hJs-)}~U=4!{qB7(sZKFn>$a7j1D8{LDf=!h!g zlgmBVM0k&PR4Xlh>stG2l2f>f^tW3mhBwFM_v4)NwzSTOe*MNp)3bNzrM$vMgQxG_ zf_9>L+3I_AOzmvDHIxPXrTkI6R!E)Q$%M_> z%a)5hZ+;ef?>ffOG)omyTfOz&DCnLB(*)wdggB}=v#t@h7+5b;NZ}hq;=-eOqVC>fYi!G!)!BmA zW4~xRDu$h8(HPOTS46AThpTa+h5NmK+^0--oz;4sb-mHTK1cXS`wUQh3A=jzQMcVT zy5jf!`1YvN#^W$uF45TYh1(Ooc|`l2dbqu4!?^ABopQ2lXu-Z#`3`98VNlIq_Ec0; zKM;D#-28w_;U#A8#}DkD1ubb`y*y_)SajBQt>ovevz*Ry1g#gc78q%@J_s> z(fv@f)A&H=;7k9jt)7(%#Kf4KB7PO>w2uWCzFq-VlDP3 zI>=&r({=ho@am;+q?8flk`W^B3<6cqa;w_i^)CL4c1F(Owv5@t`%+_%t(F0@0jNQH z)lFxWgMqKI?{>(Y+bsL5G>4YQfX2UjjHd8>1^=Dy>9UK~>>5O`g5|5CJiCfnEeY{M zQBzYg3(9Qvv49kqcs#E9=Qd{vCM!GG7Hf%xp{84VpBXjFeEH!yTJ>||A@p8}FDIB3 zFba{3E@}O%3&KMZ)PyBz7J%SQ`Dc#?`eN`@x4@(~7JwLb^y2D^UacEpg6d#YK zH{u-w^*}kt$c*F$;5J&+Z*XhwAY#>LQKwQzb|^oIhyl^+x;s?S#`=ws6u*;*_fd!V zu#GrAK1zwqUjMoo8HE4nVV<*m-T)L+_5uh`q;^O+w_#V@(tY}S?4KF1pvHBk^aq1k zZx+4}P<^jp>$=8!{wBX{X15agFca5CFx~0gBM%~qMi4U^0u09uSfN@VSs_1~7W#t8 zO={J{w!LB$nh(LH5j5}6$^EM>2Q|R=IihD{#vTFzaKOJtp`d_{O*`KXT)R)qZT zReh~$nc+z>-tc`s#hI;X6h@Ux1cErQf87K zHpL_qTu8=rdt*(X@+HJ2#ul0nkPjH*uzsI@zsw)R28COtDd1zUcw+&>-raIeAWkAM zDg^W3^+cp1&fjN~;bBa}ewmW32B+nmaGW%vz!ISuTZ}_`hZIT02~l_|1B_b2`c3!| zp`m!`sL>X2sgMM$1A)BlvdSW*Ytx;Rw4aBULyt!>O&YJW#{$rJ1>VtC?Tc>%>|Gtp zvix61rt*-Ny-p|Blx^t_&@ppR^mO08-{~IeLr1tC0ddh>e#z$`2=>z0YH4s{svJto z*8Y_R)BM%7^~Y@mgPo}>t*{W^c0$RN4JQx7vy|nGqzmwNwDn5#auc<*ka%?s1}^;< zfkHya@7}xOL7g7EVtb%MPt8=(Skau(V9~&6AynalC0D?6#Mj$!@IYZua9}|)=3weU zwP|`RGUKkGnfmJw8BqM|u*oTYL|-hBkMFp2NZRiY$nkHs`s%NrIQfiJS*8_lxVvga zHP%+x=*kv=9o0Lyzx9G>ciPf4m;Q8RI1I2Z3%nhT6>0s`ns9e3HLb;KY_bjqySJP% z%+%c^=HBok!LTA*F2}vEl7{zG79bG`({lkCsHDL>ESsJjz{b)$E5mMEWl^;uZ2a?WMysPDZ`M zG$rO{`<1;Bms%ogt1qu>7JjvTkA_1x0Wod6QExl0IrY(U@)!~MZ@vlZ=y`r&kU94H zIM-%rJmt_*cVvlKMenSnwVlV5^%Gjg8Q*ko8h%uR@c^x#?7Nq%uQ6YG7K5W>xn4c? z-r6l*nXesUovDzz2-!-+C$Z}CTx!&Zu;Anrt3SJO@Z-B2WH{c{(R>DI>llV`Pjp8` z--8~FX-+DhgFqizn2W`w-PAB=XXY#Aqe8!CQ z&I)^P_=`;~80=l!-{}qes~CQW*n>A2i`$kFb`|f6FTF-7Y1bm@^AJ;rhk zd~>q9r~a7X3eTv#@0A(PiOeOg+`%oygb9nQO<}=lIz5Z^K>ca2G-SeP{w#x`qzJVT zPjU@eUEcB*F9gSs+nr*zIj;TXk4q6ieiZv`+L57t`Zp6Ct6bKtigl#@*VFgcH8hvo z&G%Q`ys|5`gE{n?3@xB;Oa}5N9I;!kWPlA3C&)l5v#7Dc%1aJbC?Y(lgxJKKxbM`m zp#cWoKurXIzp@uyaPS82S@L&atU=dy!i4w8bf)m^|AG|D(juaT>UDP zTnp2g!liv1)Iq-O2!BmpS2JOD#V|!xVU#&pPuuO`iVGE0r8b$GI|r!<`U~q{|Im7| zS4`+v`5wt75(^_TZdIc^1-006jXb@#VgO;cYK*=$a=n0%m{$+B0{ceric)t z_8nrB%lP(D#klezkk~Sj-C9a4Gtf7v&X!JHs78c*6)K#Xm?(Kj!fY|9vU43Kj~*Q9 zgNyQq6!-vN$HlDm;Xh6-VkCK{^;kx{99V0Cb&rjCh}uIaK^Z5iF?i_5#;4p=10~Wr znG&|6Ia=4bqj;)7n7SR_2-H_5!U=^X*m|S6pEI|{E)yCXW`fn6?2=ZN5QAoZ4uver zNiQMAExVS>d>cDyLUvVyHpPFtaAUPRKv|(JxjlJ^?RS(;?Z9DkGEtE?rz+2)sIw&E z6gxHaQWm0DEN*CUti?GVAQoi?ZNlCAix5}qzotk~@O@_WewiEt+*5bmP`f@rOk>Qj z%7$5EaoJ%*)0sllSPTPp5)-Ot%H?O&a}{z+s%Falr)nP5PF%KX@>T!M#Qx9pt=fqS zWqI|?$c4COP3;N?5{W93@S#yBAvyT< z3Su_Z|0d677mF^l)S5E{2*pR@`amE>;am#i+OZg(LaClEtWC-_P)o@SI}dCtllx1m zeP%@P8tST`FR4-=RjCrFrmFUvIS@Q1`MK+j}(|}G1y`1&V9`*YE{CMPt z^j@7ut1KrthZlY0s=ci1e0^;Gq!v^lQbhn{xtdXN$|;4@McB9y5+W!`(n-Iaz((gM zm3}y*57v${0`LZ%Hjmix+PY=*>=BhLsLH?eCe9pr_z_f=W(O{|_2MI_(tSx^#dBXr z+3FT9G;n9FRin}>xOIxot)r?s$*b}rl>7h}2Us&EZXR}1oo%JhAKFD5+bC*3H%>b~ z1YSF!^esP{GO9j0ML4!m)&?@F2?DVbUIXg}#k{wQ2!00#2ss{?fg|++sMGFOxIO4Rg9mGnyRZK6+?q_ZC#X&V7O_9i+C?lnMUC1;jXKZf7CrGCF8W=Vy*ZAkGi?y7)l@m%kGi)nN8uX|8aBO) ziaxE{AIc(E9@Q-txYoTIwS23JAJQ$YPH-n}c!Oc|qqo*A&&DEGFV!vSIMxQ$EBf{o zUqUUNVMnOVHp2?p(1;}qjJLyzq|d-BK1hr-U* zbXtF>pyQ3!4(E*===eU%DsH#3MMkT^?%Y7qbSM+CQ~g{{P45TPIL=H*Ms#O$Wjn`} zI9^&WHtn~x15pn?dv%Kb|wu6*N3TF%a`M?n4x?kuBCP(pu~oSC=}Dq)b;o_2XoF2$%ch94hkiu98Tr_#$_2 z8rIAh%DcDV0l^;5IQJe&h?M9ML$VB-nIe9xv{?4HqssZrsdg9ZrEyai%2C!7Ru1_s z#S&yG&LjN}XY2Rl)JofHG-}TvGkN*HHA7#W5{;Q$tD9jIJwXo7|CpTC^x{_^BLUcW z@5{T2R=1j7w`*UI^$_c+Lan#Pd&36J^`gyxlh;cxu#u|1zpljc6&Tzy#jUZOPbZDa z7o!BFNP*H$JLPf!crM0@ldVK?WYmX82<4z=TW8Mu?X-Jjjw@#m4V?>OOHosO8F%K} zOK@Zdw3-DYO_l$?7A896S>zZ-{TO;~2VncH?%zu8&S^1g9|SyE4stj_)w_hX?^~XS zz$=zsl=ZFmyRv=mBU*6FyS28$olVW1Nsi3w%SIB^(J!cG;o+B|IN0vIeqJm79yvMN z7=oTgtA!O~>06(g77R#STt|Q>$NTc&1!b*n_X>d>O~u;jW?}nDE{<}9|4Xgz&#rVf z^~{oGlWG>=;>(dW7`DQxf}S2PiBb}wPhm}ozli%gg@nV%3d}jyelSz^QIANueRns- z*1@{#>~8)T&GCyBK35zz+g=i1A--sq+QXJEWd=yP@q!PqYGK{5pU=^no%y)A@k!i# zwsg2;N|B4B5HIJ2n$6wq#Ovk4Nt83tz_J_DxfACAF@8gypzlPf$Cw&I4WZF`W^dwe z(vOz18u1Mz5+0cQS0LZnL1e4}ang(Dh-+O=&%#CDndR3W0%iS-Yzc*er~QOfTzuzp z>v$Q6s|dA5tJ`ZiTodhGP-V>V9kf*e1m*y5Odbhaz7zi=+)LIE(oW zE!-LFB&{BN3%iykgq)VunTjKAU*#X6i$8x9O?XqT#;?{398X881AwtaCR;L(4>+se zoFhQ!!e(Zk{A?B=^GjyMgCEul>tOiw3hOmaRl^8s=QwB{r=@+ot>IePkJK#Ngn16} zX4qIWYlj%iMl5tE|DF20$?egygQRFmNq@9llq5nOnAWx-`$Zbg#L(#OLupPy7^WE-hsIbUPAs&JNW*1R&0zm%qDT7nS-F zR%5PleI481zphx(lxlUiTX>&V*p1G;&Qael6~|ue_&@jyyG2ahdwL(&_YAT(H^-vj z+g?!$uOGbb{`x?~MBMP{opWhkbG0w{J3e3)Uzi+zGIJ~bv{_?vjJ#?zgQ|Gas(KK7 zUrS6q zCPm>DR3S@SXzaby{~@ZozBZWH&h2LHTIdL9vCt^=dtkGk`^Vr20b744d}9iv;>CPw zu{5Cp%6NkUqE+oyPQlMKtz}ru;KLj{Tg-9(}q>01SbYku_g!|e#>?Iuzpc?Zd8nNuj$)i8><}= z-A5-$wN@i{A?OwbwdYF|%GqKwYg{6Txwc$)UAi-@k1& zt$+-A{I2HhL^8yOQO$|Lgkx1=bOEwt%#=59aM0? zr@~eDAI>H`ZFYfX8PL%Q&{65`pwdee*bQrq8=rK0J#A@p#2u%YGoID7xP`tX<<)uT z)%lNsa^=dHFs|ju*Q1_%Di4bErqUn_X}!@2z0qr){PWXz^`yiK!Cfa&6TgV<$uD;@?4La= zvdEf$C=?Z5JJZwOZxGN<-v{gLNqm*bywhj4y<2Z<2n}*+o|upL%}Z0X>~8eA+cT(! z)H^?79&2sFY^wQuq96Bl@3v|M7(d_6cfSL;UKjh#?AJSIcN9_~GVh68C|l9A7c^dY z^}GB}Ai;5oHz3bHtnap1x~CY*r8^VSJP7sJ3h!1|xv3U#tkOI~dc41?65_4jpOgy> z+yGDVI^Fv8zNrXG4q>?0HMzNzLIK_K4rASQZ4d!H|L9e2&~YJTN446Gf5-<`>QKlw zThou-Ne$|^|0s%hFEJeyo^DN!EDb%vEv#Qw>gqiUv2@C<>n9F@E5s#gCr9XMktMh6 z?p9#?T<;|zaZ}P-kuFBD^pR1 zDVN_8yZt$(SWmDFq;xGI}2m!Kv z@xDVdn(HkMUgLY!g1w4wtye1XETiRa7ypF;S)uu&9U0FbWlH;g=rF+P_oA$bTwIJJ zS7l>HhjXP@Y%S)&(^0GbI9d#9qguPRDHe|t0~&R@^UK-=c`9tGzNV+!@^n7T+E?}V+7C6-Tj6CY)z z?u3JmC(WkyMg=J`yk zV<BM7-vWT(OelxhrGHZf1ocy7xP7`i)9bm50 z+82r9Ad^;d7d*8NQ65!e$Q1q$1UGyKur-2<9=3`e(L|{mFDuAKnU#i{)vPgXtSXEQ zr`e1x!@;2(ryD)1D*2abY}Hu07Ou=?cXMb`l@WHU#@Zq`%p~_nagjU}12Gw$MIW8q zFB3~#2p(c$wqq`qrcs#BQoj!JYVXK97=uHJi$ST4H+T=Ijm{c*;2yDL6|`sUpiG83=4TV!Mxppv36Ht=4&u?GD)UlW}+*HX6PyDZs<_f6Gc@4Zm(WhpSEW$rc0a6~vEt)fa8o|$I9hIiq&kIlk3WW|}WCQ)Sj*>=O^ zgg`WD*cqjWEM##{!A?_XVdnzWS!GvRclVGl6RU5j041QH^ueifAi$LD$M&q2b0FTp zgrX^|TJz0XD#%<~MB3!6!TgkgabPalCk!Ljn z>IE_ZSrA00Uu&j24Mb5g+`W=h0qFs~!;Z5+>p#X`!cGOhh0NPd1XDVui?OiPPjwxF zHcW?3H;=^A9!;QTErOHX-Nn&@2#NvYpp(h5dv~IubPBJ$jkb~m3|T@!L1BW!;&Gq} zshPRj^{v7l>0^Kr_4Hrq_g%b&nl+|pCbAeT)*|?Qq zpY1;b=2eYFf38O&q?V=t7=p8SIwIKPy+W%sgYB?sit0PIaYKfgx{im^J0szFg21=Z z!16e=^L+Jol1Sx7YppE@TmWu)NKslT_r+|o{QV2Gc8>;1QRuJzR#Njp}<{fYY#{i>&riN+8 zd}>f9D1#&EpDSEyKp0t4g`gsGN~=W?3D-}g%GuLOO+r^n(Gp3^m>7^Rbc!JCOO6%7 zmP9v|+asS2P9-J)@dcIvLw4eFTs4MIX7AIxfat>Q69M{hom^3k2WJUJ2ZYgtOXNT` zg?Y?7($)jF?nQ78hhr1tgkuUXI*#_i;;SQUE)a#`bA8|kkok;~0zp_hD$)hpNr5Ft z@1|BxHrfk61Bt;a79?MWnQ3kdH=0Ox0p)~nh)_|S9~0(63YtkJ$zhyVX9+4`SlixV zFsvoVZHb946i$TO3Xkq7dkWGS<754D1%?LM_sninjq;>1K1hxV9TN{H9wg3E#FhG$ zm_P#AK(`WpVnjB%f<8D0zb?2Rv8DZ9ya@vNH*!v>)$k=6rU?TM<&3yC84L@ut8f~Z z?p|FgHE9NGNT$C&i^f*;4_xwSKNY8CXO!a<2AjT_SYQt@Wh@bRW#K@-z@J#CfN)9F zc%9a{NFYJMx_DbK@4g$P6{JKE^It+JdwXsrT(^N))|+IV7)j4YC4m$&E2g2tj)hf3 z#M=K}C&PsX7B|G9i3r5tuw;qY6+#2jki^-gHcMVFhWSP}w>yKXLN>|30uU23qI&{` zoJyq71+yRo0kwnXJB-)ROST4yg2@YuEX46V2N)F6Ak;BIm0vg6bX(o(KrB99^g2FU{ zqA{PUBAF8nazKNJ-J=5__gkou&+;O*M>#e_*r%GIzlUNe3Cl(cVzC+{LqAYRbS2fE zV^nMAjFS!z-qNAkLyiAp0UELq4e=6hFq2#wd=ev1H<;NWwlI z(rJW=5eAG~2mbTJRNA$(E(V^fX4%2>E+dxW;ZS*HSxrLCPJ#Ga>Fw%b} z)A3D}a-D%`MSBfU8h4-qouV$Pq11xobc5p*gX26*mbaE5-|3YT5B@Ey^lrQg*HdWP zZn&_5yU9<)Bvf!XF5cVDnUV9G&E_u5)jTQJc$GHx3-P?8jf%s$+nO6ZWhkw;u1#5i z-y=*R#@m?!Z=C2*g4Zru_}}-W-8!a@60O8`E6_Lj{I)dkReRg^sj4sjOEi`94MXLv zqOn~C>;9SUdG`8b?4q+@qpbgnv2zU0EQ;22Y}>YNbZpz!7k2EVlaAG~oi9emR>!t& z+n(I{cki9KGgYT**IDQMIaO!vU3;%*y>IWRQghoUm&{^soR=DU@2fI1`x!HKww=Dv z+BFlsrxcgIUtdb5?41E{ z!h*0~Q+}ir;KhV@4N8L zM%obwuU8I}TnxyijD0U}3N?;L zpT5ckvLn_+<5xFv8zC+pAP%U0!H99F?wzFnEbw*96et!b-JiD`{F!|^87Ul|0u3Po z-FK$PBYk8}lRiR!6LVmHCjL1Xy(g1H-cC+I%2VSQQ`txS-JF~_FkSZZ{)F#R+Y${O zR`Zy8B+`>ja_H)0m6ln@V6XFYmj9T=`iOL6LB3(Ic~548&)c6SeJU|nD-;i*VHIul zha5x6Jm*0T)yi|{Nj;jagi({qmk8<2Inye>_3b4Gu=)Zhefe-ZZZJGD9XcLWR3NSK z!yRFnDY#|}%0XjK<<+@2w(+nwK0Bk^_$(bo2+Eb22GIV@%i_77>3UoJ&_Bsj7NSW) z`zI^zIV@WifW3+kf=GYQC6csPqgV;Hu%vw?x_H^Cf#2s_Nwj-wLY)=&ZeeX^tBvl| z#HnhAu@k0@-V&*QH&yQk(O>;iIPMOHv~*?`SPc-|=J)P0aT}u=uE`8Tb|id3qE-d} zTK1KyuDHC6^=&}y@y&~VJ2PF}VU7H`u<%Di;4T~FnD0qt$S!RN01cH{E{R$t+n zJ?h0p!slhP*rmI~cgi~eU#sa9SLzo&nbbvN@}NB}EJEw?6%|18Ykv(l4;9uIVEjab zM5bIK+}lJOVCpups3HvU*EUUvlaq*~ zMV!Oy>eWB)kTAU0TZCVmNL%8reg0LHmN@AHdswLDtpW&Bg#7Sg9ou7XIseAWds@nQ z?lzWV`z8Gi>*xMJ*7I4vTkW5GkN;?cXx!zoPQqOgb|V4mx#`@p1jK7Z}Tv7l5oB;3V>-T_kFFBH)I)E5QJ**nPD(+G;46kT+zsw!Ny_38MdYe)ZE=;~N+ zH>En24{D6i-{UNaFyi0wu9@8V?w(#&5%v=kZWMG-;{0xpCS#4HX-M!@mdlZ}6A=W9lT3Yap!=ydf9%&Qt`ezlv%3FrBCA&_5o(>4E^}`{{ii5zrc4TFQh2ac9be^$unrRXaD=SgeD#7~E)R5s)^<{+=cisJJii^tAP&OXlA{+zsgl zGrdsbogU8I9`arubp4q!OmZI90SfgR{z`-@;}?`!I){E`R2XW_jDdUOkq z_T++AhUti8-Gxk{AZ8M{B+-6@K>E4h-y%W~6EWFbRhasI4h3C<$O&Z9C{u4FOjNFYQphdUl+TcH$0+ z2)@rQ&rgg9mRb^k$l3t1J2O<_=jR3; zO#^XF{1!<~!YMQxzY?k{A|9@TraD)@8!w9AH`PIEPAQ_0M~~A%sNsqt7l1IMW~Is=g8-h2OvZ>}im)kx|c<8%!Mi z_SCGS25G*k74$$3&CKtl`szzVW0*6s&l7Ww)-ot7U$^z0}0DBCmeZGHV< zCv-x)jfI;h%0IQ@j4IdGb0~2>{J8hLSF)f3(mPxQJi}YM977m*x0baJqzc3cMNnj? zo<%HBtP?(ZlBYR;r!KX`Sj6hw(p1yNMo*4Ab31tFs!;er!g{3IP7q`*cT|Z*Aql*6YoCy;#I1i-zIHr2X zCHcf9@x>*r#{K!h{#m$`QjOipU;jB!O@2^)nk;T*a`?CwHHBpp^eik>Nr6-)j%2^s ziq~}IgOOdBa#B@F`ZSEihdIG5h+U-=)jS=w+;kz;yqdQ(&6S^rhknKAZGG3+M#?$W z%(?WOA?0Xom?+A5w$>DhD*&vfGuRVC-IxA!sf@vFzz1M=vBuwh#+Fm@>e-z|ZMKX| zjC+3(4GELcJs~w#jXrd6yV_dI(9e2Qs`?u7b~*NH+bg}(fHR_AjQ}sj4jhUo!rn0s zJ7e_-<-&e=Qw;NbzBV45f4BtPJq90J*#kdhy}DHaT13@0L@O+U4O2ex2<#lmh&g+1 znuTlW`j!Itc`PDbByBf3q5=R)F3xWmjY-HgYSZ2?hB9$>UJo+8h*zIp`dZZ7*3Z~$ z*;JdA)rzeymfgf}F^!KSPk-5?p%Q=T{Wl}xML+U~mBVlL;C%Mr8 zBxws5<>p2fg*n?18bX~y=^2HYxGH7vhwkoTq$-ZjYSk@mn|Fhapu>^m!L3H^w8az7 z^6GQ>x%=-!#xv)U&zP?#j|Z*E&@!>{+lU3vfSq(;lpMBJj%Q(mRZ)jk5k^2gLBL&{ zM)%@^JIy0L+`yG|ZBrB<8H3V0Z0s!z?g1*>D7kskP|{m~bBF%Lgib;X3%Yzi0tRvEA_?y%dTjITb9YbB@tuYk{PtvbodFSzer;g@ax)!fZA zp2TR#@~Ue%TBug&7R?>pBz1}O-4Dhq9A&e_d_^MLlr~jdSMDo zYT5Y_sj{A)970BT8`a(8-@6c54X=|(KulBX(_M$>H+#%h(Af#wrZ?`^N z_Y0+}S{VJM01-Bl3}Bs>jOD`iSAPxoFCt5cEN8T#-`a$)h#(-$|96P@KhPW_cU#B* zhUaKbIO0oSDITC z#zw1`ljR#`hyhNSJ?MhUC-*t+cU zx_g}P>3+KNC5Cxce)FUvyNCa-onE{pf`$7?OX5cxf`r&icMetuV;?g^&e6$@Z`0NHf5kYh#9$geqQ&GGfz-n9`zV<6U? z(hjM{aK6I~D5zKqx#_G%tc>1jbJB1O3$0-?=)q60q!%I%CAUwOgKJufvZ*Xf0n(~S zq-w|vF<=e-R>sE(Ye_SGxgmD?Lu$1nAkTGjbX{*gsmbZZM_p0O2`p>t#N80!&&*a( zptrT11Kwts=+3G4B$%zlxS5r!J3txxFJAUbfo-1~YAN+zC7zbG z0}T2RQI{aze^)axBaEVhfZwW%@LNx-{Cuz)M$mcRDII@^+uZMogGc0{(;uL0N4i6D z?9>3s^)cu+f?<9L5<1h>cxL35&Y!BdrmI#@So3Qq|#h+vUH;CBH%gnv1@$S6V9HVXDMvo!yyM^`eb*$_QzRb<6N&u!%Ocwg#8J zlwbC0C!EEMk@+k(D5s(3v^b-)wjd9(YWZa^eWa40b`#~*svV}!M;M1}WiBOpf@jQg5;?l9EsVk(^P z1(X*tW#^Q$kHHZCwTMT{ad6T{LK4uV@tU#!Lm&NtaH9}cSxsB~@PourWdjCts4Q>8 z6z)Ru=W9Q*UjmS`I6j_L*-tuVw$dag78F$IHqwaMfx5!i4R}fD=sAQ8R-2OfhAW9T+KW`Tg62i` z`^UbJ5wQZNq?+5$V$#WXhMYGU0WIt!{9Rv#v;au#FXkJV_J$?T_qdzmPan_UXsB)7 zE%GtnpM|3r>lFP));E!Fr^$7@V#mI3_oIXlU%4sYCidQz|IKX?3mp0c+9rB}I|n~Y zj`h>=b#bF2`#M6LDoR$xHMk_uuL}s(929Pjv{d zV&DWC4D#+j&U^nWuRyfMPnSPXmJiv`)&+|JO#(y?9U*PIrJ4LIq6VzKN$`!zTD~az zLV$e#n@tb{t-p<*u>YC1)TJi;DT)sQa?k$%n$lU>SXkNq>y)0I0qcvg_jr6u%S>QMaD=rw%uE3O;ZEwAC}+sMMc9>M9UZBOyn*l}0%C3E z^Eh5xiD5bRftw)WsXzDvYSrP~2BB%MA^TWn+TBb^E!-uM;f7)3DDnOL!Y4y#4mOsu z*=1_BS!vR(q9la#si6*wZ?SD#I})hqBxpZxsJ9U>Q5h54P%qIxP?_EGn~{piLN0u4 zS#|dor8&`Ttn}c(;e3U~fHVv@& zk-&1YkhujCu+t1&vB}YiodyZ(wd zlXzNQ%qNfEZ^@X7f9p4A&n&W@f3C$-`w8e-c&3bASLU#8m;v80Pq5G#K2O>tQZc)f zLCMsaOOPhF`K->8@GLjWvasj%%w@lYfEcz>EKjf$@xWGLic8w(U@YRe!$#+xgUn2= z+pyrIKD1tXWq|MW(BjW6Jf34ie|pZjk@y&JE3g`H zi*2awr$87d>_8$ze~2_Ff0%c?X+!}?Us!D@>mK@^)L#QVxgecA+<{+UDd0@g;P05% z4%6gm3M_>)uM9&m>0O&h}Hnae=9VipXTA(mQ7qS4HFY>d8 zF^)F;J69k{_dDA(+&lU;xiJh5G!vK+=qJImPab*;{xbM5NZrZ7Mh|r$^)Ki^gufZv z(3fW5Yrimi-~&P5(Ru>oz|MYE^^^n>_N)aWgW(50fm}gwftvLU{jv%~%|m`>-$uB0 z`2p@keC>5lcl`YJ>*$w8PXGv59(dTVZcyhQwuoU z9-(dSK!Iy}V>nNQzkwXrC{8Gzc*`&ykk$~JkngD1Cfkz6XwP`pW_iRd&^#b{RQi)( z%%B1A9Za9rDMm*mx2Y zyP4L8USC(JG9N7JHa`5ohhiNC9IQJt>P7@To#tkwkX7whAciAp3f$d@{q7(789!Y4 z7Rn3t-my;6ZjGVh1V$BH>}Re8sG5Y_9Ub1%A3~bu4E78Lj@^LAZXun(;|I4RQfemy zN5&EaEi{3#S;{puI|=MjS_y2(Y*x$F@;u!-6-6C+7Q0?d0v$FAD!u~@b_fYh*WL&5 z0S<`ftXTq)1cH@o`Ce555#|ze1r!MdR{Exfa2rufrgfw^1v_{-q8}=dVlHkHPWloa@)3{Hp=3VtXdD}+)s(YDDzySiD3F<~ zVkPjSw7sUqiW4j%E_M=mDUb`v=2Ch1BN+f#w2h4!i5W}^Jb<2Qkn~jbO2!Ywh!oPy zP?0GMxbZrTh(VENeS^DW0x>grRE?SV^dY;%*TUscOWerq3|H2|6bTtObB%Zi16EhD zLKg@%X9Wo~L@L@kGd))ixp~YnS3tvGlR&2{0f)gb0~>ifh6PdJS7_`L#a%g ztRNfV(qD$CN1D131`%!ywv6)fzhy@-3fXOVj_bbTVu-w0f-!k45jh5pkP}WE$vIFZZ8%~R@tDDt5{gCt z_)^Gi3xG6iUe!B2eC1TD=2RmX5Lz?Fgd<$$eJBzGD6TT0Zt`n47;KG2IOF%mgi`Jb z5z*fpvuK#JmK>}2is?Sv$g_WZIc4n5$a8fE86GZ&QpCH-%K%~6hsX5wP!*m-X;psH?!V^Pw<7uWq0#=MP>C1N zS>P0pq*MN#UQOZM1{~!HyUah1v}(JDiIx7Cft0IPpGd}#P*(74u`6YBF*it}=q2-$ z=@KC-Xx(=xrYS9e+;YNAbdw_GW@m^Y?Q+#((ZC~0X8Tn+{lzVjZ$r*mPa5Bd6j zG?v6@17UnO@y8#~AoI?|-qTVc_xTZi^Gd|8!%`!UDpr2#Cd8s^QYjClY#%Bhrz2sP zH>A71;ey1J<6$k=b0A3x$YrO6yRU!zgc@3q2X+equhWx4k%eD{uXIZ+dI!tC$!pq1 z2UO)XX>U5pSAeb=yFZ7?aUr(lhTIyRPU&kdFD3V4638CRQbYvj_}4bG!xQQP-ME@I z>5%IFPzhK-TkJSzj;RPv^-hNX)1$KNmnv$HxC3G&)o8SAGuEix9(TJDc5$g`^$pZcXWd(lTRVvqeF>m%%}K%TDikjhrJ>YK zma}fHTNr^J2c^Q2yhu^t9}1q%0$BNC@nkHTMb{94%^6HtQ8o<%fDxzQ$Se(x>%I6L z!-^&?aRybQfUHoCoJ@R19tEI6p1DMD#l@h!4dZ8>`CLQ7ZQiM>=wj(qR$YEm;n#7fe>+YnueMl!1$ntd^UQy)z+=LSaU?>VNKhKoGvoO1f{T7nUG z{Sg@6 z46B7)aZ+JEwTAg)9(kiDCCW9(w3S&Ms1*X8^N}XJU;+(a%{D2c} z%&b#*wJ0Qwg3cPJ`bM$^T~6$ze6d_oWSV7Zb>*nJFnl|Km~%P_d(3I9$(j0txtb=W zNYe(}#EZrXaNK&<&wN8n(Z$XgqFgfRixOK<#cpZVE=sp}zBIGgW+aCUE>9#HtEm72nI)>n^Vx67Z4Vrmkf^SB2O}tfxIZA}@ zAJvx2cqdef+)F4uVE^0RwG9dA7d9Uxcll!>HPTa;ONQ1u_7c!!^#{d_V#B;H+4>X( zd>=B%MmFW56pft((k%fQLk!9WJ7k<85AoJoLW98BI}8IqSwA>3SX7CbPM+1`zP~|2 zsS#7g#Cl;Ca%F)r!PcBu3l%dGAyOy1jU+>TIi0>O)P{cFmy0+4VtRPn1|d;klZ&#v z@_rug1Od&E3!O32B|<=EQCe4qZGcgbR>MQE;;k->ZXKBb7b0$&kE8V;KjWd|>=~;k^!(j{LL& z^d4PHU9a>K-~Bq8yIj$k^zuy02%Uu5Hg(fQ!t6pRXYxPU1jG^Nd}5k-6)Ms=;9JzZ zqcc?BETW{nR}Otbe}jdpFj%&UH-!-`uQt&LZ~864flPud23K3LEK#M{;I}$SWVzj% ztF)HgEnHk(VnITO*>Qwo`e4zui{KE8Je-xtpJDnmNxui_ZPlhcE{;q-J3WeX&Wt{o zqCE<0dS{;O_ut+jX;1p4=&H?3w0MzJ#cBcx=Pad&O9|>~q?Gwyzic5Mz)uI7ypMTg1 zg~58G8)zN+U|T2UCwG3RGa_2p{Cld#*PGHFE7gtxb#DXc`DA`w)+RvI`Td@^%}RNY%WLP}RrVns zo+I3!HQq)~8Hb_yg4p#YxiKwlBJc7WyFCtU2SZd6;&gzKuC}lD9mH4fw;ron6#wK! z_BOS9mSe}?r?MvlefH6#& zk#E2j^R?sbUKsv-x z7)jB-{D|pfUE}yytYOMRpV8XSF1w~JME!j>>P$yzs3ufreMOk49Ox+>@HgDc$}YxVMSXf{nRNAZ8{AFxYRl^9=Xr5Y^l_+M;PVpU z8C*kryId$-HQIkZm}&TMY9v<}rpIXbRn%>8uI&w8wmg2+KDSY)6(k}S^ak|4f1_x7 z>0|F);$tRXX1^d`0lNcV!wlEC(c}E|TsDSDsft86{45h81-4-O&vJ}7r%nHw<~H^= z22wbIDV(j>Ky&()%X}AGIK8QA{tdoO-R}i*#5K0$E_3zP_V!KD0e6iJ_oOs!FOvP? z1AhGH!%q7mLg(X}&?T=wa?abN^YNwMfuuR&*i+Ruan$&zE^ZbdI}C9)%m-n@qOr9r-=U`FtF?vtz9B)I%Gc4Ik44t<`P|+- zUjk1gTU<`VyD}YPH#!#|iypKKD|bxv3b}{{jI@cU$+Uq(4d9_hX{RmtA2kL8Lv8)P zlT7%+S@6PLj8k!)7v^f!Hsxg&u1}o$6kHWlFWlj)9`T0oDk<%cI%kq(MdRh6K0KJib-ORN1c%Sn1(TTF+%h32>tp7G*5TT1UpA zi;=wGcLSH}STqHR@A8U|Pa3g{cynJr{=gB9_k0{;CV#y%TnAJmG~kzg%jUCK847r9 zE&I*sQ?v>Fh|QI4jHd$VWdt1G-`*UU??{sVa5z)1yBO~bsoH~vTZyQ`e!k;pvJ>n+ zJAadv{oM{e=p`VJH4uqfib*i$?fms&cTVw+4ja;=ZVTL4S+9<=bWyE zh(gcQCTAT%xTAI_=9i@%zR*8IM5^wb^$htVxP$4Wy5M_)KJ&YAe;4FKvQ4IFJt_iA_Z{3F2x{diCRI`mAj-Ih33)-Cb`p~7F6O6Up zEm;r?o-P_v$(@J@SJ9sQh?~sDtz7Sp6uJhrBEtmlETK14eAMLJ4Z!LPAc9Hwxy_gf8XvuO1<9)KP~O(1`7-F^^9J==*V!p1SKj&8$DLbqhaK6ex6LI; zZy`M@X_qi{wWj^rg}Ku2gt1w^>mVf}G$It68)MDmh|KIKpYvr;h{b@0XsrQ5W7IPD zLR0u>zqKN|ZxnT|HQz$gmSMDqvRYz&Zb*cHz?rhq_GyKgy}GI7&31>_b*IMUjdAjQ zXI=0NuM3j;k?wnk?TpS$`N#KFTmGT3R?k`A8vxq$I-0d`E&r)i3t91Ev=HYYA;#!jN&HLIp_@hL03Gevz z3oyZ;fvCl--q~tfn>a+Txj25}c_7^W?mjE`D59VAbi^Z7y7>8*3ETX+*H*w#|92?oE;FMdgg zyLvs@gE*JY@GNcgsLbaxtbp7SSHwz~!EB7PDu}6pOB?pK@r$HJ1F+EiY>bNhQ<+vO zW-{LlJj~~$auxR9QasCjz)QmvJL@R9_$taeO)pOoT(m6*NEzR*=Utu2j#27JVKUSn=# z8l~SG9Eq=ux56_5Tx8f?6i~c;%&(@Z+EOQ##F(BQs-7N>%ulG%YaNmA{mhs;T*^Mj zKjXLRny+)Hvmo3!dK+0_rqoKb-uM{*6|n(4lc|OS7O|vb92*`TUOgnzNO@GWdh-o( zavklO{Y>gRSRS6a`sXXq497aSbhNkX7VJHzr5QuJJv?K)8;?ibwwNrE7izmyfDC=~ z_7e~jn|F}TRg+?58)<3pU%aCJkWMLe>{=r?>EQ?9^~M zP_O-WQe!Fjl`GT?{x#f?Ytr9wS6j?&*+kIK`a_XhFFrWf5QRYMqc`j=ZS0hcA}!Dl z*5T8l%bPBRwI%n%N=d5#&3Y>*s;NSwV08SF9#e*mIO=;Y!NN$-6G%0?$DHx;ug28R z?ENtzB(2Jab+PSf7<;#i19U z9Hd^v)Zx^YR&1KH67-X;!l&B9w^aAE_FtV_Kv;>^TJ-T5i`d*G4?foxZc6Oz zL?*0%wTk7qf2{wl3Dx& ze;QdxF%TH=O-zu^5L~1k9{z1(T#a}lJySV3hwlxny6)zGO7qJS2xaeLuU}9LOh@e4z!_h4n|Vi+O&$IX>H}Hbt)CASL@8=L{!CVTkNq0p|~H!|&&6 z8waEam~Jp=%jti2+I^W40{@zKdk}Q-Je^B;88!#*vX7^e)0PZjKfBzM_kIa}5&!3M zTxw!e{1O-l$PNSu2;2WiSp4sD92+Osf2+u;P1&N0VC1Z9G^`cXLW&fXpu*uIApC@>j~T|Pe*M_tQMB6sv>{Fe!`Yv{DxG@@s8>bzTeN> zpx8*sSx4A<_q^Nmz4BqstzY3d4S>eUH=?-#zPdPq8e_AnOCYdM3@B8Un-t|!_TLls zBUE3U!4RH+50y{KbJYb`K;avRwANPD$SG-Qjw`c=z^NuXP#6E|L6FGLKqQ+^8)qZKi~6vF6LUdX1S7eN_j> z_wBTh328AUV_5OiPLiw;fqKH{)*}HmEC=38@F-7RX(x1 z$s#H?ZyyLz4nbCsC7gn#32_l~ZYIJThpTjoR$Xf>DpIFw`fZo10k9vk7{GHeS9T(- z>5Sh>tC^N<9mbC_wXFxGq@$DGH?p>mM0j@G=%A1{xvs7o#SQFUJ1$B0ySjoLI=qkI z{pcGH;J$-C&s=1^8#>&LXkErdUNQP5uo4lkMXGD&#gAG!m8ETdX;o$@P#NmNfC-?X2gXyOmVQO)N7^9z8z7bt?J>O)N0$!nEQ~5*2m+w}rp>YO z!7@PHrTWlm=nbJZ%S!)}@#iU#z2;xuEbT&7&MPW{1(v3lb>99A%`%rBBAGcGGiAea zNK!pq%UgJ8@eMJ5`7KmbDM~arUKlnx4qZErJfJ@Bbe#cle2TMIZE0K>g%M4jb(NRC ze;jMA9pTLB?-U?)ms>;`bHk{70|=M?0RPWvLMmi|;sy=^0s-~^l_s43hr+eG$bYa% zIV%Qh-vZDf)POpIqxsT4h;z%ohe zm#5&DOw$o~?=R4H;2pq_y^x8Hr>;n&mG2$hGH=17b+LGiy9QR*{Ja|W=uR5msO>K<1Pv0k$D4eXoNVkX-!ki4weF|(c$M6x@UE;+nSWSjh26<+sv zr28n`v#KtX(Ktg3!;!tRUIDoje$Ha|!T8PXOEqqe+#@9s@>5Ev!j}8=M!)@;cPSpj z^wEr*s2@CdaDqut8KR*n6v7E^zVX{%kFQM>wAg(tiUsC>mt1qo%ujjr)cb3yo7N)P zD=P{&k5DwVWUJy^kV3Dx}(k@J=f=snnLh6);KjaCKq7$TS z4w5MC0);aZ9$Xc{a zompze@1bHBzzvQUb42$gsV+BD#J#2uyw6Wz(IAo;$;tvv<%T1sgU{g6K#nEf&ZDmB23O96l)e%ryxQouZ0|9{T(J;@*Ph>!Os}Coh=d!n@VC<5NSPcJ)<~fq@VJr8 z`$%}^=)f}&IU4*9ri*fD;0fY6TrM&)zV+oIU(o0_D<^q{y!3&vC541hSeBjocU0+B z(KwwQ^=@rLLyPpJkv3#U{xkE`vr$LDQ~Zbdfz{E86!qn6S-Vi}q8kzZE+^*jBp|7d zA~2^rXY8*|N=bCDF;Pmy&wvke4k-;?d(NibD3j)IUKd*JX9RGEhThO(&!`E`p!R-pYDiMECkaf0Osb!E&5{IlclW{-ih%l3r zIkiY+p}3kZc*qkV;7v$jXb8m4!;(*xv|yMUrNM5jN6-Y-?t#>v|M9^tJQDar1B?y~ z<4x?V;rcT7V{X$WEXgvosRL1!gwRD-RJ0gAp9J(<5G^Xt;{a?j7reY7`&<% z$|&%cAf=Ng#baf^XCN%H$4=`OR3V?j zPMEHuaG>Z4n`LoAD*WaePqShaq0f(DZ_@FZRkVLhd<$wC>;nar+p-&~dGRVGGM^-I54vH|r>xtoBhI?i+OG1PQ z2+u+n4;`7(aP2^W9p3#`^uxWKVT7Y}_$g-@rL-diQO}INh#Bi3shieJBGMh1NtYQ< z1|rJF9BFTe*xZ;YN=r2ERByz!JV#^$Eh+Rnxo0hI;=>S6Pbl%bonwwq3v%{|W7jWz zw1n1Hnr;r}Ku@L>B~=#tF7%v;!oNKs3Gw}8Sk^TLZHrgLPyive7}Jj*3O}R5qqu1F zQI%zW8OdM&V7%RF&0MeJj-AvW6wJoCPVD7lN4lV_zKO6k5fb1Zfn6i=#1fnztJ#M{ zsMR(yVt|6tN=%TI(4|YNKok%n9>bw3 zBmWVfQ84S8AyY96C}I?7<_`kIDVC|xy@;h2sL9J>s*Mr5zk{4s-~O@5g!~KYYP|i+ zThcPOtiq9U*QEmzM|R<*JzaD8Xhvw?p|jQz$%0{bdAk8IVRy&+;Us z^H#*7;)+2{SKU=Uqs9J82Oy49z^2S(QE>UkhnvPClHi^wzqLuRUTAWQ(IMMgf!QJ1 zYm3>T*jo>0ShI}?w^_Qa0JmAW%?P(yzHJY;S-njNw^_Dr0N16^+f47-9CW384UO?G z0vRCPORRYL+Y&K5+a8?MT9d%{4>9z>^TvO!&9 z9kdpfsj%9CLlahN5n*KHh4~ZPciwk?klg^FNvl1s0e@1lLHo!l`(~xL6lB9dMq#~l zk%4{d@G51zkSmXHtq{#@wn4~D*an$jUCHr=b<%~Po+%ekJP`riO`I^w$11rvrF2;4V#JxDe}H-ZlE@1kL==%`9atL z{iH{FpCWk%H-+(v==yLsHErni;<_8t%5+%hrz3Zz={%W7ENR{S=| zwRider`$5rO6;@eC%RgE8HCK>P||K%=`h@Px_hjg-e29|^}h(FTC(oy<;wryoe93?-v58)MQwgDs)oQYXTyx0=ZToyDkU-S|7r!W} zUSbyz-_R(%sz>1<-i^e#47>zyKC-q^qW$?D)TOv!MB{e;;WMoBr#fasZu)$Nw%IqU zp9WS+DI)YD^W2pC) z-%(NVKEZq@#1*^)Zo4Fd=zymOH&CSf#Mlrt@Hm{f0PizY7}N>NI1SJb=zs=c5RHor zyS&>*JH@+(9Lb60`$%j9jBrD+z-)hmZ^zKrp!W;uKvZAoYr*I%QeWgjen zGn;6WIH>u=#vnaLT2gszJ)|fMRJ|9ThE5T&(u9$U126b#5cY{*BEB=Ru+czn_KcHJ zfQ##7CjNw5mCc>&W+w53N0lwSYp)&-qpRRN)=5D#kLLO2bn-dyQ?-cWxux)&UP-`W zlvn^Zko|02-aOVyM}I^+cFI7PKcXUh+d4t7`?kZn?wm0$`Q;W!O>Ly=DOKVhx2U<` zSR-%Q#x}C+V`%Cx z_~!JO8=!jlb+R`^>{$D#zf?|X>y^js>Sv|4d`)y-y~=FU+**AjzHzlUo@m4QE8 zaaYO`n;itXJ`~n!z1fbXhj@?(x%HY|{^F^kL9j8cInha}Mf<*NzzIG2S#qDaQ|^VJ zCGvGXp-_L}gM|Tp#<-XjQH@s@y75^anFy>#GrDBHQ+mt2-MSCx)1s=av0D9GV%np| zx?Pr5T#|6EL<8d)UUE7enL}44J7d#0-t{Xa@xo*lZ-q#T+$hGTs-eLyxdJ0awLP`i zT7;63xyMFf7O=hHMQ({Qp3tRDdBIN0 zIGa!^Q)h#5okL?j&?6t-%AeL@@3en40R}Xz)QtS=8>gvNS?Z7;1}`hrDw zlXIHd0CYlHC01tup6SXtc(+%@?9a+c`M2wsiuAz+`Mtk3a1QcHCMy_wPR^bcKSOOo<%*M42x=;$9cr-%qM}6^e&G^Gyl5Ke zER8N-X^$N>yF6-;(y!;1))8_xfwP?n52-8yJeqtoQWqkCjz=98#g!*gYIu%CYsmW- zUPha(?%=!0|HON{yVTF=BxPgCntzsKFnXj=j}8%Ul{XkH^TV#su3hC0dtj#p5eGP> zhn+7`{|pb8Egesq-uv+|f7Ym0qeXZGh;`BUc**$m)uDk`BDBaa?rvdw{OWwCvVEs~ zgXdaGkJ{(Ii;vvUv*5S2I~t|ojp{M8b3Yu^3|`qrlCaf>V7pI&9SW;Rtxsj=urycu zm%+C5R1CNx^|9g1l$;yIWPL8A(3(>gfLBI&|Ec~*QvHvJtMWiuFrFg*JXz@6z+d(* zIcS}tE;+~t>0s1MqiD&ZzQJEr_}dc^o(Tj*y^Fye(kp&3Xc1ci-}tzvXKaTGs{PQ* z-H~AZvio_mz)F4UbUO=j4XvX4AX>!a(FnPTAyt?M)=j3qs`b3HDM=96&adm9?QlmP zmUFRMC*s?{PzN49p7EbMU7sM4kBBe6;Nbe^PNmG-#5?~!{8Yxn%&6VzQ>|f6 z|D=4?tlGv*X~ev(K+1Zt8HUYCRLA_V zt%BHnn0|!ru^WEZf3$n;93h>XOFVvIn=``x483WjBq*;71Dxl>`B={_ z#H(06#N;A;p5Fm>bjM&ucU4apFSThW$x8Kne*PZsR#Y{b(ejyp2XlqLJ$L&Xc8E>8 z62Uz3*Dl!=W%J+zj*XBid##Shb2=M)F|~`Q-M{I03#UvM+IF!Qj-SRI(5!P}b^}N0 zu!eP_=VOqcI`K!if!;>`vux(Ys{?JM!~1vHUl4e-L*{?+z!B0BhS?%^L4*_N{6vIw zj-QhayQ=z4a(on#pGbA?f0%mXYj_K%PF=3FR=|G<1jH^|dLSjV_Jfe|`$`>3vPRw=tIxzv8qi3Z&x+y%M7} zB{S*ULb6CXZ?Fty7{>J}p2H<~i|+>W$2)W#(;vq~lUHq+<;#|i;pz0p@2HYF_&+QJ z2!qfWmK)Z@tg8meu}Kdo@d^xqI9BGYE@(?kgcRzObsTA!j;ohXUCtcw;1)ui{BPQ; z(sqx|-fCU8etUb|RqhnFQSy-8%y1!LBL5n(5EHqeI9KPPxVd%1g-dsL9LQN%Bq;HV zl(CyVJ?OK{%-Z7x1UmRSYUZg$p_ z8$Fuz?^m#(vrCp2eN6Y~;LO`her(yN)TEbj@Z6a!;z-0EQtNr^I=skLp+j!ky(MA*Fh~zrn zYmannWmjxWDoqtp8QxA5?GL&K8Wt{tYn`V&Zzad)rls%YFCral9GXr)ZLaJ+GQ)j* zDe2d-mhRV&e=Mz%j42IslX2ByYeO`e7EMbyckQy;>Al>_3)^j|yyzFpd?3sxmx+h< z?N)1}1DHqeU)zO@&F`x}pOyEHb{%IIMk-@(nnni2|Pc|L# zCbnCimsN$2eLqIa>dTKV`aq8Uyx@O1so0&BGyOIx6Adn(;^UBWirQBXA!Q43FUm?Q zg@CVEOmf(j^`1)nq&6TB`81D)%Ha5zCP69tEs(nks-6P0h625*4LQv$@0g1qAtXrf zMJrX|`@u_+5JBhQl+T_|Xwc?*Iyno!<;Kg*@w5f&6>9Fj%G`5|(S;*UL-aib@#FZf9zy}4V&IIb~2+}|~*^6V24s(h1 z47UiK$0!ty77bgSJm}3XN8_tG4s=dD_?x$U@{EUa&x{ zZCrVJb!6wi+sUt)#Mx1*WK#D9Cug1vDX*j*L^wb{lfWhdI(ybfKZV+Ij+rHJ+}RK( zI@#<>fOo!geh7`h`=_H`Spn^)yL*Ruhdh&ZX-PFRZi~#9_I+!~`RJ<X;Dct7{SslCdxu%DA>&}zm*S8hV(*$Qn}m_mTVDqq zeZlPu5e$eIH}89(B|p#1eK@~pkqgBqG;f_sG5w)k9H(@oJ3XPD@Y_oir~2nn(M9-c z(^Do(0rk}-o>Mv2o2|4S_4V1!tt%Sz;V8yG?z>Bg7QLw%4(pe+ExXTJphETB(a$&J$%QcM7||eD{_=Qv?ay+|3UU#o zu;e(AA*o5|Bq|>NP04+&XnCz{b-iSHU6ta}!2F2@%aIjq`1K9(pL5{ez;CssAEJ!t zPhLFL|8s=&e>j&GQ-66X{5Y4UTUAt&EMEy;eSy%&N$G`ZYa@H%=+*ac=;6t9MffQk z47bA(SL#|-j8_ubW;w$b$|^Tf2{=t0nurG_=WCtR>S;IUST^4YoXn5S3=FPKWf@MA zjH0hTrVJwazGs zz5MV_O}^u5Jfhn0oZX^J8a_Rv$>!cLvObBs)cA9`Yog#OEXJ*9;UG$+^l-lv-YRK% zG_ig`jZCFBwi1(I%3ayJ(lXKN^-*ozs$&_tRX-8id^TM(5-ss<(5YY~e6iesXskT> zJ3LOVU9*3vMjJmkM3YPMfU|JdC^iwXp1U)cSED~`5&h3Pel-q$)Z_e@pe$TQ-Svs3 z)wSuVsl~S0_M(}Ko1uq?X!F03%SQOfD0rPZ_;xZ{@-pM?It^~#j`!56tEMLt?7~8j zF;rov$Tc!WqZJm;KOARwZNuDYn}w85P{Q22RZ$dmT3L#lS%~e=ej4@Bk>m0CVVm5n z3{~!G))r<+{dO|}q0bAuv#M=E%0Y{zP;_x&Wbw>y4RG2ag^_70ot#yKPD)O0A_)aJ zlZqtAtI6iZVm!YTfx4kPp!mE-zxiS%a)8MpsQJ{2s?^gc>H@{nh`*5+zo1$`RDHK0 z{_t_h1!HwIH~YBwg4Q6uQ0fArKvEp;Abe16hI^s`l|9ls;XPQo=(g}LBst(OTDk}? zj5pVO+&KW59l9Ua&o>M+(6SFWU@3V6-jkYX@!-cT1O$}=raLn_4RpgY8s0~rj+z5})+3M4v27>5VU1N*^gi;-!Rq65?g+Cv|Y2l#?{A-%z<0lfgR z2I}mw0rrNJ0@?=YbjNkp-mt;_fKz0BjLnP;R(;o z-mnqCWWMzP*5Ly5AEOjo56I53_}U|hL66&xiP#!4DIP==aDNXSz}BySwKG!JpAa<3 z5l=FD5X({I=Ik;G%_{vQ+A_?o*q>r(Gwt}- z8O*c%X1seSOPjd2*4L9PEPWU6K$dzTY<<5rBu8@oV3vqp>*ntEf2MQXls0&An`zBX z3zw-KYxa#E%R5z5mt!L%Pobr;c9ZQmE3>Oxvz!cx2qIJEi4`0bgs{ku@ka5h+x432 zNrWWwi}tA`wj3=?!X>TltMa%_;PBw?F{iN3#UZ9uYE5f>Rjq9!Q>uyFGsq{c7=;lS&2|h?2zVSOnYmjTjCW;`-ZGG@ zDYhl%d7kUc@X~Im$z^Rt*a8BkR+2OP(mV2hsZ>KZ`r7sjL)WAKfn|x*2 zDXkWH5zp(dYFsCo&PR<)m6$IlLDTF{Bf?E1n>CauAKtwZ`3H*^i;n^j0dsIK2z8ii8fknEVR}U0SK` z!zoow9kr=Vw)Q=76qgeFvnB=^3`ZJZ&H;ooQn?$4wt-$gR0$kNk|O?X=B)zfwLUQBATT)Y3T%K#m^6Y)E-j}Z1M6wj z##EBpcr$fn51iim+oF-}*~p*>EUxepyCf**M>h(czfy7n-u(U{=~p%cYS)UR9SSmtI#YYb zS*7Mafe39nNLgnZNDJoHZz}fONY4WEPWi}{9%|IrSc^}MRW0PIAxk}A*fchCyohhN zxiu^WhDUvS-PTmLH*r`XIo>Lag9Gc$TYmTo@-i8>nH!(JhU4NYsm9(;b)u|X6`AWw z5PeoFK}UXw z3&J|3eK4}b!glo7*y`;)bpjhNW4VVRgM=M6;e9m*E3JqQiQO6*6}$GMP@YApHiZsx zp zc@$Z0o=ol2cg6oxV_6D~^Je%h`cA~9pET4`$Y5?poixl-S~p?q_?^&xqu1lPyB~9V zLmts;VRvX2d)X1*>X#9i)(qp2M8;_wStg1MJd`G{p z&VXy~mEn&WU3v7gO{}IKql?j^Jh4T-AY@uyZWZgip@F`L}|ecQb3q&Kv|PslSTh!ZwrzLWpDZU zW)iEtjafV8V#4%*Pqti^eqW(}3EcMU0wbQ(Hx6%5A(N zS)W|QQK3RLr#9D%`|)oro2KsCcWIg>H|8xj=B7tg&-2=qqWxRxVYS?pN?z-8Z=v-o z9md{44^4xy9o;-fTe)sbW3%y{okQ_u(>L+aot!^B6G+Cmv}RXk!kejTHJ(kHg6@tJ zBsDA`nh<8Px2?uo6uuIqtSqCB&0|N`l9-evM~3yLtYVcEae*Y?CTQ*^8r>Rt25ur6 z!&*cJRpJ|}sM=%bp&00S&H35UU5K4l+kF;A`$7?=FS$4>zSDm-m#O3Qs{!28o`V$> z(;g@T&hT79HINMJOyNr-x=kc%eY5Usuba^}jKa|HP4tKxid(^mn#H22LDssn>n5gh zy8Lvzy@qe;v58}_en#9OQyi9Aq-^BBmeO2nNbm4VvfnW`B||SwJzNF)g3p@-T7M& z-FrJ#F>OrSwfeVknl1KV+aKau@ir~>aOclA?89AJ3{-wy4XAUrt#h@jxA3TEe8LI# z+1hPf&UVWD<^{44+*iTz>+)?f(%NBq^IIQa9akLFHyPt^#jjl&L^)k5@~g zN8&iT?&&%spHBDc7@j@8!tL;oD6M0&<;z|V&+a)~N03M3T=zUWI2Bl$GKkpBewB#9 zn#6+-^Bj^}b^#Q_h$yL)!(_`i^V(xsQpb2~()7iv5a=Fo&d&|KdIZUHX`&2TjSKOn z622lQG;rGXLYMGnXncRF@;ui!S;FQod^a;W5;j>PWIp`Q4vSU1Zcm#cGVCK;?Ibfd z+za9`m`ODM&1<4bqWjr;Qbs*+P0Pe$s=CA!j z&#EuaF%UPU8oVX&(~ZN1l2?9J)jn_0%VPv?LxZ6~%95087JU@FmSRxnoAss!d8>=G z$76_YwtLW-NBI*^QA&ash4iYhxfo(j!c6DiIiOxbTt2S z!SmEBGn(LTlh(E&Z~Pf-cEmNu%am``F;$6i1>*3@ffwzKh3x1I9qbl2vt6Wj#Q3J6 z!b-1QYwK`es~ugMBozpmU=M%U;H;on7F*C0dncUQaTZ6q}F)1$uP~Xhx`n`W?a55LB%Yr<`L;d<1DgiC-kPNyG{sW`&Q1gBtmO%J~$L*&z2k z@1m(;Kh@~Kt+dM)@b7>E@u09%qk&!dhhO1dy&2gUmq1XA%FMA^HJ4C-a$~~<_q%<3 zb$@3w-NUn{gN=Kid15^2q#Owze0#@&>wTSJ|TSERO9StXjj>os` zFcs1xf=s7PlUo7pTM&j0C=F+p+#aA!Q)7xjTLLcl5l*W0F9FO9toYW{xMJXzfC}QR zjJ4Qq!)cdbqc$$U@!4t``IhRO_?BuP`IhQo`B-acDj*6TYa-EquSTM?)ur=YkTyY$ zkUBHPq_5KOS?e_ewhz#k?|qvC#kQu;}|-VlT=J%Lz0S1c`D zAW;RcBv@h6l+hepim|)Qq-ots_gie1$yXvvM+) z?y=q(C)-}38zyi~JbY$pqml0}_JB0XpuwB*6Ht?gT?R`u<~ubME{i_Cd`*#Md(d!y z)eeN}qC2E|5Ae_39>*0rP)u4^BhCRZwHE{!yfj!wfdtHKxZu=Nibh)9f7gir!{ zTh7Vp<<)6M+UV@g5;@ZpoCAROiyi^Waxq}GYT?VMOgt~G4lcSy$GJh=Q6tH|=xJP0 z0ZXSSjiij@+?KT3u(Gi<^}(xEFWGXFB(}Y)XY4$Na$(oD3r;@&`4Sx7$6Db+DASeFqA4rk8t3| z%Qhb>@8x?{FP3vWjoH^bLKsV=hvUFf!@3Z5Xn8{oMOmI+4$+HqqnNJ#7p-C_)x4YS zW5=@S>FYOMXRTpU`dea=*Xkyn#yd$}wnK&5@8hsWpzIKmtR@-N{);yj!KtXk#fa$B zo8VV+2M(l8G`){MO!iNT%OkV=#@Ag}U7MZSjeAiw)EFxK#?-|0x-ay7g}Mb2!schR zy{$eUFyI_)D2^TOxe1%MBELf#W8g z4)1j|zjZc>1E`=G8HPkqLsQ2fJP=h{h;J^MplTZ{;3V4C(hJ=}EPnAW0`tKPvFdAm zwik#L1Im_3A*31Lg|xtci7NJ9{E^{Z7ehbth+Yk3#lwRG)l<+8=z0SaxzPc46d~aS zq+wwC7<-tg8#*|(nsl)g8Gc$odEl+7>mQ7hJ5E2={*-H zt^c=rr%n(DWJ!gDDxu~-E{Gh9AKo^a_xCbJBi9F-+T>1F8Vo2XhBrKjP}^Wi({lxz zVdyx7fZy$4l!D_*_K3Vs8OzO5y){N1k49!_1g21 z#c?R@HVAYpv6jf=(EY<{dPZHnRNBx^-O8QeB7T@uzr?%nu+cHsz3TY@3blN(^ZD}* zcr{_?TiH>7i}4g-=L_=Nz5+l&1|Ap*<~h}1w11`SaQgUgcfX)u8?tGVp|t0o0Oy%M zPwVvZpL}Vgm;%i^eJ9_rynhp{da2gpC7twd%YQGQNt({)eR(5?xYWAX>9+5knrDJY z?ulDw=!T{@R4s$Zumt}~`(h5BMk2Z6RGBU31;2}Dd80hD!|Osj@Ofn)#m4l!JXA>? z@5P*{Y?)cS#)?{8o41!<`9cux4gs=De?dqG-1<7%mDD?i1*2zy?|JiA55YJU#l18Pft#kSW-@PYIZuD+ zYJg8WnzRh^vN;^<VR)8K>{N%~#Dzh80@ z4Z2Gk<%BMuo!-ixY3yiBMbfH?@bXRIy}6@{iY3)!an;pc{HID5*?xyn=|}y3 z#Vkh48`Tw;e6UHs35>sSZQbcPVOQ{;KM!kX(C2mXsi4Hetbj zUHwK9;B%TTn;I(b^+7mXAp3)M*x+bH$~h@@&wui&>-ue2^AZ!`4UEG7=;;~cDlof* z%MrcdGP3j)5mc$}K#OHGB!FD<5TcYJB(~F-P;0ul;GdF^51p{`D_@sxU-wu5;kYr0 z4>t9bgV2%11WPz3C?kdc+i(gSp=GvwCU1IG*LKdE+xtWaO7wF#^u!df^5i*{KXk+Y zU^@Z<8S(L#Z{zkat^kU{Tgi(VK+V3|X9niwmLGdHqY=YO`psIi-Hm3EaPQaUxMIE# zOA_WCRRYPXhr!X2)EHSC00eV{d7C%ucH3KR>&S8dz@1%G}h88bA{Uebl#6D3alXh0h z7x9sHVk6D1=6ZGqsg?cC)-W_)<2`Z7t2Oug*2j{oVg;^Rzo8aAl=!Q(UfaIOFf(%R zRA)`W86hUk-N@wHz;m@#$NuVjVV=N^6L-MhKt1mzIhc-*u(F9_$>;FbUbyTJi7Qs|MyHscYx5w!DMUw0>l6(>rkMt zP#m1ZP5UM9a_*Co%o1YxFkea3V_5u}Hl_v1!3!S{h;P!Qq=7m?>=+i^`>jX7j4Cq8 zKICC{MGlG~Fi6ba)mfBLxDW^FYR|G(AwfR?%TG;@x-0Or=N6<~cZbCmOvd*H6?}?> z$hS~1U_3;OrdCKNF?UE6uh(xRGZ{dOm4>HQ!|f(?iWFNyu6qp7=O&0HTd2)x6b^qH z5xE(--n+(n2r#dTkefWz*}Qo?FLp;O$64V3*FDDY$*eEB)OHJgtju7CDosj)tGxa- zgOu_(H>pG*lW632Z@Ga&q!!0w-U%AA5JTOSw5<#Lin<4oo>OnrpiGulpp9cmyaR$X z4of|v{6spRp~p_=9hNtfhBdQiQ&tUa@J46<<7BxLEa=z;3%R>fvzS_ATS0`3HW!x% zbsnqitd|Y6%rM##6;r_YXhIT-Qya6 z!hNPFe zrxc$Kx8DuTs*+N}7hgkqxyV2ynHU9~27=G2wV6}pcVf&7m3rE<1HW%Ip%U z4OZ1!uj=Hug@y48Wou7S@YFk)Viz>{3?QWhrpx_AYhH`~I#2Lqe_ZPq-O2f&&O)-k z1;e%J1;``w}j5eKdXm8h4e0`hsk9< zmWNN-3h1}Ioo&EwVJmJ0paN1{vPa$1CY327knVnFH9=H`rUPwDr!Sd&ye8uD@a9d? zhQ3?UlLdo~5a?#+N~%?UlfpWe?l2Xe4A!luvs244bkoIR5Yvvdes^l!WjkpzGpt>q zs2!zEGOxV>!GqPw@oKSHa2YQX?c^Ri15I|<-8Js_emMsbTP1l#K0}V)DmgwQb-NMn~C%US1;u@Qc*XALG2X>}+S5kK@Q}tvk@@NNc^Z zUB2JRxWfBei}3Z+5{QFdA8+H_jSUwVrREj#3BWLpYE4qqpK$Va?{!nQS+T1GX>-}? zIa>q7AV-Ww&2_>x?QMEJREUxbH5`!fkG^ZJ*|d33!xunI&sBaH$fcPzyUf+TPO84i zmFT^@Fqs$(8X(CmC)$AD`bHC^L23+5p1VMHNR4&LJ3Csl2cUGNsgZnI(f0XzUeqQb zQnE+win-!6`SD*p;mv~W4A zVKTqflZ`8zh?6fQJJdOl!k;#ZT^qr~-=IbA_hE8;8}n@|&GyZoH*W$O&{iyClOE-{ zU#-n)1AP(LPBG;sRE@kgpV$MnaZR~U5c7L-7T)e2&EVZiO5l(mpeC$ME+92Z7FaiulU69{`l7D4$vdk*U!o6 zCDp090prr2^R}Vc2+%KqIqH3}qouaCNCTyRh(Yfj?)h7&W8AEa5ztuHs(7e0DN^Vt z#ZbfO73K|g<$Uz=5Lb_1;zc3EeJBWgf9GAx-o3($JO3w6#TS6W;)2f* z@FH+_d6tz^kJJcx#V7a;>Hu)*?(TCHA#E9#!#fQM?3(10w7E!YeBCawP8@bIc^VrT1NHU>#*@AV&CHWr-78zw$! zfF#229UOk<5?XHzxFHJKF&T(Es;=D>ek#lFzRm!GGo_4wJA<s944>v zrS2>8Z4R%T30O|xC&cFLv_N?~p1NOyeLkh7<8xQkmt%9dJ%;s7YYP>Y7E|h*r3|+t zeW1D~<*a4k5A66~po5emn9_;!sNvjEBvkI2uhd3$QrDH)Xy(+&Galy!?suCDnm&nT zXWZtSR5PX_^Ri<3kL(x*%ou{Kj)~U%6j&Ao&IB;z@CnR>d2OL7cJ*7{fD5-ef#2I9 z8v03ln~PX~keTY0u5#yp(pm1QOXvHKT3C!1kMqZS%eWPsD`~X-A)TdHso(ZcZEq!E zmOJMehj!u>lN_~V1vApTAvDb>+q<=vm7nf#W2%d%?%dVegR(wf8@5n3(*PQSWnQL* zvG9>b2^+bYw>ox-Lrnc?F${7i9-D3IP6^UI`C&t`?I`dO`RZ0>cXbKL6&;t3yWr9? zPxjhMTWPY3GC~vJ7-{=ZFp*EgJp{!1?Turn!4;8R>EhE$Y6V$OrCS7aSwyk&erUw# zV0{|-NWGva5VgQ0*aOfV2sA!?Ti}LzGW>R6R0tv_;*WMVfH4d0v-Gr@xjJ)3QvC%i;&YQIR~LO5&Grqu1F-P2)i)VPi>3c zkByH^22>11MfF<-RNzMzmK>iM7fJtGsja1QGMY%vQ9VVf2X^W^7kN5J=ZO}j^ERhp z^NE7QvVWOy8CNgPD-jixiwLPyvW%v{bTW)9i_|f$(yY(L!&lL?UJuX3k0osthHlGz zWW7kg+#c+U7hWeW+#b|g{)>0r-3oB!FWt^!;BHH5&1$W3ypU{-_g9k*Hx+;0u-kAV z&#F2YQ6cOvEEe$)GTe;RAMWhb58ktu{yvzMWkhF0Pq6wo$|Nq>FhZBn<|I+N znaW(9!nVOfVE>EECR6JDaP4U##qcU=ax<)zi=jqx}mvbn_H@R zVu`ZYF~pS`BZwqTH`Wvk?@IjE{d&`OX6&E-Iuli(Io7-tk+=ai>G^VN#Q0uCousUi zYO@|>27!8XFkG?~7H6)|)8#~@v$&qd^L42VylSqbO50qlXCpIJ}Xeuk3(1>iBV9-GU;Vd3N(p z+*-Pa6-fpKWm(l(&u%s>P*=IO+P56VwpK{i+mVLr6Sx;)wC2^Gi-?tO_OjZW**e)A z-oC=`ifD}#mU$Psi;|2FuYd|Y_{wk+Dh0!w#0Y~a-}&wCOLYtqkJgq~E%>@cN7ufD zSMv2;7Y}n2+jn#;7(^5=)D6-eOWT(oWG8=ePFKH^wD+GJ&8gmKCZUs}2uk$s&=kCA zx~xAX`2XfM*VbPKYAH#mLb*nM^f^<3Gfv|rjYOXL@nfIJIBBFDVY6_KiGbwia}bW@N5Qz4sK?F=kWVCg-j{(3 zL!*`|GgsqSYZhLSfv}zPo1N6VOi3_|^}Zt=IET zza2e`JnUBsCU00j8I=Tlq>cI@CD<9{H~fE!Un}bQtNDItEL+e30Qvteel;`JGcfw^ ztF26>&7bH4gze7z)nno+X=D~WtF?TIk>C=dva(A-rNRct82qvvfe|F^^Y+MpldC3A zOVS1$@-c*n2uQi+iGOZlAjtFn?aJ21l2pXUP~Hxtm-_^~4P0!lHtq)j&hIy_E;c4? zj7^Na#fZV>*zcePx^t@#VYlCf@wrBXNSpownb4y-Kpn}8+#T1^pcAG-P7tM&=Zl3< zqVNttat~P&XX=8w0XhO zxKD-ck9JzZkFvMvv9HV<7oW|^f(hLlB`GH6p^F0!oaQ%4YC&>$&`B#}kT1vq?OI*cr7{AGb1?MUp$q^6e)dGY?ROGt* zethI%nrQwpHg#xZLb)RqYg>Fn@}g=%;XZQVBWVs>Y7t!aGYxw?^UJFxE5n+&SOb!* z;ppXbbERo6;}2Dm1JCM}r}d&jedL1Wq<7kxqz+k^l;!qTdRaLWtW#0ZRD)Y!r-qYL zSMJpE!mF{prG=Ht%esQcmCHG=+T(#fQLCx~`tzXLC*eXt(*;w*1U1TF-%6Uk|7hhx0bRKozxc0KszYKi<3YvMN0UL!Twl(=cz7dzF6{ zHOmE9Ow{)2*($M}T9x&q)L2VOjg~!~vs&C{eKRhs%x&4Ya{>)Qr9(^&==S72R#BDv z=;6W39CJJE|L`ST?MXNETE2AS=ZO+^J>VW1DF-|q&msAeinIJR0rDE>_IbdUa~8>X za7eI35Hq3|I;KL}sP}vEDIfx(=>kJziMP#D6TW)cWgLr`C0x!qa3gE@j)0j{x^EAf zIXEsy17}pH1&UTiA{f)<*bKiv>CyTHvK*ARN#LAnK7g8gAcfDAK}e?jSBfrsTz%vD ztOUV*c1Mou#qaQA_l3~Plg-n!!=n1Fnzm%?9H^r|vH3gR#g+}Z#h?mbubkIA7E=8|ZEF)sk3#>6=xZUaE`CPYrqJzsXSw#K zf$PvXQ_6lCarq#1$PoohVNq6Iv}%KNye2}wYT$BNLfwyP8a3aiZCT)ncrSWISyDCs z(14rH#~{CUO0A=$OK=mrx#{{GTVOg5Zto&(C_=ZyPBgTi(iW2(!eE;E>|EF#a4BsplY$_&UEoEJ~bZZI{-Be%)Q}qyE37-|VjOWKkUQp|$hxfGopR4d* zC-qUnPYlgp7yy9ue_w?y42}PL4IWgMw){Penss<}XVQvdlvbxd47Fsf9#TaeT0mSb zgg_jXhyj@4(ojWj}cc|0J5I2(1ePVGBc~9ELJm2nYu*gL%!7^uhgi(koi0 z3#1PO1mr#!>8pS;QhE(*^d&~=eqsA+eALNibJXd!J@G+;5Hs?3RPa38FsCKzEEyAj z8%ZKAz<#07K^)Fp+XeNNc$z@grzr+SRbyc=kL%Rfs`-k-Jzjh1iElmmfq(tohnR#? z;S?zOL4o(d@T^Uk5a^v#>LIqRN^WsRc|J**mV(NeA{^F1-fSVUHMR6akr}HSD?N0I zNro9We0p*k$b*#C{{rk30sYf1z`+l!F(zzUgdZt#@aQPI{wR;Z@ZZa5<$6!pQ)9tI zTVWbQr{9Tgg0_<8u%|;_wsLp z)_3*k0h|VU~ZReCE?J zNK(#PaT8If4Ex~Bp#P%Yki*PUe4;1hecH9t#xM7=qF=}Mw=WEPSBV`av2eJ zHBN}hZpdPh9~KEC`O)ZW_6S{MYRT=JQs!{%ifKj{YA-EvKbn#U{Cjr_I-o_P^?sE5eJ<;H$<3Utuc(_o@6QoK>`x zo+-t)8FqKuw;B;JhJ>w_!p4(v)P1)4nOe@H>QHgt5fU*2_HRPz#OSpEletAi2d zg$ zj{-ukHfAp8?ac+g<#+&Q&;|i6PSG|0O*zin9}Vh4xW_{O6B<<@$+2ixOoiGg6!|wH zn;%KQ&rLVuvdo|eOOM!qU9JZTC-!xxC09X8BdW(e!+7su>@UMq?$$u$FJi;@Y z=?Bu+k%C{ydBEt1>81Y<&=+k}`%ArC=dG>9qjWdh@{LU6YjiErpU>3dX}mr9+Bt+g z2-9t(v6Dy^I2zbHXZv71-(>fF>D}WRuX-W*=Q{9o>PvOMukk3*qVm`B7w6N)J>f^6 zc}DjpcIP}+r-SX)F<{FMs)vjAGX(U9O?iKUt|Rj&>!p|MrLX6qmzkRokwfvF<(HZB zjiLy+;Qji>t;a4qD}{>gs|_dwqLfy@`ZI?lb_2f~rROf^w~kybg(4Uw8U`9Hx|3k` z#<^N?7LoYK9qbC|0I+r-&W%C!@i>kC>v!^0kyz)W?|Yd}#ZHfE?3?Z;a3c7!^k;ul z&?i81{ONf{RkJ(+Hfg;&Cjj0*RDte$1Ys`P`YH9<%0Nk}P7HZdqIN9Is?2}Pb({*- zfCUIt2k-1wAy?l7Nz9RE9bN4lwUH{74fpdGR+c>ZKDnAvNEU88VhGIE-r!F) ziWQ%O$fbB{Wi&h{w%t5N0{r$M+7(VeIzWP-4~G%cT(|=+8j!%#---sCZ;8>uAEoFk zxgb&N05Gt=IY1pibVqC^s#{_IBLozZY1h)xzaE%JgeHHcJ}AWGZyOZOWipT(XuYn2m2ZTcvJ$E+x=56@)iF zuj8KTy?v=$RdOSFjx=-Ik27`Qytf}m1|3KG@1)z#8@uekdor=FLr<}G)I;2zuZKUr z&~|&2Q;2HVH#Dt0MgLKAZMccPdFX2SZa==}p6-C;R{3_m7jDD+YC!#JIMp$KO~&d& z3fBra&k6?`j+dCVXHC>E&T?paXHOCEOO02jNocq03*k5p8m!KW*h~)fByOOQW>Xtk zJss0VqVA(slx;5l&&C0~;zx&#z+NBNhYHK#zI&!oDSCqIeTL6@vUvZGm|#ODlezrRSC5rqns)+W5z zvmnBFve%iz)Qi!On;NB3rK7a#(S)1Z6vy-{clUh;lMXX$Tj-sv4N1s10X47a>nkJd z9< z0uht7@w=1n5fa!xL(LMgc*!6SBs&@bkhN=Xu@LWyAZVwD7ghfxC4i=OlMg5`1a@a2 zk#xP^4UQ9dF=nNu9Tw|5%*RcT;df7eVmHFal3XiNGLVhP3q=T%2nS*t%OF7PoMv_8 zON@oOQeq0*oVWp-^rkEPJq%3vBfO{v5_xgxv4TaZMkRl$?jHMsEzwnx6h>UPMYuu( zyOuSV?f+3%hHg9@!7G}Wo!>!5|A!h^dN!GW;^noWe zEp9f7LD(9EajFh`6TK&;z4=WGN+fP}JF+yz*{u-sESpR5LC8RyLfr-^&33@I1bT?a z0BQ4eGE-~L)|^p;<6-(kQdVTo&?IjYS_@+4Z-$mwV5U2y$Qff0lR+S`AfRofh++5d`uXN5br5)@gFnSw=4A>g=yy|0V* zKIT}8_HK4dyN~lXWqp52k=NXUr^{@lu(JQMr02>^Y%I5EuAe`@Z1tJKVQ3-ZVDYxj z-ya?PDEufK#?vS^#ANMNEP9B?DKZQ`K?)7(;-gUwg0+406LAmY6kGtxXlMq9oIj0y zHiBx!%$E8&Y<-DH9Q882x(SC!ShK-*yC``XYyW*MWq+Y}y>4{)$F3t%F-@!G>X^`;W3_b&d`L- zIbI_4Q;!A0#DHT$#L^==Bqo+NJ>y%2-HO)m1?sDInQnu0FQkVheCb{dr$b?~h}lGN z2_!Ypd*;ZyIqL=QfYL@pt$I~|sZdyPc}Kz0{4#%$B?R}J@$UFpX@p!I3^Yx#8zlD0 zOb(^E{zOA+%tM3*%2(EEnO~IE(DiZh%B18SF$V^tt!|ClD%7QS@e(Z?+q3q+ zga)`46$4E{69e7!v@?_9%q=DO{^bF0EtwAMG?Q;4=-xqB7n$SS*8bk%qO6_}SE&8X zAP%F{7*KLdeTNwE{;*3m`htTS;+N>14&YhWb^B`|OAK z(KMt&kZ!Q9^O0GNNUJ`EmZXW#{hiM$+j51O7pp35pTT2UUxEQvL4DH#T6??Y_#9?n z05qPH$Sa+YXTF&E@)&6>(-tIk-zTm0er9dvsH8c91V{dB3}U zgYs)Sh`?KWG=%Pg@s7E$wtHCw{&|GQW4rT@kA_ZAJ4<$cn_*C~%nbReKXU0_`RvSnxVmsw1L8F^9KHQ_?iF>X z0WUHAAK)_|S=zt7bWXNe+Sywaf~>;gL%nF^`3{7@>6L$#(PiE~3bc0gr_$B5YJ9)2 z)7ML+o?X?~ptnfV<+)sJ%AI;WHK+OFDj_qg#H$J#T*Yi7p-yq=?my0!L)Rnf+bBs! z$ZzeQ?1Ri^H3MNWF@zh!CS)SU9h0T#qg z&iD~{E_jqqd4l$C1Mu5z!D=yhR;uXuxbq&)Y`(;_`T$RMu-|7|Gwp8Bo80a1>GJ%?l7DwV`E2jAcAG@5TdJiI!D;7+O*ZB%x=$3%%S64dk%J6F?gJ5oGpQc} zp*VgGLl|S58$snp!O~YP+75E!SI4x1LLSj4NdDq0e|3-vXKBl4TzcQYY;8cZ!8=hh z-IyCS5SnhhvxkI+23+RRdmY04$zCT);#F|f}^~4qg%Va%Hpjo+#pM*m2@yzEwp!W1xi$$X%LG$^( z?7#08t1V_eE9|54a+%H`Q@B1mifgW-abtFiqN`b-YgxB$2S11s ziCsNwQ;XOA#moi$yU~`A`!)?KW0*LpDYj)j{dGNHacDdm_h|?aor*)dgqg7E15=h2^=h0fuH&#aLrRmLQ4B+xzRL zru$`5EAzGLux$L3wa2I5oM07mQ7bHF*T}>snT%H^zp;zcbnUg9@YgqX?tur5^8zw_*KRWV2vQua^a#?(xy?oo!AxId0h%pXRrjXN&}+NHDSDkuuo>I$o-fJ9KIp6%>g~o zKy7L}%)`{k(i_WcXE4`+4oN^@u}y?naqyagEaHLMCzkvMW({Nr7As`$9V(QpI9t)< z7|y=7g(Rp3aRwoCP00z`F#&UbbL^`3aBfqh@^Xh|+C-K9wXJ#KX*Jrhwwb81BDO5f zL`(cVGKDZ<*6u{M1Sx}JdhAUb4+9r?XWv90IMD*EDv{SA=iXZfWY28wR~5$6~b zCx7v+JGApLDCp+iM=sD!c$$A>Me6^i0iwM25?XqEx%xhGS#nwO)H>Cn7GrK#|M)eb z)uwUDn~uRJo>f)&H({kNviF}-`w$HumfRpA+ zHXBcw-mLM#Eleig-+_2|$KM=%Csw3PuL@OHWqTJ>F+<+I**NUp6fl?L zS06@Cpu!Qq8CKlxz#I9j12Yd}St~|5XW$SGHH3@ao1pAh00a2-*eyYIOkIm1%;8_c zfJBu0>|+)8aMZ-vBUZ`qmDuc!+Lzm_I*%;eS{*rDVIh{LkLPYv9F=4bXI#-Xf5sFH zC3USg_=EcvrxciI-W2Dslk4EQ6vJ2Hhb$?A_fo?ykMH=r)O{x@^OHNZ6O7jE+PRiI z<(@uN4b36i{Yb3V(Gc=CtTUPH+{-@n_u%T4vwH1-Gyt5$# zjc}^`VpkOcF+DiiqRJ0^2jTFM6w8OZN1PM}kc~bq0e7V_1j^HaPePLT3=EHOW@~4M zjQY8dUp}1i=*PuWm~A|<_H8-875#CNe*b~?0sHeUpg#zCEsKfrCd|ccSWg1(IxL2% zH}LF@0g@h+$?mmsi%aLJNeE#+<(u6TwbO<>+#4H>!)C1a;EKKWh_|@!L73<}w9+RU z5BC$w@GbTzunHLrllvP?3xQAbO@cea>7rM6XhlokYg!t}k!QF-9#@ixNR*h&jWkgr z;=v*LS%mmBc8fOQBR;|sf=^f-O1(F5_(gz+kpZK=;4n5)V4&h2icR5Y2mQe9(H63nm0Q8F4Xt5UgIKH> zXP!fv4q_xmR&aFvQj|ed2>EffXI@e{c=+sLVApXaK#m%4>2V4ne47JqY~S~R6)J96 z(M=bbnWZB{zKQ1X2be*_vfdhV>DkN#KQSU}(D*mTFuH8wA-a4bY&9(;h2TB-e*Du7 z4Tz()pJ-u|{?7*0$)B8DIts~91qPFW#6zlN>Zo@$46IG!N@Djc(t%9wNtqRx&ri=Z zN9)kO|4N`yn02|p|F!2rpn!n>DI5dYdzjfdIWy?#*?Sln7@3>s=`q+F{wodtA7z>n zWm$6^an#(USkRJomOg1X;1!B@G&27&bw63aA zMO8v6DbfSPlHt`5bY=6cRBcD=f}ZEapw?4DbjNclc0o-@&ZlW=1=0;kIILp!o1V9A zucn>u3<6MKcwQ_&!{Bc*yrCy!Uw7m-b1`>Q z=A9C+u@Oyfxt7|3g-m>>)e8Oir6krJ=D9_@a%tH|U4xJ?fy=AOm2wXbM**t0z~r_{ zCvRQ#c?-+BG<~&3K&)Q5{-yI(x+Jp=Qc|w3gf<7ond56RY$T&_w-?w!u}wLoJd z`v-rBX=wJ{g@W1Xs{fu{I-0-L7&RCv9Vk+>=8l&rZ(+`mqK~{uV9w8C+3@ZB4D(f# zkGB`*K?gY@GMy6yxhspn`R=OvlIgLjO(S2kssGUJMAqHMj=(Rk4PbM>E~4o0zbyST z{khhzUrpfRRx}dF_Wr4{qFI>;SrUrzIU;(qv>^!p6T!(HA0C5ll8Ak9`I zVG*NE9pjxRMu>f87z1qZp?SzLtKV<>w+Jq}KBK)>Pla`E)Ki<$S`H#G+jYsNtzAO# znru&9JK0}GRE3!hU_O7swj3T7Y2hUSHy`bpKXS3)@ZQWOV3>h!uzsO`3s%<8&I3}K z2@Cz0W;)SS)ilxs_mkT?akGP2<*Ur^{pj|dwx;**qj2?*Sxo^}R2~(8t^Q^7jR8=1 zwW06Fp{(;t|LAkNozBW+^MqITg4cE%!NsRAMBxf$W!UzyMDCe;D(KsbY4iFya_U3> z2YGT}Nni9mid+2vp6!*FtcU68D&agOkG#6zj}N8sQy49u_Q?>LcOHK|qvhR@i!Ww$ z?-!2tQ&62(b#H^`jyg(5cNm_6XV+X`9g_y5t^7^~y(rH2cFPMJtd)K<-*3w|acPCy zhHq$3mM0EUv-9w@%!8UcSHgi3?Z1-i8RMP=6GO zJ~B4GJ|6gURVlwy16AtBpd3>b14Gs80yuTp!~4!phedlbOF^uBU~YA`csGh;K-uh{(z z!weFyRQ`6u3{tOWgAXIqucg6xz^^5*WKjHC?b_Z%-+{q(7`3Q+(S4MACqUndpJY&e z@Vg9SdVHlEz&a-|b*OFoTJvczKz^*>mBDtX$|F^GRmi?byDR%Eq@QntPla5yskgHJ zR{PjcePH%1u~M%l{zeWmgL**iY_%!3G(amOF38`+{Z9@(!T%NrZxZe?fNo;4OTE$n zv5&|i_k!CyZPM=5fqaU6h6YyweQSQQLiIu4t{l`!zq0!8MDHZuUI6`Qe>x%8(swI# zqxF&PVf!CA;O?4dQ_}V3@BXEMHb8H0;6PGJHr0%Lc9xmrVk_A#Y1x2MEC$r}gKR8WMtZ&4R@yEL7j`?(ztJR6vVzxoMa>}uvQEk$U=9#7cI7quj zj@)wgMI^^+rR^dI+L^X^e%uC8@KpKyHw4ywSOri&I*pK1mjX3;uqL^JKM_sL;d8GO z64%iV(yqea($)vsM^ZAi3$*vtr3>LN-iU8Esz4yB@^=71u4fW(O>o!-b#S^q zmHrz?f*;+E#^x1`9<{vY3DZFaQS}&y9R!L~bHBZ?`hHXGn&;M2MQYci?s7_}o)nvg zDO^xZ)#vOAj-d-aNKp+4Qrv7%K;XbOIe87hOB)T22~J^<`)<~f28%HEFWm{x3&c$T zrI^(VI12G*0ZNfwI6T;E2&4V%Y~PCCP7b;F(lOLdf;Ho>`ED-MDf#hltsAvNXd1Ms zme!`0*}!Yp|3c$tB_;unJ^%HmftG-sH5CxzGU%lx`g$2x};Ti zKXyfIZ-xzZI1om(@gEfIgc^17X&Ns%Hz{b%K%W?yp6v1Z>f8Q!*pBn^TmR9#;Ro~k zX@8Nsnen`N_6@fVB-njj*yg|dYX334+4{D7@&yC6e*QMSxq1H1e|ceJM5$!fWzML( z_IkRuvvgTeaBh0J29}>Ty@~r(@eL(Y_i{owLh$lU)W37)2VVTs)k6?C_zBhTYlcF| z5ApLI^sVN_AAEG?hv(@lNB6F83)K#4wPT?R&Rgs7xhd_72n?c zJ!ea;yM*Cp7?PKv1wBo>k-ZdF1wJOg(MFfeply@>zE-hkm^H;BpBft`YaD0G*psCc zcOe&OtgHuwR+dV9Sp z_P1Wr|=IeUaO%usN{5a3f21qUYG3($BTNO#DxfWFpag|Nu2`PxmWq;m^mfy*s z178in1k7$4>)TK2<*790b{#?RJowYbi3)}22&YbxG7GK{cgO=?gJ9S2>r|~7^Y5V7 z^b2B}#ia+WfAF}+-FOBt324w+06+@6)y}*oHW?5NE}8KP!I&FFbU@) ze^_Ea?z~HKhugh*Br{!{2Q3Xa@)U$yvIe}HuVXU0yw$RZl}ubpuO>x=_Zm}4HY8RT z8h?}5ifmlOwvP*mx{M2L4lj0TAe_;k+DLvL%NpAaUT+ecCyLQQ$Vi__Mu}F<8MY^| z{NWHhwP!9RbAga5Yr%BJ&MdXh9+jXonagxQiLshCWiCb0{q^-4RYU7#BVnL3#De{Z zO*Qw;SKv*TIkCIq-KD&>91Zj<4Ja&bH%uSg|3(f*HGG4xe&~&lS6v+pg5Ks~J`uRQ z_d3%$V)fzBz+Dx1B~1LJjz<;OPx7r`|21SJj_`E4&ab(=WSUgLqpmi2}BkL&U}t;04u<2ht>E)K(ahyqpwaRKPZ)M4DcD zF4+VVuyC<3MGq#&uy!dlP*#?qliITu0;>iGQH|~Hzk4Rvo_%M$+4@Jd=Ki30Ia8rb z!iZ*pkwv95n*o`=c}~9?u_jEm6*tWIQeB4Z^)Y?@i1iOqmI|J1iv%&2CB|2RCKKxI z2UNBd21IR&}Gy)UYd(o}L*N(&-7uE@m&QxY*GE)-Lia|t{t zELikG2`gD_Uem_eZ*7h>Jq+{Ml9nh(j7G8*0 z4vrOhA;jF@eS(`o4><0v2CwWdCoeqNI}i50GYH2o)ag5MXpvEUjNt8+O%WAwq>$c; zN)~Gbt~=nuw@P7CKGP^Cy(e8`ZpUq8ffim~zt*OP1TG0N z4?&fF7Ne_>pC<%Dfz;I`sBbvGe}=m!G)CTfM1QV~`dE%#-T_m?N502c=__Yt#<~>2 zUdSYH>H`(;fwX-cNU7GxR2qV7%Pl|5>#jx}Q*tsM)h5L?b^zvdATdZ=fm>`a79g!L zvSpc9TrfKUdl(R05Gi!?JkkIqCI2L4d*NMReY1qE98hFoW$s!`N1>o`xDy{Y9X_W+ zndv_8-sSHKAZ+dm)~Cr*REZbWY3M)L^T`Vn;VKIG}m42+6?Y9+?CdsgdVk% z*gZYA|L%2j+U&VlL@8`Fu)tXsRGzC}a-}qsoM?*$l4{5*-ApTyV#pdV67g!8-ATY$ zbbwbatk9!N!{Q7YmD(@v!iiyUm*b2lUeQ|g5a;2w-!i(Y~y6U(JzsL z4BKM>gEr3Swna_zZ13t791R?+a9$^^zPDn2->eSbE!*2SeE?fITpYv()#-p-xnWK3 zK_3bEE(3Wlgh!U4-bbp!}uxM=bD zx5leM6d=>#__8fi8J>d&#b0Zh-Ik6($O}s``x2ve7>~98T<4Np+8BZywy4ia*1?y< zPPA;aS`EuO0jjkb%d?&U1Co8!rmtHCLIuT7_ZL53p%+l<^SIp?STJNX5^vzb2;LV$ zPfLW)-hByAWBf!+Vf*vf%M1UDbcW=tlLxTxS21Yu@MJ3=)?6KO zee&yORRMveWwb_T#&JG=GQo5xF_Vf3M~pK7e4#U3YOy{aZF+YAoV26=dk-l!A9-z!l6E!NRcBbk;ss_9nw$TO0o%S5}HVe($nIHl1&D(SS4TiS>y z&G@ykT6Ge|s>NI6EDa&qv80zciUz|tI~Y26g97-7vTkg`JhLrLECka{bE&s|Kb)2|4@Y`@Gy{AZLN3gSOrZMTw8O!3ASgsj)G~x03Mz z)xp*4;8Fr=rqPT%)!t{`8dm`$r`PBBnJa&2#@FwQJD+a{XZh?o0E^~olZwU4P3f;9g)!VMETfl_tW|G`Q8j^Pl_$|IZ0@yXKuf-0Qwx7pN=5Tc%3d9feV0 zGg2i;t>(S^!6pXxKj7?isqGoSORqO`PGNjTBgTPpXdz@$Hm`JMbXH*}<70!Y(f6#KhRT_rGJ;0v|yOX zpU-ve@FxI51|jzNAb*Q(b2d)!o?QZtLJ2wYXRLE4odyAxCGme#Nk1rRb$~%5jY_7* zI(AD68m0vpIN%^&0H~O=QA3r3k0I;AT_>=Ox>9^N_=Hv{ZAz3BP~ciUg|MD2s?{lJps z8eG%=oou4dC4QGKZWjr>mBzSPx=un+{-SwzEBjE>^}3O=8(-H0326$fE6eLDyW)+D zJZbp0sBzcy!bnJIb~`I=zw2ZY(-v1C9`7rCJ+g`=9C9{$m-w2yys~h$b*9WuS3_Uq-C#o?^^4B!5%2;pm- zMk>h%zGhqgJ}2;%h|`AReP* zys{PGJvkStF$fFlVTqHt=*dFV@7fjQ{tq2=d!iFit197@zuzSrA^A`N{5ON#HA@Eq zuL*^2sZIs=H6RES?CyYt>f=RN!_%M=VY%0d*t4A9yNZQxM0kIn5}8#DQ0OoH@2!?#~IWn$H(cK+tSd zo@hV$10=<=j_a4=7S)RwPMZ=0TllPG*?PdsC<51H3uFopX6t2(_^lsF=K@EzpIa8V z+!_ND)_7)C=ZfXfD%7cpQ%yB_@|xnLY^?$+62;mg!mVl;_0SOB&wX_QjcMhC zSEA9^CB}tz6Iw?{mqDKL`A(-RXu#cMS6tMQtrxECOPoZldTZ~LWt{U-_38NRCIY;c zI+STHo>FLd3L~>PyhyI8Mx9w>4}xkT%9u2(mrPlsG}kyT=vj-vY}`txM0?1TNn+?* zlW5g)o()I)%??nJqtezTl|X37`h71iP?>}@mugPE*Ho)4=F_=!2Mq1e8t48jm)@m0 zg&2|yygJhn=dqx(irGLoIJpN$f@S8IuWUSe;y4pRs=O!{CBnT=vgw7(QcT9?w_aBm zcNM^vu%3YkFs|+eNx?Cz0 zj#NOpv8p>mER+N?Sd$2y>9YVxjzqQ%>)Ntwz|P6L9qLuu&WSp+{$Nd-a^*U#qmZ## z%x5TK(M&dZ)^X}di)8VIja*wXcW^}g5#dmZ?1bi&!+6_# z8(Z!J+D9#9Ue~E$!qT$ai>6Z_R0eX~?p%dQBh6oh#A?W_OqXlt)qh(p;ho+_geEnc zX9{|0WR;&#l`c09e|1K7*)ZJ&$1=3WXmqJaR2 zs<4?>kfW4TmyCphk`tKFGm^z1W zJz`;XWwAKEX?N}tua_#)1ZW(nE@rQIvN|-8m>BS$d#}Vdt9s{Z(uMB2+VQPAGj_CY z*|?g0s(3B*J75=VLjU_bKy!YJ#m&~OSbJJ{XUbvuH{F9~l%jK~IAqcwgh_Es$%6lS zszL|eg~?9U117he9h*F-1U=-q=&HTs*x_`bnGlC8cM(VETqa>Dv78^p9-KxQx&USy zlEB5&aXgWzA2Nh`eE?LhOn)AX)go=~muh?}v#x2P96Nq2GbuJXu}p#Oq>|3E$zm%~ z=g62ay?mxDhPhJA#B6b=7L1ybM--RxgfpH_(zqIh6*xORM|6p2VxfXn8#-k1*BPz? z9a_9MA*p>bA2w^b;<22loit~7b(a?7aMh9osi4Lqqfb|$>Ap7Yiaj~qwX=+xyx1UB zQB9En9p-*>=|OE|uR=AOU==os!JwKo+qz4^q8ukQ(cogg^-(pQ$)8_1SOtlcYKE%B z3$7U|9<>|xZwcg<)Jyi;#t-O9#T2NsSUd{kWv)#rtyG2gD_lB>s(yT^U92=T=7I>=mSDXo>fZ;^_>}sQ=?Z9JCiK6oMI(d&1jnyD?td@M+Vi03#wAenf_sY5LVo#`2^*^ zIdH#hQjw3$RhiJW7*Q%p7(ru6lfEr!_ToGpGqT0(BvFIDH7h6C7dTt}Rqs0kNyN^| z{acf5R<^QhL&aoE1m_WUCQN3L1Da!>l}swnNu#OE zh74s?q{R5^COQ?u}V$tvXpF#EPVYn^@KyiJ#IpmSe<;bx?MoR`4Ufynog!$J;g_|5^Y!{#ZdtExe*s%>h0;`l`ZXp1SgV2 zqa2Y`QnzSEImQcSkBHf1`9nRxx%&21Sp_ava#2sHaN~+>$W&Im=dQW7mW(1XXB(xC z+Y?$cGStVkrkxr9G)R9RK+W+wSmlG4Y^VBWR-9WD_M!r%xl}~*x+qyEz>y=7mMHUg zmSJ=X=24BKEpcYeVk4D2M`#sJhGT^xrP@?QBrmRXu}@&BGEq&9YH7+ndp8Jy#4xup z6qHs&Cxg64mT6iOrj)4^6in=hP(I|gb|jcX)&P(Kw8B~=VpZ`MW3B_#IBJv%^ImC~ zvnY}h35I-El>+%WOUh&L!lAj>d4(-=f@%dL+m?t%X+G5|W%f8JZ$+lC(u$T(hNeI1 zlL`ACC82}(khI4VIRTP`BAMIfg{nrUSnj@+$mqJmLi295u~u~7e1T<#Ke!P!S=oRJ z@L7B*hx%QDP$^2CLf}O8h|zX&uli!uKWg9XNTq2?-=Ionha>Bh%947~LZnxvsawDG zd8mhcjXDz7v_9_KIxZVdfCaWXJ({g~wWd7z#&CV$NV$W^U(6UUoD^IFxe!ID!W^qM zA=~cp4fi4v-5RXOV&Dq)AOL#N!y;VdIF>m1DdBzeC?*s*E>|ujp^WHQ$p&!WSgz|yG->`DiS5(~%6FRzxL^Ls?&r#R7y%bMRXNq7tL>qz#U4m3yIg@Ep!Ycfw-j ztD$PzL@2l{mwD~2r%r}^idNuGX(Ntib!eqMl3rsLJ)^{ko^u#)rTUg03Z>R$DO@U+ zi|PvMIZy3=cWx+iIBT{8SC-vDWqsLS2UWJDlPcxeoax_8Ee(jlfe7X`DT&f<1soEI zHOmO~Ic;c;Y);y8nrV^^V{tHxag)8W1Sb9jf4pDPPr8^vc*Y4eYGG!lg#8;WcexRN zsRWY)#T1jW{c9(E1mgasb!&r%c6@%^{9uxhObiJy{M;;#U);7zBdE>xZEkd6SkmjD zt<~oHCzdpPSy2*)3dEy2MGs#bA_R$kh;Po7en(D|!JESFHlnbSFyqbt@HO?qyz{>k zzo*`v9DL^1U&D*Nz!x_+3UWrn$;J$?o5qIcKmH0_kp*-4NZ4@L{{{zrFFj7z!@bNg zW+w_DELJcYVVt2oi>$DTB!m<-e(m%i%OuWnS}fo`Q_G~CoONE?BF=I)B2Asd;iOWX z7ATv*y>>qp%cQ-C4B%dk=df@sRf5LWoJTN^~YXG#+3lwhMrNH3yOYdFmCLqqfT+#RMI9lifgsKLVX5 zR}-m6{JcU(!h@tPanJ!O!I06K{_0URST$64hSMK(u_S{WiKRM#g9opXX}wgkvd8_l zb;3C28gzzAiz1ZPKpOGAsc@+}mB(2rO#xIMn8s;dXmrbuiW+8v$z_st@~@M+`N%74Q{Z1#oFsu znTkAXKWcP)V z$K!cFSe}4B`XJ5_9ky|I{L;_w@A6DVzIN9OEbAWTpWa9i)D9b3#zyFi2+I_t@!1&T zaVI(2&Cwls$ZVv0W@bL6^){8RrkiTUbew{-gKx1)!_1mv?Cw>ZaKk*pzqqSL#lFR> ze^`}_^AMX(!ywP{S0@41QEe2~5hG7O-pjGEpnh}enX+haXy|I$b8**A1^-!sWM`?2 z?UMH`!+_!QEjhGD2&p|d(J-1REKob5**zaq^U9MyW~tUVgFB0F0PuyjcJxlT#K%Db z3CHVZdJjd^6_gx8n@301AKTc}9v37lVNRJpUOgKeAg}x!Z5{I5j{{FNPVacA46p$j zg)_UOzyS<$Gg|5?!or&61&M_T+p9RZe%(zb!I{ogv`>q|APvZ1Mt&EsFagPm~T;c__dmmg+`C1Nq zsExoqaZm1#*1Q0saBLk&7V{9I9`?4qU-#ZlZfHgxR`8QY9`d|ir1w3_-1!Kb?a?l9IS;zn2Y33J`q%S{(dk=bTd!zyxxX+v3%j*@zZ^XZT;!q z+tRm-z+7+hHBIQm`BsWjrN4<#WeN*dRj1U97!1mHHBVTl9=i;NiByFZ>A;N;R2oZw zan)3&G2LkKnK~#k3#OBSDZ?ZHh;4R8s##5px*Y$ZM>3&SQ2`k@353dNLR&yOCcJXR zzO$}QFV0C0jjGg|4jsrY+k#bH_QV@V+|WXt4X3+^@`%E(3s^Mk#tJ^f@={)UOpdw} z$!%=A?51`5!<0#oIu)d4zfxWtY)Rx@y|i!irdlj5;!4Kf`1}{exzv!G5NP5?FDbA+ zGS`%eqrCuJTp0Cril94v1kmL~G-d0ete%w>C(hgvoMJoNfEeL#_y>?}%yBUe=}@O{ zZFHtdKen4avZcHNY7&<;=)SW#Y zF%*#jS!G8+KU#vcb{um1J{Jv{j)n+wCh~$pm*aGB=jjX)ov_A;M)tF51>7=w=&ZP; z7I`ia4Vyd)~|&fqr9oX%Y0`BHp4jfoj>(AkGSZ?{cNo9uoi&Rqa%WB%Gw90Nj!*Udgt6 z`O21CQfoM}1yGT4IV(nqoc_tCR)E{guvCY$p3MTH>7YqsHM3%{lOUNdNsbgmPd$mi_<-YM>(5V_&NDKaC1rK-v0VH zfG;tqg475V7A0>dWN?b;{k94o4nJ@q9w}#V9~LF|tIdK((iwW!!4#5kiaub(%Hssp8jrtnq}ekFe@Wj{CMXjpz7IN9TCpJ7 ziGA1QkqgojE)?%C$&uC5EqwP*dJ06&C5o4|-&Rk}>NbJm{cj}_gHWqR@70Edu zdlkH4TzKKPB|(ykDL$$cMyPZ{FG_dJgicDw#@|d8fbedxVf^iX9=Ad<<7Os8Fd7vm zZV!Pz=FSr_X$qXttQFg1c^%tBU7m5}FTbY(JP~fFzLlSH{e*hL$gG_S*#oz6Rbi?-+E9QNY z8nLW|YQt2(1rwTK4gv}mx9xAJzs7FKN*ZI;f<73I)l3?ZfUO+_!nCZvY-O=)CqU;J z0k+mr!f4byUO1OU)s!&!d$5}5Pc~DQMa-yiv1EMvfWHMKbLEPaMBuDN45_6UD>iRz z1}nL8l6$?lViv8xcdQW)VFDqU%w9um0K&dp#{CWmrD;yqoIoTJHZMm-DnIJ!I^!^& zL<{azEt!axodHPj<3JXfwcsL3qt+(28oXj;{Ap8BE-iF8U#ZDtF^@l@HIc15Q8KvQ zzz@>yS;OHTxWb1oisSYqrpIzNN~*#G3>ZSJ11K!3qu%dYhXb$>3J?=B>XrEmA&!g* zY)jdRqXO3Fbey{llG0GbN);4QuM+DetU_ZING}{$&>%}*uGW0uhjrTsEpKgYlNhi2@|A?D1Z`^n6lxvn zKk%tqx-dr$2dt90{_$AsX#cpl^0Cw%DbXIq2l?MP%=;zCDs`BOHq`7~e2CdEh#UZR z#+-;{4^D)P8Pv{L$h3KTS*_|ra_9hCd^+_=>~-Ts6tTv0^lYpt9kQR=yP@s!)p=>%g&%!$-W zp@hb_Xu0_Xx&xg|H4EneAf-EZkXC3V>Pkm?o?HtK zas7;buki@)(#E6c;iMVrRfmLd>QEPKkGZ&2bFMFAki>Av9EVVNnt)^EIUtq!ENC&M zUnoph>8f?~92J%H*bz9a&OOIe!5c_Ct7rGRKE?4iDe+wAOB$1Rnq4-?u!1hjn6224 zMDht#&FXbY38CE~VA9M|3z&k1o%&duA#0<2t95(TCt0g0Hh>X6!D zN}ec^v-*JwjYJ4_Ik8n@Vcsul=6`YE3GP8jy0(bq!9I!NNJP+1^#d4Jsg^N>u(r2M zuoI_T)V$9lIjE~cD7(hLkm%q%*pUJoqX!UA3JzZov6?59_Xj$U8as24v`7-~+I?)} zdIQ>F1G-zzXSiUO+_`g=&(!mnCBdfY<=f93mbHjtRgiE?X3DCzojZ09&ctdCg+dZn zHt&?SaRS-}L;w2-**_jx_qH1y_29@Kvq(|BsZ`)s7_O4;@?AEZ1% zI<79i(DDZQ6GIQ}@u<)ecz>rPr%Bh^0M#N8SJ=%BRGB{hX>!z|7 z;#9++UO!~?K%uK#IMq1v#XqPhR$v!dBw2GDtD@CJkLFezAJ~wa?MuIPR;Vr=eTJ$F zg{&?GljTKw(c(ihLkOFTYOTN|uN{=O+oS9Dbv%&UEFXAA$%&sc=H#-OTSPNynxmMo z82Eb*IHTGU0rwOF!|<5`FT$3J#^`hZ&iBNF3gw4Br4%wNi1BP1(JzG)ixe7Yo<)I3 zk}N9tLzgbX5t;EP34CQvGaHOMA#`2|VjUKelR1*ywCtBs=wm7b={LizXGrgBDSBq2QHJ$~Z6Ofvh4|kZ75nPAOodj?}6J z+`*bjGpEXOMI8&5*x*T07zHfKjJa&!El_e-0(d6E8|#z~R5+Eb=kd{9y38AfwTj$p zj_RAa^(_W%Ap?VI;3M#fV0k1=e&?Z*8Ozsl)S)n{Y|Z z`zCXuTJUtb>nX09k9dQ9{=NIL+QpZ`QdxM(LgI5gD2|oTrZ~FN9=ywJ^JGfjQX6p? z7Ftb+bZrtH=at~-bP)2#_HaeQOMzQwPi#(XW^i>WRAE|Z4#jZc>nG9o=_E+yE6gLQ zYuUyZHL85YwVH_)xM52c!VR^NVqCCDHmnP{XaY6PQ`xTm9pM+On#y);1m6Wx3BGONv2EM7ZQJ^d&e*nXTW36H&ftt~+qP}<&Hdjm?|tv)C6(;dPIdO~ z?sQeBYVEZw)Z(+K*CGO;t;Lv=3VRopG9IyhE$y<&WJ`A{+Y|qCMjm_SK0eX=P&Zcc z<6cjPOfjtWO%zQX6c}_9`X!8CUFSf7LvQ^Td(6GPJaf6l=L}}<@WbZcqHr_~#SF9L zgJW;WP!+r+xBh^Ge|6=2N_(E^K*HY)R!`mj1r97dWfRfu%Rg8Q0Tl`5U3y~jzsnQI z$HliQmDO2qO=W(abFkr=6gnF$9sFszTQh9Vrhs{D7{^^)uNTmavqn=-4M3b_YIQz|TSuCgzlm+C^F6{Ee$LITah zI>scs8WvWw;^^+-+Fv3IYwg$!$uELRR_fW1ZYLZihvQM~tC}uX;nHtHhj(=~Se$HQ z!HO#sVM=_id^l{F>bI!COSODCJ1j)3EVCogJ%)%^o|Ls03e2^_oG;37`7kwaQyE7j zD9Mr~Z=w}Z^(wX6u^dhU3??vb$A`%UB9OSfa|%Jr&e zn^Bm`;M>Xno^x<=JA{~%z_#m6==n4I+5p+!<2_rzah~(0%jFS~aC&tnq)EW6c|8$ysm=oNY#M8>ll7(u+%37u~zF_Yd^%g96U?bm>77#itZPCk4j> znPY~;ra*i}D7qbfA=wY{>>}Kr=L`PS@nStH=ZE_>!|617$#s^A%d9` zWxwTO{38U5Qo@+1juDnckGdaI(wJ_vi%v1(UmYPSzqPW@?X`~)#2Up$W{{%y0sxC6 zbnofm^4~Gv)x?&@20$I}&e{9vX@QvI!8$y&%6@b|w!%TZ&SClKmRY+|j)oqVF&Z|X zByp8`oXtIX`ybyNyGC=3udD#cfiL~FxaW6F1E0-vjTB-9+^-{2_SrEj79No&T^YfO zrsp3H-XjaxIDTiR-BIQhY>OklyS+mTZ$&@-`;m0|?kC<0xCV_=yRoglDq93V`QOWK z>P9uiCDTARh1hKpI{i@C>()oOflt|fwZ2G^;Po}6FX|hNFH$H!xL$vdUcq3XtsT0L zGNcc%pl`UKZ@i#ykRX5H-h9y;fXEF%_y!<46D;JlnWS`q{n=;9Tl<_^&G;2hTq{Jx z9|{fGJB6niG;NEJ=a7k;0sO5G^`#G0ye@85?EA^p5!SSUWaZ#9O6Tm3G^z#^R^gg2 z=u*Fh7!VAs_HG|1P0@BAwa99p-#zR{E2stVcWG=k_Vu`H@VEye_TjZglPp z%SP3wU*-ETZ3;T8^Ep3OKmK+)1U(FWPQ|cZhc6pk?GRh(lqdFaQez?>~<2@ z13o#mKk8QYX@BX>ta_fd`P>-{FWYe@+N;p?rqSaQe;7&dp!wK+NU?hS58S+Ts`}?E z>`};jg9<`4H^xTy+-F512#Aa;X z7g@Z(D7?)O{R#r#Pp$KTW9hCJRW1x_MNjFw_J$qx-3R9GyJyk=a1$brXn zVw7#ndmofYS`y4wzaPWm2lcH@ULB!#@Fq?6Kg@_2`n{@*9&+o*W3tr$G*nKm!xv)0 zEQ39}7uL*aMeu|NqWGRLkw?`};n-Cu*N3kmrJ-7q7k}t5D-cy zZ~XWM-fYxJ70;o)+iaRrexL#L$XPA4+f`N{@NqDMpn0W35xCN+i`b(i8lY)*4lO;| zI0AD=uwi@<8dd!?I<{Hv#}#Rl862e&m}nqdGE=&uB@O~cA}40GLKsn7UJCNzB9Uml zDrYF-4tD9wl}px-MzV4d7;~z8{?+IVuXjHLO(SmtYfv1Qe`WnEGAkK7dd%y ziBfIuT$_}jwAr^5Qth~-5onMrTkFY_nT5iwv(%s;wfimB&JXlLCHMl#^lg1<=grgy z?SA(i>c9Q%HF%Iz?}a(Ehiz%Y-+35WL5=2hu7HU;osHk+o$B;2d*0v5fAp{XKDrYA z5Iq0(yUzb9i_+tv`7YW*e7CoI`a*uWdz}0Ng!jI6Z@#16JU0VBy=Mo98}5PwErWZ6 z=DTb;n0wyb?A_$Toyr{nJFAUC;N*2|@P}KaMbL!WpeBot$YwtYYT-7<=M9`()yF1l zZ~Xu+FCkX8%-oJpAKQ>F)LQVtj8WRvaPe~3Kx^6Qg-U&U7I&g{spoRBvEFu==Qt^x z$=E9zn{*50n@UE)@o7Sz=NEQKS;C6Eg^Y&to=RGg^C(uf><4 zM_1bGd!9G7cpVCmR(HVx3I!>ujeScr)T{_)0()x2Xs_wuWCHp-$T^f-plKU;xCW^m zt>?WL@zk`8FN^ncYsbN2jpOH&^O}SCa$>rZTf`y(8zDR0>Trao8<{e{Ag2@M7FT*r z7*reoqJ8(Y=kS^%*#E6Q!`9(|*1g?Me(92oJ_mg6EqA^2?j5~HXZyQc{WJbNn+T73 z9Iq!72C6M-jVEg1eZ*4D(eobvcq(kU+tW0^CTNf5*WvfJ|Kt9Z)#CRltf|*M3-FcK z^S!)sT&*9l20VY{9p_clbdZ1u$a}WAQ><#1iv6wmhs7cf8%}{L|uL`K-4? z6kD=&S6bfT0wAZU5mf2&AUbjY+aTF$r`|FqPy`(gpE)MAbeW*WNFl_}H_-3kg{;FS zz~sLVKHlbE-|icv{|KgEf7fmLN)z31dN|JA>!|0v1|xrYySt|M_eQwM1UQ2d(J%_A z2`!XYBuaeJq4%YUe|1#)O0XZcObmTjBtb82-4@ibv|ga0>(Sk{VVs6z138#B+JzlG(yQ{8f8KcL>2^k0kA!VR@!lU*nF$98kZmGCNhJ~{<_x24r z{f34=|J*nxztkaD+XQvEg%sf&Z=|7xf8h1))8X~*%N?}l1r2LIkmY4#-*92%aQEyd zByp>Wx^u-RzgO-$+N>!Wd%swZ?_ZqHCOUern>X@{9Snu%4?H0y;u89ytcxBbQ{^1K znH`*>O(okRB44&NZn3TrkT~u4Q?3v+y@qrS4$G73LEW+0j3^f&WB(GpFtHiQz-5Yn zvm6=pZQ0U0Z<{CxM`B`%r4m=-p;KnQnR4qwVzXqcfRNx!t`1v`(8gz0?6Fe|Z4sl! zo5Shcl?ta-o~4sGwS7f0UOI%H8rZ-qD+v8Y2>K=nB3`8B3j$JUV;Qr0t)QlDG!98g z5_?E8B&Z%H?Yr}b4s;UPIrA;~Q`12~Q^RjaSbHtUZwO2c zpRb!f&rWGZtj`L2nx!qZJRxFb28JiR+`;`_lUDkHDZDc1GY4^8HG3N9|xYwEip6um-k%{xctBI!{i~Mb%09MuUT?jM(YhK-J zS>oe%Uc0Z)MQI~yvtn)3+mYYKc4B@aKk;{8oKJhb17zYl&H^CiP|%IW;hS$;iptcu zoc8^nF7NhC&-%ahDmFZ~G=HgNB+r>{M z$)`OF7)1i=bmx|GtK;T^(ZV#&mjQ;hqe6K@3 z*A{hHZ$CLR!!+M^hlUBEJugnaNWx3}7iB^xlVs$(JU!d%v=q7ya`HBBS(#Ns@-YlP z9zWSA>xqMvzG1AKT3ttl)GFS-yyD5K#dq6NyCQL3ALb>@swL70>iypqCVKcK)G)9( zvjKwnzl(;PIFC&Euw9u5<1*>nO4aviv?p$p3aYtyw)9Glr2l9E#<0b6Mmu!&4cC6< zFD{j~dl1FUtIM}mTp{6MG|nM&9>b}hL+dd?WF2Y=sg|+`^DjbWMOY-t{*xM=#^zUN z+}Wg&R?8n*@eHd`x3#s8demTB1Brr&@vK@7J5tW3{9Pt5?)- zlSt?0-(p>ibZnxX?%zS3pE9K&m`oRn9Vu}1uF6MKpmjWpc9rsqb30f4Dhrmwze6lW zt}VJI&;l{A2DbVRybxCq-GyBNil;#Szz9#yZRZXpd?;*-~>!fMK8 zdJvU88msGgmZ|fOu7}R6;i2P z2O?5m0^mGPt=(jvw$X7!L+&q;Q%amL1KSz@W5s+9FJeF zL?L)7lYWX50OM}lz|}4wY`#bZ>V-gMVMRs3@UV zw8`4%vkCq%nO0P8d?6JYBBeUenO5Yce^L`FG-Tu+o%iJ)`wx>l7svPRV4ocwUrXNQ z9bexzGsCUTuc-$cF|X4H+50zNiJg7y@f`+_s|8<@Jgyq^x8RKk zGz}nQ?jdD1-(~O{CdW43lRP9Id&u*cpEd0wXgQx;f&BluK+Ajr0#y9rcOr=M`C(se zfCMyX6!byl?m5-BTb`a85j6ll{*Qx#dju<6uN@xf*FPA1M&wU*6+QBIndtI{05QeD z6`+k=$Wi)yoOU^<{7%4W?gmO~jev)|8V(_~YxuwCePIA*DuuG=d1rEAlFu|Xjcy^LiR-K_*ayY6 z!Q`iQXT;^HP4C0?A@7<9YsL-4@1D1wryh!~@^0LKWraMi;t=~Z={pdm7*Gb~R~D*$ z(Zf4kReK{r{cV)x#%s0r^U|-Zie?ZPn=c0=J8XlYrkIUfzm8%ce&4%aj;E=KM_N}^ zdZ9^C9pxRjQkGm=RqWaYLFGR1O~4E^r_VTrbMRTd@Eq?rPR}pNxUA~AjWhZqgu z9+rdmyl$@cieS^Px@Wvc$qYE288?s^H%=z=yS_P>k)o|nI!s@!G1 z=jRzC%h<1XKIgr5P=tf&2m}$)m5vkJ$Ky58tkb>Iw_i@tJ3S6#w_xpo^z=GipI`dx z89yJ;>tA42y|F)ZdTKn<+A34@H(T#44(C48-)U>2ZZJU^9K|0U4llxo&38tq#)lZASqQbxV6 z=97f0p~?mzNcW@GJ}b1Er#wH~I-gt_8OA-T4`W z#c|eFWeHf%to~(5W}Oj&vO&!XI$@MawYGVv884IJ1vqyUdR4vTF$-O870ny&U=i|* z_$6K*e~OxPkAsf{$*^xpG)*wY#w6~-qtY8v_OOxqt(jm%wlIvKiI*g8(7=OGi~hs? zHGz%LR8ftjIIX`iRaVa}4{GhZ6n`Ze1;$<~MV2Dp`=MjAr{ZgaZ`O(qpah}*81G_p zo*PMG8RL>mL^7$eYHQNqjA~)y=0W`jZrPC6xBidBx?1c} zJDGc-u9@{wt1&scFou4)=SBMOW;W6XBMi4S=THgsH!awAw#Zb;r`*5R@i=%icS4X_ z*Ei@tt(rsuB;&vUGU|ikd*&n!yL|2-QozeL@e4N?BlQ#3Z{I6(OFhTuTZ_;yMW`=E zC_ms{s%+Aa?I?YE0KA<*_$@K*`-XT`Ub(sICT4z52wV_-px1Isg(!CdY62)X@c%I= zkWOb6TnWL;y^4i9!_>FkJTQKMZFkFpM^G249i0MnbRhR*0?@R>R?^YepR%D7TE?iG z=aT=LANmBizc0ld^*<5T0LzcdE}iJP^c+SW2(VPBCbcU zovbwxmAb+E5-6`Re)rLPrU;#EFr_$A_5G3Os#$%Dq&ii$iEJb&TOuSoz!EJWgDedc7WZGfMjh$KUH) zjh{`nxsGFLsNotuql3~WWus1r4Pu((DoG`YVrOQX^s7nX)yd+Ubmzt@DCE81cPrt* z=lu|9D^Z?8pe~4@Z)z8_SZ|O@;Neyhgl|k}-fMVtpg~5Qd8E1|)GBV-vZ_R*;ff2p zyU5ebh~3xEzT2+~HA{mARgG6p7AJ;yltU%cD5&8sOm}J*fj_#3na9^UHA_UAo#mmy z=II}*OjDYcubr4Nr80JGk*IT|6m7bz@YMyV@^zt88Hh?pl%~V(#^()cty)IMjLMV> zgd&;wPn34o?b%DBpJP54VA?~Q-rhBP(n`~-tKN^|)c2EdPFstUtiEmBdD`-GnTtqQ z&IdlGQ4S&ST{u=FC#SJPDs*~e^^O}y_q-^$xY)X@;*g@+{eSw6tJUbLQw{lJRxNdK zp-RsytOnBVR+DC^L$1hInt^Qr875Ql+EqrhyEj@EI<{1rR=@Q~)TY=k&qD?M}arGRAMdg^~Q@hPscVx z**+P{ON9p@&{ez%^$V)K`ji7r^d9GjwpIf-+o%!@W<|&p2x#H6Hww80s;fv-RtVS$ z^E4d9{J@uTh8IK`Xk!beP2TH#U3^67gn2g*1nH6(0z5gX0O$$P&hRnYjuQbE5hy9$ zO%3nb;tO5jet9}GD;=UBxRZwg2)-*N41y})|JOnw=EI#8IxKVr+&3=pri}zb%CX<}yWWz=mD!E};!67}ex?3qM~39CX|Ww43=@3Xxb&`$I<@+!xg# zj|MCmkfMxQ14xtXwgIFl2kpXSpdti{BOQt!FNIF~3GQ^;Yt*Msr+E*dd@z!S4K|a~ z!Y~x*%$?jf_Vh5GISJ672SzyBY?KI|7r$FsS*A$@4;*{=39#;{JgdY;isbks6C_Vf zjX!!6{iX+r;~^(|&7nP0;D*SiqL`={xspBhk+2?e1aB*S;dm%_mF7UhQIOaA3*xZK zF-zW=PXxfkyXhe=C9cE~9uQk^<<{iQRm^;VX-nt{><8-AT+4#big`49E~X@UbpH)G z`gftxmKB!gj&Mio*blO4GK+m+l)16Xn4@T{3B%Th-84G|_h!An8Zu9yM7KtjyG=&S zJAxiHoSu}@qK`uKue9a5{m}Ep9)@Y)_SHp)HE?$1^pi$Y%PtmLiS4?611|( zY*xDwGN>dptZi#SYvZb!v@*VM3z#}0rkSuoP952ab-dLw05}# z&z^zHI4h~qOktN<)T`BaZ0X;fj*x8IF?%`x@vH;rX#*n|Q#`pf%fjCWo<=#!;0D*K zyy6b_cK=SZG3-xVZSxR&+Y>O(pj-)RehlHxMf6Sqg>Impcg~&=;mCt+U{xA0A`b}y zFi}k`IOLGuAu2>#G&V{H*zE`zVzorCV8}owj(>Mpf%pAEp9p)tU{}6@KzUnT!?b(x z@v#nvL8qO0J_C0$D?y#CRW6OBZ}=d3?y-QTO@R2~fqy!^_p9ch)6?UzPdPpHRCkDA zC>R;EW^QYJ8@F}yT}#FJxeuT|P$JNZan2do#a{a>`$ptKB{Bgr-;rHX49erI0A=(ynj*qcS34A2kaO#wpX~ko}Y)qhk;D?7F-V02K4=b z9f3l%qPX_4dTrVTKU!l*Oq0`A%P&n3(!A7zE z`pWi!isVjd)ZseT#Q&Wt>ty2rRAnB|H`ehM{FV6VFzMp&>OKQb={B1{D20_<037gtRZ}G&LV^-9ecg}u_*;8<`Wk3ln9h8W;`iMwxkvZs=o>-Ks0D%Y}x*X zTzOU(;i3+7PyyL32J4fC4oOCVp=?m(igji=Wa0Jubsf5#8Outzr<>lqBrw-t_IPn& zfKte)<}RSQX<5Ow>s(VG#q$(R*rbN3wHUh0deNS51PIzZnCtu8N@vYP&zTx1G0RkR zOZ#n@p{3Y(RM8?t|UffaUL19$H@{sTh z552~22SM>T&`s5?i=5A?dwl$-RSbdtk21RiPi;Spo5^uL)kL|CW-cea+PA!BV*o>; zj_JtB7PDiFzboUMj>~pk>Ea|ZNTOXJ7H`jhvzS|Pywq_y`t{&g{5HmQ9S!&M@aH7G zLff9mX`9jbd(I53;LP{B^Y3soC*ooqKRJj4NNH;u4G`f^UC%#>UQ$=r1H7;R zicVMCSF8FA88heCBl=hOc(KP}pv*J$Y4I0-=LS|sE2cGI4=boXfxOCu7uTkewkeP~ zI42Az`13C|*T`HDBomooUvJCZXxW+-ot*1&UGIQO5ei=X?M{xFR?3CQ107ZAblK;F z+Dg}m{cO@y)lO$VY$k@?D6Dh`aXer5hCbGSdtKCnHPlP&ZRagy)H!#eH`y^W@l`$$ zd<>y1_>)l}%Oyv283S4;t%B^~gR{uoh-{Wj5b$d8ua}5rGFNZL&*<#(5hG?di*WEZ zc4~Enh#c{c4#t5<(UFtnuOp6`=}nPOzC>wuTjwP>JM@~l6F~89STx%oafJpA_(czg zMcbJryr-DWy)uh2tR)?stvXA39ciS&11GkYJ-U_L!yOUkqlp)N_70@Rq)c$dDk~Q- zeRTUR^~b^rB^z>+K21ZR^)3m+*!922KD}o8^)+EK49UU(CmlrU{@yoGfs(dwkFe+z}BnNcNpA0646Zj-=+v!pC23CLy^s6#?oTBxV7Q-Ru6ZO8b? zkR`)L$`Q|Ycw*hcR}vf(@STOZh%M2QwE;qYqhOI!`X6}-cK`BtVDSF@dP3CM{MD9z zdl_qQm3A>tAmGT7gTbtE<}F!ua>>>3Kuyw3}PH3qEfmALt3eP!Rkt z_)0$TJ5FtP-zg-)K5|bSh#Oq(et7Wkdo6XgAk#*Ao9(W%m47E*CzdSRp8uxQy&dbX%B}l3W3s=UVCHDtpU6 z{Hnk^^-iqqehYl77k|;fvN1`wjE7e#?h<|0v~a@_6XZo^st=itGXgEMAc{;21-3J0 zv;9cMRgEQ^hOB&j6l4(@1XDPc6@^`fJLn7wxOS#Fc)@TE1#8*6ik7Y*9JSOl{9o71 zGBl{)?|xJ>xj<;1MkhhF{CS<7rc9<;7_3SK#4YJXXSL8ZY-h|d^17g8(@3(|0t1v< zK0`ux^F#-;PwNMR-(qUZs?Vd@Fa^jC^Rkot3hn(cp~lZBg!rC2|E@ME7E0bV(G=!At>oTzu-xWIh>-ARd8onMUsA$g8zK` zRsYwJ^q(P*mEX-%%s(AWEaJvQPHf^v@*(Yy^Y$_9K{;K|U}P0t&rsw!O&NaD;wGm& z0T*Y~~Wi?cZJE5v|_Q$m^on zD;?|i^tC#?vkz?;AD;LdGRp2!KGakc6aCpO(eJ72mHEX31gnPFVU2iG$r5TKnC;u} zf%(G+gu8}cD-=um^$=3O7U@Pi7PXbhzhm?gO;Cd*>edpaWtMotmii z>!nmaQeRx4-3x6V&fiV9_x2m~|CSKLYCB$#B>)0aR|EoL{(m!||A)2rzj)DExR}{E z|0j3vp&qP{?y`2bCZetzS$FPpzjQK%g30mOnWJm60Exb|bvDOk(qG$+a9UlNy`>rg zYTLb5-S)|LH037vwXh3*H=1=we9EziD7}Tq85h*sZtND$xbHkT|2q)Sy$7`>xCFsX# zv#q~AHtwRF$4@t4{Oo=WwxYY(p$j&*?QM)5WcjKbuQ|WWQBN-r7)92DsD56oSd_l7v-5pF8UjYOzg)lm zcKTy=Z_H}(eKg@dJzqs{xZqaHJf?4i)ZB9}>O{4hK*CJkY+$Xs+g``+u6q^jej2fP za8;>fd$t}qoKe*vmbirB;~RZyBqv8xEED%0sHnu}qCN<&^# z9n0BHL=>36{gNfG)qOY(O|Wdf3w9cE60r16^M7yQx+|bJ$rsFa)TwSrH_&#=_w9Yh zi{3gOS(pOw+Tk|GkC)|^q}>!a>t#7fsrwD(?#G$ie(0?Gvc;sok+ zMqqQVM%V$N1Em9R09^yB3cwIzEr6~?sKuy-sKu#;sYR&;sl}_scHlNdH6-0(>4E6+ z5`r}3Ex?@tUx8==sYRcGUICc_5*7m5fqVg?>48-OS_Z0y&;xHlsYR^?EdUh>Xa?E~ zz#@WS0zwwT3h3o=M>Ry;k?4UB*bF!aUIwlP-+*ZWzv1Zd>k;TN=+On{1oZ-T2fhN^ z0B*sb0p$W}fp>s%KuQ2112znE_6-2rvFm{{#9{)&0^5LXfn);Wg7W}!5Zr+x0+j;A z+7alX+Q|)Y2OuNx1Mh&maSo6KjtAO;_rTmS>Jbe9fPH}M90SJ#Zy|aR z?sNwHoSOp?g7JZSpxnp?AOht9@gcmh_tb#IfeL`Up!b9Wcf)0X`JmmJ_L5ad+q~{fwqA@sCN{B0FWQdJJta;AYC9H&^N~c_8@lw zf}kBxBfxb5-4Gw>h8Q>WJpd3hAb(&k*caX&+JI}2E>IyRmn(aqZHNxI7xNxL;4Z`$ z$)0DRZ=jw5->YljZ-^Jop2~pe0N4P3;2g*aU_Y=I#vVlAZ|E1q9z>vT1dmwIM517S zrWnTHWTpoO(BN8$oKV<*Iw?YEaEy~X=x`x@Gi7kTgF8=fh~{;Abjsj6C=iH-1&|Q! z=7vZb@%0v5){@XmrcL1X13QLrX_G&CCR3vXs3p_Us5@duV&DZT5F{psTwzzHL`o2n zrUe3FyJk%GVU8y5kw<;W*coBHJ1f5mTW<&;C`?TxN;U4$;KsT(xtp7B{Do=I` zT*Ht}Z)C%mjb(2~YN9m)>sNJd=Kv`7(AP%s(OV_;O#BefKM^2|O$}i=NWAd)MunEq zz%xa81*y)(6@%Kz`NYl?vqDsw}dIC5(ht$R3fj znS;Q>v_g&Qj2NTrNczQgdIZQI@;pFUb1lc%j>M=qqnRlYJ=7i`D9q+lDioQ4|Jg^B z{&tbcb1Z(Bh`T!dYZS3)mg76jQt=>%??;X$i)Rw?VWxw6>Js(qspN{cZ<(@U#a9v~ z7;e|loW6#L-6NrDFFxd8oSE6#JER2S{ku{%EuD?ZXl7gA8D*** zDHJ_KS6~+@Xz=Kz?`WV)skejS%gAhX(QiZhnQng9lbNSklW-5Ej@)L3dGB&80{3`= zY{$%>`Eoc5MmGIw581;Er{*((O_P+}RgudKcdhQS`49);02e`S#e7`@?>Z-W$^MTZ zV-=wBfFvM&Z4(n1)wM?Am*wf7eV*&(jTONV}=i?@Z9CRT%^=gtiD*K>6YwoSI1g_;KG&fxdehD|TMrbMZ914x$h zB5)FR3$Di!OZ9P4B`J@8>o4dql$V|&2+xgU z8N2a(dexfHj$KpfqX^EKmvc;VQ^!d_Ay}LS(eVSX44Y{*Bw4F=XJ>l@)#mv8BgiEX z3C~?z*XG+jQWLd^%*vxs*!CRxtp-50vFMo@TK!zTitqV|`EyP*8JdBMQbPpzCG(WkxVnf{o-g3%fFm z(FjIrB8H6x*sEVi{YbHH!i#XOjRbfMg5Cf9#-X{+E;l>r5bYFLJidQ>tX)=utHF30 zn&dp_ACi=P4E=PBMbzxU>o4c59(pOiF^H^?_`6aP*zz0FGrj_*S(4mtpy;1R59W?O zTHFos!Q`&J*-o_()sDJJV1c44$sZi(@J<21zy<$gjl^RuN(5womjj;^UzjRooERGEsw+ew?{hf5)M z)*>4T8?!mQS?-eNKWA%8%2KwGLKJF3(rpo zv1IXZg%?+s;y`A3M(O*TZI+2+yD4Iv^Y6?TL&!vb6aljIj9=OHn?$^g+@nG6KbuQT z*06St+@8ray*^NXzHT|PV?7Sdob_k@uSzG6P-N4_eTHNF7zX_h20zLU^t8p;?07GI zmU`p4{ut=jx7gFKaGZxVc4);4O017@u8)6qr6C4IQ)9`X?}UcCNTvFfo*N0UV@t5% zsN9&exp~XlnMj&g%I4od3K)4d1dj$I} z)?A&sXdOXcP_V-J&YJT^#p&+Vn;w|Z!n&yrO8*;EFtNQ)c(96HSh0JY9;${8UzDEa zXq@7ppJe!%>DMyt*SOc)NZRXYm)CObF9p`SjH{fsavQ1?YK6He@+=#b@S~tr;d`w z$>kn$KQ2|W5{x(3t=8%_jI!xeAEG5S2wLef1MKS$FAD4Ns`TjS>$W(rsA`%;5ZW5t zsqK6Bu8kFW{79}pA_*b>3Z=T==hLLKr zLP8A;69%x3a3E%*8>PS%Xu~=OOf^B4Ksl7qVvf|nx*iA2U9A3=gzTSSfrULhIwh{B z3Aj=%*K%yvy7dX1TY8@fP1Q48YjYZ9+v>7odm29{w^N>(t@D=ZltGm0I$eJGwEGw4 zZ7(-mRNPkh=C80+<*m7S=~gR`b6WAGSlame7>;o)^8NC5IvKE~Ojg`}oX~HT@b8+n zC!VZms`z&bKfiSfKiwf}qoH;FkbfqynQOgpAM5o`!PBZgWq&jgRk93Uu{(Hs32TBA zV*FfB5I=J(WBc!+-tP|D6xUFLGL`id-?6WQn}u~}*~Mu+Z?o^RCm%1x>e&%aoD%kD z`AyEn=ZU*y-qQEd_p*0IK(h1YM7x_=biRyEXp?k9lS{tiI6SQiV`2Nn7 zp>iKRyKYVh?_c$jv|;>1GQHbQkz+qPg(+jg^^WvA5}sPFa}Prz=a&61FkCF|q$?@J zn3B0Z*_Rx7onQylHK^e)qfGCa;<%-xA{U-|$%>6o4fsp7Rs4j4XzzTqmM4dXJW+>;4D%i*X zQ8zjPb5|Ws3Me!B*9VH#VD)Yb+WZ*d)m=(-Q^Q-mMK_<`w1p0EB(1#+ujZ_Wq^Ae< zN}g+wDC|};q@=u?GS+`9bWART*q1bq#u*AGlP4q^yM6f~hkTvVu*aRN=^!ozvA87^M(Ma)V95$wZlo#WQqZq=}`RnAFje zloLRV#yF*-nOqT2n58;M)k6=YiiMc|dsr3@w7-;-p_Dt5E8Tyb87$Z+WGkBiU_hq` zr8hx8F^|bBQXd#lvZS(!jW8n-f5g0hVqTFtB3Hnd9*O*cK_Qg$j1*)0iJ1jU%|g~p z1Y#`Z!vt=4lS)0E81jg;V*~`9@#8_~OR|?576}TWen~)!ae)jQfK(k4WdRe_0TT@h zsYoFi4J6*|O|cVZE1fPfj$p0_sKeAtO4=yMHiC2bW ziDZ1q#?l~6C}eVz@dr|4I0bCL#X=b>#L~S|2~3ZMWZX=}yi8;QO#l1c6tbVr6o;+> zxdngC5XIHwp>Rn7zpsd8h_^`vNI593Nv6pZNElMY^UFen zt5IuY^d%Xd2xg4{=p0WBj!(IJ^p-Oeq1-c6$ot99^Dn53sDF_m9vEh&aEjUowX<5K z1}Q@Gc!GXp?1L;ohJeqKF?EO{aJ9R!Qs znHWp|G&Jmc)`?aYLL}T<=pj-j3VR+h`!@aGIGgN|R}Ld7Y9nq;G0bGf=g3)qVJ(D7 zw&BoBZ`v4y}{XV>V>%I5PO63!&d zJCx#)dQ@a#Pa#ZXci442S@cvat&%IS+N0FUrKqQHL5*6CWAEeG%1}}cT%%VV550wyiNHz?d6E?s8s7;b*c*t9$|5?s3lp;1 zVpl*K+yf-2Z`aq)k2m@kW5KDHKZ&Q(Y|!rjO~p`@+`w;l*g|m7k^t^T$sUuuR^Sx!gA zz7)dwbOx!|=4Vl+<#Fuk_eC&a@+P;nT*7Ta2@FWZuwbVk!fg>mx-cInAcAc@@eDUb zupcLdVE-^Gj8+foa?1A0`VupKmgZt9qAmobCQNjCkR|@p0<|_wS5oMHjE?apm_r$+ zN-^<8&Vrqs=;&TN>m-FKUUt?-iGlK!7!;NC{1E{Y?$ne>okKE}ogeHhR>w}BO@Yt4 zBtbLzvfLkAdu~qI)#&Q_6sptuG4wTPL@FyFR4~DI{q|Z&xBK-JHL26;^ zZP6~5hIxe|SKvo{JnU8_^RFw}+O7dDJpaZY77i!eK8dTCb-DMAY%Hm=Io8K!GKlzM zW->Wwo+bULonzmU73oWP*CMB-R-)e8wuLh`YW!>JOMmWggJj~|Q zc8RX&q||k~vh{p!qVxT^g;ctvxF$#8D<4)a)_t0qW|CST(T-n>O@CcALmo@2Zc=OP z)xB)=BU{xB0@USkkP}lO`6n4Ym%2{0y?yA8SF@gN4pV?wb_~sO@VQpgbmvo@-H$gn zwK>bn9RByC&R9@&d}$}gxUllw;Oern+NR_wOKz>=of31Rtsbpup*UVkb-x_L5VSM} z%F#luZWaVwLvbz$Y|}Z@F)LNNvGZ?J)R9ZY0GfSFqjZ|zqb9o=r9ou zOQO{@6-VuxD;eQB3HuYHnjqc`jS8|_`@b0b#vspvXUkvPwr$&*v+`m7^02%ACsKm2f#gR?b`_FPV02&&BPJ2K>pR z8v|yvT{1l#`mu)f$?yy-=KWZ#8G<@!$;*I0#!Vu~mhz7@cdJLz( zp5razB$0wbsf=3c$TirdV~KKRYphcvm1n9QcNFX7lh~q-mOB2bQ+4{WHbb@p?@5;J zer3MSL#PUlhGf6^- z+CL5Re_BiXUvJtJoY3)D7oz!!K?7PWNg1!dvhJ_PRRoR!sq$}@JKn)m6GBZd#m73x zd|oKeXPWZa=lZq;n*7CiF=l)_IiuOH2BTGm!$%|WHkna5TluyM&Tv*eLd~Q|&ZnE{ z&aNlAx$6h!i8JHYkgGanQ)9xY+vnK3}1@cTof1!&G7McQ)^NGbWoM+&RE$SD;FMc7~wwOGY+v< z$Kl#rYJ9ZQT{-%!XjTd#oNBH;S6^)I8^`#q&SaYjzispC4G4;Pl3J3MijhA&a(S-P z-OS$`GOwlvYSXwYHrIt$)mmB1`)B0Vr}Au{6K@XZbr6MvWyLx;P%Q!b5i4PJbSi9Yr;cpTrB<+ zqq=UkWgGkNr`dq}YOncbD`sZZbZ6V?&X#|79^tMF>Uec;pp4RW!#+ZTBB?nqb0i0m z4s~icE!!Dc=OjS-;CQ+T@D`gK*uFJT7Ilwapb}TcY)`UQx77%HQVCJ6BOVt1SivgR zlx@lQr}LTJefpf)H&bXpQ^&s~zCqVaS?T6Gd+F9F#BU4Sw@@Ho?tz^2d zE?V1(G`|_CbaQC|^J2R7h1s7c3GtREXSIu|G(Y<4yx8>x`DPOB#^U@&)g2ezmM3LT zlbRDf&6@n=io>D($ES}!&r^6Wcus>kDo|}%|C+@4yU3ilTe#?>(Js~KHs!Ss_K2NW z*Gly*f36O{7j_;!Gq*lSd3?4!zPZ*V^oolW-z2j2sjSHj4+R%Vyb7)rOFg0u)Rx6 zJW_S%FeDh}9?8<^jWML98&8%OW#JOAJGJi@r|OJ1g<+ATO;pdT82KpCS!KR0MA3Lp zYaP4rVcci!4vw(xn%9B9Anu4xz$<RF9ukBo@Gjf z=QHs=o-pN%v){No{LshU?3KlaUrSh!NG8p(F>~=JVly@L zL&12TOU9Jgsd4*a9m)oe7SS3VM#J~CMH};QnjIQKApFEDugr8EuN=6^kdecYB+6Qh zn^0FK*B^rBV`}pgdaNMxgmu@&Lg4b&MKfIQNwdfCaDF>oHM6aGgE+lZrFq8`m$dd* zRhPI!)C{-+fufF-)KY+ozo5cD^9Fgd2ca(A%#JH2qTCDm$up`MxD~hi;_J1jJk)fE z$+n-cuwXy5AVgucIeA9b#ir&xvg`69>nN+t+^Thx(1Kufucb z+RSFzs1V;~*{S}-ZFsz2`}1M-^pm_LPBCuEhMkJMo%6ghz0$I1wMAJ4j^eUWWD3mI zNW}z##WRm>g_IPQ>NDr>TwJ}=x#>WHvNJ$43TRXKR(aV4hu%Dzmyi;SO2E43lzhM~|g|{_Ads$aTtA`uq>~HTHx4f8-xFj|v z4;rk4Ih}3iC%f31R}0*wZ{7^*;0j2hDtN$JE1Z}1CCc4OORw57rHLb6SSO79`>|#p z9YcUW;6G#L#&s=C0}TKGYYYG|{(oX73o8@je-D?}J`e^fsw+QTl4i`wGG5^c_(=)u zwKASmq{@<%L|_X`TgSJzG!9=O8lXW4p!fpBtGBgj z2EhGrz+!y{xk2<*@Ph&9ZqS`w7hQ^Kss51RUTdF5TTqFAEWZ%e{DIu>W5c~zEPI&?A3p1|{PNjwsqvmrNH5l<|S8JLI*i_u(UK`hA?4*I? z4nH?Q(x=yY;0Qg6wJn>gFgNdud%oxX=H~9wx|!Wo9jv4L&Pq#~-A!wme*k6(#sLt_ z<_c_zg+s&{K>)I>2zzwu+10sSAZ&G-(1?+nVwzon4^gb(`c&Rze0#coARuj*xNHzp zC^wp* z5z>LB59uEj1Qf^zz5uWWa;FZzCQ+dAk0U}0x(0CI6T}sS0>IT_?b!pO0Rf7*3_w@F zZQy$>0lWdM@NM{ZK-?f%K^SqbKwdyQpl85s*n5)BZ15S9UukpJJ>$5gbJ{W zS3y%?RcKU79CX)n>HxEV$-P``fa|$P0RH#%<02UzV^?TR_0Uv8#?&7!zAfaKLKm{OgKs&`C&*%yYFwq{+zhXk+dAI{B zfCS0roC;FA(fWX)tOJEvVn-4|miP-0FA3TGP$`Gppzhw_6#8u9Ab<=e*1!WG0fywO z`YOu^xPYiRnZSyc@T@8|80*TWr)THvrs26&`wNpmE)B-jtDc!7Hk;ni)c%EcrOy3?Af23lB~a* z+?hG#CDT@uH#V0y1XI{5G&rd$amB@CrS4^+IJ)p0z}h4Vj8j)TM4h1B`>b0IXIR06 zn_tYC)&m{E+Wpl*%0@y4Ycolffs`|^A%dtk?#Ka!z@`*HBVBUip#4ZZn{BvI2_(o) zzl7rq#*k=nP;?dI62@{xbmD%EA%SuIAryx^f=rB!Y{%V)c`1^IDWcMWKNqHlfWSqY zIo>mn@uHSN#O$VdYRJRmGu9IyM8CG$Fx@fjsd4FY>wT_s=zWfRPBuKscoNYZ`$fq` zRya~;yzh9p#>s`1$BmZfm3f=-is`IE?5&IZ6Gi$x1NQNp4E3f0?5Dv*^}CexDI|p7 z{enqzZnrctS|O>Qt-z*Dn@yj(ZwLHTGbP0 z^4qZ~E#7KWn-){1%9VEX7!32kch4u|wA50JDw<47>**URa|+K5N*x5Tp~fnBf*HBH zvtIGtVDpqG?E8}|&>20>*4#Q{E)oDtM@ndQdZ;a=Wrj<+GDd*pfbgXRP2F6B?ock3 z7OezAU7Y|Sn1kRz5L4d{Jb*h>RO(HRGJvh209A9&SFLQG?&L~0;VK?<>0*I(P_a>T zH5Yhd6Z)f1t)B_O4MC&Le7)^u1g%qD2E0fXtSHu#2&%1dFEA7#U0|Or^0L(7%OuH= zdYlo8pKBu!x&s@yOD^_2O-rA*`u0 zV3U|*ulp(>(3J0E}~DLqP{G8p9E8BsO#(vqtY+i%K*aiNy?i3LqV9 z@K1&HK6%b^_Ir?d&U2Q^BKpaRacp2#mJ$<JBc~g^cIY&Yfb}4L=a3wQ#O3HjeDU_6uvRAH9A=$MksvR?uRhY$y ztSo0S`H~Zhh&Er5WsaJn6Ii)|0(q1Im8z`O!jMBO2Mg(R#_`tNm8esW66Lg1 z#rQ8Os(}z1w4zKxG;%Flr3t7gJ7yNCFdH?+339SXmTA^ia5*-b>8KL34ViK2#bLRx zuqu4cbFO8q95)Tl!?vYNU6U|xq*7rJJhRs@7_=95LZ%lRY}uG6biWbkz-Cu!mM)|y z9cl+nqW(mb#ENpl=ZrHA!KJ*2$hVSli$|3vPv(G3Xai2w5z~l@Y}lH7=o$hP0vOo* zh_X!Bs$A$Qyhna?^ky2LE$c3;pR934BkCB;Wc<`rCmdE_Kjru}hFlrK66A=%tnhko zxa6cQD#@FWk6_e+Pj_;rwJRPJaO02|jKk>&%zL7I-<%YJBl*wM1k>@^1k-aa)Ad)3 z@dg5l$p-(1e}at-xP3vXJCRQfo<%+t6rrEf9mu!xmxVZ}x*yTqVaZXj_X`1|L+$6{ zANZ0wirrR&BE=lGbFTK~aFY@ve)<~PyGoAMA5f77lO`qW7|v?Xa*xr0vt~tzmsSwZ zeA&V(;vZ>}H=9pAcOyO6%g#fGNL-c}N~s{^B`EOj_H^rr*@T@%R?$y4Ui!6Ul?2V# z%NdK(t2VM`4G3;hhYakxTFzAUS#3sluZ}?}439m`zgo7p$LP7}CRunbFljKP{fZPt zIo{Jy`E0lHdWdfqzTIiZr_f{<8pID)CU~&_9=(d{lJ7G9m0@o;h<{~^-ImrGq9of>`zmXkf<`M5*b=5{ zn_9GW+jJ27!Ate8My>TJDx+kbSZ5SFVu>)td1x3rqG>5M+j>fefnQn6A;ZMxQ)a@F ziDs!AMoi_|)8s57)Rb8YluIdE>%Ydb9a(IdoU?;6(`YQUsuNJ8+B3{`m`w(3yPQzH+-f`|;{2~nq^?M{}+Q5H>3 zn6r;%r)ANzNU^}?x2pD9SQ|qXD(S3PmZ#8^9BeC&Wg06q$vCtr+ZT$OG_o9O;IT`m zWm)^Ld7&F3>&qmf|3otPe*Re`;12olK+FS)?jW|g$E{iZCajY^8!7|} z%!{XgdWAZPOU2Cr-GeU@+A?&KA8HazFhI{kghXc?NQUI%o&#nLw^-b7__aECnE>uF zO|;?Lwwl;{$6W@m=gfJ_KE!t0tHl}C?*D_rUMSx+>l!FvoXmM@`W>owt>4Dv^*jCh zlv&e1lV?xI?1sS^b*k(>M=qF)J_M#*@sq_0HNP1>-#P8^J=84XsOq@rl$L_enrrvp z%s>-YV0KbOvU4T2W^euNU zwgdEl(S10tyd?Oj9NtB7C*P&4w@^EWwuwglL7s5{4!FOiebBi83EqJ1w?b`iMjE`z zt!l10P6c1pI}fQnH_D+t5*=U8D&QlBJ#)n6!>1;hnG7NHcI|{@YpW61hOx`LjdrsH zU*CUY@pqyD{+S@{Ft8uA=q=b$vnj&p9EXp0_Nkjp$1VWsTXMo>|GL1@hENUZ9R{aC zQZ9aa>AT}y^!^#RMBu2#YY}H7Vfuhv;3UQV90*%qdT1ey_3`iif5IkyEq0~kR1wy9yHkIGImp(HhkdQ%)aXR-hZy-(ad zrgwhah|WR&$zZb#P7*|UhW)A+DdDyW2-FTbu<>J=(e$Lhs}Ai72vTae2Z1%JCG_QJS?ft;hacY=DC zJA?N(AEIKbV5?*+Hzc~FNK&|yqt}f|L%ZkD8a*%oDz+Z`!$1OV7t5>n)KWyh)j`K7 z+Ry-!v>r+8mS@xMf`ON4?U0&BY&-k(`xXb4;=m9a>qx*V02Ks!ee`$B+9RGg^ucIp z*RA85?n6!m2}jCzH2OmfIJ4;DH_>2BYILeW^z)8J)|X^Ar?X; z8~*$NpY_ZeE~7*SoND9^I7H*xBXgi~V@Q05DB!6ekoV->!%P5Gldau|qn)UtAklTs z)lIbXrN#`$zwCNN+30g;f-2IMz*AaCF`OE5C(`VpFky|;ea17KoOEQX&jTo znp1=@!BPv^1x(j`vaKckW=k}=7C6n zWwmY7^`gS*8_Cg!7u7r19=AErrqjX$*R?SMB+@h{8N!$dY8@mp!<*FpuFEIo))gFp z90=`4jej8iN;iur z_MRYP=TQ3_cH0U|kqmHn1HF2kryUPp$R7F`y&Rtr@$g)FKs;hs08d z8tq(&ZPpKp7v#4hF_oAVv?G3!d68n?(fbyGhXW0>8eT*zsRCoDd5OyuADTp<-np4A zgc1~@N|*{2bhe>yfG|6-4W?XbC^~^HJL2214(yTwYHg$9x`m%tz%H);=_+C*vzAWL zRHAHcSVKEt3|E*rXo`Hv16d&0;%i>djf9u@E8wLcl^@MAUfvVl_V^vtRx~kUnekQ0 zcU*uNgc{ztk#u40yz4s9E1nga`?{bJYT=&e&5p2H{2r|!cmO)@n9yxwevEJs6-Y=fa}a4gcX1I(XA!> z9oGUY5m{u@je1v^OCVCBokTV1(9CP}-x~e!g!ft1wY2Q0eY`-cUY3nm1SlYabc7s> zWX^;z7C~@{U?%7W#mOR4j{R}q;1q2{Y&>-VBUa^rcc1}mKqn5*K=5(u20JX{2waYO z&eOPpGdP~U??<$|ci-V(fnE~*zU){Pa5DeN+pr8|g#!*XB2u742M+y1s6Z(-qWJy& zFF#PHZ1|IZ2NfaUtO{Aj)xt}BqnT{Ij5ZVshRD@VS77C!F0=1AN5|oYtZlWg* zXZersgxiWJLujVZ2GC%MbovqhuZt0GOJbl5xy+#TQXi;!aJ0cC;J8M}OEHxhXa2T_ zInDA8o(*@_jsDUeXy>(~G3Rvi*W^qe?LOq)*NHuaJALSi%-51+_mW`1FMt^pgssP@&eUPtZ*bc%fW8+6rYt+Y zgb0J)OyR_PG!oj0AqQS016K6;sljM(i5~Q?C?aO^v4i#mO>?T|?-x{Z!xWX~3Fu!L z>FwCIKZ8CRkk4j`#Sr_8S115P#MukRRg=0i`WG3MwJ$q>_JkH)1{T^0%|tf(9U>Ym zDnc}yFe*L!Vfwr_@c}m*Mvkqp^rLu8|M(7zk&RmyW?cp!sG5_ls%&*lsdwLi)}~iS zxb%vRP2&cwOjqE#g%Or@S2ab?#==PPhDMyo!89Kq?c$2+T{eNb|F_mW}2&sYS^6n-Sfwe9*36nL`hTOO0`UIQgzS;;ae zC^nj1Jjp{}cMXa{mXbd#FWK-Ax3ve(+yc(-4ss!jMD zR*r=O0W{65(@e!`#S3GOAiooFp2ezl7)DiDMX8s)vVwcXCf!4&WmuC9Xo+VBxvA8(=Kpe~znQ_vu+$89U{TNfwRm zY)mHS6Y;Y}T&=&%$?8mUy zCv)LQ&Xk5eoGHx6@V;1v%S~eYQE*Pwe8Kb*G+=I zCJwdTt#U{?rnc+*c!e8!-@6*(P1YK#!c}5nPQ7m}@w_MAZEX7s?B(o1Q#g{#5DB#fj*p=hi*&c-F}F}i zClC8aO07ZJ<%n14o0f;<(=t$WZ_^|0pmi`ifp|`l*18vAWNAGL$ZR%ZqPPs04*ssO zJQx9k@>{Ka8*-6^>v`yyhOmXn=5Y1)URC*?BoA97~<^r@3uIu+lM9 zxboB`C8_|HT8ANhu}MsX(gAIlid9%XDMYE1^}s4eVLX+3RDLV|k)NOk;34Q?#&X;N z)2$(|8dj3Tv8<1ev#WLBzOEp=8;e&fMh+p6?c?Jx)OCSdNz zN~p?-t8WB}!BF)MeF^Wo&Tf`PE4F^4{W+%q%cQHc;DWeWSv%WNtnT;QvFS=U}x zG0#IzHp3L4qaxCS zdOu<{`AFXxS=tS?tE;I9$T^;i?u@EUOz8n47mj+(L zVGvy9Fr-3YyzcyX0FlXpb%Q?PySMdZMx z-SXV{iO*VQ9lN!56Zz%&f+!Tdfwh2SVE7$4NG#(T^f)F8(Sfx(6ktP(H^~6^LiB6q z>$Di07m6)KUOMEra0kopbqb)8VZCo4iX!ClTS*{@MMz;Y4mP9)Go=_HF$PzN zdJzD%Gi%^6tH$Yb%V9WFBd=o{(8NtwsVN8p7NG*$(#DQ#JN>=s%b-G3O~Ba7s_rheOAlknBP`q+5rnE$Jx>MiavI$}ea zActIl-9m1kz-@N$?GgKe)>Y!%@h0W(X)AwEYg2)%@%N&dXeoX7*ySkX$9zYYwf|s!>EFuT zPcGxD^Gq4pa4p^AtBYSL_4emLp6nQFAB_7xbzU7ce<}ms(FAPimfoW|Ul)R90}^}{ zcDv~X*abx~U<)E+GU7wkVs`uTr&+i%o36=w#SUj1*BnmXxCbxqu3J~LuBPyG{rh8S zm2_v}xwW7T)3i#uh2&2yP5P*s>22vZu14lhdByKdts2Tn_%DsG(s{P`mN{oAx~sZo zylT33Lo6Kn*wmCAeSSVvaoWj=of;zG?mNgYo@1VV@SBxX7 z)#I*gTQvk4p9EJLw-0daUNKU7U;6=s7qM^ZY4D{!+W)o%cz)DN&kYU*t||z@ARKn>=aw-8$rp(x&Gf%MYi?&nwwFLQLn?@=wD8+ z)o4t?0&o0UO}e*P8{uhk&o`0Zq=IVxwnM-|zb|&|)SUD;@DeQ@$GhECmLuJ21=nlz zJ!tObHov|Td#G@@&$lIqr;F>WJq{+^H++;Bq_k*Ck!Ok9@+c%8e1K(EeyIBd3Ri!E zfv2!@ghAk6GAcMa*apHPCbB3*@+tC=$!jMP=^r{q1lNK<1*&j$-m@RjEL9gKMkX~% zUQ-dBEf~M#-fRY%{P$ZM!a?Le4z}yZJ582d zX`JI~boB4KPfnge#I$A4M&emptDg1ThJ>SNef!re>1Cl z9w%1h)4UJ8VGvx3&%Sww;1IlAyWhJz;NO!bdNuHq1(rAsI%l)T8xg(@v|EvBz6bY4 zEsL?30(n9ZT&iN%)N?A|dB+6u;wtUmq*;-*f5l=E7xkhsl8$Z9slLv)`v=u<{%gNrO)nT zeiXM=KNIfV$fT}H0rzpYxqP{ZZJLS2pe+p74B~q4SH3NF_$R6AdBOJKPhCmu$6zfj zv?f>PN90R68jsV?Y+W;c5+636@?u7(*|P3|GTd;IHX4H^65MARm(&f<(XUKgpWgK?FUzDYhRGnT2p@NqPL(^C5OdB6V~oRUFLKN~{-r+Kl-_PRS7c#8 ze_QXj%=JRs!EkleJ#K@geLNSuI(vNyShD2pLRGnz|4!Pw`h)gfYgPN)-m)LNRXJ^M z#~tGveJy<-J1K`17h~01R8JOl*4uIX{xmu@yuTU#IGtPlgVxS>5REp>#`lk98r=XR z&RFV{az+3xGymD*ATNDivI$+6mY4T%JbL(};O~4{naxeQ5_Cx;l4Nf&hwfwe!B`{Z zGQ9EjHw7)NX=>))tG{9Idn-(Les!r~+PszfcW237sV$Zw7zvrRJ9;tbHKljQ<#edK zc&$w1kow7Xddc9X5q|4+tWj`$4KxmQD|oT)@CfXc89vQm3P%yFlBMYLH2+}Dja?Hx z6E2iT!Bv<_#bcX$({Rd%>B^0Qp_!L5+av1p8?p9DYxk}<-k)(K@>{-MKA?E?+e^5y zHv`|ypRf>bUpv_@Z-|U`T;`tx@3zS5gbyQen zp+NYtu%!jjs8)SQx2@0T(hN(%2r&E?d3Ai`pD!ipj$u0DXg`f-t8;hNVZ+b0!o=cI zjT;hs&O_3ThOE_@7*cvV-bUeA-P&b(4a*TK%4qi1H?+Iuq4fH#A@Vi&Zkp*Br(Zj2 zh|izlc1Ls)Q<@YO?i|~l-m>pTjepS7%{G&1(!LPyzANDOPq9#N2vTXXQDiU94yg)9`7%3r_>3UDQu+BBGFEFQTvoaBcdw z3fI@D{h_$NJ2r=}1&T5s_Lo}uwY&Iu{s$zp5`%r!Kl6c@O^Otjt{l0AZ?{o!pIbzX zj&00!UNR}EczICv`O+$`61#B?aWrWohXO+!GMmM|HTO0rhu=V^)Ai{l?w^QXvHrY^ z&zjw_U(<`0q2My=VN%DsVv(Rr$5<-!a2JN+(kOSYJ^ zoDF*uLA3L;rSxrQCC#5&X@P{x)#s+Uj`yX9dD+6~)v+6f_)W|X1C(y6DOUqLX&jwY z^~aYa2Bm0@uJh>lp87kb=G(uH7d3vjOC;?*s=GY)SuyLQpa@0VbzOw6+47d-OJ}!1 z>8HLTWJA5{>k4j($A{kxD2go@W$BxPe>lLm)1NlcMrnv4N>{Hdpi*{!)i5YwGuZi8 zAFH-kuJ4+9F(9Q1r1#`(De9)sk&B^qv~50yLn@j`Z$xVfoHa5lwB$YQrtwKtCbx2^ zlG0QASAIl3GyK}!)^AeddD8nX^u!*<5W*nLpLiTEeE_1oafG^cY5w+txEUXE+t29Pt}vd z*}!EyZ9dMV<|w6c5{%cY9#Y;*M;qnIep_Z@-S3R^8tzFWu(oBykEx~UD!GxzY$whe z{tkxoc3;5bWGB~6aUB@cJ^7tO8p(zJrXPxTwmlk(ipjo=#&+ z&VT;=%-^p9?caD~;bnIlo=NbYGXopHsdCP;)_lj{=})z7XlK2K`mWpMo_i_7zpa7z zAkx{h&wv*Ha$-)Zooy@r(F64zdo;Lno>mz~inj#p^vvOv=$g2@?_j$wsg&$gcc=B( znhCWde+EMn58)Zk&fd9lc+;{Hb^1{p$~|JP*(-%zYN&p$X2Y*>SD1EQ0dsxj?K3HI zFiZk7cyl{-NfG6icBN%3vCXQKQrEH3Y9l%Ubo^}1!lHZWsMzOnBm!3w#dh630>SKe z@xk7cO{eiLyFAc>NA_yO6UIR~I%sUQsYDEVtJw&_z8{al*YsvVlWT|FW${=}?P{Bq z-ag6Jx2Yzu*{LIe^}(81Ch{AprR0KeT#R{VA&b`jF&E#*={+(~ z$xL6?B%w`G%m3WS_GIDiaHVIg(pwkAr-Gr?_x<6)CX`>to4H)ol-ZtTTgBn}PEsV; zi}!?Ld0`+xRi6!1b8;9bS^VF6y_f8M_g?P^sCA~_p?use=|t`PGwP~b=k^xy9<_jW z|I_Yhh_n1xM%LzlSgnJACO{L2 z3$Tl^Gl-WJBSv4VR@Ek$Im}rklH>Ga!ViP@QUAL5nWm*zk?xwY=3SF8clYi9LxKW**O<-^(U0)dy{ z9-E*Z_SgZp=xr1>YjAn#hg^RwCo1dVoO_+^TCO~MX#dn>}Z# z{rz8WAK&$izh4)t!|pi>uhX-BKBiX%{cIschDKuUHJH$qeSB#7^C-z#^D(94N~5Tz zDFWp&7PI7m@~O;N^TGMGr>yu{R#+9|hy!ciSj6=zcah}$^FgNQ=Yy@F*n7}!9 z)G>nV#muNpP>h^#*RC`*YjDj?p?vmv z#6B#DdZN`hVH^)9#nPcCc5ve7F7;|9nzFKUG5u+A7ScmX zdURVHMwfYwVb5iaVlqTMB!1~pl!@S+E0XA|Cdc`@7@5iSaW+)vU166+|E^qoKZ$2z zlP$KD7;2$?xE-dBT#+DKCP(Xq%gU#l!Rq#6&>N>MOxI9ksISs<674qm#lFmxRq@m$ zRy$flVK-{X+ryc6gDLM8V_t)^X#3cD%}9{@>UHAv>4xl*9Nk@hro&_$>r7T#s-7>6 zX>jp$SBO_=boM;%blk-JlF;z)`Igrh8<`!GD%{fmeMjaBJS7aX>2zE|4h$_DYBA`E z#oK)bPVct}pu>Q=S?F`~MfJ$g_?3b0h&zK9SAU%0Cz!a(dGWU7rgLKy1Y)%wNXEDM zh&wJr6#}xzVgnHbEnF^$&Q=~lQ6M^{G5 z?C@LNsEuGThzCZ-4m<*E?Ks2e1l~;8HN{XakzaHnit^MBw$4d2ES2RUmx`riF>x@xCN2OAUIbBHDoXiXv13+^)Y2lyzANqbFYTjm1n*37EK{<3{ypgl9=de`kz$5mfp zlV53&auDeP z)glN~neD3W!}=v(E>wxMl9umu@rRPN*=olL!*|W1mKEm~VD%L(roA@h_7<3Vtx%*V zF$m%@8QN8Wp|bUDMRR;&uOLfoQFHPJTeO2MQQ zTUr>rj#bpO`LkwNzwBjh?9za4wlvLpvvcHO;O?$Yx(VQ4 z_2{I29gas}d@J&+<<-5r84ltS2Y!c_pQr9?WvuCEemI_C1sr|}0)ES<*?TqI>F-rT zI!~$cVk(dM?M5?@5&@xZr-?@ z4Z@3xt1}YaHx7(j$=s-EEL#_4elTeUvT3Yk-tEL?ev+svtu|;2Dbm0aQ7JwT930}b zRf${0?R%W6|%d(69nj%nnwBr8qX|v^$yAruI{VDJbGt zoE#-(Cm|^4wx_xyjW=@P5RX7(@SMODn1qN7Kret7>dK=flB$5y$Pn2B7m7ez2OQGT z3V1;n=ub_!Y=ABQb1{rT49bNPDet66jXg<5sAGWrmb>mlF*MwWPk<{divVOosEoV@ zKA^L&6o8Gl>RLD3cP?url~2Veo_R|$nzrQ#hJb76e8aN(2a=H>t;WSCLr21FJw#y* zsfA9^8=q{U)h9bUM|(|ds5N7coMZ{O%z{M&Aidd~iOSDhU&jZ8xl}Ur0A`>f?=^I& z@|G4Vn@u=5nrBIj2x2Q(ok18VJ8mE(g@-U_(#KfJ?x0&~>?sfa^Zb&W!8y9)vd zGkRz&Lze-GnXi;i#YQW-_rV`dP}Tv+=M!-70s1h3pNa()G_eKe>=o3?gVPROWP}PN z0snB0UyDnCED@eU=Xci~2gn7Bztx~H;&6?f3?%N+qsl2T4)CqQGrHE*rKD>B0XsWp zbMKj;hcEOI!{?OH(Beo!|3l63CWr88tpG3 ze0CUP)Vg!G?AGsa*4ui0-^;Bn3r{avrt#eTDLLpj*+-FqhTrsX(EfZ+vAhPFhX|r) zwYg0hK<}-J_#<{Wir8mHV+kNLi2~q-ih0`^j*hGEjs^# zJOM`YBCo%!nbWqqPp|6(EUja#f6vB7#jQe5FG5evVCV?wamz5^dB;c}eG%XYowG>M zOXiOPi4kHYb{md1?R#}K3##;3K2O&E8nfP$$>MucJyMW>yI>{^5V?AUqcJUeno5x| zjc;Bq-ib$PL^WNgcn5Zs$(gN1td!TQxXmb#t|xmbuU14i6&h_P%JsviC!Hu+1>S&F zc_r2>0aZF3CoP~&Bp~A8F})WV*k1Swz^u%LRwhs$Pzj@X3MG;VVsk*k6|@;Ca2X{_ z{~_ddqI3qFYs3Euff@-3$J>($lo@rGv6PP1K&1v2F!&`UO^89wB1Q!yI+6gV6bB)q zAO)I-p@0AdlnUL7cOqF z_x-$8M8Y?~)(`DM_lfveFLtYIPsP-wYU_#R3v-?T`)YVIX6J9=RrGW%x&Z9%|6=T$ zqH_zHZDZTX7w*`$ZEMH2ZQI6 zH7DP)X$s5hzwC=Du?=lcX*inZWN-VufVca)rSr=eU zoKNFh27CX5X85`cHnoTZ1e8Pdzq)2HGBGeR{pU5~P}9m&TlBqPev;^AGnhcH*8bG}gNBCR;g$_xq zYH@YqV#q8Dr0UnJ;ed?X7Ao+^3Ykgco8PH05YTY-;+iAsJCMxbjZc1sAFR)bm+xN z@p8xWid?L7Dd&M+p1+vavd?43I|#Ru&LigkM8GGhO-Igep>`($oB)N+h(+v&Y@(;| zs6I)b?Ozl&f}T#cPH;nWs1i>-5eH%^Ukm}WOCmn=?Ez)BsEd!yOEwW_Z~3-PT3gFy zwm@esO-nXWXDvcoEiE0FOfDesHoX>1!u6Ib)_m!S3rS=aO8d&95@fLSaWj3qPD<#J zap2&rFn?stN?=9LkPM9sH=6c11d({Ts4~LrJ-rE;2)S-Wb;O)}BXmWB8%a4#d6hGpVb` z)z?*RqnYOQp?sY#Wp3;c{>Gq6$f1n6eTxGh+pnrGPvIh1Y-3kK;7v zD!gCg)(ncW6SvHHl(WAZ^2nT7*)(Dz%(JZ7-lgfY@N(mc^Kt3X!pMh9P4#?ERO8|t z%tW#|nVIf0>YEaNJrGzmwHvsbtNXf+r}wq@bP&POr+EVp(`7+A0Q*>8QfaxVR&~-1 zA60L=A?k5`EGM|d3Xm;)+8!w#-NibZs*^TWCahU;{J_l)Y?yEBCmiuloNppIVYD`7P$bu(XT34+zv$1i~ znvu(_;{cc|l`F@t{ae%(@U;Qsh&c7-bPY#3zSN#+Z#zOf*>{)g-F(dHo>Lv! z?lz7}==b{t(28O+$OT_iLW|e}a>Nyp@XM4&`lqrji`G+Nv#?L=?o_VTjFv)VD}ZKn z#_L(bXUl607V_y4eT)q~`5gJlVui4oSK|%$Apd^nk(P+|8o9%%8SAVVnJE*lSTwEc z?5hJxJjN{iZVQNZ3)h)Uu}}6~5ahKn1vggkobTvy0HmTUBr~(6)X`Lj`0TVDn_=Ig z3qTJqpe@7O!qXe3ub1_VST`%%vt1!3=CJjTcSS1uLX+ zXt?e89g?nu|8nQ)vhli?$!(@}+xXqU{|DcojFx%CnRHwy!sG#`7QsiSYD$_O#JI*bhG^K3X-~$fJO^ZwJMCSeiM+Y)$$Q}NMC}o#VZXpDAX9f z?84GJ(e=3X=u$)b7xU~N@FMUGFC|u<&f;69ZuV^#B1n&%%hzqpnRbp%IR7k=7YP71WQx@H$xu;0GB^V z6m9j7K+0wAVzGPiT z7@p(v?8_evc%eoGp`{q0BxzG&vqSzj384@nv{VT*6pH7JNn9E$M_OHCq}7J>GrtCh zW(s9R|Btr=6VlRPGFN7+%pOuW^(|C}2SS1VvOadml1x(=2qX839((wn=Nxg#EhpM# z8h6;&9d$8Y98I3g^RR^W$E8Q*HR|~`V7WW@$;N~J{&Y8IeoN4Up$aZk@lC+vM&IOS z_amnpR%h&MDhg^a5xI9XFJQ}$<)PZ=`ZDvHyJvM{snY>E;P-Fzh&Sb5%HMwGi5?)Y zT{d9girq*I{BT0EZBS^p;AULOk<=x4hgM{~M=yyeG3Av$<)5=7(FRq0Dn6MIHPWOD z!HQM=rrL*2;=wISN~=A0pcYm*=7$TjPvM0+Sjb5E&3(yEz_>aN49txh2UB$De>Aq4 z|FRnnjo%>_#vXx_^e%Pfozz);aJ{i+tO(&g=7#SCgv1~e-*-;IZkhz&<}TILGjh`U z2VtWf^hU4yJWJ@Lr%>=jX85?#qy&6|i!^Y9%WEQUWHeG2u+?GLG%C*&$dOb}RogL4 zcuPh6bMBc91{hr<+x~3^N9D) zvbf$XEZLL9K3O(Aucfg5-2Mpa%wt$Dm~I7sC;*Hd2?-Bkyk6MQ3r-uPvDv0`(p6{= zl4y_3$$BO!(JM=ehy=FZkE5i}~muDtqz3E5bC0E7S>J!TF= zwO@c`!0r)va7$J+{jbG$XK%_x1xKnJt2W-o3})J#s7wimKO{J&?s?Xi<-fi7>aRKt zw$xjm)T+8CKyxD@`0!k~bpU6tp<1@AW}00{cBBc|i(6iu$H@>mbXuiU%jNT@$CHm$ z3FzmqkI)uo*Ej6V0}ZR%5|QOr$0e#p-So7rqOa2 z;%AoUe`YzM_Y^1llj}f=AenDE)mP=M?W6fFWd0Gdu^7>^d>&DnpZe40w4?mP;`RKH zKblH*z)mPgdhL$DSlUnTP2*MQRYyYHhmY>F$Qd3ce@0$W zeo>QA@-8fXPpPG}-z6jfj-i;yf#Ya*t0*4DJSmT>DAHl?g^q){svXZW01s%VM8;!M zfkv)uhEu_{xUW?ag(8*}USqdyJt^StCOI4wW|#l<3*X8wsOG7FZigA-ijG_Z`OZj2 zDh9K3iIrPWgehxzdt@jm9tKc98lN=ad{`bbpKwI)3kvv3FnmTG*-A=ATGc5^v8(zu z=A&GJY@LFwG1B#3Gfgi>GzZK@uU`mP_3W76trCG@o^*7-0KBV_}Ez}bL$g0t&{{UZX7F?s2Q@n>NVKXYr!x3{c}VXwsQUm z$`##-M{}44@99)4E(UaP*p|2fv5G5h9gSLvT3hkK0xp=!T@?Xaa7$=XOLLCr*iB!r zSfkFkwTfTS8jXZl=sB^~syv~0;7d+fiBf`hro%s+B_RnB|Ej+Rp zam)rex6k|^AS0mkA?Q3pR8=IEaad=DZUnhd1{CdE`!FWc@+;vz&S$d%k;pL{HR zBoSs6nzAny7GeMdLpDiTBMg0;LS7Ce1Rf3H>Di~4!|deIQ|No)`nh@}LtzIscI@h) zxHJBb%2XQtEr9lta25yTOyFhS0W{3~49Et&W7PXs+qr?d5m0PF3uiSdTAjgh`Zt^ z+$su0p<(DCEC}~*JV)R;1Tgey3rbuDkcOP`EY}oHyXuOI<4daZj-Ga&8*`K0xrWE$ z$j9Pu)vpTu?XI2$9+%z0h=q|@f{J6<=YsWVr+Y(mJ4(uDf{&GFQdZrt+9XLqYJ?zh^MZj8yo-!bv54Vxgm?&@b07oCU;;|lCs&d! z_10?8vfA|7b8bQPNpog? z!hhzK0il8tfzN?)(C_SH>NDEVq@Jy0&}#&igajpwg|#mZD1?I41myySx#}Em^}+mS z5B#+}B;cR|WC_~c-g88Eg#Tjb)D)=$>4ne+61lQ~Yqx^?joOqdjQ@I)a6Wdv8;__0 z;U7g%^&9dUY^zWhph@|n9_`*AJ_}_NtkLz+hMXiendfTz<*bu#w+53or@3QK0)MNE zy!@t-eARB>u;`@D*<6~q-iTDYLPMtTc9nus8S=`G*zn6TiB%aFr)szqcJ3Oj;&eT3 z)v{NAbW$z`b4qDtd)1pB)5lF!cEwjz<^5<;Vf(B_bIg8%e#4UA;!I^MT!-705x2>b zwl$qLRvyhVUrR)xkl<2e=RsT#8}GNq`qAN+$R>2ww|riLrUScgaf1!Xcym_t2BE0G0l`{JZh z2?kqL7+4)@p(4RL)@YP+lcQ>B8Nu6Z>lbY2AP@-#1yYl|C$S*!Yx&Nf?)b{B;)^_g z(W|>m&a^eo`*)vX?j1W*zMXDvE0!-^zcGfdmDRg6TWj_&$jNIsi&2&SbZOaQCC^^O zr}vV##Lq9GZxw!PNElG3({g8}G&;Cxcv*PT_n^AmKjS3t!XbO@7bqu&0th>52emD_Pq5>?VNDyRn;#jzU%= zjVdDc1Hi;hWQvOzm&lcSS%8}PIGp_URfa;mel;ITO!?y<4xc9TH8d4p5`Fm1QTfDs z@;|OE6MH?L2l@JJy^Spjb9_ZD+Z9DWzk5Dhysz`Vb9>_CKDwPl7vi`-o$T@G6|M+l zO<-2vn&*NBLz1yUBIqWo1K@_}2$Wpa*JN)}*40T^~5M*W&hYXvLGaXT?a7!!+JUNspxQkYC3q zS$e)5{N=qABA2%J_FecR*GC!lcgW)s=rExKkY&Eum&H=Rl>R>KR~SSP=g0J=Xo-IL zbs3_k_wq7No|YGn{bh5s@&}S$g*j}0a!=~WzVS$()rn#nx55~~rBaak&!!{U^IFdg z;^%%of7gpV{ntc{pWnOQjxWpONV(;36tiye>((rT91l*J$=$05Tj|}aCR=VU_4rWW zw)kt8Sji7?C%7jr`i8p>`dcW|dI5J?{jU2!DkHbKr@yJ!)H&nr_N~m0C*jF_kxqx# z>FVwEJiX0kL6TdZDf?^L-IJ?(V?C{W9)tYdNUxKd6t%h%*3IFm_HdCF65b6U`G8&1 zi3NoFODCxYesYpg@jO@kI)v+2uvhb1>BigN&vgf7W<|Y!fi9D*(1$^n0xWoE^sMl{ z1NmZn?GL-9-`X{V+M*m{?f|zpj(5qY!h@uEW@P+%v-zv`kPG#UBgn1MEBUw@?CcWX z4{F{q-%9E=eMn>e)h1jZZi)r{umi%?Xj~BPs+H98DqM(8X;a{TI?Xyb0l8F)RH2HQ zW^io0#4>Ua1{DAYF$>sxCeqxym@C1$Y@3_Jt367=y~;Gx!`mn%9Nz+?0f$`N70{k( zAvX4Msb? zT&kkABPWmO&c4{$wFOa(9c__h%L|_aYvd5=&8|t>?@q%bq4)@}-%>%0WY8FkPbrse ze;tzPdEz`u&^ZupZT`Vpd(;d?$cEcvxm@-U`x1sY+UskHjNhb-eKwae;LFq*AAaJ! zFm*z<6pv5Ts?VKbW$OxJrp|>>b)XeWb0cVdE<{H`boeL0uX+T=`)lDcUv$ttp%3G$ zW-0fVGl5>f?i6dcEgscKdaI`+5GQK#&Q2{R929uxq3Y3n;6v?H<9ZZEZb#2$QlHE- zb*PA@899555OqOHIZ;KfHjOWldw^P#LF-q8wRQo)?i|NbwiWcCvyM@Wn z7E4lfrWR{;#f5y%cRttpvZ|A_fgm7dZ01oe>qAQoUg~#-W~ioU1}lj*Vwz#ao1{qP zb4{=95)fo*#6F#VQxH0N@vMD{_Y5{ZIIN6^u#E94rmKb$)}la*Ez)Q z1WlOdjnIw?;#qVq-nECNKmvKB5`eLl@X@RcnyjL3Li1>W2aw_tq$14}OxEj+YH7GV zoN1V0K>^&xxRTy-v3W6Klv5UzRUhP{w>1R&nP4i+$;6nsr~a@rB$HucVnilLb+uqk z%gszT3MIvF!bRa^9c+&(HpZa;(~K4d87EBc-0fr=M4Dj3LxNW8Lno&4cUDoP21+A` z3+znJv2z+#rW`7HhEpqB%SN~SlBHK0rbZ2+%7~l$fYHCs9 zwmQQl-l0&%=K8RvnSY5fZnbA1G~g^-%J!Y~ezQG7p$<0I%LY4_5l_}E z5Ian^Xt%l?(HeTJpHQaysfZde;%Et`rOh2u;l$0o$VF-HLBkKa2#*jRq5wk|4dRy> zW(wc^1zB(w>=vA5q;xSru9M-e(+2OD!SM9ZmjUA-EV0p4bBli*q$jw7XUaSWW}rB# zr{Y2_WTrkDXX;QARX1{W83B%F2Rnh9I7Bo@xv} z`Cv9>PSsy?1|Uks>d)cngK3FN0$-OX(y zwN~zXiP^bq`n!XFdSicfMfxHa>RoWO?Q8~bu*=fgreFS!&cKHvh72x+&ls6+WsTV9 zQfbEsgYUcYjQz#cnU4_i0#7OD);1D-$%~Ub7uL-HVe(*$j&7*(ThJ|SZi`AYa&A6$ z;N>sbFcwI4AMlAh9XL3;kp~RZkbeL_B%2HRg(h8<)9*GGd+&bQ8f^)L6qP|`?@n%@ z`0Cz8fEnPs0&_CH@whU4D7-pvW8%aWeQLO^W9G_c>R-iCA4vF#3IBAHK(3CM@42%HEESNC_xDLBF{ zxZ+lb+%NK$pcl0VH?f&aP0X_yPPN$>6X(K6G9#8(v(c<|1$>aC(c{juY(|^yIkFwx z>jfpV0*fO2Gk&Z~CGwQ%D(rPQ9s(+H#3tE8mMyb=oBcJa(isI|rme44RjVA=Fi%b3 z0A>@bwS)fkHEKJZzC!@>PXnLwBywpGZt}lFNlq0k%E@z*idppaYaZxgubsl9deZb!-UneUz$W4@d>_CRzMk36sCQF3nC z%t5FCYzo>s3eQ@sad;o+L73u^x4sch;&x(-0nJ?)R1qY#*dTDymVFeN%f0}26UR{x zsFV_bJ_vQ-gZ72TZ>Dq$gw#bVKBYx+n6T)axb~{M9@)|Jg|T@dZ=*?fOSa2zuj7Rk zKCkPE#k4lAv%x|+^mazaUX8XlHbF#C_#id!I2R+1OrXp6l3>E2Vnj_8M^P~z+rN$% zMk~ck_Mbq~w!)6s`ZxmfMxFTjcoe5p(2UavcFz!)Zh=wXd7W0O51WBT(M^2;GBZKm z>*U%Hz=tp(ZDVMyc%MKU%5)^_@!$|_t+W%4SSB@fzmdRtRAd?iY#?K|pkqJVuQ*K~ zZn^^KJzN&!#6PLH&=MZuAG~Zk^8w`lH0LP%st0=wq^4$`#N>nl!ZL*7vl7vGo0F- zL1+63b0`RaIdO&!W(g$3W?zUi>))-?I4$LKsK&E$5l;8p*Z zajm}ITREH{_3mb4PjOg_oqbeT?9%Pgpy8!Y$O^;aoDOMMdp?ykwa&`|zUr0%DZcV} zG-i4!G(O*%eWYxsW0zySJY6Kyy|o=rgZl(wz>vZXx#cO!RW1nP*~ z6K>cb2~s&a7<4`l8Y`kdh}1$x(>2dcALzeVTxXIhVpSm_p_i#4pEK0&$@T|rpZRgZ zp+oo2>x;?WooVm;neUm&D=#0Pt+EdaA)^9doN zYbVw!C57>^Q^Uqw5T`HJ+9mt(a+74nT)?L_Yjl6EG!H z-f0It+Z?$gXS2HYDRAG+4WsJWWYw#z-v!pyuGzd~iv!=k0HoWIqt1GV&0y4x- zx6c3lszzJF&KZ-Ng$rw90{-=QA-%v%wj6_*rjF6>XZZzZ{G`g%D=eQ=T$$$yCvxy| zuaL?yM9*~fE(PDs_<|X?IheR@EsL$ZEtEFbI%^}bQ+~7!5yP!YTc&T^fS}<22Sfq% z_LmaS1CuAQi|E)gg^t=)ZOIQ)SwQdWgAYQ#a@;r_Z7K$h*ho{cJwBpuX>_!7RVznN ze;+?{#&`td2s4cyzvtw)69)bQlfQIraBfjli_6wjXw4`o;}>)*RnO}W zF)B4?dOc=(`DS`_Eww7S8E1Lp_w0z{Z}u#e3@iZTWk!Gray%mjIJOyMJTyia4r%1A zg+YxAytE3=Q)Wr1NnvjBA0<1(M|Jh&i4#+><`z)9LY$=;*Gid?UzRkuD7h3Y<l-F5qKY(BwF5QbWC;4p99_@6}8wveuw zldNH(vqLrZ9%JLAz*+QI;E45MGrGjmDjUTPgZ@K3JK>w_l7ssb1NSHH8Y>h(2@ad= z5Tjm(*}kqHZiSqN(0+c(UmH(dz-pxxChXJZXTfFT5SOtz4iTF1nodi_b;q#U}1OcIJmsZALV1umgj?5KLwE$=vF$GdoNiF4llqan+5cdgMhfeRk?Scb;+A zDcL@vNhVyeCzas0oU9Qg?y=cGW=87X@`4@GLU(M(WIMpwhwJeMrksA>+gWmWR+k6k zEj^I2zm{_dmU@W16)dR%u*J9QkMZyxvA+*I2-RAg(}s&uiBS?B0CuvOd1gU0njwl^ z4$Zv9*xbclTzi&P8Eh<@evYW(+EKTdJEBxMfBzLO4#3We8QTnXof$TX=jlyIdZ;4ejCl;B#r+cr8QIC;Rt`Wq2 zFpyV?IODUnaH9^vaJp$#+APliA)N~?A{rjTz)Xua{4l`~M;+*tcAONeeYgx{ntA3X z3y%~JaG?<`1JqnZ=!%h&EV-IKJ(J^)d`M9oHVPY5zZ=ZvUL)8 z2e=D6%Sd3^!JWU=vZqkRE1etemPrqMGkG#9Eg^nAtKsQlK}PpB=@Lkkfc*lkt;Mch z-)UW6VMif0Tbok_=c&1cB}oN^nUn=7TBmQbRtNNH|7S`**NXbxdCw~PXf%bPRh}!e zLNDvXRmkS*?MER!CcY16tsMGdc^21Wu?i)gmJHMvip_Qq|7SZnP+H2u5`*6HToP+x zd87DsG@eb+)>@(4aBOS;)x{jQkz4csjN}{oZciuK^DOQecXPPtK7I+C#VPn@{N5Ovpg>$7poh@h8b zNmdmRF@}5f2K-DbPQF<@ty6)#@%M(@wkQ}hc`U+8pSmv;aI%QlOTEMc z0gRj}(i(2p&&+Fxv&o2y$}EMu1-nC$f4ccML)Q9g6$>K?DaaaBh#^1Yh${zwI%To! z5=UggW(FRMCt(dDL^KUpwjN}q|J?F4zph~yf-zsC1=?5vC0$HElkc8J&e@BXa>+WV zGkWcJD-VAkk|VSMgI*{@3}q-*c5E0^me|aVG}B*nVlyVDzG^XJf9m0nSEtgmrmzP z1GVS~y{YQ*Xp5+vkAvSSEs8(7tB}79fSLMwVp_=iwxe4n#Y>H<>+_1ZQfvuDLCm_rehO!HH&$CUqRrAdLwVg1T z_$ODt-vae zW-V#keldSc=tk+cYrfYv$3OJ7xf8iL<}6kePOl@ikuJStIS!*t#3ZDR_Aji= zL)fI@SMd4F2gp*-fYv#Fmn%bB#X`Ggts4D_`}`Ce68s>W4SadKVz^(dM12o?@vi7ek|}s#5pUH~D$i&&dn& zm#aD>Wv?NX%`uB@&uWVaa$voCaka2JLX!YubRV3(00^vFfZQ$w%gqm(7dZI!pT3a@ zJ%Zu1j_mOud-}d|(pDz1c$FF-fHVJwgf(@Q!?H-Q22TA?^}Uc?K}JBs_wZz`+yNv4 z1_XkyMVA6-8nr5pmM0-+O=cSncXqBX0v$J3_Q4iMc1z8Xl<0@V;wei9ta{QkB0d!y zenm%y@av*c$fD8TC2wSQN70sFsw!!i$Blu-3Rkmm$$VRYO7CX%^~?Btfu~?^dJ;Ya z+NV_3w8neb;?sf;_lk#QZC?Z;z3DNXvoXuebB`AlpN5C-p}8CKvWG;vd#`waqNpMp zzSy|B<4OPe{`F&;eO3NI`hCo`_*q@#KW_=nBamUb_Lb4$OgTlaUnc~ z!4a(L!{%eY)MI%|Y5Z5c&D*+PZ|%)p2yWr`f~nT?renwAv6ZX{aRt(T3Fy+`2P0so7rjX#fuX! zFZN54S6=ihvHlj4_S^Y(2+`lqh=H3t>MuSUmF(C8c zKmTTz3?szDhfQ)`5jk*orjeT8ioe1>Cpu*)GBRRJj1}PG!yrGUDDoQdlr(GwxdAyQbn> ziz)4>$2MR*HhvtZ8;Ht<+}i`$ih9NG&KlhL^7tbpC9&D*p~f%ajI7ynC2$ht1l|`n z$h!LN;&L-t43YA9UJwYwR*(csvG^-D2e?n(=s)=VFG{A{daOf=;EzALAw(wyBShofHO?ie*a$$_wX0gH>y zuWMtDb2aik8GMh*W%3ZLM+@bQy;oiVIIt(a?l=KbFAi-m+YbdZu4HEveC^LcBB-S{ z9v#={t6t5%B+@nvPO({!^5Qt{3%o7VFEcNXeQ>{n)OWMg z&yP84z1ZwSGa8rdDy>pzv#pC}z@}faS=lZjv)=j6HcO|o7b)A6`-84e?Tey4uJ@|3 zV^o9~Y~yeaZC5KrU{I5W%t~UXuuQB~u%nWL(ici)Ww8%W2ZqZRZpxxJ%NN!~Jg^$W z>N506IZR}dsd{ej*l6$@f`6_qK5R!Ak<0OS9&3o=qy@jn z6pCN-zi!Trm7l(s?!Cqs!Jq~0c-^&2(Q`6AZr<(>JHP&RFfDsZe&%AvYMWf-2fpQT zq2+Y8IJe*^1 zqV}4MiELJ63{$a^;3d$Oc<9YseU&pjZvJY<3~ArpWOTwasy5ojVts$s_n z43eeb1{!cj?gI%U6Geu#s2Qk+xm;-ApkDhGjw^ec`Ivck5ai-Zf}j1@Cu&M5I_Z){Wgx81=Zn3;Jv#Fq zpXzsuG%5Z&?SOCE;N_s#mgW{uL`})|F!Q#3Us3dW*KBIt9!FR|*Ak?@^l@rVTRp1@ zG68;~YSKl7Qr)t>73se=@_#72c$4C5esGpO{K8U3Qip@vY>=A z1L2KvU3nW}in3O`9tVIygU|h{QtTDMqyh{Yu5(9ckx%1XaZ zo2*+@WO}kq_a&Fni>m%Qs`bD1qckLC(~-7rK`Ynp?dD$H@d^TQ z$c7nK-x8?DD*gw$vcHvZ0uM9z3mKXU#ZK$6(KN6H<%jZ>bd1nZSp{pjk9QW$5?pp) z>muZ;3)-+Cq*2;wV+ImpA+A?45Yr&Pa&DiqS0M33J%nSC_ODGMQFsgle(1J`59wFf zzijwG;JW7iw8ygseP%vpyJi^M{X+(W7E$GyVT zVsDg;CN~ih}J(W{YY?0v#6WiGJE@! zK(hO5gLkn)^zVB5b(Rj`RP)a8aWxmfTzwXZ1F3?ll}PoS0h00ygE=``!N4{n+B!^H z9bfPDQ65s>G4nm>{8B$&-i95I0fJPW+0dazFDU6N8$|>TE;Lb12-1pBxD_FW5ma*J z-*8KVK_;|HJb_cia}gksmuq-JLHn5}q(4ZE!s;Z;-PE*9q51qhFB7kiw zfsjM^U(QG>_m)A~0)5Cb(&EnfC4q^%fyG2yRrDKVBxQHDt>BC@ZfXHp@!RA2^_^1f zGHg-%NHs;LsiFM#1MO|xZ5#M&wxnl`v3DTyB&!;P>xR$}cAujFAwi(*;H?}aA#MY49U7z$3;TrLeh!D5e;ewQ8>y9FxH9&QlZ))gNJC~49!ALaCT@gqGE}^ zbrVP+KEzT$`I||~;btNy%@;#Mf|5aDv-yYxhbmf3DT3+8FR|oO)poJgVf@f5-w77O z+~78Zb-$&zT7e(d7vCn}z4E>4!eF$%;Ff?#k*7EXmeLQ+%2Opm_kF=7jwrrh9{1ue zVw8{S49Qff%eO@()claqAYRXP!cFKoLVJD6XxZBu(YA}!&ktunzS=Dt3xlNV*N@h; zkR4kMLmfBXOHyC(&WzmtajE3iI#0Q#czdqE?lJe2tb@|NpImpvG0z>nzk(lG-^ld6 zv#v(zIBn^U`~)9hfN8f*zJQE+&u3Vqfj1#%}bde>$KP?u5-#F;hI_6u73 zmIf2f=j4Wgq9z{d1FnfEauEcsWH168&5UP6g>VG%0CR5xJbnMqd@s+pX=$G=KU>~a z7H@J$1Ug1D7irfaz1wRODCX9nclv^NvRGMJO!BzgkJZT$u6p&6X4KN=i0*eXvZ(dJ z#gQkr>2KaD0PX4hqW6b_i`V|5dYqb%Mq^r|XS=7^qb&9t)^&d1=Fx^CS*Nmg3fODr-%8_Uk^M|CISPHC!c(ibkipLJ8m z6scL9_vKhEkB7lR)c*1Cd03`FLFKYh{_QAa&O>1Yc(Lx{3dW{)P<_aOLA6zVbA8TOK5tRagoDfU5(ul`>S1FwlV zyjo`qa(Ntb=es(pfImj$mBvB#ac5qirQ~thSeG@PtHI@{h+Zf|V9f^x>IAmzs>I}w z6&J=Sl}5~+T)oh}QLM2)GQc{79q&P#1lR6f3maj+P0w*p8SgoDPrSg8Q!hl+zUIrl zC$;cb-&}Q1zx?t)0C%m2(tjqtu6bVWf@%qTguR>|Q&4NO8CtKHA)mBB-!X+19=K9_ z0}PkH1=FRhJ6Imx;@cyd+3Js|rZtDAATTY1Aj4FFgRue4hJ}rMgwi`%9eqBZ()2#3 zN;{kR*KplD#t4@zNrh^T&Ixy-6+cXIq$t13{8}F8uhG_=kKrVz%p)awJ61^7-=N%+ zt8&yWPC3@F2lYA`9g{kIkJ*3H%=Aa1oz$|NCVQ*72)rp>T@HM$M0}FI*~gz#TIY8* zo+012uihtE`8AmRPDXQUzV9DRFBa4;7GUdD4+_F`KMvaEbrHd5^`P?7c9EXRfJS=} z$bl~CV=rv%x`aAl$$&^iRZZkDS;UZ~Gx92h!l-5 zW0g~9RZC6}9IRww#%YuVBGfrG1_8Pz_IO>gqyj43;mXCbL;GS>x&5k6hcSPSeHoAe zZ}L_2<}Ly0*%?#mlfOFIF!Z|2F>6Zada1Mf6NGRGNB5n&=5(h2`aIchvcv4gEAbS<)?DRl#CF$U7|Wu?XG+8hiiwL_yqgp zv*{>5jDJ^@>F5O3rGjtbFTtPCVF9g>5F;wF;6Cz~Jr_B2D3uj%E!rRqZL*YDKgF4S z^uDt&)wrRcx{D5yR7hf8V=Pd)ZUuAt0Oe3BRBPgxt(HNwEd4jHWyJkXCa~>JB>EK4 zh7p+EE1bRaA8dW55(EWSSh-L+?)~lt3w?11INY|ag+7A(_8cpmPkp8{FWO({Jd6iwp|{3K9_wEGseQI4ut7lr``nFEx$&J`+xqZDj$!_kDQUon|-yH z+t-`@Y!kX?lt$#}4y+P2QZxA}3|o=F&q_uX7s@X>sUh|nLI(6|lDUp=F&FHWV^lsI2ARW{Q@M>?5CkukEPN}8 zmMio_$lIr2L0h1>QT_c&6G>nA0!cBQ|AGU<1ikTFcwh-^+DQKLd2MBuMg+BS<2Wg7 z>I9kE^Wb$-Xy)xT=MfVjlQ**}M|Q>x*eU0GP>6vq_h5=}N0O<*($${NVKf@I=AwX11)5XRLX`@+8B!H{VyS4A-6)zk2d2I8|bL(fRRT z2?)dU0sn2j3av}|Gk3_6f2Culh*_gRtz&!-VMazuO_?t)yRj|%TFV|OX% zC91l#X@UE%)!t|7dJD{Yb!Fw`hi+wlv0@ROfZ=IB3_%)0>HJ>QYm>EIqssCVl((9p z%L*~{Wt;vfpu#)|90wiRg_kpSPFW!pMs|S?hn|q`2()`W04c?cRw5;mid2k8BSimI zTA%fW-vYX6`FBr?&i6JjojBR*6qe}gGGVA?{*ZC8KHNm-TQor zR#w1CTCKIM?r8fgwbMB9c{=o!jpeTqZPf7xokPUNK#Y;gIHOAZwIVy?A1{X>*2%5} zrm!KC_^{vy-E}(~kKL&UEnnt8^@-TE;oGDa?IFtTsxJ2KhIgl|9U>`5f{)-JfH=$3 z5=OMiltjOgA5Ji}{}}QZHpdG&{!rkP>#gwQ{^WZo4ke0P%|YngcwI2w(0%k37EH8? zgEn1kK|0cf5P1U{z|!)aMkvKpAjJi}XNZwmd`AVn6RSowfizX21^7jH7tz0IC{jXT zVWUWaf(bU|AS0*-RZWKOx*ku`p_^Qkw$>z*Ozb(<5DmW!Vby*1sIlu&Wxi&os9=n| zy7iGis?H^t)n>QJv-toRUEmEEefmdbNoXz6ZxCvt^Op_Tl^t_ULJy1XkZU~%pON)9Gv}iklv4TkRI7c5@7{s4mvx0o)91! zLn-v}Pl6LL)TmPnIEwbwk$ypCw^R^=dl` zGUpyDvL>JY?M0*!kva<8draovK;GSRYkas4Gq>Lmz9#dam8Jna*p3kxyK@rFm{6Il zglNos9|{p}-kNNs;*57354>==a((27Xx8o;L=s~e%lZp$KOq)qYsjuw{w;_!&kV+wVu zcfd)w+k{BmQFLS<0L6MOHtKK z3Q^^lrprWD;2rU#R6?bxDQPm6IV6%eX{ya0Ger`VJsM@It8$jqXYjzt zbIvtJX3sh~9RtioC~?i5s&dBsE9w>}XUaEWGJ*^9kk>~}9&(^7M;0HgG!K_$s7RUS zi@qoKn+Gr4Fg2D{_K>E&0vzvAStO?Qs>wazNC8`b*@#Rq=Q;r zgrNjv@ATU9akV`k=a~K4L9_pK>DLd-XZFNt^~BABT`U{d-YQxCgN=857q@&DWAd4X z$j^kytIq8hz1X)oY<_OHkMLN$@R>aGxoFST*xB+mrM$Kgzttw#V3)*ts_*K=KfJBU zm}(_M*9JeF6}a)$?YL%bB3+G}*5iWrv|O)u>&+E;v3otAsrI4$0lj*ua+Pu`jriJ4 zNCn~5T09b?^)0qK(HGN9J>keawG_-3O-LotO0^6vc=Y5-D{9i@N-9{g&Ng&J}#qJ=X;?x?SuVA62bcrp&448#*;gXiN)IvSep~_6Kq!X5A%9&hJ3O#XZP9N1F3lm>N znwe~dX$vS>;yDsirpcLHV3KYEsu?706acjd8Im>utqxPQ0Na>a;4EjuR|Vl3**{mH z%`0Q3oSv+I{u6KcMm-SO$DkS^8=%R}=4q7u#f6|*61ue&gW&&jJb6n()Mkp==bSN=bp(t_NqnfMei08mVuoDp(z30 zNX7p5J#6MpjJnrx)z8)dce%bG2n(zsRM3pA6QfC%8qWW*JstxLfAC_{)pm%^8kjD9 zJD&z7n&t_XX0+?DXN?kpU4e#d-ux=6a1KLM9&yA#yv6pZ)^=W3Ti3%6=@M^3+w$_q zEuJUeNQsV`p(49@9BOJZ5Mr5@7D7rdS(yoenJh&%s&VixI$PPPlBBXCy8q4rC@I1J z=-)0QFa+#7RaTZBqvXw$EXlCZhAT+Y5kJV}CU{CtpEz^!rN~hNQJ3J3%TP@L?jQ9BAujc^GzCsOa`NS+7ui{+U!8VZ3Ew8F>VC=}rPaP^Iied+&BB#2y_-$tYJSdy}exunvs6HD? z^BTSUnY{Q`A=iPOqtE>AqS{;&zS526ua^V6uL6|U0Eol-U6xEVjGQ#65>L*YH z?afdtjFfn*j&?mIrABP#{M^E(BtUxzh*Lpq*djX*4(kY*iHd zGda^8abZa1k-4&H&O)wH3K~!vY$UVPb3Vj#FA9z2?jldR3kAS z9&ioRXU6Qp7sQiko*+U^sikO1i9U03?vrX}LQ^VmNV)|QVYXMOpqiNq+6^_fL}PMX zIBy1a(;#KeL9+IalA{RGr*=*w?o6{2$+JZV`APlH_Odg&uNR_t9aI7&^oLeen3Ua+ z?iW-w3yRF=oxY+})IwU{*O6dh`s8>)N;KOjwY?F!*q?gr^Ul=UCk%BLup2$Y86(}f zkoQ(LG=Vb=(pgUywl*w@&BXyJ@tA>dsw(Hn4Y{CAyQa5ijqR$1jl#b1B-EE(HdTAs z4SL)O7um1u8hUm$z5J8w3zc%y@e8eH=*LT}$Rn*E9lY*cKj2#(+k9P5hE5z6*EGmZ z#daNelX`mAODQJpah4m|qh>0CE>_LeUBGP!eAhdBFa;oGn0eIs5KjDcj^asX|IX3Y z%V&^7(KCDEq@#O$!iR0dIdamu-*csU(9Dx^CZaGADPaF(#f2+ra$HEtD1;?#qPT-6 zOVYeiO3I8xZvqfiZLyo8sbdoBbuQ{80M5KALuTb0F8LSdCq5LRtt(N2vA@EZr%|t* zX)aD|i;shmhy%yA0BU&H07CiNnRvK#&-{Z*@W7e7kG*mAL`nVtn%PYUr|yU6Zn~E3 zrmd7dVd0Jy4GNqQBxV#uqMHT`7NjYNRU2s%qM62rWdj2)kWm;`~zHP13vk^4#5FPNH zV4#jk7UrhiNNZg`(y}lvUZ13!k2P(;Kj1W4ergYzg)w~K4kJguXP3^_-!7fDt}Uo5 z>9-fXu9%)iQ4Cj)K4RNz)#jL%`jL=;7}vPOF)wl-#|>2X7zXpQED9qxh{ZbxKHR5 zmTICKRD&%f(;=x2FiPx#5mKC2u`tnSoiMi23!Gpb1o5H?Lw1oSP$(M&Rr7Te|BT0# z&nR7#Cl53^&oY%xo$)}tPNwivst$}Y_)Ess=RiUg8!M_Q%9Xfkj>L*^fkF2R!_kg^ zMOb~KtYS8I))EH1y@rd9cExqt>qbzn8{6ya?Y>KH)?PKbl*n?J)=GN%0ps)e@0(Us zoMzZN=|SjGWWN5cm+tP|Yje}tcj&OOJFlAkvz#WO1F#zoxtv(B*itW&1iv3}6Nne3 z;z0S-Z0EI)0YUE97Mun{FJde8H1{u+bcS_R>EtdglulPp;xqMz^-l#|xcQXSEun<*g?oCuHXZwgHBLUQeT zVK+At*U9b73cS@FZE{Z;3+qO?TDdy*&e)V?b(Y3CcFn4@ORBa7pLPZBmbFEYv2E|E zhyhtx23uGjUFaTJC=Ylu;ALz9WN6X7mbG;UkhS$s->6?&wyVt8=oHtt^^{ho&Sad3 zlzip*=9TknbMSiDz5y%y1>Apf&)A@32IZ<+TwwBRydAS0@lKS8g6$qIu(ik^=_r3xfhZ>xwMJ zyF6V;@Yl)Ml5v2{kA~s{=&;-a$d@LF&#m-eerHjmWAb>-H`5zDzo_0oOaFP@?O4Om z1NbMnC&`*P24T#RmUIe5en3t5%8%`5->s+FskMy8SO$q z>VhD7*@2Yc;n=u@(yeoei@Qu3xp<`NS_u7k5M*0gC($Jm@QWvqt$%vWk`cHI)6* zkCycdY8)%2|L`riBLY1rtEG4zF;FkR3ff$WkEp?z)s z3DURy9Nk;VQpaZ(aHQ7ruNezVf>x|F4&=g`cz~wvfYqRIPoaV!K z;q0(1p7S}|!Ef<%LjvfcqrtnOth>CZfw=N^&)Ya^3ak2v=8&vnw6UD5n9Em;o5fU0 zwIFYeTo$9#!J}E*gS@3#7v7C_##M3NSjNsbza{-E;Zu(!`!CLh* z8NoTM{eflW`2>brerB8(k7d)?^>@A=fYb7rKUJ&rb8gkqfm@J7bi^HloNykI_h%M(`pWL8^ZnZ0IKJ<1_`f_Dkk8&7)8LEU9dPJc-_z9qoEA8|H-V53W$9#-)WDcIt7}_**vBX*< zBQp!*8G|wZz{jTfiO|yS{bA>Okn=e@h{oJC9dX~U!)X-JiK@xbci%Ow`G_idd@Cwy zp&_a*Y8T{;Z9!QvUoGuN$;|OfRRP|GzPbHDJl@>(MQ9#w8HQpl_;Rkqbo(hK`vL13 zRh0XC4mS{99<}m=pwpTI=rh@po7Sb!H*ca(d{biS^K0rrVrwl`#<^@20pbP zKNZx6(jP243LcLrJ2*x1A1J1?-cYP5BDm$64C|tQ>bc=;c(;(^di7 z%>N8$|1-Q!cei8rHaYBe*3NI8;clC~u)GmUMs-_&oXF^ZqT-W2N^q zX`sH+?vY@PS~ssd%)^c;3M%o3_fyG#&g)=ZtBSy$-4fo;gME9USK4_PG_OnQW543m zzv4o@0~CEsJUS6eCLzWkDs1zv`3f~$x zwlhe}Mc)4+8`|??ayAey_kj5P3z5aywZJ%x7&v^CB!hJ&NgF zBjmAOVC@g*$DcY9DDSCVTj60MjF_$Op$LPKLdr#mxfsJGX^fE5Jch}m9gq}+M$i;e ztW?UtSSv(rFO=dwk`?8GG}K_?JX0-4OxYF6$y#NWV7C=S=OU$b)!}7ESqmAKlrt*J z1B^D8+tN#`un+@Ol}+}vDl4o3iR}y+mdrD2D6-cNct*D<4h(&t6^Cj+*10L@n<`&) zs%v{O)&Gp%CQ%0>9=u$YiO0^s#KGQq_I9^bH>H{6bdkjR)D|vFfR5LN-03640-}I+ z2V)vS(qLfMQACO1W&BnHex9Q3c#yWLi-NjA9(N=J&R9hL&BVo@fV@g0(`NT&k#B0$?$u zSoCxGE$^}Bk!u00xU~>uBg8`p3W4s8?mSE1nCETf4GyDmk-mOGd;XNatz*Z3*7K}S znEUSn>7g#Xxj1`0=DW11c5c$_T*{0KIAVswI)ENa-{Se=$j-veaR{ z8q^S;IeF89K!xQ)0&CqX;@jmTYTW~d4~a^g3{|&`_|ULhZMrHfBVZt)BgN<jSP!YTD_*tfw1Xn&ZCX!rZ?|ef%%Q^3O~=nHdBCKo`>g z9CK{VZT~ywTx-}{U{9dtZLeriizd?kMx^-hZAN6B4X%H*C8jc4x~VJC=^UVZ-W))A z39Z`MkS1Pf*Km$;ID}m8rv{zG3MD4`dp#kQNcM-IJ(SGHeV4;x&(dH(m6W`fvYe-V zE@vc=USF8BZJk<)%gaBS?fi>ES+BWI{nu`R+iP{Y0AH}_dQUf}Rl9;t&=LYfQ27$Y zkU;k)0W?aMP>(7DYQ;+|C$=HEg}k<7Jg}Z*GX}lj53$X_k1v5u9t6Gsk1$8NmgpzA zpzaGvF9`l1r~XG@C=%c%AAF$w#FghNo#<0K3MW6Y)$$M1fPS~oPb2aeFRG;4iK$WH zqB_0BLaCnebX7*JG{QUHH;Y@|`w|RKHy2)#v$V%`19I+7k4A0{>7q%yvOgX-K3tHR z7Y7|t77~{KI9|-uk===^@rAjhp0bI~oR2?=04OSMiBxlq-ZKDwVXV=WKlohR0Uc z*;dwZof`+Y!ubN669fK4jassYcPgAy&EH9v1AR{i)=v|=C49GWBPDr@_ik)jL>6Dd8N zY^{#ob{(}c6^y@33KK_@t{A@R(iHPGuWduyM|Qgq%i4Is?8nIUhnUr{x~n{oo#ua% zHXPxX!O~dV_1sU{CvpacTmfRGj-&xXO&(ZFvtn`MUOj*BBg>!j78Rj+;3K@w3QsZ? zc$u@7U*d09ztSGt9#tzh^h<71E8ok$H|Cr9SbEO4%yf1cissT6CigE&$ep#r@cz8L z4PB7>+I+~X=hvVx3JV(ZcZ>9lqCCXn6XGp$m6CzV57UkfWsEVeNOwV5O^9AkC;l;$ zG=AVps$+bAIST;qt@Ba+O>JlGBIV2V`8df#Z*F+}`1{`Hfc4lr+pTZis(wZ^_ht*0 zG?CmW%9+HNogw3>sYOe$lQNOdY%7uM$nip`5KC+lO?Y6JXlxND57<05Qzpms1TKQ> zQU*(EVfD}R2ddMQMZy*dl#+Jc;HUtWoolb-a-^`D>Ro3xnF9dInPY(Qmvw-&UtVvg z{yL+a3rSBruxQ3uwQB;3(iLPnHiLK9yHwXT{p(T!6fI+$N_%Xh&aemS{(TgzK^&=s z{Ys&Z)so4Z6g5h8)XFhP%l+n7Ic?c=1A2+aC~jHQo*}1K2pd<6{})|Otu^C|f1d(W zQZK)8;+DZeqmy-rCJEaflo-uyAIhw}Hq zcwKD&=OIEk*!Gb-*jO*0ChqUd`EKqi82R(S#LQ9bn!A1bK@Ehv*iEd(j&kSga_M|e zQ`@iE*(tu?&wWq8W|Mp9Eu3{v5$ccTtw@c#QP*7CHE#Fu=c2oJ;i7(q?d-bSv+p6- z$F%Fyj!wz%>~U8bwe?0r1wWk4%+H@U_@T_GC0(7Fk$pq*+zvUF6TE3ZR{fMVGee#1 z^i}fGJURCR4 z>$1$fmfe)Tjm4F1rfQoS^sucaEaG2wScG0qklnDOY3N{`K3%~-)Zl{5ipE<+zbS+w zMDaYuSO7UdT@>whD4AoYAt2S3a1x77wQdp293jRW| z%+Bxda1M1)hqc%pDkzpR>2&o}S@kN|C12gFK2%Hpq-NNuxR|2=vP4!t5K3I42qHW? z3c#%O3p#Itg;}q*!p5#tU)Sx`WVYA>`^{{$4%}z9xGwn=n%Qpd#bmauc;kRn;&d%# z^`^Z&wrCz;lg}r(l(Fx6M)2$35{_xV{#Uw^rAZt0Edl^AS^PhzD-LEJ|6jV&V;zmH z=|wTB*0!v|{z>R6y-nLvoXP^l}dH4}lQ;kD(|A421@PHgF=IKQK!$3Krlj z*DzwHC~IpydUsr!H44C$W!-kSjYQwSQ=8dTer0jKZgwTQyyF zQgUabsj4YKD`9Br+0dA*l{iQ)=)vhK1vQrl3htgc+8qyai;u4G<(C-iP( zO4~vff-dO6+d>$UHhD^Qs(e~<#{^+t%2sk$@endOvMfsJ)9F*un6iJUZT9nwACa9LREV)C2xK!ez^uY{% zrtCrK1sV)Z7KG9VH8h^22-yWNRGuUW*`>*%PE8ht>H{5IO;&~KgB@&5mW9#>-gj5J zmDsmM=*{V*4XF#d%UQ;ytOncfs_0UB6MNS?-d^q^@L_(|bKS2D6x&g#9~=0tS@xC` zbrn-+E=z(kyhsn^6YMKfQpm*j$9pm#({rgdhAInO{D}=*TxjA?2eU8L?|uh-`!yee zOBd3Q5QJyI_yY;XZ3;u(D$5_lzNzd@&TK|3YHp1JYgby;4Dge^Cl%7k4K6eZF?0XI z0T1QSfWmm*iUa{f&A_(q&02fwh&OiBeVT=t@W z(KBP0GJ66|dgrh|B1|-+vbZ-;jV>R$O^KtFalD@6(yCV}Kt+8{^X804u(_|9xf2lF zz)**qzCP>ke~vWMFZ1YvjQ}NFB2-e#EO-H|nR_zCqg_5o0qvnm1tDu_$;az4UpUqB zaW~-9-mhW_wJ(?`lT^leXJdC~!#`>huu zGF4=$aSJ(y=8Rlya zID8;>c}eBbZ~lCe!vd^TFQopSgT8IujL5Dhvgt=@y|4LM&p;mfR2|c7%aK!b-9iOl zKZKSAU+AHM|Ie-nsyhdc3u$joYTg>YPls;B;~)V@6lYs{04_8vzTPjY@X{rvuxfo) zEnw5=T&$^oQN_(=-GdRu?~goS8mv}AW+6(2@v@2)7R)K;#b5Z+euNw>xP`-ImkSWq zNAAdF8dn?vQdgk4t8hzuA8XWB^8{PBoweMH& zyr-YSUwN z(@*#BPfCq7VbV+L4H`xlyP#{9{Yu5w%NE*O34e|%#R){)=7PZ%#bV3m0>f!@81`&E zw{gG3R!H$cQ@lU33=8BTCqox5WS6Pqi2(_Th`)pff{4&H0HTmt&dV$VlA*bS_sq2s zL|YV*&Km{L7!I1CGzgtvxM4V2i*cz~(Ql6#Al|zk94M~o}d${eD zGfpDzG&csr)iWkBV!1K}VlLIPx|{h9;D$`D!zYV#gQ1YhjC#twrWK-IJ!wKLd!&7O`oCNu|t|?7-5w-+zN?p*HBQOj`{$soF{#v3gqq zp*ME4v`(}#IxnVWX7);FRUeG@60S+(Z#7(tXE4Q{#kjTIr?g8+0)y)pWZA^Ths5GO zOeQs^oc{A#)DDv}%ktESlo1@g>l0SlNG65H-&8exgY4|}o0?8r)xuo1Pg=ocRAvmw zI81rh!vGF6q8KmRE)zDD$`G0<%hLmm=^|V7iqZu{b`$~Vi+dMB6iZIU_?`2nxB1Qt znlj}5dW5?+S})~Rj^%@D_8|6uImHXPusj)4`kk z-GYjDt=7abe0lSrP%~ucqca*~3n#S91@QO|x0z+rq>jpg6Of{jf~c-|6N-FA@T%PC znsFmp{6Os$z`j1kC-y1FzBM!WM-M*Z*!c@MJG<$XvwY55N-iHz*OmPt+nmU*Qc;@d zxM7)^+9FjoZLQ8^el=2QA&SWn>jEIWD9$QcVJUS9(HZA!hFvo&xfJ7P28DE=L#1aI=cx@Czobq^laQL zwg6173<&M3MOW=vg=5*$WIPd@3+VRvI@hg~_Bu=U^6Lhl+0Y}I)PzYk&8uSRop8YE2AA8hIU?Iuty#`tmiE*bOCdlVHihxg z8!*nOgxaeu6X`pai$@s$?8vEUcl2uk{}jB^d*uPF?$HCH-(A=2^a1((7(m}4tXCkq zncC%V_gsu~(>|;rMb-Z|!;3J0{d|<*+Dnzc+ zEJQBiw*Yj z%voZ(b~xIUgs$xhr*yAWSQ{5eQ_>oVOs)!fGzqve`qwv(QfHYA`#aB8=Jkvjcoy?Z z`V)?gvhlf7(vEfkc-Om7O=NNgmiFFbN>2yZ*7E|-jFWd+KOK6}x)3$&AxGxQ)y{)p zrUM&%Yf&Eu-0FAMY7Zuz?#Zj1lPeeW%O94OHVpud;@}sprpaF$Xp^6z3|>>-8AqX* zlE2*XnH`2ScDEd=R7)#8pk7b$hPN68&(QV!eUR*J?E5D#r8||(T6R({J2KyQ7*WC^ z%sQp3J9;Mj$rF|PNytG%nLX=XwLL?nAw8(^%zraO2S$-i1P^1(I=k7W7dn9c+BR)6 z%-qX>Um>nzGju-~k<;21gOfAS5E+6^5T2a>YndhWXzT%aD4Xi&iRo0)X5qpAOR3`A zg4+pe1(IxPj}_nW?}HHfxThkgCq)Yx#U~8-x~XZGXKN?$OO0|n@MJ^#8?@jwysAau(y*1N) zS`2oEA=3+7EdcB$(;eWs?INd#d2xn=a~t}T`&v7!w>H41;};&L=@oTur~O@ru}0Ci zHvNzDJ~V{aQIldf!^c9t=e2TVwvUZ$0 zzO!$MmP)0!)bq_TJ6_q((;_+c!SUvSskgr8{l1F*&p}gE>5piX$HK$YK@U0lEw6IU zMb4i`M=c)T>mug^OJjXgB-PTnvN+w$Pa!}7eb1l$LE@ix$dQSVAtudrFCQtLjt_ERDs&$%DG z_c#yhu=hFwJm|lxi$fJ*?rw8^+g)zjJbtH3_K~N&2k#2{9dNn3+daiM+wJ6fUa!n4 z9uWxiqMwtp8uK%Gbb33yP4@$Jyu3}{N7FCj_OEkiBg`M>WwU%bbOe z=Q+NY_x2IywBM#x?KZxxjXRs|KM9Ik^DpWy2a$HK(Qx^i8Xy*EXnp>B9a0TZe7gY}&9p~{AOEu zB5ML_W+p99id3rc^Es2G?pNn|`G%#kr- zOmsO2B*%%`V+112b`GbDED98`;MHv$;a04PBjwzoLnv152ZQX!NF=oG#=U}1vb6`P zD2(P0B?8$W{Vv};r#G)E@Tydzn@@a0M7>rm++DT#V4kMun&8#t}+DEVOjxNLB0@ zJ*zAtH;fVo1Op!0n#FM49wGg};gepLa9BC1^9*MvnA<>@vxjae5}G+-x{B;n*cK9o z35a+8VICwz*_af+IwrXhWM~WMm&)%IFlX#kv6E7he71DBoYe@?13I&NET6__Y zr&FAU`l-cM;mS0Vuny~Y_|^c1z#t}tBPx~#e$(jh6$1fUk@`h1!*cmRq|2;!KD;w&jAD@rCzBfO<;GG!bGkUgd|k3A{L9B*-r^AQgQDL=^#17 z_v79!BANCCsYnaD2{1Cjpe;qhv*pUIB!7mprHWoIH{CejYnpXi&vHr1 z-)j+>GLH!^SvY-!AT@z|f;TFb_U8&G3P%&SX$fUprP7op9i>ga*l1lfF6{9kv?cjU z2;38;N+nWmDciCY?l%)9es>{ZG?z&5>@J0Ww|Nfd9nS;>PbjmD{5q}yvJSDd zMd$_|u!SH{p4$llkaGCzPUrz&sE;R3-*8{x1nHt=Y9azXZ?gaO4#|CpKLJ%&55VU|6HY`yn!3MhP0~}~CbY~6V;GoYl zNY&H_hg>W1Rm0=@+8zC#()Tq-SosQGeS4BMhdN~syz2fAWZ(;UM#t56LUF&hvgZE&4(4l{OojmMpdXMvq`aWy!2kvuP zWOX~Ud9cpi(*<~MQ{N{OIdz2CF&9-g;5+z-*fDR{C*l+5kX~FF*8w#0z#)b|xG&qw z0mgxRDXf%7tY?^bE3Ys%=8lxO+stuXQ^KPRf7-vD`0F(2@M|0qF8?CrBIe+}`XC

lJJ7E#9)?H|3++zTliOx zsSUgD&)vzPXGi7OA?OHUU>wCg)Nv zU-92OX1|xDzW1!Y_x)xT+-q5N4zpVlQcHRjE0nJ4_*f3G4Vb7pe%l+$M}oB+D%yJX zedcy_<4XXWIVt7yg2z%MvVf&b5vHvj=XzWdp~k(SGEWvlyStXav`F~0lm+P+fswWlCTOHesUY6mH; z3~#=&bbX(<(INKUbpGDPF9$U?!J3=eT))=Nl`fmBy3AqKrKJ^ad9~Xo_1lQ5*W8@V zKYZN43E6w&nNP(5i z@9pQVDc#*F&!Kvnn%LW#fU$ZsLk(AE;dnYW{Fu9%gjX85xr0`gm~F0cAuu330REI? zJPG_2s(t}=7(t z)txin;ejj11^QiPgMkyYu>u9t2Lt9ZxM3QJLqA>M|J5&zEqxQraM|9GZDH;)2#b(u> zBUOf3ZlT<&#$^xY%O2Y8p+%<4kv(R!d`YnaCfCQkxB#Z5-Wp2UhOmal|v9&V|Nx3Y7 zM;Yunb1pN!tqS(U5U3=|)utUVd{%fx%qUly1lA*1i_P_usgKO5B1SS_O4$yZ76qRf zg_8)e1W87qq(~@9L%*gogkTTYh(m2Cb|Ti?RfGFG=PA zMq3#wuq}dBqH^M+pfb!{I;9={necWgVU<72yx|6Jhbjz}#F&NBY=TpFcRm0%+0VW0 zWpW*1x+eADWHIeQgmFTse_f%Ay!XrfAvh|}W1m?^Qj!=|9$_pi*LyO=Fw2b2 z6*fy&N07w`)ufO_r}mSV``LW+{AUqTh%snWboa|4WA>i-?P#8RWTlqog37NV_OeK%rbiqnQrA0N&4*g;x#HTrXkpxQB@ zydjV%@%nl`RUYz!Jf&BW5mBPQq9h+v5EQDcur0%(z;_ zyeUoJtO+(r6YLufmHYH}>ZCJ8i0L_SMpX%$3TDC4`tYWE_P}h)roFZzUn`7sh1A2E zDDs@4!1}JXt!wcLf0^{(6<(4Ln_zm2sB^Ns>wrMTU;2uSQ znK;DIHOL@r3gCeEe3cLc9(~G+OX+mSLG}vH3p3&Ez%-s6GT>;&T(eN7*<^9Vm~--O zg>;*?B4KKsIr0r#YNT4f&e<&+k3~e$c51fdAtf0XqWE}$G(2KOl3X%j&PHH}RNGaF z{}SdD8FnlI{_1hzjY58um9r$tQE7`$D-L9UXvQi_XMq__axHu3cF?=>^~$A>1~2B6 zp`aYTU9oFuvOexUORhzSX(Q_uoJyYTDlV={5$$n|Ro%!cU`|R)WsIXvlzM2m5~uZW zd?_nlPMMhOv#wcRrRf;>Kl&>!{@2fh ze!!g-&QQbW7#SV{jAI`M!Jd4to?RnwaK^$=h~m$)s!jpPQzqu-0X#gB2`M+@B=}<# zadNC}j=pxDc{2)mtjznBh?Kc_hs)2CiIkwsG4+NpCnb2tnY7%Ruc)I&ZkpA}Wy|eE z-_Q##XP{>V3UMb--{1=`XDKP`Z)9pF_L8la>Xs*h`+m5iD$|B)Qb++j+8Z=6BkD-O zNg~Y9S9yA}R<4JMyJo6c;U5&&kHZwkg$p7-SCVU@7B9a5zr#IAM-L2%oby1YN%*&N z#)NVeXleLVQ&SGfoE#14vbXD?^rH6%`&a{w#4;*4apV>^j+$5bZT)J3Hqp{)tOh5U0IoyVQ1RcliX?nWN=whM^YKrg}gA(<4XvlxIgB%>K z)1wMKJV|^^F*Z4cA`&}&;y)EH469FK6`Z6Rg}%2D`NY)Fr7kXpAEvbQe(ZkxzhU{) z)M6b*K0oEd)c>}P5RIqVHuxxJ8v<-XzdX?J{Op5t(5_N2P0&TH1cK-xZ~c8>0F1&f zT`){*=9QAn5DSA@7w8$P5f?N85~ITa4Rtitvv7xDBP}*CH`%}u0vP&^uS-(~1Ksol zird#2(;u!1=B0w{N?;RA^4}YH#6n)&AY$ZReBO+)9jpcYR4P7J|IcHW$r7DNn9Wgo5kT~EV}U0W4O|Kx zB-cL^*CKt2fQ;x!27TPClt5SwRSHd>XX!d!7||ZMZM9*e9v-)3b3DGM5`JLS2m^`s z1B+rApHl%JvSPt$Raubh;ZG_WR7?7Xi4ab7hwy@o@0Sb&o*$uWXM%HroO2^86R4*$ zEHTs{@d4Qdr^)Wp=GFveMii;bCSCp60%4otlzP`ivm{nUHq6dmI%EQ5MsGSvr)Wgy zcna*Rb_Ep6F@|CaVKzH_2>ItT4VkLaK`(}fYdr5>Jx4QEwPG|>hnhWN@fkpAMb9!= z;^rtpP>>2`E9%VO1d_n6oB1+QdL+DAf;AWrbF-Qi3#OJWXha zlW`1qMp)M(MhiA9;@~$3S0@vbW%<3py_5ueQV0*A;~C6vG=4&s46woWZ3r@Dn}GtO zsP6RgX9Ju>MQ$aL!F;DFR6YDLr}O->X~OK*gGZ)gx(l~+hQg4te+_FqM`K3&RzjiE zhf(h7B$qX)7b^MTgjhY$b%m5c4d)PVU!GhW6dso^1~ZCt!B29{2-2fOjCD2flqOv= zTv@4vLlY+G(s%3ism*0T*PZOShnB_J1w4FRWLpqy>jsU7v=$K>6j(5z4;etG0)raE z2Cjzwv)wQmsSg=WW@NT9;D>x{xf2YIJVy}AKm&3+;VW-W^M>{QoDvj!|BJ70j1i@Y zww;+Xwv98kZJx1h+qP}nwr$(CZJTetAMeM_O>WZZbnV*JyVFS}t7`9B>mSX+S)e9N zK*ZP})ENtgIf1>Q;{Zx2=2$AD*@A^;bn?hNlTf`<{Qb}ag1W-=g#b;?jvlxYQs<8H z=vupTS6=3@yq^G<679r?+Q^776oSX_(7lpYsWzNGO3r}HEz7vUojV2{3@TMwcv-p) z@YS27i0CP_082`ud(WjS-gme3O$d&5@a1V2UJ=A5na+J`9Y~Onc&Q2<>b;T#_ocM( zt*1i9STjMN;+$0|V_kC>a#YeE&E;~ZbgR}CKQMwNzTq>w;4a(*X>F5jd0z>(MzaWx z;9`4FCaj1!H>_0TE&5EjQiCTFm3a4`2nW_z&e&6lW|Tkw$`8_*4vL?p`ILxmUx+>_ zF*Iw<&_{s$G@Z6|>&i6&1_tb z3^1&1gy+rT7b_s)k@0=YL8ua+>W#oD;Q4TX#ExgaNrYZmcUxM(v)~HfNF*efYzgd=nTi-kdD zKh;u@I~QwCB}) z(kf0q8|#Kj^|Km48unDIt`KZw1d+h!0tWz)^=@l6%gzJ@I%e!}s?nA}DUp}@L!t;4 zF_#&sg0lgzwI-}_e6q^6v(YLA3#qk>N1f*SX?EDei0ayO5$r?j7PR>=5SE!_^89Gp z?fIWO3~N{zQZ4(scC}sB?GQ*%z1xQuE2z z{IUJI+t?p|!rQTZOp=y6yClz=QJ(f1dG@=C+4&l9>9Kh?{Ytplf8P3D^4MSU_)2ma zN^&_Ws|mw8$J>E^OyZZrxFiqgD4Fqc)!_PSc2$3#6Carm%TQZ25VPKs=`$$YmlhnX$;pX60qK`F=Pq37ZMu*Q&RH2uqgz z@l@r4%dh&eN{iys&^)W^eoIAFv3W$Z36EgKlkS;4{W$0F)S;nZq3$Rz5;@_W{C*AR z@3enuQ23%Wy$Q$B-2Zpkz*Jfwr7Du)5Zk*ZVxf~Qo5{VyS@TT>5PFl-Mw#9AVSBxH z9%Mq{>NWbPWD|DV-qd0Dv^JmI>ge^d__kHF(1mgkqq2jn!klyZULkBtN|TmNJDCmm z2yhDm3@5{*qg<(j3qRihUG1@pxU%h4x~|nV+u`~#qQPNz;e0c11&^w7!^QPxdDqCL zU`txQy{(k>=IIQYY%e^on8{gts^+G7O$n1fKdXQVyO_kn(?5vi@-gm_f3Gd$c0+nl zF7|^@PDmXP`K^2QlKd_7)>Wjnbl(C2-Z`Uq!aL}m;b`?OeTE5xj-3~-A#x6@KEbXSFbWi znRL0Iz~}7mv1zyc#6?}h*^%D232e#J_x=~V_+7dEsPy(sbXF(rmJSEkl?3U{#7hdv z5u@{cHxtEPY?8H}{2582mD%Ot7czPj;e0>BWXUyc57Y8CkE>5%o4MpUU=bq2*yi|P zn{JRg7RJwt@I6k6*q1xgnET3joJrkMSYoyO1!3ZLsw8=6+gqS8vAtaV-_?1R`zZ<9!oh(=evfohWrJ3xyh%gZO0ip+E&-=NiLy;ONtJJV#C|-A5y~&ttLXK;Z8L3sUlRXAQLY^lgk$@@N z$*~|1lQ_o!O<1S2lsM@pu#uJMItSYpUwR@vha_ml^t}P$iuE$H3XGNwn!&6-;f^a6 z2ZZGey^Op}YD4j2tF=ssy+7^P8-L3g4B2sTo{lKW!~^GYk*(+B3OSO?j$x>^%KDeo z9JFhSqwehawTf)lab40890U#)FCNn2_roua^4VCF$I=>}T{)|z>!Yj(Q&&_43uO!@ zN-~rt4=Y;zyU|k&?;QED;29(dw)*;k5X0R>@zN4=&g^-7h>w_uAibSv6u~oqtFa!? z;dz#8_ox2aEEb7vQUY_j7w#V44S*A#XU{)}! z6V6R@CKGa=mdbW_$r5no-l{7q5V55TGGwN}UM|*1wXMH0cbFO2cV0^(Xsjg` zJXwnWEtYa^9Z~NQxU3{N)z1tlm-zB3PkCTjFNIwO4;cVNDY%PwX}QH?iDY zxs+M8Q;iIf!O&W2INFg91Dsme^+9w>ndvlt%r=|?-9eKESwKd+9%UnwKaW_gj> zgL}ag<5Mm#n}A6fgF&Zn)tU|gEeDw?w}ibZJ(oSM_|vuTYFQ}$U;FvQkY*UcKI9;m zW@Ixfb0^?QXkkPlS&tSCfgTQV6lVpTyfwote-viBdW;2DWr}fsn)LXNeZ5T-|^hABec;#J4&+ z>2{KYx3SnRcz}3W0Q5RY`ZmuQncc}fhMrthQVI1p@Yy>7i+F%ozBaiuyhdaQ-ys3h z5G?5WpM;r4SvnaJtbZCqR^WQY37znZzm*Zpp)EENM2U7S9h;o^yRFwJ?n-BZ);zBr=R|VdO>m!_Mf*0X+9!-~CDRd{ zHf4yJM1^i{Q5<$3e8g?Ay^1{LQbA&`~Od-;4*Nkw?`zl4?Mi zLs$57z5A<^I#7FUg=tnMu;Qrv8xba>0?``-*d#>5g)%}Dgc!ih1#Ai9iR5tYgo6e{ z(MHjv{~lceMlVG*YTuL*m6}r)S`o5ngjhO{6_2Rf6gcKizfrhT{9|zqC=ktPk7}(} zn^)EMv!pTBKe{j;)~Hiv*YRCw=nG|z0X1ieHwOVARd8%Cpy|7$;duI^ytib}&GHA3 zNqv(uE&K2fKjNn(;$htnJ@5}S5Tvg^5S`Oy%kGtp;H?b)D-Jm9VW8}+iz5E^^AQ3Z zq))D^9)PFvUH|0^0{30yq!1sZ;{bz(B94kEFI?F@HnyY*p> zovz0&2^m&v?zPsZg5y?Tai}6nbuvE=Fk;!0V>}uj-aOUz*$!QJv?zcJS>UGm4qYVH z?YGC~*1b=im5=e*Qs}3|JQE9*?uXp~FqxHVUbT*I!i8${vKCK4#jVd$s&eZ9XzSkp zu4O&Yiw8KRzX4MgJ~M+dJ3Zvubzb7nHJcY&ZfKhOLoYlXo~@5(f`T{k(p)t+IrO0> z&>!<9Ft&cpvRVjxPF^kt_YFl-c4t*+^Q2dJ2>a#l1;Rc`P}YOQTYLSaVUgQLwq1W{ z_+Gj%$L_yC>?Yf zkM{cgHrjoV^S?uggtGo3B)uOlb)V;h*R*u2=U_kQ_=pOKw~YX&b;6g&i{AHBPQ-WG z9xC1KUYeL6CYprvdc&JsDn8np5EI(>*!=(>+z-w*V3@mK0*k-j&y|uq2As*g9s3EU zd>D}9O?pzN$NV5-8~~G2GOPz%1Kv=ElZy0%oj4Vo*p3JnCC4a*==l_BzaeBccWP!Z zR0JB-+;-lIcirpoTVemaXk*pb9z2|{$37L%Pc(s%3eOb9%v0Xvunm&fyv?RIFkCbt zgaP^-{au#0k4ET7Cya`p9fvDx`~3LhNS&4V@mq1B+j29}h1hm6>>zTRH{EohN^{OOfBc z*H1I(As&VUoU++mC%}a7*U=MpbU2xo-v?E{15}6dl%BpCUS?!%R$7`z^J>D%*eA0B|Yib&8_OgSW& zC#>^y4iA@h@sR&~WGn`vVl1{*b|1N;l-G2fdo48)(#PMpQhFUcnZBDpq%rAWkK5p& z;H4hfNKph7KIA?(!1Zdq4?j+X!2Dx_n(Vr>CE$^e`(8?Bz29z0#pUO`qZZgqd z7GX^hr7hVqx8V2c3Sl=0-Q&h|6)v@%)n`qoLcmtBMIEWDY1J$nc;=20H2@_L5!2e; z>qoamdT7bm-GjQ0S#4gpMI_*3x!|B#W(Y_&t5O`^8_5inlE*8OE6|&+tx5#%qKp0R z&n!0+D>Hy;@JBV_*tXxZThy+JlExCLK)P`Ck*q6X&8CxtqB*IDTSKU2jVwzJZ>4UD zxpnckaa)mSR>xNhGlk~!_vcEHIF1FNu6xKttW~9Io!Fkrbx4~CAuL+ko?eD7S0&_Px9bM3Pe(#t{AN^*Jlrhi3T5S zMXn18H63j8630Zsy75A_)jF2)v&_|YFEl!4T9*XyPpU=Dm^r#f?D@tX3e<`BAH1Z9 z8UQx|%UR6L*3wydm7yN$+mBPQSaFFA4)8dv;!R^wOp(wNQPV#Ha*GW9kf($Gmxk-UWw2GJ90v~;#G>LOw?`-^~_RuW+S+pIc+jQTvnmHpN8$z60+I)ue5Ay=0L zc#|NGuzQFsoETdmV!TCnV7LxfoqnXH;sY4LiAgiYBh${f7q0)azSCGtZmuku{ADh4pme0`bs6MW#EkHPP`{Y zM9j_Nnr03~Hk@4n%U_BEt2XBaoI4|b{H`s-sEHL`&b1+Z{AS&O_9pmn;alPv5ZVa1g=6$q5fg2;<4Whc^{2q?M2ASl2n&Gq@4R6zgO})9Bt}=C3DxpV@Kk8VT&}NF}CBsRzu}l zKm-rpIM9~I5=)+d89uEfg6Z@mzDMTRawj-fDfvUM!KDx;1hoXJ(Wk{9v2uO099tbA zXA!kWr}pwOP|cxMRT{xVlw{*RpRGwqENC2UM8JHY;sQWvGo^F>^u9Eit@-3M`X*%y z9HDk*I)B8{Rxzx|wgipYRGqjmxY5~8rfGXv#a{o;XHx5j%6}p6mtRyd2OsCTojR+V zEL=pz=7pO{DMTe%JOp*7+j7+`R}D;=Go$%!OQT?i43Dn^gBHFV-kd$3f6PD<8&|z^ z?6|tGIv8d?xy!pa&syLNB=Q~2C3HpG{;a+l)giZ?N;!|?G-ke{7tEJFT3jWtmdT0? zB_hD1tZgC)GGB=k_n;w7~J{qg?!?OlPywnx5X zG`erLei?l143|cJA$Pak5sr7a^N-TfvA0@>WhmCfr zzJBHl6T>0jm(L4-f`h49QQ?MOk}md+tt9b>d^g&X_^{$ZPlZwIspIe%F4?fk(zzpD zlSePI_@J39d--*;F~HK|o|$UqU17uyC z%85pqNsU_ymsRK~v2MDtON~hs0nnhrQt^UXt))e(7`v>rd!uoLtGlp94nkPciBmn^ z;-3mvR5Wn{q9$NWRUiu!f(erewQJ^B?bghEuo_av%nUL*)T}oKIOxae7`o;2U4_YU#2~lK90(!;mEmU zw;ItwkYb0g;Zaue8C6D>zCrJ@c*wm4mHO0{#cb1h(dy;b`|X1K0`M z+u-DF>q)b>pGFawwh3r0O5l|$~oya1t1N-JHFQEp3NnU{V4H&maUMR-T~=u=dFsYz!-dtI8aJs-qEGY6Nm8@LBUag8BFn!VT+S zL&q@R&E)(G84XAE`n2705~q?F4GlEy(sG{&*i^%nL?h{KJAtX zG8_Jsaa3$e{>=+8DV>1Q8qh_i{8-KCs#3p6WZOARJzKXfYpw&Mj4B2J6riZU;UkB@ zyJq2Q=(TPU(%{|+zlm?4_r_Z?W|ye@g7zD2*g1@S7xY? z4WQ-kvJO5UtJ`%N>s=V=X#sQ3OA9{qE+v|~ua;HvJuaav97#P+C7Kh(6PsNcUwU_EtYlU(b&d#GyE^J{eGMDf=N^BI zCAzBbs9*>s>8XSR(}>;J_ehZe&yRhh68(4$eWT$mJk7<3QJj5sbR`JV8vNM+8|xLY z2B>QponI5^s?AB=$RICT^EZU3b!irIA@Pc{zqzEt`4*txk#K3{M&Y*qU^Lz5tsO#N zcdCU$`6$NNNcZy5dp0Et4gnK}jTZ|t)C2Tb+y;8&U#?W1r%I7YQG3l3d5*zYQE#>B4^pSpa9o6A#U6{30Ujaulog!{3ds;d2?Th;d;=4*6p%)HXKEY#p<01Tv`SHL>>B$D&z&5 z&(rd1d-v8B2Ak2@Wua`h@a*`|MVfK*#42ZpbX9SgKmM(F8-m87g_M#^a^FEzQiqp*X@dc(EV$h+3~!fI!SbW?$yKYpk9k4p~0 zONmbr#Jvx^Qvg1^oNV$H^^xXDKRYe;E+ry7Y3c+Li>a)RKLIntP<}v$u8f_|`;G3Y zO9D>|o|VeSPR|tlZXu}2#_M5Y$Ftq`Y^=ZN2G1w+>-0TmnTI^orfX$KV34}7$2T16 zbie@2RNChhjdc+Ze}A|t1~(MTh4(9}d!CY_i@V6hMel6Rzt#3`Ah^}mjcBUcyso8@ zxSWPpSps%=`}6aaY0)Sr81oXIEac(NrC+AFSN!{ivy>$Ir%ST2LpBs7k{5La}sW96N z)H{PR0zKQ>PX{N!s-(jQAYj4|19WS9J%JRS^aL04G`$1-bz4x~`YrwQXm$5^7198v zJw5$>I$vV(WhJyJoNe8aZQ1d@for$nAp1vri9dy{se%KleW2l}YNP}YY2V1pXYDTy zRyIw~gW+4&_dSl?8*DC|`OlwQADU`d#~-}Am{&L%-u)gnZ_7QPT!_klD!5(;f2MlH zj~{~6Rw<43YEpx`^iiK70lK-WS_{9epFEaI(4J&y&Uxz|X}3{nx39@(ehh!Z!Ppsn z7%|42*m2*#EVdfkzj=EeNb9(%C~jYo z-|%R#y;Nn{+;C~N-W%?Xv#CMdghSoY&8J)BZr32wCCV;EMhleT%ywOnEIn(?iERsTBC}UPBl*kNeI=X@6ZIe4#&2e512JC>tERKc5i-j$$a@D*6)%u^C3eh)xE7R2g{RsDChpU{L4kvqe;$p}m!J*FBFXWo zqkH#)`cwL*w{IucS!)CLSdTY&ljFAeZ7A*e;rw%tb9bL5Lw>i-zk;jzp~I$Sl3`14 z!~d6W7S~?IVV3s7w<%VY{+TwoGAR?|H}l-Bi63!i6U?=oh7U$UuQM9U8E+~7LkZpu z*Y^X;59S{bQ}8_ub&LFY!k~b}6YXw(HMh3yUhZ>_wO?k_d9$Xa{_$+{M6)(TeCf4v zy+1rK-$Jod&85}(X?S;ka5WN3-dYBH@wAli-^{4x1ibA=s@#mQ;gSr6Qo%cjxrG1^! z+*b4k1O%t{j|6bt0bGtcx{yg}lu2&PseDSY+>eKmZT0+~`*cC&QFU$id>Xr78o3_1 zJ_!eb5pBIC5d{YT&jLaQy6ygWE7pJNC){ut3XAwV9Hs>S$M?l+Cgnk4q;HWEh;X5Y z9fbFh1867X43|?{np;Kko3ZqJC-xn<*@YnC*tEfJww>{oa)@fNF3~dVC2`8!FGUkV zP)g`IaIk2F^L8WqPqLSrtpgNLmy$`N=uXHEC+GdD?&8^_dX}~gg9O!k9<3C|SFbUK zy0uvIyobcS={q24*R6jCu8$46?NYqfJ(zsXwvSG}{T~a9(bSu==Ny(dfix_~@3$`z zpp0q>u8-{cZ9q%=^a^n=4^cYT+=F_z_V{CKBg)O*We=-XGvADet|r0#Wk_V!RjEuo zYrw)U|HeOEOq+V5oc@#pR>N2W)I%BxrKKn90bXNGd)i0uwi^xyL(<2x`RgQAR=afW zXaCGxPFYra?L8i@#k+>@*(R1(`%WT!4vz9ZppLToSmST%c$N5uc<8)O_-5RAxS5%s zKEw6T1!U^j-LEK`-R~__?7B&NoKgNJUA~Xu+f_duu2c)`BXZ84XsoP4> z=?Jgjeru|5ZTQb>^K!VN*OqfJR--jkl>ND`q-QRzTY0&0z3r*b>ZE z{f*!vyz)DoG5yystM2)5!*@!l#*>P?@13!$jSGRfsnVE zq8=U5+Xu6ux|d>DL@$S9*}%Rv@>WsF?}yX%lYm~d0A4~@xUi|dwWpnJ0oFQ@p&pIY zq2=PAtu=ChAD{n8G#OA}j<)nmGRcSm06_JB5KX$+>)HM<8Wq(o8zdFfo&yY@VOUtA z#y|kHQeWiw$q;T_8wso!lS%UqDEWcpWu+tLZ2Z(!F^*<@A%6WLo?In7`7-%4>k8wd zAQ*i-*F(FFo?hYPP~q^vMw!b5LISI$t&px04ri`ty^f^jkNL5wnUidX>nz5XOpoi4 z9Kk$*b@$-`@GlrAuIs8D6CcuB42IY~WZ5_|M(EjU|BBIKHSq?J>Qm_s@+lOqe>Uq9 zW~_N$W_U&1AU^c5u(InKr<9-z{Nf+rIN~8>SEP1C8U_q!lQVI=B2yGZUR9~C zsTov@ite~IC`ubkkmLoVXN!Nnr<^bLKB zOQSUe?zLIcpc})MVmRn&hssmvSjz8>Uoum2=8UPWoJ=@8EMxCIS=|+%h#SdY?d0v+ zL^x!58>o9@6?-$vrPIr<8`0tA{T3sG+k;AUCo}}0iGrDkdc3w{FU*C+d=9j7ZA|gu z9l^%+Y1*5e1{7gQrd{o}Un5R^T=9esCt+3BcL)YJnQ!XqkR4&mS4*7Jc(fErToXHo zY3P6NcTXTtS63a8e{eRg62mRoy?zF}KvX$)mI>~33~o6gXsJXDmb#jNK9%5O21f5m8SMl33QB)n#DeG?-qsgw178XzYRqd??oY z$fy$-MeIbd;oiAJqkQwHx0@SH89xa6v^3JQM&Q9%tZI($4dtsdQ^!#q$ zlCdcIrTS89zHb})%A#kuM#d$nq44^*OQMd)p~?Mj>||T-*ZIvBq_O@!du#QU0LSx{ z>&KPH7Gz}Xb>p{z#+&x$Xqrj2@=|$>YrnL&(6qPkw6`#^{S

!_aoA0|mzeo8yYiQ`dgOUyrJMu2&>3K?rhu~#!+x&)oY^1$O6@UJVBeHHJz+vGrlPhNiZ5lD>t#q?2z0*W`!zoo> zf^lNq-=?HqbW48tbq`bBW0M+VC^td{x?ct16tuhPSts+%K<#Hn_#TTqzmdFv7y+;z z&B`F{UpXABzr(chs&L(pMJ_{6ZaZjRE;N7cv!l(4=)ryx{KekcBnO32cBtva5vUJ-i}$J6esPTP5? zHrCt);rok@nJIKO&QYbx-p1b^)e?0bGydR<(6=yDzSae^=g(9d!3OR|dtkSTfEn+A zO#W{-0R5 zP`;d6^pJdevLcclF^LFaJQ;i)*}r^y$uQDIqP2jH1OnQ4)O;f0!BVmadO3sxeF4Rx z!qTwz)Ocia8e$?Paq;v*3G%R%Ud($)a*8qe$?QWEo@r9oB$<26nukG9?cIOc`-Gl} zDQ~HKmWV?9hy((Pu|cW1;CA@Grs=1#PPA3ij&tm7DYHq< zgKNi-FW6o$K8q*KPc^O_>dgzohz==__B~q*Flb`HJc|cv_UzpgP5U=RMzz8!?XJ<; z9U3=78<8h!O1S5_KfDEJ=ePsYcUMjHm`|BaUiOkp%!s=YJupNHsuBBvzW-oElq$hh zlj>-6xuXxnu+eHO^>~60xQ-K4rGevx7=zs)N))MxYs>Yrm?HN}BMm5|#n@2$^bsi= z-Ka1_&DrAm-JvT2YXp)04XJa@@=(zMVtI}1&hbDEuL6ar-GVf`40mSZhahf90Mt7s zC`=w({tWeNS{EnkFzs2|XBE3|Sfo7!Sxz_Z+V9~?xL7n(`Yavqo@xNaG~V~%*;n0Qs>5A(e2t@(nD-qxks)!PKkd#n z>AoK1)PQ`jzH{yMk`UNNSUeK?oYukd-tQ&#ajJ~5=o7rc;X%TDUr*LMe~41M=HhbQ zE9iBxIBB?B&37l00u+@WAY0{DzXUR z{=nGa^*^>qShkzwfhrtC^KOb zW+D^)vdSe8EQ{&S)6*Kn3&q>}7NRQ+$|FW_N&mAWidMpnAo7VLfmC*+Dy$WPjA0I9 zf*V9)qu{q012!xT4PF8cb%jCHR2($NCM8fs#lI8N=Ok8xq~8kv^BL(&$k<1xS_9Y# zYm2!HZY)d$YNq?2709incz^yVPgUG<)ZM`l<}X zVY~5~FAJ4jDH!LzIM(rB6K?H$E@NY1i%n zSdK^*8@A(WR)P!gao)4BSLu{<3K3TolgAUg?oh{>IM&;7QGaGRyN^+}t6^C-?^z1s zXp;M=<%%+C6Hk3ty)>v=4J<%uVZ_~8@>?(;ONDBH&iGyPxjCj0KBiyX9~8fWonV<| znv2Z*VUqPyhY<)f7vDfCp@GqJAG^>1^_^o|G(j(D>EJY|AL2}7M2=j&gjJQi2vl^Chi3Zf1!VN zozdE{m(DhZ2iJ?3Jfc`!zP4nGdVC52d3WIiWgG$5>Y8Byhg6*8IshlsWa0KuxBnEt zLU9CJ$5=5SW-c!+m^!pzuc>c7Z!<3>Mj8m}+uXBfihFFl<&0y)Rx{bx-Kl4dL(hlT z3FnFD4*TQF`v*?)*Njhkv+8$4(a}QJ&&!RCI`uD~i(qzKdv^7D}z_QocXXwHz1v~>n8irnT$Jl zSFCs4n|$7M9H#rzo3h`MkCGupXh-i318)s;4aQRdyM%KHNO-`Xnva=baDd)C?%RVi z0N(iSeS;x@J?LGg`zC;RQPJtI;JY8};OGQy-%pXS%f-V_{0`zruFI9*ozD}X&nT?YF#fZnX`D}y>bDxY@lptN0QiJpHZh+B+>xi#*b1+q<}qWT_*d;yDtSVQkkHiRi_%hjB{W?-xkS- z8!Oc6XoqX30VooE%FT+El*#rYCB$Y@JhtkJFD{(a$#{;qbao{}26AmRYKyG#%{M@J zKfUg<4_dF^yRmBppHSqT5XjeEf&RJMIW3aiPmGCam!^-y7~)bS;pT`8TSb<}11+Gb z-0@Ur`jv*s;ZS@3+;D5ZVXQoyDq>^IiX5rHK)kRt?v@HHdB>ZVIdlJ}h*cUVaE8u0 z%TH9|Th1b7#>65=BcV&fGdXmI4nr*RWL5UA0yNx%ELN$Eo<6iElNbJ7AViL6Gpy8o zmO_nJr%EX!SrR3Q0o^*FJRMV%7Yu+Vc|4Ts>0{@6ROL$HB4~m~c=>=aT3kx)PA5Bl z7!vqvoSYR>FJ#Y*OKF6tUne`?gf2_QKV?*Uhb-Za4jNsBh#p*%AC?heR`=;CAA(vi zD-xJGd>kCE>EIFX7bpW zF2>)Y5c2i5&JuF6>y&!*nw%~4z)V2G%-xVIHuxx62QO`H%^c!<*J!V!R|W25@}^l{ z+NW^O!>qCxflr<3)-BDOpn53CNfSpleVY;bOHxAhN zOFIbpP9gDno_cLM^g=^kJ0DgtKF3jx(}$&Pt{%BUCMC6`i%_0y}^M@RLfVZj#8W}fz zgtwt5^y>)HZ~}w_xD3EIcshT~4%2t#bB8I>F`@KT$gsTv^o+EC&jGE4P5`Nw=7;$e zYu=s>2Csil56J2bOV}59p6p{s(@wVUHE;Eo_EioBFVnZD{u@n{oyt_mX5=J%BrThR zi=b8plLVp`^~4;Q4$;fccaL-a(JW_l`Fd0DL2`P*j9|uko^!eKma}5<3s+3A^$%AS zLzYFB1u2AdMz?{_YLPQuld^}ptp`++HoC-wN0aQO)(yAk0Lwa#mD4dN`}Wzw49?RMZR6~j z#f_I`=Jd&QkOH37*b`S{%gp4R8xY4=$=uw?MkyO7H5}*0$?@GT`Brq5`9~=pTvum^ zx%zaE6}P+eMx|p}bV0fSBk<mFh5AaQqT|nQQFSu7G&{&4Z zGmZ7`WcC6n0j+`#GflIa++{*=O@Sd3gCa!eE56l<42`EDjU-MK)`oG}YD0>n^;Lf10I0CfCCJdW4lGWX zlVD{@B8eg;rttG3gY$iB3oB$?m32^O*OmLn%|SfRGq|3|v$iprX({g?<1O}XlM4

F*SXYucU9hYugSpEJPv6(gLU&XpzZ_wno{*ecMFY3LiZ5K_?RO2W(mHrBUE z2K(+CMquxeyU}OStq!BJtDi-lyD+c)yU_#rDV`>a9EM8;nULuol`Wk1_tJ~A+oTb% zroD}hffl@{MX=zgh81V9YOI>BHcs1_R=iD|R1d?djjWt?S}*;c$L~+D9=QgY>vd9? z4@0lVc`$<>#@o_^xeUHTPOX@RCJ22O|5BKQjsYTg!LCVCo!8Ul5hxk1CXM-P>N4XL zm#cccl)Ud3u%EZxkS(W^wDt$fE%r}Qw-^SR?M-gZw==cNga*|ssydJ4QuZ+|(zmjQ zD49%~#mpDyte0$|-JEBl`U~W2j+suEtyWtDmsjH{_O^>m&*cr~^VdnB-L#?#v}2%N zZO&QO)E}V=8h$EE^W2>V>=i!q5qhQi%$&1F!dpRrj7dhBN zHIa_V)f7Z6zf~r#xxJUS!6YZSLj?u$?n|UsWKY7+$XIc!VnJ_Oym(@7E!F!OlZl0+ zq_Z^r#guJn;8$5?*?BB&kxXb^s0;5&ygjEM4xKxbAkF!LQwSMi)(kky5yJam9Z6SL zPyXO_#nj#M^8E@>qBbt9yCj_NP+Dffw_85p(ZOUvES?#Hrux8?gY2Ms)-3Q>!f#0U zk*_$}wVrFz6j`+-5hE1EV>&1uy1|sF*}HoDZqvXyC-12_1~t=r0s#uYdx9K@J8CDo zF~G2sfzRY?$RxX;eiBy3mNca<*tpVLNcI8YX8G+V0+n0{bpmlm?XYshE;;Q3I+fsc z<=W2pmDYas)=QnVg78&M$QdvwN4AqtYB1$SRar8L{92qnC43as$6zuwu^`l2W*r7! z4+Dq0`^Ect0p{$i>~Z%JGy-%T)CtOYee?_ma8J>F1?`v|mjXB0Bm2>l6BS_ZDeC^& z2@CRuyuYqUMovMz{EK!%N|Llz!788Q;=gAv&;k{m8MCHbTNy|~@!iG}lvt(~M zGyA*Ce0wP!xnv-Ig$s=57aHvC%s8;9s;J_%lcs1w1TK{19ZNIvnmSb~7!dFpG&NNg zXtuIOL5r-=u8jXYG)AP6zo|KM_E8nFQ}v33&U=mcS=w|`ljZwMK=Y3`l8F%sBXUtP zFwlY{&ma_v(lh)MhYlhSpL~GCkN-s&of>;$(Ffq-NV*=%q>SfT0}Asvj^W*e=A2mW zcG8sFYSM*ooFQQ58Nr6ZEWQAU-mSmLbPInNfpOP-yw~jYHqmvevVQKb-)OPwh{Pd)U1n;y z&l!&%PD!8V+7rCau`4)f7qbZe@_59(UOHVi1^iqD8i!cvm@*)OPi=la1Q|c6&@!Rv z5_a;LigO*OATITC#Na?mL3kl4g{Zw&prx0@(4kN!<8;r4+Z4C{g)o9o$JbGV$dgNT zi)G`fld???5J*2cT7<;A3vB&Ie^7!WzDgLp>D?wy);bQxvOw=8B%e^XJ{~h@a$^;ns}*!-(0a2RE6F$4dPIZ ztg~r11!^qN-88j8e6LA>3qdO*f&)_1-OZDPe7wq*B_Z@VR(T^!LKs>T(a{RLw`)&vp}bFU~@P ztV{RtGWsY+TRlV|0&{E$8e$#AC7Y0PX=`IvP>y{mP$H}W(FAFl;ACsP(Jt-Shz*1C z^r=w$l1-Omi0vE)MaK7tTHQMY7dyl#2m}Ab63NsGQKE?r{p^vbB*Kts7$b>lADwpNExX{UHnE&wUfz%b4AmGe27RG{~o9*C-V1zV^4%yW6U9tw-Ce?66{e`=@L+m zLCeT71S>EQh5`-C3n-{)f@t|&IJ!^a%u%C4)6dFDgBRb=vY_@^EWkF8JGWH8 zTs=|5^c%|#_FCfVtEy(fuBdp+Z%-0zb#Ow-`uk$xuB@`8ukY0KcK?Iyhc2Ihnh^eW zjp+i=e+pwV#me>CV$$HW_Cf{DR^#MX<^x{LIqlNt3k%2+UYn5n_E+fp@uFWNdB#Bt zyjJ82lz4Fgoev6GStxw}*0op>Mp#-CvDnG0;4yfj2rZ&*eoInbr$b{=zPd4crcIH$ zxp>FVz?C6~d~?`if~)3ULA0`z!A=?#c*Uhs<0njumE0kCGtoC2w&L zm^$ssI!!I82JIX!EDyG6&%B$7=#5nw9*6Kt~e22DURukx_cjaL!cfvW__Q9$L%!L89%!RjU(A;CAw*Hw)^lgYq z_v_H&gz)FI+L!tS_~p9=b=x))WN@IyA_OdXELv?x~26LtkRn6u^t zWK-vlqC`z!h~RP1_9&Xq!^nxw_5o1~FW1Zd1^bqhki?Y_8&Cto?JTHm?G?fOW|$J2 zx1R^@_RqcD6~{(A7DwH-iDPw;{Yuz7|ASSCR5e>g z&zgXRP|r?0X9=(ASeDOSHGn$Wy1UNeTk_`VgnN!a%~(rKswZc0gvg>}ThKkR7W+8= zgx%JPyvG~x5@l6~!tcM~yxY$nIU4+#mu~J>eddcW$h@5nYUW=3khtoD`Wd<8|3RRB zS_(mfec4^2Z3Uy{ae>qR?Dv?xi|0nS#Blh)?R@U%cK!nCIB!b1E??1^{TP;TQCfj& zE4%qYu!h8$lehUs++=#!w8s4E-#qlBXH8?pNC12Qo;zJw?DyL#(pzw0iI1wbI;=N) z98FU7CQM<%;V-r*nQPc`jI_uyw%(y!Jms~8n@Z&&61k)s&9kGw%ajcszRe3crOVHo z^H5zr-6X-9mY2}vFW^+5u(6Cs&QXUMeW=$fP=IyoFJr!nM91eErdc=_5iVJxT)^1J znx!Mk-y0ehEGT0l4uwU5ojw`2t9T)b4RY$ynaoytwc#?G<_D25^?+ILmk~{OxHTV3 zt1QS87>w^rc*_Aa3b_y~SB{S*A-Jvl|M>c*AW>tb+p%riwzX&W*n4c-wr$(CZQHhO zTX)WVJAeK6epN|TzNC|fq>}Eg?zN%>0P18G=GdYD>bF=7kIid{d=sP3$QELm=BtT( zQ8XV0Lscb&>P3V! zvJVhfnyj+FhPbLzcy3x(GQ4Qn%#zp-$hPQEDHs=Q> zkjHX0Ht-CM)>l9dVXetkAMOkdZ_SfeaBp_e2@cM?NAT_}{BGRUiBNLh#nX4h%??AR z%EgyTH}m^k27Zc#0=#nxo+nq4CsWa4ac76Z_IgG4mWo%f0~X#37D6K&;2!{iJ~PZt z`5XQpy_th(8xB5OjA@Hlj~!!umc8IgM*X*Z@XrHrJ;1hf6P*GFasrp4aImjj~*AN-D(f`uY9mq^gVOGE3g^VhHOjEyiCwy?`f$v$AUY8&|TWtzo+kZN#{rq zK5FS>WQRU2(xrCx0RP(4=MY=xZI?QD0TkK`l9okZ4_kk4ISB72UiLCbj@`as{mR25#qbMP^;!#M8IRHahIdpo7UJR5~LI6E>FGMP%03HuaAC zQASF)*I7XpIl4KW_sY=9Wq-961KhkgJ>tKe8Nrt%f zweveQAwO+eb_uH&(dK0aYSXnM_{QUyjc$5w9XxE|2q%~O!V>2S-g2!gC0z5IbH$Gy zXyUiNJ=15aHa~%>4!iKB9u&Y+i~eu zM)`fiFk=)oe~IWKhI9}j9^qOT>Q-^>eu{s7fD%Mt1L26c#S_!mcON`JIq_~iyA=XE z1omSPo%}?)SKprtn{Uy@{5wA7{5ps88;$LFGc-fT@eA&=*gJ~zVMB6Y8g#UKK}nS% zk=#gqXJ?r6s0vyN+Oq2UW4Np_R=+?g^PkVkP5sB^*!|HqoR?b3$xL@*M(znGU!HTs z;TcdmG7xZhQ2+^VtH;JpUS4i`4i0*p;PeTRW0o1kUBNhaY|G8>uU&X z%oh*Ws%C^ri9pf*-K|r@g7Qf^z33)_K@=JK3~tfTs0^{Fm<|Ut z6SEl9@<_FQ*eVQC$|QdAfd6yKH^tH92w^un|H1_efQF zQXSlioz!P5J5B%K@)085O4_eQqQ|{5z0(ybID9+diDuLJDABP`1g)9{;?NsJrPu`@ z7**aeP1|M~!ukx-j~bPoQAOSo)=2K6k9(@2X)Yzq!NX|}6I<&!OQ0y~_eFr;Tbf6~ zx9`W%uJn_k8d5c^YXYg1E0HBZ4226@ZWK^|)3e1hg={rdLJMKR@M^h8aG!zOH1qoFp2jcgD0Y)t?!68}8LTQgO6Gk$T z(E3-mhh{?Y2kmu;`6k6^LHHL`UUSk&M@>6L$UB?sR?TAooNt`rJ~c!N%?{^oRBvxu zZ|$8jkMus0nnOpkx44-d4f7?DSaV)zg8>bgtq%Jes4S+1>J%_c@Noc_&ctsVsfNaBPRS@#nz&0%AFz`$vh3NmRu5 z`{|)<3CTe|%rlt--sO}7I-k&PT9bu7hGl4&UM*oSz-4_;IE;YfIl%3Mp5#xUxFTaP zkH@l%;#wFL;H8tsd!H-hrIY%5K$b_=mM%3Mi@YHl9Q$1Lcb-Xiw8m(FEA$)eMz44P zP?_UG?yF7m&lcE_UC|FHY^bxrVJqO=lC@x_xxgcM`HHN2{2V>YH2^lwd;Ht6(kwpV zZ4Kfp8PE>UtSdS?31TyE*g*3kNHC$8pq^OT2&S-}a4?cE15j@?fK^RsWMl^;oC$^| zlB!JpCWoISMk*W+?I;Q86{=?L-W<`yL?^^C3_T+XeLc+#MWcW}(jZ6j;1<&&bNwXK zFA=C0DgVKXvEI$Y%+3WO;Tifb+LsX8r{@^)v<($p13}QfeEPlyYC^;-jfNEXXqMxs z(tb%eT~&NCrJo%8KX+$6u2hZb!Ig2x=>7aVPEkX^UxmAKZ?a#Lg@+eC+7E<=Zf|eR z&D?tmfBW|Ei+Yi9$UAq1SJQ2QP-RgtTU9NG^q4KlY8vOFKkbAy8VEIHJ;ri&0f!(OW7@7*aDuohrRhzvTFa{>{qh0u5Fczf?ne{# z^O2MHTdIBzYk=WoCwyHf3e|)CC^psM;v3~Ky8PBq5GVM=jMX&bb&_qY3Upo4S}BnT z8(JC98JtLhgQTPq47^yjIjF{%_+Y;$h{n1^vI&Zp9^OVgw9*I>=q4Q)`L5vR zJGt99nJ*ADC*h{{Hx?hmE$qD75kl=DiCV*GDcrWO^B1Eyf~wp$?}9WpE3A?g^6|3D zVzD`6nbaBXjDX|`khjJwtdWvy?Nz`Ka}U#3OOI2H zBmN^iQ0=(DF~ooaP0p8A#MlC(1NcU(DhM_NYvS3M!iWGwl6mYFW{X@YRm(Ai3^N8% znJdQO4pHjmZ=lNVN6mAcOmbc$4}u~b4@Ae9FQvy5McpdZKtc`80bDO)xLKI7tE<`A zE1v7+ODDsSN@R~T5P?D4=EwDC421X}OJBR2I*BcZS8Zc&!1KNsULeZwKc))bTX_Z! z3Ir%=ds~V z2d1&X?`Hv2k@Ec7set;pS2v$jddadc-HG~g9FT2)4k_FQt-ppH9IJAf^vCmhBk|Ei14E!a{3>B3NcK+L zJs1H($AT28h0MY{TsO3DOHrvE^`Sv)YUwbRXcB%#E78>PF|LeMm!Xr@;Nh;#1j)S^ z_IA0~JkZ&PBI~R@;?;YD>rmGR{;jFu2o7oaInWo@X@tT?;#4;DV&DENh`KVW!nTJG zy?5g??kVMJj3-sI6Mkj$^Tx$M^{hFl`$46ex$_H@D=gh-6x4w(9^~<16VO3Y)tS@Q z<#$s-o`wk;cOp>Lw}&NLw4yyVq;Tz3ba+RPzPv~@nA|Lca<~)B1oI503YBfs0_iJSaqF)5G=x>fe@Jz z1KI#dWxK6{b*V&Sm4kD-VT@vw35`T;sL{nnL4ypX08tVNC^JYXmfEZJt2J3e=-e-A zp2%O*6gb>4Y3MOJN{Tr%?{KwoDWnVY%(UIbMkOdJh2=Fk0!J6$5)sDye@5@>r622g z4_(*D+}H5zJ1vpBEa5q|o5O%6BW{9lNuFOa+RQ12p^=>k1;%H?Vw#8#^^ zAvD08vqlp#^L_OZ4Z{Wm4CLo)ynJxCY;w#gKYT!w>&Liwh&vpEVNWBd|0LUv8ZLEJ zitw%({VcO#uOckabu%T6)oTdn>_nKXpo{U{l)18Xk*+v5_uCRR;PpdqYdX&96ly7r zkWGjLF`&ER-E>Ae;pl|Az)9IM<7~6^&Dq3hNZF$F{zA9OF!s&hgADp)M>HZ&b%#sc z<)}ud?LS6H-G$oO?cuMX@YfCiu0L~m^nu`;<--Zgu*=uZ%~Bea}oq>tA!rP4WV51x3@<8O+Q- zpG+)MqGFrP%IyTB-g+I^7wryir zeiMCM_x&8%KNdV1)4*|2vj{mzCA@ojJOupAjPM*~sY7<^-Rlgr>9iG@*po6pm+0-R z$|tB-Dw@_8VvZSrt)|AY8zaPEt{Q-SU3yyRW zv&Ezvgy7iRzwLF^jon4fDTrAaB9o>9G24&Apte7EhUD*5v$rH-rWyuQr1^=9*8QQN zDaBMV42Iw0+;kf0&zPhjV@4W_Bx5>=nG_>&_VG%0XiTp(lyEPbJDIRSVbxl0%wPUg z5`8DR_zL*aerO@GG5gt`|HKtJ)2Kc5SdNa4?y5+Rwi~c7V_l*(5b-GgtTZ!hKVOdGEM8O$u=!Br*Q`#Vol8uR*s8EN8NwhVTI zZevONmGplGH2z2E<3Hmuew(ssjPs_lPS_Uz>G%8XV%z*rQ}4Gq^?(00|B8FOkEwN; z{4TwHL6Vg))Bm3p+ajMEPii$WJlJ2YZ_zz?(LU5>iEvY*1-?4fUaS$Uo+W!;hv=U( zlVv_SPVhltKT;XLm8%Z=tVaU6rsj=as}brs1q&Piz#kR> zfZ~5o4{&lcvvl~Mv0q8bKNi1)@H=Z-RWwy3J~3G=VJjzOx!DFoW)M}7q6-{mz$=pU zXYfOZ4AfJq+wM&jlhua5Ts!x1{)_M7FmOtQpg)e%t<;+}LEnhV44JA7w2$OJWDs9R z-exeS1$uc~WO*<6eJh2}FH_J2K4`VRag>I$NP?C0 zf;yT=ZaPdwk)0&Oexs@!JFLD(tZ`mtVnAp_EO&70h>=?Hs zv3hNenzSbtS+B>ekdL2(jd-$*n6%U4*%ikexF=!Q4T{g8o1JcPj}w_05k3LfwujWpevB6A21Ll(E>xXv_FZHuzN=5K5AyfF4JTnM`~UU%O7 zJIyL(LI*vp=b`H#-S3IUOb%a2^2tv07p%v&{2}R6=DSTHV`u8-=;HiH=*iyf&h*sN z%ALI){jK5->1)VS@B6Ed{dH-e20z(ELRQixI=^6PXgnc{TpAFR&hDQ`)Lz{A=e8>B zQnAix2S0`65jSd4B~F>p(%g9ebiW_ZcW{;;c~yK=B78PZGfsPhAiv{>Uhv#l-GE+r zP?&Hw9oFh1p+jV(^}U=zu*N8s6DG@5qI(bz=0){2g98?>UWUB7xjSGge%+~5V}1cd zTv)k=3O!yqx&=OdxdQk-3NG;0hRgS<$Cc-fO~FR3D^%q_z5VPb>z_{F>p~b}y5>#G z6)0J=5ALzgvj^1SSxVh%=B*@`*Y_&-rzUqNpRQ^$xTQhAOpYtJ+=I6(h3p(9Yld@L zs!y1oMll>g*ew>QzmiyI7K8o4)@D4|ECcpdQ;mc%m6~MYrqn9v^;}ELSA>c>I=*4a zGVB{;t5`x|83oA=xqAtqwlP)93@*%)Xsd#m{CN7ah`f*>JjwvXkjb|pOq1x{eNuDy z{<~<_1~hf|ONvJeS+W-VH5Ceck2n^BI$PIZ_xCU58((@u`!~Y)y`R*X0F^aZ zg2Ma-*ZPWbuehTwe}FmUP@t>S@?2)t+S);r(~RP~oen6|26_K%$OI8B#`I)G*qu2_=(r*Mj(lbKu$IsF^A~vo)?>Ti+T{Ow8x>?X01#&;B#e zDJ)+|O3F$a|KPq+c`~n0-xT-6Ue*q2`kHfp9oKu6IJ=f-(EPr89&U&_e{`;Ue{{0F zC*W8Mg;lvxj;wcNL7?VFbrKDdfCD~hd6>Chs+-}b_F4{wUep%R{6zlxQL^s zm*+(a*{>Fn=&@AtYw>@{0Rpx`6lfSmb%cRyEx{_kRGUrsxQxXYTJj)blIue($gf59 zt>E+JW=Nv~Qqgq>rE{%=Nc8ic3JeKQh4Iz)L&Q~^xwO$`OI|j#79YGf0I+cJ0XQZy z?`xv~Y6J@M`5YhSR47p=GieuDX8G4{aR{j!!P_a;3QuWehb`i;-sCo=ItQaNz@|tt z;mleO_~Kx(PTVh0^xy*8$*AcSrMeE)#0;(3b>6OKLKsJ|yxCr7YzXP=I$eAQa)9*0 zwu>4Hsok9-nFdDI-c*@k=4#klfw`kaem}zq&DGWC{u2OJM4ed2xKoJ@KTokRZQt6S zZ5ijZB9!P-ite7?pfy00d;zi%dg&X#eHp96%yR9(!WI(fC+GJ4ph@|1)0cO`Uv<_b z`Z4e{mo_k$_P#sW*DFQLR{s?-pB29m3acC;cs7NuYWEgVjZoqRDgm#fqxuSpMutU-xtOYh-~Ft4tgm z-KD%pN+>`UI7sJ7(m@2AO!QPltr#eT)Ro1rhf2S%6%!FwQ96qM6UUz z5zZE*ogdY(ZPx#rbN%1%t5%1q5)c#sfHFD&0Qvv^zW%>5?>3fEdr&Cl@zK68H^$nN zqY{Q?z5q4q6HL`I24xoLEVxL_upw|2B?@tu-l2qz>Iho2=K!Y>bfIGPuxjVXIFWauVnrWWvkcQRiVb~5it-0NlE z)9r5Qu^+kZ^6J4KZS{H#XpejRCb;Od#@ufQ@>2$aj(_;xz`rp6wX0#W-w~B^^|klj z1*0SfVE^0Zy&Fu)4zL-@^NI+G9~E1=?#ERz5j-kqP zSKYdeWU^nzYG=Q9=y^LhIi`@G7(Zm>L|jnhQ(RnP+!bR>Tbvqx)m5WEKO)=tONVx!!6m?*4_ELlMXMBy_$DP&wXcK#-RwGQ9xPJb+c&eK1<9*G@ zbC3lUUz`%{Kx$*(A3tK_U*SeR<5o40f-Q=e-&bNAJTacLNRl+y)&<`up0j$rkg?AZ z$Eul)efolN+E-!+yp8v$vt#Bhn+e_rXJPH6%osTR1x>!2RhKqmH9}=pB8tPsSnTWZ z&WvV~Iu> z?^1ZVDJN?ZET&H3=$0Q<^kpOzm~;VNEv!s3OW9uNj-~CI>HBh#Xe%Q;h7$tdQ2D7J zY>FS*QUH@mK+uqB?d=}`V52N^HrHJuU$6kbHxooCqAt81jHw~ydrie?lcu%5JV_m_ z4i|_23@JHEDyyc9*p}zjtfKw-8|H2akpINJ12foRc&|+eG>`TSi~q>kPba}X`tanvHn>5p1LV`;o7g-t=QcW%gs%fQewadLg4%Kb& z(`A*u!Kt(j><~!lTcV3;z2cd;?|I;>!*e60sg)2fc~2SsaaL{OGlW72Gn|Iej4ZI`i)uM-g`zQSv!XM0o|`suU{roqJwDug`RL@`IRP5E zKl(AgE7vsF>3P4__SfA4-*>E>Yr&uFn4U|=h}7cqncK%pZsAQ2w(;QkR6j3H44qi4 zPnG*;vHm0(vpM@@iZs@f!{e3@cYp;cMj#y5S;GrF!%f=(F2oJb(i?UN;YEi+5u=#a-cH@@Lqi1$cmnpU=E)X)^Sy-vh~g!g(*W}&0 z2F-Bt@rfW^R~!PM)jA_~#};E3>stpaa;hGb}DGy}ApU|R)P7UIzEP>Awl)nZ!yWp@y zb#x2A6iIIbNuwcg&^x#R7=BPLJXC->>wf5UfDqmIc1g1fL@^iH@WDSMXStm!Xn~sa zWxX7DUT69f`}cKRcUlH_!KgFkxU5xeIBI^J?S8WHqK)+3+&lo*9j#@}YB|wI8hf4h zPMh=t%~I2EXsRhiIeAjb!K!2Ne5^GR1-t-#W@mho9;@zBnXJnES#BlVHXI8uOOAZ^ z&r(OaJ(tH)^*;3dJOxQ}%p!v!s z3=-xx`pUlT^yETXD0@FnFAB>7oq8 z1g#6G>+E;p_KjW4heFh_JiH9QgTtfo!I719rR8kd)omK#31rmogS4!?bo$Z{em+rXd7#R0Yk(%<9|J&F8_boG@FK|`tm4OB!rL$s_o zfH4FL)=rRb$YL@?g`AmLfFIt0U5W9;w67H8rf;9)J12nP}@h!GAd9C6wuI zV6F!FuxoIk&O!t`(9OG1w_4^&A|F6_*92g6=7UH%+8u>ni^}T2@WV#WR7vJ{s}Bm* zxVTHf9T=xZLqlJOeeZ6@X^_GM49@c&zl z{M8%*fc{hcet5sd*3HDm!I56i!J5&*z|_b}&w*Y?$IRNyQAdZ))(sfozoo}5EiVNC z0RS$43(5a3ZD3^YNNcEPZA5FT=V1Cfhf(aDY(E3S@Xm+I;`=U|5F#j&w5vd-F(K0L z9U&;TvaLF0nxb8D+2ew%AG*EK)N|8ubNyMVQ*dFj{@CX^Uw#+F${xu0%%DYrE~b<6 zkcb8ZO(jfu@z3?5pPjI+fjb>C@GkaT%P6blG)TU`OqvakDptAA5997@Le|5XX-Uf>VP^-&Z``GI%?w?6R4Hi=`t=xcE`y-Q3>4t%vY87+Ds} zE`3!Ke1fm~X$jhs*+Q}w%c(v`hl5n~S`~Nb(~V%y|B+_txX}6p_iF|`G5`S3{(pAB z!N}6s!OXE3r-;DjX-X*T|u=Yl4ujPl?ga`NPm{Jf{Qq>1g&V)%%@HFRu` z8uicM&OC@vlE$|`CRHvoI)O&vfB5s-VYU6>d2+mkV9zVoi=qLc?Q>5Xq*rRj^Hqsc zl};#MkR)9*c#SJ3PI%x*Ib?z&{!(D~A;jN?7S4Iv-SOq?Ad8R zjQs#25^1t!Q4|_XH=YKERC6@Zm+de3yp7|7aBKJoVh=<{MQY_lY(+@yzh)jELQAhFVgR> z6y)opRY7l_4(xe9Q@LW6VX_89Ys_7Gf`WPpjCFo+Cm0H9PQOD+)U#lW9=7c@AmDionS%+B$A6 zI2wUfx7-jRzRtQ()tp7UT{J~=OTe}*X%-(p8MdOr3%03N+f=R{7?`Ktbb>=KRLHD{ z_NWi=Oazy?j~FWI9-)W|R2 z&pLEKTXP6LRF*1qeF(`k(Qx*%W@M)LpN9MnhM}ymk(A*9o_S%`BdkS45sY#jobQ0m zJlm?C@TxcDz0Ns`_lb+6v*u@~@>J1PZDqh2b~Pj2#SEzoF#nFXFVsOg=2!DKZ)n9|xmJkrt8d1G_* z1!HqF3x`vTEN%}IruWC>W=Qi#83%?(Qbi49e(mxJ=r`PENRJSi(O9Gv)E0{de)?y3 zQ}Xrp#g7rEcNmQKQSP1&k(HTcUo^Z_GtI1Skr-?82|Kv`wJVqrl*=N({l?MA@thE# z11^vyuh5dz7vy-s8vLl>PXWB`;SuG&C*>jr{YPPnyOOMBSklXhAFPYn7e&zYy%l*Q zr}eRf#+mv}&ls?|RpFnyu{)-3c!-1s+6)L6sp9zMBCi42&|#{NP$c1wy{># zEiZenlC1UT3h4z=u*DnL$pF1I!7z_#h*pI%Czgn&MmNPt*FW!*jTp|r^j3=~JdT{R1OXO@>e=;#O@91UHxm^zcIk z)8ul0;KmUZwC!)-_Fm;%6DfS6&^1VsAc5zJyqoS7WYbX5T0nQ-$fgjWN38=j#bC{5 zVgY?md+~dSsB@ddXW1;?DZjQ~vL6;y2$p35KNAQ})gUgeE~o{zJ<5!vV;>06NX<*t zizX}I9y5(%&-NN1dLE5E?E0#cl+jezbPSM+z|GNK=Sq7tk6K*VLr}FX>0;Phy^63? zONhR$HE1H29%Jqtl|lHt{#Fp14}u%QZX>>h7@8BSK(B?D zkn|EH`Sv)O907yO5du!4qx>UF4{;k|88sYDK<5Wf z5G{lIp-qr! zVJY1Zd~u2ic>|Lz%cV(87s2tFtkE5zDfoCdM(tw;9X>sXzcNyK#6o-8fRw7-zO;W! z(+(cQ2uT>CM6NktadQ#d-?40iH;S<>1)1(Y2HLeq z-X`J|H%;4?<>}pIu-|Gc?+AbLJH)*KmAgMS!W&`x*@nqB3M!IJ#1b!1sv~^d`W8>s zmeQdHv(@Woq-FwrR$%}jxc#zVwa$RGKFrGhji{lq|adX{A=i=qdFo?lH$B_N#nNBvuz9a`7IimZFYT!yZ&A)Ovd% zGdpXlx|t~IMb(_nH+(R-UMBm!WL0HN0L(@TuykgW z3%JhdKRz<5&MY0^k}ZaBtesIsU7I-}R(ot+@vU${Z(4VZvGZBU8}#$s(FtmD7zt!a4#mgy#Lw=>mKg zc-hnF*FZh^M9({cH2P{`PZ@;Zgd={>KbDU~YhwNylzoQOfbB)S=#onrrqUQ8^ruXF zT}F#c=J8OL#49n+^risww6(GK-R;>S+7M8jX(8Ik9KG6)Bew4!r)J^q@H%t&U3aZ7 z3}5f?tI)xIO7&!n$AR}Si?b^3U4NFFR30UG?% zX!fiB(7{xrS&mx)@YITWkz9G8B7e+`(1zZZOr&P=b*=^%tKJnWwy=?o$XWxV!hb+mQR{DGRuiRBmC zKM{|uj6Qa|AhHqHH8BpeQQkAJ6&hS+vtv&U55~*&OsBktR zL%y=a{NC6>u-Kym^a-xgZR5f%!30I_%G#n{j9!cPgkE0}Wzx_y3EC;ooj#;(AIglD zvk>MOfqSF9A20vA>GV19DWRH`LzMRC5So%{)az>jyR>E_OY_`j1s&f-ZNY&e0Hk|; z#vsVitN+j%`rmWjN8Xri-3vmsHdXeMugVf!TA;i{^u?k{J?6WEdc_hAyhC*EFwcmV zfLF#c_7;}EEI`FsHH*Pg1LSt2�T2>P(AL)w%fc_P@dUeA!aGuf&}9G;H~7R^q4g zC=ms<11RRzINP5VSON~}XtgDnTb152jTfGoLl$BX~D`pNqbK_4Y zQ;b89e+94g9q);Y&F{hn`1x8;J#P?&V3FE}JEKLPDOqIgml)zV9$$j<6=dy9NO1A$ zr0_NS-t(Jd>4%=Yv1ig_zy&_D(>m;Z^UJ0lO7 zNJ61!^+Kc}7=%lbP!*7*VO}IyIZppLjSutsY-tQ4NMaZif@z9c7gmY&hFXejYeS&47tz0?tX}KWTnK8 z-x5imhMDd$d#?GD@dj(&r0(l~t>Ctv)=_IM!;^u^^r9bf#{qEE1;7nEX=LeOXDsfL zOGcFzQrp8Bgc)get1G0qn{U24yAXm!alf8tU0G!2YB7@dXb5?0_8YpVl9budwI6?_ zzkYV^&%6?BqySuUqk;8+7@JI1F6F(9+*apY#1NHw(aMLGcxC_cd)I3g3s9nVWaJ5& z{Zl3;GkU+v*h)p-GOMjB0P}ou-(hQs=z8-}NJ21UQ-jEwzx$-R|vcLj+( zKYR;S8@|)El+aE3WQeXNy4pd)6=|EdHe{IzH&!l`*DYo@N}u34S>U0^mXvded=5<; z^h)mocB*&2<8UaqOx9Spmr~FjfH=QbRTYdJvd1zP6cNv~{N-VxA5SP%i#L+uebxNC zx<(A6P24Ujcb&x45&CK$lPc0kTt`qDdRtB*cI1r-lHdDd(8%=Q))97$HGvmHMq*T%JsT5AtX zLdsoUvprwFC6wgU>)EfU1AP zma=y5D_Fs^6skd}okv>#kLas!belq;+fUdM0wPMV%24U1dLM!vGy+pd2?6;BKO>Mf&t zY}pC`5= z^+L(wqo^gM47=%xjFs{{{9%;kc|@_+*2pAt>T|LgyFn!nQc>5Xal16L`1`f4iu9i) z8%gJIQ58}*^vkIApa0>)G?SnVZ2k3s-opR@VEylUj*Xe&e-xdp6iJ(X29%(ePpHB2 zg0lQoH-HGWMuI32V(ALXhO{Un9AutYj{4td!i$sRKfduHf5dVrPCWUZqiiN6#tc)h zh^tFwLWsr+nFdOZ_@^ghLQ6y{K~w_z1pWqM^&7^IKB7y%-fje)TL)(K-r?Fj7A4>O zD|qI8uU)nwEzr<=hdEf!g=PH|=cRt5`Zw(1nXpc-B&BfoC+ai{*1j)kqaJqQK!nh~ zmibN#oRI#u1da)Y}a|<;CrNyoJdk(Y1&KiE*V^9ubE? z>Z-w5+|?&@anbtUg8e_KVlc%Bui==853Jj zA!dqFBRp^e@F;VFenW8s1ps!>A29=NIH2&1AGU(~i?IpyF|EOZ9ClkfX7-$~C7K#{ zv>r*TTaY&cCbJstQ;%EN9CPEgvz8O$6x|hJL%<`q;&3wh%}1urUb~8S@^q5KK02oI z1-9VMYGgc>Wd@vh3_(RqT~=F!XC^svLV?wAUwX*XbePrv-4sB4EQ3Fz+`SnL7xvgY zzx<CF^7gI@xkr}w-99fFQYPl?7V zp~G;S0u9t4-4q~{I;Eh&pH03v?V5MaI`&)Ls59F~x)s6G$r+|bH~mYhD%k^=AwnT& zmShU6HHw8IE$Iv}tW{R-Dxtfx4vS9eU+Yldwft|CwF-EicxQSRTqlVc!%bn=lj>Bj z9lE%)6QZB+|LwBxyt9lp2Lk|D{XOje553;j*!B0gZ>d@-?293MPuHaO)AuQ$EUF(Z zR|tWmsH2AOUcZN7#ZOOQ#&1r7q;$0Zc=Hn0qzX88t(PAlk2${Z%q;NoGOrP*muyt8 z5&AK}%~*ICcD7@dDcHM;4Fdvuq9mWh-)0Rfci&2hcug#aBDSX2@}?k3y_%2`cK6RgU7KO8Sz%je;6sm_YWB|D@wF`&3-%#g-X4>@#!D z`S0ok03uvt?x!)G#wMX7M70O{Sv&Myx)J!U`pc>OW{av3;0x*ZsrXd@6ZZoV`OOo$JJ@>miKqj7rsF9wyO+m>^;Wi3I^;wcGr{}GNi}S zVY-kn=-uo!a}yqfjJXy*XMjAA{sDZqnHz^#68YnZu;~1n{y4R0<0k*F6RZt=V^;^ zm3B=G|BGV%ia9YbM57;;s zIW02$nL3e_Xpp=APLyzEWi+mq)plUHvG*$w`Wl`{0;^_f5ogPrYqP;0FH|^4KaEe& ze>u1~5;l$NL1(i4dp1!`lfMEv4HO@8&~DJpj%iDuhi#$F=}u$x$HCoDaeUx9wYFZx z*~>Nf%|7*u0imJOX1a55bsldaSK5QKN?9Ujl4V;JMEr&)wez1$RAIFSsGZ{b5@e22 ze{p5oT-Vf_-;Mf~%+rN9$qy=KRsxzOaPulZGz%1nUo}1wB=ZAf?n@Ti;L|f@YVZv= zlIKlvNR&NHPxL-a%F!8MXPL6}zrx9%VFUdH)l+9`Mn%6Ct@a`_tQ5*x(yxx+&g>eN zxH!4HdPCr!hQR{v411G+^qS}L>e>n$ILqL`U~%l){OBc>%09%I`w&|FD704%N*y?j z01eHm>)_^Cc$6P+eF+myUqTtCkMaT}BddV*Iq*;=zR-V>rlME?lE-LUi)*)cu^~wb z9Z0v?nv1a&k|%QFO>9n9bC1Bf1R0mBWf|cgB{;3@ZqwofARWH`ZQd#ASRK4}toU64 z^+@3gmUv!cgCE`$`Z(T8uz!ysn0=~#1)|u|e~6ZW|o0QEpVd!TPouY*-zSwOh3MsoF18@AdWG^LFILH-}czA;FYpxJV4 z+qP}nwr$(CZQHhuJGSS}9oy#4+dmuMi?^{G(H$Mpl~vUp(b<_*nRyPXYFN-ZOU+%n zg3>YQ(+_ewZyqBTXYQ`^f_VAS)pmQ_H@?d0IQfMH9H}!vo_X?pSq z8fVoF3f)>3+RClx98|hc5C$>NK}|n^ju?8gKa5oRs;r*)l_LD_+k~7X?p?SNr7fTq z!lUf*VS&-?zJaP>zdu-pH~2U8f8)q`#Z{EGJ1R%v0csynSsn+f4FyqALSsKdxd17* zT0ND)G{Z9ikR?**iY<8OqSBP~gt37W`|<**iGL=PBD|3;&+Cob}iC`SIn*Dlx~Acb353s z-gdNDplBXxC1r(?z6On@gvb)nTGOeL0+aC7Hn>gEHeHzru3 zOJ%e4dy+F$UXL4s%#g`Q#XedxlNvt7UsN?vt<+3_gqNgjLy^KB8kJ?%Rdm)zNky|X z@4Z|&>~eP7QEk;%6S5#QNZ>JjHZ1Ft8ks4)N_Cl zs=LjklF%KiS?Wgdgkug_G*CK*H6ozt0a+M+gYIR}_x726p*{WrGzYhQJutu25=~$`!Q`*k5 z5%#sbyx0-s>u$+}BLb|v+;#1rXQY}l1=S{wS&6i&C3O2x7z9v%3iQPjHw;|M>g zRNQ!#TMiDLX4?1ubL7R8pz*(3%hgq5EB_;a--fqp>;=T5%*%w?c`P4F>6Tz6p zD3Qxi|4nwIhk9IuwC2APS6(gg8lX&yc_(){n9XZepQ&G7YrtNsJPy6}A5XgS_^nHm z>ge|8M69cc?^_=HNw3xNFR!gLthKCd$@kn(UI(}XHAhGToR9EmaM|geSF@k!PhlAkcfe zHxXDR=`4|H3v)38RjMCe-hhySfLvH|%lD_Hi?y9$&{w8CqH{sBp{aapbcWd*ab%lr^d(41P zz;&_v57Pt=z%n4Tl?H-OA!^XLrbGguWMT{zXjmp4ex3MyL%dKbG=)Sbkf;v>z9?9M z3+Y#paf|_-rHzp8YJ)D-0M{gUOxj++X%#HI1FD0)q*i#fCgdCdbgQo#WhHcGW9Pbe zB!QelWsK8FV2lW8eXu=q#p3YW|B!v0$&uYpdB3DItYAbPBeBW)B#`AsN)#Clos>jC z^zF#M=2HIoVe!E9kQ0PogYDkBafNm-faqX!BiqmW`#6v@_~pciFKv-2^w&_7d@}2W zTDMOo0KgtZ?k<0dTrHH)KoXR-DP{W{N-vIDEH8dO;UR!amqe_hBDt9pLSlyylvoCk z1>zeSFX~mdYSm^XPFw=CCc%c=`P!^n6(&JTIA;Ntg+7T>0oP5$gY!8umtIGldUW7R z@AKhdLj)sM9Sj<#k&F`;zvZ813kD3`CglIYe*>GN%HDs08KtyUW+}e)m}sy#&NlzC zVWIVnDPfZZa%sUo2s$e}45XXq$-wOY@G}!s=Xc#?*80A#@&5YuH_>djj4Hw?{gK(l`{|KS$_`PAGh z@&=1O>uC8R7Rs&HmyaEE+szl=Nsu77$Ua$m(bCaj>qnOk1Km_ASEQI<&F2mMSYOdq zV#{qhN3K^3Q7}W0YMm17pN{MnbabC?!bjkfqZ^u`zPU3Dg@})`)m%KRl!{4*-%AU2k$Fys?lbLJo)WD6dT%;nAF0&8) z*HltWu8}V;DejAl%LY$ym8bL#`wQ;EULN1!Htj>P1lJ>heQ9N>5$kvA#i=&VBRZT1 z^OF~WkbCg@fal6qcj{~hzxr7noYX{*v;DEOs_4x|_cQn%Ebh4~l(SCl1O5Nh{2BGV zXxZOdI$7-h>mJ4bhEe||r_|8?4aT7Pov2q#M4y#P3UM)4UYc0p3VBt?0)xuQKPhXi zkBe=IJxeh~np=N=;9z3=40C8}Pa!{d>OW1IkHF!K^=czgz8uJrN|HJ6I=`H?(G z0Pt104oKe5qB&Lo$*VG>lTX&H)dyVZc-W2&yN#4j{0C6$xCIKWBN-@O^^t}e*aMc=s9_Ve9^w~o5ym-e&5}xk)eFC^*0e6I#^|WNDg@`gim<% zw75WL^c0cml}RtgPZ;vS@1CWx4{>__U?eLpUHYCw8zFgmCX^MEmp>Dl$zN~UeCjU1 zptnUi;2yTqDcQzY2uC61%?bU1ohDvn&L_O4u||UodmZhoXx+-trwgni_#N;i2_o?& zLCD2s9|^C5FY$Db-nw*X>)~N-Z7pz|ct_ymTP~dE$94Y9t-F1w)xKXJ-kz?nn3!)R zavVvdZD==bpW$(t7>>UZ%i%PWDg1y?w8*}Rv68>VZKo#H;eJO8PY$|X- zXOpq%9P}!^OM_;}*AzUdRVj*nsgawMdthvAkzRF}lQ^em@g~H-lu=vygEG^i4W=3m zB3(<3zEq@dUD~x`n=ZXuaD#v4&^w96?koJo+`TuhlB4zbf2<7wiFZdVw5SGKk= z1Lt9_i^)A?7|-R4XP`tT?ILQt5tm+o0=F!kPu+;EyU6%0$-?7O#z#+4b6_dycx=cC z6VcN{5kt88m;0MVKzgN6%B1AcsNn`FsOw>gl3%61AYfV)$62XS*VeBx$|#2SL5hD$ zGwDLB?wy0TLphn`Ladz3PyDk#sZr+7Xb%4XraLCMhZ(wnOI+Et_Sx0Te>bDA^#3v0 zQ9C$alURQAke>YHX+xo$+qZ2EXmtvwU9vcx)9Y8K*`?F`a$;AlZkr#c2MPoq5vX95 zB2e2`Xo{OMt)_RPH`KBOAak{){}_1!u8O(-i{akgo6{L?xr#XUkB35=^LklOd8!T_ zZtl{6>;Aa9bKYD4IyJG0mAp!NE_QLxqnnubh!Xdf)r>KGdeF~`l^UY?8jvtgP1hKE zvMl2@#*Xys2*?_cjSY7y6dZ5q=nVX4i$PJ3(f3fLOXWreXE5Zx3?-c{*Eg~>bQ zg`bpyF2?PtMHcrMZuopgnpl5_S4m%pkJEe-uwqqy03Pg-Q_0W0D&>GZbPX_;Xp;ln zK1v=>&OIYGRJ3QW9n!S?$-0ZwIke)Jm{5D8p$U$#RX=R?iz(bm>xig#y=V>-F1UTZ zf9kd|&?4GI(E3yL{0b*Q2z8u5O)|nW=R8`o`p!^EEb@M*iw<9EvS?}7oV(ub^?Yz6L1qh#+NZj2^t02=fF3wtJr)Ui2w_9mPzd7Gp4VCa# zf569LPyGIIw~e~f5>)by0JDM>awboo_)`AG8yiW5*Z;T4K5R(U@k61asYklUs+*U9 zp$60I+ttBzR;f^D%p3V{O#u=J0~7<}@V zF=hH1>P7@}$U-*g1ObwpojW_pd3h8Xn?3UN+=^6Ccr6rAj+cpYO8*(_vko%dWV@NS zNm5>ASv}HVPwl2XTB6p4XH>LC>kP)vgtP%!6jqcaN0($%&EhnCFErT(7p+OvI$A9F zm!_fBQ@w?Orhqc(s`2_c3d6)jxy1(Pq=Tl)j%zh!iDj z=4)$&H|GUh3IzM-jSuMmG{Vk;ubckA>(Pqell^~Qm+I>~cp4l2rcLYX|DUXBV+T(c z3wt|SW=6(e$J|(Zc4}%g00fWN8KdygoQ;4nmoSttLoj*4o>UkSSptFNVT!Agg_&HG zpOUn>Lxi53xxPiHeQ1bgcUpv>l~=dFfQh%iLxjJPW3GXNlbDsgQG=h4kD0!igio)& z;2u3(*3M7LQm#rDk$Sn!$dBMIFf%dn4j4Fw!U!x1@V^B=r1YG#A%2$>Qy>5Uy#Mca z`fs`1|Lx6E6=&p;1yS;C-Oby(X{1K!iL_GV4Hng4*$T!&;;pcV2@rmF%C~X@>1^6f z%(10mC#7Ua#zu;9c$T59UfDl^-6qLcoSE)S_5(+<(Q`!+rM&L0vNvAL{BGF$Js$*B zO>fuwoB@15#bLg`eqN{Dc7Z_d0URleoQ@>;hC&^eW76Ju`tcvi642N684NY2qj3g? zl3WBMmB!fu-+6v{yvtHBt%!$ttR#)3e&JeGp##O16O8OFuoqRVx>9glkV+@0ria zne++J*>;_aqj*=x+j2S>u;n~zmV#$zPv7O}hc5@h03iEj%up=#Me((T^7U@Vajx9E z6IFsTUcMDoZ{A{eBsMZWeZT+h?0u?90f}xU<=Bq-+?rm^RTM9`(cEza{WGXh{uF!Q z9@KSEcLU?iXg$ceYWg%Wh%$?Qai`u-%6|VQEkjYBhoeX{qEs8L=%vcfRH#O<>cz&JSp-=QXo*QTtX4mq zVc-sO4S0#kH|$#9Vqy{xg4Iu&n>9hO0ktup1}3yvgtlx($HJ01j>hKxYaCIRl_Gih`VYMf;M8xb+)=l>r`P-4*5|KOlJp z*b^&D+|wG6l|deK-8IvPUJ`QP-BT+|-3t!b%3uq^254u#0>KB`9&|yt1lgW+LE47s zh(06gfOKGAl67F-8=f`{(gt{8;00+1;v2#t?0|KkUy^p<-s3Ba--8_bv))6+zI%|^b3Lq#xnqexCXp5(gDE-3Vgc&WL;agN(V-d*!K z$$S8T_a65}o(_ppQqIZZeLI^Nr;2*dv>N`G0lgi%E=n~V&N~{{RL;-)&*cR%q+r zWJ@NjE!%YE71pM84R8_IhD~V;yY0ECmTuFXSy;=ralErT_MUf|DZNd49g96JwuOd1 z+~9XC+d((%3R+h^z}~rlrP%vLeH(3Gvf9`zW$UI8lkGfS=C)>T!-WjbUH;gCk)3#v zZRt1J==WK6Q}b6xwp9V}z0FI|IkKx(Nin?u%`GQdcZ!29pY0lkeHhFx3DJ|e=8Fqk z#c5kzkaos#fW2R&^qk3-WG>Emchhxt%YQG2J)oXa_JW;xa=>%Z%d7B&LWi#d>X`}X z-bv5RkX-ZGZ@dT6*!4>C9NUtY-Z_0^6vg`4+0Tvg=o>0#yZdD2b%R^mccYu~b$3^7 z(vRBoS`)(=uFk&6p0}Vwnvrj*M`{CX=~}<4wYr1tSsN#r>w@)(RTWD=0G78PgW&b{ zQ*3AJC-d=qChOT{7eWbemc1Q28HFHMm=d~owZoTw9nq`0< zXq@oN)_#2ye9tq4))8Dr|A3qKVVhy9uiS7i>`Uy0cIr*H8ouPMm^%hK`&?Vz5)**T zsaSCF9ZzFAdo=w9J+Rg*8_iXl3}bE$*uA#dz9ZY4Qks>0kF#q|(k_S&r&K(obI1+5 zfN@XnF3lmXB|ophuVZ`mEvVI*WYhHpKi~RC1+vkX>Y1X;n%5T2Om$3)LEF-~$ar15 zzNHc-pXE|YpK9ma!!O5mguC#{_>4Gzu&sS&Tx$8i=Wu4{Tq2CuRooZV3euTZ;{g%?kR?m09I$*wL7&t0e$v52b8B zz`1es9zXU~E@^kPAe?vu&ih4YUOI-wG|6~y=D8)C-eiQS;)?GQQq9fIzxOH=Ompra zMA-JYG?GN0W#~GhcX1+kcpGeVyv}|=(7$ba+Q`c2nbTR%!NW zD!9!Ui|C&=B9aCz^N`m`Akihlox611K?xwcZIY0Gk|q`Qi9vvS2DZSlqhB9)vAwiT zFQo3_q&R9Na_ zUH3H@_`wUy!9ZSzj`u#+=RF5qixoFD!_c)otus6iH+0&cqpG!W%Y?%Gc|alE5V$c* zgG+Lk0EIOTof-Mj*6V?m@#YfOeRE200}Bt6X~*l&iP7&!!tvAjQu-fgK55X7+5+)# zf3<~(&K)uGwfVQmL}X$*3+3DLWnqvF9Jrx2{h0&);5TZQv351RPv07Z#8QU>~ zZyE`P-b|yR@GxlQzkl15U+oE*mvzsjzIj)4QM2!fxHA?B@2Ea+oNuc56!@tEN|Z&x zMw^2$mJe&fLm>+7Y~n5^Mgjqz9XR~_t*r2P8ZDzj1$IxLUQf_-pm%bUbFXB#r)8G5 ziLO1BIe>05;Fqsb z$`;y1PjrqpacWOgCQgI<>GfOLAy3<0VEwU3h77sarqZSCf-UWlPC=5fgtltC)uk#c zWAf2^4=I{lCjRZz_=(c8BGh_kZ@z-|R;{^8&dY!FS^j8IZ()~yK4$e^tLnYT)N8>{zZ~bU zN172%@{}kD_rRZh!HO&E>Q#}N_Rqdc-ZZg-2lJwKG#hh1X)~AjDhijLi1*7?V5(#lxq$kyakMwpi`O*c(Ug(tvmu2Rjh^B<);C_tdZt zFlL1CJBZCD#45u6XNlY{oOS}))$4HpH~XNY!@GmO)@*#>F-s{k)MAL#U%>WdVnpwk zWW^wOB`21&L4z_@CgmI3`og~#kgf;tp!neK2!2CaQx1|^M9K-~A7Ws{GjK!*27E5< zMcYWQZxq5|xIJJ>_0POTwp}#OJWyP77&`vFkK#gy)g9`1_Gey6>;iq2YN1QN=~}-T zPp1{{({o(v`cJl!bpJX70V_;|TDpmAWSHP2!)uO9kWdV*-9B5htW%h6sk8oOqpoi? zZ62zGr24_wz%k$X#M|6BYrbaoebT0b)6qu+*(w4uUpX-mc2eSBIZdQf_WuP7L<;=QNJz<0=Am*okyq zbME-Mj8D#Zz{tg9l{s)v;H4r!%&tTX_B;un{sWm4HpuAC@s9H^pEs;?Gz|$6Ab);n zuttJL;(9^>*_#c??=mMJd6F23 ztVCAgFEzDx;-2H+LF2eP!I{WnXbJYDAqlUlblg$L3Y!McGbr!D0uV-<<&|KrJ4U;0p;uM#Pki5gS&y`3ZrrioWpy3y*HY zCr%_{AZI-}#@f)r4CuaAeEt#IJ{Ej80z0BR#{QcoU!wl~^%jD&A)3f%_lVw!o!6^7 zM378ae_X_KaOIdKyjMY-$eX}+$e9(9BZ0Zvwgi%sPgD@217LzE`8B{utMluAcs^-a z93=-EsT@2nUtA%{xuw4ja5*>)=bZ!)JhyX$fCjE&0Nj3{|FhdR$Ug%oDn#Pt+uIOA z<3-v1;N2_2da%6|QhKo4P&I}CE)w{xUzJ7(J4{tekSS3fxq)JR7U6$n}sA*Cqq=1%5g^(gz zh7;6evQLSYa>S`h^4r?^2gGy(d(O>M06VIwcg?v<_gC=zzXao;{}3tj1~pBnR2R@N zt#y67BcC&aWuu<2LE9Tr0$H*KknN$&!;R z??&G`vfn2-kN6$s2<2B0>$ibrwxEC}Q_WH0ceX0z6w;-MEq4ccwzgg~@A|7|`}79E z|7t+`l|Bdkolfh&1_=1=@#)eXvSnShM6~5>Y}g^R&QFE$TOGTUbmt;LuH3~+<(~Q{ z)iT+JE+v-r!b>Xk1y!_qnLeQg*{De6ot96!&1SouI$xG8t=1Yc87z}!B4i|C$RssZ zw3~_4%x1@R3}<7#>B4|vu~0Wt!0n_?#;u59yv}ID)OZ{&7_-Luy-3wJv4TGFk(7`p zp6XrI$+0>oo8BB9N>se`(%5M-AV;~=Zb5qw5}M~dqB0>i%|S22mjxR(YV0e?kw52V zb9l;XBkw`@3;q!w10Q?sGETLdSdC)yg4FvftJiX6w*@GEZzrjjyC!s6z&pc*=#ZL( zoIfe9Ce)1;TO$N9IBg<>08s6E6eZBeUgQyWy6 z+QvCTO`L`wO~dD=czeED#H!ikCJt7bzA21$sX=wj3XOHz4*v?jEs8B|QZ1>A5*unL zwy1KX36&-@rQTz(eqk)yQy(G zY29)Xxn?6$jg^ivG`!TPc&W8!CDWV>k(*jMd>#sK676TSI<0DBRw7ryCN~3^%%M3| zlPa;s+Renz@!tw(cWKtMMZK#{>~)*n>o>dekNBt3Va*7urI=L4#Ylx8q%uu7-c!YP zioFE^-=dVvFZ2mK>*zfTRn5~w#Rk}EHvSDt=#kJL)hc%$j8DgBz|-@0_S6M_)HmFN zrV4N}fFKB<4MUR@5<1BI!hksb*(|b1RoJCTZGmvP=n~s03FEBILYELKb4`|V6$+)? zCu3D;`8TL2sN830`AtEMRZVKlx^au~QgON%lHd?f#v|zd5BF2;p*(p?avif-|V?w3%xt{L93Fv%; z%F9Q4xDpB<0#DOCbt&~>-kX0C6)UWCfFL7|Ei4>V>E?;mCR(CR+Jy>G39CN5hW|V@ zmNVgC!m;@?SUFw}GR(K3LCEo5FoCwdbXuaakJ5x?D(%<;+b4~W=j|jjVWqKV6_+`H zL^VU75O9alY#=WSnpiraR71Ghz(cI&Dy5oht?FB?>T9&ZSb3hI;h{stLx=HB$fFt{ zdS+{&C;*T-SQJpBO{qQGC0joe%4K^EO>z;kPFV|?uGD;qbZVApmv(9f(6sxxhi9i# zXVVZ43ojclW2h`N>9uf}AOkm_HHdQUWfHb$_6MF_#7@L+8LB&&J`Yh4O z3uG8kL5z|s0u5KBT7S)=df>AajKQDaB1KJJ_;N-l`bM|hg(_I);xD)BPl`cxFcJ12 zP~lFS$yl989mJ}w(N8$ae&LDp6k1P2{}KG`7}+`*A2-b!%mgd25ukLHJ)|Zu!$sc? zV6kn$VoWk#2qudw0$>CIEd|O1SD_|~h-*Ozh2jN84et%Y%FBd=Bb2Auq&`NGskEkB z?07O!=}?nOh|I&9APdJ94^7~QK021pv^Uf52dPFo1qwW*=x~w(O<}@f8!B{}qo!57 znfNquE#lfKVWDj4KbR<|gb{!r3}6|IU>U50yc*%FSXha%8$xx;3@m@fdM0KjWI3k@ zO*kWrlC5;BdHS?Gb!u=DsKEK`KXB+OoiwJoC{1&9rntJZT-Bz%WdC@<7HJnkj?uBy zDqCsvt62{}9pC{3f|8p@LX&khtwD|II+g**uKY3et!psyT+gQEmlE4I5hF((D>`t4^O&qU?#X`uM~vY6a%?; zSm!n!z+vxV_wr*gip7|WJ_Y^TgA;;KtyLf(k9HC5D!Rgdymv+4;eS{wj9Cf zZL!f|tGoQEQLDK~JogG|A};DxZ+ZzLF;z<#G4c*PWN-U?vFFwCXt_nw*=nvURV)_JOHgkHld|_P zEU_J%XUJi+yl00DJC;3i6$X5WeJ|=eGHfi;t+Mz9=oL84>#dNl7AMUy+0atAtu3n6ZECHLsh6pZk!~9% z+96)}g@Y&Nt}@PA-&;~#SfJs|Vzd{PpFauzY+A)y5PwvB2c9yH7Eqtq)FJ#qNI8l& z^6O6dOE-_Zr=6%3NSD}}HsN!q$AJMyw`K&gwn-$KNTdRUZCFg)xYf8dC2eFIud+p) zf`I3G^Nj+hyN7dJCvmd3juXm=*iuJHQ--6Y;C3#;GTgmb`6u_os=BQrT%!Uu)7{BSw3d?(do--yY@5N{j**4U%H&1Odt=(l80E6<04rBP7$) zxbx@gVE%|7qfhhe>_*I5vl3Uc5*y7_s4%}z3BJmGIVn_m%Ch97!<-BnM$Pv`&jS)z zox*xHzyaeS3lR7nDr~$NKs{&8x6Zd7Js&-zzB5lX2dwWUVd)UY%2*0(399$Pz~{#j ztx%Bug%Gf{;9|ku{b@T1Bxj;kASF^IQX~aP(xpmgy%nKV`b}2`zXa$T|3>vS`Gvt~ z&BSXy^Uo~?qGHAEoUeQyM-$>iVeQ3+= z3FR0dUpBXKdQaF9_W*l{PXzWLdzeo|`rzGq7V;eG4s;9A9qtTt3)>xf?!6(z_kerI zPXza%d)QAz_rQDTPlWsM-M$t29DX%_cLpf{-h&RHhe0LMgYY3h5#R&#QK*FYAl{A^ z8XZOl(nF>a=mYs!R-n8o?M(@pPNW0s<57wC;k-sEfZn4HsE1J{{-Ov|sf7BV-mVl{ z9aaa{L#`0$1N+!kpuK7BT?n~Otj(;>v4&tJ)PwV3KoR`~@G-7Hd?UD(EhIa{4z!13 z-QwVO&q2=8+Yd2gypaVT-*LD217$00gKe-SiXLh!ZUb(>Et)svMz9K`QGHt0kQ;FY zR3p1BX-FHv3%G8#AZb_|(F)uaZt09}gd5<>q=9Xu8*pVh_6B0~0dQf~uoLkFusUUM z74ZbPI%nXGum!&k2zDWGNnj8@0tR&-yI&*Xe6bO~VqER*ex$PKkl5PT(U1JPh8 zmbVju3w+73qz`XI4uEaww-&uFIUvXQ^+foLVo)v$ANoYh2DOfP?EZns4ZXf3;}4F& z4ZfanTG|jC5ew{=mpM#0LI>~+v-H<+gbwH#63iQ^5y2oG0~c5DlF1<7Zqr`UVWK8)!YmqP&4{1Y|(O?0Pst7Km-M#S^B{a-e8hBaZkPbHFTvO`d=a zdL5@Y9C5@bV1~J$J8~V!Kt1zYj*tux&Ag!;K^mZK!k`?{8?E6aqcMia4Yq;QjK&aJO5l*I#0Oo7PfX;6~EdTbx+3F(f4xHc)+?;H8HJ zNJNeo2^vDV$X}{DW_3PdITA4t#9sNzd08mr(AmE^guGX!-H9 zk;qF6m>B6OnHm|H!Qk$;=YzLAF}Qhlo|j8=l8K?WF&?&s&DeRcO>?uNWyeIFz1c8J zuQ98?#ZJLve3354BOW|Vq;Q2ag|qOkcc&^c=<28{BxGG)4T2W+!7bpiZZ8_aJGr_5 zX)x1rGtvtqk2woUV4{&enE*>6^{AZs<06z;nYg++-9hqP;>6B}iHnV*EFI0!>So2r zxZ8p=@0p~2M=FBaUS)BwLwVIhEO-|9uw!ZUwtQ>r$+w&B<2hJc z%?%@EyyZ#oE@I?mqMZ|8@ffzBLrE>MKQScXF;xf?(4YJ%$$$0}TBN;=pdW5w&DI7g zT5?mAuS0@5)%j#TBwP|y|i$#2#aIgu}9v# z`S*Nch8@IEi{cD3S-v)5X@;-h?!#XR+~>DjGVe#V`Hmd5qihVr z3BnE8mq0)DmeJ>zXHleGO0r)YIfm7T$JT)mS)ub~_{kAh#ELj(m@pL}Q^ahkZ>5;cP*hBD0|MH0k z1=YpHPdZQ{N70WZlrv~7Xfj$H1-&;Xh>a#H8MLApcPTslQ$n}oLmjC=~{Vq{3Rxt^V zDR{`=w_OWakaQ)9q(mKpanIPSQ>^tXy?W#i+^)>3SY0b&c9}Plo={G|_7PIfjVsqdHE-Ey$_bDEoZ5pG}@wlmDSwPwzJHHmYHoHr%MI#lU50Yxe8 z6-Ts8f4hzwuuohw^V9>`=$j}PACN9HQB8C5Et<+ju11hV*a>)^TE&u6 z3RTM!$lpQMN|3eZ*e!YUai>a*S7gIYf#B!fLFcO0NDQbEdVZpcl!+@A zTlM55Xv;lgxa@}Imf|FAC=V=f)Xb1qC(O0M_>DLCp0UXi+Uvx8ej8H{+^o#WU;YmnnWb)NJ~qWn=wH^VeVV z_a9Gg0m>c;D!=I%M(Vte>_mcxoBEff7AvH*5w_Y!yh9OVwAsQG7Bv8W>KKOJAQE1L z45-V0Yb89SksnOH{z=eTs8VUELM3t1u%=_H3uWj_34KkEhs4 zmcBS+-WI>_mievVkrjp`55dUS zVS}IgWaOBmTcca+Q~UAioo)}{#cbskp87oCT}#hNEK*+3AYr68EoRuqTFe8z>YfZ! zlaE-E5`e~f1D`!)fUj;rjZ)ezOKYF}M5dItWXg)!&FbVOevq9Wv6_@9S_duXJ8Bb@SHsLoP6b+-1Hj)*=WS_ zi)Z%>B|kpeWPf3O`AmW+nVrE^XwLFbJZBJ+SN!FrJdA~vAu5nk5=GVtyMi;>qik3T zR2u6I(i~}So=U@6L99OmTaLl5Xz)T!*-MbPpGdiiRfh7&KIuy~ z%0K)3ZyNVK`f1`t-&tVfCJ6U|BTkol5f2Q`)2X>G_AaG-fsjz3{u6__8~vSeOyTP~ z0U8V`{UPGLV4UB!S|uysR+Weo)J>vm+Ot`2dfp;(J?-@Vq`1~0lV&P?0zC;4w8#xT|W$*=d;AckJmY>a59$ZlBl1zTVgO z^cJ(zsAB1Ug_fhrF}S0(U67H)AP89MKmpc7iNSg= zUAn~Vdn{vuGZ~?1pjZh76-Ya7GVxaoo#IsNk!kEFcOrYP6KzVgn}A}J)-sWkl(Z$Q zyZkoq-QJmZxtjc>sIQb_zSc7N!`C$9x2cv!*An-H?`xMhBO^6bNRp!`C|Kc$A`(8w zR#BrQvSLpSBtb4aCpW&NgfQ9AYm?ZwM99JzCa%cSjC>!GAM59Sql%zm6U;ZSclEVf z?xp=i?p^vBroRW+T7L36PUpOpKfE)f{z(l|hyK!pKjLu+oZpPnI{qA7GV^rKVF2%Y zjY}N!j)6l#+FPJD)|>sTN$}lYpMS^D_1hj#Vbkm-876qmgWE+X-{oO2Pvgw=g56x* z;jxf2GElV1--gdPQzS>Wu?g;pcV<>;VbgHAfSs~)l?vI(Z>z?cfQ4+3iR_gR8OxbA z3=2F&wW{FFu8DoVLc3K{Bhg3pAq!hNyM-d{Wl*~N zr6DRvN@5YFZU3^d-tdd^*eRb5&9DiIt^Ij(2fXjsqGld|O!4`P^Y7p_VrXZ)%&vqXbEFS1drk#OFE(?c=TS&Rk^Lz;vrRG4{ z#q?)ElU<%dN;^w|iJqK=?Q$luTQ1syh8leW-)LhGhk5qVDV(YFpJ_kz9{R7xX$IXq zPbew=%r2eFMVN^x=V5i)v8P;CUZR&e?NlhSSFOhW`NTZdd9JOk{F~Z|Z&deaZ(Xun z@n~;f8y^%15<;eqhP)p-aalA98qSSX$@1(PerTYlt@h_5PRuIu-iqVk zTHpCWIjUdoY3;~rk6Sxgj*Zcsi^-5K`^JjIB4x(?EYC+vp58Gn8<4bgTB!J32I-&T zxN%O;IEl!uT}UOK?z?5)_WNeX$q}iK7(QmcUp#U3wE6kWBpJD?bCx99snAhA91 z1Y1%2MULY_>V)Z5VF7%7dhoAzX^@U}DFqrt&8_CPZRuGI+VdfGC3Thi&OY&hx}Tf- zJ&-lr3f|LPjnl*YyED2+?QsIMpKDKIPk~Bn1=`m`agU*XtliSaH`~#E>0=|9B_Dxo zAl@~={IC8Il}PjXdQ`jXQjc_m=DD{%QSS7?m+P6f|1DCTuGj1AuJOj#Yt`QhK`qBQ zm&Vzh5w4C`+q|g?yQb#A8|$nw6U3mC4im+0dH?0~&1MH56@zY!R0iyuw>t1gfLPpy zY$FORp?BY2H=5Mc;`EhJ!xUkb*fD%su8vMkkBH8#e8?WzG zytWMSG5>ff<}bq|4^{w9Bp_YGWL9+!Lc?46x1m)7}Zi&^~?ft**wQD_Zi*9${ zr+4lHry0r8;}R`rrDJ*90V&yRtz^&OhQtLmEbo;uz08|ohf3%_!rz2HatrZ3{Lf!P z2^V82U5u@EGRNbY(=+gv7q3&>n-u}F{7ne@Bih@YUK8lfbwOpG3mB1oBbj9`3`j?v zC~dE;xjpMO!TZJh#Gqd6~XU7na@WimM5{ug8K7$r;7v9kdVeF?S{cD;HI8dxsW|HD;kd&*jb>Tb@&Hr4%81kJqS_B1nPj#IEW@aW{jfa6vNNB&x!c0p z1ehw7N;7XWyNC1h?^$>HQo3u%!8k&(gHSbg{A*&`6BmH6`R^iD?1a~W18xs$48?KR zvO~^3-`ZFA!oV*UYcx$)tDY*_Nv^fp>Yzf40}{e4%IT9aN}CX!_K&l}&H9QI)~=85 z*$@Wud%vJAtmO=(cu;b|XhRQ*2EEk_#~3=W{VDWjQrmlU7+1*6<;MCZi{h{yvjb9j zp}l$)C;}xqIp4uzYK4=qz>HX8qp(9#dOUnSGw))7kr27`Lklzr!i%+qzWX9 zCCGHVd>Oe1;kBJvj<0iVvT?leS4pPDOd%Zx35o&`Rgpjywvg?m!*6S<@^2NQojUK6 zXo6*=It4n3Ct^>V$E~ZxSB<0LSzGh^FBYcyuieewl?kz@T*y~6fgA67Wet?zOm2BK zk*3~-(Q^}XFIXs6n6M+}u; z!K1tQm0p!4l24=*zACbUu*DZHb=JI1XRthBthVm%CuVw|)>e3-D+^ureM$zpH#&#g zE4^YN)X6}4JnmM2vRaAR)3CLA+Pv&Z?$(P}=*EhxCCn=|BCeGsx zKZJf~9igRw$VUtsrmw}P$q};6lkCkfx=V5;#?wd#$5NcPvs6gu)?qX4QDMVhk-c+M*H&-u%#=2UcS8;SKZ_VYh2k50xi-{2t zxaoRl*64S=1^I%7uFT)$5P!-4+%rf$T?5}J(CL_uZ1k*ECb{hZ&^7F4!*yNxw?^Ga zz;3_xZ9u#*;x0hGM4<0%?OJwTP@z6RomeyI#*XPq0UJ^xZGoCh+X;GUvI#oPqp$7e zgvW4VE$ZimB1BbWp4&PbegRw7;N5^T`D%Koy~)x#UWMU-EY+}Ev2JM0nmE?j2etjy zOrBIeN~=mkDvaj z(P3mLp3c=M`%{Sdq)PNpRr#N)n~MMB2-egl*V$>YBG=qFj19%@LyZg>Do>MtdcXW< z2%NZ|q0v-SqrQOCPp`3mu@G%m@i zmEQvt5~IETk{nIaDw6JQS(V79iQ=dxy*41VHI`z@WGO*ePHQoE*bqNwR)>w>Zml)7 ze4V1&-Wxk$I1=xqX&bI5cK~2hY)e&g(3g*9+!{IElvB0niLi z*was^=tMX#mD~gJ#5h6+Y+Z6rr&{&05~&hS^HcB9@El4_weApnBDNZ^4h(-9AH`G- z%PJb9e;HnFAP>7FiHJ{>91{RD&kWuyu7DtqAR-0`r}zYYmLVbr7^n0EWEL_)2h=0E zKtE4ELIxd%vv(ya8ETG_?@0g+kz{$TrJWo7=7D&6u49YB`K$0(&yaUE5y@2{>hx_SF)(o>S z6CV@up`Qctq5mB+iXRj*it;T%V$ybeXi+VDL-%uJi5gagO3OoOhsmO+yNZFZiW$~g zOS~l()g#Z!U&crLk-LkCG<|lNxIwOoA9SqiY`B2l&c80)Hm+lrwENSBYPg-Wh$}*+ zk%#Unm9j3se@8JDeMiN9N6D7KA?T_g-}ruvEQ!kSLL+9DhprQcf*Y+;7eQx*WU=j; z4|)>#iwKlT^i|BN6Z6Z!jk*eKOPBwCKadnf@ zK8Ou^lI(FJwO>0_8K&ydx*7It%ZA*(F!&lokd-(Y9Py+FIPp@jyjQR&GZ{5n#Tm|F z&n*R5p(II^D^cvEN@_68l8PEnlmS9@oYWPJhISk_)J;f9wlrBAj+AjNOhkay*smxv zL^!C|PNZx{Y=*i&@-aA0=~$IFkg$Q*>z@rZl++}ODnzUJl;EgT&@f6RdB`(WGa(L@ zDQ^_vuiw8x23*%}l z!gNZSoW_q_1)RqS)!Cv=up@swTfGtEvo~p0&9zt7Wg|P5eKeZDO0N^k{^l^sYQguR+Lj&CNt{5s?%wbg7j~41=u2cwaD@)Zgz?gp$*6$_?XA! z%+=s(*c}RMQR1n;qX6q>n;Lu!dEx8w<2ibYzU60ZD>^S2z&+m0MING2tqG_|Sq>R= zXb>%k?PFy!C3{yBXP!pXWLPyn)nA*G7vSPpv_>gohk8Avv3OPXUf;fY{UVK-Zh1~k zpJPXk=-#dq{MpWiz%<{d=XCXG=*fH;Rf2$I7Bw`d_R(w;Yn)XNV-OHMiWouN?QSLN zAVVCQW=E7Iw0n>#%t_6z56Q4^`?&&d6CZVXjLqx1xzdLCVg0=FU~g-9a7J}|cyp^q z7?p5@j2&gjkM6~Hk9-e0?{D}w`-F@rUx!eM!v^r@;eBt*JftZ z+2o4w$fJ-%y*|*LENvt~rR4fzv;s+;B)uo5sE5C8YqLd>QwC~A|4-l`h!!>?+r?@L z(m-Q|4Rsa%=H2sUu(hc^7F{8@`WNc1%Xxlx*E#hyp-FxGDB-3mK4SP}A>+gpDqIJ2Dl)w=?>%4}gTPJWSj0E1hDn8@iJIgi8E z$@mRn*&E3`vu{|C48UstXn4Q(mSwPv0#URHDnR#J2rQb=)_KZ9Ta1jHCX1OvpsC)1?T z5}SHrW&pLm`>->SO*E#W79}v8MOaW2xK-Wr6j(r$L$ywI%Hf_LCA?1tS`I>q&Uuuc zc^-L`aD!8)7qmSy9WU+;Gp409)u`#Nn|*tmpVrSW25c`}u$=FR+9=L^%RBHE#*Qm@ zFeuJa{}Uu?_(-9xF>~Uq#_DIK2P4E9U}Y2~#OiNlASJ{aXk{`f)Z%BQPc2l!UxJY_ zsuEWnl|0HCXB`ziDvi&^AT4Ag!%}(~l|AYbrZQKl_Y4|3j1KNV{K_h-A23ve5 z-<3&6_=U`45qDJR=vMi*6W)U80d8D(l&a`yc_;GuE9B6VLkQh3UcJ0kNs2i1`~B86NroOqVp zQp0TEodhYjj0-w}I`oxuc?#|WZG@I=;!bJ;X#0LqItdD)fozeRs+`ooZ=s(IEARh8 z+G3fNE_3oJfcCdZSfFwu5@G}GIL}hf@04&_$m`5MW8!mS<9C%@q7`$p^|KLKvPn9z z3A9NmMCo9-31OK22(+(1Fq7HESP?ttob}LIx(Aut0^Q1_)DAmA<$F$%U=w$G=5y68 z&Lr#*o()#+I2U>baFX0~4z|Hm+2eL5VG}!V&41*i%-kgIfH+A}F5D*U2+i|My(f3F zt&1g0vONrpFZOIixPIlEop3Byi!tn8!|p|yvA)$>_H?(S|Il0GeAlx-{mMS%3=HJi z$hEV~t+7fQyXXsbckw1dIOc1hHged+90^=~`g;o3=4Ru6$k;w;5s<{f9d_KNMLa^r zXnB(yV|Q(9k1;<_Iu56@n@Q1s7{+&OU^(Yz!>L1NyLZ+Y8Sl!@les$j*T=q9J)y+y%v#J+zm2e{IYxYah~U?d0iY zX-xeEOe8Xpj;}g}gy$A!&~tNIz{D9?0-LG4`4>;TrtHL}<_fLM(q|a$^BH3>oJ5R) zlbza`I!;^nt|a+;wUV=PZ-rr06)yIITno)s4;vd^)SyCp154-F(5;x&sww5doEg|8 zYIKVAoS8@9mV|ygJEpfURZ4}08?=)utLgr^7Ke*icYn99_vNVy;ZO{hG16KVZ}O;> zHJoi|jc{#rw4J3TQ6?w)OuQN^QW@(+j8%=zJDckZkC~Jjz*obuzc=;g*WB48+5Ono zgAV2#8YP%u&wi`|&!suquyL z{@B68M2QNG6{eF_7&nb!FErguxip%F1yitpX4Gw1T0qOzSxKICLmWH;FT&L*FB)$? z-JXu=2hI40#1%b(CHr`tvN@nMj7lyh&v92*IgHL2=pVGG0WLx}#MJz;;aloS;w8K-XQM z>s((9tB^jEX&qbc%MmtW@-S1V=(1Lquo|>CyizC`H;xa13AmO3vl9*ict*_&(IJS#E5#$0CT7$|{i+kJ0jzOB z?v)CnGZ1E{X@me{9W@^X5zt*)=V*8McRc@lG}ePm{$6P)uh*hd?rplHmY#DMrbi=_ z%4Ga#J`7#L&z1_%1_jXPbsfNQRh=e+QKN9OLf%9%l|-r(y{g>*PlhpAAa;oby$@<5 z`{da#;}zy4U?v!V>;9@=jWUy2Y0g;|e>w?0188211)-s=z01Bab9#u~R(-Q>g1M-vwJokU^<_h&m+(VM8d)0;yzzk30IEz{X#eu`@n6 zVV#xaC}QXEC)nGhIM_y#-11K^egkm`Z=PRoX&gCmk64db|2n9p>uz_yr!JSzE&3eK z5<*9w&>kL+Jy-LGCQ&liP&r(4;fy+DPDoEDIYZa9V!_N4|1~Lxpg?Mp|airi-Z8AV<2<%WPg#oxwra zs84{Wbuxwm{XE``^0`P7QvOhQ5wo6e_m+-y2joG7RY)2i9LXC85;`%`@8pR=IEBL! zx6AhCix{@11mqv0S}t4@j2kA>`AiC*pn0w|O=3ZW$&7L^&Heb%oB`b@ZHXQht>6-Y z1+*tL7a?8bD&UiO?!3L?q4{EAp-_T({2BV$iJ$ki$=BsuukLp@l6$|fY})}O(HsAe zLDF@osKkI*tNyA_=~sTYmAgteDLH2{x&e||kX~m{7^>0^k~*#+OO0Oz_qWPd_13%R zGr|^~+Aa(>IOUUU(i<5(PDL8Bn3rt$;HhUZhVBALK&zo80xjB6DoY6(k~$zX9Eq_M zd%{Uc?ev(40uih%`qErAQ#%*=o+&M$| zw|948a|D5ox}y-iTiaL$NgoB6miiesxP24?%z)Uo^)lv;!gDzUBd8&1uwto?^t4e{XzRN;D< zP0*d(z8D-fGRqrqC>Q&X8!agFdu^nVlw-QO7i3>{cM^ZTT?>0E~nTB79=PlUv8W704M!y1oPU-36 zSRU`|#^*CKNlYWmx4h-cp`96Ug2h=fQy%WdWX%!s8!F^>+SaVB8)e=N#LuWn?u$l? zoNZITWX|)esdN46kgXCCu_K|UT=cP`{_fFw+SN`d*SS7sIs3mR=6^}gcy4R;FLbq6 zdpqiUotHi);&Z2FP*tF)-Wl$^b{|l(dlD3xo~J0L?zTi6H?$0*CsY#4RZ>Qqi^jBQ zW+9Hb)6_Blw9qts7clp?V?9Ozpm+im=KvH020TiAd3?QHMR#M_omBR}wjctWs#qUl z0o#o))$(_|HB%4o5d$(78QwO4qPGjTim#ZioeA&4+w&|LU$=n7mm|pVBCg@l%iKd{ zo!NGQh(%o;USHB)@ejMf?%aLRe-C^<8_P~M^Cv>pLi+NH#i$X<`2aMq%PAh2Hhnqq|*PFhO*CcW@l<=E0=#d zQuuf%;hwO74pK$bhorY*;({UU35rZPCEl^_1e=;29D%#2{|1R9WaskgJc-fz6}q4y zaKSC4enO|j{9;^q?63*oy?pd5bAC0sd(;!S?4(=yPGyHUiv|WIqzB!HA?jqJLi<&b zguaw)8#YTWnDG2#7Hb<)IOJQli1z#I!4_+lgQ_QH0r$w1@SXRSo>vj} zeufv6#*O0l4&%OR6!C6@K{@UI8~dZGj6i~VyMOYQGudv$hp;h9Qb_3TG2uJY6Q0g4 zFPV6U-ZkoafEhK661FB~G|eCJ)eRs~ydEFsJ*K&nASQ%W29q0*AYs+KgMM(KA)9#4ogcJ4x-{AQR^6}*ItS4gP(X(gt8kIEXg6k!5;j*g zh>tZX7SlfeJj@b^7NeEjcZBVTbZG%ZU5q5{rImuOj-QVkho-jzsXA1<%rM*7p-glK z79^`AV!DNk6m6bm>eK;hh8ACDe<>V@zj>4z;OS*_x$X%hPD7@$Ny zFXSV!SNw?|9c)F9`4#VAg3}6M|REL(rFY56cbbNxxnI@ z2V$8GF`54vzXM_!;7{g`kR37!d`vv=EiMaWJo=z$NkaWj)B^pdpb2u0wh9>z~S z%zrWr|73)JGN3;hC?a;qMeKw`l}aixnSkMrqxh^Qli&Ua#BxMpGANDmrXga52JS1W zFZ*pO>jM1ffu+OuNJCF@<=hYUmIK2llc36$+0&8Sl4EzVJ^gqi%?cgz(vcM<7|%5( zue1NB*>)bO1!rx=dK3u@k#eTJa(P=Tf=jA$r7iPUaQI5j*=%M-1xGdtQNGbD9D~C0 zz6OPLzx84jfLOX+u7bH{V8$z(G7DBTPX(rSsiU6w*d3|Xg1r`1F&05b&91C>TXe!< zt6i@5&)Dk$8)a*bCkp}ZNfOx{+GI9`Mz0i`rpNZxNhKY0C^gudRNCpnSM3F!H3uQx zo9YZ~^xOB=$(NYb4^6-tspb^agU> z3(t6s0sD_>Gq5=z2UdV%!d05Ml{4q0D4ki%9^YS6y(giw1OI!08qwfI?)nFWE&d1L zmF0h4p#Hz$=l^=pcUGE`#S%r%UAtyM4-V-wXyT{ z)SbTOea&Y39VJ5a_l^$;;M+eZu(zxCE49xS01y;E3>{bnJ=i~~F{lv=%Pz)FW*2B0 zX`0Hb540at7^2NA3`IO1h!(?vq(BIW7Kx`I3WQ7GrHl~@5SSInlztM_WHh-VVz>V(wt3OxBG ziC^qshlLeyixlXWVHEMNJ$mM{cEvYnZqliqj;$vrVB^T~Hf!dBwH(p0SX8cyoD&#Y zw5Dh8hZ)AEP%6&#+*9L=&&-w#nKjp$@{GKGAnjG>AUUqGbeP|-ow~T+Z!fPL+*6g= z_4!qYGRwRn4I*(z(P>CxjG@_oNWkg`XM)f9yRdKXfOT@*KpnzA&p&7$YBl$eA;XI* z5_r2qq$zV%>K-*7H9NqmGb;`wdG!_uc@iZKBULozc|F8kS_0(b1yoTgdUeXN+LNqe z<{m~Uq~!jR&(gApayT;xL#dJvl7oO|7QtWlVbeG=C>^OfRJG4-{?_64a(kFfM#OdR z+3E6a^J%)0z;nB6;!GrU;Yj4p9P5}bAk($PWrovX)?xng@8x~pPv>lQ;%IhaZ9b5g zph?F=@_3ARK41FuzJQ&x@ z+DIhJ^ibs8(IaNtHNT1n{3m^!oG*vbQn1Dm))v!|6B4?SkOVPlBXHmcMLK1Ox=^wL0#ocv1jzt;(6q__;xeyoS^kBjSnV?q88 zO8mbpNVUq8Ez$~V@BVF+>2N)gz=gdXB9Jr$DVzWzMD*KwLR<;5ScToL7Fw0qig6bA~gsS%A=6O_hdVOR? z&%xW~4)?LoBzvzTZ%?ccantii5W*Yi<@^5A*;X!kP!B()LOPd*DRKzGZ-C!1 zjZl*I@OE;L3X_0G$T0@E^tCt0p#Y&t@Iyd){0QWzfRPbm^&n~r#1R920}^R_IbJg! zLg8}QfY{0f$fEb%=iU-{fj}pO3ZA`u7I-GP4G`UWIH5_vfI)$0{-u9spi7D`ONrj} z2l=VpM6iom0PBXlS^zB~1Eq%vVrfLIA?tn_3>!u&ZV=Rw)SuLkEwX~N=Ea&~vZEMaq zX2_c@4)9;8gQaiNnm*3I>g;&K%VMD|Z__hlk6!Qp2^F-7MN_%CWTc(NXhX5mnnEa! zSC=%6p1R=C0^uqvHz(p`O4ZpQ$7nPyku5cqk)0_rot}1P%gN3G+&>YP`${wmzZrUFr-QXu{-Qg#0Wl%szp#V(whtbasxDu|kq|E~p+jeVi_v z+w-E+FvYHD8np1^^Leb-XEgm-wINW8A0<1_XN8~)o^PdP1Dfg>niX0D*N0$<--TjPH{5$0SkQ%PHy9`#o9g`xd3F-`}1dP18r%ayPx4ha+-p2WM3Gk7@Mt zX&r2aMLQC#o$xh({mskOlBg6d{lhcPK&P(j076k7Boqt!l${siC-eu`MzJkUcVm`i zf7#=mykRJ+P>SIVK}fbOF2Yd*FxBiG^JFKlDO_j$1T_kAPE?2v!HO-16q`ZRZT;?s zS#Kzozg#%P!kfbK4wyW7jB06&D#s+-5Ki<}k&z#&pgO_nF3oZyVL0?nlq9^VAf@ zEH+rht&?1rL1O{md7OB$y9r-a!hxmH8=ruZZrzXX?zq7eww5?RH|xqU>u~%L&K|>l zSyxB6gH$!~V zUc@`r!6UyMHStJLGYX|uPMsxlWFNwoN5agu>vv^bt?uD-tn4=zB3z0zX}b=?V#R5_ zVmP#Gq2XwGyR0BXE%(A$-dK~yPdb<8TxoW>#P4J$WpOt>-DVJnpENcyH!^wYW3PGI z-_J5g=u{4dD!R-d(R5t5LfoIh+#@+SjG%{TP|j}(EOmGuiu;><9U6L>C$Kj$kR_D#M_MgE% z+V<~c%5J$jIatQQP<}#`%b8+?l;a6c<^?0ZOa6*hyHXGopU}228+Ay<3~<*f4jHC# zEzg(#r)c%3h{LE|vXn<>YUafHrwE5xGNp6TJ!W^;;Th7=CR8c9r3DU0EG=Jj68JRV ziC5v+-QWiLehNo{WqsWz9=~_>YXr6&)7m;fe~{x2nyFo|LPV3w>AZbD{}_T_N8e!H zv1o1?ivJGBM<0r_$fBuPJ_LiHE9wqQNM7Js~;Z>p6l{pk@^ zAv+$C0&EONdD9{GG!v_sD}EVX`n|HKaSn3;oEvFTPR7>L_H|%%LY?=i0n{-?$psO)G@ko1aq5p)Hx(^rj1EHUTQ;)VOuVO?617OWItpwIX`~9-0YedHa zvQk?})rpD*V6FJ`M$Zbi+6KlMS`XPrijfG#8Cy@$N6QF{vK(74>DN80?=9EZVO_# z0-(99SBU=3qPN1riaD_QtIx(_9cyG2O0ROV4Jxy5m8S1Yeqoz<*aonQVkMdK1o0ii?uo$q%hfNlMHf2JZweSVd*P|8*&|)rL9*O)x)i0AbtU z30OV`5@HTqim>hvNh|QP?Ij)cc8<0tq}DcKQ1C{*6eOU=F}uFy>KfnC!64%dafl1BNaN|Hv_7Sbn{6PG zu$qzdhA=Hd9tj4>%{0LyRkpv=4Z{#=1i>dJ#H1HP6)!k{xAaM;AtrT-nOytti)EJU zoro!g48|@mjSNU4j2|1Qli}Mw6YBEGqsR>D#rRWL(Tc@wm@@S6gh5-h#5ECiLF=|C zT_Yv08fNuDhQliG1W(@#f&Gt=L-JtgYthYtjSHgxLX(a?!4BNY+0G&}OAK$xPlrhb zuGXi5rQ`U97g3FvghvS>&WaHevJ=K%5@$ycZKH&HB}EKNh9$>ZGi=Nm?ujlw$fB(% zT4mwtl&b5XM>K{nZvWjbE+1VSq;G6z?`rH#KM2;pNuArX{J7aZ7}fkMti{%`!+5xNsKDzmA z1uoIJ#BXHsHl1t&xN##)jJ*bfQQlQ=gL`)JwWBXB$6hh8`==7-+@o03!4$m~k~B-m z2%|DR28v6T7n3K48K@ig>QWHWuHJzvJA!PRm-58xnymE;3kueD$TyRaza>zh39h0T6U41@I5AFEmjXrkDb;l2Mi^@zTE^#3 zlNKf{8w_dZ=*VOFUEM59X&4_4FO~Y-NV-br{Z!|B?J=}wM1~=i!t5Ty6-14vqhfJu z%8a2;?8`uJ%v@~cyi}OYmy2^_&IVd79P$7p2n1icN&`QDTy706YRu4BS{#qX(!BbG(&S2hy%3oVm`2+S%zN=_6}b z@mkMf+e@wwFDLd-AH|!mlJ2T2@z0}~zoL$S3f)y>S2+PmHaL^dv&TC*7{{a-H(0XH zF=>BSFT?=p|Kjj}==5iH(;;vOfY@B`28G)#lZ7h|ZTQ9A9)cUsrCHWWwWTL=33e#E z*UN$5&Fe5;;-<)F)m#7sWcv?ZmMddvvtSDqk?pJTzh*78=aS|T(Pth`K&OjpUdWOIj)G$~fuxv8?vc9y+KmTpI{L*zkw zz*yNsyCsYF6&mD#Nz%4>OA`D*lw)IBb^%tl-$qzi^)h+w1hLEs$Y#jH(!)~V;ec`c z#rQy4H->EsOPb|Uwqot~XW_EO0>m67FYo#qfpXg87@?zMUX3=l#>L({CIex8oa}_>0*nH)ZZUwwVSgmIi7^iW~46 z0Ur-fF9RndDb;*$Yx?=Yw~8Hi5iyzCr>~nqSpp z2MF`CD9IrlgEa)?Yc=s&D`8?WN0-(Gz5wHqbtk#gZ>5f3=J8o`WtNxMu5l~gs6EZd zI-beNO<4eV{QQa)`^s_Ct^o^Djx^C-REfR0T99XP<1A z5-qC~8XwV8^W&(#iVLN*Cojm>i$^+c{q%yq)CyZ#Nv4MPV<2>P@}rjSW9XUM9JEJD zb4psXuOZaUTt_`pu<;{7O1S$zZ#Sm{QW6hO#@*iRws<)rN;VF<+$|1n?UhU|3v_#C zk*rLh+-dmL^#Qn63EuXydWdG)wCd^H1od6`@eFE9e>_-v2J_3c8i?HOgA1f>v5kj* zTbhT)kM-hTerObDd(_`jCs`Tl)*zIF4eh!vT;pybWP&wF1Gks#i5^Mvit+wl<=I3X z+YXR!CN{P37^aJRVIRlbvo9IR_p2?0EaCcc7eaMnzRj2V3S<6x#unP%`grKx*?E81 zK1b?;TN0$9b5?=T5dCA5kBRAmBnI7^tT>QiBTz^ZuS_HyNth`Tks@3qVzJ`AoUbk5 zzi3WPTa?u#xn%h47+W&3eYfCT>ehjqB0iz(;o`7LMj3Ig$FM+R z`T<3cTm~F!-Fa|qK7$JureJ#10Zq4>PYISdj?|j2Zh_O=>L`hv=^Pamf9$&T?Y1fo zp<6GcZ1iuu;c}!L=vymA*-_Z4=jajRJD9JRZ&D|RiNfW=WmD6hC*OTv{>Yy6DqGUy zOoXuschAUGRv?lM))Y%Dq_NwsX=2UX95GpJDIA$_BTgs;*9BL&GC>JqVD~Z~f^NGy zr#9k*udZ%QE+$A4&m)7YN#Xb|C2@5P-6kK`Bf78Nw_Y@%@93&;k!fE+V%ww3OM?+m za=JI&2Vb_gRR3Psk52WAw)D%k^z$}~1S+%KQ~9^Kx$U`a=pXzs-}_rv{d!~C4aQW< zbfmFYfkZ`&m^j>rK=-tlUQ*tsaua+y8MA2%wnvC_uSajeHG~OH_k>dFN|cR#kZ1Kqio^`w2rR-W$XcQ~HUypEY(#pgAWXZ6MU zZk>bxwy>rr-vtQkrD`l;^Gw^JwjWfU|;!GT)?+FdTgu5!YX5*H(q}|8&`YMtaT@w=^_pk{zy{thRH5Y zKYk;qbtymbi=<=~{|%t|LVrAm%+EydR$!HzdbtHp&#Mj0RmCXuI6jOn9Kl;0Zx|n+ z*9%@>hE9_5< z9esNEa5q<6C^=)&oAYWS^6<;ldw_kXPAmr5s)FK#>G7}cSk$UA%frg?U~ck=1{0ag z!~rj4c2*m+I#I|qhLC6lh2djZQkM9mZ__*Q0z5#@AGHS zst?VE56y~?G;u04Y3BS4tYt;d5j#EPe#d|)TY4UjwcC%4rxMz?bYLXJgZ4xm8?-hd z6K;Hgfa=5u>&^=q5{eQA|Jull-a`vL3x|>iy(xoe`y0F_9itz2Q`}DO6aXJgzj!7o zDQ^LKn~oBn;1iJyF0%jv&>*9&?yBAl_g;jzM{` z8#0fQGcr$-k$I0BdeBTHWvS51EQD-x@*^ORYJaH63k)Ql{a;7Gt^u9&=vzb}TY8mc zgjKd%)ERG~n+gos)!!R~uf#cnw+H^E;MhWTX)j$DJ-TMjUr6rlFSbCcZTksCccFQ8 z0b_df$Uvll?R@xq_%POla~j%rLaAlc?gfHMDO|f~$t1E55#tUW9&bX%V@QDV>JJuI z$UlWnk>d$u>%zu!o|06j2+6`5$9Q3o^6GbkML%r~B!1d5&`?U|xM?b-1S}cUp+eBO zcf3Mr%TUg91pd*%Zz2Yk?LRCcB$+zFp&5JV6-Z6|@lX}Y%&kgv;j$n!1|;|P4hBlPlyYjRWJ}f z6j=y0=WKFbCW@B&Fgl(#9Z6m1u4k;sz8f7~DsE_V7GTQclCsF?4qTnOL~m*T{0Z?L z?_gRo6m!+;<#%U|=fd?RVkCj;cUek>P@L{Uk}?_2ibiVd>ZP}ol1B|9j#3PH8XDwAOHaGPwWf9|4;jWdSv{~ zz|8hf2h9r7vVnZ?-Pg5ggA*BgaNG$e`OV;k_25#>tVQZ6!feHzNn5OkJDqqm3V%SX zNc15Zue;ruIMpdu@+t!&;eOz&QZP%1XT%ejlA33(*q5|l!>n7RW@V-)TIdZRgcDyk zr~@?7qeTEzQHKOd+vGZ6xNLTb&^YSaR!7jKbB-29PIkSY5^duky2lxnkBazN30Q>{ zQ}`1kJ20Vx6$`{6#Ea8ALNYn+nr6>%;sl~`(4iAuN@0)Dr8i>^HpA)31FlDuk};e-WgOIT<`RRY66gYy>^tSnr$Yu`u}AS^{RZELv{_qi3!NBv#92fL6 z=L6%cY`7w~j0F^tQW7FPUX3lzz8VPcrw`gAT`a3|0UY$B2>+IBC$x*)C@q~b4L_@;#wpfKxP^nnLx zALmHnKA}OCVT-_&tp$VXCoyXAEqdsqy%dY~R^%UIXxdw%To-1!tZ3Kmt+WThuEuyC z8MZ>Vyw`zqxg$XyAwqJEE~9SLou=pc^}1@eW$?4F|375I z#QYyNaPw$IY1>75xhePQU*|j_Ua=~Hf5$unIB$UDfA>R2b40Q zj_CDgPBtX9Y&N<=!X(OxV1iUu5uLAmNdr3`8o05Mou&AJ8lZx2fm2bOUB~yamtz-@{K4l)IUS}YGik!qCeO0-Ex^tb#S^hG^NeG84Oe2 zjJ|y-C#%^TNqn0K2l;^C!as;_P<<0s4Vh%^D1I(_s`1_}tH5ll#P+M8Ig`_z%NiWm zeG(d!cC^JD zhK_a)7-5(+1Pm&@9rRV01nTPR4*`CUqq)2~r79+FGGMVdd-~XRd9s$47Dc%^RPkE- zZLxcMxbP50I6Ed^PO^Z4vA9KjNkC1ufQe3cazIWFu9CHjMT-dIC>WIzv@crs9e8G8YZUv8tlKU zIR91)^w`hAh5ye7$-qd@!1&KWTF3r@`mqi|*RK)6bSO=$ln|gpk!}kC+k(}azm`df zsnC13)BI?bx-Ty>$BO3fV8?Ze^3{}t!||^qECRd|OYcDqtE!+4^*VX6P49`tOT{sR zH^>eD9w|-S4M)2XP!q%$`Vqt_r}>+OxgSKK*wJG0XNiKHTfiI#M8L?T%z|Vl4ubxA zCBFXk20==-IyP)>1MO~ETr42{l(~KT2HrJc>?DO(l{DV+v!zur5cf^4hVZN9bEVXJ zHpH`SMC*2K1m+Z=`(M^zQTF6(`9~C0KcXP}f6w=SM8U|y`X6b;O;`-j!wWyZMf}-X zjZ0f~Mx-w?QtXffzSMbPkP*U`6jQ`}Jg*!rrmbj3a=gkOaMIA>UrS_8im0)GA5<2X zw)5gS8cJbUen@7xxqEy1QZrKT8Nc)D4j-{sV6>~fGIrb*@glgumsa!x$FHmdVSv9E zi^Q>`1>?^e0eiOrv=&x|Y$jngr1n2V!an%xaK(1?Bq14`abYV-C1W;fdoRp$`1Ap} zRWCxxIaS!l3v~i_Xm!)Onl(?%@aXwlZ`uu-LGTUSz}28{#_bEr{|fyL?7vS12`1UA z*B_Ap{oFA8Z$!iLe-$O3UlN!BKKS|-MX=i{pY}?Ff|^`sFcb9ww;@yfSEsZ9qr!@= zZ3fL2Vy0rq>vm>p)SU~Q&E^_ku}Xp9tfwjWpC@C@TDGWq8+d}g^Y@YC&yI?-&I+Hh zGXo1cEU??Az)KI@2fBWuBx6WBU}g?0J}d*s_%c67Yi4^L5bQ;$BK$c%PQ(!LmY@wX zw?@)41D4aA>{eLrXj8OuS9Ee|NfB|BW38|lHPfx%&uHpj!Xmz%X<*;rov7xp4DV#W z4SR0LMu1+C24jN$2-<(5HMFG?t~@C$1xISKtI^4>qku5Xw$BFX#I=wX;S91p0%thQ z+<*N`u`Y8f@tJg8!p-?$m4|W?g;L@a%7B>jqxzr$N zZq}Q|OdAOvlR(7x+=t+Rz0(e55hGbN+8;36l!v zPVD1a+p!gp&ubElZ|Dcr)NfxUCNBLaw+sXG325T!pdrJj8D2(_UX+&#>j#eeQ^Z(W zW;9vGvX~lhL3CscQ=%Q|_1eqXyLaYyxc}Bb9n2Z0gdf?J{m2pj|1HD+*eJ zyJt99237{mYmaS3VKxu!jZ|F5$%kEc3&ANYqsDoG-;w9F*QkhBny zn=Q(cH5Ibli@KIViINXdqs2^QON+=zO{DvERVrpo2JP0&M7tIZ)2brB=YxB%&$%By zUcbYA@z3)<&w0-CJm)#*p8L62Y;IQ1(~ieC3Y^5(i5nRiiHkGi57opvX~y3ulhrOt zOsQWO-`#zq?8c$)hy#!7vlgnxchp;J#^=@?s`s`LcQ+Kj*0c14#EGEY3H4=9Pu9fB z=6}h}V*7sBFeNh~#p(M+A!<1hwR-c7^fvoPjYabAwI`Jqbzv+A;93pMcoq;I@u#y* zaGZ|Rm3Nw3I%XwUsJ|)qO6%QUkeDS?uz$x-zRAsno)&dykHk!}U!G{=~Ioe5A z)s}k~eXu{*&>(5qWWH%>X`{--oR3SDT?>`B)V9i83V%N3Su=d=O}qc%&RZu{(hTlx z`LWPc)V+`Emnyfi=-7V}%6E#V{mRCZ(jFFVn5#cq8Siub{Hozt*<>B=r5$rV*=Yu! zbndo8{3mwjo+9v@9bj|SVf-h6<>kZd55I%8!~$pj>2qTCnJJv{lXk~G{w~eB&*YwO z0bVk;TC%ZF&t|tZ!`~&j*2pifi)C-8TBQFDsI~O=8^u@ijya7 zldcK9xHj0~_*)tFwH+=xUBSyLmb1<-_CGyw*-wjBgj-tb{o0ytzRBwe;UatDU==x*A(J@!^7Ih|`LP8~| z(zW$546z!9syxh4H~F%Nfri^x9E{bGx$;T#dA^ledBGUjPsL*5J67wF4A<(95;c;I_2oe)507)`scV*7QPAGP7=O_BtfV9N=(zRW zYb+n-F#D3Ht^an48N9B#@YWK|t9Q$Ta;LlH%xMlOZjjY1O?7_mlzYnDO~qpKl;Dzm znwer-a*A@wZ!g($JbdpnBe&mQh4!e)h*g-awyJb37W+5&Was(&DoM7_?`k}_6~>L& z%Tzfca)E1+B2{bDu~fRJj%{;RA^w6!#$^k}U(xjv+3&>4_Q+`mOZSZ-dx14`#@riV zl2ap?CeSaCFwN1V^9O&26#U6JzUuqK#wKb(7xA$kSoU8w54`2F=KN=}J+j3lO79=7 zh@bk}8q?Z#zSp}mzeuW_`!MHsJ%c^J<(!;j@QP8kMt7z|`3k1X1Wt}aOZmsw4U6oX zK5&9n^ThV_X1{-P_-s(7XkO<0>qSLMjx4`(X6FiB%)Or|rg&eF&_Al%v%cc1d)nVG zEvNL(mm8DZ&A6YSs&eJdrcBdYZ;x)SPL_1f_@TV>$XrQjb%_Vz;YF|4+#hdHm;HEG z(>&klE56NIC#{e;{lsyy5t6uW-9#!?8zB_J!vff${T{-r^?DPHEb@)sE82G{_rx}` zH(*G`ZasJEo11s@OQTXwsA*K}_7Tf9PuvHW?6F+OgF zTvcau;x_i3Sk(iJy58yk z@HGfO$B^IbtE>@X{p5d+E&0clwV!q_6jy%#D1An{!fVltjO98>PxiHMe^HjF(>YIR zmd3Jx6Hj)uUR0Beo86xN-@5&+5lNnpJ6q#gzyAhnhlr@$cXBX3NlZLrnycJS+?&|U zYr+c_5PplniGLZ+PAlvj98sa8x0hY@nTKIt_M$@8AY^K1?`CCdi3;Vs{a>ClI0^f|DxU~QIM-TMS2cNBnE3^C- zgBOC3xuvC+m92#x9MlZ_gNi>XjO@(?9rpu*m~&o>!CM@Lh&$RjJD8d~YKKLHp@d|K zCS78-h>0UsJ6?;yi=2iKe3>E4AYW#X&vs9CK+vX8D%ANbyBA|Yo-o1`9tZx$;QtOq zg$0LrvYA_%Y?MK`>ri!u+Jl2@IuB7NG9W}N)QiO&IvPTJ?=NCOanNNdykv){z`@-? zT==LGm5)4ja#lM`g|#t;O(x09xbWqN9#x`%{1EHIUEr7X<1tKu1en7T1`Hmep%DBS zBS-@*sBejm!e$c*GI-jA%HP|%jXMRD_yS4{od^z|hYQIMo=2dJF%wr;#={KER!13K z@hF40-A5TQAC}rkf@2Yt_*9ZHPNz{a2KLD)=HFWv!wdo81c{rBd67znB*|S zz1t))Y&n=@XsmGXwV6Vg{>>FisNenUO9>FP;0r3q-t}D$N+7ltsJzDgVhMMc6M0=! z{^c1|o+#x}c?0JtmOL22pU7q;dv4|O<$0wkDy;q5r!5AY!XL6NWI}%9;H4M&!UM$~ zD!M7dV`BnD^^ZI)ljh$ zMLVx?@C}QpA;W~Y`l-Jg6~G%(lu(ekF(qiYIKS!}bSxXWdP~ zJq!z(JGdGA@*4-&T1|Mx7G}H=21O3^UpA5< z)B&UaTwSpaJU$Z&6|%f?5Luo&YLrDCw2@EUjX$}z;@DnH^49s{6U?sKiX+rAd-)so~3ddWLNG@$&I!dV0>| zzj29Njz*PI`?>U`3sgwc*J9X@B(UxcN-#eepie?V$>)CdSoseGbO;zqheW>ja%6IU znxWANxyS<02?PT~y<&oe6DT)T3mb~wmCRmgnfrILjH2=Jq7EaVCH2j&yOqp;< zA7YTq$ov@ww0xp?q2bF|h%D-cW$lXw)FLGPpBatFhqOzBu88k*YXm>E1dkzy&m8-a z(DXWIU{epnLL@RAN1aOd zi3PKWs9pp)iTL;_uR(1j`yEwIqA1&FM$q!6P_r09L&^uu)+q`jcQKlz>NsylpFH?n z+bj%Q$kQJFa_|&WdirQG>*q$XoApo^9Gi>w@YRmChf$?hd8t>IlEBZ)w9)i8(~G_b z`W$uZy#ek!9;^?`FnPViLTLH4iRzZ&sg+&o7&a{nr58uh(rL3*vP|n2O>oc+5U=F? z+LbdreH6*%+s)G1EGSnmL(N16`{oZ19z}*Z;-T3_i0P}vjPME!VH5Acp>t&N{M1mur$-7Z~ zGio&D*+GD{#_m!u;9JPXP`B_K2cKa>iRph_x3D^8|FI7e5B+Wi2w5E&drA)VmTqkH z5yLi6#}`oKLx}S()R;{^q5XH!Q2TTnNAE0$>Qn?$D4A5_Mok)cNQ3auZn{3)X9Lk- z1BE)-M3wH8q+vFTp^Wa9U0~pRco_gVa?_!05W7aCDkJ(J#*Bb<4qL?WXTj_-k}guR)((FW`$D zrR7sS$sX+heZsJygR&gj4o0!upwBiE@K0W#=MMxE%^aW)KM{2CluO?Q<)ctgE1{qI z7Ia~Ijkb%?tZ)R7w`|Pd(cy0le(^THgJExeqw1nJzj?qI{PQq0L8%XK!=gC!hOz*6 z;x4svd>rq-GKxem_6m??_l8Ff9?c3Vh+YI0&|2z8rVXCa3K7v;mjdFoh7pOp!&)IC zdU;VmtZAet4qjzM&4^z66F{Fe(L)C>0isa!Hk<(Z!~Kz={pZUVx*@lY1hilOp{DWP=n+Dpw|WGqLl3A?1E<(RAoLE50Q6fc zHE7^CTL^^SBN2d#+o(ajqhTQwdW%DV+VzkcHF!`AByp&nFf0&$lxD)C?1_T{?Ee7y C@qsh| literal 0 HcmV?d00001 diff --git a/deps/pygost-5.12/pygost.egg-info/PKG-INFO b/deps/pygost-5.12/pygost.egg-info/PKG-INFO new file mode 100644 index 0000000..2ded1fa --- /dev/null +++ b/deps/pygost-5.12/pygost.egg-info/PKG-INFO @@ -0,0 +1,93 @@ +Metadata-Version: 2.1 +Name: pygost +Version: 5.12 +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/deps/pygost-5.12/pygost.egg-info/SOURCES.txt b/deps/pygost-5.12/pygost.egg-info/SOURCES.txt new file mode 100644 index 0000000..781d116 --- /dev/null +++ b/deps/pygost-5.12/pygost.egg-info/SOURCES.txt @@ -0,0 +1,71 @@ +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/deps/pygost-5.12/pygost.egg-info/dependency_links.txt b/deps/pygost-5.12/pygost.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/deps/pygost-5.12/pygost.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/deps/pygost-5.12/pygost.egg-info/top_level.txt b/deps/pygost-5.12/pygost.egg-info/top_level.txt new file mode 100644 index 0000000..d05bec6 --- /dev/null +++ b/deps/pygost-5.12/pygost.egg-info/top_level.txt @@ -0,0 +1 @@ +pygost diff --git a/deps/pygost-5.12/pygost/__init__.py b/deps/pygost-5.12/pygost/__init__.py new file mode 100644 index 0000000..c0695ad --- /dev/null +++ b/deps/pygost-5.12/pygost/__init__.py @@ -0,0 +1,6 @@ +"""Pure Python GOST cryptographic functions library. + +PyGOST is free software: see the file COPYING for copying conditions. +""" + +__version__ = "5.12" diff --git a/deps/pygost-5.12/pygost/asn1schemas/__init__.py b/deps/pygost-5.12/pygost/asn1schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/deps/pygost-5.12/pygost/asn1schemas/cert-dane-hash.py b/deps/pygost-5.12/pygost/asn1schemas/cert-dane-hash.py new file mode 100755 index 0000000..0292b9e --- /dev/null +++ b/deps/pygost-5.12/pygost/asn1schemas/cert-dane-hash.py @@ -0,0 +1,18 @@ +#!/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/deps/pygost-5.12/pygost/asn1schemas/cert-selfsigned-example.py b/deps/pygost-5.12/pygost/asn1schemas/cert-selfsigned-example.py new file mode 100755 index 0000000..bd562b1 --- /dev/null +++ b/deps/pygost-5.12/pygost/asn1schemas/cert-selfsigned-example.py @@ -0,0 +1,348 @@ +#!/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/deps/pygost-5.12/pygost/asn1schemas/cms.py b/deps/pygost-5.12/pygost/asn1schemas/cms.py new file mode 100644 index 0000000..8028d2b --- /dev/null +++ b/deps/pygost-5.12/pygost/asn1schemas/cms.py @@ -0,0 +1,431 @@ +# 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/deps/pygost-5.12/pygost/asn1schemas/oids.py b/deps/pygost-5.12/pygost/asn1schemas/oids.py new file mode 100644 index 0000000..4638900 --- /dev/null +++ b/deps/pygost-5.12/pygost/asn1schemas/oids.py @@ -0,0 +1,60 @@ +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/deps/pygost-5.12/pygost/asn1schemas/pfx.py b/deps/pygost-5.12/pygost/asn1schemas/pfx.py new file mode 100644 index 0000000..27a87d0 --- /dev/null +++ b/deps/pygost-5.12/pygost/asn1schemas/pfx.py @@ -0,0 +1,250 @@ +# 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/deps/pygost-5.12/pygost/asn1schemas/pkcs10.py b/deps/pygost-5.12/pygost/asn1schemas/pkcs10.py new file mode 100644 index 0000000..dce45dd --- /dev/null +++ b/deps/pygost-5.12/pygost/asn1schemas/pkcs10.py @@ -0,0 +1,49 @@ +# 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/deps/pygost-5.12/pygost/asn1schemas/prvkey.py b/deps/pygost-5.12/pygost/asn1schemas/prvkey.py new file mode 100644 index 0000000..7da2533 --- /dev/null +++ b/deps/pygost-5.12/pygost/asn1schemas/prvkey.py @@ -0,0 +1,100 @@ +# 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/deps/pygost-5.12/pygost/asn1schemas/x509.py b/deps/pygost-5.12/pygost/asn1schemas/x509.py new file mode 100644 index 0000000..86ad7da --- /dev/null +++ b/deps/pygost-5.12/pygost/asn1schemas/x509.py @@ -0,0 +1,262 @@ +# 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/deps/pygost-5.12/pygost/gost28147_mac.py b/deps/pygost-5.12/pygost/gost28147_mac.py new file mode 100644 index 0000000..aab2805 --- /dev/null +++ b/deps/pygost-5.12/pygost/gost28147_mac.py @@ -0,0 +1,99 @@ +# 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/deps/pygost-5.12/pygost/gost3410.py b/deps/pygost-5.12/pygost/gost3410.py new file mode 100644 index 0000000..cdb5b03 --- /dev/null +++ b/deps/pygost-5.12/pygost/gost3410.py @@ -0,0 +1,404 @@ +# 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): + """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) + """ + return curve.exp(prv) + + +def sign(curve, prv, digest, rand=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, _ = curve.exp(k) + r %= q + if r == 0: + continue + d = prv * r + k *= e + s = (d + k) % 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/deps/pygost-5.12/pygost/gost3410_vko.py b/deps/pygost-5.12/pygost/gost3410_vko.py new file mode 100644 index 0000000..92f4a26 --- /dev/null +++ b/deps/pygost-5.12/pygost/gost3410_vko.py @@ -0,0 +1,95 @@ +# 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): + 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]) + 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/deps/pygost-5.12/pygost/gost34112012.py b/deps/pygost-5.12/pygost/gost34112012.py new file mode 100644 index 0000000..91782de --- /dev/null +++ b/deps/pygost-5.12/pygost/gost34112012.py @@ -0,0 +1,299 @@ +# 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/deps/pygost-5.12/pygost/gost3412.py b/deps/pygost-5.12/pygost/gost3412.py new file mode 100644 index 0000000..b9472ee --- /dev/null +++ b/deps/pygost-5.12/pygost/gost3412.py @@ -0,0 +1,186 @@ +# 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/deps/pygost-5.12/pygost/gost3413.py b/deps/pygost-5.12/pygost/gost3413.py new file mode 100644 index 0000000..f3cb5da --- /dev/null +++ b/deps/pygost-5.12/pygost/gost3413.py @@ -0,0 +1,392 @@ +# 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/deps/pygost-5.12/pygost/iface.py b/deps/pygost-5.12/pygost/iface.py new file mode 100644 index 0000000..e2d6a4c --- /dev/null +++ b/deps/pygost-5.12/pygost/iface.py @@ -0,0 +1,50 @@ +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/deps/pygost-5.12/pygost/kdf.py b/deps/pygost-5.12/pygost/kdf.py new file mode 100644 index 0000000..4e404c6 --- /dev/null +++ b/deps/pygost-5.12/pygost/kdf.py @@ -0,0 +1,81 @@ +# 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/deps/pygost-5.12/pygost/mgm.py b/deps/pygost-5.12/pygost/mgm.py new file mode 100644 index 0000000..fb51343 --- /dev/null +++ b/deps/pygost-5.12/pygost/mgm.py @@ -0,0 +1,168 @@ +# 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/deps/pygost-5.12/pygost/pbkdf2.py b/deps/pygost-5.12/pygost/pbkdf2.py new file mode 100644 index 0000000..0fd6ddc --- /dev/null +++ b/deps/pygost-5.12/pygost/pbkdf2.py @@ -0,0 +1,41 @@ +# 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/deps/pygost-5.12/pygost/stubs/pygost/__init__.pyi b/deps/pygost-5.12/pygost/stubs/pygost/__init__.pyi new file mode 100644 index 0000000..e69de29 diff --git a/deps/pygost-5.12/pygost/stubs/pygost/gost28147.pyi b/deps/pygost-5.12/pygost/stubs/pygost/gost28147.pyi new file mode 100644 index 0000000..be31261 --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/gost28147.pyi @@ -0,0 +1,101 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/gost28147_mac.pyi b/deps/pygost-5.12/pygost/stubs/pygost/gost28147_mac.pyi new file mode 100644 index 0000000..70d90d6 --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/gost28147_mac.pyi @@ -0,0 +1,25 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/gost3410.pyi b/deps/pygost-5.12/pygost/stubs/pygost/gost3410.pyi new file mode 100644 index 0000000..8f0dcb8 --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/gost3410.pyi @@ -0,0 +1,72 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/gost3410_vko.pyi b/deps/pygost-5.12/pygost/stubs/pygost/gost3410_vko.pyi new file mode 100644 index 0000000..6ea9b82 --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/gost3410_vko.pyi @@ -0,0 +1,17 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/gost34112012.pyi b/deps/pygost-5.12/pygost/stubs/pygost/gost34112012.pyi new file mode 100644 index 0000000..3d5cc41 --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/gost34112012.pyi @@ -0,0 +1,18 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/gost34112012256.pyi b/deps/pygost-5.12/pygost/stubs/pygost/gost34112012256.pyi new file mode 100644 index 0000000..a1d2a01 --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/gost34112012256.pyi @@ -0,0 +1,21 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/gost34112012512.pyi b/deps/pygost-5.12/pygost/stubs/pygost/gost34112012512.pyi new file mode 100644 index 0000000..349bddd --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/gost34112012512.pyi @@ -0,0 +1,24 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/gost341194.pyi b/deps/pygost-5.12/pygost/stubs/pygost/gost341194.pyi new file mode 100644 index 0000000..24de2e4 --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/gost341194.pyi @@ -0,0 +1,25 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/gost3412.pyi b/deps/pygost-5.12/pygost/stubs/pygost/gost3412.pyi new file mode 100644 index 0000000..ef278b7 --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/gost3412.pyi @@ -0,0 +1,18 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/gost3413.pyi b/deps/pygost-5.12/pygost/stubs/pygost/gost3413.pyi new file mode 100644 index 0000000..4cfd694 --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/gost3413.pyi @@ -0,0 +1,81 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/iface.pyi b/deps/pygost-5.12/pygost/stubs/pygost/iface.pyi new file mode 100644 index 0000000..a5c2a85 --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/iface.pyi @@ -0,0 +1,19 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/kdf.pyi b/deps/pygost-5.12/pygost/stubs/pygost/kdf.pyi new file mode 100644 index 0000000..ccab8af --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/kdf.pyi @@ -0,0 +1,22 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/mgm.pyi b/deps/pygost-5.12/pygost/stubs/pygost/mgm.pyi new file mode 100644 index 0000000..81906b7 --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/mgm.pyi @@ -0,0 +1,17 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/utils.pyi b/deps/pygost-5.12/pygost/stubs/pygost/utils.pyi new file mode 100644 index 0000000..76460e5 --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/utils.pyi @@ -0,0 +1,19 @@ +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/deps/pygost-5.12/pygost/stubs/pygost/wrap.pyi b/deps/pygost-5.12/pygost/stubs/pygost/wrap.pyi new file mode 100644 index 0000000..776a6e7 --- /dev/null +++ b/deps/pygost-5.12/pygost/stubs/pygost/wrap.pyi @@ -0,0 +1,31 @@ +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/deps/pygost-5.12/pygost/test_cms.py b/deps/pygost-5.12/pygost/test_cms.py new file mode 100644 index 0000000..7e5781a --- /dev/null +++ b/deps/pygost-5.12/pygost/test_cms.py @@ -0,0 +1,1078 @@ +# 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/deps/pygost-5.12/pygost/test_gost28147.py b/deps/pygost-5.12/pygost/test_gost28147.py new file mode 100644 index 0000000..a83e930 --- /dev/null +++ b/deps/pygost-5.12/pygost/test_gost28147.py @@ -0,0 +1,384 @@ +# 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/deps/pygost-5.12/pygost/test_gost28147_mac.py b/deps/pygost-5.12/pygost/test_gost28147_mac.py new file mode 100644 index 0000000..ec1af3f --- /dev/null +++ b/deps/pygost-5.12/pygost/test_gost28147_mac.py @@ -0,0 +1,63 @@ +# 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/deps/pygost-5.12/pygost/test_gost3410.py b/deps/pygost-5.12/pygost/test_gost3410.py new file mode 100644 index 0000000..cd71535 --- /dev/null +++ b/deps/pygost-5.12/pygost/test_gost3410.py @@ -0,0 +1,495 @@ +# 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/deps/pygost-5.12/pygost/test_gost3410_vko.py b/deps/pygost-5.12/pygost/test_gost3410_vko.py new file mode 100644 index 0000000..a8e298e --- /dev/null +++ b/deps/pygost-5.12/pygost/test_gost3410_vko.py @@ -0,0 +1,125 @@ +# 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/deps/pygost-5.12/pygost/test_gost34112012.py b/deps/pygost-5.12/pygost/test_gost34112012.py new file mode 100644 index 0000000..c7c2df9 --- /dev/null +++ b/deps/pygost-5.12/pygost/test_gost34112012.py @@ -0,0 +1,159 @@ +# 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/deps/pygost-5.12/pygost/test_gost341194.py b/deps/pygost-5.12/pygost/test_gost341194.py new file mode 100644 index 0000000..4ffa6d6 --- /dev/null +++ b/deps/pygost-5.12/pygost/test_gost341194.py @@ -0,0 +1,200 @@ +# 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/deps/pygost-5.12/pygost/test_gost3412.py b/deps/pygost-5.12/pygost/test_gost3412.py new file mode 100644 index 0000000..5dbb200 --- /dev/null +++ b/deps/pygost-5.12/pygost/test_gost3412.py @@ -0,0 +1,137 @@ +# 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/deps/pygost-5.12/pygost/test_gost3413.py b/deps/pygost-5.12/pygost/test_gost3413.py new file mode 100644 index 0000000..0098513 --- /dev/null +++ b/deps/pygost-5.12/pygost/test_gost3413.py @@ -0,0 +1,766 @@ +# 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/deps/pygost-5.12/pygost/test_kdf.py b/deps/pygost-5.12/pygost/test_kdf.py new file mode 100644 index 0000000..6921cfc --- /dev/null +++ b/deps/pygost-5.12/pygost/test_kdf.py @@ -0,0 +1,58 @@ +# 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/deps/pygost-5.12/pygost/test_mgm.py b/deps/pygost-5.12/pygost/test_mgm.py new file mode 100644 index 0000000..e70a7f5 --- /dev/null +++ b/deps/pygost-5.12/pygost/test_mgm.py @@ -0,0 +1,75 @@ +# 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/deps/pygost-5.12/pygost/test_pfx.py b/deps/pygost-5.12/pygost/test_pfx.py new file mode 100644 index 0000000..a2760bf --- /dev/null +++ b/deps/pygost-5.12/pygost/test_pfx.py @@ -0,0 +1,680 @@ +# 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/deps/pygost-5.12/pygost/test_wrap.py b/deps/pygost-5.12/pygost/test_wrap.py new file mode 100644 index 0000000..7ecf8ee --- /dev/null +++ b/deps/pygost-5.12/pygost/test_wrap.py @@ -0,0 +1,111 @@ +# 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/deps/pygost-5.12/pygost/test_x509.py b/deps/pygost-5.12/pygost/test_x509.py new file mode 100644 index 0000000..e9fccb7 --- /dev/null +++ b/deps/pygost-5.12/pygost/test_x509.py @@ -0,0 +1,433 @@ +# 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/deps/pygost-5.12/pygost/utils.py b/deps/pygost-5.12/pygost/utils.py new file mode 100644 index 0000000..21c31b0 --- /dev/null +++ b/deps/pygost-5.12/pygost/utils.py @@ -0,0 +1,101 @@ +# 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/deps/pygost-5.12/pygost/wrap.py b/deps/pygost-5.12/pygost/wrap.py new file mode 100644 index 0000000..3fa49e7 --- /dev/null +++ b/deps/pygost-5.12/pygost/wrap.py @@ -0,0 +1,152 @@ +# 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("