Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
3e11fec257 | |||
5d6519c57a |
12
.gitignore
vendored
12
.gitignore
vendored
@ -105,3 +105,15 @@ venv.bak/
|
||||
|
||||
# pycharm
|
||||
.idea/
|
||||
|
||||
# Pipenv.lock
|
||||
Pipfile.lock
|
||||
|
||||
# Dolphin
|
||||
*.directory
|
||||
|
||||
# Private keys
|
||||
*.pem
|
||||
|
||||
# Kate
|
||||
*.kate-swp
|
||||
|
12
Pipfile
12
Pipfile
@ -1,12 +1,12 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
verify_ssl = true
|
||||
url = "https://pypi.org/simple"
|
||||
|
||||
[packages]
|
||||
pycryptodomex = "*"
|
||||
|
||||
[dev-packages]
|
||||
pycrypto = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
python_version = "3.5"
|
||||
|
||||
[dev-packages]
|
||||
|
55
Pipfile.lock
generated
55
Pipfile.lock
generated
@ -1,55 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "ea1158d96215416c8146e8deedecea8da28400d9f80633238e5816576965f13e"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.7"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"pycryptodomex": {
|
||||
"hashes": [
|
||||
"sha256:02c358fa2445821d110857266e4e400f110054694636efe678dc60ba22a1aaef",
|
||||
"sha256:09989c8a1b83e576d02ad77b9b019648648c569febca41f58fa04b9d9fdd1e8f",
|
||||
"sha256:0f8fe28aec591d1b86af596c9fc5f75fc0204fb1026188a44e5e1b199780f1e5",
|
||||
"sha256:0fb58c2065030a5381f3c466aaa7c4de707901badad0d6a0575952bb10e6c35f",
|
||||
"sha256:0fb9f3e6b28a280436afe9192a9957c7f56e20ceecb73f2d7db807368fdf3aaf",
|
||||
"sha256:12ff38a68bbd743407018f9dd87d4cc21f9cb28fe2d8ba93beca004ada9a09ff",
|
||||
"sha256:1650143106383bae79cbbda3701fd9979d0a624dba2ec2fa63f88cae29dd7174",
|
||||
"sha256:20a646cd0e690b07b7da619bc5b3ee1467243b2e32dfff579825c3ad5d7637ab",
|
||||
"sha256:284779f0908682657adb8c60d8484174baa0d2620fb1df49183be6e2e06e73ce",
|
||||
"sha256:2f3ce5bfe81d975c45e4a3cbe2bef15b809acc24f952f5f6aa67c2ae3c1a6808",
|
||||
"sha256:30ac12f0c9ac8332cc76832fea88a547b49ef60c31f74697ee2584f215723d4f",
|
||||
"sha256:4f038b815d66dea0b1d4286515d96474204e137eb5d883229616781865902789",
|
||||
"sha256:57199a867b9991b1950f438b788e818338cee8ed8698e2eebdc5664521ad92a9",
|
||||
"sha256:5c5349385e9863e3bba6804899f4125c8335f66d090e892d6a5bb915f5c89d4c",
|
||||
"sha256:5d546fac597b5007d5ff38c50c9031945057a6a6fa1ab7585058165d370ea202",
|
||||
"sha256:614eddfa0cf325e49b5b803fcb41c9334de79c4b18bf8de07e7737e1efc1d2b9",
|
||||
"sha256:82ae66244824d50b2b657c32e5912fde70a6e36f41e61f2869151f226204430d",
|
||||
"sha256:96a733f3be325fb17c2ba79648e85ab446767af3dc3b396f1404b9397aa28fe5",
|
||||
"sha256:9c3834d27c1cff84e2a5c218e373d80ebbb3edca81912656f16137f7f97e58e0",
|
||||
"sha256:9f11823636128acbe4e17c35ff668f4d0a9f3133450753a0675525b6413aa1b0",
|
||||
"sha256:a3f9ad4e3f90f14707776f13b886fbac491ebe65d96a64f3ce0b378e167c3bbf",
|
||||
"sha256:a89dee72a0f5024cc1cbaf85535eee8d14e891384513145d2f368b5c481dcd54",
|
||||
"sha256:ccadde651e712093052286ad9ee27f5aa5f657ca688a1bf6d5c41ade709467f3",
|
||||
"sha256:ced9ea10977dd52cb1b936a92119fc38fcdc5eaa4148f925ef22bbf0f0d4a5bd",
|
||||
"sha256:eb0c6d3b91d55e3481158ecf77f3963c1725454fdcf5b79302c27c1c9c0d2c2a",
|
||||
"sha256:f6714569a4039287972c672a8bd4b8d7dc78a601def8b31ffa39cd2fec00cb4b",
|
||||
"sha256:fa4036582c8755259d4b8f4fe203ae534b7b187dcea143ab53a24e0f3931d547",
|
||||
"sha256:fb31bb0c8301e5a43d8d7aad22acabef65f28f7ab057eaeb2c21433309cc41e8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.7.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
46
RFC8497.md
46
RFC8497.md
@ -141,7 +141,7 @@ Communication entre un client et un noeud
|
||||
###### En-tête ######
|
||||
|
||||
EICP2P2 V1
|
||||
type: register_client
|
||||
type: register
|
||||
|
||||
###### Contenu ######
|
||||
|
||||
@ -160,11 +160,11 @@ Cette requête est envoyée par un client pour s'enregistrer sur le réseau. Le
|
||||
|
||||
###### Contenu ######
|
||||
|
||||
id_noeud{%=&%&=%}id_client
|
||||
id_noeud::id_client
|
||||
|
||||
###### Utilisation ######
|
||||
|
||||
Réponse à un `register_client`. Le noeud communique au client son id et l'id du noeud auquel il est connecté.
|
||||
Réponse à un `register`. Le noeud communique au client son id et l'id du noeud auquel il est connecté.
|
||||
|
||||
### Send ###
|
||||
|
||||
@ -191,7 +191,7 @@ Cette requête est envoyé par un client enregistré à un noeud pour envoyer un
|
||||
|
||||
###### Contenu ######
|
||||
|
||||
Rien/Erreur client incunnu
|
||||
Rien
|
||||
|
||||
###### Utilisation ######
|
||||
|
||||
@ -217,28 +217,6 @@ Cette requête est envoyée par le destinataire d'une requête `send` après avo
|
||||
|
||||
Envoyé par le client pour se déconnecter du réseau. Ne demande aucune réponse de la part du noeud.
|
||||
|
||||
### Get users ###
|
||||
|
||||
###### En-tête ######
|
||||
|
||||
EICP2P2 V1
|
||||
type: getUsers
|
||||
|
||||
###### Contenu ######
|
||||
|
||||
Rien
|
||||
|
||||
### Get User ACK ###
|
||||
|
||||
###### En-tête ######
|
||||
|
||||
EICP2P2 V1
|
||||
type: getUsersACK
|
||||
|
||||
###### Contenu ######
|
||||
|
||||
Listedesutilisateurs
|
||||
|
||||
### Ping ###
|
||||
|
||||
###### En-tête ######
|
||||
@ -272,15 +250,14 @@ Réponse du client à une requête de ping.
|
||||
Initialisation des connections
|
||||
------------------------------
|
||||
|
||||
### RSASend ###
|
||||
### RSAGet ###
|
||||
|
||||
> En clair
|
||||
|
||||
###### En-tête ######
|
||||
|
||||
EICP2P2 V1
|
||||
type: RSASend
|
||||
from: noeud/client
|
||||
type: RSAget
|
||||
|
||||
###### Contenu ######
|
||||
|
||||
@ -293,12 +270,13 @@ Cette requête est utilisé pour initialiser la communication crypté entre deux
|
||||
|
||||
### Init ###
|
||||
|
||||
> Chiffré avec la clef RSA publique recue avec la requete RSASend
|
||||
> Chiffré avec la clef RSA publique recue avec la requete RSAGet
|
||||
|
||||
###### En-tête ######
|
||||
|
||||
EICP2P2 V1
|
||||
type: init
|
||||
from: noeud/client
|
||||
|
||||
###### Contenu ######
|
||||
|
||||
@ -306,13 +284,7 @@ Cette requête est utilisé pour initialiser la communication crypté entre deux
|
||||
|
||||
###### Utilisation ######
|
||||
|
||||
Requête envoyée en réponse à RSASend pour initialiser la connection cryptée en AES.
|
||||
|
||||
|
||||
|
||||
### Error ###
|
||||
|
||||
Error
|
||||
Requête envoyée en réponse à RSAGet pour initialiser la connection cryptée en AES.
|
||||
|
||||
Tables
|
||||
------
|
||||
|
@ -36,6 +36,7 @@
|
||||
"server": {
|
||||
"level": "DEBUG",
|
||||
"handlers": [
|
||||
"console",
|
||||
"info_file_handler",
|
||||
"error_file_handler"
|
||||
]
|
||||
|
302
client/main.py
Normal file → Executable file
302
client/main.py
Normal file → Executable file
@ -6,51 +6,30 @@ import logging
|
||||
import logging.config
|
||||
import os
|
||||
import socket
|
||||
|
||||
|
||||
#### logging ####
|
||||
# json decoder for int keys
|
||||
import threading
|
||||
|
||||
|
||||
class Decoder(json.JSONDecoder):
|
||||
def decode(self, s, **kwargs):
|
||||
result = super().decode(s) # result = super(Decoder, self).decode(s) for Python 2.x
|
||||
return self._decode(result)
|
||||
|
||||
def _decode(self, o):
|
||||
if isinstance(o, str):
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from Crypto.PublicKey import RSA as RSA
|
||||
# noinspection PyUnresolvedReferences
|
||||
from Crypto.Cipher import PKCS1_OAEP as PKCS1_OAEP
|
||||
|
||||
pycryptodome = False
|
||||
except ModuleNotFoundError: # Pycryptodomex
|
||||
from Cryptodome.PublicKey import RSA as RSA
|
||||
from Cryptodome.Cipher import PKCS1_OAEP as PKCS1_OAEP
|
||||
from Cryptodome.Cipher import AES as AES
|
||||
|
||||
pycryptodome = True
|
||||
|
||||
|
||||
class RsaGenThread(threading.Thread):
|
||||
|
||||
def __init__(self, client_, difficulty=2):
|
||||
threading.Thread.__init__(self)
|
||||
self.client = client_
|
||||
self.difficulty = difficulty
|
||||
|
||||
def run(self):
|
||||
if os.path.isfile("private.pem"):
|
||||
try:
|
||||
with open("private.pem", "rb") as keyfile:
|
||||
rsa = RSA.importKey(keyfile.read())
|
||||
if not rsa.has_private():
|
||||
warning("Le fichier clef ne contient pas de clef privée")
|
||||
raise ValueError
|
||||
self.client.rsa = rsa
|
||||
except (IndexError, ValueError):
|
||||
warning("Fichier clef corrompu")
|
||||
debug("Suppression du fichier clef corromu")
|
||||
os.remove("private.pem")
|
||||
if not os.path.isfile("private.pem"): # We're not using if/else because we may delete the file in the
|
||||
# previous if statement
|
||||
debug("Génération de la clef RSA pour %s" % self.client.name)
|
||||
self.client.rsa = RSA.generate(BUFFER_SIZE + 256 * self.difficulty)
|
||||
with open("private.pem", "wb") as keyfile:
|
||||
keyfile.write(self.client.rsa.exportKey())
|
||||
with open("public.pem", "wb") as keyfile:
|
||||
keyfile.write(self.client.rsa.publickey().exportKey())
|
||||
return int(o)
|
||||
except ValueError:
|
||||
return o
|
||||
elif isinstance(o, dict):
|
||||
return {k: self._decode(v) for k, v in o.items()}
|
||||
elif isinstance(o, list):
|
||||
return [self._decode(v) for v in o]
|
||||
else:
|
||||
return o
|
||||
|
||||
|
||||
def setup_logging(default_path='log_config.json', default_level=logging.INFO, env_key='LOG_CFG'):
|
||||
@ -81,238 +60,25 @@ critical = log_server.critical
|
||||
#### Variables ####
|
||||
HOST = '127.0.0.1'
|
||||
PORT = 8888
|
||||
BUFFER_SIZE = 4096
|
||||
CHUNK_SIZE = int(BUFFER_SIZE / 8)
|
||||
BEGIN_MESSAGE = bytes("debut".ljust(BUFFER_SIZE, ";"), "ascii")
|
||||
END_MESSAGE = bytes("fin".ljust(BUFFER_SIZE, ";"), "ascii")
|
||||
NAME = b"client1"
|
||||
VERSION = b"EICP2P2 V1"
|
||||
|
||||
REQUEST_TYPE = [
|
||||
b'ping', b'pingACK', b'updateAsk', b'updateBack', b'transfer', b'register_client', b'registerACK', b'send',
|
||||
b'sendACK',
|
||||
b'exit', b'RSASend', b'init', b'getUsers', b'getUsersACK',
|
||||
]
|
||||
BUFFER_SIZE = 16384
|
||||
|
||||
clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
clientSocket.connect((HOST, PORT))
|
||||
|
||||
|
||||
# ET ICI ON MET LE CLIENT
|
||||
|
||||
class MainThread(threading.Thread):
|
||||
"""Main client class, for each identity."""
|
||||
|
||||
def __init__(self, name):
|
||||
threading.Thread.__init__(self)
|
||||
self.name = name
|
||||
self.rsa = None
|
||||
self.aes_key = None
|
||||
self.clientSocket = None
|
||||
self.users = {
|
||||
# id: rsa
|
||||
}
|
||||
self.connected_users = {
|
||||
# id: (rsa, aes, nick)
|
||||
}
|
||||
|
||||
def run(self):
|
||||
rsa = RsaGenThread(self)
|
||||
rsa.start()
|
||||
rsa.join()
|
||||
self.clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.clientSocket.connect((HOST, PORT))
|
||||
self.initialisation()
|
||||
self.register()
|
||||
self.get_users()
|
||||
print(self.users)
|
||||
while True:
|
||||
try:
|
||||
type = bytes(input("Type: "), "ascii")
|
||||
contenu = bytes(input("Contenu: "), "ascii")
|
||||
self.send_aes(self.gen_header(type_=type) + contenu)
|
||||
print(self.receive_aes())
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
def register(self):
|
||||
self.send_aes(self.gen_header(type_=b"register_client"))
|
||||
self.id_noeud, self.id_client = self.receive_aes()[BUFFER_SIZE:].split(b"{%=&%&=%}")
|
||||
|
||||
def initialisation(self):
|
||||
header = self.gen_header(b"RSASend", from_=b"client")
|
||||
content = self.rsa.publickey().exportKey()
|
||||
self.send(header + content)
|
||||
print("oki1")
|
||||
data = self.receive_rsa()
|
||||
print("oki2")
|
||||
header = self.extract_header(data[:BUFFER_SIZE])
|
||||
if header[b"type"] != b"init":
|
||||
debug("Incorrect request type, end of connection")
|
||||
return
|
||||
self.aes_key = data[BUFFER_SIZE:]
|
||||
debug("End of initialisation")
|
||||
|
||||
@staticmethod
|
||||
def extract_header(data):
|
||||
"""Extract header from data
|
||||
|
||||
:param data: Data to extract header
|
||||
:type data: bytes
|
||||
|
||||
:return: Dictionary with header datas
|
||||
:rtype: dict{bytes: bytes}"""
|
||||
if len(data) > BUFFER_SIZE:
|
||||
debug("Header too long")
|
||||
data = data[:BUFFER_SIZE]
|
||||
data_lines = data.split(b'\n')
|
||||
if data_lines[0] != VERSION:
|
||||
raise ValueError("Version is incorrect.")
|
||||
return {
|
||||
l.split(b": ")[0]: l.split(b": ")[1].rstrip(b";") for l in data_lines[1:]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def gen_header(type_, to_=None, from_=None):
|
||||
"""Generate header
|
||||
|
||||
:param type_: Request type
|
||||
:param to_: `to` field in header, cf ../RFC8497.md
|
||||
:param from_: `from` field in header, cf ../RFC8497.md
|
||||
:type type_: bytes
|
||||
:type to_: bytes
|
||||
:type from_: bytes
|
||||
|
||||
:return: header
|
||||
:rtype: bytes"""
|
||||
if type_ not in REQUEST_TYPE:
|
||||
raise ValueError("Unknown request type")
|
||||
header = VERSION + b"\ntype: " + type_
|
||||
if to_:
|
||||
header += b"\nto: " + to_
|
||||
if from_:
|
||||
header += b"\nfrom: " + from_
|
||||
return header.ljust(BUFFER_SIZE, b';')
|
||||
|
||||
def get_users(self):
|
||||
self.send_aes(self.gen_header(type_=b'getUsers'))
|
||||
back = self.receive_aes()
|
||||
print(back)
|
||||
header = self.extract_header(back)
|
||||
contenu = back[BUFFER_SIZE:]
|
||||
if header[b"type"] == b"getUsersACK":
|
||||
self.users = {i: contenu.split(b"%!!%")[i] for i in range(len(contenu.split(b"%!!%")))}
|
||||
|
||||
################################################ COMMUNICATION WITH AES ############################################
|
||||
|
||||
def send_aes(self, to_send, key=None):
|
||||
"""Send message with aes encryption
|
||||
|
||||
:param to_send: Message to send
|
||||
:type to_send: bytes
|
||||
:param key: key to replace self.aes_key
|
||||
:type key: bytes
|
||||
|
||||
:rtype: NoneType
|
||||
:return: Nothing"""
|
||||
debug(b"Send with AES encryption: " + to_send)
|
||||
if key is None:
|
||||
key = self.aes_key
|
||||
if key is None:
|
||||
info("AES key not generated, connection failure.")
|
||||
self.client.send(b"Error")
|
||||
return
|
||||
# Get RSA key
|
||||
aes_object = AES.new(key, AES.MODE_ECB)
|
||||
encrypted = b""
|
||||
for to_send_text in [to_send[i:i + 32] for i in range(0, len(to_send), 32)]:
|
||||
encrypted += aes_object.encrypt(to_send_text.ljust(32, b"\x00"))
|
||||
self.send(encrypted)
|
||||
return None
|
||||
|
||||
def receive_aes(self, key=None):
|
||||
"""Receive message with aes encryption
|
||||
|
||||
:param key: key to replace self.aes_key
|
||||
:type key: bytes
|
||||
"""
|
||||
to_decrypt = self.receive()
|
||||
debug(b"Received in aes " + to_decrypt)
|
||||
if key is None:
|
||||
key = self.aes_key
|
||||
if key is None:
|
||||
info("AES key not generated, connection failure.")
|
||||
self.client.send(b"Error")
|
||||
return
|
||||
aes_object = AES.new(key, AES.MODE_ECB)
|
||||
decrypted = b""
|
||||
for block in [to_decrypt[i:i + 32] for i in range(0, len(to_decrypt), 32)]:
|
||||
print(len(block))
|
||||
decrypted += aes_object.decrypt(block)
|
||||
return decrypted.rstrip(b"\x00")
|
||||
|
||||
################################################ COMMUNICATION WITH RSA ############################################
|
||||
|
||||
def receive_rsa(self, rsa_key=None):
|
||||
if rsa_key is None:
|
||||
rsa_key = self.rsa.exportKey()
|
||||
if rsa_key is None:
|
||||
info("RSA key not generated, connection failure.")
|
||||
return
|
||||
recipient_key = RSA.importKey(rsa_key)
|
||||
cipher_rsa = PKCS1_OAEP.new(recipient_key)
|
||||
raw = self.receive()
|
||||
content = b""
|
||||
for to_decrypt in [raw[i:i + self.rsa.publickey().size_in_bytes()] for i in
|
||||
range(0, len(raw), self.rsa.publickey().size_in_bytes())]:
|
||||
content += cipher_rsa.decrypt(to_decrypt)
|
||||
return content
|
||||
|
||||
############################################ COMMUNICATION WITHOUT CRYPTING ############################################
|
||||
|
||||
def receive(self):
|
||||
"""Receive message from connection
|
||||
|
||||
:rtype: bytes
|
||||
:return: Message's content"""
|
||||
chunk = bytes("", "ascii") # Temp variable to store received datas
|
||||
while chunk != BEGIN_MESSAGE:
|
||||
chunk = self.clientSocket.recv(BUFFER_SIZE)
|
||||
content = b''
|
||||
while chunk != END_MESSAGE:
|
||||
chunk = self.clientSocket.recv(BUFFER_SIZE)
|
||||
# Get only interesting chucks
|
||||
if chunk != END_MESSAGE:
|
||||
# Get content part
|
||||
# int.from_bytes(chunk[:2], byteorder='big') == Get content size
|
||||
content += chunk[2:int.from_bytes(chunk[:2], byteorder='big') + 2]
|
||||
debug(b"Received: " + content)
|
||||
return content
|
||||
|
||||
def send(self, to_send):
|
||||
"""Send message to connection
|
||||
|
||||
:param to_send: message to send
|
||||
:type to_send: bytes
|
||||
|
||||
:return: Nothing
|
||||
:rtype: NoneType"""
|
||||
debug(b"Send " + to_send)
|
||||
# Sending the message start
|
||||
self.clientSocket.send(BEGIN_MESSAGE)
|
||||
i = 0
|
||||
for to_send_text in [to_send[i:i + BUFFER_SIZE - 2] for i in range(0, len(to_send), BUFFER_SIZE - 2)]:
|
||||
self.clientSocket.send(
|
||||
(len(to_send_text)).to_bytes(2, byteorder='big') # Size of the message contained by the chunk
|
||||
+ to_send_text.ljust(BUFFER_SIZE - 2, bytes(1)) # Content of the chunk
|
||||
)
|
||||
i += 1
|
||||
# Sending the message stop
|
||||
self.clientSocket.send(END_MESSAGE)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
client = MainThread(NAME)
|
||||
client.start()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,57 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
@ -1,15 +0,0 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIICYjANBgkqhkiG9w0BAQEFAAOCAk8AMIICSgKCAkEAyCjkX4LpaXrvh8oeVq6r
|
||||
dNLNpVBUiJxSX8CJizqB6Z/m8/4ZR69V0H/MzVjngnczA9Z2ITsIJ3ZLpGxL5TNC
|
||||
4Kv5q3LKsvhuSWMTq97q2bKaeCBrROOurG5FxWrTqLHbs5U47kJEDKH6Orn42l6t
|
||||
jvl/74qWTRarpZ4lbNfIkbM45rp10OhZNnjCsuxlcWEhGn5bp6IrSsQ0ZIYfwNLw
|
||||
sEDjkn/fCz9NY0ZouT8ua/RnEkW7s6KOAdoaQi6LSADq9BXK+c4Zdfb5E7ScBzHM
|
||||
jl10AqyNSHZ3DnFL5AEdzC4ZbmXLSbAKuglFnplKoXRZm6rUb2rWU3NDQ9LVIoUp
|
||||
OfHUD+ip6V9nuQHIS7KVHJ0hgu9s3lRwN5YwnNz5OqcxWB2eLw8PLaH0WNRTmfbl
|
||||
9EEf2huhUpgXv/DI+7J0dfnSpxaGO0YAZnjm0x+E/ryXq3vpGb3uErgJ8iuro6MF
|
||||
LE7u9iiAr8HQisGiVVIPgvmns3fqHJq1tJ1p0GSjHN+Of2QQPx7TSpt/5Ew3Y/pZ
|
||||
/ho8vazIBe036v6NyENywrW+UWYWdB6m9boEcg6GElSA0kVMw37syuRw7O6FhiOw
|
||||
JZNjAMA67siio1vJfHXrzLhFmlyUzE07raMFpG4aKw2zwo4PKLSxIiU66i9/ECbJ
|
||||
+3yN7p+XXljCkni7KO9JSI64hETw7BiY+S/+ZqaipnB3hWxQFsZn36RfeU/BIfGz
|
||||
kQ4+NdYZPgR4h64tcVv9BjZ8abqBb5id1wFRpKVDeDpDAgMBAAE=
|
||||
-----END PUBLIC KEY-----
|
51
private.pem
51
private.pem
@ -1,51 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
14
receiver.pem
14
receiver.pem
@ -1,14 +0,0 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnyiVFNxPTuctHL12pPcB
|
||||
zumJnSiA7CZtbUbgozPjuVuJJQcf8FPZEQWFphtUujZVMRi6PX3szkKhOb08pl75
|
||||
yzoA9H1ql5UoutuGExc0PVzaTY5BtnU38q6XvyWTYz5TNaG2bhd9DuNv9JrMn+/y
|
||||
dClxOGvTd4Y+7VI8RG8HHnwMrwRcLzQ2FSJD9mVSopFn+5Z0j0idog2O/SsZJM3c
|
||||
8ieLoOHO9vxQO8xVaUIF74nVoNiLz/YrcAhU9N3y2KcjpAsQBbFznKeQ6AcW5WjC
|
||||
9zZBVLElP2PAVd6EFKY9C6nuodsaICqgWZnyCh+tEI8i+IQq2RN1YySv+6Cn929w
|
||||
VM2tDPxaYfziSLkW5Pk9oPJ43xHsjb8yFUKsoChPmo4Qe4AVkIIbxwVWG0wymWqE
|
||||
uWeiKlc32badxf38dAm9TvgAApZ05cSIV6ieYP+ixjMFa2rrjkPtfcXey6O05D8u
|
||||
TMQx9GPZRNezceRWI89Lip83raWqjhw+hUvbGaUL0B/z7XD1zskmQ9n+RnhYRVZr
|
||||
YOTw2BWoGoMBC13/g7qG2RR+qrJFs1PLTbUgrzP5HkVpKzBvi2G3BxXGr0aflOtL
|
||||
rOU275LjdkkJrOiNMdPrGsUe9S9XPK5irHampxnJOv5oOCQZ0m/bzKXJykZAJF9f
|
||||
X8caOE++kTH0DSqsapE0fC8CAwEAAQ==
|
||||
-----END PUBLIC KEY-----
|
@ -45,6 +45,7 @@
|
||||
"root": {
|
||||
"level": "INFO",
|
||||
"handlers": [
|
||||
"console",
|
||||
"info_file_handler",
|
||||
"error_file_handler"
|
||||
]
|
||||
|
409
server/main.py
Normal file → Executable file
409
server/main.py
Normal file → Executable file
@ -6,25 +6,30 @@ import logging
|
||||
import logging.config
|
||||
import os
|
||||
import socket
|
||||
|
||||
|
||||
#### logging ####
|
||||
# json decoder for int keys
|
||||
import threading
|
||||
|
||||
|
||||
class Decoder(json.JSONDecoder):
|
||||
def decode(self, s, **kwargs):
|
||||
result = super().decode(s) # result = super(Decoder, self).decode(s) for Python 2.x
|
||||
return self._decode(result)
|
||||
|
||||
def _decode(self, o):
|
||||
if isinstance(o, str):
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from Crypto.PublicKey import RSA as RSA
|
||||
# noinspection PyUnresolvedReferences
|
||||
from Crypto.Cipher import PKCS1_OAEP as PKCS1_OAEP
|
||||
from Crypto.Cipher import AES as AES
|
||||
# noinspection PyUnresolvedReferences,PyProtectedMember
|
||||
from Crypto.Random._UserFriendlyRNG import get_random_bytes as get_random_bytes
|
||||
|
||||
pycryptodome = False
|
||||
except ModuleNotFoundError: # Pycryptodomex
|
||||
from Cryptodome.PublicKey import RSA as RSA
|
||||
from Cryptodome.Cipher import PKCS1_OAEP as PKCS1_OAEP
|
||||
from Cryptodome.Cipher import AES as AES
|
||||
from Cryptodome.Random import get_random_bytes as get_random_bytes
|
||||
|
||||
pycryptodome = True
|
||||
return int(o)
|
||||
except ValueError:
|
||||
return o
|
||||
elif isinstance(o, dict):
|
||||
return {k: self._decode(v) for k, v in o.items()}
|
||||
elif isinstance(o, list):
|
||||
return [self._decode(v) for v in o]
|
||||
else:
|
||||
return o
|
||||
|
||||
|
||||
def setup_logging(default_path='log_config.json', default_level=logging.INFO, env_key='LOG_CFG'):
|
||||
@ -55,393 +60,49 @@ critical = log_server.critical
|
||||
#### Variables ####
|
||||
HOST = ''
|
||||
PORT = 8888
|
||||
BUFFER_SIZE = 4096
|
||||
CHUNK_SIZE = int(BUFFER_SIZE / 8)
|
||||
BEGIN_MESSAGE = bytes("debut".ljust(BUFFER_SIZE, ";"), "ascii")
|
||||
END_MESSAGE = bytes("fin".ljust(BUFFER_SIZE, ";"), "ascii")
|
||||
|
||||
VERSION = b"EICP2P2 V1"
|
||||
|
||||
REQUEST_TYPE = [
|
||||
b'ping', b'pingACK', b'updateAsk', b'updateBack', b'transfer', b'register_client', b'registerACK', b'send', b'sendACK',
|
||||
b'exit', b'RSASend', b'init', b'getUsers', b'getUsersACK',
|
||||
]
|
||||
|
||||
DONE = 0
|
||||
ERROR = 1
|
||||
|
||||
T_NONE = 0b0000000000
|
||||
T_NODE = 0b0000000001
|
||||
T_CLIENT = 0b00000010
|
||||
BUFFER_SIZE = 16384
|
||||
|
||||
#### Socket ####
|
||||
main_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
main_socket.bind((HOST, PORT))
|
||||
|
||||
|
||||
#### Threads ####
|
||||
|
||||
class RsaGenThread(threading.Thread):
|
||||
|
||||
def __init__(self, difficulty=2):
|
||||
threading.Thread.__init__(self)
|
||||
self.difficulty = difficulty
|
||||
|
||||
def run(self):
|
||||
rsa = None
|
||||
if os.path.isfile("private.pem"):
|
||||
try:
|
||||
with open("private.pem", "rb") as keyfile:
|
||||
rsa = RSA.importKey(keyfile.read())
|
||||
if not rsa.has_private():
|
||||
warning("Le fichier clef ne contient pas de clef privée")
|
||||
raise ValueError
|
||||
except (IndexError, ValueError):
|
||||
warning("Fichier clef corrompu")
|
||||
debug("Suppression du fichier clef corromu")
|
||||
os.remove("private.pem")
|
||||
if not os.path.isfile("private.pem"): # We're not using if/else because we may delete the file in the
|
||||
# previous if statement
|
||||
rsa = RSA.generate(BUFFER_SIZE + 256 * self.difficulty)
|
||||
with open("private.pem", "wb") as keyfile:
|
||||
keyfile.write(rsa.exportKey())
|
||||
with open("public.pem", "wb") as keyfile:
|
||||
keyfile.write(rsa.publickey().exportKey())
|
||||
return rsa
|
||||
|
||||
|
||||
class ServerThread(threading.Thread):
|
||||
"""Main tread for server"""
|
||||
|
||||
def __init__(self, socket, ip, port):
|
||||
threading.Thread.__init__(self) # initialisation du thread
|
||||
self.socket = socket
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
t = RsaGenThread()
|
||||
self.rsa_key = None
|
||||
if os.path.isfile("private.pem"):
|
||||
try:
|
||||
with open("private.pem", "rb") as keyfile:
|
||||
self.rsa_key = RSA.importKey(keyfile.read())
|
||||
if not self.rsa_key.has_private():
|
||||
warning("Le fichier clef ne contient pas de clef privée")
|
||||
raise ValueError
|
||||
except (IndexError, ValueError):
|
||||
warning("Fichier clef corrompu")
|
||||
debug("Suppression du fichier clef corromu")
|
||||
os.remove("private.pem")
|
||||
if not os.path.isfile("private.pem"): # We're not using if/else because we may delete the file in the
|
||||
# previous if statement
|
||||
debug("Generate new rsa key")
|
||||
self.rsa_key = RSA.generate(BUFFER_SIZE + 256 * self.difficulty)
|
||||
with open("private.pem", "wb") as keyfile:
|
||||
keyfile.write(self.rsa_key.exportKey())
|
||||
with open("public.pem", "wb") as keyfile:
|
||||
keyfile.write(self.rsa_key.publickey().exportKey())
|
||||
debug("RSA key loaded")
|
||||
self.clients = {}
|
||||
self.nodes = {
|
||||
self.rsa_key.publickey().exportKey(): None,
|
||||
}
|
||||
|
||||
def register_client(self, rsa_client, client_thread):
|
||||
self.clients.update({rsa_client: (self.rsa_key, client_thread)})
|
||||
return self.rsa_key.publickey().exportKey(), rsa_client
|
||||
|
||||
def send_to(self, id_dest, to_send):
|
||||
if id_dest not in self.clients.keys():
|
||||
return b"Erreur client inconnu"
|
||||
if self.clients[id_dest][0] is None:
|
||||
return self.clients[id_dest].send_to_me(to_send)
|
||||
else:
|
||||
return self.nodes[self.clients[id_dest]].transfer(id_dest, to_send)
|
||||
|
||||
|
||||
class ClientThread(threading.Thread):
|
||||
class clientThread(threading.Thread):
|
||||
"""Main thread, for each client"""
|
||||
|
||||
def __init__(self, clientsocket, ip_client, port, server):
|
||||
"""Create ClientThread object
|
||||
def __init__(self, clientsocket, ip, port):
|
||||
"""Create clientThread object
|
||||
|
||||
:param clientsocket: Client's socket
|
||||
:param ip_client: Client's ip address
|
||||
:param clentsocket: Client's socket
|
||||
:param ip: Client's ip address
|
||||
:param port: Client's connection PORT
|
||||
:param server: Server thread
|
||||
:type clientsocket: socket.socket
|
||||
:type ip_client: str
|
||||
:type ip: str
|
||||
:type port: int
|
||||
:type server: ServerThread
|
||||
|
||||
:return: Nothing
|
||||
:rtype: NoneType"""
|
||||
debug("Creation du thread pour %s" % ip_client)
|
||||
debug("Creation du thread pour %s" % ip)
|
||||
threading.Thread.__init__(self) # initialisation du thread
|
||||
self.client = clientsocket
|
||||
self.ip = ip_client
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.running = True
|
||||
self.status = None
|
||||
self.rsa_client = None
|
||||
self.aes_key = get_random_bytes(32)
|
||||
self.type = T_NONE
|
||||
self.server = server
|
||||
debug("Creation du thread pour %s reussie" % ip_client)
|
||||
|
||||
def initialize(self):
|
||||
"""Initialize connection with client
|
||||
|
||||
:rtype: NoneType
|
||||
:return: Nothing"""
|
||||
# Receive message
|
||||
message = self.receive()
|
||||
header = self.extract_header(message)
|
||||
content = message[BUFFER_SIZE:]
|
||||
if header.get(b"type", None) is None:
|
||||
debug("The type field is not in the header")
|
||||
self.send(b"Error")
|
||||
return
|
||||
if header.get(b"from", None) is None:
|
||||
debug("The from field is not in the header")
|
||||
self.send(b"error")
|
||||
return
|
||||
if self.status is None and header[b"type"] != b"RSASend":
|
||||
debug("Requête différente de RSASend avec une connection non initialisée")
|
||||
self.send(b"Error")
|
||||
return
|
||||
if header[b"type"] == b"RSASend":
|
||||
self.type = T_CLIENT if header[b"from"] == b"client" else T_NODE
|
||||
debug("Réception de la clef RSA de %s", self.ip)
|
||||
self.rsa_client = content
|
||||
header = self.gen_header(b"init")
|
||||
content = self.aes_key
|
||||
self.send_rsa(header + content)
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def extract_header(data):
|
||||
"""Extract header from data
|
||||
|
||||
:param data: Data to extract header
|
||||
:type data: bytes
|
||||
|
||||
:return: Dictionary with header datas
|
||||
:rtype: dict{bytes: bytes}"""
|
||||
if len(data) > BUFFER_SIZE:
|
||||
debug("Header too long")
|
||||
data = data[:BUFFER_SIZE]
|
||||
data_lines = data.split(b'\n')
|
||||
if data_lines[0] != VERSION:
|
||||
raise ValueError("Version is incorrect.")
|
||||
return {
|
||||
l.split(b": ")[0]: l.split(b": ")[1].rstrip(b";") for l in data_lines[1:]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def gen_header(type_, to_=None, from_=None):
|
||||
"""Generate header
|
||||
|
||||
:param type_: Request type
|
||||
:param to_: `to` field in header, cf ../RFC8497.md
|
||||
:param from_: `from` field in header, cf ../RFC8497.md
|
||||
:type type_: bytes
|
||||
:type to_: bytes
|
||||
:type from_: bytes
|
||||
|
||||
:raise ValueError: `type_` is not a valid request type
|
||||
|
||||
:return: header
|
||||
:rtype: bytes"""
|
||||
if type_ not in REQUEST_TYPE:
|
||||
raise ValueError("Unknown request type")
|
||||
header = VERSION + b"\ntype: " + type_
|
||||
if to_:
|
||||
header += b"\nto: " + to_
|
||||
if from_:
|
||||
header += b"\nfrom: " + from_,
|
||||
return header.ljust(BUFFER_SIZE, b';')
|
||||
|
||||
################################################ COMMUNICATION WITH AES ############################################
|
||||
|
||||
def send_aes(self, to_send, key=None):
|
||||
"""Send message with aes encryption
|
||||
|
||||
:param to_send: Message to send
|
||||
:type to_send: bytes
|
||||
:param key: key to replace self.aes_key
|
||||
:type key: bytes
|
||||
|
||||
:rtype: NoneType
|
||||
:return: Nothing"""
|
||||
debug(b"Send with AES encryption: " + to_send + bytes(str(self.ip), "ascii"))
|
||||
if key is None:
|
||||
key = self.aes_key
|
||||
if key is None:
|
||||
info("AES key not generated, connection failure.")
|
||||
self.client.send(b"Error")
|
||||
return
|
||||
# Get RSA key
|
||||
aes_object = AES.new(key, AES.MODE_ECB)
|
||||
encrypted = b""
|
||||
for to_send_text in [to_send[i:i + 32] for i in range(0, len(to_send), 32)]:
|
||||
encrypted += aes_object.encrypt(to_send_text.ljust(32, b"\x00"))
|
||||
self.send(encrypted)
|
||||
return None
|
||||
|
||||
def receive_aes(self, key=None):
|
||||
"""Receive message with aes encryption
|
||||
|
||||
:param key: key to replace self.aes_key
|
||||
:type key: bytes
|
||||
"""
|
||||
to_decrypt = self.receive()
|
||||
if key is None:
|
||||
key = self.aes_key
|
||||
if key is None:
|
||||
info("AES key not generated, connection failure.")
|
||||
self.client.send(b"Error")
|
||||
return
|
||||
aes_object = AES.new(key, AES.MODE_ECB)
|
||||
decrypted = b""
|
||||
for block in [to_decrypt[i:i + 32] for i in range(0, len(to_decrypt), 32)]:
|
||||
decrypted += aes_object.decrypt(block)
|
||||
return decrypted.rstrip(b"\x00")
|
||||
|
||||
################################################ COMMUNICATION WITH RSA ############################################
|
||||
|
||||
def send_rsa(self, to_send, key=None):
|
||||
"""Send message with rsa encryption
|
||||
|
||||
:param to_send: Message to send
|
||||
:type to_send: bytes
|
||||
:param key: key to replace self.client_key
|
||||
:type key: bytes
|
||||
|
||||
:rtype: NoneType
|
||||
:return: Nothing"""
|
||||
debug(b"Send with RSA encryption: " + to_send + bytes(str(self.ip), "ascii"))
|
||||
if key is None:
|
||||
key = self.rsa_client
|
||||
if key is None:
|
||||
info("RSA key not received, connection failure.")
|
||||
self.client.send(b"Error")
|
||||
return
|
||||
# Get RSA key
|
||||
recipient_key = RSA.importKey(key)
|
||||
# RSA encryption object
|
||||
cipher_rsa = PKCS1_OAEP.new(recipient_key)
|
||||
encrypted = b""
|
||||
for to_send_text in [to_send[i:i + CHUNK_SIZE] for i in range(0, len(to_send), CHUNK_SIZE)]:
|
||||
encrypted += cipher_rsa.encrypt(to_send_text)
|
||||
self.send(encrypted)
|
||||
return None
|
||||
|
||||
############################################ COMMUNICATION WITHOUT CRYPTING ########################################
|
||||
|
||||
def receive(self):
|
||||
"""Receive message from connection
|
||||
|
||||
:rtype: bytes
|
||||
:return: Message's content"""
|
||||
chunk = bytes("", "ascii") # Temp variable to store received datas
|
||||
while chunk != BEGIN_MESSAGE:
|
||||
chunk = self.client.recv(BUFFER_SIZE)
|
||||
content = b''
|
||||
while chunk != END_MESSAGE:
|
||||
chunk = self.client.recv(BUFFER_SIZE)
|
||||
# Get only interesting chucks
|
||||
if chunk != END_MESSAGE:
|
||||
# Get content part
|
||||
# int.from_bytes(chunk[:2], byteorder='big') == Get content size
|
||||
content += chunk[2:int.from_bytes(chunk[:2], byteorder='big') + 2]
|
||||
debug(b"Received from" + bytes(str(self.ip), 'ascii') + b" : " + content)
|
||||
return content
|
||||
|
||||
def send(self, to_send):
|
||||
"""Send message to connection
|
||||
|
||||
:param to_send: message to send
|
||||
:type to_send: bytes
|
||||
|
||||
:return: Nothing
|
||||
:rtype: NoneType"""
|
||||
debug(b"Send " + to_send + b" to " + bytes(str(self.ip), "ascii"))
|
||||
# Sending the message start
|
||||
self.client.send(BEGIN_MESSAGE)
|
||||
i = 0
|
||||
for to_send_text in [to_send[i:i + BUFFER_SIZE - 2] for i in range(0, len(to_send), BUFFER_SIZE - 2)]:
|
||||
self.client.send(
|
||||
(len(to_send_text)).to_bytes(2, byteorder='big') # Size of the message contained by the chunk
|
||||
+ to_send_text.ljust(BUFFER_SIZE - 2, bytes(1)) # Content of the chunk
|
||||
)
|
||||
i += 1
|
||||
# Sending the message stop
|
||||
self.client.send(END_MESSAGE)
|
||||
return None
|
||||
|
||||
def send_users(self):
|
||||
self.send_aes(self.gen_header(type_=b"getUsersACK")+b"%!!%".join(list(self.server.clients.keys())))
|
||||
|
||||
def register_client(self):
|
||||
"""Register client
|
||||
|
||||
:rtype: NoneType
|
||||
:return: Nothing"""
|
||||
self.server.register_client(self.rsa_client, self)
|
||||
id_noeud, id_client = self.server.rsa_key.publickey().exportKey(), self.rsa_client
|
||||
self.send_aes(self.gen_header(type_=b"registerACK") + id_noeud + b"{%=&%&=%}" + id_client)
|
||||
|
||||
def send_to_me(self, to_send):
|
||||
"""Receive message from other poeple
|
||||
|
||||
:param to_send: Message to send to client
|
||||
:type to_send: bytes
|
||||
|
||||
:rtype: NoneType
|
||||
:return: Nothing"""
|
||||
self.send(self.gen_header(type_=b"send", to_=self.rsa_client) + to_send)
|
||||
return
|
||||
|
||||
def send_to_other(self, id_dest, to_send):
|
||||
"""Send message to other client
|
||||
|
||||
:param id_dest: id of receiver
|
||||
:param to_send: Message to send
|
||||
|
||||
:return: Nothing
|
||||
:rtype: NoneType"""
|
||||
server_response = self.server.send_to(id_dest, to_send)
|
||||
self.send(self.gen_header(type_=b"sendACK"), server_response)
|
||||
return
|
||||
debug("Creation du thread pour %s reussie" % ip)
|
||||
|
||||
def run(self): # main de la connection du client
|
||||
"""Run thread mainloop
|
||||
|
||||
:return: Nothing
|
||||
:rtype: NoneType"""
|
||||
info(self.ip + "connected, initialize connection...")
|
||||
self.initialize()
|
||||
info(self.ip + "connection initialized.")
|
||||
while self.running:
|
||||
data = self.receive_aes()
|
||||
header = self.extract_header(data)
|
||||
print(header)
|
||||
print(data)
|
||||
print(self.rsa_client)
|
||||
if header[b"type"] == b"register_client":
|
||||
self.register_client()
|
||||
elif header[b"type"] == b"getUsers":
|
||||
self.send_users()
|
||||
elif
|
||||
### ICI ON MET LA BOUCLE PRINCIPALE DE CONNECTION
|
||||
self.client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
clients = []
|
||||
server = ServerThread(main_socket, ip=HOST, port=PORT)
|
||||
while True:
|
||||
main_socket.listen(1) # Waiting for incoming connections
|
||||
client_socket, (ip, PORT) = main_socket.accept()
|
||||
newClient = ClientThread(client_socket, ip, PORT, server)
|
||||
main_socket.listen(1) # ecoutes des connections entrantes
|
||||
clientsocket, (ip, PORT) = main_socket.accept()
|
||||
newClient = clientThread(clientsocket, ip, PORT)
|
||||
newClient.start()
|
||||
clients.append(newClient)
|
||||
|
@ -1,57 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
@ -1,15 +0,0 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIICYjANBgkqhkiG9w0BAQEFAAOCAk8AMIICSgKCAkEAlmEl3vGDG4qxqPvO98zC
|
||||
pDbrFrNeTbwThiqCnuu0Cp5h/ejNeSChnRkmyOffMtvmBCmCJNw0vR9/Cpatzx9T
|
||||
XekXUWRjXbUfR1ECQ2Fo0xqS3GE58F8HYnhra9+i3xJXSJRpk7P1FaKGakie0GGo
|
||||
MCcImGZyWuSjNbUa4hj+I+Nlgf3rwQfll/06Wy7YLWXCXnNCTnirVYJooPphy3Qq
|
||||
tNj+clwCvoY8F7Tz5PDfffiDPjxb+uOWwtfi0LecL5YqZC8aed/70WcujItkGQ2Y
|
||||
hReYV+jsXvj1DTA0VB/RdrzRYeTNqYSoS7t8+cmWM27syEPb+11JLQZB4kPUB4yY
|
||||
Ffk/a1ffkmEZYtqRdbndM20Loksr3C/jHRrmqfCjRVmL2wHK64wUUbGLKcSMLHkA
|
||||
UR1HaeZ+PJ2qXX7ed4MBDRQDaMM7JGrkwz3hyBq2fblUT+OarHnlwRDvLL+XlACr
|
||||
NJ7yvnigwCkx7MVwakhr0yWc0o9QHEmA2DnibE4JCrNfyAwV+hKs9p+F0XjbbutO
|
||||
USXFxKdImKzf4NG1eNtMKFeIKZgtzm/JVhvlSC4p6ko+szBd89A11K1oHKRVNC64
|
||||
rnNH89o2qdT495HRfnFoEHgxA0nQH85QlCajOT0R122ha5ksJxXgj+HzTXiKa4Lt
|
||||
TXy5kWWkYGMGxK6Xmxn32QPZHCgZA1L99uvZjr1hgkqxrI8p1wBPABN3vdr1CLwq
|
||||
4kp0g+BOsFAb7X9rWn6xW9wTY8QmFW9Ze+K3Fn7aYg41AgMBAAE=
|
||||
-----END PUBLIC KEY-----
|
Loading…
Reference in New Issue
Block a user