Echo example

This commit is contained in:
Mark Qvist 2018-03-20 12:32:41 +01:00
parent 4c7d20e15b
commit d4be407821
9 changed files with 386 additions and 62 deletions

View File

@ -15,12 +15,17 @@ class Destination:
PADDINGSIZE= FPE.Identity.PADDINGSIZE;
# Constants
SINGLE = 0x00;
GROUP = 0x01;
PLAIN = 0x02;
LINK = 0x03;
SINGLE = 0x00
GROUP = 0x01
PLAIN = 0x02
LINK = 0x03
types = [SINGLE, GROUP, PLAIN, LINK]
PROVE_NONE = 0x21
PROVE_APP = 0x22
PROVE_ALL = 0x23
proof_strategies = [PROVE_NONE, PROVE_APP, PROVE_ALL]
IN = 0x11;
OUT = 0x12;
directions = [IN, OUT]
@ -56,20 +61,24 @@ class Destination:
if not direction in Destination.directions: raise ValueError("Unknown destination direction")
self.type = type
self.direction = direction
self.proof_strategy = Destination.PROVE_NONE
self.mtu = 0
if identity == None:
if identity != None and type == Destination.SINGLE:
aspects = aspects+(identity.hexhash,)
if identity == None and direction == Destination.IN:
identity = Identity()
identity.createKeys()
aspects = aspects+(identity.hexhash,)
self.identity = identity
aspects = aspects+(identity.hexhash,)
self.name = Destination.getDestinationName(app_name, *aspects)
self.hash = Destination.getDestinationHash(app_name, *aspects)
self.hexhash = self.hash.encode("hex_codec")
self.callback = None
self.proofcallback = None
FPE.Transport.registerDestination(self)
@ -81,11 +90,19 @@ class Destination:
def setCallback(self, callback):
self.callback = callback
def setProofCallback(self, callback):
self.proofcallback = callback
def receive(self, data):
plaintext = self.decrypt(data)
def setProofStrategy(self, proof_strategy):
if not proof_strategy in Destination.proof_strategies:
raise TypeError("Unsupported proof strategy")
else:
self.proof_strategy = proof_strategy
def receive(self, packet):
plaintext = self.decrypt(packet.data)
if plaintext != None and self.callback != None:
self.callback(plaintext, self)
self.callback(plaintext, packet)
def createKeys(self):

View File

