Salt la conținutul principal

Emiterea acreditărilor OpenID4VCI explicată: de la ofertă la acreditarea acceptată

OpenID4VCI, OpenID for Verifiable Credential Issuance, definește modul în care un portofel solicită și primește o acreditare de la un emitent. O singură emitere parcurge mai mulți pași distincți înainte ca portofelul să dețină efectiv o acreditare utilizabilă, iar fiecare are propriul mod de eșec. Această pagină parcurge integral acest ciclu de viață, cu exemple practice originale.

Două moduri de a începe: cod preautorizat și cod de autorizare

Emiterea începe adesea de la o Credential Offer trimisă de emitent, iar grantul indicat de aceasta decide cum este autorizat portofelul. Un portofel poate începe și el emiterea, fără nicio ofertă, folosind fluxul cu cod de autorizare. Ambele fluxuri se termină în același loc: portofelul deține un token de acces pe care îl poate folosi pentru a solicita acreditarea.

Fluxul cu cod preautorizat

1. Emitent

Cunoaște deja deținătorul, emite o Credential Offer cu un pre-authorized_code

2. Portofel

Utilizează codul la punctul final de token, opțional cu un cod de tranzacție

3. Portofel

Solicită acreditarea cu o dovadă de posesie a cheii sale

Fluxul cu cod de autorizare

1. Portofel

Scanează o Credential Offer care indică un grant authorization_code, sau începe fluxul singur, fără nicio ofertă

2. Server de autorizare

Ghidează deținătorul prin autentificare și consimțământ, apoi emite un cod

3. Portofel

Schimbă codul cu un token, apoi solicită acreditarea

Ciclul de viață complet al unei oferte de acreditare

Specificația nu definește stări numite, dar un flux inițiat de emitent, în care acreditarea este emisă imediat, este cel mai ușor de urmărit ca secvența de mai jos. Fiecare pas poate eșua în felul său, iar o implementare de portofel trebuie să gestioneze acele căi, nu doar pe cea fericită.

Ofertă creată
Codul QR sau linkul deschide portofelul
Ofertă primită
grant utilizat
Token obținut
nonce și dovadă de posesie
Acreditare solicitată
acreditare generată
Acreditare emisă
portofelul validează și salvează
Acreditare acceptată
Unde poate eșua, în ordinea în care se derulează pașii:
Codul a expirat înainte de utilizare
Cerere de token respinsă
Cerere de acreditare respinsă
Acreditare nesalvată

Exemplu practic: un certificat de inspecție tehnică pentru o flotă de transport

Un organism de inspecție tehnică auto emite un certificat de inspecție tehnică către portofelul de afaceri al unei firme de transport, după o inspecție de rutină. Inspectorul l-a autentificat deja pe managerul de flotă la punctul de inspecție, așa că emitentul folosește fluxul cu cod preautorizat. Pașii de mai jos urmăresc această singură emitere, de la ofertă până la acreditarea acceptată.

Exemplul prezintă versiunea de bază a OpenID4VCI. Implementările cu asigurare ridicată, precum EUDI Wallet, aplică peste aceasta profilul HAIP, care adaugă tokenuri de acces legate prin DPoP, atestare a portofelului la punctul final de token și atestare a cheilor pentru cheile de acreditare. Acestea sunt omise aici pentru ca fiecare pas să rămână ușor de citit.

1. Oferta de acreditare

Terminalul organismului de inspecție afișează un cod QR. Acesta conține un URI care începe cu openid-credential-offer:// și transportă oferta de mai jos, codificată URL, într-un parametru credential_offer, sau un credential_offer_uri de la care portofelul o preia. Portofelul scanează codul și citește ce acreditare este oferită și cum să o obțină.

Emitent

Afișează un cod QR cu o Credential Offer

Indică configurația acreditării și un grant pre-authorized_code

{
  "credential_issuer": "https://issuer.fleetinspect.example",
  "credential_configuration_ids": ["roadworthiness_certificate"],
  "grants": {
    "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
      "pre-authorized_code": "fi-8f2c0b3a",
      "tx_code": {
        "length": 6,
        "input_mode": "numeric",
        "description": "Enter the code sent to your phone by text message"
      }
    }
  }
}

2. Descoperirea metadatelor emitentului

