Passer au contenu principal

DCQL expliqué : comment un vérificateur demande à un wallet exactement ce dont il a besoin

Le Digital Credentials Query Language, DCQL, est le format de requête JSON qu’OpenID4VP utilise dans une demande de présentation. Il permet à une partie utilisatrice de décrire quelles attestations, et quels claims qu’elles contiennent, elle souhaite consulter, sous une forme que tout wallet conforme peut interpréter sans intégration spécifique.

Le problème que résout DCQL

Un business wallet peut contenir des attestations dans plusieurs formats : une immatriculation d’entreprise en SD-JWT VC, une qualification professionnelle encodée en mdoc, une W3C verifiable credential issue d’un pilote antérieur. Un vérificateur qui doit seulement confirmer un numéro d’immatriculation et une dénomination sociale n’a aucun moyen portable de demander exactement cela, quel que soit le format, sans accepter l’attestation entière ou coder à la main une requête distincte par format et par fournisseur de wallet.

DCQL comble ce manque côté requête. Il s’agit d’un objet JSON unique, intégré à la demande d’autorisation OpenID4VP, qui définit une ou plusieurs requêtes d’attestation, chacune liée à un format et à un ensemble de chemins de claims. Le wallet évalue la requête par rapport aux attestations stockées, détermine celles qui correspondent et ne demande qu’ensuite au titulaire d’autoriser la divulgation de ces claims précis. Le vérificateur reçoit une structure prévisible, quel que soit le wallet utilisé par le titulaire.

1. Vérificateur

Envoie une requête OpenID4VP avec une dcql_query

2. Wallet

Compare la requête aux attestations stockées

3. Titulaire

Autorise la seule divulgation des claims demandés

4. Vérificateur

Reçoit une présentation par id de requête d’attestation

La structure d’une requête DCQL

Une requête DCQL est un objet JSON contenant un tableau credentials et, en option, un tableau credential_sets. Chaque entrée de credentials est une requête d’attestation. Les champs marqués M sont obligatoires dans cette requête.

dcql_query

credentials[ ]

Une requête par attestation nécessaire

id + format

Quelle attestation, dans quel format

meta

Filtre de type, par exemple vct_values

claims[ ]

Chemins des claims à divulguer

claim_sets[ ]

Combinaisons de claims acceptées

credential_sets[ ]

Facultatif : quelles combinaisons de requêtes d’attestation satisfont la demande

ChampTypeObligatoire
idstringM
formatenum: dc+sd-jwt | mso_mdoc | jwt_vc_json | ldp_vcM
metaobjet, forme selon format
claimstableau de requêtes de claims
claim_setstableau de tableaux d’ids de claims
trusted_authoritiestableau d’objets avec un type et des values

Chaque entrée de claims est elle-même un objet : un id permettant d’y faire référence depuis claim_sets, un path, tableau qui situe le claim dans l’attestation (par exemple ["legal_name"] pour un claim SD-JWT de premier niveau, ou ["org", "registration_number"] pour un claim imbriqué), et en option values, une liste de valeurs auxquelles le claim doit correspondre.

Exemple détaillé : vérification d’une immatriculation d’entreprise

Immatriculation

dc+sd-jwt

Le vérificateur dit : voici ce que je veux recevoir

  • ✓ Numéro d’immatriculationreg_nopath: ["registration_number"]
  • ✓ Dénomination socialelegal_namepath: ["legal_name"]
  • ✓ Pays d’immatriculationreg_countrypath: ["registration_country"]
{
  "credentials": [
    {
      "id": "company_registration",
      "format": "dc+sd-jwt",
      "meta": {
        "vct_values": ["urn:eudi:business:company-registration:1"]
      },
      "claims": [
        { "id": "reg_no", "path": ["registration_number"] },
        { "id": "legal_name", "path": ["legal_name"] },
        { "id": "reg_country", "path": ["registration_country"] }
      ]
    }
  ]
}

Exemple de réponse du wallet

Le wallet répond avec un objet vp_token dont les clés sont les ids des requêtes d’attestation. Chaque valeur est un tableau de présentations. Pour dc+sd-jwt, une présentation se compose du JWT signé par l’émetteur, d’une disclosure par claim divulgué et d’un key binding JWT qui la lie au nonce et au client de cette requête.

Ce que le wallet envoie

