Skip to content

Commit

Permalink
Implemented listening package and listen command, adjusted directing …
Browse files Browse the repository at this point in the history
…to accepts cues.
  • Loading branch information
arilieb committed Aug 25, 2024
1 parent 9d9a481 commit 6cd09cb
Show file tree
Hide file tree
Showing 3 changed files with 315 additions and 8 deletions.
64 changes: 64 additions & 0 deletions src/keri/app/cli/commands/listen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# -*- encoding: utf-8 -*-
"""
KERI
keri.kli.commands module
"""
import argparse
import os
import os.path

import falcon
from hio import help
from hio.core.uxd import Server, ServerDoer
from hio.help import decking

from keri import kering
from keri.app import habbing, directing
from keri.app.cli.common import existing

from keri.app.listening import Authenticator, IdentifiersHandler, UnlockHandler, SignHandler
logger = help.ogler.getLogger()

parser = argparse.ArgumentParser(description='Run Unix domain sockets server listening for browser support')
parser.set_defaults(handler=lambda args: handler(args),
transferable=True)
# parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True)
parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore',
required=False, default="")
# parser.add_argument('--alias', '-a', help='human readable alias for the new identifier prefix', default=None)
parser.add_argument('--passcode', '-p', help='21 character encryption passcode for keystore (is not saved)',
dest="bran", default=None) # passcode => bran

parser.add_argument("--verbose", "-V", help="print JSON of all current events", action="store_true")


def loadHandlers(hby, cues):
ids = IdentifiersHandler(cues=cues, base=hby.base)
hby.exc.addHandler(ids)
unlock = UnlockHandler(cues=cues, base=hby.base)
hby.exc.addHandler(unlock)
sign = SignHandler(cues=cues, base=hby.base)
hby.exc.addHandler(sign)

def handler(args):
""" Command line list handler
"""
hby = existing.setupHby(name="listener", base=args.base, bran=args.bran)
hab = hby.habByName("listener")

hbyDoer = habbing.HaberyDoer(habery=hby) # setup doer

cues = decking.Deck()
loadHandlers(hby, cues)

if os.path.exists("/tmp/keripy_kli.s"):
os.remove("/tmp/keripy_kli.s")

server = Server(path="/tmp/keripy_kli.s",
bufsize=8069)
serverDoer = ServerDoer(server=server)
directant = directing.Directant(hab=hab, server=server, exchanger=hby.exc, cues=cues)

return [directant, serverDoer, hbyDoer]
24 changes: 16 additions & 8 deletions src/keri/app/directing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
simple direct mode demo support classes
"""
import itertools
import json

from hio.base import doing
from hio.help import decking

from .. import help
from ..core import eventing, routing
Expand Down Expand Up @@ -356,7 +359,7 @@ class Directant(doing.DoDoer):
._tock is hidden attribute for .tock property
"""

def __init__(self, hab, server, verifier=None, exchanger=None, doers=None, **kwa):
def __init__(self, hab, server, verifier=None, exchanger=None, doers=None, cues=None, **kwa):
"""
Initialize instance.
Expand All @@ -374,6 +377,8 @@ def __init__(self, hab, server, verifier=None, exchanger=None, doers=None, **kwa
self.exchanger = exchanger
self.server = server # use server for cx
self.rants = dict()
self.cues = cues if cues is not None else decking.Deck()

doers = doers if doers is not None else []
doers.extend([doing.doify(self.serviceDo)])
super(Directant, self).__init__(doers=doers, **kwa)
Expand Down Expand Up @@ -419,7 +424,7 @@ def serviceDo(self, tymth=None, tock=0.0, **opts):

if ca not in self.rants: # create Reactant and extend doers with it
rant = Reactant(tymth=self.tymth, hab=self.hab, verifier=self.verifier,
exchanger=self.exchanger, remoter=ix)
exchanger=self.exchanger, remoter=ix, cues=self.cues)
self.rants[ca] = rant
# add Reactant (rant) doer to running doers
self.extend(doers=[rant]) # open and run rant as doer
Expand Down Expand Up @@ -498,7 +503,7 @@ class Reactant(doing.DoDoer):
"""

