Compare commits

..

1 Commits

Author SHA1 Message Date
jacobeva
c6f0254695
Merge 9d744e2317 into 7f2154110c 2024-09-04 11:55:30 +01:00
19 changed files with 180 additions and 702 deletions

View File

@ -5,6 +5,7 @@
# of the packet. #
##########################################################
import os
import argparse
import RNS
@ -27,8 +28,19 @@ def server(configpath):
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# Randomly create a new identity for our echo server
# Load identity from file if it exist or randomley create
if configpath:
ifilepath = "%s/storage/identitiesy/%s" % (configpath,APP_NAME)
else:
ifilepath = "%s/storage/identities/%s" % (RNS.Reticulum.configdir,APP_NAME)
if os.path.exists(ifilepath):
# Load identity from file
server_identity = RNS.Identity.from_file(ifilepath)
RNS.log("loaded identity from file: "+ifilepath, RNS.LOG_VERBOSE)
else:
# Randomly create a new identity for our echo example
server_identity = RNS.Identity()
RNS.log("created new identity", RNS.LOG_VERBOSE)
# We create a destination that clients can query. We want
# to be able to verify echo replies to our clients, so we

View File

@ -28,8 +28,8 @@ def server(configpath):
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# Randomly create a new identity for our link example
server_identity = RNS.Identity()
RNS.log("created new identity", RNS.LOG_VERBOSE)
# We create a destination that clients can connect to. We
# want clients to create links to this destination, so we
@ -58,7 +58,7 @@ def server_loop(destination):
" running, waiting for a connection."
)
RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)")
RNS.log("Hit enter to manually send an announce (Ctrl-C or \"quit\" to quit)")
# We enter a loop that runs until the users exits.
# If the user hits enter, we will announce our server
@ -68,6 +68,12 @@ def server_loop(destination):
entered = input()
destination.announce()
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
if entered == "quit":
if latest_client_link:
latest_client_link.teardown()
break
print("")
exit()
# When a client establishes a link to our server
# destination, this function will be called with

View File

