Pular para o conteúdo principal

Assinaturas em duas etapas - Rest PKI Core

Com as sessões de assinatura, o usuário assina numa página hospedada pelo Rest PKI Core. Quando a assinatura precisa acontecer em outro lugar, use a API de assinatura em duas etapas:

  1. O seu servidor pede ao Rest PKI Core que prepare a assinatura de um arquivo com um certificado. O Rest PKI Core valida o certificado e retorna o hash a ser assinado e o estado da assinatura (state).
  2. A chave privada do certificado assina o hash: no navegador do usuário, com o Web PKI, ou no seu servidor.
  3. O seu servidor envia a assinatura e o estado ao Rest PKI Core, que conclui a assinatura e retorna o documento assinado.

Isso atende a dois casos:

  • Assinar na sua própria página: o usuário assina com um certificado instalado no computador dele, numa página da sua aplicação, por meio do Web PKI. Fora do localhost, o Web PKI precisa de uma licença.
  • Assinar com uma chave guardada no servidor: a sua aplicação assina com um certificado próprio, como um certificado da empresa, sem um usuário.
dica

Desse ponto em diante estaremos descrevendo chamadas ao Rest PKI Core. Para conseguir acompanhar, certifique-se de que já fez os Primeiros passos. A chave de API da aplicação precisa de um papel que permita assinaturas, como o Operador (veja Escolhendo os papéis).

Preparando a assinatura​

Em .NET, chame IRestPkiService.PrepareSignatureAsync() com o arquivo a assinar, o certificado do signatário e as opções da assinatura:

var fileReference = FileReference.FromFile(pdfPath, "document.pdf");
var certificateReference = CertificateReference.FromBytes(certificateContent);
var prepareSignatureResult = await restPkiService.PrepareSignatureAsync(fileReference, certificateReference, new PrepareSignatureOptions() {
SecurityContextId = securityContextId,
});
  • O arquivo é um FileReference, criado a partir de um caminho, de bytes, de um stream ou de uma URL (veja Referenciando arquivos).
  • O certificado é um CertificateReference: o FromBytes() recebe a codificação do certificado, como a que o Web PKI lê no navegador (decodificada do Base64); o FromX509Certificate2() recebe um certificado do .NET e o FromPem(), uma string PEM.
  • O PrepareSignatureOptions define o SecurityContextId, que decide em quais certificados confiar (se omitido, vale o contexto de segurança padrão da organização ou, na falta dele, o da instância); o SignatureType (Pdf, Cms ou Xml); e as opções de cada tipo, como o PdfSignatureOptions.

O resultado traz o hash a assinar em ToSignHash (Value, com o algoritmo de hash em Algorithm) e o estado da assinatura em State, uma string opaca de que o Rest PKI Core precisa para concluir a assinatura. Quando o Rest PKI Core recusa o arquivo, por exemplo um PDF corrompido, o cliente .NET lança uma PrepareSignatureException.

Em Java, chame RestPkiService.startSignature():

PrepareSignatureRequest request = new PrepareSignatureRequest();
request.setSecurityContextId(securityContextId);

FileReferenceModel file = new FileReferenceModel();
file.setContent(Files.readAllBytes(pdfPath));
file.setName("document.pdf");
request.setFile(file);

CertificateReferenceModel certificate = new CertificateReferenceModel();
certificate.setContent(Base64.getDecoder().decode(certContent));
request.setCertificate(certificate);

request.setSignatureType(SignatureTypes.PDF);

PrepareSignatureResponse result = restPkiService.startSignature(request);
if (!Boolean.TRUE.equals(result.isSuccess())) {
// result.getFailure() diz o motivo, e result.getValidationResults() por que o certificado foi recusado
throw new Exception("The signature could not be prepared: " + result.getFailure());
}

O hash a assinar é result.getToSign().getHash(), o algoritmo de hash é result.getToSign().getSignatureAlgorithm().getDigestAlgorithmOid() e o estado é result.getState().

Caso esteja chamando a API diretamente:

