Compare commits

...

14 Commits

Author SHA1 Message Date
jeremybox
f070fe4a21
Merge b4ac3df2d0 into 6ac393bbcd 2024-09-04 14:26:15 -04:00
Mark Qvist
6ac393bbcd Updated ratchet count 2024-09-04 19:33:04 +02:00
Mark Qvist
0c04663942 Merge branch 'master' of github.com:markqvist/Reticulum 2024-09-04 19:08:26 +02:00
Mark Qvist
bfa216de54 Cleanup 2024-09-04 19:08:18 +02:00
markqvist
a4b1606921
Merge pull request #542 from jacobeva/master
Remove match and therefore dependency on Python 3.10
2024-09-04 19:01:08 +02:00
Mark Qvist
ad0db9c95c Updated version 2024-09-04 17:47:26 +02:00
Mark Qvist
2fdcbec860 Updated documentation 2024-09-04 17:40:02 +02:00
Mark Qvist
dd889d16d4 Added ratchets example 2024-09-04 17:37:34 +02:00
Mark Qvist
a11f14e75f Implemented ratchets 2024-09-04 17:37:18 +02:00
Mark Qvist
c32086c6f1 Updated documentation 2024-09-04 17:00:11 +02:00
Mark Qvist
54eaff203f Implemented ratchet generation, rotation and persistence 2024-09-04 12:02:55 +02:00
Mark Qvist
2bf75f60bc Cleanup 2024-09-04 11:23:08 +02:00
Mark Qvist
3f64141455 Fixed missing establishment_rate property init on Link 2024-09-04 10:32:54 +02:00
jacob.eva
063ea2bb7a
Remove match and therefore dependency on Python 3.10 2024-09-03 22:12:25 +01:00
19 changed files with 702 additions and 180 deletions

View File

@ -5,7 +5,6 @@
# of the packet. # # of the packet. #
########################################################## ##########################################################
import os
import argparse import argparse
import RNS import RNS
@ -28,19 +27,8 @@ def server(configpath):
# We must first initialise Reticulum # We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath) reticulum = RNS.Reticulum(configpath)
# Load identity from file if it exist or randomley create # Randomly create a new identity for our echo server
if configpath: server_identity = RNS.Identity()
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 # We create a destination that clients can query. We want
# to be able to verify echo replies to our clients, so we # 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 # We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath) reticulum = RNS.Reticulum(configpath)
# Randomly create a new identity for our link example
server_identity = RNS.Identity() server_identity = RNS.Identity()
RNS.log("created new identity", RNS.LOG_VERBOSE)
# We create a destination that clients can connect to. We # We create a destination that clients can connect to. We
# want clients to create links to this destination, so we # want clients to create links to this destination, so we
@ -58,7 +58,7 @@ def server_loop(destination):
" running, waiting for a connection." " running, waiting for a connection."
) )
RNS.log("Hit enter to manually send an announce (Ctrl-C or \"quit\" to quit)") RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)")
# We enter a loop that runs until the users exits. # We enter a loop that runs until the users exits.
# If the user hits enter, we will announce our server # If the user hits enter, we will announce our server
@ -68,12 +68,6 @@ def server_loop(destination):
entered = input() entered = input()
destination.announce() destination.announce()
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash)) 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 # When a client establishes a link to our server
# destination, this function will be called with # destination, this function will be called with

343
Examples/Ratchets.py Normal file
View File

