kept 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
kept/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ __version__ = "0.0.2" # also change in setup.py
kept/app/__init__.py ADDED
File without changes
File without changes
File without changes
@@ -0,0 +1,167 @@
1
+ # -*- encoding: utf-8 -*-
2
+ """
3
+ heki.app.cli module
4
+
5
+ """
6
+
7
+ import argparse
8
+ import asyncio
9
+ from urllib.parse import urlparse, parse_qs
10
+
11
+ from keri import help
12
+ from keri import kering
13
+ from keri.app import connecting
14
+ from keri.app.cli.common import existing
15
+
16
+ from kept.core.authentication import CryptSigner
17
+ from kept.essr.client import AsyncClient
18
+
19
+ parser = argparse.ArgumentParser(
20
+ description="Perform an kurl (essr encoded) request against the provided URL."
21
+ )
22
+ parser.set_defaults(handler=lambda args: asyncio.run(launch(args)))
23
+ parser.add_argument("url", help="ESSR URL to process", metavar="<url>")
24
+ parser.add_argument(
25
+ "--name",
26
+ "-n",
27
+ help="keystore name and file location of KERI keystore",
28
+ required=False,
29
+ default="owl",
30
+ )
31
+ parser.add_argument("--alias", action="store", required=False, default="owl")
32
+ parser.add_argument(
33
+ "--base",
34
+ "-b",
35
+ help="additional optional prefix to file location of KERI keystore",
36
+ required=False,
37
+ default="",
38
+ )
39
+ parser.add_argument(
40
+ "--passcode",
41
+ "-p",
42
+ help="21 character encryption passcode for keystore (is not saved)",
43
+ dest="bran",
44
+ default=None,
45
+ ) # passcode => bran
46
+ parser.add_argument("--remote", action="store", required=False, default=None)
47
+
48
+ parser.add_argument(
49
+ "--json", "-j", help="JSON data to send with request", type=str, default=None
50
+ )
51
+ parser.add_argument(
52
+ "--header",
53
+ "-H",
54
+ help='HTTP header in format "Name: Value"',
55
+ action="append",
56
+ default=[],
57
+ )
58
+ parser.add_argument(
59
+ "--files",
60
+ "-F",
61
+ help='HTTP header in format "Name: Value"',
62
+ action="append",
63
+ default=[],
64
+ )
65
+ parser.add_argument(
66
+ "--data", "-d", help="Form data to send with request", type=str, default=None
67
+ )
68
+ parser.add_argument(
69
+ "--timeout",
70
+ "-t",
71
+ help="Timeout in seconds for waiting for response",
72
+ type=int,
73
+ default=10,
74
+ )
75
+
76
+ logger = help.ogler.getLogger()
77
+
78
+
79
+ async def launch(args):
80
+ # Arguments from the command line
81
+ name = args.name
82
+ alias = args.alias
83
+ bran = args.bran
84
+
85
+ parsed = urlparse(args.url)
86
+
87
+ # Override name, alias and passcode if provided in the URL
88
+ if parsed.username:
89
+ name = parsed.username
90
+
91
+ if parsed.password:
92
+ if ":" in parsed.password:
93
+ alias, bran = parsed.password.split(":", maxsplit=1)
94
+ else:
95
+ alias = parsed.password
96
+
97
+ with existing.existingHab(name=name, alias=alias, base=args.base, bran=bran) as (
98
+ hby,
99
+ hab,
100
+ ):
101
+ if not hab:
102
+ raise kering.ConfigurationError(
103
+ f"Identifier '{alias}' must already exist, exiting."
104
+ )
105
+ org = connecting.Organizer(hby=hby)
106
+
107
+ # Determine the remote identifier to use, start with the --remote argument, then fallback to the hostname
108
+ root = args.remote
109
+
110
+ if not root:
111
+ root = parsed.hostname
112
+
113
+ target = None
114
+ if root in hby.kevers:
115
+ target = hby.kevers[root].pre
116
+ else:
117
+ contacts = org.find("alias", root)
118
+ for contact in contacts:
119
+ if contact["alias"] == root:
120
+ target = contact["id"]
121
+
122
+ if not target:
123
+ raise kering.ConfigurationError(
124
+ f"Invalid identifier '{root}' for {args.url}, not found"
125
+ )
126
+
127
+ # Build request parameters from arguments
128
+ headers = {}
129
+ for header in args.header:
130
+ name, value = header.split(":", 1)
131
+ headers[name.strip()] = value.strip()
132
+
133
+ data = None
134
+ if args.data:
135
+ if args.data.startswith("@"):
136
+ f = open(args.data[1:], "r")
137
+ data = f.read()
138
+ else:
139
+ data = args.data.encode("utf-8")
140
+
141
+ return_route = "/owl/response"
142
+
143
+ # Parse query parameters into dictionary
144
+ query_params = {}
145
+ if parsed.query:
146
+ parsed_params = parse_qs(parsed.query)
147
+ query_params = {
148
+ k: v[0] if len(v) == 1 else v for k, v in parsed_params.items()
149
+ }
150
+
151
+ client = AsyncClient(
152
+ params=query_params,
153
+ return_route=return_route,
154
+ headers=list(headers.items()),
155
+ )
156
+
157
+ crypt_signer = CryptSigner(hby=hby, hab=hab, encryption_target=target)
158
+ response = await client.request(
159
+ crypt_signer=crypt_signer,
160
+ url=args.url,
161
+ json=args.json,
162
+ files=None, # TODO: support files from the commandline, maybe?
163
+ data=data,
164
+ )
165
+
166
+ print(response.headers)
167
+ print(await response.aread())
kept/app/cli/keeper.py ADDED
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8 -*-
2
+ """
3
+ kept.app.cli module
4
+
5
+ """
6
+
7
+ import multicommand
8
+ from keri import help
9
+
10
+ from keri.app import directing
11
+ from kept.app.cli import commands
12
+
13
+ logger = help.ogler.getLogger()
14
+
15
+
16
+ def main():
17
+ parser = multicommand.create_parser(commands)
18
+ args = parser.parse_args()
19
+
20
+ if not hasattr(args, "handler"):
21
+ parser.print_help()
22
+ return
23
+
24
+ try:
25
+ doers = args.handler(args)
26
+ directing.runController(doers=doers, expire=0.0)
27
+
28
+ except Exception as ex:
29
+ import os
30
+
31
+ if os.getenv("DEBUG_KEPT"):
32
+ import traceback
33
+
34
+ traceback.print_exc()
35
+ else:
36
+ print(f"ERR: {ex}")
37
+ return -1
38
+
39
+
40
+ if __name__ == "__main__":
41
+ main()
kept/core/__init__.py ADDED
File without changes
@@ -0,0 +1,284 @@
1
+ import math
2
+ import random
3
+
4
+ import cbor
5
+ import pysodium
6
+ from keri import kering, help, core
7
+ from keri.core import coring, serdering, MtrDex
8
+ from keri.core.counting import CtrDex_1_0
9
+ from keri.db import dbing
10
+ from keri.help import helping
11
+ from keri.kering import ConfigurationError
12
+ from keri.peer import exchanging
13
+
14
+ from kept.db import basing
15
+
16
+ logger = help.ogler.getLogger()
17
+
18
+ CHUNK_SIZE = 65536
19
+
20
+
21
+ class CryptSigner:
22
+
23
+ def __init__(self, hby, hab, rt=None, encryption_target=None):
24
+ """Factory for creating delegates for a give Delegator Hab1
25
+
26
+ Parameters:
27
+ hby (Habery): database environment and Hab factory
28
+ rt (RouteTable): RACK database environment
29
+ hab (Hab): delegator's hab
30
+
31
+ """
32
+ self.hby = hby
33
+ self._hab = hab
34
+ self.rt = (
35
+ rt
36
+ if rt is not None
37
+ else basing.RouteTable(name=hby.name, reopen=True, temp=hby.temp)
38
+ )
39
+
40
+ self.encryption_target = encryption_target
41
+ self.scan = set()
42
+
43
+ def make(
44
+ self,
45
+ count=10,
46
+ algo="randy",
47
+ salt=None,
48
+ icount=1,
49
+ isith="1",
50
+ ncount=1,
51
+ nsith="1",
52
+ ):
53
+ """Create delegates from the delegator
54
+
55
+ Parameters:
56
+ count (int): the number of delegates to create
57
+ algo (str): key generation algo, "salty" or "randy"
58
+ salt: (str): 21 character length key generation salt
59
+ icount (int): number of signing keys to create per delegate
60
+ isith: (str): signing threshold
61
+ ncount (int): number of rotation keys to create per delegate
62
+ nsith: (str): rotation threshold
63
+
64
+ Returns:
65
+ list: a list of delegate Habs
66
+
67
+ """
68
+ start = 0
69
+ for hab in self.hby.habs.values():
70
+ if hab.name.startswith(hab.name):
71
+ start += 1
72
+
73
+ anchors = list()
74
+ delegates = list()
75
+ for idx in range(count):
76
+ alias = f"{self._hab.name}-{idx + start}"
77
+
78
+ if self.hby.habByName(alias) is not None:
79
+ raise ValueError(
80
+ f"{alias} is already in use, please pick another alias prefix"
81
+ )
82
+
83
+ kwargs = dict()
84
+ kwargs["algo"] = algo
85
+ if algo == "salty":
86
+ if salt is None or len(salt) != 24:
87
+ raise ValueError("Salt is required and must be 24 characters long")
88
+
89
+ kwargs["salt"] = salt
90
+ kwargs["icount"] = int(icount)
91
+ kwargs["isith"] = int(isith)
92
+ kwargs["ncount"] = int(ncount)
93
+ kwargs["nsith"] = int(nsith)
94
+
95
+ elif algo == "randy":
96
+ kwargs["salt"] = None
97
+ kwargs["icount"] = int(icount)
98
+ kwargs["isith"] = int(isith)
99
+ kwargs["ncount"] = int(ncount)
100
+ kwargs["nsith"] = int(nsith)
101
+
102
+ kwargs["delpre"] = self._hab.pre
103
+ kwargs["estOnly"] = False
104
+
105
+ hab = self.hby.makeHab(name=alias, **kwargs)
106
+ delegates.append(hab)
107
+ anchors.append(dict(i=hab.pre, s="0", d=hab.pre))
108
+ self.rt.dlgs.add(keys=(self._hab.pre,), val=hab.pre)
109
+
110
+ self._hab.interact(data=anchors)
111
+
112
+ for anchor in anchors:
113
+ seqner = coring.Seqner(sn=self._hab.kever.serder.sn)
114
+ couple = seqner.qb64b + self._hab.kever.serder.saidb
115
+ dgkey = dbing.dgKey(anchor["i"], anchor["d"])
116
+ self.hby.db.setAes(
117
+ dgkey, couple
118
+ ) # authorizer event seal (delegator/issuer)
119
+
120
+ return delegates
121
+
122
+ def delegates(self, aid):
123
+ return self.rt.dlgs.get(keys=(aid,))
124
+
125
+ def scan_for_delegates(self, aid):
126
+ if aid not in self.scan:
127
+ scan_for_delegates(self.hby, self.rt, aid)
128
+ self.scan.add(aid)
129
+
130
+ @property
131
+ def pre(self):
132
+ return self._hab.pre
133
+
134
+ def hab(self, aid):
135
+ if (
136
+ delegate := self.rt.cur.get(keys=(aid,))
137
+ ) is not None and delegate in self.hby.habs:
138
+ return self.hby.habs[delegate]
139
+
140
+ # We don't have a current signing delegate assigned, lets see if we can assign one.
141
+ elif len(delegates := self.delegates(aid)) > 0:
142
+ if (
143
+ delegates
144
+ ): # if we have defined delegates, assign the first one as our current signer
145
+ delegate = delegates[0]
146
+ if delegate in self.hby.habs:
147
+ self.rt.cur.pin(keys=(aid,), val=delegate)
148
+ return self.hby.habs[delegate]
149
+
150
+ elif aid in self.hby.habs:
151
+ return self.hby.habs[aid]
152
+
153
+ raise kering.ConfigurationError(f"Unable to find Hab for {aid}")
154
+
155
+ def kever(self, aid):
156
+ delegates = self.delegates(aid)
157
+ if delegates:
158
+ delegate = random.choice(delegates)
159
+ if delegate in self.hby.kevers:
160
+ return self.hby.kevers[delegate]
161
+ else:
162
+ raise kering.ConfigurationError(f"Unable to find kever for {aid}")
163
+ elif aid in self.hby.kevers:
164
+ return self.hby.kevers[aid]
165
+ else:
166
+ raise kering.ConfigurationError(f"Unable to find kever for {aid}")
167
+
168
+ def encode(self, path, payload, target=None, said=None):
169
+ hab = self.hab(self._hab.pre) # Signer is always hardcoded
170
+
171
+ payload = dict(i=hab.pre, **payload)
172
+
173
+ encryption_target = (
174
+ target if target is not None else self.encryption_target
175
+ ) # Must extract the encryption
176
+
177
+ # target from the message context from DESSR or get is from self.encryption_target
178
+ if encryption_target is None:
179
+ raise ConfigurationError("Unable to determine encryption target")
180
+
181
+ rkever = self.kever(encryption_target)
182
+ if rkever is None:
183
+ raise ConfigurationError(
184
+ f"Unable to find kever for encryption target {encryption_target}"
185
+ )
186
+
187
+ # convert signing public key to encryption public key
188
+ pubkey = pysodium.crypto_sign_pk_to_box_pk(rkever.verfers[0].raw)
189
+ raw = pysodium.crypto_box_seal(cbor.dumps(payload), pubkey)
190
+ diger = coring.Diger(ser=raw, code=MtrDex.Blake3_256)
191
+
192
+ kwargs = dict()
193
+ if said is not None:
194
+ kwargs["dig"] = said
195
+
196
+ exn, _ = exchanging.exchange(
197
+ route=path, # The return_route of original message
198
+ diger=diger,
199
+ sender=hab.pre,
200
+ recipient=rkever.prefixer.qb64, # Must sign receiver
201
+ date=helping.nowIso8601(),
202
+ **kwargs,
203
+ )
204
+
205
+ ims = hab.endorse(serder=exn, pipelined=False)
206
+
207
+ size = len(raw)
208
+ chunks = math.ceil(size / CHUNK_SIZE)
209
+ ims.extend(
210
+ core.Counter(
211
+ code=CtrDex_1_0.ESSRPayloadGroup, count=chunks, gvrsn=kering.Vrsn_1_0
212
+ ).qb64b
213
+ )
214
+ for idx in range(chunks):
215
+ start = idx * CHUNK_SIZE
216
+ end = start + CHUNK_SIZE
217
+ texter = coring.Matter(raw=raw[start:end], code=MtrDex.Bytes_L0)
218
+ ims.extend(texter.qb64b)
219
+
220
+ hab.psr.parseOne(ims=bytes(ims))
221
+
222
+ hab.db.essrs.rem(keys=(exn.said,))
223
+ hab.db.epath.rem(keys=(exn.said,))
224
+ self.rt.encs.add(keys=(hab.kever.serder.said,), val=exn.said)
225
+
226
+ return ims
227
+
228
+ def rotate_signer(self, aid):
229
+ delegates = self.delegates(aid)
230
+ if not delegates:
231
+ raise kering.ConfigurationError(f"No delegates assigned for {aid}")
232
+
233
+ if not (delegate := self.rt.cur.get(keys=(aid,))):
234
+ if (
235
+ delegates
236
+ ): # if we have defined delegates, assign the first one as our current signer
237
+ delegate = delegates[0]
238
+ if delegate in self.hby.habs:
239
+ self.rt.cur.pin(keys=(aid,), val=delegate)
240
+ else:
241
+ try:
242
+ idx = delegates.index(delegate)
243
+ except ValueError:
244
+ delegate = delegates[0]
245
+ if delegate in self.hby.habs:
246
+ self.rt.cur.pin(keys=(aid,), val=delegate)
247
+ else:
248
+ idx = (idx + 1) % len(delegates)
249
+ delegate = delegates[idx]
250
+ if delegate in self.hby.habs:
251
+ self.rt.cur.pin(keys=(aid,), val=delegate)
252
+
253
+
254
+ def scan_for_delegates(hby, rt, delegator):
255
+ cloner = hby.db.clonePreIter(pre=delegator, fn=0) # create iterator at 0
256
+ rt.dlgs.rem(keys=(delegator,))
257
+ for msg in cloner:
258
+ srdr = serdering.SerderKERI(raw=msg)
259
+ process_delegator_event_seals(hby, rt, srdr)
260
+
261
+
262
+ def process_delegator_event_seals(hby, rt, srdr):
263
+ delegator = srdr.pre
264
+ for anchor in srdr.seals:
265
+ if (
266
+ "i" not in anchor and "s" not in anchor and "d" not in anchor
267
+ ): # Event seal anchor
268
+ continue
269
+
270
+ delegate = anchor["i"]
271
+ if (
272
+ anchor["s"] != "0" or delegate != anchor["d"]
273
+ ): # Ensure this is an inception anchor
274
+ continue
275
+
276
+ delegate_kever = hby.kevers[delegate]
277
+
278
+ # Check for accidental registration of non-delegate or for a delegate that was neutered.
279
+ if delegate_kever.delpre != delegator or not delegate_kever.ndigers:
280
+ continue
281
+
282
+ rt.dlgs.add(keys=(delegator,), val=delegate)
283
+
284
+ logger.info(f"Signer {delegate} added for delegator {delegator}")
@@ -0,0 +1,14 @@
1
+ # -*- encoding: utf-8 -*-
2
+ """
3
+ KERI-ESSR
4
+ kept.essr.core.exceptions package
5
+
6
+ """
7
+
8
+
9
+ class ESSRStatusError(Exception):
10
+ """Raised when the ESSR server responds with an error status."""
11
+
12
+ def __init__(self, status_code: int, message: str):
13
+ self.status_code = status_code
14
+ self.message = message
File without changes