POST {endpoint}/api/signature
{
"file": {
"content": "Base64EncodedBytes==",
"name": "document.pdf"
},
"certificate": {
"content": "Base64EncodedCertificate=="
},
"securityContextId": "...",
"signatureType": "Pdf"
}

Quando success é true, a resposta traz o hash a assinar em toSign.hash (em Base64), o algoritmo de hash em toSign.signatureAlgorithm.digestAlgorithmOid, o estado em state e o certificado do signatário em certificate. Caso contrário, failure diz o motivo: PdfAlreadySigned, CorruptedPdf, CertificateValidationFailed (com os motivos em validationResults), PasswordProtectedPdf, PdfAConversionFailed ou PdfANonCompliant. A resposta também repete o hash em toSignHash, para clientes mais antigos.

Assinando o hash​

Na sua própria página, com o Web PKI​

O certificado do usuário nunca sai do computador dele; por isso, a assinatura exige duas idas e voltas entre a página e o seu servidor:

  1. Na página, liste os certificados do usuário com o listCertificates() do Web PKI. Quando o usuário escolher um, leia a codificação dele com o readCertificate(thumbprint) e envie-a, em Base64, ao seu servidor.
  2. O seu servidor prepara a assinatura com esse certificado (CertificateReference.FromBytes() em .NET) e devolve à página o hash a assinar, o algoritmo de hash e o estado.
  3. A página assina o hash com o signHash() do Web PKI e envia a assinatura, em Base64, e o estado ao seu servidor, que conclui a assinatura.
pki.signHash({
thumbprint: thumbprint, // o certificado escolhido pelo usuário
hash: toSignHash, // o hash a assinar, em Base64
digestAlgorithm: digestAlgorithm // o OID do algoritmo de hash, como 2.16.840.1.101.3.4.2.1 para SHA-256
}).success(function (signature) {
// Envie a assinatura (em Base64) e o estado ao seu servidor
});

O estado é protegido pelo Rest PKI Core; por isso, você pode guardá-lo num campo oculto da página entre as duas etapas. Para o listCertificates(), o readCertificate() e o signHash(), veja os Primeiros passos do Web PKI.

No seu servidor​

Em .NET, assine o hash com a chave privada do certificado:

var certificate = new X509Certificate2(pfxPath, pfxPassword);
var certificateReference = CertificateReference.FromX509Certificate2(certificate);
var prepareSignatureResult = await restPkiService.PrepareSignatureAsync(fileReference, certificateReference, signatureOptions);

var signature = certificate.GetRSAPrivateKey().SignHash(
prepareSignatureResult.ToSignHash.Value,
prepareSignatureResult.ToSignHash.Algorithm.HashAlgorithmName,
RSASignaturePadding.Pkcs1
);

Concluindo a assinatura​

Em .NET, chame IRestPkiService.CompleteSignatureAsync() com o estado e a assinatura:

var signedDocument = await restPkiService.CompleteSignatureAsync(prepareSignatureResult.State, signature);
using (var stream = await signedDocument.SignedFile.OpenReadAsync()) {
// Armazene o arquivo assinado
}

O resultado é o Document assinado, o mesmo tipo que as sessões de assinatura retornam: o SignedFile.OpenReadAsync() lê o arquivo assinado, e a SignedFile.Url é um link temporário para baixá-lo. Se a assinatura ou o certificado não passar na validação, por exemplo um certificado revogado, o cliente .NET lança uma ValidationException, cujos ValidationResults dizem o motivo.

Em Java, chame RestPkiService.completeSignature():

CompleteSignatureRequestV2 request = new CompleteSignatureRequestV2();
request.setState(state);
request.setSignature(Base64.getDecoder().decode(signature));

DocumentModel signedDocument = restPkiService.completeSignature(request);
try (InputStream signedContent = restPkiService.openRead(signedDocument.getSignedFile().getUrl())) {
// Armazene o arquivo assinado
}

Caso esteja chamando a API diretamente:

POST {endpoint}/api/signature/completion
{
"state": "...",
"signature": "Base64EncodedSignature=="
}

A resposta é o DocumentModel do documento assinado, o mesmo modelo que o GET api/documents/{id} retorna.

Exemplos​

Ver em Markdown