Skip to content

Commit

Permalink
Secure Witness Support (#750)
Browse files Browse the repository at this point in the history
* Changes to support secure witness transactions.

Signed-off-by: pfeairheller <pfeairheller@gmail.com>

* Enhanced interact and rotate kli commands to allow for --authenticate parameter to ask user for a 2-factor auth code to send to witnesses for secure witness interactions.

Signed-off-by: pfeairheller <pfeairheller@gmail.com>

* Updated setup to include QR code library.

Signed-off-by: pfeairheller <pfeairheller@gmail.com>

* Update misfits to be a suber instead of a CESR suber.

Signed-off-by: pfeairheller <pfeairheller@gmail.com>

---------

Signed-off-by: pfeairheller <pfeairheller@gmail.com>
  • Loading branch information
pfeairheller committed Apr 11, 2024
1 parent 79f94ad commit 140eeaa
Show file tree
Hide file tree
Showing 13 changed files with 272 additions and 50 deletions.
19 changes: 0 additions & 19 deletions scripts/demo/basic/essr.sh

This file was deleted.

3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@
'PrettyTable>=3.10.0',
'http_sfv>=0.9.9',
'cryptography>=42.0.5',
'semver>=3.0.2'
'semver>=3.0.2',
'qrcode>=7.4.2'
],
extras_require={
},
Expand Down
16 changes: 11 additions & 5 deletions src/keri/app/agenting.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,23 @@ def __init__(self, hby, msgs=None, gets=None, cues=None):

super(Receiptor, self).__init__(doers=doers)

def receipt(self, pre, sn=None):
def receipt(self, pre, sn=None, auths=None):
""" Returns a generator for witness receipting
The returns a generator that will submit the designated event to witnesses for receipts using
the synchronous witness API, the propogate the receipts to each of the other witnesses.
the synchronous witness API, then propogate the receipts to each of the other witnesses.
Parameters:
pre (str): qualified base64 identifier to gather receipts for
sn: (Optiona[int]): sequence number of event to gather receipts for, latest is used if not provided
auths: (Options[dict]): map of witness AIDs to (time,auth) tuples for providing TOTP auth for witnessing
Returns:
list: identifiers of witnesses that returned receipts.
"""
auths = auths if auths is not None else dict()
if pre not in self.hby.prefixes:
raise kering.MissingEntryError(f"{pre} not a valid AID")

Expand Down Expand Up @@ -86,7 +88,11 @@ def receipt(self, pre, sn=None):

rcts = dict()
for wit, client in clients.items():
httping.streamCESRRequests(client=client, dest=wit, ims=bytearray(msg), path="/receipts")
headers = dict()
if wit in auths:
headers["Authorization"] = auths[wit]

httping.streamCESRRequests(client=client, dest=wit, ims=bytearray(msg), path="receipts", headers=headers)
while not client.responses:
yield self.tock

Expand All @@ -101,7 +107,7 @@ def receipt(self, pre, sn=None):
coring.Counter(qb64b=rct, strip=True)
rcts[wit] = rct
else:
logger.error(f"invalid response {rep.status} from witnesses {wit}")
print(f"invalid response {rep.status} from witnesses {wit}")

for wit in rcts:
ewits = [w for w in rcts if w != wit]
Expand Down Expand Up @@ -1057,7 +1063,7 @@ def httpClient(hab, wit):

url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https]
up = urlparse(url)
client = http.clienting.Client(scheme=up.scheme, hostname=up.hostname, port=up.port)
client = http.clienting.Client(scheme=up.scheme, hostname=up.hostname, port=up.port, path=up.path)
clientDoer = http.clienting.ClientDoer(client=client)

return client, clientDoer
Expand Down
65 changes: 65 additions & 0 deletions src/keri/app/cli/commands/decrypt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# -*- encoding: utf-8 -*-
"""
keri.kli.commands module
"""
import argparse

from hio.base import doing

from keri import kering
from keri.app.cli.common import existing
from keri.core import indexing, coring, MtrDex

