-
Notifications
You must be signed in to change notification settings - Fork 73
Expand file tree
/
Copy pathhabbing.py
More file actions
2910 lines (2412 loc) · 122 KB
/
habbing.py
File metadata and controls
2910 lines (2412 loc) · 122 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- encoding: utf-8 -*-
"""
KERI
keri.app.habbing module
"""
import json
from contextlib import contextmanager
from math import ceil
from urllib.parse import urlsplit
from hio.base import doing
from hio.help import hicting
from keri.peer import exchanging
from . import keeping, configing
from .. import help
from .. import kering
from .. import core
from ..core import (coring, eventing, parsing, routing, serdering, indexing,
Counter, Codens)
from ..db import dbing, basing
from ..kering import MissingSignatureError, Roles
logger = help.ogler.getLogger()
@contextmanager
def openHby(*, name="test", base="", temp=True, salt=None, **kwa):
"""
Context manager wrapper for Habery instance.
Context 'with' statements call .close on exit of 'with' block
Parameters:
name (str): name of habery and shared db and file path
base (str): optional if "" path component of shared db and files.
temp (bool): True means use temporary or limited resources testing.
Store .ks, .db, and .cf in /tmp
Use quick method to stretch salts for seeds such as
bran salt to seed or key creation of Habs.
Otherwise use more resources set by tier to stretch
salt (str): qb64 salt for creating key pairs
Parameters: Passed through via kwa
ks (Keeper): keystore lmdb subclass instance
db (Baser): database lmdb subclass instance
cf (Configer): config file instance
seed (str): qb64 private-signing key (seed) for the aeid from which
the private decryption key may be derived. If aeid stored in
database is not empty then seed may required to do any key
management operations. The seed value is memory only and MUST NOT
be persisted to the database for the manager with which it is used.
It MUST only be loaded once when the process that runs the Manager
is initialized. Its presence acts as an authentication, authorization,
and decryption secret for the Manager and must be stored on
another device from the device that runs the Manager.
aeid (str): qb64 of non-transferable identifier prefix for
authentication and encryption of secrets in keeper. If provided
aeid (not None) and different from aeid stored in database then
all secrets are re-encrypted using new aeid. In this case the
provided prikey must not be empty. A change in aeid should require
a second authentication mechanism besides the prikey.
bran (str): Base64 char string of which first 21 chars are used as
base material for seed. bran allows alphanumeric passcodes
generated by key managers like 1password to be key store for
bran as salt base material for seed.
pidx (int): Initial prefix index for vacuous ks
algo (str): algorithm (randy or salty) for creating key pairs
default is root algo which defaults to salty
tier (str): security tier for generating keys from salt (Tierage)
free (boo): free resources by closing on Doer exit if any
"""
habery = None
salt = salt if salt is not None else core.Salter().qb64
try:
habery = Habery(name=name, base=base, temp=temp, salt=salt, **kwa)
yield habery
finally:
if habery:
habery.close(clear=habery.temp)
@contextmanager
def openHab(name="test", base="", salt=None, temp=True, cf=None, **kwa):
"""
Context manager wrapper for Hab instance.
Defaults to temporary resources
Context 'with' statements call .close on exit of 'with' block
Parameters:
name(str): name of habitat to create
base(str): the name used for shared resources i.e. Baser and Keeper The habitat specific config file will be
in base/name
salt(bytes): passed to habitat to use for inception raw salt not qb64
temp(bool): indicates if this uses temporary databases
cf(Configer): optional configer for loading configuration data
"""
salt = core.Salter(raw=salt).qb64
with openHby(name=name, base=base, salt=salt, temp=temp, cf=cf) as hby:
if (hab := hby.habByName(name)) is None:
hab = hby.makeHab(name=name, icount=1, isith='1', ncount=1, nsith='1', cf=cf, **kwa)
yield hby, hab
class Habery:
"""Habery class provides shared database environments for all its Habitats
Key controller and identifier controller shared configuration file, keystore
and KEL databases.
Attributes:
name (str): name of associated databases
base (str): optional directory path segment inserted before name
that allows further hierarchical differentiation of databases.
"" means optional.
temp (bool): True for testing:
temporary storage of databases and config file
weak resources for stretch of salty key
ks (keeping.Keeper): lmdb key store
db (basing.Baser): lmdb data base for KEL etc
cf (configing.Configer): config file instance
mgr (keeping.Manager): creates and rotates keys in key store
rtr (routing.Router): routes reply 'rpy' messages
rvy (routing.Revery): factory that processes reply 'rpy' messages
kvy (eventing.Kevery): factory for local processing of local event msgs
psr (parsing.Parser): parses local messages for .kvy .rvy
habs (dict): Hab instances keyed by prefix.
To look up Hab by name use use .habByName
To look up Hab by prefix us .habByPrefix
to get hab from db need name for key
hab prefix in db.habs record .hid field
inited (bool): True means fully initialized wrt databases.
False means not yet fully initialized
Properties:
kevers (dict): of eventing.Kever(s) keyed by qb64 prefix
prefixes (OrderedSet): local prefixes for .db
"""
def __init__(self, *, name='test', base="", temp=False,
ks=None, db=None, cf=None, clear=False, headDirPath=None, **kwa):
"""
Initialize instance.
Parameters:
name (str): alias name for shared environment config databases etc.
base (str): optional directory path segment inserted before name
that allows further differentiation with a hierarchy. "" means
optional.
temp (bool): True means use temporary or limited resources testing.
Store .ks, .db, and .cf in /tmp
Use quick method to stretch salts for seeds such as
bran salt to seed or key creation of Habs.
Otherwise use more resources set by tier to stretch
ks (Keeper): keystore lmdb subclass instance
db (Baser): database lmdb subclass instance
cf (Configer): config file instance
clear (bool): True means remove resource directory upon close when
reopening
False means do not remove directory upon close when
reopening
headDirPath (str): directory override
Parameters: Passed through via kwa to setup for later init
seed (str): qb64 private-signing key (seed) for the aeid from which
the private decryption key may be derived. If aeid stored in
database is not empty then seed may required to do any key
management operations. The seed value is memory only and MUST NOT
be persisted to the database for the manager with which it is used.
It MUST only be loaded once when the process that runs the Manager
is initialized. Its presence acts as an authentication, authorization,
and decryption secret for the Manager and must be stored on
another device from the device that runs the Manager.
aeid (str): qb64 of non-transferable identifier prefix for
authentication and encryption of secrets in keeper. If provided
aeid (not None) and different from aeid stored in database then
all secrets Haberyare re-encrypted using new aeid. In this case the
provided prikey must not be empty. A change in aeid should require
a second authentication mechanism besides the prikey.
bran (str): Base64 char string of which first 21 chars are used as
base material for seed. bran allows alphanumeric passcodes
generated by key managers like 1password to be key store for
bran as salt base material for seed.
pidx (int): Initial prefix index for vacuous ks
algo (str): algorithm (randy or salty) for creating key pairs
default is root algo which defaults to salty
salt (str): qb64 salt for creating key pairs
tier (str): security tier for generating keys from salt (Tierage)
free (boo): free resources by closing on Doer exit if any
temp (bool): See above
"""
self.name = name
self.base = base
self.temp = temp
self.ks = ks if ks is not None else keeping.Keeper(name=self.name,
base=self.base,
temp=self.temp,
reopen=True,
clear=clear,
headDirPath=headDirPath)
self.db = db if db is not None else basing.Baser(name=self.name,
base=self.base,
temp=self.temp,
reopen=True,
clear=clear,
headDirPath=headDirPath)
self.cf = cf if cf is not None else configing.Configer(name=self.name,
base=self.base,
temp=self.temp,
reopen=True,
clear=clear)
self.mgr = None # wait to setup until after ks is known to be opened
self.rtr = routing.Router()
self.rvy = routing.Revery(db=self.db, rtr=self.rtr)
self.exc = exchanging.Exchanger(hby=self, handlers=[])
self.kvy = eventing.Kevery(db=self.db, lax=False, local=True, rvy=self.rvy)
self.kvy.registerReplyRoutes(router=self.rtr)
self.psr = parsing.Parser(framed=True, kvy=self.kvy, rvy=self.rvy,
exc=self.exc, local=True)
self.habs = {} # empty .habs
self._signator = None
self.inited = False
# save init kwy word arg parameters as ._inits in order to later finish
# init setup elseqhere after databases are opened if not below
self._inits = kwa
self._inits['temp'] = temp # add temp for seed from bran tier override
if self.db.opened and self.ks.opened:
self.setup(**self._inits) # finish setup later
def setup(self, *, seed=None, aeid=None, bran=None, pidx=None, algo=None,
salt=None, tier=None, free=False, temp=None, ):
"""
Setup Habery. Assumes that both .db and .ks have been opened.
This allows dependency injection of .db and .ks into Habery instance
prior to .db and .kx being opened to accomodate asynchronous process
setup of these resources. Putting the .db and .ks associated
initialization here enables asynchronous opening .db and .ks after
Baser and Keeper instances are instantiated. First call to .setup will
initialize databases (vacuous initialization).
Parameters:
seed (str): qb64 private-signing key (seed) for the aeid from which
the private decryption key may be derived. If aeid stored in
database is not empty then seed may required to do any key
management operations. The seed value is memory only and MUST NOT
be persisted to the database for the manager with which it is used.
It MUST only be loaded once when the process that runs the Manager
is initialized. Its presence acts as an authentication, authorization,
and decryption secret for the Manager and must be stored on
another device from the device that runs the Manager.
aeid (str): qb64 of non-transferable identifier prefix for
authentication and encryption of secrets in keeper. If provided
aeid (not None) and different from aeid stored in database then
all secrets are re-encrypted using new aeid. In this case the
provided prikey must not be empty. A change in aeid should require
a second authentication mechanism besides the prikey.
bran (str): Base64 21 char string that is used as base material for
seed. bran allows alphanumeric passcodes generated by key managers
like 1password to be Okey store for seed.
pidx (int): Initial prefix index for vacuous ks
algo (str): algorithm (randy or salty) for creating key pairs
default is root algo which defaults to salty
salt (str): qb64 salt for creating key pairs
tier (str): security tier for generating keys from salt (Tierage)
free (boo): free resources by closing on Doer exit if any
temp (bool): True means use shortcuts for testing.
Use quick method to stretch salts for seeds such as
bran salt to seed or key creation of Habs.
Otherwise use more resources set by tier to stretch
"""
if not (self.ks.opened and self.db.opened):
raise kering.ClosedError("Attempt to setup Habitat with closed "
"database, .ks or .db.")
self.free = True if free else False
if bran and not seed: # create seed from stretch of bran as salt
if len(bran) < 21:
raise ValueError(f"Bran (passcode seed material) too short.")
bran = coring.MtrDex.Salt_128 + 'A' + bran[:21] # qb64 salt for seed
signer = core.Salter(qb64=bran).signer(transferable=False,
tier=tier,
temp=temp)
seed = signer.qb64
if not aeid: # aeid must not be empty event on initial creation
aeid = signer.verfer.qb64 # lest it remove encryption
if salt is None: # salt for signing keys not aeid seed
salt = core.Salter().qb64
else:
salt = core.Salter(qb64=salt).qb64
try:
self.mgr = keeping.Manager(ks=self.ks, seed=seed, aeid=aeid, pidx=pidx,
algo=algo, salt=salt, tier=tier)
except kering.AuthError as ex:
self.close()
raise ex
self._signator = Signator(db=self.db, mgr=self.mgr, temp=self.temp, ks=self.ks, cf=self.cf,
rtr=self.rtr, kvy=self.kvy, psr=self.psr, rvy=self.rvy)
self.loadHabs()
self.inited = True
def loadHabs(self):
"""Load Habs instance from db
.db.reopen calls .db.reload which loads .db.kevers from key state in
.db.states and loads associated .db.prefixes.
It also removes any bare .habs without key state
Thus by now know that .habs are valid so can create Hab instances
"""
self.reconfigure() # pre hab load reconfiguration
groups = []
for prefix, habord in self.db.habs.getItemIter():
pre = habord.hid
# create Hab instance and inject dependencies
if habord.mid and not habord.sid:
hab = GroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
name=habord.name, pre=pre, temp=self.temp, smids=habord.smids)
groups.append(habord)
elif habord.sid and not habord.mid:
hab = SignifyHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
name=habord.name, pre=habord.sid)
elif habord.sid and habord.mid:
hab = SignifyGroupHab(smids=habord.smids, ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
name=habord.name, pre=pre)
groups.append(habord)
else:
hab = Hab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
name=habord.name, pre=pre, temp=self.temp)
# Rules for acceptance:
# It is accepted into its own local KEL even if it has not been fully
# witnessed and if delegated, its delegator has not yet sealed it
if not hab.accepted and not habord.mid:
raise kering.ConfigurationError(f"Problem loading Hab pre="
f"{pre} name={habord.name} from db.")
# read in config file and process any oobis or endpoints for hab
hab.inited = True
self.habs[hab.pre] = hab
# Populate the participant hab after loading all habs
for habord in groups:
self.habs[habord.hid].mhab = self.habs[habord.mid]
self.reconfigure() # post hab load reconfiguration
def makeHab(self, name, ns=None, cf=None, **kwa):
"""Make new Hab with name, pre is generated from **kwa
Parameters: (Passthrough to hab.make)
secrecies (list): of list of secrets to preload key pairs if any
iridx (int): initial rotation index after ingestion of secrecies
code (str): prefix derivation code
transferable (bool): True means pre is transferable (default)
False means pre is nontransferable
isith (Union[int, str, list]): incepting signing threshold as int, str hex, or list
icount (int): incepting key count for number of keys
nsith (Union[int, str, list]): next signing threshold as int, str hex or list
ncount (int): next key count for number of next keys
toad (Union[int,str]): int or str hex of witness threshold
wits (list): of qb64 prefixes of witnesses
delpre (str): qb64 of delegator identifier prefix
estOnly (str): eventing.TraitDex.EstOnly means only establishment
events allowed in KEL for this Hab
data (list | None): seal dicts
"""
if ns is not None and "." in ns:
raise kering.ConfigurationError("Hab namespace names are not allowed to contain the '.' character")
cf = cf if cf is not None else self.cf
hab = Hab(ks=self.ks, db=self.db, cf=cf, mgr=self.mgr,
rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
name=name, ns=ns, temp=self.temp)
hab.make(**kwa)
self.habs[hab.pre] = hab
return hab
def makeGroupHab(self, group, mhab, smids, rmids=None, ns=None, **kwa):
"""Make new Group Hab using group has group hab name, with lhab as local
participant.
Parameters: (non-pass-through):
group (str): human readable alias for group identifier
mhab (Hab): group member (local) hab
smids (list): group member signing ids (qb64) from which to extract
inception event current signing keys
rmids (list | None): group member rotation ids (qb64) from which to extract
inception event next key digests
if rmids is None then use assign smids to rmids
if rmids is empty then no next key digests
which means group identifier is no longer transferable.
Parameters: (**kwa pass-through to hab.make)
secrecies (list): of list of secrets to preload key pairs if any
iridx (int): initial rotation index after ingestion of secrecies
code (str): prefix derivation code
transferable (bool): True means pre is transferable (default)
False means pre is nontransferable
isith (Union[int, str, list]): incepting signing threshold as int, str hex, or list
icount (int): incepting key count for number of keys
nsith (Union[int, str, list]): next signing threshold as int, str hex or list
ncount (int): next key count for number of next keys
toad (Union[int,str]): int or str hex of witness threshold
wits (list): of qb64 prefixes of witnesses
delpre (str): qb64 of delegator identifier prefix
estOnly (str): eventing.TraitDex.EstOnly means only establishment
events allowed in KEL for this Hab
DnD (bool): eventing.TraitDex.DnD means do allow delegated identifiers from this identifier
ToDo: NRR
add midxs tuples for each group member or all in group multisig.
"""
if mhab.pre not in smids and mhab.pre not in rmids:
raise kering.ConfigurationError(f"Local member identifier "
f"{mhab.pre} must be member of "
f"smids ={smids} and/or "
f"rmids={rmids}.")
for mid in smids:
if mid not in self.kevers:
raise kering.ConfigurationError(f"KEL missing for signing member "
f"identifier {mid} from group's "
f"current members ={smids}")
if rmids is not None:
for rmid in rmids:
if rmid not in self.kevers:
raise kering.ConfigurationError(f"KEL missing for next member "
f"identifier {rmid} in group's"
f" next members ={rmids}")
# multisig group verfers of current signing keys and digers of next key digests
merfers, migers = self.extractMerfersMigers(smids, rmids) # group verfers and digers
kwa["merfers"] = merfers
kwa["migers"] = migers
# create group Hab in this Habery
hab = GroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
name=group, ns=ns, mhab=mhab, smids=smids, rmids=rmids, temp=self.temp)
hab.make(**kwa) # finish making group hab with injected pass throughs
self.habs[hab.pre] = hab
return hab
def joinGroupHab(self, pre, group, mhab, smids, rmids=None, ns=None):
"""Make new Group Hab using group has group hab name, with lhab as local
participant.
Parameters: (non-pass-through):
pre (str): qb64 identifier prefix of group
group (str): human readable alias for group identifier
mhab (Hab): group member (local) hab
smids (list): group member signing ids (qb64) from which to extract
inception event current signing keys
rmids (list | None): group member rotation ids (qb64) from which to extract
inception event next key digests
if rmids is None then use assign smids to rmids
if rmids is empty then no next key digests
which means group identifier is no longer transferable.
"""
if mhab.pre not in smids and mhab.pre not in rmids:
raise kering.ConfigurationError(f"Local member identifier "
f"{mhab.pre} must be member of "
f"smids ={smids} and/or "
f"rmids={rmids}.")
for mid in smids:
if mid not in self.kevers:
raise kering.ConfigurationError(f"KEL missing for signing member "
f"identifier {mid} from group's "
f"current members ={smids}")
if rmids is not None:
for rmid in rmids:
if rmid not in self.kevers:
raise kering.ConfigurationError(f"KEL missing for next member "
f"identifier {rmid} in group's"
f" next members ={rmids}")
# create group Hab in this Habery
hab = GroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
name=group, ns=ns, mhab=mhab, smids=smids, rmids=rmids, temp=self.temp)
hab.pre = pre
habord = basing.HabitatRecord(hid=hab.pre,
name=hab.name,
domain=ns,
mid=mhab.pre,
smids=smids,
rmids=rmids)
hab.save(habord)
hab.prefixes.add(pre)
hab.inited = True
self.habs[hab.pre] = hab
return hab
def makeSignifyHab(self, name, ns=None, **kwa):
# create group Hab in this Habery
hab = SignifyHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
name=name, ns=ns, temp=self.temp)
hab.make(**kwa) # finish making group hab with injected pass throughs
self.habs[hab.pre] = hab
return hab
def makeSignifyGroupHab(self, name, mhab, smids, rmids=None, ns=None, **kwa):
# create group Hab in this Habery
hab = SignifyGroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
name=name, mhab=mhab, smids=smids, rmids=rmids, ns=ns, temp=self.temp)
hab.make(**kwa) # finish making group hab with injected pass throughs
self.habs[hab.pre] = hab
return hab
def joinSignifyGroupHab(self, pre, name, mhab, smids, rmids=None, ns=None):
"""Make new Group Hab using group has group hab name, with lhab as local
participant.
Parameters: (non-pass-through):
pre (str): qb64 identifier prefix of group
name (str): human readable alias for group identifier
mhab (Hab): group member (local) hab
smids (list): group member signing ids (qb64) from which to extract
inception event current signing keys
rmids (list | None): group member rotation ids (qb64) from which to extract
inception event next key digests
if rmids is None then use assign smids to rmids
if rmids is empty then no next key digests
which means group identifier is no longer transferable.
"""
if mhab.pre not in smids and mhab.pre not in rmids:
raise kering.ConfigurationError(f"Local member identifier "
f"{mhab.pre} must be member of "
f"smids ={smids} and/or "
f"rmids={rmids}.")
for mid in smids:
if mid not in self.kevers:
raise kering.ConfigurationError(f"KEL missing for signing member "
f"identifier {mid} from group's "
f"current members ={smids}")
if rmids is not None:
for rmid in rmids:
if rmid not in self.kevers:
raise kering.ConfigurationError(f"KEL missing for next member "
f"identifier {rmid} in group's"
f" next members ={rmids}")
# create group Hab in this Habery
hab = SignifyGroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
name=name, mhab=mhab, smids=smids, rmids=rmids, ns=ns, temp=self.temp)
hab.pre = pre
habord = basing.HabitatRecord(hid=hab.pre,
sid=mhab.pre,
name=name,
domain=ns,
smids=smids,
rmids=rmids)
hab.save(habord)
hab.prefixes.add(pre)
hab.inited = True
self.habs[hab.pre] = hab
return hab
def deleteHab(self, name, ns=None):
hab = self.habByName(name, ns=ns)
if not hab:
return False
if not self.db.habs.rem(keys=(hab.pre,)):
return False
ns = "" if ns is None else ns
if not self.db.names.rem(keys=(ns, name)):
return False
del self.habs[hab.pre]
self.db.prefixes.remove(hab.pre)
if hab.pre in self.db.groups:
self.db.groups.remove(hab.pre)
return True
def extractMerfersMigers(self, smids, rmids=None):
"""
Extract the public key verfer and next digest diger from the current
est event of all the members of the multisig group. Assumes that the KEL
for each member is already in .kevers
Parameters:
smids (list): group signing member ids qb64 in group multisig
rmids (list): group rotating member ids qb64 in group multisig
"""
if rmids is None: # default the same for both lists
rmids = list(smids)
merfers = [] # multisig group signing key verfers
migers = [] # multisig group next key digest digers
for mid in smids:
kever = self.kevers[mid]
verfers = kever.verfers
merfers.append(verfers[0]) # assumes always verfers
if len(verfers) > 1:
raise kering.ConfigurationError("Identifier must have only one key, {} has {}"
.format(mid, len(verfers)))
for mid in rmids:
kever = self.kevers[mid]
digers = kever.ndigers
if digers: # abandoned id may have empty next digers
migers.append(digers[0])
if len(digers) > 1:
raise kering.ConfigurationError("Identifier must have only one next key commitment, {} has {}"
.format(mid, len(digers)))
return merfers, migers
def close(self, clear=False):
"""Close resources.
Parameters:
clear is boolean, True means clear resource directories
"""
if self.ks:
self.ks.close(clear=self.ks.temp or clear)
if self.db:
self.db.close(clear=self.db.temp or clear)
if self.cf:
self.cf.close(clear=self.cf.temp)
@property
def kevers(self):
"""
Returns .db.kevers of all Kevers
"""
return self.db.kevers
@property
def prefixes(self):
"""
Returns .db.prefixes of local prefixes
"""
return self.db.prefixes
def habByPre(self, pre):
"""
Returns the Hab instance from .habs or None
including the default namespace.
Args:
pre (str): qb64 aid of hab to find
Returns:
Hab: Hab instance for the aid pre or None
"""
if pre in self.habs:
return self.habs[pre]
return None
def habByName(self, name, ns=None):
"""
Returns:
hab (Hab): instance by name from .habs or .namspaces
if any otherwise None
Parameters:
name (str): alias of Hab
ns (str): optional namespace of hab
"""
ns = "" if ns is None else ns
if (pre := self.db.names.get(keys=(ns, name))) is not None:
if pre in self.habs:
return self.habs[pre]
return None
def reconfigure(self):
"""Apply configuration from config file managed by .cf. to this Habery
Process any oobis or endpoints
config file json or hjon
{
"dt": "2021-01-01T00:00:00.000000+00:00",
"nel":
{
"dt": "2021-01-01T00:00:00.000000+00:00",
"curls":
[
"tcp://localhost:5621/"
]
},
"iurls":
[
"tcp://localhost:5620/?role=peer&name=tam"
],
"durls":
[
"http://127.0.0.1:7723/oobi/EBNaNu-M9P5cgrnfl2Fvymy4E_jvxxyjb70PRtiANlJy",
"http://127.0.0.1:7723/oobi/EMhvwOlyEJ9kN4PrwCpr9Jsv7TxPhiYveZ0oP3lJzdEi",
],
"wurls":
[
"http://127.0.0.1:5644/.well-known/keri/oobi/EBNaNu-M9P5cgrnfl2Fvymy4E_jvxxyjb70PRtiANlJy?name=Root"
]
}
Config file is meant to be read only at init not changed by app at
run time. Any dynamic app changes must go in database not config file
that way we don't have to worry about multiple writers of cf.
Use config file to preload database not as a database. Config file may
have named sections for Habery or individual Habs as needed.
"""
conf = self.cf.get()
if "dt" in conf: # datetime of config file
dt = help.fromIso8601(conf["dt"]) # raises error if not convert
if "iurls" in conf: # process OOBI URLs
for oobi in conf["iurls"]:
obr = basing.OobiRecord(date=help.toIso8601(dt))
self.db.oobis.put(keys=(oobi,), val=obr)
if "durls" in conf: # process OOBI URLs
for oobi in conf["durls"]:
obr = basing.OobiRecord(date=help.toIso8601(dt))
self.db.oobis.put(keys=(oobi,), val=obr)
if "wurls" in conf: # well known OOBI URLs for MFA
for oobi in conf["wurls"]:
obr = basing.OobiRecord(date=help.toIso8601(dt))
self.db.woobi.put(keys=(oobi,), val=obr)
@property
def signator(self):
"""
signator for signing and verifying data at rest for this Habery environment
Assumes db initialized.
Returns:
Signator: signer for data at rest
"""
return self._signator
SIGNER = "__signatory__"
class Signator:
"""
Signator will create one non-transferable identifier when it is first initialized
and use that identifier to sign and verify any data it is passed. This class can be used
to maintain BADA data ensuring that it is signed at rest.
"""
def __init__(self, db, name=SIGNER, **kwa):
"""
Create a Signator by checking for a signing AID in the Habery database and creating one
if it does not exist.
Args:
db (Baser): Database environment for data signing
"""
self.db = db
spre = self.db.hbys.get(name)
if not spre:
self._hab = Hab(name=name, db=db, **kwa)
self._hab.make(transferable=False, hidden=True)
self.pre = self._hab.pre
self.db.hbys.pin(name, self.pre)
else:
self.pre = spre
self._hab = Hab(name=name, db=db, pre=self.pre, **kwa)
def sign(self, ser):
""" Sign the data in ser with the Signator's private key using the Manager
Args:
ser (bytes): Raw byte data to sign
Returns:
Cigar: signature object for non-transferable key
"""
return self._hab.sign(ser, indexed=False)[0]
def verify(self, ser, cigar):
"""
Args:
ser(bytes): Raw byte data to verify against signature
cigar (Cigar): Single non-transferable signature to verify
Returns:
bool: True means valid signature against data provided
"""
return self._hab.kever.verfers[0].verify(cigar.raw, ser)
class HaberyDoer(doing.Doer):
"""
Basic Habery Doer to initialize habery databases and config file.
.cf, .ks, .db
Inherited Attributes:
.done is Boolean completion state:
True means completed
Otherwise incomplete. Incompletion maybe due to close or abort.
Attributes:
.habery is Habery subclass
Inherited Properties:
.tyme is float relative cycle time of associated Tymist .tyme obtained
via injected .tymth function wrapper closure.
.tymth is function wrapper closure returned by Tymist .tymeth() method.
When .tymth is called it returns associated Tymist .tyme.
.tymth provides injected dependency on Tymist tyme base.
.tock is float, desired time in seconds between runs or until next run,
non negative, zero means run asap
Properties:
Methods:
.wind injects ._tymth dependency from associated Tymist to get its .tyme
.__call__ makes instance callable
Appears as generator function that returns generator
.do is generator method that returns generator
.enter is enter context action method
.recur is recur context action method or generator method
.exit is exit context method
.close is close context method
.abort is abort context method
Hidden:
._tymth is injected function wrapper closure returned by .tymen() of
associated Tymist instance that returns Tymist .tyme. when called.
._tock is hidden attribute for .tock property
"""
def __init__(self, habery, **kwa):
"""
Parameters:
habery (Habery): instance
"""
super(HaberyDoer, self).__init__(**kwa)
self.habery = habery
def enter(self, *, temp=False):
""" Enter context and set up Habery """
if not self.habery.inited:
self.habery.setup(**self.habery._inits)
def exit(self):
"""Exit context and close Habery """
if self.habery.inited and self.habery.free:
self.habery.close(clear=self.habery.temp)
class BaseHab:
"""
Hab class provides a given idetnifier controller's local resource environment
i.e. hab or habitat. Includes dependency injection of database, keystore,
configuration file as well as Kevery and key store Manager..
Attributes: (Injected)
ks (keeping.Keeper): lmdb key store
db (basing.Baser): lmdb data base for KEL etc
cf (configing.Configer): config file instance
mgr (keeping.Manager): creates and rotates keys in key store
rtr (routing.Router): routes reply 'rpy' messages
rvy (routing.Revery): factory that processes reply 'rpy' messages
kvy (eventing.Kevery): factory for local processing of local event msgs
psr (parsing.Parser): parses local messages for .kvy .rvy
Attributes:
name (str): alias of controller
pre (str): qb64 prefix of own local controller or None if new
temp (bool): True means testing:
use weak level when salty algo for stretching in key creation
for incept and rotate of keys for this hab.pre
inited (bool): True means fully initialized wrt databases.
False means not yet fully initialized
delpre (str | None): delegator prefix if any else None
Properties:
kever (Kever): instance of key state of local controller
kevers (dict): of eventing.Kever instances from KELs in local db
keyed by qb64 prefix. Read through cache of of kevers of states for
KELs in db.states
iserder (serdering.SerderKERI): own inception event
prefixes (OrderedSet): local prefixes for .db
accepted (bool): True means accepted into local KEL.
False otherwise
"""
def __init__(self, ks, db, cf, mgr, rtr, rvy, kvy, psr, *,
name='test', ns=None, pre=None, temp=False):
"""
Initialize instance.
Injected Parameters: (injected dependencies)
ks (keeping.Keeper): lmdb key store
db (basing.Baser): lmdb data base for KEL etc
cf (configing.Configer): config file instance
mgr (keeping.Manager): creates and rotates keys in key store
rtr (routing.Router): routes reply 'rpy' messages
rvy (routing.Revery): factory that processes reply 'rpy' messages
kvy (eventing.Kevery): factory for local processing of local event msgs
psr (parsing.Parser): parses local messages for .kvy .rvy
Parameters:
name (str): alias name for local controller of habitat
pre (str | None): qb64 identifier prefix of own local controller else None
temp (bool): True means testing:
use weak level when salty algo for stretching in key creation
for incept and rotate of keys for this hab.pre
"""
self.db = db # injected
self.ks = ks # injected
self.cf = cf # injected
self.mgr = mgr # injected
self.rtr = rtr # injected
self.rvy = rvy # injected
self.kvy = kvy # injected
self.psr = psr # injected
self.name = name
self.ns = ns # what is this?
self.pre = pre # wait to setup until after db is known to be opened
self.temp = True if temp else False
self.inited = False
self.delpre = None # assigned laster if delegated
def make(self, DnD, code, data, delpre, estOnly, isith, verfers, nsith, digers, toad, wits):
"""
Creates Serder of inception event for provided parameters.
Assumes injected dependencies were already setup.
Parameters:
isith (int | str | list | None): incepting signing threshold as