Skip to content

Commit 140eeaa

Browse files
authored
Secure Witness Support (WebOfTrust#750)
* Changes to support secure witness transactions. Signed-off-by: pfeairheller <[email protected]> * 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 <[email protected]> * Updated setup to include QR code library. Signed-off-by: pfeairheller <[email protected]> * Update misfits to be a suber instead of a CESR suber. Signed-off-by: pfeairheller <[email protected]> --------- Signed-off-by: pfeairheller <[email protected]>
1 parent 79f94ad commit 140eeaa

File tree

13 files changed

+272
-50
lines changed

13 files changed

+272
-50
lines changed

scripts/demo/basic/essr.sh

Lines changed: 0 additions & 19 deletions
This file was deleted.

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@
9494
'PrettyTable>=3.10.0',
9595
'http_sfv>=0.9.9',
9696
'cryptography>=42.0.5',
97-
'semver>=3.0.2'
97+
'semver>=3.0.2',
98+
'qrcode>=7.4.2'
9899
],
99100
extras_require={
100101
},

src/keri/app/agenting.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,23 @@ def __init__(self, hby, msgs=None, gets=None, cues=None):
3939

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

42-
def receipt(self, pre, sn=None):
42+
def receipt(self, pre, sn=None, auths=None):
4343
""" Returns a generator for witness receipting
4444
4545
The returns a generator that will submit the designated event to witnesses for receipts using
46-
the synchronous witness API, the propogate the receipts to each of the other witnesses.
46+
the synchronous witness API, then propogate the receipts to each of the other witnesses.
4747
4848
4949
Parameters:
5050
pre (str): qualified base64 identifier to gather receipts for
5151
sn: (Optiona[int]): sequence number of event to gather receipts for, latest is used if not provided
52+
auths: (Options[dict]): map of witness AIDs to (time,auth) tuples for providing TOTP auth for witnessing
5253
5354
Returns:
5455
list: identifiers of witnesses that returned receipts.
5556
5657
"""
58+
auths = auths if auths is not None else dict()
5759
if pre not in self.hby.prefixes:
5860
raise kering.MissingEntryError(f"{pre} not a valid AID")
5961

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

8789
rcts = dict()
8890
for wit, client in clients.items():
89-
httping.streamCESRRequests(client=client, dest=wit, ims=bytearray(msg), path="/receipts")
91+
headers = dict()
92+
if wit in auths:
93+
headers["Authorization"] = auths[wit]
94+
95+
httping.streamCESRRequests(client=client, dest=wit, ims=bytearray(msg), path="receipts", headers=headers)
9096
while not client.responses:
9197
yield self.tock
9298

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

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

10581064
url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https]
10591065
up = urlparse(url)
1060-
client = http.clienting.Client(scheme=up.scheme, hostname=up.hostname, port=up.port)
1066+
client = http.clienting.Client(scheme=up.scheme, hostname=up.hostname, port=up.port, path=up.path)
10611067
clientDoer = http.clienting.ClientDoer(client=client)
10621068

10631069
return client, clientDoer

src/keri/app/cli/commands/decrypt.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# -*- encoding: utf-8 -*-
2+
"""
3+
keri.kli.commands module
4+
5+
"""
6+
import argparse
7+
8+
from hio.base import doing
9+
10+
from keri import kering
11+
from keri.app.cli.common import existing
12+
from keri.core import indexing, coring, MtrDex
13+
14+
parser = argparse.ArgumentParser(description='Decrypt arbitrary data for AIDs with Ed25519 public keys only')
15+
parser.set_defaults(handler=lambda args: handler(args))
16+
parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True)
17+
parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore',
18+
required=False, default="")
19+
parser.add_argument('--alias', '-a', help='human readable alias for the new identifier prefix', required=True)
20+
parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)',
21+
dest="bran", default=None) # passcode => bran
22+
parser.add_argument('--data', '-d', help='Encrypted data or file (starts with "@")', required=True)
23+
24+
25+
def handler(args):
26+
"""
27+
Verify signatures on arbitrary data
28+
29+
Args:
30+
args(Namespace): arguments object from command line
31+
"""
32+
kwa = dict(args=args)
33+
return [doing.doify(decrypt, **kwa)]
34+
35+
36+
def decrypt(tymth, tock=0.0, **opts):
37+
""" Command line status handler
38+
39+
"""
40+
_ = (yield tock)
41+
args = opts["args"]
42+
43+
name = args.name
44+
alias = args.alias
45+
base = args.base
46+
bran = args.bran
47+
48+
try:
49+
with existing.existingHab(name=name, alias=alias, base=base, bran=bran) as (_, hab):
50+
51+
data = args.data
52+
if data.startswith("@"):
53+
f = open(data[1:], "r")
54+
data = f.read()
55+
else:
56+
data = data
57+
58+
m = coring.Matter(qb64=data)
59+
d = coring.Matter(qb64=hab.decrypt(m.raw))
60+
print(d.raw)
61+
62+
except kering.ConfigurationError:
63+
print(f"prefix for {name} does not exist, incept must be run first", )
64+
except FileNotFoundError:
65+
print("unable to open file", args.text[1:])

