Zum Hauptinhalt springen

OpenID4VCI-Credential-Ausstellung erklärt: vom Angebot zum akzeptierten Credential

OpenID4VCI, OpenID for Verifiable Credential Issuance, definiert, wie eine Wallet ein Credential von einem Aussteller anfordert und erhält. Eine einzelne Ausstellung durchläuft mehrere eigenständige Schritte, bevor die Wallet tatsächlich ein nutzbares Credential besitzt, und jeder Schritt hat seinen eigenen Fehlermodus. Diese Seite geht diesen gesamten Lebenszyklus durch, mit originellen ausgearbeiteten Beispielen.

Zwei Wege zum Start: pre-authorized code und authorization code

Die Ausstellung beginnt oft mit einem vom Aussteller gesendeten Credential Offer, und der darin genannte Grant entscheidet, wie die Wallet autorisiert wird. Eine Wallet kann die Ausstellung auch selbst starten, ganz ohne Angebot, über den Authorization-code-Ablauf. Beide Abläufe enden am selben Punkt: Die Wallet besitzt ein Access-Token, mit dem sie das Credential anfordern kann.

Pre-authorized-code-Ablauf

1. Aussteller

Kennt den Inhaber bereits und übergibt ein Credential Offer mit einem pre-authorized_code

2. Wallet

Löst den Code beim Token-Endpunkt ein, optional mit einem Transaktionscode

3. Wallet

Fordert das Credential mit einem Besitznachweis des Schlüssels an

Authorization-code-Ablauf

1. Wallet

Scannt ein Credential Offer mit einem authorization_code-Grant oder startet den Ablauf selbst ohne Angebot

2. Autorisierungsserver

Führt den Inhaber durch Login und Einwilligung und stellt dann einen Code aus

3. Wallet

Tauscht den Code gegen ein Token und fordert dann das Credential an

Der vollständige Lebenszyklus eines Credential-Angebots

Die Spezifikation definiert keine benannten Zustände, aber ein vom Aussteller initiierter Ablauf, bei dem das Credential sofort ausgestellt wird, lässt sich am leichtesten wie in der folgenden Abfolge nachvollziehen. Jeder Schritt kann auf seine eigene Weise fehlschlagen, und eine Wallet-Implementierung muss diese Pfade behandeln, nicht nur den erfolgreichen.

Angebot erstellt
QR-Code oder Link öffnet Wallet
Angebot empfangen
Grant eingelöst
Token erhalten
Nonce und Besitznachweis
Credential angefordert
Credential erzeugt
Credential ausgestellt
Wallet validiert und speichert
Credential akzeptiert
Wo es fehlschlagen kann, in der Reihenfolge der Schritte:
Code vor Einlösung abgelaufen
Token-Anfrage abgelehnt
Credential-Anfrage zurückgewiesen
Credential nicht gespeichert

Ausgearbeitetes Beispiel: ein Prüfzeugnis für eine Speditionsflotte

Eine Fahrzeugprüfstelle stellt der Business-Wallet eines Speditionsunternehmens nach einer routinemäßigen Prüfung ein Prüfzeugnis aus. Der Prüfer hat den Flottenmanager an der Prüfstelle bereits authentifiziert, daher verwendet der Aussteller den Pre-authorized-code-Ablauf. Die folgenden Schritte verfolgen diese eine Ausstellung vom Angebot bis zum akzeptierten Credential.

Das Beispiel zeigt das grundlegende OpenID4VCI. Hochsichere Einsätze wie die EUDI Wallet folgen zusätzlich dem HAIP-Profil, das DPoP-gebundene Access-Tokens, Wallet Attestation am Token-Endpunkt und Key Attestation für die Credential-Schlüssel hinzufügt. Diese sind hier weggelassen, damit jeder Schritt übersichtlich bleibt.

1. Credential Offer

Das Terminal der Prüfstelle zeigt einen QR-Code. Er enthält eine URI, die mit openid-credential-offer:// beginnt und das untenstehende Angebot trägt, URL-codiert in einem credential_offer-Parameter, oder eine credential_offer_uri, von der die Wallet es abruft. Die Wallet scannt sie und liest, welches Credential angeboten wird und wie es zu beziehen ist.

