Pular para o conteúdo principal

Como funciona

Esta página acompanha uma assinatura pelo CloudHub e explica o que você escolhe no caminho: o tipo de sessão, como o CloudHub descobre os provedores, quais URIs de redirecionamento ele aceita e como levar os seus próprios dados até o retorno do usuário. Primeiros passos transforma tudo isso em código.

Provedores e certificados em nuvem​

Um provedor (PSC, Prestador de Serviço de Confiança) guarda certificados e as chaves privadas deles em um HSM e permite que o titular os use remotamente. A interface dele segue o DOC-ICP-17.01 do ITI, que é OAuth 2.0: a aplicação envia o usuário à página de autorização do provedor, o usuário entra e aprova, e a aplicação recebe um token que permite assinar com a chave desse usuário.

Uma pessoa pode ter certificados em vários provedores, e mais de um certificado no mesmo provedor. O CloudHub esconde o protocolo, mas não essa escolha: quem decide o provedor é o usuário.

O fluxo, passo a passo​

Como uma assinatura em nuvem passa pelo CloudHub

1. Criar uma sessão​

O seu backend chama POST /api/sessions com o CPF (ou CNPJ) do signatário, a URI de redirecionamento para onde o CloudHub deve devolver o usuário e o tipo de sessão. O CloudHub pergunta a cada provedor se ele tem um certificado para esse identificador e responde com uma entrada para cada provedor que tem:

{
"services": [
{
"serviceInfo": {
"serviceName": "BirdID",
"provider": "SOLUTI",
"endpoint": "https://api.birdid.com.br/",
"badgeUrl": "https://cdn.lacunasoftware.com/img/psc/birdid.png"
},
"authUrl": "https://api.birdid.com.br/oauth/authorize?client_id=…"
}
]
}

Nada foi autorizado ainda. Uma lista services vazia significa que nenhum provedor tem um certificado para esse identificador, ou que nenhum dos que têm respondeu a tempo.

2. Enviar o usuário a um provedor​

Mostre os provedores, por exemplo com badgeUrl como logotipo e provider como legenda, e leve o navegador para a authUrl da opção escolhida. O usuário entra no provedor e aprova a solicitação, normalmente no aplicativo móvel do provedor, com um PIN ou lendo um QR code.

3. O usuário volta com uma sessão​

O provedor redireciona o navegador para o CloudHub, o CloudHub troca o código de autorização pelo token do provedor e o guarda, e então redireciona o navegador para a sua URI de redirecionamento com o parâmetro session acrescentado. Os parâmetros que você colocou na URI de redirecionamento são mantidos:

https://app.example.com/sign/complete?fileId=42&session=9f2c…e81a

Se o usuário cancelar, se o provedor recusar ou se a troca falhar, o CloudHub também redireciona para a sua URI de redirecionamento, mas sem o parâmetro session. O CloudHub não informa o motivo. Trate a falta de session como "não autorizado" e deixe o usuário tentar de novo.

4. Ler o certificado​

Com a sessão, GET /api/sessions/certificate devolve o certificado do signatário, em base64. GET /api/v2/sessions/certificate devolve o mesmo certificado junto com o alias dele e o serviceName do provedor.

Quando o provedor tem mais de um certificado para o usuário, o CloudHub escolhe um: um certificado dentro da validade tem preferência sobre um que não esteja e, entre eles, vale aquele cuja validade começou mais recentemente. Para garantir que o hash seja assinado exatamente com o certificado que você leu, passe o alias dele na próxima chamada.

5. Assinar o hash​

A sua biblioteca de assinatura calcula o hash a assinar a partir do documento e do certificado. O seu backend o envia para POST /api/sessions/sign-hash com a sessão e o algoritmo de hash, e o CloudHub devolve o valor da assinatura, em base64. A biblioteca de assinatura então insere a assinatura e o certificado no documento assinado.

Sessões​

O valor da sessão​

O valor session que a sua URI de redirecionamento recebe é uma string hexadecimal de 64 caracteres. O CloudHub guarda o token do provedor; a sua aplicação guarda só esse valor e o envia em toda chamada que usa a sessão.

Sem a sua chave de API, uma sessão não serve para nada, mas, juntas, elas permitem assinar com a chave do usuário. Por isso, trate o valor da sessão como uma credencial: use-o logo, mantenha-o fora dos seus logs e não o guarde por mais tempo do que precisa. Ele chega em uma query string, então pode parar no histórico do navegador e no log de acesso do seu servidor web; use HTTPS e configure os logs de acesso de acordo.

Tipos de sessão​

O tipo de sessão, enviado em type ao criar a sessão, diz o que o usuário autoriza.

TipoO usuário autorizaUse para
SingleSignatureUma assinatura. É o padrão.Assinar um documento. Assinar um desafio para autenticar o usuário.
SignatureSessionQualquer número de assinaturas até a sessão expirar.Assinar vários documentos com uma única autorização.
MultiSignatureUma operação de assinatura que cobre um ou mais hashes.Depende do provedor. O CloudHub assina um hash por chamada a sign-hash, então, para vários documentos, prefira SignatureSession.
AuthenticationSessionNenhuma assinatura: só a leitura do certificado.Não recomendado. A maioria dos provedores não oferece esse tipo; para autenticar usuários, use SingleSignature.