@ -1,343 +0,0 @@
##########################################################
# This RNS example demonstrates a simple client/server #
# echo utility that uses ratchets to rotate encryption #
# keys everytime an announce is sent. #
##########################################################
import argparse
import RNS
# 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_utilities"
##########################################################
#### Server Part #########################################
##########################################################
# This initialisation is executed when the users chooses
# to run as a server
def server(configpath):
global reticulum
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# TODO: Remove
RNS.loglevel = RNS.LOG_DEBUG
# Randomly create a new identity for our echo server
server_identity = RNS.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 = RNS.Destination(
server_identity,
RNS.Destination.IN,
RNS.Destination.SINGLE,
APP_NAME,
"ratchet",
"echo",
"request"
)
# Enable ratchets on the destination by providing a file
# path to store ratchets. In this example, we will just
# use a temporary file, but in real-world applications,
# it's extremely important to keep this file secure, since
# it contains encryption keys for the destination.
destination_hexhash = RNS.hexrep(echo_destination.hash, delimit=False)
echo_destination.enable_ratchets(f"/tmp/{destination_hexhash}.ratchets")
# We configure the destination to automatically prove all
# packets addressed to it. By doing this, RNS will automatically
# generate a proof for each incoming packet and transmit it
# back to the sender of that packet.
echo_destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
# Tell the destination which function in our program to
# run when a packet is received. We do this so we can
# print a log message when the server receives a request
echo_destination.set_packet_callback(server_callback)
# 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
RNS.log(
"Ratcheted echo server "+
RNS.prettyhexrep(destination.hash)+
" running, hit enter to manually send an announce (Ctrl-C to quit)"
)
# We enter a loop that runs until the users exits.
# If the user 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 = input()
destination.announce()
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
def server_callback(message, packet):
global reticulum
# Tell the user that we received an echo request, and
# that we are going to send a reply to the requester.
# Sending the proof is handled automatically, since we
# set up the destination to prove all incoming packets.
reception_stats = ""
if reticulum.is_connected_to_shared_instance:
reception_rssi = reticulum.get_packet_rssi(packet.packet_hash)
reception_snr = reticulum.get_packet_snr(packet.packet_hash)
if reception_rssi != None:
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
if reception_snr != None:
reception_stats += " [SNR "+str(reception_snr)+" dBm]"
else:
if packet.rssi != None:
reception_stats += " [RSSI "+str(packet.rssi)+" dBm]"
if packet.snr != None:
reception_stats += " [SNR "+str(packet.snr)+" dB]"
RNS.log("Received packet from echo client, proof sent"+reception_stats)
##########################################################
#### Client Part #########################################
##########################################################
# This initialisation is executed when the users chooses
# to run as a client
def client(destination_hexhash, configpath, timeout=None):
global reticulum
# We need a binary representation of the destination
# hash that was entered on the command line
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
raise ValueError(
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
)
destination_hash = bytes.fromhex(destination_hexhash)
except Exception as e:
RNS.log("Invalid destination entered. Check your input!")
RNS.log(str(e)+"\n")
exit()
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# We override the loglevel to provide feedback when
# an announce is received
if RNS.loglevel < RNS.LOG_INFO:
RNS.loglevel = RNS.LOG_INFO
# Tell the user that the client is ready!
RNS.log(
"Echo client ready, hit enter to send echo request to "+
destination_hexhash+
" (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:
input()
# Let's first check if RNS knows a path to the destination.
# If it does, we'll load the server identity and create a packet
if RNS.Transport.has_path(destination_hash):
# To address the server, we need to know it's public
# key, so we check if Reticulum 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 = RNS.Identity.recall(destination_hash)
# We got the correct identity instance from the
# recall method, so let's create an outgoing
# destination. We use the naming convention:
# example_utilities.ratchet.echo.request
# This matches the naming we specified in the
# server part of the code.
request_destination = RNS.Destination(
server_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
APP_NAME,
"ratchet",
"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 a random hash.
echo_request = RNS.Packet(request_destination, RNS.Identity.get_random_hash())
# Send the packet! If the packet is successfully
# sent, it will return a PacketReceipt instance.
packet_receipt = echo_request.send()
# If the user specified a timeout, we set this
# timeout on the packet receipt, and configure
# a callback function, that will get called if
# the packet times out.
if timeout != None:
packet_receipt.set_timeout(timeout)
packet_receipt.set_timeout_callback(packet_timed_out)
# We can then set a delivery callback on the receipt.
# This will get automatically called when a proof for
# this specific packet is received from the destination.
packet_receipt.set_delivery_callback(packet_delivered)
# Tell the user that the echo request was sent
RNS.log("Sent echo request to "+RNS.prettyhexrep(request_destination.hash))
else:
# If we do not know this destination, tell the
# user to wait for an announce to arrive.
RNS.log("Destination is not yet known. Requesting path...")
RNS.log("Hit enter to manually retry once an announce is received.")
RNS.Transport.request_path(destination_hash)
# This function is called when our reply destination
# receives a proof packet.
def packet_delivered(receipt):
global reticulum
if receipt.status == RNS.PacketReceipt.DELIVERED:
rtt = receipt.get_rtt()
if (rtt >= 1):
rtt = round(rtt, 3)
rttstring = str(rtt)+" seconds"
else:
rtt = round(rtt*1000, 3)
rttstring = str(rtt)+" milliseconds"
reception_stats = ""
if reticulum.is_connected_to_shared_instance:
reception_rssi = reticulum.get_packet_rssi(receipt.proof_packet.packet_hash)
reception_snr = reticulum.get_packet_snr(receipt.proof_packet.packet_hash)
if reception_rssi != None:
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
if reception_snr != None:
reception_stats += " [SNR "+str(reception_snr)+" dB]"
else:
if receipt.proof_packet != None:
if receipt.proof_packet.rssi != None:
reception_stats += " [RSSI "+str(receipt.proof_packet.rssi)+" dBm]"
if receipt.proof_packet.snr != None:
reception_stats += " [SNR "+str(receipt.proof_packet.snr)+" dB]"
RNS.log(
"Valid reply received from "+
RNS.prettyhexrep(receipt.destination.hash)+
", round-trip time is "+rttstring+
reception_stats
)
# This function is called if a packet times out.
def packet_timed_out(receipt):
if receipt.status == RNS.PacketReceipt.FAILED:
RNS.log("Packet "+RNS.prettyhexrep(receipt.hash)+" timed out")
##########################################################
#### Program Startup #####################################
##########################################################
# This part of the program gets run at startup,
# and parses input from the user, and then starts
# the desired program mode.
if __name__ == "__main__":
try:
parser = argparse.ArgumentParser(description="Simple ratcheted echo server and client utility")
parser.add_argument(
"-s",
"--server",
action="store_true",
help="wait for incoming packets from clients"
)
parser.add_argument(
"-t",
"--timeout",
action="store",
metavar="s",
default=None,
help="set a reply timeout in seconds",
type=float
)
parser.add_argument("--config",
action="store",
default=None,
help="path to alternative Reticulum 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:
if args.config:
configarg = args.config
else:
configarg = None
if args.timeout:
timeoutarg = float(args.timeout)
else:
timeoutarg = None
if (args.destination == None):
print("")
parser.print_help()
print("")
else:
client(args.destination, configarg, timeout=timeoutarg)
except KeyboardInterrupt:
print("")
exit()

View File

@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@ -20,13 +20,11 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os
import math
import time
import RNS
from RNS.Cryptography import Fernet
from .vendor import umsgpack as umsgpack
class Callbacks:
def __init__(self):
@ -40,14 +38,14 @@ class Destination:
instances are used both to create outgoing and incoming endpoints. The
destination type will decide if encryption, and what type, is used in
communication with the endpoint. A destination can also announce its
presence on the network, which will distribute necessary keys for
presence on the network, which will also distribute necessary keys for
encrypted communication with it.
:param identity: An instance of :ref:`RNS.Identity<api-identity>`. Can hold only public keys for an outgoing destination, or holding private keys for an ingoing.
:param direction: ``RNS.Destination.IN`` or ``RNS.Destination.OUT``.
:param type: ``RNS.Destination.SINGLE``, ``RNS.Destination.GROUP`` or ``RNS.Destination.PLAIN``.
:param app_name: A string specifying the app name.
:param \\*aspects: Any non-zero number of string arguments.
:param \*aspects: Any non-zero number of string arguments.
"""
# Constants
@ -72,7 +70,6 @@ class Destination:
directions = [IN, OUT]
PR_TAG_WINDOW = 30
RATCHET_COUNT = 512
@staticmethod
def expand_name(identity, app_name, *aspects):
@ -140,8 +137,6 @@ class Destination:
self.type = type
self.direction = direction
self.proof_strategy = Destination.PROVE_NONE
self.ratchets = None
self.ratchets_path = None
self.mtu = 0
self.path_responses = {}
@ -175,57 +170,6 @@ class Destination:
"""
return "<"+self.name+"/"+self.hexhash+">"
def enable_ratchets(self, ratchets_path):
if ratchets_path != None:
if os.path.isfile(ratchets_path):
try:
ratchets_file = open(ratchets_path, "rb")
persisted_data = umsgpack.unpackb(ratchets_file.read())
if "signature" in persisted_data and "ratchets" in persisted_data:
if self.identity.validate(persisted_data["signature"], persisted_data["ratchets"]):
self.ratchets = umsgpack.unpackb(persisted_data["ratchets"])
self.ratchets_path = ratchets_path
else:
raise KeyError("Invalid ratchet file signature")
except Exception as e:
self.ratchets = None
self.ratchets_path = None
raise OSError("Could not read ratchet file contents for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
else:
RNS.log("No existing ratchet data found, initialising new ratchet file for "+str(self), RNS.LOG_DEBUG)
self.ratchets = []
self.ratchets_path = ratchets_path
self.persist_ratchets()
RNS.log("Ratchets enabled on "+str(self), RNS.LOG_DEBUG) # TODO: Remove
return True
else:
raise ValueError("No ratchet file path specified for "+str(self))
def persist_ratchets(self):
try:
packed_ratchets = umsgpack.packb(self.ratchets)
persisted_data = {"signature": self.sign(packed_ratchets), "ratchets": packed_ratchets}
ratchets_file = open(self.ratchets_path, "wb")
ratchets_file.write(umsgpack.packb(persisted_data))
ratchets_file.close()
except Exception as e:
self.ratchets = None
self.ratchets_path = None
raise OSError("Could not write ratchet file contents for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def rotate_ratchets(self):
if self.ratchets != None:
RNS.log("Rotating ratchets for "+str(self), RNS.LOG_DEBUG) # TODO: Remove
new_ratchet = RNS.Identity._generate_ratchet()
self.ratchets.insert(0, new_ratchet)
if len (self.ratchets) > Destination.RATCHET_COUNT:
self.ratchets = self.ratchets[:Destination.RATCHET_COUNT]
self.persist_ratchets()
else:
raise SystemError("Cannot rotate ratchet on "+str(self)+", ratchets are not enabled")
def announce(self, app_data=None, path_response=False, attached_interface=None, tag=None, send=True):
"""
@ -241,7 +185,6 @@ class Destination:
if self.direction != Destination.IN:
raise TypeError("Only IN destination types can be announced")
ratchet = b""
now = time.time()
stale_responses = []
for entry_tag in self.path_responses:
@ -268,13 +211,6 @@ class Destination:
destination_hash = self.hash
random_hash = RNS.Identity.get_random_hash()[0:5]+int(time.time()).to_bytes(5, "big")
if self.ratchets != None:
self.rotate_ratchets()
ratchet = RNS.Identity._ratchet_public_bytes(self.ratchets[0])
# TODO: Remove debug output
RNS.log(f"Including ratchet {RNS.prettyhexrep(RNS.Identity.truncated_hash(ratchet))} in announce", RNS.LOG_DEBUG)
if app_data == None and self.default_app_data != None:
if isinstance(self.default_app_data, bytes):
app_data = self.default_app_data
@ -283,12 +219,13 @@ class Destination:
if isinstance(returned_app_data, bytes):
app_data = returned_app_data
signed_data = self.hash+self.identity.get_public_key()+self.name_hash+random_hash+ratchet
signed_data = self.hash+self.identity.get_public_key()+self.name_hash+random_hash
if app_data != None:
signed_data += app_data
signature = self.identity.sign(signed_data)
announce_data = self.identity.get_public_key()+self.name_hash+random_hash+ratchet+signature
announce_data = self.identity.get_public_key()+self.name_hash+random_hash+signature
if app_data != None:
announce_data += app_data
@ -300,13 +237,8 @@ class Destination:
else:
announce_context = RNS.Packet.NONE
if ratchet:
context_flag = RNS.Packet.FLAG_SET
else:
context_flag = RNS.Packet.FLAG_UNSET
announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context, attached_interface = attached_interface)
announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context,
attached_interface = attached_interface, context_flag=context_flag)
if send:
announce_packet.send()
else:
@ -492,7 +424,7 @@ class Destination:
return plaintext
if self.type == Destination.SINGLE and self.identity != None:
return self.identity.encrypt(plaintext, ratchet=RNS.Identity.get_ratchet(self.hash))
return self.identity.encrypt(plaintext)
if self.type == Destination.GROUP:
if hasattr(self, "prv") and self.prv != None:
@ -517,7 +449,7 @@ class Destination:
return ciphertext
if self.type == Destination.SINGLE and self.identity != None:
return self.identity.decrypt(ciphertext, ratchets=self.ratchets)
return self.identity.decrypt(ciphertext)
if self.type == Destination.GROUP:
if hasattr(self, "prv") and self.prv != None:

View File

@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors.
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@ -52,9 +52,6 @@ class Identity:
X25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.
"""
RATCHETSIZE = 256
RATCHET_EXPIRY = 60*60*24*30
# Non-configurable constants
FERNET_OVERHEAD = RNS.Cryptography.Fernet.FERNET_OVERHEAD
AES128_BLOCKSIZE = 16 # In bytes
@ -70,7 +67,6 @@ class Identity:
# Storage
known_destinations = {}
known_ratchets = {}
@staticmethod
def remember(packet_hash, destination_hash, public_key, app_data = None):
@ -225,103 +221,20 @@ class Identity:
"""
return Identity.truncated_hash(os.urandom(Identity.TRUNCATED_HASHLENGTH//8))
@staticmethod
def _ratchet_public_bytes(ratchet):
return X25519PrivateKey.from_private_bytes(ratchet).public_key().public_bytes()
@staticmethod
def _generate_ratchet():
ratchet_prv = X25519PrivateKey.generate()
ratchet_pub = ratchet_prv.public_key()
return ratchet_prv.private_bytes()
@staticmethod
def _remember_ratchet(destination_hash, ratchet):
RNS.log(f"Remembering ratchet {RNS.prettyhexrep(Identity.truncated_hash(ratchet))} for {RNS.prettyhexrep(destination_hash)}", RNS.LOG_DEBUG) # TODO: Remove
try:
Identity.known_ratchets[destination_hash] = ratchet
hexhash = RNS.hexrep(destination_hash, delimit=False)
ratchet_data = {"ratchet": ratchet, "received": time.time()}
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
if not os.path.isdir(ratchetdir):
os.makedirs(ratchetdir)
outpath = f"{ratchetdir}/{hexhash}.out"
finalpath = f"{ratchetdir}/{hexhash}"
ratchet_file = open(outpath, "wb")
ratchet_file.write(umsgpack.packb(ratchet_data))
ratchet_file.close()
os.rename(outpath, finalpath)
except Exception as e:
RNS.log(f"Could not persist ratchet for {RNS.prettyhexrep(destination_hash)} to storage.", RNS.LOG_ERROR)
RNS.log(f"The contained exception was: {e}")
RNS.trace_exception(e)
@staticmethod
def get_ratchet(destination_hash):
if not destination_hash in Identity.known_ratchets:
RNS.log(f"Trying to load ratchet for {RNS.prettyhexrep(destination_hash)} from storage") # TODO: Remove
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
hexhash = RNS.hexrep(destination_hash, delimit=False)
ratchet_path = f"{ratchetdir}/hexhash"
if os.path.isfile(ratchet_path):
try:
ratchet_file = open(ratchet_path, "rb")
ratchet_data = umsgpack.unpackb(ratchets_file.read())
if time.time() < ratchet_data["received"]+Identity.RATCHET_EXPIRY and len(ratchet_data["ratchet"]) == Identity.RATCHETSIZE//8:
Identity.known_ratchets[destination_hash] = ratchet_data["ratchet"]
else:
return None
except Exception as e:
RNS.log(f"An error occurred while loading ratchet data for {RNS.prettyhexrep(destination_hash)} from storage.", RNS.LOG_ERROR)
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
return None
if destination_hash in Identity.known_ratchets:
return Identity.known_ratchets[destination_hash]
else:
return None
@staticmethod
def validate_announce(packet, only_validate_signature=False):
try:
if packet.packet_type == RNS.Packet.ANNOUNCE:
keysize = Identity.KEYSIZE//8
ratchetsize = Identity.RATCHETSIZE//8
name_hash_len = Identity.NAME_HASH_LENGTH//8
sig_len = Identity.SIGLENGTH//8
destination_hash = packet.destination_hash
# Get public key bytes from announce
public_key = packet.data[:keysize]
# If the packet context flag is set,
# this announce contains a new ratchet
if packet.context_flag == RNS.Packet.FLAG_SET:
name_hash = packet.data[keysize:keysize+name_hash_len ]
random_hash = packet.data[keysize+name_hash_len:keysize+name_hash_len+10]
ratchet = packet.data[keysize+name_hash_len+10:keysize+name_hash_len+10+ratchetsize]
signature = packet.data[keysize+name_hash_len+10+ratchetsize:keysize+name_hash_len+10+ratchetsize+sig_len]
public_key = packet.data[:Identity.KEYSIZE//8]
name_hash = packet.data[Identity.KEYSIZE//8:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8]
random_hash = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10]
signature = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8]
app_data = b""
if len(packet.data) > keysize+name_hash_len+10+sig_len+ratchetsize:
app_data = packet.data[keysize+name_hash_len+10+sig_len+ratchetsize:]
if len(packet.data) > Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:
app_data = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:]
# If the packet context flag is not set,
# this announce does not contain a ratchet
else:
ratchet = b""
name_hash = packet.data[keysize:keysize+name_hash_len]
random_hash = packet.data[keysize+name_hash_len:keysize+name_hash_len+10]
signature = packet.data[keysize+name_hash_len+10:keysize+name_hash_len+10+sig_len]
app_data = b""
if len(packet.data) > keysize+name_hash_len+10+sig_len:
app_data = packet.data[keysize+name_hash_len+10+sig_len:]
signed_data = destination_hash+public_key+name_hash+random_hash+ratchet+app_data
signed_data = destination_hash+public_key+name_hash+random_hash+app_data
if not len(packet.data) > Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:
app_data = None
@ -368,9 +281,6 @@ class Identity:
else:
RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received on "+str(packet.receiving_interface)+signal_str, RNS.LOG_EXTREME)
if ratchet:
Identity._remember_ratchet(destination_hash, ratchet)
return True
else:
@ -559,7 +469,7 @@ class Identity:
def get_context(self):
return None
def encrypt(self, plaintext, ratchet=None):
def encrypt(self, plaintext):
"""
Encrypts information for the identity.
@ -571,13 +481,7 @@ class Identity:
ephemeral_key = X25519PrivateKey.generate()
ephemeral_pub_bytes = ephemeral_key.public_key().public_bytes()
if ratchet != None:
RNS.log(f"Encrypting with ratchet {RNS.prettyhexrep(RNS.Identity.truncated_hash(ratchet))}", RNS.LOG_DEBUG) # TODO: Remove
target_public_key = X25519PublicKey.from_public_bytes(ratchet)
else:
target_public_key = self.pub
shared_key = ephemeral_key.exchange(target_public_key)
shared_key = ephemeral_key.exchange(self.pub)
derived_key = RNS.Cryptography.hkdf(
length=32,
@ -595,7 +499,7 @@ class Identity:
raise KeyError("Encryption failed because identity does not hold a public key")
def decrypt(self, ciphertext_token, ratchets=None):
def decrypt(self, ciphertext_token):
"""
Decrypts information for the identity.
@ -609,34 +513,9 @@ class Identity:
try:
peer_pub_bytes = ciphertext_token[:Identity.KEYSIZE//8//2]
peer_pub = X25519PublicKey.from_public_bytes(peer_pub_bytes)
ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:]
if ratchets:
for ratchet in ratchets:
try:
ratchet_prv = X25519PrivateKey.from_private_bytes(ratchet)
shared_key = ratchet_prv.exchange(peer_pub)
derived_key = RNS.Cryptography.hkdf(
length=32,
derive_from=shared_key,
salt=self.get_salt(),
context=self.get_context(),
)
fernet = Fernet(derived_key)
plaintext = fernet.decrypt(ciphertext)
# TODO: Remove
RNS.log(f"Decrypted with ratchet {RNS.prettyhexrep(RNS.Identity.truncated_hash(ratchet_prv.public_key().public_bytes()))}", RNS.LOG_DEBUG)
break
except Exception as e:
pass
# RNS.log("Decryption using this ratchet failed", RNS.LOG_DEBUG) # TODO: Remove
if plaintext == None:
shared_key = self.prv.exchange(peer_pub)
derived_key = RNS.Cryptography.hkdf(
length=32,
derive_from=shared_key,
@ -645,6 +524,7 @@ class Identity:
)
fernet = Fernet(derived_key)
ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:]
plaintext = fernet.decrypt(ciphertext)
except Exception as e:

View File

@ -117,42 +117,40 @@ class KISS():
SX1280 = 0x21
def int_data_cmd_to_index(int_data_cmd):
if int_data_cmd == KISS.CMD_INT0_DATA:
match int_data_cmd:
case KISS.CMD_INT0_DATA:
return 0
elif int_data_cmd == KISS.CMD_INT1_DATA:
case KISS.CMD_INT1_DATA:
return 1
elif int_data_cmd == KISS.CMD_INT2_DATA:
case KISS.CMD_INT2_DATA:
return 2
elif int_data_cmd == KISS.CMD_INT3_DATA:
case KISS.CMD_INT3_DATA:
return 3
elif int_data_cmd == KISS.CMD_INT4_DATA:
case KISS.CMD_INT4_DATA:
return 4
elif int_data_cmd == KISS.CMD_INT5_DATA:
case KISS.CMD_INT5_DATA:
return 5
elif int_data_cmd == KISS.CMD_INT6_DATA:
case KISS.CMD_INT6_DATA:
return 6
elif int_data_cmd == KISS.CMD_INT7_DATA:
case KISS.CMD_INT7_DATA:
return 7
elif int_data_cmd == KISS.CMD_INT8_DATA:
case KISS.CMD_INT8_DATA:
return 8
elif int_data_cmd == KISS.CMD_INT9_DATA:
case KISS.CMD_INT9_DATA:
return 9
elif int_data_cmd == KISS.CMD_INT10_DATA:
case KISS.CMD_INT10_DATA:
return 10
elif int_data_cmd == KISS.CMD_INT11_DATA:
case KISS.CMD_INT11_DATA:
return 11
else:
return 0
def interface_type_to_str(interface_type):
if interface_type == KISS.SX126X or interface_type == KISS.SX1262:
match interface_type:
case KISS.SX126X | KISS.SX1262:
return "SX126X"
elif interface_type == KISS.SX127X or interface_type == KISS.SX1276 or interface_type == KISS.SX1278:
case KISS.SX127X | KISS.SX1276 | KISS.SX1278:
return "SX127X"
elif interface_type == KISS.SX128X or interface_type == KISS.SX1280:
case KISS.SX128X | KISS.SX1280:
return "SX128X"
else:
return "SX127X"
@staticmethod
def escape(data):
@ -918,43 +916,44 @@ class RNodeSubInterface(Interface):
super().__init__()
if index == 0:
match index:
case 0:
sel_cmd = KISS.CMD_SEL_INT0
data_cmd= KISS.CMD_INT0_DATA
elif index == 1:
case 1:
sel_cmd = KISS.CMD_SEL_INT1
data_cmd= KISS.CMD_INT1_DATA
elif index == 2:
case 2:
sel_cmd = KISS.CMD_SEL_INT2
data_cmd= KISS.CMD_INT2_DATA
elif index == 3:
case 3:
sel_cmd = KISS.CMD_SEL_INT3
data_cmd= KISS.CMD_INT3_DATA
elif index == 4:
case 4:
sel_cmd = KISS.CMD_SEL_INT4
data_cmd= KISS.CMD_INT4_DATA
elif index == 5:
case 5:
sel_cmd = KISS.CMD_SEL_INT5
data_cmd= KISS.CMD_INT5_DATA
elif index == 6:
case 6:
sel_cmd = KISS.CMD_SEL_INT6
data_cmd= KISS.CMD_INT6_DATA
elif index == 7:
case 7:
sel_cmd = KISS.CMD_SEL_INT7
data_cmd= KISS.CMD_INT7_DATA
elif index == 8:
case 8:
sel_cmd = KISS.CMD_SEL_INT8
data_cmd= KISS.CMD_INT8_DATA
elif index == 9:
case 9:
sel_cmd = KISS.CMD_SEL_INT9
data_cmd= KISS.CMD_INT9_DATA
elif index == 10:
case 10:
sel_cmd = KISS.CMD_SEL_INT10
data_cmd= KISS.CMD_INT10_DATA
elif index == 11:
case 11:
sel_cmd = KISS.CMD_SEL_INT11
data_cmd= KISS.CMD_INT11_DATA
else:
case _:
sel_cmd = KISS.CMD_SEL_INT0
data_cmd= KISS.CMD_INT0_DATA

View File

@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors.
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@ -142,7 +142,6 @@ class Link:
raise TypeError("Links can only be established to the \"single\" destination type")
self.rtt = None
self.establishment_cost = 0
self.establishment_rate = None
self.callbacks = LinkCallbacks()
self.resource_strategy = Link.ACCEPT_NONE
self.outgoing_resources = []

View File

@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors.
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@ -83,10 +83,6 @@ class Packet:
LRRTT = 0xFE # Packet is a link request round-trip time measurement
LRPROOF = 0xFF # Packet is a link request proof
# Context flag values
FLAG_SET = 0x01
FLAG_UNSET = 0x00
# This is used to calculate allowable
# payload sizes
HEADER_MAXSIZE = RNS.Reticulum.HEADER_MAXSIZE
@ -106,9 +102,7 @@ class Packet:
TIMEOUT_PER_HOP = RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST,
header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True, context_flag=FLAG_UNSET):
def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST, header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True):
if destination != None:
if transport_type == None:
transport_type = RNS.Transport.BROADCAST
@ -117,7 +111,6 @@ class Packet:
self.packet_type = packet_type
self.transport_type = transport_type
self.context = context
self.context_flag = context_flag
self.hops = 0;
self.destination = destination
@ -149,10 +142,9 @@ class Packet:
def get_packed_flags(self):
if self.context == Packet.LRPROOF:
packed_flags = (self.header_type << 6) | (self.context_flag << 5) | (self.transport_type << 4) | (RNS.Destination.LINK << 2) | self.packet_type
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (RNS.Destination.LINK << 2) | self.packet_type
else:
packed_flags = (self.header_type << 6) | (self.context_flag << 5) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
return packed_flags
def pack(self):
@ -224,8 +216,7 @@ class Packet:
self.hops = self.raw[1]
self.header_type = (self.flags & 0b01000000) >> 6
self.context_flag = (self.flags & 0b00100000) >> 5
self.transport_type = (self.flags & 0b00010000) >> 4
self.transport_type = (self.flags & 0b00110000) >> 4
self.destination_type = (self.flags & 0b00001100) >> 2
self.packet_type = (self.flags & 0b00000011)

View File

@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors.
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal

View File

@ -1,6 +1,6 @@
# MIT License
#
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors.
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@ -403,8 +403,7 @@ class Transport:
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = attached_interface,
context_flag = packet.context_flag,
attached_interface = attached_interface
)
new_packet.hops = announce_entry[4]

View File

@ -1 +1 @@
__version__ = "0.7.7"
__version__ = "0.7.6"

Binary file not shown.

Binary file not shown.

View File

@ -693,8 +693,7 @@ Wire Format
[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
* The HEADER field is 2 bytes long.
* Byte 1: [IFAC Flag], [Header Type], [Context Flag], [Propagation Type],
[Destination Type] and [Packet Type]
* Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
* Byte 2: Number of hops
* Interface Access Code field if the IFAC flag was set.
@ -726,16 +725,12 @@ Wire Format
type 2 1 Two byte header, two 16 byte address fields
Context Flag
-----------------
unset 0 The context flag is used for various types
set 1 of signalling, depending on packet context
Propagation Types
-----------------
broadcast 0
transport 1
broadcast 00
transport 01
reserved 10
reserved 11
Destination Types

View File

@ -656,6 +656,7 @@ the Packet interface.</p>
<span class="c1"># of the packet. #</span>
<span class="c1">##########################################################</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">import</span> <span class="nn">RNS</span>
@ -678,8 +679,19 @@ the Packet interface.</p>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
<span class="c1"># Randomly create a new identity for our echo server</span>
<span class="c1"># Load identity from file if it exist or randomley create</span>
<span class="k">if</span> <span class="n">configpath</span><span class="p">:</span>
<span class="n">ifilepath</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="si">%s</span><span class="s2">/storage/identitiesy/</span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">configpath</span><span class="p">,</span><span class="n">APP_NAME</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">ifilepath</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="si">%s</span><span class="s2">/storage/identities/</span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">configdir</span><span class="p">,</span><span class="n">APP_NAME</span><span class="p">)</span>
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">ifilepath</span><span class="p">):</span>
<span class="c1"># Load identity from file</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="o">.</span><span class="n">from_file</span><span class="p">(</span><span class="n">ifilepath</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;loaded identity from file: &quot;</span><span class="o">+</span><span class="n">ifilepath</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># Randomly create a new identity for our echo example</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;created new identity&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</span><span class="p">)</span>
<span class="c1"># We create a destination that clients can query. We want</span>
<span class="c1"># to be able to verify echo replies to our clients, so we</span>
@ -1018,8 +1030,8 @@ destination, and passing traffic back and forth over the link.</p>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
<span class="c1"># Randomly create a new identity for our link example</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;created new identity&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</span><span class="p">)</span>
<span class="c1"># We create a destination that clients can connect to. We</span>
<span class="c1"># want clients to create links to this destination, so we</span>
@ -1048,7 +1060,7 @@ destination, and passing traffic back and forth over the link.</p>
<span class="s2">&quot; running, waiting for a connection.&quot;</span>
<span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Hit enter to manually send an announce (Ctrl-C to quit)&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Hit enter to manually send an announce (Ctrl-C or </span><span class="se">\&quot;</span><span class="s2">quit</span><span class="se">\&quot;</span><span class="s2"> to quit)&quot;</span><span class="p">)</span>
<span class="c1"># We enter a loop that runs until the users exits.</span>
<span class="c1"># If the user hits enter, we will announce our server</span>
@ -1058,6 +1070,12 @@ destination, and passing traffic back and forth over the link.</p>
<span class="n">entered</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
<span class="n">destination</span><span class="o">.</span><span class="n">announce</span><span class="p">()</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Sent announce from &quot;</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">))</span>
<span class="k">if</span> <span class="n">entered</span> <span class="o">==</span> <span class="s2">&quot;quit&quot;</span><span class="p">:</span>
<span class="k">if</span> <span class="n">latest_client_link</span><span class="p">:</span>
<span class="n">latest_client_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
<span class="k">break</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="c1"># When a client establishes a link to our server</span>
<span class="c1"># destination, this function will be called with</span>

View File

@ -532,7 +532,7 @@ communication for the identity. Be very careful with this method.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.encrypt">
<span class="sig-name descname"><span class="pre">encrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">plaintext</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ratchet</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.encrypt" title="Permalink to this definition">#</a></dt>
<span class="sig-name descname"><span class="pre">encrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">plaintext</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.encrypt" title="Permalink to this definition">#</a></dt>
<dd><p>Encrypts information for the identity.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
@ -549,7 +549,7 @@ communication for the identity. Be very careful with this method.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.decrypt">
<span class="sig-name descname"><span class="pre">decrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ciphertext_token</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ratchets</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.decrypt" title="Permalink to this definition">#</a></dt>
<span class="sig-name descname"><span class="pre">decrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ciphertext_token</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.decrypt" title="Permalink to this definition">#</a></dt>
<dd><p>Decrypts information for the identity.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
@ -611,7 +611,7 @@ communication for the identity. Be very careful with this method.</p>
instances are used both to create outgoing and incoming endpoints. The
destination type will decide if encryption, and what type, is used in
communication with the endpoint. A destination can also announce its
presence on the network, which will distribute necessary keys for
presence on the network, which will also distribute necessary keys for
encrypted communication with it.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>

File diff suppressed because one or more lines are too long

View File

@ -927,8 +927,7 @@ A Reticulum packet is composed of the following fields:
[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
* The HEADER field is 2 bytes long.
* Byte 1: [IFAC Flag], [Header Type], [Context Flag], [Propagation Type],
[Destination Type] and [Packet Type]
* Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
* Byte 2: Number of hops
* Interface Access Code field if the IFAC flag was set.
@ -960,16 +959,12 @@ type 1 0 Two byte header, one 16 byte address field
type 2 1 Two byte header, two 16 byte address fields
Context Flag
-----------------
unset 0 The context flag is used for various types
set 1 of signalling, depending on packet context
Propagation Types
-----------------
broadcast 0
transport 1
broadcast 00
transport 01
reserved 10
reserved 11
Destination Types

View File

@ -693,8 +693,7 @@ Wire Format
[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
* The HEADER field is 2 bytes long.
* Byte 1: [IFAC Flag], [Header Type], [Context Flag], [Propagation Type],
[Destination Type] and [Packet Type]
* Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
* Byte 2: Number of hops
* Interface Access Code field if the IFAC flag was set.
@ -726,16 +725,12 @@ Wire Format
type 2 1 Two byte header, two 16 byte address fields
Context Flag
-----------------
unset 0 The context flag is used for various types
set 1 of signalling, depending on packet context
Propagation Types
-----------------
broadcast 0
transport 1
broadcast 00
transport 01
reserved 10
reserved 11
Destination Types