Implemented link peer identification

This commit is contained in:
Mark Qvist
2021-08-19 14:10:37 +02:00
parent 772ae44ab8
commit 384a7db974
7 changed files with 376 additions and 3 deletions
+52
View File
@@ -23,6 +23,7 @@ class LinkCallbacks:
self.resource = None
self.resource_started = None
self.resource_concluded = None
self.remote_identified = None
class Link:
"""
@@ -125,6 +126,7 @@ class Link:
self.owner = owner
self.destination = destination
self.attached_interface = None
self.__remote_identity = None
self.__encryption_disabled = False
if self.destination == None:
self.initiator = False
@@ -226,6 +228,7 @@ class Link:
if self.destination.identity.validate(signature, signed_data):
self.rtt = time.time() - self.request_time
self.attached_interface = packet.receiving_interface
self.__remote_identity = self.destination.identity
RNS.Transport.activate_link(self)
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(self.rtt), RNS.LOG_VERBOSE)
rtt_data = umsgpack.packb(self.rtt)
@@ -243,6 +246,25 @@ class Link:
RNS.log("Invalid link proof signature received by "+str(self)+". Ignoring.", RNS.LOG_DEBUG)
def identify(self, identity):
"""
Identifies the initiator of the link to the remote peer. This can only happen
once the link has been established, and is carried out over the encrypted link.
The identity is only revealed to the remote peer, and initiator anonymity is
thus preserved. This method can be used for authentication.
:param identity: An RNS.Identity instance to identify as.
"""
if self.initiator:
signed_data = self.link_id + identity.get_public_key()
signature = identity.sign(signed_data)
proof_data = identity.get_public_key() + signature
proof = RNS.Packet(self, proof_data, RNS.Packet.DATA, context = RNS.Packet.LINKIDENTIFY)
proof.send()
self.had_outbound()
def rtt_packet(self, packet):
try:
# TODO: This is crude, we should use the delta
@@ -286,6 +308,12 @@ class Link:
"""
return min(self.no_inbound_for(), self.no_outbound_for())
def get_remote_identity(self):
"""
:returns: The identity of the remote peer, if it is known
"""
return self.__remote_identity
def had_outbound(self):
self.last_outbound = time.time()
@@ -424,6 +452,21 @@ class Link:
if self.destination.callbacks.proof_requested:
self.destination.callbacks.proof_requested(packet)
elif packet.context == RNS.Packet.LINKIDENTIFY:
plaintext = self.decrypt(packet.data)
if not self.initiator and len(plaintext) == RNS.Identity.KEYSIZE//8 + RNS.Identity.SIGLENGTH//8:
public_key = plaintext[:RNS.Identity.KEYSIZE//8]
signed_data = self.link_id+public_key
signature = plaintext[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Identity.SIGLENGTH//8]
identity = RNS.Identity(create_keys=False)
identity.load_public_key(public_key)
if identity.validate(signature, signed_data):
self.__remote_identity = identity
if self.callbacks.remote_identified != None:
self.callbacks.remote_identified(self.__remote_identity)
elif packet.context == RNS.Packet.LRRTT:
if not self.initiator:
self.rtt_packet(packet)
@@ -574,6 +617,15 @@ class Link:
"""
self.callbacks.resource_concluded = callback
def set_remote_identified_callback(self, callback):
"""
Registers a function to be called when an initiating peer has
identified over this link.
:param callback: A function or method with the signature *callback(identity)* to be called.
"""
self.callbacks.remote_identified = callback
def resource_concluded(self, resource):
if resource in self.incoming_resources:
self.incoming_resources.remove(resource)
+2 -1
View File
@@ -56,7 +56,8 @@ class Packet:
PATH_RESPONSE = 0x0B # Packet is a response to a path request
COMMAND = 0x0C # Packet is a command
COMMAND_STATUS = 0x0D # Packet is a status of an executed command
KEEPALIVE = 0xFB # Packet is a keepalive packet
KEEPALIVE = 0xFA # Packet is a keepalive packet
LINKIDENTIFY = 0xFB # Packet is a link peer identification proof
LINKCLOSE = 0xFC # Packet is a link close message
LINKPROOF = 0xFD # Packet is a link packet proof
LRRTT = 0xFE # Packet is a link request round-trip time measurement
+2
View File
@@ -49,6 +49,8 @@ class Transport:
receipts = [] # Receipts of all outgoing packets for proof processing
# TODO: "destination_table" should really be renamed to "path_table"
# Notes on memory usage: 1 megabyte of memory can store approximately
# 55.100 path table entries or approximately 22.300 link table entries.
announce_table = {} # A table for storing announces currently waiting to be retransmitted
destination_table = {} # A lookup table containing the next hop to a given destination
reverse_table = {} # A lookup table for storing packet hashes used to return proofs and replies
+6 -1
View File
@@ -5,6 +5,8 @@ import time
import random
import threading
from ._version import __version__
from .Reticulum import Reticulum
from .Identity import Identity
from .Link import Link
@@ -60,6 +62,9 @@ def loglevelname(level):
return "Unknown"
def version():
return __version__
def log(msg, level=3, _override_destination = False):
global _always_override_destination
@@ -68,7 +73,7 @@ def log(msg, level=3, _override_destination = False):
logstring = "["+time.strftime(logtimefmt)+"] ["+loglevelname(level)+"] "+msg
logging_lock.acquire()
if (logdest == LOG_STDOUT or _always_override_destination):
if (logdest == LOG_STDOUT or _always_override_destination or _override_destination):
print(logstring)
logging_lock.release()
+1
View File
@@ -0,0 +1 @@
__version__ = "0.2.2"