light-mode-image
Learn
Guides

How to implement mDocs revocation status checks in your holding application

Overview

This guide demonstrates how to implement revocation status checks for mDocs in your wallet applications. By implementing status checks, your wallet can verify whether credentials have been revoked, suspended, or remain valid before displaying or presenting them.

MATTR's implementation of mDocs revocation is based on the IETF Token Status List draft. A revocable mDoc includes a reference to a status list which is managed by the issuer. The list contains the revocation status of multiple credentials, and each credential references the index of its status within a specific status list.

Status lists are automatically created and managed by the issuer's MATTR VII tenant when issuing revocable mDocs. They are publicly available and can be consumed by holder applications to check the status of claimed mDocs.

For detailed information about how mDocs revocation works, including status list structure, tokens, and signing, see the Revocation documentation.

Prerequisites

This guide builds on knowledge from the Credential Claiming tutorial. It is recommended to complete that tutorial first, then return here to learn how to implement status checks.

Understanding revocation status

Status values

Revocable mDocs can have one of the following status values:

  • Valid: The mDoc is valid and can be presented.
  • Invalid: The mDoc is permanently revoked.
  • Suspended: The mDoc is temporarily revoked.
  • Unknown: The status cannot be determined, typically because the status list has expired or is unavailable (e.g. holder is offline).

How status information is stored

When a revocable mDoc is issued, it includes a status object in its MSO payload that references a status list:

mDoc status reference
// Rest of mDoc payload

"status": {
    "status_list": {
        "uri": "https://learn.vii.au01.mattr.global/v2/credentials/mobile/status-lists/f331c9be-f526-4577-bbac-ae93d6228f7a/token",
        "idx": 0
    }
}
  • status_list: References the status list that holds the status information for this mDoc.
  • uri: The publicly available endpoint where the status list token can be retrieved.
  • idx: The index of this mDoc's status within the referenced status list.

When a verifier or holder retrieves a status list, the issuer cannot tell what specific mDoc they are checking the status for. This preserves holder privacy - the issuer does not know how often or to whom an mDoc is being presented.

Status list caching and updates

When retrieving a status list, the response is a signed status list token (a CBOR Web Token) that includes:

  • iat: Timestamp when the status list token was signed.
  • exp: Expiry timestamp.
  • ttl: Recommended duration in seconds before fetching a new token.
  • status_list: The compressed status list containing the status of all mDocs included in this list.

Retrieving a status list for every credential operation would create performance issues and make offline presentation impossible. To address this, MATTR uses a caching mechanism based on the ttl and exp values:

  • After retrieving a status list, the SDK will not fetch it again until the ttl has passed, as there are unlikely to be any changes. This optimizes performance and reduces unnecessary network requests.
  • If the SDK fails to retrieve an updated status list after the TTL (for example, because the device is offline), it can continue using the cached status until the status list token expiry date (exp).
  • If the expiry date passes without a successful update, the credential status can no longer be trusted and it is changed to Unknown. It is then up to the application to decide how to handle credentials with Unknown status.

When TTL and EXP changes take effect

If the issuer updates the status list TTL and/or EXP settings, the new values apply to the entire status list the next time it is generated and signed.

When the holder SDK next retrieves that status list, it will receive the updated token with the new TTL and EXP values. Until then, the SDK continues to use the previously cached status list with its original TTL and EXP values.

Example scenario:

  • Credential is issued with a 24-hour TTL (default).
  • The TTL configuration is changed to 2 hours by the issuer.
  • The Holder's SDK will continue to use the cached status list with the original 24-hour TTL.
  • The SDK will only receive the updated 2-hour TTL after it fetches a newly signed status list.

Implementing status checks

The Holder SDK always checks the revocation status for credentials that support revocation. This cannot be disabled.

When calling the getCredential method, you can control whether the SDK should attempt to retrieve an updated status list online using the skipStatusCheck parameter:

Controlling status list updates
// Retrieve updated status list online (default behavior)
let credential = try await mobileCredentialHolder.getCredential(
    credentialId: credentialId,
    skipStatusCheck: false
)

// Use valid cached status list only (skip online update)
let credential = try await mobileCredentialHolder.getCredential(
    credentialId: credentialId,
    skipStatusCheck: true
)
Controlling status list updates
// Retrieve updated status list online (default behavior)
val credential = mobileCredentialHolder.getCredential(
    credentialId = credentialId,
    skipStatusCheck = false
)

