diff --git a/scripts/demo/basic/essr.sh b/scripts/demo/basic/essr.sh new file mode 100755 index 000000000..e3a5cb957 --- /dev/null +++ b/scripts/demo/basic/essr.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +#kli init --name sender --salt 0ACDEyMzQ1Njc4OWxtbm9aBc --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis +#kli incept --name sender --alias sender --file ${KERI_DEMO_SCRIPT_DIR}/data/gleif-sample.json +# +#kli init --name recipient --salt 0ACDEyMzQ1Njc4OWxtbm9qWc --nopasscode --config-dir ${KERI_SCRIPT_DIR} --config-file demo-witness-oobis +#kli incept --name recipient --alias recipient --file ${KERI_DEMO_SCRIPT_DIR}/data/gleif-sample.json +# +#kli oobi resolve --name sender --oobi-alias recipient --oobi http://127.0.0.1:5642/oobi/EFXJsTFSo10FAZGR-8_Uw1DlhU8nuRFOAN9Z8ajJ56ci/witness +#kli oobi resolve --name recipient --oobi-alias sender --oobi http://127.0.0.1:5642/oobi/EJf7MfzNmehwY5310MUWXPSxhAA_3ifPW2bdsjwqnvae/witness +# +#kli vc registry incept --name sender --alias sender --registry-name vLEI +# +#kli vc create --name sender --alias sender --registry-name vLEI --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k --data @${KERI_DEMO_SCRIPT_DIR}/data/credential-data.json +#SAID=$(kli vc list --name sender --alias sender --issued --said --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao) +# +#kli ipex grant --name sender --alias sender --said "${SAID}" --recipient ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k + +kli essr send --name sender --alias sender --recipient recipient \ No newline at end of file diff --git a/scripts/demo/vLEI/issue-xbrl-attestation.sh b/scripts/demo/vLEI/issue-xbrl-attestation.sh index 628e5828f..8687de97b 100755 --- a/scripts/demo/vLEI/issue-xbrl-attestation.sh +++ b/scripts/demo/vLEI/issue-xbrl-attestation.sh @@ -52,42 +52,66 @@ kli vc registry incept --name legal-entity --alias legal-entity --registry-name kli vc registry incept --name person --passcode DoB26Fj4x9LboAFWJra17O --alias person --registry-name vLEI-person # Issue QVI credential vLEI from GLEIF External to QVI -kli vc issue --name external --alias external --registry-name vLEI-external --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX --data @${KERI_DEMO_SCRIPT_DIR}/data/qvi-data.json -kli vc list --name qvi --alias qvi --poll +kli vc create --name external --alias external --registry-name vLEI-external --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX --data @${KERI_DEMO_SCRIPT_DIR}/data/qvi-data.json +SAID=$(kli vc list --name external --alias external --issued --said --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao) +kli ipex grant --name external --alias external --said "${SAID}" --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX +GRANT=$(kli ipex list --name qvi --alias qvi --poll --said) +kli ipex admit --name qvi --alias qvi --said "${GRANT}" +kli vc list --name qvi --alias qvi # Issue LE credential from QVI to Legal Entity - have to create the edges first QVI_SAID=`kli vc list --name qvi --alias qvi --said --schema EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao` echo \"$QVI_SAID\" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/legal-entity-edges-filter.jq > /tmp/legal-entity-edges.json kli saidify --file /tmp/legal-entity-edges.json -kli vc issue --name qvi --alias qvi --registry-name vLEI-qvi --schema ENPXp1vQzRF6JwIuS-mp2U8Uf1MoADoP_GqQ62VsDZWY --recipient EIitNxxiNFXC1HDcPygyfyv3KUlBfS_Zf-ZYOvwjpTuz --data @${KERI_DEMO_SCRIPT_DIR}/data/legal-entity-data.json --edges @/tmp/legal-entity-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/rules.json -kli vc list --name legal-entity --alias legal-entity --poll +kli vc create --name qvi --alias qvi --registry-name vLEI-qvi --schema ENPXp1vQzRF6JwIuS-mp2U8Uf1MoADoP_GqQ62VsDZWY --recipient EIitNxxiNFXC1HDcPygyfyv3KUlBfS_Zf-ZYOvwjpTuz --data @${KERI_DEMO_SCRIPT_DIR}/data/legal-entity-data.json --edges @/tmp/legal-entity-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/rules.json +SAID=$(kli vc list --name qvi --alias qvi --issued --said --schema ENPXp1vQzRF6JwIuS-mp2U8Uf1MoADoP_GqQ62VsDZWY) +kli ipex grant --name qvi --alias qvi --said "${SAID}" --recipient EIitNxxiNFXC1HDcPygyfyv3KUlBfS_Zf-ZYOvwjpTuz +GRANT=$(kli ipex list --name legal-entity --alias legal-entity --poll --said) +kli ipex admit --name legal-entity --alias legal-entity --said "${GRANT}" +kli vc list --name legal-entity --alias legal-entity # Issue ECR Authorization credential from Legal Entity to QVI - have to create the edges first LE_SAID=`kli vc list --name legal-entity --alias legal-entity --said` echo \"$LE_SAID\" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/ecr-auth-edges-filter.jq > /tmp/ecr-auth-edges.json kli saidify --file /tmp/ecr-auth-edges.json -kli vc issue --name legal-entity --alias legal-entity --registry-name vLEI-legal-entity --schema EH6ekLjSr8V32WyFbGe1zXjTzFs9PkTYmupJ9H65O14g --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX --data @${KERI_DEMO_SCRIPT_DIR}/data/ecr-auth-data.json --edges @/tmp/ecr-auth-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/ecr-auth-rules.json -kli vc list --name qvi --alias qvi --poll +kli vc create --name legal-entity --alias legal-entity --registry-name vLEI-legal-entity --schema EH6ekLjSr8V32WyFbGe1zXjTzFs9PkTYmupJ9H65O14g --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX --data @${KERI_DEMO_SCRIPT_DIR}/data/ecr-auth-data.json --edges @/tmp/ecr-auth-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/ecr-auth-rules.json +SAID=$(kli vc list --name legal-entity --alias legal-entity --issued --said --schema EH6ekLjSr8V32WyFbGe1zXjTzFs9PkTYmupJ9H65O14g) +kli ipex grant --name legal-entity --alias legal-entity --said "${SAID}" --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX +GRANT=$(kli ipex list --name qvi --alias qvi --poll --said | tail -1) +kli ipex admit --name qvi --alias qvi --said "${GRANT}" +kli vc list --name qvi --alias qvi # Issue ECR credential from QVI to Person AUTH_SAID=`kli vc list --name qvi --alias qvi --said --schema EH6ekLjSr8V32WyFbGe1zXjTzFs9PkTYmupJ9H65O14g` echo "[\"$QVI_SAID\", \"$AUTH_SAID\"]" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/ecr-edges-filter.jq > /tmp/ecr-edges.json kli saidify --file /tmp/ecr-edges.json -kli vc issue --name qvi --alias qvi --private --registry-name vLEI-qvi --schema EEy9PkikFcANV1l7EHukCeXqrzT1hNZjGlUk7wuMO5jw --recipient EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe --data @${KERI_DEMO_SCRIPT_DIR}/data/ecr-data.json --edges @/tmp/ecr-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/ecr-rules.json -kli vc list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --poll +kli vc create --name qvi --alias qvi --private --registry-name vLEI-qvi --schema EEy9PkikFcANV1l7EHukCeXqrzT1hNZjGlUk7wuMO5jw --recipient EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe --data @${KERI_DEMO_SCRIPT_DIR}/data/ecr-data.json --edges @/tmp/ecr-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/ecr-rules.json +SAID=$(kli vc list --name qvi --alias qvi --issued --said --schema EEy9PkikFcANV1l7EHukCeXqrzT1hNZjGlUk7wuMO5jw) +kli ipex grant --name qvi --alias qvi --said "${SAID}" --recipient EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe +GRANT=$(kli ipex list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --poll --said) +kli ipex admit --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --said "${GRANT}" +kli vc list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O # Issue OOR Authorization credential from Legal Entity to QVI - have to create the edges first echo \"$LE_SAID\" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/oor-auth-edges-filter.jq > /tmp/oor-auth-edges.json kli saidify --file /tmp/oor-auth-edges.json -kli vc issue --name legal-entity --alias legal-entity --registry-name vLEI-legal-entity --schema EKA57bKBKxr_kN7iN5i7lMUxpMG-s19dRcmov1iDxz-E --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX --data @${KERI_DEMO_SCRIPT_DIR}/data/oor-auth-data.json --edges @/tmp/oor-auth-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/rules.json -kli vc list --name qvi --alias qvi --poll +kli vc create --name legal-entity --alias legal-entity --registry-name vLEI-legal-entity --schema EKA57bKBKxr_kN7iN5i7lMUxpMG-s19dRcmov1iDxz-E --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX --data @${KERI_DEMO_SCRIPT_DIR}/data/oor-auth-data.json --edges @/tmp/oor-auth-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/rules.json +SAID=$(kli vc list --name legal-entity --alias legal-entity --issued --said --schema EKA57bKBKxr_kN7iN5i7lMUxpMG-s19dRcmov1iDxz-E) +kli ipex grant --name legal-entity --alias legal-entity --said "${SAID}" --recipient EHMnCf8_nIemuPx-cUHaDQq8zSnQIFAurdEpwHpNbnvX +GRANT=$(kli ipex list --name qvi --alias qvi --poll --said | tail -1) +kli ipex admit --name qvi --alias qvi --said "${GRANT}" +kli vc list --name qvi --alias qvi # Issue OOR credential from QVI to Person AUTH_SAID=`kli vc list --name qvi --alias qvi --said --schema EKA57bKBKxr_kN7iN5i7lMUxpMG-s19dRcmov1iDxz-E` echo "[\"$QVI_SAID\", \"$AUTH_SAID\"]" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/oor-edges-filter.jq > /tmp/oor-edges.json kli saidify --file /tmp/oor-edges.json -kli vc issue --name qvi --alias qvi --registry-name vLEI-qvi --schema EBNaNu-M9P5cgrnfl2Fvymy4E_jvxxyjb70PRtiANlJy --recipient EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe --data @${KERI_DEMO_SCRIPT_DIR}/data/oor-data.json --edges @/tmp/oor-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/rules.json -kli vc list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --poll +kli vc create --name qvi --alias qvi --registry-name vLEI-qvi --schema EBNaNu-M9P5cgrnfl2Fvymy4E_jvxxyjb70PRtiANlJy --recipient EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe --data @${KERI_DEMO_SCRIPT_DIR}/data/oor-data.json --edges @/tmp/oor-edges.json --rules @${KERI_DEMO_SCRIPT_DIR}/data/rules.json +SAID=$(kli vc list --name qvi --alias qvi --issued --said --schema EBNaNu-M9P5cgrnfl2Fvymy4E_jvxxyjb70PRtiANlJy) +kli ipex grant --name qvi --alias qvi --said "${SAID}" --recipient EKE7b7owCvObR6dBTrU7w38_oATL9Tcrp_-xjPn05zYe +GRANT=$(kli ipex list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --poll --said | tail -1) +kli ipex admit --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --said "${GRANT}" +kli vc list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O # Issue iXBRL data attestation from Person OOR_SAID=`kli vc list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --said --schema EBNaNu-M9P5cgrnfl2Fvymy4E_jvxxyjb70PRtiANlJy` @@ -96,5 +120,5 @@ kli saidify --file /tmp/xbrl-edges.json NOW=`date -u +"%Y-%m-%dT%H:%M:%S+00:00"` echo \"$NOW\" | jq -f ${KERI_DEMO_SCRIPT_DIR}/data/xbrl-data.jq > /tmp/xbrl-data.json kli saidify --file /tmp/xbrl-data.json -kli vc issue --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --registry-name vLEI-person --schema EMhvwOlyEJ9kN4PrwCpr9Jsv7TxPhiYveZ0oP3lJzdEi --data @/tmp/xbrl-data.json --edges @/tmp/xbrl-edges.json +kli vc create --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --registry-name vLEI-person --schema EMhvwOlyEJ9kN4PrwCpr9Jsv7TxPhiYveZ0oP3lJzdEi --data @/tmp/xbrl-data.json --edges @/tmp/xbrl-edges.json kli vc list --name person --alias person --passcode DoB26Fj4x9LboAFWJra17O --issued \ No newline at end of file diff --git a/src/keri/app/agenting.py b/src/keri/app/agenting.py index ca2335ab3..1ea3842f3 100644 --- a/src/keri/app/agenting.py +++ b/src/keri/app/agenting.py @@ -10,7 +10,7 @@ from hio.base import doing from hio.core import http from hio.core.tcp import clienting -from hio.help import decking +from hio.help import decking, Hict from . import httping, forwarding from .. import help @@ -69,7 +69,6 @@ def receipt(self, pre, sn=None): if ser.ked['t'] in (coring.Ilks.rot,): adds = ser.ked["ba"] for wit in adds: - print(f"catching up {wit}") yield from self.catchup(ser.pre, wit) clients = dict() @@ -722,6 +721,100 @@ def idle(self): return len(self.sent) == self.posted +class TCPStreamMessenger(doing.DoDoer): + """ Send events to witnesses for receipting using TCP direct connection + + """ + + def __init__(self, hab, wit, url, msgs=None, sent=None, doers=None, **kwa): + """ + For the current event, gather the current set of witnesses, send the event, + gather all receipts and send them to all other witnesses + + Parameters: + hab: Habitat of the identifier to populate witnesses + + """ + self.hab = hab + self.wit = wit + self.url = url + self.posted = 0 + self.msgs = msgs if msgs is not None else decking.Deck() + self.sent = sent if sent is not None else decking.Deck() + self.parser = None + doers = doers if doers is not None else [] + doers.extend([doing.doify(self.receiptDo)]) + + self.kevery = eventing.Kevery(db=self.hab.db, + **kwa) + + super(TCPStreamMessenger, self).__init__(doers=doers) + + def receiptDo(self, tymth=None, tock=0.0): + """ + Returns doifiable Doist compatible generator method (doer dog) + + Usage: + add result of doify on this method to doers list + """ + self.wind(tymth) + self.tock = tock + _ = (yield self.tock) + + up = urlparse(self.url) + if up.scheme != kering.Schemes.tcp: + raise ValueError(f"invalid scheme {up.scheme} for TcpWitnesser") + + client = clienting.Client(host=up.hostname, port=up.port) + self.parser = parsing.Parser(ims=client.rxbs, + framed=True, + kvy=self.kevery) + + clientDoer = clienting.ClientDoer(client=client) + self.extend([clientDoer, doing.doify(self.msgDo)]) + + while True: + while not self.msgs: + yield self.tock + + msg = self.msgs.popleft() + self.posted += 1 + + client.tx(msg) # send to connected remote + + while client.txbs: + yield self.tock + + self.sent.append(msg) + yield self.tock + + def msgDo(self, tymth=None, tock=0.0, **opts): + """ + Returns doifiable Doist compatible generator method (doer dog) to process + incoming message stream of .kevery + + Doist Injected Attributes: + g.tock = tock # default tock attributes + g.done = None # default done state + g.opts + + Parameters: + tymth is injected function wrapper closure returned by .tymen() of + Tymist instance. Calling tymth() returns associated Tymist .tyme. + tock is injected initial tock value + opts is dict of injected optional additional parameters + + + Usage: + add result of doify on this method to doers list + """ + yield from self.parser.parsator() # process messages continuously + + @property + def idle(self): + return len(self.sent) == self.posted + + class HTTPMessenger(doing.DoDoer): """ Interacts with Recipients on HTTP and SSE for sending events and receiving receipts @@ -800,6 +893,65 @@ def idle(self): return len(self.msgs) == 0 and self.posted == len(self.sent) +class HTTPStreamMessenger(doing.DoDoer): + """ + Interacts with Recipients on HTTP and SSE for sending events and receiving receipts + + """ + + def __init__(self, hab, wit, url, msg=b'', headers=None, **kwa): + """ + For the current event, gather the current set of witnesses, send the event, + gather all receipts and send them to all other witnesses + + Parameters: + hab: Habitat of the identifier to populate witnesses + + """ + self.hab = hab + self.wit = wit + self.rep = None + headers = headers if headers is not None else {} + + up = urlparse(url) + if up.scheme != kering.Schemes.http and up.scheme != kering.Schemes.https: + raise ValueError(f"invalid scheme {up.scheme} for HTTPMessenger") + + self.client = http.clienting.Client(scheme=up.scheme, hostname=up.hostname, port=up.port) + clientDoer = http.clienting.ClientDoer(client=self.client) + + headers = Hict([ + ("Content-Type", "application/cesr"), + ("Content-Length", len(msg)), + (httping.CESR_DESTINATION_HEADER, self.wit), + ] + list(headers.items())) + + self.client.request( + method="PUT", + path="/", + headers=headers, + body=bytes(msg) + ) + + doers = [clientDoer] + + super(HTTPStreamMessenger, self).__init__(doers=doers, **kwa) + + def recur(self, tyme, deeds=None): + """ + Returns doifiable Doist compatible generator method (doer dog) + + Usage: + add result of doify on this method to doers list + """ + if self.client.responses: + self.rep = self.client.respond() + self.remove([self.client]) + return True + + return super(HTTPStreamMessenger, self).recur(tyme, deeds) + + def mailbox(hab, cid): for (_, erole, eid), end in hab.db.ends.getItemIter(keys=(cid, kering.Roles.mailbox)): if end.allowed: @@ -853,6 +1005,31 @@ def messengerFrom(hab, pre, urls): return witer +def streamMessengerFrom(hab, pre, urls, msg, headers=None): + """ Create a Witnesser (tcp or http) based on provided endpoints + + Parameters: + hab (Habitat): Environment to use to look up witness URLs + pre (str): qb64 identifier prefix of recipient to create a messanger for + urls (dict): map of schemes to urls of available endpoints + msg (bytes): bytes of message to send + headers (dict): optional headers to send with HTTP requests + + Returns: + Optional(TcpWitnesser, HTTPMessenger): witnesser for ensuring full reciepts + """ + if kering.Schemes.http in urls or kering.Schemes.https in urls: + url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https] + witer = HTTPStreamMessenger(hab=hab, wit=pre, url=url, msg=msg, headers=headers) + elif kering.Schemes.tcp in urls: + url = urls[kering.Schemes.tcp] + witer = TCPStreamMessenger(hab=hab, wit=pre, url=url) + else: + raise kering.ConfigurationError(f"unable to find a valid endpoint for witness {pre}") + + return witer + + def httpClient(hab, wit): """ Create and return a http.client and http.ClientDoer for the witness diff --git a/src/keri/app/cli/commands/ends/add.py b/src/keri/app/cli/commands/ends/add.py index 9b275e8df..e672abfda 100644 --- a/src/keri/app/cli/commands/ends/add.py +++ b/src/keri/app/cli/commands/ends/add.py @@ -33,7 +33,7 @@ parser.add_argument("--eid", "-e", help="qualified base64 of AID to authorize with new role for the AID identified " "by alias", required=True) -parser.add_argument("--time", help="timestamp for the revocation", required=False, default=None) +parser.add_argument("--time", help="timestamp for the end auth", required=False, default=None) def add_end(args): diff --git a/src/keri/app/cli/commands/ipex/admit.py b/src/keri/app/cli/commands/ipex/admit.py index a06618874..f45918aa2 100644 --- a/src/keri/app/cli/commands/ipex/admit.py +++ b/src/keri/app/cli/commands/ipex/admit.py @@ -7,7 +7,7 @@ from hio.base import doing -from keri.app import forwarding, connecting, habbing, grouping, indirecting, agenting +from keri.app import connecting, habbing, grouping, indirecting, agenting, forwarding from keri.app.cli.common import existing from keri.app.notifying import Notifier from keri.core import parsing, coring, eventing @@ -52,7 +52,6 @@ def __init__(self, name, alias, base, bran, said, message): self.hab = self.hby.habByName(alias) self.rgy = credentialing.Regery(hby=self.hby, name=name, base=base) self.org = connecting.Organizer(hby=self.hby) - self.postman = forwarding.Poster(hby=self.hby) self.witq = agenting.WitnessInquisitor(hby=self.hby) self.kvy = eventing.Kevery(db=self.hby.db) @@ -66,13 +65,13 @@ def __init__(self, name, alias, base, bran, said, message): self.exc = exchanging.Exchanger(hby=self.hby, handlers=[]) grouping.loadHandlers(self.exc, mux) - protocoling.loadHandlers(self.hby, rgy=self.rgy, exc=self.exc, notifier=notifier) + protocoling.loadHandlers(self.hby, exc=self.exc, notifier=notifier) mbx = indirecting.MailboxDirector(hby=self.hby, topics=["/receipt", "/multisig", "/replay", "/credential"], exc=self.exc, kvy=self.kvy, tvy=self.tvy, verifier=self.vry) - self.toRemove = [self.postman, mbx, self.witq] + self.toRemove = [mbx, self.witq] super(AdmitDoer, self).__init__(doers=self.toRemove + [doing.doify(self.admitDo)]) def admitDo(self, tymth, tock=0.0): @@ -132,24 +131,25 @@ def admitDo(self, tymth, tock=0.0): smids.remove(self.hab.mhab.pre) for recp in smids: # this goes to other participants only as a signaling mechanism - self.postman.send(src=self.hab.mhab.pre, - dest=recp, - topic="multisig", - serder=wexn, - attachment=watc) + postman = forwarding.StreamPoster(hby=self.hby, hab=self.hab.mhab, recp=recp, topic="multisig") + postman.send(serder=wexn, + attachment=watc) + doer = doing.DoDoer(doers=postman.deliver()) + self.extend([doer]) while not self.exc.complete(said=wexn.said): yield self.tock if self.exc.lead(self.hab, said=exn.said): print(f"Sending admit message to {recp}") - self.postman.send(src=self.hab.pre, - dest=recp, - topic="credential", - serder=exn, - attachment=atc) + postman = forwarding.StreamPoster(hby=self.hby, hab=self.hab, recp=recp, topic="credential") + postman.send(serder=exn, + attachment=atc) - while not self.postman.sent(exn.said): + doer = doing.DoDoer(doers=postman.deliver()) + self.extend([doer]) + + while not doer.done: yield self.tock self.remove(self.toRemove) diff --git a/src/keri/app/cli/commands/ipex/grant.py b/src/keri/app/cli/commands/ipex/grant.py index bcc9157cb..e169ced62 100644 --- a/src/keri/app/cli/commands/ipex/grant.py +++ b/src/keri/app/cli/commands/ipex/grant.py @@ -57,19 +57,18 @@ def __init__(self, name, alias, base, bran, said, recp, message, timestamp): self.hab = self.hby.habByName(alias) self.rgy = credentialing.Regery(hby=self.hby, name=name, base=base) self.org = connecting.Organizer(hby=self.hby) - self.postman = forwarding.Poster(hby=self.hby) notifier = Notifier(self.hby) mux = grouping.Multiplexor(self.hby, notifier=notifier) self.exc = exchanging.Exchanger(hby=self.hby, handlers=[]) grouping.loadHandlers(self.exc, mux) - protocoling.loadHandlers(self.hby, rgy=self.rgy, exc=self.exc, notifier=notifier) + protocoling.loadHandlers(self.hby, exc=self.exc, notifier=notifier) mbx = indirecting.MailboxDirector(hby=self.hby, topics=["/receipt", "/multisig", "/replay", "/credential"], exc=self.exc) - self.toRemove = [self.postman, mbx] + self.toRemove = [mbx] super(GrantDoer, self).__init__(doers=self.toRemove + [doing.doify(self.grantDo)]) def grantDo(self, tymth, tock=0.0): @@ -123,42 +122,44 @@ def grantDo(self, tymth, tock=0.0): parsing.Parser().parseOne(ims=bytes(msg), exc=self.exc) - sender = self.hab.pre if isinstance(self.hab, habbing.GroupHab): - sender = self.hab.mhab.pre wexn, watc = grouping.multisigExn(self.hab, exn=msg) smids = self.hab.db.signingMembers(pre=self.hab.pre) smids.remove(self.hab.mhab.pre) for part in smids: # this goes to other participants - self.postman.send(src=self.hab.mhab.pre, - dest=part, - topic="multisig", - serder=wexn, - attachment=watc) + postman = forwarding.StreamPoster(hby=self.hby, hab=self.hab.mhab, recp=part, topic="multisig") + postman.send(serder=wexn, + attachment=watc) + doer = doing.DoDoer(doers=postman.deliver()) + self.extend([doer]) while not self.exc.complete(said=exn.said): yield self.tock if self.exc.lead(self.hab, said=exn.said): print(f"Sending message {exn.said} to {recp}") + postman = forwarding.StreamPoster(hby=self.hby, hab=self.hab, recp=recp, topic="credential") + sources = self.rgy.reger.sources(self.hby.db, creder) + credentialing.sendArtifacts(self.hby, self.rgy.reger, postman, creder, recp) for source, atc in sources: - credentialing.sendArtifacts(self.hby, self.rgy.reger, self.postman, source, sender, recp) - self.postman.send(src=sender, dest=recp, topic="credential", serder=source, attachment=atc) + credentialing.sendArtifacts(self.hby, self.rgy.reger, postman, source, recp) + postman.send(serder=source, attachment=atc) atc = exchanging.serializeMessage(self.hby, exn.said) del atc[:exn.size] - self.postman.send(src=sender, - dest=recp, - topic="credential", - serder=exn, - attachment=atc) + postman.send(serder=exn, + attachment=atc) + + doer = doing.DoDoer(doers=postman.deliver()) + self.extend([doer]) - while not self.postman.sent(said=exn.said): + while not doer.done: yield self.tock - print("... grant message sent") + print(f"... grant message sent") + self.remove([postman]) self.remove(self.toRemove) diff --git a/src/keri/app/cli/commands/ipex/list.py b/src/keri/app/cli/commands/ipex/list.py index 4d977c41f..37148bc54 100644 --- a/src/keri/app/cli/commands/ipex/list.py +++ b/src/keri/app/cli/commands/ipex/list.py @@ -81,7 +81,7 @@ def __init__(self, name, alias, base, bran, poll=False, verbose=False, said=Fals self.rgy = credentialing.Regery(hby=self.hby, name=name, base=base) self.vry = verifying.Verifier(hby=self.hby, reger=self.rgy.reger) self.exc = exchanging.Exchanger(hby=self.hby, handlers=[]) - protocoling.loadHandlers(self.hby, self.exc, self.rgy, self.notifier) + protocoling.loadHandlers(self.hby, self.exc, self.notifier) self.mbx = indirecting.MailboxDirector(hby=self.hby, topics=['/replay', 'reply', '/credential'], exc=self.exc, verifier=self.vry) diff --git a/src/keri/app/cli/commands/ipex/spurn.py b/src/keri/app/cli/commands/ipex/spurn.py index 2ca50d1fe..3d4ecebe8 100644 --- a/src/keri/app/cli/commands/ipex/spurn.py +++ b/src/keri/app/cli/commands/ipex/spurn.py @@ -53,7 +53,6 @@ def __init__(self, name, alias, base, bran, said, message): self.hab = self.hby.habByName(alias) self.rgy = credentialing.Regery(hby=self.hby, name=name, base=base) self.org = connecting.Organizer(hby=self.hby) - self.postman = forwarding.Poster(hby=self.hby) kvy = eventing.Kevery(db=self.hby.db) tvy = teventing.Tevery(db=self.hby.db, reger=self.rgy.reger) @@ -66,13 +65,13 @@ def __init__(self, name, alias, base, bran, said, message): self.exc = exchanging.Exchanger(hby=self.hby, handlers=[]) grouping.loadHandlers(self.exc, mux) - protocoling.loadHandlers(self.hby, rgy=self.rgy, exc=self.exc, notifier=notifier) + protocoling.loadHandlers(self.hby, exc=self.exc, notifier=notifier) mbx = indirecting.MailboxDirector(hby=self.hby, topics=["/receipt", "/multisig", "/replay", "/credential"], exc=self.exc) - self.toRemove = [self.postman, mbx] + self.toRemove = [mbx] super(SpurnDoer, self).__init__(doers=self.toRemove + [doing.doify(self.spurnDo)]) def spurnDo(self, tymth, tock=0.0): @@ -119,24 +118,25 @@ def spurnDo(self, tymth, tock=0.0): smids.remove(self.hab.mhab.pre) for recp in smids: # this goes to other participants only as a signaling mechanism - self.postman.send(src=self.hab.mhab.pre, - dest=recp, - topic="multisig", - serder=wexn, - attachment=watc) + postman = forwarding.StreamPoster(hby=self.hby, hab=self.hab.mhab, recp=recp, topic="multisig") + postman.send(serder=wexn, + attachment=watc) + doer = doing.DoDoer(doers=postman.deliver()) + self.extend([doer]) while not self.exc.complete(said=wexn.said): yield self.tock if self.exc.lead(self.hab, said=exn.said): print("Sending spurn message...") - self.postman.send(src=self.hab.pre, - dest=recp, - topic="credential", - serder=exn, - attachment=atc) + postman = forwarding.StreamPoster(hby=self.hby, hab=self.hab, recp=recp, topic="credential") + postman.send(serder=exn, + attachment=atc) - while not self.postman.cues: + doer = doing.DoDoer(doers=postman.deliver()) + self.extend([doer]) + + while not doer.done: yield self.tock self.remove(self.toRemove) diff --git a/src/keri/app/cli/commands/vc/present.py b/src/keri/app/cli/commands/vc/present.py deleted file mode 100644 index 29f11e954..000000000 --- a/src/keri/app/cli/commands/vc/present.py +++ /dev/null @@ -1,131 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -KERI -keri.kli.commands module - -""" -import argparse - -from hio import help -from hio.base import doing - -from keri.app import connecting, forwarding -from keri.app.cli.common import existing -from keri.app.habbing import GroupHab -from keri.core import coring -from keri.vc import protocoling -from keri.vdr import credentialing - -logger = help.ogler.getLogger() - -parser = argparse.ArgumentParser(description='Send credential presentation for specified credential to recipient') -parser.set_defaults(handler=lambda args: present_credential(args), - transferable=True) -parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True) -parser.add_argument('--alias', '-a', help='human readable alias for the identifier to whom the credential was issued', - required=True) -parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore', - required=False, default="") -parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)', - dest="bran", default=None) # passcode => bran - -parser.add_argument("--said", "-s", help="SAID of the credential to present.", required=True) -parser.add_argument("--include", "-i", help="send credential and all other cryptographic artifacts with presentation", - action="store_true") -parser.add_argument("--recipient", "-r", help="alias or qb64 AID ") - - -def present_credential(args): - """ Command line credential presentation handler - - """ - - ed = PresentDoer(name=args.name, - alias=args.alias, - base=args.base, - bran=args.bran, - said=args.said, - recipient=args.recipient, - include=args.include) - return [ed] - - -class PresentDoer(doing.DoDoer): - - def __init__(self, name, alias, base, bran, said, recipient, include): - self.said = said - self.recipient = recipient - self.include = include - - self.hby = existing.setupHby(name=name, base=base, bran=bran) - self.hab = self.hby.habByName(alias) - self.org = connecting.Organizer(hby=self.hby) - self.rgy = credentialing.Regery(hby=self.hby, name=name, base=base) - self.postman = forwarding.Poster(hby=self.hby) - - doers = [self.postman, doing.doify(self.presentDo)] - - super(PresentDoer, self).__init__(doers=doers) - - def presentDo(self, tymth, tock=0.0): - """ Present credential from store and any related material - - Parameters: - tymth (function): injected function wrapper closure returned by .tymen() of - Tymist instance. Calling tymth() returns associated Tymist .tyme. - tock (float): injected initial tock value - - Returns: doifiable Doist compatible generator method - - """ - # enter context - self.wind(tymth) - self.tock = tock - _ = (yield self.tock) - - creder = self.rgy.reger.creds.get(self.said) - if creder is None: - raise ValueError(f"invalid credential SAID {self.said}") - - if self.recipient in self.hby.kevers: - recp = self.recipient - else: - recp = self.org.find("alias", self.recipient) - if len(recp) != 1: - raise ValueError(f"invalid recipient {self.recipient}") - recp = recp[0]['id'] - - if self.include: - credentialing.sendCredential(self.hby, hab=self.hab, reger=self.rgy.reger, postman=self.postman, - creder=creder, recp=recp) - - if isinstance(self.hab, GroupHab): - senderHab = self.hab.mhab - else: - senderHab = self.hab - - if senderHab.pre != creder.issuer: - for msg in senderHab.db.cloneDelegation(senderHab.kever): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - self.postman.send(src=senderHab.pre, dest=recp, topic="credential", serder=serder, attachment=atc) - - for msg in senderHab.db.clonePreIter(pre=senderHab.pre): - serder = coring.Serder(raw=msg) - atc = msg[serder.size:] - self.postman.send(src=senderHab.pre, dest=recp, topic="credential", serder=serder, attachment=atc) - - exn, atc = protocoling.presentationExchangeExn(hab=senderHab, reger=self.rgy.reger, said=self.said) - self.postman.send(src=senderHab.pre, dest=recp, topic="credential", serder=exn, attachment=atc) - - while True: - while self.postman.cues: - cue = self.postman.cues.popleft() - if "said" in cue and cue["said"] == exn.said: - print("Presentation sent") - toRemove = [self.postman] - self.remove(toRemove) - return True - yield self.tock - yield self.tock - diff --git a/src/keri/app/forwarding.py b/src/keri/app/forwarding.py index 8ba5e2ce5..26a0417cf 100644 --- a/src/keri/app/forwarding.py +++ b/src/keri/app/forwarding.py @@ -30,12 +30,11 @@ class Poster(doing.DoDoer): """ - def __init__(self, hby, mbx=None, evts=None, cues=None, klas=None, **kwa): + def __init__(self, hby, mbx=None, evts=None, cues=None, **kwa): self.hby = hby self.mbx = mbx self.evts = evts if evts is not None else decking.Deck() self.cues = cues if cues is not None else decking.Deck() - self.klas = klas if klas is not None else agenting.HTTPMessenger doers = [doing.doify(self.deliverDo)] super(Poster, self).__init__(doers=doers, **kwa) @@ -165,10 +164,10 @@ def sendDirect(self, hab, ends, serder, atc): witer.msgs.append(bytearray(msg)) # make a copy self.extend([witer]) - while not witer.idle: - _ = (yield self.tock) + while not witer.idle: + _ = (yield self.tock) - self.remove([witer]) + self.remove([witer]) def forward(self, hab, ends, recp, serder, atc, topic): # If we are one of the mailboxes, just store locally in mailbox @@ -237,6 +236,155 @@ def forwardToWitness(self, hab, ends, recp, serder, atc, topic): _ = (yield self.tock) +class StreamPoster: + """ + DoDoer that wraps any KERI event (KEL, TEL, Peer to Peer) in a /fwd `exn` envelope and + delivers them to one of the target recipient's witnesses for store and forward + to the intended recipient + + """ + + def __init__(self, hby, recp, src=None, hab=None, mbx=None, topic=None, headers=None, **kwa): + if hab is not None: + self.hab = hab + else: + self.hab = hby.habs[src] + + self.hby = hby + self.hab = hab + self.recp = recp + self.src = src + self.messagers = [] + self.mbx = mbx + self.topic = topic + self.headers = headers + self.evts = decking.Deck() + + def deliver(self): + """ + Returns: doifiable Doist compatible generator method that processes + a queue of messages and envelopes them in a `fwd` message + and sends them to one of the witnesses of the recipient for + store and forward. + + Usage: + add result of doify on this method to doers list + """ + msg = bytearray() + + while self.evts: + evt = self.evts.popleft() + + serder = evt["serder"] + atc = evt["attachment"] if "attachment" in evt else b'' + + msg.extend(serder.raw) + msg.extend(atc) + + if len(msg) == 0: + return [] + + ends = self.hab.endsFor(self.recp) + try: + # If there is a controller or agent in ends, send to all + if {Roles.controller, Roles.agent, Roles.mailbox} & set(ends): + for role in (Roles.controller, Roles.agent, Roles.mailbox): + if role in ends: + if role == Roles.mailbox: + return self.forward(self.hab, ends[role], msg=msg, topic=self.topic) + else: + return self.sendDirect(self.hab, ends[role], msg=msg) + # otherwise send to one witness + elif Roles.witness in ends: + return self.forward(self.hab, ends[Roles.witness], msg=msg, topic=self.topic) + + else: + logger.info(f"No end roles for {self.recp} to send evt={self.recp}") + return [] + + except kering.ConfigurationError as e: + logger.error(f"Error sending to {self.recp} with ends={ends}. Err={e}") + return [] + + def send(self, serder, attachment=None): + """ + Utility function to queue a msg on the Poster's buffer for + enveloping and forwarding to a witness + + Parameters: + serder (Serder) KERI event message to envelope and forward: + attachment (bytes): attachment bytes + + """ + ends = self.hab.endsFor(self.recp) + try: + # If there is a controller, agent or mailbox in ends, send to all + if {Roles.controller, Roles.agent, Roles.mailbox} & set(ends): + for role in (Roles.controller, Roles.agent, Roles.mailbox): + if role in ends: + if role == Roles.mailbox: + serder, attachment = self.createForward(self.hab, serder=serder, ends=ends, + atc=attachment, topic=self.topic) + + # otherwise send to one witness + elif Roles.witness in ends: + serder, attachment = self.createForward(self.hab, ends=ends, serder=serder, + atc=attachment, topic=self.topic) + else: + logger.info(f"No end roles for {self.recp} to send evt={self.recp}") + raise kering.ValidationError(f"No end roles for {self.recp} to send evt={self.recp}") + except kering.ConfigurationError as e: + logger.error(f"Error sending to {self.recp} with ends={ends}. Err={e}") + raise kering.ValidationError(f"Error sending to {self.recp} with ends={ends}. Err={e}") + + evt = dict(serder=serder) + if attachment is not None: + evt["attachment"] = attachment + + self.evts.append(evt) + + def sendDirect(self, hab, ends, msg): + for ctrl, locs in ends.items(): + self.messagers.append(agenting.streamMessengerFrom(hab=hab, pre=ctrl, urls=locs, msg=msg, + headers=self.headers)) + + return self.messagers + + def createForward(self, hab, ends, serder, atc, topic): + # If we are one of the mailboxes, just store locally in mailbox + owits = oset(ends.keys()) + if self.mbx and owits.intersection(hab.prefixes): + msg = bytearray(serder.raw) + if atc is not None: + msg.extend(atc) + self.mbx.storeMsg(topic=f"{self.recp}/{topic}".encode("utf-8"), msg=msg) + return None, None + + # Its not us, randomly select a mailbox and forward it on + evt = bytearray(serder.raw) + evt.extend(atc) + fwd, atc = exchanging.exchange(route='/fwd', modifiers=dict(pre=self.recp, topic=topic), + payload={}, embeds=dict(evt=evt), sender=hab.pre) + ims = hab.endorse(serder=fwd, last=False, pipelined=False) + return fwd, ims + atc + + def forward(self, hab, ends, msg, topic): + # If we are one of the mailboxes, just store locally in mailbox + owits = oset(ends.keys()) + if self.mbx and owits.intersection(hab.prefixes): + self.mbx.storeMsg(topic=f"{self.recp}/{topic}".encode("utf-8"), msg=msg) + return [] + + # Its not us, randomly select a mailbox and forward it on + mbx, mailbox = random.choice(list(ends.items())) + ims = bytearray() + ims.extend(introduce(hab, mbx)) + ims.extend(msg) + + self.messagers.append(agenting.streamMessengerFrom(hab=hab, pre=mbx, urls=mailbox, msg=bytes(ims))) + return self.messagers + + class ForwardHandler: """ Handler for forward `exn` messages used to envelope other KERI messages intended for another recipient. diff --git a/src/keri/app/habbing.py b/src/keri/app/habbing.py index 2b8c7019e..87f8e79e7 100644 --- a/src/keri/app/habbing.py +++ b/src/keri/app/habbing.py @@ -1338,6 +1338,25 @@ def sign(self, ser, verfers=None, indexed=True, indices=None, ondices=None, **kw indices=indices, ondices=ondices) + def decrypt(self, ser, verfers=None, **kwa): + """Sign given serialization ser using appropriate keys. + Use provided verfers or .kever.verfers to lookup keys to sign. + + Parameters: + ser (bytes): serialization to sign + verfers (list[Verfer] | None): Verfer instances to get pub verifier + keys to lookup private siging keys. + verfers None means use .kever.verfers. Assumes that when group + and verfers is not None then provided verfers must be .kever.verfers + + """ + if verfers is None: + verfers = self.kever.verfers # when group these provide group signing keys + + return self.mgr.decrypt(ser=ser, + verfers=verfers, + ) + def query(self, pre, src, query=None, **kwa): """ Create, sign and return a `qry` message against the attester for the prefix @@ -1934,6 +1953,8 @@ def replyEndRole(self, cid, role=None, eids=None, scheme=""): if cid not in self.kevers: return msgs + msgs.extend(self.replay(cid)) + kever = self.kevers[cid] witness = self.pre in kever.wits # see if we are cid's witness @@ -1953,7 +1974,6 @@ def replyEndRole(self, cid, role=None, eids=None, scheme=""): msgs.extend(self.loadLocScheme(eid=eid, scheme=scheme)) msgs.extend(self.loadEndRole(cid=cid, eid=eid, role=erole)) - msgs.extend(self.replay(cid)) return msgs def replyToOobi(self, aid, role, eids=None): @@ -2465,6 +2485,9 @@ def replyEndRole(self, cid, role=None, eids=None, scheme=""): if eids is None: eids = [] + # introduce yourself, please + msgs.extend(self.replay(cid)) + if role == kering.Roles.witness: if kever := self.kevers[cid] if cid in self.kevers else None: witness = self.pre in kever.wits # see if we are cid's witness @@ -2484,9 +2507,6 @@ def replyEndRole(self, cid, role=None, eids=None, scheme=""): msgs.extend(self.loadLocScheme(eid=eid, scheme=scheme)) msgs.extend(self.loadEndRole(cid=cid, eid=eid, role=erole)) - # introduce yourself, please - msgs.extend(self.replay(cid)) - return msgs diff --git a/src/keri/app/indirecting.py b/src/keri/app/indirecting.py index d44f5cc28..9896649ca 100644 --- a/src/keri/app/indirecting.py +++ b/src/keri/app/indirecting.py @@ -59,7 +59,7 @@ def setupWitness(hby, alias="witness", mbx=None, aids=None, tcpPort=5631, httpPo app = falcon.App(cors_enable=True) ending.loadEnds(app=app, hby=hby, default=hab.pre) - oobiRes = oobiing.loadEnds(app=app, hby=hby, prefix="/ext") + oobiing.loadEnds(app=app, hby=hby, prefix="/ext") rep = storing.Respondant(hby=hby, mbx=mbx, aids=aids) rvy = routing.Revery(db=hby.db, cues=cues) @@ -108,7 +108,6 @@ def setupWitness(hby, alias="witness", mbx=None, aids=None, tcpPort=5631, httpPo kvy=kvy, tvy=tvy, rvy=rvy, exc=exchanger, replies=rep.reps, responses=rep.cues, queries=httpEnd.qrycues) - doers.extend(oobiRes) doers.extend([regDoer, httpServerDoer, rep, witStart, receiptEnd, *oobiery.doers]) return doers @@ -890,27 +889,69 @@ def on_post(self, req, rep): rep.set_header('connection', "close") cr = httping.parseCesrHttpRequest(req=req) - serder = eventing.Serder(ked=cr.payload, kind=eventing.Serials.json) - msg = bytearray(serder.raw) + sadder = coring.Sadder(ked=cr.payload, kind=eventing.Serials.json) + msg = bytearray(sadder.raw) msg.extend(cr.attachments.encode("utf-8")) self.rxbs.extend(msg) - ilk = serder.ked["t"] - if ilk in (Ilks.icp, Ilks.rot, Ilks.ixn, Ilks.dip, Ilks.drt, Ilks.exn, Ilks.rpy): + if sadder.proto in ("ACDC",): rep.set_header('Content-Type', "application/json") rep.status = falcon.HTTP_204 - elif ilk in (Ilks.vcp, Ilks.vrt, Ilks.iss, Ilks.rev, Ilks.bis, Ilks.brv): - rep.set_header('Content-Type', "application/json") - rep.status = falcon.HTTP_204 - elif ilk in (Ilks.qry,): - if serder.ked["r"] in ("mbx",): - rep.set_header('Content-Type', "text/event-stream") - rep.status = falcon.HTTP_200 - rep.stream = QryRpyMailboxIterable(mbx=self.mbx, cues=self.qrycues, said=serder.said) - else: + else: + ilk = sadder.ked["t"] + if ilk in (Ilks.icp, Ilks.rot, Ilks.ixn, Ilks.dip, Ilks.drt, Ilks.exn, Ilks.rpy): + rep.set_header('Content-Type', "application/json") + rep.status = falcon.HTTP_204 + elif ilk in (Ilks.vcp, Ilks.vrt, Ilks.iss, Ilks.rev, Ilks.bis, Ilks.brv): rep.set_header('Content-Type', "application/json") rep.status = falcon.HTTP_204 + elif ilk in (Ilks.qry,): + if sadder.ked["r"] in ("mbx",): + rep.set_header('Content-Type', "text/event-stream") + rep.status = falcon.HTTP_200 + rep.stream = QryRpyMailboxIterable(mbx=self.mbx, cues=self.qrycues, said=sadder.said) + else: + rep.set_header('Content-Type', "application/json") + rep.status = falcon.HTTP_204 + + def on_put(self, req, rep): + """ + Handles PUT for KERI mbx event messages. + + Parameters: + req (Request) Falcon HTTP request + rep (Response) Falcon HTTP response + + --- + summary: Accept KERI events with attachment headers and parse + description: Accept KERI events with attachment headers and parse. + tags: + - Events + requestBody: + required: true + content: + application/json: + schema: + type: object + description: KERI event message + responses: + 200: + description: Mailbox query response for server sent events + 204: + description: KEL or EXN event accepted. + """ + if req.method == "OPTIONS": + rep.status = falcon.HTTP_200 + return + + rep.set_header('Cache-Control', "no-cache") + rep.set_header('connection', "close") + + self.rxbs.extend(req.bounded_stream.read()) + + rep.set_header('Content-Type', "application/json") + rep.status = falcon.HTTP_204 class QryRpyMailboxIterable: diff --git a/src/keri/app/keeping.py b/src/keri/app/keeping.py index 516b56853..085957fa5 100644 --- a/src/keri/app/keeping.py +++ b/src/keri/app/keeping.py @@ -26,6 +26,7 @@ from collections import namedtuple, deque from dataclasses import dataclass, asdict, field +import pysodium from hio.base import doing from .. import kering @@ -1392,6 +1393,56 @@ def sign(self, ser, pubs=None, verfers=None, indexed=True, cigars.append(signer.sign(ser)) # assigns .verfer to cigar return cigars + def decrypt(self, ser, pubs=None, verfers=None): + """ + Returns list of signatures of ser if indexed as Sigers else as Cigars with + .verfer assigned. + + Parameters: + ser (bytes): serialization to sign + pubs (list[str] | None): of qb64 public keys to lookup private keys + one of pubs or verfers is required. If both then verfers is ignored. + verfers (list[Verfer] | None): Verfer instances of public keys + one of pubs or verfers is required. If both then verfers is ignored. + If not pubs then gets public key from verfer.qb64 + + Returns: + bytes: decrypted data + + """ + signers = [] + if pubs: + for pub in pubs: + if self.aeid and not self.decrypter: + raise kering.DecryptError("Unauthorized decryption attempt. " + "Aeid but no decrypter.") + if ((signer := self.ks.pris.get(pub, decrypter=self.decrypter)) + is None): + raise ValueError("Missing prikey in db for pubkey={}".format(pub)) + signers.append(signer) + + else: + for verfer in verfers: + if self.aeid and not self.decrypter: + raise kering.DecryptError("Unauthorized decryption attempt. " + "Aeid but no decrypter.") + if ((signer := self.ks.pris.get(verfer.qb64, + decrypter=self.decrypter)) + is None): + raise ValueError("Missing prikey in db for pubkey={}".format(verfer.qb64)) + signers.append(signer) + + plain = ser + for signer in signers: + sigkey = signer.raw + signer.verfer.raw # sigkey is raw seed + raw verkey + prikey = pysodium.crypto_sign_sk_to_box_sk(sigkey) # raw private encrypt key + pubkey = pysodium.crypto_scalarmult_curve25519_base(prikey) + plain = pysodium.crypto_box_seal_open(plain, pubkey, prikey) # qb64b + + if plain == ser: + raise ValueError("unable to decrypt data") + + return plain def ingest(self, secrecies, iridx=0, ncount=1, ncode=coring.MtrDex.Ed25519_Seed, dcode=coring.MtrDex.Blake3_256, diff --git a/src/keri/app/oobiing.py b/src/keri/app/oobiing.py index b596a0ec3..073e690b9 100644 --- a/src/keri/app/oobiing.py +++ b/src/keri/app/oobiing.py @@ -36,9 +36,7 @@ def loadEnds(app, *, hby, prefix=""): oobiEnd = OobiResource(hby=hby) app.add_route(prefix + "/oobi", oobiEnd) - app.add_route(prefix + "/oobi/groups/{alias}/share", oobiEnd, suffix="share") - - return [oobiEnd] + return [] def loadHandlers(hby, exc, notifier): @@ -54,7 +52,7 @@ def loadHandlers(hby, exc, notifier): exc.addHandler(oobireq) -class OobiResource(doing.DoDoer): +class OobiResource: """ Resource for managing OOBIs @@ -69,11 +67,6 @@ def __init__(self, hby): """ self.hby = hby - self.postman = forwarding.Poster(hby=self.hby) - doers = [self.postman] - - super(OobiResource, self).__init__(doers=doers) - def on_get_alias(self, req, rep, alias=None): """ OOBI GET endpoint @@ -210,68 +203,6 @@ def on_post(self, req, rep): rep.status = falcon.HTTP_202 - def on_post_share(self, req, rep, alias): - """ Share OOBI endpoint. - - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - alias: human readable name of the local identifier context for resolving this OOBI - - --- - summary: Share OOBI and alias for remote identifier with other aids - description: Send all other participants in a group AID a copy of the OOBI with suggested alias - tags: - - OOBIs - parameters: - - in: path - name: alias - schema: - type: string - required: true - description: Human readable alias for AID to use to sign exn message - requestBody: - required: true - content: - application/json: - schema: - description: OOBI - properties: - oobis: - type: array - items: - type: string - description: URL OOBI - responses: - 202: - description: OOBI resolution to key state successful - - """ - body = req.get_media() - hab = self.hby.habByName(alias) - if hab is None: - rep.status = falcon.HTTP_404 - rep.text = f"Unknown identifier {alias}" - return - - if not isinstance(hab, GroupHab): - rep.status = falcon.HTTP_400 - rep.text = f"Identifier for {alias} is not a group hab, not supported" - return - - oobis = body["oobis"] - both = list(set(hab.smids + (hab.rmids or []))) - for mid in both: # hab.smids - if mid == hab.mhab.pre: - continue - - for oobi in oobis: - exn, atc = oobiRequestExn(hab.mhab, mid, oobi) - self.postman.send(src=hab.mhab.pre, dest=mid, topic="oobi", serder=exn, attachment=atc) - - rep.status = falcon.HTTP_200 - return - class OobiRequestHandler: """ diff --git a/src/keri/core/eventing.py b/src/keri/core/eventing.py index 066b081f5..9b008ee33 100644 --- a/src/keri/core/eventing.py +++ b/src/keri/core/eventing.py @@ -3539,7 +3539,7 @@ def processReplyEndRole(self, *, serder, saider, route, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: - raise UnverifiedReplyError(f"Unverified reply. {serder.ked}") + raise UnverifiedReplyError(f"Unverified end role reply. {serder.ked}") self.updateEnd(keys=keys, saider=saider, allowed=allowed) # update .eans and .ends @@ -3636,7 +3636,7 @@ def processReplyLocScheme(self, *, serder, saider, route, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: - raise UnverifiedReplyError(f"B Unverified reply. {serder.ked}") + raise UnverifiedReplyError(f"Unverified loc scheme reply. {serder.ked}") self.updateLoc(keys=keys, saider=saider, url=url) # update .lans and .locs @@ -3750,7 +3750,7 @@ def processReplyKeyStateNotice(self, *, serder, saider, route, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: - raise UnverifiedReplyError(f"C Unverified reply. {serder.ked}") + raise UnverifiedReplyError(f"Unverified key state notice reply. {serder.ked}") ldig = self.db.getKeLast(key=snKey(pre=pre, sn=sn)) # retrieve dig of last event at sn. diger = coring.Diger(qb64=ksr.d) diff --git a/src/keri/core/parsing.py b/src/keri/core/parsing.py index 036a3f069..e47e64248 100644 --- a/src/keri/core/parsing.py +++ b/src/keri/core/parsing.py @@ -1106,8 +1106,11 @@ def msgParsator(self, ims=None, framed=True, pipeline=False, exc.processEvent(tsgs=tsgs, **args) except AttributeError as e: + print(e) raise kering.ValidationError("No Exchange to process so dropped msg" "= {}.".format(serder.pretty())) + except Exception as e: + print(e) elif ilk in (Ilks.vcp, Ilks.vrt, Ilks.iss, Ilks.rev, Ilks.bis, Ilks.brv): # TEL msg diff --git a/src/keri/vc/protocoling.py b/src/keri/vc/protocoling.py index 4878311b3..12f4c991b 100644 --- a/src/keri/vc/protocoling.py +++ b/src/keri/vc/protocoling.py @@ -27,19 +27,17 @@ class IpexHandler: """ - def __init__(self, resource, hby, rgy, notifier): + def __init__(self, resource, hby, notifier): """ Initialize instance Parameters: resource (str): route of messages for this handler hby (Habery): local identifier environment - rgy (Regery): Credential database environment notifier (Notifier): outbound notifications """ self.resource = resource self.hby = hby - self.rgy = rgy self.notifier = notifier def verify(self, serder, attachments=None): @@ -221,7 +219,7 @@ def ipexAgreeExn(hab, message, offer): return exn, ims -def ipexGrantExn(hab, recp, message, acdc, iss, anc, agree=None, dt=None): +def ipexGrantExn(hab, recp, message, acdc, iss=None, anc=None, agree=None, dt=None): """ Disclose an ACDC Parameters: @@ -246,10 +244,14 @@ def ipexGrantExn(hab, recp, message, acdc, iss, anc, agree=None, dt=None): embeds = dict( acdc=acdc, - iss=iss, - anc=anc ) + if iss is not None: + embeds['iss'] = iss + + if anc is not None: + embeds['anc'] = anc + kwa = dict() if agree is not None: kwa['dig'] = agree.said @@ -312,19 +314,18 @@ def ipexSpurnExn(hab, message, spurned): return exn, ims -def loadHandlers(hby, exc, rgy, notifier): +def loadHandlers(hby, exc, notifier): """ Load handlers for the IPEX protocol Parameters: hby (Habery): Database and keystore for environment exc (Exchanger): Peer-to-peer message router - rgy (Regery): Credential database environment notifier (Notifier): outbound notifications """ - exc.addHandler(IpexHandler(resource="/ipex/apply", hby=hby, rgy=rgy, notifier=notifier)) - exc.addHandler(IpexHandler(resource="/ipex/offer", hby=hby, rgy=rgy, notifier=notifier)) - exc.addHandler(IpexHandler(resource="/ipex/agree", hby=hby, rgy=rgy, notifier=notifier)) - exc.addHandler(IpexHandler(resource="/ipex/grant", hby=hby, rgy=rgy, notifier=notifier)) - exc.addHandler(IpexHandler(resource="/ipex/admit", hby=hby, rgy=rgy, notifier=notifier)) - exc.addHandler(IpexHandler(resource="/ipex/spurn", hby=hby, rgy=rgy, notifier=notifier)) + exc.addHandler(IpexHandler(resource="/ipex/apply", hby=hby, notifier=notifier)) + exc.addHandler(IpexHandler(resource="/ipex/offer", hby=hby, notifier=notifier)) + exc.addHandler(IpexHandler(resource="/ipex/agree", hby=hby, notifier=notifier)) + exc.addHandler(IpexHandler(resource="/ipex/grant", hby=hby, notifier=notifier)) + exc.addHandler(IpexHandler(resource="/ipex/admit", hby=hby, notifier=notifier)) + exc.addHandler(IpexHandler(resource="/ipex/spurn", hby=hby, notifier=notifier)) diff --git a/src/keri/vdr/credentialing.py b/src/keri/vdr/credentialing.py index edf61096e..591936bf2 100644 --- a/src/keri/vdr/credentialing.py +++ b/src/keri/vdr/credentialing.py @@ -886,7 +886,7 @@ def sendCredential(hby, hab, reger, postman, creder, recp): hby: hab: reger: - postman: + postman (StreamPoster): poster to stream credential with creder: recp: @@ -898,30 +898,29 @@ def sendCredential(hby, hab, reger, postman, creder, recp): else: sender = hab.pre - sendArtifacts(hby, reger, postman, creder, sender, recp) + sendArtifacts(hby, reger, postman, creder, recp) sources = reger.sources(hby.db, creder) for source, atc in sources: - sendArtifacts(hby, reger, postman, source, sender, recp) - postman.send(src=sender, dest=recp, topic="credential", serder=source, attachment=atc) + sendArtifacts(hby, reger, postman, source, recp) + postman.send(serder=source, attachment=atc) serder, prefixer, seqner, saider = reger.cloneCred(creder.said) atc = bytearray(coring.Counter(coring.CtrDex.SealSourceTriples, count=1).qb64b) atc.extend(prefixer.qb64b) atc.extend(seqner.qb64b) atc.extend(saider.qb64b) - postman.send(src=sender, dest=recp, topic="credential", serder=creder, attachment=atc) + postman.send(serder=creder, attachment=atc) -def sendArtifacts(hby, reger, postman, creder, sender, recp): +def sendArtifacts(hby, reger, postman, creder, recp): """ Stream credential artifacts to recipient using postman Parameters: hby: reger: - postman: + postman (StreamPoster): poster to stream credential with creder: - sender: recp: Returns: @@ -935,35 +934,35 @@ def sendArtifacts(hby, reger, postman, creder, sender, recp): for msg in hby.db.cloneDelegation(ikever): serder = coring.Serder(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) for msg in hby.db.clonePreIter(pre=issr): serder = coring.Serder(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) if isse != recp: ikever = hby.db.kevers[isse] for msg in hby.db.cloneDelegation(ikever): serder = coring.Serder(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) for msg in hby.db.clonePreIter(pre=isse): serder = coring.Serder(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) if regk is not None: for msg in reger.clonePreIter(pre=regk): serder = coring.Serder(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) for msg in reger.clonePreIter(pre=creder.said): serder = coring.Serder(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) def sendRegistry(hby, reger, postman, creder, sender, recp): @@ -977,14 +976,14 @@ def sendRegistry(hby, reger, postman, creder, sender, recp): for msg in hby.db.cloneDelegation(ikever): serder = coring.Serder(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) for msg in hby.db.clonePreIter(pre=issr): serder = coring.Serder(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) for msg in reger.clonePreIter(pre=regk): serder = coring.Serder(raw=msg) atc = msg[serder.size:] - postman.send(src=sender, dest=recp, topic="credential", serder=serder, attachment=atc) + postman.send(serder=serder, attachment=atc) diff --git a/src/keri/vdr/eventing.py b/src/keri/vdr/eventing.py index a2d1df6c4..481db5527 100644 --- a/src/keri/vdr/eventing.py +++ b/src/keri/vdr/eventing.py @@ -1824,7 +1824,7 @@ def processReplyRegistryTxnState(self, *, serder, saider, route, cigars=None, ts aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: - raise kering.UnverifiedReplyError(f"Unverified reply.") + raise kering.UnverifiedReplyError(f"Unverified registry txn state reply.") ldig = self.reger.getTel(key=snKey(pre=regk, sn=sn)) # retrieve dig of last event at sn. @@ -1970,7 +1970,7 @@ def processReplyCredentialTxnState(self, *, serder, saider, route, cigars=None, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: - raise kering.UnverifiedReplyError(f"Unverified reply.") + raise kering.UnverifiedReplyError(f"Unverified credential state reply.") ldig = self.reger.getTel(key=snKey(pre=vci, sn=sn)) # retrieve dig of last event at sn. diff --git a/src/keri/vdr/verifying.py b/src/keri/vdr/verifying.py index afcfcd405..655cbc80f 100644 --- a/src/keri/vdr/verifying.py +++ b/src/keri/vdr/verifying.py @@ -42,7 +42,7 @@ def __init__(self, hby, reger=None, creds=None, cues=None, expiry=36000000000): """ self.hby = hby - self.reger = reger if reger is not None else Reger(name=self.hby.name, temp=True) + self.reger = reger if reger is not None else Reger(name=self.hby.name, temp=self.hby.temp) self.creds = creds if creds is not None else decking.Deck() # subclass of deque self.cues = cues if cues is not None else decking.Deck() # subclass of deque self.CredentialExpiry = expiry diff --git a/tests/app/test_oobiing.py b/tests/app/test_oobiing.py index c901ccab3..c6ee54712 100644 --- a/tests/app/test_oobiing.py +++ b/tests/app/test_oobiing.py @@ -72,31 +72,6 @@ def test_oobi_share(mockHelpingNowUTC): b'p-2QZzIZJ94_9hIP') -def test_oobi_share_endpoint(): - with openMultiSig(prefix="test") as ((hby1, hab1), (hby2, hab2), (hby3, hab3)): - app = falcon.App() - oobiEnd = oobiing.OobiResource(hby=hby1) - app.add_route("/oobi/groups/{alias}/share", oobiEnd, suffix="share") - client = testing.TestClient(app) - - body = dict(oobis=[ - "http://127.0.0.1:3333/oobi", - "http://127.0.0.1:5555/oobi", - "http://127.0.0.1:7777/oobi" - ]) - raw = json.dumps(body).encode("utf-8") - - result = client.simulate_post(path="/oobi/groups/test_1/share", body=raw) - assert result.status == falcon.HTTP_400 - result = client.simulate_post(path="/oobi/groups/fake/share", body=raw) - assert result.status == falcon.HTTP_404 - result = client.simulate_post(path="/oobi/groups/test_group1/share", body=raw) - assert result.status == falcon.HTTP_200 - - # Assert that a message has been send to each participant for each OOBI - assert len(oobiEnd.postman.evts) == 6 - - def test_oobiery(): with habbing.openHby(name="oobi") as hby: hab = hby.makeHab(name="oobi") @@ -198,11 +173,11 @@ def test_authenticator(mockHelpingNowUTC): hby.db.woobi.pin(keys=(url,), val=obr) app = falcon.App() # falcon.App instances are callable WSGI apps - endDoers = oobiing.loadEnds(app, hby=hby) + oobiing.loadEnds(app, hby=hby) limit = 2.0 tock = 0.03125 - doers = endDoers + authn.doers + doers = authn.doers doist = doing.Doist(limit=limit, tock=tock) doist.do(doers=doers) diff --git a/tests/end/test_ending.py b/tests/end/test_ending.py index 204f92914..5ce366def 100644 --- a/tests/end/test_ending.py +++ b/tests/end/test_ending.py @@ -431,11 +431,8 @@ def test_get_oobi(): rep = client.simulate_get('/oobi', ) assert rep.status == falcon.HTTP_OK serder = coring.Serder(raw=rep.text.encode("utf-8")) - assert serder.ked['t'] == coring.Ilks.rpy - assert serder.ked['r'] == "/loc/scheme" - assert serder.ked['a']['eid'] == hab.pre - assert serder.ked['a']['scheme'] == kering.Schemes.http - assert serder.ked['a']['url'] == "http://127.0.0.1:5555" + assert serder.ked['t'] == coring.Ilks.icp + assert serder.ked['i'] == "EOaICQwhOy3wMwecjAuHQTbv_Cmuu1azTMnHi4QtUmEU" """Done Test""" diff --git a/tests/vc/test_protocoling.py b/tests/vc/test_protocoling.py index c19cbee29..30ea3eb06 100644 --- a/tests/vc/test_protocoling.py +++ b/tests/vc/test_protocoling.py @@ -46,7 +46,7 @@ def test_ipex(seeder, mockCoringRandomNonce, mockHelpingNowIso8601, mockHelpingN sidRgy.processEscrows() sidExc = exchanging.Exchanger(hby=sidHby, handlers=[]) - protocoling.loadHandlers(hby=sidHby, exc=sidExc, rgy=sidRgy, notifier=notifier) + protocoling.loadHandlers(hby=sidHby, exc=sidExc, notifier=notifier) schema = "EMQWEcCnVRk1hatTNyK3sIykYSrrFvafX3bHQ9Gkk1kC" @@ -104,7 +104,7 @@ def test_ipex(seeder, mockCoringRandomNonce, mockHelpingNowIso8601, mockHelpingN # Successfully parsed credential is now saved in database. assert sidVer.reger.saved.get(keys=(creder.said,)) is not None - ipexhan = protocoling.IpexHandler(resource="/ipex/apply", hby=sidHby, rgy=sidRgy, notifier=notifier) + ipexhan = protocoling.IpexHandler(resource="/ipex/apply", hby=sidHby, notifier=notifier) apply0, apply0atc = protocoling.ipexApplyExn(sidHab, message="Please give me a credential", schema=schema, recp=redPre, attrs={})