parser = argparse.ArgumentParser(description='Decrypt arbitrary data for AIDs with Ed25519 public keys only')
parser.set_defaults(handler=lambda args: handler(args))
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', required=True)
parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)',
dest="bran", default=None) # passcode => bran
parser.add_argument('--data', '-d', help='Encrypted data or file (starts with "@")', required=True)


def handler(args):
"""
Verify signatures on arbitrary data
Args:
args(Namespace): arguments object from command line
"""
kwa = dict(args=args)
return [doing.doify(decrypt, **kwa)]


def decrypt(tymth, tock=0.0, **opts):
""" Command line status handler
"""
_ = (yield tock)
args = opts["args"]

name = args.name
alias = args.alias
base = args.base
bran = args.bran

try:
with existing.existingHab(name=name, alias=alias, base=base, bran=bran) as (_, hab):

data = args.data
if data.startswith("@"):
f = open(data[1:], "r")
data = f.read()
else:
data = data

m = coring.Matter(qb64=data)
d = coring.Matter(qb64=hab.decrypt(m.raw))
print(d.raw)

except kering.ConfigurationError:
print(f"prefix for {name} does not exist, incept must be run first", )
except FileNotFoundError:
print("unable to open file", args.text[1:])
2 changes: 1 addition & 1 deletion src/keri/app/cli/commands/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

logger = help.ogler.getLogger()

parser = argparse.ArgumentParser(description='List credentials and check mailboxes for any newly issued credentials')
parser = argparse.ArgumentParser(description='Export key events in CESR stream format')
parser.set_defaults(handler=lambda args: export(args),
transferable=True)
parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True)
Expand Down
45 changes: 34 additions & 11 deletions src/keri/app/cli/commands/interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
"""
import argparse
import json
from ordered_set import OrderedSet as oset

from hio.base import doing

from keri import kering
from keri.help import helping
from ..common import existing
from ... import habbing, agenting, indirecting

Expand All @@ -22,6 +22,10 @@
parser.add_argument('--passcode', '-p', help='21 character encryption passcode for keystore (is not saved)',
dest="bran", default=None) # passcode => bran
parser.add_argument('--data', '-d', help='Anchor data, \'@\' allowed', default=None, action="store", required=False)
parser.add_argument("--receipt-endpoint", help="Attempt to connect to witness receipt endpoint for witness receipts.",
dest="endpoint", action='store_true')
parser.add_argument("--authenticate", '-z', help="Prompt the controller for authentication codes for each witness",
action='store_true')


def interact(args):
Expand Down Expand Up @@ -52,7 +56,8 @@ def interact(args):
else:
data = None

ixnDoer = InteractDoer(name=name, base=base, alias=alias, bran=bran, data=data)
ixnDoer = InteractDoer(name=name, base=base, alias=alias, bran=bran, data=data, authenticate=args.authenticate,
endpoint=args.endpoint)

return [ixnDoer]

Expand All @@ -63,7 +68,7 @@ class InteractDoer(doing.DoDoer):
to all appropriate witnesses
"""

