Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F4642
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
64 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/CHANGELOG b/CHANGELOG
index 66e384d..9dfabf7 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,20 +1,24 @@
Copyright (C) 2022 LANNOCC (Shawn A. Wilson)
@%@~LICENSE:MIT~@%@
-saw_070722_1 - Fix error with register command.
+saw_070622_3 - Implement re-registration. Fix a security concern for
+ registration... this will invalidate existing signed entries.
+ Implement proper signature verification on nodes.
+
+saw_070622_2 - Fix error with register command.
saw_070622_1 - Much work implementiong node protocol v2 and proper node-based
registration.
saw_062622_1 - Implementation of peer protocol v1 (basic) and v2 (encrypted).
saw_061722_1 - Beginning crypto handler v1 (label registration).
saw_061622_1 - Beginning node protocol v1 (ping-pong).
saw_061422_3 - Use os.makedirs() for the user_data_dir (creates parents).
saw_061422_2 - Now have a simple `lank` command.
saw_061422_1 - Initial commit (barebones pip project skeleton).
diff --git a/lank/__version__.py b/lank/__version__.py
index d5d5d50..e8ea024 100644
--- a/lank/__version__.py
+++ b/lank/__version__.py
@@ -1,2 +1,2 @@
-__version__ = '0.3.1'
+__version__ = '0.3.2'
diff --git a/lank/crypto/__init__.py b/lank/crypto/__init__.py
index 5b4f1a9..86fbb1f 100644
--- a/lank/crypto/__init__.py
+++ b/lank/crypto/__init__.py
@@ -1,66 +1,57 @@
from abc import ABC, abstractmethod
VERSION = 1
cache = { }
def get_handler(version=None):
if not version:
version = VERSION
if version not in cache:
try:
exec(f'from .v{version} import Handler as Crypto_v{version}')
exec(f'cache[{version}] = Crypto_v{version}')
except ModuleNotFoundError as e:
cache[version] = e
handler = cache[version]
if isinstance(handler, Exception):
raise ValueError(f'crypto handler version {version}') from handler
return handler()
class Handler(ABC):
- def __init__(self):
- pass
-
- '''
- @abstractmethod
- def register(self):
- raise NotImplementedError()
-
@abstractmethod
- def get_private_key(self, label, password=None):
+ def make_keys(self, password=None):
raise NotImplementedError()
@abstractmethod
- def get_public_key(self, label):
+ def load_private_key(self, key_pair_pem, password=None):
raise NotImplementedError()
- '''
@abstractmethod
- def load_private_key(self, key_pair_pem, password=None):
+ def load_public_key(self, key_pair_pem):
raise NotImplementedError()
@abstractmethod
def encrypt(self, pub_key, data):
raise NotImplementedError()
@abstractmethod
def decrypt(self, priv_key, data):
raise NotImplementedError()
@abstractmethod
def sign(self, priv_key, data):
raise NotImplementedError()
@abstractmethod
def verify(self, pub_key, data, signature):
raise NotImplementedError()
diff --git a/lank/crypto/v1.py b/lank/crypto/v1.py
index 2bfda97..1cb0aad 100644
--- a/lank/crypto/v1.py
+++ b/lank/crypto/v1.py
@@ -1,158 +1,149 @@
from . import Handler as Base
#import lank.db as ldb
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.exceptions import InvalidSignature
from password_strength import PasswordPolicy
#from password_strength.tests import (
# Length, Uppercase, Numbers, Special, NonLetters, Strength)
from getpass import getpass
from datetime import datetime
import random
import sys
# Basic process from:
# https://dev.to/aaronktberry/generating-encrypted-key-pairs-in-python-69b
class Handler(Base):
VERSION = 1
TEXT_ENCODING = 'utf-8'
PASS_POLICY = PasswordPolicy.from_names(
length=8, uppercase=1, numbers=1, special=1, #nonletters=1,
strength=0.5)
#KEY_SIZE = 15360 # too slow!
KEY_SIZE = 4096
KEY_PUBLIC_EXPONENT = 65537
KEY_ENCODING = serialization.Encoding.PEM
KEY_ENCRYPTED_FORMAT = serialization.PrivateFormat.PKCS8
KEY_OPEN_FORMAT = serialization.PrivateFormat.TraditionalOpenSSL
KEY_PUBLIC_FORMAT = serialization.PublicFormat.SubjectPublicKeyInfo
SIGN_PAD = padding.PSS
SIGN_PAD_MGF = padding.MGF1
SIGN_PAD_MGF_ALGO = hashes.SHA256
SIGN_PAD_SALT_LENGTH = SIGN_PAD.MAX_LENGTH
SIGN_ALGO = hashes.SHA256
ENCRYPT_PAD = padding.OAEP
ENCRYPT_PAD_MGF = padding.MGF1
ENCRYPT_PAD_MGF_ALGO = hashes.SHA256
ENCRYPT_PAD_ALGO = hashes.SHA256
ENCRYPT_PAD_LABEL = None
- REGISTER_MSG = 'Spread love everywhere you go. ' \
- + 'Let no one ever come to you without leaving happier.'
+ REGISTER_MSG = 'Spread love everywhere you go.' \
+ + ' Let no one ever come to you without leaving happier.'
# --Mother Teresa
+ REREGISTER_MSG = 'Tis a lesson you should heed, try, try again.' \
+ + " If at first you don't succeed, try, try, and try again."
+ # --Thomas H. Palmer
+
def make_time_nonce(self):
return f'{datetime.now().timestamp()}|{random.randrange(2**32)}'
- def get_register_message(self, time_nonce):
- return f'{self.REGISTER_MSG}~{time_nonce}'.encode(self.TEXT_ENCODING)
+ def get_register_message(self, label, time_nonce):
+ assert label
+ assert time_nonce
+
+ msg = f'{label}~{self.REGISTER_MSG}~{time_nonce}'
+ return msg.encode(self.TEXT_ENCODING) + b'\x81'
+
+ def get_reregister_message(self, existing_tn_or_ref, existing_uuid,
+ new_key_pair_pem):
+ assert existing_tn_or_ref
+ assert existing_uuid
+ assert new_key_pair_pem
+
+ msg = f'{existing_uuid}~{self.REREGISTER_MSG}~{existing_tn_or_ref}~'
+ msg = msg.encode(self.TEXT_ENCODING) + new_key_pair_pem + b'\x42'
+ #print(f'*****\n{msg}\n*****')
+ return msg
def make_keys(self, password=None):
priv_key = rsa.generate_private_key(
key_size=self.KEY_SIZE,
public_exponent=self.KEY_PUBLIC_EXPONENT)
if password:
priv_key_pem = priv_key.private_bytes(
encoding=self.KEY_ENCODING,
format=self.KEY_ENCRYPTED_FORMAT,
encryption_algorithm=serialization.BestAvailableEncryption(
password.encode(self.TEXT_ENCODING)))
else:
priv_key_pem = priv_key.private_bytes(
encoding=self.KEY_ENCODING,
format=self.KEY_OPEN_FORMAT,
encryption_algorithm=serialization.NoEncryption())
pub_key_pem = priv_key.public_key().public_bytes(
encoding=self.KEY_ENCODING,
format=self.KEY_PUBLIC_FORMAT)
return (priv_key, priv_key_pem, pub_key_pem)
- '''
- def get_private_key(self, label, password=None):
- label_id = ldb.get_label_by_name(label)
- if not label_id: return None
-
- label_id = label_id['id']
- registration = ldb.find_signed(label_id, ldb.NAME_REGISTER, limit=1)
- assert registration is not None
- registration = registration[0]
- #FIXME: check signed version
-
- if password:
- password = password.encode(self.TEXT_ENCODING)
-
- return serialization.load_pem_private_key(
- registration['address'], password=password)
-
- def get_public_key(self, label):
- label_id = ldb.get_label_by_name(label)
- if not label_id: return None
-
- label_id = label_id['id']
- registration = ldb.find_signed(label_id, ldb.NAME_REGISTER, limit=1)
- assert registration is not None
- registration = registration[0]
- #FIXME: check signed version
-
- return serialization.load_pem_public_key(
- registration['address'])
- '''
-
def load_private_key(self, key_pair_pem, password=None):
if password:
password = password.encode(self.TEXT_ENCODING)
return serialization.load_pem_private_key(key_pair_pem,
password=password)
+ def load_public_key(self, key_pair_pem):
+ return serialization.load_pem_public_key(key_pair_pem)
+
def encrypt(self, pub_key, data):
return pub_key.encrypt(
plaintext=data,
padding=self.ENCRYPT_PAD(
mgf=self.ENCRYPT_PAD_MGF(algorithm=self.ENCRYPT_PAD_MGF_ALGO()),
algorithm=self.ENCRYPT_PAD_ALGO(),
label=self.ENCRYPT_PAD_LABEL))
def decrypt(self, priv_key, data):
return priv_key.decrypt(
ciphertext=data,
padding=self.ENCRYPT_PAD(
mgf=self.ENCRYPT_PAD_MGF(algorithm=self.ENCRYPT_PAD_MGF_ALGO()),
algorithm=self.ENCRYPT_PAD_ALGO(),
label=self.ENCRYPT_PAD_LABEL))
def sign(self, priv_key, data):
return priv_key.sign(
data=data,
padding=self.SIGN_PAD(
mgf=self.SIGN_PAD_MGF(algorithm=self.SIGN_PAD_MGF_ALGO()),
salt_length=self.SIGN_PAD_SALT_LENGTH),
algorithm=self.SIGN_ALGO())
def verify(self, pub_key, data, signature):
try:
pub_key.verify(
signature=signature,
data=data,
padding=self.SIGN_PAD(
mgf=self.SIGN_PAD_MGF(algorithm=self.SIGN_PAD_MGF_ALGO()),
salt_length=self.SIGN_PAD_SALT_LENGTH),
algorithm=self.SIGN_ALGO())
return True
except InvalidSignature:
return False
diff --git a/lank/node/__init__.py b/lank/node/__init__.py
index a5eb3bb..8969185 100644
--- a/lank/node/__init__.py
+++ b/lank/node/__init__.py
@@ -1,224 +1,227 @@
from .protocol import get_handler, HELLO, KEEPALIVE, VERSION
import lank.node.db as ldb
from gevent import socket, wait #, spawn
from gevent.pool import Pool
from gevent.server import StreamServer
from bidict import bidict
from ntplib import NTPClient
from uuid import UUID
from datetime import datetime, timedelta, timezone
DEFAULT_PORT = 42024
HELLO_SIZE = len(HELLO)
HELLO_TIMEOUT = 9 # seconds
GENERAL_TIMEOUT = KEEPALIVE * 2 # seconds
NODES = [ # FIXME -- this is temporary (put in database?)
('localhost', 42024),
- ('localhost', 42124),
+ #('localhost', 42124),
('72.202.195.53', 42024),
('ruckusist.com', 42024),
]
NODES_MIN = 3
NODES_MAX = 9
NODES_WAIT = 3 * 60 # seconds
NTP = 'pool.ntp.org'
class Master:
def __init__(self, port=DEFAULT_PORT):
self.port = port
uuid = ldb.get_meta(ldb.META_NODE_UUID)
assert uuid
self.uuid = UUID(uuid)
print(f' our uuid is {self.uuid}')
print(f' getting time from {NTP}...')
ntp = NTPClient().request(NTP, version=3)
self.offset = ntp.offset
print(f' our clock is {abs(self.offset)} seconds ', end='')
if self.offset < 0: print('fast')
else: print('slow')
self.pool = Pool()
self.stream_server = StreamServer(('0.0.0.0', self.port), self.server,
spawn=self.pool)
self.buffer = bytearray(HELLO_SIZE)
#self.peers_by_sock = bidict({ })
#self.labels = { }
self.labels_by_id = bidict({ })
self.nodes_by_uuid = { }
self.nodes_client = { }
self.reservations = { }
self.registrations = { }
self.signed_recently = { }
def run(self):
for label in ldb.list_labels():
self.labels_by_id[label['id']] = label['name']
print(f'S listening on port {self.port}')
self.stream_server.start()
while True:
if len(self.nodes_by_uuid) < NODES_MIN:
# FIXME: limit spawning to NODES_MAX - len(self.nodes)
# and then wait
for addr in NODES:
if len(self.nodes_by_uuid) >= NODES_MAX:
break
if addr in self.nodes_client:
continue
self.pool.spawn(self.client, addr)
wait(timeout=NODES_WAIT)
self.status()
def now(self):
return datetime.now(timezone.utc) + timedelta(seconds=self.offset)
def status(self):
nodes = len(self.nodes_by_uuid)
time = self.now().isoformat()
print(f'** STATUS ** nodes={nodes} ** time={time}')
def broadcast_nodes(self, msg, skip=None):
print(f'B (NODES) <- {msg}')
self.pool.spawn(self._broadcast_nodes_, msg, skip)
def _broadcast_nodes_(self, msg, skip=None):
for handler in self.nodes_by_uuid.values():
- if skip == handler:
+ if handler is skip:
continue
handler.send(msg)
def client(self, addr):
print(f'C+ connecting to {addr}')
self.nodes_client[addr] = True
try:
sock = socket.create_connection(addr, timeout=HELLO_TIMEOUT)
sock.settimeout(HELLO_TIMEOUT)
try:
handler = get_handler(sock, addr, VERSION)
handler.hello()
sock.settimeout(GENERAL_TIMEOUT)
try:
handler.client(self)
print(f'C- finished {addr}')
except KeyError as e:
print(f'C- terminated {addr} [DENIED: {e}]')
if 'NodeIsSelf' in str(e):
self.nodes_client[addr] = False
except ValueError as e:
print(f'C- terminated {addr} [BAD MESSAGE: {e}]')
except socket.timeout:
print(f'C- terminated {addr} [GENERAL TIMEOUT]')
except BrokenPipeError:
print(f'C- closed {addr} [BROKEN PIPE]')
except ConnectionResetError:
print(f'C- closed {addr} [CONNECTION RESET]')
except socket.timeout:
print(f'C- terminated {addr} [HELLO TIMEOUT]')
except ConnectionRefusedError:
print(f'C- closed {addr} [CONNECTION REFUSED]')
except socket.timeout:
print(f'C- terminated {addr} [CONNECT TIMEOUT]')
finally:
if self.nodes_client[addr]:
del self.nodes_client[addr]
def server(self, sock, addr):
print(f'S+ connection from {addr}')
sock.settimeout(HELLO_TIMEOUT)
try:
read = sock.recv_into(self.buffer)
if read == HELLO_SIZE:
if self.buffer == HELLO:
sock.settimeout(GENERAL_TIMEOUT)
try:
protocol = get_handler(sock, addr)
if protocol:
try:
protocol.server(self)
print(f'S- finished {addr}')
+ except KeyError as e:
+ print(f'S- terminated {addr} [DENIED: {e}]')
+
except ValueError as e:
print(f'S- terminated {addr}' \
+ f' [BAD MESSAGE: {e}]')
else:
print(f'S- closed {addr} [CLIENT ABORT]')
except ValueError as e:
print(f'S- terminated {addr} [PROTOCOL VERSION]')
except socket.timeout:
print(f'S- terminated {addr} [GENERAL TIMEOUT]')
else:
print(f'S- terminated {addr} [BAD HELLO]')
elif read:
print(f'S- terminated {addr} [BAD HELLO]')
else:
print(f'S- closed {addr} [CLIENT ABORT]')
except BrokenPipeError:
print(f'S- closed {addr} [BROKEN PIPE]')
except ConnectionResetError:
print(f'S- closed {addr} [CONNECTION RESET]')
except socket.timeout:
print(f'S- terminated {addr} [HELLO TIMEOUT]')
#finally:
# if sock in self.peers_by_sock:
# self.sign_off(self.peers_by_sock[sock])
'''
def sign_on(self, sock, label, addr):
self.sign_off(label)
self.peers_by_sock[sock] = label
self.labels[label] = addr
def sign_off(self, label):
if label not in self.labels: return
del self.peers_by_sock.inverse[label]
del self.labels[label]
'''
diff --git a/lank/node/protocol/v2/__init__.py b/lank/node/protocol/v2/__init__.py
index 31858a6..8cd4639 100644
--- a/lank/node/protocol/v2/__init__.py
+++ b/lank/node/protocol/v2/__init__.py
@@ -1,374 +1,483 @@
from ..v1 import Handler as Base
from .. import KEEPALIVE, MAX_TIME_SKEW
from .ack import *
from .sync import *
from .register import *
-from .negative import *
+from .deny import *
import lank.node.db as ldb
+from lank.crypto import get_handler as get_crypto
from bidict import bidict
from gevent import socket
from datetime import datetime, timedelta
from uuid import UUID
class Handler(Base):
VERSION = 2
BUFFER_SIZE = 8192
ENCODING = 'utf-8'
SYNC_MARGIN = 10 * 60 # seconds
MSG_BY_ID = bidict({
1: Ping,
2: Pong,
3: NodeOn,
4: NodeIsSelf,
5: NodeAlreadyConnected,
6: NodeTimeSkewed,
7: NodeIsIsolated,
- 8: Reservation,
- 9: ReservationRequired,
- 10: ReservationCancel,
- 11: Registration,
- 12: RegistrationSuccess,
- 13: GetRegistration,
- 14: LabelNotFound,
- 15: Signed,
- 16: NodesOnly,
+ 8: NodesOnly,
+ 9: Reservation,
+ 10: ReservationRequired,
+ 11: ReservationCancel,
+ 12: Registration,
+ 13: ReRegistration,
+ 14: RegistrationSuccess,
+ 15: GetRegistration,
+ 16: LabelNotFound,
+ 17: Signed,
+ 18: SignedUUIDNotFound,
+ 19: SignedLabelMismatch,
+ 20: SignedNameMismatch,
+ 21: SignatureFailure,
})
- def reservation(self, master, msg):
- assert isinstance(msg, Reservation)
- reply = None
-
- if not master.nodes_by_uuid:
- reply = NodeIsIsolated()
-
- elif msg.label in master.reservations:
- reservation = master.reservations[msg.label]
- res_uuid = reservation[0]
-
- if msg.uuid != res_uuid: # collision
- reply = ReservationCancel(msg.label)
- master.broadcast_nodes(reply, skip=self)
- del master.reservations[msg.label]
-
- else: # just an echo from another node?
- pass
-
- elif ldb.get_label_by_name(msg.label):
- reply = ReservationCancel(msg.label, True)
-
- else:
- reservation = (msg.uuid, self.sock, datetime.now())
- master.reservations[msg.label] = reservation
- master.broadcast_nodes(msg, skip=self)
- if not self.node_uuid: reply = msg
-
- return reply
-
- def reservation_cancel(self, master, msg):
- assert isinstance(msg, ReservationCancel)
-
- if msg.label in master.reservations:
- master.broadcast_nodes(msg, skip=self)
- del master.reservations[msg.label]
-
- def registration(self, master, msg):
- assert isinstance(msg, Registration)
- reply = None
-
- if not master.nodes_by_uuid:
- reply = NodeIsIsolated()
-
- elif msg.label not in master.reservations:
- reply = ReservationRequired(msg.label)
-
- else:
- reservation = master.reservations[msg.label]
- res_uuid = reservation[0]
- res_sock = reservation[1]
-
- if res_sock != self.sock: # imposter
- reply = ReservationRequired(msg.label)
-
- else:
- # FIXME: verify signature
-
- label_id = None
- with ldb.Transaction():
- label_id = ldb.insert_label(msg.label)
- time = master.now()
-
- ldb.insert_signed(str(res_uuid), label_id,
- ldb.NAME_REGISTER, msg.time_nonce,
- str(msg.key_pair_pem, self.ENCODING),
- msg.signature, msg.version, str(master.uuid), time)
-
- master.signed_recently[res_uuid] = time
- del master.reservations[msg.label]
-
- master.labels_by_id[label_id] = msg.label
-
- master.broadcast_nodes(Signed(
- msg.version,
- res_uuid,
- msg.label,
- ldb.NAME_REGISTER,
- msg.time_nonce,
- str(msg.key_pair_pem, self.ENCODING),
- msg.signature,
- master.uuid,
- time
- ), skip=self)
-
- reply = RegistrationSuccess(res_uuid)
-
- return reply
-
- def get_registration(self, master, msg):
- assert isinstance(msg, GetRegistration)
-
- if msg.label not in master.labels_by_id.inverse:
- return LabelNotFound(msg.label)
-
- label_id = master.labels_by_id.inverse[msg.label]
- signed = ldb.find_signed_by_label_name(label_id, ldb.NAME_REGISTER,
- limit=1)
-
- assert signed
- assert len(signed)==1
- signed = signed[0]
-
- return Registration(
- msg.label,
- signed['version'],
- signed['key'],
- signed['address'].encode(self.ENCODING),
- signed['signature'])
-
- def signed(self, master, msg):
- assert isinstance(msg, Signed)
-
- if msg.uuid in master.signed_recently:
- return
-
- exists = ldb.get_signed_by_uuid(str(msg.uuid))
- if exists:
- return
-
- # FIXME: verify signature
-
- label_id = None
- with ldb.Transaction():
- try:
- label_id = master.labels_by_id.inverse[msg.label]
- except KeyError:
- label_id = ldb.insert_label(msg.label)
-
- ldb.insert_signed(str(msg.uuid), label_id, msg.name, msg.key,
- msg.address, msg.signature, msg.version,
- str(msg.node_uuid),
- msg._to_datetime_(msg.timestamp))
-
- if label_id not in master.labels_by_id:
- master.labels_by_id[label_id] = msg.label
-
- if msg.label in master.reservations:
- del master.reservations[msg.label]
-
- master.signed_recently[msg.uuid] = master.now()
- master.broadcast_nodes(msg, skip=self)
-
def client(self, master):
self.node_uuid = None
sync = ldb.get_last_signed_created()
if sync:
sync = datetime.fromisoformat(sync)
sync -= timedelta(seconds=self.SYNC_MARGIN)
msg = NodeOn(master.now(), master.uuid, sync)
self.c_send(msg)
reply = self.c_recv()
if not reply: return
- if isinstance(reply, Negative):
+ if isinstance(reply, Denial):
raise KeyError(reply.__class__.__name__)
elif not isinstance(reply, NodeOn):
raise ValueError('expecting NodeOn reply')
elif reply.uuid == master.uuid:
raise KeyError(NodeIsSelf.__name__)
elif reply.nonce != msg.nonce:
raise ValueError('nonce mismatch')
elif reply.uuid in master.nodes_by_uuid:
raise ValueError('node already connected (uuid match)')
elif not reply.check_time_skew(master.now(), MAX_TIME_SKEW):
raise ValueError('time skew')
self.node_uuid = reply.uuid
+
+ self.sync_signed(master, self.c_send, sync, reply)
+
master.nodes_by_uuid[self.node_uuid] = self
master.status()
try:
self.sock.settimeout(KEEPALIVE)
ping = None
while msg:
try:
while msg := self.c_recv():
+ reply = None
+
if isinstance(msg, Pong):
if not ping:
raise ValueError('pong without ping')
elif msg.nonce != ping.nonce:
raise ValueError('nonce mismatch')
ping = None
elif isinstance(msg, Reservation):
reply = self.reservation(master, msg)
- if reply: self.c_send(reply)
elif isinstance(msg, ReservationCancel):
self.reservation_cancel(master, msg)
elif isinstance(msg, GetRegistration):
reply = self.get_registration(master, msg)
- if reply: self.c_send(reply)
elif isinstance(msg, Signed):
- self.signed(master, msg)
+ reply = self.signed(master, msg)
else:
raise ValueError('unhandled message')
+ if reply:
+ self.c_send(reply)
+
+ if isinstance(reply, Denial):
+ msg = None
+ break
+
except socket.timeout as e:
if ping: raise e
ping = Ping()
self.c_send(ping)
finally:
del master.nodes_by_uuid[self.node_uuid]
master.status()
def server(self, master):
self.node_uuid = None
try:
while msg := self.s_recv():
reply = None
if isinstance(msg, Ping):
reply = Pong(msg.nonce)
elif isinstance(msg, NodeOn):
if msg.uuid == master.uuid:
reply = NodeIsSelf()
elif msg.uuid in master.nodes_by_uuid:
reply = NodeAlreadyConnected()
elif not msg.check_time_skew(master.now(), MAX_TIME_SKEW):
reply = NodeTimeSkewed()
else:
self.node_uuid = msg.uuid
sync = ldb.get_last_signed_created()
if sync:
sync = datetime.fromisoformat(sync)
sync -= timedelta(seconds=self.SYNC_MARGIN)
self.s_send(NodeOn(master.now(), master.uuid, sync,
msg.nonce))
- msg_sync = msg._to_datetime_(msg.synced) \
- if msg.synced else None
-
- if sync and (not msg_sync or msg_sync < sync):
- if not msg_sync:
- signed_list = ldb.list_signed()
- else:
- signed_list = ldb.find_signed_since(msg_sync)
-
- #seq = 1
- for signed in signed_list:
- #print(f'XXX {signed["created"]}')
- self.s_send(Signed(
- signed['version'],
- UUID(signed['uuid']),
- master.labels_by_id[signed['label']],
- signed['name'],
- signed['key'],
- signed['address'],
- signed['signature'],
- UUID(signed['node_uuid']),
- signed['created']
- ))
- #seq += 1
-
- sync = ldb.get_last_signed_created()
- if sync:
- sync = datetime.fromisoformat(sync)
- sync -= timedelta(seconds=self.SYNC_MARGIN)
-
- #reply = NodeOn(master.now(), master.uuid, sync,
- # msg.nonce)
+ self.sync_signed(master, self.s_send, sync, msg)
master.nodes_by_uuid[self.node_uuid] = self
master.status()
elif isinstance(msg, Reservation):
reply = self.reservation(master, msg)
elif isinstance(msg, ReservationCancel):
self.reservation_cancel(master, msg)
elif isinstance(msg, Registration):
- reply = self.registration(master, msg)
+ if isinstance(msg, ReRegistration):
+ reply = self.reregistration(master, msg)
+ else:
+ reply = self.registration(master, msg)
elif isinstance(msg, GetRegistration):
reply = self.get_registration(master, msg)
elif isinstance(msg, Signed):
if not self.node_uuid:
reply = NodesOnly()
else:
- self.signed(master, msg)
+ reply = self.signed(master, msg)
#elif isinstance(msg, SignOn):
# master.sign_on(self.sock, msg.label, self.addr)
#elif isinstance(msg, SignOff):
# master.sign_off(msg.label)
#elif isinstance(msg, LabelFind):
# if msg.label in master.labels:
# host = master.labels[msg.label][0]
# port = master.labels[msg.label][1]
# reply = LabelInfo(msg.label, host, port)
# else:
# reply = LabelNone(msg.label)
else:
raise ValueError('unhandled message')
if reply:
self.s_send(reply)
- if isinstance(reply, Negative):
+ if isinstance(reply, Denial):
break
finally:
if self.node_uuid:
del master.nodes_by_uuid[self.node_uuid]
master.status()
def send(self, msg):
id_bytes = self.get_id_bytes(msg)
data = msg.to_bytes(self)
self.sock.sendall(id_bytes + (data if data else b''))
+ def reservation(self, master, msg):
+ assert isinstance(msg, Reservation)
+ reply = None
+
+ if not master.nodes_by_uuid:
+ reply = NodeIsIsolated()
+
+ elif msg.label in master.reservations:
+ reservation = master.reservations[msg.label]
+ res_uuid = reservation[0]
+
+ if msg.uuid != res_uuid: # collision
+ reply = ReservationCancel(msg.label)
+ master.broadcast_nodes(reply, skip=self)
+ del master.reservations[msg.label]
+
+ else: # just an echo from another node?
+ pass
+
+ elif ldb.get_label_by_name(msg.label):
+ reply = ReservationCancel(msg.label, True)
+
+ else:
+ reservation = (msg.uuid, self.sock, master.now())
+ master.reservations[msg.label] = reservation
+ master.broadcast_nodes(msg, skip=self)
+ if not self.node_uuid: reply = msg
+
+ return reply
+
+ def reservation_cancel(self, master, msg):
+ assert isinstance(msg, ReservationCancel)
+
+ if msg.label in master.reservations:
+ master.broadcast_nodes(msg, skip=self)
+ del master.reservations[msg.label]
+
+ def registration(self, master, msg):
+ assert isinstance(msg, Registration)
+
+ if not master.nodes_by_uuid:
+ return NodeIsIsolated()
+
+ if msg.label not in master.reservations:
+ return ReservationRequired(msg.label)
+
+ reservation = master.reservations[msg.label]
+ res_uuid = reservation[0]
+ res_sock = reservation[1]
+
+ if res_sock != self.sock: # imposter
+ return ReservationRequired(msg.label)
+
+ if res_uuid != msg.uuid: # imposter
+ return ReservationRequired(msg.label)
+
+ crypto = get_crypto(msg.version)
+ pub_key = crypto.load_public_key(msg.key_pair_pem)
+ data = crypto.get_register_message(msg.label, msg.time_nonce)
+
+ if not crypto.verify(pub_key, data, msg.signature):
+ return SignatureFailure(msg.uuid)
+
+ label_id = None
+ addr = str(msg.key_pair_pem, self.ENCODING)
+
+ with ldb.Transaction():
+ label_id = ldb.insert_label(msg.label)
+ time = master.now()
+
+ ldb.insert_signed(str(msg.uuid), label_id,
+ ldb.NAME_REGISTER, msg.time_nonce, addr,
+ msg.signature, msg.version, str(master.uuid), time)
+
+ master.signed_recently[res_uuid] = time
+ del master.reservations[msg.label]
+
+ master.labels_by_id[label_id] = msg.label
+
+ master.broadcast_nodes(Signed(
+ msg.version,
+ res_uuid,
+ msg.label,
+ ldb.NAME_REGISTER,
+ msg.time_nonce,
+ addr,
+ msg.signature,
+ master.uuid,
+ time
+ ), skip=self)
+
+ return RegistrationSuccess(res_uuid)
+
+ def reregistration(self, master, msg):
+ assert isinstance(msg, ReRegistration)
+
+ if not master.nodes_by_uuid:
+ return NodeIsIsolated()
+
+ signed = ldb.get_signed_by_uuid(str(msg.ref_uuid))
+
+ if not signed:
+ return SignedUUIDNotFound(msg.ref_uuid)
+ if master.labels_by_id[signed['label']] != msg.label:
+ return SignedLabelMismatch(msg.ref_uuid, msg.label)
+ if signed['name'] != ldb.NAME_REGISTER:
+ return SignedNameMismatch(msg.ref_uuid, ldb.NAME_REGISTER)
+
+ crypto = get_crypto(signed['version'])
+ pub_key = crypto.load_public_key(
+ signed['address'].encode(self.ENCODING))
+ data = crypto.get_reregister_message(signed['key'], signed['uuid'],
+ msg.key_pair_pem)
+
+ if not crypto.verify(pub_key, data, msg.signature):
+ return SignatureFailure(msg.uuid)
+
+ key = f'M:{msg.ref_uuid}'
+ addr = str(msg.key_pair_pem, self.ENCODING)
+
+ with ldb.Transaction():
+ time = master.now()
+
+ ldb.insert_signed(str(msg.uuid), signed['label'],
+ ldb.NAME_REGISTER, key, addr,
+ msg.signature, msg.version, str(master.uuid), time)
+
+ master.signed_recently[msg.uuid] = time
+
+ master.broadcast_nodes(Signed(
+ msg.version,
+ msg.uuid,
+ msg.label,
+ ldb.NAME_REGISTER,
+ key,
+ addr,
+ msg.signature,
+ master.uuid,
+ time
+ ), skip=self)
+
+ return RegistrationSuccess(msg.uuid)
+
+ def get_registration(self, master, msg):
+ assert isinstance(msg, GetRegistration)
+
+ if msg.label not in master.labels_by_id.inverse:
+ return LabelNotFound(msg.label)
+
+ label_id = master.labels_by_id.inverse[msg.label]
+ signed = ldb.find_signed_by_label_name(label_id, ldb.NAME_REGISTER,
+ limit=1)
+
+ assert signed
+ assert len(signed)==1
+ signed = signed[0]
+
+ return Registration(
+ UUID(signed['uuid']),
+ msg.label,
+ signed['version'],
+ signed['key'],
+ signed['address'].encode(self.ENCODING),
+ signed['signature'])
+
+ def signed(self, master, msg):
+ assert isinstance(msg, Signed)
+
+ if msg.uuid in master.signed_recently:
+ return
+
+ exists = ldb.get_signed_by_uuid(str(msg.uuid))
+ if exists:
+ return
+
+ crypto = get_crypto(msg.version)
+
+ if msg.name == ldb.NAME_REGISTER:
+ if msg.key.startswith('M:'): # re-register (ref uuid)
+ ref_uuid = msg.key[2:]
+ signed = ldb.get_signed_by_uuid(ref_uuid)
+
+ if not signed:
+ raise KeyError(f'signed uuid ref not found: {ref_uuid}')
+ if master.labels_by_id[signed['label']] != msg.label:
+ raise KeyError(f'signed label mismatch: {msg.label}')
+ if signed['name'] != ldb.NAME_REGISTER:
+ raise KeyError(f'signed name mismatch: {signed["name"]}')
+
+ pub_key = crypto.load_public_key(
+ signed['address'].encode(self.ENCODING))
+ data = crypto.get_reregister_message(signed['key'],
+ signed['uuid'], msg.address.encode(self.ENCODING))
+
+ if not crypto.verify(pub_key, data, msg.signature):
+ return SignatureFailure(msg.uuid)
+
+ else: # initial registration (time_nonce)
+ pub_key = crypto.load_public_key(
+ msg.address.encode(self.ENCODING))
+ data = crypto.get_register_message(msg.label, msg.key)
+
+ if not crypto.verify(pub_key, data, msg.signature):
+ return SignatureFailure(msg.uuid)
+
+ else:
+ raise ValueError(f'unsupported signed name id: {msg.name}')
+
+ label_id = None
+ with ldb.Transaction():
+ try:
+ label_id = master.labels_by_id.inverse[msg.label]
+ except KeyError:
+ label_id = ldb.insert_label(msg.label)
+
+ ldb.insert_signed(str(msg.uuid), label_id, msg.name, msg.key,
+ msg.address, msg.signature, msg.version,
+ str(msg.node_uuid),
+ msg._to_datetime_(msg.timestamp))
+
+ if label_id not in master.labels_by_id:
+ master.labels_by_id[label_id] = msg.label
+
+ if msg.label in master.reservations:
+ del master.reservations[msg.label]
+
+ master.signed_recently[msg.uuid] = master.now()
+ master.broadcast_nodes(msg, skip=self)
+
+ return None
+
+ def sync_signed(self, master, send, sync, msg):
+ assert isinstance(msg, NodeOn)
+
+ msg_sync = msg._to_datetime_(msg.synced) \
+ if msg.synced else None
+
+ if sync and (not msg_sync or msg_sync < sync):
+ if not msg_sync:
+ signed_list = ldb.list_signed()
+ else:
+ signed_list = ldb.find_signed_since(msg_sync)
+
+ for signed in signed_list:
+ send(Signed(
+ signed['version'],
+ UUID(signed['uuid']),
+ master.labels_by_id[signed['label']],
+ signed['name'],
+ signed['key'],
+ signed['address'],
+ signed['signature'],
+ UUID(signed['node_uuid']),
+ signed['created']
+ ))
+
+ #sync = ldb.get_last_signed_created()
+ #if sync:
+ # sync = datetime.fromisoformat(sync)
+ # sync -= timedelta(seconds=self.SYNC_MARGIN)
+
+ #reply = NodeOn(master.now(), master.uuid, sync,
+ # msg.nonce)
+
diff --git a/lank/node/protocol/v2/deny.py b/lank/node/protocol/v2/deny.py
new file mode 100644
index 0000000..adc23aa
--- /dev/null
+++ b/lank/node/protocol/v2/deny.py
@@ -0,0 +1,32 @@
+from .base import Message, Identified
+
+from abc import ABC
+
+
+class Denial(Message, ABC):
+ pass
+
+
+class NodeIsSelf(Denial):
+ pass
+
+
+class NodeAlreadyConnected(Denial):
+ pass
+
+
+class NodeTimeSkewed(Denial):
+ pass
+
+
+class NodeIsIsolated(Denial):
+ pass
+
+
+class NodesOnly(Denial):
+ pass
+
+
+class SignatureFailure(Identified, Denial):
+ pass
+
diff --git a/lank/node/protocol/v2/negative.py b/lank/node/protocol/v2/negative.py
deleted file mode 100644
index 41411de..0000000
--- a/lank/node/protocol/v2/negative.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from .base import Message
-
-from abc import ABC
-
-
-class Negative(Message, ABC):
- pass
-
-
-class NodeIsSelf(Negative):
- pass
-
-
-class NodeAlreadyConnected(Negative):
- pass
-
-
-class NodeTimeSkewed(Negative):
- pass
-
-
-class NodeIsIsolated(Negative):
- pass
-
-class NodesOnly(Negative):
- pass
-
diff --git a/lank/node/protocol/v2/register.py b/lank/node/protocol/v2/register.py
index d92a012..590f290 100644
--- a/lank/node/protocol/v2/register.py
+++ b/lank/node/protocol/v2/register.py
@@ -1,147 +1,170 @@
from .base import Labeled, Identified
-from uuid import uuid4
+from uuid import UUID, uuid4
class Reservation(Labeled, Identified):
def __init__(self, label, uuid=None):
Labeled.__init__(self, label)
Identified.__init__(self, uuid if uuid else uuid4())
def _str_(self):
return ', '.join([
Labeled._str_(self),
Identified._str_(self),
])
def to_bytes(self, handler):
return Labeled.to_bytes(self, handler) \
+ Identified.to_bytes(self, handler)
@classmethod
def recv(cls, handler):
label = cls._label_(handler)
if label is None: return None
uuid = cls._uuid_(handler)
if uuid is None: return None
return cls(label, uuid)
class ReservationCancel(Labeled):
def __init__(self, label, exists=False):
Labeled.__init__(self, label)
self.exists = exists
def _str_(self):
return ', '.join([
Labeled._str_(self),
f'exists={self.exists}'
])
def to_bytes(self, handler):
return Labeled.to_bytes(self, handler) \
+ (b'\xFF' if self.exists else b'\x00')
@classmethod
def recv(cls, handler):
label = cls._label_(handler)
if label is None: return None
exists = handler.recv_bytes(1)
if exists is None: return None
return cls(label, exists==b'\xFF')
class ReservationRequired(Labeled):
pass
-class Registration(Labeled):
+class Registration(Identified, Labeled):
VERSION_SIZE = 1 # bytes
TIME_NONCE_SIZE_SIZE = 1 # bytes
KEY_PAIR_SIZE_SIZE = 2 # bytes
SIG_SIZE = 512 # bytes
- def __init__(self, label, version, time_nonce, key_pair_pem, signature):
+ def __init__(self, uuid, label, version, time_nonce, key_pair_pem,
+ signature):
+ Identified.__init__(self, uuid)
Labeled.__init__(self, label)
self.version = version
self.time_nonce = time_nonce
self.key_pair_pem = key_pair_pem
self.signature = signature
def _str_(self):
return ', '.join([
+ Identified._str_(self),
Labeled._str_(self),
f'version={self.version}',
#f'time_nonce={self.time_nonce}',
])
def to_bytes(self, handler):
+ uuid = Identified.to_bytes(self, handler)
label = Labeled.to_bytes(self, handler)
version = self.version
assert version > 0 and version < 256**self.VERSION_SIZE
version = version.to_bytes(self.VERSION_SIZE, handler.BYTE_ORDER)
time_nonce = self.time_nonce.encode(handler.ENCODING)
time_nonce_size = len(time_nonce)
assert time_nonce_size > 0 \
and time_nonce_size < 256**self.TIME_NONCE_SIZE_SIZE
time_nonce_size = time_nonce_size.to_bytes(self.TIME_NONCE_SIZE_SIZE,
handler.BYTE_ORDER)
key_pair = self.key_pair_pem
key_pair_size = len(key_pair)
assert key_pair_size > 0 \
and key_pair_size < 256**self.KEY_PAIR_SIZE_SIZE
key_pair_size = key_pair_size.to_bytes(self.KEY_PAIR_SIZE_SIZE,
handler.BYTE_ORDER)
sig = self.signature
assert len(sig) == self.SIG_SIZE
- return label + version + time_nonce_size + time_nonce \
+ return uuid + label + version + time_nonce_size + time_nonce \
+ key_pair_size + key_pair + sig
@classmethod
def recv(cls, handler):
+ uuid = cls._uuid_(handler)
+ if uuid is None: return None
+
label = cls._label_(handler)
if label is None: return None
version = handler.recv_bytes(cls.VERSION_SIZE)
if version is None: return None
version = int.from_bytes(version, handler.BYTE_ORDER)
size = handler.recv_bytes(cls.TIME_NONCE_SIZE_SIZE)
if size is None: return None
size = int.from_bytes(size, handler.BYTE_ORDER)
time_nonce = handler.recv_bytes(size)
if time_nonce is None: return None
time_nonce = str(time_nonce, handler.ENCODING)
size = handler.recv_bytes(cls.KEY_PAIR_SIZE_SIZE)
if size is None: return None
size = int.from_bytes(size, handler.BYTE_ORDER)
key_pair = handler.recv_bytes(size)
if key_pair is None: return None
key_pair = bytes(key_pair)
sig = handler.recv_bytes(cls.SIG_SIZE)
if sig is None: return None
sig = bytes(sig)
- return cls(label, version, time_nonce, key_pair, sig)
+ return cls(uuid, label, version, time_nonce, key_pair, sig)
+
+
+class ReRegistration(Registration):
+ def __init__(self, uuid, label, version, ref_uuid, key_pair_pem, signature):
+ super().__init__(uuid, label, version, ref_uuid, key_pair_pem,
+ signature)
+
+ def _str_(self):
+ return ', '.join([
+ super()._str_(),
+ f'ref_uuid={self.ref_uuid}'
+ ])
+
+ @property
+ def ref_uuid(self):
+ return UUID(self.time_nonce)
class RegistrationSuccess(Identified):
pass
class GetRegistration(Labeled):
pass
diff --git a/lank/node/protocol/v2/sync.py b/lank/node/protocol/v2/sync.py
index f1f67d0..6e35fff 100644
--- a/lank/node/protocol/v2/sync.py
+++ b/lank/node/protocol/v2/sync.py
@@ -1,149 +1,214 @@
from .base import Nonced, Timestamped, Identified, Labeled
class NodeOn(Nonced, Timestamped, Identified):
def __init__(self, timestamp, uuid, synced=None, nonce=None):
Nonced.__init__(self, nonce)
Timestamped.__init__(self, timestamp)
Identified.__init__(self, uuid)
self.synced = Timestamped._from_datetime_(synced) if synced else 0
def _str_(self):
synced = Timestamped._to_datetime_(self.synced).isoformat() \
if self.synced else None
return ', '.join([
Nonced._str_(self),
Timestamped._str_(self),
Identified._str_(self),
f'synced={synced}',
])
def to_bytes(self, handler):
return Nonced.to_bytes(self, handler) \
+ Timestamped.to_bytes(self, handler) \
+ Identified.to_bytes(self, handler) \
+ self._timestamp_bytes_(handler, self.synced)
@classmethod
def recv(cls, handler):
nonce = cls._nonce_(handler)
if nonce is None: return None
timestamp = cls._timestamp_(handler)
if timestamp is None: return None
timestamp = cls._to_datetime_(timestamp)
uuid = cls._uuid_(handler)
if uuid is None: return None
synced = cls._timestamp_(handler)
if synced is None: return None
synced = Timestamped._to_datetime_(synced)
return cls(timestamp, uuid, synced, nonce)
class Signed(Identified, Labeled, Timestamped):
VERSION_SIZE = 1 # bytes
NAME_SIZE = 1 # bytes
KEY_SIZE_SIZE = 2 # bytes
ADDR_SIZE_SIZE = 2 # bytes
SIG_SIZE = 512 # bytes
def __init__(self, version, uuid, label, name_id, key, address, signature,
node_uuid, created):
self.version = version
Identified.__init__(self, uuid)
Labeled.__init__(self, label)
self.name = name_id
self.key = key
self.address = address
self.signature = signature
self.node_uuid = node_uuid
Timestamped.__init__(self, created)
def _str_(self):
return ', '.join([
f'version={self.version}',
Identified._str_(self),
Labeled._str_(self),
f'name={self.name}',
f'node_uuid={self.node_uuid}',
Timestamped._str_(self),
])
def to_bytes(self, handler):
ver = self.version
assert ver > 0 and ver < 256**self.VERSION_SIZE
ver = ver.to_bytes(self.VERSION_SIZE, handler.BYTE_ORDER)
uuid = Identified.to_bytes(self, handler)
label = Labeled.to_bytes(self, handler)
name = self.name
assert name > 0 and name < 256**self.NAME_SIZE
name = name.to_bytes(self.NAME_SIZE, handler.BYTE_ORDER)
key = self.key.encode(handler.ENCODING)
key_size = len(key)
assert key_size > 0 and key_size < 256**self.KEY_SIZE_SIZE
key_size = key_size.to_bytes(self.KEY_SIZE_SIZE, handler.BYTE_ORDER)
addr = self.address.encode(handler.ENCODING)
addr_size = len(addr)
assert addr_size > 0 and addr_size < 256**self.ADDR_SIZE_SIZE
addr_size = addr_size.to_bytes(self.ADDR_SIZE_SIZE, handler.BYTE_ORDER)
sig = self.signature
assert len(sig) == self.SIG_SIZE
node_uuid = self._uuid_bytes_(handler, self.node_uuid)
created = Timestamped.to_bytes(self, handler)
return ver + uuid + label + name + key_size + key \
+ addr_size + addr + sig + node_uuid + created
@classmethod
def recv(cls, handler):
ver = handler.recv_bytes(cls.VERSION_SIZE)
if ver is None: return None
ver = int.from_bytes(ver, handler.BYTE_ORDER)
uuid = cls._uuid_(handler)
if uuid is None: return None
label = cls._label_(handler)
if label is None: return None
name = handler.recv_bytes(cls.NAME_SIZE)
if name is None: return None
name = int.from_bytes(name, handler.BYTE_ORDER)
size = handler.recv_bytes(cls.KEY_SIZE_SIZE)
if size is None: return None
size = int.from_bytes(size, handler.BYTE_ORDER)
key = handler.recv_bytes(size)
if key is None: return None
key = str(key, handler.ENCODING)
size = handler.recv_bytes(cls.ADDR_SIZE_SIZE)
if size is None: return None
size = int.from_bytes(size, handler.BYTE_ORDER)
addr = handler.recv_bytes(size)
if addr is None: return None
addr = str(addr, handler.ENCODING)
sig = handler.recv_bytes(cls.SIG_SIZE)
if sig is None: return None
sig = bytes(sig)
node_uuid = cls._uuid_(handler)
if node_uuid is None: return None
created = cls._timestamp_(handler)
if created is None: return None
created = cls._to_datetime_(created)
return cls(ver, uuid, label, name, key, addr, sig, node_uuid, created)
+
+class SignedUUIDNotFound(Identified):
+ pass
+
+
+class SignedLabelMismatch(Identified, Labeled):
+ def __init__(self, uuid, label):
+ Identified.__init__(self, uuid)
+ Labeled.__init__(self, label)
+
+ def _str_(self):
+ return ', '.join([
+ Identified._str_(self),
+ Labeled._str_(self),
+ ])
+
+ def to_bytes(self, handler):
+ uuid = Identified.to_bytes(self, handler)
+ label = Labeled.to_bytes(self, handler)
+
+ return uuid + label
+
+ @classmethod
+ def recv(cls, handler):
+ uuid = cls._uuid_(handler)
+ if uuid is None: return None
+
+ label = cls._label_(handler)
+ if label is None: return None
+
+ return cls(uuid, label)
+
+
+class SignedNameMismatch(Identified):
+ NAME_SIZE = 1 # bytes
+
+ def __init__(self, uuid, name_id):
+ Identified.__init__(self, uuid)
+ self.name_id = name_id
+
+ def _str_(self):
+ return ', '.join([
+ Identified._str_(self),
+ f'name_id={self.name_id}',
+ ])
+
+ def to_bytes(self, handler):
+ uuid = Identified.to_bytes(self, handler)
+
+ assert self.name_id > 0 and self.name_id < 256**self.NAME_SIZE
+ name = self.name_id.to_bytes(self.NAME_SIZE, handler.BYTE_ORDER)
+
+ return uuid + name
+
+ @classmethod
+ def recv(cls, handler):
+ uuid = cls._uuid_(handler)
+ if uuid is None: return None
+
+ name = handler.recv_bytes(cls.NAME_SIZE)
+ if name is None: return None
+ name = int.from_bytes(name, handler.BYTE_ORDER)
+
+ return cls(uuid, name)
+
diff --git a/lank/registration.py b/lank/registration.py
index 362f9ce..af9be25 100644
--- a/lank/registration.py
+++ b/lank/registration.py
@@ -1,317 +1,346 @@
from .crypto import get_handler as crypto
from .node import NODES, HELLO_TIMEOUT, GENERAL_TIMEOUT, KEEPALIVE
from .node.protocol.v2 import *
from threading import Thread, Event
from queue import Queue, Full
import socket
import sys
from getpass import getpass
+from uuid import uuid4
class Interactive:
def __init__(self):
self.crypto = crypto()
print(f' - crypto handler v{self.crypto.VERSION}')
def run(self):
client = Client()
client.start()
try:
client.ready.wait()
if not client.go:
print('ABORTED: unable to connect to node')
return
print()
print('Ready to create/update a label with a new key pair.')
print()
label = input('Label: ')
if label: label = label.strip()
if not label or not client.go:
print('ABORTED')
return
- exists = client.check_label(label)
+ uuid = uuid4()
+ exists = client.check_label(uuid, label)
+ exists_priv_key = None
+
if exists is None:
print('ABORTED')
return
elif not exists:
password = getpass('Password: ')
else:
print(' A label with that name already exists.')
+ uuid = exists.uuid
try:
priv_key = self.crypto.load_private_key(exists.key_pair_pem)
print(' ' \
+ 'ERROR: The existing key is OPEN and must remain so.')
print('ABORTED')
return
except TypeError: # (needs a password)
pass # this is expected
- password = getpass('Existing Password: ')
- if not password:
+ exists_password = getpass('Existing Password: ')
+ if not exists_password:
print('ABORTED')
return
try:
priv_key = self.crypto.load_private_key(exists.key_pair_pem,
- password=password)
+ password=exists_password)
except ValueError as e:
if e.args: e = ' | '.join(e.args)
print(f' ERROR: {e}')
print('ABORTED')
return
+ exists_priv_key = priv_key
password = getpass('New Password: ')
+ if password == exists_password:
+ print(' ' \
+ + 'WARNING: New password is same as the old password.')
+
if password:
results = self.crypto.PASS_POLICY.test(password)
if label.lower() in password.lower():
results.append('Contains Label Name')
if results:
print(' WARNING: You have entered a WEAK PASSWORD.')
print(' ' \
+ 'This makes it VERY LIKELY somebody will STEAL it.')
print(' The following tests FAILED:')
for result in results:
print(f' - {result}')
print(' Proceed with CAUTION!')
confirm = getpass('Confirm Password: ')
if confirm != password:
print('ABORTED (passwords do not match)')
return
else:
print(' WARNING: Empty password creates an OPEN key pair.')
print(' ' \
+ 'This means EVERYBODY is allowed to control the label')
print(' ' \
+ 'FOREVER and CANNOT BE UNDONE. Proceed with CAUTION!')
agree = input('Type AGREE to continue: ')
if agree != 'AGREE':
print('ABORTED')
return
print()
print('Generating key pair...', end='')
sys.stdout.flush()
keys = self.crypto.make_keys(password)
print(' [done]')
priv_key = keys[0]
priv_key_pem = keys[1]
pub_key_pem = keys[2]
print('Creating signature...', end='')
sys.stdout.flush()
- time_nonce = self.crypto.make_time_nonce()
- msg = self.crypto.get_register_message(time_nonce)
- signature = self.crypto.sign(priv_key, msg)
+ if not exists:
+ time_nonce = self.crypto.make_time_nonce()
+ msg = self.crypto.get_register_message(label, time_nonce)
+ signature = self.crypto.sign(priv_key, msg)
+ else:
+ time_nonce = None
+ msg = self.crypto.get_reregister_message(
+ exists.time_nonce,
+ exists.uuid,
+ priv_key_pem + pub_key_pem)
+ signature = self.crypto.sign(exists_priv_key, msg)
print(' [done]')
print('Sanity check...', end='')
sys.stdout.flush()
- assert self.crypto.verify(priv_key.public_key(), msg, signature)
+ if not exists:
+ assert self.crypto.verify(priv_key.public_key(), msg, signature)
+ else:
+ assert self.crypto.verify(exists_priv_key.public_key(), msg,
+ signature)
print(' [done]')
print('Transmitting...', end='')
sys.stdout.flush()
- if client.register_label(label, time_nonce, priv_key_pem,
- pub_key_pem, signature, self.crypto.VERSION):
+ if client.register_label(uuid, label, priv_key_pem, pub_key_pem,
+ signature, self.crypto.VERSION, time_nonce):
print(' [SUCCESS]')
else:
print(' [FAIL]')
finally:
client.stop()
client.join()
class Client(Thread):
def __init__(self):
super().__init__(name='registration client')
self.go = False
self.ready = Event()
#self.queue = Queue(maxsize=2)
self.input = None
self.output = None
def stop(self):
#print('STOP')
self.go = False
- def check_label(self, label):
+ def check_label(self, uuid, label):
self.ready.clear()
- self.input = Reservation(label)
+ self.input = Reservation(label, uuid)
self.ready.wait()
if isinstance(self.output, Reservation):
return False
elif isinstance(self.output, ReservationCancel):
#return False if self.output.exists else None
# FIXME: if exists, transmit request to get the key
if not self.output.exists:
self._error_('LABEL RESERVATION CONFLICT')
return None
self.ready.clear()
self.input = GetRegistration(label)
self.ready.wait()
assert isinstance(self.output, Registration)
return self.output
else:
return None
- def register_label(self, label, time_nonce, priv_key_pem, pub_key_pem,
- signature, version):
+ def register_label(self, uuid, label, priv_key_pem, pub_key_pem,
+ signature, version, time_nonce=None):
self.ready.clear()
- self.input = Registration(label, version, time_nonce,
- priv_key_pem + pub_key_pem,
- signature)
+
+ if time_nonce:
+ self.input = Registration(uuid, label, version, time_nonce,
+ priv_key_pem + pub_key_pem,
+ signature)
+ else:
+ self.input = ReRegistration(uuid4(), label, version, str(uuid),
+ priv_key_pem + pub_key_pem,
+ signature)
+
self.ready.wait()
if isinstance(self.output, RegistrationSuccess):
return True
else:
return False
def run(self):
self.go = True
print(' - connecting to node:')
node = None
for addr in NODES:
print(f' * trying {addr}... ', end='')
sys.stdout.flush()
try:
sock = socket.create_connection(addr, timeout=HELLO_TIMEOUT)
sock.settimeout(HELLO_TIMEOUT)
node = Handler(sock, addr)
node.hello()
print('[READY]')
break
except ConnectionRefusedError:
print('[REFUSED]')
node = None
except socket.timeout:
print('[TIMEOUT]')
node = None
self.ready.set()
if not node:
self.go = False
while self.go:
node.sock.settimeout(1)
seconds = 0
while self.go and not self.input and seconds < KEEPALIVE:
try:
msg = node.recv()
if not msg:
self._error_('LOST CONNECTION')
return
else:
self._error_(f'UNEXPECTED RESPONSE: {msg}')
return
except socket.timeout:
pass # this is expected
seconds += 1
if self.go and not self.input:
self._handle_(node, Ping())
if self.go and self.input:
self._handle_(node, self.input)
self.input = None
def _handle_(self, node, req):
node.sock.settimeout(GENERAL_TIMEOUT)
try:
node.send(req)
resp = node.recv()
if not resp:
self._error_('LOST CONNECTION')
return
elif isinstance(resp, NodeIsIsolated):
self._error_('NODE IS ISOLATED')
return
if isinstance(req, Ping):
if not isinstance(resp, Pong):
self._error_(f'UNEXPECTED RESPONSE: {resp}')
elif resp.nonce != req.nonce:
self._error_(f'BAD NONCE: {resp}')
elif isinstance(req, Reservation):
if isinstance(resp, Reservation) \
or isinstance(resp, ReservationCancel):
self.output = resp
self.ready.set()
else:
self._error_(f'UNEXPECTED RESPONSE: {resp}')
elif isinstance(req, Registration):
if isinstance(resp, RegistrationSuccess):
self.output = resp
self.ready.set()
else:
self._error_(f'UNEXPECTED RESPONSE: {resp}')
elif isinstance(req, GetRegistration):
if isinstance(resp, Registration):
self.output = resp
self.ready.set()
else:
self._error_(f'UNEXPECTED RESPONSE: {resp}')
else:
self._error_(f'UNHANDLED REQUEST: {req}')
except socket.timeout:
self._error_('TIMEOUT')
except BrokenPipeError:
self._error_('BROKEN PIPE')
except ConnectionResetError:
self._error_('CONNECTION RESET')
def _error_(self, txt):
print()
print(f'** ERROR ** [{txt}]')
self.go = False
self.ready.set()
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Sep 20, 7:03 PM (20 h, 18 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3584
Default Alt Text
(64 KB)
Attached To
Mode
rLANK Encrypted Communications
Attached
Detach File
Event Timeline
Log In to Comment