{
  "vp_token": {
    "company_registration": [
      "<issuer-signed JWT>~<disclosure: registration_number>~<disclosure: legal_name>~<disclosure: registration_country>~<key binding JWT>"
    ]
  }
}

Claims visibles par le vérificateur après validation

{
  "vct": "urn:eudi:business:company-registration:1",
  "registration_number": "12345678",
  "legal_name": "Example Logistics B.V.",
  "registration_country": "NL"
}

Prévoir une solution de repli : claim_sets

claim_sets liste des groupes d’ids de claims par ordre de préférence. Le wallet renvoie le premier groupe qu’il peut satisfaire entièrement avec ce que le titulaire détient réellement, ce qui évite au vérificateur d’envoyer deux requêtes distinctes pour le cas précis et le cas de repli.

Exemple détaillé : numéro d’immatriculation, ou à défaut la seule dénomination sociale

1. Préféré

reg_nolegal_name

Renvoyé si l’attestation contient les deux claims

2. Solution de repli

legal_name

Renvoyé seulement si le premier ensemble ne peut pas être satisfait

{
  "credentials": [
    {
      "id": "company_registration",
      "format": "dc+sd-jwt",
      "meta": { "vct_values": ["urn:eudi:business:company-registration:1"] },
      "claims": [
        { "id": "reg_no", "path": ["registration_number"] },
        { "id": "legal_name", "path": ["legal_name"] }
      ],
      "claim_sets": [
        ["reg_no", "legal_name"],
        ["legal_name"]
      ]
    }
  ]
}

Ici, le vérificateur préfère un numéro d’immatriculation et la dénomination sociale, mais accepte la dénomination sociale seule si l’attestation du titulaire ne contient pas de claim de numéro d’immatriculation.

Exemple de réponse : la solution de repli a été utilisée

Ce que le wallet envoie

{
  "vp_token": {
    "company_registration": [
      "<issuer-signed JWT>~<disclosure: legal_name>~<key binding JWT>"
    ]
  }
}

Claims visibles par le vérificateur après validation

{
  "vct": "urn:eudi:business:company-registration:1",
  "legal_name": "Example Logistics B.V."
}

L’attestation du titulaire ne comporte pas de numéro d’immatriculation : le wallet a donc satisfait le second ensemble de claims et divulgué une seule disclosure. La réponse n’indique pas quel ensemble a été utilisé : le vérificateur le déduit des claims reçus.

Combiner des attestations : credential_sets

credential_sets agit un niveau au-dessus de claim_sets. Chaque entrée liste des options, chaque option étant un groupe d’ids de requêtes d’attestation. Le wallet doit satisfaire une option de chaque entrée obligatoire, ce qui offre au vérificateur une logique ET et OU entre attestations dans une seule requête.

Obligatoire

Immatriculation

AND

L’un de

Numéro de TVA

OR

L’un de

Attestation bancaire

"credential_sets": [
  { "options": [["company_registration"]] },
  { "options": [["vat_registration"], ["bank_account"]] }
]

Exemple de réponse : immatriculation et compte bancaire

Ce que le wallet envoie

{
  "vp_token": {
    "company_registration": [
      "<issuer-signed JWT>~<disclosures>~<key binding JWT>"
    ],
    "bank_account": [
      "<issuer-signed JWT>~<disclosures>~<key binding JWT>"
    ]
  }
}

Claims visibles par le vérificateur après validation

{
  "company_registration": {
    "vct": "urn:eudi:business:company-registration:1",
    "registration_number": "12345678",
    "legal_name": "Example Logistics B.V."
  },
  "bank_account": {
    "vct": "urn:eudi:business:bank-account:1",
    "iban": "NL91ABNA0417164300",
    "account_holder": "Example Logistics B.V."
  }
}

Le titulaire ne possède pas d’attestation de numéro de TVA : le wallet a donc choisi la seconde option du second ensemble. Les ids de requête non utilisés, ici vat_registration, sont simplement absents de vp_token.

Uniquement des émetteurs de confiance : trusted_authorities

trusted_authorities restreint une requête d’attestation aux attestations dont l’émetteur est garanti par une autorité à laquelle le vérificateur fait confiance. Chaque entrée comporte un type et une liste de values : aki pour un authority key identifier, etsi_tl pour une ETSI trusted list, ou openid_federation pour un trust anchor de fédération. Le wallet ne propose que les attestations correspondantes.

