(untested) slave shall now ask for the server secret key for symmetric communications

This commit is contained in:
study-faraphel 2025-01-04 18:28:13 +01:00
parent a9fe745fd7
commit 1301b10259
14 changed files with 193 additions and 16 deletions

View file

@ -1,6 +1,6 @@
from . import base from . import base
from source import packets from source import packets
from ...packets import PeerPacket from .. import roles
from ...utils.crypto.type import CipherType from ...utils.crypto.type import CipherType
@ -11,6 +11,9 @@ class DiscoveryEvent(base.BaseEvent):
def handle(self, packet: packets.DiscoveryPacket, address: tuple): def handle(self, packet: packets.DiscoveryPacket, address: tuple):
# create a peer packet containing our important information # 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 # send our information back
self.manager.communication.send(peerPacket, CipherType.PLAIN, address) self.manager.communication.send(peerPacket, CipherType.PLAIN, address)

View 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

View 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 ?

View file

@ -3,3 +3,5 @@ from . import base
from .DiscoveryEvent import DiscoveryEvent from .DiscoveryEvent import DiscoveryEvent
from .PeerEvent import PeerEvent from .PeerEvent import PeerEvent
from .AudioEvent import AudioEvent from .AudioEvent import AudioEvent
from .RequestKeyEvent import RequestKeyEvent
from .KeyEvent import KeyEvent

View file

@ -27,7 +27,7 @@ class MasterRole(base.BaseRole):
# prepare the audio file that will be streamed # prepare the audio file that will be streamed
# TODO(Faraphel): use another audio source # 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() self.play_time = datetime.now()
# calculate the number of bytes per milliseconds in the audio # 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) self.chunk_duration = timedelta(milliseconds=self.TARGET_SIZE / bytes_per_ms)
# split the audio into chunks # split the audio into chunks
self.chunk_count = 0
self.chunks = make_chunks(self.audio, self.chunk_duration.total_seconds() * 1000) 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): 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 # TODO(Faraphel): check if another server is emitting sound in the network. Return to undefined if yes
# get the next chunk # get the current chunk
chunk = self.chunks.pop(0) chunk = self.chunks[self.chunk_count]
# broadcast it in the network # broadcast it in the network
audio_packet = AudioPacket( audio_packet = AudioPacket(
@ -58,6 +59,8 @@ class MasterRole(base.BaseRole):
) )
self.manager.communication.broadcast(audio_packet, CipherType.AES_CBC) self.manager.communication.broadcast(audio_packet, CipherType.AES_CBC)
# wait for the audio to play # increment the chunk count
# TODO(Faraphel): should adapt to the compute time above self.chunk_count += 1
pause.until(datetime.now() + self.chunk_duration)
# wait for the next chunk time
pause.until(self.play_time + (self.chunk_duration * self.chunk_count))

View file

@ -1,4 +1,7 @@
from source import managers, packets
from source.behaviors.roles import base from source.behaviors.roles import base
from source.utils.crypto.type import CipherType
class SlaveRole(base.BaseRole): 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 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): def handle(self):
# TODO(Faraphel): ping the server and check if it is working properly. Return to undefined if no. # TODO(Faraphel): ping the master and check if it is working properly. Return to undefined if no.
pass
# 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)

View file

@ -4,6 +4,7 @@ from datetime import datetime, timedelta
from . import base, MasterRole from . import base, MasterRole
from source import packets from source import packets
from .SlaveRole import SlaveRole
from ...managers import Manager from ...managers import Manager
from ...utils.crypto.type import CipherType 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 # check if no more peers have been found in the previous seconds
if datetime.now() - self.previous_discovery >= timedelta(seconds=5): if datetime.now() - self.previous_discovery >= timedelta(seconds=5):
# check if no peer have been found except ourselves. # SCENARIO 1 - empty network
# TODO(Faraphel): need a better check than just that
if len(self.manager.peers) == 1: # filter ourselves out of the remote peers
# if we are the only machine, become a master 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) 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.")

View file

@ -1,4 +1,5 @@
from . import base from . import base
from .MasterRole import MasterRole from .MasterRole import MasterRole
from .SlaveRole import SlaveRole
from .UndefinedRole import UndefinedRole from .UndefinedRole import UndefinedRole

View file

@ -171,3 +171,33 @@ class CommunicationManager:
payload, address = self.socket.recvfrom(65536) payload, address = self.socket.recvfrom(65536)
# decode the payload # decode the payload
return self.packet_decode(payload), address 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

View file

@ -17,12 +17,16 @@ class Manager:
self.communication.register_packet_type(b"DISC", packets.DiscoveryPacket) self.communication.register_packet_type(b"DISC", packets.DiscoveryPacket)
self.communication.register_packet_type(b"PEER", packets.PeerPacket) self.communication.register_packet_type(b"PEER", packets.PeerPacket)
self.communication.register_packet_type(b"AUDI", packets.AudioPacket) 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 # event manager
self.event = EventManager(self) self.event = EventManager(self)
self.event.register_event_handler(packets.DiscoveryPacket, events.DiscoveryEvent(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.PeerPacket, events.PeerEvent(self))
self.event.register_event_handler(packets.AudioPacket, events.AudioEvent(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 # role manager
self.role = RoleManager(self) self.role = RoleManager(self)

View 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))

View file

@ -14,9 +14,13 @@ class PeerPacket(base.BasePacket):
# public RSA key of the machine # public RSA key of the machine
public_key: bytes = dataclasses.field(repr=False) public_key: bytes = dataclasses.field(repr=False)
# is the machine a master
master: bool = dataclasses.field()
def pack(self) -> bytes: def pack(self) -> bytes:
return msgpack.packb(( return msgpack.packb((
self.public_key, self.public_key,
self.master
)) ))
@classmethod @classmethod

View 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()

View file

@ -3,3 +3,5 @@ from . import base
from .AudioPacket import AudioPacket from .AudioPacket import AudioPacket
from .DiscoveryPacket import DiscoveryPacket from .DiscoveryPacket import DiscoveryPacket
from .PeerPacket import PeerPacket from .PeerPacket import PeerPacket
from .RequestKeyPacket import RequestKeyPacket
from .KeyPacket import KeyPacket