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:
- 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).
- A chave privada do certificado assina o hash: no navegador do usuário, com o Web PKI, ou no seu servidor.
- 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.
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: oFromBytes()recebe a codificação do certificado, como a que o Web PKI lê no navegador (decodificada do Base64); oFromX509Certificate2()recebe um certificado do .NET e oFromPem(), uma string PEM. - O
PrepareSignatureOptionsdefine oSecurityContextId, 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); oSignatureType(Pdf,CmsouXml); e as opções de cada tipo, como oPdfSignatureOptions.
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:
- 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 oreadCertificate(thumbprint)e envie-a, em Base64, ao seu servidor. - 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. - 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
- .NET, na sua própria página com o Web PKI: PadesSignatureRestCoreController.cs
- .NET, com uma chave do servidor: PadesServerKeyRestCoreController.cs
- Java, na sua própria página com o Web PKI: PadesSignatureCoreController.java
- Node.js, na sua própria página com o Web PKI: pades-signature-core.js