Exemple détaillé : une immatriculation issue d’un émetteur listé

Le vérificateur dit : uniquement des émetteurs de cette liste de confiance

type: etsi_tl

https://ec.europa.eu/tools/lotl/eu-lotl.xml

Attestation d’immatriculation d’un émetteur listé

l’émetteur figure sur la liste de confiance

✓ Correspond à la requête

Attestation d’immatriculation d’un émetteur non listé

l’émetteur ne figure pas sur la liste de confiance

✗ Ne correspond pas, non proposé au titulaire

{
  "credentials": [
    {
      "id": "company_registration",
      "format": "dc+sd-jwt",
      "meta": { "vct_values": ["urn:eudi:business:company-registration:1"] },
      "trusted_authorities": [
        {
          "type": "etsi_tl",
          "values": ["https://ec.europa.eu/tools/lotl/eu-lotl.xml"]
        }
      ],
      "claims": [
        { "id": "reg_no", "path": ["registration_number"] },
        { "id": "legal_name", "path": ["legal_name"] }
      ]
    }
  ]
}

Exemple de réponse : seule l’attestation de l’émetteur listé

Ce que le wallet envoie

{
  "vp_token": {
    "company_registration": [
      "<issuer-signed JWT>~<disclosure: registration_number>~<disclosure: legal_name>~<key binding JWT>"
    ]
  }
}

Claims visibles par le vérificateur après validation

{
  "vct": "urn:eudi:business:company-registration:1",
  "registration_number": "12345678",
  "legal_name": "Example Logistics B.V."
}

Le titulaire possédait aussi une attestation d’immatriculation d’un émetteur non listé, mais le wallet ne l’a pas proposée. trusted_authorities est un filtre pour le wallet, pas une garantie : le vérificateur contrôle lui-même l’émetteur par rapport à la liste de confiance lors de la validation de la présentation.

Correspondance de valeur : claims.values

Une requête de claim peut contenir values, une liste de chaînes, d’entiers ou de booléens. Le wallet ne renvoie le claim que si son type et sa valeur correspondent exactement à l’un d’eux. Un vérificateur peut ainsi contrôler une condition sans rien demander d’autre au préalable.

Exemple détaillé : uniquement des entreprises immatriculées aux Pays-Bas ou en Belgique

Le vérificateur dit : uniquement une entreprise immatriculée dans l’un de ces pays

reg_countryvalues:"NL""BE"

Entreprise néerlandaise

registration_country: "NL"

✓ Correspond à la requête

Entreprise allemande

registration_country: "DE"

✗ Ne correspond pas, non proposé au titulaire

{
  "credentials": [
    {
      "id": "company_registration",
      "format": "dc+sd-jwt",
      "meta": { "vct_values": ["urn:eudi:business:company-registration:1"] },
      "claims": [
        { "id": "legal_name", "path": ["legal_name"] },
        {
          "id": "reg_country",
          "path": ["registration_country"],
          "values": ["NL", "BE"]
        }
      ]
    }
  ]
}

Exemple de réponse : une entreprise néerlandaise

Ce que le wallet envoie

{
  "vp_token": {
    "company_registration": [
      "<issuer-signed JWT>~<disclosure: legal_name>~<disclosure: registration_country>~<key binding JWT>"
    ]
  }
}

Claims visibles par le vérificateur après validation

{
  "vct": "urn:eudi:business:company-registration:1",
  "legal_name": "Example Logistics B.V.",
  "registration_country": "NL"
}

L’attestation d’une entreprise allemande a registration_country "DE" : elle ne satisfait donc pas la requête et le wallet n’a rien à renvoyer pour elle. Le vérificateur doit néanmoins contrôler la valeur dans les claims validés plutôt que de se fier au filtrage du wallet.

Contraintes propres au format dans meta

SD-JWT VC : vct_values

Pour dc+sd-jwt, meta.vct_values liste les identifiants de type d’attestation acceptés par le vérificateur. Une requête ne correspond qu’à une attestation stockée dont le vct figure parmi ces valeurs. Un vérificateur qui ne fait confiance qu’au type d’attestation d’immatriculation d’un seul émetteur indique donc exactement cet identifiant.

mso_mdoc : doctype_value et namespace

Pour mso_mdoc, meta.doctype_value fixe le DocType ISO 18013-5, et chaque chemin de claim commence par le namespace mdoc dont relève le claim plutôt que par un simple nom de champ, car mdoc regroupe les claims par namespace et non dans un objet plat.