def __init__(self, hab, remoter, verifier=None, exchanger=None, doers=None, **kwa):
def __init__(self, hab, remoter, verifier=None, exchanger=None, doers=None, cues=None, **kwa):
"""
Initialize instance.
Expand All @@ -518,6 +523,7 @@ def __init__(self, hab, remoter, verifier=None, exchanger=None, doers=None, **kw
self.verifier = verifier
self.exchanger = exchanger
self.remoter = remoter # use remoter for both rx and tx
self.cues = cues if cues is not None else decking.Deck()

doers = doers if doers is not None else []
doers.extend([doing.doify(self.msgDo),
Expand Down Expand Up @@ -560,7 +566,6 @@ def wind(self, tymth):
super(Reactant, self).wind(tymth)
self.remoter.wind(tymth)


def msgDo(self, tymth=None, tock=0.0, **opts):
"""
Returns doifiable Doist compatibile generator method (doer dog) to process
Expand All @@ -586,8 +591,7 @@ def msgDo(self, tymth=None, tock=0.0, **opts):
logger.info("Server %s: received:\n%s\n...\n", self.hab.name,
self.parser.ims[:1024])
done = yield from self.parser.parsator(local=True) # process messages continuously
return done # should nover get here except forced close

return done # should never get here except forced close

def cueDo(self, tymth=None, tock=0.0, **opts):
"""
Expand Down Expand Up @@ -616,8 +620,13 @@ def cueDo(self, tymth=None, tock=0.0, **opts):

self.sendMessage(msg, label="chit or receipt or replay")
yield # throttle just do one cue at a time
while self.cues:
msg = self.cues.popleft()
data = json.dumps(msg).encode("utf-8")

self.sendMessage(data, label="exn response")
yield # throttle just do one cue at a time
yield
return False # should never get here except forced close


def escrowDo(self, tymth=None, tock=0.0, **opts):
Expand Down Expand Up @@ -645,7 +654,6 @@ def escrowDo(self, tymth=None, tock=0.0, **opts):
if self.tevery is not None:
self.tevery.processEscrows()
yield
return False # should never get here except forced close

def sendMessage(self, msg, label=""):
"""
Expand Down
235 changes: 235 additions & 0 deletions src/keri/app/listening.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import argparse
import os
import os.path

import falcon
from hio import help
from hio.core.uxd import Server, ServerDoer
from hio.help import decking

from keri import kering
from hio.help import Hict
from keri.end import ending
from keri.help import helping
from keri.app import habbing, directing
from keri.app.cli.common import existing
class SignHandler:
resource = "/sign"

def __init__(self, cues, base):
""" Initialize peer to peer challenge response messsage """

self.cues = cues
self.base = base
super(SignHandler, self).__init__()

def handle(self, serder, attachments=None):
""" Do route specific processsing of Challenge response messages
Parameters:
serder (Serder): Serder of the exn challenge response message
attachments (list): list of tuples of pather, CESR SAD path attachments to the exn event
"""
# print(serder.ked)
payload = serder.ked['a']

name = payload["name"]
passcode = payload["passcode"] if "passcode" in payload else None
method = payload["method"] if "method" in payload else None
data = payload["data"] if "data" in payload else None
path = payload["path"] if "path" in payload else None
signator = payload["signator"] if "signator" in payload else None

print(data)
try:
hby = habbing.Habery(name=name, base=self.base, bran=passcode)
for hab in hby.habs.values():
aid = hab.pre
if hab.name == signator:
try:
auth = Authenticator(path=path, name=signator, aid=aid, method=method, hab=hab)
except Exception as err:
print(err)
headers = auth.sign()


hby.close()
self.cues.append(dict(status=falcon.HTTP_200, body=headers))
except (kering.AuthError, ValueError) as e:
msg = dict(status=falcon.HTTP_400, body=str(e))
self.cues.append(msg)
class IdentifiersHandler:
""" Handle challenge response peer to peer `exn` message """

resource = "/identifiers"

def __init__(self, cues, base):
""" Initialize peer to peer challenge response messsage """

self.cues = cues
self.base = base
super(IdentifiersHandler, self).__init__()

def handle(self, serder, attachments=None):
""" Do route specific processsing of Challenge response messages
Parameters:
serder (Serder): Serder of the exn challenge response message
attachments (list): list of tuples of pather, CESR SAD path attachments to the exn event
"""
payload = serder.ked['a']
name = payload["name"]
passcode = payload["passcode"] if "passcode" in payload else None

try:
hby = habbing.Habery(name=name, base=self.base, bran=passcode)
print("habs")
identifiers = []
for hab in hby.habs.values():
msg = dict(name=hab.name, prefix=hab.pre)
identifiers.append(msg)