Aussteller

Zeigt einen QR-Code mit einem Credential Offer

Nennt die Credential-Konfiguration und einen pre-authorized_code-Grant

{
  "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. Entdeckung der Aussteller-Metadaten

Bevor sie etwas anfordert, ruft die Wallet die Metadaten des Ausstellers ab, um zu erfahren, was roadworthiness_certificate enthält und welche Endpunkte aufzurufen sind. Die Metadaten listen keine separaten Autorisierungsserver auf, daher ist der Aussteller sein eigener Autorisierungsserver, und die Wallet liest den Token-Endpunkt aus den Metadaten dieses Servers.

Wallet

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

Erfährt das Credential-Format, die Claims und die akzeptierten Proof-Typen sowie den Nonce-, Credential-, Deferred- und Notification-Endpunkt

Metadaten des Credential-Ausstellers (Auszug)

{
  "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"] }
        ]
      }
    }
  }
}

Metadaten des Autorisierungsservers (Auszug), von /.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. Token-Anfrage

Die Wallet löst den pre-authorized code beim Token-Endpunkt ein, zusammen mit dem Transaktionscode, den die Prüfstelle an das Telefon des Flottenmanagers gesendet hat. Diesen Code über einen zweiten Kanal zu senden bedeutet, dass jemand, der den QR-Code über die Schulter fotografiert, ihn trotzdem nicht einlösen kann.

Wallet

POST /token

Sendet den pre-authorized_code und tx_code, erhält ein auf dieses Angebot beschränktes Access-Token

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

Antwort

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

4. Besitznachweis

Da die Metadaten einen nonce_endpoint auflisten, ruft die Wallet dort zunächst einen frischen c_nonce ab. Anschließend beweist sie, dass sie den privaten Schlüssel besitzt, an den das Credential gebunden wird, indem sie ein Proof-JWT über die Kennung des Ausstellers und diesen c_nonce signiert.

Wallet

POST /nonce, signiert dann ein Proof-JWT mit dem Schlüssel, an den das Credential gebunden wird

Bindet das Credential an diesen Schlüssel, nicht nur an den Besitzer des Access-Tokens

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

Antwort

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

Die Wallet erstellt das Proof-JWT nun aus zwei JSON-Objekten, einem Header und einem Payload, und signiert sie mit dem privaten Schlüssel, an den das Credential gebunden wird.

Header: was dieses JWT ist und welcher Schlüssel es signiert hat

{
  "typ": "openid4vci-proof+jwt",
  "alg": "ES256",
  "jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }
}
  • typ: kennzeichnet dies als OpenID4VCI-Key-Proof, damit es nicht mit einer anderen Art von JWT verwechselt wird
  • alg: der Signaturalgorithmus, einer der vom Aussteller in proof_signing_alg_values_supported aufgeführten
  • jwk: der öffentliche Schlüssel, an den das Credential gebunden wird; der Aussteller prüft die Signatur dagegen

Payload: für wen der Proof bestimmt ist und wann er erstellt wurde

{
  "aud": "https://issuer.fleetinspect.example",
  "iat": 1789376400,
  "nonce": "fi-nonce-77aa"
}
  • aud: die Kennung des Ausstellers, damit der Proof nicht bei einem anderen Aussteller wiederverwendet werden kann
  • iat: der Zeitpunkt, zu dem der Proof erstellt wurde, in Sekunden seit 1970
  • nonce: der c_nonce vom Nonce-Endpunkt, der zeigt, dass der Proof frisch ist

Signiertes Ergebnis

Header und Payload werden jeweils base64url-codiert und mit einem Punkt verbunden. Die Wallet signiert diese Zeichenkette mit ihrem privaten Schlüssel und hängt die base64url-codierte Signatur nach einem zweiten Punkt an. Die entstehende Zeichenkette ist das Proof-JWT, das die Wallet in Schritt 5 in der Credential-Anfrage sendet.

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

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

5. Credential-Anfrage

Die Wallet ruft den Credential-Endpunkt mit dem Access-Token und dem Proof auf, und der Aussteller erstellt und liefert das signierte Credential zurück.

