Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ You can customize the service behavior using these configuration options:
- **`iurls`**: - OOBI URLs
- **`durls`**: - Schema OOBI URLs
- **`trustedLeis`**: A list of trusted LE identifiers.
- **`revocationCheck`**: Boolean. True means enable credential revocation checks
- **`allowedSchemas`**: A list of schemas allowed for authorization.

### Default Configuration
Expand Down
3 changes: 3 additions & 0 deletions scripts/keri/cf/verifier-config-public.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"https://gleif-it.github.io/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao"
],
"trustedLeis": [],
"revocationCheck": false,
"allowedSchemas": [
"ECR_SCHEMA",
"ECR_SCHEMA_PROD"
]
}
22 changes: 0 additions & 22 deletions scripts/keri/cf/verifier-config-rootsid.json

This file was deleted.

1 change: 1 addition & 0 deletions scripts/keri/cf/verifier-config-test.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao"
],
"trustedLeis": [],
"revocationCheck": true,
"allowedSchemas": [
"ECR_SCHEMA",
"ECR_SCHEMA_PROD"
Expand Down
14 changes: 9 additions & 5 deletions src/verifier/app/cli/commands/server/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,16 @@ def launch(args):
export VERIFIER_MODE=test|production
"""
verifier_mode = os.environ.get("VERIFIER_MODE", "test")
trusted_leis = config.get("trustedLeis", [])
verify_rot = os.getenv("VERIFY_ROOT_OF_TRUST", "True").lower() in ("true", "1")
trusted_leis = config.get("trustedLeis", [])
revocation_check = config.get("revocationCheck", False)

ve_init_params = {
"configuration": cf,
"mode": verifier_mode,
"trustedLeis": trusted_leis if trusted_leis else [],
"verifyRootOfTrust": verify_rot,
"revocationCheck": revocation_check
}

print("ALLOWED", allowed_schemas)
Expand Down Expand Up @@ -187,10 +189,12 @@ def launch(args):
reportDoers = reporting.setup(app=app, hby=hby, vdb=vdb)
authDoers = authorizing.setup(hby, vdb=vdb, reger=reger)

# Initialize the credential revocation checker doer
revocation_checker = CredentialRevocationChecker(hby=hby, vdb=vdb, reger=reger)

doers = obl.doers + authDoers + reportDoers + [hbyDoer, httpServerDoer, revocation_checker]
# Initialize the credential revocation checker doer if revocationCheck is True
if ve.revocationCheck is True:
revocation_checker = CredentialRevocationChecker(hby=hby, vdb=vdb, reger=reger)
doers = obl.doers + authDoers + reportDoers + [hbyDoer, httpServerDoer, revocation_checker]
else:
doers = obl.doers + authDoers + reportDoers + [hbyDoer, httpServerDoer]

print(f"vLEI Verification Service running and listening on: {httpPort}")
return doers
4 changes: 2 additions & 2 deletions src/verifier/core/authorizing.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from keri.help import helping

from verifier.core.basing import Account, CredProcessState, AUTH_REVOKED, AUTH_PENDING, AUTH_SUCCESS, AUTH_EXPIRE, \
AUTH_FAIL, CRED_CRYPT_VALID, AID_CRYPT_VALID, AidProcessState, AID_AUTH_SUCCESS
AUTH_FAIL, CRED_CRYPT_VALID, AID_CRYPT_VALID, AidProcessState, AID_AUTH_SUCCESS, OBSERVER_REVOCATION_CHECK_FAILED
from verifier.core.constants import Schema, EBA_DATA_SUBMITTER_ROLE
from verifier.core.resolve_env import VerifierEnvironment
from verifier.core.utils import add_state_to_state_history
Expand Down Expand Up @@ -112,7 +112,7 @@ def processPresentations(self):
self.vdb.iss.rem(keys=(aid,))
# We keep revoked credentials in the DB because their auth should never expire and the state
# must always be AUTH_REVOKED to avoid logging in again with the older version of the credential
elif state.state != AUTH_REVOKED and state.state != AUTH_EXPIRE and age > datetime.timedelta(
elif state.state != AUTH_REVOKED and state.state != OBSERVER_REVOCATION_CHECK_FAILED and state.state != AUTH_EXPIRE and age > datetime.timedelta(
seconds=self.TimeoutAuth):
cred_state = CredProcessState(aid=cur_state.aid, said=state.said, state=AUTH_EXPIRE,
info=f"Cred state exceeded {self.TimeoutAuth}")
Expand Down
1 change: 1 addition & 0 deletions src/verifier/core/basing.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
CRED_CRYPT_INVALID = "Credential cryptographically invalid"
CRED_CRYPT_VALID = "Credential cryptographically valid"
CRED_AGE_OFF = "Credential presentation has aged off"
OBSERVER_REVOCATION_CHECK_FAILED = "Credential revocation check failed"
AUTH_REVOKED = "Credential revoked"
AUTH_PENDING = "Credential pending authorization"
AUTH_SUCCESS = "Credential authorized"
Expand Down
51 changes: 31 additions & 20 deletions src/verifier/core/observing.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import requests

from hio.base import doing
from verifier.core.basing import CredProcessState, AUTH_REVOKED
from verifier.core.basing import CredProcessState, AUTH_REVOKED, OBSERVER_REVOCATION_CHECK_FAILED
from verifier.core.resolve_env import VerifierEnvironment
from verifier.core.utils import add_state_to_state_history, process_revocations_from_event_log, parse_cesr
from keri.vdr import verifying, eventing
Expand Down Expand Up @@ -34,14 +34,29 @@ def recur(self, tyme):
self._check_revocations()
self.lastCheck = tyme
return False

def _mark_as_revocation_check_failed(self, said: str, reason: str):
cur_state: CredProcessState = self.vdb.iss.get(keys=(said,))
aid = cur_state.aid
if aid:
cur_state: CredProcessState = self.vdb.iss.get(keys=(aid,))
rev_state = CredProcessState(aid=aid, said=said, info=reason, state=OBSERVER_REVOCATION_CHECK_FAILED,
witness_url=cur_state.witness_url)
self.vdb.iss.pin(keys=(aid,), val=rev_state)
self.vdb.iss.pin(keys=(said,), val=rev_state)
self.vdb.accts.rem(keys=(aid,))
add_state_to_state_history(self.vdb, aid, rev_state)

def _check_revocations(self):
"""Check revocation status for all credentials in the database."""
env = VerifierEnvironment.resolve_env()
if env.revocationCheck is False:
return

# Get all credentials from the database
for (aid,), state in self.vdb.iss.getItemIter():
if state.state == AUTH_REVOKED:
if state.state == AUTH_REVOKED or state.state == OBSERVER_REVOCATION_CHECK_FAILED:
continue
env = VerifierEnvironment.resolve_env()

# If witness URL is provided, check with the witness
if state.witness_url:
Expand All @@ -53,25 +68,21 @@ def _check_revocations(self):
if parsed_cesr:
process_revocations_from_event_log(self.vdb, state.said, parsed_cesr)
else:
print(f"No CESR found for credential {state.said}")
# Remove credential and associated data from database in production mode
# This ensures we don't keep invalid credentials
if env.mode == "production":
self.vdb.iss.rem(keys=(aid,))
self.vdb.iss.rem(keys=(state.said,))
self.vdb.accts.rem(keys=(aid,))
reason = f"No valid CESR found for credential {state.said}"
print(reason)
self._mark_as_revocation_check_failed(state.said, reason)
continue
except requests.exceptions.ConnectionError as e:
reason = f"Error checking witness for credential {state.said}: Witness {state.witness_url} is unavailable"
print(reason)
self._mark_as_revocation_check_failed(state.said, reason)
except Exception as e:
print(f"Error checking witness for credential {state.said}: {e}")
reason = f"Error checking witness for credential {state.said}: unexpected error"
print(reason)
self._mark_as_revocation_check_failed(state.said, reason)

else:
print(f"No witness URL provided for credential {state.said}")
# Remove credential and associated data from database in production mode
# This ensures we don't keep invalid credentials
if env.mode == "production":
self.vdb.iss.rem(keys=(aid,))
self.vdb.iss.rem(keys=(state.said,))
self.vdb.accts.rem(keys=(aid,))



reason = f"No witness URL provided for credential {state.said}"
self._mark_as_revocation_check_failed(state.said, reason)

1 change: 1 addition & 0 deletions src/verifier/core/resolve_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class VerifierEnvironment:
trustedLeis: List[str] = field(default_factory=list)
mode: str = "production"
verifyRootOfTrust: bool = True
revocationCheck: bool = True
authAllowedSchemas: List = field(default_factory=lambda: [])

_instance: "VerifierEnvironment" = None
Expand Down
17 changes: 9 additions & 8 deletions src/verifier/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,16 +379,17 @@ def parse_cesr(cesr: str) -> Optional[List[Dict[str, Any]]]:
Optional[List[Dict[str, Any]]]: A list of dictionaries containing 'signature' and 'json' keys,
or None if the CESR string is invalid
"""
# Extract signatures using regex
signature_regex = r'(?<=})(-.*?)(?={|$)'
signatures = re.findall(signature_regex, cesr)

# Replace signatures with commas to create a valid JSON array
json_string = f"[{re.sub(signature_regex, ',', cesr).strip()}]"
# Remove trailing comma if present
json_string = re.sub(r',(?=[^,]*$)', '', json_string)


try:
# Extract signatures using regex
signature_regex = r'(?<=})(-.*?)(?={|$)'
signatures = re.findall(signature_regex, cesr)

# Replace signatures with commas to create a valid JSON array
json_string = f"[{re.sub(signature_regex, ',', cesr).strip()}]"
# Remove trailing comma if present
json_string = re.sub(r',(?=[^,]*$)', '', json_string)
parsed_json = json.loads(json_string)
except json.JSONDecodeError as error:
print(f"Invalid JSON: {error}")
Expand Down
4 changes: 3 additions & 1 deletion src/verifier/core/verifying.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from keri import kering
from keri.core import coring, parsing, Siger
from keri.vdr import verifying, eventing
from pygments.lexer import default

from verifier.core.authorizing import AUTH_EXPIRE
from verifier.core.basing import (
Expand Down Expand Up @@ -343,7 +344,8 @@ def on_put(self, req, rep, said):
return

ims = req.bounded_stream.read()
witness_url = req.get_param("witness_url", default="http://localhost:5642")
# Should the `witness_url` parameter be mandatory?
witness_url = req.get_param("witness_url", default=None)

if len(self.vry.cues) > 0:
rep.status = falcon.HTTP_SERVICE_UNAVAILABLE
Expand Down
Loading