Saltar para o conteúdo principal

Emissão de credenciais OpenID4VCI explicada: da oferta à credencial aceite

OpenID4VCI, OpenID for Verifiable Credential Issuance, define como uma wallet solicita e recebe uma credencial de um issuer. Uma única emissão percorre vários passos distintos antes de a wallet ter efetivamente uma credencial utilizável, e cada um tem o seu próprio modo de falha. Esta página percorre esse ciclo de vida na íntegra, com exemplos práticos originais.

Duas formas de começar: código pré-autorizado e código de autorização

A emissão começa muitas vezes a partir de uma Credential Offer enviada pelo issuer, e o grant que esta indica decide como a wallet é autorizada. Uma wallet também pode iniciar a emissão por conta própria, sem qualquer oferta, usando o fluxo de código de autorização. Ambos os fluxos terminam no mesmo ponto: a wallet na posse de um access token que pode usar para solicitar a credencial.

Fluxo de código pré-autorizado

1. Issuer

Já conhece o titular e entrega uma Credential Offer com um pre-authorized_code

2. Wallet

Resgata o código no token endpoint, opcionalmente com um código de transação

3. Wallet

Solicita a credencial com uma prova de posse da sua chave

Fluxo de código de autorização

1. Wallet

Digitaliza uma Credential Offer que indica um grant authorization_code, ou inicia o fluxo por conta própria sem oferta

2. Authorization Server

Conduz o titular através do login e do consentimento, e depois emite um código

3. Wallet

Troca o código por um token e depois solicita a credencial

O ciclo de vida completo de uma oferta de credencial

A especificação não define estados nomeados, mas um fluxo iniciado pelo issuer em que a credencial é emitida de imediato é o mais fácil de seguir, como na sequência abaixo. Cada passo pode falhar à sua maneira, e uma implementação de wallet tem de lidar com esses caminhos, não apenas com o cenário ideal.

Oferta criada
Código QR ou link abre a wallet
Oferta recebida
grant resgatado
Token obtido
nonce e prova de posse
Credencial solicitada
credencial gerada
Credencial emitida
a wallet valida e armazena
Credencial aceite
Onde pode falhar, pela ordem em que os passos ocorrem:
Código expirado antes do resgate
Pedido de token recusado
Pedido de credencial rejeitado
Credencial não armazenada

Exemplo prático: um certificado de inspeção para uma frota de transportes

Uma entidade de inspeção de veículos emite um certificado de inspeção para a business wallet de uma transportadora após uma inspeção de rotina. O inspetor já autenticou o gestor de frota no ponto de inspeção, pelo que o issuer usa o fluxo de código pré-autorizado. Os passos abaixo seguem esta única emissão, desde a oferta até à credencial aceite.

O exemplo mostra o OpenID4VCI base. Implementações de elevada garantia, como a EUDI Wallet, seguem o perfil HAIP sobre este, que acrescenta access tokens vinculados a DPoP, wallet attestation no token endpoint e key attestation para as chaves da credencial. Estes aspetos são omitidos aqui para manter cada passo fácil de acompanhar.

1. Oferta de credencial

O terminal da entidade de inspeção mostra um código QR. Este contém um URI que começa por openid-credential-offer:// e transporta a oferta abaixo, codificada em URL num parâmetro credential_offer, ou um credential_offer_uri a partir do qual a wallet a obtém. A wallet digitaliza-o e lê qual a credencial oferecida e como obtê-la.

Issuer

Mostra um código QR com uma Credential Offer

Indica a configuração da credencial e um 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. Descoberta de metadados do issuer

Antes de solicitar seja o que for, a wallet obtém os metadados do issuer para saber o que o roadworthiness_certificate contém e quais os endpoints a chamar. Os metadados não listam authorization servers separados, pelo que o issuer é o seu próprio authorization server, e a wallet lê o token endpoint a partir dos metadados desse servidor.

Wallet

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

Conhece o formato da credencial, as claims e os tipos de prova aceites, além dos endpoints de nonce, credential, deferred e notification

Metadados do credential issuer (excerto)

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

Metadados do authorization server (excerto), de /.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. Pedido de token

