-
Notifications
You must be signed in to change notification settings - Fork 55
/
verifying.py
361 lines (282 loc) · 14.1 KB
/
verifying.py
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
# -*- encoding: utf-8 -*-
"""
KERI
keri.vdr.verifying module
VC verifier support
"""
import datetime
import logging
from typing import Type
from hio.help import decking
from .. import help, kering
from ..core import parsing, coring, scheming
from ..help import helping
from ..vdr import eventing
from ..vdr.viring import Reger
logger = help.ogler.getLogger()
class Verifier:
"""
Verifier class accepts and validates TEL events.
"""
TimeoutPSE = 3600 # seconds to timeout partially signed credential escrow
TimeoutMRE = 3600 # seconds to timeout missing registry escrows
TimeoutMRI = 3600 # seconds to timeout missing issuer escrows
TimeoutBCE = 3600 # seconds to timeout missing issuer escrows
def __init__(self, hby, reger=None, creds=None, cues=None, expiry=36000000000):
"""
Initialize Verifier instance
Parameters:
hby (Habery): for this verifier's context
reger (Reger): database instance
creds (decking.Deck): inbound credentials for handler
cues (decking.Deck): outbound cue messages from handler
"""
self.hby = hby
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
self.inited = False
self.tvy = None
self.psr = None
self.resolver = None
if self.hby.inited:
self.setup()
def setup(self):
""" Delayed initialization of instance by createing .tvy and .psr.
Should not be called until .hab is initialized
"""
self.tvy = eventing.Tevery(reger=self.reger, db=self.hby.db, local=False)
self.psr = parsing.Parser(framed=True, kvy=self.hby.kvy, tvy=self.tvy)
self.resolver = scheming.CacheResolver(db=self.hby.db)
self.inited = True
@property
def tevers(self):
""" Returns .db.tevers
"""
return self.reger.tevers
def processMessages(self, creds=None):
""" Process message dicts in msgs or if msgs is None in .msgs
Parameters:
creds (decking.Deck): each entry is dict that matches call signature of
.processCredential
"""
if creds is None:
creds = self.creds
while creds:
self.processCredential(**creds.pull())
def processCredential(self, creder, prefixer, seqner, saider):
""" Credential data and signature(s) verification
Verify the data of the credential against the schema, the SAID of the credential and
the CESR Proof on the credential and if valid, store the credential
Parameters:
creder (Creder): that contains the credential to process
prefixer (Prefixer): prefix of source anchoring KEL or TEL event
seqner (Seqner): sequence number of source anchoring KEL or TEL event
saider (Saider): SAID of source anchoring KEL or TEL event
"""
regk = creder.regi
vcid = creder.said
schema = creder.schema
prov = creder.edge if creder.edge is not None else {}
if regk not in self.tevers: # registry event not found yet
if self.escrowMRE(creder, prefixer, seqner, saider):
self.cues.append(dict(kin="telquery", q=dict(ri=regk, i=vcid, issr=creder.issuer)))
raise kering.MissingRegistryError("registry identifier {} not in Tevers".format(regk))
state = self.tevers[regk].vcState(vcid)
if state is None: # credential issuance event not found yet
if self.escrowMRE(creder, prefixer, seqner, saider):
self.cues.append(dict(kin="telquery", q=dict(ri=regk, i=vcid)))
raise kering.MissingRegistryError("credential identifier {} not in Tevers".format(vcid))
dtnow = helping.nowUTC()
dte = helping.fromIso8601(state.dt)
if (dtnow - dte) > datetime.timedelta(seconds=self.CredentialExpiry):
if self.escrowMRE(creder, prefixer, seqner, saider):
self.cues.append(dict(kin="telquery", q=dict(ri=regk, i=vcid)))
raise kering.MissingRegistryError("credential identifier {} is out of date".format(vcid))
elif state.et in (coring.Ilks.rev, coring.Ilks.brv): # no escrow, credential has been revoked
logger.error("credential {} in registrying is not in issued state".format(vcid, regk))
# Log this and continue instead of the previous exception so we save a revoked credential.
# raise kering.InvalidCredentialStateError("..."))
# Verify the credential against the schema
scraw = self.resolver.resolve(schema)
if not scraw:
if self.escrowMSE(creder, prefixer, seqner, saider):
self.cues.append(dict(kin="query", q=dict(r="schema", said=schema)))
raise kering.MissingSchemaError("schema {} not in cache".format(schema))
schemer = scheming.Schemer(raw=scraw)
try:
schemer.verify(creder.raw)
except kering.ValidationError as ex:
print("Credential {} is not valid against schema {}: {}"
.format(creder.said, schema, ex))
raise kering.FailedSchemaValidationError("Credential {} is not valid against schema {}: {}"
.format(creder.said, schema, ex))
if isinstance(prov, list):
edges = prov
elif isinstance(prov, dict):
edges = [prov]
else:
print(f"Invalid type for edges: {prov}")
raise kering.ValidationError(f"invalid type for edges: {prov}")
for edge in edges:
for label, node in edge.items():
if label in ('d', 'o'): # SAID or Operator of this edge block
continue
nodeSaid = node["n"]
op = node['o'] if 'o' in node else None
state = self.verifyChain(nodeSaid, op, creder.issuer)
if state is None:
self.escrowMCE(creder, prefixer, seqner, saider)
self.cues.append(dict(kin="proof", said=nodeSaid))
raise kering.MissingChainError("Failure to verify credential {} chain {}({})"
.format(creder.said, label, nodeSaid))
dtnow = helping.nowUTC()
dte = helping.fromIso8601(state.dt)
if (dtnow - dte) > datetime.timedelta(seconds=self.CredentialExpiry):
self.escrowMCE(creder, prefixer, seqner, saider)
self.cues.append(dict(kin="query", q=dict(r="tels", pre=nodeSaid)))
raise kering.MissingChainError("Failure to verify credential {} chain {}({})"
.format(creder.said, label, nodeSaid))
elif state.et in (coring.Ilks.rev, coring.Ilks.brv):
raise kering.RevokedChainError("Failure to verify credential {} chain {}({})"
.format(creder.said, label, nodeSaid))
else: # VcStatus == VcStates.Issued
logger.info("Successfully validated credential chain {} for credential {}"
.format(label, creder.said))
self.saveCredential(creder, prefixer, seqner, saider)
self.cues.append(dict(kin="saved", creder=creder))
def escrowMRE(self, creder, prefixer, seqner, saider):
""" Missing Registry Escrow
Parameters:
creder (Creder): that contains the credential to process
prefixer (Prefixer): prefix (AID or TEL) of event anchoring credential
seqner (Seqner): sequence number of event anchoring credential
saider (Diger) digest of anchoring event for credential
"""
key = creder.said
self.reger.logCred(creder, prefixer, seqner, saider)
return self.reger.mre.put(keys=key, val=coring.Dater())
def escrowMCE(self, creder, prefixer, seqner, saider):
""" Missing Chain Escrow
Parameters:
creder (Creder): that contains the credential to process
prefixer (Prefixer): prefix (AID or TEL) of event anchoring credential
seqner (Seqner): sequence number of event anchoring credential
saider (Diger) digest of anchoring event for credential
"""
key = creder.said
self.reger.logCred(creder, prefixer, seqner, saider)
return self.reger.mce.put(keys=key, val=coring.Dater())
def escrowMSE(self, creder, prefixer, seqner, saider):
"""
Missing Credential Schema Escrow
Parameters:
creder (Creder): that contains the credential to process
prefixer (Prefixer): prefix (AID or TEL) of event anchoring credential
seqner (Seqner): sequence number of event anchoring credential
saider (Diger) digest of anchoring event for credential
"""
key = creder.said
self.reger.logCred(creder, prefixer, seqner, saider)
return self.reger.mse.put(keys=key, val=coring.Dater())
def processEscrows(self):
""" Process all escrows once each
"""
self._processEscrow(self.reger.mce, self.TimeoutMRI, kering.MissingChainError)
self._processEscrow(self.reger.mse, self.TimeoutMRI, kering.MissingSchemaError)
self._processEscrow(self.reger.mre, self.TimeoutMRE, kering.MissingRegistryError)
def _processEscrow(self, db, timeout, etype: Type[Exception]):
""" Generic credential escrow processing
Parameters:
db (LMDBer): escrow database table to process
timeout (float): escrow specific message timeout
etype (TypeOf(Exception)): exception class to catch and ignore
"""
for (said,), dater in db.getItemIter():
creder, prefixer, seqner, saider = self.reger.cloneCred(said)
try:
dtnow = helping.nowUTC()
dte = helping.fromIso8601(dater.dts)
if (dtnow - dte) > datetime.timedelta(seconds=timeout):
# escrow stale so raise ValidationError which unescrows below
logger.info("Verifier unescrow error: Stale event escrow "
" at said = %s", said)
raise kering.ValidationError("Stale event escrow "
"at said = {}.".format(said))
self.processCredential(creder, prefixer, seqner, saider)
except etype as ex:
if logger.isEnabledFor(logging.DEBUG):
logger.exception("Verifiery unescrow failed: %s", ex.args[0])
else:
logger.error("Verifier unescrow failed: %s", ex.args[0])
except Exception as ex: # log diagnostics errors etc
# error other than missing sigs so remove from PA escrow
db.rem(said)
if logger.isEnabledFor(logging.DEBUG):
logger.exception("Verifier unescrowed: %s", ex.args[0])
else:
logger.error("Verifier unescrowed: %s", ex.args[0])
else:
db.rem(said)
logger.info("Verifier unescrow succeeded in valid group op: "
"creder=%s", creder.said)
logger.debug(f"event=\n{creder.pretty()}\n")
def saveCredential(self, creder, prefixer, seqner, saider):
""" Write the credential and associated indicies to the database
Parameters:
creder (Creder): that contains the credential to process
prefixer (Prefixer): prefix (AID or TEL) of event anchoring credential
seqner (Seqner): sequence number of event anchoring credential
saider (Diger) digest of anchoring event for credential
"""
self.reger.logCred(creder, prefixer, seqner, saider)
schema = creder.schema.encode("utf-8")
issuer = creder.issuer.encode("utf-8")
# Look up indicies
saider = coring.Saider(qb64=creder.said)
self.reger.saved.pin(keys=saider.qb64b, val=saider)
self.reger.issus.add(keys=issuer, val=saider)
self.reger.schms.add(keys=schema, val=saider)
if not isinstance(creder.attrib, str) and 'i' in creder.attrib:
subject = creder.attrib["i"].encode("utf-8")
self.reger.subjs.add(keys=subject, val=saider)
def query(self, pre, regk, vcid, *, dt=None, dta=None, dtb=None, **kwa):
""" Returns query message for querying registry
"""
serder = eventing.query(regk=regk, vcid=vcid, dt=dt, dta=dta,
dtb=dtb, **kwa)
hab = self.hby.habs[pre]
return hab.endorse(serder, last=True)
def verifyChain(self, nodeSaid, op, issuer):
""" Verifies the node credential at the end of an edge
Parameters:
nodeSaid: (str): qb64 SAID of node credential
op(str): edge operator
issuer (str) qb64 AID of issuer
Returns:
Serder: transaction event state notification message
"""
said = self.reger.saved.get(keys=nodeSaid)
if said is None:
return None
creder = self.reger.creds.get(keys=nodeSaid)
if op not in ['I2I', 'DI2I', 'NI2I']:
op = 'I2I' if 'i' in creder.attrib else 'NI2I'
if op != 'NI2I':
if 'i' not in creder.attrib:
return None
iss = self.reger.subjs.get(keys=creder.attrib['i'])
if iss is None:
return None
if op == 'I2I' and issuer != creder.attrib['i']:
return None
if op == "DI2I":
raise NotImplementedError()
if creder.regi not in self.tevers:
return None
tever = self.tevers[creder.regi]
state = tever.vcState(nodeSaid)
if state is None:
return None
return state