Înainte de a solicita ceva, portofelul obține metadatele emitentului pentru a afla ce conține roadworthiness_certificate și ce puncte finale să apeleze. Metadatele nu listează servere de autorizare separate, așa că emitentul este propriul său server de autorizare, iar portofelul citește punctul final de token din metadatele acelui server.

Portofel

GET /.well-known/openid-credential-issuer

Află formatul acreditării, atributele și tipurile de dovezi acceptate, plus punctele finale pentru nonce, acreditare, amânare și notificare

Metadatele emitentului de acreditări (extras)

{
  "credential_issuer": "https://issuer.fleetinspect.example",
  "nonce_endpoint": "https://issuer.fleetinspect.example/nonce",
  "credential_endpoint": "https://issuer.fleetinspect.example/credential",
  "deferred_credential_endpoint": "https://issuer.fleetinspect.example/deferred",
  "notification_endpoint": "https://issuer.fleetinspect.example/notify",
  "credential_configurations_supported": {
    "roadworthiness_certificate": {
      "format": "dc+sd-jwt",
      "vct": "https://fleetinspect.example/vct/roadworthiness",
      "cryptographic_binding_methods_supported": ["jwk"],
      "credential_signing_alg_values_supported": ["ES256"],
      "proof_types_supported": {
        "jwt": { "proof_signing_alg_values_supported": ["ES256"] }
      },
      "credential_metadata": {
        "claims": [
          { "path": ["vehicle_registration"] },
          { "path": ["inspection_result"] },
          { "path": ["valid_until"] }
        ]
      }
    }
  }
}

Metadatele serverului de autorizare (extras), de la /.well-known/oauth-authorization-server

{
  "issuer": "https://issuer.fleetinspect.example",
  "token_endpoint": "https://issuer.fleetinspect.example/token",
  "pre-authorized_grant_anonymous_access_supported": true
}

3. Cererea de token

Portofelul utilizează codul preautorizat la punctul final de token, împreună cu codul de tranzacție pe care organismul de inspecție l-a trimis pe telefonul managerului de flotă. Trimiterea acelui cod pe un al doilea canal înseamnă că cineva care fotografiază codul QR peste umăr tot nu îl poate folosi.

Portofel

POST /token

Trimite pre-authorized_code și tx_code, primește un token de acces limitat la această ofertă

POST /token HTTP/1.1
Host: issuer.fleetinspect.example
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code
&pre-authorized_code=fi-8f2c0b3a
&tx_code=482913

Răspuns

{
  "access_token": "fi-at-3d91e0",
  "token_type": "Bearer",
  "expires_in": 86400
}

4. Dovada de posesie

Deoarece metadatele listează un nonce_endpoint, portofelul obține mai întâi de acolo un c_nonce proaspăt. Apoi dovedește că deține cheia privată de care va fi legată acreditarea, semnând un proof JWT peste identificatorul emitentului și acel c_nonce.

Portofel

POST /nonce, apoi semnează un proof JWT cu cheia de care va fi legată acreditarea

Leagă acreditarea de acea cheie, nu doar de cine deține tokenul de acces

POST /nonce HTTP/1.1
Host: issuer.fleetinspect.example

Răspuns

{
  "c_nonce": "fi-nonce-77aa"
}

Portofelul construiește acum proof JWT din două obiecte JSON, un antet și o sarcină utilă, și le semnează cu cheia privată de care va fi legată acreditarea.

Antet: ce este acest JWT și ce cheie l-a semnat

{
  "typ": "openid4vci-proof+jwt",
  "alg": "ES256",
  "jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }
}
  • typ: marchează acest lucru ca o dovadă de cheie OpenID4VCI, astfel încât să nu poată fi confundat cu niciun alt tip de JWT
  • alg: algoritmul de semnare, unul dintre cele listate de emitent în proof_signing_alg_values_supported
  • jwk: cheia publică de care va fi legată acreditarea; emitentul verifică semnătura pe baza acesteia

Sarcină utilă: pentru cine este dovada și când a fost creată

{
  "aud": "https://issuer.fleetinspect.example",
  "iat": 1789376400,
  "nonce": "fi-nonce-77aa"
}
  • aud: identificatorul emitentului, astfel încât dovada să nu poată fi reluată la un alt emitent
  • iat: momentul la care a fost creată dovada, în secunde de la 1970
  • nonce: c_nonce de la punctul final de nonce, care arată că dovada este proaspătă

Rezultatul semnat