Wallet

POST /credential

Sendet das Access-Token, die Konfigurations-id und das Proof-JWT, erhält das signierte Credential und eine 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>"]
  }
}

Antwort

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

Der gesamte Ablauf auf einen Blick

Dieses Sequenzdiagramm fasst die fünf Schritte des ausgearbeiteten Beispiels zusammen, vom Angebot bis zur Benachrichtigung. Durchgezogene Pfeile sind Anfragen, gestrichelte Pfeile sind Antworten, und der gepunktete Pfeil ist der Transaktionscode, der außerhalb des Protokolls per SMS unterwegs ist.

Wallet

Business-Wallet des Speditionsunternehmens

Autorisierungsserver

In diesem Beispiel vom Aussteller selbst betrieben

Credential-Aussteller

Fahrzeugprüfstelle

  1. Credential-Aussteller an Wallet: Credential Offer, dargestellt als QR-Code
  2. Credential-Aussteller an Wallet: tx_code, per SMS an das Telefon des Flottenmanagers gesendet
  3. Wallet an Credential-Aussteller: GET /.well-known/openid-credential-issuer
  4. Credential-Aussteller an Wallet: Metadaten des Credential-Ausstellers
  5. Wallet an Autorisierungsserver: GET /.well-known/oauth-authorization-server
  6. Autorisierungsserver an Wallet: Metadaten des Autorisierungsservers
  7. Wallet an Autorisierungsserver: POST /token: pre-authorized_code, tx_code
  8. Autorisierungsserver an Wallet: access_token
  9. Wallet an Credential-Aussteller: POST /nonce
  10. Credential-Aussteller an Wallet: c_nonce
  11. Wallet: signiert Proof-JWT
  12. Wallet an Credential-Aussteller: POST /credential: Access-Token, Proofs
  13. Credential-Aussteller an Wallet: credentials, notification_id
  14. Wallet: validiert und speichert
  15. Wallet an Credential-Aussteller: POST /notify: credential_accepted
  16. Credential-Aussteller an Wallet: 204 No Content

Wenn das Credential noch nicht bereit ist: aufgeschobene Ausstellung

Das obige Beispiel geht davon aus, dass das Prüfergebnis bereits endgültig ist. Muss die Prüfstelle stattdessen ein Grenzfallergebnis an einen leitenden Prüfer eskalieren, kann der Credential-Endpunkt das Credential nicht sofort zurückgeben und schiebt die Ausstellung auf.

Sofortige Ausstellung

Der Credential-Endpunkt gibt das signierte Credential in derselben Antwort wie die Anfrage zurück.

Aufgeschobene Ausstellung

Der Credential-Endpunkt antwortet mit HTTP 202, einer transaction_id und einem interval. Die Wallet fragt den Deferred-Credential-Endpunkt mit dieser id ab, wobei sie mindestens interval Sekunden zwischen den Anfragen wartet, bis das Credential bereit ist.

transaction_iddeferred_credential_endpoint abfragen

Aufgeschobene Antwort von /credential

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

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

Abfragen von /deferred, bis es bereit ist

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.

Den Kreis schließen: der Notification-Endpunkt

Nach der Ausstellung kann die Wallet dem Aussteller mitteilen, was mit dem Credential geschehen ist, mithilfe der notification_id aus der Credential-Antwort. Wallets sind nicht verpflichtet, diese Benachrichtigungen zu senden, und die Zustellung ist nicht garantiert, daher kann ein Aussteller aus einer fehlenden Benachrichtigung nichts schließen.

Wallet

Sendet ein Ereignis an den notification_endpoint des Ausstellers für die erhaltene notification_id, das jedes Credential in dieser Antwort abdeckt

credential_accepted

In der Wallet gespeichert

credential_failure

Ausstellung aus einem anderen Grund fehlgeschlagen, zum Beispiel weil das Credential nicht validiert wurde

credential_deleted

Ausstellung wegen des Inhabers fehlgeschlagen, zum Beispiel weil er die Speicherung ablehnte

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"
}

Verwandte Begriffe