Où en est DCQL aujourd’hui

  • DCQL est défini au sein même de la spécification OpenID4VP, et non dans un document distinct. Il fait partie du brouillon depuis l’introduction du mécanisme destiné à remplacer une dépendance antérieure à DIF Presentation Exchange pour les requêtes OpenID4VP.
  • L’EUDI Wallet Architecture and Reference Framework désigne OpenID4VP comme protocole de présentation et, par conséquent, DCQL comme mécanisme de requête que les parties utilisatrices et les wallets de l’écosystème sont censés prendre en charge.
  • Les implémentations de référence de wallets et de vérificateurs du programme EUDI Wallet Reference Implementation ont convergé vers DCQL. Les nouvelles intégrations de business wallets construites aujourd’hui sur OpenID4VP doivent donc partir du principe que DCQL, et non Presentation Exchange, est le format de requête des demandes de présentation.

Termes associés

Questions fréquentes

En quoi DCQL diffère-t-il de DIF Presentation Exchange ?

Les deux décrivent ce qu’un vérificateur attend d’un wallet, mais DCQL est limité à OpenID4VP et défini directement dans cette spécification, tandis que Presentation Exchange est une spécification DIF distincte qui couvre aussi d’autres protocoles. DCQL est volontairement plus restreint : il ne comporte ni input descriptor groups ni submission requirements, et il exprime les contraintes propres à chaque format, comme un doctype mdoc ou un type SD-JWT VC, directement dans un objet de requête plutôt que par un filtre JSON Schema générique. L’écosystème EUDI Wallet a retenu DCQL comme standard pour les présentations OpenID4VP.

Une requête DCQL peut-elle demander plusieurs attestations ?

Oui. Le tableau credentials peut contenir plusieurs requêtes d’attestation, chacune avec son propre id. Un wallet qui dispose d’une correspondance pour chaque entrée renvoie une présentation par entrée. L’objet facultatif credential_sets peut en plus exiger certaines combinaisons, par exemple accepter soit une attestation d’immatriculation seule, soit une attestation d’immatriculation accompagnée d’une déclaration UBO, sans solliciter deux fois le titulaire.

Quel problème claim_sets résout-il au sein d’une seule requête d’attestation ?

Une attestation ne contient pas toujours tous les claims qu’un vérificateur souhaiterait. claim_sets liste des groupes alternatifs d’ids de claims qui suffiraient chacun à satisfaire la demande, classés du plus au moins souhaité. Le wallet choisit le premier groupe qu’il peut satisfaire entièrement avec les claims dont le titulaire dispose réellement. Un vérificateur peut ainsi demander un numéro d’identité précis lorsqu’il existe et se rabattre sur un contrôle plus sommaire, comme un indicateur de majorité, sans envoyer deux requêtes distinctes.

DCQL est-il propre à l’EUDI Wallet ?

Non. DCQL fait partie de la spécification de base d’OpenID4VP et toute implémentation d’OpenID4VP peut l’utiliser. L’écosystème EUDI Wallet en est un utilisateur majeur : l’Architecture and Reference Framework impose OpenID4VP avec DCQL comme mécanisme de présentation que les parties utilisatrices doivent prendre en charge. C’est pourquoi il compte tout particulièrement pour les wallets et vérificateurs conçus pour le marché européen.

DCQL effectue-t-il lui-même la divulgation sélective ?

Non. DCQL décrit seulement ce qui est demandé. La capacité du wallet à révéler exactement ces claims et rien d’autre dépend du format de l’attestation : un SD-JWT VC comme un ISO mdoc permettent de divulguer une partie de leurs claims, et un tableau claims DCQL s’appuie sur cette capacité. DCQL fonctionnerait aussi avec un format sans divulgation sélective, mais le titulaire devrait alors transmettre l’attestation complète, même pour une requête portant sur un seul claim.

Sources

  1. OpenID for Verifiable Presentations 1.0, section Digital Credentials Query Language (DCQL)
  2. EUDI Wallet Architecture and Reference Framework
  3. Spécification DIF Presentation Exchange 2.0.0

Cette page est fournie à titre informatif et ne constitue pas un avis juridique. Pour des informations faisant autorité, consultez directement l’OpenID Foundation et la Commission européenne.

Parlez-nous de votre intégration EUDI Wallet