Antetul și sarcina utilă sunt fiecare codificate base64url și unite printr-un punct. Portofelul semnează acel șir cu cheia sa privată și adaugă semnătura codificată base64url după un al doilea punct. Șirul rezultat este proof JWT pe care portofelul îl trimite în cererea de acreditare de la pasul 5.

base64url(header) . base64url(payload) . base64url(signature)

eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIs...
  .eyJhdWQiOiJodHRwczovL2lzc3Vlci5mbGVldGluc3BlY3QuZXhhbXBsZSIs...
  .<ES256 signature>

5. Cererea de acreditare

Portofelul apelează punctul final de acreditare cu tokenul de acces și dovada, iar emitentul generează și returnează acreditarea semnată.

Portofel

POST /credential

Trimite tokenul de acces, identificatorul configurației și proof JWT, primește acreditarea semnată și un notification_id

POST /credential HTTP/1.1
Host: issuer.fleetinspect.example
Content-Type: application/json
Authorization: Bearer fi-at-3d91e0

{
  "credential_configuration_id": "roadworthiness_certificate",
  "proofs": {
    "jwt": ["<proof JWT from step 4>"]
  }
}

Răspuns

{
  "credentials": [
    { "credential": "<issuer-signed SD-JWT VC>" }
  ],
  "notification_id": "fi-notif-9012"
}

Întregul flux dintr-o privire

Această diagramă de secvență reunește cei cinci pași ai exemplului practic, de la ofertă până la notificare. Săgețile continue sunt cereri, cele întrerupte sunt răspunsuri, iar săgeata punctată este codul de tranzacție care circulă în afara protocolului, printr-un mesaj text.

Portofel

Portofelul de afaceri al firmei de transport

Server de autorizare

Administrat chiar de emitent în acest exemplu

Emitent de acreditări

Organism de inspecție tehnică auto

  1. Emitent de acreditări către Portofel: Credential Offer, afișată ca cod QR
  2. Emitent de acreditări către Portofel: tx_code, trimis prin SMS pe telefonul managerului de flotă
  3. Portofel către Emitent de acreditări: GET /.well-known/openid-credential-issuer
  4. Emitent de acreditări către Portofel: metadatele emitentului de acreditări
  5. Portofel către Server de autorizare: GET /.well-known/oauth-authorization-server
  6. Server de autorizare către Portofel: metadatele serverului de autorizare
  7. Portofel către Server de autorizare: POST /token: pre-authorized_code, tx_code
  8. Server de autorizare către Portofel: access_token
  9. Portofel către Emitent de acreditări: POST /nonce
  10. Emitent de acreditări către Portofel: c_nonce
  11. Portofel: semnează proof JWT
  12. Portofel către Emitent de acreditări: POST /credential: token de acces, dovezi
  13. Emitent de acreditări către Portofel: credentials, notification_id
  14. Portofel: validează și salvează
  15. Portofel către Emitent de acreditări: POST /notify: credential_accepted
  16. Emitent de acreditări către Portofel: 204 No Content

Când acreditarea nu este încă gata: emiterea amânată

Exemplul de mai sus presupune că rezultatul inspecției este deja final. Dacă organismul de inspecție trebuie în schimb să escaladeze un rezultat la limită către un inspector superior, punctul final de acreditare nu poate returna acreditarea imediat, așa că amână emiterea.

Emitere imediată

Punctul final de acreditare returnează acreditarea semnată în același răspuns ca cererea.

Emitere amânată

Punctul final de acreditare răspunde cu HTTP 202, un transaction_id și un interval, în loc de acreditare. Portofelul interoghează deferred_credential_endpoint cu acel identificator, așteptând cel puțin numărul de secunde indicat de interval între cereri, până când acreditarea este gata.

transaction_idinterogare deferred_credential_endpoint

Răspuns amânat de la /credential

HTTP/1.1 202 Accepted
Content-Type: application/json

{
  "transaction_id": "fi-tx-55c2",
  "interval": 900
}

Interogarea /deferred până este gata

POST /deferred HTTP/1.1
Host: issuer.fleetinspect.example
Content-Type: application/json
Authorization: Bearer fi-at-3d91e0

{ "transaction_id": "fi-tx-55c2" }

// While the review is open: 202 with the same transaction_id and interval.
// Once approved: 200 with the credentials, optionally with a notification_id.
// If the review rejects the result: a credential_request_denied error.

Închiderea buclei: punctul final de notificare

