mirror of
https://github.com/markqvist/Reticulum.git
synced 2024-11-22 21:50:18 +00:00
Implemented ratchet generation, rotation and persistence
This commit is contained in:
parent
2bf75f60bc
commit
54eaff203f
@ -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 = 128
|
||||||
|
|
||||||
@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("Enabled ratchets 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):
|
||||||
"""
|
"""
|
||||||
@ -211,6 +267,11 @@ 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_pub = RNS.Identity.ratchet_public_bytes(self.ratchets[0])
|
||||||
|
RNS.log(f"Including {len(ratchet_pub)*8}-bit ratchet {RNS.hexrep(ratchet_pub)} in announce", RNS.LOG_DEBUG) # TODO: Remove
|
||||||
|
|
||||||
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
|
||||||
|
@ -221,6 +221,16 @@ class Identity:
|
|||||||
"""
|
"""
|
||||||
return Identity.truncated_hash(os.urandom(Identity.TRUNCATED_HASHLENGTH//8))
|
return Identity.truncated_hash(os.urandom(Identity.TRUNCATED_HASHLENGTH//8))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_ratchet():
|
||||||
|
ratchet_prv = X25519PrivateKey.generate()
|
||||||
|
ratchet_pub = ratchet_prv.public_key()
|
||||||
|
return ratchet_prv.private_bytes()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ratchet_public_bytes(ratchet):
|
||||||
|
return X25519PrivateKey.from_private_bytes(ratchet).public_key().public_bytes()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate_announce(packet, only_validate_signature=False):
|
def validate_announce(packet, only_validate_signature=False):
|
||||||
try:
|
try:
|
||||||
|
Loading…
Reference in New Issue
Block a user