Page MenuHomePhorge

No OneTemporary

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

Mime Type
text/x-diff
Expires
Sun, Sep 20, 7:03 PM (20 h, 11 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3584
Default Alt Text
(64 KB)

Event Timeline