Häufig gestellte Fragen

Woher weiß eine Wallet, welchen Grant-Typ sie verwenden soll?

Das Credential Offer nennt den Grant in seinem grants-Objekt. authorization_code ist vorhanden, wenn der Aussteller möchte, dass sich der Inhaber im Rahmen des Ablaufs anmeldet. pre-authorized_code ist vorhanden, wenn der Inhaber auf dem Kanal, auf dem das Angebot erstellt wurde, bereits authentifiziert war, zum Beispiel durch den Prüfer an der Prüfstelle im untenstehenden Beispiel. Ein Angebot kann beide auflisten, dann wählt die Wallet einen aus. Enthält das Angebot gar kein grants-Objekt, sucht die Wallet in den Metadaten des Autorisierungsservers nach, welche Grant-Typen dieser unterstützt.

Warum ruft die Wallet Aussteller-Metadaten ab, bevor sie etwas anfordert?

Das Credential Offer nennt nur credential_configuration_ids, die URL des Ausstellers und die Grants. Die Metadaten des Ausstellers, bereitgestellt unter einem Well-known-Pfad, beschreiben jede Konfiguration: ihr Format, ihre Claims und die akzeptierten Proof-Typen, dazu den Nonce-, Credential-, Deferred- und Notification-Endpunkt. Sie geben auch an, welcher Autorisierungsserver zu verwenden ist, und die Metadaten dieses Servers liefern den Token-Endpunkt. Ohne beides wüsste die Wallet nicht, wie sie gültige Anfragen erstellt oder was sie dem Inhaber vor dessen Einwilligung anzeigen soll.

Was beweist der Besitznachweis eigentlich?

Er beweist, dass die Wallet, die das Credential anfordert, den privaten Schlüssel besitzt, an den das Credential gebunden wird, und nicht nur, dass sie über ein gültiges Access-Token verfügt. Die Wallet signiert mit diesem Schlüssel ein Proof-JWT über die Kennung des Ausstellers und einen frischen c_nonce vom Nonce-Endpunkt des Ausstellers. Der Aussteller bettet den zugehörigen öffentlichen Schlüssel in das Credential ein. Ein Verifier, der Key-Binding verlangt, bittet den Inhaber, bei der Vorlage erneut mit demselben Schlüssel zu signieren, sodass ein kopiertes Credential ohne den Schlüssel diese Prüfung nicht besteht.

Warum würde ein Aussteller die Ausstellung aufschieben, statt das Credential sofort zurückzugeben?

Manche Prüfungen, die der Aussteller vor der Erstellung eines Credentials durchführt, lassen sich nicht innerhalb einer einzigen HTTP-Anfrage abschließen, zum Beispiel eine manuelle Prüfung oder ein Aufruf eines langsamen externen Registers. Bei aufgeschobener Ausstellung antwortet der Credential-Endpunkt sofort mit einer transaction_id, statt die Verbindung zu blockieren, und die Wallet fragt den Deferred-Endpunkt mit dieser id ab, bis die Prüfung abgeschlossen und das Credential abholbereit ist.

Ist der Notification-Endpunkt für einen Aussteller verpflichtend?

Nein. Er ist für Aussteller optional, und auch Wallets sind nicht verpflichtet, ihn zu nutzen. Unterstützen beide ihn, erfährt der Aussteller, was nach der Ausstellung geschah: Die Credentials wurden gespeichert (credential_accepted), der Inhaber hat die Ausstellung gestoppt, zum Beispiel indem er die Speicherung ablehnte (credential_deleted), oder sie schlug aus einem anderen Grund fehl (credential_failure). Die Zustellung ist nicht garantiert, daher sollte ein Aussteller eine Benachrichtigung als nützliche Information behandeln, niemals als verlässlichen Nachweis, und darf aus einer fehlenden Benachrichtigung nichts ableiten.

Quellen

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

Diese Seite dient nur zur Information und stellt keine Rechtsberatung dar. Für verbindliche Hinweise wenden Sie sich direkt an die OpenID Foundation und die Europäische Kommission.

Sprechen Sie mit uns über die EUDI-Wallet-Integration