From 3ea36ff9b3763166d174c7f0fe81446e3d23fcd0 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Mon, 19 Mar 2018 16:39:08 +0100 Subject: [PATCH] Restructuring and packet format --- FPE/Destination.py | 21 ++++--- FPE/FlexPE.py | 26 +++++--- FPE/Identity.py | 16 ++--- FPE/Interfaces/SerialInterface.py | 15 +++-- FPE/Interfaces/__init__.py | 2 +- FPE/Packet.py | 101 ++++++++++++++++++++++++------ FPE/Transport.py | 13 +++- FPE/__init__.py | 55 +++++++++++++++- Notes/Header format | 30 +++++++++ t.py | 31 +++++---- 10 files changed, 236 insertions(+), 74 deletions(-) create mode 100644 Notes/Header format diff --git a/FPE/Destination.py b/FPE/Destination.py index 7896b93..29b8de4 100755 --- a/FPE/Destination.py +++ b/FPE/Destination.py @@ -1,8 +1,7 @@ import base64 import math -from Identity import Identity -from Transport import Transport -from Packet import Packet +import FPE + from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend @@ -10,15 +9,17 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric import padding + class Destination: - KEYSIZE = Identity.KEYSIZE; - PADDINGSIZE= Identity.PADDINGSIZE; + KEYSIZE = FPE.Identity.KEYSIZE; + PADDINGSIZE= FPE.Identity.PADDINGSIZE; # Constants - SINGLE = 0x01; - GROUP = 0x02; - PLAIN = 0x03; - types = [SINGLE, GROUP, PLAIN] + SINGLE = 0x00; + GROUP = 0x01; + PLAIN = 0x02; + LINK = 0x03; + types = [SINGLE, GROUP, PLAIN, LINK] IN = 0x11; OUT = 0x12; @@ -70,7 +71,7 @@ class Destination: self.callback = None - Transport.registerDestination(self) + FPE.Transport.registerDestination(self) def __str__(self): diff --git a/FPE/FlexPE.py b/FPE/FlexPE.py index 3f06eb8..4154dbd 100755 --- a/FPE/FlexPE.py +++ b/FPE/FlexPE.py @@ -1,4 +1,6 @@ from Interfaces import * +import FPE + import ConfigParser import jsonpickle from vendor.configobj import ConfigObj @@ -25,8 +27,9 @@ class FlexPE: if os.path.isfile(self.configpath): self.config = ConfigObj(self.configpath) + FPE.log("Configuration loaded from "+self.configpath) else: - print("Could not load config file, creating default configuration...") + FPE.log("Could not load config file, creating default configuration...") self.createDefaultConfig() self.applyConfig() @@ -39,23 +42,26 @@ class FlexPE: @staticmethod def incoming(data): - - header = struct.unpack("B", data[0]) - - hash = data[1:11] - type = header[0] & 0x03 + packet = FPE.Packet(None, data) + packet.unpack() for destination in FlexPE.destinations: - if destination.hash == hash and destination.type == type: - destination.receive(data[11:]) + if destination.hash == packet.destination_hash and destination.type == packet.destination_type: + destination.receive(packet.data) @staticmethod def outbound(raw): for interface in FlexPE.interfaces: if interface.OUT: + FPE.log("Transmitting via: "+str(interface), FPE.LOG_DEBUG) interface.processOutgoing(raw) def applyConfig(self): + for option in self.config["logging"]: + value = self.config["logging"][option] + if option == "loglevel": + FPE.loglevel = int(value) + for name in self.config["interfaces"]: c = self.config["interfaces"][name] try: @@ -91,8 +97,8 @@ class FlexPE: FlexPE.interfaces.append(interface) except Exception as e: - print("The interface \""+name+"\" could not be created. Check your configuration file for errors!") - print("The contained error was: "+str(e)) + FPE.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", FPE.LOG_ERROR) + FPE.log("The contained exception was: "+str(e), FPE.LOG_ERROR) diff --git a/FPE/Identity.py b/FPE/Identity.py index 48a77a4..e4875dc 100644 --- a/FPE/Identity.py +++ b/FPE/Identity.py @@ -1,5 +1,6 @@ import base64 import math +import FPE from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization @@ -51,8 +52,7 @@ class Identity: self.hash = Identity.getHash(self.pub_bytes) self.hexhash = self.hash.encode("hex_codec") - print("Identity keys created, private length is "+str(len(self.prv_bytes))) - print("Identity keys created, public length is "+str(len(self.pub_bytes))) + FPE.log("Identity keys created, private length is "+str(len(self.prv_bytes))+" public length is "+str(len(self.pub_bytes)), FPE.LOG_INFO) def getPrivateKey(self): return self.prv_bytes @@ -83,7 +83,7 @@ class Identity: if self.prv != None: chunksize = (Identity.KEYSIZE-Identity.PADDINGSIZE)/8 chunks = int(math.ceil(len(plaintext)/(float(chunksize)))) - print("Plaintext size is "+str(len(plaintext))+", with "+str(chunks)+" chunks") + # TODO: Remove debug output print("Plaintext size is "+str(len(plaintext))+", with "+str(chunks)+" chunks") ciphertext = ""; for chunk in range(chunks): @@ -92,7 +92,7 @@ class Identity: if (chunk+1)*chunksize > len(plaintext): end = len(plaintext) - print("Processing chunk "+str(chunk+1)+" of "+str(chunks)+". Starting at "+str(start)+" and stopping at "+str(end)+". The length is "+str(len(plaintext[start:end]))) + # TODO: Remove debug output print("Processing chunk "+str(chunk+1)+" of "+str(chunks)+". Starting at "+str(start)+" and stopping at "+str(end)+". The length is "+str(len(plaintext[start:end]))) ciphertext += self.pub.encrypt( plaintext[start:end], @@ -102,7 +102,7 @@ class Identity: label=None ) ) - print("Plaintext encrypted, ciphertext length is "+str(len(ciphertext))+" bytes.") + # TODO: Remove debug output print("Plaintext encrypted, ciphertext length is "+str(len(ciphertext))+" bytes.") return ciphertext else: raise KeyError("Encryption failed because identity does not hold a private key") @@ -110,7 +110,7 @@ class Identity: def decrypt(self, ciphertext): if self.prv != None: - print("Ciphertext length is "+str(len(ciphertext))+". ") + # TODO: Remove debug output print("Ciphertext length is "+str(len(ciphertext))+". ") chunksize = (Identity.KEYSIZE)/8 chunks = int(math.ceil(len(ciphertext)/(float(chunksize)))) @@ -121,7 +121,7 @@ class Identity: if (chunk+1)*chunksize > len(ciphertext): end = len(ciphertext) - print("Processing chunk "+str(chunk+1)+" of "+str(chunks)+". Starting at "+str(start)+" and stopping at "+str(end)+". The length is "+str(len(ciphertext[start:end]))) + # TODO: Remove debug output print("Processing chunk "+str(chunk+1)+" of "+str(chunks)+". Starting at "+str(start)+" and stopping at "+str(end)+". The length is "+str(len(ciphertext[start:end]))) plaintext += self.prv.decrypt( ciphertext[start:end], @@ -150,3 +150,5 @@ class Identity: else: raise KeyError("Signing failed because identity does not hold a private key") + def announce(self): + pass \ No newline at end of file diff --git a/FPE/Interfaces/SerialInterface.py b/FPE/Interfaces/SerialInterface.py index 34728d2..c816ed5 100755 --- a/FPE/Interfaces/SerialInterface.py +++ b/FPE/Interfaces/SerialInterface.py @@ -1,8 +1,10 @@ from __future__ import print_function from Interface import Interface +from time import sleep import sys import serial import threading +import FPE class SerialInterface(Interface): MAX_CHUNK = 32768 @@ -17,6 +19,7 @@ class SerialInterface(Interface): serial = None def __init__(self, owner, port, speed, databits, parity, stopbits): + self.serial = None self.owner = owner self.port = port self.speed = speed @@ -31,6 +34,7 @@ class SerialInterface(Interface): self.parity = serial.PARITY_ODD try: + FPE.log("Opening serial port "+self.port+"...") self.serial = serial.Serial( port = self.port, baudrate = self.speed, @@ -43,16 +47,16 @@ class SerialInterface(Interface): write_timeout = None, dsrdtr = False, ) - print(self.serial.inter_byte_timeout) except Exception as e: - print("Could not create serial port", file=sys.stderr) + FPE.log("Could not create serial port", FPE.LOG_ERROR) raise e - #self.serial.open() if self.serial.is_open: thread = threading.Thread(target=self.readLoop) thread.setDaemon(True) thread.start() + sleep(0.5) + FPE.log("Serial port "+self.port+" is now open") else: raise IOError("Could not open serial port") @@ -62,10 +66,13 @@ class SerialInterface(Interface): def processOutgoing(self,data): - self.serial.write(data) + written = self.serial.write(data) + if written != len(data): + raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data))) def readLoop(self): + #pass while self.serial.is_open: data = self.serial.read(size=self.owner.__class__.MTU) if not data == "": diff --git a/FPE/Interfaces/__init__.py b/FPE/Interfaces/__init__.py index e3164b0..f49e360 100755 --- a/FPE/Interfaces/__init__.py +++ b/FPE/Interfaces/__init__.py @@ -2,4 +2,4 @@ import os import glob modules = glob.glob(os.path.dirname(__file__)+"/*.py") -__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')] +__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')] \ No newline at end of file diff --git a/FPE/Packet.py b/FPE/Packet.py index d897673..767780b 100755 --- a/FPE/Packet.py +++ b/FPE/Packet.py @@ -1,30 +1,91 @@ import struct -from Transport import * +import FPE class Packet: + # Constants + MESSAGE = 0x00; + RESOURCE = 0x01; + LINKREQUEST = 0x02; + PROOF = 0x03; + types = [MESSAGE, RESOURCE, LINKREQUEST, PROOF] - def __init__(self, destination, data): - self.destination = destination - self.data = data - self.flags = 0x00 - self.header = 0x00 - self.raw = None - self.sent = False - self.mtu = 0 + HEADER_1 = 0x00; # Normal header format + HEADER_2 = 0x01; # Header format used for link packets in transport + HEADER_3 = 0x02; # Reserved + HEADER_4 = 0x03; # Reserved + header_types = [HEADER_1, HEADER_2, HEADER_3, HEADER_4] + + def __init__(self, destination, data, packet_type = MESSAGE, transport_type = None, header_type = HEADER_1, transport_id = None): + if destination != None: + if transport_type == None: + transport_type = FPE.Transport.BROADCAST + + self.header_type = header_type + self.packet_type = packet_type + self.transport_type = transport_type + + self.hops = 0; + self.destination = destination + self.transport_id = transport_id + self.data = data + self.flags = self.getPackedFlags() + + self.raw = None + self.packed = False + self.sent = False + self.MTU = self.destination.MTU + else: + self.raw = data + self.packed = True + + def getPackedFlags(self): + packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type + return packed_flags + + def pack(self): + self.header = "" + self.header += struct.pack("!B", self.flags) + self.header += struct.pack("!B", self.hops) + if self.header_type == Packet.HEADER_2: + if t_destination != None: + self.header += self.t_destination + else: + raise IOError("Packet with header type 2 must have a transport ID") + + self.header += self.destination.hash + self.ciphertext = self.destination.encrypt(self.data) + self.raw = self.header + self.ciphertext + + if len(self.raw) > self.MTU: + raise IOError("Packet size of "+str(len(self.raw))+" exceeds MTU of "+str(self.MTU)+" bytes") + + self.packed = True + + def unpack(self): + self.flags = ord(self.raw[0]) + self.hops = ord(self.raw[1]) + + self.header_type = (self.flags & 0b11000000) >> 6 + self.transport_type = (self.flags & 0b00110000) >> 4 + self.destination_type = (self.flags & 0b00001100) >> 2 + self.packet_type = (self.flags & 0b00000011) + + if self.header_type == Packet.HEADER_2: + self.transport_id = self.raw[2:12] + self.destination_hash = self.raw[12:22] + self.data = self.raw[22:] + else: + self.transport_id = None + self.destination_hash = self.raw[2:12] + self.data = self.raw[12:] + + self.packed = False def send(self): if not self.sent: - self.MTU = self.destination.MTU - self.header = struct.pack("!B", self.header ^ self.destination.type ^ self.flags) - self.header += self.destination.hash - self.ciphertext = self.destination.encrypt(self.data) - self.raw = self.header + self.ciphertext - - if len(self.raw) > self.MTU: - raise IOError("Packet size of "+str(len(self.raw))+" exceeds MTU of "+str(self.MTU)+" bytes") - - print("Size: "+str(len(self.raw))) - Transport.outbound(self.raw) + self.pack() + FPE.log("Size: "+str(len(self.raw))+" header is "+str(len(self.header))+" payload is "+str(len(self.ciphertext)), FPE.LOG_DEBUG) + FPE.Transport.outbound(self.raw) self.sent = True else: raise IOError("Packet was already sent") diff --git a/FPE/Transport.py b/FPE/Transport.py index 947a932..f8d37c8 100755 --- a/FPE/Transport.py +++ b/FPE/Transport.py @@ -1,10 +1,17 @@ -from FlexPE import FlexPE +import FPE class Transport: + # Constants + BROADCAST = 0x00; + TRANSPORT = 0x01; + RELAY = 0x02; + TUNNEL = 0x03; + types = [BROADCAST, TRANSPORT, RELAY, TUNNEL] + @staticmethod def outbound(raw): - FlexPE.outbound(raw) + FPE.FlexPE.outbound(raw) @staticmethod def registerDestination(destination): - FlexPE.addDestination(destination) \ No newline at end of file + FPE.FlexPE.addDestination(destination) \ No newline at end of file diff --git a/FPE/__init__.py b/FPE/__init__.py index 9ab3853..e9b5218 100755 --- a/FPE/__init__.py +++ b/FPE/__init__.py @@ -1,11 +1,60 @@ import os import glob +import time -from .Destination import Destination from .FlexPE import FlexPE from .Identity import Identity -from .Packet import Packet from .Transport import Transport +from .Destination import Destination +from .Packet import Packet modules = glob.glob(os.path.dirname(__file__)+"/*.py") -__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')] \ No newline at end of file +__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')] + +LOG_CRITICAL = 0 +LOG_ERROR = 1 +LOG_WARNING = 2 +LOG_NOTICE = 3 +LOG_INFO = 4 +LOG_VERBOSE = 5 +LOG_DEBUG = 6 + +LOG_STDOUT = 0x91 +LOG_FILE = 0x92 + +loglevel = LOG_NOTICE +logfile = None +logdest = LOG_STDOUT +logtimefmt = "%Y-%m-%d %H:%M:%S" + +def loglevelname(level): + if (level == LOG_CRITICAL): + return "Critical" + if (level == LOG_ERROR): + return "Error" + if (level == LOG_WARNING): + return "Warning" + if (level == LOG_NOTICE): + return "Notice" + if (level == LOG_INFO): + return "Info" + if (level == LOG_VERBOSE): + return "Verbose" + if (level == LOG_DEBUG): + return "Debug" + + return "Unknown" + +def log(msg, level=3): + # TODO: not thread safe + if loglevel >= level: + timestamp = time.time() + logstring = "["+time.strftime(logtimefmt)+"] ["+loglevelname(level)+"] "+msg + + if (logdest == LOG_STDOUT): + print(logstring) + + if (logdest == LOG_FILE and logfile != None): + file = open(logfile, "a") + file.write(logstring+"\n") + file.close() diff --git a/Notes/Header format b/Notes/Header format new file mode 100644 index 0000000..9b373a8 --- /dev/null +++ b/Notes/Header format @@ -0,0 +1,30 @@ +header types +----------------- +type 1 00 One byte header, one 10 byte address field +type 2 01 One byte header, two 10 byte address fields +type 3 10 Reserved +type 4 11 Reserved + + +propagation types +----------------- +broadcast 00 +transport 01 +relay 10 +tunnel 11 + + +destination types +----------------- +single 00 +group 01 +plain 10 +link 11 + + +packet types +----------------- +message 00 +resource 01 +link request 10 +proof 11 diff --git a/t.py b/t.py index 18baed2..bd3d6c7 100755 --- a/t.py +++ b/t.py @@ -19,33 +19,32 @@ identity = Identity() d1=Destination(identity, Destination.IN, Destination.SINGLE, "messenger", "user") d1.setCallback(testCallback) +d2=Destination(identity, Destination.IN, Destination.PLAIN, "messenger", "user") +d2.setCallback(testCallback) + # d2=Destination(identity, Destination.IN, Destination.PLAIN, "plainchat", "markqvist") # d2.setCallback(testCallback) -print identity.hexhash -print d1.name -print d1.hexhash -print d1.identity.pub -print "---" -print +#print identity.hexhash +#print d1.name +#print d1.hexhash +#print d1.identity.pub +#print "---" +#print -# p1=Packet(d1, "testmessage") -# p1.send() msg="" for x in range(300): msg += "a" signed = d1.sign(msg) sl = len(signed) pl = len(d1.identity.pub_bytes) -print("Signature length is "+str(sl)) -print("Minimum announce is "+str(pl+sl+8)) +#print("Signature length is "+str(sl)) +#print("Minimum announce is "+str(pl+sl+8)) +p1=Packet(d1, msg) +p1.send() -p2=Packet(d1, msg) -p2.send() - -# p2=Packet(d2, "something else") +# p2=Packet(d2,"Test af msg") # p2.send() -raw_input() - +raw_input() \ No newline at end of file