From 368879343f9bc841332290dc299af1c14b027082 Mon Sep 17 00:00:00 2001 From: vvolkov <1vvoovv1ov@gmail.com> Date: Sat, 1 Jul 2023 12:38:05 +0300 Subject: [PATCH] 'project' --- requirements.txt | 7 + src/base_api.py | 100 ++++++++++++ src/client.py | 103 ++++++++++++ src/encoding.py | 84 ++++++++++ src/gui_auth.py | 188 +++++++++++++++++++++ src/images/armadillo (1).png | Bin 0 -> 30797 bytes src/images/check.png | Bin 0 -> 5006 bytes src/images/cloud.png | Bin 0 -> 14084 bytes src/images/cross.png | Bin 0 -> 7638 bytes src/images/download.png | Bin 0 -> 4293 bytes src/images/key.png | Bin 0 -> 9822 bytes src/images/key2.png | Bin 0 -> 14201 bytes src/images/table.png | Bin 0 -> 3930 bytes src/images/verify.png | Bin 0 -> 10048 bytes src/key_gen.py | 7 + src/server.py | 218 +++++++++++++++++++++++++ src/signify.py | 307 +++++++++++++++++++++++++++++++++++ test/check.png | Bin 0 -> 5006 bytes test/check.png.pem | 4 + test/check.png.sig | Bin 0 -> 71 bytes test/key_test.pem | 5 + 21 files changed, 1023 insertions(+) create mode 100644 requirements.txt create mode 100644 src/base_api.py create mode 100644 src/client.py create mode 100644 src/encoding.py create mode 100644 src/gui_auth.py create mode 100644 src/images/armadillo (1).png create mode 100644 src/images/check.png create mode 100644 src/images/cloud.png create mode 100644 src/images/cross.png create mode 100644 src/images/download.png create mode 100644 src/images/key.png create mode 100644 src/images/key2.png create mode 100644 src/images/table.png create mode 100644 src/images/verify.png create mode 100644 src/key_gen.py create mode 100644 src/server.py create mode 100644 src/signify.py create mode 100644 test/check.png create mode 100644 test/check.png.pem create mode 100644 test/check.png.sig create mode 100644 test/key_test.pem diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8ab7b28 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +cryptography==41.0.1 +customtkinter==5.1.2 +ecdsa==0.18.0 +Pillow==9.5.0 +Pillow==9.5.0 +psycopg2_binary==2.9.5 +yadisk==1.3.3 diff --git a/src/base_api.py b/src/base_api.py new file mode 100644 index 0000000..328de59 --- /dev/null +++ b/src/base_api.py @@ -0,0 +1,100 @@ +import psycopg2 + +#Параметры +host_ = "localhost" +port_ = 5432 +user_ = "postgres" +password_ = "" +database_="postgres" + +def create_database(): + try: + connection = psycopg2.connect( + host=host_, + port=port_, + user=user_, + password=password_, + database=database_ + ) + except (Exception, psycopg2.Error) as error: + print("Can't connect to DB: ", error) + return + + with connection.cursor() as cursor: + cursor.execute(""" + CREATE TABLE IF NOT EXISTS my_users( + nick_name VARCHAR(255) NOT NULL PRIMARY KEY, + first_name VARCHAR(255) NOT NULL, + last_name VARCHAR(255) NOT NULL, + password_user VARCHAR(255) NOT NULL + ); + """) + connection.commit() + print('[SERVER] table created!') + connection.close() + + +def insert_into_users(nick_name, first_name, last_name, password_user): + try: + connection = psycopg2.connect( + host=host_, + port=port_, + user=user_, + password=password_, + database=database_ + ) + except (Exception, psycopg2.Error) as error: + print("Can't connect to DB: ", error) + + with connection.cursor() as cursor: + cursor.execute(""" + INSERT INTO my_users + VALUES (%s, %s, %s, %s); """, + (nick_name, first_name, last_name, password_user)) + connection.commit() + connection.close() + +def checking_uniqueness(nick_name): + try: + connection = psycopg2.connect( + host="195.19.32.74", + port=port_, + user=user_, + password=password_, + database=database_ + ) + except (Exception, psycopg2.Error) as error: + print("Can't connect to DB: ", error) + with connection.cursor() as cursor: + cursor.execute(f""" + SELECT 1 FROM my_users WHERE nick_name = '{nick_name}' """) + + res_= cursor.fetchone() + connection.close() + return res_ + + +def user_verification(nick_name, password_user): + print(nick_name, password_user) + try: + connection = psycopg2.connect( + host="195.19.32.74", + port=port_, + user=user_, + password=password_, + database=database_ + ) + except (Exception, psycopg2.Error) as error: + print("Can't connect to DB: ", error) + with connection.cursor() as cursor: + try: + cursor.execute(f""" + SELECT 1 FROM my_users WHERE nick_name = '{nick_name}' AND password_user = '{password_user}' """ + ) + res_ = cursor.fetchone() + connection.close() + return res_ + except (Exception, psycopg2.Error) as error: + print("Can't select from database:", error) + + diff --git a/src/client.py b/src/client.py new file mode 100644 index 0000000..e65e44c --- /dev/null +++ b/src/client.py @@ -0,0 +1,103 @@ +import socket +from encoding import useKey +from time import sleep +import customtkinter as ctk +from os.path import basename + +buff_size = 1024 + + + +def sendFile(client, file_name): + try: + file = open(file_name, 'rb') + except: + print(f"[CLIENT] Can't open sended file: {file_name}") + data = file.read(buff_size) + while data: + client.send((data)) + data = file.read(buff_size) + + client.send(b'') + print('[CLIENT] msg shared!') + +def recieveFile(client, file_name): + file = open(file_name, 'wb') + data = client.recv(buff_size) + while data[-5:] != b'': + file.write(data) + data = client.recv(buff_size) + + if(len(data) > 5): + file.write(data[0:-5]) + + + print("[CLIENT] transfered succesfully") + + file.close() + + +def ServerMeet(socket_, command : str): + print(f'[CLIENT] {command}') + + cmd_ = command[0:command.find(' ')] + if cmd_ == 'send_file': + socket_.send( (cmd_+ ' ' + basename(command[command.find(' ')+1:])).encode() ) + file_name = command[command.find(' ')+1:] + sendFile(socket_, file_name) + elif cmd_ == 'use_key': + socket_.send(command.encode()) + with open(command[command.find(' ')+1:], 'rb') as f: + data = f.read(1024) + socket_.send(data) + elif cmd_ == 'use_key_pub': + socket_.send(command.encode()) + with open(command[command.find(' ')+1:], 'rb') as f: + data = f.read(1024) + socket_.send(data) + elif cmd_ == 'encode_file': + socket_.send(command.encode()) + recieveFile(socket_, command[command.find(' ')+1:]+'.sig') + + pub_k = socket_.recv(buff_size) + with open(command[command.find(' ')+1:]+'.pem', 'wb') as f: + f.write(pub_k) + elif cmd_ == 'verify_sign': + socket_.send(command.encode()) + return socket_.recv(buff_size).decode() + elif cmd_ == 'send_encode_file': #send_encode_file + socket_.send(command.encode()) + file_name = command[command.find(' ')+1:] + sendFile(socket_, file_name) + recieveFile(socket_, command[command.find(' ')+1:]+'.sig') + + elif cmd_ == 'rec_file_cloud': + socket_.send(command.encode()) + file_name = command[command.find(' ')+1:] + recieveFile(socket_, file_name) + + elif cmd_ == 'upload_file': + socket_.send((cmd_+ ' ' + basename(command[command.find(' ')+1:])).encode()) + file_name = command[command.find(' ')+1:] + sendFile(socket_, file_name) + elif cmd_ == 'reg': + socket_.send(command.encode()) + return socket_.recv(buff_size).decode() + elif cmd_ == 'login': + socket_.send(command.encode()) + return socket_.recv(buff_size).decode() + elif cmd_ == 'log_unique': + socket_.send(command.encode()) + return socket_.recv(buff_size).decode() + elif command == 'dwnld_files': + socket_.send(command.encode()) + s = socket_.recv(buff_size).decode().split('|') + return s + elif command == 'exit': + try: + socket_.send(command.encode()) + socket_.close() + except: + pass + return None + \ No newline at end of file diff --git a/src/encoding.py b/src/encoding.py new file mode 100644 index 0000000..f5cae76 --- /dev/null +++ b/src/encoding.py @@ -0,0 +1,84 @@ +from ecdsa import SigningKey, NIST256p, VerifyingKey +from ecdsa import BadSignatureError +from ecdsa.util import sigdecode_der, sigencode_der +from zlib import crc32 +from hashlib import sha256 + +def useKey(path): + try: + with open(path, 'rb') as f: + return SigningKey.from_pem(f.read()) + except: + print(f"Failed can't use key in {path}") + return None + +def UsePub(path): + try: + with open(path, 'rb') as f: + return VerifyingKey.from_pem(f.read()) + except: + print(f"Failed can't use key in {path}") + return None + +def encrypt_str(s: bytes, key): + res = "" + for c in s: + #print(c) + #1114111 - max value + res += chr((c + key) % 1114111) + + return res.encode() + +def decrypt_str(s:bytes, key): + res = "" + for c in s: + res += chr(((c - key) % 1114111 + 1114111) % 1114111) + + return res.encode() + + + +def EncodeFile(path, private_key): + if private_key == None: + print("Can't make sig, choose private_key") + return None + + #чтение файла + try: + with open(path, 'rb') as file: + data = sha256(file.read()).hexdigest().encode() + # data = b'\n\x00\x00\x00\x00' + except: + print(f'[SERVER] Cant open file {path}') + try: + + sig = private_key.sign_deterministic(data, sigencode=sigencode_der, hashfunc=sha256) + #запись подписи в файл + with open(path+'.sig', 'wb') as siq_file: + siq_file.write(sig) + print(f'[CLIENT] File: {path} signigied') + except: + print("Can't make sig") + +def VerifyFile(path_sig, path_source, public_key): + if public_key == None: + print("Can't verify sig without public_key") + return None + try: + with open(path_sig, 'rb') as f: + sig = f.read() + except: + print("Can't open sig file") + try: + with open(path_source, 'rb') as file: + data = sha256(file.read()).hexdigest().encode() + except: + print("Can't open source file") + try: + res = public_key.verify(sig, data, hashfunc=sha256, sigdecode=sigdecode_der) + return res + assert res + print('[CLIENT] Valid sign') + except BadSignatureError: + print('[CLIENT] Invalid sign') + diff --git a/src/gui_auth.py b/src/gui_auth.py new file mode 100644 index 0000000..e32af8c --- /dev/null +++ b/src/gui_auth.py @@ -0,0 +1,188 @@ +import tkinter + +from tkinter import messagebox +import customtkinter +from PIL import Image +from encoding import useKey, EncodeFile, VerifyFile, UsePub +from os import getcwd +from os.path import basename +import socket +from client import sendFile, recieveFile, ServerMeet +from hashlib import sha256 +#параметры +host = 'localhost' +port = 1234 +buff_size = 1024 + + +class Regist(customtkinter.CTk): + def __init__(self): + super().__init__() + self.salt = 'salt' + self.logd = 0 + self.con = 0 + self.cur_dir = getcwd() + self.resizable(1, 1) + + self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.try_connect() + + self.geometry('450x400+700+200') + self.title('Login page') + + self.frame1 = customtkinter.CTkFrame(self, width=600) + self.frame1.grid(row=0, column=0, padx=20, pady=20) + + self.l1 = customtkinter.CTkLabel(master=self.frame1, text="Начнём работу!", font=('Century Gothic', 15)) + self.l1.grid(row=0, column=0, padx=10) + + self.login = customtkinter.CTkEntry(master=self.frame1, + placeholder_text="Логин", + width=120, + height=25, + border_width=2, + corner_radius=10, ) + self.login.grid(row=1, column=0) + + self.password = customtkinter.CTkEntry(master=self.frame1, + placeholder_text="Пароль", + width=120, + height=25, + border_width=2, + corner_radius=10, show = '*') + self.password.grid(row=2, column=0, pady=10) + + self.button1 = customtkinter.CTkButton(master=self.frame1, text='Войти', fg_color='#C57A44', cursor='hand2', width = 20, command=self.login_button) + self.button1.grid(row=3, column=0, pady=10) + + self.button2 = customtkinter.CTkButton(master=self.frame1, fg_color='#C57A44', text='Зарегистрироваться', cursor='hand2', + command=self.register_page) + self.button2.grid(row=4, column=0, padx=10, pady=10) + + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + + self.frame1.rowconfigure(0, weight=1) + self.frame1.columnconfigure(0, weight=1) + + + + def login_button(self): + global login, pas + salted_pas = sha256((self.password.get() + self.salt).encode()).hexdigest() + if self.con == 0: + self.try_connect() + if self.con == 0: + messagebox.showerror('Ошибка', 'Нет подключения!') + #print(f"Can't connect to: {host, port}") + return + + res = (ServerMeet(self.client, f'login {self.login.get()} {salted_pas}')) + if self.login.get() == '' or self.password.get() == '': + messagebox.showerror('Ошибка', 'Не все поля заполнены') + elif res == 'None': + messagebox.showerror('Ошибка', 'Неверный логин или пароль') + else: + messagebox.showinfo('Добро пожаловать', 'Выполняется вход') + self.logd = 1 + self.destroy() + ServerMeet(self.client, f'exit') + def __del__(self): + try: + ServerMeet(self.client, f'exit') + self.destroy() + except: + pass + + + def register_page(self): + self.frame1.grid_remove() + self.frame2 = customtkinter.CTkFrame(self, corner_radius=10) + self.frame2.grid(row=0, column=0, padx=20, pady=20) + + self.l1 = customtkinter.CTkLabel(master=self.frame2, text="Регистрация в системе ", font=('Century Gothic', 15)) + self.l1.grid(row=0, column=0, sticky='e', padx=20, pady=10) + + self.first_name2 = customtkinter.CTkEntry(master=self.frame2, + placeholder_text="Имя", + width=120, + height=25, + border_width=2, + corner_radius=10, ) + self.first_name2.grid(row=1, column=0, padx=5, pady=10) + self.last_name2 = customtkinter.CTkEntry(master=self.frame2, + placeholder_text="Фамилия", + width=120, + height=25, + border_width=2, + corner_radius=10, ) + self.last_name2.grid(row=2, column=0, padx=5, pady=10) + + self.login2 = customtkinter.CTkEntry(master=self.frame2, + placeholder_text="Логин", + width=120, + height=25, + border_width=2, + corner_radius=10, ) + self.login2.grid(row=3, column=0, padx=5, pady=10) + + self.password2 = customtkinter.CTkEntry(master=self.frame2, + placeholder_text="Пароль", + width=120, + height=25, + border_width=2, + corner_radius=10, show = '*') + self.password2.grid(row=4, column=0, padx=5, pady=10) + + self.button5 = customtkinter.CTkButton(master=self.frame2, text='Зарегистрироваться', fg_color='#C57A44', cursor='hand2', + command=self.register_button) + self.button5.grid(row=5, column=0, pady=10) + + self.arleadyaccount = customtkinter.CTkLabel(master=self.frame2, text='Уже есть аккаунт?') + self.arleadyaccount.grid(row=6, column=0) + + self.loginButton = customtkinter.CTkButton(master=self.frame2, text='Войти', fg_color='#C57A44', command=self.login_page) + self.loginButton.grid(row=7, column=0, pady=10) + + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + + self.frame2.rowconfigure(0, weight=1) + self.frame2.columnconfigure(0, weight=1) + def login_page(self): + self.frame2.grid_remove() + self.frame1.grid(row=0, column=0, padx=20, pady=20) + def register_button(self): + if self.con == 0: + self.try_connect() + if self.con == 0: + messagebox.showerror('Ошибка', 'Нет подключения!') + return + + if self.login2.get() == '' or self.password2.get() == '': + messagebox.showerror('Ошибка', 'Не все поля заполнены') + + elif ServerMeet(self.client, f'log_unique {self.login2.get()}') != 'None': + messagebox.showerror('Ошибка', 'Такой логин уже существует') + else: + salted_pas = sha256((self.password2.get() + self.salt).encode()).hexdigest() + res = ServerMeet(self.client, f'reg {self.login2.get()} {self.first_name2.get()} {self.last_name2.get()} {salted_pas}') + if res == 'ex_1': + messagebox.showerror('Ошибка', 'Повторите попытку') + else: + messagebox.showinfo('Успех', 'Регистрация прошла успешно') + + self.frame2.grid_remove() + self.frame1.grid(row=0, column=0, padx=20, pady=20) + def try_connect(self): + try: + self.client.connect((host, port)) + self.con = 1 + except: + pass + + + +# if __name__ == '__main__': +# app = Regist() +# app.mainloop() diff --git a/src/images/armadillo (1).png b/src/images/armadillo (1).png new file mode 100644 index 0000000000000000000000000000000000000000..6a6c51776d6b8c258ba6077ef6b5b7d55e74df75 GIT binary patch literal 30797 zcmeFZ^;?wP7e4yT3?WDgO2>M?c=zF;}X zXgLD_Ui-fKzSLTVTk0fMu8Yn}8G`6< zAN?0Z@c)1N|9}mC3BFn&B6uq)#i|StLsOpLX8tyQn8P}S&5KV~`0s}uQw9?_(nH_` zq5l!(!>r)ihIH(Z7hUzeBvdz7mkNvKo37ptp9Anv?88t>{_~&cQ}~q7bE+Hn*6hir zZzWUEfVXwhqU=P;=<=B40*>u$Q7cVd;9ud{2%Vp_(cCQav;EgW`viDI=sn$rlTLAm zV|ZSXwX-ZxK)Q*sz<<7S1`)GI|LmBaM8GTy1^)X{hNlT(5jr=fUROEorF~?Bp%3MS zuu9^NLI!H;*Y9E1A^OB6z5KhRs?@`jE4~sHvz)VyWI#6MVYTAbl_&L$>Cf9 zRq$&5@iRI>Z2>|Y$_K|6rCd+w-pB&99has{!t-U|9G`sh;*RlvC}*q7A9MvY%n)nb9zZi8F21HPKSxOyl!tgxXFQuNR0uaMYG2XSLdwobzY+zVW(!^Y25a=mKNY($?m!C(arVIuCv*3%2Hf z_yw&CDAVTvTgS|B0^@(j(Jpi1$%`%#le~X7B!4oh7NaEP_YA+e#=yFmrqvoZJgpCB z(8k4f-tU}x%S=fJ-#hZOJg+28MA~a=WTtbe^gem89X)eHlD%96;Y?_x3Az(UTV|PV z;y6*|P*r*9-O}^4p{t7r-(I4U$(MuuCW^KkT-IU9Dq3yveNd|~=R+?R8w%F@nMIEk zxYywHwq=6PMaV}C^WxGfwzhK#JsaCC6KQLbSv=RulAO5aJ&~#2X4r!O%HM%pwz|WU z^=-N&=$r6CcY_u|%5{-MQ-hug-d2%&0urCKvR&(g5o)pdjZfgL&fD*kbua?x8Ago$(OuTrhY!-b^G6futPi_zRTgY-^RuurqHHb%UTfa zp)^V?>Mq^oA%N}(^cIzgMEmp%!mXkDzct19pSaxf7f+T;dF?J2#JZbXGWJi{YfFPV zBTTvZ5;^RRuu?2nW>u&or_zr)#VpZjNm!D5^fXx2TSTepY6eT&=bI{PGm0bU3iMz zp8Yge)#G-m`Lp+zFb&+NhvyI>@1DTl$3nAH3o|J={g_dZ3!oJO1)W8P;|cIp@&oQ1 zL^n`1TTB`4QsfX~IOzaux;jWI`7JVGYiRJ7U#RH}v;B6zK129$nOc8-R?(hT@`EBq zl~tIgX#Hm%WRb;-eu_o^5zf#8D*T_(ZG*vMmjv-4xFn7i>N^m@PJb-ycl%K8@0fBiNOhi| zIguwboOyq7yZ3SbYSy}M(mdzJ<1%hTB{^eGB;xbZqq1GwjV;?R9#%T9h7N?&ze3&+ zFi|+*D|d%@?|>}P(gmARex|@LqMoHO4MCFQG&m=7k?q0_SF};K zcArlEM0zM$7W2Z~VPY6#lb?e~>0MiKgD6aatUuFSB|azn`}hiV4oe-Qx5cZno=Sy1 z8+lRoA6dP29)~cq^%%L?R!oO75M)hC=*hCa%2RV1uViF380`V0j}z(h+w}~&v9R{} zNxIEc1RUQ39Sbc80>*HyW{7i7Uyj9n^X~xW1oVMqcB=sYzdb|m&84lACRBH7BrI@~d<^4q@Ssb&zk3?#&JJMHrJlIVp^Kn~Ec;pPGHRuwM> zVgd0E9qL0hkA*%MX z7lR6>EEy&&nGV5~P4fD4t|nuCXwRp;e|>qC$V;C7(W$Rr-dJ6MV`HdnrD0q(SxYsj zyK-uAL4tiZ)vL)S{ac#?e`r}!w7B@UKs11k+9|+(S@=qN&GB@!6IZ^7^;_4lur} zqO8XxiroA~V@oFE;hnCTxA5;^wdb@{cj&-wsIbIiIjalX%wmw|&;SRpj z{cSnu0H5|^A!;Gezuq3@}#72WB-;DgQ6DKO$!P|v0s)(7)E8y`zhEeJdcTx70>T}<9#Q^bCXT-bATk)@ zl1hSyG;O}$7TOKMP5%TxW^{VM95Pgq^5Zt><6+fTD?1w_i=4VWq=`jmUn?|KST&L4 zsn01Qz7$1l1)i)3lPo;}tM-WUm#XH2Sc}%--5H~nmQQ398j6YqytUxmhA;lc&5mUW z*8X;JnvQ)}iPZC5dH;yaa-mj{$)%n=-8Rsa{}0@SSpHMNNd&Xenp!&aSN{1DTVF3q z!hC4mV3JM#vTFaMLcvRvf96zf6PpNeMX&rxg)i9{v^`$yp;a#e6H;e_N z6jlR`#eV+*j5k%5bh=g=l#O&4c3HPkdswf}301$P9gEynO$}qFARo;xDs?SR=A_T+ zd+FQ&_c`U(QpBC~ibz65UwgrbC2$|``>CP;I1rH&JPUTf7{(;8SjM`VcA)tuC!9j& zqSrtV4!R^bJ?~ncqCdO5qax!8VSN; zBgrQmTW`G0AOQK}463yyD(W)9vftE{()V{v{sNzgjVof_R;0;M{I0-xHd~RGB`c^q zu3?&3HO!gI!*%h4Ta6oY)QDzg_g)f<;cPbROoMTaq`xo)JDCST0j>-dxLhh{_!oG- z>sxTED>r;4W`n1N@roBwzgpxE3aT2#dO9kFb3ZZ|?5uj^vkt6J4(+P7+{;9P!7y=B zak9Z#UkNXHok6)3I(_GXr$~E9SuUi`_HtPQ3S@8Fpp^K>JGxABJ+J&X5NlOQEZ^C6 z&I_Y{@$&_0oYz#Hlwbe%<|gvgVQrK#uvasqkE_mW zm5k*5C({$7vfeh(fVU_reuYMAh^`|lfL#fkX89SaTU_mW50R$%_rhMcGj;2u=JA*q zV?yxs?fP^^-z3fcc3yo2%U0Tp&i7y~#BZvTzPi5KYc5XFSMc{I{F49M9XewPx*tn+5T&TxOF^X9v{%Mphpo8n11C zRI;a$xYJ~dk@9lP$Bd!UIqT3pL}u6+jpU#Jhx&9ITY$Ti@nPX#oBd`xh9p+IXd`Z^ zV1cdMBZjj)#Qc{=0eim;mU>b{t>~2fl|LF6uc9iNtHb3|DS8HluhitCx{b^f2zSzU zws3>~p_v~|mb9=soF26M$lp&C$w4Gh(F5^|s@k`oYe*rwT!<*LV2RScfyWOkCxS6- z39G@)lP}^Fsvzd6r^{LttqtGztN!`Vckj~-hAzC?KDjWR| zCDAn_`kb~!__{^Q*KXqqD=SlO13FXr!WXC{rGxK$1S$=p#F5ci8Hm5vD(zfy3aW+J zSJQE`hhN69x9bWb_%-bHKU_-={fQ~5$N7Yi0jJvDUr zG}!FPk&&ce3UO2wFdrygs=kx14tp!T1Y_-*#yTZXA&HI#OO8ZAC-Zrzc3@TjH2YD4 zu}>f2&orHsgT?q(awYov&t{0(IPr)w?>TwO@*K_t zAD{Av=uYIUP6GZn^1uXs%*S_Zqay4}AUMXb?x?@&{{3p8<(#@yUz9nKE#-IRs4s}d zm#TfY_W~|x_STLWA~4Jo8l1S0Q*Qa;uQ7$N}nWV zKU3rbm+z=y?j1C2vBBd0(ZUk7DApQ|5UuVK&;F{QrT+{p0v3bq##$`JY^J@xoHD1J zWa>YRJo0H{Xj3#v`(7jqM9);hG2y01nLUm|c&-(n*64kxr=R5aSJDpdEGXMgb(Rg!a+yaQt>I=d>mpKU9}Zq3BuV8C0h+q?%bbjZOe?{ z(VD7Ai9Vck@Iu2P0%p9SEE#HuAUduC>4u^Hlq%~NLqp3IuU`i}t&PNM|7%IJfFW$_ zWca_CdIO!)54==iULwy7nw&Ux(kKqU{1vV^i$2zrZn!J9w`1q2j$c3V+C0bfbo=7< z$&HqHX+S^)$Kq|nkerUY7~7uc58-$0lmc7v;zVW>#1%Fw=nozzK(k*pYixTt+!;=d za*TsL_Kh`HP(vb#wU+yO6f2PzAFFi)BaA$oPSM z+3wF09j~V3O2_vK(Jsh@GBD^lEurT z31-4cK33!T0ixMBP&N3+lI7&`j_U--T0SYgAzFz`O6kg-myTBo7t+V66Ayz#fZ%;2 zN5r=8K$_o3H8rW%XV9oEd(|x0ap|uYp6mI^wcpu0m3Drr4dOpc^7t%cGC;I80`2~> zL`qh%3(aDSA4Aa>{+_%SOo3oVR`uM~vz^y%bgDf4$w$xB-VI~h|Ip%h5JO$6#bGIb zx7oQ_i9}wm$Za2VXVz>!H3yAuZLY2gBIf65I`Z~Ug{k-fVRI+t?01QPO1C_UgcSSQ ziPPtHQb+^8(X_0-;u{E8){gMq&rW*MFcZp1n1oEVpC+Jd^-Uhw?W{9Lz(P8DC*;e1 zygj+|@M`qR7}V5l`4w}pP#)`3r_PDetHzBMYy-*tto_tcVUVy&U1vSb4<<>&(#Ot!FYYim)5!c>qx|(I<)w>7w zl+nvvfnct*cnTA}-1Zanf?=4)&#>whU-;ebCHPW?zU4p}sup3_9V8-?HB+sfrnu9; znN;<@>AaM|puL(8M<&RnO&U8+ew6E)ixPf5*q`}aq_0zwa5k6aeWzl<3lM~X!ojD! zcN~Taj(Am8t5aq~@=Y`#ni(hvzrTseNB#|c}}2r zuf0u30mnq^uwL$&6M`Rqr{1YRe|afM$$qjyKYh0^$16|x;okGC3cX`?tA*x5thIkw z{+&i_TCdVVRmfA-Z~F*l^!p&^)bC$8uz=Xftu~VX4VM!B%wcQ!h;JiBPiNp8mQ-I^ z^PMsY+g{=qn5w!$$*=b-Ul&Q-s3O`Kj?qbrbj_B)fCi{u}BvFx{4VOg7#4O7xLZ;9S%E!oiAB6FLyje7#P*QeSZnj4g^u5q%GRC7P3q$!7;c(L3c zS6*+l?^N)=#_Ko9H13C#?Fkjm&3F(z=EuK;5zH$}zvTpEe^FjyVSH}*k&e^idWU^n zHF{?5I#9q>$lTYf+;X2id5)xD_j<4OUdZ_ztpv3nK;lk>(_i_GId8CdV!Q5QL%};C z%4Z^B{mfxVC@~G^k+Y)}1X~{WMF=Aqd=>TQs%-#C^@3}#AKy-UaO`DOmIw$={Jrtn z4s10*pj46aPY8T}rO5st$U?yk=0vFB&prS$GxEM~V=1}9AagFQvD2T;o8Wp*5XN3z z2=8^g_5rDK-cV|rvk-n&Yx87BbY~kqN`n!DNCG@lr%Na@~;p?HE{Mt$upc> zuG$MK+)&TJSP~!|3#MRqdrc|J%ncGp5>*BzxcbTKk|y6mgIT`h05FxYpX z5pbMn_yGI-g@id%fGjA)*4-2`Xy4&t}JnGiLbrzeeDRsvrVFK67Iy>b9VZ$RKHUeaJq=_yH!OW^av1Ah|+*buRu6!Ydko_JjcvV56f21x2aOY z*1er}2V}kPFoSSwF(Q^`Zh#X#bogsR>v@y!h%RM$Q$1~Dr4*R zpBG3;7b;VK<+@)p)ZL9pYya!@9O5l{D6!gN`t9}&V`e@7?EUZf4RgotoNyd@>AmU= zB{c&K54-M9Gb_4#>GF)kYwW7e_ihiZZ*rKU-{g3vXn%Pv(vlNg>UXi5*efHq5*co; z`q9uN<%k&dj0lE>1w4ts&)YU=HYG(IwfytX;OTY2yND#je{@SFFqpdWwPg{J~*lAB?ycy@RU=xsB*0E~ox0I>k8JY*{G0YlI94`l>sdmtg?nDT+?+NXFv|3i$sB&UnH3bUfc|?&N=Q1-U z>ZwTu)jI1jeE6dqKdQKVEESdD;Ur__W~ni8eM;zdyd26e;{aMmAFQ4cGTQl#rP5bP z(RPfuc?#xa{|Hj4E{TK?XgZ%?-9uGE+PH{!n=G(X%yIV_$-p6RqxNYgM9QZ^YSf}5 z-W#H@IY%afpITVD*+M;EhBA$BqiLwj`#9l6#YkO@=y|{_L)7~7`aaGD3(P}AUpUdZ zv3KzFDCMzvs>KsEw7C)?nU`|E0YlzHMN|Kqbac3zxd)~Td4{TL zPj&x}0!|}D)7a>{TKpr3I2K~&0#x=6snUsl@B&E?K8QQu4`{;|q2|%T=yZHbE_bSl zV5aQ84%Swh9dYWi0r~U+*RNLJA6xmfc!yo{;%CN;S|Jl7G zQhzY#`ide&CEGXsH~L&Sl2YMYTpA2ubD${4y@V=@g;;Y2gu`0_wiV*gj@)ir7h*`( zB-LszkLrq>;-!URhJK0bokUqLCtOw#Le?g>=rGja%sK2Uc7GJ&UCF7xvpO3wWPHR% zGQjPK(d&Y0ZV3EESBn9x@$(&(!3y0=Q}}hH+?KczBQL*)$IS+)WI)LKjy*1Bw#>|k z(23-n3)ocsu{{nyYPs`TD%xT_B}dnes>?rQAp`AbRek2F3ZRZ!nooR;wOIK|?6^5@ zTfblFrG(9q0%JfF%o;aB17j{UPS!(AF_D&VK(cBj!SqjSsew$$(7d#0x}e^jOdzQB z>MBMU)= zAJFWtHbdOIV`N@g__Zwk9?c8?x$}UjnMTaLA(TAF-3aAzsE0__0pkyG9Y12oR^^R#KE1hsXeCrUO(5%eWm-zJ<;#~xeLHsOPU^`IjUBgz~h9x zKQUpiu2kNsv*UXW(`+9zQz{wQIPR=&XuoloK?~<_QU%JX>)1oeCUJz(Z!uAkRa{D! zl&OuRb|S`%S6ZPTNOw-WG&yqKUR{Gp_&teWASbd(JLP$;up{H+{?8v($E!G%>_jAK zS0ne}6YTu}03N~*VFT~rbKUe7W+R-rCxAz&(&{?;0%jAQF4>n!nm!d2Wd`J*2G;rH zc`y%0)w`c~-@&HbEAJ?9;luSyJ#OMBE{UdIu>U4qEGd}>vl7azD}R#@yDj@e|PyNI_>I2ypn6TkLBwYbH3;x%8oNeozteIiOz>C=Dd;?Z3F-Umq9sx}j3#T|~IKmh{K~oRtqh8k+UM!+s`bNa}xKie1sLpxm&7=Nv zt~i%ONap;=dw*p`Yi`+%yMH}UeG;1_Dmf#ki6H32;F#anlrLugxxRFe0EQ)Rk^6x; zE9os4m_{13br}2#=J29Ko)NXoy6*oref|XhCCpr4oE?(aE;;f;drPQ4vzN*HyI0&@ zYkh0aE+0lZ%QfCODNiTyFgB{RgBQIEo>ASnKpPRiR)7W4>v~A>;%LE@OMZ}nHCKbg zYERcgJ`wUwo3lW7XLB>JwtgujPB>TnnU$-hPap0bSy!@Z2}{pv9|MuAK?xbZoO`s% zdI->FtMm}4(7r`h{xYnr0!Ooit1aYd+>5?mcJ7QzusQq8+D0r+5U2u4CQGr#LFslR z6Jj;Hk&1m}Au6JOo4I%VUNMfP?a>?o@M7rG+YgDMzCVpWd~@b~^ZT0EM{;919fyW5 zopXFlciC#|m(!YcEzP!*!YtAx+qr%E_sygG=rKt0uPPR2utD88+klHOyDfIyJ?(ve z;neaGDs zUlfD~TnB#gp$@My*Esr_mgscx^dwoEY@Cjt=Fk`QF3{NTQN+c6d|VL}>e7wgPP^O* z8>*>!nd6SgtBqbmtB=|0Y3q`5LXCQF+yqsN#x8oro^HIIK04o#Kk=&bvTWY(*Z=I? zlTO-!3xOXm#J9ACp8h~*v3|LBGs$-XZbv1Th6k@Z&^ZwvDlg9LkbwXwG~|EK%4XjG z(b~`*{dGTfHHT=&FnOI!ILL_L!|J@rplobl9= zW1?z?1Xd9W2B)y?#R>w*XMBbJqS}JjQ?C4xt^hVfJY>k&NV;v%!e^546(1VVM+Uz~ z9b)R5XwkDHH8xyjKXoKC7nm@E)vmLT#VChyDGk?OnCBx4EXpLrt*|dP4 z&n(e^d*x(>#vHu1UsAX^+OEKnE{1a51I*#*GdXtUSke!N%96=Voak@=PTm~2?i#i% z78Z+RGvLTl$`Xs4lh4|g!)1b9QhguwJD~ioe-?~vqUJ4TcX9~X8GE*2QJtLtI<1-F z3pyr=yDFlC-_DgIWw5V{H=m*!vD;!Y_SY)S_EtXIBdKVo>;40Vdw_2u)gI@&FYkA{ z7zRegER4#bD8KYa&cX{nPkwA5ijacO=k|W#wTXZyfG&6k{Q582(aRMGO@aV*HXkz7 zX|V_1Rn1yeI!XZF(;=H@Umk^Y`ypIEzIk?Gr`y3J$CPS!Y=D1DX`a-k$qg|tY)Lka ziMM`$y&iob>}^_BER;4;T!B+=IYjuEFv8yNh!;HMl8ie?Are}nB7atAE??ay@y6r_ z^?68aCx*1 z96<74u@HnE9%s7!?|k9D0R%caf*|tZ=Gz{JG!f@De3Q{+BWUt?6EWe>=fL`X(uO`1 zvfX%Js6&eTDA*nT`V9>6>?AUuMFccgrhH{I4-zrB`2E(3(tih z^2h?12L~P%x$Ug_HjI5r>bf7GsAh_x2a_bFa)8&5HuqL%}?@+;h9ntT@6QUmDljlndy(n=#_3huq{@3@jE2CM;w z_TE_)vhl0GPR@!*l3(Jmpto>tJ~X~HZ+n$!K@*Xp3}$=nU`3~Pjv{_0iJ~`gV2;j< zL;vdfDYt+DaA+ygP#S-^o&^;1aAyRqfkvsvFw{t!=C+seV|3RO9U3EpQos|s@9Upv z<_IcFXk||5?@IENDf*J3#=+)Ba&8=Yv>VfTtuZGVeqBP|*kJ+Z(7uefZdAMmyRI{* z*t{%7d>msc>1nM_*Nc^?CQnz!^;m~r1jBk@w}aWBv9&p=X5SBtnkwtCc#4%Nx7|vB zP{_#SF-rWI!?sI_DO}euBW0SNu@(*BJ#5(S%uRwsHg3f;nyFE2)m=O_tl#c@ydzie zl3P7JS=lPwxp$HuU49!FNd2h#>tixA6D&0$E#)03F6`%MoOi9?Jt9|ppsBU%l6kGs zwF)IVkx>KJ4Ct#iJ7}aGJ3^lyd$NeSkFd!)Y6CE*4;^PqCgwPE4j^4aY8p;_Re%&xWn@tf``TVaX;IFR?lm zAaLBKe6OQ^?;ov+)t|IPJX5xxZmm@3XMg)l4C1R3biRVOzIC&WWm7Emt>nh= zhh|tHDUO0vTtAyxh#pC~he8bZT(mHhmvTYyLOA}|V1eWgpyfk$^T-)%#I(AZxen;} z@40LPtD(rI-lyA2>EKcDb>=|C>7V>Z8F?S8B%y^j9;_|mi{R!-l8ohsw6&az8e_sD zR!7&h-#ZzdsQGQQ2e+F$g9$MRhR$64`-qDbL+P=JP2;0e|GDLNXGSuH z=+RDF>b2+~H|twkUeUp)Nbei29BtBl7N`v`_=rAcMcEW zCnA{mA;ZSXG~ih4QF8#nO;n#E5a*J~uOoD0JQ*k|1ZcfX^Oi)n`L}YO>!L9i#Hd(Cn-&^TFJhwPq9>6Eh;;* zjW|}%Q{$@OPQuB$4oy+PG+Y>adB&o}y$wMB(DGKuHLsanCx0l>PlFb7fF8Zn1!gw9 zHkz=*fyk-TnwH{vUMpksT+bAPvg*u)cyb=?VY@h>?L}GlkAk<&n9XP=>!3s0Mg3JI z^QSfR85rT_v+%WN#pJ|MVEC_&2T&H}J05vmf|1AB)K#gQ`_WP=tzBE$pzzEkp_sgH zHDrY`j%OQiqmIQ#%o#NUw2w87KpXyvfQY|33<7OvCm=t3 zSdVPy82;=eBgbTUR->pbn3riArw?=~Ow=vyT7s+N&Uo9V7XpKO2;ad5@)1mYsyl}@ zstSV)gmoMMa2c^3EbR>wt`y+af>$G7#Sbl0_i|zZA-~cf(HVf*mat=L%hDt8qE?yi zi&;>RaL{P2sJ{$6Vm>|v$${e73~u?%nC_DR9To93#sE%`ua|922G!+4dhRFy?6yMQly6M)bjv9 z1PdHjxZJY!PXUhor)KqrE!%ag^u3H-mdD#Xm4J7Y3}6^LY5YMi-w%5h;W9 zH-ms__1M3xdU)ELaqXi)DpU7ER;Z-2~_M(IxbIys_BuNH-1N-o|H7qTftUDs1%^7ki0j@lLi|6!pc1 zWi?u-t64p-Y376I&L|>TK!{LH-VqPOMogl%9EdryPGfVX$Nt{ z@z`M=(*rhh{d7g;i^ zn?(yo;sTNks_ot9q>-D?9)&_&!Vq9bo}gkc|8!L5qgHnfw9w9Tetx2a7&yJSAad+Q ziis;Y@?L3K3QkX>%K#|sMaky?Ke!9OKdP}DcMY`TPU@8!<`E>I&! ztu(%@+BY;ZRd|M}1)g87yTsdf6$wPkN=o)+Jspj;=d2bxzI89>PsfK(%qjR&y% zp{gd3$pFt>(AT{_#D@mGLl`KSc0};`56k_`P2GNb zCRU^#D4mJ@1%WHg2_nlT8mi_1j@MtVVSaCyE?LterF;t}F04i;A@ty*LWeNaxQ7xQ zxeIyWuhf?rIB<|nB@ee&Y2U=tztu^Hbj|xDE${x@cdzBG-38W?@{FBPM@v6L;GCG!-#^ZdpckvFZDgXH;;2n@Vp8}<~N85vQ+xNpT0)6; zLf4qAd0yc%Wdx2mNE~v(Lg#kcP@5wEV^j;uFSvR4DCF(w4H2KU`p4wnQr#kiaVFOZI1zvR^~4=Y0GziSVwti8Kl`l;7v+)oCqX#A)~qy3k(A#Ef2 zVA{?-$>3y%8O{Oxq*9NCI8?Y8+Wy9p zr|w6Uk)~A`%K*4Dk4?F=f_XK}X>2hnfDDevq^65OYq^2tcxo?}fyE;p-3OnafjqEG zM=)Gy(H}kBjQ88tMQx4y%hp5$QnhfD8M?b0_wOho%mr8Pz&b`NlnL3~+F^)?@7xC_ zSfEMtJb_#LqSZ~Dey?JhbQ;PlH6H(b#1kMmn(f!88`I_GRybOa7VW#S{?sJJbD7x8 zB6BSS8DG%pVW{^-a3KY}X1;W^<4n% ze6y9MX_RS9Qo+O+W5O0oTHTrOSIOL&WGt}9t&LrYOn9u1U*oQlLZ2cFmWl!}$$g*$ zI}mL{?EDwgan|(*r57AOWo`R6Mp(~$2c|*qVYModXR-!wDtlgm=FIrJi?arxER@(S zp#V5S#s59RB1WMCuG`7A>eWm)Uu;75J^I9K**$tJ8*nOJb{}4KO~CSxk3|r!<bLugpDCL-2$sgmK*Z4xU5Q|(zi#TlEjr$KvvP1y`uauuOCo(PR!o8n0QFd=Ib&VZ#h-1R;MR<}efWB~&6A~-JC=DBK`bDzAaGaQ7%R0Xq@RxDL`B3_^V z@B&o@C37Zt6V3WlE49`O^uHM9PyAycq}pKRp+-^gTXSf)9EG|&9L-39lB*;W3>EVl zTrVdu`WT$*t++pXy&b>Z_bSN=8cP2MfDbum^_mB8bYWmua*v)Z6^L`!b4v}_YSQB` zzW+p4&|FTMq6o9qd|+)?mMsy8u4_QG<0 zJNCRzoevZ6Y|C@Oyjs|>%PlSJXmbhKi0Qtp8}0>qQmZ`4$PFreWfPs;0tdLyfO@uG zQ5r#iz8HX(RWt~Bt}%V>I#_HlX=5Qc@*f%CT2C$o2SJz;C$JxzbL1z3fuer`bZCkm z`ei^WxsdfY&nHP!5j`&ZOW9xNzN+PTjz3&u#Mwk3ab2vy$KcqNR#swM4!)QH(Z9Q^ z8>$}m^1vz+04_&2SrIhHtx2V7cAZgL(e z>(G*ZZ>%M`nZimDBugS> zL86#D^7I;z5zl@3q+)A-FS{sjqa;m?KjEP8qS-_!<3rWhwJwT;{*wePNKz>RLIq(y z_ci@TUPd=r2}O>ZAuN!5q!#p$NvlYeZx}=oeWl-ys{YU%I{z+$`J?vsdPEX)beV`M zY-5?x*oPg@I8j=L1@|^{?I4*CUPnG0d&Wi3oN_%AB!in+*OSSCuV6~}CO6pw;P^N_ z{rNb!-NnT{BmS5sOyjwXV(vx9u-gtJiV($B=X~WeH%APlLk7mkPzDRW71;&floS)a zB6>y^0NGQ4aOMJt1IgBxr@o3Y25cGg89{$Xd_81x4;TXdd3@V8;L-uI>md>NeCUAo z%{>Xi0yVJaEI(+fTgf>ZuY>#Lc@3rfPZ=gariKS0$#l@wLsz3a|mXT1NC=ldCvCle|%ARPUjZXaxXd~B)GXn;hEu2FDSPd zLmvkGs9QCM)I$8BeLv3VMwUZ>?~m%#&EZk=9)5H9tIV(aR9nJ)Q8gkV#cgh3U9_T8+!HM|wZ*S*{+ z|Mj9F7`;i=9IjKUE1jyJW&XS`Y%B=?uSk-m%We-duOdA;pGd@U-(P|&x`l|{1pqM? zRM@#h0R0FBdApt%8F;mV6MW`r4BzByfCnaE9nf1yVs4)6jAY0oPmdJLV1UDSur zuA688$NtlVC$SrDgd$5aL58$&F=CgRqxwty*P;6lN1NQ9FhtGy$UgcwoS5nX2-V-1{8obCRlbbeZxa2_U&o0AGN=v+gp;n1IOqka~BL`=K zp9E;Ea0HBWZ~^%pp^@>LiJi8S>81{XUM`W;3v6TngEj;{_YfhMOA!+3K>zjyF+E7a zv4bB;ziOs26}3P1ky)O)EP^mSs>Al6)sG`p6o$bm9Pu7LI9*q2$=et6@>7YNjc*Hy zpVwZH{xi}msC*_{tx0tLfi$R15#tfzeZc}zyu>G)SrI;;jAVK9w36A5`V({NLm`w% z+r^4N%%A0_fX8rgywBoCbJEl>GS`m`nu$5TZS%TmepIoE`>A^onG4QeEkyN{O2PlmE(l4baJuZzDZo;51wDb!+hJd`ujC^{OV9|rRe zt-U_N1}1(uZX4)*#4;~SwRRA^1|y1@4Uv~qBC&PdEbW!d@dgS%8ooN_apC_;D!via z`G*M$4~KCV!i1cz@l{z$S1@TiptyXPWxAt@iSu{-dbDTE=Y2C(CLqRSb$!o2&Sdbo z8Ob}J+kFrwG0B2D;eXSH39oxt3)29=wq7S+i5Sni^nGM!N8vB`=KuHq0;qtR{7p8i znfwb*;h(F|M)M)V0`&;jU1Pqcl?Opoh$V-ISnb`!0Trc~6kSZM)$`r|x`gk`y6qED zV=9!3f8BmdHz#&R23ZKrl=?J#G=8;xWe%uC5xN!qY%bldc6y~a1TP9b;r-)*2B2F| zen*{n)h{ip;wW~fUt{G6-}Uk4Sl3c-a|@@f<{|Uxxdv});?LSBn5J-a`>IQbl;Q*J z)4Q`sqaJ2tnF?l^>=JolS;Bj9S#KAwvsNbY?>Ynz*S(jk=d&lv?iteLx6EoEg^WMg z(a+wDjBGT(CRs839_vCoN(kkJq3~C5mho^lepkr2>AsIjrmjYT&mQ=sFNiKg+H=gJ z?!@fpxd#{7qaq$AA_yYgio_hX-M&7jk_K-9+fSBNphahzD5cvG3|si8+5E%TX8>w8 zt3F`JcZZMCg6H1X@!6p{O85KhDax2I3P4c_lC++TBIO6$>nzC|pVU;l5a6Ikes5`@=*+x~oPg0_Sk8Jt=DqNli-9+u&MQXu8z8?@QZ0m*p*##DejB(Xq$ zPE!>DQX0_O#VHCreUtlkJK4=ua*+j8UqmDDtLGdBkj&8di+16Rz6+BX=u~#a{lO8x zbhEbzHTCr$rus7k&C%WB?fl#-Bo-bp-z+&{$FmR)skke+p;{>Ss37x`lI9pnDp>DdUsznQ=x zNqQ!PgQ#CA19K|htHM;vhTlH;&sK6$LQLs6fyK$6j(nIycnkb`g8HA5wN=Xs;%z|@ zU^MOp>^nH>2bIH`hlBZ~vbCti>leYE(}GCyz9;Faz#7uA`&Ht>vEX<0vt`w^JS6*@ zyLIwcT&avN3~q8kmN;5;)RB^sC~V+hw=~%Y7JIW)eGFPp5IT{;m+(Aw8ci6ZZZEmC zU!%TMP+l?+#O1PA_#~Af>n~>>Yfz-Va~ZTy!v_}lX>}mo5{bZ^6o{vOocOJpbljx# zJA+WEFZ_$xy04ikuF~zQGxv~{8nr*EIZO=w$34T-A|*YDL(F!kr$EX-Wac0^!sYMP zd|aKT)1bSkJ*MCSg@bDOH)H{%C-I`4KV#p$ml*i%c9T#>#SAtVSze=-9&WiwGHR5s zctRYRIr#Z#)gkbSg$Fq$19?UDkUv|JLx0I|`zFsXC3Xeqg5k#FMRAa5CdCz(g{Y?D z)%vA6DUDVyAUaJFA}*KbRn@$d2w?Of-H3#AjE(`)jPCA^(J?mId;Pxu!u#X9Uw7}h_dMsx&*wSkLcJi9 zP%|lIKc+*Y(J$oGg$}K|u6&5|&syJdDgEA7ty8ZTPC7Pv$?kwU3;6%s5GBO%sio$+ zSr=7pm_KE?^Yq*Bcm@7v=sEg`C+f_wr}gY zWzXHNY=p6H-Ok)*IXfGeNJ1GTzTHC!`pPB}ENl&WUx0Oh4L1n^r!9^K}X-b2DhMTv+orv-Gly?+j!&9Z=<&CIv#O5=V*{(BEl5!WARPHFp zgEFg-YPzdKWuaQuvsjthBU{ADG{alr5>1Gi1@$mFh?XGcJ->|kaOKx23`F-t<;?Dp z2(V+l2md>;%Oqr{OM-tin6tG)$vzK0ssFir>j$fBKYq(!DQCD)Txs+6PvN;h#5X{)+!zQw-}AUb*pY2<8N=G->a!|LZruV&L4LZ#iI4cSfOC z8vXW$lourH<)-wT_{X3$#M6RZ+$;20Ki$Y8%M#gQ1FSJY=8lxLE$bJ#lMfTUK89`s z&N;i}%%~;!B9;b%ZZkbakB_z!)lD}Z6pW&`?rz2;2;Im6#o@hjyY!if&Ij-;ZA|o` zYy7`5rWCGref@(zU<)Xs*pWUJg~bZ!8HqN(n}gc3XIEG}#PKR5;_RpPQrc@yLB+~= zP(?tv7C#^VfaUy1(kcR#q*D!FI#xSwf2b zSvPg)AIDYsvVe!kfvd3*JQN0X0=UKcz;~mlxaS$sAMAD>1oXJk5L>R)0+p9zS8+l_ zy`-^;R{nJ;!yrzeZ2Xd)I7A!d$gE00@Hio(jxfW+7xTI1K)SuI#y9j!aKFfim))$) zK-0Rit83H&|IaRfRLt7$Eyb*zixiJG{!B9ioKqgL>;lEf%=2>uokbpJ2VYX#ynpe@ zy4*bzK3#Y~-}e|CN+*HlD9Hob7v78*z50tOz5IN=hr184cg*~> z?q!mG(+q?Kr%OOXwh#zE2=GERbJx*FXV3L(Y-ukb*whi5bE3ae@ZS+MF<=JV~C_9@tQ`wE}Pk3Gp_A2HfB)k`!$Z{u78XSB>Pz(O< zdp0>u@Zg;aR*tk(w?9>*Y!Eo?C|sx*$*<$!Bk}o@87Ry#Qt15%5aUL~S8bRMcdDDP z+rtU(q0TCZvlEhS6cbc_dmCwC)3gB*je>1I(yFvE1Kozltdb49C3njw0p-;F*AsyE zWZ-S&VDP{dZY?w}H~I4W6Tl__+()m;ab_t6=VX&ai@zWb5(CXy)k=B>^5J=#|xhI>mpisdgF&Sc26`{D9Oo zPYB@Pt?mLxjbwFoBIfLYC^%_8GRm97Pgj$zItM;*p? z{Bc{^xR`aTQ9HmOe?DS!Df_LqTR8*>{q8{L9b|QBmFV^R!ki`JTmsPgX)~bK>(i*a zphjj28yau0r!+I4pU*uYV|oE7kQ~syos^K}oGB6a5YGwnvYt$@dAVB! zJHZ+iw`u>37`TE372y~r3!`0GpZUWo z=Xm!QInXa$({JuF)`(?={lu4n&93)O@}~)fzHy_motM60eHp+E3xP_(Q&O}=xdtC4 zF^&K+4xHzu;kd!~E2Q|WMPPyc-z6dYi~~$}W=~1f#x}(is3e;=DC7@G*a-ah+ZnHa zbsIzsvI0c`K?XuL@m>1^HtNvS_qfr03a7ms(yy$SuLer@KWQ!E33w=_9aohbw^p4i zLvG52FJ^*WyPna2bU$U|ghu@IIdO3``O-;Ic6+`kToi?sIVTU&q|J-F?s@^r9d(4X zJa_bRNqb)Y0`OnCTASC3tpP>+_x%eq#i#<)TS8MlI6PDlTfyo-vCSA8s1d!#$|&yt zJ7Ng3WU(`MD-F84MTT&DrF|5LN$;vwlTn>Yf(u!QWi42UQTC;Y2rg*ek1#h_aF{kg z2zDj=rg)!5)Pan-hJbd4Y3@yp*o@!U75MLa<$CcQ0EXqe-KN7JPJ5htW^?)11EwI5 zecrl#9(=j+%HXoSPN;J8=a#K4O1$ziD=cZ7lcp0B6oSynEEatERdp*P5sxs#=M(-ZnDE1eKs?*lsjZUFZ8 zJurS)kQabVjjVhHKwvgnl!V!7d8Yesm62o$;tW z8;r?t54z{gr#IObg*dc#u9(9N85>MYHo;5L0Xt_L_yO<$K^8};DM>!v4hjg4Z~x6D z!HbCnSI(&BT62|V9Q>f}>mXv2YJZZcl!gJ)1?G>ii)_p1?_cKD8Hlox$Gpp*T*bED z3M@OZp-hPx$so`yWs^R;%f}(%|NHJYyPI08=Ho^eAz$72v+9>@kG|fwJu2exhYbdE z@oOKfQ<>u-kNe9kpmlH8O~809QGpQ!bU5@Z%j)-a@8Au_$??&_stoKz2ECrp?v#{!Vm18{u&4K z@nGIGt;0ECg~>V-1=-c(Z<&)lQqqW4n$Xm*e3Pe&DUFQTa;?nQ%xllD{PA0U+m$`+ z$#lZXyFaNv9xI>onD$x3FR~;I4}DKM@cVFaaTDnUVTtmhc7tAw*UVnjqjs+hGX@i#V2wA899h+p?=o>90nUBL?|LY^zZ-4xpcvcm(61X3bCxG4IGH=d#N(5x&+Xi+RY(+M$`Ffe-!8 zD2IIQ_8F!0!a2*if+19u*XhjJRFIIgjeIH};35JeX7Siw+3fn z?1kEoLq8JO->zq1Wu^Lj&6V%k?C@k)#7D;epgl3v#S1$g6()2^;7VWwITy9#0QvEj ztP>;GAe^658fx<7*YeV+DWiSeQEzg0rTGb~KQ~yyZd2U#gNz}s@DtW>cT2$m7P4ef zlOy$S=0l`4DEaiJr=r`fpkh*h+_7i#CJD}d2@XG2Fu#M`^ zj?VTUx~&HE$Er2Bv}xRRMxISzSPP`dD0Psn1s5PHNRU;9_o{37m)5J~iPG(?I#ziF zy|kr+Zykh7v?-E{Bdae{kjh0L4^&Nu4q-8Iek&8hv`23iQa-RUb0=~suRzB|1c}(@ke`!U)0TPJ`gZt$5&uCp<`%^IsO>ZQ`NB7*N zh%!DRQPZQV=8pX4re?lg&uheg@bg0S*UiU*?@3zT&Ker{^ZXKByX%iNl&mFcAU7O1 zw29NuhiHv#4HIm%My}I(Z<*aBg0)1fq%N_@1U-1~p#S0`9~*DSLvoUv%t_Q8{EB@Ng{HaKd;c>G)ODGEo0Gq_L&UOX$PzA>2-C!2VY>rJ_oKj3;j zGw!+Xa2<)2z%f`RP2HzjfnioXPH(IqtgPOKoru9_p}Gw{Cw1$Cc$nMT=N!vU_&-vA zc7B7pJH39ZriCOMU;j_S8+;gz0D=~k%)*3lI=`9;JBI*3roa97twjb`#A{PPAf67! zh?IygZ(3|v1~M(XCR}Bj^WC~4^Je8Y50@aFGtY;aDZrqHbzFLmB^ri3?U=i{OkosD zG&*he{Tvn=nQOi(!TuNp4Qdp@Ceq2%6dxM#>!ojNs~isP zO9^k}XG0KUBNh)z>s?6%dXYif4}xn?r7ob%I)+2vfkfui+lx2BuUvphNbn?f7SkY{ zGOq@j5#221_uD!<*8Fgub1&vl3{SV}W6EmD{rEu>ngl;_+s7ON_Oy;a?MyFl*LK<} zo)eP^^FU~Lx#}%!YsAM7>iqf)?-QXX`J#jF#q&+fo9HLJY%w>3&0XC`aBO|YAXX%R zW^KvoMlT)f&>(-k@Sp|uYBx(=ri3TFt8Q^c%28n60{`8&--|QXhe4BM9@zSf)YLh) z7$zP1N%(EmiW?1Ab{gFP@m}hRrXJL$PsD&&MB>(`QYVm8qTN(c){Q(_`XD`vSfci6 z^IG6Kd`pKAeOn~}!^2~W|If9BAE>UD*7z*&^2Z}*h2cq{%Ag?=5bD^Q^9fTcjC<1V z*I)o8wrs-1@wIw9{=5^IP}7hrasA+jLiTqR7?m0OG>ymYF6J?dK9QH*l%2s96>r`S zidp9`5+MisTw+rwCcK&s-^F8`s%?{Wd%NNuKOR3(g5oQ%=(zOn=&2yPU4eViJRG$< z3V87*Pv`uV9(A~c?FI>I*1|m$3f>==HtZXucN~_FHX8YJysNIYBzUcY^ zy0->l>l)1JKZ`<(U#j8paWsB`Bxb3l8!5OrhsFJtFthQm)=$3E^`NiUQ_6^%hDP2u zH74ebf}Y()5*q!DzQEduzfufl^oLjSeCp1P_$5xt+n*M%qGE3FnkSZ)VZ^sX#Fb`~y^4p{Y;4k9=yTzke4bwPJa*VlrkH^Bvgr4aBbAGf6CQZM3k zQsFy!cX=dYQBFft2F7vf00(M>k*-T;TtJwSmV%5lo<^;vchvmZWD-#}0|}a{1>M_u z>9(VCqbsVQN-Q)W|0H2_ui%C6-nH0kb!Wkvl2a&uj1-ckp+c( zEv0Tq2Yl!3K&sp)3<4m74&&w{x~FO{@qUc{sJChR22N2V_O zzhtTLT>W)qhSr=!pHWep*&NzLI8YfiJ)Tq)`0`AxxkHqub;6f{E8t2cSapb~wYB#Ew`2ndSyByqHjQQVV*V zkEcSs&}K4An;ApVrOunt(UfTU$Pht5Jo~)N3sc%xHVYgUC1kDl*4lwr`{G0WFQ>ZDZ8|*%S5B z9UV{?Xsy;Gc&xt7uGKA$WKXtzzg$vYWSyn(c~JZLkg#Aq0rZ{Z_0m>izkD%+I_74DXSm9Fe-6M~Pa0Yyuv)c*$RfcZ{!6 z4K^eaNhHR%R-(CWlXbr~fTfRGFo`X~oP#DY-oj}PNwGtgg5b;Ww*+8I-m`th%5}nr z+n(TnCrhd+6UtY!xbt~T9qO0PmKRQ-dT(lVvsgorOO@pz*1_N`u(UjcmajW)(%p5M z(YVPsk=S?R4|ksFxsHSD7&FMvr;Z}N%2!#M(0AnzcLybb6q#>`xc6SWSO1QS{iu+k zFi9fF7^w*XGrHxFTjaF6gr0vsJ@CmvZh$xkvyjv+zY;&cq1k%_s44EcHA8GBn{e=8 z6Li#-0&@M!PIky%{*uwJ-t(y(P3_#A;cJ*waHlkhfPejgys!4hcG5=Pi_i_~9se%$ zXY6=psTj0sj_*5^yDsx#2JQT9%)v&)esh2m*D;rKgQbw{Y!<}0FDt6L6mbheUk>0n z@A_PJvY~H2pglXOX7}mnptVebc-r>AdjcL^T)3x8Vq>Nkr#PCO&f@iB!xBl8Pei|+ zZ>Dz8&^>pVvCx0bp}8NAO8_zOHVl)Vyxzm=`C?IpTwt5HTL&JDQsFFhJK+j3fyFIW zMtteG2(7(Wzj|khrn+dV@rS5|WIf$4+!AICncab~yk<7qKLnvIjGUl<$030}nIP)nr3U$xBjrxdu(t{qC=xt;FqOd+w!}i{|$} z-4nJ0(IOq0^o6t0dR0wE%6Cvgzs%n5m4P zJJ=`IxgY$(`#y7ksstQz)8{?D#Auo!Fjk}P%-$avV>bO6N(>@f39pEbCMY&Ojgwpo zTqsC8+Yaw7;c z@yY!DwzBC4hc`>46vcp$wL`pt1Pf`LxSBz9EM zxn!C6irH(2C?%Rd--P{DD~au{6lKnxPP!?&(&fPQwELVo;tNo+^e$^Fecyyn5<42f zuS!ygixxFp1aR~I-h^BG3T+hgGjW?3gQhlW>#NDdm{b!jj>%o)jahgiP@!(Jk&Mg) zv%fdK=CN4C!F3I%6EpWGjQAEDF6O8|b(Lx-ebS6f1HErM-dY4a2&+h^9VnDrT)eLF zx<`uH4-KhiF?_OtCrXg=*R2g-PNkl-90a1P{`ZZ5j{`+MGUMurS5<{aJxJKE;Rop^ zreT#f4$oZne+Kup`_zrs1i=>nPSyDtO}F#2+Y36{zFxEpK>6Jqyr7eu(eci%b(L_*I9Y2>F|ZnE zt@Xycs~jBgLYFW;mR~1cvIH@e`wF2~YkQaC#*}9f>+q`9J}HtbTj;y-ZS>kC#nw*! zFmTaVI+D4qMt48|jJ}%*k%a0={<+XXvF(U$z~rza*eU8IxLhjMk>EH zs5>kN$n+a8=A9eFNA)nv_W2HN7#^bP|b_oa0O`=hhL(l3^R8u7Rj zPv#DaT=UvJ5>QID-un01bQu0S0%QT~+S~2W95UK@#Vh#$+v|CAjb??oT(H=Xa)f7% zx9TVqtkh->ZRe%+6-F)WNZHxF;3+INU*?T|``Ea)KO)Laf4sL5+s$j}mX8Z6XXL@b z_Wp1adC{MISd~+8eSUGi-8XsxUEP%>7H~OII_EIR`FZ#(Q5;5V16X1L%Qlil!pl4Q z?whZWa3jx>C=Em^CRwa-4}UUBAMcWzw(GA+)^EQa=JjzP)_7Tru`i?wNE%|W#^))) zpg~HNo72yCh*H z$aBpP(fGU6RsL~?(sY_LGogU42%b;-eDbM=Ib5T~pJ4XQ%{#W?Q>8j2-ypv5Qto$N zfVYH#5y#CPTFBiXe?moE?ZVEgUWO>%pHMFAm1*IGsmd<-JGNO zWd;28C45VssKq68^l2m~2(T1i%<7=XTWq2zVDIq?ZNzgMzTx8RRu7UIpV>dT%h|U;Zny@G}_ufCMv| zr&Zjyrh{8CaMXHHQ33a|$kswo>u_0?pr}>=g>;Vmb zjJkAqbPjlltHa9GmAa~+fj9E}S}kST73t3Bq)CyaOXO28;_n_us$1HC`ab^-P-wxVX+~sL*!7o@VJ8#ngip zVXZ&|Tl?Dj`?DUUerrxazp>0RKj7f>H31-hB#yd2sDmut{N*o?n{0m;CjInj zm=4Q6-9#%487Y0o$MR{ZpIDnni`&^>r>hjk7u4&}YSI`?*?OMfvx1fs|pel#cn` zp3dTi>868#!i~l|7T&aM$WdLXHoKGtCN@otQ9FX{X8aSDwYpfd_wT9I7Iu}Nl`g(P zcCYnES)BYe(EO~sLjqp&Up8_5&I2o@lE{A(uw%@e&3S$rRL;D zR!@NEGu|dV?`Rl|_jgBAa_{JrR1rw0W=e^_k#%TQf@JY0K=u>K@FDTgn z5s;>|p-1R$WbDJ)L;mGnqD0x%kzS;+_Dn{HqTWqZ8#T0|w`~YhQEl(-G6f_WmnOfo zyw^(w&a6T@RxY7wqpH>0a)Gq4=E&s(Rr9|I6EsUj^?`b*8i`AW%-1hG9?HHttk%#> zhQOOmf9j%t{ZiYHCtO@9zNcjcqb;7OW(sY!o!5*0YCppSx}Te zPmZctufMMn>tIz$?K$6duDnb5kObVrvLjlR-mw(dL9GTScWlU?B~l$-wKQX1IiqN< zwA=VY+9D#Q3X*|dGG*}dT)HZ+htWhP-(%Mf`K@6>^pqo(Q*Z$*@$01*;Ijo#@SuNs zItmMAvuaUJq(%f1(u=urM3NQ9-b-qk_mqs+BME;#^;nm7fjIwN>;!3xO(J~nSAp{O z+%wWHxGv+H#EShiI|cyT+8C^HEk96~Ie?wV+40OdA9rN8Gv7qy#$R8hHT96#ky(Cy zMQ+i&{c&m2xMAfx#xJQmfou-X69^ad5TO8)KnGuh!sW9exkwy7fwWPfrJ&ed!EyoB zYGYJmF5BJRdhQtiC)_EP3O>{761|dW7-a-k?K^tEu>4X#Ue19f3Jc-im0q`FGFl5q zE|Z#MJ+q1lLYM9i>8otpm5S@1@er?S_tAchX~kt{6K*Q3b*ZEx>-)92*Y#RR&@eXo z82+Ygl!woeFh|*M&S>?L$*O~p#$-Z&`mp>CV^f-(-S7)7m1l*Hmm(?`p@fBkmYsI0 zcdtVLY(awN7-8qWQ*`_A!$y9T6sJF6HWFB8zZTpO`b>=qIhu~XLJBg;> zln*~1@&mb`&*1#3>)~L3|N4UEg0s5mvWElC(begTq#&kCo8}K|i^S3c6{_Ho=UXhh zTq-Xk^2&JHsqjQV4j|J*QSMcNzL7PuSZqz{+ZII?5#&20YeO|2*hA>SA38^5fW^X&6&RjG&`7*xYID(Nw>Pw{3ow3R1Ni|rX%gqg_#$^-l$U$)gY zTC2_1KKi~IWoU+;lNO;hRULO^vx&@PY^J1hVtU7lG6;po{C6{uWvMl##CWPrRNPL8 zTQViG<~ofCsXZ%FMSnu2yF^%27P#b)n7R{f%XcUxa2NB$k&=>YTDA2K_JS(+t5Vdw zAC20aUmV6=!MA<%H%Hy49eovxHhiXow`HOhiyS}W)0_po$kMy0_b4f&NA_*|qW?0G zYSOO*DDF3DU+~4EW70{rCC^nLH|mN+Tzul7%7rV7+CyErRagMCBMRQD(7eqtI;$+3 zIsRErj@?mRmaeY_#`SS-LW+wk&y9;mHapq+Yqc%7% z7we&h&o7>G^o%$KN!Wz7vV&_uE~$Cs92HVWhtQK7gl)+FhQ~d4DTMa&dv~}QEoJnf zbx6;yGXoxVVpv$%$_)eGY8^=_XGtnXGYLpOP8(tOM`?>3hv#TjN`hFC#8*|e8stKh ze{Q|+y|@SshCOAval=l5I%lk7}xZ6%|mnhXN4x zNzOF@r`R({g?Y%N9TfC%3-S-kA9KG@>0D-k#8iibbcg2AIFeZ)#mgR^0_mXrjyoPd zd2MtI_v0(~jKDVBzVji=D?t)iNZ-j4AdC9c@6)q5wv9RO%z*-zrw&r?40KiQDbokK zgN)SYbNE;xmMc=Zodu zbs)Cr`U0bMBzQ!Ow801X-eoERLwv(1u0bR5Db#(z;o;NW@xVM(!?ztyOopIeclR5q zn;iwd6vjKgG&}M$`1dF&A@X>eABnGR1h>Dly-qmqM`H*khp|O7h(&Hu(S+b{k*1eZ zCJv)?U9KvsUw`c8eERns|4*MoSDGpRusA~f^FD+V->EyG=d&6W1GCz~>}-OHf6z2gz3h^FH|}KJ9YLU( zK=r0hGpb+^CFRl6+b6_VB$;%0W}Dn4k#_Va-AvNkVbl%be->XvGYi&hH zz*1JizlR8A<>X$f6Kk5~(hY$%g*aH{<>iO3vs~`G`cMqm(B6CqQL})(T2xqkDE_$L zZS*=&&6Z*K-%Vi}%$lxw3>Hg}$5SXqD3J0Q`k=j}d<{gC#|j^uV=T=rPfq`+d3vwO z(P>>ro=N2jqze<_g@q`-C@?us^ubFDtTk@XtOOW(r@^zdy4@L7gvEhmdPWT@3!poA zZv4|&`lH&+6kVSc{`ZzGvAzzW;u`LchrSsjTj&geweQybzIc3!n(MqElnLqq7{=2s z7tc>y7=WKbb@6iGPcD~fM{k|G2%fZ@!``L7dLD zES#U1ZLS^8G+PRIwtGVGua8yQQY*S0=ga;*lCzp!1q5BlWZB!TAkpI1$#zgXsfKNA zM@K6s6xld0 z@NayLcvy5Mft-^01R-53JmR46IUyrA43rR{!1^mO;Xmh*^0*6aKwtowgogI#! zPDD&RAN}qZ1oJB-HWw#z9PB?kha-Fk*;o}ha#{)kX2QyqWrQ&Y#6#(G1NnS$=8u_ck7jjlIuJlw=Jf10@*#593?rC zmuTEy#v`xuyIfi){`XeGSv%mCntrzn@i;k7@xQF)z20-WA5>>eX zhX2AN?_E-GMdC&OQ<=mo8n6Y=k&q44sgVrJQU>W(4bTS3$th*U5kANJWLQev#BYOi zOUR&eQaC@J1sp+AxXv?k)H9Ajbi5yl#uwCx>Jp!ddPYd*fAh`Z`pAflp<3DY_17%< zw3y1bp8^~Vq_kY`rs(YKjQ+T9k;|*wYUz@2au$}+J>zQY5ENqJS+$~Q6`Eu9-5b!xV$mi7(IIiPrY~~D-_rj z81%w}{r!Ks&r=uxZ#2|J80x8(q$&a&KhRg9s1yP@#&jVk5~96A#!gkp&CO$eCab!u zlJ{^~t!-?){(DrP+1Ggod-4SZgoar169EO}DY$R&&eBTO4QCu7Wx^|cIk`w1t0AT* zK_h}%bSZcV_%8VS5p1{dsVTYi5Tx}4Io;mI)B^OE!b=FCwT4TFru*S#&LQ}W6P$?e zs!+HF^!R*~VZRJKmc!`XmIA#eTgH6>F4X-XcSx`idI+Zn_dp?)QBWYw5nCno2k}}# zrdv!@{-($Of&n+PwiX8FbFrLe(r-h0S`=gNK{kQ}H`c}mjvu(C|95~i(o=j1JU79|U$19a7Q=fIGQPh_O2Ju*mhA#* z6!GgR03qq|K5dGP%o0A@1%*KGlS2rr4ia4HteL!AY|temjnY z#K#;-j^IRt{f7=5j*MVOJH~Tj^9Q}=0bpXzT(@RxO2I(KZed(<82MAYXYs<> z%jPOGNI(3ua`Ciz$L5{f=RDPl;lGaHS0W?xzD#*Y-pF&2)theq*5unW3w;-cPPuOo zcRKmZbYuT@hZ-FGdeRT1Ht$b+>7DYr*`vd)qV=Psbu5i0Ulr%OrM3UpHlF0bfVbN; zNboQ7D^J)iqyg}?%VLG5-wi5DXLqkJdJWRQHX8JFaSQQC`@&mS@ZEqAUWp0^Zu|jcOd^4-i#@$U6s=PcIg==c*E}P@>AU*4*B{V?Z#y zBVOC(7VrJ)!8FkLwo?D==i}7kQg`{4>A(#Qms1Z9g9mxsnAy=HocMNL%L#XHO~6=c8v~3+$>Mpcjs$ z8Ueuywt~Ow<4B|cEGx9vze_8ItB4)EaFjo&gDkhy4A8TZ>z#;1)+OF+KCNs)8UXCv z=giK0RnP^ma3*IXk^=2mCn)%P_<0?%khe>yrbTQ!Fn$pD5PInRL!BdXn3Pzg&Pi#+ z`M`aDFW3+?JvqnU*!lBE6CyYqg084pAx})D*#c+#s__r%#)-@AXd(>petNJGXnJ&x zA9jeT{xn`R2jphi>+kaH(t9lhQ#L`2xBU6n)Lno@JEU#v2}Rxxj{FRyyiiSBj{frq zzR(b^A^k2?Z%4uME4p)Z@MHgb7=S1*%GumJ&h{=|y$rsIf%5;Q% z42?Y>W%mIF0@b-fHXr7iu}D`-eArNm&3V4v7zhw*s=Yhd(?eov4~Xgkq!d zM?OD%G_~ntAYv}!edr_Y?}q!rnmgP_x+FeE4GvTXFgp=dPQPSVe1Lk8DiNz{m=>FP z{n9VZRLzJ`vUKol1+%CEHv4l8To$QJE(|{8(d18L@8biuvxZ8cyv^`8kvyUE?Ez?#o(S^0~j#Aeeb=i1PUxWDDwwRGlv~TWs ztVrjJgnUk~ks90ads?P;ob4$7x?a-f)J&&pay%saSn+T_KaAsV@N$;hyP{M0Kk=5z z-D0%wGt#QnWvA`o!txcCYR~h=h&M{`nAfW5Bl}JZ6Ckzi zXm;w>vC#4G`BuO&7_;nFA0Mqd&EHzL0Gv3ikT@RFy4C+gt%}l3b9Nmxkaob-!fgDL zyGtep0Vj=tCm!@Abcr(f`)lMy>t4(~im+C%n9OZ{$Y%Gf` z`KU?6DHmcN19MhkNEZ#+KG>oh0C(BAvkFtmg(2H|L>UBjzr~~2ncWtvW8V2U)ENmv>I*@u44(F(nu_V-hEY-UZ5QGWBdlgEf7ivz#LfpYt4KRm zPvrNedc=(X?3VYSLHSk~)0+nI3ochCcHe!{So&;<{_Ya~ws+gzP}QpqLXhK&$G3}~ zH)Q*J&RzrOJe>QB(ooQtDWA4fK9c2icIHvv zBzpRA&Qr3l(Vw^ds~Mk}@ZqQ7DVZ3Oa!}^<{x*T+@FeNnk;l6j$ z#Y46)b0Q6#SKt9%@1VX}8xoJYh0#}*pf}xg)anya>f8#ZbOvIuI0cREwF(F*SS1y%qfT8Jao?0`c zDY%t6%@_;kOx(&jXUyauAz0^>yW);l&^EjM67P4r4FDvMf-S3DsoBM#GYgZ;dZv0cZRQy}WZLPJpP;%jz=# zw>7_U4YEC2x9X1LJLkz|EYp=<6N2+JXmIy@0v>&Cw(+KXhe^ez-waEh%)xv?5;4&R zf5beo^1*^2S;I`;R*96+0R03h45@ z`9!W3GJFzeO61^U~Tw!D=O8 z*J{%=V4tuaX|t|zlq<=W0pt+JZnzh3!SdW|0PaPeqB=VU_;5*=Q44V`hAf9{I@@WgRBlRG zR;VV=iUtJ*#?mcz{BC51U_YEZgY^)|&4iOn_yWQ#M8}lr^R~Y;lPD}%3$1(@>olZg z0^^0}JkG0ZNmvcH823=heHel!GdQx~h{;5TAP|!m!x58x8G-_8gC*;s5O@S-)1^~Q z37l-`V5SQ(N{o%JL=^{S#yrO0aReM^!u5n~x{no^x#!2B9xM2azL(+i5h7(mK@kL) z5}sVv5k{9Qx|OEEaW)(~g@QOpm>tEC-o2tDxUaNg6+-2L zT;uZ6C46854;sw0+>FqesV1}F!Q~2JxrN5%x8QPvDWUNsd@m?~O+0Vm2 zP(YImwh~w#Z2(butNqoF%)*h~FmE5F|gHyrOmsY4Zf zv2?MB2RnrJh%QNX{{1fXLn@tjYyCVhr_awUyfmOiZ^M0-V95eIZiqK>;Cpw{DGeL> zkTw38DE-abN*?Qak9|H{!c?CWd6j_jsa@G&@6I@OKCQ8mpB9BC{_BJZXjN`S!UH%o z<>{1ls3)zT>=Q{xRFP)kkI!i;s1)&=`y|1N+SRqSgsK>xN6UpQo0^Ec6Ei@i@-8*L zarAQ0>Z;h0J$h1cY6)Pa7F|$ZR9ToNrOOT(mRGw_j-GF=yP*?Y-E4t5_=&B@$YMj}5 zL3N$F%2rc04Xp(ZreLc!ub=jhw?>(~FQ_C5J8^En73(%p$txHCw^T;tkSOlJB8tt<6qeg7YXY7+GT literal 0 HcmV?d00001 diff --git a/src/images/cloud.png b/src/images/cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..bddc8b3af2ff7f9b01b64eade11c093ee32b7c9f GIT binary patch literal 14084 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 LTU+)k?XUj>Ig){R literal 0 HcmV?d00001 diff --git a/src/images/download.png b/src/images/download.png new file mode 100644 index 0000000000000000000000000000000000000000..89f0bad4ebbc8fa4b3f02507b564c9460bd7f1f2 GIT binary patch literal 4293 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%0AKbuca*xXy>`Sr0}4*L(ZWf3q6`rD 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 YefnKi!ohOznjD0;ceQ)G&;R`Y1Ku)}{{R30 literal 0 HcmV?d00001 diff --git a/src/images/table.png b/src/images/table.png new file mode 100644 index 0000000000000000000000000000000000000000..a97818c95eee69896355452dfad21d96995432b9 GIT binary patch literal 3930 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 literal 0 HcmV?d00001 diff --git a/src/key_gen.py b/src/key_gen.py new file mode 100644 index 0000000..05cb82e --- /dev/null +++ b/src/key_gen.py @@ -0,0 +1,7 @@ +from ecdsa import SigningKey, NIST256p + +key = SigningKey.generate(curve=NIST256p) + +s = input('key name:') +with open(s+".pem", "wb") as f: + f.write(key.to_pem(format="pkcs8")) \ No newline at end of file diff --git a/src/server.py b/src/server.py new file mode 100644 index 0000000..5d5248c --- /dev/null +++ b/src/server.py @@ -0,0 +1,218 @@ +import socket +import threading +from time import sleep +from encoding import EncodeFile, VerifyFile, encrypt_str, decrypt_str +from ecdsa import SigningKey, VerifyingKey +from os import getcwd, mkdir +from os.path import join +import yadisk +from base_api import * +from random import randint + +#Параметры +host = 'localhost'# +port = 1234 +#Токен yandex диска +y = yadisk.YaDisk(token='') +storage_dir = getcwd() + +buff_size = 1024 +private_key, public_key = None, None +name = None +users_now = 0 + +def recieveFile(client, file_name): + try: + file = open(join(storage_dir, name, file_name), 'wb') + except: + #print(f'[SERVER] can"t open {join(storage_dir, login,file_name)}') + return + data = client.recv(buff_size) + while data[-5:] != b'': + file.write(data) + data = client.recv(buff_size) + + if(len(data) > 5): + file.write(data[0:-5]) + + + print("[SERVER] transfered succesfully") + + file.close() + + +def sendFile(socket_, file_name): + #socket_.send(file_name.encode()) + #print(file_name) + try: + file = open(file_name, 'rb') + except: + print(f'Cant open: {file_name}') + data = file.read(buff_size) + while data: + socket_.send((data)) + data = file.read(buff_size) + socket_.send(b'') + print('[SERVER] msg shared!') + +def sendFile_(socket_, file_name): + file = open(file_name, 'rb') + data = file.read() + while data: + socket_.send((data)) + data = file.read(buff_size) + socket_.send(b'') + print('[SERVER] msg shared!') + +# +def uploadDisk(file_name): + y.upload(join(storage_dir, name, file_name), f'test/{name}/{file_name}') + + + +def recieveData(client): + global name, private_key, public_key, users_now, union_key + while True: + try: + command = client.recv(buff_size).decode() + cmd_ = command[0:command.find(' ')] + print(f'[SERVER] rec command from [{name}]: {command}') + if cmd_ == 'send_file': # file: client -> server + recieveFile(client, command[command.find(' ')+1:]) + elif cmd_ == 'use_key': #use_file + f = client.recv(buff_size) + private_key = SigningKey.from_pem(f) + elif cmd_ == 'use_key_pub': + f = client.recv(buff_size) + #print(f) + public_key = VerifyingKey.from_pem(f) + elif cmd_ == 'encode_file':#encode_file + EncodeFile(join(storage_dir, name, command[command.find(' ')+1:]).replace('\\', '/'), private_key) + print(join(storage_dir, name, command[command.find(' ')+1:]).replace('\\', '/')) + sendFile_(client, join(storage_dir, name, command[command.find(' ')+1:]+'.sig').replace('\\', '/')) + pub_ = private_key.verifying_key + #print(pub_.to_pem()) + sleep(1) + client.send(pub_.to_pem()) + + elif cmd_ == 'verify_sign':#verify_sign + args = command[command.find(' ')+1:] + res = (VerifyFile(join(storage_dir, name, args[0:args.find('.sig ')+4]), join(storage_dir, name, args[args.find('.sig ')+5:]), public_key)) + print(str(res)) + client.send(str(res).encode()) + elif cmd_ == 'send_encode_file': + recieveFile(client, command[command.find(' ')+1:]) + EncodeFile(command[command.find(' ')+1:], private_key) + + elif cmd_ == 'send_encode_file_with_key': + recieveFile(client, command[command.find(' ')+1:]) + f = client.recv(buff_size) + private_key = SigningKey.from_pem(f) + verify_key = private_key.verifying_key + print('Here') + EncodeFile(command[command.find(' ')+1:], private_key) + client.send((command[command.find(' ')+1:]+".sig").encode()) + sendFile(client, command[command.find(' ')+1:]+'.sig') + print(f'[SERVER] file {command[command.find(" ")+1:]} signed and sended back') + + elif cmd_ == 'rec_file_cloud': + file_name = command[command.find(' ')+1:] + y.download(f'/test/{name}/{file_name}', join(storage_dir, name, file_name)) + sendFile(client, join(storage_dir, name, file_name)) + elif cmd_ == 'reg': + try: + s = command.split(' ') + try: + insert_into_users(s[1], s[2], s[3], s[4]) + client.send((str('norm')).encode()) + except: + client.send((str('ex_1')).encode()) + continue + name = s[1] + mkdir(join(storage_dir, s[1])) + y.mkdir(f'/test/{name}') + print(f"[SERVER] Reg'd user: {s[1]}") + except: + print(f"[SERVER] User already exist: {s[1]}") + elif cmd_ == 'login': + s = command.split(' ') + name = s[1] + print(f'[SERVER] Entered in app: {name}') + res = user_verification(s[1],s[2]) + client.send((str(res)).encode()) + + elif cmd_ == 'log_unique': + s = command.split(' ') + res = checking_uniqueness(s[1]) + client.send((str(res)).encode()) + #print(name) + elif cmd_ == 'upload_file': + recieveFile(client, command[command.find(' ')+1:]) + uploadDisk(command[command.find(' ')+1:]) + + elif command == 'dwnld_files': + files = '|'.join([(x['name']+' '+ '%.2f' % (x['size']/1024)) for x in (y.listdir(f'/test/{name}')) ]) + + print(files) + if files == '': + files = ' ' + client.send(files.encode()) + print(f'[SERVER] files in disk shared for {name}') + elif command == 'exit': + print(f'[SERVER] client [ {name} ] disconnected') + users_now -=1 + client.close() + break + else: + print('[SERVER] Unknown command') + except: + pass + + + + + + +socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +socket_.bind((host, port)) +socket_.listen(16) +print(f"[Server listening...]") +#print(getcwd()) + +def recieve(): + global users_now + while True: + try: + conn, addr = socket_.accept() + users_now += 1 + #регает новые подключения + print(f"[New connection]: {addr}") + print(f'[SERVER] active users {users_now}') + #print(f'Current connections: {connections}') + #sendFile(socket_, '1.png') + thread = threading.Thread(target=recieveData, args=(conn,)) + thread.start() + except: + print("Can't connect") + ''' + file_name = conn.recv(buff_size).decode() + print(file_name) + + file = open(file_name, 'wb') + + data = conn.recv(buff_size) + while data: + file.write(data) + data = conn.recv(buff_size) + + print("[SERVER] transfered succesfully") + ''' + + + + + conn.close() + + +recieve() +socket_.close() \ No newline at end of file diff --git a/src/signify.py b/src/signify.py new file mode 100644 index 0000000..d80be91 --- /dev/null +++ b/src/signify.py @@ -0,0 +1,307 @@ +import tkinter as tk +import customtkinter as ctk +from PIL import Image +from encoding import useKey, EncodeFile, VerifyFile, UsePub +from os import getcwd +from os.path import basename, getsize +import socket +from client import sendFile, recieveFile, ServerMeet +from gui_auth import * +from time import sleep +from random import randint +from tkinter import messagebox + +#Параметры +host = 'localhost' +port = 1234 +buff_size = 1024 + + +class App(ctk.CTk): + def __init__(self): + super().__init__() + + + + self.cur_dir = getcwd() + + self.resizable(1,1) + + self.private_key = None + self.public_key = None + + self.encode_file_path = None + + self.verify_file_path = None + self.source_file_path = None + + self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + self.client.connect((host, port)) + except: + print(f"Can't connect to: {host, port}") + # recieveFile(self.client, 'ar.png') + + #self.client.send('login ilya'.encode()) + self.geometry('900x600+290+115') + self.title('signify') + self.list_dwnld_files = (ServerMeet(self.client, 'dwnld_files')) + + + #set grid 1 row, 2 columns + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(1, weight=1) + #self.grid_columnconfigure(2, weight=1) + self.columnconfigure(0, minsize=230) + self.columnconfigure(1, weight=1, minsize=700) + #self.rowconfigure(0, minsize=600) + + #load images + logo_image = ctk.CTkImage(Image.open('images/armadillo (1).png'), size = (60, 60)) + add_image = ctk.CTkImage(Image.open('images/cloud.png'), size = (30, 30)) + #folder_image = ctk.CTkImage(Image.open('folder.png'), size = (30, 30)) + encode_image = ctk.CTkImage(Image.open('images/key.png'), size = (30, 30)) + verify_image = ctk.CTkImage(Image.open('images/verify.png'), size = (30, 30)) + download_image = ctk.CTkImage(Image.open('images/download.png'), size = (30, 30)) + self.lablekey_image = ctk.CTkImage(Image.open('images/key2.png'), size = (400, 400)) + self.lablecloud_image = ctk.CTkImage(Image.open('images/cloud.png'), size = (400, 400)) + self.lableverify_image1 = ctk.CTkImage(Image.open('images/cross.png'), size = (400, 400)) + self.lableverify_image2 = ctk.CTkImage(Image.open('images/verify.png'), size = (400, 400)) + self.table_image = ctk.CTkImage(Image.open('images/table.png'), size = (512, 512)) + self.micro_load = ctk.CTkImage(Image.open('images/download.png'), size = (20, 20)) + + #frame + self.navigation_frame = ctk.CTkFrame(self, corner_radius=0, width=400) + self.navigation_frame.grid(row=0, column=0, sticky='nsew', padx=10, pady=10) + # self.navigation_frame.grid_rowconfigure(2, weight=1) + + #lable + self.lable_logo = ctk.CTkLabel(self.navigation_frame, corner_radius=0, image=logo_image, font=ctk.CTkFont(size=20, weight="bold"), compound='left', text='signify ') + self.lable_logo.grid(row=0, column = 0, padx = 20, pady = 15) + + + #buttons + self.add_button = ctk.CTkButton(self.navigation_frame, text=' Загрузить', image=add_image, compound='top', height=40, fg_color='#C57A44', command=self.upload_cloud, font=('Century Gothic', 15)) + self.add_button.grid(row=1, column = 0, sticky = 'nsew', padx=10, pady=5) + + self.download_button = ctk.CTkButton(self.navigation_frame, text=' Скачать', image=download_image, compound='top', height=40, fg_color='#C57A44', command=self.download_cloud, font=('Century Gothic', 15)) + self.download_button.grid(row=2, column = 0, sticky = 'nsew', padx=10, pady=5) + + self.verify = ctk.CTkButton(self.navigation_frame, text=' Проверка подписи', image=verify_image, compound='top', height=40, fg_color='#C57A44', command=self.verify_main, font=('Century Gothic', 15)) + self.verify.grid(row=3, column = 0, sticky = 'ew', padx=10, pady=5) + + self.encode = ctk.CTkButton(self.navigation_frame, text=' Подписать', image=encode_image, compound='top', height=40, command=self.encode_main, fg_color='#C57A44', font=('Century Gothic', 15)) + self.encode.grid(row=4, column = 0, sticky = 'ew',padx=10, pady=5) + + + #main frame + self.main_frame = ctk.CTkFrame(self, corner_radius=0) + # self.main_frame.grid_columnconfigure(0, weight=1) + self.main_frame.grid(row=0, column=1, padx=10, pady = 10, sticky = 'nsew') + + self.main_frame.rowconfigure(0, weight=1) + self.main_frame.rowconfigure(1, weight=1) + self.main_frame.rowconfigure(2, weight=1) + self.main_frame.rowconfigure(3, weight=1) + self.main_frame.rowconfigure(4, weight=1) + self.main_frame.columnconfigure(1, weight=1) + + + self.label = ctk.CTkLabel(self.main_frame, corner_radius=0, image=self.table_image, font=ctk.CTkFont(size=20, weight="bold"), compound='left', text='') + self.label.grid(row=0,column=1, padx=25, pady = 25) + + + #buttons for verify page + # self.button_verify = ctk.CTkButton(self.main_frame, ) + def __del__(self): + ServerMeet(self.client, 'exit') + + def encode_main(self): + for widget in self.main_frame.winfo_children(): + widget.destroy() + + + #Label + self.label = ctk.CTkLabel(self.main_frame, corner_radius=0, text='Выберите файл и ключ для шифрования:', + image=self.lablekey_image, compound='top', anchor=ctk.CENTER,font=ctk.CTkFont(size=18, weight="bold")) + self.label.grid(row=0, column=1) + + + + #buttons for encode page + self.button_file = ctk.CTkButton(self.main_frame, corner_radius=5, text = 'Закрытый ключ', fg_color='#C57A44', + command=lambda: self.pick_file(('Pem Files', '*.pem'), 'private_key')) + self.privateKey_file = ctk.CTkButton(self.main_frame, corner_radius=5, text = 'Файл для подписи', fg_color='#C57A44', + command=lambda: self.pick_file(('All files', '*.*'), 'encode_file_path')) + + #self.sign_file = ctk.CTkButton(self.main_frame, corner_radius=5, text = 'Make sign', fg_color='#C57A44', command=lambda : EncodeFile(self.encode_file_path, self.private_key)) + self.sign_file = ctk.CTkButton(self.main_frame, corner_radius=5, text = 'Сделать подпись', fg_color='#C57A44', + command=lambda : self.ServerMeet_(self.client, f'encode_file')) + + + self.button_file.grid(row=1, column = 1, sticky = 'nsew', padx=10, pady=5) + self.privateKey_file.grid(row=2, column = 1, sticky = 'nsew', padx=10, pady=5) + self.sign_file.grid(row=3, column=1, sticky = 'nsew', padx=10, pady=5) + + + # def pick_and_send(self, name): + # if (name == "private_key"): + + def ServerMeet_(self, client, s): + # if self.encode_file_path != None and self.private_key != None: + if self.encode_file_path != None and self.private_key != None: + ServerMeet(self.client, f'encode_file {basename(self.encode_file_path)}') + messagebox.showinfo('signify', 'Файл успешно подписан!') + else: + messagebox.showerror('signify', 'Выберите закрытый ключ и файл подписи!') + + #else: + # messagebox.showerror('Ошибка', 'Выберите файл и закрытый ключ для подписи') + + + def verify_main(self): + for widget in self.main_frame.winfo_children(): + widget.destroy() + + self.label = ctk.CTkLabel(self.main_frame, corner_radius=0, text='Выберите подписанный файл, исходный файл и открытый ключ:', + image=self.lablekey_image, compound='top', anchor=ctk.CENTER,font=ctk.CTkFont(size=18, weight="bold")) + self.label.grid(row=0, column=1) + + + self.label.columnconfigure(1, weight=1) + + + self.publicKey_file = ctk.CTkButton(self.main_frame, corner_radius=5, text = 'Открытый ключ', fg_color='#C57A44', + command=lambda: self.pick_file(('Pem Files', '*.pem'), 'public_key')) + self.signed_file = ctk.CTkButton(self.main_frame, corner_radius=5, text = 'Подписанный файл', fg_color='#C57A44', + command=lambda: self.pick_file(('All files', '*.*'), 'signed_file')) + self.source_file = ctk.CTkButton(self.main_frame, corner_radius=5, text = 'Исходный файл', fg_color='#C57A44', + command=lambda: self.pick_file(('All files', '*.*'), 'source_file')) + self.verify_file = ctk.CTkButton(self.main_frame, corner_radius=5, text = 'Проверка подписи', fg_color='#C57A44', + command=lambda : + self.VerifyFile_(path_sig = self.verify_file_path, path_source=self.source_file_path)) + + + self.publicKey_file.grid(row=1, column = 1, sticky = 'nsew', padx=10, pady=5) + self.signed_file.grid(row=2, column = 1, sticky = 'nsew', padx=10, pady=5) + self.source_file.grid(row=3, column = 1, sticky = 'nsew', padx=10, pady=5) + self.verify_file.grid(row=4, column=1, sticky = 'nsew', padx=10, pady=5) + + + def VerifyFile_(self, path_sig, path_source): + print('sig:',path_sig, 'source:',path_source) + if path_sig == None and path_source == None: + messagebox.showerror('signify', 'Выберите необходимые параметры') + return + res = ServerMeet(self.client, f"verify_sign {basename(path_sig)} {basename(path_source)}") + #print(res, res == True) + if res == 'True': + self.label.configure(image = self.lableverify_image2) + messagebox.showinfo('signify', 'Подпись документа достоверна') + else: + self.label.configure(image = self.lableverify_image1) + messagebox.showinfo('signify', 'Подпись документа неверна') + + def upload_cloud(self): + for widget in self.main_frame.winfo_children(): + widget.destroy() + + self.label = ctk.CTkLabel(self.main_frame, corner_radius=0, text='Выберите файл, чтобы загрузить его в облако:', + image=self.lablecloud_image, compound='top', anchor=ctk.CENTER,font=ctk.CTkFont(size=20, weight="bold")) + self.label.grid(row=0, column=1) + self.label.grid_rowconfigure(0, weight=1) + self.label.grid_columnconfigure(1, weight=1) + self.source_file2 = ctk.CTkButton(self.main_frame, corner_radius=5, text = 'Исходный файл', fg_color='#C57A44', + command=lambda: self.pick_file(('All files', '*.*'), 'upload_file')) + self.source_file2.grid(row=1, column=1, sticky = 'nsew', padx=10, pady=5) + + + + def add_item(self, name, t): + + name_ = name[:name.find('')-1] + size = name[name.find('')+6:]+' KB' + if len(name_) > 35: + name_ = name_[0:34]+'...' + name_ = name_ + ', '+size + label_left = ctk.CTkLabel(master=self.scroll_files, text = name_, anchor = 'w') + button_right = ctk.CTkButton(master=self.scroll_files, corner_radius=5, text = 'скачать', image = self.micro_load, fg_color='#C57A44', compound = 'left', + anchor='w', command=lambda: self.dwnload_file(name[:name.find('')-1])) + + label_left.grid(row=t, column=0, sticky='w') + button_right.grid(row=t, column=2, padx = 10, pady=5, sticky = 'nsew') + + def download_cloud(self): + for widget in self.main_frame.winfo_children(): + widget.destroy() + self.scroll_files = ctk.CTkScrollableFrame(master = self.main_frame, width=550, height=700) + + self.scroll_files.grid(row=0, column=1) + self.scroll_files.grid_columnconfigure(1, weight=1) + self.scroll_files.grid_columnconfigure(2, weight=1, minsize = 100) + + #self.scroll_files.grid_columnconfigure(1, weight=1) + #self.scroll_files.grid_rowconfigure(0, weight=1) + #self.scroll_files.grid_rowconfigure(1, weight=1) + + t = 0 + for item in self.list_dwnld_files: + if item != ' ': + self.add_item(item, t) + t+= 1 + + def dwnload_file(self, item): + #print('Pressed button with item: ', item) + ServerMeet(self.client, f'rec_file_cloud {item}') + + + + def pick_file(self, format, name): + filepath = ctk.filedialog.askopenfilename(filetypes=[format]) + if filepath != '': + if name == 'private_key': + self.private_key = useKey(filepath) + #self.client.send('use_key ') + ServerMeet(self.client, f'use_key {(filepath)}') + #self.public_key = self.private_key.verifying_key + if self.private_key != None: + print('Readed key') + elif name == 'public_key': + self.public_key = UsePub(filepath) + ServerMeet(self.client, f'use_key_pub {(filepath)}') + + elif name == 'encode_file_path': + self.encode_file_path = filepath + ServerMeet(self.client, f'send_file {filepath}') + #self.SendFile_() + + elif name == 'signed_file': + self.verify_file_path = filepath + ServerMeet(self.client, f'send_file {filepath}') + + elif name == 'source_file': + self.source_file_path = filepath + ServerMeet(self.client, f'send_file {filepath}') + elif name == 'upload_file': + self.source_file = filepath + size = getsize(filepath) + + self.list_dwnld_files.append(basename(filepath)+' '+'%.2f' % (size/1024) ) + ServerMeet(self.client, f'upload_file {filepath}') + messagebox.showinfo('signify', 'Файл успешно доставлен') + + + print(filepath) + +if __name__ == '__main__': + log = Regist() + log.mainloop() + ServerMeet(log.client, 'exit') + if log.logd == 1: + app = App() + app.mainloop() + ServerMeet(app.client, 'exit') + + + + diff --git a/test/check.png b/test/check.png new file mode 100644 index 0000000000000000000000000000000000000000..840eabf613c2ff526ee2f7d00c14a09a20dd09da GIT binary patch literal 5006 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 literal 0 HcmV?d00001 diff --git a/test/check.png.pem b/test/check.png.pem new file mode 100644 index 0000000..f3cc272 --- /dev/null +++ b/test/check.png.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEedidbizMAQW8hwS7JQhK5AL2gZB +bJWusPfYqKSnenOr22+6rxoXTuTsGwvwslOi2xDF3FIMEyW5aYFPpvketw== +-----END PUBLIC KEY----- diff --git a/test/check.png.sig b/test/check.png.sig new file mode 100644 index 0000000000000000000000000000000000000000..e137d2b46e8547da9be539aa02a7af25684786ef GIT binary patch literal 71 zcmV-N0J#4!MFJrJ{6csMWo_LuykaffrvpFvwT)r)2snu%-Q&Q!Gh%H00w70U3;-y7 ddsX?y{rD4<*4XBV{F6_TkD>5WFYB{4ym)ObBGv!^ literal 0 HcmV?d00001 diff --git a/test/key_test.pem b/test/key_test.pem new file mode 100644 index 0000000..a87dca9 --- /dev/null +++ b/test/key_test.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEBMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgVHMqpBZuX1szBY5U +uf3ghmtfc0HhRWDxSiJFL4fTV1mhRANCAASs8+hZ98UKywfKVVzLSfT48sua1bgx +CZdMQlx+0ThvjIDavfyLDyJVQVkxnnH0C15I56ZYNSyLUMZnZvUS8XLx +-----END PRIVATE KEY----- -- GitLab