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:
// 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
ttlhas 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 withUnknownstatus.
Implementing status checks
When calling the getCredential method, you can control whether the SDK should check for the credential's status:
// Check credential status (default behavior)
let credential = try await mobileCredentialHolder.getCredential(
credentialId: credentialId,
skipStatusCheck: false
)
// Skip status check
let credential = try await mobileCredentialHolder.getCredential(
credentialId: credentialId,
skipStatusCheck: true
)// Check credential status (default behavior)
val credential = mobileCredentialHolder.getCredential(
credentialId = credentialId,
skipStatusCheck = false
)
// Skip status check
val credential = mobileCredentialHolder.getCredential(
credentialId = credentialId,
skipStatusCheck = true
)// Check credential status (default behavior)
const credentialResult = await mobileCredentialHolder.getCredential(
credentialId,
{ skipStatusCheck: false }
)
if (credentialResult.isErr()) {
const { error } = credentialResult
// handle error scenarios
return
}
const credential = credentialResult.value
// Skip status check
const credentialResult = await mobileCredentialHolder.getCredential(
credentialId,
{ skipStatusCheck: true }
)
if (credentialResult.isErr()) {
const { error } = credentialResult
// handle error scenarios
return
}
const credential = credentialResult.valueWhen skipStatusCheck is false (the default):
- The SDK checks if the cached status list has passed its TTL.
- 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 continues using the cached status list if it hasn't expired.
- If the cached status list has 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:
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
}
}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
}
}
}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
ttlhas passed (but expiry hasn't), the cached status will be used. - Unknown status after expiry: If the device is offline and
exphas passed, the status will always be returned asUnknown.
- Cached status after TTL: If the device is offline and
- Unknown status handling: Define your application's policy for handling credentials with
Unknownstatus. 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?