După emitere, portofelul poate anunța emitentul ce s-a întâmplat cu acreditarea, folosind notification_id din răspunsul de acreditare. Portofelele nu sunt obligate să trimită aceste notificări, iar livrarea nu este garantată, așa că un emitent nu poate interpreta în niciun fel absența unei notificări.

Portofel

Trimite un eveniment către notification_endpoint al emitentului pentru notification_id primit, care acoperă fiecare acreditare din acel răspuns

credential_accepted

Salvată în portofel

credential_failure

Emiterea a eșuat din alt motiv, de exemplu acreditarea nu a trecut validarea

credential_deleted

Emiterea a eșuat din cauza deținătorului, de exemplu a refuzat să o salveze

POST /notify HTTP/1.1
Host: issuer.fleetinspect.example
Content-Type: application/json
Authorization: Bearer fi-at-3d91e0

{
  "notification_id": "fi-notif-9012",
  "event": "credential_accepted"
}

Termeni asociați

Întrebări frecvente

De unde știe portofelul ce tip de grant să folosească?

Credential Offer indică grantul în obiectul său grants. authorization_code apare atunci când emitentul dorește ca deținătorul să se autentifice ca parte a fluxului. pre-authorized_code apare atunci când deținătorul era deja autentificat pe canalul pe care a fost creată oferta, de exemplu de către inspector la punctul de inspecție din exemplul practic de mai jos. O ofertă poate include ambele, iar portofelul alege apoi una dintre ele. Dacă oferta nu are deloc un obiect grants, portofelul verifică în metadate ce tipuri de granturi sunt acceptate de serverul de autorizare.

De ce portofelul obține metadatele emitentului înainte de a solicita ceva?

Credential Offer indică doar credential_configuration_ids, adresa URL a emitentului și grants. Metadatele emitentului, furnizate la o cale bine cunoscută, sunt cele care descriu fiecare configurație: formatul, atributele și tipurile de dovezi acceptate, plus punctele finale pentru nonce, acreditare, amânare și notificare. Ele indică și ce server de autorizare trebuie folosit, iar metadatele acelui server oferă punctul final de token. Fără ambele, portofelul nu ar ști cum să construiască cereri valide sau ce să îi arate deținătorului înainte de a-și da consimțământul.

Ce dovedește de fapt dovada de posesie?

Dovedește că portofelul care solicită acreditarea deține cheia privată de care va fi legată acreditarea, nu doar că are un token de acces valid. Portofelul semnează un proof JWT peste identificatorul emitentului și un c_nonce proaspăt de la punctul final de nonce al emitentului, folosind acea cheie. Emitentul include cheia publică corespunzătoare în acreditare. Un verificator care solicită legarea de cheie îi cere deținătorului să semneze din nou cu aceeași cheie la prezentare, astfel încât o acreditare copiată fără cheie nu trece acea verificare.

De ce ar amâna un emitent emiterea în loc să returneze acreditarea imediat?

Unele verificări pe care emitentul le efectuează înainte de a genera o acreditare nu se pot finaliza în cadrul unei singure cereri HTTP, de exemplu o revizuire manuală sau un apel către un registru extern lent. Emiterea amânată permite punctului final de acreditare să răspundă imediat cu un transaction_id în loc să blocheze conexiunea, iar portofelul interoghează punctul final amânat cu acel identificator până când verificarea se finalizează și acreditarea este gata de preluat.

Este punctul final de notificare obligatoriu pentru ca un emitent să îl implementeze?

Nu. Este opțional pentru emitenți, iar portofelele nu sunt nici ele obligate să îl folosească. Când ambele îl suportă, emitentul află ce s-a întâmplat după emitere: acreditările au fost salvate (credential_accepted), deținătorul a oprit emiterea, de exemplu refuzând să le salveze (credential_deleted), sau a eșuat din alt motiv (credential_failure). Livrarea nu este garantată, așa că un emitent ar trebui să trateze o notificare ca informație utilă, niciodată ca o evidență de încredere, și nu poate deduce nimic dintr-una lipsă.

Surse

  1. OpenID for Verifiable Credential Issuance 1.0
  2. OpenID4VC High Assurance Interoperability Profile 1.0
  3. EUDI Wallet Architecture and Reference Framework

Această pagină are caracter informativ și nu constituie consultanță juridică. Pentru îndrumări autorizate, contactați direct OpenID Foundation și Comisia Europeană.

Discutați cu noi despre integrarea EUDI Wallet