Added signature validation on incoming messages

This commit is contained in:
Mark Qvist 2021-05-04 10:19:07 +02:00
parent 0c32bbcb68
commit 01b5ba60d0

View File

@ -7,22 +7,26 @@ import RNS.vendor.umsgpack as msgpack
APP_NAME = "lxmf" APP_NAME = "lxmf"
class LXMessage: class LXMessage:
DRAFT = 0x00 DRAFT = 0x00
OUTBOUND = 0x01 OUTBOUND = 0x01
SENDING = 0x02 SENDING = 0x02
SENT = 0x04 SENT = 0x04
DELIVERED = 0x08 DELIVERED = 0x08
states = [DRAFT, OUTBOUND, SENDING, SENT, DELIVERED] states = [DRAFT, OUTBOUND, SENDING, SENT, DELIVERED]
UNKNOWN = 0x00 UNKNOWN = 0x00
PACKET = 0x01 PACKET = 0x01
RESOURCE = 0x02 RESOURCE = 0x02
representations = [UNKNOWN, PACKET, RESOURCE] representations = [UNKNOWN, PACKET, RESOURCE]
OPPORTUNISTIC = 0x01 OPPORTUNISTIC = 0x01
DIRECT = 0x02 DIRECT = 0x02
PROPAGATED = 0x03 PROPAGATED = 0x03
valid_methods = [OPPORTUNISTIC, DIRECT, PROPAGATED] valid_methods = [OPPORTUNISTIC, DIRECT, PROPAGATED]
SOURCE_UNKNOWN = 0x01
SIGNATURE_INVALID = 0x02
unverified_reasons = [SOURCE_UNKNOWN, SIGNATURE_INVALID]
DESTINATION_LENGTH = RNS.Identity.TRUNCATED_HASHLENGTH//8 DESTINATION_LENGTH = RNS.Identity.TRUNCATED_HASHLENGTH//8
SIGNATURE_LENGTH = RNS.Identity.SIGLENGTH//8 SIGNATURE_LENGTH = RNS.Identity.SIGLENGTH//8
@ -100,6 +104,10 @@ class LXMessage:
self.state = LXMessage.DRAFT self.state = LXMessage.DRAFT
self.method = LXMessage.UNKNOWN self.method = LXMessage.UNKNOWN
self.incoming = False
self.signature_validated = False
self.unverified_reason = None
self.representation = LXMessage.UNKNOWN self.representation = LXMessage.UNKNOWN
self.desired_method = desired_method self.desired_method = desired_method
self.delivery_attempts = 0 self.delivery_attempts = 0
@ -163,57 +171,62 @@ class LXMessage:
self.__delivery_destination = delivery_destination self.__delivery_destination = delivery_destination
def pack(self): def pack(self):
self.timestamp = time.time() if not self.packed:
self.payload = [self.timestamp, self.title, self.content, self.fields] self.timestamp = time.time()
self.payload = [self.timestamp, self.title, self.content, self.fields]
hashed_part = b"" hashed_part = b""
hashed_part += self.__destination.hash hashed_part += self.__destination.hash
hashed_part += self.__source.hash hashed_part += self.__source.hash
hashed_part += msgpack.packb(self.payload) hashed_part += msgpack.packb(self.payload)
self.hash = RNS.Identity.fullHash(hashed_part) self.hash = RNS.Identity.fullHash(hashed_part)
self.message_id = self.hash self.message_id = self.hash
signed_part = b"" signed_part = b""
signed_part += hashed_part signed_part += hashed_part
signed_part += self.hash signed_part += self.hash
self.signature = self.__source.sign(signed_part) self.signature = self.__source.sign(signed_part)
self.signature_validated = True
self.packed = b"" self.packed = b""
self.packed += self.__destination.hash self.packed += self.__destination.hash
self.packed += self.__source.hash self.packed += self.__source.hash
self.packed += self.signature self.packed += self.signature
packed_payload = msgpack.packb(self.payload) packed_payload = msgpack.packb(self.payload)
self.packed += packed_payload self.packed += packed_payload
self.packed_size = len(self.packed) self.packed_size = len(self.packed)
content_size = len(packed_payload) content_size = len(packed_payload)
# If no desired delivery method has been defined, # If no desired delivery method has been defined,
# one will be chosen according to these rules: # one will be chosen according to these rules:
if self.desired_method == None: if self.desired_method == None:
self.desired_method == LXMessage.DIRECT self.desired_method == LXMessage.DIRECT
# TODO: Expand rules to something more intelligent # TODO: Expand rules to something more intelligent
if self.desired_method == LXMessage.OPPORTUNISTIC: if self.desired_method == LXMessage.OPPORTUNISTIC:
if self.__destination.type == RNS.Destination.SINGLE: if self.__destination.type == RNS.Destination.SINGLE:
single_packet_content_limit = LXMessage.RSA_PACKET_MAX_CONTENT single_packet_content_limit = LXMessage.RSA_PACKET_MAX_CONTENT
elif self.__destination.type == RNS.Destination.PLAIN: elif self.__destination.type == RNS.Destination.PLAIN:
single_packet_content_limit = LXMessage.PLAIN_PACKET_MAX_CONTENT single_packet_content_limit = LXMessage.PLAIN_PACKET_MAX_CONTENT
if content_size > single_packet_content_limit: if content_size > single_packet_content_limit:
raise TypeError("LXMessage desired opportunistic delivery method, but content exceeds single-packet size.") raise TypeError("LXMessage desired opportunistic delivery method, but content exceeds single-packet size.")
else: else:
self.method = LXMessage.OPPORTUNISTIC self.method = LXMessage.OPPORTUNISTIC
self.representation = LXMessage.PACKET self.representation = LXMessage.PACKET
self.__delivery_destination = self.__destination self.__delivery_destination = self.__destination
elif self.desired_method == LXMessage.DIRECT or self.desired_method == LXMessage.PROPAGATED:
single_packet_content_limit = LXMessage.LINK_PACKET_MAX_CONTENT
if content_size <= single_packet_content_limit:
self.method = self.desired_method
self.representation = LXMessage.PACKET
else:
self.method = self.desired_method
self.representation = LXMessage.RESOURCE
else:
raise ValueError("Attempt to re-pack LXMessage "+str(self)+" that was already packed")
elif self.desired_method == LXMessage.DIRECT or self.desired_method == LXMessage.PROPAGATED:
single_packet_content_limit = LXMessage.LINK_PACKET_MAX_CONTENT
if content_size <= single_packet_content_limit:
self.method = self.desired_method
self.representation = LXMessage.PACKET
else:
self.method = self.desired_method
self.representation = LXMessage.RESOURCE
def send(self): def send(self):
if self.method == LXMessage.OPPORTUNISTIC: if self.method == LXMessage.OPPORTUNISTIC:
@ -274,9 +287,13 @@ class LXMessage:
source_hash = lxmf_bytes[LXMessage.DESTINATION_LENGTH:2*LXMessage.DESTINATION_LENGTH] source_hash = lxmf_bytes[LXMessage.DESTINATION_LENGTH:2*LXMessage.DESTINATION_LENGTH]
signature = lxmf_bytes[2*LXMessage.DESTINATION_LENGTH:2*LXMessage.DESTINATION_LENGTH+LXMessage.SIGNATURE_LENGTH] signature = lxmf_bytes[2*LXMessage.DESTINATION_LENGTH:2*LXMessage.DESTINATION_LENGTH+LXMessage.SIGNATURE_LENGTH]
packed_payload = lxmf_bytes[2*LXMessage.DESTINATION_LENGTH+LXMessage.SIGNATURE_LENGTH:] packed_payload = lxmf_bytes[2*LXMessage.DESTINATION_LENGTH+LXMessage.SIGNATURE_LENGTH:]
hashed_part = b"" + destination_hash + source_hash + packed_payload
message_hash = RNS.Identity.fullHash(hashed_part)
signed_part = b"" + hashed_part + message_hash
unpacked_payload = msgpack.unpackb(packed_payload) unpacked_payload = msgpack.unpackb(packed_payload)
destination = RNS.Identity.recall(destination_hash) destination = RNS.Identity.recall(destination_hash)
source = RNS.Identity.recall(source_hash) source_identity = RNS.Identity.recall(source_hash)
source = RNS.Destination(source_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "delivery")
timestamp = unpacked_payload[0] timestamp = unpacked_payload[0]
title_bytes = unpacked_payload[1] title_bytes = unpacked_payload[1]
content_bytes = unpacked_payload[2] content_bytes = unpacked_payload[2]
@ -291,15 +308,31 @@ class LXMessage:
destination_hash = destination_hash, destination_hash = destination_hash,
source_hash = source_hash) source_hash = source_hash)
message.hash = message_hash
message.signature = signature
message.incoming = True
message.timestamp = timestamp
message.packed = lxmf_bytes
message.set_title_from_bytes(title_bytes) message.set_title_from_bytes(title_bytes)
message.set_content_from_bytes(content_bytes) message.set_content_from_bytes(content_bytes)
message.timestamp = timestamp
try:
if source:
if source.identity.validate(signature, signed_part):
message.signature_validated = True
else:
message.signature_validated = False
message.unverified_reason = LXMessage.SIGNATURE_INVALID
else:
signature_validated = False
message.unverified_reason = LXMessage.SOURCE_UNKNOWN
RNS.log("LXMF message signature could not be validated, since source identity is unknown")
except Exception as e:
message.signature_validated = False
RNS.log("Error while validating LXMF message signature. The contained exception was: "+str(e), RNS.LOG_ERROR)
return message return message
@staticmethod
def unpack_from_file(lxmf_file_handle):
pass
class LXMRouter: class LXMRouter:
MAX_DELIVERY_ATTEMPTS = 3 MAX_DELIVERY_ATTEMPTS = 3
@ -352,26 +385,30 @@ class LXMRouter:
def lxmf_delivery(self, lxmf_data, destination_type = None): def lxmf_delivery(self, lxmf_data, destination_type = None):
try: try:
message = LXMessage.unpack_from_bytes(lxmf_data) message = LXMessage.unpack_from_bytes(lxmf_data)
if RNS.Reticulum.should_allow_unencrypted():
message.transport_encryption = "Consider unencrypted (Disabling encryption was allowed in Reticulum configuration)"
else:
if destination_type == RNS.Destination.SINGLE:
message.transport_encryption = "RSA-"+str(RNS.Identity.KEYSIZE)
elif destination_type == RNS.Destination.GROUP:
message.transport_encryption = "AES-128"
elif destination_type == RNS.Destination.LINK:
message.transport_encryption = "EC-SECP256R1"
else:
message.transport_encryption = None
if self.__delivery_callback != None:
self.__delivery_callback(message)
return True
except Exception as e: except Exception as e:
RNS.log("Could not assemble LXMF message from received data", RNS.LOG_NOTICE) RNS.log("Could not assemble LXMF message from received data", RNS.LOG_NOTICE)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG) RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
raise e
return False
if RNS.Reticulum.should_allow_unencrypted():
message.transport_encryption = "Consider unencrypted (Disabling encryption was allowed in Reticulum configuration)"
else:
if destination_type == RNS.Destination.SINGLE:
message.transport_encryption = "RSA-"+str(RNS.Identity.KEYSIZE)
elif destination_type == RNS.Destination.GROUP:
message.transport_encryption = "AES-128"
elif destination_type == RNS.Destination.LINK:
message.transport_encryption = "EC-SECP256R1"
else:
message.transport_encryption = None
if self.__delivery_callback != None:
self.__delivery_callback(message)
return True
def delivery_packet(self, data, packet): def delivery_packet(self, data, packet):
try: try:
@ -404,7 +441,7 @@ class LXMRouter:
def resource_transfer_concluded(self, resource): def resource_transfer_concluded(self, resource):
RNS.log("Transfer concluded for resource "+str(resource), RNS.LOG_DEBUG) RNS.log("Transfer concluded for resource "+str(resource), RNS.LOG_DEBUG)
if resource.status == RNS.Resource.COMPLETE: if resource.status == RNS.Resource.COMPLETE:
self.lxmf_delivery(resource.data, resource.link.type) self.lxmf_delivery(resource.data.read(), resource.link.type)
def jobloop(self): def jobloop(self):
while (True): while (True):