def __init__(self, name, base, bran, alias, data: list = None):
def __init__(self, name, base, bran, alias, data: list = None, endpoint=False, authenticate=False):
"""
Returns DoDoer with all registered Doers needed to perform interaction event.
Expand All @@ -75,6 +80,8 @@ def __init__(self, name, base, bran, alias, data: list = None):

self.alias = alias
self.data = data
self.endpoint = endpoint
self.authenticate = authenticate

self.hby = existing.setupHby(name=name, base=base, bran=bran)
self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer
Expand All @@ -96,20 +103,36 @@ def interactDo(self, tymth, tock=0.0, **opts):
hab = self.hby.habByName(name=self.alias)
hab.interact(data=self.data)

witDoer = agenting.WitnessReceiptor(hby=self.hby)
self.extend(doers=[witDoer])
if self.endpoint or self.authenticate:
receiptor = agenting.Receiptor(hby=self.hby)
self.extend([receiptor])

if hab.kever.wits:
witDoer.msgs.append(dict(pre=hab.pre))
while not witDoer.cues:
_ = yield self.tock
auths = {}
if self.authenticate:
for wit in hab.kever.wits:
code = input(f"Entire code for {wit}: ")
auths[wit] = f"{code}#{helping.nowIso8601()}"
yield from receiptor.receipt(hab.pre, sn=hab.kever.sn, auths=auths)
self.remove([receiptor])

else:

witDoer = agenting.WitnessReceiptor(hby=self.hby)
self.extend(doers=[witDoer])

if hab.kever.wits:
witDoer.msgs.append(dict(pre=hab.pre))
while not witDoer.cues:
_ = yield self.tock

self.remove([witDoer])

print(f'Prefix {hab.pre}')
print(f'New Sequence No. {hab.kever.sn}')
for idx, verfer in enumerate(hab.kever.verfers):
print(f'\tPublic key {idx+1}: {verfer.qb64}')
print(f'\tPublic key {idx + 1}: {verfer.qb64}')

toRemove = [self.hbyDoer, witDoer, self.mbx]
toRemove = [self.hbyDoer, self.mbx]
self.remove(toRemove)

return
17 changes: 13 additions & 4 deletions src/keri/app/cli/commands/rotate.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from keri import kering
from keri.app.cli.common import rotating, existing, config
from keri.core import coring
from keri.help import helping
from ... import habbing, agenting, indirecting, delegating, forwarding

parser = argparse.ArgumentParser(description='Rotate keys')
Expand All @@ -26,6 +27,8 @@
default=None, type=int, required=False)
parser.add_argument("--receipt-endpoint", help="Attempt to connect to witness receipt endpoint for witness receipts.",
dest="endpoint", action='store_true')
parser.add_argument("--authenticate", '-z', help="Prompt the controller for authentication codes for each witness",
action='store_true')
parser.add_argument("--proxy", help="alias for delegation communication proxy", default="")

rotating.addRotationArgs(parser)
Expand Down Expand Up @@ -60,7 +63,7 @@ def rotate(args):
cuts=opts.witsCut, adds=opts.witsAdd,
isith=opts.isith, nsith=opts.nsith,
count=opts.ncount, toad=opts.toad,
data=opts.data, proxy=args.proxy)
data=opts.data, proxy=args.proxy, authenticate=args.authenticate)

doers = [rotDoer]

Expand Down Expand Up @@ -115,7 +118,7 @@ class RotateDoer(doing.DoDoer):
"""

def __init__(self, name, base, bran, alias, endpoint=False, isith=None, nsith=None, count=None,
toad=None, wits=None, cuts=None, adds=None, data: list = None, proxy=None):
toad=None, wits=None, cuts=None, adds=None, data: list = None, proxy=None, authenticate=False):
"""
Returns DoDoer with all registered Doers needed to perform rotation.
Expand All @@ -140,6 +143,7 @@ def __init__(self, name, base, bran, alias, endpoint=False, isith=None, nsith=No
self.data = data
self.endpoint = endpoint
self.proxy = proxy
self.authenticate = authenticate

self.wits = wits if wits is not None else []
self.cuts = cuts if cuts is not None else []
Expand Down Expand Up @@ -194,8 +198,13 @@ def rotateDo(self, tymth, tock=0.0):
yield self.tock

elif hab.kever.wits:
if self.endpoint:
yield from receiptor.receipt(hab.pre, sn=hab.kever.sn)
if self.endpoint or self.authenticate:
auths = {}
if self.authenticate:
for wit in hab.kever.wits:
code = input(f"Entire code for {wit}: ")
auths[wit] = f"{code}#{helping.nowIso8601()}"
yield from receiptor.receipt(hab.pre, sn=hab.kever.sn, auths=auths)
else:
for wit in self.adds:
self.mbx.addPoller(hab, witness=wit)
Expand Down
Loading

0 comments on commit 140eeaa

Please sign in to comment.