@ -0,0 +1,343 @@
##########################################################
# 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 # MIT License
# #
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors # Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@ -20,11 +20,13 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
import os
import math import math
import time import time
import RNS import RNS
from RNS.Cryptography import Fernet from RNS.Cryptography import Fernet
from .vendor import umsgpack as umsgpack
class Callbacks: class Callbacks:
def __init__(self): def __init__(self):
@ -38,14 +40,14 @@ class Destination:
instances are used both to create outgoing and incoming endpoints. The instances are used both to create outgoing and incoming endpoints. The
destination type will decide if encryption, and what type, is used in destination type will decide if encryption, and what type, is used in
communication with the endpoint. A destination can also announce its communication with the endpoint. A destination can also announce its
presence on the network, which will also distribute necessary keys for presence on the network, which will distribute necessary keys for
encrypted communication with it. 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 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 direction: ``RNS.Destination.IN`` or ``RNS.Destination.OUT``.
:param type: ``RNS.Destination.SINGLE``, ``RNS.Destination.GROUP`` or ``RNS.Destination.PLAIN``. :param type: ``RNS.Destination.SINGLE``, ``RNS.Destination.GROUP`` or ``RNS.Destination.PLAIN``.
:param app_name: A string specifying the app name. :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 # Constants
@ -70,6 +72,7 @@ class Destination:
directions = [IN, OUT] directions = [IN, OUT]
PR_TAG_WINDOW = 30 PR_TAG_WINDOW = 30
RATCHET_COUNT = 512
@staticmethod @staticmethod
def expand_name(identity, app_name, *aspects): def expand_name(identity, app_name, *aspects):
@ -137,6 +140,8 @@ class Destination:
self.type = type self.type = type
self.direction = direction self.direction = direction
self.proof_strategy = Destination.PROVE_NONE self.proof_strategy = Destination.PROVE_NONE
self.ratchets = None
self.ratchets_path = None
self.mtu = 0 self.mtu = 0
self.path_responses = {} self.path_responses = {}
@ -170,6 +175,57 @@ class Destination:
""" """
return "<"+self.name+"/"+self.hexhash+">" 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): def announce(self, app_data=None, path_response=False, attached_interface=None, tag=None, send=True):
""" """
@ -185,6 +241,7 @@ class Destination:
if self.direction != Destination.IN: if self.direction != Destination.IN:
raise TypeError("Only IN destination types can be announced") raise TypeError("Only IN destination types can be announced")
ratchet = b""
now = time.time() now = time.time()
stale_responses = [] stale_responses = []
for entry_tag in self.path_responses: for entry_tag in self.path_responses:
@ -211,6 +268,13 @@ class Destination:
destination_hash = self.hash destination_hash = self.hash
random_hash = RNS.Identity.get_random_hash()[0:5]+int(time.time()).to_bytes(5, "big") 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 app_data == None and self.default_app_data != None:
if isinstance(self.default_app_data, bytes): if isinstance(self.default_app_data, bytes):
app_data = self.default_app_data app_data = self.default_app_data
@ -219,13 +283,12 @@ class Destination:
if isinstance(returned_app_data, bytes): if isinstance(returned_app_data, bytes):
app_data = returned_app_data app_data = returned_app_data
signed_data = self.hash+self.identity.get_public_key()+self.name_hash+random_hash signed_data = self.hash+self.identity.get_public_key()+self.name_hash+random_hash+ratchet
if app_data != None: if app_data != None:
signed_data += app_data signed_data += app_data
signature = self.identity.sign(signed_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: if app_data != None:
announce_data += app_data announce_data += app_data
@ -237,8 +300,13 @@ class Destination:
else: else:
announce_context = RNS.Packet.NONE announce_context = RNS.Packet.NONE
announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context, attached_interface = attached_interface) 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, context_flag=context_flag)
if send: if send:
announce_packet.send() announce_packet.send()
else: else:
@ -424,7 +492,7 @@ class Destination:
return plaintext return plaintext
if self.type == Destination.SINGLE and self.identity != None: if self.type == Destination.SINGLE and self.identity != None:
return self.identity.encrypt(plaintext) return self.identity.encrypt(plaintext, ratchet=RNS.Identity.get_ratchet(self.hash))
if self.type == Destination.GROUP: if self.type == Destination.GROUP:
if hasattr(self, "prv") and self.prv != None: if hasattr(self, "prv") and self.prv != None:
@ -449,7 +517,7 @@ class Destination:
return ciphertext return ciphertext
if self.type == Destination.SINGLE and self.identity != None: if self.type == Destination.SINGLE and self.identity != None:
return self.identity.decrypt(ciphertext) return self.identity.decrypt(ciphertext, ratchets=self.ratchets)
if self.type == Destination.GROUP: if self.type == Destination.GROUP:
if hasattr(self, "prv") and self.prv != None: if hasattr(self, "prv") and self.prv != None:

View File

