-
Notifications
You must be signed in to change notification settings - Fork 0
Security Model
openvc verifies Verifiable Credentials, handles signing keys, and dereferences issuer-named URLs over the network. This page states what it defends, against whom, and how — the reference an auditor (or an integrator) starts from. The per-control hardening notes are in SECURITY.md; the code-cited auditor annex (per-parser tables, the fail-closed invariants catalog, the residual-risk register, and the fuzz-coverage / adversarial-review history) is the external-audit pack.
-
The verification decision. The accept/reject output of
verify_credential(andverify_ebsi_badge) is the asset everything else protects: a wrong accept (a forged/tampered/expired/revoked credential accepted) is the primary harm. -
Signing keys. Private key material. openvc never requires it in-process on the
signing path — signing goes through the
SigningKeyprotocol, so an HSM/Vault/KMS backend keeps keys out of the process (see Keys & HSM backends). -
Trust anchors. The roots a verifier trusts: X.509
x5ctrust anchors, the EBSI RootTAO, the DID documents a resolver returns. Compromise of an anchor is out of scope (it is the operator's root of trust) but openvc must not widen it.
Untrusted input crosses into openvc at:
- The credential itself — fully attacker-controlled bytes (a JWS/SD-JWT string or a JSON document). Every field is untrusted until the signature verifies.
-
Network dereferences —
did:web,/.well-known/jwt-vc-issuer, status-list andcredentialSchemaURLs. The issuer names these URLs and (for status/schema) controls the bytes returned. All of these are attacker-influenced. -
The
SigningKey/ key-agreement backend — an out-of-process boundary (HSM, Vault, KMS). openvc trusts it to sign/decrypt but not to hold key material for it. -
Injected resolvers —
resolver,resolve_status_list*,resolve_credential_schema,*_fetch. openvc's guarantees hold only for what these return; a custom resolver that skips verification or the SSRF guard opts out of the corresponding control (hence the blessed defaults inopenvc.resolvers).
| Attacker capability | Threat | Control |
|---|---|---|
| Present a forged / tampered credential | Wrong accept | Signature verification through the matching suite; the {ES256, ES384, EdDSA, Ed25519} allow-list runs before any crypto (rejects alg:none, RS*, HS* — alg-confusion defence); unknown JWS crit extensions are rejected on every JOSE lane (RFC 7515 §4.1.11, parity with the COSE and JWE paths); JWS is R‖S, never DER |
| Name an arbitrary issuer but sign with own key | Impersonation |
Issuer binding — a Data Integrity proof's verificationMethod must be controlled by the credential's issuer DID; VC-JWT reconciles the JWT envelope with the embedded credential; x5c binds the leaf SAN to iss
|
| Serve a malicious document at a fetched URL | SSRF (reach internal hosts / cloud metadata) |
openvc.fetch: https-only, blocks private/loopback/link-local/reserved/multicast IPs, refuses redirects, pins the connection to the validated IP (closes DNS-rebinding). Status/schema fetches use the same guard via the blessed openvc.resolvers defaults |
| Ship a tiny highly-compressible status list | Decompression bomb (OOM DoS) | Status decode caps the decompressed output at 16 MiB and fails closed (StatusListError), reading incrementally so a bomb is never materialised |
Point credentialSchema at a schema with a catastrophic pattern
|
ReDoS (CPU DoS) | Schema validation is opt-in; remote $ref is off (no SSRF via $ref). Residual pattern-ReDoS is a documented limitation (mitigation tracked) — point the schema resolver at trusted hosts |
| Swap / replay a status list or presentation | Stale-status / replay accept | Status-list token sub must equal the fetched URI (anti-swap); presentations bind aud + one-time nonce/challenge; a fetched status list is verified before it is trusted |
| Backdate / post-date validity | Expired/not-yet-valid accept | Temporal checks on validFrom/validUntil (+ VCDM 1.1 aliases) and proof expires; a present-but-unparseable timestamp fails closed, never silently skipped |
| Withhold a selectively-disclosed status/schema | Skip a fail-closed gate | Documented caveat: an issuer that needs status/schema enforced must make the pointer non-selectively-disclosable (mandatory for ecdsa-sd, outside disclosable for SD-JWT) |
| MITM a fetch | Tamper in transit | TLS with certificate validation and SNI on the pinned connection |
- Fail closed. Ambiguity, an unresolvable key, a malformed timestamp, an unrecognised status/schema shape, or a missing opted-in resolver all reject rather than accept.
- Least authority on the network. Every dereference is https-only and SSRF-guarded by default; nothing is fetched from an allow-list openvc did not vet.
- HSM-friendly. Key material need never enter the process.
openvc is silent and dependency-free by default: it attaches a NullHandler to the
openvc logger and uses a no-op span hook. Two opt-ins let an operator see why a
verification accepted or rejected a credential, without pulling in a tracing dependency.
import logging
from openvc.observability import set_span_hook
# 1. Structured logs: the "openvc" logger is silent until you attach a handler.
logging.getLogger("openvc").setLevel(logging.DEBUG)
logging.basicConfig()
# 2. Spans: bridge to your tracer (OpenTelemetry, ...); a no-op until set.
set_span_hook(lambda name, attrs: tracer.start_as_current_span(name, attributes=attrs))Neither logs nor spans carry credential contents or private key material — only format, DIDs and outcomes — so enabling them does not widen the attack surface. See the Observability API.
- Compromise of a trusted anchor, of the host, or of the
SigningKeybackend. - Availability of remote issuers / status lists (openvc bounds its own resource use — response size, decompressed size, recursion — but cannot guarantee a third party is reachable).
- Anything an injected resolver does after openvc hands it a URL, if the caller supplies a custom one instead of the guarded default.
- Side-channels in the underlying
cryptography/pyjwtprimitives.
📖 Published automatically from the wiki/ directory of the main repository — edits made directly on the wiki are overwritten by the next sync. To change a page, open a PR against wiki/. Every python block on these pages is executed by CI. · API reference · LGPL-3.0-or-later
Start
Proof formats
Verifying in practice
- Presentations & OpenID4VP
- Resolving issuer keys
- Relying-party certificates
- Credential schemas
- Status lists
- Trust anchors: EU TL & EBSI
- Async verification
Walkthroughs
Operating
Reference