hby.close()
self.cues.append(dict(status=falcon.HTTP_200, body=identifiers))
except (kering.AuthError, ValueError) as e:
msg = dict(status=falcon.HTTP_400, body=str(e))
self.cues.append(msg)


class UnlockHandler:
""" Handle challenge response peer to peer `exn` message """

resource = "/unlock"

def __init__(self, cues, base):
""" Initialize peer to peer challenge response messsage """

self.cues = cues
self.base = base
super(UnlockHandler, self).__init__()

def handle(self, serder, attachments=None):
""" Do route specific processsing of Challenge response messages
Parameters:
serder (Serder): Serder of the exn challenge response message
attachments (list): list of tuples of pather, CESR SAD path attachments to the exn event
"""
payload = serder.ked['a']
name = payload["name"]
passcode = payload["passcode"] if "passcode" in payload else None

try:
hby = habbing.Habery(name=name, base=self.base, bran=passcode, free=True)
print("unlocked")
msg = dict(status=falcon.HTTP_200, body={})
hby.close()

except (kering.AuthError, ValueError) as e:
msg = dict(status=falcon.HTTP_400, body=str(e))

self.cues.append(msg)

class Authenticator:
def __init__(self, path, name, aid, method, hab):
self.path = path
self.name = name
self.aid = aid
self.method = method
self.hab = hab
self.default_fields = ["Signify-Resource",
"@method",
"@path",
"Signify-Timestamp"]
@staticmethod
def resource(response):
headers = response.headers
if "SIGNIFY-RESOURCE" not in headers:
raise kering.AuthNError("SIGNIFY-RESOURCE not found in header.")
return headers["SIGNIFY-RESOURCE"]

def sign(self):
headers = Hict([
("Content-Type", "application/json"),
("Content-Length", "256"),
("Connection", "close"),
("Signify-Resource", self.aid),
("Signify-Timestamp", helping.nowIso8601()),
])
if self.method == "DELETE" or self.method == "GET":
headers = Hict([
("Connection", "close"),
("Signify-Resource", self.aid),
("Signify-Timestamp", helping.nowIso8601()),
])
header, qsig = ending.siginput("signify", method=self.method, path=self.path, headers=headers, fields=self.default_fields,
hab=self.hab, alg="ed25519", keyid=self.aid)
headers.extend(header)
signage = ending.Signage(markers=dict(signify=qsig), indexed=False, signer=None, ordinal=None, digest=None,
kind=None)
headers.extend(ending.signature([signage]))

return dict(headers)

def verify(self, response):
headers = response.headers

if "SIGNATURE-INPUT" not in headers or "SIGNATURE" not in headers:
return False

siginput = headers["SIGNATURE-INPUT"]
if not siginput:
return False
signature = headers["SIGNATURE"]
if not signature:
return False

inputs = ending.desiginput(siginput.encode("utf-8"))
inputs = [i for i in inputs if i.name == "signify"]

if not inputs:
return False

for inputage in inputs:
items = []
for field in inputage.fields:
key = field.upper()
field = field.lower()
if key not in headers:
continue

value = ending.normalize(headers[key])
items.append(f'"{field}": {value}')

values = [f"({' '.join(inputage.fields)})", f"created={inputage.created}"]
if inputage.expires is not None:
values.append(f"expires={inputage.expires}")
if inputage.nonce is not None:
values.append(f"nonce={inputage.nonce}")
if inputage.keyid is not None:
values.append(f"keyid={inputage.keyid}")
if inputage.context is not None:
values.append(f"context={inputage.context}")
if inputage.alg is not None:
values.append(f"alg={inputage.alg}")

params = ';'.join(values)

items.append(f'"@signature-params: {params}"')
ser = "\n".join(items).encode("utf-8")

resource = self.resource(response)

with habbing.openHab(name="hkapi", temp=False) as (hkHby, hkHab):
if resource not in hkHab.kevers:
raise kering.AuthNError("unknown or invalid controller")

ckever = hkHab.kevers[resource]
signages = ending.designature(signature)
cig = signages[0].markers[inputage.name]
if not ckever.verfers[0].verify(sig=cig.raw, ser=ser):
raise kering.AuthNError(f"Signature for {inputage} invalid")

return True

0 comments on commit 6cd09cb

Please sign in to comment.