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

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

@ -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

View file

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

View file

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

View file

@ -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.")

View file

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

View file

@ -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

View file

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

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_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

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 .DiscoveryPacket import DiscoveryPacket
from .PeerPacket import PeerPacket
from .RequestKeyPacket import RequestKeyPacket
from .KeyPacket import KeyPacket