A wallet resgata o código pré-autorizado no token endpoint, juntamente com o código de transação que a entidade de inspeção enviou ao telemóvel do gestor de frota. Enviar esse código por um segundo canal significa que alguém que fotografe o código QR por cima do ombro continua a não conseguir resgatá-lo.

Wallet

POST /token

Envia o pre-authorized_code e o tx_code, recebe um access token limitado a esta oferta

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

Resposta

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

4. Prova de posse

Como os metadados listam um nonce_endpoint, a wallet obtém primeiro um c_nonce recente aí. Depois prova que possui a chave privada a que a credencial será vinculada, assinando um JWT de prova sobre o identificador do issuer e esse c_nonce.

Wallet

POST /nonce, depois assina um JWT de prova com a chave a que a credencial será vinculada

Vincula a credencial a essa chave, não apenas a quem possui o access token

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

Resposta

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

A wallet constrói agora o JWT de prova a partir de dois objetos JSON, um header e um payload, e assina-os com a chave privada a que a credencial será vinculada.

Header: o que é este JWT e qual chave o assinou

{
  "typ": "openid4vci-proof+jwt",
  "alg": "ES256",
  "jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }
}
  • typ: marca isto como uma key proof OpenID4VCI, para não poder ser confundida com qualquer outro tipo de JWT
  • alg: o algoritmo de assinatura, um dos que o issuer listou em proof_signing_alg_values_supported
  • jwk: a chave pública a que a credencial será vinculada; o issuer verifica a assinatura em relação a esta

Payload: para quem é a prova e quando foi criada

{
  "aud": "https://issuer.fleetinspect.example",
  "iat": 1789376400,
  "nonce": "fi-nonce-77aa"
}
  • aud: o identificador do issuer, para que a prova não possa ser reutilizada noutro issuer
  • iat: o momento em que a prova foi criada, em segundos desde 1970
  • nonce: o c_nonce do nonce endpoint, que demonstra que a prova é recente

Resultado assinado

O header e o payload são cada um codificados em base64url e unidos com um ponto. A wallet assina essa string com a sua chave privada e junta a assinatura codificada em base64url após um segundo ponto. A string resultante é o JWT de prova que a wallet envia no pedido de credencial no passo 5.

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

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

5. Pedido de credencial

A wallet chama o credential endpoint com o access token e a prova, e o issuer gera e devolve a credencial assinada.

Wallet

POST /credential

Envia o access token, o id de configuração e o JWT de prova, recebe a credencial assinada e um 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>"]
  }
}

Resposta

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

Todo o fluxo num relance

Este diagrama de sequência reúne os cinco passos do exemplo prático, desde a oferta até à notificação. As setas contínuas são pedidos, as tracejadas são respostas, e a seta pontilhada é o código de transação a viajar fora do protocolo por SMS.

Wallet

Business wallet da transportadora

Authorization Server

Operado pelo próprio issuer neste exemplo

Credential Issuer

Entidade de inspeção de veículos

  1. Credential Issuer para Wallet: Credential Offer, apresentada como código QR
  2. Credential Issuer para Wallet: tx_code, enviado por SMS ao telemóvel do gestor de frota
  3. Wallet para Credential Issuer: GET /.well-known/openid-credential-issuer
  4. Credential Issuer para Wallet: metadados do credential issuer
  5. Wallet para Authorization Server: GET /.well-known/oauth-authorization-server
  6. Authorization Server para Wallet: metadados do authorization server
  7. Wallet para Authorization Server: POST /token: pre-authorized_code, tx_code
  8. Authorization Server para Wallet: access_token
  9. Wallet para Credential Issuer: POST /nonce
  10. Credential Issuer para Wallet: c_nonce
  11. Wallet: assina o JWT de prova
  12. Wallet para Credential Issuer: POST /credential: access token, provas
  13. Credential Issuer para Wallet: credentials, notification_id
  14. Wallet: valida e armazena
  15. Wallet para Credential Issuer: POST /notify: credential_accepted
  16. Credential Issuer para Wallet: 204 No Content

Quando a credencial ainda não está pronta: emissão diferida

O exemplo acima assume que o resultado da inspeção já é final. Se, em vez disso, a entidade de inspeção precisar de escalar um resultado duvidoso para um inspetor sénior, o credential endpoint não pode devolver a credencial de imediato, pelo que diferi a emissão.