src/keri/app/cli/commands/export.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
logger = help.ogler.getLogger()
1717

18-
parser = argparse.ArgumentParser(description='List credentials and check mailboxes for any newly issued credentials')
18+
parser = argparse.ArgumentParser(description='Export key events in CESR stream format')
1919
parser.set_defaults(handler=lambda args: export(args),
2020
transferable=True)
2121
parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True)

src/keri/app/cli/commands/interact.py

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
"""
66
import argparse
77
import json
8-
from ordered_set import OrderedSet as oset
98

109
from hio.base import doing
1110

1211
from keri import kering
12+
from keri.help import helping
1313
from ..common import existing
1414
from ... import habbing, agenting, indirecting
1515

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

2630

2731
def interact(args):
@@ -52,7 +56,8 @@ def interact(args):
5256
else:
5357
data = None
5458

55-
ixnDoer = InteractDoer(name=name, base=base, alias=alias, bran=bran, data=data)
59+
ixnDoer = InteractDoer(name=name, base=base, alias=alias, bran=bran, data=data, authenticate=args.authenticate,
60+
endpoint=args.endpoint)
5661

5762
return [ixnDoer]
5863

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

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

7681
self.alias = alias
7782
self.data = data
83+
self.endpoint = endpoint
84+
self.authenticate = authenticate
7885

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

99-
witDoer = agenting.WitnessReceiptor(hby=self.hby)
100-
self.extend(doers=[witDoer])
106+
if self.endpoint or self.authenticate:
107+
receiptor = agenting.Receiptor(hby=self.hby)
108+
self.extend([receiptor])
101109

102-
if hab.kever.wits:
103-
witDoer.msgs.append(dict(pre=hab.pre))
104-
while not witDoer.cues:
105-
_ = yield self.tock
110+
auths = {}
111+
if self.authenticate:
112+
for wit in hab.kever.wits:
113+
code = input(f"Entire code for {wit}: ")
114+
auths[wit] = f"{code}#{helping.nowIso8601()}"
115+
yield from receiptor.receipt(hab.pre, sn=hab.kever.sn, auths=auths)
116+
self.remove([receiptor])
117+
118+
else:
119+
120+
witDoer = agenting.WitnessReceiptor(hby=self.hby)
121+
self.extend(doers=[witDoer])
122+
123+
if hab.kever.wits:
124+
witDoer.msgs.append(dict(pre=hab.pre))
125+
while not witDoer.cues:
126+
_ = yield self.tock
127+
128+
self.remove([witDoer])
106129

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

112-
toRemove = [self.hbyDoer, witDoer, self.mbx]
135+
toRemove = [self.hbyDoer, self.mbx]
113136
self.remove(toRemove)
114137

115138
return

src/keri/app/cli/commands/rotate.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from keri import kering
1212
from keri.app.cli.common import rotating, existing, config
1313
from keri.core import coring
14+
from keri.help import helping
1415
from ... import habbing, agenting, indirecting, delegating, forwarding
1516

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

3134
rotating.addRotationArgs(parser)
@@ -60,7 +63,7 @@ def rotate(args):
6063
cuts=opts.witsCut, adds=opts.witsAdd,
6164
isith=opts.isith, nsith=opts.nsith,
6265
count=opts.ncount, toad=opts.toad,
63-
data=opts.data, proxy=args.proxy)
66+
data=opts.data, proxy=args.proxy, authenticate=args.authenticate)
6467

6568
doers = [rotDoer]
6669

@@ -115,7 +118,7 @@ class RotateDoer(doing.DoDoer):
115118
"""
116119

117120
def __init__(self, name, base, bran, alias, endpoint=False, isith=None, nsith=None, count=None,
118-
toad=None, wits=None, cuts=None, adds=None, data: list = None, proxy=None):
121+
toad=None, wits=None, cuts=None, adds=None, data: list = None, proxy=None, authenticate=False):
119122
"""
120123
Returns DoDoer with all registered Doers needed to perform rotation.
121124
@@ -140,6 +143,7 @@ def __init__(self, name, base, bran, alias, endpoint=False, isith=None, nsith=No
140143
self.data = data
141144
self.endpoint = endpoint
142145
self.proxy = proxy
146+
self.authenticate = authenticate
143147

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

196200
elif hab.kever.wits:
197-
if self.endpoint:
198-
yield from receiptor.receipt(hab.pre, sn=hab.kever.sn)
201+
if self.endpoint or self.authenticate:
202+
auths = {}
203+
if self.authenticate:
204+
for wit in hab.kever.wits:
205+
code = input(f"Entire code for {wit}: ")
206+
auths[wit] = f"{code}#{helping.nowIso8601()}"
207+
yield from receiptor.receipt(hab.pre, sn=hab.kever.sn, auths=auths)
199208
else:
200209
for wit in self.adds:
201210
self.mbx.addPoller(hab, witness=wit)

0 commit comments

Comments
 (0)