Guides

Check whether a credential was revoked

A credential that verifies can still have been revoked. SD-JWT VC leaves the how to a separate mechanism, the Token Status List: the issuer publishes one signed, compressed bit array for many credentials, each credential carries an index into it, and a relying party fetches the list once and reads a couple of bits. k2gl/token-status-list implements both ends.

Install

composer require k2gl/sd-jwt-vc k2gl/token-status-list

Relying party: verify, then check

Verify the presentation first — the spec is explicit that an expired credential with a VALID status is still expired — then hand its status claim to the resolver:

use K2gl\Dsse\PublicKey;
use K2gl\SdJwt\KeyBinding;
use K2gl\SdJwtVc\SdJwtVcVerifier;
use K2gl\TokenStatusList\StatusListResolver;
use K2gl\TokenStatusList\StatusReference;

$credential = new SdJwtVcVerifier()->verifyPresentation(
    $presentation,
    PublicKey::fromJwk($issuerJwk),
    KeyBinding::required(audience: 'https://you.example', nonce: $nonce),
);

$resolver = new StatusListResolver(
    httpClient: $psr18Client,
    requestFactory: $psr17RequestFactory,
    key: PublicKey::fromJwk($statusIssuerJwk),
    cache: $psr16Cache, // optional: reuses the list for its ttl, bounded by exp
);

$status = $resolver->check(StatusReference::fromClaim($credential->status()));

$status->isValid();      // 0x00
$status->isInvalid();    // 0x01 — revoked
$status->isSuspended();  // 0x02

check() is the whole of the spec’s validation section: GET the URI with Accept: application/statuslist+jwt, verify the Status List Token (typ, allowed algorithms, signature, sub equal to the referenced URI, iat/exp), inflate the list and read the index — an index beyond the list is a rejection, not a VALID. With a PSR-16 cache the token is reused for ttl seconds and verified again on every read.

Issuer: publish a list

use K2gl\Dsse\EcdsaP256Signer;
use K2gl\TokenStatusList\Status;
use K2gl\TokenStatusList\StatusList;
use K2gl\TokenStatusList\StatusListTokenIssuer;
use K2gl\TokenStatusList\StatusReference;

$list = StatusList::create(size: 100_000, bits: 1);
$list->set(42, Status::invalid());

$compact = new StatusListTokenIssuer(EcdsaP256Signer::fromPem($privateKeyPem))->issue(
    uri: 'https://example.com/statuslists/1',
    statusList: $list,
    expiresAt: time() + 7 * 86400,
    ttl: 43200,
);
// serve $compact at that URI as application/statuslist+jwt

// and in each credential you issue:
$claims['status'] = new StatusReference('https://example.com/statuslists/1', index: 42)->toClaim();

Use bits: 2 when you need SUSPENDED; 4 or 8 bits for application-specific statuses. The list is packed from the least significant bit, and the encoding reproduces the draft’s test vectors byte for byte.

Where it fits

k2gl/sd-jwt-vc hands you the claim; the draft requires the Status List Token of an SD-JWT VC to be a JWT, which is the format this package implements (CWT is out of scope). Context: the digital identity overview.