Emissão imediata

O credential endpoint devolve a credencial assinada na mesma resposta do pedido.

Emissão diferida

O credential endpoint responde antes com HTTP 202, um transaction_id e um intervalo. A wallet consulta o deferred credential endpoint com esse id, aguardando pelo menos o número de segundos do intervalo entre pedidos, até a credencial estar pronta.

transaction_idconsultar deferred_credential_endpoint

Resposta diferida de /credential

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

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

Consultar /deferred até estar pronta

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.

Fechar o ciclo: o notification endpoint

Após a emissão, a wallet pode informar o issuer do que aconteceu à credencial, usando o notification_id da resposta da credencial. As wallets não são obrigadas a enviar estas notificações e a entrega não é garantida, pelo que um issuer não pode interpretar uma notificação em falta como significando seja o que for.

Wallet

Envia um evento para o notification_endpoint do issuer para o notification_id recebido, que abrange todas as credenciais dessa resposta

credential_accepted

Armazenada na wallet

credential_failure

A emissão falhou por qualquer outro motivo, por exemplo a credencial não passou na validação

credential_deleted

A emissão falhou por causa do titular, por exemplo este recusou armazená-la

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

Termos relacionados

Perguntas frequentes

Como sabe uma wallet qual o tipo de grant a usar?

A Credential Offer indica o grant no seu objeto grants. authorization_code está presente quando o issuer pretende que o titular faça login como parte do fluxo. pre-authorized_code está presente quando o titular já estava autenticado no canal onde a oferta foi criada, por exemplo pelo inspetor no ponto de inspeção do exemplo abaixo. Uma oferta pode listar ambos, e a wallet escolhe então um. Se a oferta não tiver qualquer objeto grants, a wallet consulta os tipos de grant suportados pelo authorization server nos seus metadados.

Porque é que a wallet obtém os metadados do issuer antes de pedir seja o que for?

A Credential Offer indica apenas os credential_configuration_ids, o URL do issuer e os grants. Os metadados do issuer, disponibilizados num caminho well-known, descrevem cada configuração: o seu formato, as suas claims e os tipos de prova aceites, além dos endpoints de nonce, credential, deferred e notification. Indicam também qual authorization server usar, e os metadados desse servidor fornecem o token endpoint. Sem ambos, a wallet não saberia como construir pedidos válidos nem o que mostrar ao titular antes do consentimento.

O que prova realmente a prova de posse?

Prova que a wallet que solicita a credencial possui a chave privada a que a credencial será vinculada, não apenas que tem um access token válido. A wallet assina um JWT de prova sobre o identificador do issuer e um c_nonce recente do nonce endpoint do issuer, usando essa chave. O issuer incorpora a chave pública correspondente na credencial. Um verifier que exija vinculação de chave pede ao titular que assine novamente com a mesma chave ao apresentar a credencial, pelo que uma credencial copiada sem a chave não passa nessa verificação.

Porque é que um issuer diferiria a emissão em vez de devolver a credencial de imediato?

Algumas verificações que o issuer realiza antes de gerar uma credencial não conseguem concluir-se dentro de um único pedido HTTP, por exemplo uma revisão manual ou uma chamada a um registo externo lento. A emissão diferida permite ao credential endpoint responder de imediato com um transaction_id em vez de bloquear a ligação, e a wallet consulta o endpoint diferido com esse id até a verificação terminar e a credencial estar pronta a recolher.

O notification endpoint é obrigatório para um issuer implementar?

Não. É opcional para os issuers, e as wallets também não são obrigadas a usá-lo. Quando ambos o suportam, o issuer fica a saber o que aconteceu após a emissão: as credenciais foram armazenadas (credential_accepted), o titular interrompeu a emissão, por exemplo recusando armazená-las (credential_deleted), ou falhou por outro motivo (credential_failure). A entrega não é garantida, pelo que um issuer deve tratar uma notificação como informação útil, nunca como um registo fiável, e não pode interpretar nada a partir de uma notificação em falta.

Fontes

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

Esta página tem caráter meramente informativo e não constitui aconselhamento jurídico. Para orientações oficiais, consulte diretamente a OpenID Foundation e a Comissão Europeia.

Fale connosco sobre a integração com a EUDI Wallet