@ -1,7 +1,7 @@
from Interfaces import *
import ConfigParser
import jsonpickle
from vendor.configobj import ConfigObj
import atexit
import struct
import array
import os.path
@ -13,16 +13,12 @@ class FlexPE:
MTU = 500
router = None
config = None
destinations = []
interfaces = []
configdir = os.path.expanduser("~")+"/.flexpe"
configpath = ""
storagepath = ""
cachepath = ""
# TODO: Move this to Transport
packetlist = []
def __init__(self,configdir=None):
if configdir != None:
FlexPE.configdir = configdir
@ -48,23 +44,14 @@ class FlexPE:
FPE.Identity.loadKnownDestinations()
FlexPE.router = self
@staticmethod
def addDestination(destination):
destination.MTU = FlexPE.MTU
FlexPE.destinations.append(destination)
@staticmethod
def outbound(raw):
for interface in FlexPE.interfaces:
if interface.OUT:
FPE.log("Transmitting via: "+str(interface), FPE.LOG_DEBUG)
interface.processOutgoing(raw)
atexit.register(FPE.Identity.exitHandler)
def applyConfig(self):
for option in self.config["logging"]:
value = self.config["logging"][option]
if option == "loglevel":
FPE.loglevel = int(value)
if "logging" in self.config:
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]
@ -82,7 +69,7 @@ class FlexPE:
interface.OUT = True
interface.name = name
FlexPE.interfaces.append(interface)
FPE.Transport.interfaces.append(interface)
if c["type"] == "SerialInterface":
interface = SerialInterface.SerialInterface(
@ -98,7 +85,7 @@ class FlexPE:
interface.OUT = True
interface.name = name
FlexPE.interfaces.append(interface)
FPE.Transport.interfaces.append(interface)
except Exception as e:
FPE.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", FPE.LOG_ERROR)

View File

@ -37,13 +37,23 @@ class Identity:
self.createKeys()
@staticmethod
def remember(hash, public_key, app_data = None):
FPE.log("Remembering "+FPE.hexrep(hash, False), FPE.LOG_VERBOSE)
Identity.known_destinations[hash] = [time.time(), public_key, app_data]
def remember(packet_hash, destination_hash, public_key, app_data = None):
FPE.log("Remembering "+FPE.prettyhexrep(destination_hash), FPE.LOG_VERBOSE)
Identity.known_destinations[destination_hash] = [time.time(), packet_hash, public_key, app_data]
@staticmethod
def recall(identity):
pass
def recall(destination_hash):
FPE.log("Searching for "+FPE.prettyhexrep(destination_hash)+"...", FPE.LOG_DEBUG)
if destination_hash in Identity.known_destinations:
identity_data = Identity.known_destinations[destination_hash]
identity = Identity(public_only=True)
identity.loadPublicKey(identity_data[2])
FPE.log("Found "+FPE.prettyhexrep(destination_hash)+" in known destinations", FPE.LOG_DEBUG)
return identity
else:
FPE.log("Could not find "+FPE.prettyhexrep(destination_hash)+" in known destinations", FPE.LOG_DEBUG)
return None
@staticmethod
def saveKnownDestinations():
@ -80,7 +90,7 @@ class Identity:
@staticmethod
def validateAnnounce(packet):
if packet.packet_type == FPE.Packet.ANNOUNCE:
FPE.log("Validating announce from "+FPE.hexrep(packet.destination_hash), FPE.LOG_VERBOSE)
FPE.log("Validating announce from "+FPE.prettyhexrep(packet.destination_hash), FPE.LOG_VERBOSE)
destination_hash = packet.destination_hash
public_key = packet.data[10:Identity.DERKEYSIZE/8+10]
random_hash = packet.data[Identity.DERKEYSIZE/8+10:Identity.DERKEYSIZE/8+20]
@ -96,11 +106,19 @@ class Identity:
if announced_identity.validate(signature, signed_data):
FPE.log("Announce is valid", FPE.LOG_VERBOSE)
FPE.Identity.remember(destination_hash, public_key)
FPE.Identity.remember(FPE.Identity.fullHash(packet.raw), destination_hash, public_key)
FPE.log("Stored valid announce from "+FPE.prettyhexrep(destination_hash), FPE.LOG_INFO)
del announced_identity
return True
else:
FPE.log("Announce is invalid", FPE.LOG_VERBOSE)
del announced_identity
return False
@staticmethod
def exitHandler():
Identity.saveKnownDestinations()
del announced_identity
def createKeys(self):
self.prv = rsa.generate_private_key(
@ -119,10 +137,9 @@ class Identity:
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
self.hash = Identity.truncatedHash(self.pub_bytes)
self.hexhash = self.hash.encode("hex_codec")
self.updateHashes()
FPE.log("Identity keys created, private length is "+str(len(self.prv_bytes))+" public length is "+str(len(self.pub_bytes)), FPE.LOG_INFO)
FPE.log("Identity keys created for "+FPE.prettyhexrep(self.hash), FPE.LOG_INFO)
def getPrivateKey(self):
return self.prv_bytes
@ -138,10 +155,16 @@ class Identity:
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
self.updateHashes()
def loadPublicKey(self, key):
self.pub_bytes = key
self.pub = load_der_public_key(self.pub_bytes, backend=default_backend())
self.updateHashes()
def updateHashes(self):
self.hash = Identity.truncatedHash(self.pub_bytes)
self.hexhash = self.hash.encode("hex_codec")
def saveIdentity(self):
pass
@ -150,7 +173,7 @@ class Identity:
pass
def encrypt(self, plaintext):
if self.prv != None:
if self.pub != None:
chunksize = (Identity.KEYSIZE-Identity.PADDINGSIZE)/8
chunks = int(math.ceil(len(plaintext)/(float(chunksize))))
# TODO: Remove debug output print("Plaintext size is "+str(len(plaintext))+", with "+str(chunks)+" chunks")
@ -175,7 +198,7 @@ class Identity:
# 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")
raise KeyError("Encryption failed because identity does not hold a public key")
def decrypt(self, ciphertext):
@ -238,11 +261,12 @@ class Identity:
else:
raise KeyError("Signature validation failed because identity does not hold a public key")
def prove(self, packet, destination):
proof_data = packet.packet_hash + self.sign(packet.packet_hash)
proof = FPE.Packet(destination, proof_data, FPE.Packet.PROOF)
proof.send()
def getRandomHash(self):
return self.truncatedHash(os.urandom(10))
def identityExithandler():
Identity.saveKnownDestinations()
atexit.register(identityExithandler)

View File

@ -3,6 +3,7 @@ import SocketServer
import threading
import socket
import sys
import FPE
class UdpInterface(Interface):
bind_ip = None
@ -36,9 +37,13 @@ class UdpInterface(Interface):
def processIncoming(self, data):
# TODO: remove
#FPE.log("IN: "+FPE.prettyhexrep(data))
self.owner.inbound(data)
def processOutgoing(self,data):
# TODO: remove
#FPE.log("OUT: "+FPE.prettyhexrep(" "+data))
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
udp_socket.sendto(data, (self.forward_ip, self.forward_port))
@ -52,5 +57,7 @@ class UdpInterfaceHandler(SocketServer.BaseRequestHandler):
def handle(self):
if (UdpInterfaceHandler.interface != None):
data = self.request[0].strip()
# TODO: remove
#FPE.log("Datagram contents: "+FPE.prettyhexrep(self.request[0]))
data = self.request[0]
UdpInterfaceHandler.interface.processIncoming(data)

View File

@ -1,4 +1,5 @@
import struct
import time
import FPE
class Packet:
@ -29,14 +30,19 @@ class Packet:
self.transport_id = transport_id
self.data = data
self.flags = self.getPackedFlags()
self.MTU = self.destination.MTU
self.raw = None
self.packed = False
self.sent = False
self.MTU = self.destination.MTU
self.fromPacked = False
else:
self.raw = data
self.packed = True
self.raw = data
self.packed = True
self.fromPacked = True
self.sent_at = None
self.packet_hash = None
def getPackedFlags(self):
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
@ -90,6 +96,8 @@ class Packet:
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.packet_hash = FPE.Identity.fullHash(self.raw)
self.sent_at = time.time()
self.sent = True
else:
raise IOError("Packet was already sent")
@ -99,3 +107,22 @@ class Packet:
Transport.outbound(self.raw)
else:
raise IOError("Packet was not sent yet")
def prove(self, destination):
if self.fromPacked and self.destination:
if self.destination.identity and self.destination.identity.prv:
self.destination.identity.prove(self, destination)
def validateProofPacket(self, proof_packet):
return self.validateProof(proof_packet.data)
def validateProof(self, proof):
proof_hash = proof[:32]
signature = proof[32:]
if proof_hash == self.packet_hash:
return self.destination.identity.validate(signature, proof_hash)
else:
return False

View File

@ -8,11 +8,17 @@ class Transport:
TUNNEL = 0x03;
types = [BROADCAST, TRANSPORT, RELAY, TUNNEL]
interfaces = []
destinations = []
packet_hashlist = []
@staticmethod
def outbound(raw):
FPE.FlexPE.outbound(raw)
Transport.cacheRaw(raw)
for interface in Transport.interfaces:
if interface.OUT:
FPE.log("Transmitting via: "+str(interface), FPE.LOG_DEBUG)
interface.processOutgoing(raw)
@staticmethod
def inbound(raw):
@ -22,15 +28,43 @@ class Transport:
Transport.packet_hashlist.append(packet_hash)
packet = FPE.Packet(None, raw)
packet.unpack()
packet.packet_hash = packet_hash
if packet.packet_type == FPE.Packet.ANNOUNCE:
FPE.Identity.validateAnnounce(packet)
if FPE.Identity.validateAnnounce(packet):
Transport.cache(packet)
if packet.packet_type == FPE.Packet.RESOURCE:
for destination in FlexPE.destinations:
for destination in Transport.destinations:
if destination.hash == packet.destination_hash and destination.type == packet.destination_type:
destination.receive(packet.data)
packet.destination = destination
destination.receive(packet)
Transport.cache(packet)
if packet.packet_type == FPE.Packet.PROOF:
for destination in Transport.destinations:
if destination.hash == packet.destination_hash:
if destination.proofcallback != None:
destination.proofcallback(packet)
# TODO: add universal proof handling
@staticmethod
def registerDestination(destination):
FPE.FlexPE.addDestination(destination)
destination.MTU = FPE.FlexPE.MTU
if destination.direction == FPE.Destination.IN:
Transport.destinations.append(destination)
@staticmethod
def cache(packet):
FPE.Transport.cacheRaw(packet.raw)
@staticmethod
def cacheRaw(raw):
try:
file = open(FPE.FlexPE.cachepath+"/"+FPE.hexrep(FPE.Identity.fullHash(raw), delimit=False), "w")
file.write(raw)
file.close()
FPE.log("Wrote packet "+FPE.prettyhexrep(FPE.Identity.fullHash(raw))+" to cache", FPE.LOG_DEBUG)
except Exception as e:
FPE.log("Error writing packet to cache", FPE.LOG_ERROR)
FPE.log("The contained exception was: "+str(e))

222
FPE/Utilities/Echo.py Normal file
View File

@ -0,0 +1,222 @@
import argparse
import time
import FPE
# Let's define an app name. We'll use this for all
# destinations we create. Since this echo example
# is part of a range of example utilities, we'll put
# them all within the app namespace "example_utilities"
APP_NAME = "example_utilitites"
# This initialisation is executed when the users chooses
# to run as a server
def server(configpath):
# We must first initialise FlexPE
fpe = FPE.FlexPE(configpath)
# Randomly create a new identity for our echo server
server_identity = FPE.Identity()
# We create a destination that clients can query. We want
# to be able to verify echo replies to our clients, so we
# create a "single" destination that can receive encrypted
# messages. This way the client can send a request and be
# certain that no-one else than this destination was able
# to read it.
echo_destination = FPE.Destination(server_identity, FPE.Destination.IN, FPE.Destination.SINGLE, APP_NAME, "echo", "request")
# Tell the destination which function in our program to
# run when a packet is received.
echo_destination.setCallback(serverCallback)
# Everything's ready!
# Let's Wait for client requests or user input
announceLoop(echo_destination)
def announceLoop(destination):
# Let the user know that everything is ready
FPE.log("Echo server running, hit enter to send announce (Ctrl-C to quit)")
# We enter a loop that runs until the users exits.
# If the user just hits enter, we will announce our server
# destination on the network, which will let clients know
# how to create messages directed towards it.
while True:
entered = raw_input()
destination.announce()
FPE.log("Sent announce from "+FPE.prettyhexrep(destination.hash))
def serverCallback(message, packet):
# We have received am echo request from a client! When
# a client sends a request, it will include the hash of
# it's identity in the message. Since we know that the
# client has created a listening destination using this
# identity hash, we can construct an outgoing destination
# to direct our response to. The hash is sent in binary
# format, so we encode it as printable hexadecimal first,
# since aspect names need to in printable text.
client_identity_hexhash = message.encode("hex_codec")
# We can now create a destination that will let us reach
# the client which send the echo request.
reply_destination = FPE.Destination(None, FPE.Destination.OUT, FPE.Destination.PLAIN, APP_NAME, "echo", "reply", client_identity_hexhash)
# Let's encode the reply destination hash in a readable
# way, so we can output some info to the user.
reply_destination_hexhash = reply_destination.hash.encode("hex_codec")
# Tell the user that we received an echo request, and
# that we are going to send a reply to the requester.
FPE.log("Received packet from <"+reply_destination_hexhash+">, sending reply")
# To let the client know that we got the echo request,
# we will use the "proof" functions of FlexPE. In most
# applications, the proving of packets will occur fully
# automatically, but in some cases like this, it can be
# beneficial to use the functions manually, since it
# neatly provides functionality that can unequivocally
# prove the receipt of the request to the client.
#
# Using the proof functionality is very simple, we just
# need to call the "prove" method on the packet we wish
# to prove, and specify which destination it should be
# directed to.
packet.prove(reply_destination)
# We need a global list to hold sent echo requests
sent_requests = []
# This initialisation is executed when the users chooses
# to run as a client
def client(destination_hexhash, configpath):
# We need a binary representation of the destination
# hash that was entered on the command line
try:
if len(destination_hexhash) != 20:
raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
destination_hash = destination_hexhash.decode("hex")
except:
FPE.log("Invalid destination entered. Check your input!")
exit()
# We must first initialise FlexPE
fpe = FPE.FlexPE()
# Randomly create a new identity for our echo server
client_identity = FPE.Identity()
# Let's set up a destination for replies to our echo
# requests. This destination will be used by the server
# to direct replies to. We're going to use a "plain"
# destination, so the server can send replies back
# without knowing any public keys of the client. In this
# case, such a design is benificial, since any client
# can send echo requests directly to the server, without
# first having to announce it's destination, or include
# public keys in the echo request
#
# We will use the destination naming convention of:
# example_utilities.echo.reply.<IDENTITY_HASH>
# where the last part is a hex representation of the hash
# of our "client_identity". We need to include this to
# create a unique destination for the server to respond to.
# If we had used a "single" destination, something equivalent
# to this process would have happened automatically.
reply_destination = FPE.Destination(client_identity, FPE.Destination.IN, FPE.Destination.PLAIN, APP_NAME, "echo", "reply", client_identity.hexhash)
# Since we are only expecting packets of the "proof"
# type to reach our reply destination, we just set the
# proof callback (and in this case not the normal
# message callback)
reply_destination.setProofCallback(clientProofCallback)
# Tell the user that the client is ready!
FPE.log("Echo client "+FPE.prettyhexrep(reply_destination.hash)+" ready, hit enter to send echo request (Ctrl-C to quit)")
# We enter a loop that runs until the user exits.
# If the user hits enter, we will try to send an
# echo request to the destination specified on the
# command line.
while True:
raw_input()
# To address the server, we need to know it's public
# key, so we check if FlexPE knows this destination.
# This is done by calling the "recall" method of the
# Identity module. If the destination is known, it will
# return an Identity instance that can be used in
# outgoing destinations.
server_identity = FPE.Identity.recall(destination_hash)
if server_identity != None:
# We got the correct identity instance from the
# recall method, so let's create an outgoing
# destination. We use the naming convention:
# example_utilities.echo.request
# Since this is a "single" destination, the identity
# hash will be automatically added to the end of
# the name.
request_destination = FPE.Destination(server_identity, FPE.Destination.OUT, FPE.Destination.SINGLE, APP_NAME, "echo", "request")
# The destination is ready, so let's create a packet.
# We set the destination to the request_destination
# that was just created, and the only data we add
# is the identity hash of our client identity.
# Including that information will let the server
# create a destination to send replies to.
echo_request = FPE.Packet(request_destination, client_identity.hash)
# Send the packet!
echo_request.send()
# Add the request to our list of sent packets
sent_requests.append(echo_request)
# Tell the user that the echo request was sent
FPE.log("Sent echo request to "+FPE.prettyhexrep(request_destination.hash))
else:
# If we do not know this destination, tell the
# user to wait for an announce to arrive.
FPE.log("Destination is not yet known. Wait for an announce to arrive.")
def clientProofCallback(proof_packet):
now = time.time()
for unproven_packet in sent_requests:
if unproven_packet.packet_hash == proof_packet.data[:32]:
if unproven_packet.validateProofPacket(proof_packet):
rtt = now - unproven_packet.sent_at
if (rtt >= 1):
rtt = round(rtt, 3)
rttstring = str(rtt)+" seconds"
else:
rtt = round(rtt*1000, 3)
rttstring = str(rtt)+" milliseconds"
FPE.log(
"Valid echo reply, proved by "+FPE.prettyhexrep(unproven_packet.destination.hash)+
", round-trip time was "+rttstring
)
else:
FPE.log("Proof invalid")
if __name__ == "__main__":
try:
parser = argparse.ArgumentParser(description="Simple echo server and client utility")
parser.add_argument("-s", "--server", action="store_true", help="wait for incoming packets from clients")
parser.add_argument("--config", action="store", default=None, help="path to alternative FlexPE config directory", type=str)
parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the server destination", type=str)
args = parser.parse_args()
if args.server:
configarg=None
if args.config:
configarg = args.config
server(configarg)
else:
configarg=None
if args.config:
configarg = args.config
client(args.destination, configarg)
except KeyboardInterrupt:
exit()

View File

@ -68,3 +68,8 @@ def hexrep(data, delimit=True):
delimiter = ""
hexrep = delimiter.join("{:02x}".format(ord(c)) for c in data)
return hexrep
def prettyhexrep(data):
delimiter = ""
hexrep = "<"+delimiter.join("{:02x}".format(ord(c)) for c in data)+">"
return hexrep

3
t.py
View File

@ -17,6 +17,7 @@ fpe = FlexPE()
identity = Identity()
d1=Destination(identity, Destination.IN, Destination.SINGLE, "messenger", "user")
#d1.setProofStrategy(Destination.PROVE_ALL)
d1.setCallback(testCallback)
msg=""
@ -28,7 +29,7 @@ pl = len(d1.identity.pub_bytes)
d1.announce()
p1=Packet(d1, msg)
#p1.send()
p1.send()
# p2=Packet(d2,"Test af msg")
# p2.send()