How to consume a VICAL as a relying party
Once a VICAL provider has published a VICAL, relying parties need to retrieve it, verify its authenticity, and load the trusted IACAs into their verification solution. The steps below describe how to do that against a VICAL published by a MATTR-hosted ecosystem.
Retrieve the VICAL provider's root CA certificate
Call the Retrieve all public DTS root CA certificates endpoint to obtain the root certificate(s) used by the VICAL provider as the anchor of its signing chain. The endpoint is public and does not require authentication.
curl https://your-tenant.vii.au01.mattr.global/v1/ecosystems/public/certificates/caThe response includes one or more PEM-encoded root certificates:
{
"rootCertificates": [
{
"certificate": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----",
"notBefore": "2025-10-22T00:00:00Z",
"notAfter": "2030-10-22T00:00:00Z",
"fingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172",
"commonName": "Example VICAL Provider Root CA"
}
]
}Persist these certificates as your trust anchor for the VICAL provider. They only need to be refreshed when the provider rotates its root.
Retrieve the latest VICAL
Call the Retrieve latest VICAL
endpoint to download the current VICAL. The response is a binary CBOR file (application/cbor)
encoded as a COSE_Sign1 structure, as defined in
ISO/IEC 18013-5 Annex C.
curl -o vical-latest.cbor \
https://your-tenant.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/public/latestRelying parties should poll this endpoint on a schedule that matches the VICAL provider's publishing cadence so they always operate against the latest set of trusted issuers.
Validate the VICAL signature and certificate chain
The VICAL is signed by a VICAL Signer certificate, which itself chains back to the VICAL provider's root CA retrieved in step 1. Before trusting any data in the VICAL, perform the following checks:
- Extract the VICAL Signer certificate from the COSE_Sign1 unprotected header (
x5chain, COSE label33). - Verify the COSE_Sign1 signature using the public key from that signer certificate and the
signature algorithm declared in the protected header (for example, ES256 / COSE algorithm
-7). - Build the certificate chain from the signer certificate up to the root CA from step 1, and validate each link: signature, validity period, key usage, and revocation status (CRL).
- Confirm the root of the chain matches one of the trust-anchor certificates you persisted in step 1.
If any of these checks fail, reject the VICAL and continue using the previously trusted version.
Decode the VICAL payload
Once the signature has been verified, decode the COSE_Sign1 payload to access the VICAL data. The decoded structure looks like this (values are illustrative):
{
"protectedHeader": {
"1": -7
},
"unprotectedHeader": {
"33": {
"type": "X.509 Certificate",
"subject": "C=AU, CN=example-dts.vii.au01.mattr.global VICAL Signer, O=Example Provider",
"issuer": "C=AU, CN=Example Provider Root CA, O=Example Provider",
"validFrom": "2025-11-11T02:26:13.000Z",
"validTo": "2029-02-10T02:26:13.000Z",
"fingerprint": "68:58:45:41:5A:AF:10:3A:85:01:78:E2:40:C9:59:51:AB:9B:10:13",
"pem": "-----BEGIN CERTIFICATE-----\nMIICnzCCAkagAwIBAgIK...\n-----END CERTIFICATE-----"
}
},
"payload": {
"version": "1.0",
"vicalProvider": "Example Provider",
"date": "2025-11-15T20:59:01.000Z",
"vicalIssueID": 41,
"certificateInfos": [
{
"certificate": {
"type": "X.509 Certificate",
"subject": "CN=Example IACA, O=Example DLD, ST=US-XX, C=US",
"issuer": "CN=Example IACA, O=Example DLD, ST=US-XX, C=US",
"validFrom": "2024-01-01T00:00:00.000Z",
"validTo": "2034-01-01T00:00:00.000Z",
"pem": "-----BEGIN CERTIFICATE-----\nMIICeDCCAh+gAwIBAgIQ...\n-----END CERTIFICATE-----",
"isCertificateAuthority": true
},
"serialNumber": "5A5BB04A119A35796D2AD477D93A1ACC",
"ski": "3C:4C:6C:DF:E7:82:34:2E:E1:14:E6:CE:AD:12:0A:39:FD:08:34:6B",
"issuingCountry": "US",
"stateOrProvinceName": "US-XX",
"notBefore": "2024-01-01T00:00:00.000Z",
"notAfter": "2034-01-01T00:00:00.000Z",
"docType": ["org.iso.18013.5.1.mDL"],
"issuingAuthority": "CN=Example IACA, O=Example DLD, ST=US-XX, C=US"
}
]
}
}The payload.certificateInfos array contains one entry per trusted IACA. Each entry includes the
PEM-encoded IACA, its validity period, the issuing authority's identifiers, and the array of
docType values it is authorized to sign.
Load the trusted IACAs into your verification solution
Iterate over payload.certificateInfos and extract each certificate. In raw CBOR, certificate
is the DER-encoded X.509 IACA byte string (the JSON above is a rendered view). These IACAs are the
trust anchors you use when validating presented mDocs:
import { decode } from "cbor-x";
const vicalBytes = await fetch(
"https://your-tenant.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/public/latest"
).then((r) => r.arrayBuffer());
// COSE_Sign1 structure: [protected, unprotected, payload, signature]
const coseSign1 = decode(new Uint8Array(vicalBytes));
const payload = decode(coseSign1[2]);
const trustAnchors = payload.certificateInfos.map((info) => ({
certificateDer: info.certificate,
issuingAuthority: info.issuingAuthority,
docTypes: info.docType,
validFrom: info.notBefore,
validTo: info.notAfter,
}));When verifying a presented mDoc:
- Look up the IACA referenced by the mDoc's Document Signer Certificate against your loaded trust anchors.
- Confirm the mDoc's
docTypeis listed under that IACA's authorizeddocTypearray. If it is not, reject the credential even when the signature chain is otherwise valid. - Validate the full PKI chain from the mDoc's signer up to the IACA as the trust anchor.
This mechanism lets you verify mDocs from any issuer represented in the VICAL without maintaining individual trust relationships with each issuing authority.
How would you rate this page?
Last updated on