// Use valid cached status list only (skip online update)
val credential = mobileCredentialHolder.getCredential(
    credentialId = credentialId,
    skipStatusCheck = true
)
Controlling status list updates
// Retrieve updated status list online (default behavior)
const credentialResult = await mobileCredentialHolder.getCredential(
    credentialId,
    { skipStatusCheck: false }
)
if (credentialResult.isErr()) {
    const { error } = credentialResult
    // handle error scenarios
    return
}
const credential = credentialResult.value

// Use valid cached status list only (skip online update)
const credentialResult = await mobileCredentialHolder.getCredential(
    credentialId,
    { skipStatusCheck: true }
)
if (credentialResult.isErr()) {
    const { error } = credentialResult
    // handle error scenarios
    return
}
const credential = credentialResult.value

Important: Setting skipStatusCheck: true does not disable status checking. The SDK will still check the credential's status using the cached status list (as long as it is valid). This parameter only controls whether the SDK attempts to fetch an updated status list online.

Status check flow

  1. When getCredential is called, the SDK first checks the skipStatusCheck parameter.
  2. If skipStatusCheck is true, the SDK uses the cached status list (skipping online update).
  3. If skipStatusCheck is false (the default), the SDK checks if the cached status list has passed its TTL.
  4. If the TTL has passed, the SDK attempts to fetch an updated status list:
    • If the fetch succeeds, the status is updated based on the new status list.
    • If the fetch fails (e.g., device is offline), the SDK checks if the cached status list has expired.
  5. Whether using a cached status list or after a failed fetch, the SDK checks if the cached status list has expired:
    • If not expired, the status is returned from the cache.
    • If expired, the status becomes Unknown.

Checking credential status

After retrieving a credential, you can check its revocation status to determine whether it should be displayed or presented based on verification failure types:

Checking credential status
let credential = try await mobileCredentialHolder.getCredential(
    credentialId: credentialId
)

if let verificationResult = credential.verificationResult, verificationResult.verified {
    print("Credential is valid and can be presented")
    // Display credential and allow presentation
} else {
    switch credential.verificationResult?.failureType {
    case .statusRevoked:
        print("Credential has been permanently revoked")
        // Display warning, prevent presentation
    
    case .statusSuspended:
        print("Credential has been temporarily suspended")
        // Display warning, prevent presentation
    
    case .statusUnknown:
        print("Credential status cannot be determined")
        // Display warning, handle according to your security requirements
    
    default:
        // Handle other failure types
        break
    }
}
Checking credential status
val verificationResult = credential.verificationResult
if (verificationResult?.verified == true) {
    Log.d("Tag", "Credential is valid.")
} else {
    when (verificationResult?.failureType) {
        MobileCredentialVerificationFailureType.StatusRevoked -> {
            Log.d("Tag", "Credential has been revoked.")
            // Display warning, prevent presentation
        }

        MobileCredentialVerificationFailureType.StatusSuspended -> {
            Log.d("Tag", "Credential has been suspended.")
            // Display warning, prevent presentation
        }

        MobileCredentialVerificationFailureType.StatusUnknown -> {
            Log.d("Tag", "Credential status is unknown.")
            // Display warning, handle according to your security requirements
        }

        else -> {
            // Handle other failure types
        }
    }
}
Checking credential status
const credentialResult = await mobileCredentialHolder.getCredential(
    credentialId
)
if (credentialResult.isErr()) {
    const { error } = credentialResult
    // handle error scenarios
    return
}
const credential = credentialResult.value

const verificationResult = credential.verificationResult
if (verificationResult?.verified === true) {
    console.log("Credential is valid and can be presented")
    // Display credential and allow presentation
} else {
    switch (verificationResult?.failureType) {
        case MobileCredentialVerificationFailureType.StatusRevoked:
            console.log("Credential has been permanently revoked")
            // Display warning, prevent presentation
            break

        case MobileCredentialVerificationFailureType.StatusSuspended:
            console.log("Credential has been temporarily suspended")
            // Display warning, prevent presentation
            break

        case MobileCredentialVerificationFailureType.StatusUnknown:
            console.log("Credential status cannot be determined")
            // Display warning, handle according to your security requirements
            break

        default:
            // Handle other failure types
            break
    }
}

Handling offline scenarios

  • Cache reliance: When offline, the SDK relies on cached status lists.
    • Cached status after TTL: If the device is offline and ttl has passed (but expiry hasn't), the cached status will be used.
    • Unknown status after expiry: If the device is offline and exp has passed, the status will always be returned as Unknown.
  • Unknown status handling: Define your application's policy for handling credentials with Unknown status. Options include:
    • Preventing presentation.
    • Allowing presentation with a warning.
    • Allowing presentation only for recently checked credentials.
  • User communication: Clearly inform users when status checks fail and what it means for their credentials.

How would you rate this page?

On this page