(untested) slave shall now ask for the server secret key for symmetric communications
This commit is contained in:
parent
a9fe745fd7
commit
1301b10259
14 changed files with 193 additions and 16 deletions
|
@ -1,6 +1,6 @@
|
|||
from . import base
|
||||
from source import packets
|
||||
from ...packets import PeerPacket
|
||||
from .. import roles
|
||||
from ...utils.crypto.type import CipherType
|
||||
|
||||
|
||||
|
@ -11,6 +11,9 @@ class DiscoveryEvent(base.BaseEvent):
|
|||
|
||||
def handle(self, packet: packets.DiscoveryPacket, address: tuple):
|
||||
# create a peer packet containing our important information
|
||||
peerPacket = PeerPacket(self.manager.communication.public_key)
|
||||
peerPacket = packets.PeerPacket(
|
||||
self.manager.communication.public_key,
|
||||
isinstance(self.manager.role, roles.MasterRole)
|
||||
)
|
||||
# send our information back
|
||||
self.manager.communication.send(peerPacket, CipherType.PLAIN, address)
|
||||
|
|
20
source/behaviors/events/KeyEvent.py
Normal file
20
source/behaviors/events/KeyEvent.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from source import packets
|
||||
from source.behaviors import roles
|
||||
from source.behaviors.events import base
|
||||
|
||||
|
||||
class KeyEvent(base.BaseEvent):
|
||||
"""
|
||||
Event reacting to a machine sending us their secret key
|
||||
"""
|
||||
|
||||
def handle(self, packet: packets.KeyPacket, address: tuple):
|
||||
# check if we are a slave
|
||||
if not isinstance(self.manager.role, roles.SlaveRole):
|
||||
return
|
||||
|
||||
# TODO(Faraphel): check if this come from our server ?
|
||||
|
||||
# use the secret key for further symmetric communication
|
||||
# TODO(Faraphel): should not be an attribute of the manager communication system, create a dictionary with the secret key of the servers ?
|
||||
self.manager.communication.secret_key = packet.secret_key
|
22
source/behaviors/events/RequestKeyEvent.py
Normal file
22
source/behaviors/events/RequestKeyEvent.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from source import packets
|
||||
from source.behaviors import roles
|
||||
from source.behaviors.events import base
|
||||
from source.utils.crypto.type import CipherType
|
||||
|
||||
|
||||
class RequestKeyEvent(base.BaseEvent):
|
||||
"""
|
||||
Event reacting to a machine trying to get our secret symmetric key for secure communication
|
||||
"""
|
||||
|
||||
def handle(self, packet: packets.RequestKeyPacket, address: tuple):
|
||||
# check if we are a master
|
||||
if not isinstance(self.manager.role, roles.MasterRole):
|
||||
return
|
||||
|
||||
# create a packet containing our secret key
|
||||
packet = packets.KeyPacket(self.manager.communication.secret_key)
|
||||
# send it back to the slave
|
||||
self.manager.communication.send(packet, CipherType.RSA, address)
|
||||
|
||||
# TODO(Faraphel): check if we trust the slave ?
|
|
@ -2,4 +2,6 @@ from . import base
|
|||
|
||||
from .DiscoveryEvent import DiscoveryEvent
|
||||
from .PeerEvent import PeerEvent
|
||||
from .AudioEvent import AudioEvent
|
||||
from .AudioEvent import AudioEvent
|
||||
from .RequestKeyEvent import RequestKeyEvent
|
||||
from .KeyEvent import KeyEvent
|
||||
|
|
|
@ -27,7 +27,7 @@ class MasterRole(base.BaseRole):
|
|||
|
||||
# prepare the audio file that will be streamed
|
||||
# TODO(Faraphel): use another audio source
|
||||
self.audio = pydub.AudioSegment.from_file("../assets/Caravan Palace - Wonderland.mp3")
|
||||
self.audio = pydub.AudioSegment.from_file("../assets/Queen - Another One Bites the Dust.mp3")
|
||||
self.play_time = datetime.now()
|
||||
|
||||
# calculate the number of bytes per milliseconds in the audio
|
||||
|
@ -36,6 +36,7 @@ class MasterRole(base.BaseRole):
|
|||
self.chunk_duration = timedelta(milliseconds=self.TARGET_SIZE / bytes_per_ms)
|
||||
|
||||
# split the audio into chunks
|
||||
self.chunk_count = 0
|
||||
self.chunks = make_chunks(self.audio, self.chunk_duration.total_seconds() * 1000)
|
||||
|
||||
|
||||
|
@ -44,8 +45,8 @@ class MasterRole(base.BaseRole):
|
|||
# TODO(Faraphel): share the secret key generated with the other *allowed* peers ! How to select them ? A file ?
|
||||
# TODO(Faraphel): check if another server is emitting sound in the network. Return to undefined if yes
|
||||
|
||||
# get the next chunk
|
||||
chunk = self.chunks.pop(0)
|
||||
# get the current chunk
|
||||
chunk = self.chunks[self.chunk_count]
|
||||
|
||||
# broadcast it in the network
|
||||
audio_packet = AudioPacket(
|
||||
|
@ -58,6 +59,8 @@ class MasterRole(base.BaseRole):
|
|||
)
|
||||
self.manager.communication.broadcast(audio_packet, CipherType.AES_CBC)
|
||||
|
||||
# wait for the audio to play
|
||||
# TODO(Faraphel): should adapt to the compute time above
|
||||
pause.until(datetime.now() + self.chunk_duration)
|
||||
# increment the chunk count
|
||||
self.chunk_count += 1
|
||||
|
||||
# wait for the next chunk time
|
||||
pause.until(self.play_time + (self.chunk_duration * self.chunk_count))
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
from source import managers, packets
|
||||
from source.behaviors.roles import base
|
||||
from source.utils.crypto.type import CipherType
|
||||
|
||||
|
||||
class SlaveRole(base.BaseRole):
|
||||
"""
|
||||
|
@ -6,6 +9,16 @@ class SlaveRole(base.BaseRole):
|
|||
It shall listen for a master and check if everything is working properly
|
||||
"""
|
||||
|
||||
def __init__(self, manager: "managers.Manager", master_address: tuple):
|
||||
super().__init__(manager)
|
||||
|
||||
# the address of the server
|
||||
self.master_address = master_address
|
||||
|
||||
def handle(self):
|
||||
# TODO(Faraphel): ping the server and check if it is working properly. Return to undefined if no.
|
||||
pass
|
||||
# TODO(Faraphel): ping the master and check if it is working properly. Return to undefined if no.
|
||||
|
||||
# NOTE(Faraphel): the secret key might be stored somewhere else than here, or need to be reset
|
||||
if self.manager.communication.secret_key is None:
|
||||
packet = packets.RequestKeyPacket()
|
||||
self.manager.communication.send(packet, CipherType.AES_CBC, self.master_address)
|
||||
|
|
|
@ -4,6 +4,7 @@ from datetime import datetime, timedelta
|
|||
from . import base, MasterRole
|
||||
|
||||
from source import packets
|
||||
from .SlaveRole import SlaveRole
|
||||
from ...managers import Manager
|
||||
from ...utils.crypto.type import CipherType
|
||||
|
||||
|
@ -35,8 +36,38 @@ class UndefinedRole(base.BaseRole):
|
|||
|
||||
# check if no more peers have been found in the previous seconds
|
||||
if datetime.now() - self.previous_discovery >= timedelta(seconds=5):
|
||||
# check if no peer have been found except ourselves.
|
||||
# TODO(Faraphel): need a better check than just that
|
||||
if len(self.manager.peers) == 1:
|
||||
# if we are the only machine, become a master
|
||||
# SCENARIO 1 - empty network
|
||||
|
||||
# filter ourselves out of the remote peers
|
||||
remote_peers = {
|
||||
address: peer
|
||||
for (address, peer) in self.manager.peers.items()
|
||||
if not self.manager.communication.is_address_local(address)
|
||||
}
|
||||
|
||||
# if no other peers have been found
|
||||
if len(remote_peers) == 0:
|
||||
# declare ourselves as the master of the network
|
||||
self.manager.role.current = MasterRole(self.manager)
|
||||
return
|
||||
|
||||
# SCENARIO 2 - network with a master
|
||||
|
||||
# list all the peers considered as masters
|
||||
master_peers = {
|
||||
address: peer
|
||||
for (address, peer) in remote_peers.items()
|
||||
if peer.master
|
||||
}
|
||||
|
||||
# if there is a master, become a slave
|
||||
if len(master_peers) >= 1:
|
||||
master_address, master_peer = master_peers[0]
|
||||
|
||||
# declare ourselves as a slave of the network
|
||||
self.manager.role.current = SlaveRole(self.manager, master_address)
|
||||
|
||||
# SCENARIO 3 - network with no master
|
||||
|
||||
# TODO(Faraphel): elect the machine with the lowest ping in the network
|
||||
raise NotImplementedError("Not implemented: elect the machine with the lowest ping as a master.")
|
|
@ -1,4 +1,5 @@
|
|||
from . import base
|
||||
|
||||
from .MasterRole import MasterRole
|
||||
from .UndefinedRole import UndefinedRole
|
||||
from .SlaveRole import SlaveRole
|
||||
from .UndefinedRole import UndefinedRole
|
||||
|
|
|
@ -171,3 +171,33 @@ class CommunicationManager:
|
|||
payload, address = self.socket.recvfrom(65536)
|
||||
# decode the payload
|
||||
return self.packet_decode(payload), address
|
||||
|
||||
@staticmethod
|
||||
def get_local_addresses() -> list[tuple]:
|
||||
"""
|
||||
Get the local addresses of the machine
|
||||
:return: the local addresses of the machine
|
||||
"""
|
||||
|
||||
return socket.getaddrinfo(socket.gethostname(), None)
|
||||
|
||||
def is_address_local(self, address: tuple) -> bool:
|
||||
"""
|
||||
Is the given address local
|
||||
:return: true if the address is local, false otherwise
|
||||
"""
|
||||
|
||||
host, _, _, scope = address
|
||||
|
||||
# check for all the interfaces of our machine
|
||||
for interface in self.get_local_addresses():
|
||||
# unpack the interface information
|
||||
interface_family, _, _, _, interface_address = interface
|
||||
interface_host, _, _, interface_scope = interface_address
|
||||
|
||||
# check if it matches the address interface
|
||||
if host == interface_host and scope == interface_scope:
|
||||
return True
|
||||
|
||||
# no matching interfaces have been found
|
||||
return False
|
||||
|
|
|
@ -17,12 +17,16 @@ class Manager:
|
|||
self.communication.register_packet_type(b"DISC", packets.DiscoveryPacket)
|
||||
self.communication.register_packet_type(b"PEER", packets.PeerPacket)
|
||||
self.communication.register_packet_type(b"AUDI", packets.AudioPacket)
|
||||
self.communication.register_packet_type(b"RQSK", packets.RequestKeyPacket)
|
||||
self.communication.register_packet_type(b"GTSK", packets.KeyPacket)
|
||||
|
||||
# event manager
|
||||
self.event = EventManager(self)
|
||||
self.event.register_event_handler(packets.DiscoveryPacket, events.DiscoveryEvent(self))
|
||||
self.event.register_event_handler(packets.PeerPacket, events.PeerEvent(self))
|
||||
self.event.register_event_handler(packets.AudioPacket, events.AudioEvent(self))
|
||||
self.event.register_event_handler(packets.RequestKeyPacket, events.RequestKeyEvent(self))
|
||||
self.event.register_event_handler(packets.KeyPacket, events.KeyEvent(self))
|
||||
|
||||
# role manager
|
||||
self.role = RoleManager(self)
|
||||
|
|
23
source/packets/KeyPacket.py
Normal file
23
source/packets/KeyPacket.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
import dataclasses
|
||||
|
||||
import msgpack
|
||||
|
||||
from source.packets import base
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class KeyPacket(base.BasePacket):
|
||||
"""
|
||||
Represent a packet containing a secret symmetric key
|
||||
"""
|
||||
|
||||
secret_key: bytes = dataclasses.field(repr=False)
|
||||
|
||||
def pack(self) -> bytes:
|
||||
return msgpack.packb((
|
||||
self.secret_key
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def unpack(cls, data: bytes):
|
||||
return cls(*msgpack.unpackb(data))
|
|
@ -14,9 +14,13 @@ class PeerPacket(base.BasePacket):
|
|||
# public RSA key of the machine
|
||||
public_key: bytes = dataclasses.field(repr=False)
|
||||
|
||||
# is the machine a master
|
||||
master: bool = dataclasses.field()
|
||||
|
||||
def pack(self) -> bytes:
|
||||
return msgpack.packb((
|
||||
self.public_key,
|
||||
self.master
|
||||
))
|
||||
|
||||
@classmethod
|
||||
|
|
19
source/packets/RequestKeyPacket.py
Normal file
19
source/packets/RequestKeyPacket.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
import dataclasses
|
||||
|
||||
import msgpack
|
||||
|
||||
from source.packets import base
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class RequestKeyPacket(base.BasePacket):
|
||||
"""
|
||||
Represent a packet used to request a secret symmetric key
|
||||
"""
|
||||
|
||||
def pack(self) -> bytes:
|
||||
return msgpack.packb(())
|
||||
|
||||
@classmethod
|
||||
def unpack(cls, data: bytes):
|
||||
return cls()
|
|
@ -3,3 +3,5 @@ from . import base
|
|||
from .AudioPacket import AudioPacket
|
||||
from .DiscoveryPacket import DiscoveryPacket
|
||||
from .PeerPacket import PeerPacket
|
||||
from .RequestKeyPacket import RequestKeyPacket
|
||||
from .KeyPacket import KeyPacket
|
||||
|
|
Loading…
Reference in a new issue