Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'kli listen' Command and 'listening.py' Package #852

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading