Pular para o conteúdo principal

Assinando um PDF

Esta página junta o fluxo inteiro: uma assinatura PAdES de um PDF com certificado em nuvem, em C#, PHP, Python, Node.js e Java. O CloudHub fornece o certificado e a assinatura; uma biblioteca de assinatura monta o PDF assinado. Cada exemplo segue o exemplo correspondente do PKI Suite Samples.

LinguagemBiblioteca de assinaturaFramework web
C#PKI SDKASP.NET Core MVC
PHPRest PKIPHP puro
PythonPKI ExpressFlask
Node.jsRest PKIExpress
JavaRest PKISpring MVC

Cada biblioteca de assinatura precisa da própria configuração antes: uma licença para o PKI SDK, um access token para o Rest PKI, o PKI Express instalado no servidor. Os exemplos leem o token do Rest PKI de RESTPKI_ACCESS_TOKEN e a chave do CloudHub de CLOUDHUB_API_KEY.

Como as peças se encaixam​

Todo exemplo tem dois handlers:

  • discover recebe o CPF do signatário, cria uma sessão no CloudHub e lista os provedores, cada um com um link para a sua authUrl. Ele também guarda o CPF na sessão web do usuário, para conferir o certificado depois.
  • complete é a URI de redirecionamento. Ele executa quatro passos:
  1. Lê o certificado do signatário no CloudHub.
  2. Entrega o certificado à biblioteca de assinatura, que inicia a assinatura e devolve o hash a assinar, o algoritmo de hash e o que ela precisa para finalizar (dados de transferência, um token ou um arquivo de transferência).
  3. Envia o hash ao sign-hash do CloudHub. O provedor o assina com a chave do usuário.
  4. Entrega a assinatura à biblioteca de assinatura, que finaliza o PDF assinado. Depois, confere se o CPF do signatário é o que foi guardado.

As bibliotecas de assinatura devolvem o hash em codificações diferentes. O CloudHub o recebe em base64, e as bibliotecas cliente fazem a conversão a partir do tipo que aceitam:

ExemploO hash vindo da biblioteca de assinaturaO que o cliente do CloudHub recebe
C#, PKI SDKbytes, de DigestAlgorithm.ComputeHashbytes
PHP, Rest PKIbytes brutos em $start->toSignHashbase64: passe-o por base64_encode
Python, PKI Expressbase64 em start['toSignHash']base64
Node.js, Rest PKIbase64 em start.toSignHashbase64
Java, Rest PKIbytes, de getToSignHashRaw()bytes

O exemplo​

Controllers/CloudSignatureController.cs
using System.Text.RegularExpressions;
using Lacuna.Cloudhub.Api;
using Lacuna.Cloudhub.Client;
using Lacuna.Pki;
using Lacuna.Pki.Pades;
using Microsoft.AspNetCore.Mvc;

public class CloudSignatureController : Controller {

private static readonly CloudhubClient cloudhub = new CloudhubClient(
"https://cloudhub.lacunasoftware.com/",
Environment.GetEnvironmentVariable("CLOUDHUB_API_KEY"));

// POST /CloudSignature/Discover: the form with the signer's CPF
[HttpPost]
public async Task<IActionResult> Discover(string fileId, string cpf) {
cpf = DigitsOnly(cpf);
// Remember whose signature this is, to check the certificate later
TempData["cloudhubCpf"] = cpf;

var session = await cloudhub.CreateSessionAsync(new SessionCreateRequest {
Identifier = cpf,
RedirectUri = Url.Action(nameof(Complete), "CloudSignature", new { fileId }, Request.Scheme),
Type = TrustServiceSessionTypes.SingleSignature,
});

// The view links each entry of session.Services to its AuthUrl
return View(session.Services);
}

// GET /CloudSignature/Complete?fileId=…&session=…: the redirect URI
public async Task<IActionResult> Complete(string fileId, string session) {
if (string.IsNullOrEmpty(session)) {
return RedirectToAction("Index", new { fileId }); // not authorized
}

// 1. The signer's certificate, from CloudHub
var certificate = PKCertificate.Decode(await cloudhub.GetCertificateAsync(session));
if (DigitsOnly(certificate.PkiBrazil.CPF) != (string)TempData["cloudhubCpf"]) {
return StatusCode(403); // someone else signed in at the provider
}

// 2. PKI SDK starts the signature. It throws a ValidationException
// when the certificate is not trusted.
var signer = new PadesSigner();
signer.SetPolicy(PadesPoliciesForGeneration.GetPadesBasic(TrustArbitrators.PkiBrazil));
signer.SetPdfToSign(GetPdfPath(fileId));
signer.SetSigningCertificate(certificate);
var toSignBytes = signer.GetToSignBytes(out var signatureAlgorithm, out var transferData);

// 3. CloudHub signs the hash with the user's key
var signature = await cloudhub.SignHashAsync(new SignHashRequest {
Session = session,
Hash = signatureAlgorithm.DigestAlgorithm.ComputeHash(toSignBytes),
DigestAlgorithmOid = signatureAlgorithm.DigestAlgorithm.Oid,
});

// 4. PKI SDK finishes the signed PDF
signer.SetPreComputedSignature(signature, transferData);
var signedPdf = signer.GetPadesSignature();

return File(signedPdf, "application/pdf", "signed.pdf");
}

private static string DigitsOnly(string value) => Regex.Replace(value ?? "", @"\D", "");
}

Com o PKI SDK, o certificado é conhecido antes de qualquer assinatura, então a conferência do CPF vem primeiro. GetPdfPath representa o seu próprio armazenamento.

Os exemplos só confiam em certificados ICP-Brasil (TrustArbitrators.PkiBrazil, PKI_BRAZIL, SecurityContext.pkiBrazil). Para testar com os certificados de teste de um provedor, confie na raiz deles só em desenvolvimento; os exemplos do PKI Suite Samples mostram como fazer isso em cada biblioteca.

Variações​

  • Um carimbo visível da assinatura. Adicione uma representação visual pela sua biblioteca de assinatura, exatamente como com qualquer outro certificado. Todos os exemplos citados acima adicionam uma.
  • Vários documentos com uma única autorização. Crie a sessão com TrustServiceSessionTypes.SignatureSession e um lifetimeInSeconds longo o bastante para o lote, e repita os passos 2 a 4 para cada documento com a mesma sessão. Veja Tipos de sessão.
  • CAdES e XAdES. O fluxo é o mesmo com o starter de CAdES ou de XML da sua biblioteca de assinatura. Os exemplos em PHP incluem uma assinatura XML com o CloudHub.
Ver em Markdown