Pular para o conteúdo principal

Signing a PDF

This page puts the whole flow together: a PAdES signature of a PDF with a cloud certificate, in C#, PHP, Python, Node.js and Java. CloudHub provides the certificate and the signature; a signature library builds the signed PDF. Each example follows the matching sample in PKI Suite Samples.

LanguageSignature libraryWeb framework
C#PKI SDKASP.NET Core MVC
PHPRest PKIplain PHP
PythonPKI ExpressFlask
Node.jsRest PKIExpress
JavaRest PKISpring MVC

Each signature library needs its own setup first: a license for PKI SDK, an access token for Rest PKI, PKI Express installed on the server. The examples read the Rest PKI token from RESTPKI_ACCESS_TOKEN and the CloudHub key from CLOUDHUB_API_KEY.

How the pieces fit​

Every example has two handlers:

  • discover receives the signer's CPF, creates a CloudHub session and lists the providers, each linked to its authUrl. It also stores the CPF in the user's web session, to check the certificate later.
  • complete is the redirect URI. It runs four steps:
  1. Read the signer's certificate from CloudHub.
  2. Give the certificate to the signature library, which starts the signature and returns the hash to sign, its digest algorithm, and what it needs to finish (transfer data, a token or a transfer file).
  3. Send the hash to CloudHub's sign-hash. The provider signs it with the user's key.
  4. Give the signature to the signature library, which finishes the signed PDF. Then check that the signer's CPF is the one you stored.

The signature libraries return the hash in different encodings. CloudHub takes it base64-encoded on the wire, and the client libraries convert from the type they accept:

ExampleThe hash from the signature libraryWhat the CloudHub client takes
C#, PKI SDKbytes, from DigestAlgorithm.ComputeHashbytes
PHP, Rest PKIraw bytes in $start->toSignHashbase64: wrap it in base64_encode
Python, PKI Expressbase64 in start['toSignHash']base64
Node.js, Rest PKIbase64 in start.toSignHashbase64
Java, Rest PKIbytes, from getToSignHashRaw()bytes

The example​

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", "");
}

With PKI SDK, the certificate is known before anything is signed, so the CPF check comes first. GetPdfPath stands for your own storage.

The examples trust ICP-Brasil certificates only (TrustArbitrators.PkiBrazil, PKI_BRAZIL, SecurityContext.pkiBrazil). To test with a provider's test certificates, trust their root in development only; the samples show how for each library.

Variations​

  • A visible signature stamp. Add a visual representation through your signature library, exactly as with any other certificate. The samples linked above all add one.
  • Several documents with one authorization. Create the session with TrustServiceSessionTypes.SignatureSession and a lifetimeInSeconds long enough for the batch, then repeat steps 2 to 4 for each document with the same session. See Session types.
  • CAdES and XAdES. The flow is the same with your signature library's CAdES or XML starter. The PHP samples include an XML signature with CloudHub.
View as Markdown