Ler o certificado não conta como assinatura: uma sessão SingleSignature pode ler o certificado e depois assinar uma vez.

Tempo de vida​

lifetimeInSeconds pede ao provedor uma sessão com essa duração. Quem decide é o provedor: ele pode conceder menos e, quando o valor é omitido, aplica o próprio padrão. Peça o que a operação precisa: alguns minutos para uma assinatura, mais para uma SignatureSession que assina um lote.

Depois que uma sessão expira, o CloudHub responde com o erro SessionExpired a uma chamada que leia o certificado dela ou assine com ela. Isso pode acontecer já na primeira chamada, se o usuário demorou a autorizar. Crie uma nova sessão e envie o usuário ao provedor de novo.

Descoberta​

A descoberta é como o CloudHub encontra os provedores que têm um certificado para o signatário. Ao criar uma sessão:

  • Com um identificador, o CloudHub consulta todos os provedores em paralelo e devolve só os que têm um certificado para ele. Um provedor que não responde fica de fora, em vez de fazer a chamada falhar. Envie o CPF ou o CNPJ só com os dígitos.
  • identifierType diz se o identificador é um CPF ou um CNPJ. Quando omitido, um identificador com mais de 11 dígitos é tratado como CNPJ.
  • discover: false pula as consultas e devolve todos os provedores, passando o identificador a cada um como dica de login (login hint).
  • Sem identificador, o CloudHub devolve todos os provedores, e o usuário se identifica no provedor, por exemplo lendo um QR code. discover: true sem identificador é recusado com IdentifierRequiredToDiscover.

Outras três operações ajudam quando você precisa de mais controle:

  • GET /api/sessions/services?identifier=… faz a descoberta sem criar uma sessão. Devolve os provedores, mas sem authUrl.
  • GET /api/sessions/services/{name}/availability?identifier=… consulta um único provedor. discoveryAvailable: false significa que o provedor não conseguiu responder; caso contrário, certificateFound diz se ele tem um certificado para o identificador.
  • POST /api/sessions/services/{name} cria uma sessão em um único provedor, quando o usuário já o escolheu. name é o serviceName que o CloudHub devolveu, como BirdID.

A página API REST detalha cada uma.

URIs de redirecionamento​

O CloudHub só devolve usuários a URIs de redirecionamento cadastradas para a sua chave de API. Ele confere a URI quando você cria a sessão e recusa uma URI não cadastrada com UnauthorizedRedirectUri. A Lacuna cadastra as URIs ao emitir a chave; uma chave pode ter várias. Cada entrada tem uma destas formas:

Entrada cadastradaAceita
https://app.example.com/sign/completeExatamente essa URI, sem diferenciar maiúsculas de minúsculas. A query string entra na comparação.
https://app.example.comQualquer URI que comece com esse esquema, host e porta.
https://*.example.comQualquer subdomínio de example.com (mas não o próprio example.com), com o mesmo esquema. Inclua a porta quando não for a padrão: https://*.example.com:8443.
https://*.example.com/callbackQualquer subdomínio, mas só quando o caminho for exatamente /callback.

Se você acrescenta os seus próprios parâmetros à URI de redirecionamento (veja a próxima seção), cadastre uma entrada de host ou com curinga: uma entrada exata deixa de corresponder assim que a query string muda.

Para desenvolvimento local, peça uma entrada como https://localhost:5001 além das de produção.

Como levar os seus dados até o retorno​

O usuário sai da sua aplicação para autorizar, então a requisição que volta à sua URI de redirecionamento precisa saber a que ela se refere: qual documento assinar, qual pedido aprovar. Há duas formas de levar essa informação:

  • Parâmetros na URI de redirecionamento, como ?fileId=42. Eles voltam inalterados, ao lado de session, e todas as bibliotecas cliente os suportam. O usuário consegue vê-los e alterá-los.
  • customState, uma string que você envia ao criar a sessão. O CloudHub a guarda dentro do estado OAuth criptografado dele, então o usuário não consegue lê-la nem alterá-la, e a devolve em GET /api/sessions/custom-state?session=…. A API REST, o cliente PHP e o cliente Node.js a enviam; os clientes .NET e Java publicados hoje não têm o campo.

De um jeito ou de outro, confira se o que volta se refere a algo que o usuário atual pode assinar.

Lista de verificação de segurança​

  • Mantenha a chave de API no seu servidor. Nunca a envie a um navegador nem a inclua em um aplicativo móvel. Se ela vazar, peça uma nova ao suporte da Lacuna.
  • Confira quem assinou. O usuário pode entrar no provedor com uma conta diferente da esperada. Depois de ler o certificado, compare o CPF (ou CNPJ) dele com o que você pediu.
  • Valide o certificado com a sua biblioteca de assinatura. O CloudHub devolve o que o provedor devolve. É o trust arbitrator (PKI SDK) ou o contexto de segurança (Rest PKI) da sua biblioteca de assinatura que decide se o certificado é confiável.
  • Trate o valor da sessão como uma credencial, e só o aceite na URI de redirecionamento com a qual você criou a sessão.
  • Use HTTPS em toda URI de redirecionamento fora do desenvolvimento local.
Ver em Markdown