@ -1,6 +1,6 @@
# MIT License # MIT License
# #
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors. # Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors.
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@ -52,6 +52,9 @@ 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. 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 # Non-configurable constants
FERNET_OVERHEAD = RNS.Cryptography.Fernet.FERNET_OVERHEAD FERNET_OVERHEAD = RNS.Cryptography.Fernet.FERNET_OVERHEAD
AES128_BLOCKSIZE = 16 # In bytes AES128_BLOCKSIZE = 16 # In bytes
@ -67,6 +70,7 @@ class Identity:
# Storage # Storage
known_destinations = {} known_destinations = {}
known_ratchets = {}
@staticmethod @staticmethod
def remember(packet_hash, destination_hash, public_key, app_data = None): def remember(packet_hash, destination_hash, public_key, app_data = None):
@ -221,20 +225,103 @@ class Identity:
""" """
return Identity.truncated_hash(os.urandom(Identity.TRUNCATED_HASHLENGTH//8)) 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 @staticmethod
def validate_announce(packet, only_validate_signature=False): def validate_announce(packet, only_validate_signature=False):
try: try:
if packet.packet_type == RNS.Packet.ANNOUNCE: 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 destination_hash = packet.destination_hash
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) > 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:]
signed_data = destination_hash+public_key+name_hash+random_hash+app_data # 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]
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 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
if not len(packet.data) > Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8: if not len(packet.data) > Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:
app_data = None app_data = None
@ -281,6 +368,9 @@ class Identity:
else: 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) 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 return True
else: else:
@ -469,7 +559,7 @@ class Identity:
def get_context(self): def get_context(self):
return None return None
def encrypt(self, plaintext): def encrypt(self, plaintext, ratchet=None):
""" """
Encrypts information for the identity. Encrypts information for the identity.
@ -481,7 +571,13 @@ class Identity:
ephemeral_key = X25519PrivateKey.generate() ephemeral_key = X25519PrivateKey.generate()
ephemeral_pub_bytes = ephemeral_key.public_key().public_bytes() ephemeral_pub_bytes = ephemeral_key.public_key().public_bytes()
shared_key = ephemeral_key.exchange(self.pub) 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)
derived_key = RNS.Cryptography.hkdf( derived_key = RNS.Cryptography.hkdf(
length=32, length=32,
@ -499,7 +595,7 @@ class Identity:
raise KeyError("Encryption failed because identity does not hold a public key") raise KeyError("Encryption failed because identity does not hold a public key")
def decrypt(self, ciphertext_token): def decrypt(self, ciphertext_token, ratchets=None):
""" """
Decrypts information for the identity. Decrypts information for the identity.
@ -513,19 +609,43 @@ class Identity:
try: try:
peer_pub_bytes = ciphertext_token[:Identity.KEYSIZE//8//2] peer_pub_bytes = ciphertext_token[:Identity.KEYSIZE//8//2]
peer_pub = X25519PublicKey.from_public_bytes(peer_pub_bytes) peer_pub = X25519PublicKey.from_public_bytes(peer_pub_bytes)
shared_key = self.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)
ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:] ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:]
plaintext = fernet.decrypt(ciphertext)
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,
salt=self.get_salt(),
context=self.get_context(),
)
fernet = Fernet(derived_key)
plaintext = fernet.decrypt(ciphertext)
except Exception as e: except Exception as e:
RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed: "+str(e), RNS.LOG_DEBUG) RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed: "+str(e), RNS.LOG_DEBUG)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

View File

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

View File

@ -656,7 +656,6 @@ the Packet interface.</p>
<span class="c1"># of the packet. #</span> <span class="c1"># of the packet. #</span>
<span class="c1">##########################################################</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">argparse</span>
<span class="kn">import</span> <span class="nn">RNS</span> <span class="kn">import</span> <span class="nn">RNS</span>
@ -679,19 +678,8 @@ the Packet interface.</p>
<span class="c1"># We must first initialise Reticulum</span> <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="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"># Load identity from file if it exist or randomley create</span> <span class="c1"># Randomly create a new identity for our echo server</span>
<span class="k">if</span> <span class="n">configpath</span><span class="p">:</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">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"># 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> <span class="c1"># to be able to verify echo replies to our clients, so we</span>
@ -1030,8 +1018,8 @@ destination, and passing traffic back and forth over the link.</p>
<span class="c1"># We must first initialise Reticulum</span> <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="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">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"># 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> <span class="c1"># want clients to create links to this destination, so we</span>
@ -1060,7 +1048,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="s2">&quot; running, waiting for a connection.&quot;</span>
<span class="p">)</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="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="c1"># We enter a loop that runs until the users exits.</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> <span class="c1"># If the user hits enter, we will announce our server</span>
@ -1070,12 +1058,6 @@ 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">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">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="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"># When a client establishes a link to our server</span>
<span class="c1"># destination, this function will be called with</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"> <dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.encrypt"> <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><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>, <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>
<dd><p>Encrypts information for the identity.</p> <dd><p>Encrypts information for the identity.</p>
<dl class="field-list simple"> <dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt> <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"> <dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.decrypt"> <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><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>, <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>
<dd><p>Decrypts information for the identity.</p> <dd><p>Decrypts information for the identity.</p>
<dl class="field-list simple"> <dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt> <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 instances are used both to create outgoing and incoming endpoints. The
destination type will decide if encryption, and what type, is used in destination type will decide if encryption, and what type, is used in
communication with the endpoint. A destination can also announce its communication with the endpoint. A destination can also announce its
presence on the network, which will also distribute necessary keys for presence on the network, which will distribute necessary keys for
encrypted communication with it.</p> encrypted communication with it.</p>
<dl class="field-list simple"> <dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt> <dt class="field-odd">Parameters<span class="colon">:</span></dt>

File diff suppressed because one or more lines are too long

View File

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

View File

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