diff --git a/README.md b/README.md index cd33a09..b5ccca2 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/scripts/keri/cf/verifier-config-public.json b/scripts/keri/cf/verifier-config-public.json index cf4f988..d9f9dcb 100755 --- a/scripts/keri/cf/verifier-config-public.json +++ b/scripts/keri/cf/verifier-config-public.json @@ -17,6 +17,9 @@ "https://gleif-it.github.io/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao" ], "trustedLeis": [], + "revocationCheck": false, "allowedSchemas": [ + "ECR_SCHEMA", + "ECR_SCHEMA_PROD" ] } \ No newline at end of file diff --git a/scripts/keri/cf/verifier-config-rootsid.json b/scripts/keri/cf/verifier-config-rootsid.json deleted file mode 100755 index ad2b207..0000000 --- a/scripts/keri/cf/verifier-config-rootsid.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "dt": "2022-01-20T12:57:59.823350+00:00", - "iurls": [ - "https://witness-dev01.rootsid.cloud/oobi/BHI7yViNOGWd1X0aKMgxLm4dUgbQDYoCFSJM2U8Hb3cx/controller", - "https://witness-dev02.rootsid.cloud/oobi/BOUZ4v-vPMP5KyZQP-d_8B30UHI4KWgXczBgWcRJnnYd/controller", - "https://witness-dev03.rootsid.cloud/oobi/BNY3LWk2BzX8wXmkXuvpYRVSdfynanwKQwD80KOG00VH/controller" - ], - "durls": [ - "http://schemas.rootsid.cloud/oobi/EBNaNu-M9P5cgrnfl2Fvymy4E_jvxxyjb70PRtiANlJy", - "http://schemas.rootsid.cloud/oobi/EMhvwOlyEJ9kN4PrwCpr9Jsv7TxPhiYveZ0oP3lJzdEi", - "http://schemas.rootsid.cloud/oobi/EKA57bKBKxr_kN7iN5i7lMUxpMG-s19dRcmov1iDxz-E", - "http://schemas.rootsid.cloud/oobi/EEy9PkikFcANV1l7EHukCeXqrzT1hNZjGlUk7wuMO5jw", - "http://schemas.rootsid.cloud/oobi/ENPXp1vQzRF6JwIuS-mp2U8Uf1MoADoP_GqQ62VsDZWY", - "http://schemas.rootsid.cloud/oobi/EH6ekLjSr8V32WyFbGe1zXjTzFs9PkTYmupJ9H65O14g", - "http://schemas.rootsid.cloud/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao" - ], - "trustedLeis": [], - "allowedSchemas": [ - "ECR_SCHEMA", - "ECR_SCHEMA_PROD" - ] -} \ No newline at end of file diff --git a/scripts/keri/cf/verifier-config-test.json b/scripts/keri/cf/verifier-config-test.json index f75f834..3da58f1 100755 --- a/scripts/keri/cf/verifier-config-test.json +++ b/scripts/keri/cf/verifier-config-test.json @@ -15,6 +15,7 @@ "http://127.0.0.1:7723/oobi/EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao" ], "trustedLeis": [], + "revocationCheck": true, "allowedSchemas": [ "ECR_SCHEMA", "ECR_SCHEMA_PROD" diff --git a/src/verifier/app/cli/commands/server/start.py b/src/verifier/app/cli/commands/server/start.py index 107d91f..058067e 100644 --- a/src/verifier/app/cli/commands/server/start.py +++ b/src/verifier/app/cli/commands/server/start.py @@ -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) @@ -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 diff --git a/src/verifier/core/authorizing.py b/src/verifier/core/authorizing.py index 67aecff..d961942 100644 --- a/src/verifier/core/authorizing.py +++ b/src/verifier/core/authorizing.py @@ -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 @@ -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}") diff --git a/src/verifier/core/basing.py b/src/verifier/core/basing.py index 0908409..2b3aab0 100644 --- a/src/verifier/core/basing.py +++ b/src/verifier/core/basing.py @@ -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" diff --git a/src/verifier/core/observing.py b/src/verifier/core/observing.py index c6878b5..83c1223 100644 --- a/src/verifier/core/observing.py +++ b/src/verifier/core/observing.py @@ -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 @@ -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: @@ -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) diff --git a/src/verifier/core/resolve_env.py b/src/verifier/core/resolve_env.py index 6c8d017..b0a2161 100644 --- a/src/verifier/core/resolve_env.py +++ b/src/verifier/core/resolve_env.py @@ -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 diff --git a/src/verifier/core/utils.py b/src/verifier/core/utils.py index aaf2d3b..f47aa85 100644 --- a/src/verifier/core/utils.py +++ b/src/verifier/core/utils.py @@ -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}") diff --git a/src/verifier/core/verifying.py b/src/verifier/core/verifying.py index 95d488d..a9d49d9 100644 --- a/src/verifier/core/verifying.py +++ b/src/verifier/core/verifying.py @@ -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 ( @@ -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